493 lines
17 KiB
Rust
493 lines
17 KiB
Rust
//! Phase 273 P3: PlanLowerer - CorePlan → MIR 生成 (SSOT)
|
|
//!
|
|
//! # Responsibilities
|
|
//!
|
|
//! - Receive CorePlan from PlanNormalizer
|
|
//! - Emit MIR instructions using pre-allocated ValueIds
|
|
//! - No pattern-specific knowledge (pattern-agnostic)
|
|
//!
|
|
//! # Key Design Decision
|
|
//!
|
|
//! Lowerer processes CorePlan ONLY. It does not know about scan, split, or
|
|
//! any other pattern-specific semantics. All pattern knowledge is in Normalizer.
|
|
//!
|
|
//! # Phase 273 P3: SSOT Finalization
|
|
//!
|
|
//! - Generalized fields (block_effects/phis/frag/final_values) are now REQUIRED
|
|
//! - Legacy fallback has been removed (Fail-Fast on missing fields)
|
|
//! - Pattern-specific emission functions (emit_scan_with_init_edgecfg) no longer used
|
|
|
|
use super::{CoreEffectPlan, CoreExitPlan, CoreIfPlan, CoreLoopPlan, CorePlan};
|
|
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
|
|
use crate::mir::builder::MirBuilder;
|
|
use crate::mir::{MirInstruction, ValueId};
|
|
|
|
/// Phase 273 P1: PlanLowerer - CorePlan → MIR 生成 (SSOT)
|
|
pub(in crate::mir::builder) struct PlanLowerer;
|
|
|
|
impl PlanLowerer {
|
|
/// CorePlan を受け取り、MIR を生成
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `builder` - MIR builder (mutable access for instruction emission)
|
|
/// * `plan` - CorePlan from Normalizer (pre-allocated ValueIds)
|
|
/// * `ctx` - Loop pattern context for debug/func_name
|
|
pub(in crate::mir::builder) fn lower(
|
|
builder: &mut MirBuilder,
|
|
plan: CorePlan,
|
|
ctx: &LoopPatternContext,
|
|
) -> Result<Option<ValueId>, String> {
|
|
match plan {
|
|
CorePlan::Seq(plans) => Self::lower_seq(builder, plans, ctx),
|
|
CorePlan::Loop(loop_plan) => Self::lower_loop(builder, loop_plan, ctx),
|
|
CorePlan::If(if_plan) => Self::lower_if(builder, if_plan, ctx),
|
|
CorePlan::Effect(effect) => {
|
|
Self::emit_effect(builder, &effect)?;
|
|
Ok(None)
|
|
}
|
|
CorePlan::Exit(exit) => Self::lower_exit(builder, exit),
|
|
}
|
|
}
|
|
|
|
/// Seq: process plans in order
|
|
fn lower_seq(
|
|
builder: &mut MirBuilder,
|
|
plans: Vec<CorePlan>,
|
|
ctx: &LoopPatternContext,
|
|
) -> Result<Option<ValueId>, String> {
|
|
let mut result = None;
|
|
for plan in plans {
|
|
result = Self::lower(builder, plan, ctx)?;
|
|
}
|
|
Ok(result)
|
|
}
|
|
|
|
/// Loop: emit blocks, effects, PHI, and edge CFG
|
|
///
|
|
/// This is pattern-agnostic. All pattern knowledge is in Normalizer.
|
|
/// Phase 273 P3: Generalized fields are now REQUIRED (Fail-Fast).
|
|
fn lower_loop(
|
|
builder: &mut MirBuilder,
|
|
loop_plan: CoreLoopPlan,
|
|
ctx: &LoopPatternContext,
|
|
) -> Result<Option<ValueId>, String> {
|
|
use crate::mir::builder::control_flow::joinir::trace;
|
|
|
|
let trace_logger = trace::trace();
|
|
let debug = ctx.debug;
|
|
|
|
if debug {
|
|
trace_logger.debug(
|
|
"lowerer/loop",
|
|
&format!(
|
|
"Phase 273 P3: Lowering CoreLoopPlan for {}",
|
|
ctx.func_name
|
|
),
|
|
);
|
|
}
|
|
|
|
// Phase 273 P3: Generalized fields are now struct fields (not Option)
|
|
// No validation needed - type system guarantees presence
|
|
|
|
if debug {
|
|
trace_logger.debug("lowerer/loop", "Processing generalized fields (SSOT)");
|
|
}
|
|
|
|
Self::lower_loop_generalized(builder, loop_plan, ctx)
|
|
}
|
|
|
|
/// If: emit Branch and lower then/else plans (standalone)
|
|
fn lower_if(
|
|
builder: &mut MirBuilder,
|
|
if_plan: CoreIfPlan,
|
|
ctx: &LoopPatternContext,
|
|
) -> Result<Option<ValueId>, String> {
|
|
use crate::mir::builder::emission::branch::{emit_conditional, emit_jump};
|
|
|
|
let _pre_branch_bb = builder
|
|
.current_block
|
|
.ok_or_else(|| "[lowerer] No current block for CorePlan::If".to_string())?;
|
|
|
|
let then_bb = builder.next_block_id();
|
|
let else_bb = builder.next_block_id();
|
|
let merge_bb = builder.next_block_id();
|
|
|
|
builder.ensure_block_exists(then_bb)?;
|
|
builder.ensure_block_exists(else_bb)?;
|
|
builder.ensure_block_exists(merge_bb)?;
|
|
|
|
let mut condition_val = if_plan.condition;
|
|
crate::mir::builder::ssa::local::finalize_branch_cond(builder, &mut condition_val);
|
|
emit_conditional(builder, condition_val, then_bb, else_bb)?;
|
|
|
|
// then
|
|
builder.start_new_block(then_bb)?;
|
|
for plan in if_plan.then_plans {
|
|
Self::lower(builder, plan, ctx)?;
|
|
}
|
|
let then_reaches_merge = !builder.is_current_block_terminated();
|
|
if then_reaches_merge {
|
|
emit_jump(builder, merge_bb)?;
|
|
}
|
|
|
|
// else
|
|
builder.start_new_block(else_bb)?;
|
|
if let Some(else_plans) = if_plan.else_plans {
|
|
for plan in else_plans {
|
|
Self::lower(builder, plan, ctx)?;
|
|
}
|
|
}
|
|
let else_reaches_merge = !builder.is_current_block_terminated();
|
|
if else_reaches_merge {
|
|
emit_jump(builder, merge_bb)?;
|
|
}
|
|
|
|
// merge (may be unreachable if both branches terminate)
|
|
builder.start_new_block(merge_bb)?;
|
|
Ok(None)
|
|
}
|
|
|
|
/// Exit: emit Return (standalone); Break/Continue require loop context
|
|
fn lower_exit(
|
|
builder: &mut MirBuilder,
|
|
exit: CoreExitPlan,
|
|
) -> Result<Option<ValueId>, String> {
|
|
match exit {
|
|
CoreExitPlan::Return(opt_val) => {
|
|
builder.emit_instruction(MirInstruction::Return { value: opt_val })?;
|
|
Ok(opt_val)
|
|
}
|
|
CoreExitPlan::Break => Err("[lowerer] CorePlan::Exit::Break requires loop context".to_string()),
|
|
CoreExitPlan::Continue => {
|
|
Err("[lowerer] CorePlan::Exit::Continue requires loop context".to_string())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Phase 273 P3: Generalized loop lowering (SSOT)
|
|
fn lower_loop_generalized(
|
|
builder: &mut MirBuilder,
|
|
loop_plan: CoreLoopPlan,
|
|
ctx: &LoopPatternContext,
|
|
) -> Result<Option<ValueId>, String> {
|
|
use crate::mir::builder::control_flow::joinir::trace;
|
|
use crate::mir::builder::control_flow::edgecfg::api::emit_frag;
|
|
|
|
let trace_logger = trace::trace();
|
|
let debug = ctx.debug;
|
|
|
|
// Phase 273 P3: Direct access (not Option - type system guarantees presence)
|
|
let block_effects = &loop_plan.block_effects;
|
|
let phis = &loop_plan.phis;
|
|
let frag = &loop_plan.frag;
|
|
let final_values = &loop_plan.final_values;
|
|
|
|
// Step 1: Emit Jump from current block to loop entry
|
|
if builder.current_block.is_some() {
|
|
builder.emit_instruction(MirInstruction::Jump {
|
|
target: frag.entry,
|
|
edge_args: None,
|
|
})?;
|
|
}
|
|
|
|
// Step 2: Emit block effects in SSOT order (preheader, header, body, step)
|
|
// Note: Body effects are handled separately via body CorePlan
|
|
for (block_id, effects) in block_effects {
|
|
builder.start_new_block(*block_id)?;
|
|
|
|
// Special handling for body block: emit body CorePlan instead of effects
|
|
if *block_id == loop_plan.body_bb {
|
|
for plan in &loop_plan.body {
|
|
Self::emit_body_plan(builder, plan)?;
|
|
}
|
|
} else {
|
|
// Normal block: emit effects
|
|
for effect in effects {
|
|
Self::emit_effect(builder, effect)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
if debug {
|
|
trace_logger.debug(
|
|
"lowerer/loop_generalized",
|
|
&format!("Block effects emitted: {} blocks", block_effects.len()),
|
|
);
|
|
}
|
|
|
|
// Step 3: Ensure non-effect blocks exist (after_bb, found_bb, etc.)
|
|
builder.ensure_block_exists(loop_plan.after_bb)?;
|
|
builder.ensure_block_exists(loop_plan.found_bb)?;
|
|
|
|
// Step 4: Insert PHIs
|
|
use crate::mir::builder::emission::phi::insert_loop_phi;
|
|
|
|
for phi in phis {
|
|
insert_loop_phi(
|
|
builder,
|
|
phi.block,
|
|
phi.dst,
|
|
phi.inputs.clone(),
|
|
&phi.tag,
|
|
)?;
|
|
}
|
|
|
|
if debug {
|
|
trace_logger.debug(
|
|
"lowerer/loop_generalized",
|
|
&format!("PHI inserted: {} PHIs", phis.len()),
|
|
);
|
|
}
|
|
|
|
// Step 5: Emit Frag (terminators)
|
|
if let Some(ref mut func) = builder.scope_ctx.current_function {
|
|
emit_frag(func, frag)?;
|
|
} else {
|
|
return Err("[lowerer] current_function is None".to_string());
|
|
}
|
|
|
|
if debug {
|
|
trace_logger.debug("lowerer/loop_generalized", "Frag emitted");
|
|
}
|
|
|
|
// Step 6: Update variable_map for final values
|
|
for (name, value_id) in final_values {
|
|
builder
|
|
.variable_ctx
|
|
.variable_map
|
|
.insert(name.clone(), *value_id);
|
|
}
|
|
|
|
// Step 7: Setup after_bb for subsequent AST lowering
|
|
builder.start_new_block(loop_plan.after_bb)?;
|
|
|
|
// Step 8: Return Void (pattern applied successfully)
|
|
use crate::mir::builder::emission::constant::emit_void;
|
|
let void_val = emit_void(builder);
|
|
|
|
if debug {
|
|
trace_logger.debug(
|
|
"lowerer/loop_generalized",
|
|
&format!("Loop complete, returning Void {:?}", void_val),
|
|
);
|
|
}
|
|
|
|
Ok(Some(void_val))
|
|
}
|
|
|
|
// Phase 273 P3: lower_loop_legacy() has been REMOVED
|
|
// All patterns must use generalized fields (block_effects/phis/frag/final_values)
|
|
// Pattern-specific emission functions (emit_scan_with_init_edgecfg) are no longer used
|
|
|
|
/// Emit a single CoreEffectPlan as MirInstruction
|
|
fn emit_effect(builder: &mut MirBuilder, effect: &CoreEffectPlan) -> Result<(), String> {
|
|
match effect {
|
|
CoreEffectPlan::Const { dst, value } => {
|
|
builder.emit_instruction(MirInstruction::Const {
|
|
dst: *dst,
|
|
value: value.clone(),
|
|
})?;
|
|
}
|
|
CoreEffectPlan::MethodCall { dst, object, method, args, effects } => {
|
|
// P2: dst and effects are now specified by Normalizer
|
|
builder.emit_instruction(MirInstruction::BoxCall {
|
|
dst: *dst,
|
|
box_val: *object,
|
|
method: method.clone(),
|
|
method_id: None,
|
|
args: args.clone(),
|
|
effects: *effects,
|
|
})?;
|
|
}
|
|
CoreEffectPlan::BinOp { dst, lhs, op, rhs } => {
|
|
builder.emit_instruction(MirInstruction::BinOp {
|
|
dst: *dst,
|
|
lhs: *lhs,
|
|
op: *op,
|
|
rhs: *rhs,
|
|
})?;
|
|
}
|
|
CoreEffectPlan::Compare { dst, lhs, op, rhs } => {
|
|
builder.emit_instruction(MirInstruction::Compare {
|
|
dst: *dst,
|
|
lhs: *lhs,
|
|
op: *op,
|
|
rhs: *rhs,
|
|
})?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn emit_body_plan(builder: &mut MirBuilder, plan: &CorePlan) -> Result<(), String> {
|
|
match plan {
|
|
CorePlan::Effect(effect) => Self::emit_effect(builder, effect),
|
|
CorePlan::Seq(plans) => {
|
|
for nested in plans {
|
|
Self::emit_body_plan(builder, nested)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
_ => Err("[lowerer] Non-Effect plan in Loop body not yet supported".to_string()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::ast::{ASTNode, LiteralValue, Span};
|
|
use crate::mir::builder::control_flow::edgecfg::api::Frag;
|
|
use crate::mir::{ConstValue, MirInstruction};
|
|
|
|
fn make_ctx<'a>(condition: &'a ASTNode, body: &'a [ASTNode]) -> LoopPatternContext<'a> {
|
|
LoopPatternContext::new(condition, body, "test_coreplan", false, false)
|
|
}
|
|
|
|
#[test]
|
|
fn test_lower_exit_return_sets_terminator() {
|
|
let mut builder = MirBuilder::new();
|
|
builder.enter_function_for_test("test_exit".to_string());
|
|
|
|
let ret_val = builder.alloc_value_for_test();
|
|
builder
|
|
.emit_for_test(MirInstruction::Const {
|
|
dst: ret_val,
|
|
value: ConstValue::Integer(1),
|
|
})
|
|
.expect("emit const");
|
|
|
|
let cond = ASTNode::Literal {
|
|
value: LiteralValue::Bool(true),
|
|
span: Span::unknown(),
|
|
};
|
|
let body: Vec<ASTNode> = vec![];
|
|
let ctx = make_ctx(&cond, &body);
|
|
|
|
let plan = CorePlan::Exit(CoreExitPlan::Return(Some(ret_val)));
|
|
let result = PlanLowerer::lower(&mut builder, plan, &ctx);
|
|
assert!(result.is_ok());
|
|
|
|
let entry = builder.current_block_for_test().expect("entry block");
|
|
let func = builder.scope_ctx.current_function.as_ref().expect("function");
|
|
let block = func.get_block(entry).expect("block");
|
|
assert!(
|
|
matches!(block.terminator, Some(MirInstruction::Return { value: Some(v) }) if v == ret_val),
|
|
"expected Return terminator"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_lower_if_emits_branch() {
|
|
let mut builder = MirBuilder::new();
|
|
builder.enter_function_for_test("test_if".to_string());
|
|
|
|
let entry = builder.current_block_for_test().expect("entry block");
|
|
let cond_val = builder.alloc_value_for_test();
|
|
builder
|
|
.emit_for_test(MirInstruction::Const {
|
|
dst: cond_val,
|
|
value: ConstValue::Bool(true),
|
|
})
|
|
.expect("emit const");
|
|
|
|
let then_val = builder.alloc_value_for_test();
|
|
let if_plan = CoreIfPlan {
|
|
condition: cond_val,
|
|
then_plans: vec![CorePlan::Effect(CoreEffectPlan::Const {
|
|
dst: then_val,
|
|
value: ConstValue::Integer(2),
|
|
})],
|
|
else_plans: None,
|
|
};
|
|
|
|
let cond = ASTNode::Literal {
|
|
value: LiteralValue::Bool(true),
|
|
span: Span::unknown(),
|
|
};
|
|
let body: Vec<ASTNode> = vec![];
|
|
let ctx = make_ctx(&cond, &body);
|
|
|
|
let result = PlanLowerer::lower(&mut builder, CorePlan::If(if_plan), &ctx);
|
|
assert!(result.is_ok());
|
|
|
|
let func = builder.scope_ctx.current_function.as_ref().expect("function");
|
|
let block = func.get_block(entry).expect("entry block");
|
|
assert!(
|
|
matches!(block.terminator, Some(MirInstruction::Branch { .. })),
|
|
"expected Branch terminator"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_lower_loop_body_seq_flattens() {
|
|
let mut builder = MirBuilder::new();
|
|
builder.enter_function_for_test("test_loop_body_seq".to_string());
|
|
|
|
let preheader_bb = builder.next_block_id();
|
|
let header_bb = builder.next_block_id();
|
|
let body_bb = builder.next_block_id();
|
|
let step_bb = builder.next_block_id();
|
|
let after_bb = builder.next_block_id();
|
|
let found_bb = builder.next_block_id();
|
|
|
|
let eff1 = CoreEffectPlan::Const {
|
|
dst: builder.alloc_value_for_test(),
|
|
value: ConstValue::Integer(1),
|
|
};
|
|
let eff2 = CoreEffectPlan::Const {
|
|
dst: builder.alloc_value_for_test(),
|
|
value: ConstValue::Integer(2),
|
|
};
|
|
let eff3 = CoreEffectPlan::Const {
|
|
dst: builder.alloc_value_for_test(),
|
|
value: ConstValue::Integer(3),
|
|
};
|
|
|
|
let loop_plan = CoreLoopPlan {
|
|
preheader_bb,
|
|
header_bb,
|
|
body_bb,
|
|
step_bb,
|
|
after_bb,
|
|
found_bb,
|
|
body: vec![CorePlan::Seq(vec![
|
|
CorePlan::Effect(eff1),
|
|
CorePlan::Seq(vec![CorePlan::Effect(eff2)]),
|
|
CorePlan::Effect(eff3),
|
|
])],
|
|
cond_loop: builder.alloc_value_for_test(),
|
|
cond_match: builder.alloc_value_for_test(),
|
|
block_effects: vec![
|
|
(preheader_bb, vec![]),
|
|
(header_bb, vec![]),
|
|
(body_bb, vec![]),
|
|
(step_bb, vec![]),
|
|
],
|
|
phis: vec![],
|
|
frag: Frag::new(header_bb),
|
|
final_values: vec![],
|
|
};
|
|
|
|
let cond = ASTNode::Literal {
|
|
value: LiteralValue::Bool(true),
|
|
span: Span::unknown(),
|
|
};
|
|
let body: Vec<ASTNode> = vec![];
|
|
let ctx = make_ctx(&cond, &body);
|
|
|
|
let result = PlanLowerer::lower(&mut builder, CorePlan::Loop(loop_plan), &ctx);
|
|
assert!(result.is_ok());
|
|
|
|
let func = builder.scope_ctx.current_function.as_ref().expect("function");
|
|
let block = func.get_block(body_bb).expect("body block");
|
|
let const_count = block
|
|
.instructions
|
|
.iter()
|
|
.filter(|inst| matches!(inst, MirInstruction::Const { .. }))
|
|
.count();
|
|
assert_eq!(const_count, 3, "expected 3 Const effects in body");
|
|
}
|
|
}
|