Phase 10_6b scheduler complete; 10_4 GC hooks + counting/strict tracing; 10_c minimal JIT path (i64/bool consts, binop/compare/return, hostcall opt-in); docs & examples; add Phase 10.7 roadmap (JIT branch wiring + minimal ABI).
This commit is contained in:
@ -12,6 +12,10 @@ pub enum CmpKind { Eq, Ne, Lt, Le, Gt, Ge }
|
||||
pub trait IRBuilder {
|
||||
fn begin_function(&mut self, name: &str);
|
||||
fn end_function(&mut self);
|
||||
/// Optional: prepare a simple `i64` ABI signature with `argc` params
|
||||
fn prepare_signature_i64(&mut self, _argc: usize, _has_ret: bool) { }
|
||||
/// Load i64 parameter at index and push to value stack (Core-1 path)
|
||||
fn emit_param_i64(&mut self, _index: usize) { }
|
||||
fn emit_const_i64(&mut self, _val: i64);
|
||||
fn emit_const_f64(&mut self, _val: f64);
|
||||
fn emit_binop(&mut self, _op: BinOpKind);
|
||||
@ -38,6 +42,7 @@ impl NoopBuilder {
|
||||
impl IRBuilder for NoopBuilder {
|
||||
fn begin_function(&mut self, _name: &str) {}
|
||||
fn end_function(&mut self) {}
|
||||
fn emit_param_i64(&mut self, _index: usize) { self.consts += 1; }
|
||||
fn emit_const_i64(&mut self, _val: i64) { self.consts += 1; }
|
||||
fn emit_const_f64(&mut self, _val: f64) { self.consts += 1; }
|
||||
fn emit_binop(&mut self, _op: BinOpKind) { self.binops += 1; }
|
||||
@ -59,6 +64,9 @@ pub struct CraneliftBuilder {
|
||||
entry_block: Option<cranelift_codegen::ir::Block>,
|
||||
// Finalized function pointer (if any)
|
||||
compiled_closure: Option<std::sync::Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>>,
|
||||
// Desired simple ABI (Phase 10_c minimal): i64 params count and i64 return
|
||||
desired_argc: usize,
|
||||
desired_has_ret: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
@ -66,8 +74,99 @@ use cranelift_module::Module;
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
use cranelift_codegen::ir::InstBuilder;
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_host_stub0() -> i64 { 0 }
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_array_len(arr_param_index: i64) -> i64 {
|
||||
// Interpret first arg as function param index and fetch from thread-local args
|
||||
if arr_param_index < 0 { return 0; }
|
||||
crate::jit::rt::with_args(|args| {
|
||||
let idx = arr_param_index as usize;
|
||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
if let Some(ib) = ab.length().as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
||||
return ib.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
})
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_array_push(arr_param_index: i64, val: i64) -> i64 {
|
||||
if arr_param_index < 0 { return 0; }
|
||||
crate::jit::rt::with_args(|args| {
|
||||
let idx = arr_param_index as usize;
|
||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
// Push integer value only (PoC)
|
||||
let ib = crate::box_trait::IntegerBox::new(val);
|
||||
let _ = ab.push(Box::new(ib));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
0
|
||||
})
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_array_get(arr_param_index: i64, idx: i64) -> i64 {
|
||||
if arr_param_index < 0 { return 0; }
|
||||
crate::jit::rt::with_args(|args| {
|
||||
let pidx = arr_param_index as usize;
|
||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(pidx) {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
let val = ab.get(Box::new(crate::box_trait::IntegerBox::new(idx)));
|
||||
if let Some(ib) = val.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
||||
return ib.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
})
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_array_set(arr_param_index: i64, idx: i64, val: i64) -> i64 {
|
||||
if arr_param_index < 0 { return 0; }
|
||||
crate::jit::rt::with_args(|args| {
|
||||
let pidx = arr_param_index as usize;
|
||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(pidx) {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
let _ = ab.set(
|
||||
Box::new(crate::box_trait::IntegerBox::new(idx)),
|
||||
Box::new(crate::box_trait::IntegerBox::new(val)),
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
0
|
||||
})
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_map_get(_map: u64, _key: i64) -> i64 { 0 }
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_map_set(_map: u64, _key: i64, _val: i64) -> i64 { 0 }
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_map_size(map_param_index: i64) -> i64 {
|
||||
if map_param_index < 0 { return 0; }
|
||||
crate::jit::rt::with_args(|args| {
|
||||
let idx = map_param_index as usize;
|
||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
|
||||
if let Some(mb) = b.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||
if let Some(ib) = mb.size().as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
||||
return ib.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
impl IRBuilder for CraneliftBuilder {
|
||||
fn prepare_signature_i64(&mut self, argc: usize, has_ret: bool) {
|
||||
self.desired_argc = argc;
|
||||
self.desired_has_ret = has_ret;
|
||||
}
|
||||
fn begin_function(&mut self, name: &str) {
|
||||
use cranelift_codegen::ir::{AbiParam, Signature, types};
|
||||
use cranelift_frontend::FunctionBuilder;
|
||||
@ -76,10 +175,11 @@ impl IRBuilder for CraneliftBuilder {
|
||||
self.value_stack.clear();
|
||||
self.entry_block = None;
|
||||
|
||||
// Minimal signature: () -> i64 (Core-1 integer path)
|
||||
// Minimal signature: (i64 x argc) -> i64? (Core-1 integer path)
|
||||
let call_conv = self.module.isa().default_call_conv();
|
||||
let mut sig = Signature::new(call_conv);
|
||||
sig.returns.push(AbiParam::new(types::I64));
|
||||
for _ in 0..self.desired_argc { sig.params.push(AbiParam::new(types::I64)); }
|
||||
if self.desired_has_ret { sig.returns.push(AbiParam::new(types::I64)); }
|
||||
self.ctx.func.signature = sig;
|
||||
self.ctx.func.name = cranelift_codegen::ir::UserFuncName::user(0, 0);
|
||||
|
||||
@ -116,7 +216,7 @@ impl IRBuilder for CraneliftBuilder {
|
||||
// Get finalized code pointer and wrap into a safe closure
|
||||
let code = self.module.get_finalized_function(func_id);
|
||||
|
||||
// SAFETY: We compiled a function with signature () -> i64
|
||||
// SAFETY: We compiled a function with simple i64 ABI; we still call without args for now
|
||||
unsafe {
|
||||
let f: extern "C" fn() -> i64 = std::mem::transmute(code);
|
||||
let closure = std::sync::Arc::new(move |_args: &[crate::backend::vm::VMValue]| -> crate::backend::vm::VMValue {
|
||||
@ -200,14 +300,83 @@ impl IRBuilder for CraneliftBuilder {
|
||||
}
|
||||
fb.finalize();
|
||||
}
|
||||
|
||||
fn emit_host_call(&mut self, symbol: &str, _argc: usize, has_ret: bool) {
|
||||
use cranelift_codegen::ir::{AbiParam, Signature, types};
|
||||
use cranelift_frontend::FunctionBuilder;
|
||||
use cranelift_module::{Linkage, Module};
|
||||
|
||||
// Minimal import+call to a registered stub symbol; ignore args for now
|
||||
let call_conv = self.module.isa().default_call_conv();
|
||||
let mut sig = Signature::new(call_conv);
|
||||
// Collect up to _argc i64 values from stack as arguments (right-to-left)
|
||||
let mut args: Vec<cranelift_codegen::ir::Value> = Vec::new();
|
||||
let take_n = _argc.min(self.value_stack.len());
|
||||
for _ in 0..take_n { if let Some(v) = self.value_stack.pop() { args.push(v); } }
|
||||
args.reverse();
|
||||
// Build params for each collected arg
|
||||
for _ in 0..args.len() { sig.params.push(AbiParam::new(types::I64)); }
|
||||
if has_ret { sig.returns.push(AbiParam::new(types::I64)); }
|
||||
|
||||
let func_id = self.module
|
||||
.declare_function(symbol, Linkage::Import, &sig)
|
||||
.expect("declare import failed");
|
||||
|
||||
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||
if let Some(b) = self.entry_block { fb.switch_to_block(b); }
|
||||
let fref = self.module.declare_func_in_func(func_id, fb.func);
|
||||
let call_inst = fb.ins().call(fref, &args);
|
||||
if has_ret {
|
||||
let results = fb.inst_results(call_inst).to_vec();
|
||||
if let Some(v) = results.get(0).copied() {
|
||||
self.value_stack.push(v);
|
||||
}
|
||||
}
|
||||
fb.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
impl CraneliftBuilder {
|
||||
fn entry_param(&mut self, index: usize) -> Option<cranelift_codegen::ir::Value> {
|
||||
use cranelift_frontend::FunctionBuilder;
|
||||
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||
if let Some(b) = self.entry_block {
|
||||
fb.switch_to_block(b);
|
||||
let params = fb.func.dfg.block_params(b).to_vec();
|
||||
if let Some(v) = params.get(index).copied() { return Some(v); }
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
impl IRBuilder for CraneliftBuilder {
|
||||
fn emit_param_i64(&mut self, index: usize) {
|
||||
if let Some(v) = self.entry_param(index) {
|
||||
self.value_stack.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
impl CraneliftBuilder {
|
||||
pub fn new() -> Self {
|
||||
// Initialize a minimal JITModule to validate linking; not used yet
|
||||
let builder = cranelift_jit::JITBuilder::new(cranelift_module::default_libcall_names())
|
||||
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);
|
||||
{
|
||||
use crate::jit::r#extern::collections as c;
|
||||
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);
|
||||
builder.symbol(c::SYM_ARRAY_PUSH, nyash_array_push as *const u8);
|
||||
builder.symbol(c::SYM_MAP_GET, nyash_map_get as *const u8);
|
||||
builder.symbol(c::SYM_MAP_SET, nyash_map_set as *const u8);
|
||||
builder.symbol(c::SYM_MAP_SIZE, nyash_map_size as *const u8);
|
||||
}
|
||||
let module = cranelift_jit::JITModule::new(builder);
|
||||
let ctx = cranelift_codegen::Context::new();
|
||||
let fbc = cranelift_frontend::FunctionBuilderContext::new();
|
||||
@ -218,6 +387,8 @@ impl CraneliftBuilder {
|
||||
value_stack: Vec::new(),
|
||||
entry_block: None,
|
||||
compiled_closure: None,
|
||||
desired_argc: 0,
|
||||
desired_has_ret: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::mir::{MirFunction, MirInstruction, ConstValue, BinaryOp, CompareOp};
|
||||
use crate::mir::{MirFunction, MirInstruction, ConstValue, BinaryOp, CompareOp, ValueId};
|
||||
use super::builder::{IRBuilder, BinOpKind, CmpKind};
|
||||
|
||||
/// Lower(Core-1): Minimal lowering skeleton for Const/Move/BinOp/Cmp/Branch/Ret
|
||||
@ -6,14 +6,25 @@ use super::builder::{IRBuilder, BinOpKind, CmpKind};
|
||||
pub struct LowerCore {
|
||||
pub unsupported: usize,
|
||||
pub covered: usize,
|
||||
/// Minimal constant propagation for i64 to feed host-call args
|
||||
known_i64: std::collections::HashMap<ValueId, i64>,
|
||||
/// Parameter index mapping for ValueId
|
||||
param_index: std::collections::HashMap<ValueId, usize>,
|
||||
}
|
||||
|
||||
impl LowerCore {
|
||||
pub fn new() -> Self { Self { unsupported: 0, covered: 0 } }
|
||||
pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), param_index: std::collections::HashMap::new() } }
|
||||
|
||||
/// 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
|
||||
// Build param index map
|
||||
self.param_index.clear();
|
||||
for (i, v) in func.params.iter().copied().enumerate() {
|
||||
self.param_index.insert(v, i);
|
||||
}
|
||||
builder.prepare_signature_i64(func.params.len(), true);
|
||||
builder.begin_function(&func.signature.name);
|
||||
for (_bb_id, bb) in func.blocks.iter() {
|
||||
for instr in bb.instructions.iter() {
|
||||
@ -29,6 +40,17 @@ impl LowerCore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Push a value onto the builder stack if it is a known i64 const or a parameter.
|
||||
fn push_value_if_known_or_param(&self, b: &mut dyn IRBuilder, id: &ValueId) {
|
||||
if let Some(pidx) = self.param_index.get(id).copied() {
|
||||
b.emit_param_i64(pidx);
|
||||
return;
|
||||
}
|
||||
if let Some(v) = self.known_i64.get(id).copied() {
|
||||
b.emit_const_i64(v);
|
||||
}
|
||||
}
|
||||
|
||||
fn cover_if_supported(&mut self, instr: &MirInstruction) {
|
||||
use crate::mir::MirInstruction as I;
|
||||
let supported = matches!(
|
||||
@ -49,16 +71,33 @@ impl LowerCore {
|
||||
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction) {
|
||||
use crate::mir::MirInstruction as I;
|
||||
match instr {
|
||||
I::Const { value, .. } => match value {
|
||||
ConstValue::Integer(i) => b.emit_const_i64(*i),
|
||||
I::Const { dst, value } => match value {
|
||||
ConstValue::Integer(i) => {
|
||||
b.emit_const_i64(*i);
|
||||
self.known_i64.insert(*dst, *i);
|
||||
}
|
||||
ConstValue::Float(f) => b.emit_const_f64(*f),
|
||||
ConstValue::Bool(_)
|
||||
| ConstValue::String(_) | ConstValue::Null | ConstValue::Void => {
|
||||
ConstValue::Bool(bv) => {
|
||||
let iv = if *bv { 1 } else { 0 };
|
||||
b.emit_const_i64(iv);
|
||||
self.known_i64.insert(*dst, iv);
|
||||
}
|
||||
ConstValue::String(_) | ConstValue::Null | ConstValue::Void => {
|
||||
// leave unsupported for now
|
||||
}
|
||||
},
|
||||
I::Copy { .. } => { /* no-op for now */ }
|
||||
I::BinOp { op, .. } => {
|
||||
I::Copy { dst, src } => {
|
||||
if let Some(v) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, v); }
|
||||
// If source is a parameter, materialize it on the stack for downstream ops
|
||||
if let Some(pidx) = self.param_index.get(src).copied() {
|
||||
b.emit_param_i64(pidx);
|
||||
}
|
||||
// Otherwise no-op for codegen (stack-machine handles sources directly later)
|
||||
}
|
||||
I::BinOp { dst, op, lhs, rhs } => {
|
||||
// Ensure operands are on stack when available (param or known const)
|
||||
self.push_value_if_known_or_param(b, lhs);
|
||||
self.push_value_if_known_or_param(b, rhs);
|
||||
let kind = match op {
|
||||
BinaryOp::Add => BinOpKind::Add,
|
||||
BinaryOp::Sub => BinOpKind::Sub,
|
||||
@ -70,8 +109,22 @@ impl LowerCore {
|
||||
| BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor | BinaryOp::Shl | BinaryOp::Shr => { return; }
|
||||
};
|
||||
b.emit_binop(kind);
|
||||
if let (Some(a), Some(b)) = (self.known_i64.get(lhs), self.known_i64.get(rhs)) {
|
||||
let res = match op {
|
||||
BinaryOp::Add => a.wrapping_add(*b),
|
||||
BinaryOp::Sub => a.wrapping_sub(*b),
|
||||
BinaryOp::Mul => a.wrapping_mul(*b),
|
||||
BinaryOp::Div => if *b != 0 { a.wrapping_div(*b) } else { 0 },
|
||||
BinaryOp::Mod => if *b != 0 { a.wrapping_rem(*b) } else { 0 },
|
||||
_ => 0,
|
||||
};
|
||||
self.known_i64.insert(*dst, res);
|
||||
}
|
||||
}
|
||||
I::Compare { op, .. } => {
|
||||
I::Compare { op, lhs, rhs, .. } => {
|
||||
// Ensure operands are on stack when available (param or known const)
|
||||
self.push_value_if_known_or_param(b, lhs);
|
||||
self.push_value_if_known_or_param(b, rhs);
|
||||
let kind = match op {
|
||||
CompareOp::Eq => CmpKind::Eq,
|
||||
CompareOp::Ne => CmpKind::Ne,
|
||||
@ -84,12 +137,57 @@ impl LowerCore {
|
||||
}
|
||||
I::Jump { .. } => b.emit_jump(),
|
||||
I::Branch { .. } => b.emit_branch(),
|
||||
I::Return { .. } => b.emit_return(),
|
||||
I::ArrayGet { .. } => {
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_GET, 2, true);
|
||||
I::Return { value } => {
|
||||
if let Some(v) = value { self.push_value_if_known_or_param(b, v); }
|
||||
b.emit_return()
|
||||
}
|
||||
I::ArraySet { .. } => {
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_SET, 3, false);
|
||||
I::ArrayGet { array, index, .. } => {
|
||||
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
|
||||
// Push args: array param index (or -1), index (known or 0)
|
||||
let idx = self.known_i64.get(index).copied().unwrap_or(0);
|
||||
let arr_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
||||
b.emit_const_i64(arr_idx);
|
||||
b.emit_const_i64(idx);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_GET, 2, true);
|
||||
}
|
||||
}
|
||||
I::ArraySet { array, index, value } => {
|
||||
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
|
||||
let idx = self.known_i64.get(index).copied().unwrap_or(0);
|
||||
let val = self.known_i64.get(value).copied().unwrap_or(0);
|
||||
let arr_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
||||
b.emit_const_i64(arr_idx);
|
||||
b.emit_const_i64(idx);
|
||||
b.emit_const_i64(val);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_SET, 3, false);
|
||||
}
|
||||
}
|
||||
I::BoxCall { box_val: array, method, args, dst, .. } => {
|
||||
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
|
||||
match method.as_str() {
|
||||
"len" | "length" => {
|
||||
// argc=1: (array_param_index)
|
||||
let arr_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
||||
b.emit_const_i64(arr_idx);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
|
||||
}
|
||||
"push" => {
|
||||
// argc=2: (array, value)
|
||||
let val = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
let arr_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
||||
b.emit_const_i64(arr_idx);
|
||||
b.emit_const_i64(val);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_PUSH, 2, false);
|
||||
}
|
||||
"size" => {
|
||||
// MapBox.size(): argc=1 (map_param_index)
|
||||
let map_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
||||
b.emit_const_i64(map_idx);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE, 1, dst.is_some());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user