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

This commit is contained in:
nyash-dev
2025-09-06 21:25:03 +09:00
parent c5c3a2624c
commit 80fe676cd4
4 changed files with 70 additions and 7 deletions

View File

@ -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";

View File

@ -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<cranelift_codegen::ir::Value> = {
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);

View File

@ -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")

View File

@ -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", "<jit>");
return 0;
}
if let Some(obj) = crate::jit::rt::handles::get(handle) {
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
// Convert value handle to Box<dyn NyashBox>
if let Some(v_arc) = crate::jit::rt::handles::get(val_h) {
// Prefer share semantics for identity boxes
let val_box: Box<dyn crate::box_trait::NyashBox> = 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", "<jit>");
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};