Phase 21.5 optimization readiness - C-level performance target: - MirBuilder: JsonFrag purify toggle (HAKO_MIR_BUILDER_JSONFRAG_PURIFY=1) - Normalizer: extended f64 canonicalization + dedupe improvements - loop_opts_adapter: JsonFrag path refinement for crate EXE compatibility Infrastructure improvements: - provider_registry: add diagnostics + ring-1 providers (array/console/map/path) - mir_interpreter: add normalization/purify feature gates - tools/selfhost_exe_stageb.sh: new end-to-end Stage-B→crate EXE pipeline - tools/perf/microbench.sh: performance measurement tooling Smoke tests (phase2100): - Extend timeout 15s→120s for heavy crate EXE builds - Add stageb_loop_jsonfrag_crate_exe_canary_vm.sh (target test) - Add s3_backend_selector_crate_exe_vm_parity_return42_canary_vm.sh Documentation: - ENV_VARS.md: add Phase 21.5 optimization toggles - README updates: clarify crate backend strategy - phase215-optimization.md: new optimization roadmap This commit sets the stage for Phase 21.5 critical optimization: achieving C-level performance to decide hakorune's future viability.
318 lines
13 KiB
Rust
318 lines
13 KiB
Rust
use super::*;
|
||
use crate::mir::basic_block::BasicBlock;
|
||
use std::mem;
|
||
|
||
impl MirInterpreter {
|
||
fn trace_enabled() -> bool {
|
||
std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
|
||
|| std::env::var("NYASH_VM_TRACE_EXEC").ok().as_deref() == Some("1")
|
||
}
|
||
|
||
pub(super) fn exec_function_inner(
|
||
&mut self,
|
||
func: &MirFunction,
|
||
arg_vals: Option<&[VMValue]>,
|
||
) -> Result<VMValue, VMError> {
|
||
// Phase 1: delegate cross-class reroute / narrow fallbacks to method_router
|
||
if let Some(r) = super::method_router::pre_exec_reroute(self, func, arg_vals) { return r; }
|
||
let saved_regs = mem::take(&mut self.regs);
|
||
let saved_fn = self.cur_fn.clone();
|
||
self.cur_fn = Some(func.signature.name.clone());
|
||
|
||
// Check if this is a static box method call
|
||
let static_box_name = self.is_static_box_method(&func.signature.name);
|
||
|
||
match arg_vals {
|
||
Some(args) => {
|
||
// Regular parameter binding: params and args are 1:1
|
||
for (i, pid) in func.params.iter().enumerate() {
|
||
let v = args.get(i).cloned().unwrap_or(VMValue::Void);
|
||
self.regs.insert(*pid, v);
|
||
}
|
||
}
|
||
None => {
|
||
// Seed all parameters with Void so SSA consumers observe defined values.
|
||
for pid in &func.params {
|
||
self.regs.insert(*pid, VMValue::Void);
|
||
}
|
||
}
|
||
}
|
||
|
||
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)
|
||
.ok_or_else(|| VMError::InvalidBasicBlock(format!("bb {:?} not found", cur)))?;
|
||
|
||
if Self::trace_enabled() {
|
||
eprintln!(
|
||
"[vm-trace] enter bb={:?} pred={:?} fn={}",
|
||
cur,
|
||
last_pred,
|
||
self.cur_fn.as_deref().unwrap_or("")
|
||
);
|
||
}
|
||
|
||
self.apply_phi_nodes(block, last_pred)?;
|
||
if let Err(e) = self.execute_block_instructions(block) {
|
||
if Self::trace_enabled() {
|
||
eprintln!(
|
||
"[vm-trace] error in bb={:?}: {:?}\n last_inst={:?}",
|
||
cur, e, self.last_inst
|
||
);
|
||
}
|
||
// Optional concise error location print (env‑gated)
|
||
if std::env::var("HAKO_VM_ERROR_LOC").ok().as_deref() == Some("1") {
|
||
eprintln!(
|
||
"[vm/error/loc] fn={} bb={:?} last_inst={:?}",
|
||
self.cur_fn.as_deref().unwrap_or("<unknown>"),
|
||
cur,
|
||
self.last_inst
|
||
);
|
||
}
|
||
return Err(e);
|
||
}
|
||
|
||
match self.handle_terminator(block)? {
|
||
BlockOutcome::Return(result) => {
|
||
self.cur_fn = saved_fn;
|
||
self.regs = saved_regs;
|
||
return Ok(result);
|
||
}
|
||
BlockOutcome::Next {
|
||
target,
|
||
predecessor,
|
||
} => {
|
||
last_pred = Some(predecessor);
|
||
cur = target;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
fn apply_phi_nodes(
|
||
&mut self,
|
||
block: &BasicBlock,
|
||
last_pred: Option<BasicBlockId>,
|
||
) -> Result<(), VMError> {
|
||
let trace_phi = std::env::var("NYASH_VM_TRACE_PHI").ok().as_deref() == Some("1");
|
||
if trace_phi {
|
||
eprintln!(
|
||
"[vm-trace-phi] enter bb={:?} last_pred={:?} preds={:?}",
|
||
block.id,
|
||
last_pred,
|
||
block.predecessors
|
||
);
|
||
}
|
||
for inst in block.phi_instructions() {
|
||
if let MirInstruction::Phi { dst, inputs } = inst {
|
||
let dst_id = *dst;
|
||
if trace_phi {
|
||
let in_preds: Vec<_> = inputs.iter().map(|(bb, _)| *bb).collect();
|
||
eprintln!(
|
||
"[vm-trace-phi] phi dst={:?} inputs.pred={:?}",
|
||
dst_id, in_preds
|
||
);
|
||
}
|
||
if let Some(pred) = last_pred {
|
||
if let Some((_, val)) = inputs.iter().find(|(bb, _)| *bb == pred) {
|
||
let v = match self.reg_load(*val) {
|
||
Ok(v) => v,
|
||
Err(e) => {
|
||
// Dev safety valve: tolerate undefined phi inputs by substituting Void
|
||
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") {
|
||
if Self::trace_enabled() {
|
||
eprintln!("[vm-trace] phi tolerate undefined input {:?} -> Void (err={:?})", val, e);
|
||
}
|
||
VMValue::Void
|
||
} else {
|
||
return Err(e);
|
||
}
|
||
}
|
||
};
|
||
self.regs.insert(dst_id, v);
|
||
if Self::trace_enabled() {
|
||
eprintln!(
|
||
"[vm-trace] phi dst={:?} take pred={:?} val={:?}",
|
||
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 {
|
||
if trace_phi {
|
||
eprintln!(
|
||
"[vm-trace-phi][strict] mismatch dst={:?} last_pred={:?} inputs={:?} preds_of_bb={:?}",
|
||
dst_id,
|
||
pred,
|
||
inputs,
|
||
block.predecessors
|
||
);
|
||
}
|
||
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) {
|
||
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 tolerate undefined default input {:?} -> Void (err={:?})", val, e);
|
||
}
|
||
VMValue::Void
|
||
} else {
|
||
return Err(e);
|
||
}
|
||
}
|
||
};
|
||
self.regs.insert(dst_id, v);
|
||
if Self::trace_enabled() {
|
||
eprintln!(
|
||
"[vm-trace] phi dst={:?} take default val={:?}",
|
||
dst_id, val
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
fn execute_block_instructions(&mut self, block: &BasicBlock) -> Result<(), VMError> {
|
||
for inst in block.non_phi_instructions() {
|
||
self.last_block = Some(block.id);
|
||
self.last_inst = Some(inst.clone());
|
||
if Self::trace_enabled() {
|
||
eprintln!("[vm-trace] inst bb={:?} {:?}", block.id, inst);
|
||
}
|
||
// Dev counters: count non-phi instructions and compares
|
||
self.inst_count = self.inst_count.saturating_add(1);
|
||
if let MirInstruction::Compare { .. } = inst {
|
||
self.compare_count = self.compare_count.saturating_add(1);
|
||
}
|
||
self.execute_instruction(inst)?;
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
fn handle_terminator(&mut self, block: &BasicBlock) -> Result<BlockOutcome, VMError> {
|
||
match &block.terminator {
|
||
Some(MirInstruction::Return { value }) => {
|
||
let result = if let Some(v) = value {
|
||
self.reg_load(*v)?
|
||
} else {
|
||
VMValue::Void
|
||
};
|
||
Ok(BlockOutcome::Return(result))
|
||
}
|
||
Some(MirInstruction::Jump { target }) => Ok(BlockOutcome::Next {
|
||
target: *target,
|
||
predecessor: block.id,
|
||
}),
|
||
Some(MirInstruction::Branch {
|
||
condition,
|
||
then_bb,
|
||
else_bb,
|
||
}) => {
|
||
// Dev counter: count branch terminators actually evaluated
|
||
self.branch_count = self.branch_count.saturating_add(1);
|
||
let cond = self.reg_load(*condition)?;
|
||
let branch = to_bool_vm(&cond).map_err(VMError::TypeError)?;
|
||
let target = if branch { *then_bb } else { *else_bb };
|
||
Ok(BlockOutcome::Next {
|
||
target,
|
||
predecessor: block.id,
|
||
})
|
||
}
|
||
None => Err(VMError::InvalidBasicBlock(format!(
|
||
"unterminated block {:?}",
|
||
block.id
|
||
))),
|
||
Some(other) => Err(VMError::InvalidInstruction(format!(
|
||
"invalid terminator in MIR interp: {:?}",
|
||
other
|
||
))),
|
||
}
|
||
}
|
||
}
|
||
|
||
enum BlockOutcome {
|
||
Return(VMValue),
|
||
Next {
|
||
target: BasicBlockId,
|
||
predecessor: BasicBlockId,
|
||
},
|
||
}
|