feat(joinir): Phase 228 - CarrierInit for ConditionOnly header PHI initialization

- Add CarrierInit enum (FromHost/BoolConst) for explicit initialization policy
- LoopHeaderPhiBuilder generates Const instruction for BoolConst carriers
- merge/mod.rs uses carrier_info to include ALL carriers in header PHI
- Pattern 2/4 pass carrier_info to boundary builder
- ConditionOnly carriers (is_digit_pos) now included in header PHI

Remaining: latch incoming for ConditionOnly carriers (Phase 228-8)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-10 21:10:28 +09:00
parent 478cc0012e
commit 192620f842
17 changed files with 174 additions and 15 deletions

View File

@ -51,13 +51,19 @@ impl LoopHeaderPhiBuilder {
///
/// LoopHeaderPhiInfo with allocated PHI dsts.
/// Note: latch_incoming is not yet set - that happens in instruction_rewriter.
///
/// # Phase 228 Update
///
/// Added CarrierInit and CarrierRole to carrier tuples:
/// * `CarrierInit::FromHost` - Use host_id directly as PHI init value
/// * `CarrierInit::BoolConst(val)` - Generate explicit bool constant for ConditionOnly carriers
pub fn build(
builder: &mut crate::mir::builder::MirBuilder,
header_block: BasicBlockId,
entry_block: BasicBlockId,
loop_var_name: &str,
loop_var_init: ValueId,
carriers: &[(String, ValueId)], // (name, init_value) pairs
carriers: &[(String, ValueId, crate::mir::join_ir::lowering::carrier_info::CarrierInit, crate::mir::join_ir::lowering::carrier_info::CarrierRole)], // Phase 228: Added CarrierInit and CarrierRole
expr_result_is_loop_var: bool,
debug: bool,
) -> Result<LoopHeaderPhiInfo, String> {
@ -96,15 +102,35 @@ impl LoopHeaderPhiBuilder {
}
// Allocate PHIs for other carriers
for (name, init_value) in carriers {
for (name, host_id, init, role) in carriers {
// Phase 228-5: Generate explicit const for BoolConst, use host_id for FromHost
let init_value = match init {
crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost => *host_id,
crate::mir::join_ir::lowering::carrier_info::CarrierInit::BoolConst(val) => {
// Phase 228: Generate explicit bool constant for ConditionOnly carriers
let const_id = builder.next_value_id();
builder.emit_instruction(MirInstruction::Const {
dst: const_id,
value: crate::mir::types::ConstValue::Bool(*val),
});
if debug {
eprintln!(
"[cf_loop/joinir] Phase 228: Generated const {:?} = Bool({}) for ConditionOnly carrier '{}'",
const_id, val, name
);
}
const_id
}
};
let phi_dst = builder.next_value_id();
info.carrier_phis.insert(
name.clone(),
CarrierPhiEntry {
phi_dst,
entry_incoming: (entry_block, *init_value),
entry_incoming: (entry_block, init_value),
latch_incoming: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, // Phase 227: Default to LoopState (will be updated later if needed)
role: *role, // Phase 228: Use role from carrier_info
},
);
// Phase 177-STRUCT-2: Record insertion order
@ -112,8 +138,8 @@ impl LoopHeaderPhiBuilder {
if debug {
eprintln!(
"[cf_loop/joinir] Carrier '{}' PHI: {:?} = phi [(from {:?}, {:?}), (latch TBD)]",
name, phi_dst, entry_block, init_value
"[cf_loop/joinir] Carrier '{}' PHI: {:?} = phi [(from {:?}, {:?}), (latch TBD)], role={:?}",
name, phi_dst, entry_block, init_value, role
);
}
}

View File

@ -141,12 +141,23 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
"Phase 201-A: No host_inputs in boundary for loop_var_init"
)?;
// Extract other carriers from exit_bindings
let other_carriers: Vec<(String, ValueId)> = boundary.exit_bindings
.iter()
.filter(|b| b.carrier_name != *loop_var_name)
.map(|b| (b.carrier_name.clone(), b.host_slot))
.collect();
// Phase 228-4: Extract carriers with their initialization strategy
let other_carriers: Vec<(String, ValueId, crate::mir::join_ir::lowering::carrier_info::CarrierInit, crate::mir::join_ir::lowering::carrier_info::CarrierRole)> =
if let Some(ref carrier_info) = boundary.carrier_info {
// Use carrier_info if available (Phase 228)
carrier_info.carriers
.iter()
.filter(|c| c.name != *loop_var_name)
.map(|c| (c.name.clone(), c.host_id, c.init, c.role))
.collect()
} else {
// Fallback: exit_bindings から取得(既存動作)
boundary.exit_bindings
.iter()
.filter(|b| b.carrier_name != *loop_var_name)
.map(|b| (b.carrier_name.clone(), b.host_slot, crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState))
.collect()
};
if debug {
eprintln!(
@ -156,7 +167,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
eprintln!(
"[cf_loop/joinir] loop_var_init={:?}, carriers={:?}",
loop_var_init,
other_carriers.iter().map(|(n, _)| n.as_str()).collect::<Vec<_>>()
other_carriers.iter().map(|(n, _, _, _)| n.as_str()).collect::<Vec<_>>()
);
}

View File

@ -163,6 +163,7 @@ impl CommonPatternInitializer {
host_id: ValueId(0), // Dummy
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, // Phase 227: Default
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228: Default
})
} else {
None

View File

@ -143,6 +143,7 @@ mod tests {
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}],
);
@ -183,12 +184,14 @@ mod tests {
host_id: ValueId(11),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
},
CarrierVar {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
},
],
);
@ -232,6 +235,7 @@ mod tests {
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}],
);
@ -261,6 +265,7 @@ mod tests {
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}],
);
@ -290,6 +295,7 @@ mod tests {
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}],
);
@ -322,6 +328,7 @@ mod tests {
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}],
);
@ -352,6 +359,7 @@ mod tests {
condition_bindings: vec![], // Phase 171-fix: Add missing field
expr_result: None, // Phase 33-14: Add missing field
loop_var_name: None, // Phase 33-16: Add missing field
carrier_info: None, // Phase 228: Add missing field
};
builder.apply_to_boundary(&mut boundary)

View File

@ -108,6 +108,7 @@ mod tests {
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}],
);
@ -134,6 +135,7 @@ mod tests {
condition_bindings: vec![], // Phase 171-fix: Add missing field
expr_result: None, // Phase 33-14: Add missing field
loop_var_name: None, // Phase 33-16: Add missing field
carrier_info: None, // Phase 228: Add missing field
};
apply_exit_bindings_to_boundary(&carrier_info, &exit_meta, &variable_map, &mut boundary)

View File

@ -92,6 +92,7 @@ mod tests {
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}],
);
@ -130,12 +131,14 @@ mod tests {
host_id: ValueId(11),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
},
CarrierVar {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
},
],
);

View File

@ -70,6 +70,7 @@ mod tests {
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}],
);
@ -90,12 +91,14 @@ mod tests {
host_id: ValueId(11),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
},
CarrierVar {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
},
],
);
@ -119,6 +122,7 @@ mod tests {
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}],
);
@ -140,6 +144,7 @@ mod tests {
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}],
);
@ -161,6 +166,7 @@ mod tests {
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}],
);

View File

@ -578,6 +578,7 @@ impl MirBuilder {
.with_exit_bindings(exit_bindings.clone())
.with_expr_result(fragment_meta.expr_result) // Phase 33-14: Pass expr_result to merger
.with_loop_var_name(Some(loop_var_name.clone())) // Phase 33-16: For LoopHeaderPhiBuilder
.with_carrier_info(carrier_info.clone()) // Phase 228-6: Pass carrier_info for ConditionOnly header PHI init
.build();
// Phase 33-22: Use JoinIRConversionPipeline for unified conversion flow

View File

@ -277,18 +277,21 @@ mod tests {
host_id: ValueId(1),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
},
CarrierVar {
name: "sum".to_string(),
host_id: ValueId(2),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
},
CarrierVar {
name: "M".to_string(),
host_id: ValueId(3),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
},
],
trim_helper: None,

View File

@ -378,6 +378,7 @@ impl MirBuilder {
.with_inputs(join_inputs, host_inputs) // Dynamic carrier count
.with_exit_bindings(exit_bindings)
.with_loop_var_name(Some(loop_var_name.clone())) // Phase 33-19: Enable exit PHI collection
.with_carrier_info(carrier_info.clone()) // Phase 228-6: Pass carrier_info for ConditionOnly header PHI init
.build();
// Phase 33-22: Use JoinIRConversionPipeline for unified conversion flow

View File

@ -397,12 +397,14 @@ mod tests {
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
},
CarrierVar {
name: "count".to_string(),
host_id: ValueId(11),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
},
],
trim_helper: None,

View File

@ -54,6 +54,29 @@ pub enum CarrierRole {
ConditionOnly,
}
/// Phase 228: Initialization policy for carrier variables
///
/// When carriers participate in header PHI, they need an initial value.
/// Most carriers use their host_id value (FromHost), but promoted LoopBodyLocal
/// carriers need explicit bool initialization (BoolConst).
///
/// # Example
///
/// ```ignore
/// // Regular carrier (sum): Use host_id value
/// CarrierVar { name: "sum", host_id: ValueId(10), init: FromHost, .. }
///
/// // ConditionOnly carrier (is_digit_pos): Initialize with false
/// CarrierVar { name: "is_digit_pos", host_id: ValueId(15), init: BoolConst(false), .. }
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CarrierInit {
/// No explicit initialization (use host_id value)
FromHost,
/// Initialize with bool constant (for ConditionOnly carriers)
BoolConst(bool),
}
/// Phase 224-D: Alias for promoted LoopBodyLocal in condition expressions
///
/// When a LoopBodyLocal variable is promoted to a carrier, the original variable
@ -97,6 +120,11 @@ pub struct CarrierVar {
/// - `LoopState`: Value needed after loop (participates in exit PHI)
/// - `ConditionOnly`: Only used for loop condition (no exit PHI)
pub role: CarrierRole,
/// Phase 228: Initialization policy for header PHI
///
/// - `FromHost`: Use host_id value (regular carriers)
/// - `BoolConst(false)`: Initialize with false (promoted LoopBodyLocal carriers)
pub init: CarrierInit,
}
impl CarrierVar {
@ -110,6 +138,7 @@ impl CarrierVar {
host_id,
join_id: None,
role: CarrierRole::LoopState,
init: CarrierInit::FromHost, // Phase 228: Default to FromHost
}
}
@ -120,6 +149,23 @@ impl CarrierVar {
host_id,
join_id: None,
role,
init: CarrierInit::FromHost, // Phase 228: Default to FromHost
}
}
/// Phase 228: Create a CarrierVar with explicit role and init policy
pub fn with_role_and_init(
name: String,
host_id: ValueId,
role: CarrierRole,
init: CarrierInit,
) -> Self {
Self {
name,
host_id,
join_id: None,
role,
init,
}
}
}
@ -197,6 +243,7 @@ impl CarrierInfo {
host_id: id,
join_id: None, // Phase 177-STRUCT-1: Set by header PHI generation
role: CarrierRole::LoopState, // Phase 227: Default to LoopState
init: CarrierInit::FromHost, // Phase 228: Default to FromHost
})
.collect();
@ -257,6 +304,7 @@ impl CarrierInfo {
host_id,
join_id: None, // Phase 177-STRUCT-1: Set by header PHI generation
role: CarrierRole::LoopState, // Phase 227: Default to LoopState
init: CarrierInit::FromHost, // Phase 228: Default to FromHost
});
}
@ -576,6 +624,7 @@ mod tests {
host_id: ValueId(id),
join_id: None, // Phase 177-STRUCT-1
role: CarrierRole::LoopState, // Phase 227: Default to LoopState
init: CarrierInit::FromHost, // Phase 228: Default to FromHost
}
}

View File

@ -422,6 +422,7 @@ mod tests {
host_id: ValueId(host_id),
join_id: None, // Phase 177-STRUCT-1
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}
}

View File

@ -246,6 +246,16 @@ pub struct JoinInlineBoundary {
/// The name of the loop control variable (e.g., "i" in `loop(i < 3)`).
/// Used to track which PHI corresponds to the loop variable.
pub loop_var_name: Option<String>,
/// Phase 228: Carrier metadata (for header PHI generation)
///
/// Contains full carrier information including initialization policies.
/// This allows header PHI generation to handle ConditionOnly carriers
/// with explicit bool initialization.
///
/// - `Some(CarrierInfo)`: Full carrier metadata available
/// - `None`: Legacy path (derive carriers from exit_bindings)
pub carrier_info: Option<super::carrier_info::CarrierInfo>,
}
impl JoinInlineBoundary {
@ -272,6 +282,7 @@ impl JoinInlineBoundary {
condition_bindings: vec![], // Phase 171-fix: Default to empty
expr_result: None, // Phase 33-14: Default to carrier-only pattern
loop_var_name: None, // Phase 33-16
carrier_info: None, // Phase 228: Default to None
}
}
@ -314,6 +325,7 @@ impl JoinInlineBoundary {
condition_bindings: vec![], // Phase 171-fix: Default to empty
expr_result: None, // Phase 33-14
loop_var_name: None, // Phase 33-16
carrier_info: None, // Phase 228
}
}
@ -373,6 +385,7 @@ impl JoinInlineBoundary {
condition_bindings: vec![], // Phase 171-fix: Default to empty
expr_result: None, // Phase 33-14
loop_var_name: None, // Phase 33-16
carrier_info: None, // Phase 228
}
}
@ -418,6 +431,7 @@ impl JoinInlineBoundary {
condition_bindings: vec![], // Phase 171-fix: Will be populated by new constructor
expr_result: None, // Phase 33-14
loop_var_name: None, // Phase 33-16
carrier_info: None, // Phase 228
}
}
@ -467,6 +481,7 @@ impl JoinInlineBoundary {
condition_bindings: vec![], // Phase 171-fix: Will be populated by new constructor
expr_result: None, // Phase 33-14
loop_var_name: None, // Phase 33-16
carrier_info: None, // Phase 228
}
}
@ -523,6 +538,7 @@ impl JoinInlineBoundary {
condition_bindings,
expr_result: None, // Phase 33-14
loop_var_name: None, // Phase 33-16
carrier_info: None, // Phase 228
}
}
}

View File

@ -27,7 +27,7 @@
use crate::mir::ValueId;
use super::inline_boundary::{JoinInlineBoundary, LoopExitBinding};
use super::condition_to_joinir::ConditionBinding;
use super::carrier_info::CarrierRole;
use super::carrier_info::CarrierRole; // Phase 228: Restored for test code
/// Role of a parameter in JoinIR lowering (Phase 200-A)
///
@ -96,6 +96,7 @@ impl JoinInlineBoundaryBuilder {
condition_bindings: vec![],
expr_result: None,
loop_var_name: None,
carrier_info: None, // Phase 228: Initialize as None
}
}
}
@ -261,6 +262,29 @@ impl JoinInlineBoundaryBuilder {
.find(|b| b.name == name)
.map(|b| b.join_value)
}
/// Phase 228: Set carrier metadata
///
/// Provides full carrier information including initialization policies.
/// This allows header PHI generation to handle ConditionOnly carriers
/// with explicit bool initialization.
///
/// # Arguments
///
/// * `carrier_info` - Complete carrier metadata from pattern lowerer
///
/// # Example
///
/// ```ignore
/// let boundary = JoinInlineBoundaryBuilder::new()
/// .with_inputs(join_inputs, host_inputs)
/// .with_carrier_info(ctx.carrier_info.clone())
/// .build();
/// ```
pub fn with_carrier_info(mut self, carrier_info: super::carrier_info::CarrierInfo) -> Self {
self.boundary.carrier_info = Some(carrier_info);
self
}
}
impl Default for JoinInlineBoundaryBuilder {

View File

@ -299,6 +299,7 @@ mod tests {
host_id: crate::mir::ValueId(0),
join_id: None, // Phase 177-STRUCT-1
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}];
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
@ -357,6 +358,7 @@ mod tests {
host_id: crate::mir::ValueId(0),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}];
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
@ -416,6 +418,7 @@ mod tests {
host_id: crate::mir::ValueId(0),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}];
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
@ -475,6 +478,7 @@ mod tests {
host_id: crate::mir::ValueId(0),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
}];
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);

View File

@ -183,12 +183,13 @@ impl DigitPosPromoter {
// For DigitPos pattern, we add a NEW carrier (not replace loop_var)
let carrier_name = format!("is_{}", var_in_cond);
use crate::mir::join_ir::lowering::carrier_info::{CarrierVar, CarrierRole};
use crate::mir::join_ir::lowering::carrier_info::{CarrierVar, CarrierRole, CarrierInit};
let promoted_carrier = CarrierVar {
name: carrier_name.clone(),
host_id: ValueId(0), // Placeholder (will be remapped)
join_id: None, // Will be allocated later
role: CarrierRole::ConditionOnly, // Phase 227: DigitPos is condition-only
init: CarrierInit::BoolConst(false), // Phase 228: Initialize with false
};
// Create CarrierInfo with a dummy loop_var_name (will be ignored during merge)