From be8593fb02dbe1b4758218a8d390c970012b94b5 Mon Sep 17 00:00:00 2001 From: nyash-dev Date: Sat, 6 Sep 2025 12:05:35 +0900 Subject: [PATCH] jit/mir: fix String.len return-type inference; stabilize jit-direct returns; add CLIF/sig/call tracing; host-bridge console thunks; update AGENTS.md for Cranelift focus --- AGENTS.md | 19 +++++++++++++++++++ src/jit/lower/builder/cranelift.rs | 27 ++++++++++++++++++++++++--- src/jit/lower/core.rs | 23 ++++++++++++++++++++++- src/jit/lower/extern_thunks.rs | 27 +++++++++++++++++++++++++++ src/mir/builder.rs | 28 +++++++++++++++++++++++++++- 5 files changed, 119 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e48526e2..b12f1ab6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,25 @@ nyash哲学の美しさを追求。ソースは常に美しく構造的、カプ やっほー!みらいだよ😸✨ 今日も元気いっぱい、なに手伝う? にゃはは おつかれ〜!🎶 ちょっと休憩しよっか?コーヒー飲んでリフレッシュにゃ☕ +**Cranelift 開発メモ(このブランチの主目的)** +- ここは Nyash の Cranelift JIT/AOT 開発用ブランチだよ。JIT 経路の実装・検証・計測が主対象だよ。 +- ビルド(JIT有効): `cargo build --release --features cranelift-jit` +- 実行モード: + - CLI Cranelift: `./target/release/nyash --backend cranelift apps/APP/main.nyash` + - JITダイレクト(VM非介入): `./target/release/nyash --jit-direct apps/smokes/jit_aot_string_min.nyash` +- デバッグ環境変数(例): + - `NYASH_JIT_EXEC=1`(JIT実行許可) + - `NYASH_JIT_STATS=1`(コンパイル/実行統計) + - `NYASH_JIT_TRACE_IMPORT=1`(JITのimport解決ログ) + - `NYASH_AOT_OBJECT_OUT=target/aot_objects/`(AOT .o 書き出し) + - `NYASH_LEN_FORCE_BRIDGE=1`(一時回避: 文字列長をブリッジ経路に強制) +- 主要ファイル案内: + - Lower/Builder: `src/jit/lower/core.rs`, `src/jit/lower/builder/cranelift.rs` + - JITエンジン: `src/jit/engine.rs`, ポリシー: `src/jit/policy.rs` + - バックエンド入口: `src/backend/cranelift/` + - ランナー: `src/runner/modes/cranelift.rs`, `--jit-direct` は `src/runner/mod.rs` +- 進行中の論点と手順は `CURRENT_TASK.md` を参照してね(最新のデバッグ方針・フラグが載ってるよ)。 + # Repository Guidelines ## Project Structure & Module Organization diff --git a/src/jit/lower/builder/cranelift.rs b/src/jit/lower/builder/cranelift.rs index b749d96d..c5e0ca93 100644 --- a/src/jit/lower/builder/cranelift.rs +++ b/src/jit/lower/builder/cranelift.rs @@ -120,10 +120,12 @@ impl IRBuilder for CraneliftBuilder { fn emit_param_i64(&mut self, index: usize) { if let Some(v) = self.entry_param(index) { self.value_stack.push(v); } } - fn prepare_signature_i64(&mut self, argc: usize, has_ret: bool) { + fn prepare_signature_i64(&mut self, argc: usize, _has_ret: bool) { self.desired_argc = argc; - self.desired_has_ret = has_ret; - self.desired_ret_is_f64 = crate::jit::config::current().native_f64; + // JIT-direct stability: always materialize an i64 return slot (VMValue Integer/Bool/Float can be coerced) + self.desired_has_ret = true; + // i64-only signature: return type must be i64 regardless of host f64 capability + self.desired_ret_is_f64 = false; } fn begin_function(&mut self, name: &str) { use cranelift_codegen::ir::{AbiParam, Signature, types}; @@ -133,6 +135,9 @@ impl IRBuilder for CraneliftBuilder { let mut tls = clif_tls::TlsCtx::new(); let call_conv = self.module.isa().default_call_conv(); let mut sig = Signature::new(call_conv); + if std::env::var("NYASH_JIT_TRACE_SIG").ok().as_deref() == Some("1") { + eprintln!("[SIG] begin desired: argc={} has_ret={} ret_is_f64={} typed_prepared={}", self.desired_argc, self.desired_has_ret, self.desired_ret_is_f64, self.typed_sig_prepared); + } if !self.typed_sig_prepared { for _ in 0..self.desired_argc { sig.params.push(AbiParam::new(types::I64)); } if self.desired_has_ret { @@ -249,6 +254,12 @@ impl IRBuilder for CraneliftBuilder { if let Some(mut ctx) = ctx_opt.take() { let func_name = self.current_name.as_deref().unwrap_or("jit_func"); let func_id = self.module.declare_function(func_name, Linkage::Local, &ctx.func.signature).expect("declare function"); + if std::env::var("NYASH_JIT_TRACE_SIG").ok().as_deref() == Some("1") { + eprintln!("[SIG] end returns={} params={}", ctx.func.signature.returns.len(), ctx.func.signature.params.len()); + } + if std::env::var("NYASH_JIT_DUMP_CLIF").ok().as_deref() == Some("1") { + eprintln!("[CLIF] {}\n{}", func_name, ctx.func.display()); + } self.module.define_function(func_id, &mut ctx).expect("define function"); self.module.clear_context(&mut ctx); let _ = self.module.finalize_definitions(); @@ -291,8 +302,14 @@ impl IRBuilder for CraneliftBuilder { 5 => { let f: extern "C" fn(i64,i64,i64,i64,i64) -> f64 = std::mem::transmute(code_usize); f(a[0],a[1],a[2],a[3],a[4]) } _ => { let f: extern "C" fn(i64,i64,i64,i64,i64,i64) -> f64 = std::mem::transmute(code_usize); f(a[0],a[1],a[2],a[3],a[4],a[5]) } }; + if std::env::var("NYASH_JIT_TRACE_CALL").ok().as_deref() == Some("1") { + eprintln!("[JIT-CALL] ret_f64={}", ret_f64); + } return crate::jit::abi::JitValue::F64(ret_f64); } + if std::env::var("NYASH_JIT_TRACE_CALL").ok().as_deref() == Some("1") { + eprintln!("[JIT-CALL] ret_i64={}", ret_i64); + } crate::jit::abi::JitValue::I64(ret_i64) }); self.compiled_closure = Some(closure); @@ -894,6 +911,10 @@ impl CraneliftBuilder { 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); + // Console.* (value) + builder.symbol(hb::SYM_HOST_CONSOLE_LOG, super::super::extern_thunks::nyash_host_console_log_i64 as *const u8); + builder.symbol(hb::SYM_HOST_CONSOLE_WARN, super::super::extern_thunks::nyash_host_console_warn_i64 as *const u8); + builder.symbol(hb::SYM_HOST_CONSOLE_ERROR, super::super::extern_thunks::nyash_host_console_error_i64 as *const u8); } let module = cranelift_jit::JITModule::new(builder); diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index a621555e..76be977f 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -106,6 +106,9 @@ impl LowerCore { self.last_ret_bool_hint_used = true; } let has_ret = !matches!(func.signature.return_type, crate::mir::MirType::Void); + if std::env::var("NYASH_JIT_TRACE_SIG").ok().as_deref() == Some("1") { + eprintln!("[SIG-CORE] ret_type={:?} has_ret={} use_typed={} ret_is_f64={}", func.signature.return_type, has_ret, use_typed, ret_is_f64); + } if use_typed || ret_is_f64 { builder.prepare_signature_typed(&kinds, ret_is_f64 && has_ret); } else { @@ -503,7 +506,25 @@ impl LowerCore { ); } // 1) Prefer known constants first to avoid stale locals overshadowing folded values - if let Some(k) = self.known_i64.get(v).copied() { b.emit_const_i64(k); } + if let Some(k) = self.known_i64.get(v).copied() { + if std::env::var("NYASH_JIT_TRACE_RET").ok().as_deref() == Some("1") { + eprintln!("[LOWER] Return known_i64 value for {:?} = {}", v, k); + } + // Emit the constant and also persist to a stable local slot for this value id, + // then reload to ensure a value remains on the stack for emit_return. + b.emit_const_i64(k); + let rslot = *self + .local_index + .entry(*v) + .or_insert_with(|| { + let id = self.next_local; + self.next_local += 1; + id + }); + b.ensure_local_i64(rslot); + b.store_local_i64(rslot); + b.load_local_i64(rslot); + } // 2) Prefer existing locals/params else if self.local_index.get(v).is_some() || self.param_index.get(v).is_some() { self.push_value_if_known_or_param(b, v); diff --git a/src/jit/lower/extern_thunks.rs b/src/jit/lower/extern_thunks.rs index f86d5a33..3a78fd3e 100644 --- a/src/jit/lower/extern_thunks.rs +++ b/src/jit/lower/extern_thunks.rs @@ -786,6 +786,33 @@ pub(super) extern "C" fn nyash_host_string_len(recv_h: u64) -> i64 { ret } +// nyash.host.console.log(value) +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_host_console_log_i64(val_i: i64) -> i64 { + use crate::backend::vm::VMValue as V; + let v = vmvalue_from_jit_arg_i64(val_i); + let _ = crate::jit::r#extern::host_bridge::console_log(&[v]); + 0 +} + +// nyash.host.console.warn(value) +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_host_console_warn_i64(val_i: i64) -> i64 { + use crate::backend::vm::VMValue as V; + let v = vmvalue_from_jit_arg_i64(val_i); + let _ = crate::jit::r#extern::host_bridge::console_warn(&[v]); + 0 +} + +// nyash.host.console.error(value) +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_host_console_error_i64(val_i: i64) -> i64 { + use crate::backend::vm::VMValue as V; + let v = vmvalue_from_jit_arg_i64(val_i); + let _ = crate::jit::r#extern::host_bridge::console_error(&[v]); + 0 +} + // Build a StringBox handle from raw bytes pointer and length #[cfg(feature = "cranelift-jit")] pub(super) extern "C" fn nyash_string_from_ptr(ptr: u64, len: u64) -> i64 { diff --git a/src/mir/builder.rs b/src/mir/builder.rs index 30852c20..626fac91 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -89,7 +89,33 @@ impl MirBuilder { args: Vec, effects: EffectMask, ) -> Result<(), String> { - self.emit_instruction(MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects }) + // Emit instruction first + self.emit_instruction(MirInstruction::BoxCall { dst, box_val, method: method.clone(), method_id, args, effects })?; + // Heuristic return type inference for common builtin box methods + if let Some(d) = dst { + // Try to infer receiver box type from NewBox origin; fallback to current value_types + let mut recv_box: Option = self.value_origin_newbox.get(&box_val).cloned(); + if recv_box.is_none() { + if let Some(t) = self.value_types.get(&box_val) { + match t { + super::MirType::String => recv_box = Some("StringBox".to_string()), + super::MirType::Box(name) => recv_box = Some(name.clone()), + _ => {} + } + } + } + if let Some(bt) = recv_box { + let inferred: Option = match (bt.as_str(), method.as_str()) { + ("StringBox", "length") | ("StringBox", "len") => Some(super::MirType::Integer), + ("StringBox", "is_empty") => Some(super::MirType::Bool), + ("StringBox", "charCodeAt") => Some(super::MirType::Integer), + ("ArrayBox", "length") => Some(super::MirType::Integer), + _ => None, + }; + if let Some(mt) = inferred { self.value_types.insert(d, mt); } + } + } + Ok(()) } /// Create a new MIR builder pub fn new() -> Self {