📚 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:
@ -378,6 +378,7 @@ mod tests {
|
||||
emit_cfg: None,
|
||||
jit_only: false,
|
||||
jit_direct: false,
|
||||
cli_verbose: false,
|
||||
};
|
||||
|
||||
assert_eq!(config.backend, "interpreter");
|
||||
|
||||
@ -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"))]
|
||||
|
||||
4
src/jit/extern/collections.rs
vendored
4
src/jit/extern/collections.rs
vendored
@ -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) {
|
||||
|
||||
1
src/jit/extern/mod.rs
vendored
1
src/jit/extern/mod.rs
vendored
@ -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
8
src/jit/extern/runtime.rs
vendored
Normal 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";
|
||||
|
||||
@ -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 });
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 } => {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
|
||||
@ -296,8 +296,10 @@ impl MirBuilder {
|
||||
self.current_function = Some(main_function);
|
||||
self.current_block = Some(entry_block);
|
||||
|
||||
// Add safepoint at function entry
|
||||
self.emit_instruction(MirInstruction::Safepoint)?;
|
||||
// Optional: Add safepoint at function entry (disabled by default)
|
||||
if std::env::var("NYASH_BUILDER_SAFEPOINT_ENTRY").ok().as_deref() == Some("1") {
|
||||
self.emit_instruction(MirInstruction::Safepoint)?;
|
||||
}
|
||||
|
||||
// Convert AST to MIR
|
||||
let result_value = self.build_expression(ast)?;
|
||||
@ -761,7 +763,7 @@ impl MirBuilder {
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
/// Build print statement - converts to console output
|
||||
/// Build print statement - ExternCall to env.console.log (Box哲学準拠)
|
||||
fn build_print_statement(&mut self, expression: ASTNode) -> Result<ValueId, String> {
|
||||
builder_debug_log("enter build_print_statement");
|
||||
// 根治: print(isType(...)) / print(asType(...)) / print(obj.is(...)) / print(obj.as(...)) は必ずTypeOpを先に生成してからprintする
|
||||
@ -776,7 +778,14 @@ impl MirBuilder {
|
||||
let op = if name == "isType" { super::TypeOpKind::Check } else { super::TypeOpKind::Cast };
|
||||
builder_debug_log(&format!("emit TypeOp {:?} value={} dst= {}", op, val, dst));
|
||||
self.emit_instruction(MirInstruction::TypeOp { dst, op, value: val, ty })?;
|
||||
self.emit_instruction(MirInstruction::Print { value: dst, effects: EffectMask::PURE.add(Effect::Io) })?;
|
||||
// console.log(dst)
|
||||
self.emit_instruction(MirInstruction::ExternCall {
|
||||
dst: None,
|
||||
iface_name: "env.console".to_string(),
|
||||
method_name: "log".to_string(),
|
||||
args: vec![dst],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
return Ok(dst);
|
||||
} else {
|
||||
builder_debug_log("extract_string_literal FAIL");
|
||||
@ -792,7 +801,14 @@ impl MirBuilder {
|
||||
let op = if method == "is" { super::TypeOpKind::Check } else { super::TypeOpKind::Cast };
|
||||
builder_debug_log(&format!("emit TypeOp {:?} obj={} dst= {}", op, obj_val, dst));
|
||||
self.emit_instruction(MirInstruction::TypeOp { dst, op, value: obj_val, ty })?;
|
||||
self.emit_instruction(MirInstruction::Print { value: dst, effects: EffectMask::PURE.add(Effect::Io) })?;
|
||||
// console.log(dst)
|
||||
self.emit_instruction(MirInstruction::ExternCall {
|
||||
dst: None,
|
||||
iface_name: "env.console".to_string(),
|
||||
method_name: "log".to_string(),
|
||||
args: vec![dst],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
return Ok(dst);
|
||||
} else {
|
||||
builder_debug_log("extract_string_literal FAIL");
|
||||
@ -804,9 +820,12 @@ impl MirBuilder {
|
||||
let value = self.build_expression(expression)?;
|
||||
builder_debug_log(&format!("fallback print value={}", value));
|
||||
|
||||
// For now, use a special Print instruction (minimal scope)
|
||||
self.emit_instruction(MirInstruction::Print {
|
||||
value,
|
||||
// 統一: env.console.log(value)
|
||||
self.emit_instruction(MirInstruction::ExternCall {
|
||||
dst: None,
|
||||
iface_name: "env.console".to_string(),
|
||||
method_name: "log".to_string(),
|
||||
args: vec![value],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
|
||||
@ -975,6 +994,12 @@ impl MirBuilder {
|
||||
|
||||
/// Build a try/catch statement
|
||||
fn build_try_catch_statement(&mut self, try_body: Vec<ASTNode>, catch_clauses: Vec<crate::ast::CatchClause>, finally_body: Option<Vec<ASTNode>>) -> Result<ValueId, String> {
|
||||
if std::env::var("NYASH_BUILDER_DISABLE_TRYCATCH").ok().as_deref() == Some("1") {
|
||||
// Compatibility fallback: build try body only; ignore handlers/finally
|
||||
let try_ast = ASTNode::Program { statements: try_body, span: crate::ast::Span::unknown() };
|
||||
let result = self.build_expression(try_ast)?;
|
||||
return Ok(result);
|
||||
}
|
||||
let try_block = self.block_gen.next();
|
||||
let catch_block = self.block_gen.next();
|
||||
let finally_block = if finally_body.is_some() { Some(self.block_gen.next()) } else { None };
|
||||
@ -1066,6 +1091,18 @@ impl MirBuilder {
|
||||
|
||||
/// Build a throw statement
|
||||
fn build_throw_statement(&mut self, expression: ASTNode) -> Result<ValueId, String> {
|
||||
if std::env::var("NYASH_BUILDER_DISABLE_THROW").ok().as_deref() == Some("1") {
|
||||
// Fallback: route to debug trace and return the value
|
||||
let v = self.build_expression(expression)?;
|
||||
self.emit_instruction(MirInstruction::ExternCall {
|
||||
dst: None,
|
||||
iface_name: "env.debug".to_string(),
|
||||
method_name: "trace".to_string(),
|
||||
args: vec![v],
|
||||
effects: EffectMask::PURE.add(Effect::Debug),
|
||||
})?;
|
||||
return Ok(v);
|
||||
}
|
||||
let exception_value = self.build_expression(expression)?;
|
||||
|
||||
// Emit throw instruction with PANIC effect (this is a terminator)
|
||||
|
||||
@ -45,8 +45,10 @@ impl MirBuilder {
|
||||
self.current_function = Some(main_function);
|
||||
self.current_block = Some(entry_block);
|
||||
|
||||
// Add safepoint at function entry
|
||||
self.emit_instruction(MirInstruction::Safepoint)?;
|
||||
// Optional: Add safepoint at function entry
|
||||
if std::env::var("NYASH_BUILDER_SAFEPOINT_ENTRY").ok().as_deref() == Some("1") {
|
||||
self.emit_instruction(MirInstruction::Safepoint)?;
|
||||
}
|
||||
|
||||
// Convert AST to MIR
|
||||
let result_value = self.build_expression(ast)?;
|
||||
@ -113,4 +115,4 @@ mod tests {
|
||||
let result = builder.build_module(ast);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +119,9 @@ impl MirBuilder {
|
||||
let exit_block = self.block_gen.next();
|
||||
|
||||
// Set up exception handler for the try block (before we enter it)
|
||||
if let Some(catch_clause) = catch_clauses.first() {
|
||||
if std::env::var("NYASH_BUILDER_DISABLE_TRYCATCH").ok().as_deref() == Some("1") {
|
||||
// Fallback: build try body only
|
||||
} else if let Some(catch_clause) = catch_clauses.first() {
|
||||
let exception_value = self.value_gen.next();
|
||||
|
||||
// Register catch handler for exceptions that may occur in try block
|
||||
@ -152,6 +154,7 @@ impl MirBuilder {
|
||||
self.start_new_block(catch_block)?;
|
||||
|
||||
// Handle catch clause
|
||||
if std::env::var("NYASH_BUILDER_DISABLE_TRYCATCH").ok().as_deref() != Some("1") {
|
||||
if let Some(catch_clause) = catch_clauses.first() {
|
||||
// Build catch body
|
||||
let catch_ast = ASTNode::Program {
|
||||
@ -159,7 +162,7 @@ impl MirBuilder {
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
self.build_expression(catch_ast)?;
|
||||
}
|
||||
}}
|
||||
|
||||
// Catch completion - jump to finally or exit (if not already terminated)
|
||||
if !self.is_current_block_terminated() {
|
||||
|
||||
@ -95,27 +95,59 @@ impl MirBuilder {
|
||||
}
|
||||
|
||||
pub(super) fn emit_weak_new(&mut self, box_val: ValueId) -> Result<ValueId, String> {
|
||||
// Legacy emission toggle (default OFF): NYASH_BUILDER_LEGACY_WEAK=1
|
||||
let legacy = std::env::var("NYASH_BUILDER_LEGACY_WEAK").ok().as_deref() == Some("1");
|
||||
let dst = self.value_gen.next();
|
||||
let instruction = MirInstruction::WeakNew { dst, box_val };
|
||||
let instruction = if legacy {
|
||||
if builder_debug_enabled() { eprintln!("[BUILDER] emit WeakNew (legacy on)"); }
|
||||
MirInstruction::WeakNew { dst, box_val }
|
||||
} else {
|
||||
if builder_debug_enabled() { eprintln!("[BUILDER] emit WeakRef(New)"); }
|
||||
MirInstruction::WeakRef { dst, op: super::super::WeakRefOp::New, value: box_val }
|
||||
};
|
||||
self.emit_instruction(instruction)?;
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
pub(super) fn emit_weak_load(&mut self, weak_ref: ValueId) -> Result<ValueId, String> {
|
||||
// Legacy emission toggle (default OFF): NYASH_BUILDER_LEGACY_WEAK=1
|
||||
let legacy = std::env::var("NYASH_BUILDER_LEGACY_WEAK").ok().as_deref() == Some("1");
|
||||
let dst = self.value_gen.next();
|
||||
let instruction = MirInstruction::WeakLoad { dst, weak_ref };
|
||||
let instruction = if legacy {
|
||||
if builder_debug_enabled() { eprintln!("[BUILDER] emit WeakLoad (legacy on)"); }
|
||||
MirInstruction::WeakLoad { dst, weak_ref }
|
||||
} else {
|
||||
if builder_debug_enabled() { eprintln!("[BUILDER] emit WeakRef(Load)"); }
|
||||
MirInstruction::WeakRef { dst, op: super::super::WeakRefOp::Load, value: weak_ref }
|
||||
};
|
||||
self.emit_instruction(instruction)?;
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
pub(super) fn emit_barrier_read(&mut self, ptr: ValueId) -> Result<(), String> {
|
||||
let instruction = MirInstruction::BarrierRead { ptr };
|
||||
// Legacy emission toggle (default OFF): NYASH_BUILDER_LEGACY_BARRIER=1
|
||||
let legacy = std::env::var("NYASH_BUILDER_LEGACY_BARRIER").ok().as_deref() == Some("1");
|
||||
let instruction = if legacy {
|
||||
if builder_debug_enabled() { eprintln!("[BUILDER] emit BarrierRead (legacy on)"); }
|
||||
MirInstruction::BarrierRead { ptr }
|
||||
} else {
|
||||
if builder_debug_enabled() { eprintln!("[BUILDER] emit Barrier(Read)"); }
|
||||
MirInstruction::Barrier { op: super::super::BarrierOp::Read, ptr }
|
||||
};
|
||||
self.emit_instruction(instruction)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn emit_barrier_write(&mut self, ptr: ValueId) -> Result<(), String> {
|
||||
let instruction = MirInstruction::BarrierWrite { ptr };
|
||||
// Legacy emission toggle (default OFF): NYASH_BUILDER_LEGACY_BARRIER=1
|
||||
let legacy = std::env::var("NYASH_BUILDER_LEGACY_BARRIER").ok().as_deref() == Some("1");
|
||||
let instruction = if legacy {
|
||||
if builder_debug_enabled() { eprintln!("[BUILDER] emit BarrierWrite (legacy on)"); }
|
||||
MirInstruction::BarrierWrite { ptr }
|
||||
} else {
|
||||
if builder_debug_enabled() { eprintln!("[BUILDER] emit Barrier(Write)"); }
|
||||
MirInstruction::Barrier { op: super::super::BarrierOp::Write, ptr }
|
||||
};
|
||||
self.emit_instruction(instruction)?;
|
||||
Ok(())
|
||||
}
|
||||
@ -189,8 +221,10 @@ impl MirBuilder {
|
||||
self.current_function = Some(main_function);
|
||||
self.current_block = Some(entry_block);
|
||||
|
||||
// Entry safepoint
|
||||
self.emit_instruction(MirInstruction::Safepoint)?;
|
||||
// Optional entry safepoint
|
||||
if std::env::var("NYASH_BUILDER_SAFEPOINT_ENTRY").ok().as_deref() == Some("1") {
|
||||
self.emit_instruction(MirInstruction::Safepoint)?;
|
||||
}
|
||||
|
||||
// Lower AST to MIR
|
||||
let result_value = self.build_expression(ast)?;
|
||||
|
||||
@ -25,7 +25,7 @@ impl MirBuilder {
|
||||
let op = if name == "isType" { TypeOpKind::Check } else { TypeOpKind::Cast };
|
||||
builder_debug_log(&format!("emit TypeOp {:?} value={} dst= {}", op, val, dst));
|
||||
self.emit_instruction(MirInstruction::TypeOp { dst, op, value: val, ty })?;
|
||||
self.emit_instruction(MirInstruction::Print { value: dst, effects: EffectMask::PURE.add(Effect::Io) })?;
|
||||
self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![dst], effects: EffectMask::PURE.add(Effect::Io) })?;
|
||||
return Ok(dst);
|
||||
} else {
|
||||
builder_debug_log("extract_string_literal FAIL");
|
||||
@ -41,7 +41,7 @@ impl MirBuilder {
|
||||
let op = if method == "is" { TypeOpKind::Check } else { TypeOpKind::Cast };
|
||||
builder_debug_log(&format!("emit TypeOp {:?} obj={} dst= {}", op, obj_val, dst));
|
||||
self.emit_instruction(MirInstruction::TypeOp { dst, op, value: obj_val, ty })?;
|
||||
self.emit_instruction(MirInstruction::Print { value: dst, effects: EffectMask::PURE.add(Effect::Io) })?;
|
||||
self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![dst], effects: EffectMask::PURE.add(Effect::Io) })?;
|
||||
return Ok(dst);
|
||||
} else {
|
||||
builder_debug_log("extract_string_literal FAIL");
|
||||
@ -53,11 +53,7 @@ impl MirBuilder {
|
||||
let value = self.build_expression(expression)?;
|
||||
builder_debug_log(&format!("fallback print value={}", value));
|
||||
|
||||
// For now, use a special Print instruction (minimal scope)
|
||||
self.emit_instruction(MirInstruction::Print {
|
||||
value,
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![value], effects: EffectMask::PURE.add(Effect::Io) })?;
|
||||
|
||||
// Return the value that was printed
|
||||
Ok(value)
|
||||
@ -134,16 +130,13 @@ impl MirBuilder {
|
||||
|
||||
/// Build a throw statement
|
||||
pub(super) fn build_throw_statement(&mut self, expression: ASTNode) -> Result<ValueId, String> {
|
||||
if std::env::var("NYASH_BUILDER_DISABLE_THROW").ok().as_deref() == Some("1") {
|
||||
let v = self.build_expression(expression)?;
|
||||
self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.debug".to_string(), method_name: "trace".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Debug) })?;
|
||||
return Ok(v);
|
||||
}
|
||||
let exception_value = self.build_expression(expression)?;
|
||||
|
||||
// Emit throw instruction with PANIC effect (this is a terminator)
|
||||
self.emit_instruction(MirInstruction::Throw {
|
||||
exception: exception_value,
|
||||
effects: EffectMask::PANIC,
|
||||
})?;
|
||||
|
||||
// Throw doesn't return normally, but we need to return a value for the type system
|
||||
// We can't add more instructions after throw, so just return the exception value
|
||||
self.emit_instruction(MirInstruction::Throw { exception: exception_value, effects: EffectMask::PANIC })?;
|
||||
Ok(exception_value)
|
||||
}
|
||||
|
||||
|
||||
@ -81,7 +81,10 @@ impl<'a> LoopBuilder<'a> {
|
||||
|
||||
// 7. ループボディの構築
|
||||
self.set_current_block(body_id)?;
|
||||
self.emit_safepoint()?;
|
||||
// Optional safepoint per loop-iteration
|
||||
if std::env::var("NYASH_BUILDER_SAFEPOINT_LOOP").ok().as_deref() == Some("1") {
|
||||
self.emit_safepoint()?;
|
||||
}
|
||||
|
||||
// ボディをビルド
|
||||
for stmt in body {
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
* - Dead code elimination
|
||||
*/
|
||||
|
||||
use super::{MirModule, MirFunction, MirInstruction, ValueId, MirType, TypeOpKind};
|
||||
use super::{MirModule, MirFunction, MirInstruction, ValueId, MirType, TypeOpKind, EffectMask, Effect};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
/// MIR optimization passes
|
||||
@ -367,9 +367,13 @@ impl MirOptimizer {
|
||||
/// - TypeCheck/Cast → TypeOp(Check/Cast)
|
||||
/// - WeakNew/WeakLoad → WeakRef(New/Load)
|
||||
/// - BarrierRead/BarrierWrite → Barrier(Read/Write)
|
||||
/// - Print → ExternCall(env.console.log)
|
||||
fn normalize_legacy_instructions(&mut self, module: &mut MirModule) -> OptimizationStats {
|
||||
use super::{TypeOpKind, WeakRefOp, BarrierOp, MirInstruction as I, MirType};
|
||||
let mut stats = OptimizationStats::new();
|
||||
let rw_dbg = std::env::var("NYASH_REWRITE_DEBUG").ok().as_deref() == Some("1");
|
||||
let rw_sp = std::env::var("NYASH_REWRITE_SAFEPOINT").ok().as_deref() == Some("1");
|
||||
let rw_future = std::env::var("NYASH_REWRITE_FUTURE").ok().as_deref() == Some("1");
|
||||
for (_fname, function) in &mut module.functions {
|
||||
for (_bb, block) in &mut function.blocks {
|
||||
// Rewrite in-place for normal instructions
|
||||
@ -400,6 +404,30 @@ impl MirOptimizer {
|
||||
let val = *ptr;
|
||||
*inst = I::Barrier { op: BarrierOp::Write, ptr: val };
|
||||
}
|
||||
I::Print { value, .. } => {
|
||||
let v = *value;
|
||||
*inst = I::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Io) };
|
||||
}
|
||||
I::Debug { value, .. } if rw_dbg => {
|
||||
let v = *value;
|
||||
*inst = I::ExternCall { dst: None, iface_name: "env.debug".to_string(), method_name: "trace".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Debug) };
|
||||
}
|
||||
I::Safepoint if rw_sp => {
|
||||
*inst = I::ExternCall { dst: None, iface_name: "env.runtime".to_string(), method_name: "checkpoint".to_string(), args: vec![], effects: EffectMask::PURE };
|
||||
}
|
||||
// Future/Await の段階移行: ExternCall(env.future.*) に書き換え(トグル)
|
||||
I::FutureNew { dst, value } if rw_future => {
|
||||
let d = *dst; let v = *value;
|
||||
*inst = I::ExternCall { dst: Some(d), iface_name: "env.future".to_string(), method_name: "new".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Io) };
|
||||
}
|
||||
I::FutureSet { future, value } if rw_future => {
|
||||
let f = *future; let v = *value;
|
||||
*inst = I::ExternCall { dst: None, iface_name: "env.future".to_string(), method_name: "set".to_string(), args: vec![f, v], effects: EffectMask::PURE.add(Effect::Io) };
|
||||
}
|
||||
I::Await { dst, future } if rw_future => {
|
||||
let d = *dst; let f = *future;
|
||||
*inst = I::ExternCall { dst: Some(d), iface_name: "env.future".to_string(), method_name: "await".to_string(), args: vec![f], effects: EffectMask::PURE.add(Effect::Io) };
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -430,6 +458,30 @@ impl MirOptimizer {
|
||||
let val = *ptr;
|
||||
*term = I::Barrier { op: BarrierOp::Write, ptr: val };
|
||||
}
|
||||
I::Print { value, .. } => {
|
||||
let v = *value;
|
||||
*term = I::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Io) };
|
||||
}
|
||||
I::Debug { value, .. } if rw_dbg => {
|
||||
let v = *value;
|
||||
*term = I::ExternCall { dst: None, iface_name: "env.debug".to_string(), method_name: "trace".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Debug) };
|
||||
}
|
||||
I::Safepoint if rw_sp => {
|
||||
*term = I::ExternCall { dst: None, iface_name: "env.runtime".to_string(), method_name: "checkpoint".to_string(), args: vec![], effects: EffectMask::PURE };
|
||||
}
|
||||
// Future/Await (終端側)
|
||||
I::FutureNew { dst, value } if rw_future => {
|
||||
let d = *dst; let v = *value;
|
||||
*term = I::ExternCall { dst: Some(d), iface_name: "env.future".to_string(), method_name: "new".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Io) };
|
||||
}
|
||||
I::FutureSet { future, value } if rw_future => {
|
||||
let f = *future; let v = *value;
|
||||
*term = I::ExternCall { dst: None, iface_name: "env.future".to_string(), method_name: "set".to_string(), args: vec![f, v], effects: EffectMask::PURE.add(Effect::Io) };
|
||||
}
|
||||
I::Await { dst, future } if rw_future => {
|
||||
let d = *dst; let f = *future;
|
||||
*term = I::ExternCall { dst: Some(d), iface_name: "env.future".to_string(), method_name: "await".to_string(), args: vec![f], effects: EffectMask::PURE.add(Effect::Io) };
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -682,8 +734,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dce_does_not_drop_typeop_used_by_print() {
|
||||
// Build a simple function: %v=TypeOp(check); print %v; ensure TypeOp remains after optimize
|
||||
fn test_dce_does_not_drop_typeop_used_by_console_log() {
|
||||
// Build: %v=TypeOp(check); extern_call env.console.log(%v); ensure TypeOp remains after optimize
|
||||
let signature = FunctionSignature {
|
||||
name: "main".to_string(),
|
||||
params: vec![],
|
||||
@ -697,7 +749,7 @@ mod tests {
|
||||
let v1 = ValueId::new(1);
|
||||
b0.add_instruction(MirInstruction::NewBox { dst: v0, box_type: "IntegerBox".to_string(), args: vec![] });
|
||||
b0.add_instruction(MirInstruction::TypeOp { dst: v1, op: TypeOpKind::Check, value: v0, ty: MirType::Integer });
|
||||
b0.add_instruction(MirInstruction::Print { value: v1, effects: super::super::effect::EffectMask::IO });
|
||||
b0.add_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![v1], effects: super::super::effect::EffectMask::IO });
|
||||
b0.add_instruction(MirInstruction::Return { value: None });
|
||||
func.add_block(b0);
|
||||
let mut module = MirModule::new("test".to_string());
|
||||
@ -710,6 +762,6 @@ mod tests {
|
||||
let f = module.get_function("main").unwrap();
|
||||
let block = f.get_block(bb0).unwrap();
|
||||
let has_typeop = block.all_instructions().any(|i| matches!(i, MirInstruction::TypeOp { .. }));
|
||||
assert!(has_typeop, "TypeOp should not be dropped by DCE when used by print");
|
||||
assert!(has_typeop, "TypeOp should not be dropped by DCE when used by console.log (ExternCall)");
|
||||
}
|
||||
}
|
||||
|
||||
104
src/mir/passes/escape.rs
Normal file
104
src/mir/passes/escape.rs
Normal file
@ -0,0 +1,104 @@
|
||||
//! Escape Analysis (VM-only footing)
|
||||
//! Conservative analysis to elide write/read barriers for definitely non-escaping boxes.
|
||||
//! Enabled for VM backend as a staging step before LLVM.
|
||||
|
||||
use crate::mir::{MirModule, MirFunction, MirInstruction, ValueId};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
/// Run a conservative escape analysis and remove Barrier(Read/Write) for non-escaping boxes.
|
||||
/// Returns the number of barriers removed.
|
||||
pub fn escape_elide_barriers_vm(module: &mut MirModule) -> usize {
|
||||
let mut removed = 0usize;
|
||||
let mut analysis: HashMap<String, EscapeInfo> = HashMap::new();
|
||||
|
||||
// 1) Analyze each function
|
||||
for (name, func) in module.functions.iter() {
|
||||
analysis.insert(name.clone(), analyze_function(func));
|
||||
}
|
||||
|
||||
// 2) Apply in-place edits per function
|
||||
for (name, info) in analysis.into_iter() {
|
||||
if let Some(func) = module.functions.get_mut(&name) {
|
||||
removed += elide_barriers_in_function(func, &info);
|
||||
}
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct EscapeInfo {
|
||||
local_boxes: HashSet<ValueId>,
|
||||
escaping: HashSet<ValueId>,
|
||||
}
|
||||
|
||||
impl EscapeInfo {
|
||||
fn is_non_escaping(&self, v: &ValueId) -> bool { self.local_boxes.contains(v) && !self.escaping.contains(v) }
|
||||
}
|
||||
|
||||
fn analyze_function(func: &MirFunction) -> EscapeInfo {
|
||||
let mut info = EscapeInfo::default();
|
||||
// Collect local boxes: results of NewBox in this function
|
||||
for block in func.blocks.values() {
|
||||
for ins in block.instructions.iter() {
|
||||
if let MirInstruction::NewBox { dst, .. } = ins { info.local_boxes.insert(*dst); }
|
||||
}
|
||||
if let Some(term) = &block.terminator {
|
||||
if let MirInstruction::NewBox { dst, .. } = term { info.local_boxes.insert(*dst); }
|
||||
}
|
||||
}
|
||||
// Conservative escape marking
|
||||
for block in func.blocks.values() {
|
||||
for ins in block.all_instructions() {
|
||||
match ins {
|
||||
MirInstruction::Return { value: Some(v) } => { if info.local_boxes.contains(v) { info.escaping.insert(*v); } }
|
||||
MirInstruction::Call { args, .. }
|
||||
| MirInstruction::BoxCall { args, .. }
|
||||
| MirInstruction::ExternCall { args, .. }
|
||||
| MirInstruction::PluginInvoke { args, .. } => {
|
||||
for a in args { if info.local_boxes.contains(a) { info.escaping.insert(*a); } }
|
||||
}
|
||||
MirInstruction::Store { value, .. } => {
|
||||
if info.local_boxes.contains(value) { info.escaping.insert(*value); }
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
info
|
||||
}
|
||||
|
||||
fn elide_barriers_in_function(func: &mut MirFunction, info: &EscapeInfo) -> usize {
|
||||
let mut removed = 0usize;
|
||||
for block in func.blocks.values_mut() {
|
||||
for ins in block.instructions.iter_mut() {
|
||||
match ins {
|
||||
MirInstruction::Barrier { ptr, .. }
|
||||
| MirInstruction::BarrierRead { ptr }
|
||||
| MirInstruction::BarrierWrite { ptr } => {
|
||||
if info.is_non_escaping(ptr) {
|
||||
// Replace with Nop (keeps indices stable; verifier tolerates Nop)
|
||||
*ins = MirInstruction::Nop;
|
||||
removed += 1;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(term) = &mut block.terminator {
|
||||
match term {
|
||||
MirInstruction::Barrier { ptr, .. }
|
||||
| MirInstruction::BarrierRead { ptr }
|
||||
| MirInstruction::BarrierWrite { ptr } => {
|
||||
if info.is_non_escaping(ptr) {
|
||||
*term = MirInstruction::Nop;
|
||||
removed += 1;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if removed > 0 { func.update_cfg(); }
|
||||
removed
|
||||
}
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
// Minimal scaffold to unblock builds when type hint propagation is not yet implemented.
|
||||
|
||||
pub mod type_hints;
|
||||
|
||||
pub mod escape;
|
||||
|
||||
@ -1086,6 +1086,25 @@ mod tests {
|
||||
iterations: 10,
|
||||
vm_stats: false,
|
||||
vm_stats_json: false,
|
||||
// JIT defaults for test
|
||||
jit_exec: false,
|
||||
jit_stats: false,
|
||||
jit_stats_json: false,
|
||||
jit_dump: false,
|
||||
jit_events: false,
|
||||
jit_events_compile: false,
|
||||
jit_events_runtime: false,
|
||||
jit_events_path: None,
|
||||
jit_threshold: None,
|
||||
jit_phi_min: false,
|
||||
jit_hostcall: false,
|
||||
jit_handle_debug: false,
|
||||
jit_native_f64: false,
|
||||
jit_native_bool: false,
|
||||
emit_cfg: None,
|
||||
jit_only: false,
|
||||
jit_direct: false,
|
||||
cli_verbose: false,
|
||||
};
|
||||
|
||||
let runner = NyashRunner::new(config);
|
||||
|
||||
@ -55,9 +55,18 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: VM-only escape analysis to elide barriers before execution
|
||||
let mut module_vm = compile_result.module.clone();
|
||||
if std::env::var("NYASH_VM_ESCAPE_ANALYSIS").ok().as_deref() == Some("1") {
|
||||
let removed = nyash_rust::mir::passes::escape::escape_elide_barriers_vm(&mut module_vm);
|
||||
if removed > 0 && std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[VM] escape_elide_barriers: removed {} barriers", removed);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute with VM using prepared runtime
|
||||
let mut vm = VM::with_runtime(runtime);
|
||||
match vm.execute_module(&compile_result.module) {
|
||||
match vm.execute_module(&module_vm) {
|
||||
Ok(result) => {
|
||||
println!("✅ VM execution completed successfully!");
|
||||
// Pretty-print using MIR return type when available to avoid Void-looking floats/bools
|
||||
|
||||
@ -468,6 +468,50 @@ impl PluginLoaderV2 {
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
("env.debug", "trace") => {
|
||||
// Minimal debug trace; prints to stderr when enabled
|
||||
if std::env::var("NYASH_DEBUG_TRACE").ok().as_deref() == Some("1") {
|
||||
for a in args { eprintln!("[debug.trace] {}", a.to_string_box().value); }
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
("env.runtime", "checkpoint") => {
|
||||
// Minimal safepoint checkpoint stub (no-op)
|
||||
if std::env::var("NYASH_RUNTIME_CHECKPOINT_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[runtime.checkpoint] reached");
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
// Future/Await bridge (scaffold): maps MIR Future* to Box operations
|
||||
("env.future", "new") => {
|
||||
// new(value) -> FutureBox(set to value)
|
||||
let fut = crate::boxes::future::FutureBox::new();
|
||||
if let Some(v) = args.get(0) { fut.set_result(v.clone_box()); }
|
||||
Ok(Some(Box::new(fut)))
|
||||
}
|
||||
("env.future", "set") => {
|
||||
// set(future, value)
|
||||
if args.len() >= 2 {
|
||||
if let Some(fut) = args[0].as_any().downcast_ref::<crate::boxes::future::FutureBox>() {
|
||||
fut.set_result(args[1].clone_box());
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
("env.future", "await") => {
|
||||
// await(future) -> value (pass-through if not a FutureBox)
|
||||
if let Some(arg) = args.get(0) {
|
||||
if let Some(fut) = arg.as_any().downcast_ref::<crate::boxes::future::FutureBox>() {
|
||||
match fut.wait_and_get() { Ok(v) => return Ok(Some(v)), Err(e) => {
|
||||
eprintln!("[env.future.await] error: {}", e);
|
||||
return Ok(None);
|
||||
} }
|
||||
} else {
|
||||
return Ok(Some(arg.clone_box()));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
("env.canvas", _) => {
|
||||
eprintln!("[env.canvas] {} invoked (stub)", method_name);
|
||||
Ok(None)
|
||||
|
||||
@ -27,8 +27,8 @@ mod tests {
|
||||
let v1 = func.next_value_id();
|
||||
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::TypeOp { dst: v1, op: crate::mir::TypeOpKind::Check, value: v0, ty: MirType::Integer });
|
||||
|
||||
// Print result (should be true)
|
||||
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Print { value: v1, effects: EffectMask::IO });
|
||||
// console.log(result) via ExternCall
|
||||
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![v1], effects: EffectMask::IO });
|
||||
|
||||
// Cast (no-op for PoC semantics)
|
||||
let v2 = func.next_value_id();
|
||||
@ -109,7 +109,7 @@ mod tests {
|
||||
|
||||
let v1 = func.next_value_id();
|
||||
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::TypeCheck { dst: v1, value: v0, expected_type: "IntegerBox".to_string() });
|
||||
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Print { value: v1, effects: EffectMask::IO });
|
||||
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![v1], effects: EffectMask::IO });
|
||||
|
||||
let v2 = func.next_value_id();
|
||||
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Cast { dst: v2, value: v0, target_type: MirType::Integer });
|
||||
@ -141,8 +141,8 @@ mod tests {
|
||||
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Barrier { op: crate::mir::BarrierOp::Read, ptr: v2 });
|
||||
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Barrier { op: crate::mir::BarrierOp::Write, ptr: v2 });
|
||||
|
||||
// Print loaded value
|
||||
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Print { value: v2, effects: EffectMask::IO });
|
||||
// Print loaded value via env.console.log
|
||||
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![v2], effects: EffectMask::IO });
|
||||
|
||||
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: None });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user