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:
@ -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);
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
39
src/cli.rs
39
src/cli.rs
@ -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");
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -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")]
|
||||
{
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
36
src/jit/extern/collections.rs
vendored
36
src/jit/extern/collections.rs
vendored
@ -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);
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
225
src/jit/lower/extern_thunks.rs
Normal file
225
src/jit/lower/extern_thunks.rs
Normal 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
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
//! Lowering entry for JIT
|
||||
pub mod core;
|
||||
pub mod builder;
|
||||
pub mod extern_thunks;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
Reference in New Issue
Block a user