AOT/JIT: StringBox.length デバッグ追跡とローカル材化強化

- ops_ext: StringBox.len/lengthの結果を必ずローカルに保存
  - param/local/literal/handle.of全経路で dst があれば local_index に格納
  - Returnが確実に値を拾えるよう修正

- デバッグ計測追加:
  - NYASH_JIT_TRACE_LOWER: BoxCall処理の追跡
  - NYASH_JIT_TRACE_RET: Return時の値解決追跡
  - NYASH_JIT_TRACE_LOCAL: ローカルslot I/O追跡
  - NYASH_JIT_TRACE_LEN: string.len_h thunk実行追跡

- 診断用プローブ追加: tmp_len_stringbox_probe.nyash
- CURRENT_TASK更新: 3rdハンドオフ進捗記録

現状: lowering/Return/ローカル材化は正しく配線されているが、
hostcall実行時に0を返している疑い。シンボル解決の追跡継続中。

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Tomoaki
2025-09-06 07:45:20 +09:00
parent 4c5301e700
commit 8c02093cfe
6 changed files with 255 additions and 18 deletions

View File

@ -4,6 +4,114 @@
— 最終更新: 20250906 (Phase 15.16 反映, AOT/JIT-AOT 足場強化)
【ハンドオフ20250906 2nd— AOT/JITAOT String.length 修正進捗と引き継ぎ】
概要
- 目的: AOT/JITAOT で `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 生成/リンクは通るが、稀に segfaultDT_TEXTREL 警告あり。再現性低。TLS/extern 紐付け順の追跡要。
- `apps/smokes/jit_aot_any_len_string.nyash`: 依然 `Result: 0`。lower は `string.len_h` 優先・二段 select 経路に切替済み。値保存の材化は追加済。残る根因は Return 直前の値材化/参照不整合の可能性が高い(下記 TODO
残課題(優先)
1) Return 材化の強化JITdirect / JITAOT 共通)
- 症状: `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 へ接続。
- 既存のローカル保存(本変更で追加)も活用。
進捗20250906 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_typedst 有無)
- Return 追跡: `NYASH_JIT_TRACE_RET=1`known/param/local の命中状況)
- ローカルslot I/O: `NYASH_JIT_TRACE_LOCAL=1``store/load idx=<N>` を吐く)
- String.len_h 実行: `NYASH_JIT_TRACE_LEN=1`thunk 到達と any.length_h フォールバック値を吐く)
- 再現確認
- `apps/smokes/jit_aot_any_len_string.nyash` は依然 Result: 0JIT-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`
- JITAOT.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 15Self-Hosting: Cranelift AOTへフォーカス移行。

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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<String> = 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);
}
}
}
}

View File

@ -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<String> = 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<i64> = 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<String> = 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);
}
}

View File

@ -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", "<jit>");
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::<crate::box_trait::StringBox>() { 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 {