Merge selfhosting-dev into main (Core-13 pure CI/tests + LLVM bridge) (#126)
* WIP: sync before merging origin/main * fix: unify using/module + build CLI; add missing helper in runner; build passes; core smokes green; jit any.len string now returns 3 * Apply local changes after merging main; keep docs/phase-15 removed per main; add phase-15.1 docs and tests * Remove legacy docs/phase-15/README.md to align with main * integration: add Core-13 pure CI, tests, and minimal LLVM execute bridge (no docs) (#125) Co-authored-by: Tomoaki <tomoaki@example.com> --------- Co-authored-by: Selfhosting Dev <selfhost@example.invalid> Co-authored-by: Tomoaki <tomoaki@example.com>
This commit is contained in:
@ -885,6 +885,83 @@ impl LLVMCompiler {
|
||||
}
|
||||
} else { vmap.insert(*d, rv); }
|
||||
}
|
||||
} else if iface_name == "env.local" && method_name == "get" {
|
||||
// Core-13 pure shim: get(ptr) → return the SSA value of ptr
|
||||
if let Some(d) = dst { let av = *vmap.get(&args[0]).ok_or("extern arg missing")?; vmap.insert(*d, av); }
|
||||
} else if iface_name == "env.local" && method_name == "set" {
|
||||
// set(ptr, val) → no-op at AOT SSA level (ptr is symbolic)
|
||||
// No dst expected in our normalization; ignore safely
|
||||
} else if iface_name == "env.box" && method_name == "new" {
|
||||
// Call NyRT shim:
|
||||
// - 1 arg: i64 @nyash.env.box.new(i8* type)
|
||||
// - >=2arg: i64 @nyash.env.box.new_i64(i8* type, i64 argc, i64 a1, i64 a2)
|
||||
if args.len() < 1 { return Err("env.box.new expects at least 1 arg (type name)".to_string()); }
|
||||
let i8p = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
let tyv = *vmap.get(&args[0]).ok_or("type name arg missing")?;
|
||||
let ty_ptr = match tyv { BasicValueEnum::PointerValue(p) => p, _ => return Err("env.box.new type must be i8* string".to_string()) };
|
||||
let i64t = codegen.context.i64_type();
|
||||
let ret_to_ptr = |rv: BasicValueEnum| -> Result<BasicValueEnum, String> {
|
||||
let i64v = if let BasicValueEnum::IntValue(iv) = rv { iv } else { return Err("env.box.new ret expected i64".to_string()); };
|
||||
let pty = i8p;
|
||||
let ptr = codegen.builder.build_int_to_ptr(i64v, pty, "box_handle_to_ptr").map_err(|e| e.to_string())?;
|
||||
Ok(ptr.into())
|
||||
};
|
||||
// Helper: coerce arbitrary BasicValueEnum to i64; for i8* assume string and convert to box-handle via nyash.box.from_i8_string
|
||||
let to_i64 = |v: BasicValueEnum| -> Result<inkwell::values::IntValue, String> {
|
||||
match v {
|
||||
BasicValueEnum::IntValue(iv) => Ok(iv),
|
||||
BasicValueEnum::FloatValue(fv) => {
|
||||
// Route via NyRT: i64 @nyash.box.from_f64(double)
|
||||
let i64t = codegen.context.i64_type();
|
||||
let fnty = i64t.fn_type(&[codegen.context.f64_type().into()], false);
|
||||
let callee = codegen.module.get_function("nyash.box.from_f64").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_f64", fnty, None));
|
||||
let call = codegen.builder.build_call(callee, &[fv.into()], "arg_f64_to_box").map_err(|e| e.to_string())?;
|
||||
let rv = call.try_as_basic_value().left().ok_or("from_f64 returned void".to_string())?;
|
||||
if let BasicValueEnum::IntValue(h) = rv { Ok(h) } else { Err("from_f64 ret expected i64".to_string()) }
|
||||
}
|
||||
BasicValueEnum::PointerValue(pv) => {
|
||||
// If pointer is i8*, call nyash.box.from_i8_string to obtain a handle (i64)
|
||||
let ty = pv.get_type();
|
||||
let elem = ty.get_element_type();
|
||||
if elem == codegen.context.i8_type().as_basic_type_enum() {
|
||||
let i64t = codegen.context.i64_type();
|
||||
let fnty = i64t.fn_type(&[i8p.into()], false);
|
||||
let callee = codegen.module.get_function("nyash.box.from_i8_string").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty, None));
|
||||
let call = codegen.builder.build_call(callee, &[pv.into()], "arg_i8_to_box").map_err(|e| e.to_string())?;
|
||||
let rv = call.try_as_basic_value().left().ok_or("from_i8_string returned void".to_string())?;
|
||||
if let BasicValueEnum::IntValue(h) = rv { Ok(h) } else { Err("from_i8_string ret expected i64".to_string()) }
|
||||
} else {
|
||||
Ok(codegen.builder.build_ptr_to_int(pv, codegen.context.i64_type(), "p2i").map_err(|e| e.to_string())?)
|
||||
}
|
||||
}
|
||||
_ => Err("unsupported arg value for env.box.new".to_string()),
|
||||
}
|
||||
};
|
||||
let out_val = if args.len() == 1 {
|
||||
let fnty = i64t.fn_type(&[i8p.into()], false);
|
||||
let callee = codegen.module.get_function("nyash.env.box.new").unwrap_or_else(|| codegen.module.add_function("nyash.env.box.new", fnty, None));
|
||||
let call = codegen.builder.build_call(callee, &[ty_ptr.into()], "env_box_new").map_err(|e| e.to_string())?;
|
||||
call.try_as_basic_value().left().ok_or("env.box.new returned void".to_string())?
|
||||
} else {
|
||||
// Support up to 4 args for now
|
||||
if args.len() - 1 > 4 { return Err("env.box.new supports up to 4 args in AOT shim".to_string()); }
|
||||
let fnty = i64t.fn_type(&[i8p.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into()], false);
|
||||
let callee = codegen.module.get_function("nyash.env.box.new_i64x").unwrap_or_else(|| codegen.module.add_function("nyash.env.box.new_i64x", fnty, None));
|
||||
let argc_val = i64t.const_int((args.len() - 1) as u64, false);
|
||||
// helper to coerce to i64
|
||||
let get_i64 = |vid: ValueId| -> Result<inkwell::values::IntValue, String> { to_i64(*vmap.get(&vid).ok_or("arg missing")?) };
|
||||
let mut a1 = i64t.const_zero();
|
||||
let mut a2 = i64t.const_zero();
|
||||
if args.len() >= 2 { a1 = get_i64(args[1])?; }
|
||||
if args.len() >= 3 { a2 = get_i64(args[2])?; }
|
||||
let mut a3 = i64t.const_zero();
|
||||
let mut a4 = i64t.const_zero();
|
||||
if args.len() >= 4 { a3 = get_i64(args[3])?; }
|
||||
if args.len() >= 5 { a4 = get_i64(args[4])?; }
|
||||
let call = codegen.builder.build_call(callee, &[ty_ptr.into(), argc_val.into(), a1.into(), a2.into(), a3.into(), a4.into()], "env_box_new_i64x").map_err(|e| e.to_string())?;
|
||||
call.try_as_basic_value().left().ok_or("env.box.new_i64 returned void".to_string())?
|
||||
};
|
||||
if let Some(d) = dst { vmap.insert(*d, ret_to_ptr(out_val)?); }
|
||||
} else {
|
||||
return Err(format!("ExternCall lowering unsupported: {}.{} (enable NYASH_LLVM_ALLOW_BY_NAME=1 to try by-name, or add a NyRT shim)", iface_name, method_name));
|
||||
}
|
||||
@ -1257,9 +1334,65 @@ impl LLVMCompiler {
|
||||
mir_module: &MirModule,
|
||||
temp_path: &str,
|
||||
) -> Result<Box<dyn NyashBox>, String> {
|
||||
// 1) Emit object via real LLVM lowering to ensure IR generation remains healthy
|
||||
let obj_path = format!("{}.o", temp_path);
|
||||
self.compile_module(mir_module, &obj_path)?;
|
||||
// For now, return 0 as IntegerBox (skeleton)
|
||||
|
||||
// 2) Execute via a minimal MIR interpreter for parity (until full AOT linkage is wired)
|
||||
// Supports: Const(Integer/Bool/String/Null), BinOp on Integer, Return(Some/None)
|
||||
// This mirrors the non-LLVM mock path just enough for simple parity tests.
|
||||
self.values.clear();
|
||||
let func = mir_module
|
||||
.functions
|
||||
.get("Main.main")
|
||||
.or_else(|| mir_module.functions.get("main"))
|
||||
.or_else(|| mir_module.functions.values().next())
|
||||
.ok_or_else(|| "Main.main function not found".to_string())?;
|
||||
|
||||
use crate::mir::instruction::MirInstruction as I;
|
||||
for inst in &func.get_block(func.entry_block).unwrap().instructions {
|
||||
match inst {
|
||||
I::Const { dst, value } => {
|
||||
let v: Box<dyn NyashBox> = match value {
|
||||
ConstValue::Integer(i) => Box::new(IntegerBox::new(*i)),
|
||||
ConstValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(*f)),
|
||||
ConstValue::String(s) => Box::new(crate::box_trait::StringBox::new(s.clone())),
|
||||
ConstValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(*b)),
|
||||
ConstValue::Null => Box::new(crate::boxes::null_box::NullBox::new()),
|
||||
ConstValue::Void => Box::new(IntegerBox::new(0)),
|
||||
};
|
||||
self.values.insert(*dst, v);
|
||||
}
|
||||
I::BinOp { dst, op, lhs, rhs } => {
|
||||
let l = self
|
||||
.values
|
||||
.get(lhs)
|
||||
.and_then(|b| b.as_any().downcast_ref::<IntegerBox>())
|
||||
.ok_or_else(|| format!("binop lhs %{} not integer", lhs.0))?;
|
||||
let r = self
|
||||
.values
|
||||
.get(rhs)
|
||||
.and_then(|b| b.as_any().downcast_ref::<IntegerBox>())
|
||||
.ok_or_else(|| format!("binop rhs %{} not integer", rhs.0))?;
|
||||
let res = match op {
|
||||
BinaryOp::Add => l.value() + r.value(),
|
||||
BinaryOp::Sub => l.value() - r.value(),
|
||||
BinaryOp::Mul => l.value() * r.value(),
|
||||
BinaryOp::Div => {
|
||||
if r.value() == 0 { return Err("division by zero".into()); }
|
||||
l.value() / r.value()
|
||||
}
|
||||
BinaryOp::Mod => l.value() % r.value(),
|
||||
};
|
||||
self.values.insert(*dst, Box::new(IntegerBox::new(res)));
|
||||
}
|
||||
I::Return { value } => {
|
||||
if let Some(v) = value { return self.values.get(v).map(|b| b.clone_box()).ok_or_else(|| format!("return %{} missing", v.0)); }
|
||||
return Ok(Box::new(IntegerBox::new(0)));
|
||||
}
|
||||
_ => { /* ignore for now */ }
|
||||
}
|
||||
}
|
||||
Ok(Box::new(IntegerBox::new(0)))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user