257 lines
11 KiB
Rust
257 lines
11 KiB
Rust
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())
|
||
}
|
||
}
|
||
}
|