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:
@ -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 cross‑box 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 }
|
||||
|
||||
Reference in New Issue
Block a user