refactor(joinir): Extract carrier_update_emitter from loop_with_break_minimal
Phase 179 Task 1: Modular separation for single responsibility Changes: - NEW: carrier_update_emitter.rs (416 lines) - emit_carrier_update() function + 6 unit tests - Focused module for UpdateExpr → JoinInst conversion - REDUCED: loop_with_break_minimal.rs (998→597 lines, -401 lines) - Removed emit_carrier_update() and carrier_update_tests module - Now imports from carrier_update_emitter - UPDATED: mod.rs - added carrier_update_emitter module Benefits: - Single responsibility: carrier update emission isolated - Reusability: can be used by Pattern 3, 4, and future patterns - Testability: independent unit tests - Maintainability: 40% size reduction in loop_with_break_minimal.rs Note: Pre-existing test failure in test_pattern2_accepts_loop_param_only is unrelated to this refactoring (test expects 1 var but gets 3 due to literal "10" and "5" being counted as variables).
This commit is contained in:
416
src/mir/join_ir/lowering/carrier_update_emitter.rs
Normal file
416
src/mir/join_ir/lowering/carrier_update_emitter.rs
Normal file
@ -0,0 +1,416 @@
|
||||
//! Phase 176-2 / Phase 179: Carrier Update Emission
|
||||
//!
|
||||
//! Converts UpdateExpr (from LoopUpdateAnalyzer) into JoinIR instructions
|
||||
//! that compute the updated carrier value.
|
||||
//!
|
||||
//! This module is extracted from loop_with_break_minimal.rs to improve
|
||||
//! modularity and single responsibility.
|
||||
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
|
||||
use crate::mir::join_ir::lowering::condition_to_joinir::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs};
|
||||
use crate::mir::join_ir::{BinOpKind, ConstValue, JoinInst, MirLikeInst};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Emit JoinIR instructions for a single carrier update
|
||||
///
|
||||
/// Converts UpdateExpr (from LoopUpdateAnalyzer) into JoinIR instructions
|
||||
/// that compute the updated carrier value.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `carrier` - Carrier variable information (name, ValueId)
|
||||
/// * `update` - Update expression (e.g., CounterLike, AccumulationLike)
|
||||
/// * `alloc_value` - ValueId allocator closure
|
||||
/// * `env` - ConditionEnv for variable resolution
|
||||
/// * `instructions` - Output vector to append instructions to
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// ValueId of the computed update result
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// // For "count = count + 1":
|
||||
/// let count_next = emit_carrier_update(
|
||||
/// &count_carrier,
|
||||
/// &UpdateExpr::BinOp { lhs: "count", op: Add, rhs: Const(1) },
|
||||
/// &mut alloc_value,
|
||||
/// &env,
|
||||
/// &mut instructions,
|
||||
/// )?;
|
||||
/// // Generates:
|
||||
/// // const_1 = Const(1)
|
||||
/// // count_next = BinOp(Add, count_param, const_1)
|
||||
/// ```
|
||||
pub fn emit_carrier_update(
|
||||
carrier: &CarrierVar,
|
||||
update: &UpdateExpr,
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &ConditionEnv,
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String> {
|
||||
match update {
|
||||
UpdateExpr::Const(step) => {
|
||||
// CounterLike: carrier = carrier + step
|
||||
// Allocate const ValueId
|
||||
let const_id = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_id,
|
||||
value: ConstValue::Integer(*step),
|
||||
}));
|
||||
|
||||
// Get carrier parameter ValueId from env
|
||||
let carrier_param = env
|
||||
.get(&carrier.name)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Carrier '{}' not found in ConditionEnv",
|
||||
carrier.name
|
||||
)
|
||||
})?;
|
||||
|
||||
// Allocate result ValueId
|
||||
let result = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: result,
|
||||
op: BinOpKind::Add,
|
||||
lhs: carrier_param,
|
||||
rhs: const_id,
|
||||
}));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
UpdateExpr::BinOp { lhs, op, rhs } => {
|
||||
// General binary operation: carrier = carrier op rhs
|
||||
// Verify lhs matches carrier name
|
||||
if lhs != &carrier.name {
|
||||
return Err(format!(
|
||||
"Update expression LHS '{}' doesn't match carrier '{}'",
|
||||
lhs, carrier.name
|
||||
));
|
||||
}
|
||||
|
||||
// Get carrier parameter ValueId from env
|
||||
let carrier_param = env
|
||||
.get(&carrier.name)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Carrier '{}' not found in ConditionEnv",
|
||||
carrier.name
|
||||
)
|
||||
})?;
|
||||
|
||||
// Resolve RHS
|
||||
let rhs_id = match rhs {
|
||||
UpdateRhs::Const(n) => {
|
||||
let const_id = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_id,
|
||||
value: ConstValue::Integer(*n),
|
||||
}));
|
||||
const_id
|
||||
}
|
||||
UpdateRhs::Variable(var_name) => {
|
||||
env.get(var_name).ok_or_else(|| {
|
||||
format!(
|
||||
"Update RHS variable '{}' not found in ConditionEnv",
|
||||
var_name
|
||||
)
|
||||
})?
|
||||
}
|
||||
// Phase 178: String updates detected but not lowered to JoinIR yet
|
||||
// The Rust MIR path handles string concatenation
|
||||
// For JoinIR: just pass through the carrier param (no JoinIR update)
|
||||
UpdateRhs::StringLiteral(_) | UpdateRhs::Other => {
|
||||
eprintln!(
|
||||
"[joinir/pattern2] Phase 178: Carrier '{}' has string/complex update - skipping JoinIR emit, using param passthrough",
|
||||
carrier.name
|
||||
);
|
||||
return Ok(carrier_param); // Pass-through: no JoinIR update
|
||||
}
|
||||
};
|
||||
|
||||
// Allocate result ValueId
|
||||
let result = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: result,
|
||||
op: *op,
|
||||
lhs: carrier_param,
|
||||
rhs: rhs_id,
|
||||
}));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
|
||||
|
||||
// Helper: Create a test ConditionEnv
|
||||
fn test_env() -> ConditionEnv {
|
||||
let mut env = ConditionEnv::new();
|
||||
env.insert("count".to_string(), ValueId(10));
|
||||
env.insert("sum".to_string(), ValueId(20));
|
||||
env.insert("i".to_string(), ValueId(30));
|
||||
env
|
||||
}
|
||||
|
||||
// Helper: Create a test CarrierVar
|
||||
fn test_carrier(name: &str, host_id: u32) -> CarrierVar {
|
||||
CarrierVar {
|
||||
name: name.to_string(),
|
||||
host_id: ValueId(host_id),
|
||||
join_id: None, // Phase 177-STRUCT-1
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_const_update() {
|
||||
// Test: count = count + 1 (UpdateExpr::Const)
|
||||
let carrier = test_carrier("count", 100);
|
||||
let update = UpdateExpr::Const(1);
|
||||
let env = test_env();
|
||||
|
||||
let mut value_counter = 50u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let result_id = result.unwrap();
|
||||
|
||||
// Should generate 2 instructions: Const(1) + BinOp(Add)
|
||||
assert_eq!(instructions.len(), 2);
|
||||
|
||||
// Instruction 1: Const(1)
|
||||
match &instructions[0] {
|
||||
JoinInst::Compute(MirLikeInst::Const { dst, value }) => {
|
||||
assert_eq!(*dst, ValueId(50)); // First allocated
|
||||
assert!(matches!(value, ConstValue::Integer(1)));
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
|
||||
// Instruction 2: BinOp(Add, count, const_1)
|
||||
match &instructions[1] {
|
||||
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
|
||||
assert_eq!(*dst, ValueId(51)); // Second allocated
|
||||
assert_eq!(*op, BinOpKind::Add);
|
||||
assert_eq!(*lhs, ValueId(10)); // count from env
|
||||
assert_eq!(*rhs, ValueId(50)); // const_1
|
||||
}
|
||||
_ => panic!("Expected BinOp instruction"),
|
||||
}
|
||||
|
||||
assert_eq!(result_id, ValueId(51));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_binop_update_with_const() {
|
||||
// Test: sum = sum + 5 (UpdateExpr::BinOp with Const RHS)
|
||||
let carrier = test_carrier("sum", 200);
|
||||
let update = UpdateExpr::BinOp {
|
||||
lhs: "sum".to_string(),
|
||||
op: BinOpKind::Add,
|
||||
rhs: UpdateRhs::Const(5),
|
||||
};
|
||||
let env = test_env();
|
||||
|
||||
let mut value_counter = 60u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let result_id = result.unwrap();
|
||||
|
||||
// Should generate 2 instructions: Const(5) + BinOp(Add)
|
||||
assert_eq!(instructions.len(), 2);
|
||||
|
||||
// Instruction 1: Const(5)
|
||||
match &instructions[0] {
|
||||
JoinInst::Compute(MirLikeInst::Const { dst, value }) => {
|
||||
assert_eq!(*dst, ValueId(60));
|
||||
assert!(matches!(value, ConstValue::Integer(5)));
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
|
||||
// Instruction 2: BinOp(Add, sum, const_5)
|
||||
match &instructions[1] {
|
||||
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
|
||||
assert_eq!(*dst, ValueId(61));
|
||||
assert_eq!(*op, BinOpKind::Add);
|
||||
assert_eq!(*lhs, ValueId(20)); // sum from env
|
||||
assert_eq!(*rhs, ValueId(60)); // const_5
|
||||
}
|
||||
_ => panic!("Expected BinOp instruction"),
|
||||
}
|
||||
|
||||
assert_eq!(result_id, ValueId(61));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_binop_update_with_variable() {
|
||||
// Test: sum = sum + i (UpdateExpr::BinOp with Variable RHS)
|
||||
let carrier = test_carrier("sum", 200);
|
||||
let update = UpdateExpr::BinOp {
|
||||
lhs: "sum".to_string(),
|
||||
op: BinOpKind::Add,
|
||||
rhs: UpdateRhs::Variable("i".to_string()),
|
||||
};
|
||||
let env = test_env();
|
||||
|
||||
let mut value_counter = 70u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let result_id = result.unwrap();
|
||||
|
||||
// Should generate 1 instruction: BinOp(Add, sum, i)
|
||||
assert_eq!(instructions.len(), 1);
|
||||
|
||||
// Instruction: BinOp(Add, sum, i)
|
||||
match &instructions[0] {
|
||||
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
|
||||
assert_eq!(*dst, ValueId(70));
|
||||
assert_eq!(*op, BinOpKind::Add);
|
||||
assert_eq!(*lhs, ValueId(20)); // sum from env
|
||||
assert_eq!(*rhs, ValueId(30)); // i from env
|
||||
}
|
||||
_ => panic!("Expected BinOp instruction"),
|
||||
}
|
||||
|
||||
assert_eq!(result_id, ValueId(70));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_update_carrier_not_in_env() {
|
||||
// Test error case: carrier not found in env
|
||||
let carrier = test_carrier("unknown", 300);
|
||||
let update = UpdateExpr::Const(1);
|
||||
let env = test_env(); // doesn't have "unknown"
|
||||
|
||||
let mut value_counter = 80u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Carrier 'unknown' not found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_update_lhs_mismatch() {
|
||||
// Test error case: LHS doesn't match carrier name
|
||||
let carrier = test_carrier("count", 100);
|
||||
let update = UpdateExpr::BinOp {
|
||||
lhs: "sum".to_string(), // Wrong! Should be "count"
|
||||
op: BinOpKind::Add,
|
||||
rhs: UpdateRhs::Const(1),
|
||||
};
|
||||
let env = test_env();
|
||||
|
||||
let mut value_counter = 90u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("doesn't match carrier"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_update_rhs_variable_not_found() {
|
||||
// Test error case: RHS variable not in env
|
||||
let carrier = test_carrier("sum", 200);
|
||||
let update = UpdateExpr::BinOp {
|
||||
lhs: "sum".to_string(),
|
||||
op: BinOpKind::Add,
|
||||
rhs: UpdateRhs::Variable("unknown_var".to_string()),
|
||||
};
|
||||
let env = test_env();
|
||||
|
||||
let mut value_counter = 100u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Update RHS variable 'unknown_var' not found"));
|
||||
}
|
||||
}
|
||||
@ -56,10 +56,11 @@
|
||||
//! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later.
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierVar, ExitMeta, JoinFragmentMeta};
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta, JoinFragmentMeta};
|
||||
use crate::mir::join_ir::lowering::carrier_update_emitter::emit_carrier_update;
|
||||
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::lowering::loop_update_analyzer::UpdateExpr;
|
||||
use crate::mir::join_ir::{
|
||||
BinOpKind, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule,
|
||||
MirLikeInst, UnaryOp,
|
||||
@ -71,141 +72,6 @@ use crate::mir::loop_pattern_detection::error_messages::{
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Phase 176-2: Emit JoinIR instructions for a single carrier update
|
||||
///
|
||||
/// Converts UpdateExpr (from LoopUpdateAnalyzer) into JoinIR instructions
|
||||
/// that compute the updated carrier value.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `carrier` - Carrier variable information (name, ValueId)
|
||||
/// * `update` - Update expression (e.g., CounterLike, AccumulationLike)
|
||||
/// * `alloc_value` - ValueId allocator closure
|
||||
/// * `env` - ConditionEnv for variable resolution
|
||||
/// * `instructions` - Output vector to append instructions to
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// ValueId of the computed update result
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// // For "count = count + 1":
|
||||
/// let count_next = emit_carrier_update(
|
||||
/// &count_carrier,
|
||||
/// &UpdateExpr::BinOp { lhs: "count", op: Add, rhs: Const(1) },
|
||||
/// &mut alloc_value,
|
||||
/// &env,
|
||||
/// &mut instructions,
|
||||
/// )?;
|
||||
/// // Generates:
|
||||
/// // const_1 = Const(1)
|
||||
/// // count_next = BinOp(Add, count_param, const_1)
|
||||
/// ```
|
||||
fn emit_carrier_update(
|
||||
carrier: &CarrierVar,
|
||||
update: &UpdateExpr,
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &ConditionEnv,
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String> {
|
||||
match update {
|
||||
UpdateExpr::Const(step) => {
|
||||
// CounterLike: carrier = carrier + step
|
||||
// Allocate const ValueId
|
||||
let const_id = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_id,
|
||||
value: ConstValue::Integer(*step),
|
||||
}));
|
||||
|
||||
// Get carrier parameter ValueId from env
|
||||
let carrier_param = env
|
||||
.get(&carrier.name)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Carrier '{}' not found in ConditionEnv",
|
||||
carrier.name
|
||||
)
|
||||
})?;
|
||||
|
||||
// Allocate result ValueId
|
||||
let result = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: result,
|
||||
op: BinOpKind::Add,
|
||||
lhs: carrier_param,
|
||||
rhs: const_id,
|
||||
}));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
UpdateExpr::BinOp { lhs, op, rhs } => {
|
||||
// General binary operation: carrier = carrier op rhs
|
||||
// Verify lhs matches carrier name
|
||||
if lhs != &carrier.name {
|
||||
return Err(format!(
|
||||
"Update expression LHS '{}' doesn't match carrier '{}'",
|
||||
lhs, carrier.name
|
||||
));
|
||||
}
|
||||
|
||||
// Get carrier parameter ValueId from env
|
||||
let carrier_param = env
|
||||
.get(&carrier.name)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Carrier '{}' not found in ConditionEnv",
|
||||
carrier.name
|
||||
)
|
||||
})?;
|
||||
|
||||
// Resolve RHS
|
||||
let rhs_id = match rhs {
|
||||
UpdateRhs::Const(n) => {
|
||||
let const_id = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_id,
|
||||
value: ConstValue::Integer(*n),
|
||||
}));
|
||||
const_id
|
||||
}
|
||||
UpdateRhs::Variable(var_name) => {
|
||||
env.get(var_name).ok_or_else(|| {
|
||||
format!(
|
||||
"Update RHS variable '{}' not found in ConditionEnv",
|
||||
var_name
|
||||
)
|
||||
})?
|
||||
}
|
||||
// Phase 178: String updates detected but not lowered to JoinIR yet
|
||||
// The Rust MIR path handles string concatenation
|
||||
// For JoinIR: just pass through the carrier param (no JoinIR update)
|
||||
UpdateRhs::StringLiteral(_) | UpdateRhs::Other => {
|
||||
eprintln!(
|
||||
"[joinir/pattern2] Phase 178: Carrier '{}' has string/complex update - skipping JoinIR emit, using param passthrough",
|
||||
carrier.name
|
||||
);
|
||||
return Ok(carrier_param); // Pass-through: no JoinIR update
|
||||
}
|
||||
};
|
||||
|
||||
// Allocate result ValueId
|
||||
let result = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: result,
|
||||
op: *op,
|
||||
lhs: carrier_param,
|
||||
rhs: rhs_id,
|
||||
}));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Lower Pattern 2 (Loop with Conditional Break) to JoinIR
|
||||
///
|
||||
/// # Phase 188-Impl-2: Pure JoinIR Fragment Generation
|
||||
@ -728,271 +594,4 @@ mod tests {
|
||||
assert!(vars.contains("ch")); // The problematic body-local variable
|
||||
}
|
||||
|
||||
// Phase 176-2: Tests for emit_carrier_update helper
|
||||
mod carrier_update_tests {
|
||||
use super::*;
|
||||
use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs};
|
||||
|
||||
// Helper: Create a test ConditionEnv
|
||||
fn test_env() -> ConditionEnv {
|
||||
let mut env = ConditionEnv::new();
|
||||
env.insert("count".to_string(), ValueId(10));
|
||||
env.insert("sum".to_string(), ValueId(20));
|
||||
env.insert("i".to_string(), ValueId(30));
|
||||
env
|
||||
}
|
||||
|
||||
// Helper: Create a test CarrierVar
|
||||
fn test_carrier(name: &str, host_id: u32) -> CarrierVar {
|
||||
CarrierVar {
|
||||
name: name.to_string(),
|
||||
host_id: ValueId(host_id),
|
||||
join_id: None, // Phase 177-STRUCT-1
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_const_update() {
|
||||
// Test: count = count + 1 (UpdateExpr::Const)
|
||||
let carrier = test_carrier("count", 100);
|
||||
let update = UpdateExpr::Const(1);
|
||||
let env = test_env();
|
||||
|
||||
let mut value_counter = 50u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let result_id = result.unwrap();
|
||||
|
||||
// Should generate 2 instructions: Const(1) + BinOp(Add)
|
||||
assert_eq!(instructions.len(), 2);
|
||||
|
||||
// Instruction 1: Const(1)
|
||||
match &instructions[0] {
|
||||
JoinInst::Compute(MirLikeInst::Const { dst, value }) => {
|
||||
assert_eq!(*dst, ValueId(50)); // First allocated
|
||||
assert!(matches!(value, ConstValue::Integer(1)));
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
|
||||
// Instruction 2: BinOp(Add, count, const_1)
|
||||
match &instructions[1] {
|
||||
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
|
||||
assert_eq!(*dst, ValueId(51)); // Second allocated
|
||||
assert_eq!(*op, BinOpKind::Add);
|
||||
assert_eq!(*lhs, ValueId(10)); // count from env
|
||||
assert_eq!(*rhs, ValueId(50)); // const_1
|
||||
}
|
||||
_ => panic!("Expected BinOp instruction"),
|
||||
}
|
||||
|
||||
assert_eq!(result_id, ValueId(51));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_binop_update_with_const() {
|
||||
// Test: sum = sum + 5 (UpdateExpr::BinOp with Const RHS)
|
||||
let carrier = test_carrier("sum", 200);
|
||||
let update = UpdateExpr::BinOp {
|
||||
lhs: "sum".to_string(),
|
||||
op: BinOpKind::Add,
|
||||
rhs: UpdateRhs::Const(5),
|
||||
};
|
||||
let env = test_env();
|
||||
|
||||
let mut value_counter = 60u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let result_id = result.unwrap();
|
||||
|
||||
// Should generate 2 instructions: Const(5) + BinOp(Add)
|
||||
assert_eq!(instructions.len(), 2);
|
||||
|
||||
// Instruction 1: Const(5)
|
||||
match &instructions[0] {
|
||||
JoinInst::Compute(MirLikeInst::Const { dst, value }) => {
|
||||
assert_eq!(*dst, ValueId(60));
|
||||
assert!(matches!(value, ConstValue::Integer(5)));
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
|
||||
// Instruction 2: BinOp(Add, sum, const_5)
|
||||
match &instructions[1] {
|
||||
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
|
||||
assert_eq!(*dst, ValueId(61));
|
||||
assert_eq!(*op, BinOpKind::Add);
|
||||
assert_eq!(*lhs, ValueId(20)); // sum from env
|
||||
assert_eq!(*rhs, ValueId(60)); // const_5
|
||||
}
|
||||
_ => panic!("Expected BinOp instruction"),
|
||||
}
|
||||
|
||||
assert_eq!(result_id, ValueId(61));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_binop_update_with_variable() {
|
||||
// Test: sum = sum + i (UpdateExpr::BinOp with Variable RHS)
|
||||
let carrier = test_carrier("sum", 200);
|
||||
let update = UpdateExpr::BinOp {
|
||||
lhs: "sum".to_string(),
|
||||
op: BinOpKind::Add,
|
||||
rhs: UpdateRhs::Variable("i".to_string()),
|
||||
};
|
||||
let env = test_env();
|
||||
|
||||
let mut value_counter = 70u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let result_id = result.unwrap();
|
||||
|
||||
// Should generate 1 instruction: BinOp(Add, sum, i)
|
||||
assert_eq!(instructions.len(), 1);
|
||||
|
||||
// Instruction: BinOp(Add, sum, i)
|
||||
match &instructions[0] {
|
||||
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
|
||||
assert_eq!(*dst, ValueId(70));
|
||||
assert_eq!(*op, BinOpKind::Add);
|
||||
assert_eq!(*lhs, ValueId(20)); // sum from env
|
||||
assert_eq!(*rhs, ValueId(30)); // i from env
|
||||
}
|
||||
_ => panic!("Expected BinOp instruction"),
|
||||
}
|
||||
|
||||
assert_eq!(result_id, ValueId(70));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_update_carrier_not_in_env() {
|
||||
// Test error case: carrier not found in env
|
||||
let carrier = test_carrier("unknown", 300);
|
||||
let update = UpdateExpr::Const(1);
|
||||
let env = test_env(); // doesn't have "unknown"
|
||||
|
||||
let mut value_counter = 80u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Carrier 'unknown' not found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_update_lhs_mismatch() {
|
||||
// Test error case: LHS doesn't match carrier name
|
||||
let carrier = test_carrier("count", 100);
|
||||
let update = UpdateExpr::BinOp {
|
||||
lhs: "sum".to_string(), // Wrong! Should be "count"
|
||||
op: BinOpKind::Add,
|
||||
rhs: UpdateRhs::Const(1),
|
||||
};
|
||||
let env = test_env();
|
||||
|
||||
let mut value_counter = 90u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("doesn't match carrier"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_update_rhs_variable_not_found() {
|
||||
// Test error case: RHS variable not in env
|
||||
let carrier = test_carrier("sum", 200);
|
||||
let update = UpdateExpr::BinOp {
|
||||
lhs: "sum".to_string(),
|
||||
op: BinOpKind::Add,
|
||||
rhs: UpdateRhs::Variable("unknown_var".to_string()),
|
||||
};
|
||||
let env = test_env();
|
||||
|
||||
let mut value_counter = 100u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Update RHS variable 'unknown_var' not found"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
|
||||
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 carrier_update_emitter; // Phase 179: Carrier update instruction emission
|
||||
pub mod common;
|
||||
pub mod condition_env; // Phase 171-fix: Condition expression environment
|
||||
pub mod condition_lowerer; // Phase 171-fix: Core condition lowering logic
|
||||
|
||||
Reference in New Issue
Block a user