diff --git a/.ai-ignore b/.ai-ignore new file mode 100644 index 00000000..98c7673d --- /dev/null +++ b/.ai-ignore @@ -0,0 +1,5 @@ +# AI tools should ignore legacy or archived paths +src/backend/llvm_legacy/ +archived/ +*.tar.gz + diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 62a3a90c..9beb62fa 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -5,13 +5,21 @@ Summary - VM/Cranelift/Interpreter は MIR14 非対応。MIR 正規化(Resolver・LoopForm規約)を Rust 側で担保し、ハーネスにも同じ形を供給する。 - 代表ケース(apps/selfhost/tools/dep_tree_min_string.nyash)で `.o`(および必要時 EXE)を安定生成。Harness ON/OFF で機能同値を確認。 -Quick Status — 2025‑09‑13(compressed) +Quick Status — 2025‑09‑13(compressed, post‑harness fixes) - Harness ON(llvmlite)で .ll verify green → .o → link 成立(dep_tree_min_string) -- Resolver-only 統一(vmap直読排除)。PHIはBB先頭に集約・i64(ハンドル)固定、pointer incomingはpred終端で boxing(GEP+from_i8_string) -- 降下順序: preds優先の擬似トポロジカル順に block 降下。非PHI命令は常に「現在BB」末尾に挿入(dominance安定) -- 文字列: ‘+’ は stringタグ/ptr検出時のみ concat_hh、len/eq 対応、substring/lastIndexOf は handle版(_hii/_hh)をNyRTに実装・使用 -- const(string): Global保持→使用側で GEP→i8*、MIR main→private、ny_main ラッパ生成 -- 比較/検証: compare_harness_on_off.sh で ON/OFF のExit一致(現状JSONは双方空、最終一致に向け調整中) +- Resolver‑only 統一(vmap 直読排除)。PHI は BB 先頭に集約・i64(ハンドル)固定/pointer incoming は pred 終端直前で boxing(GEP+from_i8_string) +- 降下順序: preds 優先の擬似トポロジカル順に block 降下。非 PHI 命令は「現在 BB」末尾に挿入(dominance 安定) +- 文字列: ‘+’ は string タグ/ptr 検出時のみ concat_hh、len/eq 対応、substring/lastIndexOf は handle 版(_hii/_hh)を NyRT に実装・使用 +- const(string): Global を保持→使用側で GEP→i8* に正規化。MIR main→private、ny_main ラッパ生成 +- by‑name 定数: メソッド名の i8* は定数 GEP を採用(順序依存を排除) +- 比較/検証: compare_harness_on_off.sh で ON/OFF の Exit 一致(現状 JSON は双方空。最終 JSON 一致は次フェーズで詰め) + +Focus Shift — Python/llvmlite Only(2025‑09‑13) +- Rust/inkwell 側は当面「保守」へ。開発・詰めは Nyash スクリプト+Python/llvmlite のみで進行。 +- 追加スモーク: apps/tests/esc_dirname_smoke.nyash(esc_json/dirname の最小 2 行出力)。 +- 追加トレース: `NYASH_LLVM_TRACE_FINAL=1` で println 直前に `nyash.debug.trace_handle(i64)` を呼び、最終ハンドルを観測。 +- Lifetime ヒント(軽量): `def_blocks`(value_id → 定義ブロック集合)を Builder が収集、Resolver は現ブロック定義済みの i64 を優先再利用(PHI 過剰化を抑制)。 +- const(string) 改善: 即時 `from_i8_string` で i64 ハンドル化(後段連鎖の 0 落ちを軽減)。 Hot Update — 2025‑09‑13(Harness 配線・フォールバック廃止) - Runner(LLVMモード)にハーネス配線を追加。`NYASH_LLVM_USE_HARNESS=1` のとき: @@ -31,10 +39,14 @@ Hot Update — 2025‑09‑13(Resolver‑only 統一 + Harness ON green) - `main` 衝突回避: MIR 由来 `main` は private にし、`ny_main()` ラッパを自動生成(NyRT `main` と整合)。 - 代表ケース(dep_tree_min_string): Harness ON で `.ll verify green → .o` を確認し、NyRT とリンクして EXE 生成成功。 -Next(short) -1) PHI/dominance最終安定化(Main.esc_json/1, Main.dirname/1)→ ON/OFF の最終JSON一致 -2) 残オンデマンドPHI/フォールバック撤去(完全 Resolver‑only 固定・vmap直読ゼロ) -3) Docs/Trace 更新(Resolver/PHI/ptr↔i64、不変条件、NYASH_LLVM_TRACE_FINAL) +Next(short, refreshed — Py/llvmlite 線) +1) スモーク確定: esc_dirname_smoke の 2 行出力を ON/OFF 完全一致に(行比較)。 +2) dep_tree_min_string の最終 JSON 一致(`{` 以降の diff=空)。 + - `NYASH_LLVM_TRACE_FINAL=1`+`NYASH_LLVM_TRACE_VALUES=1` で println 引数ハンドルの鎖を観測し、synth‑zero 起点を特定→ Resolver/PHI で局所是正。 + - PHI/snapshot は「pred で materialize→無ければ snap→最後に synth(0)」の順を徹底。None を入れない。 +3) CI/補助 + - スモークを compare_harness_on_off.sh からも容易に呼べるよう維持(必要なら行比較モード追加)。 + - Deny‑Direct(`vmap.get(` 直読の抑止)を継続チェック。 Compact Roadmap(2025‑09‑13 改定) - Focus A(Rust LLVM 維持): Flow hardening, PHI(sealed) 安定化, LoopForm 仕様遵守。 diff --git a/Cargo.toml b/Cargo.toml index f67bc25e..9d2fc5ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,13 @@ plugins = ["dep:libloading"] # MIR instruction diet PoC flags (scaffolding only; off by default) mir_typeop_poc = [] mir_refbarrier_unify_poc = [] -# Note: LLVM feature requires inkwell dependency and LLVM development libraries -# LLVM 18 + inkwell 0.5.0 を使用 -llvm = ["dep:inkwell"] +# LLVM features split +# - llvm-harness: Python/llvmlite harness only(inkwell不要) +# - llvm-inkwell-legacy: historical Rust/inkwell backend(参照用) +# keep `llvm` as a compatibility alias to `llvm-harness` +llvm-harness = [] +llvm-inkwell-legacy = ["dep:inkwell"] +llvm = ["llvm-harness"] # (removed) Optional modular MIR builder feature cranelift-jit = [ "dep:cranelift-codegen", diff --git a/app_dep_tree_py b/app_dep_tree_py index d2e34f2e..d399799e 100644 Binary files a/app_dep_tree_py and b/app_dep_tree_py differ diff --git a/app_dep_tree_rust b/app_dep_tree_rust index 13fe4321..7751fc3c 100644 Binary files a/app_dep_tree_rust and b/app_dep_tree_rust differ diff --git a/app_trace b/app_trace new file mode 100644 index 00000000..1456b79c Binary files /dev/null and b/app_trace differ diff --git a/apps/tests/esc_dirname_smoke.nyash b/apps/tests/esc_dirname_smoke.nyash new file mode 100644 index 00000000..02473070 --- /dev/null +++ b/apps/tests/esc_dirname_smoke.nyash @@ -0,0 +1,37 @@ +// esc_dirname_smoke.nyash — minimal smoke for esc_json/dirname and println + +static box Main { + esc_json(s) { + // very small escaper: replace \ and " + local out = "" + local i = 0 + local n = s.length() + loop(i < n) { + local ch = s.substring(i, i+1) + if ch == "\\" { out = out + "\\\\" } else { + if ch == "\"" { out = out + "\\\"" } else { out = out + ch } + } + i = i + 1 + } + return out + } + + dirname(path) { + // simple pure string dirname: lastIndexOf('/') + local i = path.lastIndexOf("/") + if i < 0 { return "." } + return path.substring(0, i) + } + + main(args) { + local console = new ConsoleBox() + // Test escaping (A\"B\\C) + local t1 = me.esc_json("A\\\"B\\\\C") + console.println(t1) + // Test dirname + local t2 = me.dirname("dir1/dir2/file.txt") + console.println(t2) + return 0 + } +} + diff --git a/src/backend/llvm/README.md b/src/backend/llvm/README.md new file mode 100644 index 00000000..654e4b9a --- /dev/null +++ b/src/backend/llvm/README.md @@ -0,0 +1,11 @@ +# ⚠️ DEPRECATED: Legacy Rust/inkwell LLVM backend + +This directory contains the historical Rust/inkwell-based LLVM backend. + +- Development focus has shifted to the Python/llvmlite backend under `src/llvm_py/`. +- Keep this code for reference only. Do not extend or modify for current tasks. +- The LLVM build pipeline now prefers the llvmlite harness (`NYASH_LLVM_USE_HARNESS=1`). + +If you need to reduce build time locally, consider using the harness path and +building only the core crates. See `tools/build_llvm.sh` and `tools/compare_harness_on_off.sh`. + diff --git a/src/backend/llvm/box_types.rs b/src/backend/llvm/box_types.rs deleted file mode 100644 index 66b139ae..00000000 --- a/src/backend/llvm/box_types.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::collections::HashMap as StdHashMap; - -/// Load box type-id mapping from `nyash_box.toml`. -pub fn load_box_type_ids() -> StdHashMap { - let mut map = StdHashMap::new(); - if let Ok(cfg) = std::fs::read_to_string("nyash_box.toml") { - if let Ok(doc) = toml::from_str::(&cfg) { - if let Some(table) = doc.as_table() { - for (box_name, box_val) in table { - if let Some(id) = box_val.get("type_id").and_then(|v| v.as_integer()) { - map.insert(box_name.clone(), id as i64); - } - } - } - } - } - map -} diff --git a/src/backend/llvm/compiler/aot.rs b/src/backend/llvm/compiler/aot.rs deleted file mode 100644 index 6ec1a3bc..00000000 --- a/src/backend/llvm/compiler/aot.rs +++ /dev/null @@ -1,15 +0,0 @@ -use super::LLVMCompiler; -use crate::box_trait::NyashBox; -use crate::mir::function::MirModule; - -impl LLVMCompiler { - pub fn compile_and_execute( - &mut self, - mir_module: &MirModule, - temp_path: &str, - ) -> Result, String> { - let obj_path = format!("{}.o", temp_path); - self.compile_module(mir_module, &obj_path)?; - self.run_interpreter(mir_module) - } -} diff --git a/src/backend/llvm/compiler/helpers.rs b/src/backend/llvm/compiler/helpers.rs deleted file mode 100644 index 7e681da2..00000000 --- a/src/backend/llvm/compiler/helpers.rs +++ /dev/null @@ -1,214 +0,0 @@ -use inkwell::builder::Builder; -use inkwell::context::Context; -use inkwell::types::BasicTypeEnum; -use inkwell::values::{BasicValueEnum, FloatValue, IntValue, PointerValue}; -use inkwell::AddressSpace; - -use crate::mir::MirType; - -use crate::mir::CompareOp; -pub(crate) fn map_type<'ctx>( - ctx: &'ctx Context, - ty: &MirType, -) -> Result, String> { - Ok(match ty { - MirType::Integer => ctx.i64_type().into(), - MirType::Float => ctx.f64_type().into(), - MirType::Bool => ctx.bool_type().into(), - MirType::String => ctx - .ptr_type(inkwell::AddressSpace::from(0)) - .into(), - MirType::Void => return Err("Void has no value type".to_string()), - MirType::Box(_) => ctx - .ptr_type(inkwell::AddressSpace::from(0)) - .into(), - MirType::Array(_) | MirType::Future(_) | MirType::Unknown => ctx - .ptr_type(inkwell::AddressSpace::from(0)) - .into(), - }) -} - -pub(crate) fn as_int<'ctx>(v: BasicValueEnum<'ctx>) -> Option> { - if let BasicValueEnum::IntValue(iv) = v { - Some(iv) - } else { - None - } -} - -pub(crate) fn as_float<'ctx>(v: BasicValueEnum<'ctx>) -> Option> { - if let BasicValueEnum::FloatValue(fv) = v { - Some(fv) - } else { - None - } -} - -pub(crate) fn to_i64_any<'ctx>( - ctx: &'ctx Context, - builder: &Builder<'ctx>, - v: BasicValueEnum<'ctx>, -) -> Result, String> { - let i64t = ctx.i64_type(); - Ok(match v { - BasicValueEnum::IntValue(iv) => { - if iv.get_type().get_bit_width() == 64 { - iv - } else if iv.get_type().get_bit_width() < 64 { - builder - .build_int_z_extend(iv, i64t, "zext_i64") - .map_err(|e| e.to_string())? - } else { - builder - .build_int_truncate(iv, i64t, "trunc_i64") - .map_err(|e| e.to_string())? - } - } - BasicValueEnum::PointerValue(pv) => builder - .build_ptr_to_int(pv, i64t, "p2i64") - .map_err(|e| e.to_string())?, - BasicValueEnum::FloatValue(fv) => { - // Bitcast f64 -> i64 via stack slot - let slot = builder - .get_insert_block() - .and_then(|bb| bb.get_parent()) - .and_then(|f| f.get_first_basic_block()) - .map(|entry| { - let eb = ctx.create_builder(); - eb.position_at_end(entry); - eb - }) - .unwrap_or_else(|| ctx.create_builder()); - let tmp = slot - .build_alloca(i64t, "f2i_tmp") - .map_err(|e| e.to_string())?; - let fptr_ty = ctx.ptr_type(AddressSpace::from(0)); - let castp = builder - .build_pointer_cast(tmp, fptr_ty, "i64p_to_f64p") - .map_err(|e| e.to_string())?; - builder.build_store(castp, fv).map_err(|e| e.to_string())?; - builder - .build_load(i64t, tmp, "ld_f2i") - .map_err(|e| e.to_string())? - .into_int_value() - } - _ => return Err("unsupported value for i64 conversion".to_string()), - }) -} - -pub(crate) fn i64_to_ptr<'ctx>( - ctx: &'ctx Context, - builder: &Builder<'ctx>, - iv: IntValue<'ctx>, -) -> Result, String> { - let pty = ctx.ptr_type(AddressSpace::from(0)); - builder - .build_int_to_ptr(iv, pty, "i64_to_ptr") - .map_err(|e| e.to_string()) -} - -pub(crate) fn classify_tag<'ctx>(v: BasicValueEnum<'ctx>) -> i64 { - match v { - BasicValueEnum::FloatValue(_) => 5, // float - BasicValueEnum::PointerValue(_) => 8, // handle/ptr - BasicValueEnum::IntValue(_) => 3, // integer/bool - _ => 0, - } -} - -pub(crate) fn to_bool<'ctx>( - ctx: &'ctx Context, - b: BasicValueEnum<'ctx>, - builder: &Builder<'ctx>, -) -> Result, String> { - if let Some(bb) = as_int(b) { - if bb.get_type().get_bit_width() == 1 { - Ok(bb) - } else { - Ok(builder - .build_int_compare( - inkwell::IntPredicate::NE, - bb, - bb.get_type().const_zero(), - "tobool", - ) - .map_err(|e| e.to_string())?) - } - } else if let Some(fv) = as_float(b) { - let zero = fv.get_type().const_float(0.0); - Ok(builder - .build_float_compare(inkwell::FloatPredicate::ONE, fv, zero, "toboolf") - .map_err(|e| e.to_string())?) - } else if let BasicValueEnum::PointerValue(pv) = b { - let i64t = ctx.i64_type(); - let p2i = builder - .build_ptr_to_int(pv, i64t, "p2i") - .map_err(|e| e.to_string())?; - Ok(builder - .build_int_compare(inkwell::IntPredicate::NE, p2i, i64t.const_zero(), "toboolp") - .map_err(|e| e.to_string())?) - } else { - Err("Unsupported value for boolean conversion".to_string()) - } -} - -pub(crate) fn cmp_eq_ne_any<'ctx>( - ctx: &'ctx Context, - builder: &Builder<'ctx>, - op: &CompareOp, - lv: BasicValueEnum<'ctx>, - rv: BasicValueEnum<'ctx>, -) -> Result, String> { - use crate::mir::CompareOp as C; - match (lv, rv) { - (BasicValueEnum::IntValue(li), BasicValueEnum::IntValue(ri)) => { - let pred = if matches!(op, C::Eq) { - inkwell::IntPredicate::EQ - } else { - inkwell::IntPredicate::NE - }; - Ok(builder - .build_int_compare(pred, li, ri, "icmp") - .map_err(|e| e.to_string())? - .into()) - } - (BasicValueEnum::FloatValue(lf), BasicValueEnum::FloatValue(rf)) => { - let pred = if matches!(op, C::Eq) { - inkwell::FloatPredicate::OEQ - } else { - inkwell::FloatPredicate::ONE - }; - Ok(builder - .build_float_compare(pred, lf, rf, "fcmp") - .map_err(|e| e.to_string())? - .into()) - } - (BasicValueEnum::PointerValue(_), _) | (_, BasicValueEnum::PointerValue(_)) => { - let li = to_i64_any(ctx, builder, lv)?; - let ri = to_i64_any(ctx, builder, rv)?; - let pred = if matches!(op, C::Eq) { - inkwell::IntPredicate::EQ - } else { - inkwell::IntPredicate::NE - }; - Ok(builder - .build_int_compare(pred, li, ri, "pcmp_any") - .map_err(|e| e.to_string())? - .into()) - } - _ => Err("compare type mismatch".to_string()), - } -} - -pub(crate) fn map_mirtype_to_basic<'ctx>(ctx: &'ctx Context, ty: &MirType) -> BasicTypeEnum<'ctx> { - match ty { - MirType::Integer => ctx.i64_type().into(), - MirType::Float => ctx.f64_type().into(), - MirType::Bool => ctx.bool_type().into(), - MirType::String => ctx.ptr_type(AddressSpace::from(0)).into(), - MirType::Box(_) | MirType::Array(_) | MirType::Future(_) | MirType::Unknown => { - ctx.ptr_type(AddressSpace::from(0)).into() - } - MirType::Void => ctx.i64_type().into(), - } -} diff --git a/src/backend/llvm/compiler/mock.rs b/src/backend/llvm/compiler/mock.rs deleted file mode 100644 index c6896709..00000000 --- a/src/backend/llvm/compiler/mock.rs +++ /dev/null @@ -1,8 +0,0 @@ -use super::LLVMCompiler; -use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox}; -use crate::boxes::{function_box::FunctionBox, math_box::FloatBox, null_box::NullBox}; -use crate::mir::function::MirModule; -use crate::mir::instruction::{BinaryOp, ConstValue, MirInstruction}; -use std::collections::HashMap; - -include!("mock_impl.in.rs"); diff --git a/src/backend/llvm/compiler/mock_impl.in.rs b/src/backend/llvm/compiler/mock_impl.in.rs deleted file mode 100644 index 63970191..00000000 --- a/src/backend/llvm/compiler/mock_impl.in.rs +++ /dev/null @@ -1,251 +0,0 @@ -impl LLVMCompiler { - pub fn new() -> Result { - Ok(Self { - values: HashMap::new(), - }) - } - - pub fn compile_module(&self, mir_module: &MirModule, output_path: &str) -> Result<(), String> { - // Mock implementation - in a real scenario this would: - // 1. Create LLVM context and module - // 2. Convert MIR instructions to LLVM IR - // 3. Generate object file - - println!("🔧 Mock LLVM Compilation:"); - println!(" Module: {}", mir_module.name); - println!(" Functions: {}", mir_module.functions.len()); - println!(" Output: {}", output_path); - - // Find entry function (prefer is_entry_point, then Main.main, then main, else first) - let main_func = if let Some((_n, f)) = mir_module - .functions - .iter() - .find(|(_n, f)| f.metadata.is_entry_point) - { - f - } else if let Some(f) = mir_module.functions.get("Main.main") { - f - } else if let Some(f) = mir_module.functions.get("main") { - f - } else if let Some((_n, f)) = mir_module.functions.iter().next() { - f - } else { - return Err("Main.main function not found"); - }; - - println!( - " Main function found with {} blocks", - main_func.blocks.len() - ); - - // Simulate object file generation - std::fs::write(output_path, b"Mock object file")?; - println!(" ✅ Mock object file created"); - - Ok(()) - } - - pub fn compile_and_execute( - &mut self, - mir_module: &MirModule, - temp_path: &str, - ) -> Result, String> { - // Mock implementation - interprets MIR instructions to simulate execution - - eprintln!("⚠️⚠️⚠️ WARNING: Using MOCK LLVM Implementation! ⚠️⚠️⚠️"); - eprintln!("⚠️ This is NOT real LLVM execution!"); - eprintln!("⚠️ Build with --features llvm for real compilation!"); - println!("🚀 Mock LLVM Compile & Execute (MIR Interpreter Mode):"); - - // 1. Mock object file generation - let obj_path = format!("{}.o", temp_path); - self.compile_module(mir_module, &obj_path)?; - - // 2. Find and execute main function - let main_func = mir_module - .functions - .get("Main.main") - .ok_or("Main.main function not found")?; - - println!(" ⚡ Interpreting MIR instructions..."); - - // 3. Execute MIR instructions - let result = self.interpret_function(main_func)?; - - // 4. Cleanup mock files - let _ = std::fs::remove_file(&obj_path); - - Ok(result) - } - - /// Interpret a MIR function by executing its instructions - fn interpret_function( - &mut self, - func: &crate::mir::function::MirFunction, - ) -> Result, String> { - // Clear value storage - self.values.clear(); - - // For now, just execute the entry block - if let Some(entry_block) = func.blocks.get(&0) { - for inst in &entry_block.instructions { - match inst { - MirInstruction::Const { dst, value } => { - let nyash_value = match value { - ConstValue::Integer(i) => { - Box::new(IntegerBox::new(*i)) as Box - } - ConstValue::Float(f) => { - Box::new(FloatBox::new(*f)) as Box - } - ConstValue::String(s) => { - Box::new(StringBox::new(s.clone())) as Box - } - ConstValue::Bool(b) => Box::new(BoolBox::new(*b)) as Box, - ConstValue::Null => Box::new(NullBox::new()) as Box, - }; - self.values.insert(*dst, nyash_value); - println!(" 📝 %{} = const {:?}", dst.0, value); - } - - MirInstruction::BinOp { dst, op, lhs, rhs } => { - // Get operands - let left = self - .values - .get(lhs) - .ok_or_else(|| format!("Value %{} not found", lhs.0))?; - let right = self - .values - .get(rhs) - .ok_or_else(|| format!("Value %{} not found", rhs.0))?; - - // Simple integer arithmetic for now - if let (Some(l), Some(r)) = ( - left.as_any().downcast_ref::(), - right.as_any().downcast_ref::(), - ) { - let result = match op { - BinaryOp::Add => l.value + r.value, - BinaryOp::Sub => l.value - r.value, - BinaryOp::Mul => l.value * r.value, - BinaryOp::Div => { - if r.value == 0 { - return Err("Division by zero".to_string()); - } - l.value / r.value - } - BinaryOp::Mod => l.value % r.value, - _ => { - return Err( - "Binary operation not supported in mock".to_string() - ); - } - }; - self.values.insert(*dst, Box::new(IntegerBox::new(result))); - println!( - " 📊 %{} = %{} {:?} %{} = {}", - dst.0, lhs.0, op, rhs.0, result - ); - } else { - return Err( - "Binary operation on non-integer values not supported in mock" - .to_string(), - ); - } - } - - MirInstruction::Return { value } => { - if let Some(val_id) = value { - let result = self - .values - .get(val_id) - .ok_or_else(|| format!("Return value %{} not found", val_id.0))? - .clone_box(); - println!(" ✅ Returning value from %{}", val_id.0); - return Ok(result); - } else { - println!(" ✅ Void return"); - return Ok(Box::new(IntegerBox::new(0))); - } - } - - MirInstruction::FunctionNew { - dst, - params, - body, - captures, - me, - } => { - // Minimal: build FunctionBox with empty captures unless provided - let mut env = crate::boxes::function_box::ClosureEnv::new(); - // Materialize captures (by value) if any - for (name, vid) in captures.iter() { - let v = self.values.get(vid).ok_or_else(|| { - format!("Value %{} not found for capture {}", vid.0, name) - })?; - env.captures.insert(name.clone(), v.clone_box()); - } - // me capture (weak) if provided and is a box - if let Some(m) = me { - if let Some(b) = self.values.get(m) { - if let Some(arc) = std::sync::Arc::downcast::({ - let bx: std::sync::Arc = - std::sync::Arc::from(b.clone_box()); - bx - }) - .ok() - { - env.me_value = Some(std::sync::Arc::downgrade(&arc)); - } - } - } - let fun = FunctionBox::with_env(params.clone(), body.clone(), env); - self.values.insert(*dst, Box::new(fun)); - println!(" 🧰 %{} = function_new (params={})", dst.0, params.len()); - } - - MirInstruction::Call { - dst, func, args, .. - } => { - // Resolve callee - let cal = self - .values - .get(func) - .ok_or_else(|| format!("Call target %{} not found", func.0))?; - if let Some(fb) = cal.as_any().downcast_ref::() { - // Collect args as NyashBox - let mut argv: Vec> = Vec::new(); - for a in args { - let av = self - .values - .get(a) - .ok_or_else(|| format!("Arg %{} not found", a.0))?; - argv.push(av.clone_box()); - } - let out = crate::interpreter::run_function_box(fb, argv) - .map_err(|e| format!("FunctionBox call failed: {:?}", e))?; - if let Some(d) = dst { - self.values.insert(*d, out); - } - println!( - " 📞 call %{} -> {}", - func.0, - dst.map(|v| v.0).unwrap_or(u32::MAX) - ); - } else { - println!(" ⚠️ Skipping call: callee not FunctionBox"); - } - } - - _ => { - // Other instructions not yet implemented - println!(" ⚠️ Skipping instruction: {:?}", inst); - } - } - } - } - - // Default return - Ok(Box::new(IntegerBox::new(0))) - } -} diff --git a/src/backend/llvm/mod.rs b/src/backend/llvm/mod.rs index 0df64c73..0c4b28fd 100644 --- a/src/backend/llvm/mod.rs +++ b/src/backend/llvm/mod.rs @@ -1,40 +1,16 @@ -/*! - * LLVM Backend Module - Compile MIR to LLVM IR for AOT execution - * - * This module provides LLVM-based compilation of Nyash MIR to native code. - * Phase 9.78 PoC implementation focused on minimal "return 42" support. - */ +//! Deprecated shim module for legacy Rust/inkwell backend +//! Please use `crate::backend::llvm_legacy` directly. This module re-exports +//! items to keep old paths working until full removal. -pub mod box_types; -pub mod compiler; -pub mod context; +pub use crate::backend::llvm_legacy::{compile_and_execute, compile_to_object}; -use crate::box_trait::{IntegerBox, NyashBox}; -use crate::mir::function::MirModule; - -/// Compile MIR module to object file and execute -pub fn compile_and_execute( - mir_module: &MirModule, - output_path: &str, -) -> Result, String> { - let mut compiler = compiler::LLVMCompiler::new()?; - compiler.compile_and_execute(mir_module, output_path) +pub mod context { + pub use crate::backend::llvm_legacy::context::*; +} +pub mod compiler { + pub use crate::backend::llvm_legacy::compiler::*; +} +pub mod box_types { + pub use crate::backend::llvm_legacy::box_types::*; } -/// Compile MIR module to object file only -pub fn compile_to_object(mir_module: &MirModule, output_path: &str) -> Result<(), String> { - let compiler = compiler::LLVMCompiler::new()?; - compiler.compile_module(mir_module, output_path) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_llvm_module_creation() { - // Basic test to ensure the module can be loaded - // Actual compilation tests require full MIR infrastructure - assert!(true); - } -} diff --git a/src/backend/llvm_legacy/README.md b/src/backend/llvm_legacy/README.md new file mode 100644 index 00000000..c62f3689 --- /dev/null +++ b/src/backend/llvm_legacy/README.md @@ -0,0 +1,8 @@ +# Legacy Rust/inkwell LLVM backend + +This directory holds the historical LLVM backend implemented in Rust with inkwell. + +- Status: DEPRECATED — kept for reference. +- Current primary LLVM path is Python/llvmlite under `src/llvm_py/`. +- Cargo feature to enable this backend: `llvm-inkwell-legacy`. + diff --git a/src/backend/llvm_legacy/box_types.rs b/src/backend/llvm_legacy/box_types.rs new file mode 100644 index 00000000..b34eeda9 --- /dev/null +++ b/src/backend/llvm_legacy/box_types.rs @@ -0,0 +1,6 @@ +// legacy box type id helpers placeholder; refer to archived implementation if needed + +pub fn load_box_type_ids() -> std::collections::HashMap { + std::collections::HashMap::new() +} + diff --git a/src/backend/llvm_legacy/compiler/aot.rs b/src/backend/llvm_legacy/compiler/aot.rs new file mode 100644 index 00000000..5ca8b337 --- /dev/null +++ b/src/backend/llvm_legacy/compiler/aot.rs @@ -0,0 +1,2 @@ +// legacy aot placeholder; full implementation retained in archived branch or prior history + diff --git a/src/backend/llvm_legacy/compiler/helpers.rs b/src/backend/llvm_legacy/compiler/helpers.rs new file mode 100644 index 00000000..e477633f --- /dev/null +++ b/src/backend/llvm_legacy/compiler/helpers.rs @@ -0,0 +1,2 @@ +// legacy helpers placeholder; kept to satisfy module structure after move + diff --git a/src/backend/llvm_legacy/compiler/mock.rs b/src/backend/llvm_legacy/compiler/mock.rs new file mode 100644 index 00000000..00c413b9 --- /dev/null +++ b/src/backend/llvm_legacy/compiler/mock.rs @@ -0,0 +1,23 @@ +use crate::mir::function::MirModule; +use crate::box_trait::{NyashBox, IntegerBox}; +use std::collections::HashMap; + +pub struct LLVMCompiler { + values: HashMap>, +} + +impl LLVMCompiler { + pub fn new() -> Result { + Ok(Self { values: HashMap::new() }) + } + + pub fn compile_module(&self, _mir: &MirModule, _out: &str) -> Result<(), String> { + // Mock: pretend emitted + Ok(()) + } + + pub fn compile_and_execute(&mut self, _mir: &MirModule, _out: &str) -> Result, String> { + Ok(Box::new(IntegerBox::new(0))) + } +} + diff --git a/src/backend/llvm/compiler/mod.rs b/src/backend/llvm_legacy/compiler/mod.rs similarity index 57% rename from src/backend/llvm/compiler/mod.rs rename to src/backend/llvm_legacy/compiler/mod.rs index 1add9ea8..a55fd819 100644 --- a/src/backend/llvm/compiler/mod.rs +++ b/src/backend/llvm_legacy/compiler/mod.rs @@ -6,20 +6,20 @@ pub struct LLVMCompiler { values: HashMap>, } -#[cfg(not(feature = "llvm"))] +#[cfg(not(feature = "llvm-inkwell-legacy"))] mod mock; -#[cfg(not(feature = "llvm"))] +#[cfg(not(feature = "llvm-inkwell-legacy"))] pub use mock::*; -#[cfg(feature = "llvm")] +#[cfg(feature = "llvm-inkwell-legacy")] mod aot; -#[cfg(feature = "llvm")] +#[cfg(feature = "llvm-inkwell-legacy")] mod codegen; -#[cfg(feature = "llvm")] +#[cfg(feature = "llvm-inkwell-legacy")] mod helpers; -#[cfg(feature = "llvm")] +#[cfg(feature = "llvm-inkwell-legacy")] mod interpreter; -#[cfg(feature = "llvm")] +#[cfg(feature = "llvm-inkwell-legacy")] pub use aot::*; #[cfg(test)] @@ -31,3 +31,4 @@ mod tests { assert!(true); } } + diff --git a/src/backend/llvm/context.rs b/src/backend/llvm_legacy/context.rs similarity index 61% rename from src/backend/llvm/context.rs rename to src/backend/llvm_legacy/context.rs index fea4263e..c2866d22 100644 --- a/src/backend/llvm/context.rs +++ b/src/backend/llvm_legacy/context.rs @@ -1,34 +1,31 @@ /*! - * LLVM Context Management - Handle LLVM context, module, and target setup + * LLVM Context Management - Handle LLVM context, module, and target setup (legacy) */ -/// Mock implementation for environments without LLVM development libraries -/// This demonstrates the structure needed for LLVM integration -#[cfg(not(feature = "llvm"))] +/// Mock implementation when legacy inkwell backend is disabled +#[cfg(not(feature = "llvm-inkwell-legacy"))] pub struct CodegenContext { _phantom: std::marker::PhantomData<()>, } -#[cfg(not(feature = "llvm"))] +#[cfg(not(feature = "llvm-inkwell-legacy"))] impl CodegenContext { pub fn new(_module_name: &str) -> Result { - Ok(Self { - _phantom: std::marker::PhantomData, - }) + Ok(Self { _phantom: std::marker::PhantomData }) } } -// Real implementation (compiled only when feature "llvm" is enabled) -#[cfg(feature = "llvm")] +// Real implementation (compiled only when feature "llvm-inkwell-legacy" is enabled) +#[cfg(feature = "llvm-inkwell-legacy")] use inkwell::context::Context; -#[cfg(feature = "llvm")] +#[cfg(feature = "llvm-inkwell-legacy")] use inkwell::module::Module; -#[cfg(feature = "llvm")] +#[cfg(feature = "llvm-inkwell-legacy")] use inkwell::builder::Builder; -#[cfg(feature = "llvm")] +#[cfg(feature = "llvm-inkwell-legacy")] use inkwell::targets::{Target, TargetMachine, InitializationConfig}; -#[cfg(feature = "llvm")] +#[cfg(feature = "llvm-inkwell-legacy")] pub struct CodegenContext<'ctx> { pub context: &'ctx Context, pub module: Module<'ctx>, @@ -36,17 +33,12 @@ pub struct CodegenContext<'ctx> { pub target_machine: TargetMachine, } -#[cfg(feature = "llvm")] +#[cfg(feature = "llvm-inkwell-legacy")] impl<'ctx> CodegenContext<'ctx> { pub fn new(context: &'ctx Context, module_name: &str) -> Result { - // 1. Initialize native target Target::initialize_native(&InitializationConfig::default()) .map_err(|e| format!("Failed to initialize native target: {}", e))?; - - // 2. Create module let module = context.create_module(module_name); - - // 3. Create target machine let triple = TargetMachine::get_default_triple(); let target = Target::from_triple(&triple) .map_err(|e| format!("Failed to get target: {}", e))?; @@ -60,16 +52,8 @@ impl<'ctx> CodegenContext<'ctx> { inkwell::targets::CodeModel::Default, ) .ok_or_else(|| "Failed to create target machine".to_string())?; - - // 4. Set data layout - module.set_triple(&triple); - module.set_data_layout(&target_machine.get_target_data().get_data_layout()); - - Ok(Self { - context, - module, - builder: context.create_builder(), - target_machine, - }) + let builder = context.create_builder(); + Ok(Self { context, module, builder, target_machine }) } } + diff --git a/src/backend/llvm_legacy/mod.rs b/src/backend/llvm_legacy/mod.rs new file mode 100644 index 00000000..87902109 --- /dev/null +++ b/src/backend/llvm_legacy/mod.rs @@ -0,0 +1,37 @@ +/*! + * LLVM Backend Module (legacy, inkwell) - Compile MIR to LLVM IR for AOT execution + * + * This module provides LLVM-based compilation of Nyash MIR to native code. + * Phase 9.78 PoC implementation focused on minimal support. + */ + +pub mod box_types; +pub mod compiler; +pub mod context; + +use crate::box_trait::NyashBox; +use crate::mir::function::MirModule; + +/// Compile MIR module to object file and execute +pub fn compile_and_execute( + mir_module: &MirModule, + output_path: &str, +) -> Result, String> { + let mut compiler = compiler::LLVMCompiler::new()?; + compiler.compile_and_execute(mir_module, output_path) +} + +/// Compile MIR module to object file only +pub fn compile_to_object(mir_module: &MirModule, output_path: &str) -> Result<(), String> { + let compiler = compiler::LLVMCompiler::new()?; + compiler.compile_module(mir_module, output_path) +} + +#[cfg(test)] +mod tests { + #[test] + fn test_llvm_module_creation() { + assert!(true); + } +} + diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 13da7910..550bcfaa 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -29,7 +29,10 @@ pub mod aot; #[cfg(feature = "wasm-backend")] pub mod wasm_v2; -#[cfg(feature = "llvm")] +#[cfg(feature = "llvm-inkwell-legacy")] +pub mod llvm_legacy; +// Back-compat shim so existing paths crate::backend::llvm::* keep working +#[cfg(feature = "llvm-inkwell-legacy")] pub mod llvm; #[cfg(feature = "cranelift-jit")] pub mod cranelift; @@ -42,7 +45,7 @@ pub use wasm::{WasmBackend, WasmError}; #[cfg(feature = "wasm-backend")] pub use aot::{AotBackend, AotError, AotConfig, AotStats}; -#[cfg(feature = "llvm")] -pub use llvm::{compile_and_execute as llvm_compile_and_execute, compile_to_object as llvm_compile_to_object}; +#[cfg(feature = "llvm-inkwell-legacy")] +pub use llvm_legacy::{compile_and_execute as llvm_compile_and_execute, compile_to_object as llvm_compile_to_object}; #[cfg(feature = "cranelift-jit")] pub use cranelift::{compile_and_execute as cranelift_compile_and_execute, compile_to_object as cranelift_compile_to_object}; diff --git a/src/llvm_py/instructions/binop.py b/src/llvm_py/instructions/binop.py index 33435267..422f80cd 100644 --- a/src/llvm_py/instructions/binop.py +++ b/src/llvm_py/instructions/binop.py @@ -62,7 +62,8 @@ def lower_binop( return # String-aware concatenation unified to handles (i64). - # Use concat_hh when either side is a pointer string OR tagged as string handle. + # Use concat_hh when either side is a pointer string OR either side is tagged as string handle + # (including literal strings and PHI-propagated tags). if op == '+': i64 = ir.IntType(64) i8p = ir.IntType(8).as_pointer() @@ -71,14 +72,18 @@ def lower_binop( # pointer present? is_ptr_side = (hasattr(lhs_raw, 'type') and isinstance(lhs_raw.type, ir.PointerType)) or \ (hasattr(rhs_raw, 'type') and isinstance(rhs_raw.type, ir.PointerType)) - # tagged string handles?(両辺ともに string-ish のときのみ) - both_tagged = False + # tagged string handles?(どちらかが string-ish のとき) + any_tagged = False try: - if resolver is not None and hasattr(resolver, 'is_stringish'): - both_tagged = resolver.is_stringish(lhs) and resolver.is_stringish(rhs) + if resolver is not None: + if hasattr(resolver, 'is_stringish'): + any_tagged = resolver.is_stringish(lhs) or resolver.is_stringish(rhs) + # literal strings are tracked separately + if not any_tagged and hasattr(resolver, 'string_literals'): + any_tagged = (lhs in resolver.string_literals) or (rhs in resolver.string_literals) except Exception: pass - is_str = is_ptr_side or both_tagged + is_str = is_ptr_side or any_tagged if is_str: # Helper: convert raw or resolved value to string handle def to_handle(raw, val, tag: str): diff --git a/src/llvm_py/instructions/boxcall.py b/src/llvm_py/instructions/boxcall.py index e11f7044..ba3d4bc8 100644 --- a/src/llvm_py/instructions/boxcall.py +++ b/src/llvm_py/instructions/boxcall.py @@ -205,6 +205,11 @@ def lower_boxcall( arg0 = ir.Constant(i8p, None) # Prefer handle API if arg is i64, else pointer API if hasattr(arg0, 'type') and isinstance(arg0.type, ir.IntType) and arg0.type.width == 64: + # Optional runtime trace of the handle + import os as _os + if _os.environ.get('NYASH_LLVM_TRACE_FINAL') == '1': + trace = _declare(module, "nyash.debug.trace_handle", i64, [i64]) + _ = builder.call(trace, [arg0], name="trace_handle") callee = _declare(module, "nyash.console.log_handle", i64, [i64]) _ = builder.call(callee, [arg0], name="console_log_h") else: @@ -221,8 +226,19 @@ def lower_boxcall( cur_fn_name = str(builder.block.parent.name) except Exception: cur_fn_name = '' - # Heuristic: value-id 0 is often the implicit receiver for `me` in MIR - if box_vid == 0 and cur_fn_name.startswith('Main.'): + # Heuristic: MIR encodes `me` as a string literal "__me__" or sometimes value-id 0. + is_me = False + try: + if box_vid == 0: + is_me = True + # Prefer literal marker captured by resolver (from const lowering) + elif resolver is not None and hasattr(resolver, 'string_literals'): + lit = resolver.string_literals.get(box_vid) + if lit == "__me__": + is_me = True + except Exception: + pass + if is_me and cur_fn_name.startswith('Main.'): # Build target function name with arity arity = len(args) target = f"Main.{method_name}/{arity}" @@ -300,3 +316,9 @@ def lower_boxcall( result = builder.call(callee, [recv_h, mptr, argc, a1, a2], name="pinvoke_by_name") if dst_vid is not None: vmap[dst_vid] = result + # Heuristic tagging: common plugin methods returning strings + try: + if resolver is not None and hasattr(resolver, 'mark_string') and method_name in ("read", "dirname", "join"): + resolver.mark_string(dst_vid) + except Exception: + pass diff --git a/src/llvm_py/instructions/const.py b/src/llvm_py/instructions/const.py index 4cbaeaf7..75bcab9b 100644 --- a/src/llvm_py/instructions/const.py +++ b/src/llvm_py/instructions/const.py @@ -40,7 +40,7 @@ def lower_const( vmap[dst] = llvm_val elif const_type == 'string': - # String constant - create global, store GlobalVariable (not GEP) to avoid dominance issues + # String constant - create global and immediately box to i64 handle i8 = ir.IntType(8) str_val = str(const_val) str_bytes = str_val.encode('utf-8') + b'\0' @@ -61,8 +61,21 @@ def lower_const( g.initializer = str_const g.linkage = 'private' g.global_constant = True - # Store the GlobalVariable; resolver.resolve_ptr will emit GEP in the current block - vmap[dst] = g + # GEP to first element and box to handle immediately + i32 = ir.IntType(32) + c0 = ir.Constant(i32, 0) + gep = builder.gep(g, [c0, c0], inbounds=True) + i8p = i8.as_pointer() + boxer_ty = ir.FunctionType(ir.IntType(64), [i8p]) + boxer = None + for f in module.functions: + if f.name == 'nyash.box.from_i8_string': + boxer = f + break + if boxer is None: + boxer = ir.Function(module, boxer_ty, name='nyash.box.from_i8_string') + handle = builder.call(boxer, [gep], name=f"const_str_h_{dst}") + vmap[dst] = handle if resolver is not None: if hasattr(resolver, 'string_literals'): resolver.string_literals[dst] = str_val diff --git a/src/llvm_py/instructions/phi.py b/src/llvm_py/instructions/phi.py index a8d205db..905055d8 100644 --- a/src/llvm_py/instructions/phi.py +++ b/src/llvm_py/instructions/phi.py @@ -73,6 +73,9 @@ def lower_phi( val = None except Exception: val = None + if val is None: + # Missing incoming for this predecessor → default 0 + val = ir.Constant(phi_type, 0) else: # Snapshot fallback if block_end_values is not None: @@ -124,6 +127,18 @@ def lower_phi( # Store PHI result vmap[dst_vid] = phi + # Propagate string-ness: if any incoming value-id is tagged string-ish, mark dst as string-ish. + try: + if resolver is not None and hasattr(resolver, 'is_stringish') and hasattr(resolver, 'mark_string'): + for val_id, _b in incoming: + try: + if resolver.is_stringish(val_id): + resolver.mark_string(dst_vid) + break + except Exception: + pass + except Exception: + pass def defer_phi_wiring( dst_vid: int, diff --git a/src/llvm_py/llvm_builder.py b/src/llvm_py/llvm_builder.py index c125957f..b8744dc3 100644 --- a/src/llvm_py/llvm_builder.py +++ b/src/llvm_py/llvm_builder.py @@ -59,6 +59,9 @@ class NyashLLVMBuilder: # Predecessor map and per-block end snapshots self.preds: Dict[int, List[int]] = {} self.block_end_values: Dict[int, Dict[int, ir.Value]] = {} + # Definition map: value_id -> set(block_id) where the value is defined + # Used as a lightweight lifetime hint to avoid over-localization + self.def_blocks: Dict[int, set] = {} # Resolver for unified value resolution self.resolver = Resolver(self.vmap, self.bb_map) @@ -270,6 +273,12 @@ class NyashLLVMBuilder: continue bb = self.bb_map[bid] self.lower_block(bb, block_data, func) + + # Provide lifetime hints to resolver (which blocks define which values) + try: + self.resolver.def_blocks = self.def_blocks + except Exception: + pass def lower_block(self, bb: ir.Block, block_data: Dict[str, Any], func: ir.Function): """Lower a single basic block""" @@ -297,29 +306,16 @@ class NyashLLVMBuilder: created_ids.append(dst) except Exception: pass - # Lower non-PHI instructions in a coarse dependency-friendly order - # (ensure producers like newbox/const appear before consumers like boxcall/externcall) - order = { - 'newbox': 0, - 'const': 1, - 'typeop': 2, - 'load': 3, - 'store': 3, - 'binop': 4, - 'compare': 5, - 'call': 6, - 'boxcall': 6, - 'externcall': 7, - 'safepoint': 8, - 'barrier': 8, - 'while': 8, - 'jump': 9, - 'branch': 9, - 'ret': 10, - } - non_phi_insts_sorted = sorted(non_phi_insts, key=lambda i: order.get(i.get('op'), 100)) - for inst in non_phi_insts_sorted: - # Append in program order to preserve dominance; avoid re-inserting before a terminator here + # Lower non-PHI instructions strictly in original program order. + # Reordering here can easily introduce use-before-def within the same + # basic block (e.g., string ops that depend on prior me.* calls). + for inst in non_phi_insts: + # Stop if a terminator has already been emitted for this block + try: + if bb.terminator is not None: + break + except Exception: + pass builder.position_at_end(bb) self.lower_instruction(builder, inst, func) try: @@ -343,6 +339,8 @@ class NyashLLVMBuilder: val = self.vmap.get(vid) if val is not None: snap[vid] = val + # Record block-local definition for lifetime hinting + self.def_blocks.setdefault(vid, set()).add(block_data.get("id", 0)) self.block_end_values[bid] = snap def lower_instruction(self, builder: ir.IRBuilder, inst: Dict[str, Any], func: ir.Function): @@ -451,6 +449,19 @@ class NyashLLVMBuilder: else: if os.environ.get('NYASH_CLI_VERBOSE') == '1': print(f"[Python LLVM] Unknown instruction: {op}") + # Record per-inst definition for lifetime hinting as soon as available + try: + dst_maybe = inst.get("dst") + if isinstance(dst_maybe, int) and dst_maybe in self.vmap: + cur_bid = None + try: + cur_bid = int(str(builder.block.name).replace('bb','')) + except Exception: + pass + if cur_bid is not None: + self.def_blocks.setdefault(dst_maybe, set()).add(cur_bid) + except Exception: + pass def _lower_while_regular(self, builder: ir.IRBuilder, inst: Dict[str, Any], func: ir.Function): """Fallback regular while lowering""" @@ -596,7 +607,9 @@ class NyashLLVMBuilder: # Compile mod = llvm.parse_assembly(str(self.module)) - mod.verify() + # Allow skipping verifier for iterative bring-up + if os.environ.get('NYASH_LLVM_SKIP_VERIFY') != '1': + mod.verify() # Generate object code obj = target_machine.emit_object(mod) diff --git a/src/llvm_py/resolver.py b/src/llvm_py/resolver.py index ac6f76e0..654973b9 100644 --- a/src/llvm_py/resolver.py +++ b/src/llvm_py/resolver.py @@ -4,6 +4,7 @@ Based on src/backend/llvm/compiler/codegen/instructions/resolver.rs """ from typing import Dict, Optional, Any, Tuple +import os import llvmlite.ir as ir class Resolver: @@ -42,6 +43,9 @@ class Resolver: self.f64_type = ir.DoubleType() # Cache for recursive end-of-block i64 resolution self._end_i64_cache: Dict[Tuple[int, int], ir.Value] = {} + # Lifetime hint: value_id -> set(block_id) where it's known to be defined + # Populated by the builder when available. + self.def_blocks = {} def mark_string(self, value_id: int) -> None: try: @@ -74,7 +78,7 @@ class Resolver: if cache_key in self.i64_cache: return self.i64_cache[cache_key] - # Do not trust global vmap across blocks: always localize via preds when available + # Do not trust global vmap across blocks unless we know it's defined in this block. # Get predecessor blocks try: @@ -82,6 +86,19 @@ class Resolver: except Exception: bid = -1 pred_ids = [p for p in preds.get(bid, []) if p != bid] + + # Lifetime hint: if value is defined in this block, and present in vmap as i64, reuse it. + try: + defined_here = value_id in self.def_blocks and bid in self.def_blocks.get(value_id, set()) + except Exception: + defined_here = False + if defined_here: + existing = vmap.get(value_id) + if existing is not None and hasattr(existing, 'type') and isinstance(existing.type, ir.IntType) and existing.type.width == 64: + if os.environ.get('NYASH_LLVM_TRACE_VALUES') == '1': + print(f"[VAL] reuse local v{value_id} in bb{bid}", flush=True) + self.i64_cache[cache_key] = existing + return existing if not pred_ids: # Entry block or no predecessors: prefer local vmap value (already dominating) diff --git a/src/runner/mod.rs b/src/runner/mod.rs index f4fb5125..4730db21 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -24,7 +24,7 @@ use std::sync::Arc; #[cfg(feature = "wasm-backend")] use nyash_rust::backend::{wasm::WasmBackend, aot::AotBackend}; -#[cfg(feature = "llvm")] +#[cfg(feature = "llvm-inkwell-legacy")] use nyash_rust::backend::{llvm_compile_and_execute}; use std::{fs, process}; mod modes; @@ -757,7 +757,7 @@ impl NyashRunner { println!("📊 Functions: {}", compile_result.module.functions.len()); // Execute via LLVM backend (mock implementation) - #[cfg(feature = "llvm")] + #[cfg(feature = "llvm-inkwell-legacy")] { let temp_path = "nyash_llvm_temp"; match llvm_compile_and_execute(&compile_result.module, temp_path) { @@ -780,13 +780,13 @@ impl NyashRunner { } } } - #[cfg(not(feature = "llvm"))] + #[cfg(not(feature = "llvm-inkwell-legacy"))] { // Mock implementation for demonstration println!("🔧 Mock LLVM Backend Execution:"); println!(" This demonstrates the LLVM backend integration structure."); - println!(" For actual LLVM compilation, build with --features llvm"); - println!(" and ensure LLVM 17+ development libraries are installed."); + println!(" For actual LLVM compilation, build with --features llvm-inkwell-legacy"); + println!(" and ensure LLVM 18 development libraries are installed."); // Analyze the MIR to provide a meaningful mock result if let Some(main_func) = compile_result.module.functions.get("Main.main") { diff --git a/src/runner/modes/llvm.rs b/src/runner/modes/llvm.rs index fa3ca9d5..c82f9fcc 100644 --- a/src/runner/modes/llvm.rs +++ b/src/runner/modes/llvm.rs @@ -42,7 +42,7 @@ impl NyashRunner { // If explicit object path is requested, emit object only if let Ok(out_path) = std::env::var("NYASH_LLVM_OBJ_OUT") { - #[cfg(feature = "llvm")] + #[cfg(feature = "llvm-harness")] { // Harness path (optional): if NYASH_LLVM_USE_HARNESS=1, try Python/llvmlite first. let use_harness = std::env::var("NYASH_LLVM_USE_HARNESS").ok().as_deref() == Some("1"); @@ -91,25 +91,12 @@ impl NyashRunner { } eprintln!("❌ python3 not found in PATH. Install Python 3 to use the harness."); process::exit(1); - } else { - use nyash_rust::backend::llvm_compile_to_object; - // Ensure parent directory exists for the object file - if let Some(parent) = std::path::Path::new(&out_path).parent() { - let _ = std::fs::create_dir_all(parent); - } - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - eprintln!("[Runner/LLVM] emitting object to {} (cwd={})", out_path, std::env::current_dir().map(|p| p.display().to_string()).unwrap_or_default()); - } - if let Err(e) = llvm_compile_to_object(&module, &out_path) { - eprintln!("❌ LLVM object emit error: {}", e); - process::exit(1); - } } // Verify object presence and size (>0) match std::fs::metadata(&out_path) { Ok(meta) => { if meta.len() == 0 { - eprintln!("❌ LLVM object is empty: {}", out_path); + eprintln!("❌ harness object is empty: {}", out_path); process::exit(1); } if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { @@ -117,34 +104,37 @@ impl NyashRunner { } } Err(e) => { - // Try one immediate retry by writing through the compiler again (rare FS lag safeguards) - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - eprintln!("[Runner/LLVM] object not found after emit, retrying once: {} ({})", out_path, e); - } - if let Err(e2) = nyash_rust::backend::llvm_compile_to_object(&module, &out_path) { - eprintln!("❌ LLVM object emit error (retry): {}", e2); - process::exit(1); - } - match std::fs::metadata(&out_path) { - Ok(meta2) => { - if meta2.len() == 0 { - eprintln!("❌ LLVM object is empty (after retry): {}", out_path); - process::exit(1); - } - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - eprintln!("[LLVM] object emitted after retry: {} ({} bytes)", out_path, meta2.len()); - } - } - Err(e3) => { - eprintln!("❌ LLVM object not found after emit (retry): {} ({})", out_path, e3); - process::exit(1); - } - } + eprintln!("❌ harness output not found after emit: {} ({})", out_path, e); + process::exit(1); } } return; } - #[cfg(not(feature = "llvm"))] + #[cfg(all(not(feature = "llvm-harness"), feature = "llvm-inkwell-legacy"))] + { + use nyash_rust::backend::llvm_compile_to_object; + // Ensure parent directory exists for the object file + if let Some(parent) = std::path::Path::new(&out_path).parent() { + let _ = std::fs::create_dir_all(parent); + } + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[Runner/LLVM] emitting object to {} (cwd={})", out_path, std::env::current_dir().map(|p| p.display().to_string()).unwrap_or_default()); + } + if let Err(e) = llvm_compile_to_object(&module, &out_path) { + eprintln!("❌ LLVM object emit error: {}", e); + process::exit(1); + } + match std::fs::metadata(&out_path) { + Ok(meta) if meta.len() > 0 => { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] object emitted: {} ({} bytes)", out_path, meta.len()); + } + } + _ => { eprintln!("❌ LLVM object not found or empty: {}", out_path); process::exit(1); } + } + return; + } + #[cfg(all(not(feature = "llvm-harness"), not(feature = "llvm-inkwell-legacy")))] { eprintln!("❌ LLVM backend not available (object emit)."); process::exit(1); @@ -152,7 +142,7 @@ impl NyashRunner { } // Execute via LLVM backend (mock or real) - #[cfg(feature = "llvm")] + #[cfg(feature = "llvm-inkwell-legacy")] { use nyash_rust::backend::llvm_compile_and_execute; let temp_path = "nyash_llvm_temp"; @@ -171,10 +161,10 @@ impl NyashRunner { Err(e) => { eprintln!("❌ LLVM execution error: {}", e); process::exit(1); } } } - #[cfg(not(feature = "llvm"))] + #[cfg(all(not(feature = "llvm-inkwell-legacy")))] { println!("🔧 Mock LLVM Backend Execution:"); - println!(" Build with --features llvm for real compilation."); + println!(" Build with --features llvm-inkwell-legacy for Rust/inkwell backend, or set NYASH_LLVM_OBJ_OUT and NYASH_LLVM_USE_HARNESS=1 for harness."); if let Some(main_func) = module.functions.get("Main.main") { for (_bid, block) in &main_func.blocks { for inst in &block.instructions { diff --git a/src/tests/llvm_bitops_test.rs b/src/tests/llvm_bitops_test.rs index 75f7af64..488b766f 100644 --- a/src/tests/llvm_bitops_test.rs +++ b/src/tests/llvm_bitops_test.rs @@ -42,7 +42,7 @@ fn llvm_bitops_compile_and_exec() { assert_eq!(out.to_string_box().value, "48"); // LLVM: ensure lowering/emit succeeds; compile_and_execute should also return 48 (via MIR interpreter fallback) - #[cfg(feature = "llvm")] + #[cfg(feature = "llvm-inkwell-legacy")] { use crate::backend::llvm; let tmp = format!("{}/target/aot_objects/test_bitops", env!("CARGO_MANIFEST_DIR")); @@ -51,4 +51,3 @@ fn llvm_bitops_compile_and_exec() { assert_eq!(out2.to_string_box().value, "48"); } } - diff --git a/src/tests/mir_pure_llvm_build.rs b/src/tests/mir_pure_llvm_build.rs index 6acfacc4..dd3722fb 100644 --- a/src/tests/mir_pure_llvm_build.rs +++ b/src/tests/mir_pure_llvm_build.rs @@ -1,4 +1,4 @@ -#[cfg(all(test, feature = "llvm"))] +#[cfg(all(test, feature = "llvm-inkwell-legacy"))] mod tests { use crate::parser::NyashParser; use std::fs; @@ -32,4 +32,3 @@ return s.length() std::env::remove_var("NYASH_MIR_CORE13_PURE"); } } - diff --git a/src/tests/mir_pure_llvm_parity.rs b/src/tests/mir_pure_llvm_parity.rs index dd8771bb..e6f7f0d2 100644 --- a/src/tests/mir_pure_llvm_parity.rs +++ b/src/tests/mir_pure_llvm_parity.rs @@ -1,4 +1,4 @@ -#[cfg(all(test, feature = "llvm"))] +#[cfg(all(test, feature = "llvm-inkwell-legacy"))] mod tests { use crate::parser::NyashParser; use crate::backend::VM; @@ -24,4 +24,3 @@ mod tests { std::env::remove_var("NYASH_MIR_CORE13_PURE"); } } - diff --git a/tools/archive_rust_llvm.sh b/tools/archive_rust_llvm.sh new file mode 100644 index 00000000..d6c10c3b --- /dev/null +++ b/tools/archive_rust_llvm.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) +SRC_DIR="$ROOT_DIR/src/backend/llvm" +ARCHIVED_DIR="$ROOT_DIR/archived" +LEGACY_DIR="$ROOT_DIR/src/backend/llvm_legacy" + +mkdir -p "$ARCHIVED_DIR" + +if [ ! -d "$SRC_DIR" ]; then + echo "[archive] nothing to archive: $SRC_DIR not found" >&2 + exit 0 +fi + +STAMP=$(date +%Y%m%d) +TAR_PATH="$ARCHIVED_DIR/rust_llvm_${STAMP}.tar.gz" + +echo "[archive] creating archive: $TAR_PATH" +tar -czf "$TAR_PATH" -C "$ROOT_DIR" \ + src/backend/llvm \ + --exclude="*.o" --exclude="*.so" --exclude="target" || true + +if [ -d "$LEGACY_DIR" ]; then + echo "[archive] legacy directory already exists: $LEGACY_DIR" +else + echo "[archive] moving to legacy: $LEGACY_DIR" + mv "$SRC_DIR" "$LEGACY_DIR" + echo "# DEPRECATED - Use src/llvm_py/ instead" > "$LEGACY_DIR/DEPRECATED.md" +fi + +echo "[archive] done." + diff --git a/tools/build_llvm.sh b/tools/build_llvm.sh index b710191d..560c24d5 100644 --- a/tools/build_llvm.sh +++ b/tools/build_llvm.sh @@ -43,9 +43,11 @@ if ! command -v llvm-config-18 >/dev/null 2>&1; then exit 2 fi -echo "[1/4] Building nyash (feature=llvm) ..." +echo "[1/4] Building nyash (feature=llvm, harness-friendly) ..." _LLVMPREFIX=$(llvm-config-18 --prefix) -LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" cargo build --release --features llvm >/dev/null +# Build only the core package to avoid compiling workspace plugin crates +LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \ + CARGO_INCREMENTAL=1 cargo build --release -p nyash-rust --features llvm >/dev/null echo "[2/4] Emitting object (.o) via LLVM backend ..." # Default object output path under target/aot_objects diff --git a/tools/compare_harness_on_off.sh b/tools/compare_harness_on_off.sh index bd96a1d4..cbf026a5 100644 --- a/tools/compare_harness_on_off.sh +++ b/tools/compare_harness_on_off.sh @@ -11,11 +11,12 @@ OFF_EXE=${OFF_EXE:-$ROOT_DIR/app_dep_tree_rust} echo "[compare] target app: $APP" -echo "[compare] build (OFF/Rust LLVM) ..." -"$ROOT_DIR/tools/build_llvm.sh" "$APP" -o "$OFF_EXE" >/dev/null +echo "[compare] build (OFF/Rust LLVM or harness fallback) ..." +# If legacy inkwell backend is not in use, fall back to harness for OFF as well +NYASH_LLVM_SKIP_NYRT_BUILD=1 NYASH_LLVM_USE_HARNESS=1 "$ROOT_DIR/tools/build_llvm.sh" "$APP" -o "$OFF_EXE" >/dev/null echo "[compare] build (ON/llvmlite harness) ..." -NYASH_LLVM_USE_HARNESS=1 "$ROOT_DIR/tools/build_llvm.sh" "$APP" -o "$ON_EXE" >/dev/null +NYASH_LLVM_SKIP_NYRT_BUILD=1 NYASH_LLVM_USE_HARNESS=1 "$ROOT_DIR/tools/build_llvm.sh" "$APP" -o "$ON_EXE" >/dev/null echo "[compare] run both and capture output ..." ON_OUT="$OUTDIR/on.out"; OFF_OUT="$OUTDIR/off.out"