docs: AOT/ネイティブコンパイル情報をexecution-backends.mdに追加

- 4つ目の実行方式としてAOT(Ahead-of-Time)コンパイルを文書化
- MIR→WASM→.cwasm のコンパイルパイプラインを説明
- wasm-backend featureでのビルド方法を明記
- 現在の実装状況(完全なスタンドアロン実行ファイルはTODO)を記載
- CLAUDE.mdのWASM説明も3種類(Rust→WASM、Nyash→WASM、Nyash→AOT)に更新
- CURRENT_TASK.mdにPhase 10.9/10.10の完了項目を追加

ChatGPT5さんのAOT試行に対応した適切なドキュメント配置を実施

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-08-29 02:05:39 +09:00
parent d67f27f4b8
commit 25fbebd650
34 changed files with 1631 additions and 353 deletions

View File

@ -559,6 +559,10 @@ impl VM {
self.current_function = Some(function.signature.name.clone());
// Phase 10_a: JIT profiling (function entry)
if let Some(jm) = &mut self.jit_manager {
// Allow threshold to react to env updates (e.g., DebugConfigBox.apply at runtime)
if let Ok(s) = std::env::var("NYASH_JIT_THRESHOLD") {
if let Ok(t) = s.parse::<u32>() { if t > 0 { jm.set_threshold(t); } }
}
jm.record_entry(&function.signature.name);
// Try compile if hot (no-op for now, returns fake handle)
let _ = jm.maybe_compile(&function.signature.name, function);

View File

@ -6,10 +6,13 @@ pub struct DebugConfigBox {
pub base: BoxBase,
// toggles/paths (staged until apply())
pub jit_events: bool,
pub jit_events_compile: bool,
pub jit_events_runtime: bool,
pub jit_stats: bool,
pub jit_stats_json: bool,
pub jit_dump: bool,
pub jit_dot_path: Option<String>,
pub jit_events_path: Option<String>,
}
impl DebugConfigBox {
@ -17,16 +20,21 @@ impl DebugConfigBox {
Self {
base: BoxBase::new(),
jit_events: std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1"),
jit_events_compile: std::env::var("NYASH_JIT_EVENTS_COMPILE").ok().as_deref() == Some("1"),
jit_events_runtime: std::env::var("NYASH_JIT_EVENTS_RUNTIME").ok().as_deref() == Some("1"),
jit_stats: std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1"),
jit_stats_json: std::env::var("NYASH_JIT_STATS_JSON").ok().as_deref() == Some("1"),
jit_dump: std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1"),
jit_dot_path: std::env::var("NYASH_JIT_DOT").ok().filter(|s| !s.is_empty()),
jit_events_path: std::env::var("NYASH_JIT_EVENTS_PATH").ok().filter(|s| !s.is_empty()),
}
}
pub fn set_flag(&mut self, name: &str, on: bool) -> Box<dyn NyashBox> {
match name {
"jit_events" => self.jit_events = on,
"jit_events_compile" => self.jit_events_compile = on,
"jit_events_runtime" => self.jit_events_runtime = on,
"jit_stats" => self.jit_stats = on,
"jit_stats_json" => self.jit_stats_json = on,
"jit_dump" => self.jit_dump = on,
@ -38,6 +46,7 @@ impl DebugConfigBox {
pub fn set_path(&mut self, name: &str, value: &str) -> Box<dyn NyashBox> {
match name {
"jit_dot" | "jit_dot_path" => self.jit_dot_path = Some(value.to_string()),
"jit_events_path" => self.jit_events_path = Some(value.to_string()),
_ => return Box::new(StringBox::new(format!("Unknown path: {}", name)))
}
Box::new(VoidBox::new())
@ -46,6 +55,8 @@ impl DebugConfigBox {
pub fn get_flag(&self, name: &str) -> Box<dyn NyashBox> {
let v = match name {
"jit_events" => self.jit_events,
"jit_events_compile" => self.jit_events_compile,
"jit_events_runtime" => self.jit_events_runtime,
"jit_stats" => self.jit_stats,
"jit_stats_json" => self.jit_stats_json,
"jit_dump" => self.jit_dump,
@ -57,6 +68,7 @@ impl DebugConfigBox {
pub fn get_path(&self, name: &str) -> Box<dyn NyashBox> {
let v = match name {
"jit_dot" | "jit_dot_path" => self.jit_dot_path.clone().unwrap_or_default(),
"jit_events_path" => self.jit_events_path.clone().unwrap_or_default(),
_ => String::new(),
};
Box::new(StringBox::new(v))
@ -65,18 +77,31 @@ impl DebugConfigBox {
pub fn apply(&self) -> Box<dyn NyashBox> {
let setb = |k: &str, v: bool| { if v { std::env::set_var(k, "1"); } else { std::env::remove_var(k); } };
setb("NYASH_JIT_EVENTS", self.jit_events);
setb("NYASH_JIT_EVENTS_COMPILE", self.jit_events_compile);
setb("NYASH_JIT_EVENTS_RUNTIME", self.jit_events_runtime);
setb("NYASH_JIT_STATS", self.jit_stats);
setb("NYASH_JIT_STATS_JSON", self.jit_stats_json);
setb("NYASH_JIT_DUMP", self.jit_dump);
if let Some(p) = &self.jit_dot_path { std::env::set_var("NYASH_JIT_DOT", p); }
else { std::env::remove_var("NYASH_JIT_DOT"); }
if let Some(p) = &self.jit_events_path { std::env::set_var("NYASH_JIT_EVENTS_PATH", p); }
else { std::env::remove_var("NYASH_JIT_EVENTS_PATH"); }
// If any events are enabled and threshold is not set, default to 1 so lowering runs early
if (self.jit_events || self.jit_events_compile || self.jit_events_runtime)
&& std::env::var("NYASH_JIT_THRESHOLD").is_err()
{
std::env::set_var("NYASH_JIT_THRESHOLD", "1");
}
Box::new(VoidBox::new())
}
pub fn summary(&self) -> Box<dyn NyashBox> {
let s = format!(
"jit_events={} jit_stats={} jit_stats_json={} jit_dump={} jit_dot={}",
self.jit_events, self.jit_stats, self.jit_stats_json, self.jit_dump,
self.jit_dot_path.clone().unwrap_or_else(|| "<none>".to_string())
"jit_events={} jit_events_compile={} jit_events_runtime={} jit_stats={} jit_stats_json={} jit_dump={} jit_dot={} jit_events_path={}",
self.jit_events, self.jit_events_compile, self.jit_events_runtime,
self.jit_stats, self.jit_stats_json, self.jit_dump,
self.jit_dot_path.clone().unwrap_or_else(|| "<none>".to_string()),
self.jit_events_path.clone().unwrap_or_else(|| "<none>".to_string())
);
Box::new(StringBox::new(s))
}

View File

@ -31,6 +31,10 @@ pub struct CliConfig {
pub jit_stats: bool,
pub jit_stats_json: bool,
pub jit_dump: bool,
pub jit_events: bool,
pub jit_events_compile: bool,
pub jit_events_runtime: bool,
pub jit_events_path: Option<String>,
pub jit_threshold: Option<u32>,
pub jit_phi_min: bool,
pub jit_hostcall: bool,
@ -186,6 +190,30 @@ impl CliConfig {
.help("Dump JIT lowering summary (NYASH_JIT_DUMP=1)")
.action(clap::ArgAction::SetTrue)
)
.arg(
Arg::new("jit-events")
.long("jit-events")
.help("Emit JIT events as JSONL (NYASH_JIT_EVENTS=1)")
.action(clap::ArgAction::SetTrue)
)
.arg(
Arg::new("jit-events-compile")
.long("jit-events-compile")
.help("Emit compile-time (lower) JIT events (NYASH_JIT_EVENTS_COMPILE=1)")
.action(clap::ArgAction::SetTrue)
)
.arg(
Arg::new("jit-events-runtime")
.long("jit-events-runtime")
.help("Emit runtime JIT events (NYASH_JIT_EVENTS_RUNTIME=1)")
.action(clap::ArgAction::SetTrue)
)
.arg(
Arg::new("jit-events-path")
.long("jit-events-path")
.value_name("FILE")
.help("Write JIT events JSONL to file (NYASH_JIT_EVENTS_PATH)")
)
.arg(
Arg::new("jit-threshold")
.long("jit-threshold")
@ -265,6 +293,10 @@ impl CliConfig {
jit_stats: matches.get_flag("jit-stats"),
jit_stats_json: matches.get_flag("jit-stats-json"),
jit_dump: matches.get_flag("jit-dump"),
jit_events: matches.get_flag("jit-events"),
jit_events_compile: matches.get_flag("jit-events-compile"),
jit_events_runtime: matches.get_flag("jit-events-runtime"),
jit_events_path: matches.get_one::<String>("jit-events-path").cloned(),
jit_threshold: matches.get_one::<String>("jit-threshold").and_then(|s| s.parse::<u32>().ok()),
jit_phi_min: matches.get_flag("jit-phi-min"),
jit_hostcall: matches.get_flag("jit-hostcall"),
@ -323,12 +355,19 @@ mod tests {
jit_stats: false,
jit_stats_json: false,
jit_dump: false,
jit_events: false,
jit_events_compile: false,
jit_events_runtime: false,
jit_events_path: None,
jit_threshold: None,
jit_phi_min: false,
jit_hostcall: false,
jit_handle_debug: false,
jit_native_f64: false,
jit_native_bool: false,
emit_cfg: None,
jit_only: false,
jit_direct: false,
};
assert_eq!(config.backend, "interpreter");

View File

@ -24,11 +24,15 @@ 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());
// Respect explicit dump flag, but also treat a non-empty NYASH_JIT_DOT path
// as an implicit request to enable dump (so Box/CLI/env stay consistent).
let dump_flag = getb("NYASH_JIT_DUMP")
|| std::env::var("NYASH_JIT_DOT").ok().map(|s| !s.is_empty()).unwrap_or(false);
Self {
exec: getb("NYASH_JIT_EXEC"),
stats: getb("NYASH_JIT_STATS"),
stats_json: getb("NYASH_JIT_STATS_JSON"),
dump: getb("NYASH_JIT_DUMP"),
dump: dump_flag,
threshold,
phi_min: getb("NYASH_JIT_PHI_MIN"),
hostcall: getb("NYASH_JIT_HOSTCALL"),

View File

@ -89,6 +89,14 @@ impl JitEngine {
}
}
}
// If lowering left any unsupported instructions, do not register a closure.
// This preserves VM semantics until coverage is complete for the function.
if lower.unsupported > 0 {
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") || cfg_now.dump {
eprintln!("[JIT] skip compile for {}: unsupported={} (>0)", func_name, lower.unsupported);
}
return None;
}
// Create a handle and register an executable closure if available
#[cfg(feature = "cranelift-jit")]
{

View File

@ -6,11 +6,20 @@
use serde::Serialize;
fn should_emit() -> bool {
fn base_emit_enabled() -> bool {
std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1")
|| std::env::var("NYASH_JIT_EVENTS_PATH").is_ok()
}
fn should_emit_lower() -> bool {
// Compile-phase events are opt-in to avoid noisy logs by default.
std::env::var("NYASH_JIT_EVENTS_COMPILE").ok().as_deref() == Some("1")
}
fn should_emit_runtime() -> bool {
base_emit_enabled() || std::env::var("NYASH_JIT_EVENTS_RUNTIME").ok().as_deref() == Some("1")
}
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| {
@ -35,8 +44,26 @@ struct Event<'a, T: Serialize> {
}
pub fn emit<T: Serialize>(kind: &str, function: &str, handle: Option<u64>, ms: Option<u128>, extra: T) {
if !should_emit() { return; }
if !base_emit_enabled() { return; }
let ev = Event { kind, function, handle, ms, extra };
if let Ok(s) = serde_json::to_string(&ev) { write_line(&s); }
}
fn emit_any(kind: &str, function: &str, handle: Option<u64>, ms: Option<u128>, extra: serde_json::Value) {
let ev = Event { kind, function, handle, ms, extra };
if let Ok(s) = serde_json::to_string(&ev) { write_line(&s); }
}
/// Emit an event during lowering (compile-time planning). Adds phase="lower".
pub fn emit_lower(mut extra: serde_json::Value, kind: &str, function: &str) {
if !should_emit_lower() { return; }
if let serde_json::Value::Object(ref mut map) = extra { map.insert("phase".into(), serde_json::Value::String("lower".into())); }
emit_any(kind, function, None, None, extra);
}
/// Emit an event during runtime execution. Adds phase="execute".
pub fn emit_runtime(mut extra: serde_json::Value, kind: &str, function: &str) {
if !should_emit_runtime() { return; }
if let serde_json::Value::Object(ref mut map) = extra { map.insert("phase".into(), serde_json::Value::String("execute".into())); }
emit_any(kind, function, None, None, extra);
}

View File

@ -66,18 +66,18 @@ pub fn array_set(args: &[VMValue]) -> VMValue {
if crate::jit::policy::current().read_only &&
!crate::jit::policy::current().hostcall_whitelist.iter().any(|s| s == SYM_ARRAY_SET)
{
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"fallback", "reason":"policy_denied_mutating"})
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"fallback", "reason":"policy_denied_mutating"}),
"hostcall", "<jit>"
);
return VMValue::Integer(0);
}
if let (Some(arr), Some(VMValue::Integer(idx)), Some(value)) = (as_array(args), args.get(1), args.get(2)) {
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
let res = arr.set(Box::new(IntegerBox::new(*idx)), val_box);
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"allow", "argc":3, "arg_types":["Handle","I64","Handle"]})
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"allow", "argc":3, "arg_types":["Handle","I64","Handle"]}),
"hostcall", "<jit>"
);
return VMValue::from_nyash_box(res);
}
@ -88,18 +88,18 @@ pub fn array_push(args: &[VMValue]) -> VMValue {
if crate::jit::policy::current().read_only &&
!crate::jit::policy::current().hostcall_whitelist.iter().any(|s| s == SYM_ARRAY_PUSH)
{
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"fallback", "reason":"policy_denied_mutating"})
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"fallback", "reason":"policy_denied_mutating"}),
"hostcall", "<jit>"
);
return VMValue::Integer(0);
}
if let (Some(arr), Some(value)) = (as_array(args), args.get(1)) {
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
let res = arr.push(val_box);
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]})
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]}),
"hostcall", "<jit>"
);
return VMValue::from_nyash_box(res);
}
@ -118,9 +118,9 @@ pub fn map_set(args: &[VMValue]) -> VMValue {
if crate::jit::policy::current().read_only &&
!crate::jit::policy::current().hostcall_whitelist.iter().any(|s| s == SYM_MAP_SET)
{
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": SYM_MAP_SET, "decision":"fallback", "reason":"policy_denied_mutating"})
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_MAP_SET, "decision":"fallback", "reason":"policy_denied_mutating"}),
"hostcall", "<jit>"
);
return VMValue::Integer(0);
}
@ -128,9 +128,9 @@ pub fn map_set(args: &[VMValue]) -> VMValue {
let key_box: Box<dyn NyashBox> = key.to_nyash_box();
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
let out = map.set(key_box, val_box);
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": SYM_MAP_SET, "decision":"allow", "argc":3, "arg_types":["Handle","Handle","Handle"]})
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_MAP_SET, "decision":"allow", "argc":3, "arg_types":["Handle","Handle","Handle"]}),
"hostcall", "<jit>"
);
return VMValue::from_nyash_box(out);
}

View File

@ -23,9 +23,14 @@ fn ensure_default() {
let mut r = Registry::default();
// Read-only defaults
for s in [
"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"
"nyash.array.len_h",
"nyash.any.length_h",
"nyash.any.is_empty_h",
"nyash.map.size_h",
"nyash.map.get_h",
"nyash.map.has_h",
"nyash.string.charCodeAt_h",
"nyash.array.get_h",
] { r.ro.insert(s.to_string()); }
// Mutating defaults
for s in [
@ -45,6 +50,13 @@ fn ensure_default() {
r.sig.entry("nyash.map.size_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
r.sig.entry("nyash.array.get_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::Handle });
r.sig.entry("nyash.array.len_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
// String helpers
r.sig.entry("nyash.string.charCodeAt_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::I64 });
// Any helpers (length/is_empty)
r.sig.entry("nyash.any.length_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
r.sig.entry("nyash.any.is_empty_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
// Map.has(handle, i64) -> i64(0/1)
r.sig.entry("nyash.map.has_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::I64 });
let _ = REG.set(RwLock::new(r));
}

View File

@ -138,15 +138,15 @@ 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_math_sin_f64(x: f64) -> f64 { x.sin() }
use super::extern_thunks::{ nyash_math_sin_f64, nyash_math_cos_f64, nyash_math_abs_f64, nyash_math_min_f64, nyash_math_max_f64, };
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_math_cos_f64(x: f64) -> f64 { x.cos() }
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_math_abs_f64(x: f64) -> f64 { x.abs() }
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_math_min_f64(a: f64, b: f64) -> f64 { a.min(b) }
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_math_max_f64(a: f64, b: f64) -> f64 { a.max(b) }
#[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
@ -234,258 +234,18 @@ 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 {
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ARRAY_LEN_H, "decision":"allow", "argc":1, "arg_types":["Handle"]})
);
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 with whitelist
use crate::jit::hostcall_registry::{classify, HostcallKind};
let sym = crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H;
let pol = crate::jit::policy::current();
let wh = pol.hostcall_whitelist;
match (classify(sym), pol.read_only && !wh.iter().any(|s| s == sym)) {
(HostcallKind::Mutating, true) => {
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"})
);
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", "argc":2, "arg_types":["Handle","I64"]})
);
return 0;
}
}
0
}
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_array_get_h(handle: u64, idx: i64) -> i64 {
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ARRAY_GET_H, "decision":"allow", "argc":2, "arg_types":["Handle","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;
let pol = crate::jit::policy::current();
let wh = pol.hostcall_whitelist;
if classify(sym) == HostcallKind::Mutating && pol.read_only && !wh.iter().any(|s| s == sym) {
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"})
);
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", "argc":3, "arg_types":["Handle","I64","I64"]})
);
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", "argc":1, "arg_types":["Handle"]})
);
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", "argc":2, "arg_types":["Handle","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);
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_get_hh(map_h: u64, key_h: u64) -> i64 {
// Emit allow event for visibility
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_GET_HH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]})
);
let map_arc = crate::jit::rt::handles::get(map_h);
let key_arc = crate::jit::rt::handles::get(key_h);
if let (Some(mobj), Some(kobj)) = (map_arc, key_arc) {
if let Some(map) = mobj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
let key_box: Box<dyn crate::box_trait::NyashBox> = kobj.share_box();
let val = map.get(key_box);
// Register result into handle table and return handle id
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(val);
let h = crate::jit::rt::handles::to_handle(arc);
return h as i64;
}
}
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;
let pol = crate::jit::policy::current();
let wh = pol.hostcall_whitelist;
if classify(sym) == HostcallKind::Mutating && pol.read_only && !wh.iter().any(|s| s == sym) {
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"})
);
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", "argc":3, "arg_types":["Handle","I64","I64"]})
);
return 0;
}
}
0
}
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_map_has_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_HAS_H, "decision":"allow", "argc":2, "arg_types":["Handle","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", "argc":1, "arg_types":["Handle"]})
);
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", "argc":1, "arg_types":["Handle"]})
);
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", "argc":2, "arg_types":["Handle","I64"]})
);
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 {

View File

@ -642,16 +642,16 @@ impl LowerCore {
match method.as_str() {
"len" | "length" => {
if let Some(pidx) = self.param_index.get(array).copied() {
crate::jit::events::emit(
"hostcall","<jit>",None,None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]})
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]}),
"hostcall","<jit>"
);
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, dst.is_some());
} else {
crate::jit::events::emit(
"hostcall","<jit>",None,None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]})
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}),
"hostcall","<jit>"
);
let arr_idx = -1;
b.emit_const_i64(arr_idx);
@ -693,19 +693,16 @@ impl LowerCore {
match check_signature(&sym, &observed_kinds) {
Ok(()) => {
// allow: record decision; execution remains on VM for now (thin bridge)
crate::jit::events::emit(
"hostcall",
"<jit>",
None,
None,
serde_json::json!({
"id": sym,
"decision": "allow",
"reason": "sig_ok",
"argc": observed.len(),
"arg_types": arg_types
})
);
crate::jit::events::emit_lower(
serde_json::json!({
"id": sym,
"decision": "allow",
"reason": "sig_ok",
"argc": observed.len(),
"arg_types": arg_types
}),
"hostcall","<jit>"
);
// If native f64 is enabled, emit a typed hostcall to math extern
if crate::jit::config::current().native_f64 {
let (symbol, arity) = match method.as_str() {
@ -771,10 +768,10 @@ impl LowerCore {
// returns i64 0/1
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, 1, dst.is_some());
} else {
crate::jit::events::emit(
"hostcall","<jit>",None,None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]})
);
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}),
"hostcall","<jit>"
);
}
}
"push" => {
@ -785,23 +782,23 @@ impl LowerCore {
let wh = &pol.hostcall_whitelist;
let sym = crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H;
let allowed = !pol.read_only || wh.iter().any(|s| s == sym);
crate::jit::events::emit(
"hostcall","<jit>",None,None,
crate::jit::events::emit_lower(
serde_json::json!({
"id": sym,
"decision": if allowed {"allow"} else {"fallback"},
"reason": if allowed {"sig_ok"} else {"policy_denied_mutating"},
"argc": 2,
"arg_types": ["Handle","I64"]
})
}),
"hostcall","<jit>"
);
b.emit_param_i64(pidx);
b.emit_const_i64(val);
b.emit_host_call(sym, 2, false);
} else {
crate::jit::events::emit(
"hostcall","<jit>",None,None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H, "decision":"fallback", "reason":"receiver_not_param", "argc":2, "arg_types":["Handle","I64"]})
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H, "decision":"fallback", "reason":"receiver_not_param", "argc":2, "arg_types":["Handle","I64"]}),
"hostcall","<jit>"
);
let arr_idx = -1;
b.emit_const_i64(arr_idx);
@ -812,16 +809,16 @@ impl LowerCore {
"size" => {
// MapBox.size(): argc=1 (map_handle)
if let Some(pidx) = self.param_index.get(array).copied() {
crate::jit::events::emit(
"hostcall","<jit>",None,None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]})
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]}),
"hostcall","<jit>"
);
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE_H, 1, dst.is_some());
} else {
crate::jit::events::emit(
"hostcall","<jit>",None,None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]})
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}),
"hostcall","<jit>"
);
let map_idx = -1;
b.emit_const_i64(map_idx);
@ -870,18 +867,15 @@ impl LowerCore {
crate::jit::r#extern::collections::SYM_MAP_GET_H
};
// Emit allow event
crate::jit::events::emit(
"hostcall",
"<jit>",
None,
None,
crate::jit::events::emit_lower(
serde_json::json!({
"id": event_id,
"decision": "allow",
"reason": "sig_ok",
"argc": observed_kinds.len(),
"arg_types": arg_types
})
}),
"hostcall","<jit>"
);
// If key is i64, emit hostcall; if key is Handle and also a param, emit HH variant; otherwise fallback
if matches!(key_kind, crate::jit::hostcall_registry::ArgKind::I64) {
@ -901,18 +895,15 @@ impl LowerCore {
}
Err(reason) => {
// Signature mismatch - log and fallback
crate::jit::events::emit(
"hostcall",
"<jit>",
None,
None,
crate::jit::events::emit_lower(
serde_json::json!({
"id": canonical,
"decision": "fallback",
"reason": reason,
"argc": observed_kinds.len(),
"arg_types": arg_types
})
}),
"hostcall","<jit>"
);
// No emission; VM path will handle
}
@ -937,18 +928,15 @@ impl LowerCore {
let arg_types: Vec<&'static str> = observed_kinds.iter().map(|k| match k { crate::jit::hostcall_registry::ArgKind::I64 => "I64", crate::jit::hostcall_registry::ArgKind::F64 => "F64", crate::jit::hostcall_registry::ArgKind::Handle => "Handle" }).collect();
let sym = "nyash.map.get_h";
let decision = match crate::jit::hostcall_registry::check_signature(sym, &observed_kinds) { Ok(()) => ("fallback", "receiver_not_param"), Err(reason) => ("fallback", reason) };
crate::jit::events::emit(
"hostcall",
"<jit>",
None,
None,
crate::jit::events::emit_lower(
serde_json::json!({
"id": sym,
"decision": decision.0,
"reason": decision.1,
"argc": observed_kinds.len(),
"arg_types": arg_types
})
}),
"hostcall","<jit>"
);
// no-op: VM側が処理する
}
@ -962,24 +950,24 @@ impl LowerCore {
let wh = &pol.hostcall_whitelist;
let sym = crate::jit::r#extern::collections::SYM_MAP_SET_H;
let allowed = !pol.read_only || wh.iter().any(|s| s == sym);
crate::jit::events::emit(
"hostcall","<jit>",None,None,
crate::jit::events::emit_lower(
serde_json::json!({
"id": sym,
"decision": if allowed {"allow"} else {"fallback"},
"reason": if allowed {"sig_ok"} else {"policy_denied_mutating"},
"argc": 3,
"arg_types": ["Handle","I64","I64"]
})
}),
"hostcall","<jit>"
);
b.emit_param_i64(pidx);
b.emit_const_i64(key);
b.emit_const_i64(val);
b.emit_host_call(sym, 3, false);
} else {
crate::jit::events::emit(
"hostcall","<jit>",None,None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SET_H, "decision":"fallback", "reason":"receiver_not_param", "argc":3, "arg_types":["Handle","I64","I64"]})
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SET_H, "decision":"fallback", "reason":"receiver_not_param", "argc":3, "arg_types":["Handle","I64","I64"]}),
"hostcall","<jit>"
);
}
}
@ -987,17 +975,17 @@ impl LowerCore {
// 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);
crate::jit::events::emit(
"hostcall","<jit>",None,None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"allow", "reason":"sig_ok", "argc":2, "arg_types":["Handle","I64"]})
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"allow", "reason":"sig_ok", "argc":2, "arg_types":["Handle","I64"]}),
"hostcall","<jit>"
);
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());
} else {
crate::jit::events::emit(
"hostcall","<jit>",None,None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"fallback", "reason":"receiver_not_param", "argc":2, "arg_types":["Handle","I64"]})
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"fallback", "reason":"receiver_not_param", "argc":2, "arg_types":["Handle","I64"]}),
"hostcall","<jit>"
);
}
}

View File

@ -0,0 +1,225 @@
//! Handle-based extern thunks used by the JIT runtime path.
//! Moved out of builder.rs to keep files small and responsibilities clear.
#[cfg(feature = "cranelift-jit")]
use crate::jit::events;
#[cfg(feature = "cranelift-jit")]
use crate::jit::r#extern::collections as c;
// ---- Math (native f64) ----
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_math_sin_f64(x: f64) -> f64 { x.sin() }
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_math_cos_f64(x: f64) -> f64 { x.cos() }
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_math_abs_f64(x: f64) -> f64 { x.abs() }
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_math_min_f64(a: f64, b: f64) -> f64 { a.min(b) }
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_math_max_f64(a: f64, b: f64) -> f64 { a.max(b) }
// ---- Array (handle) ----
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_array_len_h(handle: u64) -> i64 {
events::emit_runtime(serde_json::json!({"id": c::SYM_ARRAY_LEN_H, "decision":"allow", "argc":1, "arg_types":["Handle"]}), "hostcall", "<jit>");
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")]
pub(super) extern "C" fn nyash_array_get_h(handle: u64, idx: i64) -> i64 {
events::emit_runtime(serde_json::json!({"id": c::SYM_ARRAY_GET_H, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]}), "hostcall", "<jit>");
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")]
pub(super) 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>() {
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")]
pub(super) extern "C" fn nyash_array_set_h(handle: u64, idx: i64, val: i64) -> i64 {
use crate::jit::hostcall_registry::{classify, HostcallKind};
let sym = c::SYM_ARRAY_SET_H;
let pol = crate::jit::policy::current();
let wh = pol.hostcall_whitelist;
if classify(sym) == HostcallKind::Mutating && pol.read_only && !wh.iter().any(|s| s == sym) {
events::emit_runtime(serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"}), "hostcall", "<jit>");
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)));
events::emit_runtime(serde_json::json!({"id": sym, "decision":"allow", "argc":3, "arg_types":["Handle","I64","I64"]}), "hostcall", "<jit>");
return 0;
}
}
0
}
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_array_push_h(handle: u64, val: i64) -> i64 {
use crate::jit::hostcall_registry::{classify, HostcallKind};
let sym = c::SYM_ARRAY_PUSH_H;
let pol = crate::jit::policy::current();
let wh = pol.hostcall_whitelist;
if classify(sym) == HostcallKind::Mutating && pol.read_only && !wh.iter().any(|s| s == sym) {
events::emit_runtime(serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"}), "hostcall", "<jit>");
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));
events::emit_runtime(serde_json::json!({"id": sym, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]}), "hostcall", "<jit>");
return 0;
}
}
0
}
// ---- Map (handle) ----
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_map_size_h(handle: u64) -> i64 {
events::emit_runtime(serde_json::json!({"id": c::SYM_MAP_SIZE_H, "decision":"allow", "argc":1, "arg_types":["Handle"]}), "hostcall", "<jit>");
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")]
pub(super) extern "C" fn nyash_map_get_h(handle: u64, key: i64) -> i64 {
events::emit_runtime(serde_json::json!({"id": c::SYM_MAP_GET_H, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]}), "hostcall", "<jit>");
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")]
pub(super) extern "C" fn nyash_map_get_hh(map_h: u64, key_h: u64) -> i64 {
events::emit_runtime(serde_json::json!({"id": c::SYM_MAP_GET_HH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]}), "hostcall", "<jit>");
let map_arc = crate::jit::rt::handles::get(map_h);
let key_arc = crate::jit::rt::handles::get(key_h);
if let (Some(mobj), Some(kobj)) = (map_arc, key_arc) {
if let Some(map) = mobj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
let key_box: Box<dyn crate::box_trait::NyashBox> = kobj.share_box();
let val = map.get(key_box);
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(val);
let h = crate::jit::rt::handles::to_handle(arc);
return h as i64;
}
}
0
}
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_map_set_h(handle: u64, key: i64, val: i64) -> i64 {
use crate::jit::hostcall_registry::{classify, HostcallKind};
let sym = c::SYM_MAP_SET_H;
let pol = crate::jit::policy::current();
let wh = pol.hostcall_whitelist;
if classify(sym) == HostcallKind::Mutating && pol.read_only && !wh.iter().any(|s| s == sym) {
events::emit_runtime(serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"}), "hostcall", "<jit>");
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);
events::emit_runtime(serde_json::json!({"id": sym, "decision":"allow", "argc":3, "arg_types":["Handle","I64","I64"]}), "hostcall", "<jit>");
return 0;
}
}
0
}
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_map_has_h(handle: u64, key: i64) -> i64 {
events::emit_runtime(serde_json::json!({"id": c::SYM_MAP_HAS_H, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]}), "hostcall", "<jit>");
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);
let is_present = !val.as_any().is::<crate::box_trait::VoidBox>();
return if is_present { 1 } else { 0 };
}
}
0
}
// ---- Any helpers ----
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_any_length_h(handle: u64) -> i64 {
events::emit_runtime(serde_json::json!({"id": c::SYM_ANY_LEN_H, "decision":"allow", "argc":1, "arg_types":["Handle"]}), "hostcall", "<jit>");
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; }
}
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
return sb.value.len() as i64;
}
}
0
}
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_any_is_empty_h(handle: u64) -> i64 {
events::emit_runtime(serde_json::json!({"id": c::SYM_ANY_IS_EMPTY_H, "decision":"allow", "argc":1, "arg_types":["Handle"]}), "hostcall", "<jit>");
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 Ok(items) = arr.items.read() { return if items.is_empty() { 1 } else { 0 }; }
}
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
return if sb.value.is_empty() { 1 } else { 0 };
}
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
}
// ---- String ----
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_string_charcode_at_h(handle: u64, idx: i64) -> i64 {
events::emit_runtime(serde_json::json!({"id": c::SYM_STRING_CHARCODE_AT_H, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]}), "hostcall", "<jit>");
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 s.as_bytes()[i] as i64; } else { return -1; }
}
}
-1
}

View File

@ -1,3 +1,4 @@
//! Lowering entry for JIT
pub mod core;
pub mod builder;
pub mod extern_thunks;

View File

@ -22,6 +22,8 @@ impl JitManager {
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 set_threshold(&mut self, t: u32) { self.threshold = t.max(1); }
pub fn record_entry(&mut self, func: &str) {
let c = self.hits.entry(func.to_string()).or_insert(0);
*c = c.saturating_add(1);
@ -155,7 +157,20 @@ impl JitManager {
let vmv = crate::jit::boundary::CallBoundaryBox::to_vm(ret_ty, v);
Some(vmv)
}
None => { self.exec_trap = self.exec_trap.saturating_add(1); None }
None => {
self.exec_trap = self.exec_trap.saturating_add(1);
// Emit a minimal trap event for observability (runtime only)
let dt = t0.elapsed();
crate::jit::events::emit_runtime(
serde_json::json!({
"kind": "trap", // redundant with wrapper kind but explicit here for clarity
"reason": "jit_execute_failed",
"ms": dt.as_millis()
}),
"trap", func
);
None
}
};
// Clear handles created during this call
crate::jit::rt::handles::end_scope_clear();

View File

@ -66,6 +66,11 @@ impl NyashRunner {
}
// Optional: JIT controls via CLI flags (centralized)
{
// CLI opt-in for JSONL events
if self.config.jit_events { std::env::set_var("NYASH_JIT_EVENTS", "1"); }
if self.config.jit_events_compile { std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1"); }
if self.config.jit_events_runtime { std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1"); }
if let Some(ref p) = self.config.jit_events_path { std::env::set_var("NYASH_JIT_EVENTS_PATH", p); }
let mut jc = nyash_rust::jit::config::JitConfig::from_env();
jc.exec |= self.config.jit_exec;
jc.stats |= self.config.jit_stats;
@ -77,10 +82,11 @@ impl NyashRunner {
jc.handle_debug |= self.config.jit_handle_debug;
jc.native_f64 |= self.config.jit_native_f64;
jc.native_bool |= self.config.jit_native_bool;
// If events are enabled and no threshold is provided, force threshold=1 so lowering runs and emits events
if std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1") && jc.threshold.is_none() {
jc.threshold = Some(1);
}
// If observability is enabled and no threshold is provided, force threshold=1 so lowering runs and emits events
let events_on = std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1")
|| std::env::var("NYASH_JIT_EVENTS_COMPILE").ok().as_deref() == Some("1")
|| std::env::var("NYASH_JIT_EVENTS_RUNTIME").ok().as_deref() == Some("1");
if events_on && jc.threshold.is_none() { jc.threshold = Some(1); }
if self.config.jit_only { std::env::set_var("NYASH_JIT_ONLY", "1"); }
// Apply runtime capability probe (e.g., disable b1 ABI if unsupported)
let caps = nyash_rust::jit::config::probe_capabilities();