From 9bc945472692ad561e6abb5d7f0ccc6e67740b11 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Mon, 29 Dec 2025 05:27:14 +0900 Subject: [PATCH] phase29af(p1): add boundary hygiene contract checks --- docs/development/current/main/10-Now.md | 8 ++- docs/development/current/main/30-Backlog.md | 2 +- .../current/main/phases/phase-29af/README.md | 15 +++- .../contract_checks/boundary_creation.rs | 4 ++ .../merge/contract_checks/boundary_hygiene.rs | 72 +++++++++++++++++++ .../joinir/merge/contract_checks/mod.rs | 2 + .../patterns/pattern2_inputs_facts_box.rs | 32 ++++++--- 7 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_hygiene.rs diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index ee059f4f..f069eb2a 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -1,6 +1,12 @@ # Self Current Task — Now (main) -## Current Focus: Phase 29af (Pattern2 Boundary Hygiene) +## Current Focus: Phase 29af(Boundary Hygiene SSOT 固定) + +**2025-12-29: Phase 29af P1 完了** ✅ +- 目的: boundary hygiene を merge 入口(`contract_checks`)へ集約して再発検知を SSOT 化(仕様不変) +- 実装: `src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_hygiene.rs`(strict/dev のみ) +- 配線: `src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_creation.rs` +- 検証: `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-29: Phase 29af P0 完了** ✅ - 目的: Pattern2 の boundary 情報の歪みを SSOT 化し、exit/header/latch の責務境界を固定(仕様不変) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 0dbdbf45..de254f5c 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -8,7 +8,7 @@ Related: ## 直近(JoinIR/selfhost) -- **Phase 29af P0(in progress): Pattern2 Boundary Hygiene(SSOT固定)** +- **Phase 29af P1(✅ COMPLETE): Pattern2 Boundary Hygiene(contract_checks 集約)** - 入口: `docs/development/current/main/phases/phase-29af/README.md` - **Phase 29ae P1(✅ COMPLETE): JoinIR Regression Pack (SSOT固定)** diff --git a/docs/development/current/main/phases/phase-29af/README.md b/docs/development/current/main/phases/phase-29af/README.md index 1ede5336..f451747d 100644 --- a/docs/development/current/main/phases/phase-29af/README.md +++ b/docs/development/current/main/phases/phase-29af/README.md @@ -2,6 +2,11 @@ Goal: Pattern2 の boundary 情報の歪みを SSOT で整理し、将来の回帰を防ぐ(仕様不変)。 +## Status + +- P0: ✅ COMPLETE(commit: `19f2c6b7f`) +- P1: ✅ COMPLETE(merge `contract_checks` への集約) + ## Boundary Contract (SSOT) - Header PHI 対象: @@ -20,8 +25,14 @@ Goal: Pattern2 の boundary 情報の歪みを SSOT で整理し、将来の回 - 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` + +## P1: Contract Checks (merge 入口) + +P0 で確定した boundary hygiene を、merge 入口の `contract_checks` に集約する(仕様不変)。 + +- 実装: `src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_hygiene.rs` +- 配線: `src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_creation.rs` +- 実行条件: `joinir_strict` または `joinir_dev` のみ Fail-Fast ## Verification diff --git a/src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_creation.rs b/src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_creation.rs index 06a92a7a..f1e29af9 100644 --- a/src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_creation.rs +++ b/src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_creation.rs @@ -1,4 +1,5 @@ use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; +use super::verify_boundary_hygiene; /// Phase 286 P1: Boundary contract validation (B1/C2 invariants) /// @@ -31,6 +32,9 @@ pub(in crate::mir::builder::control_flow::joinir) fn verify_boundary_contract_at eprintln!(" (This should help identify which pattern is causing the issue)"); } + // Phase 29af P1: Boundary hygiene checks (strict/dev only) + verify_boundary_hygiene(boundary)?; + // B1: join_inputs in Param region (100-999) for (i, join_id) in boundary.join_inputs.iter().enumerate() { if !(PARAM_MIN..=PARAM_MAX).contains(&join_id.0) { diff --git a/src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_hygiene.rs b/src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_hygiene.rs new file mode 100644 index 00000000..1b4969ba --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/contract_checks/boundary_hygiene.rs @@ -0,0 +1,72 @@ +use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; +use crate::mir::ValueId; +use std::collections::BTreeSet; + +/// Phase 29af P1: Boundary hygiene contract checks (strict/dev only). +pub(in crate::mir::builder::control_flow::joinir::merge) fn verify_boundary_hygiene( + boundary: &JoinInlineBoundary, +) -> Result<(), String> { + use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole}; + use crate::mir::join_ir::lowering::error_tags; + + let strict = crate::config::env::joinir_strict_enabled() + || crate::config::env::joinir_dev_enabled(); + if !strict { + return Ok(()); + } + + let mut exit_names = BTreeSet::new(); + for binding in &boundary.exit_bindings { + if binding.role != CarrierRole::LoopState { + return Err(error_tags::freeze_with_hint( + "phase29af/boundary_hygiene/exit_binding_role", + &format!( + "exit_binding '{}' role {:?} is not LoopState", + binding.carrier_name, binding.role + ), + "exclude ConditionOnly carriers from exit_bindings", + )); + } + if !exit_names.insert(binding.carrier_name.clone()) { + return Err(error_tags::freeze_with_hint( + "phase29af/boundary_hygiene/exit_binding_duplicate", + &format!("duplicate exit_binding carrier '{}'", binding.carrier_name), + "deduplicate exit_bindings by carrier_name", + )); + } + } + + if let Some(carrier_info) = &boundary.carrier_info { + let mut carrier_names = BTreeSet::new(); + carrier_names.insert(carrier_info.loop_var_name.clone()); + + for carrier in &carrier_info.carriers { + carrier_names.insert(carrier.name.clone()); + if carrier.init == CarrierInit::FromHost && carrier.host_id == ValueId(0) { + return Err(error_tags::freeze_with_hint( + "phase29af/boundary_hygiene/fromhost_host_id", + &format!( + "carrier '{}' has FromHost init with host_id=ValueId(0)", + carrier.name + ), + "assign a valid host ValueId for FromHost carriers", + )); + } + } + + for binding in &boundary.exit_bindings { + if !carrier_names.contains(&binding.carrier_name) { + return Err(error_tags::freeze_with_hint( + "phase29af/boundary_hygiene/exit_binding_unknown", + &format!( + "exit_binding '{}' not found in carrier_info", + binding.carrier_name + ), + "ensure exit_bindings carriers are declared in carrier_info", + )); + } + } + } + + Ok(()) +} diff --git a/src/mir/builder/control_flow/joinir/merge/contract_checks/mod.rs b/src/mir/builder/control_flow/joinir/merge/contract_checks/mod.rs index 3b1a2c59..950883db 100644 --- a/src/mir/builder/control_flow/joinir/merge/contract_checks/mod.rs +++ b/src/mir/builder/control_flow/joinir/merge/contract_checks/mod.rs @@ -14,6 +14,7 @@ mod terminator_targets; mod exit_bindings; mod carrier_inputs; mod boundary_creation; +mod boundary_hygiene; mod entry_params; mod entry_like_policy; @@ -21,6 +22,7 @@ mod entry_like_policy; 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 boundary_hygiene::verify_boundary_hygiene; 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; diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_inputs_facts_box.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_inputs_facts_box.rs index e31398c6..58787e87 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_inputs_facts_box.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_inputs_facts_box.rs @@ -332,17 +332,29 @@ impl Pattern2InputsFactsBox { } } - if verbose { - log.log( - "phase100_p2", - format!("Promoting '{}' to mutable LoopState carrier", spec.target_name), - ); - } + if spec.target_name == loop_var_name { + if verbose { + log.log( + "phase100_p2", + format!( + "Skip promoting loop var '{}' to carrier (already loop_var)", + spec.target_name + ), + ); + } + } else { + if verbose { + log.log( + "phase100_p2", + format!("Promoting '{}' to mutable LoopState carrier", spec.target_name), + ); + } - use crate::mir::join_ir::lowering::carrier_info::CarrierVar; - carrier_info - .carriers - .push(CarrierVar::new(spec.target_name.clone(), *target_id)); + use crate::mir::join_ir::lowering::carrier_info::CarrierVar; + carrier_info + .carriers + .push(CarrierVar::new(spec.target_name.clone(), *target_id)); + } } else if verbose { log.log("phase100_p2", "No mutable accumulator pattern detected"); }