feat(joinir): Phase 171-fix ConditionEnv/ConditionBinding architecture

Proper HOST↔JoinIR ValueId separation for condition variables:

- Add ConditionEnv struct (name → JoinIR-local ValueId mapping)
- Add ConditionBinding struct (HOST/JoinIR ValueId pairs)
- Modify condition_to_joinir to use ConditionEnv instead of builder.variable_map
- Update Pattern2 lowerer to build ConditionEnv and ConditionBindings
- Extend JoinInlineBoundary with condition_bindings field
- Update BoundaryInjector to inject Copy instructions for condition variables

This fixes the undefined ValueId errors where HOST ValueIds were being
used directly in JoinIR instructions. Programs now execute (RC: 0),
though loop variable exit values still need Phase 172 work.

Key invariants established:
1. JoinIR uses ONLY JoinIR-local ValueIds
2. HOST↔JoinIR bridging is ONLY through JoinInlineBoundary
3. condition_to_joinir NEVER accesses builder.variable_map

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-07 01:45:03 +09:00
parent b8a9d08894
commit e30116f53d
27 changed files with 5419 additions and 85 deletions

View File

@ -391,12 +391,29 @@ pub(super) fn merge_and_rewrite(
// Create HashMap from remapper for BoundaryInjector (temporary adapter)
let mut value_map_for_injector = HashMap::new();
// Phase 171-fix: Add join_inputs to value_map
for join_in in &boundary.join_inputs {
if let Some(remapped) = remapper.get_value(*join_in) {
value_map_for_injector.insert(*join_in, remapped);
}
}
// Phase 171-fix: Add condition_bindings to value_map
// Each binding specifies JoinIR ValueId → HOST ValueId mapping
// We remap the JoinIR ValueId to a new MIR ValueId
for binding in &boundary.condition_bindings {
if let Some(remapped) = remapper.get_value(binding.join_value) {
value_map_for_injector.insert(binding.join_value, remapped);
if debug {
eprintln!(
"[cf_loop/joinir] Phase 171-fix: Condition binding '{}': JoinIR {:?} → remapped {:?} (HOST {:?})",
binding.name, binding.join_value, remapped, binding.host_value
);
}
}
}
// Use BoundaryInjector to inject Copy instructions
if let Some(ref mut current_func) = builder.current_function {
BoundaryInjector::inject_boundary_copies(

View File

@ -74,9 +74,22 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
let mut remapper = block_allocator::allocate_blocks(builder, mir_module, debug)?;
// Phase 2: Collect values from all functions
let (used_values, value_to_func_name, function_params) =
let (mut used_values, value_to_func_name, function_params) =
value_collector::collect_values(mir_module, &remapper, debug)?;
// Phase 171-fix: Add condition_bindings' join_values to used_values for remapping
if let Some(boundary) = boundary {
for binding in &boundary.condition_bindings {
if debug {
eprintln!(
"[cf_loop/joinir] Phase 171-fix: Adding condition binding '{}' JoinIR {:?} to used_values",
binding.name, binding.join_value
);
}
used_values.insert(binding.join_value);
}
}
// Phase 3: Remap ValueIds
remap_values(builder, &used_values, &mut remapper, debug)?;

View File

@ -385,6 +385,8 @@ mod tests {
join_inputs: vec![],
host_outputs: vec![],
join_outputs: vec![],
exit_bindings: vec![], // Phase 171: Add missing field
condition_inputs: vec![], // Phase 171: Add missing field
};
builder.apply_to_boundary(&mut boundary)

View File

@ -70,6 +70,61 @@ impl MirBuilder {
// Phase 195: Use unified trace
trace::trace().varmap("pattern2_start", &self.variable_map);
// Phase 171-fix: Build ConditionEnv and ConditionBindings
use crate::mir::join_ir::lowering::condition_to_joinir::{
extract_condition_variables, ConditionEnv, ConditionBinding,
};
let condition_var_names = extract_condition_variables(condition, &[loop_var_name.clone()]);
let mut env = ConditionEnv::new();
let mut condition_bindings = Vec::new();
// Phase 171-fix: Add loop parameter to env (ValueId(0) in JoinIR space)
// The loop parameter is NOT a condition binding (it's a join_input instead)
env.insert(loop_var_name.clone(), crate::mir::ValueId(0));
// Create a local allocator for JoinIR-local ValueIds for condition-only variables
let mut join_value_counter = 1u32; // Start from 1 (0 is reserved for loop param)
let mut alloc_join_value = || {
let id = crate::mir::ValueId(join_value_counter);
join_value_counter += 1;
id
};
// For each condition variable, allocate JoinIR-local ValueId and build binding
for var_name in &condition_var_names {
let host_id = self.variable_map.get(var_name)
.copied()
.ok_or_else(|| {
format!(
"[cf_loop/pattern2] Condition variable '{}' not found in variable_map. \
Loop condition references undefined variable.",
var_name
)
})?;
let join_id = alloc_join_value(); // Allocate JoinIR-local ValueId
env.insert(var_name.clone(), join_id);
condition_bindings.push(ConditionBinding {
name: var_name.clone(),
host_value: host_id,
join_value: join_id,
});
}
// Phase 171-fix Debug: Log condition bindings
eprintln!("[cf_loop/pattern2] Phase 171-fix: ConditionEnv contains {} variables:", env.name_to_join.len());
eprintln!(" Loop param '{}' → JoinIR ValueId(0)", loop_var_name);
if !condition_bindings.is_empty() {
eprintln!(" {} condition-only bindings:", condition_bindings.len());
for binding in &condition_bindings {
eprintln!(" '{}': HOST {:?} → JoinIR {:?}", binding.name, binding.host_value, binding.join_value);
}
} else {
eprintln!(" No condition-only variables");
}
// Create a minimal LoopScopeShape (Phase 188: hardcoded for joinir_min_loop.hako)
// Pattern 2 lowerer ignores the scope anyway, so this is just a placeholder
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
@ -86,13 +141,13 @@ impl MirBuilder {
variable_definitions: BTreeMap::new(),
};
// Call Pattern 2 lowerer
let join_module = match lower_loop_with_break_minimal(scope) {
Some(module) => module,
None => {
// Phase 169 / Phase 171-fix: Call Pattern 2 lowerer with ConditionEnv
let join_module = match lower_loop_with_break_minimal(scope, condition, &env) {
Ok(module) => module,
Err(e) => {
// Phase 195: Use unified trace
trace::trace().debug("pattern2", "Pattern 2 lowerer returned None");
return Ok(None);
trace::trace().debug("pattern2", &format!("Pattern 2 lowerer failed: {}", e));
return Err(format!("[cf_loop/pattern2] Lowering failed: {}", e));
}
};
@ -119,11 +174,19 @@ impl MirBuilder {
);
// Merge JoinIR blocks into current function
// Phase 188-Impl-2: Create and pass JoinInlineBoundary for Pattern 2
let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable
);
// Phase 171-fix: Create boundary with ConditionBindings
let boundary = if condition_bindings.is_empty() {
crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable
)
} else {
crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_with_condition_bindings(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable
condition_bindings, // Phase 171-fix: ConditionBindings with JoinIR ValueIds
)
};
// Phase 189: Discard exit PHI result (Pattern 2 returns void)
let _ = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;

View File

@ -181,13 +181,13 @@ impl MirBuilder {
variable_definitions: BTreeMap::new(),
};
// Call Pattern 4 lowerer (Phase 197: pass carrier_updates for semantic correctness)
let (join_module, exit_meta) = match lower_loop_with_continue_minimal(scope, &carrier_info, &carrier_updates) {
Some(result) => result,
None => {
// Phase 169: Call Pattern 4 lowerer with condition AST
let (join_module, exit_meta) = match lower_loop_with_continue_minimal(scope, condition, self, &carrier_info, &carrier_updates) {
Ok(result) => result,
Err(e) => {
// Phase 195: Use unified trace
trace::trace().debug("pattern4", "Pattern 4 lowerer returned None");
return Ok(None);
trace::trace().debug("pattern4", &format!("Pattern 4 lowerer failed: {}", e));
return Err(format!("[cf_loop/pattern4] Lowering failed: {}", e));
}
};

View File

@ -40,6 +40,7 @@ impl MirBuilder {
// - Core OFF では従来通り dev フラグで opt-in
// Note: Arity does NOT include implicit `me` receiver
// Phase 188: Add "main" routing for loop pattern expansion
// Phase 170: Add JsonParserBox methods for selfhost validation
let core_on = crate::config::env::joinir_core_enabled();
let is_target = match func_name.as_str() {
"main" => true, // Phase 188-Impl-1: Enable JoinIR for main function (Pattern 1)
@ -64,6 +65,16 @@ impl MirBuilder {
== Some("1")
}
}
// Phase 170-A-1: Enable JsonParserBox methods for JoinIR routing
"JsonParserBox._trim/1" => true,
"JsonParserBox._skip_whitespace/2" => true,
"JsonParserBox._match_literal/2" => true,
"JsonParserBox._parse_string/2" => true,
"JsonParserBox._parse_array/2" => true,
"JsonParserBox._parse_object/2" => true,
// Phase 170-A-1: Test methods (simplified versions)
"TrimTest.trim/1" => true,
"Main.trim/1" => true, // Phase 171-fix: Main box variant
_ => false,
};

View File

@ -33,15 +33,18 @@ impl BoundaryInjector {
value_map: &HashMap<ValueId, ValueId>,
debug: bool,
) -> Result<(), String> {
// Boundary が空の場合はスキップ
if boundary.join_inputs.is_empty() {
// Phase 171-fix: Check both join_inputs and condition_bindings
let total_inputs = boundary.join_inputs.len() + boundary.condition_bindings.len();
if total_inputs == 0 {
return Ok(());
}
if debug {
eprintln!(
"[BoundaryInjector] Injecting {} Copy instructions at entry block {:?}",
"[BoundaryInjector] Phase 171-fix: Injecting {} Copy instructions ({} join_inputs, {} condition_bindings) at entry block {:?}",
total_inputs,
boundary.join_inputs.len(),
boundary.condition_bindings.len(),
entry_block_id
);
}
@ -54,6 +57,7 @@ impl BoundaryInjector {
// Copy instructions を生成して挿入
let mut copy_instructions = Vec::new();
// Phase 171: Inject Copy instructions for join_inputs (loop parameters)
for (join_input, host_input) in boundary
.join_inputs
.iter()
@ -73,12 +77,36 @@ impl BoundaryInjector {
if debug {
eprintln!(
"[BoundaryInjector] Copy {:?} = Copy {:?}",
"[BoundaryInjector] Join input: Copy {:?} = Copy {:?}",
remapped_join, remapped_host
);
}
}
// Phase 171-fix: Inject Copy instructions for condition_bindings (condition-only variables)
// These variables are read-only and used ONLY in the loop condition.
// Each binding explicitly specifies HOST ValueId → JoinIR ValueId mapping.
// We inject Copy: remapped_join_value = Copy host_value
for binding in &boundary.condition_bindings {
// Look up the remapped JoinIR ValueId from value_map
let remapped_join = value_map.get(&binding.join_value).copied().unwrap_or(binding.join_value);
// Copy instruction: remapped_join_value = Copy host_value
let copy_inst = MirInstruction::Copy {
dst: remapped_join,
src: binding.host_value,
};
copy_instructions.push(copy_inst);
if debug {
eprintln!(
"[BoundaryInjector] Condition binding '{}': Copy {:?} = Copy {:?} (JoinIR {:?} → remapped {:?})",
binding.name, remapped_join, binding.host_value, binding.join_value, remapped_join
);
}
}
// Entry block の先頭に Copy instructions を挿入
// Reverse order to preserve original order when inserting at position 0
// Phase 189 FIX: Also insert corresponding spans

View File

@ -0,0 +1,370 @@
//! Phase 168: BoolExprLowerer - Boolean Expression Lowering to SSA
//!
//! This module provides lowering of complex boolean expressions (AST → SSA)
//! for use within JoinIR loop patterns. It handles:
//! - Comparisons: `<`, `==`, `!=`, `<=`, `>=`, `>`
//! - Logical operators: `&&`, `||`, `!`
//! - Mixed conditions: `ch == " " || ch == "\t" || ch == "\n"`
//!
//! ## Design Philosophy
//!
//! BoolExprLowerer is a SEPARATE module from loop patterns (Pattern1-4).
//! It focuses purely on expression lowering, while loop patterns handle
//! control flow structure.
//!
//! **Separation of Concerns**:
//! - Loop patterns (Pattern1-4): Loop structure (header, body, exit)
//! - BoolExprLowerer: Expression evaluation (AST → SSA ValueId)
//!
//! ## Target Use Case
//!
//! JsonParserBox methods `_trim` and `_skip_whitespace` have OR chains:
//! ```nyash
//! ch == " " || ch == "\t" || ch == "\n" || ch == "\r"
//! ```
//!
//! This lowerer converts such expressions into SSA form:
//! ```text
//! %cmp1 = Compare Eq %ch " "
//! %cmp2 = Compare Eq %ch "\t"
//! %cmp3 = Compare Eq %ch "\n"
//! %cmp4 = Compare Eq %ch "\r"
//! %or1 = BinOp Or %cmp1 %cmp2
//! %or2 = BinOp Or %or1 %cmp3
//! %result = BinOp Or %or2 %cmp4
//! ```
use crate::ast::{ASTNode, BinaryOperator, UnaryOperator};
use crate::mir::builder::MirBuilder;
use crate::mir::{BinaryOp, CompareOp, MirInstruction, MirType, ValueId};
/// BoolExprLowerer - Converts boolean expression AST to SSA form
///
/// This box handles lowering of complex boolean expressions within loop conditions.
/// It produces ValueIds that can be used by loop patterns for control flow decisions.
pub struct BoolExprLowerer<'a> {
builder: &'a mut MirBuilder,
}
impl<'a> BoolExprLowerer<'a> {
/// Create a new BoolExprLowerer with access to MirBuilder
pub fn new(builder: &'a mut MirBuilder) -> Self {
BoolExprLowerer { builder }
}
/// Lower a boolean expression AST to SSA form
///
/// # Arguments
///
/// * `cond_ast` - AST node representing the boolean condition
///
/// # Returns
///
/// * `Result<ValueId, String>` - Register holding the result (bool 0/1), or error
///
/// # Supported Operators
///
/// - Comparisons: `<`, `==`, `!=`, `<=`, `>=`, `>`
/// - Logical: `&&`, `||`, `!`
/// - Variables and literals
pub fn lower_condition(&mut self, cond_ast: &ASTNode) -> Result<ValueId, String> {
match cond_ast {
// Comparison operations: <, ==, !=, <=, >=, >
ASTNode::BinaryOp {
operator,
left,
right,
..
} => match operator {
BinaryOperator::Less
| BinaryOperator::Equal
| BinaryOperator::NotEqual
| BinaryOperator::LessEqual
| BinaryOperator::GreaterEqual
| BinaryOperator::Greater => {
// Lower comparison operators
let lhs = self.lower_condition(left)?;
let rhs = self.lower_condition(right)?;
let dst = self.builder.next_value_id();
let cmp_op = match operator {
BinaryOperator::Less => CompareOp::Lt,
BinaryOperator::Equal => CompareOp::Eq,
BinaryOperator::NotEqual => CompareOp::Ne,
BinaryOperator::LessEqual => CompareOp::Le,
BinaryOperator::GreaterEqual => CompareOp::Ge,
BinaryOperator::Greater => CompareOp::Gt,
_ => unreachable!(),
};
// Emit Compare instruction
self.builder
.emit_instruction(MirInstruction::Compare {
dst,
op: cmp_op,
lhs,
rhs,
})?;
// Mark result type as Bool
self.builder.value_types.insert(dst, MirType::Bool);
Ok(dst)
}
BinaryOperator::And => {
// Logical AND: evaluate both sides and combine
let lhs = self.lower_condition(left)?;
let rhs = self.lower_condition(right)?;
let dst = self.builder.next_value_id();
// Emit BinOp And instruction
self.builder
.emit_instruction(MirInstruction::BinOp {
dst,
op: BinaryOp::BitAnd, // Use BitAnd for logical AND
lhs,
rhs,
})?;
// Mark result type as Bool
self.builder.value_types.insert(dst, MirType::Bool);
Ok(dst)
}
BinaryOperator::Or => {
// Logical OR: evaluate both sides and combine
let lhs = self.lower_condition(left)?;
let rhs = self.lower_condition(right)?;
let dst = self.builder.next_value_id();
// Emit BinOp Or instruction
self.builder
.emit_instruction(MirInstruction::BinOp {
dst,
op: BinaryOp::BitOr, // Use BitOr for logical OR
lhs,
rhs,
})?;
// Mark result type as Bool
self.builder.value_types.insert(dst, MirType::Bool);
Ok(dst)
}
_ => {
// Other operators (arithmetic, etc.) - delegate to builder
self.builder.build_expression(cond_ast.clone())
}
},
// Unary NOT operator
ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand,
..
} => {
let operand_val = self.lower_condition(operand)?;
let dst = self.builder.next_value_id();
// Emit UnaryOp Not instruction
self.builder
.emit_instruction(MirInstruction::UnaryOp {
dst,
op: crate::mir::UnaryOp::Not,
operand: operand_val,
})?;
// Mark result type as Bool
self.builder.value_types.insert(dst, MirType::Bool);
Ok(dst)
}
// Variables - delegate to builder
ASTNode::Variable { .. } => self.builder.build_expression(cond_ast.clone()),
// Literals - delegate to builder
ASTNode::Literal { .. } => self.builder.build_expression(cond_ast.clone()),
// Method calls - delegate to builder
ASTNode::MethodCall { .. } => self.builder.build_expression(cond_ast.clone()),
// Field access - delegate to builder
ASTNode::FieldAccess { .. } => self.builder.build_expression(cond_ast.clone()),
// Other operators - delegate to builder
_ => self.builder.build_expression(cond_ast.clone()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
use crate::mir::builder::MirBuilder;
use crate::mir::FunctionSignature;
/// Helper to create a test MirBuilder
fn create_test_builder() -> MirBuilder {
let mut builder = MirBuilder::new();
// Initialize a test function
let sig = FunctionSignature {
name: "test_function".to_string(),
params: vec!["i".to_string(), "ch".to_string()],
arity: 2,
return_type: crate::mir::MirType::Integer,
};
builder.start_function(sig);
builder.start_new_block();
builder
}
/// Test: Simple comparison (i < 10)
#[test]
fn test_simple_comparison() {
let mut builder = create_test_builder();
let mut lowerer = BoolExprLowerer::new(&mut builder);
// AST: i < 10
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(10),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lowerer.lower_condition(&ast);
assert!(result.is_ok(), "Simple comparison should succeed");
}
/// Test: OR chain (ch == " " || ch == "\t")
#[test]
fn test_or_chain() {
let mut builder = create_test_builder();
let mut lowerer = BoolExprLowerer::new(&mut builder);
// AST: ch == " " || ch == "\t"
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Or,
left: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "ch".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::String(" ".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
right: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "ch".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::String("\t".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lowerer.lower_condition(&ast);
assert!(result.is_ok(), "OR chain should succeed");
}
/// Test: Complex mixed condition (i < len && (c == " " || c == "\t"))
#[test]
fn test_complex_mixed_condition() {
let mut builder = create_test_builder();
let mut lowerer = BoolExprLowerer::new(&mut builder);
// AST: i < len && (c == " " || c == "\t")
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::And,
left: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "len".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
right: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Or,
left: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "c".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::String(" ".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
right: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "c".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::String("\t".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lowerer.lower_condition(&ast);
assert!(result.is_ok(), "Complex mixed condition should succeed");
}
/// Test: NOT operator (!condition)
#[test]
fn test_not_operator() {
let mut builder = create_test_builder();
let mut lowerer = BoolExprLowerer::new(&mut builder);
// AST: !(i < 10)
let ast = ASTNode::UnaryOp {
operator: crate::ast::UnaryOperator::Not,
operand: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(10),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lowerer.lower_condition(&ast);
assert!(result.is_ok(), "NOT operator should succeed");
}
}

View File

@ -0,0 +1,596 @@
//! Phase 169: JoinIR Condition Lowering Helper
//!
//! This module provides AST → JoinIR condition lowering for loop patterns.
//! Unlike BoolExprLowerer (which generates MIR), this generates JoinIR instructions.
//!
//! ## Design Philosophy
//!
//! **Separation of Concerns**:
//! - BoolExprLowerer: AST → MIR (for regular control flow)
//! - condition_to_joinir: AST → JoinIR (for loop lowerers)
//!
//! This dual approach maintains clean boundaries:
//! - Loop lowerers work in JoinIR space (pure functional transformation)
//! - Regular control flow uses MIR space (stateful builder)
//!
//! ## Usage Example
//!
//! ```rust
//! let mut value_counter = 0u32;
//! let mut alloc_value = || {
//! let id = ValueId(value_counter);
//! value_counter += 1;
//! id
//! };
//!
//! // Lower condition: i < end
//! let (cond_value, cond_insts) = lower_condition_to_joinir(
//! condition_ast,
//! &mut alloc_value,
//! builder,
//! )?;
//!
//! // cond_value: ValueId holding boolean result
//! // cond_insts: Vec<JoinInst::Compute> to evaluate condition
//! ```
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, UnaryOperator};
use crate::mir::join_ir::{BinOpKind, CompareOp, ConstValue, JoinInst, MirLikeInst};
use crate::mir::ValueId;
use std::collections::HashMap;
/// Phase 171-fix: Environment for condition expression lowering
/// Maps variable names to JoinIR-local ValueIds
#[derive(Debug, Clone, Default)]
pub struct ConditionEnv {
pub name_to_join: HashMap<String, ValueId>,
}
impl ConditionEnv {
pub fn new() -> Self {
Self { name_to_join: HashMap::new() }
}
pub fn insert(&mut self, name: String, join_id: ValueId) {
self.name_to_join.insert(name, join_id);
}
pub fn get(&self, name: &str) -> Option<ValueId> {
self.name_to_join.get(name).copied()
}
}
/// Phase 171-fix: Binding between HOST and JoinIR ValueIds for condition variables
#[derive(Debug, Clone)]
pub struct ConditionBinding {
pub name: String,
pub host_value: ValueId,
pub join_value: ValueId,
}
/// Lower an AST condition to JoinIR instructions
///
/// Phase 171-fix: Changed to use ConditionEnv instead of builder
///
/// # Arguments
///
/// * `cond_ast` - AST node representing the boolean condition
/// * `alloc_value` - ValueId allocator function
/// * `env` - ConditionEnv for variable resolution (JoinIR-local ValueIds)
///
/// # Returns
///
/// * `Ok((ValueId, Vec<JoinInst>))` - Condition result ValueId and evaluation instructions
/// * `Err(String)` - Lowering error message
///
/// # Supported Patterns
///
/// - Comparisons: `i < n`, `x == y`, `a != b`, `x <= y`, `x >= y`, `x > y`
/// - Logical: `a && b`, `a || b`, `!cond`
/// - Variables and literals
pub fn lower_condition_to_joinir<F>(
cond_ast: &ASTNode,
alloc_value: &mut F,
env: &ConditionEnv,
) -> Result<(ValueId, Vec<JoinInst>), String>
where
F: FnMut() -> ValueId,
{
let mut instructions = Vec::new();
let result_value = lower_condition_recursive(cond_ast, alloc_value, env, &mut instructions)?;
Ok((result_value, instructions))
}
/// Recursive helper for condition lowering
///
/// Phase 171-fix: Changed to use ConditionEnv instead of builder
fn lower_condition_recursive<F>(
cond_ast: &ASTNode,
alloc_value: &mut F,
env: &ConditionEnv,
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String>
where
F: FnMut() -> ValueId,
{
match cond_ast {
// Comparison operations: <, ==, !=, <=, >=, >
ASTNode::BinaryOp {
operator,
left,
right,
..
} => match operator {
BinaryOperator::Less
| BinaryOperator::Equal
| BinaryOperator::NotEqual
| BinaryOperator::LessEqual
| BinaryOperator::GreaterEqual
| BinaryOperator::Greater => {
// Lower left and right sides
let lhs = lower_value_expression(left, alloc_value, env, instructions)?;
let rhs = lower_value_expression(right, alloc_value, env, instructions)?;
let dst = alloc_value();
let cmp_op = match operator {
BinaryOperator::Less => CompareOp::Lt,
BinaryOperator::Equal => CompareOp::Eq,
BinaryOperator::NotEqual => CompareOp::Ne,
BinaryOperator::LessEqual => CompareOp::Le,
BinaryOperator::GreaterEqual => CompareOp::Ge,
BinaryOperator::Greater => CompareOp::Gt,
_ => unreachable!(),
};
// Emit Compare instruction
instructions.push(JoinInst::Compute(MirLikeInst::Compare {
dst,
op: cmp_op,
lhs,
rhs,
}));
Ok(dst)
}
BinaryOperator::And => {
// Logical AND: evaluate both sides and combine
let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?;
let rhs = lower_condition_recursive(right, alloc_value, env, instructions)?;
let dst = alloc_value();
// Emit BinOp And instruction
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst,
op: BinOpKind::And,
lhs,
rhs,
}));
Ok(dst)
}
BinaryOperator::Or => {
// Logical OR: evaluate both sides and combine
let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?;
let rhs = lower_condition_recursive(right, alloc_value, env, instructions)?;
let dst = alloc_value();
// Emit BinOp Or instruction
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst,
op: BinOpKind::Or,
lhs,
rhs,
}));
Ok(dst)
}
_ => {
// Other operators (arithmetic, etc.)
Err(format!(
"Unsupported binary operator in condition: {:?}",
operator
))
}
},
// Unary NOT operator
ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand,
..
} => {
let operand_val = lower_condition_recursive(operand, alloc_value, env, instructions)?;
let dst = alloc_value();
// Emit UnaryOp Not instruction
instructions.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst,
op: crate::mir::join_ir::UnaryOp::Not,
operand: operand_val,
}));
Ok(dst)
}
// Variables - resolve from ConditionEnv (Phase 171-fix)
ASTNode::Variable { name, .. } => {
// Look up variable in ConditionEnv (JoinIR-local ValueIds)
env.get(name)
.ok_or_else(|| format!("Variable '{}' not bound in ConditionEnv", name))
}
// Literals - emit as constants
ASTNode::Literal { value, .. } => {
let dst = alloc_value();
let const_value = match value {
LiteralValue::Integer(n) => ConstValue::Integer(*n),
LiteralValue::String(s) => ConstValue::String(s.clone()),
LiteralValue::Bool(b) => ConstValue::Bool(*b),
LiteralValue::Float(_) => {
// Float literals not supported in JoinIR ConstValue yet
return Err("Float literals not supported in JoinIR conditions yet".to_string());
}
_ => {
return Err(format!("Unsupported literal type in condition: {:?}", value));
}
};
instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst,
value: const_value,
}));
Ok(dst)
}
_ => Err(format!("Unsupported AST node in condition: {:?}", cond_ast)),
}
}
/// Lower a value expression (for comparison operands, etc.)
///
/// Phase 171-fix: Changed to use ConditionEnv instead of builder
///
/// This handles the common case where we need to evaluate a simple value
/// (variable or literal) as part of a comparison.
fn lower_value_expression<F>(
expr: &ASTNode,
alloc_value: &mut F,
env: &ConditionEnv,
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String>
where
F: FnMut() -> ValueId,
{
match expr {
// Variables - look up in ConditionEnv (Phase 171-fix)
ASTNode::Variable { name, .. } => env
.get(name)
.ok_or_else(|| format!("Variable '{}' not bound in ConditionEnv", name)),
// Literals - emit as constants
ASTNode::Literal { value, .. } => {
let dst = alloc_value();
let const_value = match value {
LiteralValue::Integer(n) => ConstValue::Integer(*n),
LiteralValue::String(s) => ConstValue::String(s.clone()),
LiteralValue::Bool(b) => ConstValue::Bool(*b),
LiteralValue::Float(_) => {
// Float literals not supported in JoinIR ConstValue yet
return Err("Float literals not supported in JoinIR value expressions yet".to_string());
}
_ => {
return Err(format!("Unsupported literal type: {:?}", value));
}
};
instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst,
value: const_value,
}));
Ok(dst)
}
// Binary operations (for arithmetic in conditions like i + 1 < n)
ASTNode::BinaryOp {
operator,
left,
right,
..
} => {
let lhs = lower_value_expression(left, alloc_value, env, instructions)?;
let rhs = lower_value_expression(right, alloc_value, env, instructions)?;
let dst = alloc_value();
let bin_op = match operator {
BinaryOperator::Add => BinOpKind::Add,
BinaryOperator::Subtract => BinOpKind::Sub,
BinaryOperator::Multiply => BinOpKind::Mul,
BinaryOperator::Divide => BinOpKind::Div,
BinaryOperator::Modulo => BinOpKind::Mod,
_ => {
return Err(format!("Unsupported binary operator in expression: {:?}", operator));
}
};
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst,
op: bin_op,
lhs,
rhs,
}));
Ok(dst)
}
_ => Err(format!("Unsupported expression in value context: {:?}", expr)),
}
}
/// Extract all variable names used in a condition AST (Phase 171)
///
/// This helper recursively traverses the condition AST and collects all
/// unique variable names. Used to determine which variables need to be
/// available in JoinIR scope.
///
/// # Arguments
///
/// * `cond_ast` - AST node representing the condition
/// * `exclude_vars` - Variable names to exclude (e.g., loop parameters already registered)
///
/// # Returns
///
/// Sorted vector of unique variable names found in the condition
///
/// # Example
///
/// ```ignore
/// // For condition: start < end && i < len
/// let vars = extract_condition_variables(
/// condition_ast,
/// &["i".to_string()], // Exclude loop variable 'i'
/// );
/// // Result: ["end", "len", "start"] (sorted, 'i' excluded)
/// ```
pub fn extract_condition_variables(
cond_ast: &ASTNode,
exclude_vars: &[String],
) -> Vec<String> {
use std::collections::BTreeSet;
let mut all_vars = BTreeSet::new();
collect_variables_recursive(cond_ast, &mut all_vars);
// Filter out excluded variables and return sorted list
all_vars.into_iter()
.filter(|name| !exclude_vars.contains(name))
.collect()
}
/// Recursive helper to collect variable names
fn collect_variables_recursive(ast: &ASTNode, vars: &mut std::collections::BTreeSet<String>) {
match ast {
ASTNode::Variable { name, .. } => {
vars.insert(name.clone());
}
ASTNode::BinaryOp { left, right, .. } => {
collect_variables_recursive(left, vars);
collect_variables_recursive(right, vars);
}
ASTNode::UnaryOp { operand, .. } => {
collect_variables_recursive(operand, vars);
}
ASTNode::Literal { .. } => {
// Literals have no variables
}
_ => {
// Other AST nodes not expected in conditions
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
/// Phase 171-fix: Helper to create a test ConditionEnv with variables
fn create_test_env() -> ConditionEnv {
let mut env = ConditionEnv::new();
// Register test variables (using JoinIR-local ValueIds)
env.insert("i".to_string(), ValueId(0));
env.insert("end".to_string(), ValueId(1));
env
}
#[test]
fn test_simple_comparison() {
let env = create_test_env();
let mut value_counter = 2u32; // Start after i=0, end=1
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// AST: i < end
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "end".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env);
assert!(result.is_ok(), "Simple comparison should succeed");
let (_cond_value, instructions) = result.unwrap();
assert_eq!(instructions.len(), 1, "Should generate 1 Compare instruction");
}
#[test]
fn test_comparison_with_literal() {
let env = create_test_env();
let mut value_counter = 2u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// AST: i < 10
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(10),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env);
assert!(result.is_ok(), "Comparison with literal should succeed");
let (_cond_value, instructions) = result.unwrap();
// Should have: Const(10), Compare
assert_eq!(instructions.len(), 2, "Should generate Const + Compare");
}
#[test]
fn test_logical_or() {
let mut env = ConditionEnv::new();
env.insert("a".to_string(), ValueId(2));
env.insert("b".to_string(), ValueId(3));
let mut value_counter = 4u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// AST: a < 5 || b < 5
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Or,
left: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "a".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(5),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
right: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "b".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(5),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env);
assert!(result.is_ok(), "OR expression should succeed");
let (_cond_value, instructions) = result.unwrap();
// Should have: Const(5), Compare(a<5), Const(5), Compare(b<5), BinOp(Or)
assert_eq!(instructions.len(), 5, "Should generate proper OR chain");
}
#[test]
fn test_extract_condition_variables_simple() {
// AST: start < end
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "start".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "end".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let vars = extract_condition_variables(&ast, &[]);
assert_eq!(vars, vec!["end", "start"]); // Sorted order
}
#[test]
fn test_extract_condition_variables_with_exclude() {
// AST: i < end
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "end".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let vars = extract_condition_variables(&ast, &["i".to_string()]);
assert_eq!(vars, vec!["end"]); // 'i' excluded
}
#[test]
fn test_extract_condition_variables_complex() {
// AST: start < end && i < len
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::And,
left: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "start".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "end".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
right: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "len".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let vars = extract_condition_variables(&ast, &["i".to_string()]);
assert_eq!(vars, vec!["end", "len", "start"]); // Sorted, 'i' excluded
}
}

View File

@ -164,6 +164,47 @@ pub struct JoinInlineBoundary {
/// ]
/// ```
pub exit_bindings: Vec<LoopExitBinding>,
/// Condition-only input variables (Phase 171+ / Phase 171-fix)
///
/// **DEPRECATED**: Use `condition_bindings` instead (Phase 171-fix).
///
/// These are variables used ONLY in the loop condition, NOT as loop parameters.
/// They need to be available in JoinIR scope but are not modified by the loop.
///
/// # Example
///
/// For `loop(start < end) { i = i + 1 }`:
/// - Loop parameter: `i` → goes in `join_inputs`/`host_inputs`
/// - Condition-only: `start`, `end` → go in `condition_inputs`
///
/// # Format
///
/// Each entry is `(variable_name, host_value_id)`:
/// ```
/// condition_inputs: vec![
/// ("start".to_string(), ValueId(33)), // HOST ID for "start"
/// ("end".to_string(), ValueId(34)), // HOST ID for "end"
/// ]
/// ```
///
/// The merger will:
/// 1. Extract unique variable names from condition AST
/// 2. Look up HOST ValueIds from `builder.variable_map`
/// 3. Inject Copy instructions for each condition input
/// 4. Remap JoinIR references to use the copied values
#[deprecated(since = "Phase 171-fix", note = "Use condition_bindings instead")]
pub condition_inputs: Vec<(String, ValueId)>,
/// Phase 171-fix: Condition bindings with explicit JoinIR ValueIds
///
/// Each binding explicitly specifies:
/// - Variable name
/// - HOST ValueId (source)
/// - JoinIR ValueId (destination)
///
/// This replaces `condition_inputs` to ensure proper ValueId separation.
pub condition_bindings: Vec<super::condition_to_joinir::ConditionBinding>,
}
impl JoinInlineBoundary {
@ -185,6 +226,9 @@ impl JoinInlineBoundary {
#[allow(deprecated)]
host_outputs: vec![],
exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
condition_bindings: vec![], // Phase 171-fix: Default to empty
}
}
@ -221,6 +265,9 @@ impl JoinInlineBoundary {
#[allow(deprecated)]
host_outputs,
exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
condition_bindings: vec![], // Phase 171-fix: Default to empty
}
}
@ -253,6 +300,9 @@ impl JoinInlineBoundary {
#[allow(deprecated)]
host_outputs,
exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
condition_bindings: vec![], // Phase 171-fix: Default to empty
}
}
@ -307,6 +357,153 @@ impl JoinInlineBoundary {
#[allow(deprecated)]
host_outputs: vec![],
exit_bindings,
#[allow(deprecated)]
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
condition_bindings: vec![], // Phase 171-fix: Default to empty
}
}
/// Create a new boundary with condition inputs (Phase 171+)
///
/// # Arguments
///
/// * `join_inputs` - JoinIR-local ValueIds for loop parameters
/// * `host_inputs` - HOST ValueIds for loop parameters
/// * `condition_inputs` - Condition-only variables [(name, host_value_id)]
///
/// # Example
///
/// ```ignore
/// let boundary = JoinInlineBoundary::new_with_condition_inputs(
/// vec![ValueId(0)], // join_inputs (i)
/// vec![ValueId(5)], // host_inputs (i)
/// vec![
/// ("start".to_string(), ValueId(33)),
/// ("end".to_string(), ValueId(34)),
/// ],
/// );
/// ```
pub fn new_with_condition_inputs(
join_inputs: Vec<ValueId>,
host_inputs: Vec<ValueId>,
condition_inputs: Vec<(String, ValueId)>,
) -> Self {
assert_eq!(
join_inputs.len(),
host_inputs.len(),
"join_inputs and host_inputs must have same length"
);
Self {
join_inputs,
host_inputs,
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs,
condition_bindings: vec![], // Phase 171-fix: Will be populated by new constructor
}
}
/// Create boundary with inputs, exit bindings, AND condition inputs (Phase 171+)
///
/// This is the most complete constructor for loops with carriers and condition variables.
///
/// # Example: Pattern 3 with condition variables
///
/// ```ignore
/// let boundary = JoinInlineBoundary::new_with_exit_and_condition_inputs(
/// vec![ValueId(0), ValueId(1)], // join_inputs (i, sum)
/// vec![ValueId(5), ValueId(10)], // host_inputs
/// vec![
/// LoopExitBinding {
/// carrier_name: "sum".to_string(),
/// join_exit_value: ValueId(18),
/// host_slot: ValueId(10),
/// }
/// ],
/// vec![
/// ("start".to_string(), ValueId(33)),
/// ("end".to_string(), ValueId(34)),
/// ],
/// );
/// ```
pub fn new_with_exit_and_condition_inputs(
join_inputs: Vec<ValueId>,
host_inputs: Vec<ValueId>,
exit_bindings: Vec<LoopExitBinding>,
condition_inputs: Vec<(String, ValueId)>,
) -> Self {
assert_eq!(
join_inputs.len(),
host_inputs.len(),
"join_inputs and host_inputs must have same length"
);
Self {
join_inputs,
host_inputs,
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
exit_bindings,
#[allow(deprecated)]
condition_inputs,
condition_bindings: vec![], // Phase 171-fix: Will be populated by new constructor
}
}
/// Phase 171-fix: Create boundary with ConditionBindings (NEW constructor)
///
/// This is the recommended constructor that uses ConditionBindings instead of
/// the deprecated condition_inputs.
///
/// # Arguments
///
/// * `join_inputs` - JoinIR-local ValueIds for loop parameters
/// * `host_inputs` - HOST ValueIds for loop parameters
/// * `condition_bindings` - Explicit HOST ↔ JoinIR mappings for condition variables
///
/// # Example
///
/// ```ignore
/// let boundary = JoinInlineBoundary::new_with_condition_bindings(
/// vec![ValueId(0)], // join_inputs (loop param i)
/// vec![ValueId(5)], // host_inputs (loop param i)
/// vec![
/// ConditionBinding {
/// name: "start".to_string(),
/// host_value: ValueId(33), // HOST
/// join_value: ValueId(1), // JoinIR
/// },
/// ConditionBinding {
/// name: "end".to_string(),
/// host_value: ValueId(34), // HOST
/// join_value: ValueId(2), // JoinIR
/// },
/// ],
/// );
/// ```
pub fn new_with_condition_bindings(
join_inputs: Vec<ValueId>,
host_inputs: Vec<ValueId>,
condition_bindings: Vec<super::condition_to_joinir::ConditionBinding>,
) -> Self {
assert_eq!(
join_inputs.len(),
host_inputs.len(),
"join_inputs and host_inputs must have same length"
);
Self {
join_inputs,
host_inputs,
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs: vec![], // Deprecated, use condition_bindings instead
condition_bindings,
}
}
}
@ -325,7 +522,12 @@ mod tests {
assert_eq!(boundary.join_inputs.len(), 1);
assert_eq!(boundary.host_inputs.len(), 1);
assert_eq!(boundary.join_outputs.len(), 0);
assert_eq!(boundary.host_outputs.len(), 0);
#[allow(deprecated)]
{
assert_eq!(boundary.host_outputs.len(), 0);
assert_eq!(boundary.condition_inputs.len(), 0); // Phase 171: Deprecated field
}
assert_eq!(boundary.condition_bindings.len(), 0); // Phase 171-fix: New field
}
#[test]

View File

@ -341,7 +341,9 @@ pub fn lower_loop_with_break_to_joinir(
};
// Generate JoinIR module
let _join_module = lower_loop_with_break_minimal(placeholder_scope)?;
// Phase 169: lower_loop_with_break_minimal now requires condition AST and builder
// This test stub is not used by the actual router, so commenting out for now
// let _join_module = lower_loop_with_break_minimal(placeholder_scope, condition, builder)?;
// Phase 188-Impl-2: Pattern 2 is now integrated in control_flow.rs via cf_loop_pattern2_with_break()
// This function (lower_loop_with_break_to_joinir) is currently not used by the router.

View File

@ -55,6 +55,8 @@
//!
//! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later.
use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::condition_to_joinir::{lower_condition_to_joinir, ConditionEnv};
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule,
@ -85,11 +87,13 @@ use crate::mir::ValueId;
/// # Arguments
///
/// * `_scope` - LoopScopeShape (reserved for future generic implementation)
/// * `condition` - Loop condition AST node (e.g., `i < end`)
/// * `env` - ConditionEnv for variable resolution (JoinIR-local ValueIds) - Phase 171-fix
///
/// # Returns
///
/// * `Some(JoinModule)` - Successfully lowered to JoinIR
/// * `None` - Pattern not matched (fallback to other lowerers)
/// * `Ok(JoinModule)` - Successfully lowered to JoinIR
/// * `Err(String)` - Pattern not matched or lowering error
///
/// # Boundary Contract
///
@ -97,7 +101,16 @@ use crate::mir::ValueId;
/// - **Input slot**: ValueId(0) in main function represents the loop variable init
/// - **Output slot**: k_exit returns the final loop variable value
/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds
pub fn lower_loop_with_break_minimal(_scope: LoopScopeShape) -> Option<JoinModule> {
///
/// # Phase 171-fix: ConditionEnv Usage
///
/// The caller must build a ConditionEnv that maps variable names to JoinIR-local ValueIds.
/// This ensures JoinIR never accesses HOST ValueIds directly.
pub fn lower_loop_with_break_minimal(
_scope: LoopScopeShape,
condition: &ASTNode,
env: &ConditionEnv,
) -> Result<JoinModule, String> {
// Phase 188-Impl-2: Use local ValueId allocator (sequential from 0)
// JoinIR has NO knowledge of host ValueIds - boundary handled separately
let mut value_counter = 0u32;
@ -125,16 +138,24 @@ pub fn lower_loop_with_break_minimal(_scope: LoopScopeShape) -> Option<JoinModul
// loop_step locals
let i_param = alloc_value(); // ValueId(2) - parameter
let const_3 = alloc_value(); // ValueId(3) - natural exit limit
let cmp_lt = alloc_value(); // ValueId(4) - i < 3
let exit_cond = alloc_value(); // ValueId(5) - !(i < 3)
let const_2 = alloc_value(); // ValueId(6) - break limit
let break_cond = alloc_value(); // ValueId(7) - i >= 2
let const_1 = alloc_value(); // ValueId(8) - increment constant
let i_next = alloc_value(); // ValueId(9) - i + 1
// Phase 169 / Phase 171-fix: Lower condition using condition_to_joinir helper with ConditionEnv
// This will allocate ValueIds dynamically based on condition complexity
let (cond_value, mut cond_instructions) = lower_condition_to_joinir(
condition,
&mut alloc_value,
env,
)?;
// After condition lowering, allocate remaining ValueIds
let exit_cond = alloc_value(); // Exit condition (negated loop condition)
let const_2 = alloc_value(); // Break limit (hardcoded for now)
let break_cond = alloc_value(); // Break condition (i >= 2)
let const_1 = alloc_value(); // Increment constant
let i_next = alloc_value(); // i + 1
// k_exit locals
let i_exit = alloc_value(); // ValueId(10) - exit parameter (PHI)
let i_exit = alloc_value(); // Exit parameter (PHI)
// ==================================================================
// main() function
@ -168,33 +189,18 @@ pub fn lower_loop_with_break_minimal(_scope: LoopScopeShape) -> Option<JoinModul
);
// ------------------------------------------------------------------
// Natural Exit Condition Check: !(i < 3)
// Natural Exit Condition Check (Phase 169: from AST)
// ------------------------------------------------------------------
// Step 1: const 3
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_3,
value: ConstValue::Integer(3),
}));
// Insert all condition evaluation instructions
loop_step_func.body.append(&mut cond_instructions);
// Step 2: cmp_lt = (i < 3)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_lt,
op: CompareOp::Lt,
lhs: i_param,
rhs: const_3,
}));
// Step 3: exit_cond = !cmp_lt
// Negate the condition for exit check: exit_cond = !cond_value
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst: exit_cond,
op: UnaryOp::Not,
operand: cmp_lt,
operand: cond_value,
}));
// Jump(k_exit, [i], cond=exit_cond) // Natural exit path
@ -283,9 +289,10 @@ pub fn lower_loop_with_break_minimal(_scope: LoopScopeShape) -> Option<JoinModul
// Set entry point
join_module.entry = Some(main_id);
eprintln!("[joinir/pattern2] Generated JoinIR for Loop with Break Pattern");
eprintln!("[joinir/pattern2] Generated JoinIR for Loop with Break Pattern (Phase 169)");
eprintln!("[joinir/pattern2] Functions: main, loop_step, k_exit");
eprintln!("[joinir/pattern2] Condition from AST (not hardcoded)");
eprintln!("[joinir/pattern2] Exit PHI: k_exit receives i from both natural exit and break");
Some(join_module)
Ok(join_module)
}

View File

@ -44,7 +44,10 @@
//!
//! Following the "80/20 rule" from CLAUDE.md - now generalizing after working.
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta};
use crate::mir::join_ir::lowering::condition_to_joinir::{lower_condition_to_joinir, ConditionEnv};
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs};
use crate::mir::join_ir::{
@ -77,13 +80,15 @@ use std::collections::HashMap;
/// # Arguments
///
/// * `_scope` - LoopScopeShape (reserved for future generic implementation)
/// * `condition` - Loop condition AST node (e.g., `i < end`) (Phase 169)
/// * `builder` - MirBuilder for variable resolution (Phase 169)
/// * `carrier_info` - Phase 196: Carrier metadata for dynamic multi-carrier support
/// * `carrier_updates` - Phase 197: Update expressions for each carrier variable
///
/// # Returns
///
/// * `Some((JoinModule, ExitMeta))` - Successfully lowered to JoinIR with exit metadata
/// * `None` - Pattern not matched (fallback to other lowerers)
/// * `Ok((JoinModule, ExitMeta))` - Successfully lowered to JoinIR with exit metadata
/// * `Err(String)` - Pattern not matched or lowering error
///
/// # Boundary Contract (Phase 196 updated)
///
@ -94,9 +99,11 @@ use std::collections::HashMap;
/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds
pub fn lower_loop_with_continue_minimal(
_scope: LoopScopeShape,
condition: &ASTNode,
builder: &mut MirBuilder,
carrier_info: &CarrierInfo,
carrier_updates: &HashMap<String, UpdateExpr>,
) -> Option<(JoinModule, ExitMeta)> {
) -> Result<(JoinModule, ExitMeta), String> {
// Phase 196: Use local ValueId allocator (sequential from 0)
// JoinIR has NO knowledge of host ValueIds - boundary handled separately
let mut value_counter = 0u32;
@ -140,9 +147,34 @@ pub fn lower_loop_with_continue_minimal(
carrier_param_ids.push(alloc_value());
}
// Phase 171-fix: Build ConditionEnv for condition lowering
// TODO(Phase 171-fix): This is a temporary workaround. Pattern 3 lowerer should build
// ConditionEnv at the call site and pass it here, similar to Pattern 2.
// For now, we build it internally since this function still needs builder for carrier_info.
use crate::mir::join_ir::lowering::condition_to_joinir::extract_condition_variables;
let mut env = ConditionEnv::new();
// Add loop parameter to env (ValueId(0) in JoinIR space)
let loop_var_name = carrier_info.loop_var_name.clone();
env.insert(loop_var_name.clone(), ValueId(0)); // i_param is ValueId(0) equivalent
// Extract and add condition-only variables
let condition_var_names = extract_condition_variables(condition, &[loop_var_name.clone()]);
let mut join_value_counter = carrier_count as u32 + 1; // After loop param and carriers
for var_name in &condition_var_names {
let join_id = ValueId(join_value_counter);
join_value_counter += 1;
env.insert(var_name.clone(), join_id);
}
// Phase 169 / Phase 171-fix: Lower condition using condition_to_joinir helper with ConditionEnv
let (cond_value, mut cond_instructions) = lower_condition_to_joinir(
condition,
&mut alloc_value,
&env,
)?;
// Loop control temporaries
let const_10 = alloc_value();
let cmp_lt = alloc_value();
let exit_cond = alloc_value();
let const_1 = alloc_value();
let i_next = alloc_value();
@ -202,30 +234,18 @@ pub fn lower_loop_with_continue_minimal(
);
// ------------------------------------------------------------------
// Natural Exit Condition Check: !(i < 10)
// Natural Exit Condition Check (Phase 169: from AST)
// ------------------------------------------------------------------
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_10,
value: ConstValue::Integer(10),
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_lt,
op: CompareOp::Lt,
lhs: i_param,
rhs: const_10,
}));
// Insert all condition evaluation instructions
loop_step_func.body.append(&mut cond_instructions);
// Negate the condition for exit check: exit_cond = !cond_value
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst: exit_cond,
op: UnaryOp::Not,
operand: cmp_lt,
operand: cond_value,
}));
// Jump(k_exit, [carrier1, carrier2, ...], cond=exit_cond)
@ -455,9 +475,9 @@ pub fn lower_loop_with_continue_minimal(
let exit_meta = ExitMeta::multiple(exit_values);
eprintln!(
"[joinir/pattern4] ExitMeta total: {} bindings",
"[joinir/pattern4] Phase 169: ExitMeta total: {} bindings (condition from AST)",
exit_meta.exit_values.len()
);
Some((join_module, exit_meta))
Ok((join_module, exit_meta))
}

View File

@ -14,9 +14,12 @@
//! - `funcscanner_append_defs.rs`: FuncScannerBox._append_defs/2 の配列結合 loweringPhase 27.14
//! - `if_select.rs`: Phase 33 If/Else → Select lowering
//! - `if_dry_runner.rs`: Phase 33-10 If lowering dry-run スキャナー(箱化版)
//! - `bool_expr_lowerer.rs`: Phase 168 Boolean expression lowering (AST → SSA)
pub mod bool_expr_lowerer; // Phase 168: Boolean expression lowering for complex conditions
pub mod carrier_info; // Phase 196: Carrier metadata for loop lowering
pub mod common;
pub mod condition_to_joinir; // Phase 169: JoinIR condition lowering helper
pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carrier semantics
pub mod exit_args_resolver;
pub mod funcscanner_append_defs;