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:
@ -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(
|
||||
|
||||
@ -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)?;
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)?;
|
||||
|
||||
|
||||
@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
370
src/mir/join_ir/lowering/bool_expr_lowerer.rs
Normal file
370
src/mir/join_ir/lowering/bool_expr_lowerer.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
596
src/mir/join_ir/lowering/condition_to_joinir.rs
Normal file
596
src/mir/join_ir/lowering/condition_to_joinir.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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]
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -14,9 +14,12 @@
|
||||
//! - `funcscanner_append_defs.rs`: FuncScannerBox._append_defs/2 の配列結合 lowering(Phase 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;
|
||||
|
||||
Reference in New Issue
Block a user