Phase 11-12: LLVM backend initial, semantics layer, plugin unification
Major changes: - LLVM backend initial implementation (compiler.rs, llvm mode) - Semantics layer integration in interpreter (operators.rs) - Phase 12 plugin architecture revision (3-layer system) - Builtin box removal preparation - MIR instruction set documentation (26→Core-15 migration) - Cross-backend testing infrastructure - Await/nowait syntax support New features: - LLVM AOT compilation support (--backend llvm) - Semantics layer for interpreter→VM flow - Tri-backend smoke tests - Plugin-only registry mode Bug fixes: - Interpreter plugin box arithmetic operations - Branch test returns incorrect values Documentation: - Phase 12 README.md updated with new plugin architecture - Removed obsolete NYIR proposals - Added LLVM test programs documentation Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -189,6 +189,9 @@ impl LLVMCompiler {
|
||||
mir_module: &MirModule,
|
||||
output_path: &str,
|
||||
) -> Result<(), String> {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[LLVM] compile_module start: functions={}, out={}", mir_module.functions.len(), output_path);
|
||||
}
|
||||
let context = Context::create();
|
||||
let codegen = CodegenContext::new(&context, "nyash_module")?;
|
||||
// Lower only Main.main for now
|
||||
@ -257,9 +260,45 @@ impl LLVMCompiler {
|
||||
// SSA value map
|
||||
let mut vmap: StdHashMap<ValueId, BasicValueEnum> = StdHashMap::new();
|
||||
|
||||
// Helper ops
|
||||
// Helper ops (centralized conversions and comparisons)
|
||||
fn as_int<'ctx>(v: BasicValueEnum<'ctx>) -> Option<IntValue<'ctx>> { if let BasicValueEnum::IntValue(iv) = v { Some(iv) } else { None } }
|
||||
fn as_float<'ctx>(v: BasicValueEnum<'ctx>) -> Option<FloatValue<'ctx>> { if let BasicValueEnum::FloatValue(fv) = v { Some(fv) } else { None } }
|
||||
fn to_i64_any<'ctx>(ctx: &'ctx Context, builder: &inkwell::builder::Builder<'ctx>, v: BasicValueEnum<'ctx>) -> Result<IntValue<'ctx>, String> {
|
||||
let i64t = ctx.i64_type();
|
||||
Ok(match v {
|
||||
BasicValueEnum::IntValue(iv) => {
|
||||
if iv.get_type().get_bit_width() == 64 { iv }
|
||||
else if iv.get_type().get_bit_width() < 64 { builder.build_int_z_extend(iv, i64t, "zext_i64").map_err(|e| e.to_string())? }
|
||||
else { builder.build_int_truncate(iv, i64t, "trunc_i64").map_err(|e| e.to_string())? }
|
||||
}
|
||||
BasicValueEnum::PointerValue(pv) => builder.build_ptr_to_int(pv, i64t, "p2i64").map_err(|e| e.to_string())?,
|
||||
BasicValueEnum::FloatValue(fv) => {
|
||||
// Bitcast f64 -> i64 via stack slot
|
||||
let slot = builder.get_insert_block().and_then(|bb| bb.get_parent()).and_then(|f| f.get_first_basic_block()).map(|entry| {
|
||||
let eb = ctx.create_builder(); eb.position_at_end(entry); eb
|
||||
}).unwrap_or_else(|| ctx.create_builder());
|
||||
let i64p = i64t.ptr_type(AddressSpace::from(0));
|
||||
let tmp = slot.build_alloca(i64t, "f2i_tmp").map_err(|e| e.to_string())?;
|
||||
let fptr_ty = ctx.f64_type().ptr_type(AddressSpace::from(0));
|
||||
let castp = builder.build_pointer_cast(tmp, fptr_ty, "i64p_to_f64p").map_err(|e| e.to_string())?;
|
||||
builder.build_store(castp, fv).map_err(|e| e.to_string())?;
|
||||
builder.build_load(i64t, tmp, "ld_f2i").map_err(|e| e.to_string())?.into_int_value()
|
||||
}
|
||||
_ => return Err("unsupported value for i64 conversion".to_string()),
|
||||
})
|
||||
}
|
||||
fn i64_to_ptr<'ctx>(ctx: &'ctx Context, builder: &inkwell::builder::Builder<'ctx>, iv: IntValue<'ctx>) -> Result<PointerValue<'ctx>, String> {
|
||||
let pty = ctx.i8_type().ptr_type(AddressSpace::from(0));
|
||||
builder.build_int_to_ptr(iv, pty, "i64_to_ptr").map_err(|e| e.to_string())
|
||||
}
|
||||
fn classify_tag<'ctx>(v: BasicValueEnum<'ctx>) -> i64 {
|
||||
match v {
|
||||
BasicValueEnum::FloatValue(_) => 5, // float
|
||||
BasicValueEnum::PointerValue(_) => 8, // handle/ptr
|
||||
BasicValueEnum::IntValue(_) => 3, // integer/bool
|
||||
_ => 3,
|
||||
}
|
||||
}
|
||||
fn to_bool<'ctx>(ctx: &'ctx Context, b: BasicValueEnum<'ctx>, builder: &inkwell::builder::Builder<'ctx>) -> Result<IntValue<'ctx>, String> {
|
||||
if let Some(bb) = as_int(b) {
|
||||
// If not i1, compare != 0
|
||||
@ -276,6 +315,26 @@ impl LLVMCompiler {
|
||||
Err("Unsupported value for boolean conversion".to_string())
|
||||
}
|
||||
}
|
||||
fn cmp_eq_ne_any<'ctx>(ctx: &'ctx Context, builder: &inkwell::builder::Builder<'ctx>, op: &crate::mir::CompareOp, lv: BasicValueEnum<'ctx>, rv: BasicValueEnum<'ctx>) -> Result<BasicValueEnum<'ctx>, String> {
|
||||
use crate::mir::CompareOp as C;
|
||||
match (lv, rv) {
|
||||
(BasicValueEnum::IntValue(li), BasicValueEnum::IntValue(ri)) => {
|
||||
let pred = if matches!(op, C::Eq) { inkwell::IntPredicate::EQ } else { inkwell::IntPredicate::NE };
|
||||
Ok(builder.build_int_compare(pred, li, ri, "icmp").map_err(|e| e.to_string())?.into())
|
||||
}
|
||||
(BasicValueEnum::FloatValue(lf), BasicValueEnum::FloatValue(rf)) => {
|
||||
let pred = if matches!(op, C::Eq) { inkwell::FloatPredicate::OEQ } else { inkwell::FloatPredicate::ONE };
|
||||
Ok(builder.build_float_compare(pred, lf, rf, "fcmp").map_err(|e| e.to_string())?.into())
|
||||
}
|
||||
(BasicValueEnum::PointerValue(_), _) | (_, BasicValueEnum::PointerValue(_)) => {
|
||||
let li = to_i64_any(ctx, builder, lv)?;
|
||||
let ri = to_i64_any(ctx, builder, rv)?;
|
||||
let pred = if matches!(op, C::Eq) { inkwell::IntPredicate::EQ } else { inkwell::IntPredicate::NE };
|
||||
Ok(builder.build_int_compare(pred, li, ri, "pcmp_any").map_err(|e| e.to_string())?.into())
|
||||
}
|
||||
_ => Err("compare type mismatch".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-create allocas for locals on demand (entry-only builder)
|
||||
let mut allocas: StdHashMap<ValueId, PointerValue> = StdHashMap::new();
|
||||
@ -422,7 +481,16 @@ impl LLVMCompiler {
|
||||
let i64t = codegen.context.i64_type();
|
||||
// Receiver handle (i64)
|
||||
let recv_v = *vmap.get(box_val).ok_or("box receiver missing")?;
|
||||
let recv_p = if let BasicValueEnum::PointerValue(pv) = recv_v { pv } else { return Err("box receiver must be pointer".to_string()); };
|
||||
// Accept either an opaque pointer (i8*) or an i64 handle for the receiver
|
||||
let recv_p = match recv_v {
|
||||
BasicValueEnum::PointerValue(pv) => pv,
|
||||
BasicValueEnum::IntValue(iv) => {
|
||||
// Treat as Nyash handle and convert to opaque pointer
|
||||
let pty = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
codegen.builder.build_int_to_ptr(iv, pty, "recv_i2p").map_err(|e| e.to_string())?
|
||||
}
|
||||
_ => return Err("box receiver must be pointer or i64 handle".to_string()),
|
||||
};
|
||||
let recv_h = codegen.builder.build_ptr_to_int(recv_p, i64t, "recv_p2i").map_err(|e| e.to_string())?;
|
||||
// Resolve type_id from metadata (Box("Type")) via nyash.toml
|
||||
let type_id: i64 = if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) {
|
||||
@ -431,9 +499,9 @@ impl LLVMCompiler {
|
||||
*box_type_ids.get("StringBox").unwrap_or(&0)
|
||||
} else { 0 };
|
||||
|
||||
// Special-case ArrayBox get/set until general by-id is widely annotated
|
||||
// Special-case ArrayBox get/set/push/length until general by-id is widely annotated
|
||||
if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) {
|
||||
if bname == "ArrayBox" && (method == "get" || method == "set") {
|
||||
if bname == "ArrayBox" && (method == "get" || method == "set" || method == "push" || method == "length") {
|
||||
match method.as_str() {
|
||||
"get" => {
|
||||
if args.len() != 1 { return Err("ArrayBox.get expects 1 arg".to_string()); }
|
||||
@ -443,7 +511,6 @@ impl LLVMCompiler {
|
||||
let callee = codegen.module.get_function("nyash_array_get_h").unwrap_or_else(|| codegen.module.add_function("nyash_array_get_h", fnty, None));
|
||||
let call = codegen.builder.build_call(callee, &[recv_h.into(), idx_i.into()], "aget").map_err(|e| e.to_string())?;
|
||||
if let Some(d) = dst { let rv = call.try_as_basic_value().left().ok_or("array_get_h returned void".to_string())?; vmap.insert(*d, rv); }
|
||||
return Ok(());
|
||||
}
|
||||
"set" => {
|
||||
if args.len() != 2 { return Err("ArrayBox.set expects 2 arg".to_string()); }
|
||||
@ -454,72 +521,275 @@ impl LLVMCompiler {
|
||||
let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into()], false);
|
||||
let callee = codegen.module.get_function("nyash_array_set_h").unwrap_or_else(|| codegen.module.add_function("nyash_array_set_h", fnty, None));
|
||||
let _ = codegen.builder.build_call(callee, &[recv_h.into(), idx_i.into(), val_i.into()], "aset").map_err(|e| e.to_string())?;
|
||||
return Ok(());
|
||||
}
|
||||
"push" => {
|
||||
if args.len() != 1 { return Err("ArrayBox.push expects 1 arg".to_string()); }
|
||||
let val_v = *vmap.get(&args[0]).ok_or("array.push value missing")?;
|
||||
let val_i = match val_v {
|
||||
BasicValueEnum::IntValue(iv) => iv,
|
||||
BasicValueEnum::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "val_p2i").map_err(|e| e.to_string())?,
|
||||
_ => return Err("array.push value must be int or handle ptr".to_string()),
|
||||
};
|
||||
let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false);
|
||||
let callee = codegen.module.get_function("nyash_array_push_h").unwrap_or_else(|| codegen.module.add_function("nyash_array_push_h", fnty, None));
|
||||
let _ = codegen.builder.build_call(callee, &[recv_h.into(), val_i.into()], "apush").map_err(|e| e.to_string())?;
|
||||
}
|
||||
"length" => {
|
||||
if !args.is_empty() { return Err("ArrayBox.length expects 0 arg".to_string()); }
|
||||
let fnty = i64t.fn_type(&[i64t.into()], false);
|
||||
let callee = codegen.module.get_function("nyash_array_length_h").unwrap_or_else(|| codegen.module.add_function("nyash_array_length_h", fnty, None));
|
||||
let call = codegen.builder.build_call(callee, &[recv_h.into()], "alen").map_err(|e| e.to_string())?;
|
||||
if let Some(d) = dst { let rv = call.try_as_basic_value().left().ok_or("array_length_h returned void".to_string())?; vmap.insert(*d, rv); }
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Instance field helpers: getField/setField (safe path)
|
||||
if method == "getField" {
|
||||
if args.len() != 1 { return Err("getField expects 1 arg (name)".to_string()); }
|
||||
let name_v = *vmap.get(&args[0]).ok_or("getField name missing")?;
|
||||
let name_p = if let BasicValueEnum::PointerValue(pv) = name_v { pv } else { return Err("getField name must be pointer".to_string()); };
|
||||
let i8p = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
let fnty = i64t.fn_type(&[i64t.into(), i8p.into()], false);
|
||||
let callee = codegen.module.get_function("nyash.instance.get_field_h").unwrap_or_else(|| codegen.module.add_function("nyash.instance.get_field_h", fnty, None));
|
||||
let call = codegen.builder.build_call(callee, &[recv_h.into(), name_p.into()], "getField").map_err(|e| e.to_string())?;
|
||||
if let Some(d) = dst {
|
||||
let rv = call.try_as_basic_value().left().ok_or("get_field returned void".to_string())?;
|
||||
// rv is i64 handle; convert to i8*
|
||||
let h = if let BasicValueEnum::IntValue(iv) = rv { iv } else { return Err("get_field ret expected i64".to_string()); };
|
||||
let pty = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
let ptr = codegen.builder.build_int_to_ptr(h, pty, "gf_handle_to_ptr").map_err(|e| e.to_string())?;
|
||||
vmap.insert(*d, ptr.into());
|
||||
}
|
||||
// no early return; continue lowering
|
||||
}
|
||||
if method == "setField" {
|
||||
if args.len() != 2 { return Err("setField expects 2 args (name, value)".to_string()); }
|
||||
let name_v = *vmap.get(&args[0]).ok_or("setField name missing")?;
|
||||
let val_v = *vmap.get(&args[1]).ok_or("setField value missing")?;
|
||||
let name_p = if let BasicValueEnum::PointerValue(pv) = name_v { pv } else { return Err("setField name must be pointer".to_string()); };
|
||||
let val_h = match val_v {
|
||||
BasicValueEnum::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "val_p2i").map_err(|e| e.to_string())?,
|
||||
BasicValueEnum::IntValue(iv) => iv,
|
||||
_ => return Err("setField value must be handle/ptr or i64".to_string()),
|
||||
};
|
||||
let i8p = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
let fnty = i64t.fn_type(&[i64t.into(), i8p.into(), i64t.into()], false);
|
||||
let callee = codegen.module.get_function("nyash.instance.set_field_h").unwrap_or_else(|| codegen.module.add_function("nyash.instance.set_field_h", fnty, None));
|
||||
let _ = codegen.builder.build_call(callee, &[recv_h.into(), name_p.into(), val_h.into()], "setField").map_err(|e| e.to_string())?;
|
||||
// no early return; continue lowering
|
||||
}
|
||||
|
||||
// General by-id invoke when method_id is available
|
||||
if let Some(mid) = method_id {
|
||||
// Prepare up to 2 args for now (extend later)
|
||||
// Prepare up to 4 args (i64 or f64 bits or handle)
|
||||
let argc_val = i64t.const_int(args.len() as u64, false);
|
||||
let mut a1 = i64t.const_zero();
|
||||
let mut a2 = i64t.const_zero();
|
||||
let mut a3 = i64t.const_zero();
|
||||
let mut a4 = i64t.const_zero();
|
||||
let mut get_i64 = |vid: ValueId| -> Result<inkwell::values::IntValue, String> {
|
||||
let v = *vmap.get(&vid).ok_or("arg missing")?;
|
||||
Ok(match v {
|
||||
BasicValueEnum::IntValue(iv) => iv,
|
||||
BasicValueEnum::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "p2i").map_err(|e| e.to_string())?,
|
||||
_ => return Err("unsupported arg value (expect int or handle ptr)".to_string()),
|
||||
})
|
||||
to_i64_any(codegen.context, &codegen.builder, v)
|
||||
};
|
||||
if args.len() >= 1 { a1 = get_i64(args[0])?; }
|
||||
if args.len() >= 2 { a2 = get_i64(args[1])?; }
|
||||
// declare i64 @nyash_plugin_invoke3_i64(i64 type_id, i64 method_id, i64 argc, i64 a0, i64 a1, i64 a2)
|
||||
let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into()], false);
|
||||
let callee = codegen.module.get_function("nyash_plugin_invoke3_i64").unwrap_or_else(|| codegen.module.add_function("nyash_plugin_invoke3_i64", fnty, None));
|
||||
let tid = i64t.const_int(type_id as u64, true);
|
||||
let midv = i64t.const_int((*mid) as u64, false);
|
||||
let call = codegen.builder.build_call(callee, &[tid.into(), midv.into(), argc_val.into(), recv_h.into(), a1.into(), a2.into()], "pinvoke").map_err(|e| e.to_string())?;
|
||||
if let Some(d) = dst {
|
||||
let rv = call.try_as_basic_value().left().ok_or("invoke3_i64 returned void".to_string())?;
|
||||
// Decide return lowering by dst annotated type
|
||||
if let Some(mt) = func.metadata.value_types.get(d) {
|
||||
match mt {
|
||||
crate::mir::MirType::Integer | crate::mir::MirType::Bool => { vmap.insert(*d, rv); }
|
||||
crate::mir::MirType::Box(_) | crate::mir::MirType::String | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => {
|
||||
// rv is i64 handle; convert to i8*
|
||||
let h = if let BasicValueEnum::IntValue(iv) = rv { iv } else { return Err("invoke ret expected i64".to_string()); };
|
||||
let pty = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
let ptr = codegen.builder.build_int_to_ptr(h, pty, "ret_handle_to_ptr").map_err(|e| e.to_string())?;
|
||||
vmap.insert(*d, ptr.into());
|
||||
}
|
||||
_ => { vmap.insert(*d, rv); }
|
||||
}
|
||||
} else {
|
||||
if args.len() >= 3 { a3 = get_i64(args[2])?; }
|
||||
if args.len() >= 4 { a4 = get_i64(args[3])?; }
|
||||
// Choose return ABI by dst annotated type
|
||||
let dst_ty = dst.as_ref().and_then(|d| func.metadata.value_types.get(d));
|
||||
let use_f64_ret = matches!(dst_ty, Some(crate::mir::MirType::Float));
|
||||
if use_f64_ret {
|
||||
// declare double @nyash_plugin_invoke3_f64(i64,i64,i64,i64,i64,i64)
|
||||
let fnty = codegen.context.f64_type().fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into()], false);
|
||||
let callee = codegen.module.get_function("nyash_plugin_invoke3_f64").unwrap_or_else(|| codegen.module.add_function("nyash_plugin_invoke3_f64", fnty, None));
|
||||
let tid = i64t.const_int(type_id as u64, true);
|
||||
let midv = i64t.const_int((*mid) as u64, false);
|
||||
let call = codegen.builder.build_call(callee, &[tid.into(), midv.into(), argc_val.into(), recv_h.into(), a1.into(), a2.into()], "pinvoke_f64").map_err(|e| e.to_string())?;
|
||||
if let Some(d) = dst {
|
||||
let rv = call.try_as_basic_value().left().ok_or("invoke3_f64 returned void".to_string())?;
|
||||
vmap.insert(*d, rv);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
// For argument typing, use tagged variant to allow f64/handle
|
||||
// Prepare tags for a1..a4: 5=float, 8=handle(ptr), 3=int
|
||||
let mut tag1 = i64t.const_int(3, false);
|
||||
let mut tag2 = i64t.const_int(3, false);
|
||||
let mut tag3 = i64t.const_int(3, false);
|
||||
let mut tag4 = i64t.const_int(3, false);
|
||||
let classify = |vid: ValueId| -> Option<i64> { vmap.get(&vid).map(|v| classify_tag(*v)) };
|
||||
if args.len() >= 1 { if let Some(t) = classify(args[0]) { tag1 = i64t.const_int(t as u64, false); } }
|
||||
if args.len() >= 2 { if let Some(t) = classify(args[1]) { tag2 = i64t.const_int(t as u64, false); } }
|
||||
if args.len() >= 3 { if let Some(t) = classify(args[2]) { tag3 = i64t.const_int(t as u64, false); } }
|
||||
if args.len() >= 4 { if let Some(t) = classify(args[3]) { tag4 = i64t.const_int(t as u64, false); } }
|
||||
if args.len() <= 4 {
|
||||
// Call fixed-arity tagged shim (up to 4 args)
|
||||
let fnty = i64t.fn_type(&[
|
||||
i64t.into(), i64t.into(), i64t.into(), i64t.into(),
|
||||
i64t.into(), i64t.into(), i64t.into(), i64t.into(),
|
||||
i64t.into(), i64t.into(), i64t.into(), i64t.into()
|
||||
], false);
|
||||
let callee = codegen.module.get_function("nyash_plugin_invoke3_tagged_i64").unwrap_or_else(|| codegen.module.add_function("nyash_plugin_invoke3_tagged_i64", fnty, None));
|
||||
let tid = i64t.const_int(type_id as u64, true);
|
||||
let midv = i64t.const_int((*mid) as u64, false);
|
||||
let call = codegen.builder.build_call(
|
||||
callee,
|
||||
&[tid.into(), midv.into(), argc_val.into(), recv_h.into(),
|
||||
a1.into(), tag1.into(), a2.into(), tag2.into(),
|
||||
a3.into(), tag3.into(), a4.into(), tag4.into()],
|
||||
"pinvoke_tagged"
|
||||
).map_err(|e| e.to_string())?;
|
||||
if let Some(d) = dst {
|
||||
let rv = call.try_as_basic_value().left().ok_or("invoke3_i64 returned void".to_string())?;
|
||||
// Decide return lowering by dst annotated type
|
||||
if let Some(mt) = func.metadata.value_types.get(d) {
|
||||
match mt {
|
||||
crate::mir::MirType::Integer | crate::mir::MirType::Bool => { vmap.insert(*d, rv); }
|
||||
crate::mir::MirType::Box(_) | crate::mir::MirType::String | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => {
|
||||
let h = if let BasicValueEnum::IntValue(iv) = rv { iv } else { return Err("invoke ret expected i64".to_string()); };
|
||||
let pty = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
let ptr = codegen.builder.build_int_to_ptr(h, pty, "ret_handle_to_ptr").map_err(|e| e.to_string())?;
|
||||
vmap.insert(*d, ptr.into());
|
||||
}
|
||||
_ => { vmap.insert(*d, rv); }
|
||||
}
|
||||
} else {
|
||||
vmap.insert(*d, rv);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Variable-length path: build arrays of values/tags and call vector shim
|
||||
let n = args.len() as u32;
|
||||
// alloca [N x i64] for vals and tags
|
||||
let arr_ty = i64t.array_type(n);
|
||||
let vals_arr = entry_builder.build_alloca(arr_ty, "vals_arr").map_err(|e| e.to_string())?;
|
||||
let tags_arr = entry_builder.build_alloca(arr_ty, "tags_arr").map_err(|e| e.to_string())?;
|
||||
for (i, vid) in args.iter().enumerate() {
|
||||
let idx = [codegen.context.i32_type().const_zero(), codegen.context.i32_type().const_int(i as u64, false)];
|
||||
let gep_v = unsafe { codegen.builder.build_in_bounds_gep(arr_ty, vals_arr, &idx, &format!("v_gep_{}", i)).map_err(|e| e.to_string())? };
|
||||
let gep_t = unsafe { codegen.builder.build_in_bounds_gep(arr_ty, tags_arr, &idx, &format!("t_gep_{}", i)).map_err(|e| e.to_string())? };
|
||||
let vi = get_i64(*vid)?;
|
||||
let tag = classify(*vid).unwrap_or(3);
|
||||
let tagv = i64t.const_int(tag as u64, false);
|
||||
codegen.builder.build_store(gep_v, vi).map_err(|e| e.to_string())?;
|
||||
codegen.builder.build_store(gep_t, tagv).map_err(|e| e.to_string())?;
|
||||
}
|
||||
// cast to i64* pointers
|
||||
let i64p = i64t.ptr_type(AddressSpace::from(0));
|
||||
let vals_ptr = codegen.builder.build_pointer_cast(vals_arr, i64p, "vals_ptr").map_err(|e| e.to_string())?;
|
||||
let tags_ptr = codegen.builder.build_pointer_cast(tags_arr, i64p, "tags_ptr").map_err(|e| e.to_string())?;
|
||||
// declare i64 @nyash.plugin.invoke_tagged_v_i64(i64,i64,i64,i64,i64*,i64*)
|
||||
let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64p.into(), i64p.into()], false);
|
||||
let callee = codegen.module.get_function("nyash.plugin.invoke_tagged_v_i64").unwrap_or_else(|| codegen.module.add_function("nyash.plugin.invoke_tagged_v_i64", fnty, None));
|
||||
let tid = i64t.const_int(type_id as u64, true);
|
||||
let midv = i64t.const_int((*mid) as u64, false);
|
||||
let call = codegen.builder.build_call(callee, &[tid.into(), midv.into(), argc_val.into(), recv_h.into(), vals_ptr.into(), tags_ptr.into()], "pinvoke_tagged_v").map_err(|e| e.to_string())?;
|
||||
if let Some(d) = dst {
|
||||
let rv = call.try_as_basic_value().left().ok_or("invoke_v returned void".to_string())?;
|
||||
if let Some(mt) = func.metadata.value_types.get(d) {
|
||||
match mt {
|
||||
crate::mir::MirType::Integer | crate::mir::MirType::Bool => { vmap.insert(*d, rv); }
|
||||
crate::mir::MirType::Box(_) | crate::mir::MirType::String | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => {
|
||||
let h = if let BasicValueEnum::IntValue(iv) = rv { iv } else { return Err("invoke ret expected i64".to_string()); };
|
||||
let pty = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
let ptr = codegen.builder.build_int_to_ptr(h, pty, "ret_handle_to_ptr").map_err(|e| e.to_string())?;
|
||||
vmap.insert(*d, ptr.into());
|
||||
}
|
||||
_ => { vmap.insert(*d, rv); }
|
||||
}
|
||||
} else {
|
||||
vmap.insert(*d, rv);
|
||||
}
|
||||
}
|
||||
}
|
||||
// handled above per-branch
|
||||
} else {
|
||||
return Err("BoxCall without method_id not supported in by-id path (enable by-name wrapper if needed)".to_string());
|
||||
// Optional by-name fallback (debug): use NYASH_LLVM_ALLOW_BY_NAME=1
|
||||
if std::env::var("NYASH_LLVM_ALLOW_BY_NAME").ok().as_deref() == Some("1") {
|
||||
// Build global string for method name
|
||||
let gsp = codegen.builder.build_global_string_ptr(method, "method_name").map_err(|e| e.to_string())?;
|
||||
let mptr = gsp.as_pointer_value();
|
||||
let argc_val = i64t.const_int(args.len() as u64, false);
|
||||
let mut a1 = i64t.const_zero();
|
||||
let mut a2 = i64t.const_zero();
|
||||
let mut get_i64 = |vid: ValueId| -> Result<inkwell::values::IntValue, String> {
|
||||
let v = *vmap.get(&vid).ok_or("arg missing")?;
|
||||
Ok(match v {
|
||||
BasicValueEnum::IntValue(iv) => iv,
|
||||
BasicValueEnum::FloatValue(fv) => {
|
||||
let slot = entry_builder.build_alloca(i64t, "f2i_slot").map_err(|e| e.to_string())?;
|
||||
let fptr_ty = codegen.context.f64_type().ptr_type(AddressSpace::from(0));
|
||||
let castp = codegen.builder.build_pointer_cast(slot, fptr_ty, "i64p_to_f64p").map_err(|e| e.to_string())?;
|
||||
let _ = codegen.builder.build_store(castp, fv).map_err(|e| e.to_string())?;
|
||||
codegen.builder.build_load(i64t, slot, "ld_f2i").map_err(|e| e.to_string())?.into_int_value()
|
||||
},
|
||||
BasicValueEnum::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "p2i").map_err(|e| e.to_string())?,
|
||||
_ => return Err("unsupported arg value (expect int or handle ptr)".to_string()),
|
||||
})
|
||||
};
|
||||
if args.len() >= 1 { a1 = get_i64(args[0])?; }
|
||||
if args.len() >= 2 { a2 = get_i64(args[1])?; }
|
||||
// declare i64 @nyash.plugin.invoke_by_name_i64(i64 recv_h, i8* name, i64 argc, i64 a1, i64 a2)
|
||||
let i8p = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
let fnty = i64t.fn_type(&[i64t.into(), i8p.into(), i64t.into(), i64t.into(), i64t.into()], false);
|
||||
let callee = codegen.module.get_function("nyash.plugin.invoke_by_name_i64").unwrap_or_else(|| codegen.module.add_function("nyash.plugin.invoke_by_name_i64", fnty, None));
|
||||
let call = codegen.builder.build_call(callee, &[recv_h.into(), mptr.into(), argc_val.into(), a1.into(), a2.into()], "pinvoke_byname").map_err(|e| e.to_string())?;
|
||||
if let Some(d) = dst {
|
||||
let rv = call.try_as_basic_value().left().ok_or("invoke_by_name returned void".to_string())?;
|
||||
// Treat like i64 path
|
||||
if let Some(mt) = func.metadata.value_types.get(d) {
|
||||
match mt {
|
||||
crate::mir::MirType::Integer | crate::mir::MirType::Bool => { vmap.insert(*d, rv); }
|
||||
crate::mir::MirType::Box(_) | crate::mir::MirType::String | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => {
|
||||
let h = if let BasicValueEnum::IntValue(iv) = rv { iv } else { return Err("invoke ret expected i64".to_string()); };
|
||||
let pty = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
let ptr = codegen.builder.build_int_to_ptr(h, pty, "ret_handle_to_ptr").map_err(|e| e.to_string())?;
|
||||
vmap.insert(*d, ptr.into());
|
||||
}
|
||||
_ => { vmap.insert(*d, rv); }
|
||||
}
|
||||
} else { vmap.insert(*d, rv); }
|
||||
}
|
||||
} else {
|
||||
return Err(format!("BoxCall requires method_id (by-id). Enable NYASH_LLVM_ALLOW_BY_NAME=1 to use by-name fallback for method '{}'", method));
|
||||
}
|
||||
}
|
||||
}
|
||||
MirInstruction::ExternCall { dst, iface_name, method_name, args, effects: _ } => {
|
||||
// Minimal: map console.log / debug.trace to libc puts
|
||||
if (iface_name == "env.console" && method_name == "log") || (iface_name == "env.debug" && method_name == "trace") {
|
||||
if args.len() != 1 { return Err("console.log/debug.trace expect 1 arg (string)".to_string()); }
|
||||
// Route console.log/warn/error/readLine and debug.trace to NyRT shims
|
||||
if (iface_name == "env.console" && (method_name == "log" || method_name == "warn" || method_name == "error"))
|
||||
|| (iface_name == "env.debug" && method_name == "trace") {
|
||||
if args.len() != 1 { return Err(format!("{}.{} expects 1 arg (string)", iface_name, method_name)); }
|
||||
let av = *vmap.get(&args[0]).ok_or("extern arg missing")?;
|
||||
let sp = if let BasicValueEnum::PointerValue(pv) = av { pv } else { return Err("extern arg must be string pointer".to_string()); };
|
||||
// declare i32 @puts(i8*)
|
||||
let i8p = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
let puts_ty = codegen.context.i32_type().fn_type(&[i8p.into()], false);
|
||||
let puts = codegen.module.get_function("puts").unwrap_or_else(|| codegen.module.add_function("puts", puts_ty, None));
|
||||
let _ = codegen.builder.build_call(puts, &[sp.into()], "puts").map_err(|e| e.to_string())?;
|
||||
let sp = match av {
|
||||
BasicValueEnum::PointerValue(pv) => pv,
|
||||
BasicValueEnum::IntValue(iv) => codegen.builder.build_int_to_ptr(iv, i8p, "arg_i2p").map_err(|e| e.to_string())?,
|
||||
_ => return Err("extern arg must be string pointer or i64 handle".to_string()),
|
||||
};
|
||||
let i8p = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
let fnty = codegen.context.i64_type().fn_type(&[i8p.into()], false);
|
||||
let fname = if iface_name == "env.console" {
|
||||
match method_name.as_str() { "log" => "nyash.console.log", "warn" => "nyash.console.warn", _ => "nyash.console.error" }
|
||||
} else { "nyash.debug.trace" };
|
||||
let callee = codegen.module.get_function(fname).unwrap_or_else(|| codegen.module.add_function(fname, fnty, None));
|
||||
let _ = codegen.builder.build_call(callee, &[sp.into()], "extern_rt").map_err(|e| e.to_string())?;
|
||||
if let Some(d) = dst { vmap.insert(*d, codegen.context.i64_type().const_zero().into()); }
|
||||
} else if iface_name == "env.console" && method_name == "readLine" {
|
||||
if !args.is_empty() { return Err("console.readLine expects 0 args".to_string()); }
|
||||
let i8p = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
let fnty = i8p.fn_type(&[], false);
|
||||
let callee = codegen.module.get_function("nyash.console.readline").unwrap_or_else(|| codegen.module.add_function("nyash.console.readline", fnty, None));
|
||||
let call = codegen.builder.build_call(callee, &[], "readline").map_err(|e| e.to_string())?;
|
||||
if let Some(d) = dst {
|
||||
let rv = call.try_as_basic_value().left().ok_or("readline returned void".to_string())?;
|
||||
vmap.insert(*d, rv);
|
||||
}
|
||||
} else {
|
||||
return Err("ExternCall lowering supports only env.console.log/env.debug.trace in 11.2 minimal".to_string());
|
||||
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));
|
||||
}
|
||||
}
|
||||
MirInstruction::UnaryOp { dst, op, operand } => {
|
||||
@ -540,43 +810,82 @@ impl LLVMCompiler {
|
||||
MirInstruction::BinOp { dst, op, lhs, rhs } => {
|
||||
let lv = *vmap.get(lhs).ok_or("lhs missing")?;
|
||||
let rv = *vmap.get(rhs).ok_or("rhs missing")?;
|
||||
let out = if let (Some(li), Some(ri)) = (as_int(lv), as_int(rv)) {
|
||||
use crate::mir::BinaryOp as B;
|
||||
match op {
|
||||
B::Add => codegen.builder.build_int_add(li, ri, "iadd").map_err(|e| e.to_string())?.into(),
|
||||
B::Sub => codegen.builder.build_int_sub(li, ri, "isub").map_err(|e| e.to_string())?.into(),
|
||||
B::Mul => codegen.builder.build_int_mul(li, ri, "imul").map_err(|e| e.to_string())?.into(),
|
||||
B::Div => codegen.builder.build_int_signed_div(li, ri, "idiv").map_err(|e| e.to_string())?.into(),
|
||||
B::Mod => codegen.builder.build_int_signed_rem(li, ri, "imod").map_err(|e| e.to_string())?.into(),
|
||||
B::BitAnd => codegen.builder.build_and(li, ri, "iand").map_err(|e| e.to_string())?.into(),
|
||||
B::BitOr => codegen.builder.build_or(li, ri, "ior").map_err(|e| e.to_string())?.into(),
|
||||
B::BitXor => codegen.builder.build_xor(li, ri, "ixor").map_err(|e| e.to_string())?.into(),
|
||||
B::Shl => codegen.builder.build_left_shift(li, ri, "ishl").map_err(|e| e.to_string())?.into(),
|
||||
B::Shr => codegen.builder.build_right_shift(li, ri, false, "ishr").map_err(|e| e.to_string())?.into(),
|
||||
B::And | B::Or => {
|
||||
// Treat as logical on integers: convert to i1 and and/or
|
||||
let lb = to_bool(codegen.context, li.into(), &codegen.builder)?;
|
||||
let rb = to_bool(codegen.context, ri.into(), &codegen.builder)?;
|
||||
match op {
|
||||
B::And => codegen.builder.build_and(lb, rb, "land").map_err(|e| e.to_string())?.into(),
|
||||
_ => codegen.builder.build_or(lb, rb, "lor").map_err(|e| e.to_string())?.into(),
|
||||
let mut handled_concat = false;
|
||||
// String-like concat handling: if either side is a pointer (i8*),
|
||||
// and op is Add, route to NyRT concat helpers
|
||||
if let crate::mir::BinaryOp::Add = op {
|
||||
let i8p = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
|
||||
match (lv, rv) {
|
||||
(BasicValueEnum::PointerValue(lp), BasicValueEnum::PointerValue(rp)) => {
|
||||
let fnty = i8p.fn_type(&[i8p.into(), i8p.into()], false);
|
||||
let callee = codegen.module.get_function("nyash.string.concat_ss").unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_ss", fnty, None));
|
||||
let call = codegen.builder.build_call(callee, &[lp.into(), rp.into()], "concat_ss").map_err(|e| e.to_string())?;
|
||||
let rv = call.try_as_basic_value().left().ok_or("concat_ss returned void".to_string())?;
|
||||
vmap.insert(*dst, rv);
|
||||
handled_concat = true;
|
||||
}
|
||||
(BasicValueEnum::PointerValue(lp), BasicValueEnum::IntValue(ri)) => {
|
||||
let i64t = codegen.context.i64_type();
|
||||
let fnty = i8p.fn_type(&[i8p.into(), i64t.into()], false);
|
||||
let callee = codegen.module.get_function("nyash.string.concat_si").unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_si", fnty, None));
|
||||
let call = codegen.builder.build_call(callee, &[lp.into(), ri.into()], "concat_si").map_err(|e| e.to_string())?;
|
||||
let rv = call.try_as_basic_value().left().ok_or("concat_si returned void".to_string())?;
|
||||
vmap.insert(*dst, rv);
|
||||
handled_concat = true;
|
||||
}
|
||||
(BasicValueEnum::IntValue(li), BasicValueEnum::PointerValue(rp)) => {
|
||||
let i64t = codegen.context.i64_type();
|
||||
let fnty = i8p.fn_type(&[i64t.into(), i8p.into()], false);
|
||||
let callee = codegen.module.get_function("nyash.string.concat_is").unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_is", fnty, None));
|
||||
let call = codegen.builder.build_call(callee, &[li.into(), rp.into()], "concat_is").map_err(|e| e.to_string())?;
|
||||
let rv = call.try_as_basic_value().left().ok_or("concat_is returned void".to_string())?;
|
||||
vmap.insert(*dst, rv);
|
||||
handled_concat = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if handled_concat {
|
||||
// Concat already lowered and dst set
|
||||
} else {
|
||||
let out = if let (Some(li), Some(ri)) = (as_int(lv), as_int(rv)) {
|
||||
use crate::mir::BinaryOp as B;
|
||||
match op {
|
||||
B::Add => codegen.builder.build_int_add(li, ri, "iadd").map_err(|e| e.to_string())?.into(),
|
||||
B::Sub => codegen.builder.build_int_sub(li, ri, "isub").map_err(|e| e.to_string())?.into(),
|
||||
B::Mul => codegen.builder.build_int_mul(li, ri, "imul").map_err(|e| e.to_string())?.into(),
|
||||
B::Div => codegen.builder.build_int_signed_div(li, ri, "idiv").map_err(|e| e.to_string())?.into(),
|
||||
B::Mod => codegen.builder.build_int_signed_rem(li, ri, "imod").map_err(|e| e.to_string())?.into(),
|
||||
B::BitAnd => codegen.builder.build_and(li, ri, "iand").map_err(|e| e.to_string())?.into(),
|
||||
B::BitOr => codegen.builder.build_or(li, ri, "ior").map_err(|e| e.to_string())?.into(),
|
||||
B::BitXor => codegen.builder.build_xor(li, ri, "ixor").map_err(|e| e.to_string())?.into(),
|
||||
B::Shl => codegen.builder.build_left_shift(li, ri, "ishl").map_err(|e| e.to_string())?.into(),
|
||||
B::Shr => codegen.builder.build_right_shift(li, ri, false, "ishr").map_err(|e| e.to_string())?.into(),
|
||||
B::And | B::Or => {
|
||||
// Treat as logical on integers: convert to i1 and and/or
|
||||
let lb = to_bool(codegen.context, li.into(), &codegen.builder)?;
|
||||
let rb = to_bool(codegen.context, ri.into(), &codegen.builder)?;
|
||||
match op {
|
||||
B::And => codegen.builder.build_and(lb, rb, "land").map_err(|e| e.to_string())?.into(),
|
||||
_ => codegen.builder.build_or(lb, rb, "lor").map_err(|e| e.to_string())?.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let (Some(lf), Some(rf)) = (as_float(lv), as_float(rv)) {
|
||||
use crate::mir::BinaryOp as B;
|
||||
match op {
|
||||
B::Add => codegen.builder.build_float_add(lf, rf, "fadd").map_err(|e| e.to_string())?.into(),
|
||||
B::Sub => codegen.builder.build_float_sub(lf, rf, "fsub").map_err(|e| e.to_string())?.into(),
|
||||
B::Mul => codegen.builder.build_float_mul(lf, rf, "fmul").map_err(|e| e.to_string())?.into(),
|
||||
B::Div => codegen.builder.build_float_div(lf, rf, "fdiv").map_err(|e| e.to_string())?.into(),
|
||||
B::Mod => return Err("fmod not supported yet".to_string()),
|
||||
_ => return Err("bit/logic ops on float".to_string()),
|
||||
}
|
||||
} else {
|
||||
return Err("binop type mismatch".to_string());
|
||||
};
|
||||
vmap.insert(*dst, out);
|
||||
} else if let (Some(lf), Some(rf)) = (as_float(lv), as_float(rv)) {
|
||||
use crate::mir::BinaryOp as B;
|
||||
match op {
|
||||
B::Add => codegen.builder.build_float_add(lf, rf, "fadd").map_err(|e| e.to_string())?.into(),
|
||||
B::Sub => codegen.builder.build_float_sub(lf, rf, "fsub").map_err(|e| e.to_string())?.into(),
|
||||
B::Mul => codegen.builder.build_float_mul(lf, rf, "fmul").map_err(|e| e.to_string())?.into(),
|
||||
B::Div => codegen.builder.build_float_div(lf, rf, "fdiv").map_err(|e| e.to_string())?.into(),
|
||||
B::Mod => return Err("fmod not supported yet".to_string()),
|
||||
_ => return Err("bit/logic ops on float".to_string()),
|
||||
}
|
||||
} else {
|
||||
return Err("binop type mismatch".to_string());
|
||||
};
|
||||
vmap.insert(*dst, out);
|
||||
}
|
||||
}
|
||||
MirInstruction::Compare { dst, op, lhs, rhs } => {
|
||||
let lv = *vmap.get(lhs).ok_or("lhs missing")?;
|
||||
@ -589,7 +898,47 @@ impl LLVMCompiler {
|
||||
use crate::mir::CompareOp as C;
|
||||
let pred = match op { C::Eq=>inkwell::FloatPredicate::OEQ, C::Ne=>inkwell::FloatPredicate::ONE, C::Lt=>inkwell::FloatPredicate::OLT, C::Le=>inkwell::FloatPredicate::OLE, C::Gt=>inkwell::FloatPredicate::OGT, C::Ge=>inkwell::FloatPredicate::OGE };
|
||||
codegen.builder.build_float_compare(pred, lf, rf, "fcmp").map_err(|e| e.to_string())?.into()
|
||||
} else if let (BasicValueEnum::PointerValue(lp), BasicValueEnum::PointerValue(rp)) = (lv, rv) {
|
||||
// Support pointer equality/inequality comparisons
|
||||
use crate::mir::CompareOp as C;
|
||||
match op {
|
||||
C::Eq | C::Ne => {
|
||||
let i64t = codegen.context.i64_type();
|
||||
let li = codegen.builder.build_ptr_to_int(lp, i64t, "pi_l").map_err(|e| e.to_string())?;
|
||||
let ri = codegen.builder.build_ptr_to_int(rp, i64t, "pi_r").map_err(|e| e.to_string())?;
|
||||
let pred = if matches!(op, C::Eq) { inkwell::IntPredicate::EQ } else { inkwell::IntPredicate::NE };
|
||||
codegen.builder.build_int_compare(pred, li, ri, "pcmp").map_err(|e| e.to_string())?.into()
|
||||
}
|
||||
_ => return Err("unsupported pointer comparison (only Eq/Ne)".to_string()),
|
||||
}
|
||||
} else if let (BasicValueEnum::PointerValue(lp), BasicValueEnum::IntValue(ri)) = (lv, rv) {
|
||||
use crate::mir::CompareOp as C;
|
||||
match op {
|
||||
C::Eq | C::Ne => {
|
||||
let i64t = codegen.context.i64_type();
|
||||
let li = codegen.builder.build_ptr_to_int(lp, i64t, "pi_l").map_err(|e| e.to_string())?;
|
||||
let pred = if matches!(op, C::Eq) { inkwell::IntPredicate::EQ } else { inkwell::IntPredicate::NE };
|
||||
codegen.builder.build_int_compare(pred, li, ri, "pcmpi").map_err(|e| e.to_string())?.into()
|
||||
}
|
||||
_ => return Err("unsupported pointer-int comparison (only Eq/Ne)".to_string()),
|
||||
}
|
||||
} else if let (BasicValueEnum::IntValue(li), BasicValueEnum::PointerValue(rp)) = (lv, rv) {
|
||||
use crate::mir::CompareOp as C;
|
||||
match op {
|
||||
C::Eq | C::Ne => {
|
||||
let i64t = codegen.context.i64_type();
|
||||
let ri = codegen.builder.build_ptr_to_int(rp, i64t, "pi_r").map_err(|e| e.to_string())?;
|
||||
let pred = if matches!(op, C::Eq) { inkwell::IntPredicate::EQ } else { inkwell::IntPredicate::NE };
|
||||
codegen.builder.build_int_compare(pred, li, ri, "ipcmi").map_err(|e| e.to_string())?.into()
|
||||
}
|
||||
_ => return Err("unsupported int-pointer comparison (only Eq/Ne)".to_string()),
|
||||
}
|
||||
} else {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
let lk = match lv { BasicValueEnum::IntValue(_) => "int", BasicValueEnum::FloatValue(_) => "float", BasicValueEnum::PointerValue(_) => "ptr", _ => "other" };
|
||||
let rk = match rv { BasicValueEnum::IntValue(_) => "int", BasicValueEnum::FloatValue(_) => "float", BasicValueEnum::PointerValue(_) => "ptr", _ => "other" };
|
||||
eprintln!("[LLVM] compare type mismatch: lhs={}, rhs={} (op={:?})", lk, rk, op);
|
||||
}
|
||||
return Err("compare type mismatch".to_string());
|
||||
};
|
||||
vmap.insert(*dst, out);
|
||||
@ -774,8 +1123,36 @@ impl LLVMCompiler {
|
||||
if !llvm_func.verify(true) {
|
||||
return Err("Function verification failed".to_string());
|
||||
}
|
||||
codegen.target_machine.write_to_file(&codegen.module, inkwell::targets::FileType::Object, output_path.as_ref()).map_err(|e| format!("Failed to write object file: {}", e))?;
|
||||
Ok(())
|
||||
// Try writing via file API first; if it succeeds but file is missing due to env/FS quirks,
|
||||
// also write via memory buffer as a fallback to ensure presence.
|
||||
let verbose = std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1");
|
||||
if verbose { eprintln!("[LLVM] emitting object to {} (begin)", output_path); }
|
||||
match codegen.target_machine.write_to_file(&codegen.module, inkwell::targets::FileType::Object, std::path::Path::new(output_path)) {
|
||||
Ok(_) => {
|
||||
// Verify; if missing, fallback to memory buffer write
|
||||
if std::fs::metadata(output_path).is_err() {
|
||||
let buf = codegen.target_machine
|
||||
.write_to_memory_buffer(&codegen.module, inkwell::targets::FileType::Object)
|
||||
.map_err(|e| format!("Failed to get object buffer: {}", e))?;
|
||||
std::fs::write(output_path, buf.as_slice()).map_err(|e| format!("Failed to write object to '{}': {}", output_path, e))?;
|
||||
if verbose { eprintln!("[LLVM] wrote object via memory buffer fallback: {} ({} bytes)", output_path, buf.get_size()); }
|
||||
} else if verbose {
|
||||
if let Ok(meta) = std::fs::metadata(output_path) { eprintln!("[LLVM] wrote object via file API: {} ({} bytes)", output_path, meta.len()); }
|
||||
}
|
||||
if verbose { eprintln!("[LLVM] emit complete (Ok branch) for {}", output_path); }
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
// Fallback: memory buffer
|
||||
let buf = codegen.target_machine
|
||||
.write_to_memory_buffer(&codegen.module, inkwell::targets::FileType::Object)
|
||||
.map_err(|ee| format!("Failed to write object ({}); and memory buffer failed: {}", e, ee))?;
|
||||
std::fs::write(output_path, buf.as_slice()).map_err(|ee| format!("Failed to write object to '{}': {} (original error: {})", output_path, ee, e))?;
|
||||
if verbose { eprintln!("[LLVM] wrote object via error fallback: {} ({} bytes)", output_path, buf.get_size()); }
|
||||
if verbose { eprintln!("[LLVM] emit complete (Err branch handled) for {}", output_path); }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_and_execute(
|
||||
|
||||
Reference in New Issue
Block a user