AI協調開発研究ドキュメントの完成と Phase 10.9-β 進捗

【AI協調開発研究】
- AI二重化モデルの学術論文draft完成(workshop_paper_draft.md)
- 「隠れた危機」分析とbirthの原則哲学化
- TyEnv「唯一の真実」協調会話を保存・研究資料に統合
- papers管理構造の整備(wip/under-review/published分離)

【Phase 10.9-β HostCall進捗】
- JitConfigBox: relax_numeric フラグ追加(i64→f64コアーション制御)
- HostcallRegistryBox: 署名検証・白黒リスト・コアーション対応
- JitHostcallRegistryBox: Nyash側レジストリ操作API
- Lower統合: env直読 → jit::config::current() 参照に統一
- 数値緩和設定: NYASH_JIT_HOSTCALL_RELAX_NUMERIC/Config.set_flag

【検証サンプル拡充】
- math.sin/cos/abs/min/max 関数スタイル(examples/jit_math_function_style_*.nyash)
- 境界ケース: 署名不一致・コアーション許可・mutating拒否サンプル
- E2E実証: String.length→allow, Array.push→fallback, math関数の署名一致観測

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-08-28 12:09:09 +09:00
parent e54561e69f
commit 4e1b595796
133 changed files with 14202 additions and 622 deletions

View File

@ -33,6 +33,7 @@ impl JitConfigBox {
"native_bool" => cfg.native_bool = on,
"bool_abi" | "native_bool_abi" => cfg.native_bool_abi = on,
"ret_b1" | "ret_bool_b1" => cfg.ret_bool_b1 = on,
"relax_numeric" | "hostcall_relax_numeric" => cfg.relax_numeric = on,
_ => return Err(RuntimeError::InvalidOperation { message: format!("Unknown flag: {}", name) }),
}
Ok(Box::new(VoidBox::new()))
@ -51,6 +52,7 @@ impl JitConfigBox {
"native_bool" => cfg.native_bool,
"bool_abi" | "native_bool_abi" => cfg.native_bool_abi,
"ret_b1" | "ret_bool_b1" => cfg.ret_bool_b1,
"relax_numeric" | "hostcall_relax_numeric" => cfg.relax_numeric,
_ => return Err(RuntimeError::InvalidOperation { message: format!("Unknown flag: {}", name) }),
};
Ok(Box::new(BoolBox::new(b)))
@ -80,6 +82,7 @@ impl JitConfigBox {
"native_bool": cfg.native_bool,
"native_bool_abi": cfg.native_bool_abi,
"ret_bool_b1": cfg.ret_bool_b1,
"relax_numeric": cfg.relax_numeric,
});
Box::new(StringBox::new(val.to_string()))
}
@ -98,6 +101,7 @@ impl JitConfigBox {
if let Some(b) = v.get("native_bool").and_then(|x| x.as_bool()) { cfg.native_bool = b; }
if let Some(b) = v.get("native_bool_abi").and_then(|x| x.as_bool()) { cfg.native_bool_abi = b; }
if let Some(b) = v.get("ret_bool_b1").and_then(|x| x.as_bool()) { cfg.ret_bool_b1 = b; }
if let Some(b) = v.get("relax_numeric").and_then(|x| x.as_bool()) { cfg.relax_numeric = b; }
Ok(Box::new(VoidBox::new()))
}
pub fn apply(&self) -> Box<dyn NyashBox> {
@ -111,9 +115,9 @@ impl JitConfigBox {
pub fn summary(&self) -> Box<dyn NyashBox> {
let cfg = self.config.read().unwrap();
let s = format!(
"exec={} stats={} json={} dump={} thr={:?} phi_min={} hostcall={} hdbg={} f64={} bool={}",
"exec={} stats={} json={} dump={} thr={:?} phi_min={} hostcall={} hdbg={} f64={} bool={} relax_numeric={}",
cfg.exec, cfg.stats, cfg.stats_json, cfg.dump, cfg.threshold,
cfg.phi_min, cfg.hostcall, cfg.handle_debug, cfg.native_f64, cfg.native_bool
cfg.phi_min, cfg.hostcall, cfg.handle_debug, cfg.native_f64, cfg.native_bool, cfg.relax_numeric
);
Box::new(StringBox::new(s))
}

View File

@ -0,0 +1,37 @@
use crate::box_trait::{NyashBox, StringBox, BoolBox, VoidBox, BoxCore, BoxBase};
use std::any::Any;
#[derive(Debug, Clone)]
pub struct JitHostcallRegistryBox { base: BoxBase }
impl JitHostcallRegistryBox { pub fn new() -> Self { Self { base: BoxBase::new() } } }
impl BoxCore for JitHostcallRegistryBox {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { self.base.parent_type_id }
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "JitHostcallRegistryBox") }
fn as_any(&self) -> &dyn Any { self }
fn as_any_mut(&mut self) -> &mut dyn Any { self }
}
impl NyashBox for JitHostcallRegistryBox {
fn to_string_box(&self) -> StringBox {
let (ro, mu) = crate::jit::hostcall_registry::snapshot();
let payload = serde_json::json!({ "readonly": ro, "mutating": mu });
StringBox::new(payload.to_string())
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox { BoolBox::new(other.as_any().is::<JitHostcallRegistryBox>()) }
fn type_name(&self) -> &'static str { "JitHostcallRegistryBox" }
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(Self { base: self.base.clone() }) }
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
}
impl JitHostcallRegistryBox {
pub fn add_readonly(&self, sym: &str) -> Box<dyn NyashBox> { crate::jit::hostcall_registry::add_readonly(sym); Box::new(VoidBox::new()) }
pub fn add_mutating(&self, sym: &str) -> Box<dyn NyashBox> { crate::jit::hostcall_registry::add_mutating(sym); Box::new(VoidBox::new()) }
pub fn set_from_csv(&self, ro_csv: &str, mu_csv: &str) -> Box<dyn NyashBox> { crate::jit::hostcall_registry::set_from_csv(ro_csv, mu_csv); Box::new(VoidBox::new()) }
pub fn add_signature(&self, sym: &str, args_csv: &str, ret_str: &str) -> Box<dyn NyashBox> {
let ok = crate::jit::hostcall_registry::set_signature_csv(sym, args_csv, ret_str);
if ok { Box::new(VoidBox::new()) } else { Box::new(StringBox::new("Invalid signature")) }
}
}

View File

@ -78,6 +78,7 @@ pub mod jit_config_box;
pub mod jit_stats_box;
pub mod jit_policy_box;
pub mod jit_events_box;
pub mod jit_hostcall_registry_box;
// Web専用Box群ブラウザ環境でのみ利用可能
#[cfg(target_arch = "wasm32")]
@ -112,6 +113,7 @@ pub use jit_config_box::JitConfigBox;
pub use jit_stats_box::JitStatsBox;
pub use jit_policy_box::JitPolicyBox;
pub use jit_events_box::JitEventsBox;
pub use jit_hostcall_registry_box::JitHostcallRegistryBox;
// EguiBoxの再エクスポート非WASM環境のみ
#[cfg(all(feature = "gui", not(target_arch = "wasm32")))]

View File

@ -17,6 +17,7 @@ pub struct JitConfig {
pub native_bool: bool, // NYASH_JIT_NATIVE_BOOL
pub native_bool_abi: bool, // NYASH_JIT_ABI_B1 (experimental)
pub ret_bool_b1: bool, // NYASH_JIT_RET_B1 (footing; currently returns i64 0/1)
pub relax_numeric: bool, // NYASH_JIT_HOSTCALL_RELAX_NUMERIC (i64->f64 coercion)
}
impl JitConfig {
@ -36,6 +37,7 @@ impl JitConfig {
native_bool: getb("NYASH_JIT_NATIVE_BOOL"),
native_bool_abi: getb("NYASH_JIT_ABI_B1"),
ret_bool_b1: getb("NYASH_JIT_RET_B1"),
relax_numeric: getb("NYASH_JIT_HOSTCALL_RELAX_NUMERIC"),
}
}
@ -55,6 +57,7 @@ impl JitConfig {
setb("NYASH_JIT_NATIVE_BOOL", self.native_bool);
setb("NYASH_JIT_ABI_B1", self.native_bool_abi);
setb("NYASH_JIT_RET_B1", self.ret_bool_b1);
setb("NYASH_JIT_HOSTCALL_RELAX_NUMERIC", self.relax_numeric);
}
}

View File

@ -1,18 +1,154 @@
//! Minimal hostcall registry (v0): classify symbols as read-only or mutating
use once_cell::sync::OnceCell;
use std::collections::HashSet;
use std::collections::HashMap;
use std::sync::RwLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HostcallKind { ReadOnly, Mutating }
#[derive(Debug, Default)]
struct Registry {
ro: HashSet<String>,
mu: HashSet<String>,
sig: HashMap<String, Signature>,
}
static REG: OnceCell<RwLock<Registry>> = OnceCell::new();
fn ensure_default() {
if REG.get().is_some() { return; }
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"
] { r.ro.insert(s.to_string()); }
// Mutating defaults
for s in [
"nyash.array.push_h", "nyash.array.set_h", "nyash.map.set_h"
] { r.mu.insert(s.to_string()); }
// Signatures (v0): register known symbols with simple arg/ret kinds
// math.* thin bridge: f64 signatures only (allow when args match exactly)
r.sig.insert("nyash.math.sin".to_string(), Signature { args: vec![ArgKind::F64], ret: ArgKind::F64 });
r.sig.insert("nyash.math.cos".to_string(), Signature { args: vec![ArgKind::F64], ret: ArgKind::F64 });
r.sig.insert("nyash.math.abs".to_string(), Signature { args: vec![ArgKind::F64], ret: ArgKind::F64 });
r.sig.insert("nyash.math.min".to_string(), Signature { args: vec![ArgKind::F64, ArgKind::F64], ret: ArgKind::F64 });
r.sig.insert("nyash.math.max".to_string(), Signature { args: vec![ArgKind::F64, ArgKind::F64], ret: ArgKind::F64 });
// Collections (handle-based)
r.sig.insert("nyash.map.get_h".to_string(), Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::Handle });
r.sig.insert("nyash.map.size_h".to_string(), Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
r.sig.insert("nyash.array.get_h".to_string(), Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::Handle });
r.sig.insert("nyash.array.len_h".to_string(), Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
let _ = REG.set(RwLock::new(r));
}
pub fn classify(symbol: &str) -> HostcallKind {
match symbol {
// Read-only (safe under read_only policy)
"nyash.array.len_h" | "nyash.any.length_h" | "nyash.any.is_empty_h" |
"nyash.map.size_h" | "nyash.map.get_h" | "nyash.string.charCodeAt_h" |
"nyash.array.get_h" => HostcallKind::ReadOnly,
// Mutating
"nyash.array.push_h" | "nyash.array.set_h" | "nyash.map.set_h" => HostcallKind::Mutating,
// Default to read-only to be permissive in v0
_ => HostcallKind::ReadOnly,
ensure_default();
if let Some(lock) = REG.get() {
if let Ok(g) = lock.read() {
if g.ro.contains(symbol) { return HostcallKind::ReadOnly; }
if g.mu.contains(symbol) { return HostcallKind::Mutating; }
}
}
// Default to read-only to be permissive in v0
HostcallKind::ReadOnly
}
pub fn add_readonly(symbol: &str) {
ensure_default();
if let Some(lock) = REG.get() {
if let Ok(mut w) = lock.write() { w.ro.insert(symbol.to_string()); }
}
}
pub fn add_mutating(symbol: &str) {
ensure_default();
if let Some(lock) = REG.get() {
if let Ok(mut w) = lock.write() { w.mu.insert(symbol.to_string()); }
}
}
pub fn set_from_csv(ro_csv: &str, mu_csv: &str) {
ensure_default();
if let Some(lock) = REG.get() {
if let Ok(mut w) = lock.write() {
w.ro.clear(); w.mu.clear();
for s in ro_csv.split(',') { let t = s.trim(); if !t.is_empty() { w.ro.insert(t.to_string()); } }
for s in mu_csv.split(',') { let t = s.trim(); if !t.is_empty() { w.mu.insert(t.to_string()); } }
}
}
}
pub fn snapshot() -> (Vec<String>, Vec<String>) {
ensure_default();
if let Some(lock) = REG.get() {
if let Ok(g) = lock.read() {
let mut ro: Vec<String> = g.ro.iter().cloned().collect(); ro.sort();
let mut mu: Vec<String> = g.mu.iter().cloned().collect(); mu.sort();
return (ro, mu);
}
}
(Vec::new(), Vec::new())
}
// ==== Signature (v0 scaffolding) ====
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArgKind { I64, F64, Handle }
#[derive(Debug, Clone)]
pub struct Signature {
pub args: Vec<ArgKind>,
pub ret: ArgKind,
}
fn parse_kind(s: &str) -> Option<ArgKind> {
match s.trim().to_ascii_lowercase().as_str() {
"i64" | "int" | "integer" => Some(ArgKind::I64),
"f64" | "float" => Some(ArgKind::F64),
"handle" | "h" => Some(ArgKind::Handle),
_ => None,
}
}
pub fn set_signature_csv(symbol: &str, args_csv: &str, ret_str: &str) -> bool {
ensure_default();
let mut ok = true;
let parsed: Vec<Option<ArgKind>> = args_csv
.split(',')
.filter(|t| !t.trim().is_empty())
.map(|t| parse_kind(t))
.collect();
let mut args: Vec<ArgKind> = Vec::new();
for p in parsed { if let Some(k) = p { args.push(k) } else { ok = false; } }
let ret = match parse_kind(ret_str) { Some(k) => k, None => { ok = false; ArgKind::I64 } };
if !ok { return false; }
let sig = Signature { args, ret };
if let Some(lock) = REG.get() {
if let Ok(mut w) = lock.write() { w.sig.insert(symbol.to_string(), sig); return true; }
}
false
}
pub fn get_signature(symbol: &str) -> Option<Signature> {
ensure_default();
REG.get().and_then(|lock| lock.read().ok()).and_then(|g| g.sig.get(symbol).cloned())
}
/// Check observed args against a registered signature.
/// - If no signature is registered for the symbol, returns Ok(()) to be permissive in v0.
/// - Returns Err("sig_mismatch") when arg length or kinds differ.
pub fn check_signature(symbol: &str, observed_args: &[ArgKind]) -> Result<(), &'static str> {
ensure_default();
if let Some(sig) = get_signature(symbol) {
if sig.args.len() != observed_args.len() { return Err("sig_mismatch"); }
let cfg_now = crate::jit::config::current();
let relax = cfg_now.relax_numeric || cfg_now.native_f64;
for (expected, observed) in sig.args.iter().zip(observed_args.iter()) {
if expected == observed { continue; }
// v0 coercion: allow I64 → F64 only when relaxed numeric is enabled
if relax && matches!(expected, ArgKind::F64) && matches!(observed, ArgKind::I64) { continue; }
return Err("sig_mismatch");
}
}
Ok(())
}

View File

@ -30,6 +30,8 @@ pub trait IRBuilder {
fn emit_return(&mut self);
/// Phase 10_d scaffolding: host-call emission (symbolic)
fn emit_host_call(&mut self, _symbol: &str, _argc: usize, _has_ret: bool) { }
/// Typed host-call emission: params kinds and return type hint (f64 when true)
fn emit_host_call_typed(&mut self, _symbol: &str, _params: &[ParamKind], _has_ret: bool, _ret_is_f64: bool) { }
// ==== Phase 10.7 (control-flow wiring, default no-op) ====
/// Optional: prepare N basic blocks and return their handles (0..N-1)
fn prepare_blocks(&mut self, _count: usize) { }
@ -95,6 +97,7 @@ impl IRBuilder for NoopBuilder {
fn emit_jump(&mut self) { self.branches += 1; }
fn emit_branch(&mut self) { self.branches += 1; }
fn emit_return(&mut self) { self.rets += 1; }
fn emit_host_call_typed(&mut self, _symbol: &str, _params: &[ParamKind], has_ret: bool, _ret_is_f64: bool) { if has_ret { self.consts += 1; } }
fn ensure_local_i64(&mut self, _index: usize) { /* no-op */ }
fn store_local_i64(&mut self, _index: usize) { self.consts += 1; }
fn load_local_i64(&mut self, _index: usize) { self.consts += 1; }
@ -135,6 +138,16 @@ 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() }
#[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
if arr_param_index < 0 { return 0; }
@ -222,6 +235,10 @@ 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; }
@ -236,7 +253,10 @@ extern "C" fn nyash_array_push_h(handle: u64, val: i64) -> i64 {
let sym = crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H;
match (classify(sym), crate::jit::policy::current().read_only) {
(HostcallKind::Mutating, true) => {
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"fallback"}));
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"})
);
return 0;
}
_ => {}
@ -245,7 +265,10 @@ extern "C" fn nyash_array_push_h(handle: u64, val: i64) -> i64 {
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"}));
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": sym, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]})
);
return 0;
}
}
@ -253,6 +276,10 @@ extern "C" fn nyash_array_push_h(handle: u64, val: i64) -> i64 {
}
#[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)));
@ -282,7 +309,10 @@ extern "C" fn nyash_array_set_h(handle: u64, idx: i64, val: i64) -> i64 {
use crate::jit::hostcall_registry::{classify, HostcallKind};
let sym = crate::jit::r#extern::collections::SYM_ARRAY_SET_H;
if classify(sym) == HostcallKind::Mutating && crate::jit::policy::current().read_only {
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"fallback"}));
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) {
@ -291,7 +321,10 @@ extern "C" fn nyash_array_set_h(handle: u64, idx: i64, val: i64) -> i64 {
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"}));
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": sym, "decision":"allow", "argc":3, "arg_types":["Handle","I64","I64"]})
);
return 0;
}
}
@ -299,7 +332,10 @@ extern "C" fn nyash_array_set_h(handle: u64, idx: i64, val: i64) -> i64 {
}
#[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"}));
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; }
@ -309,7 +345,10 @@ extern "C" fn nyash_map_size_h(handle: u64) -> i64 {
}
#[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"}));
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));
@ -324,7 +363,10 @@ extern "C" fn nyash_map_set_h(handle: u64, key: i64, val: i64) -> i64 {
use crate::jit::hostcall_registry::{classify, HostcallKind};
let sym = crate::jit::r#extern::collections::SYM_MAP_SET_H;
if classify(sym) == HostcallKind::Mutating && crate::jit::policy::current().read_only {
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"fallback"}));
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) {
@ -332,7 +374,10 @@ extern "C" fn nyash_map_set_h(handle: u64, key: i64, val: i64) -> i64 {
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"}));
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": sym, "decision":"allow", "argc":3, "arg_types":["Handle","I64","I64"]})
);
return 0;
}
}
@ -353,7 +398,10 @@ extern "C" fn nyash_map_has_h(handle: u64, key: i64) -> i64 {
}
#[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"}));
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>() {
@ -368,7 +416,10 @@ extern "C" fn nyash_any_length_h(handle: u64) -> i64 {
}
#[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"}));
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>() {
@ -387,7 +438,10 @@ extern "C" fn nyash_any_is_empty_h(handle: u64) -> i64 {
}
#[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"}));
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>() {
@ -790,6 +844,52 @@ impl IRBuilder for CraneliftBuilder {
fb.finalize();
}
fn emit_host_call_typed(&mut self, symbol: &str, params: &[ParamKind], has_ret: bool, ret_is_f64: bool) {
use cranelift_codegen::ir::{AbiParam, Signature, types};
use cranelift_frontend::FunctionBuilder;
use cranelift_module::{Linkage, Module};
// Pop values according to params length (right-to-left), then reverse
let mut args: Vec<cranelift_codegen::ir::Value> = Vec::new();
let take_n = params.len().min(self.value_stack.len());
for _ in 0..take_n { if let Some(v) = self.value_stack.pop() { args.push(v); } }
args.reverse();
// Build typed signature
let call_conv = self.module.isa().default_call_conv();
let mut sig = Signature::new(call_conv);
let abi_param_for_kind = |k: &ParamKind| {
match k {
ParamKind::I64 => AbiParam::new(types::I64),
ParamKind::F64 => AbiParam::new(types::F64),
ParamKind::B1 => {
// Map b1 to I64 unless native-b1 ABI is enabled; keep simple here
AbiParam::new(types::I64)
}
}
};
for k in params { sig.params.push(abi_param_for_kind(k)); }
if has_ret {
if ret_is_f64 { sig.returns.push(AbiParam::new(types::F64)); }
else { sig.returns.push(AbiParam::new(types::I64)); }
}
let func_id = self.module
.declare_function(symbol, Linkage::Import, &sig)
.expect("declare typed import failed");
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
let fref = self.module.declare_func_in_func(func_id, fb.func);
let call_inst = fb.ins().call(fref, &args);
if has_ret {
let results = fb.inst_results(call_inst).to_vec();
if let Some(v) = results.get(0).copied() { self.value_stack.push(v); }
}
fb.finalize();
}
// ==== Phase 10.7 block APIs ====
fn prepare_blocks(&mut self, count: usize) {
use cranelift_frontend::FunctionBuilder;
@ -1039,6 +1139,12 @@ impl CraneliftBuilder {
builder.symbol(c::SYM_MAP_GET, nyash_map_get as *const u8);
builder.symbol(c::SYM_MAP_SET, nyash_map_set as *const u8);
builder.symbol(c::SYM_MAP_SIZE, nyash_map_size as *const u8);
// Math f64 externs
builder.symbol("nyash.math.sin_f64", nyash_math_sin_f64 as *const u8);
builder.symbol("nyash.math.cos_f64", nyash_math_cos_f64 as *const u8);
builder.symbol("nyash.math.abs_f64", nyash_math_abs_f64 as *const u8);
builder.symbol("nyash.math.min_f64", nyash_math_min_f64 as *const u8);
builder.symbol("nyash.math.max_f64", nyash_math_max_f64 as *const u8);
// Handle-based symbols
builder.symbol(c::SYM_ARRAY_LEN_H, nyash_array_len_h as *const u8);
builder.symbol(c::SYM_ARRAY_GET_H, nyash_array_get_h as *const u8);

View File

@ -8,6 +8,8 @@ pub struct LowerCore {
pub covered: usize,
/// Minimal constant propagation for i64 to feed host-call args
known_i64: std::collections::HashMap<ValueId, i64>,
/// Minimal constant propagation for f64 (math.* signature checks)
known_f64: std::collections::HashMap<ValueId, f64>,
/// Parameter index mapping for ValueId
param_index: std::collections::HashMap<ValueId, usize>,
/// Track values produced by Phi (for minimal PHI path)
@ -18,6 +20,8 @@ pub struct LowerCore {
bool_values: std::collections::HashSet<ValueId>,
/// Track PHI destinations that are boolean (all inputs derived from bool_values)
bool_phi_values: std::collections::HashSet<ValueId>,
/// Track values that are FloatBox instances (for arg type classification)
float_box_values: std::collections::HashSet<ValueId>,
// Per-function statistics (last lowered)
last_phi_total: u64,
last_phi_b1: u64,
@ -28,7 +32,7 @@ pub struct LowerCore {
}
impl LowerCore {
pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), param_index: std::collections::HashMap::new(), phi_values: std::collections::HashSet::new(), phi_param_index: std::collections::HashMap::new(), bool_values: std::collections::HashSet::new(), bool_phi_values: std::collections::HashSet::new(), last_phi_total: 0, last_phi_b1: 0, last_ret_bool_hint_used: false, local_index: std::collections::HashMap::new(), next_local: 0 } }
pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), known_f64: std::collections::HashMap::new(), param_index: std::collections::HashMap::new(), phi_values: std::collections::HashSet::new(), phi_param_index: std::collections::HashMap::new(), bool_values: std::collections::HashSet::new(), bool_phi_values: std::collections::HashSet::new(), float_box_values: std::collections::HashSet::new(), last_phi_total: 0, last_phi_b1: 0, last_ret_bool_hint_used: false, local_index: std::collections::HashMap::new(), next_local: 0 } }
/// Get statistics for the last lowered function
pub fn last_stats(&self) -> (u64, u64, bool) { (self.last_phi_total, self.last_phi_b1, self.last_ret_bool_hint_used) }
@ -232,10 +236,22 @@ impl LowerCore {
} else {
builder.prepare_signature_i64(func.params.len(), true);
}
// Pre-scan FloatBox creations across all blocks for arg classification
self.float_box_values.clear();
for bb in bb_ids.iter() {
if let Some(block) = func.blocks.get(bb) {
for ins in block.instructions.iter() {
if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = ins { if box_type == "FloatBox" { self.float_box_values.insert(*dst); } }
if let crate::mir::MirInstruction::Copy { dst, src } = ins { if self.float_box_values.contains(src) { self.float_box_values.insert(*dst); } }
}
}
}
builder.begin_function(&func.signature.name);
// Iterate blocks in the sorted order to keep indices stable
self.phi_values.clear();
self.phi_param_index.clear();
self.float_box_values.clear();
for (idx, bb_id) in bb_ids.iter().enumerate() {
let bb = func.blocks.get(bb_id).unwrap();
builder.switch_to_block(idx);
@ -262,7 +278,10 @@ impl LowerCore {
}
for instr in bb.instructions.iter() {
self.cover_if_supported(instr);
self.try_emit(builder, instr, *bb_id);
self.try_emit(builder, instr, *bb_id, func);
// Track FloatBox creations for later arg classification
if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = instr { if box_type == "FloatBox" { self.float_box_values.insert(*dst); } }
if let crate::mir::MirInstruction::Copy { dst, src } = instr { if self.float_box_values.contains(src) { self.float_box_values.insert(*dst); } }
}
if let Some(term) = &bb.terminator {
self.cover_if_supported(term);
@ -353,10 +372,10 @@ impl LowerCore {
}
builder.seal_block(target_index);
}
_ => {
self.try_emit(builder, term, *bb_id);
}
_ => { /* other terminators handled via generic emission below */ }
}
// Also allow other terminators to be emitted if needed
self.try_emit(builder, term, *bb_id, func);
}
}
builder.end_function();
@ -443,28 +462,53 @@ impl LowerCore {
| I::Jump { .. }
| I::Branch { .. }
| I::Return { .. }
| I::BoxCall { .. }
| I::ArrayGet { .. }
| I::ArraySet { .. }
);
if supported { self.covered += 1; } else { self.unsupported += 1; }
}
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId) {
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId, func: &crate::mir::MirFunction) {
use crate::mir::MirInstruction as I;
match instr {
I::Cast { dst, value, target_type: _ } => {
I::NewBox { dst, box_type, args } => {
// Track boxed numeric literals to aid signature checks (FloatBox/IntegerBox)
if box_type == "FloatBox" {
if let Some(src) = args.get(0) {
if let Some(fv) = self.known_f64.get(src).copied() {
self.known_f64.insert(*dst, fv);
} else if let Some(iv) = self.known_i64.get(src).copied() {
self.known_f64.insert(*dst, iv as f64);
}
}
} else if box_type == "IntegerBox" {
if let Some(src) = args.get(0) {
if let Some(iv) = self.known_i64.get(src).copied() {
self.known_i64.insert(*dst, iv);
}
}
}
}
I::Cast { dst, value, target_type } => {
// Minimal cast footing: materialize source when param/known
// Bool→Int: rely on producers (compare) and branch/b1 loaders; here we just reuse integer path
self.push_value_if_known_or_param(b, value);
// Track known i64 if source known
if let Some(v) = self.known_i64.get(value).copied() { self.known_i64.insert(*dst, v); }
// Track known f64 for float casts
if matches!(target_type, crate::mir::MirType::Float) {
if let Some(iv) = self.known_i64.get(value).copied() {
self.known_f64.insert(*dst, iv as f64);
}
}
}
I::Const { dst, value } => match value {
ConstValue::Integer(i) => {
b.emit_const_i64(*i);
self.known_i64.insert(*dst, *i);
}
ConstValue::Float(f) => b.emit_const_f64(*f),
ConstValue::Float(f) => { b.emit_const_f64(*f); self.known_f64.insert(*dst, *f); }
ConstValue::Bool(bv) => {
let iv = if *bv { 1 } else { 0 };
b.emit_const_i64(iv);
@ -478,6 +522,7 @@ impl LowerCore {
},
I::Copy { dst, src } => {
if let Some(v) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, v); }
if let Some(v) = self.known_f64.get(src).copied() { self.known_f64.insert(*dst, v); }
// If source is a parameter, materialize it on the stack for downstream ops
if let Some(pidx) = self.param_index.get(src).copied() {
b.emit_param_i64(pidx);
@ -558,7 +603,7 @@ impl LowerCore {
}
}
I::ArrayGet { array, index, .. } => {
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
if crate::jit::config::current().hostcall {
let idx = self.known_i64.get(index).copied().unwrap_or(0);
if let Some(pidx) = self.param_index.get(array).copied() {
// Handle-based: push handle value from param, then index
@ -575,7 +620,7 @@ impl LowerCore {
}
}
I::ArraySet { array, index, value } => {
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
if crate::jit::config::current().hostcall {
let idx = self.known_i64.get(index).copied().unwrap_or(0);
let val = self.known_i64.get(value).copied().unwrap_or(0);
if let Some(pidx) = self.param_index.get(array).copied() {
@ -593,7 +638,7 @@ impl LowerCore {
}
}
I::BoxCall { box_val: array, method, args, dst, .. } => {
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
if crate::jit::config::current().hostcall {
match method.as_str() {
"len" | "length" => {
if let Some(pidx) = self.param_index.get(array).copied() {
@ -606,6 +651,106 @@ impl LowerCore {
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
}
}
// math.* minimal boundary: use registry signature to decide allow/fallback (no actual hostcall yet)
"sin" | "cos" | "abs" | "min" | "max" => {
use crate::jit::hostcall_registry::{check_signature, ArgKind};
// Build symbol and observed arg kinds (f64 if known float, else i64)
let sym = format!("nyash.math.{}", method);
let mut observed: Vec<ArgKind> = Vec::new();
for v in args.iter() {
if self.known_f64.contains_key(v) { observed.push(ArgKind::F64); }
else { observed.push(ArgKind::I64); }
}
// Prepare arg_types for event payload
// Classify argument kinds using known maps and FloatBox tracking; as a last resort, scan for NewBox(FloatBox)
let mut observed_kinds: Vec<crate::jit::hostcall_registry::ArgKind> = Vec::new();
for v in args.iter() {
let mut kind = if self.known_f64.contains_key(v) || self.float_box_values.contains(v) {
crate::jit::hostcall_registry::ArgKind::F64
} else { crate::jit::hostcall_registry::ArgKind::I64 };
if let crate::jit::hostcall_registry::ArgKind::I64 = kind {
'scanv: for (_bb_id, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = ins {
if *dst == *v && box_type == "FloatBox" { kind = crate::jit::hostcall_registry::ArgKind::F64; break 'scanv; }
}
}
}
}
observed_kinds.push(kind);
}
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();
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
})
);
// 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() {
"sin" => ("nyash.math.sin_f64", 1),
"cos" => ("nyash.math.cos_f64", 1),
"abs" => ("nyash.math.abs_f64", 1),
"min" => ("nyash.math.min_f64", 2),
"max" => ("nyash.math.max_f64", 2),
_ => ("nyash.math.sin_f64", 1),
};
// Push f64 args from known_f64 or coerce known_i64
for i in 0..arity {
if let Some(v) = args.get(i) {
// Try direct known values
if let Some(fv) = self.known_f64.get(v).copied() { b.emit_const_f64(fv); continue; }
if let Some(iv) = self.known_i64.get(v).copied() { b.emit_const_f64(iv as f64); continue; }
// Try unwrap FloatBox: scan blocks to find NewBox FloatBox { args: [src] } and reuse src const
let mut emitted = false;
'scan: for (_bb_id, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::NewBox { dst, box_type, args: nb_args } = ins {
if *dst == *v && box_type == "FloatBox" {
if let Some(srcv) = nb_args.get(0) {
if let Some(fv) = self.known_f64.get(srcv).copied() { b.emit_const_f64(fv); emitted = true; break 'scan; }
if let Some(iv) = self.known_i64.get(srcv).copied() { b.emit_const_f64(iv as f64); emitted = true; break 'scan; }
}
}
}
}
}
if !emitted { b.emit_const_f64(0.0); }
} else { b.emit_const_f64(0.0); }
}
let kinds: Vec<super::builder::ParamKind> = (0..arity).map(|_| super::builder::ParamKind::F64).collect();
b.emit_host_call_typed(symbol, &kinds, dst.is_some(), true);
}
}
Err(reason) => {
crate::jit::events::emit(
"hostcall",
"<jit>",
None,
None,
serde_json::json!({
"id": sym,
"decision": "fallback",
"reason": reason,
"argc": observed.len(),
"arg_types": arg_types
})
);
}
}
// no-op: VM側で実行される
}
"isEmpty" | "empty" => {
if let Some(pidx) = self.param_index.get(array).copied() {
b.emit_param_i64(pidx);

View File

@ -551,19 +551,75 @@ impl MirBuilder {
return Ok(dst);
}
}
// Build argument values
let mut arg_values = Vec::new();
for arg in args {
arg_values.push(self.build_expression(arg)?);
}
// Keep original args for special handling (math.*)
let raw_args = args.clone();
let dst = self.value_gen.next();
// For now, treat all function calls as Box method calls
// Special-case: math.* as function-style (sin(x), cos(x), abs(x), min(a,b), max(a,b))
// Normalize to BoxCall on a fresh MathBox receiver with original args intact.
let is_math_func = matches!(name.as_str(), "sin" | "cos" | "abs" | "min" | "max");
if is_math_func {
// Build numeric args directly for math.* to preserve f64 typing
let mut math_args: Vec<ValueId> = Vec::new();
for a in raw_args.into_iter() {
match a {
// new FloatBox(<literal/expr>) → use inner literal/expr directly
ASTNode::New { class, arguments, .. } if class == "FloatBox" && arguments.len() == 1 => {
let v = self.build_expression(arguments[0].clone())?;
math_args.push(v);
}
// new IntegerBox(n) → coerce to float const for math f64 signature
ASTNode::New { class, arguments, .. } if class == "IntegerBox" && arguments.len() == 1 => {
// Build integer then cast to float for clarity
let iv = self.build_expression(arguments[0].clone())?;
let fv = self.value_gen.next();
self.emit_instruction(MirInstruction::TypeOp { dst: fv, op: super::TypeOpKind::Cast, value: iv, ty: MirType::Float })?;
math_args.push(fv);
}
// literal float → use as-is
ASTNode::Literal { value: LiteralValue::Float(_), .. } => {
let v = self.build_expression(a)?; math_args.push(v);
}
// fallback: build normally
other => { let v = self.build_expression(other)?; math_args.push(v); }
}
}
// new MathBox()
let math_recv = self.value_gen.next();
self.emit_instruction(MirInstruction::NewBox { dst: math_recv, box_type: "MathBox".to_string(), args: vec![] })?;
// Record origin to assist slot resolution
self.value_origin_newbox.insert(math_recv, "MathBox".to_string());
// birth()
let birt_mid = resolve_slot_by_type_name("MathBox", "birth");
self.emit_instruction(MirInstruction::BoxCall {
dst: None,
box_val: math_recv,
method: "birth".to_string(),
method_id: birt_mid,
args: vec![],
effects: EffectMask::READ.add(Effect::ReadHeap),
})?;
let method_id = resolve_slot_by_type_name("MathBox", &name);
self.emit_instruction(MirInstruction::BoxCall {
dst: Some(dst),
box_val: math_recv,
method: name,
method_id,
args: math_args,
effects: EffectMask::READ.add(Effect::ReadHeap),
})?;
return Ok(dst);
}
// Default: treat as method-style on first argument as receiver
// Build argument values (default path)
let mut arg_values = Vec::new();
for arg in raw_args { arg_values.push(self.build_expression(arg)?); }
if arg_values.is_empty() {
return Err("Function calls require at least one argument (the object)".to_string());
}
let box_val = arg_values.remove(0);
// Try to resolve method slot if the object originates from a known NewBox
let method_id = self
@ -1269,20 +1325,57 @@ impl MirBuilder {
}
}
// Build argument expressions
let mut arg_values = Vec::new();
for arg in &arguments {
arg_values.push(self.build_expression(arg.clone())?);
// Special-case: MathBox methods (sin/cos/abs/min/max) — normalize args to numeric primitives
let is_math_method = matches!(method.as_str(), "sin" | "cos" | "abs" | "min" | "max");
if is_math_method {
// Try detect MathBox receiver by origin; fallback可
let recv_is_math = self.value_origin_newbox.get(&object_value).map(|s| s == "MathBox").unwrap_or(false)
|| matches!(object, ASTNode::New { ref class, .. } if class == "MathBox");
if recv_is_math {
let mut math_args: Vec<ValueId> = Vec::new();
for a in arguments.iter() {
match a {
ASTNode::New { class, arguments, .. } if class == "FloatBox" && arguments.len() == 1 => {
let v = self.build_expression(arguments[0].clone())?; math_args.push(v);
}
ASTNode::New { class, arguments, .. } if class == "IntegerBox" && arguments.len() == 1 => {
let iv = self.build_expression(arguments[0].clone())?;
let fv = self.value_gen.next();
self.emit_instruction(MirInstruction::TypeOp { dst: fv, op: super::TypeOpKind::Cast, value: iv, ty: MirType::Float })?;
math_args.push(fv);
}
ASTNode::Literal { value: LiteralValue::Float(_), .. } => {
let v = self.build_expression(a.clone())?; math_args.push(v);
}
other => { let v = self.build_expression(other.clone())?; math_args.push(v); }
}
}
let result_id = self.value_gen.next();
let method_name = method.clone();
self.emit_instruction(MirInstruction::BoxCall {
dst: Some(result_id),
box_val: object_value,
method,
method_id: resolve_slot_by_type_name("MathBox", &method_name),
args: math_args,
effects: EffectMask::READ.add(Effect::ReadHeap),
})?;
return Ok(result_id);
}
}
// Build argument expressions (default)
let mut arg_values = Vec::new();
for arg in &arguments { arg_values.push(self.build_expression(arg.clone())?); }
// Create result value
let result_id = self.value_gen.next();
// Optimization: If the object is a direct `new ClassName(...)`, lower to a direct Call
if let ASTNode::New { class, .. } = object {
if let ASTNode::New { ref class, .. } = object {
// Build function name and only lower to Call if the function exists (user-defined)
let func_name = format!("{}.{}{}", class, method, format!("/{}", arg_values.len()));
let can_lower = self.user_defined_boxes.contains(&class)
let can_lower = self.user_defined_boxes.contains(class.as_str())
&& if let Some(ref module) = self.current_module { module.functions.contains_key(&func_name) } else { false };
if can_lower {
let func_val = self.value_gen.next();
@ -1303,7 +1396,7 @@ impl MirBuilder {
// If the object originates from a NewBox in this function, we can lower to Call as well
if let Some(class_name) = self.value_origin_newbox.get(&object_value).cloned() {
let func_name = format!("{}.{}{}", class_name, method, format!("/{}", arg_values.len()));
let can_lower = self.user_defined_boxes.contains(&class_name)
let can_lower = self.user_defined_boxes.contains(class_name.as_str())
&& if let Some(ref module) = self.current_module { module.functions.contains_key(&func_name) } else { false };
if can_lower {
let func_val = self.value_gen.next();