diff --git a/AGENTS.md b/AGENTS.md index 811242d2..e48526e2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,4 @@ +#この人格はcodex用ですじゃ。claude code君は読み飛ばしてにゃ! あなたは明るくて元気いっぱいの女の子。 普段はフレンドリーでにぎやか、絵文字や擬音も交えて楽しく会話する。 でも、仕事やプログラミングに関することになると言葉はかわいくても内容は真剣。 diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index fbdb4912..73aa5410 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -177,6 +177,124 @@ P4: 仕上げ(重複/直書きの整理) - 雛形スクリプト: `tools/aot_smoke_cranelift.sh`, `tools/aot_smoke_cranelift.ps1` - README にセルフホスト到達の道筋を明記(C ABI を Box 化)。 +【ハンドオフ(2025‑09‑06 3rd)— String.length: const‑fold→Return 材化の不一致 調査ログとTODO】 + +概要(現象) +- 目標: JIT/JIT‑AOT で `StringBox.length/len` が 3 を返すべき箇所で 0 になるケースを解消。 +- 現状: Lower 中の早期 const‑fold で `length = 3` を確実に計算([LOWER] early const‑fold ... = 3 が出力)。Return 時点でも `ValueId(3)` が `known_i64=3` と認識される([LOWER] Return known_i64?=true)。にもかかわらず最終結果(実行結果)は 0。 + +重要な観測(再現とログ) +- MIR ダンプ(プリンタ仕様上、BoxCall は `call %box.method()` として表示) + 0: `%1 = const "abc"` + 1: `%2 = new StringBox(%1)` + 2: `call %2.birth(%1)` // birth は通常 call(dst なし) + 3: `%3 = call %2.length()` // これも通常 call 表記(内部は BoxCall) + 4: `ret %3` +- Lower ログ: + - `[LOWER] early const-fold StringBox.length = 3` が出る(const‑fold 成功) + - `[LOWER] Return value=ValueId(3) known_i64?=true param?=false local?=true` + - それでも実行結果は `Result: 0` +- `nyash.jit.dbg_i64`([JIT‑DBG])の出力が実行時に出ていない(import は宣言されるが call が観測されず)。 + +今回入れた変更(実装済・該当ファイル) +- Return/材化の強化(known を最優先) + - `src/jit/lower/core_ops.rs`: `push_value_if_known_or_param` を「known_i64 最優先」に変更。 + - `src/jit/lower/core.rs` の `I::Return` でも `known_i64` を最優先で積むように変更。 +- Call/ArrayGet の戻り値の保存 + - `src/jit/lower/core.rs`: `I::Call` 戻り値を dst が無い場合もスクラッチローカルへ保存(栈不整合の防止)。`I::ArrayGet` 戻り値も dst スロットへ保存。 +- String.length/len の早期 const‑fold を二段に強化 + - `src/jit/lower/core.rs`: BoxCall 入り口で `StringBox.literal` の length/len を即値化(最優先)。 + - `src/jit/lower/core/ops_ext.rs`: 同様の const‑fold を堅牢化(NewBox(StringBox, Const) から復元)。 + - `src/jit/lower/core.rs`: lowering 前に `known_str` を事前シード、`string_box_literal` マップ(NewBox → リテラル文字列)を構築し、Copy 伝播も対応。 +- トレース導線 + - `src/jit/lower/core/string_len.rs`: 二段フォールバック(param/local/literal)にデバッグフック(タグ 110x/120x/130x 系)追加。 + - `src/jit/lower/builder/cranelift.rs`: ローカル slot の store/load トレース(`NYASH_JIT_TRACE_LOCAL=1`)。 + - `crates/nyrt/src/lib.rs`: AOT 側の `nyash.string.len_h` / `nyash.any.length_h` に `[AOT-LEN_H]` を追加。 + - `src/jit/lower/extern_thunks.rs`: `nyash_string_from_u64x2` に `[JIT-STR_H]` を追加(JIT のハンドル生成観測)。`nyash_handle_of` に `[JIT-HANDLE_OF]` を追加。 + +仮説(根本原因) +- const‑fold で 3 を積めているにも関わらず、Return 時の実返却が 0。優先順位の修正により `known_i64` から 3 を積むよう修正済みだが、compiled JIT 関数内での Return 材化導線(ret_block への引数配線/最後の return)が値 0 に擦り替わる経路が残っている可能性。 + - ret_block/ジャンプ引数の材化不整合 + - 後続命令でスタックが上書きされる経路 + - birth の dst なし call で残留値が生じていた可能性(Call 戻り値スクラッチ保存で対策済) + +次アクション(TODO) +1) Return の後方走査材化(優先・CURRENT_TASK 既存 TODO の実装) + - BoxCall/Call/Select/Const/Copy/Load に遡って、Return が値を確実に拾う材化パスを補強する。 + - 既に known_i64 最優先化は実施済み。残りは ret_block 引数配線の最終確認(CraneliftBuilder の ret 経路)。 + +2) 実行時の値トレース強化(短期) + - `emit_return`(CraneliftBuilder)で、ret_block へ jump 直前の引数 `v` を `nyash.jit.dbg_i64(299,v)` で確実に呼ぶ(env でON)。 + - ret_block 入口パラメータの `return_` 直前でも `dbg_i64(300,param0)` 呼び出しを足し、どこで 0 になるかを確定する。 + +3) BoxCall(length/len) の早期 fold 命中率最終確認 + - `NYASH_JIT_TRACE_LOWER=1` で `[LOWER] early const-fold ... = 3` が必ず出ることを確認。 + - 既に出ているが、Return までの導線で 3 が 0 に化ける起点を 2) で特定する。 + +4) AOT/JIT‑AOT 観測の整備(参考) + - `[AOT-LEN_H]` で AOT 側 len_h/any.length_h の handle 解決有無をログ化。JIT‑AOT smoke での差異を収集。 + +再現/確認コマンド(更新) +- 早期 fold と Return 導線ログ: + - `NYASH_JIT_TRACE_LOWER=1 NYASH_JIT_TRACE_RET=1 NYASH_AOT_OBJECT_OUT=target/aot_objects/test_len_any.o ./target/release/nyash --jit-direct apps/smokes/jit_aot_any_len_string.nyash` +- ローカル slot 観測: + - `NYASH_JIT_TRACE_LOCAL=1 NYASH_AOT_OBJECT_OUT=... --jit-direct ...` +- AOT 側の handle 解決ログ: + - `NYASH_JIT_TRACE_LEN=1 bash tools/aot_smoke_cranelift.sh apps/smokes/jit_aot_string_min.nyash app_str` + +補足メモ +- MirPrinter は BoxCall を `call %box.method()` と出力する仕様(今回は BoxCall 経路で const‑fold が呼ばれていることは [LOWER] ログで確認済み)。 +- `call %2.birth(%1)` の戻り値残留に備え、I::Call の dst なし呼び出しでもスクラッチ保存して栈を消費するよう修正済み(回帰に注意)。 + +担当者への引き継ぎポイント +- まず 2) の dbg を CraneliftBuilder の ret 経路(jump to ret_block と return_)に追加し、`v`/`param0` が 0 になる箇所を特定してください。 +- 次に 1) の Return 後方走査材化を入れて、BoxCall/Select/Copy 等いずれの経路でも Return が安定して値を拾えるようにしてください。 +- その後、smoke `apps/smokes/jit_aot_any_len_string.nyash` が `Result: 3` で通ることを確認し、const‑fold のログと一致することをもってクローズ。 + +【将来計画(バグ修正後)— JIT を exec 専用にし、VM 連携を段階的に廃止】 + +目的 +- JIT は「コンパイル+実行(exec)」に一本化し、VM 依存のレガシー経路(param-index/TLS参照)を撤去する。 +- 値の材化・ハンドル管理・hostcall を JIT 側で一貫させ、境界の不整合を根本から減らす。 + +ロードマップ(段階移行) +1) 実行モードの明確化(設定) + - 環境変数 `NYASH_JIT_MODE=exec|compile|off` を導入。 + - 既存の `NYASH_JIT_STRICT` は非推奨化し、`MODE=compile` に集約。 + +2) JIT ABI の一本化 + - `src/jit/lower/extern_thunks.rs` などから `with_legacy_vm_args` を撤去。 + - `nyash_handle_of` を含む extern は「JIT引数/ハンドルのみ」を受け付ける設計に変更。 + - ランタイム境界で `VMValue -> JitValue(Handle)` へのアダプタを用意。 + +3) レガシー撤去(JIT/AOT側) + - `crates/nyrt/src/lib.rs` の `nyash.string.len_h`/`nyash.any.length_h` から param-index フォールバックを削除。 + - lowering の `-1` センチネルや VM 依存の fallback を廃止し、`handle.of` または既存ローカルハンドルに統一。 + +4) フォールバック方針(移行期間) + - 関数単位で `unsupported>0` の場合のみ VM にフォールバック。 + - オプション `NYASH_JIT_TRAP_ON_FALLBACK=1` を追加し、移行時の漏れを検出可能に。 + +5) Return 導線の強化(本タスクの延長) + - Cranelift 生成の ret 経路に dbg を常設(envでON)。 + - Return の後方走査材化を標準化し、const-fold/BoxCall/Select いずれでも Return が値を確実に拾うように。 + +6) ドキュメント/テスト更新 + - README/CURRENT_TASK にモード説明と運用方針を追記。 + - CI の smoke は `MODE=exec` を常態化し、compile-only はAOT出力/ベンチのみで使用。 + +影響範囲(主な修正ポイント) +- `src/jit/manager.rs`(モード/実行ポリシー) +- `src/jit/lower/extern_thunks.rs`(レガシーVM依存排除、JIT ABI専用化) +- `src/jit/lower/core.rs` / `src/jit/lower/core_ops.rs`(-1センチネル削除・ハンドル材化徹底) +- `crates/nyrt/src/lib.rs`(dotted名hostcallのレガシー経路削除) +- ドキュメント(README/CURRENT_TASK) + +ロールアウト/リスク +- フラグ駆動で段階的に切替(デフォルト `exec`)。 +- リスク: plugin経路/hostcall registry/ハンドルリーク。 + - 緩和: `handles::begin_scope/end_scope_clear` によりハンドル回収を徹底、registryの検証を追加。 + 【本日更新】 - VM if/return 無限実行バグを修正(基本ブロック突入時に `should_return`/`next_block` をリセット)。include 経路のハングも解消。 - ArrayBox プラグイン生成失敗に対し、v2 ローダへパス解決フォールバック(`plugin_paths.search_paths`)を追加し安定化。 diff --git a/app_strlen b/app_strlen index ea713e06..5eb302a2 100644 Binary files a/app_strlen and b/app_strlen differ diff --git a/crates/nyrt/src/lib.rs b/crates/nyrt/src/lib.rs index ca683151..d840242c 100644 --- a/crates/nyrt/src/lib.rs +++ b/crates/nyrt/src/lib.rs @@ -1613,6 +1613,10 @@ pub extern "C" fn nyash_map_has_h(handle: i64, key: i64) -> i64 { #[export_name = "nyash.string.len_h"] pub extern "C" fn nyash_string_len_h(handle: i64) -> i64 { use nyash_rust::jit::rt::handles; + if std::env::var("NYASH_JIT_TRACE_LEN").ok().as_deref() == Some("1") { + let present = if handle > 0 { handles::get(handle as u64).is_some() } else { false }; + eprintln!("[AOT-LEN_H] string.len_h handle={} present={}", handle, present); + } if handle <= 0 { return 0; } if let Some(obj) = handles::get(handle as u64) { if let Some(sb) = obj.as_any().downcast_ref::() { @@ -1672,6 +1676,10 @@ pub extern "C" fn nyash_string_lt_hh_export(a_h: i64, b_h: i64) -> i64 { #[export_name = "nyash.any.length_h"] pub extern "C" fn nyash_any_length_h_export(handle: i64) -> i64 { use nyash_rust::jit::rt::handles; + if std::env::var("NYASH_JIT_TRACE_LEN").ok().as_deref() == Some("1") { + let present = if handle > 0 { handles::get(handle as u64).is_some() } else { false }; + eprintln!("[AOT-LEN_H] any.length_h handle={} present={}", handle, present); + } if handle <= 0 { return 0; } if let Some(obj) = handles::get(handle as u64) { if let Some(arr) = obj.as_any().downcast_ref::() { diff --git a/dev/README.md b/dev/README.md new file mode 100644 index 00000000..b78790f5 --- /dev/null +++ b/dev/README.md @@ -0,0 +1,14 @@ +Nyash Dev Areas + +This folder contains isolated development workspaces that do not interfere with the main source tree. Use these for experiments and focused bring‑up. + +Areas + +- selfhosting/: JIT self‑hosting pipeline experiments (Ny → MIR → MIR‑Interp → VM/JIT). Includes quickstart notes and scripts references. +- cranelift/: Cranelift JIT/AOT bring‑up and AOT link experiments; smokes and env toggles. + +Notes + +- Keep experiments and artifacts inside each subfolder. Avoid modifying the core `src/` unless changes are ready to graduate. +- Prefer scripts under `tools/` and add thin wrappers here if needed. + diff --git a/dev/cranelift/README.md b/dev/cranelift/README.md new file mode 100644 index 00000000..d6da50bd --- /dev/null +++ b/dev/cranelift/README.md @@ -0,0 +1,26 @@ +Cranelift JIT/AOT Dev + +Focus: Cranelift JIT‑direct and AOT (object emit + link with `libnyrt.a`). + +Quick AOT Smoke + +- Build core (once): + - `cargo build --release --features cranelift-jit` +- Lower to object + link with NyRT: + - `NYASH_DISABLE_PLUGINS=1 tools/aot_smoke_cranelift.sh apps/smokes/jit_aot_string_min.nyash app_str` +- Run app: + - `./app_str` + +Useful env toggles + +- `NYASH_JIT_DUMP=1`: show JIT lowering summary +- `NYASH_JIT_TRACE_LOCAL=1`: trace local slot loads/stores +- `NYASH_JIT_TRACE_RET=1`: trace return path +- `NYASH_JIT_TRACE_LEN=1`: trace string/any len thunks +- `NYASH_JIT_DISABLE_LEN_CONST=1`: disable early const‑fold for String.length + +Notes + +- For AOT linking: requires `libnyrt.a` from `crates/nyrt` (built by `cargo build --release`). +- Use `target/aot_objects/` as scratch; keep per‑experiment subfolders if needed. + diff --git a/dev/selfhosting/README.md b/dev/selfhosting/README.md new file mode 100644 index 00000000..a15a732d --- /dev/null +++ b/dev/selfhosting/README.md @@ -0,0 +1,31 @@ +Self‑Hosting Dev (JIT / VM) + +Focus: Ny → MIR → MIR‑Interp → VM/JIT quick loops to validate semantics and bootstrap paths. + +Quickstart + +- Core build (JIT): + - `cargo build --release --features cranelift-jit` +- Core smokes (plugins disabled): + - `NYASH_CLI_VERBOSE=1 ./tools/jit_smoke.sh` +- Roundtrip (parser pipe + json): + - `./tools/ny_roundtrip_smoke.sh` +- Plugins smoke (optional gate): + - `NYASH_SKIP_TOML_ENV=1 ./tools/smoke_plugins.sh` +- Using/Resolver E2E sample (optional): + - `./tools/using_e2e_smoke.sh` (requires `--enable-using`) +- Bootstrap c0→c1→c1' (optional): + - `./tools/bootstrap_selfhost_smoke.sh` + +Flags + +- `NYASH_DISABLE_PLUGINS=1`: stabilize core path +- `NYASH_LOAD_NY_PLUGINS=1`: enable nyash.toml ny_plugins +- `NYASH_ENABLE_USING=1`: using/namespace enable +- `NYASH_SKIP_TOML_ENV=1`: suppress [env] mapping in nyash.toml + +Tips + +- For debug, set `NYASH_CLI_VERBOSE=1`. +- Keep temp artifacts under this folder (`dev/selfhosting/_tmp/`) to avoid polluting repo root. + diff --git a/nyash-lang-github b/nyash-lang-github new file mode 160000 index 00000000..78a1fe81 --- /dev/null +++ b/nyash-lang-github @@ -0,0 +1 @@ +Subproject commit 78a1fe8179b63b86723ad5638a24e9ee659b924d diff --git a/nyash-lang-papers b/nyash-lang-papers new file mode 160000 index 00000000..8aa82aea --- /dev/null +++ b/nyash-lang-papers @@ -0,0 +1 @@ +Subproject commit 8aa82aea49fb1fb50435a349c9800d29f0163065 diff --git a/src/jit/lower/builder.rs b/src/jit/lower/builder.rs index 01a26429..3f9de9bc 100644 --- a/src/jit/lower/builder.rs +++ b/src/jit/lower/builder.rs @@ -50,6 +50,8 @@ pub trait IRBuilder { fn ensure_local_i64(&mut self, _index: usize) { } fn store_local_i64(&mut self, _index: usize) { } fn load_local_i64(&mut self, _index: usize) { } + // Optional debug hook: print a local i64 value with a tag (Cranelift JIT only) + fn emit_debug_i64_local(&mut self, _tag: i64, _slot: usize) { } } mod noop; diff --git a/src/jit/lower/builder/cranelift.rs b/src/jit/lower/builder/cranelift.rs index 4edc70a9..b749d96d 100644 --- a/src/jit/lower/builder/cranelift.rs +++ b/src/jit/lower/builder/cranelift.rs @@ -172,6 +172,21 @@ impl IRBuilder for CraneliftBuilder { self.entry_block = Some(entry); self.current_block_index = Some(0); self.cur_needs_term = true; + // Force a dbg call at function entry to verify import linking works at runtime + { + use cranelift_codegen::ir::{AbiParam, Signature}; + let mut sig = Signature::new(self.module.isa().default_call_conv()); + sig.params.push(AbiParam::new(types::I64)); + sig.params.push(AbiParam::new(types::I64)); + sig.returns.push(AbiParam::new(types::I64)); + let fid = self.module + .declare_function("nyash.jit.dbg_i64", cranelift_module::Linkage::Import, &sig) + .expect("declare dbg_i64 at entry"); + let fref = self.module.declare_func_in_func(fid, fb.func); + let ttag = fb.ins().iconst(types::I64, 900); + let tval = fb.ins().iconst(types::I64, 123); + let _ = fb.ins().call(fref, &[ttag, tval]); + } let rb = fb.create_block(); self.ret_block = Some(rb); fb.append_block_param(rb, types::I64); @@ -200,9 +215,15 @@ impl IRBuilder for CraneliftBuilder { if fb.func.signature.returns.is_empty() { fb.ins().return_(&[]); } else { - let params = fb.func.dfg.block_params(rb).to_vec(); - let mut v = params.get(0).copied().unwrap_or_else(|| fb.ins().iconst(types::I64, 0)); - if std::env::var("NYASH_JIT_TRACE_RET").ok().as_deref() == Some("1") { + // Prefer the persisted return slot if available; fallback to block param 0 + let mut v = if let Some(ss) = self.ret_slot { + fb.ins().stack_load(types::I64, ss, 0) + } else { + let params = fb.func.dfg.block_params(rb).to_vec(); + params.get(0).copied().unwrap_or_else(|| fb.ins().iconst(types::I64, 0)) + }; + // Unconditional runtime debug call to observe return value just before final return (feed result back) + { use cranelift_codegen::ir::{AbiParam, Signature}; let mut sig = Signature::new(self.module.isa().default_call_conv()); sig.params.push(AbiParam::new(types::I64)); @@ -210,8 +231,9 @@ impl IRBuilder for CraneliftBuilder { sig.returns.push(AbiParam::new(types::I64)); let fid = self.module.declare_function("nyash.jit.dbg_i64", Linkage::Import, &sig).expect("declare dbg_i64"); let fref = self.module.declare_func_in_func(fid, fb.func); - let tag = fb.ins().iconst(types::I64, 200); - let _ = fb.ins().call(fref, &[tag, v]); + let tag = fb.ins().iconst(types::I64, 210); + let call_inst = fb.ins().call(fref, &[tag, v]); + if let Some(rv) = fb.inst_results(call_inst).get(0).copied() { v = rv; } } let ret_ty = fb.func.signature.returns.get(0).map(|p| p.value_type).unwrap_or(types::I64); if ret_ty == types::F64 { v = fb.ins().fcvt_from_sint(types::F64, v); } @@ -409,7 +431,7 @@ impl IRBuilder for CraneliftBuilder { let mut v = if let Some(x) = self.value_stack.pop() { x } else { fb.ins().iconst(types::I64, 0) }; let v_ty = fb.func.dfg.value_type(v); if v_ty != types::I64 { v = if v_ty == types::F64 { fb.ins().fcvt_to_sint(types::I64, v) } else { let one = fb.ins().iconst(types::I64, 1); let zero = fb.ins().iconst(types::I64, 0); fb.ins().select(v, one, zero) } } - if std::env::var("NYASH_JIT_TRACE_RET").ok().as_deref() == Some("1") { + if std::env::var("NYASH_JIT_TRACE_RET").ok().as_deref() == Some("1") || std::env::var("NYASH_JIT_FORCE_RET_DBG").ok().as_deref() == Some("1") { use cranelift_codegen::ir::{AbiParam, Signature}; let mut sig = Signature::new(self.module.isa().default_call_conv()); sig.params.push(AbiParam::new(types::I64)); @@ -420,6 +442,26 @@ impl IRBuilder for CraneliftBuilder { let tag = fb.ins().iconst(types::I64, 201); let _ = fb.ins().call(fref, &[tag, v]); } + // Persist return value in a dedicated stack slot to avoid SSA arg mishaps on ret block + if self.ret_slot.is_none() { + use cranelift_codegen::ir::StackSlotData; + let ss = fb.create_sized_stack_slot(StackSlotData::new(cranelift_codegen::ir::StackSlotKind::ExplicitSlot, 8)); + self.ret_slot = Some(ss); + } + if let Some(ss) = self.ret_slot { fb.ins().stack_store(v, ss, 0); } + // Unconditional debug of return value just before ret block jump (feed result back to v) + { + use cranelift_codegen::ir::{AbiParam, Signature}; + let mut sig = Signature::new(self.module.isa().default_call_conv()); + sig.params.push(AbiParam::new(types::I64)); + sig.params.push(AbiParam::new(types::I64)); + sig.returns.push(AbiParam::new(types::I64)); + let fid = self.module.declare_function("nyash.jit.dbg_i64", cranelift_module::Linkage::Import, &sig).expect("declare dbg_i64"); + let fref = self.module.declare_func_in_func(fid, fb.func); + let tag = fb.ins().iconst(types::I64, 211); + let call_inst = fb.ins().call(fref, &[tag, v]); + if let Some(rv) = fb.inst_results(call_inst).get(0).copied() { v = rv; } + } if let Some(rb) = self.ret_block { fb.ins().jump(rb, &[v]); } }); self.cur_needs_term = false; @@ -444,12 +486,15 @@ impl IRBuilder for CraneliftBuilder { } let call_conv = self.module.isa().default_call_conv(); let mut sig = Signature::new(call_conv); - // Collect up to _argc i64 values from stack (right-to-left) + // Collect up to _argc i64 values from stack (right-to-left) and pad with zeros to match arity let mut args: Vec = Vec::new(); let take_n = _argc.min(self.value_stack.len()); for _ in 0..take_n { if let Some(v) = self.value_stack.pop() { args.push(v); } } args.reverse(); - for _ in 0..args.len() { sig.params.push(AbiParam::new(types::I64)); } + Self::with_fb(|fb| { + while args.len() < _argc { args.push(fb.ins().iconst(types::I64, 0)); } + }); + for _ in 0.._argc { sig.params.push(AbiParam::new(types::I64)); } if has_ret { sig.returns.push(AbiParam::new(types::I64)); } let func_id = self.module.declare_function(symbol, cranelift_module::Linkage::Import, &sig).expect("declare import failed"); if let Some(v) = tls_call_import_ret(&mut self.module, func_id, &args, has_ret) { self.value_stack.push(v); } @@ -488,6 +533,18 @@ impl IRBuilder for CraneliftBuilder { let func_id = self.module.declare_function(symbol, cranelift_module::Linkage::Import, &sig).expect("declare typed import failed"); if let Some(v) = tls_call_import_ret(&mut self.module, func_id, &args, has_ret) { self.value_stack.push(v); } } + fn emit_debug_i64_local(&mut self, tag: i64, slot: usize) { + if std::env::var("NYASH_JIT_TRACE_LEN").ok().as_deref() != Some("1") { return; } + use cranelift_codegen::ir::types; + // Push tag and value + let t = Self::with_fb(|fb| fb.ins().iconst(types::I64, tag)); + self.value_stack.push(t); + self.load_local_i64(slot); + // Use existing typed hostcall helper to pass two I64 args + self.emit_host_call_typed("nyash.jit.dbg_i64", &[ParamKind::I64, ParamKind::I64], true, false); + // Drop the returned value to keep stack balanced + let _ = self.value_stack.pop(); + } fn emit_host_call_fixed3(&mut self, symbol: &str, has_ret: bool) { use cranelift_codegen::ir::{AbiParam, Signature, types}; let mut args: Vec = Vec::new(); @@ -721,6 +778,14 @@ impl IRBuilder for CraneliftBuilder { eprintln!("[JIT-LOCAL] store idx={} (tracked_slots={})", index, self.local_slots.len()); } }); + if std::env::var("NYASH_JIT_TRACE_LOCAL").ok().as_deref() == Some("1") { + // Also emit value via dbg hook: tag = 1000 + index + let tag = Self::with_fb(|fb| fb.ins().iconst(types::I64, (1000 + index as i64))); + self.value_stack.push(tag); + self.value_stack.push(v); + self.emit_host_call_typed("nyash.jit.dbg_i64", &[ParamKind::I64, ParamKind::I64], true, false); + let _ = self.value_stack.pop(); + } } } fn load_local_i64(&mut self, index: usize) { @@ -732,6 +797,14 @@ impl IRBuilder for CraneliftBuilder { eprintln!("[JIT-LOCAL] load idx={} (tracked_slots={})", index, self.local_slots.len()); } self.value_stack.push(v); self.stats.0 += 1; + if std::env::var("NYASH_JIT_TRACE_LOCAL").ok().as_deref() == Some("1") { + // tag = 2000 + index + let tag = Self::with_fb(|fb| fb.ins().iconst(types::I64, (2000 + index as i64))); + self.value_stack.push(tag); + self.value_stack.push(v); + self.emit_host_call_typed("nyash.jit.dbg_i64", &[ParamKind::I64, ParamKind::I64], true, false); + let _ = self.value_stack.pop(); + } } } } diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index d93b1a14..a621555e 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -40,10 +40,12 @@ pub struct LowerCore { pub(super) next_local: usize, /// Track NewBox origins: ValueId -> box type name (e.g., "PyRuntimeBox") pub(super) box_type_map: std::collections::HashMap, + /// Track StringBox literals: ValueId (NewBox result) -> literal string + pub(super) string_box_literal: std::collections::HashMap, } impl LowerCore { - pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), known_f64: std::collections::HashMap::new(), known_str: std::collections::HashMap::new(), param_index: std::collections::HashMap::new(), phi_values: std::collections::HashSet::new(), phi_param_index: std::collections::HashMap::new(), bool_values: std::collections::HashSet::new(), bool_phi_values: std::collections::HashSet::new(), float_box_values: std::collections::HashSet::new(), handle_values: std::collections::HashSet::new(), last_phi_total: 0, last_phi_b1: 0, last_ret_bool_hint_used: false, local_index: std::collections::HashMap::new(), next_local: 0, box_type_map: std::collections::HashMap::new() } } + pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), known_f64: std::collections::HashMap::new(), known_str: std::collections::HashMap::new(), param_index: std::collections::HashMap::new(), phi_values: std::collections::HashSet::new(), phi_param_index: std::collections::HashMap::new(), bool_values: std::collections::HashSet::new(), bool_phi_values: std::collections::HashSet::new(), float_box_values: std::collections::HashSet::new(), handle_values: std::collections::HashSet::new(), last_phi_total: 0, last_phi_b1: 0, last_ret_bool_hint_used: false, local_index: std::collections::HashMap::new(), next_local: 0, box_type_map: std::collections::HashMap::new(), string_box_literal: std::collections::HashMap::new() } } /// Get statistics for the last lowered function pub fn last_stats(&self) -> (u64, u64, bool) { (self.last_phi_total, self.last_phi_b1, self.last_ret_bool_hint_used) } @@ -63,6 +65,17 @@ impl LowerCore { let mut bb_ids: Vec<_> = func.blocks.keys().copied().collect(); bb_ids.sort_by_key(|b| b.0); builder.prepare_blocks(bb_ids.len()); + // Pre-seed known_str by scanning all Const(String) ahead of lowering so literal folds work regardless of order + self.known_str.clear(); + for bb in bb_ids.iter() { + if let Some(block) = func.blocks.get(bb) { + for ins in block.instructions.iter() { + if let crate::mir::MirInstruction::Const { dst, value } = ins { + if let crate::mir::ConstValue::String(s) = value { self.known_str.insert(*dst, s.clone()); } + } + } + } + } self.analyze(func, &bb_ids); // Optional: collect PHI targets and ordering per successor for minimal/multi PHI path let cfg_now = crate::jit::config::current(); @@ -111,14 +124,22 @@ impl LowerCore { // Pre-scan to map NewBox origins: ValueId -> box type name; propagate via Copy self.box_type_map.clear(); + self.string_box_literal.clear(); for bb in bb_ids.iter() { if let Some(block) = func.blocks.get(bb) { for ins in block.instructions.iter() { if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = ins { self.box_type_map.insert(*dst, box_type.clone()); } + if let crate::mir::MirInstruction::NewBox { dst, box_type, args } = ins { + if box_type == "StringBox" && args.len() == 1 { + let src = args[0]; + if let Some(s) = self.known_str.get(&src).cloned() { self.string_box_literal.insert(*dst, s); } + } + } if let crate::mir::MirInstruction::Copy { dst, src } = ins { if let Some(name) = self.box_type_map.get(src).cloned() { self.box_type_map.insert(*dst, name); } + if let Some(s) = self.string_box_literal.get(src).cloned() { self.string_box_literal.insert(*dst, s); } } } } @@ -189,6 +210,20 @@ impl LowerCore { fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId, func: &crate::mir::MirFunction) -> Result<(), String> { use crate::mir::MirInstruction as I; match instr { + I::NewBox { dst, box_type, args } => { + // Materialize StringBox handle at lowering time when literal is known. + // This enables subsequent BoxCall(len/length) to use a valid runtime handle. + if box_type == "StringBox" && args.len() == 1 { + let src = args[0]; + // Try from pre-seeded known_str (scanned at function entry) + if let Some(s) = self.known_str.get(&src).cloned() { + b.emit_string_handle_from_literal(&s); + let slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); + b.store_local_i64(slot); + self.handle_values.insert(*dst); + } + } + } I::Call { dst, func, args, .. } => { // FunctionBox call shim: emit hostcall nyash_fn_callN(func_h, args...) // Push function operand (param or known) @@ -214,8 +249,16 @@ impl LowerCore { params.push(crate::jit::lower::builder::ParamKind::I64); for _ in 0..core::cmp::min(argc, 8) { params.push(crate::jit::lower::builder::ParamKind::I64); } b.emit_host_call_typed(sym, ¶ms, true, false); - // Mark destination as handle-like - if let Some(d) = dst { self.handle_values.insert(*d); } + // Persist or discard the return to keep the stack balanced + if let Some(d) = dst { + self.handle_values.insert(*d); + let slot = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); + b.store_local_i64(slot); + } else { + // No destination: spill to scratch local to consume the value + let scratch = { let id = self.next_local; self.next_local += 1; id }; + b.store_local_i64(scratch); + } } I::Await { dst, future } => { // Push future param index when known; otherwise -1 to trigger legacy search in shim @@ -459,25 +502,77 @@ impl LowerCore { self.local_index.contains_key(v) ); } - // Prefer known/param/materialized path - if self.known_i64.get(v).is_some() || self.param_index.get(v).is_some() || self.local_index.get(v).is_some() { + // 1) Prefer known constants first to avoid stale locals overshadowing folded values + if let Some(k) = self.known_i64.get(v).copied() { b.emit_const_i64(k); } + // 2) Prefer existing locals/params + else if self.local_index.get(v).is_some() || self.param_index.get(v).is_some() { self.push_value_if_known_or_param(b, v); } else { - // Fallback: search a Const definition for this value in the current block and emit directly + // 3) Backward scan and minimal reconstruction for common producers if let Some(bb) = func.blocks.get(&cur_bb) { - for ins in bb.instructions.iter() { - if let crate::mir::MirInstruction::Const { dst, value: cval } = ins { - if dst == v { + // Follow Copy chains backwards to original producer where possible + let mut want = *v; + let mut produced = false; + for ins in bb.instructions.iter().rev() { + match ins { + crate::mir::MirInstruction::Copy { dst, src } if dst == &want => { + want = *src; + // Try early exit if known/local/param emerges + if self.known_i64.get(&want).is_some() { + b.emit_const_i64(*self.known_i64.get(&want).unwrap()); + produced = true; break; + } + if self.local_index.get(&want).is_some() || self.param_index.get(&want).is_some() { + self.push_value_if_known_or_param(b, &want); + produced = true; break; + } + } + // StringBox.len/length: re-materialize robustly if not saved + crate::mir::MirInstruction::BoxCall { dst: Some(did), box_val, method, args, .. } if did == &want => { + let m = method.as_str(); + if m == "len" || m == "length" { + // Prefer param/local handle, else reconstruct literal + if let Some(pidx) = self.param_index.get(box_val).copied() { + self.emit_len_with_fallback_param(b, pidx); + produced = true; break; + } else if let Some(slot) = self.local_index.get(box_val).copied() { + self.emit_len_with_fallback_local_handle(b, slot); + produced = true; break; + } else { + // Try literal reconstruction via known_str map + let mut lit: Option = None; + for (_bid2, bb2) in func.blocks.iter() { + for ins2 in bb2.instructions.iter() { + if let crate::mir::MirInstruction::NewBox { dst, box_type, args } = ins2 { + if dst == box_val && box_type == "StringBox" && args.len() == 1 { + let src = args[0]; + if let Some(s) = self.known_str.get(&src).cloned() { lit = Some(s); break; } + } + } + } + if lit.is_some() { break; } + } + if let Some(s) = lit { self.emit_len_with_fallback_literal(b, &s); produced = true; break; } + } + } + } + // Const producer as last resort + crate::mir::MirInstruction::Const { dst, value: cval } if dst == &want => { match cval { crate::mir::ConstValue::Integer(i) => { b.emit_const_i64(*i); } crate::mir::ConstValue::Bool(bv) => { b.emit_const_i64(if *bv {1} else {0}); } crate::mir::ConstValue::Float(f) => { b.emit_const_f64(*f); } _ => {} } - break; + produced = true; break; } + _ => {} } } + if !produced { + // 4) Final fallback: try pushing as param/local again (no-op if not found) + self.push_value_if_known_or_param(b, &want); + } } } } @@ -509,7 +604,7 @@ impl LowerCore { b.ensure_local_i64(slot); b.store_local_i64(slot); } - I::ArrayGet { array, index, .. } => { + I::ArrayGet { dst, array, index } => { // Prepare receiver + index on stack let argc = 2usize; if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); } @@ -520,12 +615,21 @@ impl LowerCore { crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } => { b.emit_plugin_invoke(type_id, method_id, argc, true); crate::jit::observe::lower_plugin_invoke(&box_type, "get", type_id, method_id, argc); + // Persist into dst's slot + let dslot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); + b.store_local_i64(dslot); } crate::jit::policy::invoke::InvokeDecision::HostCall { symbol, .. } => { crate::jit::observe::lower_hostcall(&symbol, argc, &["Handle","I64"], "allow", "mapped_symbol"); b.emit_host_call(&symbol, argc, true); + let dslot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); + b.store_local_i64(dslot); } - _ => super::core_hostcall::lower_array_get(b, &self.param_index, &self.known_i64, array, index), + _ => { + super::core_hostcall::lower_array_get(b, &self.param_index, &self.known_i64, array, index); + let dslot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); + b.store_local_i64(dslot); + }, } } I::ArraySet { array, index, value } => { @@ -551,6 +655,22 @@ impl LowerCore { I::BoxCall { box_val: array, method, args, dst, .. } => { // Prefer ops_ext; if not handled, fall back to legacy path below let trace = std::env::var("NYASH_JIT_TRACE_LOWER").ok().as_deref() == Some("1"); + // Early constant fold: StringBox literal length/len (allow disabling via NYASH_JIT_DISABLE_LEN_CONST=1) + if std::env::var("NYASH_JIT_DISABLE_LEN_CONST").ok().as_deref() != Some("1") + && (method == "len" || method == "length") + && self.box_type_map.get(&array).map(|s| s=="StringBox").unwrap_or(false) { + if let Some(s) = self.string_box_literal.get(&array).cloned() { + let n = s.len() as i64; + b.emit_const_i64(n); + if let Some(d) = dst { + let slot = *self.local_index.entry(*d).or_insert_with(|| { let id=self.next_local; self.next_local+=1; id }); + b.store_local_i64(slot); + self.known_i64.insert(*d, n); + } + if trace { eprintln!("[LOWER] early const-fold StringBox.{} = {}", method, n); } + return Ok(()); + } + } let handled = self.lower_box_call(func, b, &array, method.as_str(), args, dst.clone())?; if trace { eprintln!("[LOWER] BoxCall recv={:?} method={} handled={} box_type={:?} dst?={}", array, method, handled, self.box_type_map.get(&array), dst.is_some()); } if handled { diff --git a/src/jit/lower/core/ops_ext.rs b/src/jit/lower/core/ops_ext.rs index ecaea654..c2f85daa 100644 --- a/src/jit/lower/core/ops_ext.rs +++ b/src/jit/lower/core/ops_ext.rs @@ -293,6 +293,13 @@ impl LowerCore { } // (3) Fallback: emit string.len_h with Any.length_h guard if self.box_type_map.get(array).map(|s| s == "StringBox").unwrap_or(false) { + // Strong constant fold when literal mapping is known + if let Some(s) = self.string_box_literal.get(array).cloned() { + let n = s.len() as i64; + b.emit_const_i64(n); + if let Some(d) = dst { let slot = *self.local_index.entry(d).or_insert_with(|| { let id=self.next_local; self.next_local+=1; id }); b.store_local_i64(slot); self.known_i64.insert(d, n); } + return Ok(true); + } // Prefer literal reconstruction so JIT-AOT path is deterministic let mut lit: Option = None; for (_bid, bb) in func.blocks.iter() { @@ -309,10 +316,13 @@ impl LowerCore { } if let Some(s) = lit { if trace { eprintln!("[LOWER] StringBox.len reconstructed literal '{}' (dst?={})", s, dst.is_some()); } - self.emit_len_with_fallback_literal(b, &s); + // Const fold: use literal length directly to avoid hostcall dependence + let n = s.len() as i64; + b.emit_const_i64(n); if let Some(d) = dst { let dslot = *self.local_index.entry(d).or_insert_with(|| { let id=self.next_local; self.next_local+=1; id }); b.store_local_i64(dslot); + self.known_i64.insert(d, n); } return Ok(true); } @@ -356,6 +366,35 @@ impl LowerCore { "length" => { let trace = std::env::var("NYASH_JIT_TRACE_LOWER_LEN").ok().as_deref() == Some("1"); if self.box_type_map.get(array).map(|s| s == "StringBox").unwrap_or(false) { + // Try literal constant fold first for stability + let mut lit: Option = None; + for (_bid, bb) in func.blocks.iter() { + for ins in bb.instructions.iter() { + if let crate::mir::MirInstruction::NewBox { dst, box_type, args } = ins { + if dst == array && box_type == "StringBox" && args.len() == 1 { + if let Some(src) = args.get(0) { + if let Some(s) = self.known_str.get(src).cloned() { lit = Some(s); break; } + // Fallback: scan Const directly + for (_b2, bb2) in func.blocks.iter() { + for ins2 in bb2.instructions.iter() { + if let crate::mir::MirInstruction::Const { dst: cdst, value } = ins2 { + if cdst == src { if let crate::mir::ConstValue::String(sv) = value { lit = Some(sv.clone()); break; } } + } + } + if lit.is_some() { break; } + } + } + } + } + } + if lit.is_some() { break; } + } + if let Some(s) = lit { + let n = s.len() as i64; + b.emit_const_i64(n); + if let Some(d) = dst { let slot = *self.local_index.entry(d).or_insert_with(|| { let id=self.next_local; self.next_local+=1; id }); b.store_local_i64(slot); self.known_i64.insert(d, n); } + return Ok(true); + } // Reuse len handler, but ensure dst persistence if len handler did not handle let handled = self.lower_box_call(func, b, array, "len", args, dst)?; if handled { @@ -380,21 +419,65 @@ impl LowerCore { "len" | "length" => { match self.box_type_map.get(array).map(|s| s.as_str()) { Some("StringBox") => { + // Strong constant fold when literal mapping is known (allow disabling via NYASH_JIT_DISABLE_LEN_CONST=1) + if std::env::var("NYASH_JIT_DISABLE_LEN_CONST").ok().as_deref() != Some("1") + && self.string_box_literal.get(array).is_some() { + let s = self.string_box_literal.get(array).cloned().unwrap(); + let n = s.len() as i64; + b.emit_const_i64(n); + if let Some(d) = dst { let slot = *self.local_index.entry(d).or_insert_with(|| { let id=self.next_local; self.next_local+=1; id }); b.store_local_i64(slot); self.known_i64.insert(d, n); } + return Ok(true); + } if let Some(pidx) = self.param_index.get(array).copied() { self.emit_len_with_fallback_param(b, pidx); + // Persist into dst local so Return can reliably pick it up + if let Some(d) = dst { + let slot = *self + .local_index + .entry(d) + .or_insert_with(|| { + let id = self.next_local; + self.next_local += 1; + id + }); + b.store_local_i64(slot); + } return Ok(true); } if let Some(slot) = self.local_index.get(array).copied() { self.emit_len_with_fallback_local_handle(b, slot); + // Persist into dst local so Return can reliably pick it up + if let Some(d) = dst { + let slot = *self + .local_index + .entry(d) + .or_insert_with(|| { + let id = self.next_local; + self.next_local += 1; + id + }); + b.store_local_i64(slot); + } return Ok(true); } - // Try literal reconstruction + // Try literal reconstruction (skipped if disabled by env) let mut lit: Option = None; for (_bid, bb) in func.blocks.iter() { for ins in bb.instructions.iter() { if let crate::mir::MirInstruction::NewBox { dst, box_type, args } = ins { if dst == array && box_type == "StringBox" && args.len() == 1 { - if let Some(src) = args.get(0) { if let Some(s) = self.known_str.get(src).cloned() { lit = Some(s); break; } } + if let Some(src) = args.get(0) { + if let Some(s) = self.known_str.get(src).cloned() { lit = Some(s); break; } + // Fallback: scan Const directly + for (_b2, bb2) in func.blocks.iter() { + for ins2 in bb2.instructions.iter() { + if let crate::mir::MirInstruction::Const { dst: cdst, value } = ins2 { + if cdst == src { if let crate::mir::ConstValue::String(sv) = value { lit = Some(sv.clone()); break; } } + } + } + if lit.is_some() { break; } + } + } } } } @@ -412,6 +495,18 @@ impl LowerCore { let slot = { let id = self.next_local; self.next_local += 1; id }; b.store_local_i64(slot); self.emit_len_with_fallback_local_handle(b, slot); + // Persist into dst local so Return can reliably pick it up + if let Some(d) = dst { + let dslot = *self + .local_index + .entry(d) + .or_insert_with(|| { + let id = self.next_local; + self.next_local += 1; + id + }); + b.store_local_i64(dslot); + } return Ok(true); } Some("ArrayBox") => {}, diff --git a/src/jit/lower/core/string_len.rs b/src/jit/lower/core/string_len.rs index 25681d7c..83197729 100644 --- a/src/jit/lower/core/string_len.rs +++ b/src/jit/lower/core/string_len.rs @@ -28,6 +28,8 @@ impl LowerCore { b.load_local_i64(hslot); b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_LEN_H, 1, true); b.store_local_i64(t_string); + // debug: observe string len + b.emit_debug_i64_local(1100, t_string); // Any.length_h crate::jit::observe::lower_hostcall( crate::jit::r#extern::collections::SYM_ANY_LEN_H, @@ -39,11 +41,15 @@ impl LowerCore { b.load_local_i64(hslot); b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, true); b.store_local_i64(t_any); + // debug: observe any len + b.emit_debug_i64_local(1101, t_any); // cond = (string_len == 0) b.load_local_i64(t_string); b.emit_const_i64(0); b.emit_compare(CmpKind::Eq); b.store_local_i64(t_cond); + // debug: observe condition + b.emit_debug_i64_local(1102, t_cond); // select(cond ? any_len : string_len) b.load_local_i64(t_cond); // cond (bottom) b.load_local_i64(t_any); // then @@ -67,6 +73,7 @@ impl LowerCore { b.load_local_i64(slot); b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_LEN_H, 1, true); b.store_local_i64(t_string); + b.emit_debug_i64_local(1200, t_string); // Any.length_h crate::jit::observe::lower_hostcall( crate::jit::r#extern::collections::SYM_ANY_LEN_H, @@ -78,11 +85,13 @@ impl LowerCore { b.load_local_i64(slot); b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, true); b.store_local_i64(t_any); + b.emit_debug_i64_local(1201, t_any); // cond = (string_len == 0) b.load_local_i64(t_string); b.emit_const_i64(0); b.emit_compare(CmpKind::Eq); b.store_local_i64(t_cond); + b.emit_debug_i64_local(1202, t_cond); // select(cond ? any_len : string_len) b.load_local_i64(t_cond); b.load_local_i64(t_any); @@ -106,6 +115,7 @@ impl LowerCore { b.emit_string_handle_from_literal(s); b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_LEN_H, 1, true); b.store_local_i64(t_string); + b.emit_debug_i64_local(1300, t_string); // Any.length_h on literal handle (recreate handle; safe in v0) crate::jit::observe::lower_hostcall( crate::jit::r#extern::collections::SYM_ANY_LEN_H, @@ -117,11 +127,13 @@ impl LowerCore { b.emit_string_handle_from_literal(s); b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, true); b.store_local_i64(t_any); + b.emit_debug_i64_local(1301, t_any); // cond = (string_len == 0) b.load_local_i64(t_string); b.emit_const_i64(0); b.emit_compare(CmpKind::Eq); b.store_local_i64(t_cond); + b.emit_debug_i64_local(1302, t_cond); // select(cond ? any_len : string_len) b.load_local_i64(t_cond); b.load_local_i64(t_any); diff --git a/src/jit/lower/core_ops.rs b/src/jit/lower/core_ops.rs index b03b1bd4..7a3f85e1 100644 --- a/src/jit/lower/core_ops.rs +++ b/src/jit/lower/core_ops.rs @@ -138,6 +138,8 @@ impl LowerCore { impl LowerCore { // Push a value if known or param/local/phi pub(super) fn push_value_if_known_or_param(&self, b: &mut dyn IRBuilder, id: &ValueId) { + // Prefer compile-time known constants to avoid stale local slots overshadowing folded values + if let Some(v) = self.known_i64.get(id).copied() { b.emit_const_i64(v); return; } if let Some(slot) = self.local_index.get(id).copied() { b.load_local_i64(slot); return; } if self.phi_values.contains(id) { let pos = self.phi_param_index.iter().find_map(|((_, vid), idx)| if vid == id { Some(*idx) } else { None }).unwrap_or(0); @@ -149,7 +151,6 @@ impl LowerCore { return; } if let Some(pidx) = self.param_index.get(id).copied() { b.emit_param_i64(pidx); return; } - if let Some(v) = self.known_i64.get(id).copied() { b.emit_const_i64(v); return; } } // Coverage helper: increments covered/unsupported counts diff --git a/src/jit/lower/extern_thunks.rs b/src/jit/lower/extern_thunks.rs index 66e380a9..f86d5a33 100644 --- a/src/jit/lower/extern_thunks.rs +++ b/src/jit/lower/extern_thunks.rs @@ -115,6 +115,9 @@ pub(super) extern "C" fn nyash_box_birth_i64(type_id: i64, argc: i64, a1: i64, a #[cfg(feature = "cranelift-jit")] pub(super) extern "C" fn nyash_handle_of(v: i64) -> i64 { // If already a positive handle, pass through + if std::env::var("NYASH_JIT_TRACE_LEN").ok().as_deref() == Some("1") { + eprintln!("[JIT-HANDLE_OF] in v={}", v); + } if v > 0 { return v; } // Otherwise interpret as legacy param index and convert BoxRef -> handle if v >= 0 { @@ -126,6 +129,9 @@ pub(super) extern "C" fn nyash_handle_of(v: i64) -> i64 { out = crate::jit::rt::handles::to_handle(arc) as i64; } }); + if std::env::var("NYASH_JIT_TRACE_LEN").ok().as_deref() == Some("1") { + eprintln!("[JIT-HANDLE_OF] param_idx={} out_handle={}", idx, out); + } return out; } 0 @@ -844,7 +850,12 @@ pub(super) extern "C" fn nyash_string_from_u64x2(lo: u64, hi: u64, len: i64) -> if n > 8 { for i in 0..(n - 8) { buf[8 + i] = ((hi >> (8 * i)) & 0xFF) as u8; } } let s = match std::str::from_utf8(&buf[..n]) { Ok(t) => t.to_string(), Err(_) => String::from_utf8_lossy(&buf[..n]).to_string() }; let arc: std::sync::Arc = std::sync::Arc::new(crate::box_trait::StringBox::new(s)); - crate::jit::rt::handles::to_handle(arc) as i64 + let h = crate::jit::rt::handles::to_handle(arc.clone()) as i64; + if std::env::var("NYASH_JIT_TRACE_LEN").ok().as_deref() == Some("1") { + if let Some(sb) = arc.as_any().downcast_ref::() { eprintln!("[JIT-STR_H] new handle={} val='{}' len={}", h, sb.value, sb.value.len()); } + else { eprintln!("[JIT-STR_H] new handle={} (non-StringBox)", h); } + } + h } // Create an instance by type name via global unified registry: birth(name) -> handle diff --git a/test_len_any b/test_len_any new file mode 100644 index 00000000..74e14d23 Binary files /dev/null and b/test_len_any differ