feat(llvm): Complete function call system implementation by ChatGPT5

Major improvements to LLVM backend function call infrastructure:

## Key Changes

### Function Call System Complete
- All MIR functions now properly lowered to LLVM (not just entry)
- Function parameter binding to LLVM arguments implemented
- ny_main() wrapper added for proper entry point handling
- Callee resolution from ValueId to function symbols working

### Call Instruction Analysis
- MirInstruction::Call was implemented but system was incomplete
- Fixed "rhs missing" errors caused by undefined Call return values
- Function calls now properly return values through the system

### Code Modularization (Ongoing)
- BoxCall → instructions/boxcall.rs ✓
- ExternCall → instructions/externcall.rs ✓
- Call remains in mod.rs (to be refactored)

### Phase 21 Documentation
- Added comprehensive AI evaluation from Gemini and Codex
- Both AIs confirm academic paper potential for self-parsing AST DB approach
- "Code as Database" concept validated as novel contribution

Co-authored-by: ChatGPT5 <noreply@openai.com>

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Selfhosting Dev
2025-09-12 01:45:00 +09:00
parent 4f4c6397a9
commit 40d0cac0f1
17 changed files with 2219 additions and 608 deletions

View File

@ -3,19 +3,92 @@ use std::collections::HashMap;
use inkwell::values::BasicValueEnum;
use crate::backend::llvm::context::CodegenContext;
use crate::mir::{CompareOp, ValueId};
use crate::mir::{function::MirFunction, CompareOp, ValueId};
/// Compare lowering: return the resulting BasicValueEnum (i1)
pub(in super::super) fn lower_compare<'ctx>(
codegen: &CodegenContext<'ctx>,
func: &MirFunction,
vmap: &HashMap<ValueId, BasicValueEnum<'ctx>>,
op: &CompareOp,
lhs: &ValueId,
rhs: &ValueId,
) -> Result<BasicValueEnum<'ctx>, String> {
use crate::backend::llvm::compiler::helpers::{as_float, as_int};
let lv = *vmap.get(lhs).ok_or("lhs missing")?;
let rv = *vmap.get(rhs).ok_or("rhs missing")?;
let lv = *vmap
.get(lhs)
.ok_or_else(|| format!("lhs missing: {}", lhs.as_u32()))?;
let rv = *vmap
.get(rhs)
.ok_or_else(|| format!("rhs missing: {}", rhs.as_u32()))?;
// String equality/inequality by content when annotated as String/StringBox
if matches!(op, CompareOp::Eq | CompareOp::Ne) {
let l_is_str = match func.metadata.value_types.get(lhs) {
Some(crate::mir::MirType::String) => true,
Some(crate::mir::MirType::Box(b)) if b == "StringBox" => true,
_ => false,
};
let r_is_str = match func.metadata.value_types.get(rhs) {
Some(crate::mir::MirType::String) => true,
Some(crate::mir::MirType::Box(b)) if b == "StringBox" => true,
_ => false,
};
if l_is_str && r_is_str {
let i64t = codegen.context.i64_type();
// Convert both sides to handles if needed
let to_handle = |v: BasicValueEnum<'ctx>| -> Result<inkwell::values::IntValue<'ctx>, String> {
match v {
BasicValueEnum::IntValue(iv) => {
if iv.get_type() == i64t { Ok(iv) } else { codegen.builder.build_int_s_extend(iv, i64t, "i2i64").map_err(|e| e.to_string()) }
}
BasicValueEnum::PointerValue(pv) => {
let fnty = i64t.fn_type(&[codegen.context.ptr_type(inkwell::AddressSpace::from(0)).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()], "str_ptr_to_handle_cmp")
.map_err(|e| e.to_string())?;
let rv = call
.try_as_basic_value()
.left()
.ok_or("from_i8_string returned void".to_string())?;
Ok(rv.into_int_value())
}
_ => Err("unsupported value for string compare".to_string()),
}
};
let lh = to_handle(lv)?;
let rh = to_handle(rv)?;
let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false);
let callee = codegen
.module
.get_function("nyash.string.eq_hh")
.unwrap_or_else(|| codegen.module.add_function("nyash.string.eq_hh", fnty, None));
let call = codegen
.builder
.build_call(callee, &[lh.into(), rh.into()], "str_eq_hh")
.map_err(|e| e.to_string())?;
let iv = call
.try_as_basic_value()
.left()
.ok_or("eq_hh returned void".to_string())?
.into_int_value();
let zero = i64t.const_zero();
let pred = if matches!(op, CompareOp::Eq) {
inkwell::IntPredicate::NE
} else {
inkwell::IntPredicate::EQ
};
let b = codegen
.builder
.build_int_compare(pred, iv, zero, "str_eq_to_bool")
.map_err(|e| e.to_string())?;
return Ok(b.into());
}
}
let out = if let (Some(li), Some(ri)) = (as_int(lv), as_int(rv)) {
use CompareOp as C;
let pred = match op {

View File

@ -63,8 +63,12 @@ pub(in super::super) fn lower_binop<'ctx>(
use crate::backend::llvm::compiler::helpers::{as_float, as_int};
use inkwell::values::BasicValueEnum as BVE;
use inkwell::IntPredicate;
let lv = *vmap.get(lhs).ok_or("lhs missing")?;
let rv = *vmap.get(rhs).ok_or("rhs missing")?;
let lv = *vmap
.get(lhs)
.ok_or_else(|| format!("lhs missing: {}", lhs.as_u32()))?;
let rv = *vmap
.get(rhs)
.ok_or_else(|| format!("rhs missing: {}", rhs.as_u32()))?;
let mut handled_concat = false;
if let BinaryOp::Add = op {
let i8p = codegen.context.ptr_type(AddressSpace::from(0));

View File

@ -1,465 +0,0 @@
use std::collections::HashMap;
use inkwell::AddressSpace;
use inkwell::values::BasicValueEnum as BVE;
use crate::backend::llvm::context::CodegenContext;
use crate::mir::{function::MirFunction, ValueId};
/// Full ExternCall lowering (console/debug, future.spawn_instance, env.local, env.box.new)
pub(in super::super) fn lower_externcall<'ctx>(
codegen: &CodegenContext<'ctx>,
func: &MirFunction,
vmap: &mut HashMap<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
dst: &Option<ValueId>,
iface_name: &str,
method_name: &str,
args: &[ValueId],
) -> Result<(), String> {
use crate::backend::llvm::compiler::helpers::{as_float, as_int};
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", iface_name, method_name));
}
let av = *vmap.get(&args[0]).ok_or("extern arg missing")?;
match av {
// If argument is i8* (string), call string variant
BVE::PointerValue(pv) => {
let i8p = codegen.context.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 {
"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, &[pv.into()], "console_log_p")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
vmap.insert(*d, codegen.context.i64_type().const_zero().into());
}
return Ok(());
}
// Otherwise, convert to i64 and call handle variant
_ => {
let arg_val = match av {
BVE::IntValue(iv) => {
if iv.get_type() == codegen.context.bool_type() {
codegen
.builder
.build_int_z_extend(iv, codegen.context.i64_type(), "bool2i64")
.map_err(|e| e.to_string())?
} else if iv.get_type() == codegen.context.i64_type() {
iv
} else {
codegen
.builder
.build_int_s_extend(iv, codegen.context.i64_type(), "int2i64")
.map_err(|e| e.to_string())?
}
}
BVE::PointerValue(_) => unreachable!(),
_ => return Err("console.log arg conversion failed".to_string()),
};
let fnty = codegen
.context
.i64_type()
.fn_type(&[codegen.context.i64_type().into()], false);
let fname = if iface_name == "env.console" {
match method_name {
"log" => "nyash.console.log_handle",
"warn" => "nyash.console.warn_handle",
_ => "nyash.console.error_handle",
}
} else {
"nyash.debug.trace_handle"
};
let callee = codegen
.module
.get_function(fname)
.unwrap_or_else(|| codegen.module.add_function(fname, fnty, None));
let _ = codegen
.builder
.build_call(callee, &[arg_val.into()], "console_log_h")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
vmap.insert(*d, codegen.context.i64_type().const_zero().into());
}
return Ok(());
}
}
}
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.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);
}
return Ok(());
}
if iface_name == "env.future" && method_name == "spawn_instance" {
if args.len() < 2 {
return Err("env.future.spawn_instance expects at least (recv, method_name)".to_string());
}
let i64t = codegen.context.i64_type();
let i8p = codegen.context.ptr_type(AddressSpace::from(0));
let recv_v = *vmap.get(&args[0]).ok_or("recv missing")?;
let recv_h = match recv_v {
BVE::IntValue(iv) => iv,
BVE::PointerValue(pv) => codegen
.builder
.build_ptr_to_int(pv, i64t, "recv_p2i")
.map_err(|e| e.to_string())?,
_ => return Err("spawn_instance recv must be int or ptr".to_string()),
};
let name_v = *vmap.get(&args[1]).ok_or("method name missing")?;
let name_p = match name_v {
BVE::PointerValue(pv) => pv,
_ => return Err("spawn_instance method name must be i8*".to_string()),
};
let fnty = i64t.fn_type(&[i64t.into(), i8p.into()], false);
let callee = codegen
.module
.get_function("nyash.future.spawn_instance")
.unwrap_or_else(|| codegen.module.add_function("nyash.future.spawn_instance", fnty, None));
let call = codegen
.builder
.build_call(callee, &[recv_h.into(), name_p.into()], "spawn_instance")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
let rv = call
.try_as_basic_value()
.left()
.ok_or("spawn_instance returned void".to_string())?;
vmap.insert(*d, rv);
}
return Ok(());
}
if iface_name == "env.local" && method_name == "get" {
if args.len() != 1 {
return Err("env.local.get expects 1 arg".to_string());
}
let name_v = *vmap.get(&args[0]).ok_or("local.get name missing")?;
let name_p = if let BVE::PointerValue(pv) = name_v {
pv
} else {
return Err("env.local.get name must be i8*".to_string());
};
let i64t = codegen.context.i64_type();
let i8p = codegen.context.ptr_type(AddressSpace::from(0));
let fnty = i64t.fn_type(&[i8p.into()], false);
let callee = codegen
.module
.get_function("nyash.env.local.get_h")
.unwrap_or_else(|| codegen.module.add_function("nyash.env.local.get_h", fnty, None));
let call = codegen
.builder
.build_call(callee, &[name_p.into()], "local_get_h")
.map_err(|e| e.to_string())?;
let rv = call
.try_as_basic_value()
.left()
.ok_or("local.get returned void".to_string())?;
// Cast handle to pointer for Box-like return types
if let Some(d) = dst {
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::String => {
// keep as handle (i64)
vmap.insert(*d, rv);
}
crate::mir::MirType::Box(_)
| crate::mir::MirType::Array(_)
| crate::mir::MirType::Future(_)
| crate::mir::MirType::Unknown => {
let h = rv.into_int_value();
let pty = codegen.context.ptr_type(AddressSpace::from(0));
let ptr = codegen
.builder
.build_int_to_ptr(h, pty, "local_get_handle_to_ptr")
.map_err(|e| e.to_string())?;
vmap.insert(*d, ptr.into());
}
_ => {
vmap.insert(*d, rv);
}
}
} else {
vmap.insert(*d, rv);
}
}
return Ok(());
}
if iface_name == "env.box" && method_name == "new" {
// Two variants: (name) and (argc, arg1, arg2, arg3, arg4) with optional ptr conversion
// Prefer the i64 birth when possible; else call env.box.new(name)
let i64t = codegen.context.i64_type();
let i8p = codegen.context.ptr_type(AddressSpace::from(0));
if args.len() == 1 {
let name_v = *vmap.get(&args[0]).ok_or("env.box.new name missing")?;
let name_p = if let BVE::PointerValue(pv) = name_v {
pv
} else {
return Err("env.box.new name must be i8*".to_string());
};
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, &[name_p.into()], "env_box_new")
.map_err(|e| e.to_string())?;
let h = call
.try_as_basic_value()
.left()
.ok_or("env.box.new returned void".to_string())?
.into_int_value();
let out_ptr = codegen
.builder
.build_int_to_ptr(h, i8p, "box_handle_to_ptr")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
vmap.insert(*d, out_ptr.into());
}
return Ok(());
}
if !args.is_empty() {
// argc + up to 4 i64 payloads: build i64 via conversions
let argc_val = i64t.const_int(args.len() as u64, false);
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_i64")
.unwrap_or_else(|| codegen.module.add_function("nyash.env.box.new_i64", fnty, None));
// arg0: type name string pointer
if args.is_empty() {
return Err("env.box.new_i64 requires at least type name".to_string());
}
let ty_ptr = match *vmap.get(&args[0]).ok_or("type name missing")? {
BVE::PointerValue(pv) => pv,
_ => return Err("env.box.new_i64 arg0 must be i8* type name".to_string()),
};
let mut a1 = i64t.const_zero();
if args.len() >= 2 {
let bv = *vmap.get(&args[1]).ok_or("arg missing")?;
a1 = match bv {
BVE::IntValue(iv) => iv,
BVE::FloatValue(fv) => {
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()], "arg1_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 BVE::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BVE::PointerValue(pv) => {
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()], "arg1_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 BVE::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
_ => return Err("unsupported arg value for env.box.new".to_string()),
};
}
let mut a2 = i64t.const_zero();
if args.len() >= 3 {
let bv = *vmap.get(&args[2]).ok_or("arg missing")?;
a2 = match bv {
BVE::IntValue(iv) => iv,
BVE::FloatValue(fv) => {
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()], "arg2_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 BVE::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BVE::PointerValue(pv) => {
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()], "arg2_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 BVE::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
_ => return Err("unsupported arg value for env.box.new".to_string()),
};
}
let mut a3 = i64t.const_zero();
if args.len() >= 4 {
let bv = *vmap.get(&args[3]).ok_or("arg missing")?;
a3 = match bv {
BVE::IntValue(iv) => iv,
BVE::FloatValue(fv) => {
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()], "arg3_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 BVE::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BVE::PointerValue(pv) => {
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()], "arg3_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 BVE::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
_ => return Err("unsupported arg value for env.box.new".to_string()),
};
}
let mut a4 = i64t.const_zero();
if args.len() >= 5 {
let bv = *vmap.get(&args[4]).ok_or("arg missing")?;
a4 = match bv {
BVE::IntValue(iv) => iv,
BVE::FloatValue(fv) => {
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()], "arg4_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 BVE::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BVE::PointerValue(pv) => {
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()], "arg4_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 BVE::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
_ => return Err("unsupported arg value for env.box.new".to_string()),
};
}
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())?;
let rv = call
.try_as_basic_value()
.left()
.ok_or("env.box.new_i64 returned void".to_string())?;
let i64v = if let BVE::IntValue(iv) = rv { iv } else { return Err("env.box.new_i64 ret expected i64".to_string()); };
let out_ptr = codegen
.builder
.build_int_to_ptr(i64v, i8p, "box_handle_to_ptr")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
vmap.insert(*d, out_ptr.into());
}
return Ok(());
}
}
Err(format!(
"ExternCall lowering unsupported: {}.{} (add a NyRT shim for this interface method)",
iface_name, method_name
))
}

View File

@ -0,0 +1,126 @@
use std::collections::HashMap;
use inkwell::values::BasicValueEnum as BVE;
use inkwell::AddressSpace;
use crate::backend::llvm::context::CodegenContext;
use crate::mir::ValueId;
pub(super) fn lower_log_or_trace<'ctx>(
codegen: &CodegenContext<'ctx>,
vmap: &mut HashMap<ValueId, BVE<'ctx>>,
dst: &Option<ValueId>,
iface_name: &str,
method_name: &str,
args: &[ValueId],
) -> Result<(), String> {
if args.len() != 1 {
return Err(format!("{}.{} expects 1 arg", iface_name, method_name));
}
let av = *vmap.get(&args[0]).ok_or("extern arg missing")?;
match av {
// If argument is i8* (string), call string variant
BVE::PointerValue(pv) => {
let i8p = codegen.context.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 {
"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, &[pv.into()], "console_log_p")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
vmap.insert(*d, codegen.context.i64_type().const_zero().into());
}
Ok(())
}
// Otherwise, convert to i64 and call handle variant
_ => {
let arg_val = match av {
BVE::IntValue(iv) => {
if iv.get_type() == codegen.context.bool_type() {
codegen
.builder
.build_int_z_extend(iv, codegen.context.i64_type(), "bool2i64")
.map_err(|e| e.to_string())?
} else if iv.get_type() == codegen.context.i64_type() {
iv
} else {
codegen
.builder
.build_int_s_extend(iv, codegen.context.i64_type(), "int2i64")
.map_err(|e| e.to_string())?
}
}
BVE::PointerValue(_) => unreachable!(),
_ => return Err("console.log arg conversion failed".to_string()),
};
let fnty = codegen
.context
.i64_type()
.fn_type(&[codegen.context.i64_type().into()], false);
let fname = if iface_name == "env.console" {
match method_name {
"log" => "nyash.console.log_handle",
"warn" => "nyash.console.warn_handle",
_ => "nyash.console.error_handle",
}
} else {
"nyash.debug.trace_handle"
};
let callee = codegen
.module
.get_function(fname)
.unwrap_or_else(|| codegen.module.add_function(fname, fnty, None));
let _ = codegen
.builder
.build_call(callee, &[arg_val.into()], "console_log_h")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
vmap.insert(*d, codegen.context.i64_type().const_zero().into());
}
Ok(())
}
}
}
pub(super) fn lower_readline<'ctx>(
codegen: &CodegenContext<'ctx>,
vmap: &mut HashMap<ValueId, BVE<'ctx>>,
dst: &Option<ValueId>,
args: &[ValueId],
) -> Result<(), String> {
if !args.is_empty() {
return Err("console.readLine expects 0 args".to_string());
}
let i8p = codegen.context.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);
}
Ok(())
}

View File

@ -0,0 +1,356 @@
use std::collections::HashMap;
use inkwell::values::BasicValueEnum as BVE;
use inkwell::AddressSpace;
use crate::backend::llvm::context::CodegenContext;
use crate::mir::{function::MirFunction, ValueId};
pub(super) fn lower_future_spawn_instance<'ctx>(
codegen: &CodegenContext<'ctx>,
vmap: &mut HashMap<ValueId, BVE<'ctx>>,
dst: &Option<ValueId>,
args: &[ValueId],
) -> Result<(), String> {
if args.len() < 2 {
return Err("env.future.spawn_instance expects at least (recv, method_name)".to_string());
}
let i64t = codegen.context.i64_type();
let i8p = codegen.context.ptr_type(AddressSpace::from(0));
let recv_v = *vmap.get(&args[0]).ok_or("recv missing")?;
let recv_h = match recv_v {
BVE::IntValue(iv) => iv,
BVE::PointerValue(pv) => codegen
.builder
.build_ptr_to_int(pv, i64t, "recv_p2i")
.map_err(|e| e.to_string())?,
_ => return Err("spawn_instance recv must be int or ptr".to_string()),
};
let name_v = *vmap.get(&args[1]).ok_or("method name missing")?;
let name_p = match name_v {
BVE::PointerValue(pv) => pv,
_ => return Err("spawn_instance method name must be i8*".to_string()),
};
let fnty = i64t.fn_type(&[i64t.into(), i8p.into()], false);
let callee = codegen
.module
.get_function("nyash.future.spawn_instance")
.unwrap_or_else(|| codegen.module.add_function("nyash.future.spawn_instance", fnty, None));
let call = codegen
.builder
.build_call(callee, &[recv_h.into(), name_p.into()], "spawn_instance")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
let rv = call
.try_as_basic_value()
.left()
.ok_or("spawn_instance returned void".to_string())?;
vmap.insert(*d, rv);
}
Ok(())
}
pub(super) fn lower_local_get<'ctx>(
codegen: &CodegenContext<'ctx>,
func: &MirFunction,
vmap: &mut HashMap<ValueId, BVE<'ctx>>,
dst: &Option<ValueId>,
args: &[ValueId],
) -> Result<(), String> {
if args.len() != 1 {
return Err("env.local.get expects 1 arg".to_string());
}
let name_v = *vmap.get(&args[0]).ok_or("local.get name missing")?;
let name_p = if let BVE::PointerValue(pv) = name_v {
pv
} else {
return Err("env.local.get name must be i8*".to_string());
};
let i64t = codegen.context.i64_type();
let i8p = codegen.context.ptr_type(AddressSpace::from(0));
let fnty = i64t.fn_type(&[i8p.into()], false);
let callee = codegen
.module
.get_function("nyash.env.local.get_h")
.unwrap_or_else(|| codegen.module.add_function("nyash.env.local.get_h", fnty, None));
let call = codegen
.builder
.build_call(callee, &[name_p.into()], "local_get_h")
.map_err(|e| e.to_string())?;
let rv = call
.try_as_basic_value()
.left()
.ok_or("local.get returned void".to_string())?;
// Cast handle to pointer for Box-like return types
if let Some(d) = dst {
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::String => {
// keep as handle (i64)
vmap.insert(*d, rv);
}
crate::mir::MirType::Box(_)
| crate::mir::MirType::Array(_)
| crate::mir::MirType::Future(_)
| crate::mir::MirType::Unknown => {
let h = rv.into_int_value();
let pty = codegen.context.ptr_type(AddressSpace::from(0));
let ptr = codegen
.builder
.build_int_to_ptr(h, pty, "local_get_handle_to_ptr")
.map_err(|e| e.to_string())?;
vmap.insert(*d, ptr.into());
}
_ => {
vmap.insert(*d, rv);
}
}
} else {
vmap.insert(*d, rv);
}
}
Ok(())
}
pub(super) fn lower_box_new<'ctx>(
codegen: &CodegenContext<'ctx>,
vmap: &mut HashMap<ValueId, BVE<'ctx>>,
dst: &Option<ValueId>,
args: &[ValueId],
) -> Result<(), String> {
// Two variants: (name) and (argc, arg1, arg2, arg3, arg4) with optional ptr conversion
// Prefer the i64 birth when possible; else call env.box.new(name)
let i64t = codegen.context.i64_type();
let i8p = codegen.context.ptr_type(AddressSpace::from(0));
if args.len() == 1 {
let name_v = *vmap.get(&args[0]).ok_or("env.box.new name missing")?;
let name_p = if let BVE::PointerValue(pv) = name_v {
pv
} else {
return Err("env.box.new name must be i8*".to_string());
};
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, &[name_p.into()], "env_box_new")
.map_err(|e| e.to_string())?;
let h = call
.try_as_basic_value()
.left()
.ok_or("env.box.new returned void".to_string())?
.into_int_value();
let out_ptr = codegen
.builder
.build_int_to_ptr(h, i8p, "box_handle_to_ptr")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
vmap.insert(*d, out_ptr.into());
}
return Ok(());
}
if !args.is_empty() {
// argc + up to 4 i64 payloads: build i64 via conversions
let argc_val = i64t.const_int(args.len() as u64, false);
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_i64")
.unwrap_or_else(|| codegen.module.add_function("nyash.env.box.new_i64", fnty, None));
// arg0: type name string pointer
if args.is_empty() {
return Err("env.box.new_i64 requires at least type name".to_string());
}
let ty_ptr = match *vmap.get(&args[0]).ok_or("type name missing")? {
BVE::PointerValue(pv) => pv,
_ => return Err("env.box.new_i64 arg0 must be i8* type name".to_string()),
};
let mut a1 = i64t.const_zero();
if args.len() >= 2 {
let bv = *vmap.get(&args[1]).ok_or("arg missing")?;
a1 = match bv {
BVE::IntValue(iv) => iv,
BVE::FloatValue(fv) => {
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()], "arg1_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 BVE::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BVE::PointerValue(pv) => {
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()], "arg1_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 BVE::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
_ => return Err("unsupported arg value for env.box.new".to_string()),
};
}
let mut a2 = i64t.const_zero();
if args.len() >= 3 {
let bv = *vmap.get(&args[2]).ok_or("arg missing")?;
a2 = match bv {
BVE::IntValue(iv) => iv,
BVE::FloatValue(fv) => {
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()], "arg2_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 BVE::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BVE::PointerValue(pv) => {
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()], "arg2_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 BVE::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
_ => return Err("unsupported arg value for env.box.new".to_string()),
};
}
let mut a3 = i64t.const_zero();
if args.len() >= 4 {
let bv = *vmap.get(&args[3]).ok_or("arg missing")?;
a3 = match bv {
BVE::IntValue(iv) => iv,
BVE::FloatValue(fv) => {
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()], "arg3_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 BVE::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BVE::PointerValue(pv) => {
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()], "arg3_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 BVE::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
_ => return Err("unsupported arg value for env.box.new".to_string()),
};
}
let mut a4 = i64t.const_zero();
if args.len() >= 5 {
let bv = *vmap.get(&args[4]).ok_or("arg missing")?;
a4 = match bv {
BVE::IntValue(iv) => iv,
BVE::FloatValue(fv) => {
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()], "arg4_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 BVE::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BVE::PointerValue(pv) => {
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()], "arg4_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 BVE::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
_ => return Err("unsupported arg value for env.box.new".to_string()),
};
}
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())?;
let rv = call
.try_as_basic_value()
.left()
.ok_or("env.box.new_i64 returned void".to_string())?;
let i64v = if let BVE::IntValue(iv) = rv { iv } else { return Err("env.box.new_i64 ret expected i64".to_string()); };
let out_ptr = codegen
.builder
.build_int_to_ptr(i64v, i8p, "box_handle_to_ptr")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
vmap.insert(*d, out_ptr.into());
}
return Ok(());
}
Err("env.box.new requires at least 1 arg".to_string())
}

View File

@ -0,0 +1,47 @@
mod console;
mod env;
use std::collections::HashMap;
use crate::backend::llvm::context::CodegenContext;
use crate::mir::{function::MirFunction, ValueId};
use inkwell::values::BasicValueEnum as BVE;
/// Full ExternCall lowering dispatcher (console/debug/env.*)
pub(in super::super) fn lower_externcall<'ctx>(
codegen: &CodegenContext<'ctx>,
func: &MirFunction,
vmap: &mut HashMap<ValueId, BVE<'ctx>>,
dst: &Option<ValueId>,
iface_name: &str,
method_name: &str,
args: &[ValueId],
) -> Result<(), String> {
// console/debug
if (iface_name == "env.console"
&& matches!(method_name, "log" | "warn" | "error"))
|| (iface_name == "env.debug" && method_name == "trace")
{
return console::lower_log_or_trace(codegen, vmap, dst, iface_name, method_name, args);
}
if iface_name == "env.console" && method_name == "readLine" {
return console::lower_readline(codegen, vmap, dst, args);
}
// env.*
if iface_name == "env.future" && method_name == "spawn_instance" {
return env::lower_future_spawn_instance(codegen, vmap, dst, args);
}
if iface_name == "env.local" && method_name == "get" {
return env::lower_local_get(codegen, func, vmap, dst, args);
}
if iface_name == "env.box" && method_name == "new" {
return env::lower_box_new(codegen, vmap, dst, args);
}
Err(format!(
"ExternCall lowering unsupported: {}.{} (add a NyRT shim for this interface method)",
iface_name, method_name
))
}

View File

@ -146,6 +146,82 @@ pub(super) fn try_handle_string_method<'ctx>(
return Ok(true);
}
// substring(start, end) -> i8*
if method == "substring" {
if args.len() != 2 {
return Err("String.substring expects 2 args (start, end)".to_string());
}
let i64t = codegen.context.i64_type();
let i8p = codegen.context.ptr_type(AddressSpace::from(0));
// receiver must be i8* for this fast path
let recv_p = match recv_v {
BVE::PointerValue(p) => p,
_ => return Ok(false),
};
let a0 = *vmap.get(&args[0]).ok_or("substring start arg missing")?;
let a1 = *vmap.get(&args[1]).ok_or("substring end arg missing")?;
let s = match a0 {
BVE::IntValue(iv) => iv,
_ => return Err("substring start must be integer".to_string()),
};
let e = match a1 {
BVE::IntValue(iv) => iv,
_ => return Err("substring end must be integer".to_string()),
};
let fnty = i8p.fn_type(&[i8p.into(), i64t.into(), i64t.into()], false);
let callee = codegen
.module
.get_function("nyash.string.substring_sii")
.unwrap_or_else(|| codegen.module.add_function("nyash.string.substring_sii", fnty, None));
let call = codegen
.builder
.build_call(callee, &[recv_p.into(), s.into(), e.into()], "substring_call")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
let rv = call
.try_as_basic_value()
.left()
.ok_or("substring returned void".to_string())?;
vmap.insert(*d, rv);
}
return Ok(true);
}
// lastIndexOf(needle) -> i64
if method == "lastIndexOf" {
if args.len() != 1 {
return Err("String.lastIndexOf expects 1 arg".to_string());
}
let i64t = codegen.context.i64_type();
let i8p = codegen.context.ptr_type(AddressSpace::from(0));
// receiver must be i8* for this fast path
let recv_p = match recv_v {
BVE::PointerValue(p) => p,
_ => return Ok(false),
};
let a0 = *vmap.get(&args[0]).ok_or("lastIndexOf arg missing")?;
let needle_p = match a0 {
BVE::PointerValue(p) => p,
_ => return Err("lastIndexOf arg must be i8*".to_string()),
};
let fnty = i64t.fn_type(&[i8p.into(), i8p.into()], false);
let callee = codegen
.module
.get_function("nyash.string.lastIndexOf_ss")
.unwrap_or_else(|| codegen.module.add_function("nyash.string.lastIndexOf_ss", fnty, None));
let call = codegen
.builder
.build_call(callee, &[recv_p.into(), needle_p.into()], "lastindexof_call")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
let rv = call
.try_as_basic_value()
.left()
.ok_or("lastIndexOf returned void".to_string())?;
vmap.insert(*d, rv);
}
return Ok(true);
}
Ok(false)
}

View File

@ -36,122 +36,152 @@ impl LLVMCompiler {
}
let context = Context::create();
let codegen = CodegenContext::new(&context, "nyash_module")?;
// Lower only Main.main for now
// Load box type-id mapping from nyash_box.toml (central plugin registry)
let box_type_ids = crate::backend::llvm::box_types::load_box_type_ids();
// Utility: sanitize MIR function name to a valid C symbol
let sanitize = |name: &str| -> String {
name.chars()
.map(|c| match c {
'.' | '/' | '-' => '_',
other => other,
})
.collect()
};
// Find entry function
let func = if let Some((_n, f)) = mir_module
let (entry_name, _entry_func_ref) = if let Some((n, f)) = mir_module
.functions
.iter()
.find(|(_n, f)| f.metadata.is_entry_point)
{
f
(n.clone(), f)
} else if let Some(f) = mir_module.functions.get("Main.main") {
f
("Main.main".to_string(), f)
} else if let Some(f) = mir_module.functions.get("main") {
f
} else if let Some((_n, f)) = mir_module.functions.iter().next() {
f
("main".to_string(), f)
} else if let Some((n, f)) = mir_module.functions.iter().next() {
(n.clone(), f)
} else {
return Err("Main.main function not found in module".to_string());
};
// Map MIR types to LLVM types via helpers
// Load box type-id mapping from nyash_box.toml (central plugin registry)
let box_type_ids = crate::backend::llvm::box_types::load_box_type_ids();
// Function type
let ret_type = match func.signature.return_type {
crate::mir::MirType::Void => None,
ref t => Some(map_type(codegen.context, t)?),
};
let fn_type = match ret_type {
Some(BasicTypeEnum::IntType(t)) => t.fn_type(&[], false),
Some(BasicTypeEnum::FloatType(t)) => t.fn_type(&[], false),
Some(BasicTypeEnum::PointerType(t)) => t.fn_type(&[], false),
Some(_) => return Err("Unsupported return basic type".to_string()),
None => codegen.context.void_type().fn_type(&[], false),
};
let llvm_func = codegen.module.add_function("ny_main", fn_type, None);
// Create LLVM basic blocks: ensure entry is created first to be function entry
let (mut bb_map, entry_bb) = instructions::create_basic_blocks(&codegen, llvm_func, func);
// Position at entry
codegen.builder.position_at_end(entry_bb);
// SSA value map
let mut vmap: HashMap<ValueId, BasicValueEnum> = HashMap::new();
// Helper ops are now provided by codegen/types.rs
// Pre-create allocas for locals on demand (entry-only builder)
let mut allocas: HashMap<ValueId, PointerValue> = HashMap::new();
let entry_builder = codegen.context.create_builder();
entry_builder.position_at_end(entry_bb);
// Helper: map MirType to LLVM basic type (value type) is provided by types::map_mirtype_to_basic
// Helper: create (or get) an alloca for a given pointer-typed SSA value id
let mut alloca_elem_types: HashMap<ValueId, BasicTypeEnum> = HashMap::new();
// Pre-create PHI nodes for all blocks (so we can add incoming from predecessors)
let mut phis_by_block: HashMap<
crate::mir::BasicBlockId,
Vec<(ValueId, PhiValue, Vec<(crate::mir::BasicBlockId, ValueId)>)>,
> = HashMap::new();
for bid in func.block_ids() {
let bb = *bb_map.get(&bid).ok_or("missing bb in map")?;
// Position at start of the block (no instructions emitted yet)
codegen.builder.position_at_end(bb);
let block = func.blocks.get(&bid).unwrap();
for inst in block
.instructions
.iter()
.take_while(|i| matches!(i, MirInstruction::Phi { .. }))
{
if let MirInstruction::Phi { dst, inputs } = inst {
// Decide PHI type: prefer annotated value type; fallback to first input's annotated type; finally i64
let mut phi_ty: Option<BasicTypeEnum> = None;
if let Some(mt) = func.metadata.value_types.get(dst) {
phi_ty = Some(map_mirtype_to_basic(codegen.context, mt));
} else if let Some((_, iv)) = inputs.first() {
if let Some(mt) = func.metadata.value_types.get(iv) {
phi_ty = Some(map_mirtype_to_basic(codegen.context, mt));
}
}
let phi_ty = phi_ty.unwrap_or_else(|| codegen.context.i64_type().into());
let phi = codegen
.builder
.build_phi(phi_ty, &format!("phi_{}", dst.as_u32()))
.map_err(|e| e.to_string())?;
vmap.insert(*dst, phi.as_basic_value());
phis_by_block
.entry(bid)
.or_default()
.push((*dst, phi, inputs.clone()));
}
// Predeclare all MIR functions as LLVM functions
let mut llvm_funcs: HashMap<String, FunctionValue> = HashMap::new();
for (name, f) in &mir_module.functions {
let ret_bt = match f.signature.return_type {
crate::mir::MirType::Void => codegen.context.i64_type().into(),
ref t => map_type(codegen.context, t)?,
};
let mut params_bt: Vec<BasicTypeEnum> = Vec::new();
for pt in &f.signature.params {
params_bt.push(map_type(codegen.context, pt)?);
}
let ll_fn_ty = match ret_bt {
BasicTypeEnum::IntType(t) => t.fn_type(&params_bt.iter().map(|t| (*t).into()).collect::<Vec<_>>(), false),
BasicTypeEnum::FloatType(t) => t.fn_type(&params_bt.iter().map(|t| (*t).into()).collect::<Vec<_>>(), false),
BasicTypeEnum::PointerType(t) => t.fn_type(&params_bt.iter().map(|t| (*t).into()).collect::<Vec<_>>(), false),
_ => return Err("Unsupported return basic type".to_string()),
};
let sym = format!("ny_f_{}", sanitize(name));
let lf = codegen.module.add_function(&sym, ll_fn_ty, None);
llvm_funcs.insert(name.clone(), lf);
}
// Lower in block order
for bid in func.block_ids() {
let bb = *bb_map.get(&bid).unwrap();
if codegen
.builder
.get_insert_block()
.map(|b| b != bb)
.unwrap_or(true)
{
codegen.builder.position_at_end(bb);
}
let block = func.blocks.get(&bid).unwrap();
for inst in &block.instructions {
match inst {
MirInstruction::NewBox { dst, box_type, args } => {
instructions::lower_newbox(&codegen, &mut vmap, *dst, box_type, args, &box_type_ids)?;
// Helper to build a map of ValueId -> const string for each function (to resolve call targets)
let build_const_str_map = |f: &crate::mir::function::MirFunction| -> HashMap<ValueId, String> {
let mut m = HashMap::new();
for bid in f.block_ids() {
if let Some(b) = f.blocks.get(&bid) {
for inst in &b.instructions {
if let MirInstruction::Const { dst, value: ConstValue::String(s) } = inst {
m.insert(*dst, s.clone());
}
}
MirInstruction::Const { dst, value } => {
let bval = match value {
if let Some(MirInstruction::Const { dst, value: ConstValue::String(s) }) = &b.terminator {
m.insert(*dst, s.clone());
}
}
}
m
};
// Lower all functions
for (name, func) in &mir_module.functions {
let llvm_func = *llvm_funcs.get(name).ok_or("predecl not found")?;
// Create basic blocks
let (mut bb_map, entry_bb) = instructions::create_basic_blocks(&codegen, llvm_func, func);
codegen.builder.position_at_end(entry_bb);
let mut vmap: HashMap<ValueId, BasicValueEnum> = HashMap::new();
let mut allocas: HashMap<ValueId, PointerValue> = HashMap::new();
let entry_builder = codegen.context.create_builder();
entry_builder.position_at_end(entry_bb);
let mut alloca_elem_types: HashMap<ValueId, BasicTypeEnum> = HashMap::new();
let mut phis_by_block: HashMap<
crate::mir::BasicBlockId,
Vec<(ValueId, PhiValue, Vec<(crate::mir::BasicBlockId, ValueId)>)>,
> = HashMap::new();
// Bind parameters
for (i, pid) in func.params.iter().enumerate() {
if let Some(av) = llvm_func.get_nth_param(i as u32) {
vmap.insert(*pid, av);
}
}
// Precreate phis
for bid in func.block_ids() {
let bb = *bb_map.get(&bid).ok_or("missing bb in map")?;
codegen.builder.position_at_end(bb);
let block = func.blocks.get(&bid).unwrap();
for inst in block
.instructions
.iter()
.take_while(|i| matches!(i, MirInstruction::Phi { .. }))
{
if let MirInstruction::Phi { dst, inputs } = inst {
let mut phi_ty: Option<BasicTypeEnum> = None;
if let Some(mt) = func.metadata.value_types.get(dst) {
phi_ty = Some(map_mirtype_to_basic(codegen.context, mt));
} else if let Some((_, iv)) = inputs.first() {
if let Some(mt) = func.metadata.value_types.get(iv) {
phi_ty = Some(map_mirtype_to_basic(codegen.context, mt));
}
}
let phi_ty = phi_ty.unwrap_or_else(|| codegen.context.i64_type().into());
let phi = codegen
.builder
.build_phi(phi_ty, &format!("phi_{}", dst.as_u32()))
.map_err(|e| e.to_string())?;
vmap.insert(*dst, phi.as_basic_value());
phis_by_block
.entry(bid)
.or_default()
.push((*dst, phi, inputs.clone()));
}
}
}
// Map of const strings for Call resolution
let const_strs = build_const_str_map(func);
// Lower body
for bid in func.block_ids() {
let bb = *bb_map.get(&bid).unwrap();
if codegen
.builder
.get_insert_block()
.map(|b| b != bb)
.unwrap_or(true)
{
codegen.builder.position_at_end(bb);
}
let block = func.blocks.get(&bid).unwrap();
for inst in &block.instructions {
match inst {
MirInstruction::NewBox { dst, box_type, args } => {
instructions::lower_newbox(&codegen, &mut vmap, *dst, box_type, args, &box_type_ids)?;
}
MirInstruction::Const { dst, value } => {
let bval = match value {
ConstValue::Integer(i) => {
codegen.context.i64_type().const_int(*i as u64, true).into()
}
@ -209,16 +239,42 @@ impl LLVMCompiler {
.into(),
ConstValue::Void => return Err("Const Void unsupported".to_string()),
};
vmap.insert(*dst, bval);
}
MirInstruction::BoxCall {
dst,
box_val,
method,
method_id,
args,
effects: _,
} => {
vmap.insert(*dst, bval);
}
MirInstruction::Call { dst, func: callee, args, .. } => {
// Resolve callee name from const string -> lookup predeclared function
let name_s = const_strs.get(callee).ok_or_else(|| format!("call: callee value {} not a const string", callee.as_u32()))?;
let sym = format!("ny_f_{}", sanitize(name_s));
let target = codegen
.module
.get_function(&sym)
.ok_or_else(|| format!("call: function symbol not found: {}", sym))?;
// Collect args
let mut avs: Vec<BasicValueEnum> = Vec::new();
for a in args {
let v = *vmap
.get(a)
.ok_or_else(|| format!("call arg missing: {}", a.as_u32()))?;
avs.push(v);
}
let call = codegen
.builder
.build_call(target, &avs.iter().map(|v| (*v).into()).collect::<Vec<_>>(), "call")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
if let Some(rv) = call.try_as_basic_value().left() {
vmap.insert(*d, rv);
}
}
}
MirInstruction::BoxCall {
dst,
box_val,
method,
method_id,
args,
effects: _,
} => {
// Delegate to refactored lowering and skip legacy body
instructions::lower_boxcall(
&codegen,
@ -521,7 +577,7 @@ impl LLVMCompiler {
}
}
MirInstruction::Compare { dst, op, lhs, rhs } => {
let out = instructions::lower_compare(&codegen, &vmap, op, lhs, rhs)?;
let out = instructions::lower_compare(&codegen, func, &vmap, op, lhs, rhs)?;
vmap.insert(*dst, out);
}
MirInstruction::Store { value, ptr } => {
@ -550,11 +606,59 @@ impl LLVMCompiler {
_ => {}
}
}
// Verify per-function
if !llvm_func.verify(true) {
return Err(format!("Function verification failed: {}", name));
}
}
// Verify and emit
if !llvm_func.verify(true) {
return Err("Function verification failed".to_string());
// Build entry wrapper ny_main -> call entry function
let i64t = codegen.context.i64_type();
let ny_main_ty = i64t.fn_type(&[], false);
let ny_main = codegen.module.add_function("ny_main", ny_main_ty, None);
let entry_bb = codegen.context.append_basic_block(ny_main, "entry");
codegen.builder.position_at_end(entry_bb);
let entry_sym = format!("ny_f_{}", sanitize(&entry_name));
let entry_fn = codegen
.module
.get_function(&entry_sym)
.ok_or_else(|| format!("entry function symbol not found: {}", entry_sym))?;
let call = codegen
.builder
.build_call(entry_fn, &[], "call_main")
.map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left();
// Normalize to i64 return
let ret_v = if let Some(v) = rv {
match v {
BasicValueEnum::IntValue(iv) => {
if iv.get_type().get_bit_width() == 64 {
iv
} else {
codegen
.builder
.build_int_z_extend(iv, i64t, "ret_zext")
.map_err(|e| e.to_string())?
}
}
BasicValueEnum::PointerValue(pv) => codegen
.builder
.build_ptr_to_int(pv, i64t, "ret_p2i")
.map_err(|e| e.to_string())?,
BasicValueEnum::FloatValue(fv) => codegen
.builder
.build_float_to_signed_int(fv, i64t, "ret_f2i")
.map_err(|e| e.to_string())?,
_ => i64t.const_zero(),
}
} else {
i64t.const_zero()
};
codegen.builder.build_return(Some(&ret_v)).map_err(|e| e.to_string())?;
// Verify and emit final object
if !ny_main.verify(true) {
return Err("ny_main verification failed".to_string());
}
// 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.