phase29am(p0): implement coreplan if/exit lowering

This commit is contained in:
2025-12-29 16:43:41 +09:00
parent fd7e3fee35
commit 2dbc4b5968
2 changed files with 219 additions and 9 deletions

View File

@ -17,7 +17,7 @@
//! - 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, CoreLoopPlan, CorePlan};
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};
@ -41,18 +41,12 @@ impl PlanLowerer {
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) => {
// P1: If is handled inline in Loop body
Err("[lowerer] Standalone CorePlan::If not yet supported".to_string())
}
CorePlan::If(if_plan) => Self::lower_if(builder, if_plan, ctx),
CorePlan::Effect(effect) => {
Self::emit_effect(builder, &effect)?;
Ok(None)
}
CorePlan::Exit(_exit) => {
// P1: Exit is handled by edge CFG in Loop
Err("[lowerer] Standalone CorePlan::Exit not yet supported".to_string())
}
CorePlan::Exit(exit) => Self::lower_exit(builder, exit),
}
}
@ -103,6 +97,74 @@ impl PlanLowerer {
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,
@ -265,3 +327,89 @@ impl PlanLowerer {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{ASTNode, LiteralValue, Span};
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"
);
}
}

View File

@ -17,6 +17,7 @@
//! - V8: Frag entry matches header_bb
//! - V9: block_effects contains header_bb
//! - V10: body_bb effects go in loop_plan.body (block_effects[body_bb] must be empty)
//! - V11: Exit must be last in Seq/If branch
//!
//! Phase 273 P3: V1 (Carrier completeness) removed with CoreCarrierInfo
@ -53,6 +54,8 @@ impl PlanVerifier {
));
}
Self::verify_exit_position(plans, depth, "Seq")?;
for (i, plan) in plans.iter().enumerate() {
Self::verify_plan(plan, depth + 1, in_loop).map_err(|e| {
format!("[Seq[{}]] {}", i, e)
@ -164,6 +167,20 @@ impl PlanVerifier {
));
}
if let Some(else_plans) = &if_plan.else_plans {
if else_plans.is_empty() {
return Err(format!(
"[V5] If at depth {} has empty else_plans",
depth
));
}
}
Self::verify_exit_position(&if_plan.then_plans, depth, "If.then")?;
if let Some(else_plans) = &if_plan.else_plans {
Self::verify_exit_position(else_plans, depth, "If.else")?;
}
for (i, plan) in if_plan.then_plans.iter().enumerate() {
Self::verify_plan(plan, depth + 1, in_loop).map_err(|e| {
format!("[If.then[{}]] {}", i, e)
@ -246,6 +263,21 @@ impl PlanVerifier {
Ok(())
}
fn verify_exit_position(plans: &[CorePlan], depth: usize, scope: &str) -> Result<(), String> {
for (i, plan) in plans.iter().enumerate() {
if matches!(plan, CorePlan::Exit(_)) && i + 1 != plans.len() {
return Err(format!(
"[V11] Exit at depth {} in {} must be last (index {}, len {})",
depth,
scope,
i,
plans.len()
));
}
}
Ok(())
}
/// V6: Basic ValueId validity check
///
/// Note: This is a basic check. Full validity would require builder context.
@ -291,6 +323,36 @@ mod tests {
assert!(result.is_ok());
}
#[test]
fn test_verify_if_empty_else_fails() {
let if_plan = CoreIfPlan {
condition: ValueId(1),
then_plans: vec![CorePlan::Effect(CoreEffectPlan::Const {
dst: ValueId(2),
value: ConstValue::Integer(1),
})],
else_plans: Some(vec![]),
};
let plan = CorePlan::If(if_plan);
let result = PlanVerifier::verify(&plan);
assert!(result.is_err());
assert!(result.unwrap_err().contains("[V5]"));
}
#[test]
fn test_verify_exit_not_last_fails() {
let plan = CorePlan::Seq(vec![
CorePlan::Exit(CoreExitPlan::Return(None)),
CorePlan::Effect(CoreEffectPlan::Const {
dst: ValueId(1),
value: ConstValue::Integer(0),
}),
]);
let result = PlanVerifier::verify(&plan);
assert!(result.is_err());
assert!(result.unwrap_err().contains("[V11]"));
}
#[test]
fn test_v10_body_bb_effects_in_block_effects_fails() {
// V10: body_bb effects must be empty in block_effects