diff --git a/src/backend/llvm/compiler/aot.rs b/src/backend/llvm/compiler/aot.rs new file mode 100644 index 00000000..6ec1a3bc --- /dev/null +++ b/src/backend/llvm/compiler/aot.rs @@ -0,0 +1,15 @@ +use super::LLVMCompiler; +use crate::box_trait::NyashBox; +use crate::mir::function::MirModule; + +impl LLVMCompiler { + pub fn compile_and_execute( + &mut self, + mir_module: &MirModule, + temp_path: &str, + ) -> Result, String> { + let obj_path = format!("{}.o", temp_path); + self.compile_module(mir_module, &obj_path)?; + self.run_interpreter(mir_module) + } +} diff --git a/src/backend/llvm/compiler/real.rs b/src/backend/llvm/compiler/codegen.rs similarity index 94% rename from src/backend/llvm/compiler/real.rs rename to src/backend/llvm/compiler/codegen.rs index d6700335..6d0a95e8 100644 --- a/src/backend/llvm/compiler/real.rs +++ b/src/backend/llvm/compiler/codegen.rs @@ -1,6 +1,7 @@ +use super::helpers::{as_float, as_int, map_type}; use super::LLVMCompiler; use crate::backend::llvm::context::CodegenContext; -use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox}; +use crate::box_trait::{BoolBox, IntegerBox, StringBox}; use crate::boxes::{function_box::FunctionBox, math_box::FloatBox, null_box::NullBox}; use crate::mir::function::MirModule; use crate::mir::instruction::{BinaryOp, CompareOp, ConstValue, MirInstruction, UnaryOp}; @@ -49,32 +50,7 @@ impl LLVMCompiler { return Err("Main.main function not found in module".to_string()); }; - // Map MIR types to LLVM types - fn map_type<'ctx>( - ctx: &'ctx Context, - ty: &crate::mir::MirType, - ) -> Result, String> { - Ok(match ty { - crate::mir::MirType::Integer => ctx.i64_type().into(), - crate::mir::MirType::Float => ctx.f64_type().into(), - crate::mir::MirType::Bool => ctx.bool_type().into(), - crate::mir::MirType::String => ctx - .i8_type() - .ptr_type(inkwell::AddressSpace::from(0)) - .into(), - crate::mir::MirType::Void => return Err("Void has no value type".to_string()), - crate::mir::MirType::Box(_) => ctx - .i8_type() - .ptr_type(inkwell::AddressSpace::from(0)) - .into(), - crate::mir::MirType::Array(_) - | crate::mir::MirType::Future(_) - | crate::mir::MirType::Unknown => ctx - .i8_type() - .ptr_type(inkwell::AddressSpace::from(0)) - .into(), - }) - } + // 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(); @@ -117,20 +93,6 @@ impl LLVMCompiler { let mut vmap: HashMap = HashMap::new(); // Helper ops (centralized conversions and comparisons) - fn as_int<'ctx>(v: BasicValueEnum<'ctx>) -> Option> { - if let BasicValueEnum::IntValue(iv) = v { - Some(iv) - } else { - None - } - } - fn as_float<'ctx>(v: BasicValueEnum<'ctx>) -> Option> { - if let BasicValueEnum::FloatValue(fv) = v { - Some(fv) - } else { - None - } - } fn to_i64_any<'ctx>( ctx: &'ctx Context, builder: &inkwell::builder::Builder<'ctx>, @@ -2555,157 +2517,7 @@ impl LLVMCompiler { } } } - - pub fn compile_and_execute( - &mut self, - mir_module: &MirModule, - temp_path: &str, - ) -> Result, String> { - // 1) Emit object via real LLVM lowering to ensure IR generation remains healthy - let obj_path = format!("{}.o", temp_path); - self.compile_module(mir_module, &obj_path)?; - - // 2) Execute via a minimal MIR interpreter for parity (until full AOT linkage is wired) - // Supports: Const(Integer/Bool/String/Null), BinOp on Integer, Return(Some/None) - // This mirrors the non-LLVM mock path just enough for simple parity tests. - self.values.clear(); - let func = mir_module - .functions - .get("Main.main") - .or_else(|| mir_module.functions.get("main")) - .or_else(|| mir_module.functions.values().next()) - .ok_or_else(|| "Main.main function not found".to_string())?; - - use crate::mir::instruction::MirInstruction as I; - for inst in &func.get_block(func.entry_block).unwrap().instructions { - match inst { - I::Const { dst, value } => { - let v: Box = match value { - ConstValue::Integer(i) => Box::new(IntegerBox::new(*i)), - ConstValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(*f)), - ConstValue::String(s) => { - Box::new(crate::box_trait::StringBox::new(s.clone())) - } - ConstValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(*b)), - ConstValue::Null => Box::new(crate::boxes::null_box::NullBox::new()), - ConstValue::Void => Box::new(IntegerBox::new(0)), - }; - self.values.insert(*dst, v); - } - I::BinOp { dst, op, lhs, rhs } => { - let l = self - .values - .get(lhs) - .and_then(|b| b.as_any().downcast_ref::()) - .ok_or_else(|| format!("binop lhs %{} not integer", lhs.0))?; - let r = self - .values - .get(rhs) - .and_then(|b| b.as_any().downcast_ref::()) - .ok_or_else(|| format!("binop rhs %{} not integer", rhs.0))?; - let res = match op { - BinaryOp::Add => l.value + r.value, - BinaryOp::Sub => l.value - r.value, - BinaryOp::Mul => l.value * r.value, - BinaryOp::Div => { - if r.value == 0 { - return Err("division by zero".into()); - } - l.value / r.value - } - BinaryOp::Mod => l.value % r.value, - BinaryOp::BitAnd => l.value & r.value, - BinaryOp::BitOr => l.value | r.value, - BinaryOp::BitXor => l.value ^ r.value, - BinaryOp::Shl => l.value << r.value, - BinaryOp::Shr => l.value >> r.value, - BinaryOp::And => { - if (l.value != 0) && (r.value != 0) { - 1 - } else { - 0 - } - } - BinaryOp::Or => { - if (l.value != 0) || (r.value != 0) { - 1 - } else { - 0 - } - } - }; - self.values.insert(*dst, Box::new(IntegerBox::new(res))); - } - I::ExternCall { - dst, - iface_name, - method_name, - args, - .. - } => { - // Handle console methods via runtime handles (like AOT does) - if iface_name == "env.console" { - if let Some(arg0) = args.get(0) { - // Convert argument to handle and get string representation - use crate::jit::rt::handles; - if let Some(boxed_val) = self.values.get(arg0) { - // Convert NyashBox to handle - let arc: std::sync::Arc = boxed_val.clone_box().into(); - let handle = handles::to_handle(arc) as i64; - - // Debug output - eprintln!("DEBUG: handle={}", handle); - - // Get object from handle registry and print - if let Some(obj) = handles::get(handle as u64) { - let s = obj.to_string_box().value; - match method_name.as_str() { - "log" => println!("{}", s), - "warn" => eprintln!("[warn] {}", s), - "error" => eprintln!("[error] {}", s), - _ => {} - } - } else { - eprintln!("DEBUG: handle {} not found in registry", handle); - match method_name.as_str() { - "log" => println!("{}", handle), - "warn" => eprintln!("[warn] {}", handle), - "error" => eprintln!("[error] {}", handle), - _ => {} - } - } - } else { - // Fallback: try direct string conversion - match method_name.as_str() { - "log" => println!(""), - "warn" => eprintln!("[warn] "), - "error" => eprintln!("[error] "), - _ => {} - } - } - } - if let Some(d) = dst { - self.values.insert(*d, Box::new(IntegerBox::new(0))); - } - } - } - I::Return { value } => { - if let Some(v) = value { - return self - .values - .get(v) - .map(|b| b.clone_box()) - .ok_or_else(|| format!("return %{} missing", v.0)); - } - return Ok(Box::new(IntegerBox::new(0))); - } - _ => { /* ignore for now */ } - } - } - Ok(Box::new(IntegerBox::new(0))) - } } - #[cfg(test)] mod tests { use super::*; diff --git a/src/backend/llvm/compiler/helpers.rs b/src/backend/llvm/compiler/helpers.rs new file mode 100644 index 00000000..578a474b --- /dev/null +++ b/src/backend/llvm/compiler/helpers.rs @@ -0,0 +1,218 @@ +use inkwell::builder::Builder; +use inkwell::context::Context; +use inkwell::types::BasicTypeEnum; +use inkwell::values::{BasicValueEnum, FloatValue, IntValue, PointerValue}; +use inkwell::AddressSpace; + +use crate::mir::MirType; + +use crate::mir::CompareOp; +pub(crate) fn map_type<'ctx>( + ctx: &'ctx Context, + ty: &MirType, +) -> Result, String> { + Ok(match ty { + MirType::Integer => ctx.i64_type().into(), + MirType::Float => ctx.f64_type().into(), + MirType::Bool => ctx.bool_type().into(), + MirType::String => ctx + .i8_type() + .ptr_type(inkwell::AddressSpace::from(0)) + .into(), + MirType::Void => return Err("Void has no value type".to_string()), + MirType::Box(_) => ctx + .i8_type() + .ptr_type(inkwell::AddressSpace::from(0)) + .into(), + MirType::Array(_) | MirType::Future(_) | MirType::Unknown => ctx + .i8_type() + .ptr_type(inkwell::AddressSpace::from(0)) + .into(), + }) +} + +pub(crate) fn as_int<'ctx>(v: BasicValueEnum<'ctx>) -> Option> { + if let BasicValueEnum::IntValue(iv) = v { + Some(iv) + } else { + None + } +} + +pub(crate) fn as_float<'ctx>(v: BasicValueEnum<'ctx>) -> Option> { + if let BasicValueEnum::FloatValue(fv) = v { + Some(fv) + } else { + None + } +} + +pub(crate) fn to_i64_any<'ctx>( + ctx: &'ctx Context, + builder: &Builder<'ctx>, + v: BasicValueEnum<'ctx>, +) -> Result, 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()), + }) +} + +pub(crate) fn i64_to_ptr<'ctx>( + ctx: &'ctx Context, + builder: &Builder<'ctx>, + iv: IntValue<'ctx>, +) -> Result, 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()) +} + +pub(crate) fn classify_tag<'ctx>(v: BasicValueEnum<'ctx>) -> i64 { + match v { + BasicValueEnum::FloatValue(_) => 5, // float + BasicValueEnum::PointerValue(_) => 8, // handle/ptr + BasicValueEnum::IntValue(_) => 3, // integer/bool + _ => 0, + } +} + +pub(crate) fn to_bool<'ctx>( + ctx: &'ctx Context, + b: BasicValueEnum<'ctx>, + builder: &Builder<'ctx>, +) -> Result, String> { + if let Some(bb) = as_int(b) { + if bb.get_type().get_bit_width() == 1 { + Ok(bb) + } else { + Ok(builder + .build_int_compare( + inkwell::IntPredicate::NE, + bb, + bb.get_type().const_zero(), + "tobool", + ) + .map_err(|e| e.to_string())?) + } + } else if let Some(fv) = as_float(b) { + let zero = fv.get_type().const_float(0.0); + Ok(builder + .build_float_compare(inkwell::FloatPredicate::ONE, fv, zero, "toboolf") + .map_err(|e| e.to_string())?) + } else if let BasicValueEnum::PointerValue(pv) = b { + let i64t = ctx.i64_type(); + let p2i = builder + .build_ptr_to_int(pv, i64t, "p2i") + .map_err(|e| e.to_string())?; + Ok(builder + .build_int_compare(inkwell::IntPredicate::NE, p2i, i64t.const_zero(), "toboolp") + .map_err(|e| e.to_string())?) + } else { + Err("Unsupported value for boolean conversion".to_string()) + } +} + +pub(crate) fn cmp_eq_ne_any<'ctx>( + ctx: &'ctx Context, + builder: &Builder<'ctx>, + op: &CompareOp, + lv: BasicValueEnum<'ctx>, + rv: BasicValueEnum<'ctx>, +) -> Result, 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()), + } +} + +pub(crate) fn map_mirtype_to_basic<'ctx>(ctx: &'ctx Context, ty: &MirType) -> BasicTypeEnum<'ctx> { + match ty { + MirType::Integer => ctx.i64_type().into(), + MirType::Float => ctx.f64_type().into(), + MirType::Bool => ctx.bool_type().into(), + MirType::String => ctx.i8_type().ptr_type(AddressSpace::from(0)).into(), + MirType::Box(_) | MirType::Array(_) | MirType::Future(_) | MirType::Unknown => { + ctx.i8_type().ptr_type(AddressSpace::from(0)).into() + } + MirType::Void => ctx.i64_type().into(), + } +} diff --git a/src/backend/llvm/compiler/interpreter.rs b/src/backend/llvm/compiler/interpreter.rs new file mode 100644 index 00000000..c50a10f1 --- /dev/null +++ b/src/backend/llvm/compiler/interpreter.rs @@ -0,0 +1,138 @@ +use super::LLVMCompiler; +use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox}; +use crate::boxes::{math_box::FloatBox, null_box::NullBox}; +use crate::mir::function::MirModule; +use crate::mir::instruction::{BinaryOp, ConstValue, MirInstruction as I}; + +impl LLVMCompiler { + pub(crate) fn run_interpreter( + &mut self, + mir_module: &MirModule, + ) -> Result, String> { + self.values.clear(); + let func = mir_module + .functions + .get("Main.main") + .or_else(|| mir_module.functions.get("main")) + .or_else(|| mir_module.functions.values().next()) + .ok_or_else(|| "Main.main function not found".to_string())?; + + for inst in &func.get_block(func.entry_block).unwrap().instructions { + match inst { + I::Const { dst, value } => { + let v: Box = match value { + ConstValue::Integer(i) => Box::new(IntegerBox::new(*i)), + ConstValue::Float(f) => Box::new(FloatBox::new(*f)), + ConstValue::String(s) => Box::new(StringBox::new(s.clone())), + ConstValue::Bool(b) => Box::new(BoolBox::new(*b)), + ConstValue::Null => Box::new(NullBox::new()), + ConstValue::Void => Box::new(IntegerBox::new(0)), + }; + self.values.insert(*dst, v); + } + I::BinOp { dst, op, lhs, rhs } => { + let l = self + .values + .get(lhs) + .and_then(|b| b.as_any().downcast_ref::()) + .ok_or_else(|| format!("binop lhs %{} not integer", lhs.0))?; + let r = self + .values + .get(rhs) + .and_then(|b| b.as_any().downcast_ref::()) + .ok_or_else(|| format!("binop rhs %{} not integer", rhs.0))?; + let res = match op { + BinaryOp::Add => l.value + r.value, + BinaryOp::Sub => l.value - r.value, + BinaryOp::Mul => l.value * r.value, + BinaryOp::Div => { + if r.value == 0 { + return Err("division by zero".into()); + } + l.value / r.value + } + BinaryOp::Mod => l.value % r.value, + BinaryOp::BitAnd => l.value & r.value, + BinaryOp::BitOr => l.value | r.value, + BinaryOp::BitXor => l.value ^ r.value, + BinaryOp::Shl => l.value << r.value, + BinaryOp::Shr => l.value >> r.value, + BinaryOp::And => { + if (l.value != 0) && (r.value != 0) { + 1 + } else { + 0 + } + } + BinaryOp::Or => { + if (l.value != 0) || (r.value != 0) { + 1 + } else { + 0 + } + } + }; + self.values.insert(*dst, Box::new(IntegerBox::new(res))); + } + I::ExternCall { + dst, + iface_name, + method_name, + args, + .. + } => { + if iface_name == "env.console" { + if let Some(arg0) = args.get(0) { + use crate::jit::rt::handles; + if let Some(boxed_val) = self.values.get(arg0) { + let arc: std::sync::Arc = + boxed_val.clone_box().into(); + let handle = handles::to_handle(arc) as i64; + eprintln!("DEBUG: handle={}", handle); + if let Some(obj) = handles::get(handle as u64) { + let s = obj.to_string_box().value; + match method_name.as_str() { + "log" => println!("{}", s), + "warn" => eprintln!("[warn] {}", s), + "error" => eprintln!("[error] {}", s), + _ => {} + } + } else { + eprintln!("DEBUG: handle {} not found in registry", handle); + match method_name.as_str() { + "log" => println!("{}", handle), + "warn" => eprintln!("[warn] {}", handle), + "error" => eprintln!("[error] {}", handle), + _ => {} + } + } + } else { + match method_name.as_str() { + "log" => println!(""), + "warn" => eprintln!("[warn] "), + "error" => eprintln!("[error] "), + _ => {} + } + } + } + if let Some(d) = dst { + self.values.insert(*d, Box::new(IntegerBox::new(0))); + } + } + } + I::Return { value } => { + if let Some(v) = value { + return self + .values + .get(v) + .map(|b| b.clone_box()) + .ok_or_else(|| format!("return %{} missing", v.0)); + } + return Ok(Box::new(IntegerBox::new(0))); + } + _ => {} + } + } + Ok(Box::new(IntegerBox::new(0))) + } +} diff --git a/src/backend/llvm/compiler/mod.rs b/src/backend/llvm/compiler/mod.rs index 2e4693db..1add9ea8 100644 --- a/src/backend/llvm/compiler/mod.rs +++ b/src/backend/llvm/compiler/mod.rs @@ -12,9 +12,15 @@ mod mock; pub use mock::*; #[cfg(feature = "llvm")] -mod real; +mod aot; #[cfg(feature = "llvm")] -pub use real::*; +mod codegen; +#[cfg(feature = "llvm")] +mod helpers; +#[cfg(feature = "llvm")] +mod interpreter; +#[cfg(feature = "llvm")] +pub use aot::*; #[cfg(test)] mod tests {