feat(llvm): LoopForm IR experimental scaffolding (Phase 1)
- Add NYASH_ENABLE_LOOPFORM=1 gate for experimental loop normalization - Detect simple while-patterns in Branch terminator (header→body→header) - Add loopform.rs with scaffold for future Signal-based lowering - Wire detection in codegen/mod.rs (non-invasive, logs only) - Update CURRENT_TASK.md with LoopForm experimental plan - Goal: Centralize PHIs at dispatch blocks, simplify terminator management This is the first step towards the LoopForm IR revolution where "Everything is Box × Everything is Loop". Currently detection-only, actual lowering will follow once basic patterns are validated. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -370,3 +370,111 @@ pub(in super::super) fn seal_block<'ctx, 'b>(
|
||||
}
|
||||
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 if func.params.contains(in_vid) {
|
||||
vmap.get(in_vid).copied().unwrap_or_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(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
})
|
||||
} 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
|
||||
let saved_block = codegen.builder.get_insert_block();
|
||||
if let Some(pred_llbb) = bb_map.get(pred) {
|
||||
let term = unsafe { pred_llbb.get_terminator() };
|
||||
if let Some(t) = term { codegen.builder.position_before(&t); }
|
||||
else { codegen.builder.position_at_end(*pred_llbb); }
|
||||
}
|
||||
val = coerce_to_type(codegen, phi, val)?;
|
||||
if let Some(bb) = saved_block { codegen.builder.position_at_end(bb); }
|
||||
let pred_bb = *bb_map.get(pred).ok_or("pred bb missing")?;
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!(
|
||||
"[PHI] finalize add pred_bb={} val={} ty={}",
|
||||
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 std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!(
|
||||
"[PHI] finalize add (synth) pred_bb={} zero-ty={}",
|
||||
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(())
|
||||
}
|
||||
|
||||
84
src/backend/llvm/compiler/codegen/instructions/loopform.rs
Normal file
84
src/backend/llvm/compiler/codegen/instructions/loopform.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use inkwell::{
|
||||
basic_block::BasicBlock,
|
||||
values::{BasicValueEnum, FunctionValue, IntValue},
|
||||
};
|
||||
|
||||
use crate::backend::llvm::context::CodegenContext;
|
||||
use crate::mir::{
|
||||
function::MirFunction,
|
||||
instruction::MirInstruction,
|
||||
ValueId,
|
||||
};
|
||||
|
||||
use super::builder_cursor::BuilderCursor;
|
||||
|
||||
/// LoopForm scaffolding — fixed block layout for while/loop normalization
|
||||
pub struct LoopFormContext<'ctx> {
|
||||
pub header: BasicBlock<'ctx>,
|
||||
pub body: BasicBlock<'ctx>,
|
||||
pub dispatch: BasicBlock<'ctx>,
|
||||
pub latch: BasicBlock<'ctx>,
|
||||
pub exit: BasicBlock<'ctx>,
|
||||
pub loop_id: u32,
|
||||
}
|
||||
|
||||
impl<'ctx> LoopFormContext<'ctx> {
|
||||
/// Create a new LoopForm block set under `function` with a readable name prefix.
|
||||
pub fn new(
|
||||
codegen: &CodegenContext<'ctx>,
|
||||
function: FunctionValue<'ctx>,
|
||||
loop_id: u32,
|
||||
prefix: &str,
|
||||
) -> Self {
|
||||
let header = codegen
|
||||
.context
|
||||
.append_basic_block(function, &format!("{}_lf{}_header", prefix, loop_id));
|
||||
let body = codegen
|
||||
.context
|
||||
.append_basic_block(function, &format!("{}_lf{}_body", prefix, loop_id));
|
||||
let dispatch = codegen
|
||||
.context
|
||||
.append_basic_block(function, &format!("{}_lf{}_dispatch", prefix, loop_id));
|
||||
let latch = codegen
|
||||
.context
|
||||
.append_basic_block(function, &format!("{}_lf{}_latch", prefix, loop_id));
|
||||
let exit = codegen
|
||||
.context
|
||||
.append_basic_block(function, &format!("{}_lf{}_exit", prefix, loop_id));
|
||||
Self { header, body, dispatch, latch, exit, loop_id }
|
||||
}
|
||||
}
|
||||
|
||||
/// Lower a while-like loop using LoopForm shape (Phase 1: scaffold only).
|
||||
/// - condition: MIR value producing i1/i64 truthy
|
||||
/// - body_mir: MIR instructions of loop body
|
||||
/// Note: In Phase 1, this function is not invoked by default lowering; it is a gated scaffold.
|
||||
pub fn lower_while_loopform<'ctx, 'b>(
|
||||
codegen: &CodegenContext<'ctx>,
|
||||
cursor: &mut BuilderCursor<'ctx, 'b>,
|
||||
_func: &MirFunction,
|
||||
_llvm_func: FunctionValue<'ctx>,
|
||||
_condition: &ValueId,
|
||||
_body_mir: &[MirInstruction],
|
||||
_loop_id: u32,
|
||||
_prefix: &str,
|
||||
) -> Result<(), String> {
|
||||
// Gate via env; currently a no-op scaffold so the call sites can be added safely later.
|
||||
let enabled = std::env::var("NYASH_ENABLE_LOOPFORM").ok().as_deref() == Some("1");
|
||||
if !enabled {
|
||||
return Ok(());
|
||||
}
|
||||
// Intentionally minimal implementation placeholder to keep compilation stable.
|
||||
// The full lowering will:
|
||||
// 1) Create LoopFormContext blocks
|
||||
// 2) Emit header with conditional branch to body/dispatch
|
||||
// 3) Lower body and build Signal(tag,payload)
|
||||
// 4) In dispatch, create PHIs (payload/tag) and switch(tag) to latch/exit
|
||||
// 5) Latch branches back to header
|
||||
// For now, do nothing to avoid interfering with current lowering flow.
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[LoopForm] scaffold active but not wired (Phase 1)");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ mod arrays;
|
||||
mod maps;
|
||||
mod arith_ops;
|
||||
mod call;
|
||||
mod loopform;
|
||||
|
||||
pub(super) use blocks::{create_basic_blocks, precreate_phis};
|
||||
pub(super) use flow::{emit_branch, emit_jump, emit_return};
|
||||
@ -23,3 +24,4 @@ pub(super) use mem::{lower_load, lower_store};
|
||||
pub(super) use consts::lower_const;
|
||||
pub(super) use arith_ops::{lower_binop, lower_unary};
|
||||
pub(super) use call::lower_call;
|
||||
pub(super) use loopform::{LoopFormContext, lower_while_loopform};
|
||||
|
||||
Reference in New Issue
Block a user