From 80fe676cd431b4722b676c120c27b17460b1208c Mon Sep 17 00:00:00 2001 From: nyash-dev Date: Sat, 6 Sep 2025 21:25:03 +0900 Subject: [PATCH] cranelift: fix FB block-switch panic by safe switch wrapper; add Array.set(H,H) thunk + lowering; route all builder switching via safe path; pass identical_exec add/console_log/string_len/array_map_string --- src/jit/extern/collections.rs | 1 + src/jit/lower/builder/cranelift.rs | 17 ++++++++------- src/jit/lower/core.rs | 33 ++++++++++++++++++++++++++++++ src/jit/lower/extern_thunks.rs | 26 +++++++++++++++++++++++ 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/jit/extern/collections.rs b/src/jit/extern/collections.rs index 0d384f50..20594a03 100644 --- a/src/jit/extern/collections.rs +++ b/src/jit/extern/collections.rs @@ -17,6 +17,7 @@ pub const SYM_MAP_SIZE: &str = "nyash.map.size"; pub const SYM_ARRAY_LEN_H: &str = "nyash.array.len_h"; pub const SYM_ARRAY_GET_H: &str = "nyash.array.get_h"; pub const SYM_ARRAY_SET_H: &str = "nyash.array.set_h"; +pub const SYM_ARRAY_SET_HH: &str = "nyash.array.set_hh"; pub const SYM_ARRAY_PUSH_H: &str = "nyash.array.push_h"; pub const SYM_ARRAY_LAST_H: &str = "nyash.array.last_h"; pub const SYM_MAP_SIZE_H: &str = "nyash.map.size_h"; diff --git a/src/jit/lower/builder/cranelift.rs b/src/jit/lower/builder/cranelift.rs index c5e0ca93..bbbd4872 100644 --- a/src/jit/lower/builder/cranelift.rs +++ b/src/jit/lower/builder/cranelift.rs @@ -615,9 +615,10 @@ impl IRBuilder for CraneliftBuilder { if has_ret { sig.returns.push(AbiParam::new(if use_f64 { types::F64 } else { types::I64 })); } let symbol = if use_f64 { "nyash_plugin_invoke3_f64" } else { "nyash_plugin_invoke3_i64" }; let func_id = self.module.declare_function(symbol, cranelift_module::Linkage::Import, &sig).expect("declare plugin shim failed"); + // Ensure we are in a valid block context using the builder's safe switch + if let Some(idx) = self.current_block_index { self.switch_to_block(idx); } + else if self.entry_block.is_some() { self.switch_to_block(0); } let ret_val = Self::with_fb(|fb| { - 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); } while arg_vals.len() < 3 { let z = fb.ins().iconst(types::I64, 0); arg_vals.push(z); } // handle.of on receiver (redundant-safe) let call_conv_h = self.module.isa().default_call_conv(); @@ -639,10 +640,9 @@ impl IRBuilder for CraneliftBuilder { } fn emit_plugin_invoke_by_name(&mut self, method: &str, argc: usize, has_ret: bool) { use cranelift_codegen::ir::{AbiParam, Signature, types}; - Self::with_fb(|fb| { - 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); } - }); + // Ensure we are in a valid block context using the builder's safe switch + if let Some(idx) = self.current_block_index { self.switch_to_block(idx); } + else if self.entry_block.is_some() { self.switch_to_block(0); } // Collect call args let mut arg_vals: Vec = { let take_n = argc.min(self.value_stack.len()); @@ -710,10 +710,12 @@ impl IRBuilder for CraneliftBuilder { } fn switch_to_block(&mut self, index: usize) { if index >= self.blocks.len() { return; } + // If already on the target block, avoid re-switching (CLIF panics when switching from an unfilled block) + if let Some(cur) = self.current_block_index { if cur == index { return; } } Self::with_fb(|fb| { // If switching away from a non-terminated block, inject jump to keep CFG sane if let Some(cur) = self.current_block_index { - if self.cur_needs_term && cur != index { fb.ins().jump(self.blocks[index], &[]); self.cur_needs_term = false; } + if self.cur_needs_term { fb.ins().jump(self.blocks[index], &[]); self.cur_needs_term = false; } } fb.switch_to_block(self.blocks[index]); self.current_block_index = Some(index); @@ -872,6 +874,7 @@ impl CraneliftBuilder { builder.symbol(c::SYM_ARRAY_LEN_H, nyash_array_len_h as *const u8); builder.symbol(c::SYM_ARRAY_GET_H, nyash_array_get_h as *const u8); builder.symbol(c::SYM_ARRAY_SET_H, nyash_array_set_h as *const u8); + builder.symbol(c::SYM_ARRAY_SET_HH, super::super::extern_thunks::nyash_array_set_hh as *const u8); builder.symbol(c::SYM_ARRAY_PUSH_H, nyash_array_push_h as *const u8); builder.symbol(c::SYM_ARRAY_LAST_H, nyash_array_last_h as *const u8); builder.symbol(c::SYM_MAP_SIZE_H, nyash_map_size_h as *const u8); diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index e522c7ac..eca78edb 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -696,6 +696,39 @@ impl LowerCore { I::BoxCall { box_val: array, method, args, dst, .. } => { // Prefer ops_ext; if not handled, fall back to legacy path below let trace = std::env::var("NYASH_JIT_TRACE_LOWER").ok().as_deref() == Some("1"); + // Handle ArrayBox.set with handle-valued value for literal strings + if method == "set" && self.box_type_map.get(&array).map(|s| s=="ArrayBox").unwrap_or(false) { + // Expect args: [index, value] + let argc = 3usize; + // Receiver handle: prefer param or local slot; else -1 sentinel + if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } + else if let Some(slot) = self.local_index.get(&array).copied() { b.load_local_i64(slot); } + else { b.emit_const_i64(-1); } + // Index as i64 + if let Some(idx_v) = args.get(0) { + if let Some(iv) = self.known_i64.get(idx_v).copied() { b.emit_const_i64(iv); } + else { self.push_value_if_known_or_param(b, idx_v); } + } else { b.emit_const_i64(0); } + // Value as handle: for String literal, synthesize a handle; else prefer param/local handle + if let Some(val_v) = args.get(1) { + let mut emitted_val_handle = false; + if let Some(s) = self.known_str.get(val_v).cloned() { + b.emit_string_handle_from_literal(&s); + emitted_val_handle = true; + } else if let Some(slot) = self.local_index.get(val_v).copied() { + b.load_local_i64(slot); + emitted_val_handle = true; + } else if let Some(pidx) = self.param_index.get(val_v).copied() { + b.emit_param_i64(pidx); + emitted_val_handle = true; + } + if !emitted_val_handle { b.emit_const_i64(0); } + } else { b.emit_const_i64(0); } + // Emit handle-handle variant hostcall + b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_SET_HH, argc, false); + if trace { eprintln!("[LOWER] BoxCall(ArrayBox.set) → ARRAY_SET_HH"); } + return Ok(()); + } // Early constant fold: StringBox literal length/len (allow disabling via NYASH_JIT_DISABLE_LEN_CONST=1) if std::env::var("NYASH_JIT_DISABLE_LEN_CONST").ok().as_deref() != Some("1") && (method == "len" || method == "length") diff --git a/src/jit/lower/extern_thunks.rs b/src/jit/lower/extern_thunks.rs index 639cfe0d..e2988e83 100644 --- a/src/jit/lower/extern_thunks.rs +++ b/src/jit/lower/extern_thunks.rs @@ -241,6 +241,32 @@ pub(super) extern "C" fn nyash_array_set_h(handle: u64, idx: i64, val: i64) -> i 0 } +// Array.set where value is a handle (StringBox, IntegerBox, etc.) +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_array_set_hh(handle: u64, idx: i64, val_h: u64) -> i64 { + use crate::jit::hostcall_registry::{classify, HostcallKind}; + let sym = c::SYM_ARRAY_SET_HH; + let pol = crate::jit::policy::current(); + let wh = pol.hostcall_whitelist; + if classify(sym) == HostcallKind::Mutating && pol.read_only && !wh.iter().any(|s| s == sym) { + events::emit_runtime(serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"}), "hostcall", ""); + return 0; + } + if let Some(obj) = crate::jit::rt::handles::get(handle) { + if let Some(arr) = obj.as_any().downcast_ref::() { + // Convert value handle to Box + if let Some(v_arc) = crate::jit::rt::handles::get(val_h) { + // Prefer share semantics for identity boxes + let val_box: Box = v_arc.as_ref().clone_or_share(); + let _ = arr.set(Box::new(crate::box_trait::IntegerBox::new(idx)), val_box); + events::emit_runtime(serde_json::json!({"id": sym, "decision":"allow", "argc":3, "arg_types":["Handle","I64","Handle"]}), "hostcall", ""); + return 0; + } + } + } + 0 +} + #[cfg(feature = "cranelift-jit")] pub(super) extern "C" fn nyash_array_push_h(handle: u64, val: i64) -> i64 { use crate::jit::hostcall_registry::{classify, HostcallKind};