Files
hakorune/src/backend/mir_interpreter/exec.rs
nyash-codex 07a254fc0d feat(phase21.5): MirBuilder optimization prep + crate EXE infrastructure
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.
2025-11-11 02:07:12 +09:00

318 lines
13 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 (envgated)
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,
},
}