diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index d14f15c5..6176f631 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1162,65 +1162,50 @@ Update (2025-09-02 AM / Async unify + VM await fix + JIT AOT builder plan) 5) Wire tri-backend/async/timeout smokes in tools/ (minimal, concise outputs) and add to CI. ## Phase 12 — Handoff (VM/JIT 統一経路・Nyash ABI vtable/by-slot) +Updated: 2025-09-03 — Quick Handoff Summary(長文は下に残し、ここを最新ソースに) + 目的 -- VM/JIT の挙動を vtable/slot ベースで統一。Extern と BoxCall の経路分離と診断の安定化。 -- 逆呼び(plugins→host)by-slot 安定化、HostHandle/PluginHandle のTLV往復、GCバリア安全性(TLS)担保。 +- VM/JIT を vtable/by-slot で統一し、Extern と BoxCall の経路分離(BoxCall→vtable/PIC/汎用、Extern→name/slot)を確立。 +- 逆呼び(plugins→host)をTLV+スロットで安定化。JIT/VM の安全な境界(TLS/GCバリア)を担保。 -実装済み(主要ポイント) -- TypeRegistry: InstanceBox の代表スロットを固定化(1:getField, 2:setField, 3:has, 4:size)。Array/Map/String は既存の100/200/300番台。 -- VM(vtable→PIC→汎用)正式化: Instance/Array/Map/String の get/set/len/size/has をVTで実行。STRICT時は型.メソッド(arity)+known一覧でエラー。 -- Extern(env.*): レジストリ拡充(console.info/debug, task.yieldNow/sleepMs)。`NYASH_EXTERN_ROUTE_SLOTS=1` で name→slot→専用ハンドラ経路を追加(console/debug/runtime.checkpoint/future.new|set|await/task.*)。 -- 逆呼びAPI(C ABI): `nyrt_host_call_{name,slot}` 実装。slot表は Instance(1/2/3/4)/Array(100/101/102)/Map(200..204)/String(300)。テスト時の no_mangle 多重定義は feature=`c-abi-export` に切替。 -- JITパリティ(host-bridge): VM側JIT境界で `set_current_vm/clear_current_vm` を挿入(GCバリア安全性)。host-bridgeに Instance.getField/setField(slot1/2)/String.len(slot300)を登録。Lowerer(ops_ext) で getField/setField と String.len を host-bridge へ降ろす分岐を追加(NYASH_JIT_HOST_BRIDGE=1)。 -- テスト追加: - - `src/tests/identical_exec_instance.rs`: new Person → setField/getField → "Alice" を返す(VM/JIT一致) - - `src/tests/identical_exec_string.rs`: String.len 一致("hello"→5) - - `src/tests/identical_exec_collections.rs`: Array/Map/String の一致(最小) - - `src/tests/host_reverse_slot.rs`: 逆呼び by-slot(Map.set/size) - - 既存 vtable_* 系テストは残置 -- CI: `.github/workflows/vm-jit-identical.yml` を STRICT×VT のマトリクスに拡張。サブセット(identical/host_reverse/vtable_*)を実行。 -- ドキュメント: - - `include/nyrt_host_api.h`: 逆呼び C ヘッダ雛形 - - `docs/development/abi/host_api.md`: TLVタグ/スロット/バリア(TLS) 要点 - - `docs/development/design/extern-vs-boxcall.md`: 分離方針・スロット/アリティ一覧・STRICT・環境変数 +今回の到達点(実装済み) +- JIT host-bridge 完配線(Cranelift thunk+シンボル登録) + - Instance.getField/setField: 統一3引数シンボル(field3: (recv,name,val/-1))で by-slot 呼び出し + - String.len: 受け手 StringBox は host-bridge 経路へ + - 文字列リテラル→StringBox ハンドル化: `nyash.string.from_u64x2`+builder API + - NewBox(Instance, 引数なし): `nyash.instance.birth_name_u64x2` でグローバルUnifiedRegistry経由生誕 +- Lowering 強化 + - Instance.getField/setField と String.len を最優先ルートに(simple_reads より前) + - Const(String) の伝搬(known_str)を追加 → name/val を確実にハンドル化 +- 一致テスト(ローカル) + - OK: `identical_exec_string`("hello".len → 5) + - OK: `identical_exec_instance`(Person.setField/getField → "Alice") -ブランチ現状の課題/未了タスク(優先度順) -1) JIT host-bridge の Cranelift 外部thunk配線(要) - - 現状: host-bridge シンボル(`nyash.host.instance.getField`, `setField`, `string.len`)は Engine に登録済みだが、Cranelift の外部呼び → ランタイム関数への橋渡し(thunk)が未配線のため、戻り値(String/BoxRef)が JIT 側で 0 に潰れる。 - - 対応: `jit/lower/builder/rt_shims.rs` or `jit/lower/extern_thunks.rs` に、上記シンボルの関数を追加し、戻り値は i64 ハンドル(JitValue::I64)で返す。`CallBoundaryBox::to_vm` が handle→BoxRef を復元。 - - これにより以下の一致テストが通る見込み: - - `identical_exec_instance.rs`(VM=Alice, JIT=Alice) - - `identical_exec_string.rs`(VM=5, JIT=5) +How to Run(再現手順) +- 前提: `--features cranelift-jit` +- 文字列/インスタンス一致 + - `NYASH_ABI_VTABLE=1 NYASH_JIT_HOST_BRIDGE=1 cargo test --features cranelift-jit --lib tests::identical_exec_string::identical_vm_and_jit_string_len -- --nocapture` + - `NYASH_ABI_VTABLE=1 NYASH_JIT_HOST_BRIDGE=1 cargo test --features cranelift-jit --lib tests::identical_exec_instance::identical_vm_and_jit_person_get_set_slots -- --nocapture` -2) Array/Map の NewBox が Unit 環境で失敗(要) - - 理由: 既定 `NyashRuntime` が「プラグイン専用レジストリ」構成で、ArrayBox/MapBox を持たない。 - - 対応: vtable_* 系ユニットテスト内で、簡易ビルトインFactoryを差し込む or CI 側で plugins を有効化する。手早いのは各テストでビルトインFactory注入(`NyashRuntimeBuilder.with_factory(...)`)。 - -3) CI 失敗時のトレース収集(任意だが推奨) - - 落ちたマトリクスのとき、`NYASH_VM_VT_TRACE=1`, `NYASH_VM_PIC_TRACE=1`, `NYASH_EXTERN_TRACE=1` を追加した再実行とログアーティファクト化。 - -既存の既知制約 -- 一部の広域テストはレガシー/プラグイン依存のため、本Phase対象外の赤が残る。CIは一致テスト系のサブセットに限定。 -- greet() 等のユーザーメソッド実行(文字列連結)は AST/Interpreter 依存が強いため、Instance一致テストは get/setField の slot 検証に留めている。 - -環境変数早見表 -- `NYASH_ABI_VTABLE=1`(VM vtable 経路) -- `NYASH_ABI_STRICT=1`(STRICT診断ON) -- `NYASH_EXTERN_ROUTE_SLOTS=1`(Extern name→slot 統一ルート) +主要フラグ +- `NYASH_ABI_VTABLE=1`(VM vtable) - `NYASH_JIT_HOST_BRIDGE=1`(JIT host-bridge 経路) -- `NYASH_VM_PIC_THRESHOLD=8`(PICモノ化しきい値) -- 任意: `NYASH_VM_VT_TRACE=1`, `NYASH_VM_PIC_TRACE=1`, `NYASH_EXTERN_TRACE=1`, `NYASH_RUNTIME_CHECKPOINT_TRACE=1` +- 任意: `NYASH_JIT_TRACE_BRIDGE=1`(ブリッジ経路の最小ログ) -コード参照(主な変更点) -- 逆呼びAPI: `src/runtime/host_api.rs`(TLS, TLV, slot switch) -- TypeRegistry: `src/runtime/type_registry.rs`(Instance/Array/Map/String のスロット定義) -- VM vtable 経路: `src/backend/vm_instructions.rs`(try_boxcall_vtable_stub, Extern name→slot) -- JIT host-bridge: `src/jit/extern/host_bridge.rs`(by-slot 呼び出し)/ `src/jit/engine.rs`(シンボル登録)/ `src/jit/lower/core/ops_ext.rs`(getField/setField/String.len を host-bridge に降ろす) -- 一致テスト/逆呼びテスト: `src/tests/*.rs`(identical_* / vtable_* / host_reverse_slot) -- CI: `.github/workflows/vm-jit-identical.yml` +主な変更点(ファイル) +- Host-bridge/Thunk: `src/jit/extern/host_bridge.rs`, `src/jit/lower/extern_thunks.rs` +- Lowering: `src/jit/lower/core/ops_ext.rs`, `src/jit/lower/core.rs`, `src/jit/lower/builder/{cranelift.rs,builder.rs,noop.rs}` +- JITエンジン経路: `src/backend/cranelift/mod.rs`, `src/jit/engine.rs` +- Runtime/Registry: `src/runtime/{nyash_runtime.rs,unified_registry.rs}`(既存) +- テスト: `src/tests/{identical_exec_string.rs,identical_exec_instance.rs}`(Factory注入を追加) -直近の作業手順(提案) -1. Cranelift 外部thunkを追加(instance.getField/setField, string.len 用)し、戻り値を i64 handle で返す -2. vtable_* ユニットテストに簡易ビルトインFactoryを注入して Array/Map の NewBox 失敗を解消 -3. 一致テストサブセットを再実行(Person/String/Collections/逆呼び/vtable_*) -4. CI 失敗時のトレース収集を追加 +未了/次の一手(小さく) +- Collections/Reverse-call サブセットをVM/JITで再確認(Map/Array by-slot の最小一致) +- vtable_* ユニットの NewBox 失敗時はテスト内で Factory 注入(必要箇所のみ) +- CI: 一致系サブセット(string/instance/host_reverse)を first-wave に追加 + +メモ/注意 +- host-bridge シンボルは固定アリティ・固定戻り(i64)で宣言し、call-site側で戻り値の使用有無を制御 +- NewBox(Instance) はJIT側で UnifiedRegistry を直接叩くため、グローバルに必要Factoryを登録しておく(テスト内で注入済み) + +(以下、旧詳細ログは履歴のため残置) diff --git a/src/backend/cranelift/mod.rs b/src/backend/cranelift/mod.rs index f12cc0c1..72da3b56 100644 --- a/src/backend/cranelift/mod.rs +++ b/src/backend/cranelift/mod.rs @@ -33,30 +33,30 @@ pub fn compile_and_execute(mir_module: &MirModule, _temp_name: &str) -> Result CraneliftBuilder -> 実行) #[cfg(feature = "cranelift-jit")] { - match crate::backend::cranelift::jit::compile_and_execute_minimal(main) { - Ok(i) => { - return Ok(Box::new(IntegerBox::new(i))); - } - Err(e) => { - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - eprintln!("[CLIF] minimal JIT fallback failed: {} — falling back to skeleton eval", e); - } + let mut engine = crate::jit::engine::JitEngine::new(); + if let Some(h) = engine.compile_function(&main.signature.name, main) { + // 実行(引数なし)。戻り値は MIR の型に合わせて変換 + let out = engine.execute_handle(h, &[]); + if let Some(jv) = out { + let vmv = crate::jit::boundary::CallBoundaryBox::to_vm(&main.signature.return_type, jv); + let boxed: Box = match vmv { + crate::backend::vm::VMValue::Integer(i) => Box::new(IntegerBox::new(i)), + crate::backend::vm::VMValue::Float(f) => Box::new(crate::boxes::FloatBox::new(f)), + crate::backend::vm::VMValue::Bool(b) => Box::new(BoolBox::new(b)), + crate::backend::vm::VMValue::String(s) => Box::new(StringBox::new(&s)), + crate::backend::vm::VMValue::BoxRef(b) => b.share_box(), + crate::backend::vm::VMValue::Future(fu) => Box::new(fu), + crate::backend::vm::VMValue::Void => Box::new(VoidBox::new()), + }; + return Ok(boxed); } } - // Optional: LowerCore→ClifBuilder 実IR経路(Const/Add/Return)。環境指定で実行 - if std::env::var("NYASH_JIT_LOWERCORE").ok().as_deref() == Some("1") { - let mut clif_builder = crate::backend::cranelift::builder::ClifBuilder::new(); - let mut lower = LowerCore::new(); - lower.lower_function(main, &mut clif_builder).map_err(|e| format!("lowercore: {}", e))?; - let i = clif_builder.finish_and_execute().map_err(|e| format!("clifbuilder: {}", e))?; + // 失敗した場合はミニマルJITへフォールバック + if let Ok(i) = crate::backend::cranelift::jit::compile_and_execute_minimal(main) { return Ok(Box::new(IntegerBox::new(i))); - } else { - let mut clif_builder = crate::backend::cranelift::builder::ClifBuilder::new(); - let mut lower = LowerCore::new(); - let _ = lower.lower_function(main, &mut clif_builder); } } let mut regs: HashMap = HashMap::new(); diff --git a/src/jit/extern/host_bridge.rs b/src/jit/extern/host_bridge.rs index 1d7a4b21..fc23957d 100644 --- a/src/jit/extern/host_bridge.rs +++ b/src/jit/extern/host_bridge.rs @@ -90,6 +90,10 @@ pub const SYM_HOST_CONSOLE_WARN: &str = "nyash.host.console.warn"; // (value) pub const SYM_HOST_CONSOLE_ERROR: &str = "nyash.host.console.error"; // (value) pub const SYM_HOST_INSTANCE_GETFIELD: &str = "nyash.host.instance.getField"; // (InstanceBox, name) pub const SYM_HOST_INSTANCE_SETFIELD: &str = "nyash.host.instance.setField"; // (InstanceBox, name, value) +// Arity-stable variants for Cranelift imports (avoid signature conflicts) +pub const SYM_HOST_INSTANCE_GETFIELD2: &str = "nyash.host.instance.getField2"; // (InstanceBox, name) +pub const SYM_HOST_INSTANCE_SETFIELD3: &str = "nyash.host.instance.setField3"; // (InstanceBox, name, value) +pub const SYM_HOST_INSTANCE_FIELD3: &str = "nyash.host.instance.field3"; // (recv,name,val or sentinel) pub const SYM_HOST_STRING_LEN: &str = "nyash.host.string.len"; // (StringBox) pub fn array_get(args: &[VMValue]) -> VMValue { diff --git a/src/jit/lower/builder.rs b/src/jit/lower/builder.rs index 732cf68a..01a26429 100644 --- a/src/jit/lower/builder.rs +++ b/src/jit/lower/builder.rs @@ -28,6 +28,7 @@ pub trait IRBuilder { fn emit_select_i64(&mut self) { } fn emit_host_call(&mut self, _symbol: &str, _argc: usize, _has_ret: bool) { } fn emit_host_call_typed(&mut self, _symbol: &str, _params: &[ParamKind], _has_ret: bool, _ret_is_f64: bool) { } + fn emit_host_call_fixed3(&mut self, _symbol: &str, _has_ret: bool) { } fn emit_plugin_invoke(&mut self, _type_id: u32, _method_id: u32, _argc: usize, _has_ret: bool) { } fn emit_plugin_invoke_by_name(&mut self, _method: &str, _argc: usize, _has_ret: bool) { } // Create a StringBox handle from a string literal and push its handle (i64) onto the stack. diff --git a/src/jit/lower/builder/cranelift.rs b/src/jit/lower/builder/cranelift.rs index 93272f80..fcc0c8a1 100644 --- a/src/jit/lower/builder/cranelift.rs +++ b/src/jit/lower/builder/cranelift.rs @@ -456,6 +456,26 @@ impl IRBuilder for CraneliftBuilder { let func_id = self.module.declare_function(symbol, cranelift_module::Linkage::Import, &sig).expect("declare typed import failed"); if let Some(v) = tls_call_import_ret(&mut self.module, func_id, &args, has_ret) { self.value_stack.push(v); } } + fn emit_host_call_fixed3(&mut self, symbol: &str, has_ret: bool) { + use cranelift_codegen::ir::{AbiParam, Signature, types}; + let mut args: Vec = Vec::new(); + // Pop up to 3 values; pad with zeros to reach exactly 3 + let take_n = core::cmp::min(3, self.value_stack.len()); + for _ in 0..take_n { if let Some(v) = self.value_stack.pop() { args.push(v); } } + args.reverse(); + Self::with_fb(|fb| { + while args.len() < 3 { args.push(fb.ins().iconst(types::I64, 0)); } + }); + let call_conv = self.module.isa().default_call_conv(); + let mut sig = Signature::new(call_conv); + for _ in 0..3 { sig.params.push(AbiParam::new(types::I64)); } + // Always declare with I64 return to keep signature stable across call sites + sig.returns.push(AbiParam::new(types::I64)); + let func_id = self.module.declare_function(symbol, cranelift_module::Linkage::Import, &sig).expect("declare import fixed3 failed"); + if let Some(v) = tls_call_import_ret(&mut self.module, func_id, &args, true) { + if has_ret { self.value_stack.push(v); } + } + } fn emit_plugin_invoke(&mut self, type_id: u32, method_id: u32, argc: usize, has_ret: bool) { use cranelift_codegen::ir::{AbiParam, Signature, types}; // Pop argc values (right-to-left): receiver + up to 2 args @@ -742,6 +762,7 @@ impl CraneliftBuilder { builder.symbol(c::SYM_STRING_LT_HH, nyash_string_lt_hh as *const u8); builder.symbol(b::SYM_BOX_BIRTH_H, nyash_box_birth_h as *const u8); builder.symbol("nyash.box.birth_i64", nyash_box_birth_i64 as *const u8); + builder.symbol("nyash.instance.birth_name_u64x2", super::super::extern_thunks::nyash_instance_birth_name_u64x2 as *const u8); builder.symbol(h::SYM_HANDLE_OF, nyash_handle_of as *const u8); builder.symbol(r::SYM_RT_CHECKPOINT, nyash_rt_checkpoint as *const u8); builder.symbol(r::SYM_GC_BARRIER_WRITE, nyash_gc_barrier_write as *const u8); @@ -757,8 +778,8 @@ impl CraneliftBuilder { if std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1") { use crate::jit::r#extern::host_bridge as hb; // Instance.getField/setField (recv_h, name_i[, val_i]) - builder.symbol(hb::SYM_HOST_INSTANCE_GETFIELD, super::super::extern_thunks::nyash_host_instance_getfield as *const u8); - builder.symbol(hb::SYM_HOST_INSTANCE_SETFIELD, super::super::extern_thunks::nyash_host_instance_setfield as *const u8); + // Use arity-stable import symbols to avoid signature collisions + builder.symbol(hb::SYM_HOST_INSTANCE_FIELD3, super::super::extern_thunks::nyash_host_instance_field3 as *const u8); // String.len (recv_h) builder.symbol(hb::SYM_HOST_STRING_LEN, super::super::extern_thunks::nyash_host_string_len as *const u8); } diff --git a/src/jit/lower/builder/noop.rs b/src/jit/lower/builder/noop.rs index 2c86fd11..14670ade 100644 --- a/src/jit/lower/builder/noop.rs +++ b/src/jit/lower/builder/noop.rs @@ -25,6 +25,7 @@ impl IRBuilder for NoopBuilder { fn emit_return(&mut self) { self.rets += 1; } fn emit_select_i64(&mut self) { self.binops += 1; } fn emit_host_call_typed(&mut self, _symbol: &str, _params: &[ParamKind], has_ret: bool, _ret_is_f64: bool) { if has_ret { self.consts += 1; } } + fn emit_host_call_fixed3(&mut self, _symbol: &str, has_ret: bool) { if has_ret { self.consts += 1; } } fn emit_plugin_invoke(&mut self, _type_id: u32, _method_id: u32, _argc: usize, has_ret: bool) { if has_ret { self.consts += 1; } } fn emit_plugin_invoke_by_name(&mut self, _method: &str, _argc: usize, has_ret: bool) { if has_ret { self.consts += 1; } } fn emit_string_handle_from_literal(&mut self, _s: &str) { self.consts += 1; } diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index 64e10c67..64adb3cb 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -14,6 +14,8 @@ pub struct LowerCore { pub(super) known_i64: std::collections::HashMap, /// Minimal constant propagation for f64 (math.* signature checks) pub(super) known_f64: std::collections::HashMap, + /// Minimal constant propagation for String literals + pub(super) known_str: std::collections::HashMap, /// Parameter index mapping for ValueId pub(super) param_index: std::collections::HashMap, /// Track values produced by Phi (for minimal PHI path) @@ -40,7 +42,7 @@ pub struct LowerCore { } impl LowerCore { - pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), known_f64: std::collections::HashMap::new(), param_index: std::collections::HashMap::new(), phi_values: std::collections::HashSet::new(), phi_param_index: std::collections::HashMap::new(), bool_values: std::collections::HashSet::new(), bool_phi_values: std::collections::HashSet::new(), float_box_values: std::collections::HashSet::new(), handle_values: std::collections::HashSet::new(), last_phi_total: 0, last_phi_b1: 0, last_ret_bool_hint_used: false, local_index: std::collections::HashMap::new(), next_local: 0, box_type_map: std::collections::HashMap::new() } } + pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), known_f64: std::collections::HashMap::new(), known_str: std::collections::HashMap::new(), param_index: std::collections::HashMap::new(), phi_values: std::collections::HashSet::new(), phi_param_index: std::collections::HashMap::new(), bool_values: std::collections::HashSet::new(), bool_phi_values: std::collections::HashSet::new(), float_box_values: std::collections::HashSet::new(), handle_values: std::collections::HashSet::new(), last_phi_total: 0, last_phi_b1: 0, last_ret_bool_hint_used: false, local_index: std::collections::HashMap::new(), next_local: 0, box_type_map: std::collections::HashMap::new() } } /// Get statistics for the last lowered function pub fn last_stats(&self) -> (u64, u64, bool) { (self.last_phi_total, self.last_phi_b1, self.last_ret_bool_hint_used) } @@ -253,15 +255,26 @@ impl LowerCore { // - 引数なし: 汎用 birth_h(type_idのみ)でハンドル生成 // - 引数あり: 既存のチェーン(直後の plugin_invoke birth で初期化)を維持(段階的導入) if args.is_empty() { - let decision = crate::jit::policy::invoke::decide_box_method(box_type, "birth", 0, true); - if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, .. } = decision { - b.emit_const_i64(type_id as i64); - b.emit_host_call(crate::jit::r#extern::birth::SYM_BOX_BIRTH_H, 1, true); + // 文字列の型名からインスタンスを生成(グローバルUnifiedRegistry経由) + // name → u64x2 パックで渡す + let name = box_type.clone(); + { + use cranelift_codegen::ir::{AbiParam, Signature, types}; + let name_bytes = name.as_bytes(); + let mut lo: u64 = 0; let mut hi: u64 = 0; + let take = core::cmp::min(16, name_bytes.len()); + for i in 0..take.min(8) { lo |= (name_bytes[i] as u64) << (8 * i as u32); } + for i in 8..take { hi |= (name_bytes[i] as u64) << (8 * (i - 8) as u32); } + // Push immediates + b.emit_const_i64(lo as i64); + b.emit_const_i64(hi as i64); + b.emit_const_i64(name_bytes.len() as i64); + // Call import (lo, hi, len) -> handle + // Use typed hostcall (I64,I64,I64)->I64 + b.emit_host_call_typed("nyash.instance.birth_name_u64x2", &[crate::jit::lower::builder::ParamKind::I64, crate::jit::lower::builder::ParamKind::I64, crate::jit::lower::builder::ParamKind::I64], true, false); self.handle_values.insert(*dst); let slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); b.store_local_i64(slot); - } else { - self.unsupported += 1; } } else { // 引数あり: 安全なパターンから段階的に birth_i64 に切替 @@ -378,13 +391,13 @@ impl LowerCore { // Mark this value as boolean producer self.bool_values.insert(*dst); } - ConstValue::String(_) | ConstValue::Null | ConstValue::Void => { - // leave unsupported for now - } + ConstValue::String(sv) => { self.known_str.insert(*dst, sv.clone()); } + ConstValue::Null | ConstValue::Void => { } }, I::Copy { dst, src } => { if let Some(v) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, v); } if let Some(v) = self.known_f64.get(src).copied() { self.known_f64.insert(*dst, v); } + if let Some(v) = self.known_str.get(src).cloned() { self.known_str.insert(*dst, v); } // If source is a parameter, materialize it on the stack for downstream ops if let Some(pidx) = self.param_index.get(src).copied() { b.emit_param_i64(pidx); diff --git a/src/jit/lower/core/ops_ext.rs b/src/jit/lower/core/ops_ext.rs index 50c1dc87..5f8f18e6 100644 --- a/src/jit/lower/core/ops_ext.rs +++ b/src/jit/lower/core/ops_ext.rs @@ -210,50 +210,44 @@ impl LowerCore { self.push_value_if_known_or_param(b, array); // name: if const string, build a StringBox handle from literal; else best-effort push if let Some(name_id) = args.get(0) { - // Scan MIR for string constant defining this ValueId - let mut found_str: Option = None; - for (_bbid, bb) in func.blocks.iter() { - for ins in bb.instructions.iter() { - if let crate::mir::MirInstruction::Const { dst, value } = ins { - if dst == name_id { - if let crate::mir::ConstValue::String(s) = value { found_str = Some(s.clone()); } - break; - } - } - } - if found_str.is_some() { break; } - } - if let Some(s) = found_str { b.emit_string_handle_from_literal(&s); } - else { self.push_value_if_known_or_param(b, name_id); } + if let Some(s) = self.known_str.get(name_id).cloned() { b.emit_string_handle_from_literal(&s); } + else { b.emit_const_i64(0); } } else { b.emit_const_i64(0); } // value for setField let argc = if method == "setField" { if let Some(val_id) = args.get(1) { - // If value is const string, materialize handle - let mut found_val_str: Option = None; - for (_bbid, bb) in func.blocks.iter() { - for ins in bb.instructions.iter() { - if let crate::mir::MirInstruction::Const { dst, value } = ins { - if dst == val_id { - if let crate::mir::ConstValue::String(s) = value { found_val_str = Some(s.clone()); } - break; - } - } - } - if found_val_str.is_some() { break; } - } - if let Some(s) = found_val_str { b.emit_string_handle_from_literal(&s); } + if let Some(s) = self.known_str.get(val_id).cloned() { b.emit_string_handle_from_literal(&s); } else { self.push_value_if_known_or_param(b, val_id); } } else { b.emit_const_i64(0); } 3 } else { 2 }; - let sym = if method == "setField" { crate::jit::r#extern::host_bridge::SYM_HOST_INSTANCE_SETFIELD } else { crate::jit::r#extern::host_bridge::SYM_HOST_INSTANCE_GETFIELD }; - b.emit_host_call(sym, argc, dst.is_some()); + // Unified 3-arity call: getField uses val=-1 sentinel + let sym = crate::jit::r#extern::host_bridge::SYM_HOST_INSTANCE_FIELD3; + if method == "getField" { b.emit_const_i64(-1); } + b.emit_host_call_fixed3(sym, dst.is_some()); return Ok(true); } } - // String.len via host-bridge when receiver is StringBox + // String.len: (1) const string → 定数埋め込み、(2) StringBox → host-bridge "len" => { + // (1) const string literal case + let mut lit_len: Option = None; + for (_bbid, bb) in func.blocks.iter() { + for ins in bb.instructions.iter() { + if let crate::mir::MirInstruction::Const { dst, value } = ins { + if dst == array { + if let crate::mir::ConstValue::String(s) = value { lit_len = Some(s.len() as i64); } + break; + } + } + } + if lit_len.is_some() { break; } + } + if let Some(n) = lit_len { + b.emit_const_i64(n); + return Ok(true); + } + // (2) StringBox via host-bridge if std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1") { if let Some(bt) = self.box_type_map.get(array) { if bt == "StringBox" { diff --git a/src/jit/lower/extern_thunks.rs b/src/jit/lower/extern_thunks.rs index 65209d15..c2abb331 100644 --- a/src/jit/lower/extern_thunks.rs +++ b/src/jit/lower/extern_thunks.rs @@ -721,6 +721,22 @@ pub(super) extern "C" fn nyash_host_instance_setfield(recv_h: u64, name_i: i64, i64_from_vmvalue(out) } +// Unified instance field op: (recv, name, val_or_sentinel) → getField if val == -1, else setField +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_host_instance_field3(recv_h: u64, name_i: i64, val_i: i64) -> i64 { + use crate::backend::vm::VMValue as V; + let recv = match crate::jit::rt::handles::get(recv_h) { Some(a) => a, None => return 0 }; + let name_v = vmvalue_from_jit_arg_i64(name_i); + if val_i == -1 { // getField + let out = hb::instance_getfield(&[V::BoxRef(recv), name_v]); + return i64_from_vmvalue(out); + } + // setField + let val_v = vmvalue_from_jit_arg_i64(val_i); + let _ = hb::instance_setfield(&[V::BoxRef(recv), name_v, val_v]); + 0 +} + // nyash.host.string.len(recv) #[cfg(feature = "cranelift-jit")] pub(super) extern "C" fn nyash_host_string_len(recv_h: u64) -> i64 { @@ -756,3 +772,24 @@ pub(super) extern "C" fn nyash_string_from_u64x2(lo: u64, hi: u64, len: i64) -> let arc: std::sync::Arc = std::sync::Arc::new(crate::box_trait::StringBox::new(s)); crate::jit::rt::handles::to_handle(arc) as i64 } + +// Create an instance by type name via global unified registry: birth(name) -> handle +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_instance_birth_name_u64x2(lo: u64, hi: u64, len: i64) -> i64 { + let n = if len <= 0 { 0usize } else { core::cmp::min(len as usize, 32usize) }; + let mut buf = [0u8; 32]; + for i in 0..core::cmp::min(8, n) { buf[i] = ((lo >> (8 * i)) & 0xFF) as u8; } + if n > 8 { for i in 0..core::cmp::min(8, n - 8) { buf[8 + i] = ((hi >> (8 * i)) & 0xFF) as u8; } } + let name = match std::str::from_utf8(&buf[..n]) { Ok(t) => t.to_string(), Err(_) => String::from_utf8_lossy(&buf[..n]).to_string() }; + let registry = crate::runtime::get_global_unified_registry(); + if let Ok(reg) = registry.lock() { + match reg.create_box(&name, &[]) { + Ok(b) => { + let arc: std::sync::Arc = std::sync::Arc::from(b); + return crate::jit::rt::handles::to_handle(arc) as i64; + } + Err(_) => return 0, + } + } + 0 +} diff --git a/src/tests/identical_exec_instance.rs b/src/tests/identical_exec_instance.rs index 758135b1..7809f22f 100644 --- a/src/tests/identical_exec_instance.rs +++ b/src/tests/identical_exec_instance.rs @@ -64,6 +64,9 @@ mod tests { rt_builder = rt_builder.with_factory(Arc::new(PersonFactory)); let runtime = rt_builder.build(); + // Also register factory globally for JIT path (host-bridge creates via global registry) + crate::runtime::register_user_defined_factory(Arc::new(PersonFactory)); + // Build module let module = build_person_module(); @@ -82,4 +85,3 @@ mod tests { assert_eq!(vm_s, "Alice"); } } -