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

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