From f6e0d5111e2e9fe4cddd07951540c98cf8d66e9a Mon Sep 17 00:00:00 2001 From: Tomoaki Date: Sat, 6 Sep 2025 08:09:46 +0900 Subject: [PATCH] =?UTF-8?q?Phase=20A=20=E3=83=AA=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=AF=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0:=20String.length?= =?UTF-8?q?=20=E3=83=87=E3=83=90=E3=83=83=E3=82=B0=E5=9F=BA=E7=9B=A4?= =?UTF-8?q?=E3=81=AE=E5=BC=B7=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 実装内容(振る舞い変更なし) 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 --- CURRENT_TASK.md | 58 ++++++++++++- src/jit/extern/collections.rs | 1 + src/jit/lower/builder/cranelift.rs | 32 +++++++ src/jit/lower/builder/object.rs | 32 +++++++ src/jit/lower/core.rs | 88 +------------------ src/jit/lower/core/ops_ext.rs | 12 +-- src/jit/lower/core/string_len.rs | 131 +++++++++++++++++++++++++++++ 7 files changed, 261 insertions(+), 93 deletions(-) create mode 100644 src/jit/lower/core/string_len.rs diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 7a3a7b2e..fbdb4912 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -2,7 +2,7 @@ このドキュメントは「いま何をすれば良いか」を最小で共有するためのコンパクト版です。詳細は git 履歴と `docs/`(phase-15)を参照してください。 -— 最終更新: 2025‑09‑06 (Phase 15.16 反映, AOT/JIT-AOT 足場強化) +— 最終更新: 2025‑09‑06 (Phase 15.16 反映, AOT/JIT-AOT 足場強化 + Phase A リファクタ着手準備) 【ハンドオフ(2025‑09‑06 2nd)— AOT/JIT‑AOT String.length 修正進捗と引き継ぎ】 @@ -50,7 +50,7 @@ - `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 を返している可能性が高い。 +- しかし `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)` を設定済み。 @@ -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` - 追加で thunk 到達確認: `NYASH_JIT_TRACE_LEN=1 ...`(現状は無出力=未到達の可能性) +Phase A 進捗(実施済) +- A‑1: Hostcall シンボルの定数化(直書き排除)完了 + - `nyash.handle.of` / `nyash.string.len_h` / `nyash.console.birth_h` を `SYM_*` に統一 +- A‑2: string_len ヘルパ抽出(共通化)完了 + - `src/jit/lower/core/string_len.rs` 新設、`emit_len_with_fallback_*` を移設 + - 呼び出し元はそのまま(挙動は不変) +- A‑3: 観測の統一(第一弾) + - string_len 内で `observe::lower_hostcall` を発火(len_h/any.length_h) + - Cranelift/ObjectBuilder の `emit_host_call[_typed]` に `NYASH_JIT_TRACE_IMPORT=1` によるインポート解決ログを追加 + +観測結果(A‑3 導入後) +- `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 を暫定的に host‑bridge (`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(無振る舞い変更)リファクタ方針(着手予定) +- A‑1: 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::...` に追加して使用) +- A‑2: 長さ取得の共通化 + - 新規: `src/jit/lower/core/string_len.rs` + - 既存の `emit_len_with_fallback_{param,local,literal}` をこのモジュールへ抽出し、`core.rs`/`ops_ext.rs` から呼び出すだけにする(挙動は据え置き)。 + - 目的: 重複と分岐のばらけを解消し、シンボル差し替えや観測フックを一点で行えるようにする。 +※ Phase A は「振る舞いを変えない」ことを厳守する。 + 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`)。 diff --git a/src/jit/extern/collections.rs b/src/jit/extern/collections.rs index 7a4a1ec7..0d384f50 100644 --- a/src/jit/extern/collections.rs +++ b/src/jit/extern/collections.rs @@ -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_BIRTH_H: &str = "nyash.string.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) pub const SYM_STRING_CONCAT_HH: &str = "nyash.string.concat_hh"; pub const SYM_STRING_EQ_HH: &str = "nyash.string.eq_hh"; diff --git a/src/jit/lower/builder/cranelift.rs b/src/jit/lower/builder/cranelift.rs index e38002d7..4edc70a9 100644 --- a/src/jit/lower/builder/cranelift.rs +++ b/src/jit/lower/builder/cranelift.rs @@ -426,6 +426,22 @@ impl IRBuilder for CraneliftBuilder { } fn emit_host_call(&mut self, symbol: &str, _argc: usize, has_ret: bool) { 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","" + ); + } 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) @@ -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) { 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","" + ); + } let mut args: Vec = Vec::new(); 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); } } diff --git a/src/jit/lower/builder/object.rs b/src/jit/lower/builder/object.rs index c6451809..37575e02 100644 --- a/src/jit/lower/builder/object.rs +++ b/src/jit/lower/builder/object.rs @@ -285,6 +285,22 @@ impl IRBuilder for ObjectBuilder { fn emit_host_call(&mut self, symbol: &str, argc: usize, has_ret: bool) { use cranelift_codegen::ir::{AbiParam, Signature, types}; 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","" + ); + } 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]); } 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) { use cranelift_codegen::ir::{AbiParam, Signature, types}; 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","" + ); + } 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]); } else if let Some(b) = self.entry_block { fb.switch_to_block(b); } diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index 207cf728..d93b1a14 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -4,6 +4,7 @@ use super::builder::{IRBuilder, BinOpKind, CmpKind}; mod analysis; mod cfg; mod ops_ext; +mod string_len; /// 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. @@ -182,90 +183,7 @@ impl LowerCore { Ok(()) } - /// 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. - 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(); - } + // string_len helper moved to core/string_len.rs (no behavior change) 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" { // Emit hostcall to create/get ConsoleBox handle // 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 { // Unknown RefGet: treat as no-op const 0 to avoid strict fail for now b.emit_const_i64(0); diff --git a/src/jit/lower/core/ops_ext.rs b/src/jit/lower/core/ops_ext.rs index 4b66720c..ecaea654 100644 --- a/src/jit/lower/core/ops_ext.rs +++ b/src/jit/lower/core/ops_ext.rs @@ -77,7 +77,7 @@ impl LowerCore { return Ok(()); } // 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 if let Some(arg0) = args.get(0) { self.push_value_if_known_or_param(b, arg0); } // Resolve plugin invoke for ConsoleBox.method @@ -203,15 +203,15 @@ impl LowerCore { 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); + 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); } return Ok(true); } // is_empty / charCodeAt: keep mapped hostcall path // 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 { 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; 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); } @@ -338,7 +338,7 @@ impl LowerCore { // 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); + 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 }; b.store_local_i64(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 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::handles::SYM_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 }); diff --git a/src/jit/lower/core/string_len.rs b/src/jit/lower/core/string_len.rs new file mode 100644 index 00000000..25681d7c --- /dev/null +++ b/src/jit/lower/core/string_len.rs @@ -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(); + } +}