fix(normalization): Phase 143 execution fix - Param region SSOT
Problem: normalized_helpers allocated env params as ValueId(1,2...) in PHI Reserved region (0-99) instead of Param region (100-999) per JoinValueSpace contract. Root cause: All 4 normalized shadow modules started from next_value_id=1, violating the Param region contract. Solution: - Add NormalizedHelperBox::alloc_env_params_param_region() that allocates params starting from PARAM_MIN (100) - Update 4 normalized shadow files to use new API: - loop_true_if_break_continue.rs - loop_true_break_once.rs - if_as_last_join_k.rs - post_if_post_k.rs - Fix instruction_rewriter.rs type mismatch (func.signature.params → func.params) Verification: - Unit tests: 69/69 PASS - VM smoke: exit code 7 ✅ - LLVM EXE smoke: exit code 7 ✅ (timeout resolved!) ValueId Space Contract (Phase 201): | Region | Range | Purpose | |--------------|----------|------------------------------| | PHI Reserved | 0-99 | Loop header PHI dst | | Param | 100-999 | env params (flag, counter) | | Local | 1000+ | Const, BinOp, condition | 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -5,3 +5,4 @@ pub mod expr_lowerer_box;
|
||||
pub mod expr_lowering_contract;
|
||||
pub mod known_intrinsics; // Phase 141 P1.5
|
||||
pub mod loop_if_exit_contract; // Phase 143 R0: Contract SSOT for loop-if-exit patterns
|
||||
pub mod normalized_helpers; // Phase 143.5: Common helper functions for Normalized lowering
|
||||
|
||||
@ -0,0 +1,186 @@
|
||||
//! Phase 143.5: Normalized lowering 共通ヘルパー関数
|
||||
//!
|
||||
//! 複数のパターン(loop, if)で共有されるヘルパー関数群
|
||||
//! - alloc_value_id: ValueId割り当て
|
||||
//! - alloc_env_params: Env parameters 割り当て
|
||||
//! - build_env_map: BTreeMap構築
|
||||
//! - collect_env_args: Env arguments 収集
|
||||
//! - is_bool_true_literal: Loop condition 検証
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::lowering::join_value_space::{PARAM_MIN, LOCAL_MIN};
|
||||
use crate::ast::{ASTNode, Span};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Box-First: Normalized lowering 共通ヘルパー
|
||||
pub struct NormalizedHelperBox;
|
||||
|
||||
impl NormalizedHelperBox {
|
||||
/// Allocate next ValueId and increment counter
|
||||
pub fn alloc_value_id(next_value_id: &mut u32) -> ValueId {
|
||||
let vid = ValueId(*next_value_id);
|
||||
*next_value_id += 1;
|
||||
vid
|
||||
}
|
||||
|
||||
/// Allocate env parameters (one ValueId per field)
|
||||
pub fn alloc_env_params(
|
||||
fields: &[String],
|
||||
next_value_id: &mut u32,
|
||||
) -> Vec<ValueId> {
|
||||
fields
|
||||
.iter()
|
||||
.map(|_| Self::alloc_value_id(next_value_id))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Build env map from fields and parameters
|
||||
pub fn build_env_map(
|
||||
fields: &[String],
|
||||
params: &[ValueId],
|
||||
) -> BTreeMap<String, ValueId> {
|
||||
let mut env = BTreeMap::new();
|
||||
for (name, vid) in fields.iter().zip(params.iter()) {
|
||||
env.insert(name.clone(), *vid);
|
||||
}
|
||||
env
|
||||
}
|
||||
|
||||
/// Collect env arguments from environment
|
||||
pub fn collect_env_args(
|
||||
fields: &[String],
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> Result<Vec<ValueId>, String> {
|
||||
let mut args = Vec::with_capacity(fields.len());
|
||||
for name in fields {
|
||||
let vid = env
|
||||
.get(name)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("Missing env variable: {}", name))?;
|
||||
args.push(vid);
|
||||
}
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
/// Check if AST node is Bool(true) literal
|
||||
pub fn is_bool_true_literal(ast: &ASTNode) -> bool {
|
||||
matches!(
|
||||
ast,
|
||||
ASTNode::Literal {
|
||||
value: crate::ast::LiteralValue::Bool(true),
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Allocate env parameters in Param region (100+)
|
||||
///
|
||||
/// Phase 143 fix: env params must be in Param region (100-999) per JoinValueSpace contract.
|
||||
///
|
||||
/// Returns (params, next_local) where:
|
||||
/// - params: Vec<ValueId> in Param region [100, 100+n)
|
||||
/// - next_local: Starting point for local allocations (1000+)
|
||||
///
|
||||
/// Call sites should use `next_local` for subsequent alloc_value_id() calls.
|
||||
///
|
||||
/// ## Contract
|
||||
///
|
||||
/// - PHI Reserved (0-99): Loop header PHI dst
|
||||
/// - Param Region (100-999): env params (this function)
|
||||
/// - Local Region (1000+): Const, BinOp, condition results
|
||||
pub fn alloc_env_params_param_region(fields: &[String]) -> (Vec<ValueId>, u32) {
|
||||
let params: Vec<ValueId> = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, _)| ValueId(PARAM_MIN + i as u32))
|
||||
.collect();
|
||||
// Local region starts at LOCAL_MIN (1000)
|
||||
(params, LOCAL_MIN)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_alloc_value_id() {
|
||||
let mut next = 10;
|
||||
let vid = NormalizedHelperBox::alloc_value_id(&mut next);
|
||||
assert_eq!(vid, ValueId(10));
|
||||
assert_eq!(next, 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alloc_value_id_increments() {
|
||||
let mut next = 1;
|
||||
let vid1 = NormalizedHelperBox::alloc_value_id(&mut next);
|
||||
let vid2 = NormalizedHelperBox::alloc_value_id(&mut next);
|
||||
assert_eq!(vid1, ValueId(1));
|
||||
assert_eq!(vid2, ValueId(2));
|
||||
assert_eq!(next, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alloc_env_params() {
|
||||
let fields = vec!["a".to_string(), "b".to_string()];
|
||||
let mut next = 1;
|
||||
let params = NormalizedHelperBox::alloc_env_params(&fields, &mut next);
|
||||
assert_eq!(params, vec![ValueId(1), ValueId(2)]);
|
||||
assert_eq!(next, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_env_map() {
|
||||
let fields = vec!["x".to_string(), "y".to_string()];
|
||||
let params = vec![ValueId(10), ValueId(20)];
|
||||
let env = NormalizedHelperBox::build_env_map(&fields, ¶ms);
|
||||
assert_eq!(env.get("x"), Some(&ValueId(10)));
|
||||
assert_eq!(env.get("y"), Some(&ValueId(20)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collect_env_args() {
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert("a".to_string(), ValueId(1));
|
||||
env.insert("b".to_string(), ValueId(2));
|
||||
let fields = vec!["a".to_string(), "b".to_string()];
|
||||
let args = NormalizedHelperBox::collect_env_args(&fields, &env).unwrap();
|
||||
assert_eq!(args, vec![ValueId(1), ValueId(2)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_bool_true_literal() {
|
||||
let true_lit = ASTNode::Literal {
|
||||
value: crate::ast::LiteralValue::Bool(true),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(NormalizedHelperBox::is_bool_true_literal(&true_lit));
|
||||
|
||||
let false_lit = ASTNode::Literal {
|
||||
value: crate::ast::LiteralValue::Bool(false),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(!NormalizedHelperBox::is_bool_true_literal(&false_lit));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alloc_env_params_param_region() {
|
||||
let fields = vec!["a".to_string(), "b".to_string(), "c".to_string()];
|
||||
let (params, next_local) = NormalizedHelperBox::alloc_env_params_param_region(&fields);
|
||||
|
||||
// Params should be in Param region [100, 103)
|
||||
assert_eq!(params, vec![ValueId(100), ValueId(101), ValueId(102)]);
|
||||
// next_local should be at LOCAL_MIN (1000)
|
||||
assert_eq!(next_local, 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alloc_env_params_param_region_empty() {
|
||||
let fields: Vec<String> = vec![];
|
||||
let (params, next_local) = NormalizedHelperBox::alloc_env_params_param_region(&fields);
|
||||
|
||||
assert!(params.is_empty());
|
||||
assert_eq!(next_local, 1000);
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,12 @@
|
||||
//! Phase 129-B: if-as-last lowering with join_k (dev-only)
|
||||
|
||||
use super::env_layout::EnvLayout;
|
||||
use super::common::normalized_helpers::NormalizedHelperBox;
|
||||
use super::legacy::LegacyLowerer;
|
||||
use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind, StepTree};
|
||||
use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta;
|
||||
use crate::mir::join_ir::lowering::error_tags;
|
||||
use crate::mir::join_ir::{ConstValue, JoinFunction, JoinFuncId, JoinInst, JoinModule, MirLikeInst};
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub struct IfAsLastJoinKLowererBox;
|
||||
|
||||
@ -47,45 +46,9 @@ impl IfAsLastJoinKLowererBox {
|
||||
};
|
||||
|
||||
let env_fields = env_layout.env_fields();
|
||||
|
||||
fn alloc_value_id(next_value_id: &mut u32) -> ValueId {
|
||||
let vid = ValueId(*next_value_id);
|
||||
*next_value_id += 1;
|
||||
vid
|
||||
}
|
||||
|
||||
let mut next_value_id: u32 = 1;
|
||||
|
||||
let alloc_env_params =
|
||||
|fields: &[String], next_value_id: &mut u32| -> Vec<ValueId> {
|
||||
fields
|
||||
.iter()
|
||||
.map(|_| alloc_value_id(next_value_id))
|
||||
.collect()
|
||||
};
|
||||
|
||||
let build_env_map = |fields: &[String], params: &[ValueId]| -> BTreeMap<String, ValueId> {
|
||||
let mut env = BTreeMap::new();
|
||||
for (name, vid) in fields.iter().zip(params.iter()) {
|
||||
env.insert(name.clone(), *vid);
|
||||
}
|
||||
env
|
||||
};
|
||||
|
||||
let collect_env_args = |fields: &[String], env: &BTreeMap<String, ValueId>| -> Result<Vec<ValueId>, String> {
|
||||
let mut args = Vec::with_capacity(fields.len());
|
||||
for name in fields {
|
||||
let vid = env.get(name).copied().ok_or_else(|| {
|
||||
error_tags::freeze_with_hint(
|
||||
"phase129/join_k/env_missing",
|
||||
&format!("env missing required field '{name}'"),
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
)
|
||||
})?;
|
||||
args.push(vid);
|
||||
}
|
||||
Ok(args)
|
||||
};
|
||||
// Phase 143 fix: env params must be in Param region (100+) per JoinValueSpace contract.
|
||||
// All functions share the same params (env passing via continuation).
|
||||
let (main_params, mut next_value_id) = NormalizedHelperBox::alloc_env_params_param_region(&env_fields);
|
||||
|
||||
// IDs (stable, dev-only)
|
||||
let main_id = JoinFuncId::new(0);
|
||||
@ -94,9 +57,9 @@ impl IfAsLastJoinKLowererBox {
|
||||
let join_k_id = JoinFuncId::new(3);
|
||||
|
||||
// main(env)
|
||||
let main_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let mut env_main = build_env_map(&env_fields, &main_params);
|
||||
let mut main_func = JoinFunction::new(main_id, "main".to_string(), main_params);
|
||||
// main_params allocated above in Param region. Clone for reuse.
|
||||
let mut env_main = NormalizedHelperBox::build_env_map(&env_fields, &main_params);
|
||||
let mut main_func = JoinFunction::new(main_id, "main".to_string(), main_params.clone());
|
||||
|
||||
// Lower prefix (pre-if) statements into main
|
||||
for n in prefix_nodes {
|
||||
@ -249,8 +212,9 @@ impl IfAsLastJoinKLowererBox {
|
||||
}
|
||||
|
||||
// join_k(env_phi): return env_phi[ret_var]
|
||||
let join_k_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let env_join_k = build_env_map(&env_fields, &join_k_params);
|
||||
// Phase 143 fix: reuse Param region IDs for all functions
|
||||
let join_k_params = main_params.clone();
|
||||
let env_join_k = NormalizedHelperBox::build_env_map(&env_fields, &join_k_params);
|
||||
let ret_vid = env_join_k.get(&ret_var).copied().ok_or_else(|| {
|
||||
error_tags::freeze_with_hint(
|
||||
"phase129/join_k/ret_vid_missing",
|
||||
@ -262,8 +226,9 @@ impl IfAsLastJoinKLowererBox {
|
||||
join_k_func.body.push(JoinInst::Ret { value: Some(ret_vid) });
|
||||
|
||||
// k_then(env_in): <prefix> ; tailcall join_k(env_out)
|
||||
let then_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let mut env_then = build_env_map(&env_fields, &then_params);
|
||||
// Phase 143 fix: reuse Param region IDs for all functions
|
||||
let then_params = main_params.clone();
|
||||
let mut env_then = NormalizedHelperBox::build_env_map(&env_fields, &then_params);
|
||||
let mut then_func = JoinFunction::new(k_then_id, "k_then".to_string(), then_params);
|
||||
for n in then_prefix {
|
||||
match n {
|
||||
@ -291,7 +256,12 @@ impl IfAsLastJoinKLowererBox {
|
||||
}
|
||||
}
|
||||
}
|
||||
let then_args = collect_env_args(&env_fields, &env_then)?;
|
||||
let then_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_then)
|
||||
.map_err(|e| error_tags::freeze_with_hint(
|
||||
"phase129/join_k/env_missing",
|
||||
&e,
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
))?;
|
||||
then_func.body.push(JoinInst::Call {
|
||||
func: join_k_id,
|
||||
args: then_args,
|
||||
@ -300,8 +270,9 @@ impl IfAsLastJoinKLowererBox {
|
||||
});
|
||||
|
||||
// k_else(env_in): <prefix> ; tailcall join_k(env_out)
|
||||
let else_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let mut env_else = build_env_map(&env_fields, &else_params);
|
||||
// Phase 143 fix: reuse Param region IDs for all functions
|
||||
let else_params = main_params.clone();
|
||||
let mut env_else = NormalizedHelperBox::build_env_map(&env_fields, &else_params);
|
||||
let mut else_func = JoinFunction::new(k_else_id, "k_else".to_string(), else_params);
|
||||
for n in else_prefix {
|
||||
match n {
|
||||
@ -329,7 +300,12 @@ impl IfAsLastJoinKLowererBox {
|
||||
}
|
||||
}
|
||||
}
|
||||
let else_args = collect_env_args(&env_fields, &env_else)?;
|
||||
let else_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_else)
|
||||
.map_err(|e| error_tags::freeze_with_hint(
|
||||
"phase129/join_k/env_missing",
|
||||
&e,
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
))?;
|
||||
else_func.body.push(JoinInst::Call {
|
||||
func: join_k_id,
|
||||
args: else_args,
|
||||
@ -346,12 +322,12 @@ impl IfAsLastJoinKLowererBox {
|
||||
"ensure the if condition uses a variable from writes or captured inputs",
|
||||
)
|
||||
})?;
|
||||
let rhs_vid = alloc_value_id(&mut next_value_id);
|
||||
let rhs_vid = NormalizedHelperBox::alloc_value_id(&mut next_value_id);
|
||||
main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: rhs_vid,
|
||||
value: ConstValue::Integer(rhs_literal),
|
||||
}));
|
||||
let cond_vid = alloc_value_id(&mut next_value_id);
|
||||
let cond_vid = NormalizedHelperBox::alloc_value_id(&mut next_value_id);
|
||||
main_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cond_vid,
|
||||
op,
|
||||
@ -359,7 +335,12 @@ impl IfAsLastJoinKLowererBox {
|
||||
rhs: rhs_vid,
|
||||
}));
|
||||
|
||||
let main_args = collect_env_args(&env_fields, &env_main)?;
|
||||
let main_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_main)
|
||||
.map_err(|e| error_tags::freeze_with_hint(
|
||||
"phase129/join_k/env_missing",
|
||||
&e,
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
))?;
|
||||
main_func.body.push(JoinInst::Jump {
|
||||
cont: k_then_id.as_cont(),
|
||||
args: main_args.clone(),
|
||||
|
||||
@ -48,6 +48,7 @@
|
||||
//! - In scope but conversion failed → Err (with freeze_with_hint in strict mode)
|
||||
|
||||
use super::common::return_value_lowerer_box::ReturnValueLowererBox;
|
||||
use super::common::normalized_helpers::NormalizedHelperBox;
|
||||
use super::env_layout::EnvLayout;
|
||||
use super::legacy::LegacyLowerer;
|
||||
use crate::ast::{ASTNode, LiteralValue};
|
||||
@ -95,7 +96,7 @@ impl LoopTrueBreakOnceBuilderBox {
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
if !Self::is_bool_true_literal(&cond_ast.0) {
|
||||
if !NormalizedHelperBox::is_bool_true_literal(&cond_ast.0) {
|
||||
return Ok(None); // Condition is not Bool(true)
|
||||
}
|
||||
|
||||
@ -118,45 +119,9 @@ impl LoopTrueBreakOnceBuilderBox {
|
||||
}
|
||||
|
||||
let env_fields = env_layout.env_fields();
|
||||
|
||||
fn alloc_value_id(next_value_id: &mut u32) -> ValueId {
|
||||
let vid = ValueId(*next_value_id);
|
||||
*next_value_id += 1;
|
||||
vid
|
||||
}
|
||||
|
||||
let alloc_env_params =
|
||||
|fields: &[String], next_value_id: &mut u32| -> Vec<ValueId> {
|
||||
fields
|
||||
.iter()
|
||||
.map(|_| alloc_value_id(next_value_id))
|
||||
.collect()
|
||||
};
|
||||
|
||||
let build_env_map = |fields: &[String], params: &[ValueId]| -> BTreeMap<String, ValueId> {
|
||||
let mut env = BTreeMap::new();
|
||||
for (name, vid) in fields.iter().zip(params.iter()) {
|
||||
env.insert(name.clone(), *vid);
|
||||
}
|
||||
env
|
||||
};
|
||||
|
||||
let collect_env_args = |fields: &[String], env: &BTreeMap<String, ValueId>| -> Result<Vec<ValueId>, String> {
|
||||
let mut args = Vec::with_capacity(fields.len());
|
||||
for name in fields {
|
||||
let vid = env.get(name).copied().ok_or_else(|| {
|
||||
error_tags::freeze_with_hint(
|
||||
"phase131/loop_true/env_missing",
|
||||
&format!("env missing required field '{name}'"),
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
)
|
||||
})?;
|
||||
args.push(vid);
|
||||
}
|
||||
Ok(args)
|
||||
};
|
||||
|
||||
let mut next_value_id: u32 = 1;
|
||||
// Phase 143 fix: env params must be in Param region (100+) per JoinValueSpace contract.
|
||||
// All functions share the same params (env passing via continuation).
|
||||
let (main_params, mut next_value_id) = NormalizedHelperBox::alloc_env_params_param_region(&env_fields);
|
||||
|
||||
// Function IDs (stable, dev-only).
|
||||
//
|
||||
@ -167,9 +132,9 @@ impl LoopTrueBreakOnceBuilderBox {
|
||||
let loop_body_id = JoinFuncId::new(3);
|
||||
|
||||
// main(env): <prefix> → TailCall(loop_step, env)
|
||||
let main_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let mut env_main = build_env_map(&env_fields, &main_params);
|
||||
let mut main_func = JoinFunction::new(main_id, "join_func_0".to_string(), main_params);
|
||||
// main_params allocated above in Param region. Clone for reuse.
|
||||
let mut env_main = NormalizedHelperBox::build_env_map(&env_fields, &main_params);
|
||||
let mut main_func = JoinFunction::new(main_id, "join_func_0".to_string(), main_params.clone());
|
||||
|
||||
// Lower prefix (pre-loop) statements into main
|
||||
for n in prefix_nodes {
|
||||
@ -200,7 +165,12 @@ impl LoopTrueBreakOnceBuilderBox {
|
||||
}
|
||||
|
||||
// main → loop_step tailcall
|
||||
let main_args = collect_env_args(&env_fields, &env_main)?;
|
||||
let main_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_main)
|
||||
.map_err(|e| error_tags::freeze_with_hint(
|
||||
"phase131/loop_true/env_missing",
|
||||
&e,
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
))?;
|
||||
main_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: main_args,
|
||||
@ -212,11 +182,17 @@ impl LoopTrueBreakOnceBuilderBox {
|
||||
//
|
||||
// Contract: loop condition is Bool(true), so loop_step has no conditional branch.
|
||||
// This avoids introducing an unreachable "else" exit path that would require PHI.
|
||||
let loop_step_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let env_loop_step = build_env_map(&env_fields, &loop_step_params);
|
||||
// Phase 143 fix: reuse Param region IDs for all functions
|
||||
let loop_step_params = main_params.clone();
|
||||
let env_loop_step = NormalizedHelperBox::build_env_map(&env_fields, &loop_step_params);
|
||||
let mut loop_step_func =
|
||||
JoinFunction::new(loop_step_id, "join_func_1".to_string(), loop_step_params);
|
||||
let loop_step_args = collect_env_args(&env_fields, &env_loop_step)?;
|
||||
let loop_step_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_loop_step)
|
||||
.map_err(|e| error_tags::freeze_with_hint(
|
||||
"phase131/loop_true/env_missing",
|
||||
&e,
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
))?;
|
||||
loop_step_func.body.push(JoinInst::Call {
|
||||
func: loop_body_id,
|
||||
args: loop_step_args,
|
||||
@ -225,8 +201,9 @@ impl LoopTrueBreakOnceBuilderBox {
|
||||
});
|
||||
|
||||
// loop_body(env): <assign statements> → TailCall(k_exit, env)
|
||||
let loop_body_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let mut env_loop_body = build_env_map(&env_fields, &loop_body_params);
|
||||
// Phase 143 fix: reuse Param region IDs for all functions
|
||||
let loop_body_params = main_params.clone();
|
||||
let mut env_loop_body = NormalizedHelperBox::build_env_map(&env_fields, &loop_body_params);
|
||||
let env_loop_body_before = env_loop_body.clone();
|
||||
let mut loop_body_func =
|
||||
JoinFunction::new(loop_body_id, "join_func_3".to_string(), loop_body_params);
|
||||
@ -260,7 +237,12 @@ impl LoopTrueBreakOnceBuilderBox {
|
||||
}
|
||||
|
||||
// loop_body → k_exit tailcall
|
||||
let loop_body_args = collect_env_args(&env_fields, &env_loop_body)?;
|
||||
let loop_body_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_loop_body)
|
||||
.map_err(|e| error_tags::freeze_with_hint(
|
||||
"phase131/loop_true/env_missing",
|
||||
&e,
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
))?;
|
||||
if crate::config::env::joinir_strict_enabled() {
|
||||
for n in body_prefix {
|
||||
let StepNode::Stmt { kind, .. } = n else { continue };
|
||||
@ -361,15 +343,21 @@ impl LoopTrueBreakOnceBuilderBox {
|
||||
}
|
||||
|
||||
// k_exit(env): handle post-loop or return
|
||||
let k_exit_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let env_k_exit = build_env_map(&env_fields, &k_exit_params);
|
||||
// Phase 143 fix: reuse Param region IDs for all functions
|
||||
let k_exit_params = main_params.clone();
|
||||
let env_k_exit = NormalizedHelperBox::build_env_map(&env_fields, &k_exit_params);
|
||||
let mut k_exit_func =
|
||||
JoinFunction::new(k_exit_id, "join_func_2".to_string(), k_exit_params);
|
||||
|
||||
if has_post_computation {
|
||||
// Phase 132-P4/133-P0: k_exit → TailCall(post_k, env)
|
||||
let post_k_id = JoinFuncId::new(4);
|
||||
let k_exit_args = collect_env_args(&env_fields, &env_k_exit)?;
|
||||
let k_exit_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_k_exit)
|
||||
.map_err(|e| error_tags::freeze_with_hint(
|
||||
"phase131/loop_true/env_missing",
|
||||
&e,
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
))?;
|
||||
k_exit_func.body.push(JoinInst::Call {
|
||||
func: post_k_id,
|
||||
args: k_exit_args,
|
||||
@ -378,8 +366,9 @@ impl LoopTrueBreakOnceBuilderBox {
|
||||
});
|
||||
|
||||
// post_k(env): <post assign>* → Ret(env[x])
|
||||
let post_k_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let mut env_post_k = build_env_map(&env_fields, &post_k_params);
|
||||
// Phase 143 fix: reuse Param region IDs for all functions
|
||||
let post_k_params = main_params.clone();
|
||||
let mut env_post_k = NormalizedHelperBox::build_env_map(&env_fields, &post_k_params);
|
||||
let mut post_k_func =
|
||||
JoinFunction::new(post_k_id, "join_func_4".to_string(), post_k_params);
|
||||
|
||||
@ -560,17 +549,6 @@ impl LoopTrueBreakOnceBuilderBox {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if AST node is Bool(true) literal
|
||||
fn is_bool_true_literal(ast: &ASTNode) -> bool {
|
||||
matches!(
|
||||
ast,
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Bool(true),
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if body ends with break statement
|
||||
fn body_ends_with_break(stmts: &[StepNode]) -> bool {
|
||||
if let Some(last) = stmts.last() {
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
//! Phase 143 P0: loop(true) + if + break Normalized lowering
|
||||
//! Phase 143 P0/P1: loop(true) + if + break/continue Normalized lowering
|
||||
//!
|
||||
//! ## Responsibility
|
||||
//!
|
||||
//! - Lower `loop(true) { if(cond_pure) break }` to Normalized JoinModule
|
||||
//! - Lower `loop(true) { if(cond_pure) break/continue }` to Normalized JoinModule
|
||||
//! - Conditional exit control flow (Branch instructions)
|
||||
//! - PHI-free: all state passing via env arguments + continuations
|
||||
//! - Pure condition expressions only (Phase 143 P0 scope)
|
||||
//! - Pure condition expressions only (Phase 143 scope)
|
||||
//!
|
||||
//! ## Scope (P0)
|
||||
//! ## Scope
|
||||
//!
|
||||
//! - Loop condition: `true` literal only
|
||||
//! - Loop body: Single `if` statement only
|
||||
//! - If branch: `break` only (no continue, no else)
|
||||
//! - If branch: `break` (P0) or `continue` (P1)
|
||||
//! - Condition expression: Pure only (variables, literals, arith, compare)
|
||||
//! - Else branch: Not supported (P2 feature)
|
||||
//!
|
||||
//! ## Return Behavior
|
||||
//!
|
||||
@ -23,45 +24,61 @@
|
||||
use super::env_layout::EnvLayout;
|
||||
use super::common::expr_lowerer_box::NormalizedExprLowererBox;
|
||||
use super::common::expr_lowering_contract::ExprLoweringScope;
|
||||
use super::common::loop_if_exit_contract::{LoopIfExitShape, LoopIfExitThen, OutOfScopeReason};
|
||||
use super::common::normalized_helpers::NormalizedHelperBox;
|
||||
use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind, StepTree};
|
||||
use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta;
|
||||
use crate::mir::join_ir::{JoinFunction, JoinFuncId, JoinInst, JoinModule};
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Box-First: loop(true) + if + break lowering to Normalized
|
||||
/// Box-First: loop(true) + if + break/continue lowering to Normalized
|
||||
pub struct LoopTrueIfBreakContinueBuilderBox;
|
||||
|
||||
impl LoopTrueIfBreakContinueBuilderBox {
|
||||
/// Try to lower loop(true) + if + break pattern to Normalized JoinModule.
|
||||
/// Try to lower loop(true) + if + break/continue pattern to Normalized JoinModule.
|
||||
///
|
||||
/// Phase 143 P0: Minimal scope - loop(true){ if(cond) break } only
|
||||
/// Phase 143 P0/P1: Supports break (P0) and continue (P1)
|
||||
pub fn lower(
|
||||
step_tree: &StepTree,
|
||||
env_layout: &EnvLayout,
|
||||
) -> Result<Option<(JoinModule, JoinFragmentMeta)>, String> {
|
||||
// DEBUG: Log attempt
|
||||
if crate::config::env::joinir_dev_enabled() {
|
||||
eprintln!("[phase143/debug] Attempting loop_true_if_break pattern (P0 minimal scope)");
|
||||
eprintln!("[phase143/debug] Attempting loop_true_if_break/continue pattern (P0/P1)");
|
||||
}
|
||||
|
||||
// Pattern: loop(true) { if(cond) break }
|
||||
// Pattern: loop(true) { if(cond) break/continue }
|
||||
// Step 1: Extract pattern match
|
||||
let cond_ast = match Self::extract_loop_true_if_break(&step_tree.root) {
|
||||
Some(cond) => {
|
||||
let (shape, cond_ast) = match Self::extract_pattern_shape(&step_tree.root) {
|
||||
Ok(Some((s, cond))) => {
|
||||
if crate::config::env::joinir_dev_enabled() {
|
||||
eprintln!("[phase143/debug] Pattern matched: loop(true) if break");
|
||||
eprintln!("[phase143/debug] Pattern matched: loop(true) if break/continue");
|
||||
}
|
||||
cond
|
||||
(s, cond)
|
||||
}
|
||||
None => {
|
||||
Ok(None) => {
|
||||
if crate::config::env::joinir_dev_enabled() {
|
||||
eprintln!("[phase143/debug] Pattern not matched, out of scope");
|
||||
}
|
||||
return Ok(None); // Out of scope
|
||||
}
|
||||
Err(reason) => {
|
||||
if crate::config::env::joinir_dev_enabled() {
|
||||
eprintln!("[phase143/debug] Pattern out-of-scope: {:?}", reason);
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
// Validate that shape is P1-compatible (supports both break and continue)
|
||||
if let Err(reason) = shape.validate_for_p1() {
|
||||
if crate::config::env::joinir_dev_enabled() {
|
||||
eprintln!("[phase143/debug] P1 validation failed: {:?}", reason);
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Step 3 (P0): Validate condition lowering with PureOnly scope
|
||||
// This step checks if the condition can be lowered as a pure expression.
|
||||
// We don't emit the actual JoinModule yet (that's Steps 4-6),
|
||||
@ -105,43 +122,11 @@ impl LoopTrueIfBreakContinueBuilderBox {
|
||||
}
|
||||
|
||||
// Step 4 (P0): Build JoinModule with 6 functions and Branch instructions
|
||||
// === Helper closures ===
|
||||
fn alloc_value_id(next_value_id: &mut u32) -> ValueId {
|
||||
let vid = ValueId(*next_value_id);
|
||||
*next_value_id += 1;
|
||||
vid
|
||||
}
|
||||
|
||||
let alloc_env_params =
|
||||
|fields: &[String], next_value_id: &mut u32| -> Vec<ValueId> {
|
||||
fields
|
||||
.iter()
|
||||
.map(|_| alloc_value_id(next_value_id))
|
||||
.collect()
|
||||
};
|
||||
|
||||
let build_env_map = |fields: &[String], params: &[ValueId]| -> BTreeMap<String, ValueId> {
|
||||
let mut env = BTreeMap::new();
|
||||
for (name, vid) in fields.iter().zip(params.iter()) {
|
||||
env.insert(name.clone(), *vid);
|
||||
}
|
||||
env
|
||||
};
|
||||
|
||||
let collect_env_args = |fields: &[String], env: &BTreeMap<String, ValueId>| -> Result<Vec<ValueId>, String> {
|
||||
let mut args = Vec::with_capacity(fields.len());
|
||||
for name in fields {
|
||||
let vid = env.get(name).copied().ok_or_else(|| {
|
||||
format!("phase143/branch_emission: env missing required field '{name}'")
|
||||
})?;
|
||||
args.push(vid);
|
||||
}
|
||||
Ok(args)
|
||||
};
|
||||
|
||||
// === Phase 4: Allocate IDs and build env ===
|
||||
let mut next_value_id: u32 = 1;
|
||||
let env_fields = env_layout.env_fields();
|
||||
// Phase 143 fix: env params must be in Param region (100+) per JoinValueSpace contract.
|
||||
// All functions share the same params (env passing via continuation).
|
||||
let (main_params, mut next_value_id) = NormalizedHelperBox::alloc_env_params_param_region(&env_fields);
|
||||
|
||||
// Allocate 6 JoinFuncIds
|
||||
let main_id = JoinFuncId::new(0);
|
||||
@ -152,12 +137,12 @@ impl LoopTrueIfBreakContinueBuilderBox {
|
||||
let k_exit_id = JoinFuncId::new(5);
|
||||
|
||||
// === main(env) ===
|
||||
let main_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let env_main = build_env_map(&env_fields, &main_params);
|
||||
// main_params allocated above in Param region. Clone for reuse.
|
||||
let env_main = NormalizedHelperBox::build_env_map(&env_fields, &main_params);
|
||||
let main_func = {
|
||||
let mut f = JoinFunction::new(main_id, "main".to_string(), main_params);
|
||||
let mut f = JoinFunction::new(main_id, "main".to_string(), main_params.clone());
|
||||
// main: Call(loop_step, env)
|
||||
let loop_step_args = collect_env_args(&env_fields, &env_main)?;
|
||||
let loop_step_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_main)?;
|
||||
f.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: loop_step_args,
|
||||
@ -168,12 +153,13 @@ impl LoopTrueIfBreakContinueBuilderBox {
|
||||
};
|
||||
|
||||
// === loop_step(env) ===
|
||||
let loop_step_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let env_loop_step = build_env_map(&env_fields, &loop_step_params);
|
||||
// Phase 143 fix: reuse Param region IDs for all functions (consistent env passing)
|
||||
let loop_step_params = main_params.clone();
|
||||
let env_loop_step = NormalizedHelperBox::build_env_map(&env_fields, &loop_step_params);
|
||||
let loop_step_func = {
|
||||
let mut f = JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_step_params);
|
||||
// loop_step: Call(loop_cond_check, env)
|
||||
let loop_cond_check_args = collect_env_args(&env_fields, &env_loop_step)?;
|
||||
let loop_cond_check_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_loop_step)?;
|
||||
f.body.push(JoinInst::Call {
|
||||
func: loop_cond_check_id,
|
||||
args: loop_cond_check_args,
|
||||
@ -184,8 +170,9 @@ impl LoopTrueIfBreakContinueBuilderBox {
|
||||
};
|
||||
|
||||
// === loop_cond_check(env): Lower condition and emit Jump ===
|
||||
let loop_cond_check_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let env_loop_cond_check = build_env_map(&env_fields, &loop_cond_check_params);
|
||||
// Phase 143 fix: reuse Param region IDs
|
||||
let loop_cond_check_params = main_params.clone();
|
||||
let env_loop_cond_check = NormalizedHelperBox::build_env_map(&env_fields, &loop_cond_check_params);
|
||||
let loop_cond_check_func = {
|
||||
let mut f = JoinFunction::new(
|
||||
loop_cond_check_id,
|
||||
@ -207,44 +194,64 @@ impl LoopTrueIfBreakContinueBuilderBox {
|
||||
}
|
||||
};
|
||||
|
||||
// Emit conditional Jump instruction
|
||||
// Jump { cond: Some(cond_vid), cont: k_exit }
|
||||
// If cond_vid is true: jump to k_exit (break)
|
||||
// If cond_vid is false: fall through to next instruction (continue)
|
||||
let k_exit_args = collect_env_args(&env_fields, &env_loop_cond_check)?;
|
||||
f.body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: k_exit_args,
|
||||
cond: Some(cond_vid),
|
||||
});
|
||||
// P1: Emit conditional Jump based on exit action (break vs continue)
|
||||
let k_exit_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_loop_cond_check)?;
|
||||
let loop_step_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_loop_cond_check)?;
|
||||
|
||||
// If we don't jump (condition is false), continue the loop
|
||||
// Call loop_step() to iterate again
|
||||
let loop_step_args = collect_env_args(&env_fields, &env_loop_cond_check)?;
|
||||
f.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: loop_step_args,
|
||||
k_next: None,
|
||||
dst: None,
|
||||
});
|
||||
match shape.then {
|
||||
LoopIfExitThen::Break => {
|
||||
// P0: If cond_vid is true: jump to k_exit (BREAK)
|
||||
f.body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: k_exit_args,
|
||||
cond: Some(cond_vid),
|
||||
});
|
||||
// If cond_vid is false: call loop_step() to iterate again
|
||||
f.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: loop_step_args,
|
||||
k_next: None,
|
||||
dst: None,
|
||||
});
|
||||
}
|
||||
LoopIfExitThen::Continue => {
|
||||
// P1: `if(cond){continue}` with a single-if loop body.
|
||||
//
|
||||
// Phase 143 P1 scope is intentionally conservative (no else branch, no in-loop state updates).
|
||||
// In this shape, `continue` means "proceed to next iteration", and the loop never terminates.
|
||||
//
|
||||
// We still lower the condition as a pure expression to validate scope, but we do not
|
||||
// emit a conditional Jump here (Jump has "early return" semantics in the bridge).
|
||||
// Instead, we tail-call loop_step unconditionally.
|
||||
let _ = cond_vid;
|
||||
f.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: loop_step_args,
|
||||
k_next: None,
|
||||
dst: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
f
|
||||
};
|
||||
|
||||
// === k_then(env): Not used in P0 (direct Jump from loop_cond_check) ===
|
||||
// Kept for structural clarity in case future extensions need it
|
||||
let k_then_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let _env_k_then = build_env_map(&env_fields, &k_then_params);
|
||||
// === k_then(env): Reserved (P2+) ===
|
||||
//
|
||||
// Phase 143 P1 does not emit conditional Jump for Continue (see loop_cond_check note),
|
||||
// so k_then is currently unused. It is kept as a placeholder for P2+ extensions
|
||||
// (e.g., else-branch support).
|
||||
// Phase 143 fix: reuse Param region IDs
|
||||
let k_then_params = main_params.clone();
|
||||
let k_then_func = {
|
||||
let f = JoinFunction::new(k_then_id, "k_then".to_string(), k_then_params);
|
||||
// Empty placeholder: P0 doesn't use this function
|
||||
f
|
||||
JoinFunction::new(k_then_id, "k_then".to_string(), k_then_params)
|
||||
};
|
||||
|
||||
// === k_else(env): Not used in P0 (direct Call from loop_cond_check fallthrough) ===
|
||||
// Kept for structural clarity in case future extensions need it
|
||||
let k_else_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let _env_k_else = build_env_map(&env_fields, &k_else_params);
|
||||
// Phase 143 fix: reuse Param region IDs
|
||||
let k_else_params = main_params.clone();
|
||||
let _env_k_else = NormalizedHelperBox::build_env_map(&env_fields, &k_else_params);
|
||||
let k_else_func = {
|
||||
let f = JoinFunction::new(k_else_id, "k_else".to_string(), k_else_params);
|
||||
// Empty placeholder: P0 doesn't use this function
|
||||
@ -252,8 +259,9 @@ impl LoopTrueIfBreakContinueBuilderBox {
|
||||
};
|
||||
|
||||
// === k_exit(env): Return with exit values (Phase 143 P0 Steps 5-6) ===
|
||||
let k_exit_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let env_k_exit = build_env_map(&env_fields, &k_exit_params);
|
||||
// Phase 143 fix: reuse Param region IDs
|
||||
let k_exit_params = main_params.clone();
|
||||
let env_k_exit = NormalizedHelperBox::build_env_map(&env_fields, &k_exit_params);
|
||||
let k_exit_func = {
|
||||
let mut f = JoinFunction::new(k_exit_id, "k_exit".to_string(), k_exit_params.clone());
|
||||
|
||||
@ -322,17 +330,20 @@ impl LoopTrueIfBreakContinueBuilderBox {
|
||||
Ok(Some((module, meta)))
|
||||
}
|
||||
|
||||
/// Extract loop(true) + single if + break pattern
|
||||
/// Extract loop(true) + single if + break/continue pattern
|
||||
///
|
||||
/// Returns Some(cond_ast) if pattern matches, None otherwise.
|
||||
/// Pattern: StepNode::Loop with body containing single If with break
|
||||
/// Returns Ok(Some((shape, cond_ast))) if pattern matches, Ok(None) if clearly out of scope,
|
||||
/// and Err for specific out-of-scope reasons.
|
||||
/// Pattern: StepNode::Loop with body containing single If with break/continue
|
||||
/// Also returns the If condition AST for lowering.
|
||||
fn extract_loop_true_if_break(root: &StepNode) -> Option<crate::ast::ASTNode> {
|
||||
fn extract_pattern_shape(
|
||||
root: &StepNode,
|
||||
) -> Result<Option<(LoopIfExitShape, crate::ast::ASTNode)>, OutOfScopeReason> {
|
||||
match root {
|
||||
StepNode::Loop { cond_ast, body, .. } => {
|
||||
// Condition must be Bool(true) literal
|
||||
if !Self::is_bool_true_literal(&cond_ast.0) {
|
||||
return None;
|
||||
if !NormalizedHelperBox::is_bool_true_literal(&cond_ast.0) {
|
||||
return Err(OutOfScopeReason::NotLoopTrue);
|
||||
}
|
||||
|
||||
// Body must be single if statement (possibly wrapped in Block)
|
||||
@ -345,9 +356,11 @@ impl LoopTrueIfBreakContinueBuilderBox {
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}?;
|
||||
};
|
||||
|
||||
// If statement structure: if(cond_pure) { break }
|
||||
let if_node = if_node.ok_or(OutOfScopeReason::BodyNotSingleIf)?;
|
||||
|
||||
// If statement structure: if(cond_pure) { break/continue }
|
||||
if let StepNode::If {
|
||||
cond_ast: if_cond,
|
||||
then_branch,
|
||||
@ -355,52 +368,55 @@ impl LoopTrueIfBreakContinueBuilderBox {
|
||||
..
|
||||
} = if_node
|
||||
{
|
||||
// Phase 143 P0: no else branch allowed
|
||||
// P1: No else branch allowed
|
||||
if else_branch.is_some() {
|
||||
return None;
|
||||
return Err(OutOfScopeReason::ElseNotSupported(
|
||||
LoopIfExitThen::Break,
|
||||
));
|
||||
}
|
||||
|
||||
// Then branch must be Break statement
|
||||
let has_break = match then_branch.as_ref() {
|
||||
StepNode::Stmt {
|
||||
kind: StepStmtKind::Break,
|
||||
..
|
||||
} => true,
|
||||
StepNode::Block(stmts) if stmts.len() == 1 => {
|
||||
matches!(
|
||||
&stmts[0],
|
||||
StepNode::Stmt {
|
||||
kind: StepStmtKind::Break,
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
// Extract then action (P1: Break OR Continue)
|
||||
let then_action = Self::extract_exit_action(then_branch)?;
|
||||
|
||||
// Build contract shape
|
||||
let shape = LoopIfExitShape {
|
||||
has_else: false,
|
||||
then: then_action,
|
||||
else_: None,
|
||||
cond_scope: ExprLoweringScope::PureOnly,
|
||||
};
|
||||
|
||||
if has_break {
|
||||
Some((*if_cond.0).clone()) // Return If condition AST
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Ok(Some((shape, (*if_cond.0).clone())))
|
||||
} else {
|
||||
None
|
||||
Err(OutOfScopeReason::BodyNotSingleIf)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
_ => Err(OutOfScopeReason::NotLoopTrue),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if AST node is Bool(true) literal
|
||||
fn is_bool_true_literal(ast: &crate::ast::ASTNode) -> bool {
|
||||
matches!(
|
||||
ast,
|
||||
crate::ast::ASTNode::Literal {
|
||||
value: crate::ast::LiteralValue::Bool(true),
|
||||
..
|
||||
}
|
||||
)
|
||||
/// Extract exit action from branch node
|
||||
///
|
||||
/// Returns Ok(LoopIfExitThen::Break) for break statements
|
||||
/// Returns Ok(LoopIfExitThen::Continue) for continue statements
|
||||
/// Returns Err for unsupported branches
|
||||
fn extract_exit_action(branch: &StepNode) -> Result<LoopIfExitThen, OutOfScopeReason> {
|
||||
match branch {
|
||||
StepNode::Stmt { kind: StepStmtKind::Break, .. } => Ok(LoopIfExitThen::Break),
|
||||
StepNode::Stmt { kind: StepStmtKind::Continue, .. } => Ok(LoopIfExitThen::Continue),
|
||||
StepNode::Block(stmts) if stmts.len() == 1 => match &stmts[0] {
|
||||
StepNode::Stmt { kind: StepStmtKind::Break, .. } => Ok(LoopIfExitThen::Break),
|
||||
StepNode::Stmt { kind: StepStmtKind::Continue, .. } => Ok(LoopIfExitThen::Continue),
|
||||
_ => Err(OutOfScopeReason::ThenNotExit(
|
||||
"Not break/continue".to_string(),
|
||||
)),
|
||||
},
|
||||
_ => Err(OutOfScopeReason::ThenNotExit(
|
||||
"Complex branch not supported".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Unit tests are in: normalized_shadow/tests/phase143_loop_if_exit_contract.rs
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
use super::env_layout::EnvLayout;
|
||||
use super::legacy::LegacyLowerer;
|
||||
use super::common::return_value_lowerer_box::ReturnValueLowererBox;
|
||||
use super::common::normalized_helpers::NormalizedHelperBox;
|
||||
use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind, StepTree};
|
||||
use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta;
|
||||
use crate::mir::join_ir::lowering::error_tags;
|
||||
@ -59,45 +60,9 @@ impl PostIfPostKBuilderBox {
|
||||
};
|
||||
|
||||
let env_fields = env_layout.env_fields();
|
||||
|
||||
fn alloc_value_id(next_value_id: &mut u32) -> ValueId {
|
||||
let vid = ValueId(*next_value_id);
|
||||
*next_value_id += 1;
|
||||
vid
|
||||
}
|
||||
|
||||
let alloc_env_params =
|
||||
|fields: &[String], next_value_id: &mut u32| -> Vec<ValueId> {
|
||||
fields
|
||||
.iter()
|
||||
.map(|_| alloc_value_id(next_value_id))
|
||||
.collect()
|
||||
};
|
||||
|
||||
let build_env_map = |fields: &[String], params: &[ValueId]| -> BTreeMap<String, ValueId> {
|
||||
let mut env = BTreeMap::new();
|
||||
for (name, vid) in fields.iter().zip(params.iter()) {
|
||||
env.insert(name.clone(), *vid);
|
||||
}
|
||||
env
|
||||
};
|
||||
|
||||
let collect_env_args = |fields: &[String], env: &BTreeMap<String, ValueId>| -> Result<Vec<ValueId>, String> {
|
||||
let mut args = Vec::with_capacity(fields.len());
|
||||
for name in fields {
|
||||
let vid = env.get(name).copied().ok_or_else(|| {
|
||||
error_tags::freeze_with_hint(
|
||||
"phase129/post_k/env_missing",
|
||||
&format!("env missing required field '{name}'"),
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
)
|
||||
})?;
|
||||
args.push(vid);
|
||||
}
|
||||
Ok(args)
|
||||
};
|
||||
|
||||
let mut next_value_id: u32 = 1;
|
||||
// Phase 143 fix: env params must be in Param region (100+) per JoinValueSpace contract.
|
||||
// All functions share the same params (env passing via continuation).
|
||||
let (main_params, mut next_value_id) = NormalizedHelperBox::alloc_env_params_param_region(&env_fields);
|
||||
|
||||
// IDs (stable, dev-only)
|
||||
let main_id = JoinFuncId::new(0);
|
||||
@ -107,9 +72,9 @@ impl PostIfPostKBuilderBox {
|
||||
let post_k_id = JoinFuncId::new(4);
|
||||
|
||||
// main(env)
|
||||
let main_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let mut env_main = build_env_map(&env_fields, &main_params);
|
||||
let mut main_func = JoinFunction::new(main_id, "main".to_string(), main_params);
|
||||
// main_params allocated above in Param region. Clone for reuse.
|
||||
let mut env_main = NormalizedHelperBox::build_env_map(&env_fields, &main_params);
|
||||
let mut main_func = JoinFunction::new(main_id, "main".to_string(), main_params.clone());
|
||||
|
||||
// Lower prefix (pre-if) statements into main
|
||||
for n in prefix_nodes {
|
||||
@ -160,8 +125,9 @@ impl PostIfPostKBuilderBox {
|
||||
let else_stmts = Self::extract_branch_stmts(else_branch)?;
|
||||
|
||||
// k_then(env_in): <then_stmts> ; tailcall join_k(env_out)
|
||||
let then_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let mut env_then = build_env_map(&env_fields, &then_params);
|
||||
// Phase 143 fix: reuse Param region IDs for all functions
|
||||
let then_params = main_params.clone();
|
||||
let mut env_then = NormalizedHelperBox::build_env_map(&env_fields, &then_params);
|
||||
let mut then_func = JoinFunction::new(k_then_id, "k_then".to_string(), then_params);
|
||||
|
||||
for stmt in then_stmts {
|
||||
@ -186,7 +152,12 @@ impl PostIfPostKBuilderBox {
|
||||
}
|
||||
}
|
||||
|
||||
let then_args = collect_env_args(&env_fields, &env_then)?;
|
||||
let then_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_then)
|
||||
.map_err(|e| error_tags::freeze_with_hint(
|
||||
"phase129/post_k/env_missing",
|
||||
&e,
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
))?;
|
||||
then_func.body.push(JoinInst::Call {
|
||||
func: join_k_id,
|
||||
args: then_args,
|
||||
@ -195,8 +166,9 @@ impl PostIfPostKBuilderBox {
|
||||
});
|
||||
|
||||
// k_else(env_in): <else_stmts> ; tailcall join_k(env_out)
|
||||
let else_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let mut env_else = build_env_map(&env_fields, &else_params);
|
||||
// Phase 143 fix: reuse Param region IDs for all functions
|
||||
let else_params = main_params.clone();
|
||||
let mut env_else = NormalizedHelperBox::build_env_map(&env_fields, &else_params);
|
||||
let mut else_func = JoinFunction::new(k_else_id, "k_else".to_string(), else_params);
|
||||
|
||||
for stmt in else_stmts {
|
||||
@ -221,7 +193,12 @@ impl PostIfPostKBuilderBox {
|
||||
}
|
||||
}
|
||||
|
||||
let else_args = collect_env_args(&env_fields, &env_else)?;
|
||||
let else_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_else)
|
||||
.map_err(|e| error_tags::freeze_with_hint(
|
||||
"phase129/post_k/env_missing",
|
||||
&e,
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
))?;
|
||||
else_func.body.push(JoinInst::Call {
|
||||
func: join_k_id,
|
||||
args: else_args,
|
||||
@ -230,11 +207,17 @@ impl PostIfPostKBuilderBox {
|
||||
});
|
||||
|
||||
// join_k(env_phi): tailcall post_k(env_phi)
|
||||
let join_k_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let env_join_k = build_env_map(&env_fields, &join_k_params);
|
||||
// Phase 143 fix: reuse Param region IDs for all functions
|
||||
let join_k_params = main_params.clone();
|
||||
let env_join_k = NormalizedHelperBox::build_env_map(&env_fields, &join_k_params);
|
||||
let mut join_k_func = JoinFunction::new(join_k_id, "join_k".to_string(), join_k_params);
|
||||
|
||||
let join_k_args = collect_env_args(&env_fields, &env_join_k)?;
|
||||
let join_k_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_join_k)
|
||||
.map_err(|e| error_tags::freeze_with_hint(
|
||||
"phase129/post_k/env_missing",
|
||||
&e,
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
))?;
|
||||
join_k_func.body.push(JoinInst::Call {
|
||||
func: post_k_id,
|
||||
args: join_k_args,
|
||||
@ -243,8 +226,9 @@ impl PostIfPostKBuilderBox {
|
||||
});
|
||||
|
||||
// post_k(env): <post_stmts> ; Ret
|
||||
let post_k_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||
let mut env_post_k = build_env_map(&env_fields, &post_k_params);
|
||||
// Phase 143 fix: reuse Param region IDs for all functions
|
||||
let post_k_params = main_params.clone();
|
||||
let mut env_post_k = NormalizedHelperBox::build_env_map(&env_fields, &post_k_params);
|
||||
let mut post_k_func = JoinFunction::new(post_k_id, "post_k".to_string(), post_k_params);
|
||||
|
||||
// Lower post-if statements
|
||||
@ -295,13 +279,13 @@ impl PostIfPostKBuilderBox {
|
||||
)
|
||||
})?;
|
||||
|
||||
let rhs_vid = alloc_value_id(&mut next_value_id);
|
||||
let rhs_vid = NormalizedHelperBox::alloc_value_id(&mut next_value_id);
|
||||
main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: rhs_vid,
|
||||
value: ConstValue::Integer(rhs_literal),
|
||||
}));
|
||||
|
||||
let cond_vid = alloc_value_id(&mut next_value_id);
|
||||
let cond_vid = NormalizedHelperBox::alloc_value_id(&mut next_value_id);
|
||||
main_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cond_vid,
|
||||
op,
|
||||
@ -309,7 +293,12 @@ impl PostIfPostKBuilderBox {
|
||||
rhs: rhs_vid,
|
||||
}));
|
||||
|
||||
let main_args = collect_env_args(&env_fields, &env_main)?;
|
||||
let main_args = NormalizedHelperBox::collect_env_args(&env_fields, &env_main)
|
||||
.map_err(|e| error_tags::freeze_with_hint(
|
||||
"phase129/post_k/env_missing",
|
||||
&e,
|
||||
"ensure env layout and env map are built from the same SSOT field list",
|
||||
))?;
|
||||
main_func.body.push(JoinInst::Jump {
|
||||
cont: k_then_id.as_cont(),
|
||||
args: main_args.clone(),
|
||||
|
||||
Reference in New Issue
Block a user