diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 70862670..7a3a7b2e 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -4,6 +4,114 @@ — 最終更新: 2025‑09‑06 (Phase 15.16 反映, AOT/JIT-AOT 足場強化) +【ハンドオフ(2025‑09‑06 2nd)— AOT/JIT‑AOT String.length 修正進捗と引き継ぎ】 + +概要 +- 目的: AOT/JIT‑AOT で `StringBox.length/len` が 0 になるケースの是正と足場強化。 +- 方針: 受けをハンドル化して `nyash.string.len_h` を優先呼び出し、0 の場合に `nyash.any.length_h` へフォールバック(select)する二段経路を Lower に実装。型/ハンドル伝播は Param/Local/リテラルの順でカバー。 + +実装(済) +- LowerCore: 二段フォールバック実装を追加(Param/Local/リテラル)。 + - `emit_len_with_fallback_param`/`_local_handle`/`_literal` + - `core.rs` の `len/length` で二段フォールバックを使用。結果をローカルスロットへ保存(Return で拾えるように) +- BoxCall 共通(ops_ext): + - StringBox の `len/length` を最優先で処理(Param/Local/リテラル/handle.of)。 + - リテラル `new StringBox("...")` → `length` は即値畳み込み(const 返却)。 +- Hostcall registry 追補: `nyash.string.len_h` を ReadOnly 登録 + 署名 `(Handle)->I64` 追加。 +- JIT ブリッジ: `extern_thunks.rs` に `nyash_string_len_h` を追加、`cranelift` ビルダーに `SYM_STRING_LEN_H` を登録。 +- ポリシー: `StringBox.length/len` マッピングを `nyash.any.length_h` → `nyash.string.len_h` に是正。 +- デッドコード整理: 旧 `lower_boxcall_simple_reads` を削除(conflict 回避)。 +- ツール/スモーク: `tools/aot_smoke_cranelift.sh` 追加、`apps/smokes/jit_aot_string_length_smoke.nyash` 追加。 + +確認状況 +- `apps/smokes/jit_aot_string_min.nyash`(concat/eq): AOT 連結→`Result: 1`(OK)。 +- `apps/smokes/jit_aot_string_length_smoke.nyash`(print 経由): AOT .o 生成/リンクは通るが、稀に segfault(DT_TEXTREL 警告あり)。再現性低。TLS/extern 紐付け順の追跡要。 +- `apps/smokes/jit_aot_any_len_string.nyash`: 依然 `Result: 0`。lower は `string.len_h` 優先・二段 select 経路に切替済み。値保存の材化は追加済。残る根因は Return 直前の値材化/参照不整合の可能性が高い(下記 TODO)。 + +残課題(優先) +1) Return 材化の強化(JIT‑direct / JIT‑AOT 共通) + - 症状: `len/length` の計算値が Return シーンで 0 に化けるケース。 + - 推定: `push_value_if_known_or_param` が unknown を 0 補完するため、BoxCall 結果がローカルに材化されていない/ValueId 不一致時に 0 が返る。 + - 対応: `I::Return { value }` で materialize 後方走査を実装。 + - 現 BB を後方走査し、`value` を定義した命令(BoxCall/Call/Select 等)を特定→スタックに積む/ローカル保存→Return へ接続。 + - 既存のローカル保存(本変更で追加)も活用。 + +進捗(2025‑09‑06 3rd 追記) +- ops_ext: StringBox.len/length の結果を必ずローカルに保存するよう修正(Return が確実に値を拾える) + - 対象: param/local/literal/handle.of 各経路。`dst` があれば `local_index` に slot を割当てて `store_local_i64`。 +- デバッグ計測を追加 + - JIT Lower 追跡: `NYASH_JIT_TRACE_LOWER=1`(BoxCall の handled 判定/box_type/dst 有無) + - Return 追跡: `NYASH_JIT_TRACE_RET=1`(known/param/local の命中状況) + - ローカルslot I/O: `NYASH_JIT_TRACE_LOCAL=1`(`store/load idx=` を吐く) + - String.len_h 実行: `NYASH_JIT_TRACE_LEN=1`(thunk 到達と any.length_h フォールバック値を吐く) +- 再現確認 + - `apps/smokes/jit_aot_any_len_string.nyash` は依然 Result: 0(JIT-direct)。 + - 追跡ログ(要 `NYASH_JIT_TRACE_LOWER=1 NYASH_JIT_TRACE_RET=1 NYASH_JIT_TRACE_LOCAL=1`) + - `BoxCall ... method=length handled=true box_type=Some("StringBox") dst?=true` + - ローカル slot の流れ: `idx=0` recv(handle) → `idx=1` string_len → `idx=2` any_len → `idx=3` cond → select → `idx=4` dst 保存 → Return で `load idx=4` + - つまり lowering/Return/ローカル材化は正しく配線されている。 + - しかし `NYASH_JIT_TRACE_LEN=1` の thunk ログが出ず、`nyash.string.len_h` が実行されていない/0 を返している可能性が高い。 + - 仮説: Cranelift import のシンボル解決が `extern_thunks::nyash_string_len_h` ではなく別実装(0返却)に解決されている/あるいは呼出し自体が落ちて 0 初期値になっている。 + - 参考: CraneliftBuilder では `builder.symbol(c::SYM_STRING_LEN_H, nyash_string_len_h as *const u8)` を設定済み。 + +暫定変更(フォールバック強化) +- `ops_ext` の StringBox.len で「リテラル復元(NewBox(StringBox, Const String))」を param/local より先に優先。 + - JIT-AOT 経路で文字列リテラルの length は常に即値化(select/hostcall を経由せず 3 を返す)。 + - ただし今回のケースでは local 経路が発火しており、まだ 0 のまま(hostcall 実行が 0 を返している疑い)。 + +未解決/次アクション(デバッグ指針) +- [ ] `nyash.string.len_h` が実際にどの関数へリンクされているかを確認 + - Cranelift JIT: `src/jit/lower/builder/cranelift.rs` の `builder.symbol(...)` 群は設定済みだが、実行時に thunk 側の `eprintln` が出ない。 + - 追加案: `emit_host_call` で宣言した `func_id` と `builder.symbol` 登録可否の整合をダンプ(シンボル直列化や missing import の検知)。 +- [ ] `extern_thunks::nyash_string_len_h` へ確実に到達させるため、一時的に `emit_len_with_fallback_*` で `SYM_STRING_LEN_H` を文字列リテラル直書きではなく定数経由に統一。 +- [ ] `nyash.string.from_u64x2` の呼び出し可否を同様にトレース(`NYASH_JIT_TRACE_LOCAL=1` の直後に `NYASH_JIT_TRACE_LEN=1` が見えるか) +- [ ] ワークアラウンド検証: `NYASH_JIT_HOST_BRIDGE=1` 強制でも 0 → host-bridge 経路の呼び出しが発火していない可能性。bridge シンボル登録も再確認。 + +メモ/所見 +- lowering と Return 材化(ローカルslot への保存→Return で load)は動いている。値自体が 0 になっているので hostcall 側の解決/戻りが疑わしい。 +- AOT .o の生成は成功。segv は今回は再現せず。 + +実行コマンド(デバッグ用) +- `NYASH_JIT_TRACE_LOWER=1 NYASH_JIT_TRACE_RET=1 NYASH_JIT_TRACE_LOCAL=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` +- 追加で thunk 到達確認: `NYASH_JIT_TRACE_LEN=1 ...`(現状は無出力=未到達の可能性) + +2) 診断イベントの追加(軽量) + - `emit_len_with_fallback_*` と `lower_box_call(len/length)` に `observe::lower_hostcall` を追加し、 + Param/Local/リテラル/handle.of どの経路か、select の条件(string_len==0)をトレース可能にする(`NYASH_JIT_EVENTS=1`)。 + +3) AOT segfault (稀発) の追跡 + - `tools/aot_smoke_cranelift.sh` 実行中に稀に segv(`.o` 生成直後/リンク前後)。 + - `nyash.string.from_u64x2` 載せ替えと DT_TEXTREL 警告が出るので、PIE/LTO/relro 周りと TLS/extern の登録順を確認。 + +4) 警告のノイズ低減(低優先) + - `core_hostcall.rs` の unreachable 警告(case 統合の名残)。 + - `jit/lower/*` の unused 変数/unused mut の警告。 + +影響ファイル(今回差分) +- `src/jit/lower/core.rs`(len/length 二段フォールバック呼出し、保存強化) +- `src/jit/lower/core/ops_ext.rs`(StringBox len/length 優先処理、リテラル即値畳み込み、保存) +- `src/jit/hostcall_registry.rs`(`nyash.string.len_h` 追補) +- `src/jit/extern/collections.rs`(`SYM_STRING_LEN_H` 追加) +- `src/jit/lower/extern_thunks.rs`(`nyash_string_len_h` 追加) +- `src/jit/lower/builder/cranelift.rs`(`SYM_STRING_LEN_H` のシンボル登録) +- `tools/aot_smoke_cranelift.sh`(新規) +- `apps/smokes/jit_aot_string_length_smoke.nyash`(新規) + +再現/確認コマンド +- ビルド(JIT/AOT): `cargo build --release --features cranelift-jit` +- JIT‑AOT(.o出力): `NYASH_AOT_OBJECT_OUT=target/aot_objects/test_len_any.o ./target/release/nyash --jit-direct apps/smokes/jit_aot_any_len_string.nyash` +- AOT 連結〜実行: `bash tools/aot_smoke_cranelift.sh apps/smokes/jit_aot_string_min.nyash app_str` + +次アクション(引き継ぎ TODO) +- [ ] Return の後方走査材化を実装(BoxCall/Call/Select 等の定義→保存→Return 接続)。 +- [ ] `emit_len_with_fallback_*` / `lower_box_call(len/length)` にイベント出力を追加(選択分岐/経路ログ)。 +- [ ] AOT segv の最小再現収集(PIE/relro/TLSの前提確認)→ `nyrt` 側エクスポート/リンカフラグ点検。 +- [ ] `NYASH_USE_PLUGIN_BUILTINS=1` 時の `length` も robust path を常に使用することを E2E で再確認。 + +メモ +- `jit_aot_any_len_string.nyash` は `return s.length()` の Return 経路解決が決め手。材化を強化すれば `3` が期待値。 +- 既存の Array/Map 経路・他の smokes は影響なし(len/size/get/has/set の HostCall/PluginInvoke は従来どおり)。 + ■ 進捗サマリ - Phase 12 クローズアウト完了。言語糖衣(12.7-B/P0)と VM 分割は反映済み。 - Phase 15(Self-Hosting: Cranelift AOT)へフォーカス移行。 diff --git a/apps/tmp_len_stringbox_probe.nyash b/apps/tmp_len_stringbox_probe.nyash new file mode 100644 index 00000000..9cc4e3db --- /dev/null +++ b/apps/tmp_len_stringbox_probe.nyash @@ -0,0 +1,12 @@ +// Probe: StringBox.length() value path under JIT-direct +static box Main { + main() { + local s + s = new StringBox("abc") + local n + n = s.length() + print("len=" + n.toString()) + return n + } +} + diff --git a/src/jit/lower/builder/cranelift.rs b/src/jit/lower/builder/cranelift.rs index 94feaaa8..e38002d7 100644 --- a/src/jit/lower/builder/cranelift.rs +++ b/src/jit/lower/builder/cranelift.rs @@ -685,6 +685,9 @@ impl IRBuilder for CraneliftBuilder { else { let one = fb.ins().iconst(types::I64, 1); let zero = fb.ins().iconst(types::I64, 0); let b1 = fb.ins().icmp_imm(IntCC::NotEqual, v, 0); v = fb.ins().select(b1, one, zero); } } if let Some(slot) = slot { fb.ins().stack_store(v, slot, 0); } + if std::env::var("NYASH_JIT_TRACE_LOCAL").ok().as_deref() == Some("1") { + eprintln!("[JIT-LOCAL] store idx={} (tracked_slots={})", index, self.local_slots.len()); + } }); } } @@ -693,6 +696,9 @@ impl IRBuilder for CraneliftBuilder { if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); } if let Some(&slot) = self.local_slots.get(&index) { let v = Self::with_fb(|fb| fb.ins().stack_load(types::I64, slot, 0)); + if std::env::var("NYASH_JIT_TRACE_LOCAL").ok().as_deref() == Some("1") { + eprintln!("[JIT-LOCAL] load idx={} (tracked_slots={})", index, self.local_slots.len()); + } self.value_stack.push(v); self.stats.0 += 1; } } diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index 232a99c1..207cf728 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -532,6 +532,15 @@ impl LowerCore { I::Branch { .. } => self.lower_branch(b), I::Return { value } => { if let Some(v) = value { + if std::env::var("NYASH_JIT_TRACE_RET").ok().as_deref() == Some("1") { + eprintln!( + "[LOWER] Return value={:?} known_i64?={} param?={} local?={}", + v, + self.known_i64.contains_key(v), + self.param_index.contains_key(v), + 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() { self.push_value_if_known_or_param(b, v); @@ -623,7 +632,10 @@ impl LowerCore { } I::BoxCall { box_val: array, method, args, dst, .. } => { // Prefer ops_ext; if not handled, fall back to legacy path below - if self.lower_box_call(func, b, &array, method.as_str(), args, dst.clone())? { + let trace = std::env::var("NYASH_JIT_TRACE_LOWER").ok().as_deref() == Some("1"); + 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 { return Ok(()); } } @@ -791,6 +803,10 @@ impl LowerCore { if let Some(pidx) = self.param_index.get(array).copied() { // Param 経路: string.len_h → 0 の場合 any.length_h へフォールバック self.emit_len_with_fallback_param(b, pidx); + if let Some(d) = dst.as_ref() { + 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 { crate::jit::events::emit_lower( serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}), @@ -800,6 +816,10 @@ impl LowerCore { if let Some(slot) = self.local_index.get(array).copied() { // ローカルハンドル: string.len_h → any.length_h フォールバック self.emit_len_with_fallback_local_handle(b, slot); + if let Some(d) = dst.as_ref() { + let slotd = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); + b.store_local_i64(slotd); + } } else if self.box_type_map.get(array).map(|s| s == "StringBox").unwrap_or(false) { // Attempt reconstruction for StringBox literal: scan NewBox(StringBox, Const String) let mut lit: Option = None; @@ -825,15 +845,27 @@ impl LowerCore { if let Some(s) = lit { // リテラル復元: string.len_h → any.length_h フォールバック self.emit_len_with_fallback_literal(b, &s); + if let Some(d) = dst.as_ref() { + let slotd = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); + b.store_local_i64(slotd); + } } else { let arr_idx = -1; b.emit_const_i64(arr_idx); b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some()); + if let Some(d) = dst.as_ref() { + let slotd = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); + b.store_local_i64(slotd); + } } } else { let arr_idx = -1; b.emit_const_i64(arr_idx); b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some()); + if let Some(d) = dst.as_ref() { + let slotd = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); + b.store_local_i64(slotd); + } } } } diff --git a/src/jit/lower/core/ops_ext.rs b/src/jit/lower/core/ops_ext.rs index f933beec..4b66720c 100644 --- a/src/jit/lower/core/ops_ext.rs +++ b/src/jit/lower/core/ops_ext.rs @@ -179,14 +179,32 @@ impl LowerCore { if matches!(method, "length" | "is_empty" | "charCodeAt") { if method == "length" { // Prefer robust fallback path (param/local/literal/handle.of) - if let Some(pidx) = self.param_index.get(array).copied() { self.emit_len_with_fallback_param(b, pidx); return Ok(true); } - if let Some(slot) = self.local_index.get(array).copied() { self.emit_len_with_fallback_local_handle(b, slot); return Ok(true); } + if let Some(pidx) = self.param_index.get(array).copied() { + self.emit_len_with_fallback_param(b, pidx); + 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); + 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); + } // literal? 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 lit.is_some() { break; } } - if let Some(s) = lit { self.emit_len_with_fallback_literal(b, &s); return Ok(true); } + if let Some(s) = lit { + let n = s.len() as i64; + b.emit_const_i64(n); + if let Some(d) = dst { + self.known_i64.insert(d, n); + 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); + } // last resort: handle.of + any.length_h self.push_value_if_known_or_param(b, array); b.emit_host_call("nyash.handle.of", 1, true); b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, true); + 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); } // is_empty / charCodeAt: keep mapped hostcall path @@ -235,6 +253,7 @@ impl LowerCore { } // String.len/length: robust handling "len" => { + let trace = std::env::var("NYASH_JIT_TRACE_LOWER_LEN").ok().as_deref() == Some("1"); // (1) const string literal case let mut lit_len: Option = None; for (_bbid, bb) in func.blocks.iter() { @@ -249,29 +268,32 @@ impl LowerCore { if lit_len.is_some() { break; } } if let Some(n) = lit_len { + if trace { eprintln!("[LOWER] StringBox.len: literal length={} (dst?={})", n, dst.is_some()); } b.emit_const_i64(n); + if let Some(d) = dst { + // Persist literal length so Return can reliably load + 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); } // (2) prefer host-bridge when enabled if std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1") { if self.box_type_map.get(array).map(|s| s == "StringBox").unwrap_or(false) { if std::env::var("NYASH_JIT_TRACE_BRIDGE").ok().as_deref() == Some("1") { eprintln!("[LOWER]string.len via host-bridge"); } + if trace { eprintln!("[LOWER] StringBox.len via host-bridge (dst?={})", dst.is_some()); } self.push_value_if_known_or_param(b, array); b.emit_host_call(crate::jit::r#extern::host_bridge::SYM_HOST_STRING_LEN, 1, dst.is_some()); + 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); } } // (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) { - if let Some(pidx) = self.param_index.get(array).copied() { - self.emit_len_with_fallback_param(b, pidx); - return Ok(true); - } - if let Some(slot) = self.local_index.get(array).copied() { - self.emit_len_with_fallback_local_handle(b, slot); - return Ok(true); - } - // Try to reconstruct literal handle + // Prefer literal reconstruction so JIT-AOT path is deterministic let mut lit: Option = None; for (_bid, bb) in func.blocks.iter() { for ins in bb.instructions.iter() { @@ -285,23 +307,71 @@ impl LowerCore { } if lit.is_some() { break; } } - if let Some(s) = lit { self.emit_len_with_fallback_literal(b, &s); return Ok(true); } + 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); + 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); + } + // Param/local fallback when not a reconstructable literal + if let Some(pidx) = self.param_index.get(array).copied() { + if trace { eprintln!("[LOWER] StringBox.len param p{} (dst?={})", pidx, dst.is_some()); } + self.emit_len_with_fallback_param(b, pidx); + 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() { + if trace { eprintln!("[LOWER] StringBox.len local slot#{} (dst?={})", slot, dst.is_some()); } + self.emit_len_with_fallback_local_handle(b, slot); + 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); + } // As a last resort, convert receiver to handle via nyash.handle.of and apply fallback on temp slot + if trace { eprintln!("[LOWER] StringBox.len last-resort handle.of + fallback (dst?={})", dst.is_some()); } self.push_value_if_known_or_param(b, array); b.emit_host_call("nyash.handle.of", 1, true); let t_recv = { let id = self.next_local; self.next_local += 1; id }; b.store_local_i64(t_recv); self.emit_len_with_fallback_local_handle(b, t_recv); + 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); } // Not a StringBox: let other branches handle + if trace { eprintln!("[LOWER] StringBox.len not handled (box_type={:?})", self.box_type_map.get(array)); } return Ok(false); } // Alias: String.length → same as len "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) { - // Reuse len handler - return self.lower_box_call(func, b, array, "len", args, dst); + // 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 { + // len branch already persisted when dst.is_some() + return Ok(true); + } + // As a conservative fallback, try direct any.length_h on handle.of + if trace { eprintln!("[LOWER] StringBox.length fallback any.length_h on handle.of (dst?={})", dst.is_some()); } + self.push_value_if_known_or_param(b, array); + b.emit_host_call("nyash.handle.of", 1, true); + b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, dst.is_some()); + 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); } // Array length is handled below; otherwise not handled here return Ok(false); @@ -330,7 +400,12 @@ impl LowerCore { } if lit.is_some() { break; } } - if let Some(s) = lit { self.emit_len_with_fallback_literal(b, &s); return Ok(true); } + 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); + } // Last resort: handle.of self.push_value_if_known_or_param(b, array); b.emit_host_call("nyash.handle.of", 1, true); @@ -345,6 +420,7 @@ impl LowerCore { self.push_value_if_known_or_param(b, array); b.emit_host_call("nyash.handle.of", 1, true); b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, true); + 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); } } diff --git a/src/jit/lower/extern_thunks.rs b/src/jit/lower/extern_thunks.rs index 4c36ed9b..66e380a9 100644 --- a/src/jit/lower/extern_thunks.rs +++ b/src/jit/lower/extern_thunks.rs @@ -543,12 +543,15 @@ pub(super) extern "C" fn nyash_string_charcode_at_h(handle: u64, idx: i64) -> i6 #[cfg(feature = "cranelift-jit")] pub(super) extern "C" fn nyash_string_len_h(handle: u64) -> i64 { events::emit_runtime(serde_json::json!({"id": c::SYM_STRING_LEN_H, "decision":"allow", "argc":1, "arg_types":["Handle"]}), "hostcall", ""); + if std::env::var("NYASH_JIT_TRACE_LEN").ok().as_deref() == Some("1") { eprintln!("[JIT-LEN_H] handle={}", handle); } if handle > 0 { if let Some(obj) = crate::jit::rt::handles::get(handle) { if let Some(sb) = obj.as_any().downcast_ref::() { return sb.value.len() as i64; } } // Fallback to any.length_h for non-string handles - return nyash_any_length_h(handle); + let v = nyash_any_length_h(handle); + if std::env::var("NYASH_JIT_TRACE_LEN").ok().as_deref() == Some("1") { eprintln!("[JIT-LEN_H] any.length_h(handle={}) -> {}", handle, v); } + return v; } // Legacy param index fallback (0..16): read from VM args if handle <= 16 {