mir/vm: SSA pin+PHI + short-circuit; user-defined method calls → functions; entry single-pred PHIs; compare-operand pin; VM BoxCall fallback to InstanceBox methods; docs: update CURRENT_TASK (plan + acceptance)
- Lower And/Or to branch+PHI (RHS not evaluated) - Always slotify compare operands (dominance safety) - Insert single-predecessor PHIs at then/else/short-circuit entries - pin_to_slot now logs (NYASH_PIN_TRACE) and participates in PHI - Rewrite user-defined instance method calls to Box.method/Arity (builder) - VM fallback: BoxCall on InstanceBox dispatches to lowered functions with 'me'+args - Keep plugin/BoxCall path for core boxes (String/Array/Map) - Add env-gated pre-pin for if/loop (NYASH_MIR_PREPIN) - CURRENT_TASK: add SSA/userbox plan, debug steps, acceptance criteria
This commit is contained in:
@ -481,6 +481,23 @@ impl MirInterpreter {
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
// Dynamic fallback for user-defined InstanceBox: dispatch to lowered function "Class.method/Arity"
|
||||
if let Some(inst) = recv_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
let class_name = inst.class_name.clone();
|
||||
let arity = args.len(); // function name arity excludes 'me'
|
||||
let fname = format!("{}.{}{}", class_name, method, format!("/{}", arity));
|
||||
if let Some(func) = self.functions.get(&fname).cloned() {
|
||||
let mut argv: Vec<VMValue> = Vec::with_capacity(arity + 1);
|
||||
// Pass receiver as first arg ('me')
|
||||
argv.push(recv.clone());
|
||||
for a in args {
|
||||
argv.push(self.reg_load(*a)?);
|
||||
}
|
||||
let ret = self.exec_function_inner(&func, Some(&argv))?;
|
||||
if let Some(d) = dst { self.regs.insert(d, ret); }
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(VMError::InvalidInstruction(format!(
|
||||
"BoxCall unsupported on {}.{}",
|
||||
recv_box.type_name(),
|
||||
|
||||
@ -38,6 +38,11 @@ impl MirInterpreter {
|
||||
use BinaryOp::*;
|
||||
use VMValue::*;
|
||||
Ok(match (op, a, b) {
|
||||
// Safety valve: treat Void as 0 for + (dev fallback for scanners)
|
||||
(Add, VMValue::Void, Integer(y)) => Integer(y),
|
||||
(Add, Integer(x), VMValue::Void) => Integer(x),
|
||||
(Add, VMValue::Void, Float(y)) => Float(y),
|
||||
(Add, Float(x), VMValue::Void) => Float(x),
|
||||
(Add, Integer(x), Integer(y)) => Integer(x + y),
|
||||
(Add, String(s), Integer(y)) => String(format!("{}{}", s, y)),
|
||||
(Add, String(s), Float(y)) => String(format!("{}{}", s, y)),
|
||||
|
||||
@ -173,6 +173,18 @@ pub fn mir_core13_pure() -> bool {
|
||||
std::env::var("NYASH_MIR_CORE13_PURE").ok().as_deref() == Some("1")
|
||||
}
|
||||
|
||||
/// Enable heuristic pre-pin of comparison operands in if/loop headers.
|
||||
/// Default: OFF (0). Set NYASH_MIR_PREPIN=1 to enable.
|
||||
pub fn mir_pre_pin_compare_operands() -> bool {
|
||||
match std::env::var("NYASH_MIR_PREPIN").ok() {
|
||||
Some(v) => {
|
||||
let lv = v.to_ascii_lowercase();
|
||||
!(lv == "0" || lv == "false" || lv == "off")
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Optimizer diagnostics ----
|
||||
pub fn opt_debug() -> bool {
|
||||
std::env::var("NYASH_OPT_DEBUG").is_ok()
|
||||
|
||||
@ -130,6 +130,8 @@ pub struct MirBuilder {
|
||||
|
||||
/// Internal counter for temporary pin slots (block-crossing ephemeral values)
|
||||
temp_slot_counter: u32,
|
||||
/// If true, skip entry materialization of pinned slots on the next start_new_block call.
|
||||
suppress_pin_entry_copy_next: bool,
|
||||
}
|
||||
|
||||
impl MirBuilder {
|
||||
@ -168,6 +170,7 @@ impl MirBuilder {
|
||||
cleanup_allow_throw: false,
|
||||
hint_sink: crate::mir::hints::HintSink::new(),
|
||||
temp_slot_counter: 0,
|
||||
suppress_pin_entry_copy_next: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,6 +178,9 @@ impl MirBuilder {
|
||||
pub(super) fn push_if_merge(&mut self, bb: BasicBlockId) { self.if_merge_stack.push(bb); }
|
||||
pub(super) fn pop_if_merge(&mut self) { let _ = self.if_merge_stack.pop(); }
|
||||
|
||||
/// Suppress entry pin copy for the next start_new_block (used for merge blocks).
|
||||
pub(super) fn suppress_next_entry_pin_copy(&mut self) { self.suppress_pin_entry_copy_next = true; }
|
||||
|
||||
// ---- Hint helpers (no-op by default) ----
|
||||
#[inline]
|
||||
pub(crate) fn hint_loop_header(&mut self) { self.hint_sink.loop_header(); }
|
||||
|
||||
@ -57,8 +57,44 @@ impl super::MirBuilder {
|
||||
args: Vec<ValueId>,
|
||||
) -> Result<(), String> {
|
||||
match target {
|
||||
CallTarget::Method { receiver, method, .. } => {
|
||||
// Use existing emit_box_or_plugin_call
|
||||
CallTarget::Method { receiver, method, box_type } => {
|
||||
// If receiver is a user-defined box, lower to function call: "Box.method/(1+arity)" with receiver as first arg
|
||||
let mut is_user_box = false;
|
||||
let mut class_name_opt: Option<String> = None;
|
||||
if let Some(bt) = box_type.clone() { class_name_opt = Some(bt); }
|
||||
if class_name_opt.is_none() {
|
||||
if let Some(cn) = self.value_origin_newbox.get(&receiver) { class_name_opt = Some(cn.clone()); }
|
||||
}
|
||||
if class_name_opt.is_none() {
|
||||
if let Some(t) = self.value_types.get(&receiver) {
|
||||
if let super::MirType::Box(bn) = t { class_name_opt = Some(bn.clone()); }
|
||||
}
|
||||
}
|
||||
if let Some(cls) = class_name_opt.clone() {
|
||||
// Prefer explicit registry of user-defined boxes when available
|
||||
if self.user_defined_boxes.contains(&cls) { is_user_box = true; }
|
||||
}
|
||||
if is_user_box {
|
||||
let cls = class_name_opt.unwrap();
|
||||
let arity = args.len(); // function name arity excludes 'me'
|
||||
let fname = super::calls::function_lowering::generate_method_function_name(&cls, &method, arity);
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: super::ConstValue::String(fname),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(arity);
|
||||
call_args.push(receiver); // pass 'me' first
|
||||
call_args.extend(args.into_iter());
|
||||
return self.emit_instruction(MirInstruction::Call {
|
||||
dst,
|
||||
func: name_const,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: EffectMask::READ.add(Effect::ReadHeap),
|
||||
});
|
||||
}
|
||||
// Else fall back to plugin/boxcall path (StringBox/ArrayBox/MapBox etc.)
|
||||
self.emit_box_or_plugin_call(dst, receiver, method, None, args, EffectMask::IO)
|
||||
},
|
||||
CallTarget::Constructor(box_type) => {
|
||||
|
||||
@ -14,6 +14,7 @@ impl MirBuilder {
|
||||
// Heuristic pre-pin: if condition is a comparison, evaluate its operands now and pin them
|
||||
// so that subsequent branches can safely reuse these values across blocks.
|
||||
// This leverages existing variable_map merges (PHI) at the merge block.
|
||||
if crate::config::env::mir_pre_pin_compare_operands() {
|
||||
if let ASTNode::BinaryOp { operator, left, right, .. } = &condition {
|
||||
match operator {
|
||||
BinaryOperator::Equal
|
||||
@ -32,6 +33,7 @@ impl MirBuilder {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let condition_val = self.build_expression(condition)?;
|
||||
|
||||
@ -41,6 +43,7 @@ impl MirBuilder {
|
||||
let merge_block = self.block_gen.next();
|
||||
|
||||
// Branch
|
||||
let pre_branch_bb = self.current_block()?;
|
||||
self.emit_instruction(MirInstruction::Branch {
|
||||
condition: condition_val,
|
||||
then_bb: then_block,
|
||||
@ -51,12 +54,18 @@ impl MirBuilder {
|
||||
let pre_if_var_map = self.variable_map.clone();
|
||||
|
||||
// then
|
||||
self.current_block = Some(then_block);
|
||||
self.ensure_block_exists(then_block)?;
|
||||
self.start_new_block(then_block)?;
|
||||
// Scope enter for then-branch
|
||||
self.hint_scope_enter(0);
|
||||
let then_ast_for_analysis = then_branch.clone();
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
// Materialize all variables at block entry via single-pred Phi (correctness-first)
|
||||
for (name, &pre_v) in pre_if_var_map.iter() {
|
||||
let phi_val = self.value_gen.next();
|
||||
let inputs = vec![(pre_branch_bb, pre_v)];
|
||||
self.emit_instruction(MirInstruction::Phi { dst: phi_val, inputs })?;
|
||||
self.variable_map.insert(name.clone(), phi_val);
|
||||
}
|
||||
let then_value_raw = self.build_expression(then_branch)?;
|
||||
let then_exit_block = self.current_block()?;
|
||||
let then_var_map_end = self.variable_map.clone();
|
||||
@ -67,10 +76,16 @@ impl MirBuilder {
|
||||
}
|
||||
|
||||
// else
|
||||
self.current_block = Some(else_block);
|
||||
self.ensure_block_exists(else_block)?;
|
||||
self.start_new_block(else_block)?;
|
||||
// Scope enter for else-branch
|
||||
self.hint_scope_enter(0);
|
||||
// Materialize all variables at block entry via single-pred Phi (correctness-first)
|
||||
for (name, &pre_v) in pre_if_var_map.iter() {
|
||||
let phi_val = self.value_gen.next();
|
||||
let inputs = vec![(pre_branch_bb, pre_v)];
|
||||
self.emit_instruction(MirInstruction::Phi { dst: phi_val, inputs })?;
|
||||
self.variable_map.insert(name.clone(), phi_val);
|
||||
}
|
||||
let (else_value_raw, else_ast_for_analysis, else_var_map_end_opt) = if let Some(else_ast) = else_branch {
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
let val = self.build_expression(else_ast.clone())?;
|
||||
@ -88,8 +103,9 @@ impl MirBuilder {
|
||||
}
|
||||
|
||||
// merge: primary result via helper, then delta-based variable merges
|
||||
self.current_block = Some(merge_block);
|
||||
self.ensure_block_exists(merge_block)?;
|
||||
// Ensure PHIs are first in the block by suppressing entry pin copies here
|
||||
self.suppress_next_entry_pin_copy();
|
||||
self.start_new_block(merge_block)?;
|
||||
self.push_if_merge(merge_block);
|
||||
|
||||
// Pre-analysis: identify then/else assigned var for skip and hints
|
||||
|
||||
@ -97,6 +97,84 @@ impl MirBuilder {
|
||||
arg_values.push(self.build_expression(arg.clone())?);
|
||||
}
|
||||
|
||||
// If receiver is a user-defined box, lower to function call: "Box.method/(1+arity)"
|
||||
let mut class_name_opt: Option<String> = None;
|
||||
if let Some(cn) = self.value_origin_newbox.get(&object_value) { class_name_opt = Some(cn.clone()); }
|
||||
if class_name_opt.is_none() {
|
||||
if let Some(t) = self.value_types.get(&object_value) {
|
||||
if let MirType::Box(bn) = t { class_name_opt = Some(bn.clone()); }
|
||||
}
|
||||
}
|
||||
if let Some(cls) = class_name_opt.clone() {
|
||||
if self.user_defined_boxes.contains(&cls) {
|
||||
let arity = arg_values.len(); // function name arity excludes 'me'
|
||||
let fname = crate::mir::builder::calls::function_lowering::generate_method_function_name(&cls, &method, arity);
|
||||
// Only use userbox path if such a function actually exists in the module
|
||||
let has_fn = if let Some(ref module) = self.current_module {
|
||||
module.functions.contains_key(&fname)
|
||||
} else { false };
|
||||
if has_fn {
|
||||
if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
|
||||
super::utils::builder_debug_log(&format!("userbox method-call cls={} method={} fname={}", cls, method, fname));
|
||||
}
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(fname),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(arity + 1);
|
||||
call_args.push(object_value); // 'me'
|
||||
call_args.extend(arg_values.into_iter());
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func: name_const,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: if exactly one user-defined method matches by name/arity across module, resolve to that
|
||||
if let Some(ref module) = self.current_module {
|
||||
let tail = format!(".{}{}", method, format!("/{}", arg_values.len()));
|
||||
let mut cands: Vec<String> = module
|
||||
.functions
|
||||
.keys()
|
||||
.filter(|k| k.ends_with(&tail))
|
||||
.cloned()
|
||||
.collect();
|
||||
if cands.len() == 1 {
|
||||
let fname = cands.remove(0);
|
||||
// sanity: ensure the box prefix looks like a user-defined box
|
||||
if let Some((bx, _)) = fname.split_once('.') {
|
||||
if self.user_defined_boxes.contains(bx) {
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(fname),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(arg_values.len() + 1);
|
||||
call_args.push(object_value); // 'me'
|
||||
call_args.extend(arg_values.into_iter());
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func: name_const,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Else fall back to plugin/boxcall path
|
||||
let result_id = self.value_gen.next();
|
||||
self.emit_box_or_plugin_call(
|
||||
Some(result_id),
|
||||
@ -109,4 +187,4 @@ impl MirBuilder {
|
||||
|
||||
Ok(result_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,9 +87,10 @@ impl super::MirBuilder {
|
||||
} else {
|
||||
(lhs, rhs)
|
||||
};
|
||||
// Materialize operands in the current block to avoid dominance/undef issues
|
||||
let lhs2 = lhs2_raw;
|
||||
let rhs2 = rhs2_raw;
|
||||
// Ensure operands are safe across blocks: pin ephemeral values into slots
|
||||
// This guarantees they participate in PHI merges and have block-local defs.
|
||||
let lhs2 = self.ensure_slotified_for_use(lhs2_raw, "@cmp_lhs")?;
|
||||
let rhs2 = self.ensure_slotified_for_use(rhs2_raw, "@cmp_rhs")?;
|
||||
self.emit_instruction(MirInstruction::Compare {
|
||||
dst,
|
||||
op,
|
||||
@ -128,16 +129,24 @@ impl super::MirBuilder {
|
||||
then_bb: then_block,
|
||||
else_bb: else_block,
|
||||
})?;
|
||||
// Record predecessor block for branch (for single-pred PHI materialization)
|
||||
let pre_branch_bb = self.current_block()?;
|
||||
|
||||
// Snapshot variables before entering branches
|
||||
let pre_if_var_map = self.variable_map.clone();
|
||||
|
||||
// ---- THEN branch ----
|
||||
self.current_block = Some(then_block);
|
||||
self.ensure_block_exists(then_block)?;
|
||||
self.start_new_block(then_block)?;
|
||||
self.hint_scope_enter(0);
|
||||
// Reset scope to pre-if snapshot for clean deltas
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
// Materialize all variables at entry via single-pred PHI (correctness-first)
|
||||
for (name, &pre_v) in pre_if_var_map.iter() {
|
||||
let phi_val = self.value_gen.next();
|
||||
let inputs = vec![(pre_branch_bb, pre_v)];
|
||||
self.emit_instruction(MirInstruction::Phi { dst: phi_val, inputs })?;
|
||||
self.variable_map.insert(name.clone(), phi_val);
|
||||
}
|
||||
|
||||
// AND: then → evaluate RHS and reduce to bool
|
||||
// OR: then → constant true
|
||||
@ -187,10 +196,16 @@ impl super::MirBuilder {
|
||||
}
|
||||
|
||||
// ---- ELSE branch ----
|
||||
self.current_block = Some(else_block);
|
||||
self.ensure_block_exists(else_block)?;
|
||||
self.start_new_block(else_block)?;
|
||||
self.hint_scope_enter(0);
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
// Materialize all variables at entry via single-pred PHI (correctness-first)
|
||||
for (name, &pre_v) in pre_if_var_map.iter() {
|
||||
let phi_val = self.value_gen.next();
|
||||
let inputs = vec![(pre_branch_bb, pre_v)];
|
||||
self.emit_instruction(MirInstruction::Phi { dst: phi_val, inputs })?;
|
||||
self.variable_map.insert(name.clone(), phi_val);
|
||||
}
|
||||
// AND: else → false
|
||||
// OR: else → evaluate RHS and reduce to bool
|
||||
let else_value_raw = if is_and {
|
||||
@ -238,8 +253,9 @@ impl super::MirBuilder {
|
||||
}
|
||||
|
||||
// ---- MERGE ----
|
||||
self.current_block = Some(merge_block);
|
||||
self.ensure_block_exists(merge_block)?;
|
||||
// Merge block: suppress entry pin copy so PHIs remain first and materialize pins explicitly
|
||||
self.suppress_next_entry_pin_copy();
|
||||
self.start_new_block(merge_block)?;
|
||||
self.push_if_merge(merge_block);
|
||||
|
||||
// Result PHI (bool)
|
||||
|
||||
@ -25,6 +25,8 @@ impl MirBuilder {
|
||||
then_map_end,
|
||||
else_map_end_opt,
|
||||
);
|
||||
use std::collections::HashSet;
|
||||
let changed_set: HashSet<String> = changed.iter().cloned().collect();
|
||||
for name in changed {
|
||||
if skip_var.map(|s| s == name).unwrap_or(false) {
|
||||
continue;
|
||||
@ -50,6 +52,29 @@ impl MirBuilder {
|
||||
self.emit_instruction(MirInstruction::Phi { dst: merged, inputs })?;
|
||||
self.variable_map.insert(name, merged);
|
||||
}
|
||||
|
||||
// Ensure pinned synthetic slots ("__pin$...") have a block-local definition at the merge,
|
||||
// even if their values did not change across branches. This avoids undefined uses when
|
||||
// subsequent blocks re-use pinned values without modifications.
|
||||
for (pin_name, pre_val) in pre_if_snapshot.iter() {
|
||||
if !pin_name.starts_with("__pin$") { continue; }
|
||||
if skip_var.map(|s| s == pin_name.as_str()).unwrap_or(false) { continue; }
|
||||
if changed_set.contains(pin_name) { continue; }
|
||||
let then_v = then_map_end.get(pin_name.as_str()).copied().unwrap_or(*pre_val);
|
||||
let else_v = else_map_end_opt
|
||||
.as_ref()
|
||||
.and_then(|m| m.get(pin_name.as_str()).copied())
|
||||
.unwrap_or(*pre_val);
|
||||
let then_pred = then_exit_block;
|
||||
let else_pred = else_exit_block_opt.unwrap_or(else_block);
|
||||
let merged = self.value_gen.next();
|
||||
let inputs = vec![(then_pred, then_v), (else_pred, else_v)];
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
self.emit_instruction(MirInstruction::Phi { dst: merged, inputs })?;
|
||||
self.variable_map.insert(pin_name.clone(), merged);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Normalize Phi creation for if/else constructs.
|
||||
|
||||
@ -32,17 +32,21 @@ impl super::MirBuilder {
|
||||
if let Some(ref mut function) = self.current_function {
|
||||
function.add_block(BasicBlock::new(block_id));
|
||||
self.current_block = Some(block_id);
|
||||
// Entry materialization for pinned slots only: ensure a local def exists in this block
|
||||
// This avoids dominance/undef issues when pinned values are referenced across blocks.
|
||||
let names: Vec<String> = self.variable_map.keys().cloned().collect();
|
||||
for name in names {
|
||||
if !name.starts_with("__pin$") { continue; }
|
||||
if let Some(&src) = self.variable_map.get(&name) {
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(super::MirInstruction::Copy { dst, src })?;
|
||||
self.variable_map.insert(name.clone(), dst);
|
||||
// Entry materialization for pinned slots only when not suppressed.
|
||||
// This provides block-local defs in single-predecessor flows without touching user vars.
|
||||
if !self.suppress_pin_entry_copy_next {
|
||||
let names: Vec<String> = self.variable_map.keys().cloned().collect();
|
||||
for name in names {
|
||||
if !name.starts_with("__pin$") { continue; }
|
||||
if let Some(&src) = self.variable_map.get(&name) {
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(super::MirInstruction::Copy { dst, src })?;
|
||||
self.variable_map.insert(name.clone(), dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reset suppression flag after use (one-shot)
|
||||
self.suppress_pin_entry_copy_next = false;
|
||||
Ok(())
|
||||
} else {
|
||||
Err("No current function".to_string())
|
||||
@ -204,12 +208,16 @@ impl super::MirBuilder {
|
||||
})
|
||||
}
|
||||
|
||||
/// Pin a block-crossing ephemeral value into a pseudo local slot so it participates in PHI merges.
|
||||
/// Pin a block-crossing ephemeral value into a pseudo local slot and register it in variable_map
|
||||
/// so it participates in PHI merges across branches/blocks. Safe default for correctness-first.
|
||||
pub(crate) fn pin_to_slot(&mut self, v: super::ValueId, hint: &str) -> Result<super::ValueId, String> {
|
||||
self.temp_slot_counter = self.temp_slot_counter.wrapping_add(1);
|
||||
let slot_name = format!("__pin${}${}", self.temp_slot_counter, hint);
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(super::MirInstruction::Copy { dst, src: v })?;
|
||||
if super::utils::builder_debug_enabled() || std::env::var("NYASH_PIN_TRACE").ok().as_deref() == Some("1") {
|
||||
super::utils::builder_debug_log(&format!("pin slot={} src={} dst={}", slot_name, v.0, dst.0));
|
||||
}
|
||||
self.variable_map.insert(slot_name, dst);
|
||||
Ok(dst)
|
||||
}
|
||||
@ -220,4 +228,10 @@ impl super::MirBuilder {
|
||||
self.emit_instruction(super::MirInstruction::Copy { dst, src: v })?;
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
/// Ensure a value is safe to use in the current block by slotifying (pinning) it.
|
||||
/// Currently correctness-first: always pin to get a block-local def and PHI participation.
|
||||
pub(crate) fn ensure_slotified_for_use(&mut self, v: super::ValueId, hint: &str) -> Result<super::ValueId, String> {
|
||||
self.pin_to_slot(v, hint)
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,6 +152,7 @@ impl<'a> LoopBuilder<'a> {
|
||||
// 5. 条件評価(Phi nodeの結果を使用)
|
||||
// Heuristic pre-pin: if condition is a comparison, evaluate its operands and pin them
|
||||
// so that the loop body/next iterations can safely reuse these values across blocks.
|
||||
if crate::config::env::mir_pre_pin_compare_operands() {
|
||||
if let ASTNode::BinaryOp { operator, left, right, .. } = &condition {
|
||||
use crate::ast::BinaryOperator as BO;
|
||||
match operator {
|
||||
@ -166,15 +167,27 @@ impl<'a> LoopBuilder<'a> {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let condition_value = self.build_expression_with_phis(condition)?;
|
||||
|
||||
// 6. 条件分岐
|
||||
let pre_branch_bb = self.current_block()?;
|
||||
self.emit_branch(condition_value, body_id, after_loop_id)?;
|
||||
let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, body_id, header_id);
|
||||
let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, after_loop_id, header_id);
|
||||
|
||||
// 7. ループボディの構築
|
||||
self.set_current_block(body_id)?;
|
||||
// Materialize pinned slots at entry via single-pred Phi
|
||||
let names: Vec<String> = self.parent_builder.variable_map.keys().cloned().collect();
|
||||
for name in names {
|
||||
if !name.starts_with("__pin$") { continue; }
|
||||
if let Some(&pre_v) = self.parent_builder.variable_map.get(&name) {
|
||||
let phi_val = self.new_value();
|
||||
self.emit_phi_at_block_start(body_id, phi_val, vec![(pre_branch_bb, pre_v)])?;
|
||||
self.update_variable(name, phi_val);
|
||||
}
|
||||
}
|
||||
// Scope enter for loop body
|
||||
self.parent_builder.hint_scope_enter(0);
|
||||
// Optional safepoint per loop-iteration
|
||||
@ -467,6 +480,7 @@ impl<'a> LoopBuilder<'a> {
|
||||
else_body: Option<Vec<ASTNode>>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Pre-pin comparison operands to slots so repeated uses across blocks are safe
|
||||
if crate::config::env::mir_pre_pin_compare_operands() {
|
||||
if let ASTNode::BinaryOp { operator, left, right, .. } = &condition {
|
||||
use crate::ast::BinaryOperator as BO;
|
||||
match operator {
|
||||
@ -481,11 +495,13 @@ impl<'a> LoopBuilder<'a> {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Evaluate condition and create blocks
|
||||
let cond_val = self.parent_builder.build_expression(condition)?;
|
||||
let then_bb = self.new_block();
|
||||
let else_bb = self.new_block();
|
||||
let merge_bb = self.new_block();
|
||||
let pre_branch_bb = self.current_block()?;
|
||||
self.emit_branch(cond_val, then_bb, else_bb)?;
|
||||
|
||||
// Capture pre-if variable map (used for phi normalization)
|
||||
@ -494,6 +510,15 @@ impl<'a> LoopBuilder<'a> {
|
||||
|
||||
// then branch
|
||||
self.set_current_block(then_bb)?;
|
||||
// Materialize all variables at entry via single-pred Phi (correctness-first)
|
||||
let names_then: Vec<String> = self.parent_builder.variable_map.keys().cloned().collect();
|
||||
for name in names_then {
|
||||
if let Some(&pre_v) = self.parent_builder.variable_map.get(&name) {
|
||||
let phi_val = self.new_value();
|
||||
self.emit_phi_at_block_start(then_bb, phi_val, vec![(pre_branch_bb, pre_v)])?;
|
||||
self.update_variable(name, phi_val);
|
||||
}
|
||||
}
|
||||
for s in then_body.iter().cloned() {
|
||||
let _ = self.build_statement(s)?;
|
||||
// フェーズS修正:統一終端検出ユーティリティ使用
|
||||
@ -510,6 +535,15 @@ impl<'a> LoopBuilder<'a> {
|
||||
|
||||
// else branch
|
||||
self.set_current_block(else_bb)?;
|
||||
// Materialize all variables at entry via single-pred Phi (correctness-first)
|
||||
let names2: Vec<String> = self.parent_builder.variable_map.keys().cloned().collect();
|
||||
for name in names2 {
|
||||
if let Some(&pre_v) = self.parent_builder.variable_map.get(&name) {
|
||||
let phi_val = self.new_value();
|
||||
self.emit_phi_at_block_start(else_bb, phi_val, vec![(pre_branch_bb, pre_v)])?;
|
||||
self.update_variable(name, phi_val);
|
||||
}
|
||||
}
|
||||
let mut else_var_map_end_opt: Option<HashMap<String, ValueId>> = None;
|
||||
if let Some(es) = else_body.clone() {
|
||||
for s in es.into_iter() {
|
||||
|
||||
Reference in New Issue
Block a user