phase29af(p0): pattern2 boundary hygiene ssot
This commit is contained in:
@ -0,0 +1,12 @@
|
||||
//! Entry-like policy (SSOT)
|
||||
//!
|
||||
//! Contract:
|
||||
//! - "entry-like" は MAIN のみを対象にする(by-name 以外の推測は禁止)
|
||||
|
||||
use crate::mir::join_ir::lowering::canonical_names;
|
||||
|
||||
pub(in crate::mir::builder::control_flow::joinir::merge) fn is_entry_like_source(
|
||||
func_name: &str,
|
||||
) -> bool {
|
||||
func_name == canonical_names::MAIN
|
||||
}
|
||||
@ -15,11 +15,13 @@ mod exit_bindings;
|
||||
mod carrier_inputs;
|
||||
mod boundary_creation;
|
||||
mod entry_params;
|
||||
mod entry_like_policy;
|
||||
|
||||
// Re-export public functions
|
||||
pub(super) use terminator_targets::verify_all_terminator_targets_exist;
|
||||
pub(super) use exit_bindings::verify_exit_bindings_have_exit_phis;
|
||||
pub(super) use carrier_inputs::verify_carrier_inputs_complete;
|
||||
pub(super) use entry_like_policy::is_entry_like_source;
|
||||
pub(in crate::mir::builder::control_flow::joinir) use boundary_creation::verify_boundary_contract_at_creation;
|
||||
pub(in crate::mir::builder::control_flow::joinir) use entry_params::run_all_pipeline_checks;
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::lowering::carrier_info::ExitMeta;
|
||||
use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding;
|
||||
use crate::mir::ValueId; // Phase 228-8: For ConditionOnly placeholder
|
||||
|
||||
/// ExitMetaCollector: A Box that builds exit_bindings from ExitMeta
|
||||
///
|
||||
@ -59,17 +58,15 @@ impl ExitMetaCollector {
|
||||
/// 2. Create LoopExitBinding with carrier_name, join_exit_value, host_slot
|
||||
/// 3. Collect into Vec<LoopExitBinding>
|
||||
///
|
||||
/// # Phase 228-8: ConditionOnly carrier handling
|
||||
/// # Phase 29af: Boundary hygiene
|
||||
///
|
||||
/// ConditionOnly carriers are included in exit_bindings even if they're not
|
||||
/// in variable_ctx.variable_map, because they need latch incoming values for header PHI.
|
||||
/// The host_slot is set to ValueId(0) as a placeholder since ConditionOnly
|
||||
/// carriers don't participate in exit PHI.
|
||||
/// ConditionOnly / LoopLocalZero carriers do not participate in exit reconnection.
|
||||
/// They are excluded from exit_bindings and are handled by header PHIs via carrier_info.
|
||||
///
|
||||
/// # Skipped carriers
|
||||
///
|
||||
/// Carriers not found in variable_ctx.variable_map AND not in carrier_info are silently skipped.
|
||||
/// This is intentional: some carriers may not be relevant to the current pattern.
|
||||
/// - ConditionOnly / LoopLocalZero carriers are excluded from exit_bindings.
|
||||
/// - Other carriers not found in variable_ctx.variable_map are skipped (or strict-fail).
|
||||
///
|
||||
/// # Logging
|
||||
///
|
||||
@ -148,8 +145,6 @@ impl ExitMetaCollector {
|
||||
|
||||
bindings.push(binding);
|
||||
} else {
|
||||
// Phase 228-8: Check if this is a ConditionOnly carrier
|
||||
// Phase 247-EX: Also check if this is a FromHost carrier (e.g., digit_value)
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole};
|
||||
let carrier_meta = if let Some(ci) = carrier_info {
|
||||
ci.carriers
|
||||
@ -161,80 +156,34 @@ impl ExitMetaCollector {
|
||||
};
|
||||
|
||||
match carrier_meta {
|
||||
Some((CarrierRole::ConditionOnly, _)) => {
|
||||
// Phase 228-8: Include ConditionOnly carrier in exit_bindings
|
||||
// (needed for latch incoming, not for exit PHI)
|
||||
let binding = LoopExitBinding {
|
||||
carrier_name: carrier_name.clone(),
|
||||
join_exit_value: *join_exit_value,
|
||||
host_slot: ValueId(0), // Placeholder - not used for ConditionOnly
|
||||
role: CarrierRole::ConditionOnly,
|
||||
};
|
||||
|
||||
Some((CarrierRole::ConditionOnly, _))
|
||||
| Some((CarrierRole::LoopState, CarrierInit::LoopLocalZero)) => {
|
||||
if verbose {
|
||||
trace.emit_if(
|
||||
"exit-line",
|
||||
"collector",
|
||||
&format!(
|
||||
"collected ConditionOnly carrier '{}' JoinIR {:?} (not in variable_ctx.variable_map)",
|
||||
"skipping non-exit carrier '{}' JoinIR {:?} (ConditionOnly/LoopLocalZero)",
|
||||
carrier_name, join_exit_value
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
bindings.push(binding);
|
||||
}
|
||||
Some((CarrierRole::LoopState, CarrierInit::FromHost)) => {
|
||||
// Phase 247-EX: Include FromHost carrier in exit_bindings
|
||||
// (needed for latch incoming, not for exit PHI or variable_ctx.variable_map)
|
||||
let binding = LoopExitBinding {
|
||||
carrier_name: carrier_name.clone(),
|
||||
join_exit_value: *join_exit_value,
|
||||
host_slot: ValueId(0), // Placeholder - not used for FromHost
|
||||
role: CarrierRole::LoopState,
|
||||
};
|
||||
|
||||
if verbose {
|
||||
trace.emit_if(
|
||||
"exit-line",
|
||||
"collector",
|
||||
&format!(
|
||||
"collected FromHost carrier '{}' JoinIR {:?} (not in variable_ctx.variable_map)",
|
||||
carrier_name, join_exit_value
|
||||
),
|
||||
true,
|
||||
);
|
||||
let msg = format!(
|
||||
"[joinir/exit-line] carrier '{}' missing host slot for FromHost",
|
||||
carrier_name
|
||||
);
|
||||
if strict {
|
||||
panic!("{}", msg);
|
||||
} else if verbose {
|
||||
trace.emit_if("exit-line", "collector", &msg, true);
|
||||
}
|
||||
|
||||
bindings.push(binding);
|
||||
}
|
||||
Some((CarrierRole::LoopState, CarrierInit::LoopLocalZero)) => {
|
||||
// Loop-local derived carrier: include binding with placeholder host slot
|
||||
let binding = LoopExitBinding {
|
||||
carrier_name: carrier_name.clone(),
|
||||
join_exit_value: *join_exit_value,
|
||||
host_slot: ValueId(0), // No host slot; used for latch/PHI only
|
||||
role: CarrierRole::LoopState,
|
||||
};
|
||||
|
||||
if verbose {
|
||||
trace.emit_if(
|
||||
"exit-line",
|
||||
"collector",
|
||||
&format!(
|
||||
"collected loop-local carrier '{}' JoinIR {:?} (no host slot)",
|
||||
carrier_name, join_exit_value
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
bindings.push(binding);
|
||||
}
|
||||
_ => {
|
||||
let msg = format!(
|
||||
"[joinir/exit-line] carrier '{}' not in variable_ctx.variable_map and not ConditionOnly/FromHost (skip)",
|
||||
"[joinir/exit-line] carrier '{}' not in variable_ctx.variable_map (skip)",
|
||||
carrier_name
|
||||
);
|
||||
if strict {
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
use super::{entry_selector, LoopHeaderPhiBuilder, LoopHeaderPhiInfo};
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
|
||||
use crate::mir::{BasicBlockId, MirModule, ValueId};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
/// Pre-build loop header PHIs and reserve ValueIds
|
||||
///
|
||||
@ -81,12 +81,6 @@ pub(super) fn prebuild_header_phis(
|
||||
};
|
||||
|
||||
// Extract carriers with their initialization strategy
|
||||
let exit_carrier_names: BTreeSet<&str> = boundary
|
||||
.exit_bindings
|
||||
.iter()
|
||||
.map(|b| b.carrier_name.as_str())
|
||||
.collect();
|
||||
|
||||
let other_carriers: Vec<(
|
||||
String,
|
||||
ValueId,
|
||||
@ -97,7 +91,6 @@ pub(super) fn prebuild_header_phis(
|
||||
.carriers
|
||||
.iter()
|
||||
.filter(|c| c.name != *loop_var_name)
|
||||
.filter(|c| exit_carrier_names.contains(c.name.as_str()))
|
||||
.map(|c| (c.name.clone(), c.host_id, c.init, c.role))
|
||||
.collect()
|
||||
} else {
|
||||
|
||||
@ -62,16 +62,28 @@ pub(in crate::mir::builder::control_flow::joinir::merge) fn record_if_backedge(
|
||||
|
||||
// Other carriers (excluding loop_var)
|
||||
let mut carrier_arg_idx = if boundary.loop_var_name.is_some() { 1 } else { 0 };
|
||||
for binding in boundary.exit_bindings.iter() {
|
||||
if let Some(ref loop_var) = boundary.loop_var_name {
|
||||
if &binding.carrier_name == loop_var {
|
||||
if let Some(ref carrier_info) = boundary.carrier_info {
|
||||
for carrier in carrier_info.carriers.iter() {
|
||||
if boundary.loop_var_name.as_deref() == Some(carrier.name.as_str()) {
|
||||
continue;
|
||||
}
|
||||
if let Some(&latch_value) = args.get(carrier_arg_idx) {
|
||||
loop_header_phi_info.set_latch_incoming(&carrier.name, new_block_id, latch_value);
|
||||
carrier_arg_idx += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for binding in boundary.exit_bindings.iter() {
|
||||
if let Some(ref loop_var) = boundary.loop_var_name {
|
||||
if &binding.carrier_name == loop_var {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(&latch_value) = args.get(carrier_arg_idx) {
|
||||
loop_header_phi_info.set_latch_incoming(&binding.carrier_name, new_block_id, latch_value);
|
||||
carrier_arg_idx += 1;
|
||||
if let Some(&latch_value) = args.get(carrier_arg_idx) {
|
||||
loop_header_phi_info.set_latch_incoming(&binding.carrier_name, new_block_id, latch_value);
|
||||
carrier_arg_idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,20 +4,20 @@
|
||||
//! - Identify entry-like source blocks (LoopEntry vs BackEdge)
|
||||
//! - Record latch incoming in one place for BackEdge
|
||||
|
||||
use crate::mir::builder::control_flow::joinir::merge::contract_checks::is_entry_like_source;
|
||||
use crate::mir::builder::control_flow::joinir::merge::loop_header_phi_info::LoopHeaderPhiInfo;
|
||||
use crate::mir::builder::control_flow::joinir::merge::rewriter::latch_incoming_recorder;
|
||||
use crate::mir::builder::control_flow::joinir::merge::tail_call_classifier::TailCallKind;
|
||||
use crate::mir::join_ir::lowering::canonical_names;
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
|
||||
use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId};
|
||||
|
||||
/// Entry-like source is only MAIN's entry block.
|
||||
/// Entry-like source is MAIN's blocks.
|
||||
pub(super) fn is_loop_entry_source(
|
||||
func_name: &str,
|
||||
old_block_id: BasicBlockId,
|
||||
func_entry_block: BasicBlockId,
|
||||
_old_block_id: BasicBlockId,
|
||||
_func_entry_block: BasicBlockId,
|
||||
) -> bool {
|
||||
func_name == canonical_names::MAIN && old_block_id == func_entry_block
|
||||
is_entry_like_source(func_name)
|
||||
}
|
||||
|
||||
/// Record latch incoming for BackEdge using copies in the rewritten block.
|
||||
@ -38,7 +38,31 @@ pub(super) fn record_latch_incoming_if_backedge(
|
||||
let mut latch_args: Vec<ValueId> = Vec::new();
|
||||
let mut loop_var_updated = false;
|
||||
|
||||
for (idx, carrier_name) in loop_header_phi_info.carrier_order.iter().enumerate() {
|
||||
let mut ordered_carriers: Vec<&str> = Vec::new();
|
||||
let mut other_phi_dsts: std::collections::BTreeSet<ValueId> =
|
||||
std::collections::BTreeSet::new();
|
||||
if let Some(loop_var) = boundary.loop_var_name.as_deref() {
|
||||
ordered_carriers.push(loop_var);
|
||||
for (name, entry) in loop_header_phi_info.carrier_phis.iter() {
|
||||
if name.as_str() != loop_var {
|
||||
other_phi_dsts.insert(entry.phi_dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(ref carrier_info) = boundary.carrier_info {
|
||||
for carrier in carrier_info.carriers.iter() {
|
||||
ordered_carriers.push(carrier.name.as_str());
|
||||
}
|
||||
} else {
|
||||
for binding in boundary.exit_bindings.iter() {
|
||||
if boundary.loop_var_name.as_deref() == Some(binding.carrier_name.as_str()) {
|
||||
continue;
|
||||
}
|
||||
ordered_carriers.push(binding.carrier_name.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
for (idx, carrier_name) in ordered_carriers.iter().enumerate() {
|
||||
let phi_dst = match loop_header_phi_info.get_carrier_phi(carrier_name) {
|
||||
Some(dst) => dst,
|
||||
None => continue,
|
||||
@ -50,8 +74,8 @@ pub(super) fn record_latch_incoming_if_backedge(
|
||||
if *dst == phi_dst {
|
||||
chosen = Some(*src);
|
||||
if *src != *dst {
|
||||
if boundary.loop_var_name.as_deref() == Some(carrier_name) {
|
||||
loop_var_updated = true;
|
||||
if boundary.loop_var_name.as_deref() == Some(*carrier_name) {
|
||||
loop_var_updated = !other_phi_dsts.contains(src);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -62,7 +86,10 @@ pub(super) fn record_latch_incoming_if_backedge(
|
||||
if let Some(val) = chosen {
|
||||
latch_args.push(val);
|
||||
} else if let Some(arg) = args.get(idx) {
|
||||
if boundary.loop_var_name.as_deref() == Some(carrier_name) && *arg != phi_dst {
|
||||
if boundary.loop_var_name.as_deref() == Some(*carrier_name)
|
||||
&& *arg != phi_dst
|
||||
&& !other_phi_dsts.contains(arg)
|
||||
{
|
||||
loop_var_updated = true;
|
||||
}
|
||||
latch_args.push(*arg);
|
||||
|
||||
@ -27,6 +27,14 @@
|
||||
- `CarrierInit::BoolConst(_)` / `CarrierInit::LoopLocalZero` -> host binding is skipped
|
||||
- ConditionOnly carriers must not use `FromHost`
|
||||
|
||||
## Boundary hygiene (Phase 29af)
|
||||
- Header PHI 対象: `carrier_info` の carriers(LoopState + ConditionOnly + LoopLocalZero)
|
||||
- Exit reconnection 対象: LoopState のみ(ConditionOnly は exit_bindings に入れない)
|
||||
- Host binding 対象: `CarrierInit::FromHost` のみ(BoolConst / LoopLocalZero は host slot 不要)
|
||||
- Fail-Fast: exit_bindings の `carrier_name` 重複は禁止(debug_assert)
|
||||
- Fail-Fast: `CarrierInit::FromHost` が `host_id=0` の場合は Err
|
||||
- SSOT: `docs/development/current/main/phases/phase-29af/README.md`
|
||||
|
||||
## Out of scope
|
||||
- multiple breaks / continue / return in the loop body
|
||||
- reassigned LoopBodyLocal outside the derived-slot shape
|
||||
|
||||
@ -66,7 +66,25 @@ impl EmitJoinIRStepBox {
|
||||
|
||||
let exit_meta = &fragment_meta.exit_meta;
|
||||
use crate::mir::builder::control_flow::joinir::merge::exit_line::ExitMetaCollector;
|
||||
let exit_bindings = ExitMetaCollector::collect(builder, exit_meta, Some(&inputs.carrier_info), debug);
|
||||
let mut exit_bindings =
|
||||
ExitMetaCollector::collect(builder, exit_meta, Some(&inputs.carrier_info), debug);
|
||||
// Phase 29af P0: Exit reconnection targets LoopState only.
|
||||
exit_bindings.retain(|binding| {
|
||||
binding.role == crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState
|
||||
});
|
||||
// Phase 29af P0: Reject duplicate carrier_name in exit_bindings.
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
use std::collections::HashSet;
|
||||
let mut seen = HashSet::new();
|
||||
for binding in &exit_bindings {
|
||||
debug_assert!(
|
||||
seen.insert(&binding.carrier_name),
|
||||
"Phase 29af Fail-Fast: duplicate exit_binding carrier '{}'",
|
||||
binding.carrier_name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 256.8.5: Use JoinModule.entry.params as SSOT (no hardcoded ValueIds)
|
||||
use super::super::common::get_entry_function;
|
||||
@ -78,7 +96,22 @@ impl EmitJoinIRStepBox {
|
||||
// Build host_input_values in same order (loop_var + carriers)
|
||||
let mut host_input_values = vec![inputs.loop_var_id];
|
||||
for carrier in inputs.carrier_info.carriers.iter() {
|
||||
host_input_values.push(carrier.host_id);
|
||||
use super::super::common::{decide_carrier_binding_policy, CarrierBindingPolicy};
|
||||
match decide_carrier_binding_policy(carrier) {
|
||||
CarrierBindingPolicy::BindFromHost => {
|
||||
if carrier.host_id == crate::mir::ValueId(0) {
|
||||
return Err(format!(
|
||||
"[emit_joinir] Phase 29af Fail-Fast: FromHost carrier '{}' has host_id=0",
|
||||
carrier.name
|
||||
));
|
||||
}
|
||||
host_input_values.push(carrier.host_id);
|
||||
}
|
||||
CarrierBindingPolicy::SkipBinding => {
|
||||
// Placeholder: SkipBinding does not require a host slot.
|
||||
host_input_values.push(crate::mir::ValueId(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify count consistency (fail-fast)
|
||||
|
||||
Reference in New Issue
Block a user