Files
hakorune/src/mir/builder/builder_emit.rs

257 lines
11 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::{origin, observe, utils};
use super::{BasicBlockId, MirBuilder, MirInstruction, ValueId};
impl MirBuilder {
/// Emit an instruction to the current basic block
pub(in crate::mir) fn emit_instruction(&mut self, instruction: MirInstruction) -> Result<(), String> {
let block_id = self.current_block.ok_or("No current basic block")?;
// Make instruction mutable for potential receiver materialization
let mut instruction = instruction;
// Precompute debug metadata to avoid borrow conflicts later
let _dbg_fn_name = self
.scope_ctx
.current_function
.as_ref()
.map(|f| f.signature.name.clone());
let _dbg_region_id = self.debug_current_region_id();
// P0: PHI の軽量補強と観測は、関数ブロック取得前に実施して借用競合を避ける
if let MirInstruction::Phi { dst, inputs, .. } = &instruction {
origin::phi::propagate_phi_meta(self, *dst, inputs);
observe::ssa::emit_phi(self, *dst, inputs);
}
// CRITICAL: Final receiver materialization for MethodCall
// This ensures the receiver has an in-block definition in the same block as the Call.
// Must happen BEFORE function mutable borrow to avoid borrowck conflicts.
if let MirInstruction::Call {
callee: Some(callee),
dst,
args,
effects,
..
} = &instruction
{
use crate::mir::definitions::call_unified::Callee;
if let Callee::Method {
box_name,
method,
receiver: Some(r),
certainty,
box_kind,
} = callee.clone()
{
// LocalSSA: ensure receiver has a Copy in current_block
let r_local = crate::mir::builder::ssa::local::recv(self, r);
// Update instruction with materialized receiver
let new_callee = Callee::Method {
box_name: box_name.clone(),
method: method.clone(),
receiver: Some(r_local),
certainty,
box_kind,
};
instruction = MirInstruction::Call {
dst: *dst,
func: crate::mir::ValueId::INVALID, // Legacy dummy (not a real SSA use)
callee: Some(new_callee),
args: args.clone(),
effects: *effects,
};
}
}
if let Some(ref mut function) = self.scope_ctx.current_function {
// Pre-capture branch/jump targets for predecessor update after we finish
// mutably borrowing the current block.
let (then_t, else_t, jump_t) = match &instruction {
MirInstruction::Branch {
then_bb, else_bb, ..
} => (Some(*then_bb), Some(*else_bb), None),
MirInstruction::Jump { target, .. } => (None, None, Some(*target)),
_ => (None, None, None),
};
// Extract function name before mutable borrow to avoid borrowck error
let current_fn_name = function.signature.name.clone();
if let Some(block) = function.get_block_mut(block_id) {
// CRITICAL: Copy専用トレースLocalSSA調査用
if let MirInstruction::Copy { dst, src } = &instruction {
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
eprintln!(
"[emit-inst] fn={} bb={:?} COPY %{} <- %{}",
current_fn_name,
self.current_block.map(|b| b.0).unwrap_or(0),
dst.0,
src.0
);
}
}
// Invariant: Call must always carry a Callee (unified path).
if let MirInstruction::Call { callee, .. } = &instruction {
if callee.is_none() {
return Err("builder invariant violated: MirInstruction::Call.callee must be Some (unified call)".into());
} else if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
use crate::mir::definitions::call_unified::Callee;
if let Some(Callee::Method {
box_name,
method,
receiver: Some(r),
..
}) = callee
{
eprintln!(
"[emit-inst] fn={} bb={:?} Call {}.{} recv=%{}",
current_fn_name,
self.current_block.map(|b| b.0).unwrap_or(0),
box_name,
method,
r.0
);
}
} else if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1")
{
use crate::mir::definitions::call_unified::Callee;
if let Some(Callee::Method {
box_name,
method,
receiver: Some(r),
..
}) = callee
{
let names: Vec<String> = self
.variable_ctx
.variable_map
.iter()
.filter(|(_, &vid)| vid == *r)
.map(|(k, _)| k.clone())
.collect();
eprintln!(
"[builder/recv-trace] fn={} bb={:?} method={}.{} recv=%{} aliases={:?}",
current_fn_name,
self.current_block,
box_name,
method,
r.0,
names
);
}
}
}
if utils::builder_debug_enabled() {
eprintln!(
"[BUILDER] emit @bb{} -> {}",
block_id,
match &instruction {
MirInstruction::TypeOp { dst, op, value, ty } =>
format!("typeop {:?} {} {:?} -> {}", op, value, ty, dst),
MirInstruction::Print { value, .. } => format!("print {}", value),
MirInstruction::BoxCall {
box_val,
method,
method_id,
args,
dst,
..
} => {
if let Some(mid) = method_id {
format!(
"boxcall {}.{}[#{}]({:?}) -> {:?}",
box_val, method, mid, args, dst
)
} else {
format!(
"boxcall {}.{}({:?}) -> {:?}",
box_val, method, args, dst
)
}
}
MirInstruction::Call {
func, args, dst, ..
} => format!("call {}({:?}) -> {:?}", func, args, dst),
MirInstruction::NewBox {
dst,
box_type,
args,
} => format!("new {}({:?}) -> {}", box_type, args, dst),
MirInstruction::Const { dst, value } =>
format!("const {:?} -> {}", value, dst),
MirInstruction::Branch {
condition,
then_bb,
else_bb,
..
} => format!("br {}, {}, {}", condition, then_bb, else_bb),
MirInstruction::Jump { target, .. } => format!("br {}", target),
_ => format!("{:?}", instruction),
}
);
}
// Phase 136 Step 6/7: Use metadata_ctx for span
block.add_instruction_with_span(
instruction.clone(),
self.metadata_ctx.current_span(),
);
// Drop the mutable borrow of `block` before updating other blocks
}
// Update predecessor sets for branch/jump immediately so that
// debug_verify_phi_inputs can observe a consistent CFG without
// requiring a full function.update_cfg() pass.
if let Some(t) = then_t {
if let Some(succ) = function.get_block_mut(t) {
succ.add_predecessor(block_id);
}
}
if let Some(t) = else_t {
if let Some(succ) = function.get_block_mut(t) {
succ.add_predecessor(block_id);
}
}
if let Some(t) = jump_t {
if let Some(succ) = function.get_block_mut(t) {
succ.add_predecessor(block_id);
}
}
Ok(())
} else {
Err(format!("Basic block {} does not exist", block_id))
}
}
/// Update an existing PHI instruction's inputs (for loop sealing)
/// Used by LoopFormBuilder to complete incomplete PHI nodes
#[allow(dead_code)]
pub(super) fn update_phi_instruction(
&mut self,
block: BasicBlockId,
phi_id: ValueId,
new_inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> {
if let Some(ref mut function) = self.scope_ctx.current_function {
if let Some(block_data) = function.get_block_mut(block) {
// Find PHI instruction with matching dst
for inst in &mut block_data.instructions {
if let MirInstruction::Phi { dst, inputs, .. } = inst {
if *dst == phi_id {
*inputs = new_inputs;
return Ok(());
}
}
}
Err(format!(
"PHI instruction {} not found in block {}",
phi_id, block
))
} else {
Err(format!("Block {} not found", block))
}
} else {
Err("No current function".to_string())
}
}
}