VM: PHI strict default-ON + loop-header PHI fix; add VM step budget; StringBox.lastIndexOf; docs + strict smoke
- PHI strict: default ON, disable via HAKO_VM_PHI_STRICT=0 (alias NYASH_VM_PHI_STRICT=0) - LoopForm: insert initial header PHI (preheader input) and rebind vars before condition; seal updates PHI inputs; avoid duplicate PHIs by replace - MIR interpreter: add HAKO_VM_MAX_STEPS (alias NYASH_VM_MAX_STEPS) fail-fast budget to prevent infinite loops - StringBox.lastIndexOf implemented (rfind, returns -1 when not found) in VM handlers - Smokes: add strict/core/vm_phi_strict_smoke.sh (opt-in); quick remains green 120/120 - Docs: lang/src/vm/README.md and CURRENT_TASK.md updated with PHI strict policy and step budget
This commit is contained in:
@ -36,8 +36,23 @@ impl MirInterpreter {
|
||||
|
||||
let mut cur = func.entry_block;
|
||||
let mut last_pred: Option<BasicBlockId> = None;
|
||||
// Dev/runtime safety valve: cap the number of interpreter steps per function
|
||||
// to prevent infinite loops from hanging the process.
|
||||
let max_steps: u64 = std::env::var("HAKO_VM_MAX_STEPS")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<u64>().ok())
|
||||
.or_else(|| std::env::var("NYASH_VM_MAX_STEPS").ok().and_then(|v| v.parse::<u64>().ok()))
|
||||
.unwrap_or(1_000_000);
|
||||
let mut steps: u64 = 0;
|
||||
|
||||
loop {
|
||||
steps += 1;
|
||||
if steps > max_steps {
|
||||
return Err(VMError::InvalidInstruction(format!(
|
||||
"vm step budget exceeded (max_steps={})",
|
||||
max_steps
|
||||
)));
|
||||
}
|
||||
let block = func
|
||||
.blocks
|
||||
.get(&cur)
|
||||
@ -111,6 +126,64 @@ impl MirInterpreter {
|
||||
dst_id, pred, val
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// No matching predecessor in PHI inputs. Fallback policy:
|
||||
// - Prefer first input when available to avoid use-before-def crashes.
|
||||
// - Emit a trace note when tracing is enabled.
|
||||
// Strict mode: fail-fast when enabled.
|
||||
// Strict PHI (default ON). Can be disabled with HAKO_VM_PHI_STRICT=0 (or NYASH_VM_PHI_STRICT=0).
|
||||
let strict = {
|
||||
let on = |s: &str| {
|
||||
matches!(s, "1"|"true"|"on"|"yes"|"y"|"t"|"enabled"|"enable")
|
||||
};
|
||||
let off = |s: &str| {
|
||||
matches!(s, "0"|"false"|"off"|"no"|"n"|"f"|"disabled"|"disable")
|
||||
};
|
||||
if let Ok(v) = std::env::var("HAKO_VM_PHI_STRICT") {
|
||||
let v = v.to_ascii_lowercase();
|
||||
if off(&v) { false } else if on(&v) { true } else { true }
|
||||
} else if let Ok(v) = std::env::var("NYASH_VM_PHI_STRICT") {
|
||||
let v = v.to_ascii_lowercase();
|
||||
if off(&v) { false } else if on(&v) { true } else { true }
|
||||
} else {
|
||||
true
|
||||
}
|
||||
};
|
||||
if strict {
|
||||
return Err(VMError::InvalidInstruction(format!(
|
||||
"phi pred mismatch at {:?}: no input for predecessor {:?}",
|
||||
dst_id, pred
|
||||
)));
|
||||
}
|
||||
if let Some((_, val0)) = inputs.first() {
|
||||
let v = match self.reg_load(*val0) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") {
|
||||
if Self::trace_enabled() {
|
||||
eprintln!("[vm-trace] phi missing pred match {:?}; fallback first input {:?} -> Void (err={:?})", pred, val0, e);
|
||||
}
|
||||
VMValue::Void
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
self.regs.insert(dst_id, v);
|
||||
if Self::trace_enabled() {
|
||||
eprintln!(
|
||||
"[vm-trace] phi dst={:?} pred-miss fallback first val={:?}",
|
||||
dst_id, val0
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Empty inputs — assign Void (with optional trace) to avoid undefined dst
|
||||
let v = VMValue::Void;
|
||||
self.regs.insert(dst_id, v);
|
||||
if Self::trace_enabled() {
|
||||
eprintln!("[vm-trace] phi dst={:?} no inputs; assign Void", dst_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some((_, val)) = inputs.first() {
|
||||
let v = match self.reg_load(*val) {
|
||||
|
||||
Reference in New Issue
Block a user