diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index dc75bfd2..00c58265 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,11 +1,47 @@ -# Current Task (2025-09-11) +# Current Task (2025-09-11) — Phase 15 LLVM‑only -> Phase 15 LLVM‑only notes (authoritative) -> -> - LLVM AOT is the stable/authoritative path. VM/Cranelift JIT/AOT and the interpreter are not MIR14‑ready in this phase. -> - Fallback logic must be minimal. Prefer fixing MIR type annotations over adding broad implicit conversions. -> - ExternCall (console/debug) selects C‑string vs handle variants by the argument IR type. -> - StringBox: NewBox keeps i8* fast path (no birth); print/log choose automatically based on IR type. +Summary +- LLVM is the authoritative path; VM/Cranelift/Interpreter are not MIR14‑ready. +- Keep fallbacks minimal; fix MIR annotations first. +- ExternCall(console/debug) auto‑selects ptr/handle by IR type. +- StringBox NewBox i8* fast path; print/log choose automatically. + +Done (today) +- BoxCall legacy block removed in LLVM codegen; delegation only. +- BinOp concat safety: minimal fallback for i8*+i64/i64+i8* when both sides are annotated String/Box → from_i8_string + concat_hh; otherwise keep concat_ss/si/is. +- String fast‑path: length/len lowered to nyash.string.len_h (handle), with ptr→handle bridge when needed. +- Map core‑first: NewBox(MapBox) routes via nyash.env.box.new("MapBox") → NyRT特例でコアMapを生成。LLVM BoxCall(Map.size/get/set/has) は NyRT の nyash.map.* を呼ぶ。 +- Plugin強制スイッチ: NYASH_LLVM_FORCE_PLUGIN_MAP=1 で MapBox のプラグイン経路を明示切替(デフォルトはコア)。 +- Docs: ARCHITECTURE/LOWERING_LLVM/EXTERNCALL/PLUGIN_ABI を追加・整備。 +- Smokes: plugin‑ret green, map smoke green(core‑first)。 + +Next — Refactor instructions.rs (no behavior change) +- Goal: Make `src/backend/llvm/compiler/codegen/instructions.rs` maintainable (now >1400 lines). +- Plan (split into focused submodules under `codegen/`): + - `instructions/externcall.rs` — env.console/debug, readline + - `instructions/boxcall.rs` — generic BoxCall lowering, by‑id/tagged paths glue + - `instructions/newbox.rs` — NewBox + birth/env.box.new bridge + - `instructions/strings.rs` — concat fast‑paths, length/len (later: substring/indexOf/replace/trim/toUpper/toLower) + - `instructions/arrays.rs` — get/set/push/length + - `instructions/maps.rs` — size/get/set/has (core NyRT shims) + - `instructions/arith.rs` — UnaryOp/BinOp/Compare (includes concat fallback) + - `instructions/flow.rs` — Return/Branch/Jump/PHI finalize + - Keep `types.rs` for helpers; keep `create_basic_blocks`/`precreate_phis` in a small `blocks.rs` if needed. +- Constraints: + - 0‑diff behavior for existing smokes; no fallback拡大。 + - No new deps; keep function signatures stable; pub(super) only. + - Update mod wiring in `codegen/mod.rs` accordingly. + +After refactor (follow‑ups) +- Expand String AOT fast‑paths: substring/indexOf/replace/trim/toUpper/toLower. +- Remove temporary env.box.new(MapBox) special‑case by moving MapBox生生成 into a dedicated NyRT entry. +- Tighten MIR annotations for Map.get returns in common patterns. +- Add targeted smokes after each implementation step(実装後にテスト)。 + +Risks/Guards +- Avoid broad implicit conversions; keep concat fallback gated by annotations only. +- Ensure nyash.map.* との一致(core Map); plugin経路は環境変数で明示切替。 +- Keep LLVM smokes green continuously; do not gate on VM/JIT. ## 🎉 LLVMプラグイン戻り値表示問題修正進行中(2025-09-10) diff --git a/src/backend/llvm/compiler/codegen/instructions.rs b/src/backend/llvm/compiler/codegen/instructions.rs deleted file mode 100644 index 825d27b0..00000000 --- a/src/backend/llvm/compiler/codegen/instructions.rs +++ /dev/null @@ -1,1463 +0,0 @@ -use inkwell::basic_block::BasicBlock; -use inkwell::values::{BasicValueEnum, FunctionValue, PhiValue}; -use std::collections::HashMap; - -use crate::mir::{function::MirFunction, BasicBlockId}; - -use crate::backend::llvm::context::CodegenContext; -use crate::mir::instruction::{ConstValue, MirInstruction}; -use crate::mir::{CompareOp, ValueId}; - -use super::types::{to_bool, to_i64_any}; -use inkwell::AddressSpace; - -// Small, safe extraction: create LLVM basic blocks for a MIR function and -// return the block map together with the entry block. -pub(super) fn create_basic_blocks<'ctx>( - codegen: &CodegenContext<'ctx>, - llvm_func: FunctionValue<'ctx>, - func: &MirFunction, -) -> (HashMap>, BasicBlock<'ctx>) { - let mut bb_map: HashMap = HashMap::new(); - let entry_first = func.entry_block; - let entry_bb = codegen - .context - .append_basic_block(llvm_func, &format!("bb{}", entry_first.as_u32())); - bb_map.insert(entry_first, entry_bb); - for bid in func.block_ids() { - if bid == entry_first { - continue; - } - let name = format!("bb{}", bid.as_u32()); - let bb = codegen.context.append_basic_block(llvm_func, &name); - bb_map.insert(bid, bb); - } - (bb_map, entry_bb) -} - -// Pre-create PHI nodes for all blocks; also inserts placeholder values into vmap. -pub(super) fn precreate_phis<'ctx>( - codegen: &CodegenContext<'ctx>, - func: &MirFunction, - bb_map: &HashMap>, - vmap: &mut HashMap>, -) -> Result< - HashMap< - BasicBlockId, - Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>, - >, - String, -> { - use crate::mir::instruction::MirInstruction; - use super::types::map_mirtype_to_basic; - let mut phis_by_block: HashMap< - BasicBlockId, - Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>, - > = HashMap::new(); - 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 = 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())); - } - } - } - Ok(phis_by_block) -} - -// Lower Store: handle allocas with element type tracking and integer width adjust -pub(super) fn lower_store<'ctx>( - codegen: &CodegenContext<'ctx>, - vmap: &HashMap>, - allocas: &mut HashMap>, - alloca_elem_types: &mut HashMap>, - value: &ValueId, - ptr: &ValueId, -) -> Result<(), String> { - use inkwell::types::BasicTypeEnum; - let val = *vmap.get(value).ok_or("store value missing")?; - let elem_ty = match val { - BasicValueEnum::IntValue(iv) => BasicTypeEnum::IntType(iv.get_type()), - BasicValueEnum::FloatValue(fv) => BasicTypeEnum::FloatType(fv.get_type()), - BasicValueEnum::PointerValue(pv) => BasicTypeEnum::PointerType(pv.get_type()), - _ => return Err("unsupported store value type".to_string()), - }; - if let Some(existing) = allocas.get(ptr).copied() { - let existing_elem = *alloca_elem_types.get(ptr).ok_or("alloca elem type missing")?; - if existing_elem != elem_ty { - match (val, existing_elem) { - (BasicValueEnum::IntValue(iv), BasicTypeEnum::IntType(t)) => { - let bw_src = iv.get_type().get_bit_width(); - let bw_dst = t.get_bit_width(); - if bw_src < bw_dst { - let adj = codegen.builder.build_int_z_extend(iv, t, "zext").map_err(|e| e.to_string())?; - codegen.builder.build_store(existing, adj).map_err(|e| e.to_string())?; - } else if bw_src > bw_dst { - let adj = codegen.builder.build_int_truncate(iv, t, "trunc").map_err(|e| e.to_string())?; - codegen.builder.build_store(existing, adj).map_err(|e| e.to_string())?; - } else { - codegen.builder.build_store(existing, iv).map_err(|e| e.to_string())?; - } - } - (BasicValueEnum::PointerValue(pv), BasicTypeEnum::PointerType(pt)) => { - let adj = codegen.builder.build_pointer_cast(pv, pt, "pcast").map_err(|e| e.to_string())?; - codegen.builder.build_store(existing, adj).map_err(|e| e.to_string())?; - } - (BasicValueEnum::FloatValue(fv), BasicTypeEnum::FloatType(ft)) => { - // Only f64 currently expected - if fv.get_type() != ft { return Err("float width mismatch in store".to_string()); } - codegen.builder.build_store(existing, fv).map_err(|e| e.to_string())?; - } - _ => return Err("store type mismatch".to_string()), - } - } else { - codegen.builder.build_store(existing, val).map_err(|e| e.to_string())?; - } - } else { - let slot = codegen - .builder - .build_alloca(elem_ty, &format!("slot_{}", ptr.as_u32())) - .map_err(|e| e.to_string())?; - codegen.builder.build_store(slot, val).map_err(|e| e.to_string())?; - allocas.insert(*ptr, slot); - alloca_elem_types.insert(*ptr, elem_ty); - } - Ok(()) -} - -pub(super) fn lower_load<'ctx>( - codegen: &CodegenContext<'ctx>, - vmap: &mut HashMap>, - allocas: &mut HashMap>, - alloca_elem_types: &mut HashMap>, - dst: &ValueId, - ptr: &ValueId, -) -> Result<(), String> { - use inkwell::types::BasicTypeEnum; - let (slot, elem_ty) = if let Some(s) = allocas.get(ptr).copied() { - let et = *alloca_elem_types.get(ptr).ok_or("alloca elem type missing")?; - (s, et) - } else { - // Default new slot as i64 for uninitialized loads - let i64t = codegen.context.i64_type(); - let slot = codegen - .builder - .build_alloca(i64t, &format!("slot_{}", ptr.as_u32())) - .map_err(|e| e.to_string())?; - allocas.insert(*ptr, slot); - alloca_elem_types.insert(*ptr, i64t.into()); - (slot, i64t.into()) - }; - let lv = codegen - .builder - .build_load(elem_ty, slot, &format!("load_{}", dst.as_u32())) - .map_err(|e| e.to_string())?; - vmap.insert(*dst, lv); - Ok(()) -} - -// Const lowering: produce a BasicValue and store into vmap -pub(super) fn lower_const<'ctx>( - codegen: &CodegenContext<'ctx>, - vmap: &mut HashMap>, - dst: ValueId, - value: &ConstValue, -) -> Result<(), String> { - let bval = match value { - ConstValue::Integer(i) => codegen - .context - .i64_type() - .const_int(*i as u64, true) - .into(), - ConstValue::Float(f) => codegen.context.f64_type().const_float(*f).into(), - ConstValue::Bool(b) => codegen - .context - .bool_type() - .const_int(*b as u64, false) - .into(), - ConstValue::String(s) => { - let gv = codegen - .builder - .build_global_string_ptr(s, "str") - .map_err(|e| e.to_string())?; - let len = codegen - .context - .i32_type() - .const_int(s.len() as u64, false); - // declare i8* @nyash_string_new(i8*, i32) - let rt = codegen.context.ptr_type(inkwell::AddressSpace::from(0)); - let fn_ty = rt.fn_type( - &[ - codegen - .context - .ptr_type(inkwell::AddressSpace::from(0)) - .into(), - codegen.context.i32_type().into(), - ], - false, - ); - let callee = codegen - .module - .get_function("nyash_string_new") - .unwrap_or_else(|| codegen.module.add_function("nyash_string_new", fn_ty, None)); - let call = codegen - .builder - .build_call(callee, &[gv.as_pointer_value().into(), len.into()], "strnew") - .map_err(|e| e.to_string())?; - call.try_as_basic_value() - .left() - .ok_or("nyash_string_new returned void".to_string())? - } - ConstValue::Null => codegen - .context - .ptr_type(inkwell::AddressSpace::from(0)) - .const_null() - .into(), - ConstValue::Void => return Err("Const Void unsupported".to_string()), - }; - vmap.insert(dst, bval); - Ok(()) -} - -// Compare lowering: return the resulting BasicValueEnum (i1) -pub(super) fn lower_compare<'ctx>( - codegen: &CodegenContext<'ctx>, - vmap: &HashMap>, - op: &CompareOp, - lhs: &ValueId, - rhs: &ValueId, -) -> Result, 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 out = if let (Some(li), Some(ri)) = (as_int(lv), as_int(rv)) { - use CompareOp as C; - let pred = match op { - C::Eq => inkwell::IntPredicate::EQ, - C::Ne => inkwell::IntPredicate::NE, - C::Lt => inkwell::IntPredicate::SLT, - C::Le => inkwell::IntPredicate::SLE, - C::Gt => inkwell::IntPredicate::SGT, - C::Ge => inkwell::IntPredicate::SGE, - }; - codegen - .builder - .build_int_compare(pred, li, ri, "icmp") - .map_err(|e| e.to_string())? - .into() - } else if let (Some(lf), Some(rf)) = (as_float(lv), as_float(rv)) { - use 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 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 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 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()); - }; - Ok(out) -} - -pub(super) fn emit_return<'ctx>( - codegen: &CodegenContext<'ctx>, - func: &MirFunction, - vmap: &HashMap>, - value: &Option, -) -> Result<(), String> { - match (&func.signature.return_type, value) { - (crate::mir::MirType::Void, _) => { - codegen.builder.build_return(None).unwrap(); - Ok(()) - } - (_t, Some(vid)) => { - let v = *vmap.get(vid).ok_or("ret value missing")?; - codegen - .builder - .build_return(Some(&v)) - .map_err(|e| e.to_string())?; - Ok(()) - } - (_t, None) => Err("non-void function missing return value".to_string()), - } -} - -pub(super) fn emit_jump<'ctx>( - codegen: &CodegenContext<'ctx>, - bid: BasicBlockId, - target: &BasicBlockId, - bb_map: &HashMap>, - phis_by_block: &HashMap< - BasicBlockId, - Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>, - >, - vmap: &HashMap>, -) -> Result<(), String> { - if let Some(list) = phis_by_block.get(target) { - for (_dst, phi, inputs) in list { - if let Some((_, in_vid)) = inputs.iter().find(|(pred, _)| pred == &bid) { - let val = *vmap.get(in_vid).ok_or("phi incoming value missing")?; - let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?; - match val { - BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]), - BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]), - BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]), - _ => return Err("unsupported phi incoming value".to_string()), - } - } - } - } - let tbb = *bb_map.get(target).ok_or("target bb missing")?; - codegen - .builder - .build_unconditional_branch(tbb) - .map_err(|e| e.to_string())?; - Ok(()) -} - -pub(super) fn emit_branch<'ctx>( - codegen: &CodegenContext<'ctx>, - bid: BasicBlockId, - condition: &ValueId, - then_bb: &BasicBlockId, - else_bb: &BasicBlockId, - bb_map: &HashMap>, - phis_by_block: &HashMap< - BasicBlockId, - Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>, - >, - vmap: &HashMap>, -) -> Result<(), String> { - let cond_v = *vmap.get(condition).ok_or("cond missing")?; - let b = to_bool(codegen.context, cond_v, &codegen.builder)?; - // then - if let Some(list) = phis_by_block.get(then_bb) { - for (_dst, phi, inputs) in list { - if let Some((_, in_vid)) = inputs.iter().find(|(pred, _)| pred == &bid) { - let val = *vmap - .get(in_vid) - .ok_or("phi incoming (then) value missing")?; - let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?; - match val { - BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]), - BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]), - BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]), - _ => return Err("unsupported phi incoming value (then)".to_string()), - } - } - } - } - // else - if let Some(list) = phis_by_block.get(else_bb) { - for (_dst, phi, inputs) in list { - if let Some((_, in_vid)) = inputs.iter().find(|(pred, _)| pred == &bid) { - let val = *vmap - .get(in_vid) - .ok_or("phi incoming (else) value missing")?; - let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?; - match val { - BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]), - BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]), - BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]), - _ => return Err("unsupported phi incoming value (else)".to_string()), - } - } - } - } - let tbb = *bb_map.get(then_bb).ok_or("then bb missing")?; - let ebb = *bb_map.get(else_bb).ok_or("else bb missing")?; - codegen - .builder - .build_conditional_branch(b, tbb, ebb) - .map_err(|e| e.to_string())?; - Ok(()) -} - -// Full ExternCall lowering (console/debug, future.spawn_instance, env.local, env.box.new) -pub(super) fn lower_externcall<'ctx>( - codegen: &CodegenContext<'ctx>, - func: &MirFunction, - vmap: &mut HashMap>, - dst: &Option, - iface_name: &str, - method_name: &str, - args: &[ValueId], -) -> Result<(), String> { - use inkwell::values::PointerValue; - use super::super::helpers::{as_float, as_int}; - use inkwell::values::BasicValueEnum as BVE; - - 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 a0_v = *vmap.get(&args[0]).ok_or("recv missing")?; - let a0 = to_i64_any(codegen.context, &codegen.builder, a0_v)?; - let a1_v = *vmap.get(&args[1]).ok_or("method_name missing")?; - let a1 = match a1_v { - BVE::PointerValue(pv) => codegen - .builder - .build_ptr_to_int(pv, i64t, "mname_p2i") - .map_err(|e| e.to_string())?, - _ => to_i64_any(codegen.context, &codegen.builder, a1_v)?, - }; - let a2 = if args.len() >= 3 { - let v = *vmap.get(&args[2]).ok_or("arg2 missing")?; - to_i64_any(codegen.context, &codegen.builder, v)? - } else { - i64t.const_zero() - }; - let argc_total = i64t.const_int(args.len().saturating_sub(1) as u64, false); - let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into()], false); - let callee = codegen - .module - .get_function("nyash.future.spawn_instance3_i64") - .unwrap_or_else(|| { - codegen - .module - .add_function("nyash.future.spawn_instance3_i64", fnty, None) - }); - let call = codegen - .builder - .build_call(callee, &[a0.into(), a1.into(), a2.into(), argc_total.into()], "spawn_i3") - .map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call - .try_as_basic_value() - .left() - .ok_or("spawn_instance3 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 iv = if let BVE::IntValue(iv) = rv { - iv - } else { - return Err("spawn ret expected i64".to_string()); - }; - let pty = codegen.context.ptr_type(AddressSpace::from(0)); - let ptr = codegen - .builder - .build_int_to_ptr(iv, 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); - } - } - return Ok(()); - } - - if iface_name == "env.local" && method_name == "get" { - if let Some(d) = dst { - let av = *vmap.get(&args[0]).ok_or("extern arg missing")?; - vmap.insert(*d, av); - } - return Ok(()); - } - - if iface_name == "env.local" && method_name == "set" { - // no-op in AOT - return Ok(()); - } - - if iface_name == "env.box" && method_name == "new" { - if args.len() < 1 { - return Err("env.box.new expects at least 1 arg (type name)".to_string()); - } - let i8p = codegen.context.ptr_type(AddressSpace::from(0)); - let tyv = *vmap.get(&args[0]).ok_or("type name arg missing")?; - let ty_ptr = match tyv { BVE::PointerValue(p) => p, _ => return Err("env.box.new type must be i8* string".to_string()) }; - let i64t = codegen.context.i64_type(); - let out_ptr: PointerValue = if args.len() == 1 { - let fnty = i64t.fn_type(&[i8p.into()], false); - let callee = codegen - .module - .get_function("nyash.env.box.new") - .unwrap_or_else(|| codegen.module.add_function("nyash.env.box.new", fnty, None)); - let call = codegen - .builder - .build_call(callee, &[ty_ptr.into()], "env_box_new") - .map_err(|e| e.to_string())?; - let rv = call - .try_as_basic_value() - .left() - .ok_or("env.box.new returned void".to_string())?; - let i64v = if let BVE::IntValue(iv) = rv { iv } else { return Err("env.box.new ret expected i64".to_string()); }; - codegen - .builder - .build_int_to_ptr(i64v, i8p, "box_handle_to_ptr") - .map_err(|e| e.to_string())? - } else { - if args.len() - 1 > 4 { - return Err("env.box.new supports up to 4 args in AOT shim".to_string()); - } - let fnty = i64t.fn_type(&[i8p.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into()], false); - let callee = codegen - .module - .get_function("nyash.env.box.new_i64x") - .unwrap_or_else(|| codegen.module.add_function("nyash.env.box.new_i64x", fnty, None)); - let argc_val = i64t.const_int((args.len() - 1) as u64, false); - 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()); }; - 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 - )) -} - -// NewBox lowering (subset consistent with existing code) -pub(super) fn lower_newbox<'ctx>( - codegen: &CodegenContext<'ctx>, - vmap: &mut HashMap>, - dst: ValueId, - box_type: &str, - args: &[ValueId], - box_type_ids: &HashMap, -) -> Result<(), String> { - use inkwell::values::BasicValueEnum as BVE; - match (box_type, args.len()) { - ("StringBox", 1) => { - // Keep as i8* string pointer (AOT string fast-path) - let av = *vmap.get(&args[0]).ok_or("StringBox arg missing")?; - vmap.insert(dst, av); - Ok(()) - } - (_, n) if n == 1 || n == 2 => { - let type_id = *box_type_ids.get(box_type).unwrap_or(&0); - let i64t = codegen.context.i64_type(); - let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into()], false); - let callee = codegen - .module - .get_function("nyash.box.birth_i64") - .unwrap_or_else(|| codegen.module.add_function("nyash.box.birth_i64", fnty, None)); - let argc = i64t.const_int(args.len() as u64, false); - let mut a1 = i64t.const_zero(); - let mut a2 = i64t.const_zero(); - if args.len() >= 1 { - let v = *vmap.get(&args[0]).ok_or("newbox arg[0] missing")?; - a1 = match v { - BVE::IntValue(iv) => iv, - BVE::PointerValue(pv) => codegen - .builder - .build_ptr_to_int(pv, i64t, "arg0_p2i") - .map_err(|e| e.to_string())?, - _ => { - return Err( - "newbox arg[0]: unsupported type (expect int or handle ptr)" - .to_string(), - ) - } - }; - } - if args.len() >= 2 { - let v = *vmap.get(&args[1]).ok_or("newbox arg[1] missing")?; - a2 = match v { - BVE::IntValue(iv) => iv, - BVE::PointerValue(pv) => codegen - .builder - .build_ptr_to_int(pv, i64t, "arg1_p2i") - .map_err(|e| e.to_string())?, - _ => { - return Err( - "newbox arg[1]: unsupported type (expect int or handle ptr)" - .to_string(), - ) - } - }; - } - let tid = i64t.const_int(type_id as u64, true); - let call = codegen - .builder - .build_call(callee, &[tid.into(), argc.into(), a1.into(), a2.into()], "birth_i64") - .map_err(|e| e.to_string())?; - let h = call - .try_as_basic_value() - .left() - .ok_or("birth_i64 returned void".to_string())? - .into_int_value(); - let pty = codegen.context.ptr_type(AddressSpace::from(0)); - let ptr = codegen - .builder - .build_int_to_ptr(h, pty, "handle_to_ptr") - .map_err(|e| e.to_string())?; - vmap.insert(dst, ptr.into()); - Ok(()) - } - _ => { - if !args.is_empty() { - return Err("NewBox with >2 args not yet supported in LLVM lowering".to_string()); - } - let type_id = *box_type_ids.get(box_type).unwrap_or(&0); - let i64t = codegen.context.i64_type(); - let fn_ty = i64t.fn_type(&[i64t.into()], false); - let callee = codegen - .module - .get_function("nyash.box.birth_h") - .unwrap_or_else(|| codegen.module.add_function("nyash.box.birth_h", fn_ty, None)); - let tid = i64t.const_int(type_id as u64, true); - let call = codegen - .builder - .build_call(callee, &[tid.into()], "birth") - .map_err(|e| e.to_string())?; - let h_i64 = call - .try_as_basic_value() - .left() - .ok_or("birth_h returned void".to_string())? - .into_int_value(); - let pty = codegen.context.ptr_type(AddressSpace::from(0)); - let ptr = codegen - .builder - .build_int_to_ptr(h_i64, pty, "handle_to_ptr") - .map_err(|e| e.to_string())?; - vmap.insert(dst, ptr.into()); - Ok(()) - } - } -} - -// BoxCall lowering (large): mirrors existing logic; kept in one function for now -pub(super) fn lower_boxcall<'ctx>( - codegen: &CodegenContext<'ctx>, - func: &MirFunction, - vmap: &mut HashMap>, - dst: &Option, - box_val: &ValueId, - method: &str, - method_id: &Option, - args: &[ValueId], - box_type_ids: &HashMap, - entry_builder: &inkwell::builder::Builder<'ctx>, -) -> Result<(), String> { - use super::super::helpers::{as_float, as_int}; - use super::types::classify_tag; - use inkwell::values::BasicValueEnum as BVE; - let i64t = codegen.context.i64_type(); - let recv_v = *vmap.get(box_val).ok_or("box receiver missing")?; - let recv_p = match recv_v { - BVE::PointerValue(pv) => pv, - BVE::IntValue(iv) => { - let pty = codegen.context.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 - let type_id: i64 = if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) { - *box_type_ids.get(bname).unwrap_or(&0) - } else if let Some(crate::mir::MirType::String) = func.metadata.value_types.get(box_val) { - *box_type_ids.get("StringBox").unwrap_or(&0) - } else { - 0 - }; - - // String concat fast-path (avoid plugin path for builtin StringBox) - if method == "concat" { - // Recognize receiver typed as String or StringBox - let is_string_recv = match func.metadata.value_types.get(box_val) { - Some(crate::mir::MirType::String) => true, - Some(crate::mir::MirType::Box(b)) if b == "StringBox" => true, - _ => false, - }; - if is_string_recv { - if args.len() != 1 { return Err("String.concat expects 1 arg".to_string()); } - // Prefer pointer-based concat to keep AOT string fast-path - let i8p = codegen.context.ptr_type(AddressSpace::from(0)); - let rhs_v = *vmap.get(&args[0]).ok_or("concat arg missing")?; - match (recv_v, rhs_v) { - (BVE::PointerValue(lp), BVE::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_call").map_err(|e| e.to_string())?; - if let Some(d) = dst { let rv = call.try_as_basic_value().left().ok_or("concat_ss returned void".to_string())?; vmap.insert(*d, rv); } - return Ok(()); - } - (BVE::PointerValue(lp), BVE::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_call").map_err(|e| e.to_string())?; - if let Some(d) = dst { let rv = call.try_as_basic_value().left().ok_or("concat_si returned void".to_string())?; vmap.insert(*d, rv); } - return Ok(()); - } - (BVE::IntValue(li), BVE::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_call").map_err(|e| e.to_string())?; - if let Some(d) = dst { let rv = call.try_as_basic_value().left().ok_or("concat_is returned void".to_string())?; vmap.insert(*d, rv); } - return Ok(()); - } - _ => { /* fall through to generic path below */ } - } - } - } - - // String length fast-path: length/len - if method == "length" || method == "len" { - // Only when receiver is String/StringBox by annotation - let is_string_recv = match func.metadata.value_types.get(box_val) { - Some(crate::mir::MirType::String) => true, - Some(crate::mir::MirType::Box(b)) if b == "StringBox" => true, - _ => false, - }; - if is_string_recv { - let i64t = codegen.context.i64_type(); - // Ensure we have a handle: convert i8* receiver to handle when needed - let recv_h = match recv_v { - BVE::IntValue(h) => h, - BVE::PointerValue(p) => { - let fnty = i64t.fn_type(&[codegen.context.ptr_type(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, &[p.into()], "str_ptr_to_handle") - .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(iv) = rv { iv } else { return Err("from_i8_string ret expected i64".to_string()); } - } - _ => return Err("String.length receiver type unsupported".to_string()), - }; - // call i64 @nyash.string.len_h(i64) - let fnty = i64t.fn_type(&[i64t.into()], false); - let callee = codegen - .module - .get_function("nyash.string.len_h") - .unwrap_or_else(|| codegen.module.add_function("nyash.string.len_h", fnty, None)); - let call = codegen - .builder - .build_call(callee, &[recv_h.into()], "strlen_h") - .map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call - .try_as_basic_value() - .left() - .ok_or("len_h returned void".to_string())?; - vmap.insert(*d, rv); - } - return Ok(()); - } - } - - // Array fast-paths - if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) { - if bname == "ArrayBox" && (method == "get" || method == "set" || method == "push" || method == "length") { - match method { - "get" => { - if args.len() != 1 { return Err("ArrayBox.get expects 1 arg".to_string()); } - let idx_v = *vmap.get(&args[0]).ok_or("array.get index missing")?; - let idx_i = if let BVE::IntValue(iv) = idx_v { iv } else { return Err("array.get index must be int".to_string()); }; - let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); - 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()); } - let idx_v = *vmap.get(&args[0]).ok_or("array.set index missing")?; - let val_v = *vmap.get(&args[1]).ok_or("array.set value missing")?; - let idx_i = if let BVE::IntValue(iv) = idx_v { iv } else { return Err("array.set index must be int".to_string()); }; - let val_i = if let BVE::IntValue(iv) = val_v { iv } else { return Err("array.set value must be int".to_string()); }; - 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 { - BVE::IntValue(iv) => iv, - BVE::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())?; - return Ok(()); - } - "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); - } - return Ok(()); - } - _ => {} - } - } - } - - // Map fast-paths (core-first): get/set/has/size using NyRT shims with handle receiver - if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) { - if bname == "MapBox" && (method == "get" || method == "set" || method == "has" || method == "size") { - let i64t = codegen.context.i64_type(); - match method { - "size" => { - if !args.is_empty() { return Err("MapBox.size expects 0 arg".to_string()); } - let fnty = i64t.fn_type(&[i64t.into()], false); - let callee = codegen.module.get_function("nyash.map.size_h").unwrap_or_else(|| codegen.module.add_function("nyash.map.size_h", fnty, None)); - let call = codegen.builder.build_call(callee, &[recv_h.into()], "msize").map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call.try_as_basic_value().left().ok_or("map.size_h returned void".to_string())?; - vmap.insert(*d, rv); - } - return Ok(()); - } - "has" => { - if args.len() != 1 { return Err("MapBox.has expects 1 arg".to_string()); } - let key_v = *vmap.get(&args[0]).ok_or("map.has key missing")?; - let key_i = match key_v { - BVE::IntValue(iv) => iv, - BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "key_p2i").map_err(|e| e.to_string())?, - _ => return Err("map.has key 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.map.has_h").unwrap_or_else(|| codegen.module.add_function("nyash.map.has_h", fnty, None)); - let call = codegen.builder.build_call(callee, &[recv_h.into(), key_i.into()], "mhas").map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call.try_as_basic_value().left().ok_or("map.has_h returned void".to_string())?; - vmap.insert(*d, rv); - } - return Ok(()); - } - "get" => { - if args.len() != 1 { return Err("MapBox.get expects 1 arg".to_string()); } - let key_v = *vmap.get(&args[0]).ok_or("map.get key missing")?; - // prefer integer key; if pointer, convert to handle and call get_hh - let call = match key_v { - BVE::IntValue(iv) => { - let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash.map.get_h").unwrap_or_else(|| codegen.module.add_function("nyash.map.get_h", fnty, None)); - codegen.builder.build_call(callee, &[recv_h.into(), iv.into()], "mget").map_err(|e| e.to_string())? - } - BVE::PointerValue(pv) => { - // key: i8* -> i64 handle via from_i8_string (string key) - let fnty_conv = i64t.fn_type(&[codegen.context.ptr_type(AddressSpace::from(0)).into()], false); - let conv = codegen.module.get_function("nyash.box.from_i8_string").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty_conv, None)); - let kcall = codegen.builder.build_call(conv, &[pv.into()], "key_i8_to_handle").map_err(|e| e.to_string())?; - let kh = kcall.try_as_basic_value().left().ok_or("from_i8_string returned void".to_string())?.into_int_value(); - let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash.map.get_hh").unwrap_or_else(|| codegen.module.add_function("nyash.map.get_hh", fnty, None)); - codegen.builder.build_call(callee, &[recv_h.into(), kh.into()], "mget_hh").map_err(|e| e.to_string())? - } - _ => return Err("map.get key must be int or pointer".to_string()), - }; - if let Some(d) = dst { - let rv = call.try_as_basic_value().left().ok_or("map.get returned void".to_string())?; - vmap.insert(*d, rv); - } - return Ok(()); - } - "set" => { - if args.len() != 2 { return Err("MapBox.set expects 2 args (key, value)".to_string()); } - let key_v = *vmap.get(&args[0]).ok_or("map.set key missing")?; - let val_v = *vmap.get(&args[1]).ok_or("map.set value missing")?; - let key_i = match key_v { - BVE::IntValue(iv) => iv, - BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "key_p2i").map_err(|e| e.to_string())?, - _ => return Err("map.set key must be int or handle ptr".to_string()), - }; - let val_i = match val_v { - BVE::IntValue(iv) => iv, - BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "val_p2i").map_err(|e| e.to_string())?, - _ => return Err("map.set value must be int or handle ptr".to_string()), - }; - let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash.map.set_h").unwrap_or_else(|| codegen.module.add_function("nyash.map.set_h", fnty, None)); - let _ = codegen.builder.build_call(callee, &[recv_h.into(), key_i.into(), val_i.into()], "mset").map_err(|e| e.to_string())?; - return Ok(()); - } - _ => {} - } - } - } - - // getField - 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 BVE::PointerValue(pv) = name_v { pv } else { return Err("getField name must be pointer".to_string()); }; - let i8p = codegen.context.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())?; - let h = if let BVE::IntValue(iv) = rv { iv } else { return Err("get_field ret expected i64".to_string()); }; - let pty = codegen.context.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()); - } - // continue for potential further 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 BVE::PointerValue(pv) = name_v { pv } else { return Err("setField name must be pointer".to_string()); }; - let val_h = match val_v { - BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "valp2i").map_err(|e| e.to_string())?, - BVE::IntValue(iv) => iv, - BVE::FloatValue(_) => return Err("setField value must be int/handle".to_string()), - _ => return Err("setField value must be int/handle".to_string()), - }; - let i8p = codegen.context.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())?; - // continue for method_id path if any - } - - if let Some(mid) = method_id { - // Build argc and a1..a4 - 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 get_i64 = |vid: ValueId| -> Result, String> { - let bv = *vmap.get(&vid).ok_or("arg missing")?; - match bv { - BVE::IntValue(iv) => Ok(iv), - BVE::PointerValue(pv) => codegen - .builder - .build_ptr_to_int(pv, i64t, "p2i") - .map_err(|e| e.to_string()), - 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()], "arg_f64_to_box") - .map_err(|e| e.to_string())?; - let rv = call.try_as_basic_value().left().ok_or("from_f64 returned void".to_string())?; - if let BVE::IntValue(h) = rv { Ok(h) } else { Err("from_f64 ret expected i64".to_string()) } - } - _ => Err("unsupported arg value".to_string()), - } - }; - if args.len() >= 1 { a1 = get_i64(args[0])?; } - if args.len() >= 2 { a2 = get_i64(args[1])?; } - if args.len() >= 3 { a3 = get_i64(args[2])?; } - if args.len() >= 4 { a4 = get_i64(args[3])?; } - - // Note: String.concat should return a handle (i64) and be processed by the tagged path below. - // tagged fixed-arity <=4 - 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 { 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 { - 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())?; - if let Some(mt) = func.metadata.value_types.get(d) { - match mt { - crate::mir::MirType::Integer | crate::mir::MirType::Bool => { vmap.insert(*d, rv); } - // String: keep as i64 handle (do not cast to i8*) - crate::mir::MirType::String => { vmap.insert(*d, rv); } - // Box/Array/Future/Unknown: cast handle to opaque pointer - crate::mir::MirType::Box(_) | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => { - let h = if let BVE::IntValue(iv) = rv { iv } else { return Err("invoke ret expected i64".to_string()); }; - let pty = codegen.context.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); - } - } - return Ok(()); - } - // variable length path - let n = args.len() as u32; - 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 ti = classify_tag(*vmap.get(vid).ok_or("missing arg")?); - codegen.builder.build_store(gep_v, vi).map_err(|e| e.to_string())?; - codegen.builder.build_store(gep_t, i64t.const_int(ti as u64, false)).map_err(|e| e.to_string())?; - } - let vals_ptr = codegen.builder.build_pointer_cast(vals_arr, codegen.context.ptr_type(AddressSpace::from(0)), "vals_arr_i8p").map_err(|e| e.to_string())?; - let tags_ptr = codegen.builder.build_pointer_cast(tags_arr, codegen.context.ptr_type(AddressSpace::from(0)), "tags_arr_i8p").map_err(|e| e.to_string())?; - let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into(), codegen.context.ptr_type(AddressSpace::from(0)).into(), codegen.context.ptr_type(AddressSpace::from(0)).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::String => { vmap.insert(*d, rv); } - crate::mir::MirType::Box(_) | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => { - let h = if let BVE::IntValue(iv) = rv { iv } else { return Err("invoke ret expected i64".to_string()); }; - let pty = codegen.context.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); - } - } - Ok(()) - } else { - Err(format!("BoxCall requires method_id for method '{}'. The method_id should be automatically injected during MIR compilation.", method)) - } -} diff --git a/src/backend/llvm/compiler/codegen/instructions/arith.rs b/src/backend/llvm/compiler/codegen/instructions/arith.rs new file mode 100644 index 00000000..170fb5d2 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/arith.rs @@ -0,0 +1,124 @@ +use std::collections::HashMap; + +use inkwell::values::BasicValueEnum; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::{CompareOp, ValueId}; + +/// Compare lowering: return the resulting BasicValueEnum (i1) +pub(in super::super) fn lower_compare<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &HashMap>, + op: &CompareOp, + lhs: &ValueId, + rhs: &ValueId, +) -> Result, 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 out = if let (Some(li), Some(ri)) = (as_int(lv), as_int(rv)) { + use CompareOp as C; + let pred = match op { + C::Eq => inkwell::IntPredicate::EQ, + C::Ne => inkwell::IntPredicate::NE, + C::Lt => inkwell::IntPredicate::SLT, + C::Le => inkwell::IntPredicate::SLE, + C::Gt => inkwell::IntPredicate::SGT, + C::Ge => inkwell::IntPredicate::SGE, + }; + codegen + .builder + .build_int_compare(pred, li, ri, "icmp") + .map_err(|e| e.to_string())? + .into() + } else if let (Some(lf), Some(rf)) = (as_float(lv), as_float(rv)) { + use 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 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 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 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, "pcmpi") + .map_err(|e| e.to_string())? + .into() + } + _ => return Err("unsupported int-pointer comparison (only Eq/Ne)".to_string()), + } + } else { + return Err("compare type mismatch".to_string()); + }; + Ok(out) +} diff --git a/src/backend/llvm/compiler/codegen/instructions/blocks.rs b/src/backend/llvm/compiler/codegen/instructions/blocks.rs new file mode 100644 index 00000000..47fe7c8b --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/blocks.rs @@ -0,0 +1,82 @@ +use inkwell::basic_block::BasicBlock; +use inkwell::values::{BasicValueEnum, FunctionValue, PhiValue}; +use std::collections::HashMap; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::{function::MirFunction, BasicBlockId, ValueId}; + +// Small, safe extraction: create LLVM basic blocks for a MIR function and +// return the block map together with the entry block. +pub(in super::super) fn create_basic_blocks<'ctx>( + codegen: &CodegenContext<'ctx>, + llvm_func: FunctionValue<'ctx>, + func: &MirFunction, +) -> (HashMap>, BasicBlock<'ctx>) { + let mut bb_map: HashMap = HashMap::new(); + let entry_first = func.entry_block; + let entry_bb = codegen + .context + .append_basic_block(llvm_func, &format!("bb{}", entry_first.as_u32())); + bb_map.insert(entry_first, entry_bb); + for bid in func.block_ids() { + if bid == entry_first { + continue; + } + let name = format!("bb{}", bid.as_u32()); + let bb = codegen.context.append_basic_block(llvm_func, &name); + bb_map.insert(bid, bb); + } + (bb_map, entry_bb) +} + +// Pre-create PHI nodes for all blocks; also inserts placeholder values into vmap. +pub(in super::super) fn precreate_phis<'ctx>( + codegen: &CodegenContext<'ctx>, + func: &MirFunction, + bb_map: &HashMap>, + vmap: &mut HashMap>, +) -> Result< + HashMap< + BasicBlockId, + Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>, + >, + String, +> { + use super::super::types::map_mirtype_to_basic; + let mut phis_by_block: HashMap< + BasicBlockId, + Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>, + > = HashMap::new(); + 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, crate::mir::instruction::MirInstruction::Phi { .. })) + { + if let crate::mir::instruction::MirInstruction::Phi { dst, inputs } = inst { + let mut phi_ty: Option = 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())); + } + } + } + Ok(phis_by_block) +} diff --git a/src/backend/llvm/compiler/codegen/instructions/boxcall.rs b/src/backend/llvm/compiler/codegen/instructions/boxcall.rs new file mode 100644 index 00000000..61873315 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/boxcall.rs @@ -0,0 +1,444 @@ +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}; + +// BoxCall lowering (large): mirrors existing logic; kept in one function for now +pub(in super::super) fn lower_boxcall<'ctx>( + codegen: &CodegenContext<'ctx>, + func: &MirFunction, + vmap: &mut HashMap>, + dst: &Option, + box_val: &ValueId, + method: &str, + method_id: &Option, + args: &[ValueId], + box_type_ids: &HashMap, + entry_builder: &inkwell::builder::Builder<'ctx>, +) -> Result<(), String> { + use crate::backend::llvm::compiler::helpers::{as_float, as_int}; + use super::super::types::classify_tag; + let i64t = codegen.context.i64_type(); + let recv_v = *vmap.get(box_val).ok_or("box receiver missing")?; + let recv_p = match recv_v { + BVE::PointerValue(pv) => pv, + BVE::IntValue(iv) => { + let pty = codegen.context.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 + let type_id: i64 = if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) { + *box_type_ids.get(bname).unwrap_or(&0) + } else if let Some(crate::mir::MirType::String) = func.metadata.value_types.get(box_val) { + *box_type_ids.get("StringBox").unwrap_or(&0) + } else { + 0 + }; + + // String concat fast-path (avoid plugin path for builtin StringBox) + if method == "concat" { + // Recognize receiver typed as String or StringBox + let is_string_recv = match func.metadata.value_types.get(box_val) { + Some(crate::mir::MirType::String) => true, + Some(crate::mir::MirType::Box(b)) if b == "StringBox" => true, + _ => false, + }; + if is_string_recv { + if args.len() != 1 { return Err("String.concat expects 1 arg".to_string()); } + // Prefer pointer-based concat to keep AOT string fast-path + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + let rhs_v = *vmap.get(&args[0]).ok_or("concat arg missing")?; + match (recv_v, rhs_v) { + (BVE::PointerValue(lp), BVE::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_call").map_err(|e| e.to_string())?; + if let Some(d) = dst { let rv = call.try_as_basic_value().left().ok_or("concat_ss returned void".to_string())?; vmap.insert(*d, rv); } + return Ok(()); + } + (BVE::PointerValue(lp), BVE::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_call").map_err(|e| e.to_string())?; + if let Some(d) = dst { let rv = call.try_as_basic_value().left().ok_or("concat_si returned void".to_string())?; vmap.insert(*d, rv); } + return Ok(()); + } + (BVE::IntValue(li), BVE::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_call").map_err(|e| e.to_string())?; + if let Some(d) = dst { let rv = call.try_as_basic_value().left().ok_or("concat_is returned void".to_string())?; vmap.insert(*d, rv); } + return Ok(()); + } + _ => { /* fall through to generic path below */ } + } + } + } + + // String length fast-path: length/len + if method == "length" || method == "len" { + // Only when receiver is String/StringBox by annotation + let is_string_recv = match func.metadata.value_types.get(box_val) { + Some(crate::mir::MirType::String) => true, + Some(crate::mir::MirType::Box(b)) if b == "StringBox" => true, + _ => false, + }; + if is_string_recv { + let i64t = codegen.context.i64_type(); + // Ensure we have a handle: convert i8* receiver to handle when needed + let recv_h = match recv_v { + BVE::IntValue(h) => h, + BVE::PointerValue(p) => { + let fnty = i64t.fn_type(&[codegen.context.ptr_type(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, &[p.into()], "str_ptr_to_handle") + .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(iv) = rv { iv } else { return Err("from_i8_string ret expected i64".to_string()); } + } + _ => return Err("String.length receiver type unsupported".to_string()), + }; + // call i64 @nyash.string.len_h(i64) + let fnty = i64t.fn_type(&[i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.string.len_h") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.len_h", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[recv_h.into()], "strlen_h") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("len_h returned void".to_string())?; + vmap.insert(*d, rv); + } + return Ok(()); + } + } + + // Array fast-paths + if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) { + if bname == "ArrayBox" && (method == "get" || method == "set" || method == "push" || method == "length") { + match method { + "get" => { + if args.len() != 1 { return Err("ArrayBox.get expects 1 arg".to_string()); } + let idx_v = *vmap.get(&args[0]).ok_or("array.get index missing")?; + let idx_i = if let BVE::IntValue(iv) = idx_v { iv } else { return Err("array.get index must be int".to_string()); }; + let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); + 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()); } + let idx_v = *vmap.get(&args[0]).ok_or("array.set index missing")?; + let val_v = *vmap.get(&args[1]).ok_or("array.set value missing")?; + let idx_i = if let BVE::IntValue(iv) = idx_v { iv } else { return Err("array.set index must be int".to_string()); }; + let val_i = if let BVE::IntValue(iv) = val_v { iv } else { return Err("array.set value must be int".to_string()); }; + 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 { + BVE::IntValue(iv) => iv, + BVE::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())?; + return Ok(()); + } + "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); + } + return Ok(()); + } + _ => {} + } + } + } + + // Map fast-paths (core-first): get/set/has/size using NyRT shims with handle receiver + if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) { + if bname == "MapBox" && (method == "get" || method == "set" || method == "has" || method == "size") { + let i64t = codegen.context.i64_type(); + match method { + "size" => { + if !args.is_empty() { return Err("MapBox.size expects 0 arg".to_string()); } + let fnty = i64t.fn_type(&[i64t.into()], false); + let callee = codegen.module.get_function("nyash.map.size_h").unwrap_or_else(|| codegen.module.add_function("nyash.map.size_h", fnty, None)); + let call = codegen.builder.build_call(callee, &[recv_h.into()], "msize").map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call.try_as_basic_value().left().ok_or("map.size_h returned void".to_string())?; + vmap.insert(*d, rv); + } + return Ok(()); + } + "has" => { + if args.len() != 1 { return Err("MapBox.has expects 1 arg".to_string()); } + let key_v = *vmap.get(&args[0]).ok_or("map.has key missing")?; + let key_i = match key_v { + BVE::IntValue(iv) => iv, + BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "key_p2i").map_err(|e| e.to_string())?, + _ => return Err("map.has key 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.map.has_h").unwrap_or_else(|| codegen.module.add_function("nyash.map.has_h", fnty, None)); + let call = codegen.builder.build_call(callee, &[recv_h.into(), key_i.into()], "mhas").map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call.try_as_basic_value().left().ok_or("map.has_h returned void".to_string())?; + vmap.insert(*d, rv); + } + return Ok(()); + } + "get" => { + if args.len() != 1 { return Err("MapBox.get expects 1 arg".to_string()); } + let key_v = *vmap.get(&args[0]).ok_or("map.get key missing")?; + // prefer integer key; if pointer, convert to handle and call get_hh + let call = match key_v { + BVE::IntValue(iv) => { + let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); + let callee = codegen.module.get_function("nyash.map.get_h").unwrap_or_else(|| codegen.module.add_function("nyash.map.get_h", fnty, None)); + codegen.builder.build_call(callee, &[recv_h.into(), iv.into()], "mget").map_err(|e| e.to_string())? + } + BVE::PointerValue(pv) => { + // key: i8* -> i64 handle via from_i8_string (string key) + let fnty_conv = i64t.fn_type(&[codegen.context.ptr_type(AddressSpace::from(0)).into()], false); + let conv = codegen.module.get_function("nyash.box.from_i8_string").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty_conv, None)); + let kcall = codegen.builder.build_call(conv, &[pv.into()], "key_i8_to_handle").map_err(|e| e.to_string())?; + let kh = kcall.try_as_basic_value().left().ok_or("from_i8_string returned void".to_string())?.into_int_value(); + let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); + let callee = codegen.module.get_function("nyash.map.get_hh").unwrap_or_else(|| codegen.module.add_function("nyash.map.get_hh", fnty, None)); + codegen.builder.build_call(callee, &[recv_h.into(), kh.into()], "mget_hh").map_err(|e| e.to_string())? + } + _ => return Err("map.get key must be int or pointer".to_string()), + }; + if let Some(d) = dst { + let rv = call.try_as_basic_value().left().ok_or("map.get returned void".to_string())?; + vmap.insert(*d, rv); + } + return Ok(()); + } + "set" => { + if args.len() != 2 { return Err("MapBox.set expects 2 args (key, value)".to_string()); } + let key_v = *vmap.get(&args[0]).ok_or("map.set key missing")?; + let val_v = *vmap.get(&args[1]).ok_or("map.set value missing")?; + let key_i = match key_v { + BVE::IntValue(iv) => iv, + BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "key_p2i").map_err(|e| e.to_string())?, + _ => return Err("map.set key must be int or handle ptr".to_string()), + }; + let val_i = match val_v { + BVE::IntValue(iv) => iv, + BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "val_p2i").map_err(|e| e.to_string())?, + _ => return Err("map.set value must be int or handle ptr".to_string()), + }; + let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into()], false); + let callee = codegen.module.get_function("nyash.map.set_h").unwrap_or_else(|| codegen.module.add_function("nyash.map.set_h", fnty, None)); + let _ = codegen.builder.build_call(callee, &[recv_h.into(), key_i.into(), val_i.into()], "mset").map_err(|e| e.to_string())?; + return Ok(()); + } + _ => {} + } + } + } + + // getField + 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 BVE::PointerValue(pv) = name_v { pv } else { return Err("getField name must be pointer".to_string()); }; + let i8p = codegen.context.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())?; + let h = if let BVE::IntValue(iv) = rv { iv } else { return Err("get_field ret expected i64".to_string()); }; + let pty = codegen.context.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()); + } + // continue for potential further 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 BVE::PointerValue(pv) = name_v { pv } else { return Err("setField name must be pointer".to_string()); }; + let val_h = match val_v { + BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "valp2i").map_err(|e| e.to_string())?, + BVE::IntValue(iv) => iv, + BVE::FloatValue(_) => return Err("setField value must be int/handle".to_string()), + _ => return Err("setField value must be int/handle".to_string()), + }; + let i8p = codegen.context.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())?; + // continue for method_id path if any + } + + if let Some(mid) = method_id { + // Build argc and a1..a4 + 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 get_i64 = |vid: ValueId| -> Result, String> { + let bv = *vmap.get(&vid).ok_or("arg missing")?; + match bv { + BVE::IntValue(iv) => Ok(iv), + BVE::PointerValue(pv) => codegen + .builder + .build_ptr_to_int(pv, i64t, "p2i") + .map_err(|e| e.to_string()), + 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()], "arg_f64_to_box") + .map_err(|e| e.to_string())?; + let rv = call.try_as_basic_value().left().ok_or("from_f64 returned void".to_string())?; + if let BVE::IntValue(h) = rv { Ok(h) } else { Err("from_f64 ret expected i64".to_string()) } + } + _ => Err("unsupported arg value".to_string()), + } + }; + if args.len() >= 1 { a1 = get_i64(args[0])?; } + if args.len() >= 2 { a2 = get_i64(args[1])?; } + if args.len() >= 3 { a3 = get_i64(args[2])?; } + if args.len() >= 4 { a4 = get_i64(args[3])?; } + + // Note: String.concat should return a handle (i64) and be processed by the tagged path below. + // tagged fixed-arity <=4 + 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 { 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 { + 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())?; + if let Some(mt) = func.metadata.value_types.get(d) { + match mt { + crate::mir::MirType::Integer | crate::mir::MirType::Bool => { vmap.insert(*d, rv); } + // String: keep as i64 handle (do not cast to i8*) + crate::mir::MirType::String => { vmap.insert(*d, rv); } + // Box/Array/Future/Unknown: cast handle to opaque pointer + crate::mir::MirType::Box(_) | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => { + let h = if let BVE::IntValue(iv) = rv { iv } else { return Err("invoke ret expected i64".to_string()); }; + let pty = codegen.context.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); + } + } + return Ok(()); + } + // variable length path + let n = args.len() as u32; + 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 ti = classify_tag(*vmap.get(vid).ok_or("missing arg")?); + codegen.builder.build_store(gep_v, vi).map_err(|e| e.to_string())?; + codegen.builder.build_store(gep_t, i64t.const_int(ti as u64, false)).map_err(|e| e.to_string())?; + } + let vals_ptr = codegen.builder.build_pointer_cast(vals_arr, codegen.context.ptr_type(AddressSpace::from(0)), "vals_arr_i8p").map_err(|e| e.to_string())?; + let tags_ptr = codegen.builder.build_pointer_cast(tags_arr, codegen.context.ptr_type(AddressSpace::from(0)), "tags_arr_i8p").map_err(|e| e.to_string())?; + let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into(), codegen.context.ptr_type(AddressSpace::from(0)).into(), codegen.context.ptr_type(AddressSpace::from(0)).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::String => { vmap.insert(*d, rv); } + crate::mir::MirType::Box(_) | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => { + let h = if let BVE::IntValue(iv) = rv { iv } else { return Err("invoke ret expected i64".to_string()); }; + let pty = codegen.context.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); + } + } + return Ok(()); + } else { + Err(format!("BoxCall requires method_id for method '{}'. The method_id should be automatically injected during MIR compilation.", method)) + } +} diff --git a/src/backend/llvm/compiler/codegen/instructions/consts.rs b/src/backend/llvm/compiler/codegen/instructions/consts.rs new file mode 100644 index 00000000..dbe552a3 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/consts.rs @@ -0,0 +1,68 @@ +use std::collections::HashMap; + +use inkwell::values::BasicValueEnum; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::{instruction::ConstValue, ValueId}; + +pub(in super::super) fn lower_const<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &mut HashMap>, + dst: ValueId, + value: &ConstValue, +) -> Result<(), String> { + let bval = match value { + ConstValue::Integer(i) => codegen + .context + .i64_type() + .const_int(*i as u64, true) + .into(), + ConstValue::Float(f) => codegen.context.f64_type().const_float(*f).into(), + ConstValue::Bool(b) => codegen + .context + .bool_type() + .const_int(*b as u64, false) + .into(), + ConstValue::String(s) => { + let gv = codegen + .builder + .build_global_string_ptr(s, "str") + .map_err(|e| e.to_string())?; + let len = codegen + .context + .i32_type() + .const_int(s.len() as u64, false); + // declare i8* @nyash_string_new(i8*, i32) + let rt = codegen.context.ptr_type(inkwell::AddressSpace::from(0)); + let fn_ty = rt.fn_type( + &[ + codegen + .context + .ptr_type(inkwell::AddressSpace::from(0)) + .into(), + codegen.context.i32_type().into(), + ], + false, + ); + let callee = codegen + .module + .get_function("nyash_string_new") + .unwrap_or_else(|| codegen.module.add_function("nyash_string_new", fn_ty, None)); + let call = codegen + .builder + .build_call(callee, &[gv.as_pointer_value().into(), len.into()], "strnew") + .map_err(|e| e.to_string())?; + call.try_as_basic_value() + .left() + .ok_or("nyash_string_new returned void".to_string())? + } + ConstValue::Null => codegen + .context + .ptr_type(inkwell::AddressSpace::from(0)) + .const_null() + .into(), + ConstValue::Void => return Err("Const Void unsupported".to_string()), + }; + vmap.insert(dst, bval); + Ok(()) +} diff --git a/src/backend/llvm/compiler/codegen/instructions/externcall.rs b/src/backend/llvm/compiler/codegen/instructions/externcall.rs new file mode 100644 index 00000000..34a9b49e --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/externcall.rs @@ -0,0 +1,465 @@ +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>, + dst: &Option, + 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 + )) +} diff --git a/src/backend/llvm/compiler/codegen/instructions/flow.rs b/src/backend/llvm/compiler/codegen/instructions/flow.rs new file mode 100644 index 00000000..180b6375 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/flow.rs @@ -0,0 +1,122 @@ +use inkwell::basic_block::BasicBlock; +use inkwell::values::{BasicValueEnum, PhiValue}; +use std::collections::HashMap; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::{function::MirFunction, BasicBlockId, ValueId}; + +use super::super::types::to_bool; + +pub(in super::super) fn emit_return<'ctx>( + codegen: &CodegenContext<'ctx>, + func: &MirFunction, + vmap: &HashMap>, + value: &Option, +) -> Result<(), String> { + match (&func.signature.return_type, value) { + (crate::mir::MirType::Void, _) => { + codegen.builder.build_return(None).unwrap(); + Ok(()) + } + (_t, Some(vid)) => { + let v = *vmap.get(vid).ok_or("ret value missing")?; + codegen + .builder + .build_return(Some(&v)) + .map_err(|e| e.to_string())?; + Ok(()) + } + (_t, None) => Err("non-void function missing return value".to_string()), + } +} + +pub(in super::super) fn emit_jump<'ctx>( + codegen: &CodegenContext<'ctx>, + bid: BasicBlockId, + target: &BasicBlockId, + bb_map: &HashMap>, + phis_by_block: &HashMap< + BasicBlockId, + Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>, + >, + vmap: &HashMap>, +) -> Result<(), String> { + if let Some(list) = phis_by_block.get(target) { + for (_dst, phi, inputs) in list { + if let Some((_, in_vid)) = inputs.iter().find(|(pred, _)| pred == &bid) { + let val = *vmap.get(in_vid).ok_or("phi incoming value missing")?; + let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?; + match val { + BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]), + BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]), + BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]), + _ => return Err("unsupported phi incoming value".to_string()), + } + } + } + } + let tbb = *bb_map.get(target).ok_or("target bb missing")?; + codegen + .builder + .build_unconditional_branch(tbb) + .map_err(|e| e.to_string())?; + Ok(()) +} + +pub(in super::super) fn emit_branch<'ctx>( + codegen: &CodegenContext<'ctx>, + bid: BasicBlockId, + condition: &ValueId, + then_bb: &BasicBlockId, + else_bb: &BasicBlockId, + bb_map: &HashMap>, + phis_by_block: &HashMap< + BasicBlockId, + Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>, + >, + vmap: &HashMap>, +) -> Result<(), String> { + let cond_v = *vmap.get(condition).ok_or("cond missing")?; + let b = to_bool(codegen.context, cond_v, &codegen.builder)?; + // then + if let Some(list) = phis_by_block.get(then_bb) { + for (_dst, phi, inputs) in list { + if let Some((_, in_vid)) = inputs.iter().find(|(pred, _)| pred == &bid) { + let val = *vmap + .get(in_vid) + .ok_or("phi incoming (then) value missing")?; + let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?; + match val { + BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]), + BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]), + BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]), + _ => return Err("unsupported phi incoming value (then)".to_string()), + } + } + } + } + // else + if let Some(list) = phis_by_block.get(else_bb) { + for (_dst, phi, inputs) in list { + if let Some((_, in_vid)) = inputs.iter().find(|(pred, _)| pred == &bid) { + let val = *vmap + .get(in_vid) + .ok_or("phi incoming (else) value missing")?; + let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?; + match val { + BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]), + BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]), + BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]), + _ => return Err("unsupported phi incoming value (else)".to_string()), + } + } + } + } + let tbb = *bb_map.get(then_bb).ok_or("then bb missing")?; + let ebb = *bb_map.get(else_bb).ok_or("else bb missing")?; + codegen + .builder + .build_conditional_branch(b, tbb, ebb) + .map_err(|e| e.to_string())?; + Ok(()) +} diff --git a/src/backend/llvm/compiler/codegen/instructions/mem.rs b/src/backend/llvm/compiler/codegen/instructions/mem.rs new file mode 100644 index 00000000..415bedef --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/mem.rs @@ -0,0 +1,129 @@ +use std::collections::HashMap; + +use inkwell::values::BasicValueEnum; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::ValueId; + +// Lower Store: handle allocas with element type tracking and integer width adjust +pub(in super::super) fn lower_store<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &HashMap>, + allocas: &mut HashMap>, + alloca_elem_types: &mut HashMap>, + value: &ValueId, + ptr: &ValueId, +) -> Result<(), String> { + use inkwell::types::BasicTypeEnum; + let val = *vmap.get(value).ok_or("store value missing")?; + let elem_ty = match val { + BasicValueEnum::IntValue(iv) => BasicTypeEnum::IntType(iv.get_type()), + BasicValueEnum::FloatValue(fv) => BasicTypeEnum::FloatType(fv.get_type()), + BasicValueEnum::PointerValue(pv) => BasicTypeEnum::PointerType(pv.get_type()), + _ => return Err("unsupported store value type".to_string()), + }; + if let Some(existing) = allocas.get(ptr).copied() { + let existing_elem = *alloca_elem_types.get(ptr).ok_or("alloca elem type missing")?; + if existing_elem != elem_ty { + match (val, existing_elem) { + (BasicValueEnum::IntValue(iv), BasicTypeEnum::IntType(t)) => { + let bw_src = iv.get_type().get_bit_width(); + let bw_dst = t.get_bit_width(); + if bw_src < bw_dst { + let adj = codegen + .builder + .build_int_z_extend(iv, t, "zext") + .map_err(|e| e.to_string())?; + codegen + .builder + .build_store(existing, adj) + .map_err(|e| e.to_string())?; + } else if bw_src > bw_dst { + let adj = codegen + .builder + .build_int_truncate(iv, t, "trunc") + .map_err(|e| e.to_string())?; + codegen + .builder + .build_store(existing, adj) + .map_err(|e| e.to_string())?; + } else { + codegen + .builder + .build_store(existing, iv) + .map_err(|e| e.to_string())?; + } + } + (BasicValueEnum::PointerValue(pv), BasicTypeEnum::PointerType(pt)) => { + let adj = codegen + .builder + .build_pointer_cast(pv, pt, "pcast") + .map_err(|e| e.to_string())?; + codegen + .builder + .build_store(existing, adj) + .map_err(|e| e.to_string())?; + } + (BasicValueEnum::FloatValue(fv), BasicTypeEnum::FloatType(ft)) => { + // Only f64 currently expected + if fv.get_type() != ft { + return Err("float width mismatch in store".to_string()); + } + codegen + .builder + .build_store(existing, fv) + .map_err(|e| e.to_string())?; + } + _ => return Err("store type mismatch".to_string()), + } + } else { + codegen + .builder + .build_store(existing, val) + .map_err(|e| e.to_string())?; + } + } else { + let slot = codegen + .builder + .build_alloca(elem_ty, &format!("slot_{}", ptr.as_u32())) + .map_err(|e| e.to_string())?; + codegen + .builder + .build_store(slot, val) + .map_err(|e| e.to_string())?; + allocas.insert(*ptr, slot); + alloca_elem_types.insert(*ptr, elem_ty); + } + Ok(()) +} + +pub(in super::super) fn lower_load<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &mut HashMap>, + allocas: &mut HashMap>, + alloca_elem_types: &mut HashMap>, + dst: &ValueId, + ptr: &ValueId, +) -> Result<(), String> { + use inkwell::types::BasicTypeEnum; + let (slot, elem_ty) = if let Some(s) = allocas.get(ptr).copied() { + let et = *alloca_elem_types.get(ptr).ok_or("alloca elem type missing")?; + (s, et) + } else { + // Default new slot as i64 for uninitialized loads + let i64t = codegen.context.i64_type(); + let slot = codegen + .builder + .build_alloca(i64t, &format!("slot_{}", ptr.as_u32())) + .map_err(|e| e.to_string())?; + allocas.insert(*ptr, slot); + alloca_elem_types.insert(*ptr, i64t.into()); + (slot, i64t.into()) + }; + let lv = codegen + .builder + .build_load(elem_ty, slot, &format!("load_{}", dst.as_u32())) + .map_err(|e| e.to_string())?; + vmap.insert(*dst, lv); + Ok(()) +} diff --git a/src/backend/llvm/compiler/codegen/instructions/mod.rs b/src/backend/llvm/compiler/codegen/instructions/mod.rs new file mode 100644 index 00000000..8a3b9b50 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/mod.rs @@ -0,0 +1,18 @@ +mod blocks; +mod flow; +mod externcall; +mod newbox; +mod boxcall; +mod arith; +mod mem; +mod consts; + +pub(super) use blocks::{create_basic_blocks, precreate_phis}; +pub(super) use flow::{emit_branch, emit_jump, emit_return}; +pub(super) use externcall::lower_externcall; +pub(super) use newbox::lower_newbox; +pub(super) use boxcall::lower_boxcall; +pub(super) use arith::lower_compare; +pub(super) use mem::{lower_load, lower_store}; +pub(super) use consts::lower_const; + diff --git a/src/backend/llvm/compiler/codegen/instructions/newbox.rs b/src/backend/llvm/compiler/codegen/instructions/newbox.rs new file mode 100644 index 00000000..3964b43d --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/newbox.rs @@ -0,0 +1,154 @@ +use std::collections::HashMap; + +use inkwell::AddressSpace; +use inkwell::values::BasicValueEnum as BVE; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::ValueId; + +// NewBox lowering (subset consistent with existing code) +pub(in super::super) fn lower_newbox<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &mut HashMap>, + dst: ValueId, + box_type: &str, + args: &[ValueId], + box_type_ids: &HashMap, +) -> Result<(), String> { + match (box_type, args.len()) { + ("StringBox", 1) => { + // Keep as i8* string pointer (AOT string fast-path) + let av = *vmap.get(&args[0]).ok_or("StringBox arg missing")?; + vmap.insert(dst, av); + Ok(()) + } + (_, n) if n == 1 || n == 2 => { + let type_id = *box_type_ids.get(box_type).unwrap_or(&0); + let i64t = codegen.context.i64_type(); + let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.box.birth_i64") + .unwrap_or_else(|| codegen.module.add_function("nyash.box.birth_i64", fnty, None)); + let argc = i64t.const_int(args.len() as u64, false); + let mut a1 = i64t.const_zero(); + let mut a2 = i64t.const_zero(); + if args.len() >= 1 { + let v = *vmap.get(&args[0]).ok_or("newbox arg[0] missing")?; + a1 = match v { + BVE::IntValue(iv) => iv, + BVE::PointerValue(pv) => codegen + .builder + .build_ptr_to_int(pv, i64t, "arg0_p2i") + .map_err(|e| e.to_string())?, + _ => { + return Err( + "newbox arg[0]: unsupported type (expect int or handle ptr)" + .to_string(), + ) + } + }; + } + if args.len() >= 2 { + let v = *vmap.get(&args[1]).ok_or("newbox arg[1] missing")?; + a2 = match v { + BVE::IntValue(iv) => iv, + BVE::PointerValue(pv) => codegen + .builder + .build_ptr_to_int(pv, i64t, "arg1_p2i") + .map_err(|e| e.to_string())?, + _ => { + return Err( + "newbox arg[1]: unsupported type (expect int or handle ptr)" + .to_string(), + ) + } + }; + } + let tid = i64t.const_int(type_id as u64, true); + let call = codegen + .builder + .build_call(callee, &[tid.into(), argc.into(), a1.into(), a2.into()], "birth_i64") + .map_err(|e| e.to_string())?; + let h = call + .try_as_basic_value() + .left() + .ok_or("birth_i64 returned void".to_string())? + .into_int_value(); + let pty = codegen.context.ptr_type(AddressSpace::from(0)); + let ptr = codegen + .builder + .build_int_to_ptr(h, pty, "handle_to_ptr") + .map_err(|e| e.to_string())?; + vmap.insert(dst, ptr.into()); + Ok(()) + } + _ => { + // No-arg birth via central type registry (preferred), + // fallback to env.box.new(name) when type_id is unavailable. + if !args.is_empty() { + return Err("NewBox with >2 args not yet supported in LLVM lowering".to_string()); + } + let type_id = *box_type_ids.get(box_type).unwrap_or(&0); + // Temporary gate: allow forcing MapBox to plugin path explicitly + let force_plugin_map = std::env::var("NYASH_LLVM_FORCE_PLUGIN_MAP") + .ok() + .as_deref() + == Some("1"); + let i64t = codegen.context.i64_type(); + if type_id != 0 && !(box_type == "MapBox" && !force_plugin_map) { + // declare i64 @nyash.box.birth_h(i64) + let fn_ty = i64t.fn_type(&[i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.box.birth_h") + .unwrap_or_else(|| codegen.module.add_function("nyash.box.birth_h", fn_ty, None)); + let tid = i64t.const_int(type_id as u64, true); + let call = codegen + .builder + .build_call(callee, &[tid.into()], "birth") + .map_err(|e| e.to_string())?; + let h_i64 = call + .try_as_basic_value() + .left() + .ok_or("birth_h returned void".to_string())? + .into_int_value(); + let pty = codegen.context.ptr_type(AddressSpace::from(0)); + let ptr = codegen + .builder + .build_int_to_ptr(h_i64, pty, "handle_to_ptr") + .map_err(|e| e.to_string())?; + vmap.insert(dst, ptr.into()); + Ok(()) + } else { + // Fallback: call i64 @nyash.env.box.new(i8*) with type name + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + let fn_ty = 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", fn_ty, None)); + let tn = codegen + .builder + .build_global_string_ptr(box_type, "box_type_name") + .map_err(|e| e.to_string())?; + let call = codegen + .builder + .build_call(callee, &[tn.as_pointer_value().into()], "env_box_new") + .map_err(|e| e.to_string())?; + let h_i64 = call + .try_as_basic_value() + .left() + .ok_or("env.box.new returned void".to_string())? + .into_int_value(); + let pty = codegen.context.ptr_type(AddressSpace::from(0)); + let ptr = codegen + .builder + .build_int_to_ptr(h_i64, pty, "handle_to_ptr") + .map_err(|e| e.to_string())?; + vmap.insert(dst, ptr.into()); + Ok(()) + } + } + } +}