📚 Phase 11 documentation: Everything is Box × MIR15 revolution

Key updates:
- Document MIR 26→15 instruction reduction plan (transitioning status)
- Add Core-15 target instruction set in INSTRUCTION_SET.md
- Save AI conference analyses validating Box Theory and 15-instruction design
- Create MIR annotation system proposal for optimization hints
- Update SKIP_PHASE_10_DECISION.md with LLVM direct migration rationale

Technical insights:
- RefNew/RefGet/RefSet can be eliminated through Box unification
- GC/sync/async all achievable with 15 core instructions
- BoxCall lowering can automatically insert GC barriers
- 2-3x performance improvement expected with LLVM
- Build time reduction 50%, binary size reduction 40%

Status: Design complete, implementation pending
This commit is contained in:
Moe Charm
2025-08-31 03:03:04 +09:00
parent 1812cda7d5
commit b003bdf25b
50 changed files with 2621 additions and 136 deletions

View File

@ -145,6 +145,30 @@ impl JitEngine {
return Some(h);
}
// If Cranelift path did not produce a closure, treat as not compiled
// Even if a closure was not produced, attempt AOT object emission when requested
if let Ok(path) = std::env::var("NYASH_AOT_OBJECT_OUT") {
if !path.is_empty() {
let mut lower2 = crate::jit::lower::core::LowerCore::new();
let mut objb = crate::jit::lower::builder::ObjectBuilder::new();
match lower2.lower_function(mir, &mut objb) {
Err(e) => eprintln!("[AOT] lower failed for {}: {}", func_name, e),
Ok(()) => {
if let Some(bytes) = objb.take_object_bytes() {
use std::path::Path;
let p = Path::new(&path);
let out_path = if p.is_dir() || path.ends_with('/') { p.join(format!("{}.o", func_name)) } else { p.to_path_buf() };
if let Some(parent) = out_path.parent() { let _ = std::fs::create_dir_all(parent); }
match std::fs::write(&out_path, bytes) {
Ok(_) => eprintln!("[AOT] wrote object: {}", out_path.display()),
Err(e) => eprintln!("[AOT] failed to write object {}: {}", out_path.display(), e),
}
} else {
eprintln!("[AOT] no object bytes available for {}", func_name);
}
}
}
}
}
return None;
}
#[cfg(not(feature = "cranelift-jit"))]

View File

@ -30,6 +30,10 @@ pub const SYM_ANY_IS_EMPTY_H: &str = "nyash.any.is_empty_h";
pub const SYM_STRING_CHARCODE_AT_H: &str = "nyash.string.charCodeAt_h";
pub const SYM_STRING_BIRTH_H: &str = "nyash.string.birth_h";
pub const SYM_INTEGER_BIRTH_H: &str = "nyash.integer.birth_h";
// String-like operations (handle, handle)
pub const SYM_STRING_CONCAT_HH: &str = "nyash.string.concat_hh";
pub const SYM_STRING_EQ_HH: &str = "nyash.string.eq_hh";
pub const SYM_STRING_LT_HH: &str = "nyash.string.lt_hh";
fn as_array(args: &[VMValue]) -> Option<&crate::boxes::array::ArrayBox> {
match args.get(0) {

View File

@ -7,3 +7,4 @@
pub mod collections;
pub mod handles;
pub mod birth;
pub mod runtime;

8
src/jit/extern/runtime.rs vendored Normal file
View File

@ -0,0 +1,8 @@
//! Runtime/GC related hostcall symbol names reserved for JIT/AOT.
/// Runtime safepoint checkpoint (no-op stub for now)
pub const SYM_RT_CHECKPOINT: &str = "nyash.rt.checkpoint";
/// Write barrier hint for GC (no-op stub for now)
pub const SYM_GC_BARRIER_WRITE: &str = "nyash.gc.barrier_write";

View File

@ -30,6 +30,9 @@ fn ensure_default() {
"nyash.map.get_h",
"nyash.map.has_h",
"nyash.string.charCodeAt_h",
"nyash.string.concat_hh",
"nyash.string.eq_hh",
"nyash.string.lt_hh",
"nyash.array.get_h",
] { r.ro.insert(s.to_string()); }
// Mutating defaults
@ -52,6 +55,9 @@ fn ensure_default() {
r.sig.entry("nyash.array.len_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
// String helpers
r.sig.entry("nyash.string.charCodeAt_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::I64 });
r.sig.entry("nyash.string.concat_hh".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::Handle], ret: ArgKind::Handle });
r.sig.entry("nyash.string.eq_hh".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::Handle], ret: ArgKind::I64 });
r.sig.entry("nyash.string.lt_hh".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::Handle], ret: ArgKind::I64 });
// Any helpers (length/is_empty)
r.sig.entry("nyash.any.length_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
r.sig.entry("nyash.any.is_empty_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });

View File

@ -536,7 +536,8 @@ use super::extern_thunks::{
nyash_array_last_h, nyash_map_size_h, nyash_map_get_h, nyash_map_get_hh,
nyash_map_set_h, nyash_map_has_h,
nyash_string_charcode_at_h, nyash_string_birth_h, nyash_integer_birth_h,
nyash_any_length_h, nyash_any_is_empty_h,
nyash_string_concat_hh, nyash_string_eq_hh, nyash_string_lt_hh,
nyash_any_length_h, nyash_any_is_empty_h, nyash_console_birth_h,
};
#[cfg(feature = "cranelift-jit")]
@ -742,8 +743,7 @@ impl IRBuilder for CraneliftBuilder {
let entry = self.blocks[0];
fb.append_block_params_for_function_params(entry);
fb.switch_to_block(entry);
// Entry block can be sealed immediately
fb.seal_block(entry);
// Defer sealing to allow entry PHI params when needed
self.entry_block = Some(entry);
self.current_block_index = Some(0);
fb.finalize();
@ -775,6 +775,7 @@ impl IRBuilder for CraneliftBuilder {
// SAFETY: We compiled a function with simple (i64 x N) -> i64/f64 というABIだよ。
// ランタイムでは JitValue から i64 へ正規化して、引数個数に応じた関数型にtransmuteして呼び出すにゃ。
let argc = self.desired_argc;
let has_ret = self.desired_has_ret;
let ret_is_f64 = self.desired_has_ret && self.desired_ret_is_f64;
// capture code as usize to avoid raw pointer Send/Sync issues in closure
let code_usize = code as usize;
@ -786,35 +787,26 @@ impl IRBuilder for CraneliftBuilder {
for i in 0..take {
a[i] = match args[i] { crate::jit::abi::JitValue::I64(v) => v, crate::jit::abi::JitValue::Bool(b) => if b {1} else {0}, crate::jit::abi::JitValue::F64(f) => f as i64, crate::jit::abi::JitValue::Handle(h) => h as i64 };
}
let ret_i64 = match argc {
0 => {
let f: extern "C" fn() -> i64 = std::mem::transmute(code_usize);
f()
// Call according to return type expectation
let ret_i64 = if has_ret {
match argc {
0 => { let f: extern "C" fn() -> i64 = std::mem::transmute(code_usize); f() }
1 => { let f: extern "C" fn(i64) -> i64 = std::mem::transmute(code_usize); f(a[0]) }
2 => { let f: extern "C" fn(i64,i64) -> i64 = std::mem::transmute(code_usize); f(a[0],a[1]) }
3 => { let f: extern "C" fn(i64,i64,i64) -> i64 = std::mem::transmute(code_usize); f(a[0],a[1],a[2]) }
4 => { let f: extern "C" fn(i64,i64,i64,i64) -> i64 = std::mem::transmute(code_usize); f(a[0],a[1],a[2],a[3]) }
5 => { let f: extern "C" fn(i64,i64,i64,i64,i64) -> i64 = 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) -> i64 = std::mem::transmute(code_usize); f(a[0],a[1],a[2],a[3],a[4],a[5]) }
}
1 => {
let f: extern "C" fn(i64) -> i64 = std::mem::transmute(code_usize);
f(a[0])
}
2 => {
let f: extern "C" fn(i64, i64) -> i64 = std::mem::transmute(code_usize);
f(a[0], a[1])
}
3 => {
let f: extern "C" fn(i64, i64, i64) -> i64 = std::mem::transmute(code_usize);
f(a[0], a[1], a[2])
}
4 => {
let f: extern "C" fn(i64, i64, i64, i64) -> i64 = std::mem::transmute(code_usize);
f(a[0], a[1], a[2], a[3])
}
5 => {
let f: extern "C" fn(i64, i64, i64, i64, i64) -> i64 = std::mem::transmute(code_usize);
f(a[0], a[1], a[2], a[3], a[4])
}
_ => {
// 上限6十分なPoC
let f: extern "C" fn(i64, i64, i64, i64, i64, i64) -> i64 = std::mem::transmute(code_usize);
f(a[0], a[1], a[2], a[3], a[4], a[5])
} else {
match argc {
0 => { let f: extern "C" fn() = std::mem::transmute(code_usize); f(); 0 }
1 => { let f: extern "C" fn(i64) = std::mem::transmute(code_usize); f(a[0]); 0 }
2 => { let f: extern "C" fn(i64,i64) = std::mem::transmute(code_usize); f(a[0],a[1]); 0 }
3 => { let f: extern "C" fn(i64,i64,i64) = std::mem::transmute(code_usize); f(a[0],a[1],a[2]); 0 }
4 => { let f: extern "C" fn(i64,i64,i64,i64) = std::mem::transmute(code_usize); f(a[0],a[1],a[2],a[3]); 0 }
5 => { let f: extern "C" fn(i64,i64,i64,i64,i64) = std::mem::transmute(code_usize); f(a[0],a[1],a[2],a[3],a[4]); 0 }
_ => { let f: extern "C" fn(i64,i64,i64,i64,i64,i64) = std::mem::transmute(code_usize); f(a[0],a[1],a[2],a[3],a[4],a[5]); 0 }
}
};
if ret_is_f64 {
@ -833,6 +825,73 @@ impl IRBuilder for CraneliftBuilder {
});
self.compiled_closure = Some(closure);
}
// Important: keep finalized code alive by preserving the JITModule.
// Swap current module with a fresh one and leak the old module to avoid freeing code memory.
{
// Build a fresh JITModule with the same symbol registrations for the next compilation
let mut jb = cranelift_jit::JITBuilder::new(cranelift_module::default_libcall_names())
.expect("failed to create JITBuilder");
// Register host-call symbols (keep in sync with new())
jb.symbol("nyash.host.stub0", nyash_host_stub0 as *const u8);
{
use crate::jit::r#extern::collections as c;
use crate::jit::r#extern::{handles as h, birth as b, runtime as r};
use super::extern_thunks::{
nyash_plugin_invoke_name_getattr_i64, nyash_plugin_invoke_name_call_i64,
nyash_handle_of, nyash_box_birth_h, nyash_box_birth_i64,
nyash_rt_checkpoint, nyash_gc_barrier_write,
};
jb.symbol(c::SYM_ARRAY_LEN, nyash_array_len as *const u8);
jb.symbol(c::SYM_ARRAY_GET, nyash_array_get as *const u8);
jb.symbol(c::SYM_ARRAY_SET, nyash_array_set as *const u8);
jb.symbol(c::SYM_ARRAY_PUSH, nyash_array_push as *const u8);
jb.symbol(c::SYM_MAP_GET, nyash_map_get as *const u8);
jb.symbol(c::SYM_MAP_SET, nyash_map_set as *const u8);
jb.symbol(c::SYM_MAP_SIZE, nyash_map_size as *const u8);
jb.symbol("nyash.math.sin_f64", nyash_math_sin_f64 as *const u8);
jb.symbol("nyash.math.cos_f64", nyash_math_cos_f64 as *const u8);
jb.symbol("nyash.math.abs_f64", nyash_math_abs_f64 as *const u8);
jb.symbol("nyash.math.min_f64", nyash_math_min_f64 as *const u8);
jb.symbol("nyash.math.max_f64", nyash_math_max_f64 as *const u8);
// Handle-based symbols
jb.symbol(c::SYM_ARRAY_LEN_H, nyash_array_len_h as *const u8);
jb.symbol(c::SYM_ARRAY_GET_H, nyash_array_get_h as *const u8);
jb.symbol(c::SYM_ARRAY_SET_H, nyash_array_set_h as *const u8);
jb.symbol(c::SYM_ARRAY_PUSH_H, nyash_array_push_h as *const u8);
jb.symbol(c::SYM_ARRAY_LAST_H, nyash_array_last_h as *const u8);
jb.symbol(c::SYM_MAP_SIZE_H, nyash_map_size_h as *const u8);
jb.symbol(c::SYM_MAP_GET_H, nyash_map_get_h as *const u8);
jb.symbol(c::SYM_MAP_GET_HH, nyash_map_get_hh as *const u8);
jb.symbol(c::SYM_MAP_SET_H, nyash_map_set_h as *const u8);
jb.symbol(c::SYM_MAP_HAS_H, nyash_map_has_h as *const u8);
jb.symbol(c::SYM_ANY_LEN_H, nyash_any_length_h as *const u8);
jb.symbol(c::SYM_ANY_IS_EMPTY_H, nyash_any_is_empty_h as *const u8);
jb.symbol(c::SYM_STRING_CHARCODE_AT_H, nyash_string_charcode_at_h as *const u8);
jb.symbol(c::SYM_STRING_BIRTH_H, nyash_string_birth_h as *const u8);
jb.symbol(c::SYM_INTEGER_BIRTH_H, nyash_integer_birth_h as *const u8);
// String-like binary ops (handle, handle)
jb.symbol(c::SYM_STRING_CONCAT_HH, nyash_string_concat_hh as *const u8);
jb.symbol(c::SYM_STRING_EQ_HH, nyash_string_eq_hh as *const u8);
jb.symbol(c::SYM_STRING_LT_HH, nyash_string_lt_hh as *const u8);
jb.symbol(b::SYM_BOX_BIRTH_H, nyash_box_birth_h as *const u8);
jb.symbol("nyash.box.birth_i64", nyash_box_birth_i64 as *const u8);
// Handle helpers
jb.symbol(h::SYM_HANDLE_OF, nyash_handle_of as *const u8);
// Plugin invoke shims (i64/f64)
jb.symbol("nyash_plugin_invoke3_i64", nyash_plugin_invoke3_i64 as *const u8);
jb.symbol("nyash_plugin_invoke3_f64", nyash_plugin_invoke3_f64 as *const u8);
// By-name plugin invoke shims (method-name specific)
jb.symbol("nyash_plugin_invoke_name_getattr_i64", nyash_plugin_invoke_name_getattr_i64 as *const u8);
jb.symbol("nyash_plugin_invoke_name_call_i64", nyash_plugin_invoke_name_call_i64 as *const u8);
// Reserved runtime/GC symbols
jb.symbol(r::SYM_RT_CHECKPOINT, nyash_rt_checkpoint as *const u8);
jb.symbol(r::SYM_GC_BARRIER_WRITE, nyash_gc_barrier_write as *const u8);
}
let new_module = cranelift_jit::JITModule::new(jb);
// Leak the old module so finalized code stays valid
let old = std::mem::replace(&mut self.module, new_module);
let _leaked: &'static mut cranelift_jit::JITModule = Box::leak(Box::new(old));
}
// Reset typed signature flag for next function
self.typed_sig_prepared = false;
}
@ -956,6 +1015,12 @@ impl IRBuilder for CraneliftBuilder {
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
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); }
// If function has no return values, emit a plain return
if fb.func.signature.returns.is_empty() {
fb.ins().return_(&[]);
fb.finalize();
return;
}
if let Some(mut v) = self.value_stack.pop() {
// Normalize return type if needed
let ret_ty = fb.func.signature.returns.get(0).map(|p| p.value_type).unwrap_or(cranelift_codegen::ir::types::I64);
@ -1528,7 +1593,7 @@ impl IRBuilder for ObjectBuilder {
let entry = self.blocks[0];
fb.append_block_params_for_function_params(entry);
fb.switch_to_block(entry);
fb.seal_block(entry);
// Defer sealing to allow entry PHI params when needed
self.entry_block = Some(entry);
self.current_block_index = Some(0);
fb.finalize();
@ -1621,6 +1686,11 @@ impl IRBuilder for ObjectBuilder {
use cranelift_frontend::FunctionBuilder; use cranelift_codegen::ir::types;
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
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); }
if fb.func.signature.returns.is_empty() {
fb.ins().return_(&[]);
fb.finalize();
return;
}
if let Some(mut v) = self.value_stack.pop() {
let ret_ty = fb.func.signature.returns.get(0).map(|p| p.value_type).unwrap_or(types::I64);
let v_ty = fb.func.dfg.value_type(v);
@ -1748,12 +1818,16 @@ impl CraneliftBuilder {
// Initialize a minimal JITModule to validate linking; not used yet
let mut builder = cranelift_jit::JITBuilder::new(cranelift_module::default_libcall_names())
.expect("failed to create JITBuilder");
// Register host-call symbols (PoC: map to simple C-ABI stubs)
builder.symbol("nyash.host.stub0", nyash_host_stub0 as *const u8);
// Register host-call symbols (PoC: map to simple C-ABI stubs)
builder.symbol("nyash.host.stub0", nyash_host_stub0 as *const u8);
{
use crate::jit::r#extern::collections as c;
use crate::jit::r#extern::{handles as h, birth as b};
use super::extern_thunks::{nyash_plugin_invoke_name_getattr_i64, nyash_plugin_invoke_name_call_i64, nyash_handle_of, nyash_box_birth_h, nyash_box_birth_i64};
use crate::jit::r#extern::{handles as h, birth as b, runtime as r};
use super::extern_thunks::{
nyash_plugin_invoke_name_getattr_i64, nyash_plugin_invoke_name_call_i64,
nyash_handle_of, nyash_box_birth_h, nyash_box_birth_i64,
nyash_rt_checkpoint, nyash_gc_barrier_write,
};
builder.symbol(c::SYM_ARRAY_LEN, nyash_array_len as *const u8);
builder.symbol(c::SYM_ARRAY_GET, nyash_array_get as *const u8);
builder.symbol(c::SYM_ARRAY_SET, nyash_array_set as *const u8);
@ -1783,6 +1857,11 @@ impl CraneliftBuilder {
builder.symbol(c::SYM_STRING_CHARCODE_AT_H, nyash_string_charcode_at_h as *const u8);
builder.symbol(c::SYM_STRING_BIRTH_H, nyash_string_birth_h as *const u8);
builder.symbol(c::SYM_INTEGER_BIRTH_H, nyash_integer_birth_h as *const u8);
builder.symbol("nyash.console.birth_h", nyash_console_birth_h as *const u8);
// String-like binary ops (handle, handle)
builder.symbol(c::SYM_STRING_CONCAT_HH, nyash_string_concat_hh as *const u8);
builder.symbol(c::SYM_STRING_EQ_HH, nyash_string_eq_hh as *const u8);
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);
// Handle helpers
@ -1793,6 +1872,9 @@ impl CraneliftBuilder {
// By-name plugin invoke shims (method-name specific)
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);
// Reserved runtime/GC symbols
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);
}
let module = cranelift_jit::JITModule::new(builder);
let ctx = cranelift_codegen::Context::new();

View File

@ -11,7 +11,7 @@ pub struct LowerCore {
/// Minimal constant propagation for f64 (math.* signature checks)
known_f64: std::collections::HashMap<ValueId, f64>,
/// Parameter index mapping for ValueId
param_index: std::collections::HashMap<ValueId, usize>,
pub(super) param_index: std::collections::HashMap<ValueId, usize>,
/// Track values produced by Phi (for minimal PHI path)
phi_values: std::collections::HashSet<ValueId>,
/// Map (block, phi dst) -> param index in that block (for multi-PHI)
@ -23,16 +23,16 @@ pub struct LowerCore {
/// Track values that are FloatBox instances (for arg type classification)
float_box_values: std::collections::HashSet<ValueId>,
/// Track values that are plugin handles (generic box/handle, type unknown at compile time)
handle_values: std::collections::HashSet<ValueId>,
pub(super) handle_values: std::collections::HashSet<ValueId>,
// Per-function statistics (last lowered)
last_phi_total: u64,
last_phi_b1: u64,
last_ret_bool_hint_used: bool,
// Minimal local slot mapping for Load/Store (ptr ValueId -> slot index)
local_index: std::collections::HashMap<ValueId, usize>,
next_local: usize,
pub(super) local_index: std::collections::HashMap<ValueId, usize>,
pub(super) next_local: usize,
/// Track NewBox origins: ValueId -> box type name (e.g., "PyRuntimeBox")
box_type_map: std::collections::HashMap<ValueId, String>,
pub(super) box_type_map: std::collections::HashMap<ValueId, String>,
}
impl LowerCore {
@ -44,7 +44,7 @@ impl LowerCore {
/// Walk the MIR function and count supported/unsupported instructions.
/// In the future, this will build CLIF via Cranelift builders.
pub fn lower_function(&mut self, func: &MirFunction, builder: &mut dyn IRBuilder) -> Result<(), String> {
// Prepare a simple i64 ABI based on param count; always assume i64 return for now
// Prepare ABI based on MIR signature
// Reset per-function stats
self.last_phi_total = 0; self.last_phi_b1 = 0; self.last_ret_bool_hint_used = false;
// Build param index map
@ -235,10 +235,11 @@ impl LowerCore {
crate::jit::rt::ret_bool_hint_inc(1);
self.last_ret_bool_hint_used = true;
}
let has_ret = !matches!(func.signature.return_type, crate::mir::MirType::Void);
if use_typed || ret_is_f64 {
builder.prepare_signature_typed(&kinds, ret_is_f64);
builder.prepare_signature_typed(&kinds, ret_is_f64 && has_ret);
} else {
builder.prepare_signature_i64(func.params.len(), true);
builder.prepare_signature_i64(func.params.len(), has_ret);
}
// Pre-scan FloatBox creations across all blocks for arg classification
self.float_box_values.clear();
@ -725,8 +726,8 @@ impl LowerCore {
if self.bool_values.contains(src) { self.bool_values.insert(*dst); }
// Otherwise no-op for codegen (stack-machine handles sources directly later)
}
I::BinOp { dst, op, lhs, rhs } => { self.lower_binop(b, op, lhs, rhs, dst); }
I::Compare { op, lhs, rhs, dst } => { self.lower_compare(b, op, lhs, rhs, dst); }
I::BinOp { dst, op, lhs, rhs } => { self.lower_binop(b, op, lhs, rhs, dst, func); }
I::Compare { op, lhs, rhs, dst } => { self.lower_compare(b, op, lhs, rhs, dst, func); }
I::Jump { .. } => self.lower_jump(b),
I::Branch { .. } => self.lower_branch(b),
I::Return { value } => {

View File

@ -1,11 +1,44 @@
//! Core ops lowering (non-hostcall): BinOp, Compare, Branch, Jump
use super::builder::{IRBuilder, BinOpKind, CmpKind};
use crate::mir::{BinaryOp, CompareOp, ValueId};
use crate::mir::{BinaryOp, CompareOp, ValueId, MirFunction, MirType};
use super::core::LowerCore;
impl LowerCore {
pub fn lower_binop(&mut self, b: &mut dyn IRBuilder, op: &BinaryOp, lhs: &ValueId, rhs: &ValueId, dst: &ValueId) {
fn is_string_like(&self, func: &MirFunction, v: &ValueId) -> bool {
// Check per-value type metadata
if let Some(mt) = func.metadata.value_types.get(v) {
if matches!(mt, MirType::String) { return true; }
if let MirType::Box(ref name) = mt { if name == "StringBox" { return true; } }
}
// Check if this value is a parameter with String or StringBox type
if let Some(pidx) = self.param_index.get(v).copied() {
if let Some(pt) = func.signature.params.get(pidx) {
if matches!(pt, MirType::String) { return true; }
if let MirType::Box(ref name) = pt { if name == "StringBox" { return true; } }
}
}
// Check if it originates from a StringBox NewBox
if let Some(name) = self.box_type_map.get(v) { if name == "StringBox" { return true; } }
false
}
pub fn lower_binop(&mut self, b: &mut dyn IRBuilder, op: &BinaryOp, lhs: &ValueId, rhs: &ValueId, dst: &ValueId, func: &MirFunction) {
// Route string-like addition to hostcall (handle,handle)
if crate::jit::config::current().hostcall {
if matches!(op, BinaryOp::Add) {
if self.is_string_like(func, lhs) || self.is_string_like(func, rhs) {
self.push_value_if_known_or_param(b, lhs);
self.push_value_if_known_or_param(b, rhs);
b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_CONCAT_HH, 2, true);
// Track handle result for downstream usages
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;
}
}
}
self.push_value_if_known_or_param(b, lhs);
self.push_value_if_known_or_param(b, rhs);
let kind = match op {
@ -30,7 +63,20 @@ impl LowerCore {
}
}
pub fn lower_compare(&mut self, b: &mut dyn IRBuilder, op: &CompareOp, lhs: &ValueId, rhs: &ValueId, dst: &ValueId) {
pub fn lower_compare(&mut self, b: &mut dyn IRBuilder, op: &CompareOp, lhs: &ValueId, rhs: &ValueId, dst: &ValueId, func: &MirFunction) {
// Route string-like comparisons (Eq/Lt) to hostcalls (i64 0/1)
if crate::jit::config::current().hostcall {
if matches!(op, CompareOp::Eq | CompareOp::Lt) {
if self.is_string_like(func, lhs) || self.is_string_like(func, rhs) {
self.push_value_if_known_or_param(b, lhs);
self.push_value_if_known_or_param(b, rhs);
let sym = match op { CompareOp::Eq => crate::jit::r#extern::collections::SYM_STRING_EQ_HH, CompareOp::Lt => crate::jit::r#extern::collections::SYM_STRING_LT_HH, _ => unreachable!() };
b.emit_host_call(sym, 2, true);
self.bool_values.insert(*dst);
return;
}
}
}
self.push_value_if_known_or_param(b, lhs);
self.push_value_if_known_or_param(b, rhs);
let kind = match op {

View File

@ -141,6 +141,38 @@ pub(super) extern "C" fn nyash_math_min_f64(a: f64, b: f64) -> f64 { a.min(b) }
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_math_max_f64(a: f64, b: f64) -> f64 { a.max(b) }
// ---- Console (handle) ----
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_console_birth_h() -> i64 {
if let Ok(host_g) = crate::runtime::get_global_plugin_host().read() {
if let Ok(b) = host_g.create_box("ConsoleBox", &[]) {
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(b);
let h = crate::jit::rt::handles::to_handle(arc);
return h as i64;
}
}
0
}
// ---- Runtime/GC stubs ----
// Minimal no-op checkpoints and barriers for reservation. They optionally trace when envs are set.
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_rt_checkpoint() -> i64 {
if std::env::var("NYASH_RUNTIME_CHECKPOINT_TRACE").ok().as_deref() == Some("1") {
eprintln!("[nyash.rt.checkpoint] reached");
}
0
}
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_gc_barrier_write(handle_or_ptr: u64) -> i64 {
let _ = handle_or_ptr; // reserved; currently unused
if std::env::var("NYASH_GC_BARRIER_TRACE").ok().as_deref() == Some("1") {
eprintln!("[nyash.gc.barrier_write] h=0x{:x}", handle_or_ptr);
}
0
}
// ---- Array (handle) ----
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_array_len_h(handle: u64) -> i64 {
@ -522,3 +554,69 @@ pub(super) extern "C" fn nyash_integer_birth_h() -> i64 {
}
0
}
// ---- String-like helpers and ops (handle, handle) ----
#[cfg(feature = "cranelift-jit")]
fn handle_to_string_like(handle: u64) -> Option<String> {
// Prefer runtime handle registry
if let Some(obj) = crate::jit::rt::handles::get(handle) {
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
return Some(sb.value.clone());
}
if let Some(pb) = obj.as_any().downcast_ref::<PluginBoxV2>() {
if pb.box_type == "StringBox" {
if let Ok(host) = crate::runtime::get_global_plugin_host().read() {
if let Ok(val_opt) = host.invoke_instance_method("StringBox", "toUtf8", pb.instance_id(), &[]) {
if let Some(vb) = val_opt { if let Some(sbb) = vb.as_any().downcast_ref::<crate::box_trait::StringBox>() { return Some(sbb.value.clone()); } }
}
}
}
}
// Fallback for any NyashBox
return Some(obj.to_string_box().value);
}
// Legacy fallback: treat small values as VM arg index
if handle <= 16 {
let idx = handle as usize;
let val = crate::jit::rt::with_legacy_vm_args(|args| args.get(idx).cloned());
if let Some(v) = val {
use crate::backend::vm::VMValue as V;
return match v {
V::String(s) => Some(s),
V::BoxRef(b) => Some(b.to_string_box().value),
V::Integer(i) => Some(i.to_string()),
V::Float(f) => Some(f.to_string()),
V::Bool(b) => Some(b.to_string()),
_ => None,
};
}
}
None
}
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_string_concat_hh(a_h: u64, b_h: u64) -> i64 {
events::emit_runtime(serde_json::json!({"id": c::SYM_STRING_CONCAT_HH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]}), "hostcall", "<jit>");
let a = handle_to_string_like(a_h).unwrap_or_default();
let b = handle_to_string_like(b_h).unwrap_or_default();
let s = format!("{}{}", a, b);
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(crate::box_trait::StringBox::new(s));
let h = crate::jit::rt::handles::to_handle(arc);
h as i64
}
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_string_eq_hh(a_h: u64, b_h: u64) -> i64 {
events::emit_runtime(serde_json::json!({"id": c::SYM_STRING_EQ_HH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]}), "hostcall", "<jit>");
let a = handle_to_string_like(a_h).unwrap_or_default();
let b = handle_to_string_like(b_h).unwrap_or_default();
if a == b { 1 } else { 0 }
}
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_string_lt_hh(a_h: u64, b_h: u64) -> i64 {
events::emit_runtime(serde_json::json!({"id": c::SYM_STRING_LT_HH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]}), "hostcall", "<jit>");
let a = handle_to_string_like(a_h).unwrap_or_default();
let b = handle_to_string_like(b_h).unwrap_or_default();
if a < b { 1 } else { 0 }
}