Phase 10.10: GC Switchable Runtime & Unified Debug System 実装完了
Phase 10.10の主要実装: - GcConfigBox: GC設定の実行時制御(counting/trace/barrier_strict) - DebugConfigBox: デバッグ設定の統一管理(JIT events/stats/dump/dot) - メソッドディスパッチ: system_methods.rsで両Boxのメソッド実装 - CountingGC動作確認: write_barriers正常カウント(VM実行時) 技術的詳細: - BoxCore/BoxBase統一アーキテクチャを活用 - setFlag/getFlag/apply/summaryメソッドで統一API提供 - 環境変数経由でVM/JITランタイムと連携 - GcConfigBox.apply()は次回実行から有効(ランタイム作成前に環境変数参照) テスト済み: - examples/gc_counting_demo.nyash: CountingGCの動作確認 - write_barriers=3でArray.push/set, Map.setを正しくカウント - NYASH_GC_TRACE=1でGC統計出力確認 Box-First哲学の体現: 設定も制御も観測もすべてBox! 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
62
src/jit/boundary.rs
Normal file
62
src/jit/boundary.rs
Normal file
@ -0,0 +1,62 @@
|
||||
//! CallBoundaryBox: unify JIT→VM return conversion in one place
|
||||
use crate::backend::vm::VMValue;
|
||||
use super::abi::JitValue;
|
||||
|
||||
pub struct CallBoundaryBox;
|
||||
|
||||
impl CallBoundaryBox {
|
||||
pub fn to_vm(ret_ty: &crate::mir::MirType, v: JitValue) -> VMValue {
|
||||
match ret_ty {
|
||||
crate::mir::MirType::Float => match v {
|
||||
JitValue::F64(f) => VMValue::Float(f),
|
||||
JitValue::I64(i) => VMValue::Float(i as f64),
|
||||
JitValue::Bool(b) => VMValue::Float(if b {1.0} else {0.0}),
|
||||
JitValue::Handle(h) => {
|
||||
if let Some(_) = crate::jit::rt::handles::get(h) { VMValue::Float(0.0) } else { VMValue::Float(0.0) }
|
||||
}
|
||||
},
|
||||
crate::mir::MirType::Integer => match v {
|
||||
JitValue::I64(i) => VMValue::Integer(i),
|
||||
JitValue::F64(f) => VMValue::Integer(f as i64),
|
||||
JitValue::Bool(b) => VMValue::Integer(if b {1} else {0}),
|
||||
JitValue::Handle(h) => {
|
||||
if let Some(_) = crate::jit::rt::handles::get(h) { VMValue::Integer(0) } else { VMValue::Integer(0) }
|
||||
}
|
||||
},
|
||||
crate::mir::MirType::Bool => match v {
|
||||
JitValue::Bool(b) => VMValue::Bool(b),
|
||||
JitValue::I64(i) => VMValue::Bool(i != 0),
|
||||
JitValue::F64(f) => VMValue::Bool(f != 0.0),
|
||||
JitValue::Handle(h) => {
|
||||
if let Some(_) = crate::jit::rt::handles::get(h) { VMValue::Bool(true) } else { VMValue::Bool(false) }
|
||||
}
|
||||
},
|
||||
// Box-like returns: if we received a handle id (encoded as I64), resolve to BoxRef; also honor explicit Handle
|
||||
crate::mir::MirType::Box(_) | crate::mir::MirType::String | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) => {
|
||||
match v {
|
||||
JitValue::I64(i) => {
|
||||
let h = i as u64;
|
||||
if let Some(arc) = crate::jit::rt::handles::get(h) { VMValue::BoxRef(arc) }
|
||||
else { VMValue::Integer(i) }
|
||||
}
|
||||
JitValue::Handle(h) => {
|
||||
if let Some(arc) = crate::jit::rt::handles::get(h) { VMValue::BoxRef(arc) } else { VMValue::Void }
|
||||
}
|
||||
JitValue::F64(f) => VMValue::Float(f),
|
||||
JitValue::Bool(b) => VMValue::Bool(b),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Default adapter with heuristic: treat I64 matching a known handle as BoxRef
|
||||
match v {
|
||||
JitValue::I64(i) => {
|
||||
let h = i as u64;
|
||||
if let Some(arc) = crate::jit::rt::handles::get(h) { VMValue::BoxRef(arc) }
|
||||
else { super::abi::adapter::from_jit_value(JitValue::I64(i)) }
|
||||
}
|
||||
_ => super::abi::adapter::from_jit_value(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/jit/extern/collections.rs
vendored
44
src/jit/extern/collections.rs
vendored
@ -21,6 +21,7 @@ pub const SYM_ARRAY_PUSH_H: &str = "nyash.array.push_h";
|
||||
pub const SYM_ARRAY_LAST_H: &str = "nyash.array.last_h";
|
||||
pub const SYM_MAP_SIZE_H: &str = "nyash.map.size_h";
|
||||
pub const SYM_MAP_GET_H: &str = "nyash.map.get_h";
|
||||
pub const SYM_MAP_GET_HH: &str = "nyash.map.get_hh";
|
||||
pub const SYM_MAP_SET_H: &str = "nyash.map.set_h";
|
||||
pub const SYM_MAP_HAS_H: &str = "nyash.map.has_h";
|
||||
// Generic read-only helper
|
||||
@ -61,18 +62,45 @@ pub fn array_get(args: &[VMValue]) -> VMValue {
|
||||
}
|
||||
|
||||
pub fn array_set(args: &[VMValue]) -> VMValue {
|
||||
// Enforce policy for mutating operation
|
||||
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"})
|
||||
);
|
||||
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"]})
|
||||
);
|
||||
return VMValue::from_nyash_box(res);
|
||||
}
|
||||
VMValue::BoxRef(Arc::new(StringBox::new("Error: array.set expects (ArrayBox, i64, value)")))
|
||||
}
|
||||
|
||||
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"})
|
||||
);
|
||||
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"]})
|
||||
);
|
||||
return VMValue::from_nyash_box(res);
|
||||
}
|
||||
VMValue::BoxRef(Arc::new(StringBox::new("Error: array.push expects (ArrayBox, value)")))
|
||||
@ -87,10 +115,24 @@ pub fn map_get(args: &[VMValue]) -> VMValue {
|
||||
}
|
||||
|
||||
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"})
|
||||
);
|
||||
return VMValue::Integer(0);
|
||||
}
|
||||
if let (Some(map), Some(key), Some(value)) = (as_map(args), args.get(1), args.get(2)) {
|
||||
let key_box: Box<dyn NyashBox> = key.to_nyash_box();
|
||||
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
|
||||
return VMValue::from_nyash_box(map.set(key_box, val_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"]})
|
||||
);
|
||||
return VMValue::from_nyash_box(out);
|
||||
}
|
||||
VMValue::BoxRef(Arc::new(StringBox::new("Error: map.set expects (MapBox, key, value)")))
|
||||
}
|
||||
|
||||
@ -12,7 +12,8 @@ pub enum HostcallKind { ReadOnly, Mutating }
|
||||
struct Registry {
|
||||
ro: HashSet<String>,
|
||||
mu: HashSet<String>,
|
||||
sig: HashMap<String, Signature>,
|
||||
// Allow multiple signatures per symbol (overloads)
|
||||
sig: HashMap<String, Vec<Signature>>,
|
||||
}
|
||||
|
||||
static REG: OnceCell<RwLock<Registry>> = OnceCell::new();
|
||||
@ -32,16 +33,18 @@ fn ensure_default() {
|
||||
] { 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 });
|
||||
r.sig.entry("nyash.math.sin".to_string()).or_default().push(Signature { args: vec![ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.entry("nyash.math.cos".to_string()).or_default().push(Signature { args: vec![ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.entry("nyash.math.abs".to_string()).or_default().push(Signature { args: vec![ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.entry("nyash.math.min".to_string()).or_default().push(Signature { args: vec![ArgKind::F64, ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.entry("nyash.math.max".to_string()).or_default().push(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 });
|
||||
// Map get: support both integer and handle keys (overload)
|
||||
r.sig.entry("nyash.map.get_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::Handle });
|
||||
r.sig.entry("nyash.map.get_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::Handle], ret: ArgKind::Handle });
|
||||
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 });
|
||||
let _ = REG.set(RwLock::new(r));
|
||||
}
|
||||
|
||||
@ -124,30 +127,40 @@ pub fn set_signature_csv(symbol: &str, args_csv: &str, ret_str: &str) -> bool {
|
||||
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; }
|
||||
if let Ok(mut w) = lock.write() {
|
||||
w.sig.entry(symbol.to_string()).or_default().push(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");
|
||||
if let Some(lock) = REG.get() {
|
||||
if let Ok(g) = lock.read() {
|
||||
if let Some(sigs) = g.sig.get(symbol) {
|
||||
let cfg_now = crate::jit::config::current();
|
||||
let relax = cfg_now.relax_numeric || cfg_now.native_f64;
|
||||
// Match against any one of the overload signatures
|
||||
'outer: for sig in sigs.iter() {
|
||||
if sig.args.len() != observed_args.len() { continue; }
|
||||
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; }
|
||||
// Mismatch for this candidate signature
|
||||
continue 'outer;
|
||||
}
|
||||
// All args matched for this signature
|
||||
return Ok(());
|
||||
}
|
||||
// No overload matched
|
||||
return Err("sig_mismatch");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@ -248,10 +248,12 @@ extern "C" fn nyash_array_len_h(handle: u64) -> i64 {
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_array_push_h(handle: u64, val: i64) -> i64 {
|
||||
// Policy/Events: classify and decide
|
||||
// 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;
|
||||
match (classify(sym), crate::jit::policy::current().read_only) {
|
||||
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,
|
||||
@ -308,7 +310,9 @@ extern "C" fn nyash_array_last_h(handle: u64) -> i64 {
|
||||
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 {
|
||||
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"})
|
||||
@ -359,10 +363,33 @@ extern "C" fn nyash_map_get_h(handle: u64, key: i64) -> i64 {
|
||||
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;
|
||||
if classify(sym) == HostcallKind::Mutating && crate::jit::policy::current().read_only {
|
||||
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"})
|
||||
@ -385,6 +412,10 @@ extern "C" fn nyash_map_set_h(handle: u64, key: i64, val: i64) -> i64 {
|
||||
}
|
||||
#[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));
|
||||
@ -1153,6 +1184,7 @@ impl CraneliftBuilder {
|
||||
builder.symbol(c::SYM_ARRAY_LAST_H, nyash_array_last_h as *const u8);
|
||||
builder.symbol(c::SYM_MAP_SIZE_H, nyash_map_size_h as *const u8);
|
||||
builder.symbol(c::SYM_MAP_GET_H, nyash_map_get_h as *const u8);
|
||||
builder.symbol(c::SYM_MAP_GET_HH, nyash_map_get_hh as *const u8);
|
||||
builder.symbol(c::SYM_MAP_SET_H, nyash_map_set_h as *const u8);
|
||||
builder.symbol(c::SYM_MAP_HAS_H, nyash_map_has_h as *const u8);
|
||||
builder.symbol(c::SYM_ANY_LEN_H, nyash_any_length_h as *const u8);
|
||||
|
||||
@ -278,7 +278,7 @@ impl LowerCore {
|
||||
}
|
||||
for instr in bb.instructions.iter() {
|
||||
self.cover_if_supported(instr);
|
||||
self.try_emit(builder, instr, *bb_id, func);
|
||||
if let Err(e) = self.try_emit(builder, instr, *bb_id, func) { return Err(e); }
|
||||
// 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); } }
|
||||
@ -375,7 +375,7 @@ impl LowerCore {
|
||||
_ => { /* other terminators handled via generic emission below */ }
|
||||
}
|
||||
// Also allow other terminators to be emitted if needed
|
||||
self.try_emit(builder, term, *bb_id, func);
|
||||
if let Err(e) = self.try_emit(builder, term, *bb_id, func) { return Err(e); }
|
||||
}
|
||||
}
|
||||
builder.end_function();
|
||||
@ -469,7 +469,7 @@ impl LowerCore {
|
||||
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, func: &crate::mir::MirFunction) {
|
||||
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId, func: &crate::mir::MirFunction) -> Result<(), String> {
|
||||
use crate::mir::MirInstruction as I;
|
||||
match instr {
|
||||
I::NewBox { dst, box_type, args } => {
|
||||
@ -543,7 +543,7 @@ impl LowerCore {
|
||||
BinaryOp::Mod => BinOpKind::Mod,
|
||||
// Not yet supported in Core-1
|
||||
BinaryOp::And | BinaryOp::Or
|
||||
| BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor | BinaryOp::Shl | BinaryOp::Shr => { return; }
|
||||
| BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor | BinaryOp::Shl | BinaryOp::Shr => { return Ok(()); }
|
||||
};
|
||||
b.emit_binop(kind);
|
||||
if let (Some(a), Some(b)) = (self.known_i64.get(lhs), self.known_i64.get(rhs)) {
|
||||
@ -642,10 +642,17 @@ impl LowerCore {
|
||||
match method.as_str() {
|
||||
"len" | "length" => {
|
||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||
// Handle-based generic length: supports ArrayBox and StringBox
|
||||
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"]})
|
||||
);
|
||||
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"]})
|
||||
);
|
||||
let arr_idx = -1;
|
||||
b.emit_const_i64(arr_idx);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
|
||||
@ -662,21 +669,24 @@ impl LowerCore {
|
||||
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)
|
||||
// Classify argument kinds using TyEnv when available; fallback to known maps/FloatBox tracking
|
||||
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; }
|
||||
}
|
||||
let kind = if let Some(mt) = func.metadata.value_types.get(v) {
|
||||
match mt {
|
||||
crate::mir::MirType::Float => crate::jit::hostcall_registry::ArgKind::F64,
|
||||
crate::mir::MirType::Integer => crate::jit::hostcall_registry::ArgKind::I64,
|
||||
crate::mir::MirType::Bool => crate::jit::hostcall_registry::ArgKind::I64, // b1はI64 0/1に正規化
|
||||
crate::mir::MirType::String | crate::mir::MirType::Box(_) => crate::jit::hostcall_registry::ArgKind::Handle,
|
||||
_ => {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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 }
|
||||
};
|
||||
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();
|
||||
@ -753,19 +763,46 @@ impl LowerCore {
|
||||
}
|
||||
"isEmpty" | "empty" => {
|
||||
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_IS_EMPTY_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]})
|
||||
);
|
||||
b.emit_param_i64(pidx);
|
||||
// returns i64 0/1
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, 1, dst.is_some());
|
||||
} 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"]})
|
||||
);
|
||||
}
|
||||
}
|
||||
"push" => {
|
||||
// argc=2: (array_handle, value)
|
||||
let val = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||
let pol = crate::jit::policy::current();
|
||||
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,
|
||||
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"]
|
||||
})
|
||||
);
|
||||
b.emit_param_i64(pidx);
|
||||
b.emit_const_i64(val);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H, 2, false);
|
||||
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"]})
|
||||
);
|
||||
let arr_idx = -1;
|
||||
b.emit_const_i64(arr_idx);
|
||||
b.emit_const_i64(val);
|
||||
@ -775,21 +812,145 @@ 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"]})
|
||||
);
|
||||
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"]})
|
||||
);
|
||||
let map_idx = -1;
|
||||
b.emit_const_i64(map_idx);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE, 1, dst.is_some());
|
||||
}
|
||||
}
|
||||
"get" => {
|
||||
// MapBox.get(key): (map_handle, key_i64)
|
||||
// MapBox.get(key): check TyEnv to choose signature (handle|i64)
|
||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||
let key = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
b.emit_param_i64(pidx);
|
||||
b.emit_const_i64(key);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_GET_H, 2, dst.is_some());
|
||||
// Build observed arg kinds using TyEnv when available
|
||||
let mut observed_kinds: Vec<crate::jit::hostcall_registry::ArgKind> = Vec::new();
|
||||
// First arg = map handle
|
||||
observed_kinds.push(crate::jit::hostcall_registry::ArgKind::Handle);
|
||||
// Second arg = key (classify from TyEnv; fallback to I64 if known integer literal)
|
||||
let key_kind = if let Some(key_vid) = args.get(0) {
|
||||
if let Some(mt) = func.metadata.value_types.get(key_vid) {
|
||||
match mt {
|
||||
crate::mir::MirType::Float => crate::jit::hostcall_registry::ArgKind::I64, // coerced via VM path
|
||||
crate::mir::MirType::Integer => crate::jit::hostcall_registry::ArgKind::I64,
|
||||
crate::mir::MirType::Bool => crate::jit::hostcall_registry::ArgKind::I64,
|
||||
crate::mir::MirType::String | crate::mir::MirType::Box(_) => crate::jit::hostcall_registry::ArgKind::Handle,
|
||||
_ => {
|
||||
if let Some(_) = self.known_i64.get(key_vid) { crate::jit::hostcall_registry::ArgKind::I64 } else { crate::jit::hostcall_registry::ArgKind::Handle }
|
||||
}
|
||||
}
|
||||
} else if let Some(_) = self.known_i64.get(key_vid) {
|
||||
crate::jit::hostcall_registry::ArgKind::I64
|
||||
} else {
|
||||
crate::jit::hostcall_registry::ArgKind::Handle
|
||||
}
|
||||
} else { crate::jit::hostcall_registry::ArgKind::I64 };
|
||||
observed_kinds.push(key_kind);
|
||||
|
||||
// Prepare arg_types strings for events
|
||||
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();
|
||||
|
||||
// Signature check against registry (supports overloads) using canonical id
|
||||
let canonical = "nyash.map.get_h";
|
||||
match crate::jit::hostcall_registry::check_signature(canonical, &observed_kinds) {
|
||||
Ok(()) => {
|
||||
// Choose symbol id for event/emit
|
||||
let event_id = if matches!(key_kind, crate::jit::hostcall_registry::ArgKind::Handle)
|
||||
&& args.get(0).and_then(|v| self.param_index.get(v)).is_some() {
|
||||
crate::jit::r#extern::collections::SYM_MAP_GET_HH
|
||||
} else {
|
||||
crate::jit::r#extern::collections::SYM_MAP_GET_H
|
||||
};
|
||||
// Emit allow event
|
||||
crate::jit::events::emit(
|
||||
"hostcall",
|
||||
"<jit>",
|
||||
None,
|
||||
None,
|
||||
serde_json::json!({
|
||||
"id": event_id,
|
||||
"decision": "allow",
|
||||
"reason": "sig_ok",
|
||||
"argc": observed_kinds.len(),
|
||||
"arg_types": arg_types
|
||||
})
|
||||
);
|
||||
// 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) {
|
||||
let key_i = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
b.emit_param_i64(pidx);
|
||||
b.emit_const_i64(key_i);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_GET_H, 2, dst.is_some());
|
||||
} else if let Some(kp) = args.get(0).and_then(|v| self.param_index.get(v)).copied() {
|
||||
// key is a function parameter (handle), use HH variant
|
||||
b.emit_param_i64(pidx);
|
||||
b.emit_param_i64(kp);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_GET_HH, 2, dst.is_some());
|
||||
} else {
|
||||
// Not a param: fall back (receiver_not_param or key_not_param already logged)
|
||||
// no emission; VM will execute
|
||||
}
|
||||
}
|
||||
Err(reason) => {
|
||||
// Signature mismatch - log and fallback
|
||||
crate::jit::events::emit(
|
||||
"hostcall",
|
||||
"<jit>",
|
||||
None,
|
||||
None,
|
||||
serde_json::json!({
|
||||
"id": canonical,
|
||||
"decision": "fallback",
|
||||
"reason": reason,
|
||||
"argc": observed_kinds.len(),
|
||||
"arg_types": arg_types
|
||||
})
|
||||
);
|
||||
// No emission; VM path will handle
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Receiver is not a function parameter; we cannot obtain a stable runtime handle.
|
||||
// Still classify and emit an event for visibility, then fallback to VM.
|
||||
let mut observed_kinds: Vec<crate::jit::hostcall_registry::ArgKind> = Vec::new();
|
||||
observed_kinds.push(crate::jit::hostcall_registry::ArgKind::Handle); // Map receiver (conceptually a handle)
|
||||
let key_kind = if let Some(key_vid) = args.get(0) {
|
||||
if let Some(mt) = func.metadata.value_types.get(key_vid) {
|
||||
match mt {
|
||||
crate::mir::MirType::Integer => crate::jit::hostcall_registry::ArgKind::I64,
|
||||
crate::mir::MirType::Float => crate::jit::hostcall_registry::ArgKind::I64,
|
||||
crate::mir::MirType::Bool => crate::jit::hostcall_registry::ArgKind::I64,
|
||||
crate::mir::MirType::String | crate::mir::MirType::Box(_) => crate::jit::hostcall_registry::ArgKind::Handle,
|
||||
_ => crate::jit::hostcall_registry::ArgKind::Handle,
|
||||
}
|
||||
} else { crate::jit::hostcall_registry::ArgKind::Handle }
|
||||
} else { crate::jit::hostcall_registry::ArgKind::Handle };
|
||||
observed_kinds.push(key_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();
|
||||
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,
|
||||
serde_json::json!({
|
||||
"id": sym,
|
||||
"decision": decision.0,
|
||||
"reason": decision.1,
|
||||
"argc": observed_kinds.len(),
|
||||
"arg_types": arg_types
|
||||
})
|
||||
);
|
||||
// no-op: VM側が処理する
|
||||
}
|
||||
}
|
||||
"set" => {
|
||||
@ -797,19 +958,47 @@ impl LowerCore {
|
||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||
let key = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
let val = args.get(1).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
let pol = crate::jit::policy::current();
|
||||
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,
|
||||
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"]
|
||||
})
|
||||
);
|
||||
b.emit_param_i64(pidx);
|
||||
b.emit_const_i64(key);
|
||||
b.emit_const_i64(val);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SET_H, 3, false);
|
||||
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"]})
|
||||
);
|
||||
}
|
||||
}
|
||||
"charCodeAt" => {
|
||||
// String.charCodeAt(index)
|
||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||
let idx = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
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"]})
|
||||
);
|
||||
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"]})
|
||||
);
|
||||
}
|
||||
}
|
||||
"has" => {
|
||||
@ -827,6 +1016,7 @@ impl LowerCore {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -134,7 +134,7 @@ impl JitManager {
|
||||
}
|
||||
|
||||
/// 10_c: execute compiled function if present (stub: empty args). Returns Some(VMValue) if JIT path was taken.
|
||||
pub fn execute_compiled(&mut self, func: &str, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
|
||||
pub fn execute_compiled(&mut self, func: &str, ret_ty: &crate::mir::MirType, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
|
||||
if let Some(h) = self.handle_of(func) {
|
||||
// Expose args to both legacy VM hostcalls and new JIT ABI TLS
|
||||
crate::jit::rt::set_legacy_vm_args(args);
|
||||
@ -149,7 +149,12 @@ impl JitManager {
|
||||
eprintln!("[JIT] exec_time_ms={} for {}", dt.as_millis(), func);
|
||||
}
|
||||
let res = match out {
|
||||
Some(v) => { self.exec_ok = self.exec_ok.saturating_add(1); Some(crate::jit::abi::adapter::from_jit_value(v)) }
|
||||
Some(v) => {
|
||||
self.exec_ok = self.exec_ok.saturating_add(1);
|
||||
// Use CallBoundaryBox to convert JitValue → VMValue with MIR ret type hint
|
||||
let vmv = crate::jit::boundary::CallBoundaryBox::to_vm(ret_ty, v);
|
||||
Some(vmv)
|
||||
}
|
||||
None => { self.exec_trap = self.exec_trap.saturating_add(1); None }
|
||||
};
|
||||
// Clear handles created during this call
|
||||
|
||||
@ -10,3 +10,4 @@ pub mod config;
|
||||
pub mod policy;
|
||||
pub mod events;
|
||||
pub mod hostcall_registry;
|
||||
pub mod boundary;
|
||||
|
||||
Reference in New Issue
Block a user