Phase A リファクタリング: String.length デバッグ基盤の強化

## 実装内容(振る舞い変更なし)

A-1: Hostcall シンボルの定数化
- 直書き文字列を SYM_* 定数に統一
- nyash.handle.of / nyash.string.len_h / nyash.console.birth_h

A-2: string_len ヘルパー抽出(共通化)
- src/jit/lower/core/string_len.rs 新設
- emit_len_with_fallback_{param,local_handle,literal} を移設
- 二段フォールバック(string.len_h → any.length_h)の集約

A-3: 観測の統一
- import 呼び出しトレース機能を追加(NYASH_JIT_TRACE_IMPORT=1)
- CraneliftBuilder/ObjectBuilder の emit_host_call に構造化イベント
- observe::lower_hostcall で len_h/any.length_h の追跡

## 今後の道筋(CURRENT_TASK.md に記載)
- P0: フェイルセーフ(NYASH_LEN_FORCE_BRIDGE=1)
- P1: シンボル解決の可視化
- P2: リテラル最優先の安定化
- P3: Return 材化の後方走査

バグは手強いけど、デバッグ基盤が整ったにゃ!

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Tomoaki
2025-09-06 08:09:46 +09:00
parent 8c02093cfe
commit f6e0d5111e
7 changed files with 261 additions and 93 deletions

View File

@ -2,7 +2,7 @@
このドキュメントは「いま何をすれば良いか」を最小で共有するためのコンパクト版です。詳細は git 履歴と `docs/`phase-15を参照してください。 このドキュメントは「いま何をすれば良いか」を最小で共有するためのコンパクト版です。詳細は git 履歴と `docs/`phase-15を参照してください。
— 最終更新: 20250906 (Phase 15.16 反映, AOT/JIT-AOT 足場強化) — 最終更新: 20250906 (Phase 15.16 反映, AOT/JIT-AOT 足場強化 + Phase A リファクタ着手準備)
【ハンドオフ20250906 2nd— AOT/JITAOT String.length 修正進捗と引き継ぎ】 【ハンドオフ20250906 2nd— AOT/JITAOT String.length 修正進捗と引き継ぎ】
@ -75,6 +75,60 @@
- `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` - `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 ...`(現状は無出力=未到達の可能性) - 追加で thunk 到達確認: `NYASH_JIT_TRACE_LEN=1 ...`(現状は無出力=未到達の可能性)
Phase A 進捗(実施済)
- A1: Hostcall シンボルの定数化(直書き排除)完了
- `nyash.handle.of` / `nyash.string.len_h` / `nyash.console.birth_h``SYM_*` に統一
- A2: string_len ヘルパ抽出(共通化)完了
- `src/jit/lower/core/string_len.rs` 新設、`emit_len_with_fallback_*` を移設
- 呼び出し元はそのまま(挙動は不変)
- A3: 観測の統一(第一弾)
- string_len 内で `observe::lower_hostcall` を発火len_h/any.length_h
- Cranelift/ObjectBuilder の `emit_host_call[_typed]``NYASH_JIT_TRACE_IMPORT=1` によるインポート解決ログを追加
観測結果A3 導入後)
- `NYASH_JIT_TRACE_IMPORT=1``nyash.string.len_h` / `nyash.any.length_h` の import 呼び出しを確認JIT/AOT 両方)
- それでも `NYASH_JIT_TRACE_LEN=1` の thunk 到達ログは出ず → 依然解決先に差異がある疑い(要継続調査)
■ 緑への道筋(短期着地プラン)
P0: フェイルセーフ(テストを緑にする最短経路)
- 追加フラグ: `NYASH_LEN_FORCE_BRIDGE=1` で StringBox.len/length を暫定的に hostbridge (`nyash.host.string.len`) に強制JIT でも常に正しい長さを返す)。
- 実装: ops_ext の StringBox.len/length で当該フラグを見て bridge 経路へ分岐。
- 影響範囲限定(読み取り系のみ)。スモーク/CI を先に緑化。
P1: ひも付けの可視化と是正(根因切り分け)
- CraneliftBuilder::new の `builder.symbol(...)` 登録を JSON で列挙id→アドレスの疑似ダンプ
- import 発行側emit_host_call/_typedと登録側の id を突き合わせ、`nyash.string.len_h` の実アドレスが `extern_thunks::nyash_string_len_h` に一致することを確認。相違なら登録漏れ/重複名を是正。
P2: リテラル最優先の安定化
- NewBox(StringBox, Const String) → length は必ず即値化const fold
- 実装補強: `box_type_map` に加えて「NewBox(StringBox) の引数→Const String」の逆引きテーブルを構築して判定を O(1) に。param/local 経路より前に評価。
P3: Return 材化の後方走査(再発防止)
- `I::Return { value }` で、未材化値に対し現BBを後方走査BoxCall/Call/Select/Const
- 見つけた生成値をローカルslotに保存→Return直前に load。
- 既存の len/length 結果保存と併用し、0化の再発を根治。
P4: 仕上げ(重複/直書きの整理)
- ops_ext の重複分岐len/length の多重ガード)を削除し、`core/string_len.rs` に集約。
- 残る直書きシンボルを `SYM_*` に統一(検索: `nyash.` 直書き)。
■ 検証チェックリスト
- JIT 直実行(強制 bridge 無効): `./target/release/nyash --jit-direct apps/smokes/jit_aot_string_min.nyash` → Result:1
- JIT 直実行len 強制 bridge 有効): `NYASH_LEN_FORCE_BRIDGE=1 ./target/release/nyash --jit-direct apps/smokes/jit_aot_any_len_string.nyash` → Result:3緑化
- import/登録の一致: `NYASH_JIT_EVENTS=1 NYASH_JIT_TRACE_IMPORT=1 ...``id=nyash.string.len_h` の import と登録ダンプを突合id一致を確認
— Phase A無振る舞い変更リファクタ方針着手予定
- A1: Hostcall シンボルを定数に統一(直書き排除)
- `"nyash.handle.of"``jit::extern::handles::SYM_HANDLE_OF`
- `"nyash.string.len_h"``jit::extern::collections::SYM_STRING_LEN_H`
- `"nyash.console.birth_h"` → 既存の定数へ(なければ `extern::...` に追加して使用)
- A2: 長さ取得の共通化
- 新規: `src/jit/lower/core/string_len.rs`
- 既存の `emit_len_with_fallback_{param,local,literal}` をこのモジュールへ抽出し、`core.rs`/`ops_ext.rs` から呼び出すだけにする(挙動は据え置き)。
- 目的: 重複と分岐のばらけを解消し、シンボル差し替えや観測フックを一点で行えるようにする。
※ Phase A は「振る舞いを変えない」ことを厳守する。
2) 診断イベントの追加(軽量) 2) 診断イベントの追加(軽量)
- `emit_len_with_fallback_*``lower_box_call(len/length)``observe::lower_hostcall` を追加し、 - `emit_len_with_fallback_*``lower_box_call(len/length)``observe::lower_hostcall` を追加し、
Param/Local/リテラル/handle.of どの経路か、select の条件string_len==0をトレース可能にする`NYASH_JIT_EVENTS=1`)。 Param/Local/リテラル/handle.of どの経路か、select の条件string_len==0をトレース可能にする`NYASH_JIT_EVENTS=1`)。

View File

@ -31,6 +31,7 @@ pub const SYM_STRING_CHARCODE_AT_H: &str = "nyash.string.charCodeAt_h";
pub const SYM_STRING_LEN_H: &str = "nyash.string.len_h"; pub const SYM_STRING_LEN_H: &str = "nyash.string.len_h";
pub const SYM_STRING_BIRTH_H: &str = "nyash.string.birth_h"; pub const SYM_STRING_BIRTH_H: &str = "nyash.string.birth_h";
pub const SYM_INTEGER_BIRTH_H: &str = "nyash.integer.birth_h"; pub const SYM_INTEGER_BIRTH_H: &str = "nyash.integer.birth_h";
pub const SYM_CONSOLE_BIRTH_H: &str = "nyash.console.birth_h";
// String-like operations (handle, handle) // String-like operations (handle, handle)
pub const SYM_STRING_CONCAT_HH: &str = "nyash.string.concat_hh"; pub const SYM_STRING_CONCAT_HH: &str = "nyash.string.concat_hh";
pub const SYM_STRING_EQ_HH: &str = "nyash.string.eq_hh"; pub const SYM_STRING_EQ_HH: &str = "nyash.string.eq_hh";

View File

@ -426,6 +426,22 @@ impl IRBuilder for CraneliftBuilder {
} }
fn emit_host_call(&mut self, symbol: &str, _argc: usize, has_ret: bool) { fn emit_host_call(&mut self, symbol: &str, _argc: usize, has_ret: bool) {
use cranelift_codegen::ir::{AbiParam, Signature, types}; use cranelift_codegen::ir::{AbiParam, Signature, types};
// Structured lower event for import call
{
let mut arg_types: Vec<&'static str> = Vec::new();
for _ in 0.._argc { arg_types.push("I64"); }
crate::jit::events::emit_lower(
serde_json::json!({
"id": symbol,
"decision": "allow",
"reason": "import_call",
"argc": _argc,
"arg_types": arg_types,
"ret": if has_ret { "I64" } else { "Void" }
}),
"hostcall","<jit>"
);
}
let call_conv = self.module.isa().default_call_conv(); let call_conv = self.module.isa().default_call_conv();
let mut sig = Signature::new(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)
@ -440,6 +456,22 @@ impl IRBuilder for CraneliftBuilder {
} }
fn emit_host_call_typed(&mut self, symbol: &str, params: &[ParamKind], has_ret: bool, ret_is_f64: bool) { fn emit_host_call_typed(&mut self, symbol: &str, params: &[ParamKind], has_ret: bool, ret_is_f64: bool) {
use cranelift_codegen::ir::{AbiParam, Signature, types}; use cranelift_codegen::ir::{AbiParam, Signature, types};
// Structured lower event for typed import call
{
let mut arg_types: Vec<&'static str> = Vec::new();
for k in params { arg_types.push(match k { ParamKind::I64 | ParamKind::B1 => "I64", ParamKind::F64 => "F64" }); }
crate::jit::events::emit_lower(
serde_json::json!({
"id": symbol,
"decision": "allow",
"reason": "import_call_typed",
"argc": params.len(),
"arg_types": arg_types,
"ret": if has_ret { if ret_is_f64 { "F64" } else { "I64" } } else { "Void" }
}),
"hostcall","<jit>"
);
}
let mut args: Vec<cranelift_codegen::ir::Value> = Vec::new(); let mut args: Vec<cranelift_codegen::ir::Value> = Vec::new();
let take_n = params.len().min(self.value_stack.len()); let take_n = params.len().min(self.value_stack.len());
for _ in 0..take_n { if let Some(v) = self.value_stack.pop() { args.push(v); } } for _ in 0..take_n { if let Some(v) = self.value_stack.pop() { args.push(v); } }

View File

@ -285,6 +285,22 @@ impl IRBuilder for ObjectBuilder {
fn emit_host_call(&mut self, symbol: &str, argc: usize, has_ret: bool) { fn emit_host_call(&mut self, symbol: &str, argc: usize, has_ret: bool) {
use cranelift_codegen::ir::{AbiParam, Signature, types}; use cranelift_codegen::ir::{AbiParam, Signature, types};
use cranelift_frontend::FunctionBuilder; use cranelift_frontend::FunctionBuilder;
// Structured lower event for import call (AOT builder)
{
let mut arg_types: Vec<&'static str> = Vec::new();
for _ in 0..argc { arg_types.push("I64"); }
crate::jit::events::emit_lower(
serde_json::json!({
"id": symbol,
"decision": "allow",
"reason": "import_call",
"argc": argc,
"arg_types": arg_types,
"ret": if has_ret { "I64" } else { "Void" }
}),
"hostcall","<aot>"
);
}
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); } if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); } else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
@ -304,6 +320,22 @@ impl IRBuilder for ObjectBuilder {
fn emit_host_call_typed(&mut self, symbol: &str, params: &[super::ParamKind], has_ret: bool, ret_is_f64: bool) { fn emit_host_call_typed(&mut self, symbol: &str, params: &[super::ParamKind], has_ret: bool, ret_is_f64: bool) {
use cranelift_codegen::ir::{AbiParam, Signature, types}; use cranelift_codegen::ir::{AbiParam, Signature, types};
use cranelift_frontend::FunctionBuilder; use cranelift_frontend::FunctionBuilder;
// Structured lower event for typed import call (AOT builder)
{
let mut arg_types: Vec<&'static str> = Vec::new();
for k in params { arg_types.push(match k { super::ParamKind::I64 | super::ParamKind::B1 => "I64", super::ParamKind::F64 => "F64" }); }
crate::jit::events::emit_lower(
serde_json::json!({
"id": symbol,
"decision": "allow",
"reason": "import_call_typed",
"argc": params.len(),
"arg_types": arg_types,
"ret": if has_ret { if ret_is_f64 { "F64" } else { "I64" } } else { "Void" }
}),
"hostcall","<aot>"
);
}
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); } if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); } else if let Some(b) = self.entry_block { fb.switch_to_block(b); }

View File

@ -4,6 +4,7 @@ use super::builder::{IRBuilder, BinOpKind, CmpKind};
mod analysis; mod analysis;
mod cfg; mod cfg;
mod ops_ext; mod ops_ext;
mod string_len;
/// Lower(Core-1): Minimal lowering skeleton for Const/Move/BinOp/Cmp/Branch/Ret /// Lower(Core-1): Minimal lowering skeleton for Const/Move/BinOp/Cmp/Branch/Ret
/// This does not emit real CLIF yet; it only walks MIR and validates coverage. /// This does not emit real CLIF yet; it only walks MIR and validates coverage.
@ -182,90 +183,7 @@ impl LowerCore {
Ok(()) Ok(())
} }
/// Emit robust length retrieval with fallback for String/Any: // string_len helper moved to core/string_len.rs (no behavior change)
/// 1) Prefer `nyash.string.len_h(recv)`
/// 2) If that yields 0 at runtime, select `nyash.any.length_h(recv)`
/// Returns: pushes selected length (i64) onto builder stack.
fn emit_len_with_fallback_param(&mut self, b: &mut dyn IRBuilder, pidx: usize) {
use super::builder::CmpKind;
// Temp locals
let hslot = self.next_local; self.next_local += 1; // receiver handle slot
let t_string = self.next_local; self.next_local += 1;
let t_any = self.next_local; self.next_local += 1;
let t_cond = self.next_local; self.next_local += 1;
// Materialize receiver handle from param index
b.emit_param_i64(pidx);
b.emit_host_call("nyash.handle.of", 1, true);
b.store_local_i64(hslot);
// String.len_h
b.load_local_i64(hslot);
b.emit_host_call("nyash.string.len_h", 1, true);
b.store_local_i64(t_string);
// Any.length_h
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);
// 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);
// select(cond ? any_len : string_len)
b.load_local_i64(t_cond); // cond (bottom)
b.load_local_i64(t_any); // then
b.load_local_i64(t_string); // else
b.emit_select_i64();
}
fn emit_len_with_fallback_local_handle(&mut self, b: &mut dyn IRBuilder, slot: usize) {
use super::builder::CmpKind;
let t_string = self.next_local; self.next_local += 1;
let t_any = self.next_local; self.next_local += 1;
let t_cond = self.next_local; self.next_local += 1;
// String.len_h
b.load_local_i64(slot);
b.emit_host_call("nyash.string.len_h", 1, true);
b.store_local_i64(t_string);
// Any.length_h
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);
// 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);
// select(cond ? any_len : string_len)
b.load_local_i64(t_cond);
b.load_local_i64(t_any);
b.load_local_i64(t_string);
b.emit_select_i64();
}
fn emit_len_with_fallback_literal(&mut self, b: &mut dyn IRBuilder, s: &str) {
use super::builder::CmpKind;
let t_string = self.next_local; self.next_local += 1;
let t_any = self.next_local; self.next_local += 1;
let t_cond = self.next_local; self.next_local += 1;
// String.len_h on literal handle
b.emit_string_handle_from_literal(s);
b.emit_host_call("nyash.string.len_h", 1, true);
b.store_local_i64(t_string);
// Any.length_h on literal handle (recreate handle; safe in v0)
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);
// 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);
// select(cond ? any_len : string_len)
b.load_local_i64(t_cond);
b.load_local_i64(t_any);
b.load_local_i64(t_string);
b.emit_select_i64();
}
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId, func: &crate::mir::MirFunction) -> Result<(), String> { fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId, func: &crate::mir::MirFunction) -> Result<(), String> {
@ -340,7 +258,7 @@ impl LowerCore {
if field == "console" { if field == "console" {
// Emit hostcall to create/get ConsoleBox handle // Emit hostcall to create/get ConsoleBox handle
// Symbol exported by nyrt: nyash.console.birth_h // Symbol exported by nyrt: nyash.console.birth_h
b.emit_host_call("nyash.console.birth_h", 0, true); b.emit_host_call(crate::jit::r#extern::collections::SYM_CONSOLE_BIRTH_H, 0, true);
} else { } else {
// Unknown RefGet: treat as no-op const 0 to avoid strict fail for now // Unknown RefGet: treat as no-op const 0 to avoid strict fail for now
b.emit_const_i64(0); b.emit_const_i64(0);

View File

@ -77,7 +77,7 @@ impl LowerCore {
return Ok(()); return Ok(());
} }
// Ensure we have a Console handle (hostcall birth shim) // Ensure we have a Console handle (hostcall birth shim)
b.emit_host_call("nyash.console.birth_h", 0, true); b.emit_host_call(crate::jit::r#extern::collections::SYM_CONSOLE_BIRTH_H, 0, true);
// a1: first argument best-effort // a1: first argument best-effort
if let Some(arg0) = args.get(0) { self.push_value_if_known_or_param(b, arg0); } if let Some(arg0) = args.get(0) { self.push_value_if_known_or_param(b, arg0); }
// Resolve plugin invoke for ConsoleBox.method // Resolve plugin invoke for ConsoleBox.method
@ -203,15 +203,15 @@ impl LowerCore {
return Ok(true); return Ok(true);
} }
// last resort: handle.of + any.length_h // 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); self.push_value_if_known_or_param(b, array); b.emit_host_call(crate::jit::r#extern::handles::SYM_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); } 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); return Ok(true);
} }
// is_empty / charCodeAt: keep mapped hostcall path // is_empty / charCodeAt: keep mapped hostcall path
// Ensure receiver is a valid runtime handle (param or materialized via handle.of) // Ensure receiver is a valid runtime handle (param or materialized via handle.of)
if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); b.emit_host_call("nyash.handle.of", 1, true); } if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); b.emit_host_call(crate::jit::r#extern::handles::SYM_HANDLE_OF, 1, true); }
else if let Some(slot) = self.local_index.get(array).copied() { b.load_local_i64(slot); } else if let Some(slot) = self.local_index.get(array).copied() { b.load_local_i64(slot); }
else { self.push_value_if_known_or_param(b, array); b.emit_host_call("nyash.handle.of", 1, true); } else { self.push_value_if_known_or_param(b, array); b.emit_host_call(crate::jit::r#extern::handles::SYM_HANDLE_OF, 1, true); }
let mut argc = 1usize; let mut argc = 1usize;
if method == "charCodeAt" { if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); } argc = 2; } if method == "charCodeAt" { if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); } argc = 2; }
if method == "is_empty" { b.hint_ret_bool(true); } if method == "is_empty" { b.hint_ret_bool(true); }
@ -338,7 +338,7 @@ impl LowerCore {
// As a last resort, convert receiver to handle via nyash.handle.of and apply fallback on temp slot // 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()); } if trace { eprintln!("[LOWER] StringBox.len last-resort handle.of + fallback (dst?={})", dst.is_some()); }
self.push_value_if_known_or_param(b, array); 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::handles::SYM_HANDLE_OF, 1, true);
let t_recv = { let id = self.next_local; self.next_local += 1; id }; let t_recv = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(t_recv); b.store_local_i64(t_recv);
self.emit_len_with_fallback_local_handle(b, t_recv); self.emit_len_with_fallback_local_handle(b, t_recv);
@ -365,7 +365,7 @@ impl LowerCore {
// As a conservative fallback, try direct any.length_h on handle.of // 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()); } 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); 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::handles::SYM_HANDLE_OF, 1, true);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, dst.is_some()); b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, dst.is_some());
if let Some(d) = dst { 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 }); let slot = *self.local_index.entry(d).or_insert_with(|| { let id=self.next_local; self.next_local+=1; id });

View File

@ -0,0 +1,131 @@
use super::super::builder::IRBuilder;
use super::LowerCore;
impl LowerCore {
/// Emit robust length retrieval with fallback for String/Any:
/// 1) Prefer `nyash.string.len_h(recv)`
/// 2) If that yields 0 at runtime, select `nyash.any.length_h(recv)`
/// Returns: pushes selected length (i64) onto builder stack.
pub(super) fn emit_len_with_fallback_param(&mut self, b: &mut dyn IRBuilder, pidx: usize) {
use super::super::builder::CmpKind;
// Temp locals
let hslot = self.next_local; self.next_local += 1; // receiver handle slot
let t_string = self.next_local; self.next_local += 1;
let t_any = self.next_local; self.next_local += 1;
let t_cond = self.next_local; self.next_local += 1;
// Materialize receiver handle from param index
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::handles::SYM_HANDLE_OF, 1, true);
b.store_local_i64(hslot);
// String.len_h
crate::jit::observe::lower_hostcall(
crate::jit::r#extern::collections::SYM_STRING_LEN_H,
1,
&["Handle"],
"allow",
"core_len_param"
);
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);
// Any.length_h
crate::jit::observe::lower_hostcall(
crate::jit::r#extern::collections::SYM_ANY_LEN_H,
1,
&["Handle"],
"allow",
"core_len_param"
);
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);
// 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);
// select(cond ? any_len : string_len)
b.load_local_i64(t_cond); // cond (bottom)
b.load_local_i64(t_any); // then
b.load_local_i64(t_string); // else
b.emit_select_i64();
}
pub(super) fn emit_len_with_fallback_local_handle(&mut self, b: &mut dyn IRBuilder, slot: usize) {
use super::super::builder::CmpKind;
let t_string = self.next_local; self.next_local += 1;
let t_any = self.next_local; self.next_local += 1;
let t_cond = self.next_local; self.next_local += 1;
// String.len_h
crate::jit::observe::lower_hostcall(
crate::jit::r#extern::collections::SYM_STRING_LEN_H,
1,
&["Handle"],
"allow",
"core_len_local"
);
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);
// Any.length_h
crate::jit::observe::lower_hostcall(
crate::jit::r#extern::collections::SYM_ANY_LEN_H,
1,
&["Handle"],
"allow",
"core_len_local"
);
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);
// 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);
// select(cond ? any_len : string_len)
b.load_local_i64(t_cond);
b.load_local_i64(t_any);
b.load_local_i64(t_string);
b.emit_select_i64();
}
pub(super) fn emit_len_with_fallback_literal(&mut self, b: &mut dyn IRBuilder, s: &str) {
use super::super::builder::CmpKind;
let t_string = self.next_local; self.next_local += 1;
let t_any = self.next_local; self.next_local += 1;
let t_cond = self.next_local; self.next_local += 1;
// String.len_h on literal handle
crate::jit::observe::lower_hostcall(
crate::jit::r#extern::collections::SYM_STRING_LEN_H,
1,
&["Handle"],
"allow",
"core_len_lit"
);
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);
// 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,
1,
&["Handle"],
"allow",
"core_len_lit"
);
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);
// 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);
// select(cond ? any_len : string_len)
b.load_local_i64(t_cond);
b.load_local_i64(t_any);
b.load_local_i64(t_string);
b.emit_select_i64();
}
}