phase29af(p0): pattern2 boundary hygiene ssot
This commit is contained in:
@ -17,7 +17,7 @@ Pattern6(1-level nested loop)の JoinIR→bridge→merge 経路で発生し
|
||||
|
||||
- SSOT(mergeの契約):
|
||||
- latch_incoming を記録してよいのは `TailCallKind::BackEdge` のみ(LoopEntry は上書き禁止)
|
||||
- `LoopEntry` 判定は “JoinIR main の entry block のみ” を entry-like とする(誤分類で latch が壊れるのを防止)
|
||||
- entry-like 判定は “JoinIR MAIN のみ” を対象にする(block-id 推測はしない): `src/mir/builder/control_flow/joinir/merge/contract_checks/entry_like_policy.rs`
|
||||
- latch 二重設定は `debug_assert!` で fail-fast(回帰検知)
|
||||
- 変更箇所:
|
||||
- `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs`
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
# Self Current Task — Now (main)
|
||||
|
||||
## Current Focus: Phase 29ae (JoinIR Regression Pack)
|
||||
## Current Focus: Phase 29af (Pattern2 Boundary Hygiene)
|
||||
|
||||
**2025-12-29: Phase 29af P0 完了** ✅
|
||||
- 目的: Pattern2 の boundary 情報の歪みを SSOT 化し、exit/header/latch の責務境界を固定(仕様不変)
|
||||
- 入口: `docs/development/current/main/phases/phase-29af/README.md`
|
||||
- 変更: exit_bindings は LoopState のみ(ConditionOnly/LoopLocalZero は carrier_info→header PHI)
|
||||
- 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/run.sh --profile integration --filter "phase29ab_pattern2_"` / `./tools/smokes/v2/run.sh --profile integration --filter "phase1883_"` PASS
|
||||
|
||||
**2025-12-28: Phase 29ae P1 完了** ✅
|
||||
- 目的: Merge/Phi Contract SSOT + 回帰パック完全固定
|
||||
|
||||
@ -8,6 +8,9 @@ Related:
|
||||
|
||||
## 直近(JoinIR/selfhost)
|
||||
|
||||
- **Phase 29af P0(in progress): Pattern2 Boundary Hygiene(SSOT固定)**
|
||||
- 入口: `docs/development/current/main/phases/phase-29af/README.md`
|
||||
|
||||
- **Phase 29ae P1(✅ COMPLETE): JoinIR Regression Pack (SSOT固定)**
|
||||
- 入口: `docs/development/current/main/phases/phase-29ae/README.md`
|
||||
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
# Phase 29af P1: Boundary Hygiene Contract Checks — Instructions
|
||||
|
||||
Status: Ready for execution
|
||||
Scope: JoinIR merge の contract_checks に boundary hygiene を集約(仕様不変)
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 29af P0 で確定した boundary hygiene(Pattern2)を、merge の `contract_checks` 側でも検証できる形に収束する。
|
||||
|
||||
- upstream(Pattern2 lowerer 側)だけに Fail-Fast が散らばらないようにする
|
||||
- future pattern / future refactor で boundary 構築の歪みが再発したときに、merge 入口で検知できるようにする
|
||||
|
||||
## Non-goals
|
||||
|
||||
- 挙動変更(release ビルド既定挙動の変更)
|
||||
- env var の追加
|
||||
- fixture/smoke の増加(必要性が明確になった場合のみ)
|
||||
|
||||
## Contract (SSOT)
|
||||
|
||||
SSOT: `docs/development/current/main/phases/phase-29af/README.md`
|
||||
|
||||
- Exit reconnection (`boundary.exit_bindings`) は LoopState のみ
|
||||
- Header PHI の対象は `boundary.carrier_info` の carriers(LoopState + ConditionOnly + LoopLocalZero)
|
||||
- `CarrierInit::FromHost` の `host_id=0` は契約違反(Fail-Fast)
|
||||
- `exit_bindings.carrier_name` の重複は禁止(Fail-Fast)
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1) **contract_checks に boundary hygiene チェックを追加**
|
||||
- 追加ファイル案: `src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_hygiene.rs`
|
||||
- 関数案:
|
||||
- `verify_boundary_hygiene(boundary: &JoinInlineBoundary) -> Result<(), String>`
|
||||
- 検証項目:
|
||||
- `exit_bindings` の `role` が全て LoopState であること
|
||||
- `exit_bindings` の `carrier_name` が重複しないこと
|
||||
- `carrier_info` がある場合:
|
||||
- `CarrierInit::FromHost` の carrier は `host_id != ValueId(0)` であること
|
||||
- `exit_bindings` の carrier が `carrier_info` と整合すること(最低限: 同名が存在すること)
|
||||
|
||||
2) **merge 入口のチェックに配線**
|
||||
- `src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_creation.rs`
|
||||
- boundary の概要ログの直後(Fail-Fast 位置)で `verify_boundary_hygiene()` を呼ぶ
|
||||
- `src/mir/builder/control_flow/joinir/merge/contract_checks/mod.rs`
|
||||
- module 宣言と re-export を追加
|
||||
|
||||
3) **docs を更新(入口と責務の明文化)**
|
||||
- `docs/development/current/main/phases/phase-29af/README.md`
|
||||
- P1: contract_checks に集約した旨を追記
|
||||
- `docs/development/current/main/10-Now.md`
|
||||
- Phase 29af P1 の進捗(Started/Complete)に反映
|
||||
- `docs/development/current/main/30-Backlog.md`
|
||||
- Phase 29af のステータスを更新
|
||||
|
||||
## Verification
|
||||
|
||||
- `cargo build --release`
|
||||
- `./tools/smokes/v2/run.sh --profile quick`
|
||||
- `./tools/smokes/v2/run.sh --profile integration --filter "phase29ab_pattern2_"`
|
||||
- `./tools/smokes/v2/run.sh --profile integration --filter "phase1883_"`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- 既存 smokes(quick / phase29ab_pattern2_ / phase1883_)が PASS のまま
|
||||
- release ビルドでの挙動は不変(Fail-Fast は debug/strict のみ)
|
||||
- boundary hygiene の契約が “merge 入口の SSOT(contract_checks)” でも検証される
|
||||
35
docs/development/current/main/phases/phase-29af/README.md
Normal file
35
docs/development/current/main/phases/phase-29af/README.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Phase 29af P0: Pattern2 Boundary Hygiene (SSOT)
|
||||
|
||||
Goal: Pattern2 の boundary 情報の歪みを SSOT で整理し、将来の回帰を防ぐ(仕様不変)。
|
||||
|
||||
## Boundary Contract (SSOT)
|
||||
|
||||
- Header PHI 対象:
|
||||
- `carrier_info` の carriers(LoopState + ConditionOnly + LoopLocalZero)
|
||||
- Exit reconnection 対象:
|
||||
- LoopState のみ(ConditionOnly は exit_bindings に入れない)
|
||||
- Host binding 対象:
|
||||
- `CarrierInit::FromHost` のみ(BoolConst / LoopLocalZero は host slot 不要)
|
||||
|
||||
## Fail-Fast Rules
|
||||
|
||||
- exit_bindings の `carrier_name` 重複は禁止(debug_assert)
|
||||
- `CarrierInit::FromHost` の `host_id=0` は Fail-Fast
|
||||
|
||||
## Entry Points
|
||||
|
||||
- boundary 構築: `src/mir/builder/control_flow/joinir/patterns/pattern2_steps/emit_joinir_step_box.rs`
|
||||
- header PHI 事前構築: `src/mir/builder/control_flow/joinir/merge/header_phi_prebuild.rs`
|
||||
- exit_bindings 収集: `src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs`
|
||||
- latch 記録: `src/mir/builder/control_flow/joinir/merge/rewriter/{tail_call_policy,latch_incoming_recorder}.rs`
|
||||
|
||||
## Verification
|
||||
|
||||
- `cargo build --release`
|
||||
- `./tools/smokes/v2/run.sh --profile quick`
|
||||
- `./tools/smokes/v2/run.sh --profile integration --filter "phase29ab_pattern2_"`
|
||||
- `./tools/smokes/v2/run.sh --profile integration --filter "phase1883_"`
|
||||
|
||||
## Notes
|
||||
|
||||
- Merge 側の Header PHI Entry/Latch contract は Phase 29ae で SSOT 化済み: `docs/development/current/main/phases/phase-29ae/README.md`
|
||||
@ -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)
|
||||
|
||||
@ -36,8 +36,8 @@ impl BoundaryInjector {
|
||||
///
|
||||
/// # Phase 33-20: All Carriers Header PHI Support
|
||||
///
|
||||
/// When `boundary.loop_var_name` is set, ALL carriers (loop var + other carriers
|
||||
/// from exit_bindings) are handled by header PHIs. We skip ALL join_inputs
|
||||
/// When `boundary.loop_var_name` is set, ALL carriers (loop var + carrier_info carriers)
|
||||
/// are handled by header PHIs. We skip ALL join_inputs
|
||||
/// Copy instructions to avoid overwriting the PHI results.
|
||||
///
|
||||
/// # Phase 177-3: PHI Collision Avoidance (Option B)
|
||||
@ -62,7 +62,7 @@ impl BoundaryInjector {
|
||||
) -> Result<BTreeMap<ValueId, ValueId>, String> {
|
||||
// Phase 222.5-E: HashMap → BTreeMap for determinism
|
||||
// Phase 33-20: When loop_var_name is set, ALL join_inputs are handled by header PHIs
|
||||
// This includes the loop variable AND all other carriers from exit_bindings.
|
||||
// This includes the loop variable AND all other carriers from carrier_info.
|
||||
// We skip ALL join_inputs Copy instructions, only condition_bindings remain.
|
||||
let skip_all_join_inputs = boundary.loop_var_name.is_some();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user