trace: add execution route visibility + debug passthrough; phase2170 canaries; docs

- Add HAKO_TRACE_EXECUTION to trace executor route
  - Rust hv1_inline: stderr [trace] executor: hv1_inline (rust)
  - Hakovm dispatcher: stdout [trace] executor: hakovm (hako)
  - test_runner: trace lines for hv1_inline/core/hakovm routes
- Add HAKO_VERIFY_SHOW_LOGS and HAKO_DEBUG=1 (enables both)
  - verify_v1_inline_file() log passthrough with numeric rc extraction
  - test_runner exports via HAKO_DEBUG
- Canary expansion under phase2170 (state spec)
  - Array: push×5/10 → size, len/length alias, per‑recv/global, flow across blocks
  - Map: set dup-key non-increment, value_state get/has
  - run_all.sh: unify, remove SKIPs; all PASS
- Docs
  - ENV_VARS.md: add Debug/Tracing toggles and examples
  - PLAN.md/CURRENT_TASK.md: mark 21.7 green, add Quickstart lines

All changes gated by env vars; default behavior unchanged.
This commit is contained in:
nyash-codex
2025-11-08 23:45:29 +09:00
parent bf185ec2b2
commit fa3091061d
49 changed files with 1334 additions and 110 deletions

View File

@ -8,6 +8,10 @@ use std::collections::HashMap;
/// - mir_call(Method ArrayBox.push/size/len/length) with optional per-receiver state
/// - ret (by register id)
pub fn run_json_v1_inline(json: &str) -> i32 {
// Optional execution trace (stderr) for debugging
if crate::config::env::env_bool("HAKO_TRACE_EXECUTION") {
eprintln!("[trace] executor: hv1_inline (rust)");
}
// Parse JSON
let v: Value = match serde_json::from_str(json) {
Ok(v) => v,
@ -31,13 +35,21 @@ pub fn run_json_v1_inline(json: &str) -> i32 {
// Registers and simple method state
let mut regs: HashMap<i64, i64> = HashMap::new();
let mut str_regs: HashMap<i64, String> = HashMap::new(); // Track string values loaded via const
let sizestate = crate::config::env::env_bool("HAKO_VM_MIRCALL_SIZESTATE");
let per_recv = crate::config::env::env_bool("HAKO_VM_MIRCALL_SIZESTATE_PER_RECV");
let value_state = crate::config::env::env_bool("HAKO_VM_MIRCALL_VALUESTATE");
// Keep Array and Map length states separate to avoid crossbox interference
let mut len_global_arr: i64 = 0;
let mut len_by_recv_arr: HashMap<i64, i64> = HashMap::new();
let mut len_global_map: i64 = 0;
let mut len_by_recv_map: HashMap<i64, i64> = HashMap::new();
// Map dup-key detection: track which keys have been set (per-receiver or global)
let mut map_keys_global: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut map_keys_by_recv: HashMap<i64, std::collections::HashSet<String>> = HashMap::new();
// Map value storage (when value_state=1)
let mut map_vals_global: HashMap<String, i64> = HashMap::new();
let mut map_vals_by_recv: HashMap<i64, HashMap<String, i64>> = HashMap::new();
fn is_size_alias(name: &str) -> bool {
matches!(name, "size" | "len" | "length")
@ -140,6 +152,10 @@ pub fn run_json_v1_inline(json: &str) -> i32 {
// Prefer i64 numeric constants; otherwise stub non-numeric to 0 for inline path
if let Some(n) = inst.get("value").and_then(|vv| vv.get("value")).and_then(Value::as_i64) {
regs.insert(dst, n);
} else if let Some(s) = inst.get("value").and_then(|vv| vv.get("value")).and_then(Value::as_str) {
// Store string value for Map key tracking
str_regs.insert(dst, s.to_string());
regs.insert(dst, 0); // Also set numeric register to 0 for compatibility
} else {
regs.insert(dst, 0);
}
@ -205,17 +221,93 @@ pub fn run_json_v1_inline(json: &str) -> i32 {
let method = callee.get("method").and_then(Value::as_str).unwrap_or("");
let recv = callee.get("receiver").and_then(Value::as_i64).unwrap_or(-1);
if method == "set" {
if sizestate {
if per_recv {
let e = len_by_recv_map.entry(recv).or_insert(0);
*e += 1;
// Extract key from first arg (register containing string value)
let args = if let Some(mc) = mc { mc.get("args") } else { inst.get("args") };
let key_str = if let Some(args_arr) = args.and_then(Value::as_array) {
if let Some(key_reg) = args_arr.get(0).and_then(Value::as_i64) {
// Look up string value from str_regs
str_regs.get(&key_reg).cloned().unwrap_or_default()
} else { String::new() }
} else { String::new() };
if sizestate && !key_str.is_empty() {
let is_new_key = if per_recv {
let keys = map_keys_by_recv.entry(recv).or_insert_with(std::collections::HashSet::new);
keys.insert(key_str.clone())
} else {
len_global_map += 1;
map_keys_global.insert(key_str.clone())
};
// Only increment size if this is a new key
if is_new_key {
if per_recv {
let e = len_by_recv_map.entry(recv).or_insert(0);
*e += 1;
} else {
len_global_map += 1;
}
}
}
// value_state: store the value
if value_state && !key_str.is_empty() {
if let Some(args_arr) = args.and_then(Value::as_array) {
if let Some(val_reg) = args_arr.get(1).and_then(Value::as_i64) {
let val = *regs.get(&val_reg).unwrap_or(&0);
if per_recv {
let vals = map_vals_by_recv.entry(recv).or_insert_with(HashMap::new);
vals.insert(key_str.clone(), val);
} else {
map_vals_global.insert(key_str.clone(), val);
}
}
}
}
if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, 0); }
continue;
}
if method == "get" && value_state {
// Extract key and retrieve value
let args = if let Some(mc) = mc { mc.get("args") } else { inst.get("args") };
let key_str = if let Some(args_arr) = args.and_then(Value::as_array) {
if let Some(key_reg) = args_arr.get(0).and_then(Value::as_i64) {
str_regs.get(&key_reg).cloned().unwrap_or_default()
} else { String::new() }
} else { String::new() };
if !key_str.is_empty() {
let val = if per_recv {
map_vals_by_recv.get(&recv).and_then(|m| m.get(&key_str)).cloned().unwrap_or(0)
} else {
*map_vals_global.get(&key_str).unwrap_or(&0)
};
if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, val); }
}
continue;
}
if method == "has" && value_state {
// Check if key exists
let args = if let Some(mc) = mc { mc.get("args") } else { inst.get("args") };
let key_str = if let Some(args_arr) = args.and_then(Value::as_array) {
if let Some(key_reg) = args_arr.get(0).and_then(Value::as_i64) {
str_regs.get(&key_reg).cloned().unwrap_or_default()
} else { String::new() }
} else { String::new() };
let has = if !key_str.is_empty() {
if per_recv {
map_vals_by_recv.get(&recv).map(|m| m.contains_key(&key_str)).unwrap_or(false)
} else {
map_vals_global.contains_key(&key_str)
}
} else { false };
if let Some(dst) = inst.get("dst").and_then(Value::as_i64) {
regs.insert(dst, if has { 1 } else { 0 });
}
continue;
}
if is_size_alias(method) {
let value = if sizestate {
if per_recv { *len_by_recv_map.get(&recv).unwrap_or(&0) } else { len_global_map }