Files
hakorune/archive/rust-llvm-backend/llvm/compiler/codegen/instructions/flow.rs

589 lines
26 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 inkwell::basic_block::BasicBlock;
use inkwell::values::{BasicValueEnum, IntValue, PhiValue};
use std::collections::HashMap;
use crate::backend::llvm::context::CodegenContext;
use crate::mir::{function::MirFunction, BasicBlockId, ValueId};
use super::super::types::{map_mirtype_to_basic, to_bool};
use super::builder_cursor::BuilderCursor;
use super::Resolver;
fn phi_trace_on() -> bool {
std::env::var("NYASH_LLVM_TRACE_PHI").ok().as_deref() == Some("1")
}
pub(in super::super) fn emit_return<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut Resolver<'ctx>,
cur_bid: BasicBlockId,
func: &MirFunction,
vmap: &HashMap<ValueId, BasicValueEnum<'ctx>>,
value: &Option<ValueId>,
bb_map: &HashMap<BasicBlockId, BasicBlock<'ctx>>,
preds: &HashMap<BasicBlockId, Vec<BasicBlockId>>,
block_end_values: &HashMap<BasicBlockId, HashMap<ValueId, BasicValueEnum<'ctx>>>,
) -> Result<(), String> {
match (&func.signature.return_type, value) {
(crate::mir::MirType::Void, _) => {
cursor.emit_term(cur_bid, |b| {
b.build_return(None).unwrap();
});
Ok(())
}
(_t, Some(vid)) => {
// Resolve return value according to expected type
let expected = map_mirtype_to_basic(codegen.context, &func.signature.return_type);
use inkwell::types::BasicTypeEnum as BT;
let v_adj: BasicValueEnum<'ctx> = match expected {
BT::IntType(it) => {
let iv = resolver.resolve_i64(
codegen,
cursor,
cur_bid,
*vid,
bb_map,
preds,
block_end_values,
vmap,
)?;
// Cast to expected width
let bw_src = iv.get_type().get_bit_width();
let bw_dst = it.get_bit_width();
if bw_src == bw_dst {
iv.into()
} else if bw_src < bw_dst {
cursor
.emit_instr(cur_bid, |b| b.build_int_z_extend(iv, it, "ret_zext"))
.map_err(|e| e.to_string())?
.into()
} else if bw_dst == 1 {
to_bool(codegen.context, iv.into(), &codegen.builder)?.into()
} else {
cursor
.emit_instr(cur_bid, |b| b.build_int_truncate(iv, it, "ret_trunc"))
.map_err(|e| e.to_string())?
.into()
}
}
BT::PointerType(pt) => {
let pv = resolver.resolve_ptr(
codegen,
cursor,
cur_bid,
*vid,
bb_map,
preds,
block_end_values,
vmap,
)?;
// If expected pointer type differs (e.g., typed ptr vs i8*), bitcast
if pv.get_type() == pt {
pv.into()
} else {
codegen
.builder
.build_pointer_cast(pv, pt, "ret_bitcast")
.map_err(|e| e.to_string())?
.into()
}
}
BT::FloatType(ft) => {
let fv = resolver.resolve_f64(
codegen,
cursor,
cur_bid,
*vid,
bb_map,
preds,
block_end_values,
vmap,
)?;
if fv.get_type() == ft {
fv.into()
} else {
cursor
.emit_instr(cur_bid, |b| b.build_float_cast(fv, ft, "ret_fcast"))
.map_err(|e| e.to_string())?
.into()
}
}
_ => return Err("unsupported return basic type".to_string()),
};
cursor.emit_term(cur_bid, |b| {
b.build_return(Some(&v_adj))
.map_err(|e| e.to_string())
.unwrap();
});
Ok(())
}
(_t, None) => Err("non-void function missing return value".to_string()),
}
}
pub(in super::super) fn emit_jump<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
bid: BasicBlockId,
target: &BasicBlockId,
bb_map: &HashMap<BasicBlockId, BasicBlock<'ctx>>,
phis_by_block: &HashMap<
BasicBlockId,
Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>,
>,
) -> Result<(), String> {
// Non-sealed incoming wiring removed: rely on sealed snapshots and resolver-driven PHIs.
let tbb = *bb_map.get(target).ok_or("target bb missing")?;
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!("[LLVM] emit_jump: {} -> {}", bid.as_u32(), target.as_u32());
}
cursor.emit_term(bid, |b| {
b.build_unconditional_branch(tbb)
.map_err(|e| e.to_string())
.unwrap();
});
if phi_trace_on() {
eprintln!(
"[PHI:jump] pred={} -> succ={}",
bid.as_u32(),
target.as_u32()
);
}
Ok(())
}
pub(in super::super) fn emit_branch<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut super::Resolver<'ctx>,
bid: BasicBlockId,
condition: &ValueId,
then_bb: &BasicBlockId,
else_bb: &BasicBlockId,
bb_map: &HashMap<BasicBlockId, BasicBlock<'ctx>>,
phis_by_block: &HashMap<
BasicBlockId,
Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>,
>,
vmap: &HashMap<ValueId, BasicValueEnum<'ctx>>,
preds: &HashMap<BasicBlockId, Vec<BasicBlockId>>,
block_end_values: &HashMap<BasicBlockId, HashMap<ValueId, BasicValueEnum<'ctx>>>,
) -> Result<(), String> {
// Localize condition as i64 and convert to i1 via != 0Resolver 経由のみ)
let ci = resolver.resolve_i64(
codegen,
cursor,
bid,
*condition,
bb_map,
preds,
block_end_values,
vmap,
)?;
let zero = codegen.context.i64_type().const_zero();
let b = codegen
.builder
.build_int_compare(inkwell::IntPredicate::NE, ci, zero, "cond_nez")
.map_err(|e| e.to_string())?;
// Non-sealed incoming wiring removed: rely on sealed snapshots and resolver-driven PHIs.
let tbb = *bb_map.get(then_bb).ok_or("then bb missing")?;
let ebb = *bb_map.get(else_bb).ok_or("else bb missing")?;
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!(
"[LLVM] emit_branch: {} -> then {} / else {}",
bid.as_u32(),
then_bb.as_u32(),
else_bb.as_u32()
);
}
cursor.emit_term(bid, |bd| {
bd.build_conditional_branch(b, tbb, ebb)
.map_err(|e| e.to_string())
.unwrap();
});
Ok(())
}
// Coerce a value to the PHI node's type, inserting casts in the current block if necessary.
fn coerce_to_type<'ctx>(
codegen: &CodegenContext<'ctx>,
phi: &PhiValue<'ctx>,
val: BasicValueEnum<'ctx>,
) -> Result<BasicValueEnum<'ctx>, String> {
use inkwell::types::BasicTypeEnum as BT;
match (phi.as_basic_value().get_type(), val) {
(BT::IntType(it), BasicValueEnum::IntValue(iv)) => {
let bw_src = iv.get_type().get_bit_width();
let bw_dst = it.get_bit_width();
if bw_src == bw_dst {
Ok(iv.into())
} else if bw_src < bw_dst {
Ok(codegen
.builder
.build_int_z_extend(iv, it, "phi_zext")
.map_err(|e| e.to_string())?
.into())
} else if bw_dst == 1 {
// Narrow to i1 via != 0
Ok(
super::super::types::to_bool(codegen.context, iv.into(), &codegen.builder)?
.into(),
)
} else {
Ok(codegen
.builder
.build_int_truncate(iv, it, "phi_trunc")
.map_err(|e| e.to_string())?
.into())
}
}
(BT::IntType(it), BasicValueEnum::PointerValue(pv)) => Ok(codegen
.builder
.build_ptr_to_int(pv, it, "phi_p2i")
.map_err(|e| e.to_string())?
.into()),
(BT::IntType(it), BasicValueEnum::FloatValue(fv)) => Ok(codegen
.builder
.build_float_to_signed_int(fv, it, "phi_f2i")
.map_err(|e| e.to_string())?
.into()),
(BT::PointerType(pt), BasicValueEnum::IntValue(iv)) => Ok(codegen
.builder
.build_int_to_ptr(iv, pt, "phi_i2p")
.map_err(|e| e.to_string())?
.into()),
(BT::PointerType(_), BasicValueEnum::PointerValue(pv)) => Ok(pv.into()),
(BT::FloatType(ft), BasicValueEnum::IntValue(iv)) => Ok(codegen
.builder
.build_signed_int_to_float(iv, ft, "phi_i2f")
.map_err(|e| e.to_string())?
.into()),
(BT::FloatType(_), BasicValueEnum::FloatValue(fv)) => Ok(fv.into()),
// Already matching or unsupported combination
(_, v) => Ok(v),
}
}
/// Sealed-SSA style: when a block is finalized, add PHI incoming for all successor blocks.
pub(in super::super) fn seal_block<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
func: &MirFunction,
bid: BasicBlockId,
succs: &HashMap<BasicBlockId, Vec<BasicBlockId>>,
bb_map: &HashMap<BasicBlockId, BasicBlock<'ctx>>,
phis_by_block: &HashMap<
BasicBlockId,
Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>,
>,
// Snapshot of value map at end of each predecessor block
block_end_values: &HashMap<BasicBlockId, HashMap<ValueId, BasicValueEnum<'ctx>>>,
) -> Result<(), String> {
if let Some(slist) = succs.get(&bid) {
for sb in slist {
if let Some(pl) = phis_by_block.get(sb) {
for (_dst, phi, inputs) in pl {
// Handle only the current predecessor (bid)
if let Some((_, in_vid)) = inputs.iter().find(|(p, _)| p == &bid) {
// Prefer the predecessorの block-end snapshot。なければ型ゼロを合成
let snap_opt = block_end_values
.get(&bid)
.and_then(|m| m.get(in_vid).copied());
let mut val = if let Some(sv) = snap_opt {
sv
} else {
// Synthesize zero to avoid dominance violationsvmap には依存しない)
let bt = phi.as_basic_value().get_type();
use inkwell::types::BasicTypeEnum as BT;
match bt {
BT::IntType(it) => it.const_zero().into(),
BT::FloatType(ft) => ft.const_zero().into(),
BT::PointerType(pt) => pt.const_zero().into(),
_ => return Err(format!(
"phi incoming (seal) missing: pred={} succ_bb={} in_vid={} (no snapshot)",
bid.as_u32(), sb.as_u32(), in_vid.as_u32()
)),
}
};
// Insert any required casts in the predecessor block, right before its terminator
if let Some(pred_llbb) = bb_map.get(&bid) {
cursor.with_block(bid, *pred_llbb, |c| {
let term = unsafe { pred_llbb.get_terminator() };
if let Some(t) = term {
codegen.builder.position_before(&t);
} else {
c.position_at_end(*pred_llbb);
}
val = coerce_to_type(codegen, phi, val)
.expect("coerce_to_type in seal_block");
});
}
let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?;
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
let tys = phi
.as_basic_value()
.get_type()
.print_to_string()
.to_string();
eprintln!(
"[PHI] sealed add pred_bb={} val={} ty={}{}",
bid.as_u32(),
in_vid.as_u32(),
tys,
if snap_opt.is_some() {
" (snapshot)"
} else {
" (synth)"
}
);
}
match val {
BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]),
BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]),
BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]),
_ => return Err("unsupported phi incoming value (seal)".to_string()),
}
} else {
// Missing mapping for this predecessor: synthesize a typed zero
let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?;
use inkwell::types::BasicTypeEnum as BT;
let bt = phi.as_basic_value().get_type();
let z: BasicValueEnum = match bt {
BT::IntType(it) => it.const_zero().into(),
BT::FloatType(ft) => ft.const_zero().into(),
BT::PointerType(pt) => pt.const_zero().into(),
_ => {
return Err("unsupported phi type for zero synth (seal)".to_string())
}
};
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!(
"[PHI] sealed add (synth) pred_bb={} zero-ty={}",
bid.as_u32(),
bt.print_to_string().to_string()
);
}
match z {
BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]),
BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]),
BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]),
_ => return Err("unsupported phi incoming (synth)".to_string()),
}
}
}
}
}
}
Ok(())
}
/// Normalize PHI incoming entries for a successor block, ensuring exactly
/// one entry per predecessor. This runs once all preds have been sealed.
pub(in super::super) fn finalize_phis<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
func: &MirFunction,
succ_bb: BasicBlockId,
preds: &HashMap<BasicBlockId, Vec<BasicBlockId>>,
bb_map: &HashMap<BasicBlockId, BasicBlock<'ctx>>,
phis_by_block: &HashMap<
BasicBlockId,
Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>,
>,
block_end_values: &HashMap<BasicBlockId, HashMap<ValueId, BasicValueEnum<'ctx>>>,
vmap: &HashMap<ValueId, BasicValueEnum<'ctx>>,
) -> Result<(), String> {
let pred_list = preds.get(&succ_bb).cloned().unwrap_or_default();
if pred_list.is_empty() {
return Ok(());
}
if let Some(phis) = phis_by_block.get(&succ_bb) {
for (_dst, phi, inputs) in phis {
for pred in &pred_list {
// If this phi expects a value from pred, find the associated Mir ValueId
if let Some((_, in_vid)) = inputs.iter().find(|(p, _)| p == pred) {
// If an incoming from this pred already exists, skip
// Note: inkwell does not expose an iterator over incoming; rely on the fact
// we add at most once per pred in seal_block. If duplicates occurred earlier,
// adding again is harmlessly ignored by verifier if identical; otherwise rely on our new regime.
// Fetch value snapshot at end of pred; fallback per our policy
let snap_opt = block_end_values
.get(pred)
.and_then(|m| m.get(in_vid).copied());
let mut val = if let Some(sv) = snap_opt {
sv
} else {
let bt = phi.as_basic_value().get_type();
use inkwell::types::BasicTypeEnum as BT;
match bt {
BT::IntType(it) => it.const_zero().into(),
BT::FloatType(ft) => ft.const_zero().into(),
BT::PointerType(pt) => pt.const_zero().into(),
_ => return Err(format!(
"phi incoming (finalize) missing: pred={} succ_bb={} in_vid={} (no snapshot)",
pred.as_u32(), succ_bb.as_u32(), in_vid.as_u32()
)),
}
};
// Insert casts in pred block, just before its terminator
if let Some(pred_llbb) = bb_map.get(pred) {
cursor.with_block(*pred, *pred_llbb, |c| {
let term = unsafe { pred_llbb.get_terminator() };
if let Some(t) = term {
codegen.builder.position_before(&t);
} else {
c.position_at_end(*pred_llbb);
}
val = coerce_to_type(codegen, phi, val)
.expect("coerce_to_type finalize_phis");
});
}
let pred_bb = *bb_map.get(pred).ok_or("pred bb missing")?;
if phi_trace_on() {
eprintln!(
"[PHI:finalize] succ={} pred={} vid={} ty={}",
succ_bb.as_u32(),
pred.as_u32(),
in_vid.as_u32(),
phi.as_basic_value()
.get_type()
.print_to_string()
.to_string()
);
}
match val {
BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]),
BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]),
BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]),
_ => return Err("unsupported phi incoming value (finalize)".to_string()),
}
} else {
// This PHI lacks a mapping for this predecessor entirely; synthesize zero
let pred_bb = *bb_map.get(pred).ok_or("pred bb missing")?;
use inkwell::types::BasicTypeEnum as BT;
let bt = phi.as_basic_value().get_type();
let z: BasicValueEnum = match bt {
BT::IntType(it) => it.const_zero().into(),
BT::FloatType(ft) => ft.const_zero().into(),
BT::PointerType(pt) => pt.const_zero().into(),
_ => {
return Err("unsupported phi type for zero synth (finalize)".to_string())
}
};
if phi_trace_on() {
eprintln!(
"[PHI:finalize] succ={} pred={} vid=? ty={} src=synth_zero",
succ_bb.as_u32(),
pred.as_u32(),
bt.print_to_string().to_string()
);
}
match z {
BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]),
BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]),
BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]),
_ => return Err("unsupported phi incoming (synth finalize)".to_string()),
}
}
}
}
}
Ok(())
}
/// Localize a MIR value as an i64 in the current block by creating a PHI that merges
/// predecessor snapshots. This avoids using values defined in non-dominating blocks.
/// Sealed SSA mode is assumed; when a predecessor snapshot is missing, synthesize zero.
pub(in super::super) fn localize_to_i64<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
cur_bid: BasicBlockId,
vid: ValueId,
bb_map: &std::collections::HashMap<BasicBlockId, BasicBlock<'ctx>>,
preds: &std::collections::HashMap<BasicBlockId, Vec<BasicBlockId>>,
block_end_values: &std::collections::HashMap<
BasicBlockId,
std::collections::HashMap<ValueId, BasicValueEnum<'ctx>>,
>,
vmap: &std::collections::HashMap<ValueId, BasicValueEnum<'ctx>>,
) -> Result<IntValue<'ctx>, String> {
let i64t = codegen.context.i64_type();
// Note: avoid using current vmap directly here, as it may hold values
// defined in non-dominating predecessors. We rely on predecessor snapshots
// in sealed SSA mode to maintain dominance.
let cur_llbb = *bb_map.get(&cur_bid).ok_or("cur bb missing")?;
// If no predecessors, conservatively return zerovmap には依存しない)
let pred_list = preds.get(&cur_bid).cloned().unwrap_or_default();
if pred_list.is_empty() {
return Ok(i64t.const_zero());
}
// Build PHI at the top of current block (before any non-PHI), then restore insertion point
let saved_ip = codegen.builder.get_insert_block();
if let Some(first) = cur_llbb.get_first_instruction() {
codegen.builder.position_before(&first);
} else {
codegen.builder.position_at_end(cur_llbb);
}
let phi = codegen
.builder
.build_phi(i64t, &format!("loc_i64_{}", vid.as_u32()))
.map_err(|e| e.to_string())?;
for p in &pred_list {
let pred_bb = *bb_map.get(p).ok_or("pred bb missing")?;
// Fetch snapshot at end of pred; if missing, synthesize zero
let base = block_end_values
.get(p)
.and_then(|m| m.get(&vid).copied())
.unwrap_or_else(|| i64t.const_zero().into());
// Insert required casts in the predecessor block before its terminator
let mut iv_out = i64t.const_zero();
cursor.with_block(*p, pred_bb, |c| {
let term = unsafe { pred_bb.get_terminator() };
if let Some(t) = term {
codegen.builder.position_before(&t);
} else {
c.position_at_end(pred_bb);
}
iv_out = match base {
BasicValueEnum::IntValue(iv) => {
if iv.get_type() == i64t {
iv
} else {
codegen
.builder
.build_int_z_extend(iv, i64t, "loc_zext_p")
.map_err(|e| e.to_string())
.unwrap()
}
}
BasicValueEnum::PointerValue(pv) => codegen
.builder
.build_ptr_to_int(pv, i64t, "loc_p2i_p")
.map_err(|e| e.to_string())
.unwrap(),
BasicValueEnum::FloatValue(fv) => codegen
.builder
.build_float_to_signed_int(fv, i64t, "loc_f2i_p")
.map_err(|e| e.to_string())
.unwrap(),
_ => i64t.const_zero(),
};
});
phi.add_incoming(&[(&iv_out, pred_bb)]);
if phi_trace_on() {
eprintln!(
"[PHI:resolve] cur={} pred={} vid={} ty=i64",
cur_bid.as_u32(),
p.as_u32(),
vid.as_u32()
);
}
}
// Restore insertion point
if let Some(bb) = saved_ip {
codegen.builder.position_at_end(bb);
}
Ok(phi.as_basic_value().into_int_value())
}