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:
Moe Charm
2025-08-28 22:31:51 +09:00
parent 4e1b595796
commit d67f27f4b8
27 changed files with 1341 additions and 63 deletions

View File

@ -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);

View File

@ -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(())
}
}