Phase 12: VM/JIT identical execution tests + host API slot routing

ChatGPT5による統一実行パス実装:
- VM/JIT同一実行テスト追加(Array/Map/String/Instance)
- host_api slot経由呼び出し(NYASH_JIT_HOST_BRIDGE=1)
- extern_registry拡張(console系メソッドslot登録)
- CI: vm-jit-identical.yml(STRICT/非STRICT両系テスト)
- InstanceBox getField/setField slot 1,2統一

技術的改善:
- JIT: ops_ext委譲による統一メソッド解決
- VM: vtable/PIC/名前ベースフォールバック階層
- host_bridge: TLV encode/decode BoxRef対応
- C ABI: nyrt_host_api.h外部公開ヘッダー

テスト追加:
- identical_exec_collections: Array/Map操作一致
- identical_exec_instance: ユーザー定義Box一致
- identical_exec_string: StringBox操作一致
- host_reverse_slot: 逆引きslot解決テスト

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-09-03 09:12:39 +09:00
parent 51e8e7582a
commit 773256380d
25 changed files with 988 additions and 37 deletions

View File

@ -226,6 +226,9 @@ impl JitEngine {
self.register_extern(hb::SYM_HOST_CONSOLE_LOG, Arc::new(|args| hb::console_log(args)));
self.register_extern(hb::SYM_HOST_CONSOLE_WARN, Arc::new(|args| hb::console_warn(args)));
self.register_extern(hb::SYM_HOST_CONSOLE_ERROR, Arc::new(|args| hb::console_error(args)));
self.register_extern(hb::SYM_HOST_INSTANCE_GETFIELD, Arc::new(|args| hb::instance_getfield(args)));
self.register_extern(hb::SYM_HOST_INSTANCE_SETFIELD, Arc::new(|args| hb::instance_setfield(args)));
self.register_extern(hb::SYM_HOST_STRING_LEN, Arc::new(|args| hb::string_len(args)));
}
}

View File

@ -13,7 +13,29 @@ fn tlv_encode_values(args: &[VMValue]) -> Vec<u8> {
VMValue::Float(f) => enc::f64(&mut buf, *f),
VMValue::Bool(b) => enc::bool(&mut buf, *b),
VMValue::String(s) => enc::string(&mut buf, s),
VMValue::BoxRef(_) | VMValue::Future(_) | VMValue::Void => enc::string(&mut buf, ""),
VMValue::BoxRef(arc) => {
// Try to downcast common primitives for stable TLV
if let Some(sb) = arc.as_any().downcast_ref::<crate::box_trait::StringBox>() {
enc::string(&mut buf, &sb.value);
} else if let Some(ib) = arc.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
enc::i64(&mut buf, ib.value);
} else if let Some(bb) = arc.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
enc::bool(&mut buf, bb.value);
} else if let Some(fb) = arc.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
enc::f64(&mut buf, fb.value);
} else {
// Fallback: send HostHandle so host can operate on it if needed
let h = crate::runtime::host_handles::to_handle_arc(arc.clone());
enc::host_handle(&mut buf, h);
}
}
VMValue::Future(fu) => {
let bx: Box<dyn crate::box_trait::NyashBox> = Box::new(fu.clone());
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(bx);
let h = crate::runtime::host_handles::to_handle_arc(arc);
enc::host_handle(&mut buf, h);
}
VMValue::Void => enc::string(&mut buf, "void"),
}
}
buf
@ -27,7 +49,12 @@ fn call_slot(handle: u64, slot: u64, argv: &[VMValue]) -> VMValue {
if code != 0 { return VMValue::Void; }
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
match tag {
6|7 => VMValue::String(crate::runtime::plugin_ffi_common::decode::string(payload)),
6|7 => {
let s = crate::runtime::plugin_ffi_common::decode::string(payload);
let sb = crate::box_trait::StringBox::new(&s);
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(sb);
VMValue::BoxRef(arc)
}
1 => crate::runtime::plugin_ffi_common::decode::bool(payload).map(VMValue::Bool).unwrap_or(VMValue::Void),
2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void),
3 => crate::runtime::plugin_ffi_common::decode::u64(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void),
@ -61,6 +88,9 @@ pub const SYM_HOST_MAP_HAS: &str = "nyash.host.map.has"; // (MapBox, key)
pub const SYM_HOST_CONSOLE_LOG: &str = "nyash.host.console.log"; // (value)
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)
pub const SYM_HOST_STRING_LEN: &str = "nyash.host.string.len"; // (StringBox)
pub fn array_get(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) { call_slot(h, 100, &args[1..]) } else { VMValue::Void }
@ -101,3 +131,14 @@ pub fn console_error(args: &[VMValue]) -> VMValue {
if let Some(a0) = args.get(0) { eprintln!("[error] {}", a0.to_string()); }
VMValue::Void
}
pub fn instance_getfield(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) { call_slot(h, 1, &args[1..]) } else { VMValue::Void }
}
pub fn instance_setfield(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) { call_slot(h, 2, &args[1..]) } else { VMValue::Void }
}
pub fn string_len(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) { call_slot(h, 300, &[]) } else { VMValue::Integer(0) }
}

View File

@ -30,6 +30,8 @@ pub trait IRBuilder {
fn emit_host_call_typed(&mut self, _symbol: &str, _params: &[ParamKind], _has_ret: bool, _ret_is_f64: 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.
fn emit_string_handle_from_literal(&mut self, _s: &str) { }
fn prepare_blocks(&mut self, _count: usize) { }
fn switch_to_block(&mut self, _index: usize) { }
fn seal_block(&mut self, _index: usize) { }
@ -70,4 +72,3 @@ mod tls;
pub(crate) use tls::clif_tls;
#[cfg(feature = "cranelift-jit")]
mod rt_shims;

View File

@ -29,7 +29,7 @@ use super::super::extern_thunks::{
nyash_box_birth_h, nyash_box_birth_i64,
nyash_handle_of,
nyash_rt_checkpoint, nyash_gc_barrier_write,
nyash_console_birth_h,
nyash_console_birth_h, nyash_string_from_ptr,
};
use crate::jit::r#extern::r#async::nyash_future_await_h;
@ -542,6 +542,33 @@ impl IRBuilder for CraneliftBuilder {
});
if let Some(v) = ret_val { self.value_stack.push(v); }
}
fn emit_string_handle_from_literal(&mut self, s: &str) {
use cranelift_codegen::ir::{AbiParam, Signature, types};
// Pack up to 16 bytes into two u64 words (little-endian)
let bytes = s.as_bytes();
let mut lo: u64 = 0; let mut hi: u64 = 0;
let take = core::cmp::min(16, bytes.len());
for i in 0..take.min(8) { lo |= (bytes[i] as u64) << (8 * i as u32); }
for i in 8..take { hi |= (bytes[i] as u64) << (8 * (i - 8) as u32); }
// Call thunk: nyash.string.from_u64x2(lo, hi, len) -> handle(i64)
let call_conv = self.module.isa().default_call_conv();
let mut sig = Signature::new(call_conv);
sig.params.push(AbiParam::new(types::I64)); // lo
sig.params.push(AbiParam::new(types::I64)); // hi
sig.params.push(AbiParam::new(types::I64)); // len
sig.returns.push(AbiParam::new(types::I64));
let func_id = self.module.declare_function("nyash.string.from_u64x2", cranelift_module::Linkage::Import, &sig).expect("declare string.from_u64x2");
let v = Self::with_fb(|fb| {
let lo_v = fb.ins().iconst(types::I64, lo as i64);
let hi_v = fb.ins().iconst(types::I64, hi as i64);
let len_v = fb.ins().iconst(types::I64, bytes.len() as i64);
let fref = self.module.declare_func_in_func(func_id, fb.func);
let call_inst = fb.ins().call(fref, &[lo_v, hi_v, len_v]);
fb.inst_results(call_inst).get(0).copied().expect("str.from_ptr ret")
});
self.value_stack.push(v);
self.stats.0 += 1;
}
fn prepare_blocks(&mut self, count: usize) {
// Allow being called before begin_function; stash desired count
let mut need_tls = false;
@ -724,6 +751,17 @@ impl CraneliftBuilder {
builder.symbol("nyash_plugin_invoke3_f64", nyash_plugin_invoke3_f64 as *const u8);
builder.symbol("nyash_plugin_invoke_name_getattr_i64", nyash_plugin_invoke_name_getattr_i64 as *const u8);
builder.symbol("nyash_plugin_invoke_name_call_i64", nyash_plugin_invoke_name_call_i64 as *const u8);
builder.symbol("nyash.string.from_u64x2", super::super::extern_thunks::nyash_string_from_u64x2 as *const u8);
// Host-bridge (by-slot) imports (opt-in)
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);
// String.len (recv_h)
builder.symbol(hb::SYM_HOST_STRING_LEN, super::super::extern_thunks::nyash_host_string_len as *const u8);
}
let module = cranelift_jit::JITModule::new(builder);
let ctx = cranelift_codegen::Context::new();

View File

@ -27,8 +27,8 @@ impl IRBuilder for NoopBuilder {
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_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; }
fn ensure_local_i64(&mut self, _index: usize) { }
fn store_local_i64(&mut self, _index: usize) { self.consts += 1; }
fn load_local_i64(&mut self, _index: usize) { self.consts += 1; }
}

View File

@ -285,6 +285,31 @@ impl LowerCore {
}
}
}
// 2) StringBox(const string) → 文字列リテラルから直接ハンドル生成
if box_type == "StringBox" && args.len() == 1 {
if let Some(src) = args.get(0) {
// 探索: 同一関数内で src を定義する Const(String)
let mut lit: Option<String> = None;
for (_bid, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::Const { dst: cdst, value } = ins {
if cdst == src {
if let crate::mir::ConstValue::String(s) = value { lit = Some(s.clone()); }
break;
}
}
}
if lit.is_some() { break; }
}
if let Some(s) = lit {
b.emit_string_handle_from_literal(&s);
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);
return Ok(());
}
}
}
// 2) 引数がハンドルStringBox等で既に存在する場合最大2引数
if args.len() <= 2 && args.iter().all(|a| self.handle_values.contains(a)) {
if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, .. } = crate::jit::policy::invoke::decide_box_method(box_type, "birth", args.len(), true) {

View File

@ -63,12 +63,17 @@ impl LowerCore {
args: &Vec<ValueId>,
_func: &MirFunction,
) -> Result<(), String> {
// env.console.log/println → ConsoleBox に委譲host-bridge有効時は直接ログ
if iface_name == "env.console" && (method_name == "log" || method_name == "println") {
// env.console.log/warn/error/println → ConsoleBox に委譲host-bridge有効時は直接ログ
if iface_name == "env.console" && (method_name == "log" || method_name == "println" || method_name == "warn" || method_name == "error") {
if std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1") {
// a0: 先頭引数を最小限で積む
if let Some(arg0) = args.get(0) { self.push_value_if_known_or_param(b, arg0); } else { b.emit_const_i64(0); }
b.emit_host_call(crate::jit::r#extern::host_bridge::SYM_HOST_CONSOLE_LOG, 1, false);
let sym = match method_name {
"warn" => crate::jit::r#extern::host_bridge::SYM_HOST_CONSOLE_WARN,
"error" => crate::jit::r#extern::host_bridge::SYM_HOST_CONSOLE_ERROR,
_ => crate::jit::r#extern::host_bridge::SYM_HOST_CONSOLE_LOG,
};
b.emit_host_call(sym, 1, false);
return Ok(());
}
// Ensure we have a Console handle (hostcall birth shim)
@ -154,10 +159,7 @@ impl LowerCore {
args: &Vec<ValueId>,
dst: Option<ValueId>,
) -> Result<bool, String> {
// Delegate to existing helpers first
if super::super::core_hostcall::lower_boxcall_simple_reads(b, &self.param_index, &self.known_i64, array, method, args, dst.clone()) {
return Ok(true);
}
// Note: simple_reads は後段の分岐のフォールバックとして扱うString/Instance優先
if matches!(method, "sin" | "cos" | "abs" | "min" | "max") {
super::super::core_hostcall::lower_math_call(
func,
@ -200,6 +202,69 @@ impl LowerCore {
}
// Array/Map minimal handling
match method {
// Instance field ops via host-bridge
"getField" | "setField" => {
if std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1") {
// receiver: allow param/local/phi/known
if let Some(v) = args.get(0) { let _ = v; } // keep args in scope
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<String> = 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); }
} 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<String> = 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); }
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());
return Ok(true);
}
}
// String.len via host-bridge when receiver is StringBox
"len" => {
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" {
if std::env::var("NYASH_JIT_TRACE_BRIDGE").ok().as_deref() == Some("1") { eprintln!("[LOWER]string.len via host-bridge"); }
self.push_value_if_known_or_param(b, array);
b.emit_host_call(crate::jit::r#extern::host_bridge::SYM_HOST_STRING_LEN, 1, dst.is_some());
return Ok(true);
}
}
}
}
// Array length variants (length/len)
"len" | "length" => {
if let Ok(ph) = crate::runtime::plugin_loader_unified::get_global_plugin_host().read() {

View File

@ -28,7 +28,7 @@ pub fn lower_array_get(
}
}
pub fn lower_map_size(
pub fn lower_map_size_simple(
b: &mut dyn IRBuilder,
param_index: &HashMap<ValueId, usize>,
recv: &ValueId,
@ -42,7 +42,7 @@ pub fn lower_map_size(
}
}
pub fn lower_map_get(
pub fn lower_map_get_simple(
b: &mut dyn IRBuilder,
param_index: &HashMap<ValueId, usize>,
known_i64: &HashMap<ValueId, i64>,
@ -59,7 +59,7 @@ pub fn lower_map_get(
}
}
pub fn lower_map_has(
pub fn lower_map_has_simple(
b: &mut dyn IRBuilder,
param_index: &HashMap<ValueId, usize>,
known_i64: &HashMap<ValueId, i64>,
@ -76,7 +76,7 @@ pub fn lower_map_has(
}
}
pub fn lower_map_set(
pub fn lower_map_set_simple(
b: &mut dyn IRBuilder,
param_index: &HashMap<ValueId, usize>,
known_i64: &HashMap<ValueId, i64>,
@ -257,10 +257,10 @@ pub fn lower_box_call(
}
}
// Map
"size" => { lower_map_size(b, param_index, recv, dst.is_some()); }
"get" => { if let Some(k) = args.get(0) { lower_map_get(b, param_index, known_i64, recv, k, dst.is_some()); } }
"has" => { if let Some(k) = args.get(0) { lower_map_has(b, param_index, known_i64, recv, k, dst.is_some()); } }
"set" => { if args.len() >= 2 { lower_map_set(b, param_index, known_i64, recv, &args[0], &args[1]); } }
"size" => { lower_map_size_simple(b, param_index, recv, dst.is_some()); }
"get" => { if let Some(k) = args.get(0) { lower_map_get_simple(b, param_index, known_i64, recv, k, dst.is_some()); } }
"has" => { if let Some(k) = args.get(0) { lower_map_has_simple(b, param_index, known_i64, recv, k, dst.is_some()); } }
"set" => { if args.len() >= 2 { lower_map_set_simple(b, param_index, known_i64, recv, &args[0], &args[1]); } }
"has" => {
// Decide on key kind via registry and known values
use crate::jit::hostcall_registry::{check_signature, ArgKind};

View File

@ -7,6 +7,8 @@ use crate::jit::events;
#[cfg(feature = "cranelift-jit")]
use crate::jit::r#extern::collections as c;
#[cfg(feature = "cranelift-jit")]
use crate::jit::r#extern::host_bridge as hb;
#[cfg(feature = "cranelift-jit")]
use crate::runtime::plugin_loader_unified;
#[cfg(feature = "cranelift-jit")]
use crate::runtime::plugin_loader_v2::PluginBoxV2;
@ -656,3 +658,101 @@ pub(super) extern "C" fn nyash_semantics_add_hh(lhs_h: u64, rhs_h: u64) -> i64 {
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(StringBox::new(s));
handles::to_handle(arc) as i64
}
// ==== Host-bridge (by-slot) external thunks ====
// These thunks adapt JIT runtime handles and primitive args into VMValue vectors
// and call the host-bridge helpers, which TLV-encode and invoke NyRT C-ABI by slot.
#[cfg(feature = "cranelift-jit")]
fn vmvalue_from_jit_arg_i64(v: i64) -> crate::backend::vm::VMValue {
use crate::backend::vm::VMValue as V;
if v <= 0 { return V::Integer(v); }
if let Some(obj) = crate::jit::rt::handles::get(v as u64) {
return V::BoxRef(obj);
}
// Legacy fallback: allow small indices to refer into legacy VM args for string/name lookups
if (v as u64) <= 16 {
return crate::jit::rt::with_legacy_vm_args(|args| args.get(v as usize).cloned()).unwrap_or(V::Integer(v));
}
V::Integer(v)
}
#[cfg(feature = "cranelift-jit")]
fn i64_from_vmvalue(v: crate::backend::vm::VMValue) -> i64 {
use crate::backend::vm::VMValue as V;
match v {
V::Integer(i) => i,
V::Bool(b) => if b { 1 } else { 0 },
V::Float(f) => f as i64,
V::String(s) => {
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(crate::box_trait::StringBox::new(&s));
crate::jit::rt::handles::to_handle(arc) as i64
}
V::BoxRef(b) => crate::jit::rt::handles::to_handle(b) as i64,
V::Future(fu) => {
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(fu);
crate::jit::rt::handles::to_handle(arc) as i64
}
V::Void => 0,
}
}
// nyash.host.instance.getField(recv, name)
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_host_instance_getfield(recv_h: u64, name_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 std::env::var("NYASH_JIT_TRACE_BRIDGE").ok().as_deref() == Some("1") {
eprintln!("[HB|getField] name_i={} kind={}", name_i, match &name_v { V::String(_) => "String", V::BoxRef(b) => if b.as_any().downcast_ref::<crate::box_trait::StringBox>().is_some() {"StringBox"} else {"BoxRef"}, V::Integer(_) => "Integer", V::Bool(_) => "Bool", V::Float(_) => "Float", V::Void => "Void", V::Future(_) => "Future" });
}
let out = hb::instance_getfield(&[V::BoxRef(recv), name_v]);
i64_from_vmvalue(out)
}
// nyash.host.instance.setField(recv, name, value)
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_host_instance_setfield(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);
let val_v = vmvalue_from_jit_arg_i64(val_i);
let out = hb::instance_setfield(&[V::BoxRef(recv), name_v, val_v]);
i64_from_vmvalue(out)
}
// nyash.host.string.len(recv)
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_host_string_len(recv_h: u64) -> i64 {
use crate::backend::vm::VMValue as V;
if std::env::var("NYASH_JIT_TRACE_BRIDGE").ok().as_deref() == Some("1") { eprintln!("[HB|string.len] recv_h={}", recv_h); }
let recv = match crate::jit::rt::handles::get(recv_h) { Some(a) => a, None => { if std::env::var("NYASH_JIT_TRACE_BRIDGE").ok().as_deref()==Some("1") { eprintln!("[HB|string.len] recv handle not found"); } return 0 } };
let out = hb::string_len(&[V::BoxRef(recv)]);
let ret = i64_from_vmvalue(out);
if std::env::var("NYASH_JIT_TRACE_BRIDGE").ok().as_deref() == Some("1") { eprintln!("[HB|string.len] ret_i64={}", ret); }
ret
}
// 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 {
if ptr == 0 || len == 0 { return 0; }
unsafe {
let slice = std::slice::from_raw_parts(ptr as *const u8, len as usize);
let s = match std::str::from_utf8(slice) { Ok(t) => t.to_string(), Err(_) => String::from_utf8_lossy(slice).to_string() };
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(crate::box_trait::StringBox::new(s));
return crate::jit::rt::handles::to_handle(arc) as i64;
}
}
// Build a StringBox handle from two u64 chunks (little-endian) and length (<=16)
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_string_from_u64x2(lo: u64, hi: u64, len: i64) -> i64 {
let n = if len <= 0 { 0usize } else { core::cmp::min(len as usize, 16usize) };
let mut buf = [0u8; 16];
for i in 0..core::cmp::min(8, n) { buf[i] = ((lo >> (8 * i)) & 0xFF) as u8; }
if n > 8 { for i in 0..(n - 8) { buf[8 + i] = ((hi >> (8 * i)) & 0xFF) as u8; } }
let s = match std::str::from_utf8(&buf[..n]) { Ok(t) => t.to_string(), Err(_) => String::from_utf8_lossy(&buf[..n]).to_string() };
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(crate::box_trait::StringBox::new(s));
crate::jit::rt::handles::to_handle(arc) as i64
}