Phase 10.7 - JIT統計とイベント機能の完成

主要な実装:
- PHI(b1)統計追跡: phi_total_slots/phi_b1_slotsをJSON出力
- 関数単位統計API: JitStatsBox.perFunction()で詳細統計取得
- JITイベントシステム: compile/execute/fallback/trapをJSONL形式で記録
- Store/Load命令対応: ローカル変数を含む関数のJIT実行が可能に

新しいBox:
- JitStatsBox: JIT統計の取得
- JitConfigBox: JIT設定の管理(将来用)
- JitEventsBox: イベントのJSONL出力(将来用)
- JitPolicyBox: 実行ポリシー管理(将来用)

CLI拡張:
- --jit-exec, --jit-stats, --jit-dump等のフラグ追加
- --jit-directモードでの独立JIT実行
- NYASH_JIT_*環境変数によるきめ細かい制御

ドキュメント:
- Phase 10.7実装計画の詳細化
- Phase 10.9 (ビルトインBox JIT) の計画追加
- JIT統計JSONスキーマ v1の仕様化

ChatGPT5との共同開発により、JIT基盤が大幅に強化されました。
次はPhase 10.9でビルトインBoxのJIT対応を進め、
Python統合(Phase 10.1)への道を開きます。

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-08-28 09:26:58 +09:00
parent 99e59e24e2
commit e54561e69f
64 changed files with 4311 additions and 189 deletions

62
src/jit/abi.rs Normal file
View File

@ -0,0 +1,62 @@
//! JIT minimal ABI types independent from VM internals
#[derive(Debug, Clone, Copy)]
pub enum JitValue {
I64(i64),
F64(f64),
Bool(bool),
/// Opaque handle for host objects (future use)
Handle(u64),
}
impl JitValue {
pub fn as_i64(&self) -> Option<i64> { if let JitValue::I64(v) = self { Some(*v) } else { None } }
}
/// Adapter between VMValue and JitValue — keeps JIT decoupled from VM internals
pub mod adapter {
use super::JitValue;
use crate::backend::vm::VMValue;
pub fn to_jit_values(args: &[VMValue]) -> Vec<JitValue> {
args.iter().map(|v| match v {
VMValue::Integer(i) => JitValue::I64(*i),
VMValue::Float(f) => JitValue::F64(*f),
VMValue::Bool(b) => JitValue::Bool(*b),
VMValue::BoxRef(arc) => {
let h = crate::jit::rt::handles::to_handle(arc.clone());
JitValue::Handle(h)
}
// For now, map others to handle via boxing where reasonable
VMValue::String(s) => {
let bx = Box::new(crate::box_trait::StringBox::new(s));
let bx_dyn: Box<dyn crate::box_trait::NyashBox> = bx;
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(bx_dyn);
let h = crate::jit::rt::handles::to_handle(arc);
JitValue::Handle(h)
}
VMValue::Void => JitValue::Handle(0),
VMValue::Future(f) => {
let bx_dyn: Box<dyn crate::box_trait::NyashBox> = Box::new(f.clone());
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(bx_dyn);
let h = crate::jit::rt::handles::to_handle(arc);
JitValue::Handle(h)
}
}).collect()
}
pub fn from_jit_value(v: JitValue) -> VMValue {
match v {
JitValue::I64(i) => VMValue::Integer(i),
JitValue::F64(f) => VMValue::Float(f),
JitValue::Bool(b) => VMValue::Bool(b),
JitValue::Handle(h) => {
if let Some(arc) = crate::jit::rt::handles::get(h) {
VMValue::BoxRef(arc)
} else {
VMValue::Void
}
}
}
}
}

102
src/jit/config.rs Normal file
View File

@ -0,0 +1,102 @@
//! JIT configuration aggregator
//!
//! Centralizes JIT-related flags so callers and tests can use a single
//! source of truth instead of scattering env access across modules.
#[derive(Debug, Clone, Default)]
pub struct JitConfig {
pub exec: bool, // NYASH_JIT_EXEC
pub stats: bool, // NYASH_JIT_STATS
pub stats_json: bool, // NYASH_JIT_STATS_JSON
pub dump: bool, // NYASH_JIT_DUMP
pub threshold: Option<u32>,// NYASH_JIT_THRESHOLD
pub phi_min: bool, // NYASH_JIT_PHI_MIN
pub hostcall: bool, // NYASH_JIT_HOSTCALL
pub handle_debug: bool, // NYASH_JIT_HANDLE_DEBUG
pub native_f64: bool, // NYASH_JIT_NATIVE_F64
pub native_bool: bool, // NYASH_JIT_NATIVE_BOOL
pub native_bool_abi: bool, // NYASH_JIT_ABI_B1 (experimental)
pub ret_bool_b1: bool, // NYASH_JIT_RET_B1 (footing; currently returns i64 0/1)
}
impl JitConfig {
pub fn from_env() -> Self {
let getb = |k: &str| std::env::var(k).ok().as_deref() == Some("1");
let threshold = std::env::var("NYASH_JIT_THRESHOLD").ok().and_then(|s| s.parse::<u32>().ok());
Self {
exec: getb("NYASH_JIT_EXEC"),
stats: getb("NYASH_JIT_STATS"),
stats_json: getb("NYASH_JIT_STATS_JSON"),
dump: getb("NYASH_JIT_DUMP"),
threshold,
phi_min: getb("NYASH_JIT_PHI_MIN"),
hostcall: getb("NYASH_JIT_HOSTCALL"),
handle_debug: getb("NYASH_JIT_HANDLE_DEBUG"),
native_f64: getb("NYASH_JIT_NATIVE_F64"),
native_bool: getb("NYASH_JIT_NATIVE_BOOL"),
native_bool_abi: getb("NYASH_JIT_ABI_B1"),
ret_bool_b1: getb("NYASH_JIT_RET_B1"),
}
}
/// Apply current struct values into environment variables.
/// This keeps existing env untouched unless the value is explicitly set here.
pub fn apply_env(&self) {
let setb = |k: &str, v: bool| { if v { std::env::set_var(k, "1"); } };
setb("NYASH_JIT_EXEC", self.exec);
setb("NYASH_JIT_STATS", self.stats);
setb("NYASH_JIT_STATS_JSON", self.stats_json);
setb("NYASH_JIT_DUMP", self.dump);
if let Some(t) = self.threshold { std::env::set_var("NYASH_JIT_THRESHOLD", t.to_string()); }
setb("NYASH_JIT_PHI_MIN", self.phi_min);
setb("NYASH_JIT_HOSTCALL", self.hostcall);
setb("NYASH_JIT_HANDLE_DEBUG", self.handle_debug);
setb("NYASH_JIT_NATIVE_F64", self.native_f64);
setb("NYASH_JIT_NATIVE_BOOL", self.native_bool);
setb("NYASH_JIT_ABI_B1", self.native_bool_abi);
setb("NYASH_JIT_RET_B1", self.ret_bool_b1);
}
}
// Global current JIT config (thread-safe), defaults to env when unset
use once_cell::sync::OnceCell;
use std::sync::RwLock;
static GLOBAL_JIT_CONFIG: OnceCell<RwLock<JitConfig>> = OnceCell::new();
/// Get current JIT config (falls back to env-derived default if unset)
pub fn current() -> JitConfig {
if let Some(lock) = GLOBAL_JIT_CONFIG.get() {
if let Ok(cfg) = lock.read() { return cfg.clone(); }
}
JitConfig::from_env()
}
/// Set current JIT config (overrides env lookups in hot paths)
pub fn set_current(cfg: JitConfig) {
if let Some(lock) = GLOBAL_JIT_CONFIG.get() {
if let Ok(mut w) = lock.write() { *w = cfg; return; }
}
let _ = GLOBAL_JIT_CONFIG.set(RwLock::new(cfg));
}
// --- Runtime capability probing (minimal, safe defaults) ---
#[derive(Debug, Clone, Copy, Default)]
pub struct JitCapabilities {
pub supports_b1_sig: bool,
}
/// Probe JIT backend capabilities once. Safe default: b1 signatures are unsupported.
pub fn probe_capabilities() -> JitCapabilities {
// Current toolchain: allow forcing via env for experiments; otherwise false.
// When upgrading Cranelift to a version with B1 signature support, set NYASH_JIT_ABI_B1_SUPPORT=1
let forced = std::env::var("NYASH_JIT_ABI_B1_SUPPORT").ok().as_deref() == Some("1");
JitCapabilities { supports_b1_sig: forced }
}
/// Apply runtime capabilities onto a JitConfig (e.g., disable b1 ABI when unsupported)
pub fn apply_runtime_caps(mut cfg: JitConfig, caps: JitCapabilities) -> JitConfig {
if cfg.native_bool_abi && !caps.supports_b1_sig { cfg.native_bool_abi = false; }
cfg
}

View File

@ -12,11 +12,15 @@ pub struct JitEngine {
initialized: bool,
next_handle: u64,
/// Stub function table: handle -> callable closure
fntab: HashMap<u64, Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>>,
fntab: HashMap<u64, Arc<dyn Fn(&[crate::jit::abi::JitValue]) -> crate::jit::abi::JitValue + Send + Sync>>,
/// Host externs by symbol name (Phase 10_d)
externs: HashMap<String, Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>>,
#[cfg(feature = "cranelift-jit")]
isa: Option<cranelift_codegen::isa::OwnedTargetIsa>,
// Last lower stats (per function)
last_phi_total: u64,
last_phi_b1: u64,
last_ret_bool_hint: bool,
}
impl JitEngine {
@ -27,6 +31,9 @@ impl JitEngine {
fntab: HashMap::new(),
externs: HashMap::new(),
#[cfg(feature = "cranelift-jit")] isa: None,
last_phi_total: 0,
last_phi_b1: 0,
last_ret_bool_hint: false,
};
#[cfg(feature = "cranelift-jit")]
{ this.isa = None; }
@ -47,26 +54,46 @@ impl JitEngine {
eprintln!("[JIT] lower failed for {}: {}", func_name, e);
return None;
}
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
// Capture per-function lower stats for manager to query later
let (phi_t, phi_b1, ret_b) = lower.last_stats();
self.last_phi_total = phi_t; self.last_phi_b1 = phi_b1; self.last_ret_bool_hint = ret_b;
// Record per-function stats into manager via callback if available (handled by caller)
let cfg_now = crate::jit::config::current();
if cfg_now.dump {
let phi_min = cfg_now.phi_min;
let native_f64 = cfg_now.native_f64;
let native_bool = cfg_now.native_bool;
#[cfg(feature = "cranelift-jit")]
{
let s = builder.stats;
eprintln!("[JIT] lower {}: covered={} unsupported={} (consts={}, binops={}, cmps={}, branches={}, rets={})",
func_name, lower.covered, lower.unsupported,
eprintln!("[JIT] lower {}: argc={} phi_min={} f64={} bool={} covered={} unsupported={} (consts={}, binops={}, cmps={}, branches={}, rets={})",
func_name, mir.params.len(), phi_min, native_f64, native_bool,
lower.covered, lower.unsupported,
s.0, s.1, s.2, s.3, s.4);
}
#[cfg(not(feature = "cranelift-jit"))]
{
eprintln!("[JIT] lower {}: covered={} unsupported={} (consts={}, binops={}, cmps={}, branches={}, rets={})",
func_name, lower.covered, lower.unsupported,
eprintln!("[JIT] lower {}: argc={} phi_min={} f64={} bool={} covered={} unsupported={} (consts={}, binops={}, cmps={}, branches={}, rets={})",
func_name, mir.params.len(), phi_min, native_f64, native_bool,
lower.covered, lower.unsupported,
builder.consts, builder.binops, builder.cmps, builder.branches, builder.rets);
}
// Optional DOT export
if let Ok(path) = std::env::var("NYASH_JIT_DOT") {
if !path.is_empty() {
if let Err(e) = crate::jit::lower::core::dump_cfg_dot(mir, &path, phi_min) {
eprintln!("[JIT] DOT export failed: {}", e);
} else {
eprintln!("[JIT] DOT written to {}", path);
}
}
}
}
// Create a handle and register an executable closure
let h = self.next_handle;
self.next_handle = self.next_handle.saturating_add(1);
// Create a handle and register an executable closure if available
#[cfg(feature = "cranelift-jit")]
{
let h = self.next_handle;
self.next_handle = self.next_handle.saturating_add(1);
if let Some(closure) = builder.take_compiled_closure() {
self.fntab.insert(h, closure);
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
@ -75,21 +102,27 @@ impl JitEngine {
}
return Some(h);
}
// If Cranelift path did not produce a closure, treat as not compiled
return None;
}
// Fallback: insert a stub closure
self.fntab.insert(h, Arc::new(|_args: &[crate::backend::vm::VMValue]| {
crate::backend::vm::VMValue::Void
}));
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
let dt = t0.elapsed();
eprintln!("[JIT] compile_time_ms={} for {} (stub)", dt.as_millis(), func_name);
#[cfg(not(feature = "cranelift-jit"))]
{
// Without Cranelift, do not register a stub that alters program semantics.
// Report as not compiled so VM path remains authoritative.
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
let dt = t0.elapsed();
eprintln!("[JIT] compile skipped (no cranelift) for {} after {}ms", func_name, dt.as_millis());
}
return None;
}
Some(h)
}
/// Get statistics from the last lowered function
pub fn last_lower_stats(&self) -> (u64, u64, bool) { (self.last_phi_total, self.last_phi_b1, self.last_ret_bool_hint) }
/// Execute compiled function by handle with trap fallback.
/// Returns Some(VMValue) if executed successfully; None on missing handle or trap (panic).
pub fn execute_handle(&self, handle: u64, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
pub fn execute_handle(&self, handle: u64, args: &[crate::jit::abi::JitValue]) -> Option<crate::jit::abi::JitValue> {
let f = match self.fntab.get(&handle) { Some(f) => f, None => return None };
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (f)(args)));
match res {

42
src/jit/events.rs Normal file
View File

@ -0,0 +1,42 @@
//! JIT Events (v0): minimal JSONL appender for compile/execute/fallback/trap
//!
//! Emission is opt-in via env:
//! - NYASH_JIT_EVENTS=1 prints to stdout (one JSON per line)
//! - NYASH_JIT_EVENTS_PATH=/path/to/file.jsonl appends to file
use serde::Serialize;
fn should_emit() -> bool {
std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1")
|| std::env::var("NYASH_JIT_EVENTS_PATH").is_ok()
}
fn write_line(s: &str) {
if let Ok(path) = std::env::var("NYASH_JIT_EVENTS_PATH") {
let _ = std::fs::OpenOptions::new().create(true).append(true).open(path).and_then(|mut f| {
use std::io::Write;
writeln!(f, "{}", s)
});
} else {
println!("{}", s);
}
}
#[derive(Serialize)]
struct Event<'a, T: Serialize> {
kind: &'a str,
function: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
handle: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
ms: Option<u128>,
#[serde(flatten)]
extra: T,
}
pub fn emit<T: Serialize>(kind: &str, function: &str, handle: Option<u64>, ms: Option<u128>, extra: T) {
if !should_emit() { return; }
let ev = Event { kind, function, handle, ms, extra };
if let Ok(s) = serde_json::to_string(&ev) { write_line(&s); }
}

View File

@ -13,6 +13,21 @@ pub const SYM_MAP_GET: &str = "nyash.map.get";
pub const SYM_MAP_SET: &str = "nyash.map.set";
pub const SYM_MAP_SIZE: &str = "nyash.map.size";
// Handle-based variants for direct JIT bridging
pub const SYM_ARRAY_LEN_H: &str = "nyash.array.len_h";
pub const SYM_ARRAY_GET_H: &str = "nyash.array.get_h";
pub const SYM_ARRAY_SET_H: &str = "nyash.array.set_h";
pub const SYM_ARRAY_PUSH_H: &str = "nyash.array.push_h";
pub const SYM_ARRAY_LAST_H: &str = "nyash.array.last_h";
pub const SYM_MAP_SIZE_H: &str = "nyash.map.size_h";
pub const SYM_MAP_GET_H: &str = "nyash.map.get_h";
pub const SYM_MAP_SET_H: &str = "nyash.map.set_h";
pub const SYM_MAP_HAS_H: &str = "nyash.map.has_h";
// Generic read-only helper
pub const SYM_ANY_LEN_H: &str = "nyash.any.length_h";
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";
fn as_array(args: &[VMValue]) -> Option<&crate::boxes::array::ArrayBox> {
match args.get(0) {
Some(VMValue::BoxRef(b)) => b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>(),
@ -88,4 +103,3 @@ pub fn map_size(args: &[VMValue]) -> VMValue {
}
VMValue::Integer(0)
}

View File

@ -0,0 +1,18 @@
//! Minimal hostcall registry (v0): classify symbols as read-only or mutating
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HostcallKind { ReadOnly, Mutating }
pub fn classify(symbol: &str) -> HostcallKind {
match symbol {
// Read-only (safe under read_only policy)
"nyash.array.len_h" | "nyash.any.length_h" | "nyash.any.is_empty_h" |
"nyash.map.size_h" | "nyash.map.get_h" | "nyash.string.charCodeAt_h" |
"nyash.array.get_h" => HostcallKind::ReadOnly,
// Mutating
"nyash.array.push_h" | "nyash.array.set_h" | "nyash.map.set_h" => HostcallKind::Mutating,
// Default to read-only to be permissive in v0
_ => HostcallKind::ReadOnly,
}
}

View File

@ -9,11 +9,16 @@ pub enum BinOpKind { Add, Sub, Mul, Div, Mod }
#[derive(Debug, Clone, Copy)]
pub enum CmpKind { Eq, Ne, Lt, Le, Gt, Ge }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParamKind { I64, F64, B1 }
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) { }
/// Optional: prepare typed ABI signature for params and f64 return flag
fn prepare_signature_typed(&mut self, _params: &[ParamKind], _ret_is_f64: 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);
@ -36,10 +41,18 @@ pub trait IRBuilder {
fn br_if_top_is_true(&mut self, _then_index: usize, _else_index: usize) { }
/// Optional: unconditional jump to target block index
fn jump_to(&mut self, _target_index: usize) { }
/// Optional: ensure target block has one i64 block param (for minimal PHI)
fn ensure_block_param_i64(&mut self, _index: usize) { }
/// Optional: push current block's first param (i64) onto the value stack
fn push_block_param_i64(&mut self) { }
/// Optional: ensure target block has N i64 block params (for PHI)
fn ensure_block_params_i64(&mut self, _index: usize, _count: usize) { }
/// Optional: ensure target block has N b1 block params (for PHI of bool)
fn ensure_block_params_b1(&mut self, index: usize, count: usize) { self.ensure_block_params_i64(index, count); }
/// Optional: ensure target block has one i64 block param (backward compat)
fn ensure_block_param_i64(&mut self, index: usize) { self.ensure_block_params_i64(index, 1); }
/// Optional: push current block's param at position onto the value stack (default=0)
fn push_block_param_i64_at(&mut self, _pos: usize) { }
/// Optional: push current block's boolean param (b1) at position; default converts i64 0/1 → b1
fn push_block_param_b1_at(&mut self, _pos: usize) { self.push_block_param_i64_at(_pos); }
/// Optional: push current block's first param (i64) onto the value stack (backward compat)
fn push_block_param_i64(&mut self) { self.push_block_param_i64_at(0); }
/// Optional: conditional branch with explicit arg counts for then/else; pops args from stack
fn br_if_with_args(&mut self, _then_index: usize, _else_index: usize, _then_n: usize, _else_n: usize) {
// fallback to no-arg br_if
@ -47,6 +60,16 @@ pub trait IRBuilder {
}
/// Optional: jump with explicit arg count; pops args from stack
fn jump_with_args(&mut self, _target_index: usize, _n: usize) { self.jump_to(_target_index); }
/// Optional: hint that function returns a boolean (b1) value (footing only)
fn hint_ret_bool(&mut self, _is_b1: bool) { }
// ==== Minimal local slots for Load/Store (i64 only) ====
/// Ensure an i64 local slot exists for the given index
fn ensure_local_i64(&mut self, _index: usize) { }
/// Store top-of-stack (normalized to i64) into local slot
fn store_local_i64(&mut self, _index: usize) { }
/// Load i64 from local slot and push to stack
fn load_local_i64(&mut self, _index: usize) { }
}
pub struct NoopBuilder {
@ -72,6 +95,9 @@ impl IRBuilder for NoopBuilder {
fn emit_jump(&mut self) { self.branches += 1; }
fn emit_branch(&mut self) { self.branches += 1; }
fn emit_return(&mut self) { self.rets += 1; }
fn ensure_local_i64(&mut self, _index: usize) { /* no-op */ }
fn store_local_i64(&mut self, _index: usize) { self.consts += 1; }
fn load_local_i64(&mut self, _index: usize) { self.consts += 1; }
}
#[cfg(feature = "cranelift-jit")]
@ -88,11 +114,17 @@ pub struct CraneliftBuilder {
blocks: Vec<cranelift_codegen::ir::Block>,
current_block_index: Option<usize>,
block_param_counts: std::collections::HashMap<usize, usize>,
// Local stack slots for minimal Load/Store lowering (i64 only)
local_slots: std::collections::HashMap<usize, cranelift_codegen::ir::StackSlot>,
// Finalized function pointer (if any)
compiled_closure: Option<std::sync::Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>>,
compiled_closure: Option<std::sync::Arc<dyn Fn(&[crate::jit::abi::JitValue]) -> crate::jit::abi::JitValue + Send + Sync>>,
// Desired simple ABI (Phase 10_c minimal): i64 params count and i64 return
desired_argc: usize,
desired_has_ret: bool,
desired_ret_is_f64: bool,
typed_sig_prepared: bool,
// Return-type hint: function returns boolean (footing only; ABI remains i64 for now)
ret_hint_is_b1: bool,
}
#[cfg(feature = "cranelift-jit")]
@ -106,7 +138,7 @@ extern "C" fn nyash_host_stub0() -> i64 { 0 }
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| {
crate::jit::rt::with_legacy_vm_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>() {
@ -121,7 +153,7 @@ extern "C" fn nyash_array_len(arr_param_index: i64) -> i64 {
#[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| {
crate::jit::rt::with_legacy_vm_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>() {
@ -137,7 +169,7 @@ extern "C" fn nyash_array_push(arr_param_index: i64, val: i64) -> i64 {
#[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| {
crate::jit::rt::with_legacy_vm_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>() {
@ -153,7 +185,7 @@ extern "C" fn nyash_array_get(arr_param_index: i64, idx: i64) -> i64 {
#[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| {
crate::jit::rt::with_legacy_vm_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>() {
@ -174,7 +206,7 @@ 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| {
crate::jit::rt::with_legacy_vm_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>() {
@ -187,8 +219,234 @@ extern "C" fn nyash_map_size(map_param_index: i64) -> i64 {
})
}
// === Handle-based externs (10.7c) ===
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_array_len_h(handle: u64) -> i64 {
if let Some(obj) = crate::jit::rt::handles::get(handle) {
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
if let Some(ib) = arr.length().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
}
}
0
}
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_array_push_h(handle: u64, val: i64) -> i64 {
// Policy/Events: classify and decide
use crate::jit::hostcall_registry::{classify, HostcallKind};
let sym = crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H;
match (classify(sym), crate::jit::policy::current().read_only) {
(HostcallKind::Mutating, true) => {
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"fallback"}));
return 0;
}
_ => {}
}
if let Some(obj) = crate::jit::rt::handles::get(handle) {
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let ib = crate::box_trait::IntegerBox::new(val);
let _ = arr.push(Box::new(ib));
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"allow"}));
return 0;
}
}
0
}
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_array_get_h(handle: u64, idx: i64) -> i64 {
if let Some(obj) = crate::jit::rt::handles::get(handle) {
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let val = arr.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_last_h(handle: u64) -> i64 {
if let Some(obj) = crate::jit::rt::handles::get(handle) {
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
// Return last element as i64 if IntegerBox, else 0
if let Ok(items) = arr.items.read() {
if let Some(last) = items.last() {
if let Some(ib) = last.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
return ib.value;
}
}
}
}
}
0
}
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_array_set_h(handle: u64, idx: i64, val: i64) -> i64 {
use crate::jit::hostcall_registry::{classify, HostcallKind};
let sym = crate::jit::r#extern::collections::SYM_ARRAY_SET_H;
if classify(sym) == HostcallKind::Mutating && crate::jit::policy::current().read_only {
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"fallback"}));
return 0;
}
if let Some(obj) = crate::jit::rt::handles::get(handle) {
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let _ = arr.set(
Box::new(crate::box_trait::IntegerBox::new(idx)),
Box::new(crate::box_trait::IntegerBox::new(val)),
);
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"allow"}));
return 0;
}
}
0
}
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_map_size_h(handle: u64) -> i64 {
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"allow"}));
if let Some(obj) = crate::jit::rt::handles::get(handle) {
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
if let Some(ib) = map.size().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
}
}
0
}
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_map_get_h(handle: u64, key: i64) -> i64 {
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_GET_H, "decision":"allow"}));
if let Some(obj) = crate::jit::rt::handles::get(handle) {
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
let key_box = Box::new(crate::box_trait::IntegerBox::new(key));
let val = map.get(key_box);
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_map_set_h(handle: u64, key: i64, val: i64) -> i64 {
use crate::jit::hostcall_registry::{classify, HostcallKind};
let sym = crate::jit::r#extern::collections::SYM_MAP_SET_H;
if classify(sym) == HostcallKind::Mutating && crate::jit::policy::current().read_only {
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"fallback"}));
return 0;
}
if let Some(obj) = crate::jit::rt::handles::get(handle) {
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
let key_box = Box::new(crate::box_trait::IntegerBox::new(key));
let val_box = Box::new(crate::box_trait::IntegerBox::new(val));
let _ = map.set(key_box, val_box);
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"allow"}));
return 0;
}
}
0
}
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_map_has_h(handle: u64, key: i64) -> i64 {
if let Some(obj) = crate::jit::rt::handles::get(handle) {
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
let key_box = Box::new(crate::box_trait::IntegerBox::new(key));
let val = map.get(key_box);
// Treat presence if result is not Void
let is_present = !val.as_any().is::<crate::box_trait::VoidBox>();
return if is_present { 1 } else { 0 };
}
}
0
}
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_any_length_h(handle: u64) -> i64 {
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"allow"}));
if let Some(obj) = crate::jit::rt::handles::get(handle) {
// Array length
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
if let Some(ib) = arr.length().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
}
// String length
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
return sb.value.len() as i64;
}
}
0
}
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_any_is_empty_h(handle: u64) -> i64 {
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, "decision":"allow"}));
if let Some(obj) = crate::jit::rt::handles::get(handle) {
// Array empty?
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
if let Ok(items) = arr.items.read() { return if items.is_empty() { 1 } else { 0 }; }
}
// String empty?
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
return if sb.value.is_empty() { 1 } else { 0 };
}
// Map empty?
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
if let Some(ib) = map.size().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return if ib.value == 0 { 1 } else { 0 }; }
}
}
0
}
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_string_charcode_at_h(handle: u64, idx: i64) -> i64 {
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"allow"}));
if idx < 0 { return -1; }
if let Some(obj) = crate::jit::rt::handles::get(handle) {
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
let s = &sb.value;
let i = idx as usize;
if i < s.len() {
// Return UTF-8 byte at index (ASCII-friendly PoC)
return s.as_bytes()[i] as i64;
} else { return -1; }
}
}
-1
}
#[cfg(feature = "cranelift-jit")]
impl IRBuilder for CraneliftBuilder {
fn prepare_signature_typed(&mut self, params: &[ParamKind], ret_is_f64: bool) {
use cranelift_codegen::ir::{AbiParam, Signature, types};
fn abi_param_for_kind(k: ParamKind, cfg: &crate::jit::config::JitConfig) -> cranelift_codegen::ir::AbiParam {
use cranelift_codegen::ir::types;
match k {
ParamKind::I64 => cranelift_codegen::ir::AbiParam::new(types::I64),
ParamKind::F64 => cranelift_codegen::ir::AbiParam::new(types::F64),
ParamKind::B1 => {
let _ = cfg.native_bool_abi;
#[cfg(feature = "jit-b1-abi")]
{
if crate::jit::config::probe_capabilities().supports_b1_sig && cfg.native_bool_abi { return cranelift_codegen::ir::AbiParam::new(types::B1); }
}
cranelift_codegen::ir::AbiParam::new(types::I64)
}
}
}
self.desired_argc = params.len();
self.desired_has_ret = true;
self.desired_ret_is_f64 = ret_is_f64;
let call_conv = self.module.isa().default_call_conv();
let mut sig = Signature::new(call_conv);
let cfg_now = crate::jit::config::current();
for &k in params { sig.params.push(abi_param_for_kind(k, &cfg_now)); }
if self.desired_has_ret {
// Decide return ABI: prefer F64 if requested; otherwise Bool may use B1 when supported
if self.desired_ret_is_f64 { sig.returns.push(AbiParam::new(types::F64)); }
else {
let mut used_b1 = false;
#[cfg(feature = "jit-b1-abi")]
{
let cfg_now = crate::jit::config::current();
if crate::jit::config::probe_capabilities().supports_b1_sig && cfg_now.native_bool_abi && self.ret_hint_is_b1 {
sig.returns.push(AbiParam::new(types::B1));
used_b1 = true;
}
}
if !used_b1 { sig.returns.push(AbiParam::new(types::I64)); }
}
}
self.ctx.func.signature = sig;
self.typed_sig_prepared = true;
}
fn emit_param_i64(&mut self, index: usize) {
if let Some(v) = self.entry_param(index) {
self.value_stack.push(v);
@ -197,6 +455,7 @@ 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;
self.desired_ret_is_f64 = crate::jit::config::current().native_f64;
}
fn begin_function(&mut self, name: &str) {
use cranelift_codegen::ir::{AbiParam, Signature, types};
@ -204,14 +463,31 @@ impl IRBuilder for CraneliftBuilder {
self.current_name = Some(name.to_string());
self.value_stack.clear();
// Keep any pre-created blocks (from prepare_blocks)
// Keep any pre-created blocks (from prepare_blocks or typed signature)
// 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);
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;
// Build default signature only if a typed one wasn't prepared
if !self.typed_sig_prepared {
// 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);
for _ in 0..self.desired_argc { sig.params.push(AbiParam::new(types::I64)); }
if self.desired_has_ret {
if self.desired_ret_is_f64 { sig.returns.push(AbiParam::new(types::F64)); }
else {
let mut used_b1 = false;
#[cfg(feature = "jit-b1-abi")]
{
let cfg_now = crate::jit::config::current();
if crate::jit::config::probe_capabilities().supports_b1_sig && cfg_now.native_bool_abi && self.ret_hint_is_b1 {
sig.returns.push(AbiParam::new(types::B1));
used_b1 = true;
}
}
if !used_b1 { sig.returns.push(AbiParam::new(types::I64)); }
}
}
self.ctx.func.signature = sig;
}
self.ctx.func.name = cranelift_codegen::ir::UserFuncName::user(0, 0);
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
@ -253,15 +529,69 @@ 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 simple i64 ABI; we still call without args for now
// SAFETY: We compiled a function with simple (i64 x N) -> i64/f64 というABIだよ。
// ランタイムでは JitValue から i64 へ正規化して、引数個数に応じた関数型にtransmuteして呼び出すにゃ。
let argc = self.desired_argc;
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;
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 {
let v = f();
crate::backend::vm::VMValue::Integer(v)
let closure = std::sync::Arc::new(move |args: &[crate::jit::abi::JitValue]| -> crate::jit::abi::JitValue {
// 正規化: 足りなければ0で埋め、余分は切り捨て
let mut a: [i64; 6] = [0; 6];
let take = core::cmp::min(core::cmp::min(argc, 6), args.len());
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()
}
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])
}
};
if ret_is_f64 {
let ret_f64 = match argc {
0 => { let f: extern "C" fn() -> f64 = std::mem::transmute(code_usize); f() }
1 => { let f: extern "C" fn(i64) -> f64 = std::mem::transmute(code_usize); f(a[0]) }
2 => { let f: extern "C" fn(i64,i64) -> f64 = std::mem::transmute(code_usize); f(a[0],a[1]) }
3 => { let f: extern "C" fn(i64,i64,i64) -> f64 = std::mem::transmute(code_usize); f(a[0],a[1],a[2]) }
4 => { let f: extern "C" fn(i64,i64,i64,i64) -> f64 = 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) -> f64 = 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) -> f64 = std::mem::transmute(code_usize); f(a[0],a[1],a[2],a[3],a[4],a[5]) }
};
return crate::jit::abi::JitValue::F64(ret_f64);
}
crate::jit::abi::JitValue::I64(ret_i64)
});
self.compiled_closure = Some(closure);
}
// Reset typed signature flag for next function
self.typed_sig_prepared = false;
}
fn emit_const_i64(&mut self, val: i64) {
@ -277,22 +607,56 @@ impl IRBuilder for CraneliftBuilder {
fb.finalize();
}
fn emit_const_f64(&mut self, _val: f64) { self.stats.0 += 1; /* not yet supported in Core-1 */ }
fn emit_binop(&mut self, op: BinOpKind) {
fn emit_const_f64(&mut self, val: f64) {
self.stats.0 += 1;
if !crate::jit::config::current().native_f64 { return; }
use cranelift_codegen::ir::types;
use cranelift_frontend::FunctionBuilder;
if self.value_stack.len() < 2 { return; }
let rhs = self.value_stack.pop().unwrap();
let lhs = self.value_stack.pop().unwrap();
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); }
let res = match op {
BinOpKind::Add => fb.ins().iadd(lhs, rhs),
BinOpKind::Sub => fb.ins().isub(lhs, rhs),
BinOpKind::Mul => fb.ins().imul(lhs, rhs),
BinOpKind::Div => fb.ins().sdiv(lhs, rhs),
BinOpKind::Mod => fb.ins().srem(lhs, rhs),
let v = fb.ins().f64const(val);
self.value_stack.push(v);
fb.finalize();
}
fn emit_binop(&mut self, op: BinOpKind) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::types;
if self.value_stack.len() < 2 { return; }
let mut rhs = self.value_stack.pop().unwrap();
let mut lhs = self.value_stack.pop().unwrap();
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); }
// Choose op by operand type (I64 vs F64). If mixed and native_f64, promote to F64.
let lty = fb.func.dfg.value_type(lhs);
let rty = fb.func.dfg.value_type(rhs);
let native_f64 = crate::jit::config::current().native_f64;
let mut use_f64 = native_f64 && (lty == types::F64 || rty == types::F64);
if use_f64 {
if lty != types::F64 { lhs = fb.ins().fcvt_from_sint(types::F64, lhs); }
if rty != types::F64 { rhs = fb.ins().fcvt_from_sint(types::F64, rhs); }
}
let res = if use_f64 {
match op {
BinOpKind::Add => fb.ins().fadd(lhs, rhs),
BinOpKind::Sub => fb.ins().fsub(lhs, rhs),
BinOpKind::Mul => fb.ins().fmul(lhs, rhs),
BinOpKind::Div => fb.ins().fdiv(lhs, rhs),
BinOpKind::Mod => {
// Minimal path: produce 0.0 (fmod未実装)。将来はホストコール/Libcallに切替
fb.ins().f64const(0.0)
}
}
} else {
match op {
BinOpKind::Add => fb.ins().iadd(lhs, rhs),
BinOpKind::Sub => fb.ins().isub(lhs, rhs),
BinOpKind::Mul => fb.ins().imul(lhs, rhs),
BinOpKind::Div => fb.ins().sdiv(lhs, rhs),
BinOpKind::Mod => fb.ins().srem(lhs, rhs),
}
};
self.value_stack.push(res);
self.stats.1 += 1;
@ -300,24 +664,42 @@ impl IRBuilder for CraneliftBuilder {
}
fn emit_compare(&mut self, op: CmpKind) {
use cranelift_codegen::ir::{condcodes::IntCC};
use cranelift_codegen::ir::{condcodes::{IntCC, FloatCC}, types};
use cranelift_frontend::FunctionBuilder;
if self.value_stack.len() < 2 { return; }
let rhs = self.value_stack.pop().unwrap();
let lhs = self.value_stack.pop().unwrap();
let mut rhs = self.value_stack.pop().unwrap();
let mut lhs = self.value_stack.pop().unwrap();
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); }
let cc = match op {
CmpKind::Eq => IntCC::Equal,
CmpKind::Ne => IntCC::NotEqual,
CmpKind::Lt => IntCC::SignedLessThan,
CmpKind::Le => IntCC::SignedLessThanOrEqual,
CmpKind::Gt => IntCC::SignedGreaterThan,
CmpKind::Ge => IntCC::SignedGreaterThanOrEqual,
let lty = fb.func.dfg.value_type(lhs);
let rty = fb.func.dfg.value_type(rhs);
let native_f64 = crate::jit::config::current().native_f64;
let use_f64 = native_f64 && (lty == types::F64 || rty == types::F64);
let b1 = if use_f64 {
if lty != types::F64 { lhs = fb.ins().fcvt_from_sint(types::F64, lhs); }
if rty != types::F64 { rhs = fb.ins().fcvt_from_sint(types::F64, rhs); }
let cc = match op {
CmpKind::Eq => FloatCC::Equal,
CmpKind::Ne => FloatCC::NotEqual,
CmpKind::Lt => FloatCC::LessThan,
CmpKind::Le => FloatCC::LessThanOrEqual,
CmpKind::Gt => FloatCC::GreaterThan,
CmpKind::Ge => FloatCC::GreaterThanOrEqual,
};
fb.ins().fcmp(cc, lhs, rhs)
} else {
let cc = match op {
CmpKind::Eq => IntCC::Equal,
CmpKind::Ne => IntCC::NotEqual,
CmpKind::Lt => IntCC::SignedLessThan,
CmpKind::Le => IntCC::SignedLessThanOrEqual,
CmpKind::Gt => IntCC::SignedGreaterThan,
CmpKind::Ge => IntCC::SignedGreaterThanOrEqual,
};
fb.ins().icmp(cc, lhs, rhs)
};
let b1 = fb.ins().icmp(cc, lhs, rhs);
// Keep b1 on the stack; users (branch) can consume directly, arithmetic should not assume compare value as i64
// Keep b1 on the stack; users (branch) can consume directly
self.value_stack.push(b1);
self.stats.2 += 1;
fb.finalize();
@ -331,13 +713,44 @@ 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 let Some(v) = self.value_stack.pop() {
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);
let v_ty = fb.func.dfg.value_type(v);
if v_ty != ret_ty {
use cranelift_codegen::ir::types;
if ret_ty == types::F64 && v_ty == types::I64 {
v = fb.ins().fcvt_from_sint(types::F64, v);
} else if ret_ty == types::I64 && v_ty == types::F64 {
v = fb.ins().fcvt_to_sint(types::I64, v);
} else if ret_ty == types::I64 {
// If returning i64 but we currently have a boolean, normalize via select(b1,1,0)
use cranelift_codegen::ir::types;
let one = fb.ins().iconst(types::I64, 1);
let zero = fb.ins().iconst(types::I64, 0);
v = fb.ins().select(v, one, zero);
}
#[cfg(feature = "jit-b1-abi")]
{
use cranelift_codegen::ir::types;
if ret_ty == types::B1 && v_ty == types::I64 {
use cranelift_codegen::ir::condcodes::IntCC;
v = fb.ins().icmp_imm(IntCC::NotEqual, v, 0);
}
}
}
fb.ins().return_(&[v]);
} else {
// Return 0 if empty stack (defensive)
use cranelift_codegen::ir::types;
let zero = fb.ins().iconst(types::I64, 0);
fb.ins().return_(&[zero]);
let ret_ty = fb.func.signature.returns.get(0).map(|p| p.value_type).unwrap_or(types::I64);
if ret_ty == types::F64 {
let z = fb.ins().f64const(0.0);
fb.ins().return_(&[z]);
} else {
let zero = fb.ins().iconst(types::I64, 0);
fb.ins().return_(&[zero]);
}
}
fb.finalize();
}
@ -416,14 +829,18 @@ impl IRBuilder for CraneliftBuilder {
let cond_b1 = if let Some(v) = self.value_stack.pop() {
let ty = fb.func.dfg.value_type(v);
if ty == types::I64 {
fb.ins().icmp_imm(IntCC::NotEqual, v, 0)
let out = fb.ins().icmp_imm(IntCC::NotEqual, v, 0);
crate::jit::rt::b1_norm_inc(1);
out
} else {
// assume already b1
v
}
} else {
let zero = fb.ins().iconst(types::I64, 0);
fb.ins().icmp_imm(IntCC::NotEqual, zero, 0)
let out = fb.ins().icmp_imm(IntCC::NotEqual, zero, 0);
crate::jit::rt::b1_norm_inc(1);
out
};
fb.ins().brif(cond_b1, self.blocks[then_index], &[], self.blocks[else_index], &[]);
self.stats.3 += 1;
@ -440,34 +857,60 @@ impl IRBuilder for CraneliftBuilder {
fb.finalize();
}
fn ensure_block_param_i64(&mut self, index: usize) {
self.ensure_block_params_i64(index, 1);
}
fn ensure_block_params_i64(&mut self, index: usize, needed: usize) {
use cranelift_codegen::ir::types;
use cranelift_frontend::FunctionBuilder;
if index >= self.blocks.len() { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
let count = self.block_param_counts.get(&index).copied().unwrap_or(0);
if count == 0 {
let have = self.block_param_counts.get(&index).copied().unwrap_or(0);
if needed > have {
let b = self.blocks[index];
let _v = fb.append_block_param(b, types::I64);
self.block_param_counts.insert(index, 1);
for _ in have..needed {
let _v = fb.append_block_param(b, types::I64);
}
self.block_param_counts.insert(index, needed);
}
fb.finalize();
}
fn push_block_param_i64(&mut self) {
fn ensure_block_params_b1(&mut self, index: usize, needed: usize) {
// Store as i64 block params for ABI stability; consumers can convert to b1
self.ensure_block_params_i64(index, needed);
}
fn push_block_param_i64_at(&mut self, pos: usize) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::types;
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
// Determine current block
let b = if let Some(idx) = self.current_block_index { self.blocks[idx] } else if let Some(b) = self.entry_block { b } else { fb.create_block() };
// Fetch first param if exists
// Ensure we have an active insertion point before emitting any instructions
fb.switch_to_block(b);
let params = fb.func.dfg.block_params(b).to_vec();
if let Some(v) = params.get(0).copied() { self.value_stack.push(v); }
if let Some(v) = params.get(pos).copied() { self.value_stack.push(v); }
else {
// defensive: push 0
// defensive fallback
let zero = fb.ins().iconst(types::I64, 0);
self.value_stack.push(zero);
}
fb.finalize();
}
fn push_block_param_b1_at(&mut self, pos: usize) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::{types, condcodes::IntCC};
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
let b = if let Some(idx) = self.current_block_index { self.blocks[idx] } else if let Some(b) = self.entry_block { b } else { fb.create_block() };
let params = fb.func.dfg.block_params(b).to_vec();
if let Some(v) = params.get(pos).copied() {
let ty = fb.func.dfg.value_type(v);
let b1 = if ty == types::I64 { fb.ins().icmp_imm(IntCC::NotEqual, v, 0) } else { v };
self.value_stack.push(b1);
} else {
let zero = fb.ins().iconst(types::I64, 0);
let b1 = fb.ins().icmp_imm(IntCC::NotEqual, zero, 0);
self.value_stack.push(b1);
}
fb.finalize();
}
fn br_if_with_args(&mut self, then_index: usize, else_index: usize, then_n: usize, else_n: usize) {
use cranelift_codegen::ir::{types, condcodes::IntCC};
use cranelift_frontend::FunctionBuilder;
@ -478,10 +921,12 @@ impl IRBuilder for CraneliftBuilder {
// Condition
let cond_b1 = if let Some(v) = self.value_stack.pop() {
let ty = fb.func.dfg.value_type(v);
if ty == types::I64 { fb.ins().icmp_imm(IntCC::NotEqual, v, 0) } else { v }
if ty == types::I64 { let out = fb.ins().icmp_imm(IntCC::NotEqual, v, 0); crate::jit::rt::b1_norm_inc(1); out } else { v }
} else {
let zero = fb.ins().iconst(types::I64, 0);
fb.ins().icmp_imm(IntCC::NotEqual, zero, 0)
let out = fb.ins().icmp_imm(IntCC::NotEqual, zero, 0);
crate::jit::rt::b1_norm_inc(1);
out
};
// Pop else args then then args (so stack order can be value-friendly)
let mut else_args: Vec<cranelift_codegen::ir::Value> = Vec::new();
@ -507,6 +952,58 @@ impl IRBuilder for CraneliftBuilder {
self.stats.3 += 1;
fb.finalize();
}
fn hint_ret_bool(&mut self, is_b1: bool) { self.ret_hint_is_b1 = is_b1; }
fn ensure_local_i64(&mut self, index: usize) {
use cranelift_codegen::ir::{StackSlotData, StackSlotKind};
use cranelift_frontend::FunctionBuilder;
if self.local_slots.contains_key(&index) { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
let slot = fb.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 8));
self.local_slots.insert(index, slot);
fb.finalize();
}
fn store_local_i64(&mut self, index: usize) {
use cranelift_codegen::ir::{types, condcodes::IntCC};
use cranelift_frontend::FunctionBuilder;
if let Some(mut v) = self.value_stack.pop() {
// Ensure slot without overlapping FunctionBuilder borrows
if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); }
let slot = self.local_slots.get(&index).copied();
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); }
let ty = fb.func.dfg.value_type(v);
if ty != types::I64 {
if ty == types::F64 {
v = fb.ins().fcvt_to_sint(types::I64, v);
} else {
// Convert unknown ints/bools to i64 via (v!=0)?1:0
let one = fb.ins().iconst(types::I64, 1);
let zero = fb.ins().iconst(types::I64, 0);
let b1 = fb.ins().icmp_imm(IntCC::NotEqual, v, 0);
v = fb.ins().select(b1, one, zero);
}
}
if let Some(slot) = slot { fb.ins().stack_store(v, slot, 0); }
fb.finalize();
}
}
fn load_local_i64(&mut self, index: usize) {
use cranelift_codegen::ir::types;
use cranelift_frontend::FunctionBuilder;
if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); }
if let Some(&slot) = self.local_slots.get(&index) {
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); }
let v = fb.ins().stack_load(types::I64, slot, 0);
self.value_stack.push(v);
self.stats.0 += 1;
fb.finalize();
}
}
}
#[cfg(feature = "cranelift-jit")]
@ -542,6 +1039,19 @@ impl CraneliftBuilder {
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);
// Handle-based symbols
builder.symbol(c::SYM_ARRAY_LEN_H, nyash_array_len_h as *const u8);
builder.symbol(c::SYM_ARRAY_GET_H, nyash_array_get_h as *const u8);
builder.symbol(c::SYM_ARRAY_SET_H, nyash_array_set_h as *const u8);
builder.symbol(c::SYM_ARRAY_PUSH_H, nyash_array_push_h as *const u8);
builder.symbol(c::SYM_ARRAY_LAST_H, nyash_array_last_h as *const u8);
builder.symbol(c::SYM_MAP_SIZE_H, nyash_map_size_h as *const u8);
builder.symbol(c::SYM_MAP_GET_H, nyash_map_get_h as *const u8);
builder.symbol(c::SYM_MAP_SET_H, nyash_map_set_h as *const u8);
builder.symbol(c::SYM_MAP_HAS_H, nyash_map_has_h as *const u8);
builder.symbol(c::SYM_ANY_LEN_H, nyash_any_length_h as *const u8);
builder.symbol(c::SYM_ANY_IS_EMPTY_H, nyash_any_is_empty_h as *const u8);
builder.symbol(c::SYM_STRING_CHARCODE_AT_H, nyash_string_charcode_at_h as *const u8);
}
let module = cranelift_jit::JITModule::new(builder);
let ctx = cranelift_codegen::Context::new();
@ -555,14 +1065,18 @@ impl CraneliftBuilder {
blocks: Vec::new(),
current_block_index: None,
block_param_counts: std::collections::HashMap::new(),
local_slots: std::collections::HashMap::new(),
compiled_closure: None,
desired_argc: 0,
desired_has_ret: true,
desired_ret_is_f64: false,
typed_sig_prepared: false,
ret_hint_is_b1: false,
}
}
/// Take ownership of compiled closure if available
pub fn take_compiled_closure(&mut self) -> Option<std::sync::Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>> {
pub fn take_compiled_closure(&mut self) -> Option<std::sync::Arc<dyn Fn(&[crate::jit::abi::JitValue]) -> crate::jit::abi::JitValue + Send + Sync>> {
self.compiled_closure.take()
}
}

View File

@ -12,15 +12,33 @@ pub struct LowerCore {
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)
phi_param_index: std::collections::HashMap<(crate::mir::BasicBlockId, ValueId), usize>,
/// Track values that are boolean (b1) results, e.g., Compare destinations
bool_values: std::collections::HashSet<ValueId>,
/// Track PHI destinations that are boolean (all inputs derived from bool_values)
bool_phi_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,
}
impl LowerCore {
pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), param_index: std::collections::HashMap::new(), phi_values: std::collections::HashSet::new() } }
pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), param_index: std::collections::HashMap::new(), phi_values: std::collections::HashSet::new(), phi_param_index: std::collections::HashMap::new(), bool_values: std::collections::HashSet::new(), bool_phi_values: std::collections::HashSet::new(), last_phi_total: 0, last_phi_b1: 0, last_ret_bool_hint_used: false, local_index: std::collections::HashMap::new(), next_local: 0 } }
/// Get statistics for the last lowered function
pub fn last_stats(&self) -> (u64, u64, bool) { (self.last_phi_total, self.last_phi_b1, self.last_ret_bool_hint_used) }
/// 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
// 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
self.param_index.clear();
for (i, v) in func.params.iter().copied().enumerate() {
@ -30,34 +48,221 @@ impl LowerCore {
let mut bb_ids: Vec<_> = func.blocks.keys().copied().collect();
bb_ids.sort_by_key(|b| b.0);
builder.prepare_blocks(bb_ids.len());
// Optional: collect single-PHI targets for minimal PHI path
let enable_phi_min = std::env::var("NYASH_JIT_PHI_MIN").ok().as_deref() == Some("1");
let mut phi_targets: std::collections::HashMap<crate::mir::BasicBlockId, std::collections::HashMap<crate::mir::BasicBlockId, crate::mir::ValueId>> = std::collections::HashMap::new();
if enable_phi_min {
for (bb_id, bb) in func.blocks.iter() {
// gather Phi instructions in this block
let mut phis: Vec<&crate::mir::MirInstruction> = Vec::new();
for ins in bb.instructions.iter() { if let crate::mir::MirInstruction::Phi { .. } = ins { phis.push(ins); } }
if phis.len() == 1 {
if let crate::mir::MirInstruction::Phi { inputs, .. } = phis[0] {
let mut map: std::collections::HashMap<crate::mir::BasicBlockId, crate::mir::ValueId> = std::collections::HashMap::new();
for (pred, val) in inputs.iter() { map.insert(*pred, *val); }
phi_targets.insert(*bb_id, map);
// Seed boolean lattice with boolean parameters from MIR signature
if !func.signature.params.is_empty() {
for (idx, vid) in func.params.iter().copied().enumerate() {
if let Some(mt) = func.signature.params.get(idx) {
if matches!(mt, crate::mir::MirType::Bool) {
self.bool_values.insert(vid);
}
}
}
}
builder.prepare_signature_i64(func.params.len(), true);
// Pre-scan to classify boolean-producing values and propagate via Copy/Phi/Load-Store heuristics.
self.bool_values.clear();
let mut copy_edges: Vec<(crate::mir::ValueId, crate::mir::ValueId)> = Vec::new();
let mut phi_defs: Vec<(crate::mir::ValueId, Vec<crate::mir::ValueId>)> = Vec::new();
let mut stores: Vec<(crate::mir::ValueId, crate::mir::ValueId)> = Vec::new(); // (ptr, value)
let mut loads: Vec<(crate::mir::ValueId, crate::mir::ValueId)> = Vec::new(); // (dst, ptr)
for bb in bb_ids.iter() {
if let Some(block) = func.blocks.get(bb) {
for ins in block.instructions.iter() {
match ins {
crate::mir::MirInstruction::Compare { dst, .. } => { self.bool_values.insert(*dst); }
crate::mir::MirInstruction::Const { dst, value } => {
if let ConstValue::Bool(_) = value { self.bool_values.insert(*dst); }
}
crate::mir::MirInstruction::Cast { dst, target_type, .. } => {
if matches!(target_type, crate::mir::MirType::Bool) { self.bool_values.insert(*dst); }
}
crate::mir::MirInstruction::TypeOp { dst, op, ty, .. } => {
// Check and cast-to-bool produce boolean
if matches!(op, crate::mir::TypeOpKind::Check) || matches!(ty, crate::mir::MirType::Bool) { self.bool_values.insert(*dst); }
}
crate::mir::MirInstruction::Copy { dst, src } => { copy_edges.push((*dst, *src)); }
crate::mir::MirInstruction::Phi { dst, inputs } => {
let vs: Vec<_> = inputs.iter().map(|(_, v)| *v).collect();
phi_defs.push((*dst, vs));
}
crate::mir::MirInstruction::Store { value, ptr } => { stores.push((*ptr, *value)); }
crate::mir::MirInstruction::Load { dst, ptr } => { loads.push((*dst, *ptr)); }
_ => {}
}
}
if let Some(term) = &block.terminator {
match term {
crate::mir::MirInstruction::Compare { dst, .. } => { self.bool_values.insert(*dst); }
crate::mir::MirInstruction::Const { dst, value } => {
if let ConstValue::Bool(_) = value { self.bool_values.insert(*dst); }
}
crate::mir::MirInstruction::Cast { dst, target_type, .. } => {
if matches!(target_type, crate::mir::MirType::Bool) { self.bool_values.insert(*dst); }
}
crate::mir::MirInstruction::TypeOp { dst, op, ty, .. } => {
if matches!(op, crate::mir::TypeOpKind::Check) || matches!(ty, crate::mir::MirType::Bool) { self.bool_values.insert(*dst); }
}
crate::mir::MirInstruction::Copy { dst, src } => { copy_edges.push((*dst, *src)); }
crate::mir::MirInstruction::Phi { dst, inputs } => {
let vs: Vec<_> = inputs.iter().map(|(_, v)| *v).collect();
phi_defs.push((*dst, vs));
}
crate::mir::MirInstruction::Branch { condition, .. } => { self.bool_values.insert(*condition); }
crate::mir::MirInstruction::Store { value, ptr } => { stores.push((*ptr, *value)); }
crate::mir::MirInstruction::Load { dst, ptr } => { loads.push((*dst, *ptr)); }
_ => {}
}
}
}
}
// Fixed-point boolean lattice propagation
let mut changed = true;
let mut store_bool_ptrs: std::collections::HashSet<crate::mir::ValueId> = std::collections::HashSet::new();
while changed {
changed = false;
// Copy propagation
for (dst, src) in copy_edges.iter().copied() {
if self.bool_values.contains(&src) && !self.bool_values.contains(&dst) {
self.bool_values.insert(dst);
changed = true;
}
// Pointer alias propagation for Store/Load lattice
if store_bool_ptrs.contains(&src) && !store_bool_ptrs.contains(&dst) {
store_bool_ptrs.insert(dst);
changed = true;
}
}
// Store marking
for (ptr, val) in stores.iter().copied() {
if self.bool_values.contains(&val) && !store_bool_ptrs.contains(&ptr) {
store_bool_ptrs.insert(ptr);
changed = true;
}
}
// Load propagation
for (dst, ptr) in loads.iter().copied() {
if store_bool_ptrs.contains(&ptr) && !self.bool_values.contains(&dst) {
self.bool_values.insert(dst);
changed = true;
}
}
// PHI closure for value booleans
for (dst, inputs) in phi_defs.iter() {
if inputs.iter().all(|v| self.bool_values.contains(v)) && !self.bool_values.contains(dst) {
self.bool_values.insert(*dst);
self.bool_phi_values.insert(*dst);
changed = true;
}
}
// PHI closure for pointer aliases: if all inputs are bool-storing pointers, mark dst pointer as such
for (dst, inputs) in phi_defs.iter() {
if inputs.iter().all(|v| store_bool_ptrs.contains(v)) && !store_bool_ptrs.contains(dst) {
store_bool_ptrs.insert(*dst);
changed = true;
}
}
}
// Always-on PHI statistics: count total/b1 phi slots using current heuristics
{
use crate::mir::MirInstruction;
let mut total_phi_slots: usize = 0;
let mut total_phi_b1_slots: usize = 0;
for (dst, inputs) in phi_defs.iter() {
total_phi_slots += 1;
// Heuristics consistent with dump path
let used_as_branch = func.blocks.values().any(|bbx| {
if let Some(MirInstruction::Branch { condition, .. }) = &bbx.terminator { condition == dst } else { false }
});
let is_b1 = self.bool_phi_values.contains(dst)
|| inputs.iter().all(|v| {
self.bool_values.contains(v) || self.known_i64.get(v).map(|&iv| iv == 0 || iv == 1).unwrap_or(false)
})
|| used_as_branch;
if is_b1 { total_phi_b1_slots += 1; }
}
if total_phi_slots > 0 {
crate::jit::rt::phi_total_inc(total_phi_slots as u64);
crate::jit::rt::phi_b1_inc(total_phi_b1_slots as u64);
self.last_phi_total = total_phi_slots as u64;
self.last_phi_b1 = total_phi_b1_slots as u64;
}
}
// Optional: collect PHI targets and ordering per successor for minimal/multi PHI path
let cfg_now = crate::jit::config::current();
let enable_phi_min = cfg_now.phi_min;
// For each successor block, store ordered list of phi dst and a map pred->input for each phi
let mut succ_phi_order: std::collections::HashMap<crate::mir::BasicBlockId, Vec<crate::mir::ValueId>> = std::collections::HashMap::new();
let mut succ_phi_inputs: std::collections::HashMap<crate::mir::BasicBlockId, Vec<(crate::mir::BasicBlockId, crate::mir::ValueId)>> = std::collections::HashMap::new();
if enable_phi_min {
for (bb_id, bb) in func.blocks.iter() {
let mut order: Vec<crate::mir::ValueId> = Vec::new();
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
order.push(*dst);
// store all (pred,val) pairs in flat vec grouped by succ
for (pred, val) in inputs.iter() { succ_phi_inputs.entry(*bb_id).or_default().push((*pred, *val)); }
}
}
if !order.is_empty() { succ_phi_order.insert(*bb_id, order); }
}
}
// Decide ABI: typed or i64-only
let native_f64 = cfg_now.native_f64;
let native_bool = cfg_now.native_bool;
let mut use_typed = false;
let mut kinds: Vec<super::builder::ParamKind> = Vec::new();
for mt in func.signature.params.iter() {
let k = match mt {
crate::mir::MirType::Float if native_f64 => { use_typed = true; super::builder::ParamKind::F64 }
crate::mir::MirType::Bool if native_bool => { use_typed = true; super::builder::ParamKind::B1 }
_ => super::builder::ParamKind::I64,
};
kinds.push(k);
}
let ret_is_f64 = native_f64 && matches!(func.signature.return_type, crate::mir::MirType::Float);
// Hint return bool footing (no-op in current backend; keeps switch point centralized)
let ret_is_bool = matches!(func.signature.return_type, crate::mir::MirType::Bool);
if ret_is_bool {
builder.hint_ret_bool(true);
// Track how many functions are lowered with boolean return hint (for stats)
crate::jit::rt::ret_bool_hint_inc(1);
self.last_ret_bool_hint_used = true;
}
if use_typed || ret_is_f64 {
builder.prepare_signature_typed(&kinds, ret_is_f64);
} else {
builder.prepare_signature_i64(func.params.len(), true);
}
builder.begin_function(&func.signature.name);
// Iterate blocks in the sorted order to keep indices stable
self.phi_values.clear();
self.phi_param_index.clear();
for (idx, bb_id) in bb_ids.iter().enumerate() {
let bb = func.blocks.get(bb_id).unwrap();
builder.switch_to_block(idx);
// Pre-scan PHIs in this block and ensure block parameters count (multi-PHI)
if enable_phi_min {
let mut local_phi_order: Vec<ValueId> = Vec::new();
// Also detect boolean PHIs: inputs all from boolean-producing values
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
local_phi_order.push(*dst);
// decide if this phi is boolean
if inputs.iter().all(|(_, v)| self.bool_values.contains(v)) && !inputs.is_empty() {
self.bool_phi_values.insert(*dst);
}
}
}
if !local_phi_order.is_empty() {
builder.ensure_block_params_i64(idx, local_phi_order.len());
for (i, v) in local_phi_order.into_iter().enumerate() {
self.phi_values.insert(v);
self.phi_param_index.insert((*bb_id, v), i);
}
}
}
for instr in bb.instructions.iter() {
self.cover_if_supported(instr);
if let MirInstruction::Phi { dst, .. } = instr { self.phi_values.insert(*dst); }
self.try_emit(builder, instr);
self.try_emit(builder, instr, *bb_id);
}
if let Some(term) = &bb.terminator {
self.cover_if_supported(term);
@ -70,14 +275,47 @@ impl LowerCore {
let then_index = bb_ids.iter().position(|x| x == then_bb).unwrap_or(0);
let else_index = bb_ids.iter().position(|x| x == else_bb).unwrap_or(0);
if enable_phi_min {
// For minimal PHI, pass one i64 arg if successor defines a single PHI with this block as pred
let mut then_n = 0usize;
let mut else_n = 0usize;
if let Some(pred_map) = phi_targets.get(then_bb) {
if let Some(v) = pred_map.get(bb_id) { self.push_value_if_known_or_param(builder, v); then_n = 1; builder.ensure_block_param_i64(then_index); }
// For multi-PHI, push args in successor's phi order
let mut then_n = 0usize; let mut else_n = 0usize;
if let Some(order) = succ_phi_order.get(then_bb) {
let mut cnt = 0usize;
for dst in order.iter() {
// find input from current block
if let Some(bb_succ) = func.blocks.get(then_bb) {
// locate the specific phi to read its inputs
for ins in bb_succ.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst: d2, inputs } = ins {
if d2 == dst {
if let Some((_, val)) = inputs.iter().find(|(pred, _)| pred == bb_id) {
self.push_value_if_known_or_param(builder, val);
cnt += 1;
}
}
}
}
}
}
if cnt > 0 { builder.ensure_block_params_i64(then_index, cnt); }
then_n = cnt;
}
if let Some(pred_map) = phi_targets.get(else_bb) {
if let Some(v) = pred_map.get(bb_id) { self.push_value_if_known_or_param(builder, v); else_n = 1; builder.ensure_block_param_i64(else_index); }
if let Some(order) = succ_phi_order.get(else_bb) {
let mut cnt = 0usize;
for dst in order.iter() {
if let Some(bb_succ) = func.blocks.get(else_bb) {
for ins in bb_succ.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst: d2, inputs } = ins {
if d2 == dst {
if let Some((_, val)) = inputs.iter().find(|(pred, _)| pred == bb_id) {
self.push_value_if_known_or_param(builder, val);
cnt += 1;
}
}
}
}
}
}
if cnt > 0 { builder.ensure_block_params_i64(else_index, cnt); }
else_n = cnt;
}
builder.br_if_with_args(then_index, else_index, then_n, else_n);
} else {
@ -90,8 +328,24 @@ impl LowerCore {
let target_index = bb_ids.iter().position(|x| x == target).unwrap_or(0);
if enable_phi_min {
let mut n = 0usize;
if let Some(pred_map) = phi_targets.get(target) {
if let Some(v) = pred_map.get(bb_id) { self.push_value_if_known_or_param(builder, v); n = 1; builder.ensure_block_param_i64(target_index); }
if let Some(order) = succ_phi_order.get(target) {
let mut cnt = 0usize;
if let Some(bb_succ) = func.blocks.get(target) {
for dst in order.iter() {
for ins in bb_succ.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst: d2, inputs } = ins {
if d2 == dst {
if let Some((_, val)) = inputs.iter().find(|(pred, _)| pred == bb_id) {
self.push_value_if_known_or_param(builder, val);
cnt += 1;
}
}
}
}
}
}
if cnt > 0 { builder.ensure_block_params_i64(target_index, cnt); }
n = cnt;
}
builder.jump_with_args(target_index, n);
} else {
@ -100,20 +354,72 @@ impl LowerCore {
builder.seal_block(target_index);
}
_ => {
self.try_emit(builder, term);
self.try_emit(builder, term, *bb_id);
}
}
}
}
builder.end_function();
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
let succs = succ_phi_order.len();
eprintln!("[JIT] cfg: blocks={} phi_succ={} (phi_min={})", bb_ids.len(), succs, enable_phi_min);
if enable_phi_min {
let mut total_phi_slots: usize = 0;
let mut total_phi_b1_slots: usize = 0;
for (succ, order) in succ_phi_order.iter() {
let mut preds_set: std::collections::BTreeSet<i64> = std::collections::BTreeSet::new();
let mut phi_lines: Vec<String> = Vec::new();
if let Some(bb_succ) = func.blocks.get(succ) {
for ins in bb_succ.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
// collect preds for block-level summary
for (pred, _) in inputs.iter() { preds_set.insert(pred.0 as i64); }
// build detailed mapping text: dst<-pred:val,...
let mut pairs: Vec<String> = Vec::new();
for (pred, val) in inputs.iter() {
pairs.push(format!("{}:{}", pred.0, val.0));
}
// Heuristics: boolean PHI if (1) pre-analysis marked it, or
// (2) all inputs look boolean-like (from bool producers or 0/1 const), or
// (3) used as a branch condition somewhere.
let used_as_branch = func.blocks.values().any(|bbx| {
if let Some(MirInstruction::Branch { condition, .. }) = &bbx.terminator { condition == dst } else { false }
});
let is_b1 = self.bool_phi_values.contains(dst)
|| inputs.iter().all(|(_, v)| {
self.bool_values.contains(v) || self.known_i64.get(v).map(|&iv| iv == 0 || iv == 1).unwrap_or(false)
})
|| used_as_branch;
let tag = if is_b1 { " (b1)" } else { "" };
phi_lines.push(format!(" dst v{}{} <- {}", dst.0, tag, pairs.join(", ")));
total_phi_slots += 1;
if is_b1 { total_phi_b1_slots += 1; }
}
}
}
let preds_list: Vec<String> = preds_set.into_iter().map(|p| p.to_string()).collect();
eprintln!("[JIT] phi: bb={} slots={} preds={}", succ.0, order.len(), preds_list.join("|"));
for ln in phi_lines { eprintln!("[JIT]{}", ln); }
}
eprintln!("[JIT] phi_summary: total_slots={} b1_slots={}", total_phi_slots, total_phi_b1_slots);
}
}
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 self.phi_values.contains(id) {
// Minimal PHI: read current block param
b.push_block_param_i64();
// Multi-PHI: find the param index for this phi in the current block
// We don't have the current block id here; rely on builder's current block context and our stored index being positional.
// As an approximation, prefer position 0 if unknown.
let pos = self.phi_param_index.iter().find_map(|((_, vid), idx)| if vid == id { Some(*idx) } else { None }).unwrap_or(0);
// Use b1 loader for boolean PHIs when enabled
if crate::jit::config::current().native_bool && self.bool_phi_values.contains(id) {
b.push_block_param_b1_at(pos);
} else {
b.push_block_param_i64_at(pos);
}
return;
}
if let Some(pidx) = self.param_index.get(id).copied() {
@ -131,6 +437,7 @@ impl LowerCore {
instr,
I::Const { .. }
| I::Copy { .. }
| I::Cast { .. }
| I::BinOp { .. }
| I::Compare { .. }
| I::Jump { .. }
@ -142,9 +449,16 @@ impl LowerCore {
if supported { self.covered += 1; } else { self.unsupported += 1; }
}
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction) {
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId) {
use crate::mir::MirInstruction as I;
match instr {
I::Cast { dst, value, target_type: _ } => {
// Minimal cast footing: materialize source when param/known
// Bool→Int: rely on producers (compare) and branch/b1 loaders; here we just reuse integer path
self.push_value_if_known_or_param(b, value);
// Track known i64 if source known
if let Some(v) = self.known_i64.get(value).copied() { self.known_i64.insert(*dst, v); }
}
I::Const { dst, value } => match value {
ConstValue::Integer(i) => {
b.emit_const_i64(*i);
@ -155,6 +469,8 @@ impl LowerCore {
let iv = if *bv { 1 } else { 0 };
b.emit_const_i64(iv);
self.known_i64.insert(*dst, iv);
// Mark this value as boolean producer
self.bool_values.insert(*dst);
}
ConstValue::String(_) | ConstValue::Null | ConstValue::Void => {
// leave unsupported for now
@ -166,6 +482,8 @@ impl LowerCore {
if let Some(pidx) = self.param_index.get(src).copied() {
b.emit_param_i64(pidx);
}
// Propagate boolean classification through Copy
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 } => {
@ -208,6 +526,8 @@ impl LowerCore {
CompareOp::Ge => CmpKind::Ge,
};
b.emit_compare(kind);
// Mark the last dst (compare produces a boolean)
if let MirInstruction::Compare { dst, .. } = instr { self.bool_values.insert(*dst); }
}
I::Jump { .. } => b.emit_jump(),
I::Branch { .. } => b.emit_branch(),
@ -215,53 +535,146 @@ impl LowerCore {
if let Some(v) = value { self.push_value_if_known_or_param(b, v); }
b.emit_return()
}
I::Phi { .. } => {
// Minimal PHI: load current block param as value (i64)
b.push_block_param_i64();
I::Store { value, ptr } => {
// Minimal lowering: materialize value if known/param and store to a local slot keyed by ptr
self.push_value_if_known_or_param(b, value);
let slot = *self.local_index.entry(*ptr).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.ensure_local_i64(slot);
b.store_local_i64(slot);
}
I::Load { dst: _, ptr } => {
// Minimal lowering: load from local slot keyed by ptr, default 0 if unset
let slot = *self.local_index.entry(*ptr).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.ensure_local_i64(slot);
b.load_local_i64(slot);
}
I::Phi { dst, .. } => {
// Minimal PHI: load current block param; b1 when classified boolean
let pos = self.phi_param_index.get(&(cur_bb, *dst)).copied().unwrap_or(0);
if self.bool_phi_values.contains(dst) {
b.push_block_param_b1_at(pos);
} else {
b.push_block_param_i64_at(pos);
}
}
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);
if let Some(pidx) = self.param_index.get(array).copied() {
// Handle-based: push handle value from param, then index
b.emit_param_i64(pidx);
b.emit_const_i64(idx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_GET_H, 2, true);
} else {
// Fallback to index-based (param index unknown)
let arr_idx = -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);
if let Some(pidx) = self.param_index.get(array).copied() {
b.emit_param_i64(pidx);
b.emit_const_i64(idx);
b.emit_const_i64(val);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_SET_H, 3, false);
} else {
let arr_idx = -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());
if let Some(pidx) = self.param_index.get(array).copied() {
// Handle-based generic length: supports ArrayBox and StringBox
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, dst.is_some());
} else {
let arr_idx = -1;
b.emit_const_i64(arr_idx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
}
}
"isEmpty" | "empty" => {
if let Some(pidx) = self.param_index.get(array).copied() {
b.emit_param_i64(pidx);
// returns i64 0/1
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, 1, dst.is_some());
}
}
"push" => {
// argc=2: (array, value)
// argc=2: (array_handle, 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);
if let Some(pidx) = self.param_index.get(array).copied() {
b.emit_param_i64(pidx);
b.emit_const_i64(val);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H, 2, false);
} else {
let arr_idx = -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());
// MapBox.size(): argc=1 (map_handle)
if let Some(pidx) = self.param_index.get(array).copied() {
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE_H, 1, dst.is_some());
} else {
let map_idx = -1;
b.emit_const_i64(map_idx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE, 1, dst.is_some());
}
}
"get" => {
// MapBox.get(key): (map_handle, key_i64)
if let Some(pidx) = self.param_index.get(array).copied() {
let key = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
b.emit_param_i64(pidx);
b.emit_const_i64(key);
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_GET_H, 2, dst.is_some());
}
}
"set" => {
// MapBox.set(key, value): (map_handle, key_i64, val_i64) — PoC: integer-only
if let Some(pidx) = self.param_index.get(array).copied() {
let key = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
let val = args.get(1).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
b.emit_param_i64(pidx);
b.emit_const_i64(key);
b.emit_const_i64(val);
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SET_H, 3, false);
}
}
"charCodeAt" => {
// String.charCodeAt(index)
if let Some(pidx) = self.param_index.get(array).copied() {
let idx = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
b.emit_param_i64(pidx);
b.emit_const_i64(idx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, 2, dst.is_some());
}
}
"has" => {
// MapBox.has(key_i64) -> 0/1
if let Some(pidx) = self.param_index.get(array).copied() {
let key = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
b.emit_param_i64(pidx);
b.emit_const_i64(key);
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_HAS_H, 2, dst.is_some());
}
}
_ => {}
}
@ -271,3 +684,66 @@ impl LowerCore {
}
}
}
/// Emit a simple DOT graph for a MIR function's CFG.
/// Includes block ids, successor edges, and PHI counts per block when phi_min is enabled.
pub fn dump_cfg_dot(func: &crate::mir::MirFunction, path: &str, phi_min: bool) -> std::io::Result<()> {
use std::io::Write;
let mut out = String::new();
out.push_str(&format!("digraph \"{}\" {{\n", func.signature.name));
out.push_str(" node [shape=box, fontsize=10];\n");
// Derive simple bool sets: compare dsts are bool; phi of all-bool inputs are bool
let mut bool_values: std::collections::HashSet<crate::mir::ValueId> = std::collections::HashSet::new();
for (_bb_id, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::Compare { dst, .. } = ins { bool_values.insert(*dst); }
}
}
let mut bool_phi: std::collections::HashSet<crate::mir::ValueId> = std::collections::HashSet::new();
if phi_min {
for (_bb_id, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
if !inputs.is_empty() && inputs.iter().all(|(_, v)| bool_values.contains(v)) {
bool_phi.insert(*dst);
}
}
}
}
}
// Sort blocks for deterministic output
let mut bb_ids: Vec<_> = func.blocks.keys().copied().collect();
bb_ids.sort_by_key(|b| b.0);
// Emit nodes with labels
for bb_id in bb_ids.iter() {
let bb = func.blocks.get(bb_id).unwrap();
let phi_count = bb.instructions.iter().filter(|ins| matches!(ins, crate::mir::MirInstruction::Phi { .. })).count();
let phi_b1_count = bb.instructions.iter().filter(|ins| match ins { crate::mir::MirInstruction::Phi { dst, .. } => bool_phi.contains(dst), _ => false }).count();
let mut label = format!("bb{}", bb_id.0);
if phi_min && phi_count > 0 {
if phi_b1_count > 0 { label = format!("{}\\nphi:{} (b1:{})", label, phi_count, phi_b1_count); }
else { label = format!("{}\\nphi:{}", label, phi_count); }
}
if *bb_id == func.entry_block { label = format!("{}\\nENTRY", label); }
out.push_str(&format!(" n{} [label=\"{}\"];\n", bb_id.0, label));
}
// Emit edges based on terminators
for bb_id in bb_ids.iter() {
let bb = func.blocks.get(bb_id).unwrap();
if let Some(term) = &bb.terminator {
match term {
crate::mir::MirInstruction::Jump { target } => {
out.push_str(&format!(" n{} -> n{};\n", bb_id.0, target.0));
}
crate::mir::MirInstruction::Branch { then_bb, else_bb, .. } => {
// Branch condition is boolean (b1)
out.push_str(&format!(" n{} -> n{} [label=\"then cond:b1\"];\n", bb_id.0, then_bb.0));
out.push_str(&format!(" n{} -> n{} [label=\"else cond:b1\"];\n", bb_id.0, else_bb.0));
}
_ => {}
}
}
}
out.push_str("}\n");
std::fs::write(path, out)
}

View File

@ -9,11 +9,17 @@ pub struct JitManager {
hits: HashMap<String, u32>,
compiled: HashMap<String, u64>,
engine: crate::jit::engine::JitEngine,
exec_ok: u64,
exec_trap: u64,
// Per-function lowering stats (accumulated)
func_phi_total: HashMap<String, u64>,
func_phi_b1: HashMap<String, u64>,
func_ret_bool_hint: HashMap<String, u64>,
}
impl JitManager {
pub fn new(threshold: u32) -> Self {
Self { threshold, hits: HashMap::new(), compiled: HashMap::new(), engine: crate::jit::engine::JitEngine::new() }
Self { threshold, hits: HashMap::new(), compiled: HashMap::new(), engine: crate::jit::engine::JitEngine::new(), exec_ok: 0, exec_trap: 0, func_phi_total: HashMap::new(), func_phi_b1: HashMap::new(), func_ret_bool_hint: HashMap::new() }
}
pub fn record_entry(&mut self, func: &str) {
@ -35,6 +41,9 @@ impl JitManager {
if self.should_jit(func) {
if let Some(handle) = self.engine.compile_function(func, mir) {
self.mark_compiled(func, handle);
// Record per-function lower stats captured by engine
let (phi_t, phi_b1, ret_b) = self.engine.last_lower_stats();
self.record_lower_stats(func, phi_t, phi_b1, ret_b);
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
eprintln!("[JIT] compiled {} -> handle={}", func, handle);
}
@ -47,12 +56,59 @@ impl JitManager {
pub fn is_compiled(&self, func: &str) -> bool { self.compiled.contains_key(func) }
pub fn handle_of(&self, func: &str) -> Option<u64> { self.compiled.get(func).copied() }
// --- Stats accessors for unified reporting ---
pub fn sites(&self) -> usize { self.hits.len() }
pub fn compiled_count(&self) -> usize { self.compiled.len() }
pub fn total_hits(&self) -> u64 { self.hits.values().map(|v| *v as u64).sum() }
pub fn exec_ok_count(&self) -> u64 { self.exec_ok }
pub fn exec_trap_count(&self) -> u64 { self.exec_trap }
// --- Per-function stats ---
pub fn record_lower_stats(&mut self, func: &str, phi_total: u64, phi_b1: u64, ret_bool_hint: bool) {
if phi_total > 0 { *self.func_phi_total.entry(func.to_string()).or_insert(0) += phi_total; }
if phi_b1 > 0 { *self.func_phi_b1.entry(func.to_string()).or_insert(0) += phi_b1; }
if ret_bool_hint { *self.func_ret_bool_hint.entry(func.to_string()).or_insert(0) += 1; }
}
pub fn per_function_stats(&self) -> Vec<(String, u64, u64, u64, u32, bool, u64)> {
// name, phi_total, phi_b1, ret_bool_hint, hits, compiled, handle
let mut names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
names.extend(self.hits.keys().cloned());
names.extend(self.func_phi_total.keys().cloned());
names.extend(self.func_phi_b1.keys().cloned());
names.extend(self.func_ret_bool_hint.keys().cloned());
let mut out = Vec::new();
for name in names {
let phi_t = self.func_phi_total.get(&name).copied().unwrap_or(0);
let phi_b1 = self.func_phi_b1.get(&name).copied().unwrap_or(0);
let rb = self.func_ret_bool_hint.get(&name).copied().unwrap_or(0);
let hits = self.hits.get(&name).copied().unwrap_or(0);
let compiled = self.compiled.contains_key(&name);
let handle = self.compiled.get(&name).copied().unwrap_or(0);
out.push((name, phi_t, phi_b1, rb, hits, compiled, handle));
}
out
}
/// Return top-N hot functions by hits, with compiled flag and handle
pub fn top_hits(&self, n: usize) -> Vec<(String, u32, bool, u64)> {
let mut v: Vec<(&String, &u32)> = self.hits.iter().collect();
v.sort_by(|a, b| b.1.cmp(a.1));
v.into_iter()
.take(n)
.map(|(k, h)| {
let compiled = self.compiled.contains_key(k);
let handle = self.compiled.get(k).copied().unwrap_or(0);
(k.clone(), *h, compiled, handle)
})
.collect()
}
pub fn print_summary(&self) {
if std::env::var("NYASH_JIT_STATS").ok().as_deref() != Some("1") { return; }
let sites = self.hits.len();
let total_hits: u64 = self.hits.values().map(|v| *v as u64).sum();
let compiled = self.compiled.len();
eprintln!("[JIT] sites={} compiled={} hits_total={}", sites, compiled, total_hits);
eprintln!("[JIT] sites={} compiled={} hits_total={} exec_ok={} exec_trap={}", sites, compiled, total_hits, self.exec_ok, self.exec_trap);
// Top 5 hot functions
let mut v: Vec<(&String, &u32)> = self.hits.iter().collect();
v.sort_by(|a, b| b.1.cmp(a.1));
@ -78,17 +134,27 @@ impl JitManager {
}
/// 10_c: execute compiled function if present (stub: empty args). Returns Some(VMValue) if JIT path was taken.
pub fn execute_compiled(&self, func: &str, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
pub fn execute_compiled(&mut self, func: &str, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
if let Some(h) = self.handle_of(func) {
// Expose current args to hostcall shims
crate::jit::rt::set_current_args(args);
// Expose args to both legacy VM hostcalls and new JIT ABI TLS
crate::jit::rt::set_legacy_vm_args(args);
let jit_args = crate::jit::abi::adapter::to_jit_values(args);
crate::jit::rt::set_current_jit_args(&jit_args);
let t0 = std::time::Instant::now();
let out = self.engine.execute_handle(h, args);
// Begin handle scope so temporary handles are reclaimed after the call
crate::jit::rt::handles::begin_scope();
let out = self.engine.execute_handle(h, &jit_args);
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
let dt = t0.elapsed();
eprintln!("[JIT] exec_time_ms={} for {}", dt.as_millis(), func);
}
return out;
let res = match out {
Some(v) => { self.exec_ok = self.exec_ok.saturating_add(1); Some(crate::jit::abi::adapter::from_jit_value(v)) }
None => { self.exec_trap = self.exec_trap.saturating_add(1); None }
};
// Clear handles created during this call
crate::jit::rt::handles::end_scope_clear();
return res;
}
None
}

View File

@ -5,3 +5,8 @@ pub mod engine;
pub mod lower;
pub mod r#extern;
pub mod rt;
pub mod abi;
pub mod config;
pub mod policy;
pub mod events;
pub mod hostcall_registry;

42
src/jit/policy.rs Normal file
View File

@ -0,0 +1,42 @@
//! JIT Policy (Box-First): centralizes runtime decisions
//!
//! Minimal v0:
//! - read_only: if true, deny write-effects in jit-direct and other independent paths
//! - hostcall_whitelist: symbolic names allowed (future use)
use once_cell::sync::OnceCell;
use std::sync::RwLock;
#[derive(Debug, Clone, Default)]
pub struct JitPolicy {
pub read_only: bool,
pub hostcall_whitelist: Vec<String>,
}
impl JitPolicy {
pub fn from_env() -> Self {
let ro = std::env::var("NYASH_JIT_READ_ONLY").ok().as_deref() == Some("1");
// Comma-separated hostcall names
let hc = std::env::var("NYASH_JIT_HOSTCALL_WHITELIST").ok().map(|s| {
s.split(',').map(|t| t.trim().to_string()).filter(|s| !s.is_empty()).collect::<Vec<_>>()
}).unwrap_or_default();
Self { read_only: ro, hostcall_whitelist: hc }
}
}
static GLOBAL: OnceCell<RwLock<JitPolicy>> = OnceCell::new();
pub fn current() -> JitPolicy {
if let Some(l) = GLOBAL.get() {
if let Ok(g) = l.read() { return g.clone(); }
}
JitPolicy::from_env()
}
pub fn set_current(p: JitPolicy) {
if let Some(l) = GLOBAL.get() {
if let Ok(mut w) = l.write() { *w = p; return; }
}
let _ = GLOBAL.set(RwLock::new(p));
}

View File

@ -1,25 +1,140 @@
use std::cell::RefCell;
use crate::backend::vm::VMValue;
use crate::jit::abi::JitValue;
// Legacy TLS for hostcalls that still expect VMValue — keep for compatibility
thread_local! {
static CURRENT_ARGS: RefCell<Vec<VMValue>> = RefCell::new(Vec::new());
static LEGACY_VM_ARGS: RefCell<Vec<VMValue>> = RefCell::new(Vec::new());
}
pub fn set_current_args(args: &[VMValue]) {
CURRENT_ARGS.with(|cell| {
pub fn set_legacy_vm_args(args: &[VMValue]) {
LEGACY_VM_ARGS.with(|cell| {
let mut v = cell.borrow_mut();
v.clear();
v.extend_from_slice(args);
});
}
pub fn with_args<F, R>(f: F) -> R
pub fn with_legacy_vm_args<F, R>(f: F) -> R
where
F: FnOnce(&[VMValue]) -> R,
{
CURRENT_ARGS.with(|cell| {
LEGACY_VM_ARGS.with(|cell| {
let v = cell.borrow();
f(&v)
})
}
// New TLS for independent JIT ABI values
thread_local! {
static CURRENT_JIT_ARGS: RefCell<Vec<JitValue>> = RefCell::new(Vec::new());
}
pub fn set_current_jit_args(args: &[JitValue]) {
CURRENT_JIT_ARGS.with(|cell| {
let mut v = cell.borrow_mut();
v.clear();
v.extend_from_slice(args);
});
}
pub fn with_jit_args<F, R>(f: F) -> R
where
F: FnOnce(&[JitValue]) -> R,
{
CURRENT_JIT_ARGS.with(|cell| {
let v = cell.borrow();
f(&v)
})
}
// === JIT runtime counters (minimal) ===
use std::sync::atomic::{AtomicU64, Ordering};
static B1_NORM_COUNT: AtomicU64 = AtomicU64::new(0);
static RET_BOOL_HINT_COUNT: AtomicU64 = AtomicU64::new(0);
static PHI_TOTAL_SLOTS: AtomicU64 = AtomicU64::new(0);
static PHI_B1_SLOTS: AtomicU64 = AtomicU64::new(0);
pub fn b1_norm_inc(delta: u64) { B1_NORM_COUNT.fetch_add(delta, Ordering::Relaxed); }
pub fn b1_norm_get() -> u64 { B1_NORM_COUNT.load(Ordering::Relaxed) }
pub fn ret_bool_hint_inc(delta: u64) { RET_BOOL_HINT_COUNT.fetch_add(delta, Ordering::Relaxed); }
pub fn ret_bool_hint_get() -> u64 { RET_BOOL_HINT_COUNT.load(Ordering::Relaxed) }
pub fn phi_total_inc(delta: u64) { PHI_TOTAL_SLOTS.fetch_add(delta, Ordering::Relaxed); }
pub fn phi_total_get() -> u64 { PHI_TOTAL_SLOTS.load(Ordering::Relaxed) }
pub fn phi_b1_inc(delta: u64) { PHI_B1_SLOTS.fetch_add(delta, Ordering::Relaxed); }
pub fn phi_b1_get() -> u64 { PHI_B1_SLOTS.load(Ordering::Relaxed) }
// === 10.7c PoC: JIT Handle Registry (thread-local) ===
use std::collections::HashMap;
use std::sync::Arc;
pub mod handles {
use super::*;
thread_local! {
static REG: RefCell<HandleRegistry> = RefCell::new(HandleRegistry::new());
static CREATED: RefCell<Vec<u64>> = RefCell::new(Vec::new());
static SCOPES: RefCell<Vec<usize>> = RefCell::new(Vec::new());
}
struct HandleRegistry {
next: u64,
map: HashMap<u64, Arc<dyn crate::box_trait::NyashBox>>, // BoxRef-compatible
}
impl HandleRegistry {
fn new() -> Self { Self { next: 1, map: HashMap::new() } }
fn to_handle(&mut self, obj: Arc<dyn crate::box_trait::NyashBox>) -> u64 {
// Reuse existing handle if already present (pointer equality check)
// For PoC simplicity, always assign new handle
let h = self.next;
self.next = self.next.saturating_add(1);
self.map.insert(h, obj);
if std::env::var("NYASH_JIT_HANDLE_DEBUG").ok().as_deref() == Some("1") {
eprintln!("[JIT][handle] new h={}", h);
}
h
}
fn get(&self, h: u64) -> Option<Arc<dyn crate::box_trait::NyashBox>> { self.map.get(&h).cloned() }
#[allow(dead_code)]
fn drop_handle(&mut self, h: u64) { self.map.remove(&h); }
#[allow(dead_code)]
fn clear(&mut self) { self.map.clear(); self.next = 1; }
}
pub fn to_handle(obj: Arc<dyn crate::box_trait::NyashBox>) -> u64 {
let h = REG.with(|cell| cell.borrow_mut().to_handle(obj));
CREATED.with(|c| c.borrow_mut().push(h));
h
}
pub fn get(h: u64) -> Option<Arc<dyn crate::box_trait::NyashBox>> {
REG.with(|cell| cell.borrow().get(h))
}
#[allow(dead_code)]
pub fn clear() { REG.with(|cell| cell.borrow_mut().clear()); }
pub fn len() -> usize { REG.with(|cell| cell.borrow().map.len()) }
// Scope management: track and clear handles created within a JIT call
pub fn begin_scope() {
CREATED.with(|c| {
let cur_len = c.borrow().len();
SCOPES.with(|s| s.borrow_mut().push(cur_len));
});
}
pub fn end_scope_clear() {
let start = SCOPES.with(|s| s.borrow_mut().pop()).unwrap_or(0);
let to_drop: Vec<u64> = CREATED.with(|c| {
let mut v = c.borrow_mut();
let slice = v[start..].to_vec();
v.truncate(start);
slice
});
REG.with(|cell| {
let mut reg = cell.borrow_mut();
for h in to_drop { reg.map.remove(&h); }
});
}
}