phase29ao(p9): scaffold block params for valuejoin (strict/dev verify)

This commit is contained in:
2025-12-30 06:42:00 +09:00
parent 4c072c394b
commit ff2285cc2b
26 changed files with 329 additions and 21 deletions

View File

@ -18,8 +18,8 @@ Scope: Repo root の旧リンク互換。現行の入口は `docs/development/cu
**CorePlan migration 道筋 SSOT**
`docs/development/current/main/design/coreplan-migration-roadmap-ssot.md` が移行タスクの Done 判定の入口。
**Next implementation (Phase 29ao P9)**
`docs/development/current/main/phases/phase-29ao/P9-VALUEJOIN-MINIMAL-WIRE-INSTRUCTIONS.md`
**Next implementation (Phase 29ao P10, TBD)**
`docs/development/current/main/phases/phase-29ao/P9-VALUEJOIN-MINIMAL-WIRE-INSTRUCTIONS.md` の Next を参照
**2025-12-29: Phase 29am P0 COMPLETE (CorePlan If/Exit lowerer/verifier)**
CorePlan の If/Exit を lowerer/verifier で扱えるようにして、CorePlan 移行の土台を作った。

View File

@ -2,8 +2,8 @@
## Current Focus: Phase 29aoCorePlan composition
Next: Phase 29ao P9ValueJoin minimal wire
指示書: `docs/development/current/main/phases/phase-29ao/P9-VALUEJOIN-MINIMAL-WIRE-INSTRUCTIONS.md`
Next: Phase 29ao P10ValueJoin minimal wiring, TBD
指示書: `docs/development/current/main/phases/phase-29ao/P9-VALUEJOIN-MINIMAL-WIRE-INSTRUCTIONS.md` の Next を参照
運用ルール: integration filter で phase143_* は回さないJoinIR 回帰は phase29ae pack のみ)
運用ルール: phase286_pattern9_* は legacy pack (SKIP) を使う
移行道筋 SSOT: `docs/development/current/main/design/coreplan-migration-roadmap-ssot.md`
@ -53,6 +53,11 @@ Next: Phase 29ao P9ValueJoin minimal wire
- 変更: `src/mir/builder/control_flow/edgecfg/api/compose/seq.rs` / `src/mir/builder/control_flow/edgecfg/api/compose/if_.rs` / `src/mir/builder/control_flow/edgecfg/api/compose/cleanup.rs` / `docs/development/current/main/phases/phase-29ao/README.md` / `docs/development/current/main/10-Now.md` / `docs/development/current/main/30-Backlog.md` / `CURRENT_TASK.md`
- 検証: `cargo test --release -p nyash-rust` / `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh`
**2025-12-30: Phase 29ao P9 完了**
- 目的: EdgeCFG の block params 足場を追加し、strict/dev で join 受け口の整合を fail-fast で固定
- 変更: `src/mir/builder/control_flow/edgecfg/api/block_params.rs` / `src/mir/builder/control_flow/edgecfg/api/frag.rs` / `src/mir/builder/control_flow/edgecfg/api/compose/seq.rs` / `src/mir/builder/control_flow/edgecfg/api/compose/if_.rs` / `src/mir/builder/control_flow/edgecfg/api/compose/cleanup.rs` / `src/mir/builder/control_flow/edgecfg/api/compose/loop_.rs` / `src/mir/builder/control_flow/edgecfg/api/verify.rs` / `src/mir/builder/control_flow/edgecfg/api/emit.rs` / `docs/development/current/main/phases/phase-29ao/README.md` / `docs/development/current/main/10-Now.md` / `docs/development/current/main/30-Backlog.md` / `CURRENT_TASK.md`
- 検証: `cargo test --release -p nyash-rust --lib` / `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh`
**2025-12-29: Phase 29an P15 完了**
- 目的: P0P14 の成果を closeout 形式でまとめ、次フェーズPhase 29ao入口を固定
- 変更: `docs/development/current/main/phases/phase-29an/README.md` / `docs/development/current/main/10-Now.md` / `docs/development/current/main/30-Backlog.md` / `CURRENT_TASK.md`

View File

@ -15,8 +15,8 @@ Related:
- **Phase 29aoactive: CorePlan composition from Skeleton/Feature**
- 入口: `docs/development/current/main/phases/phase-29ao/README.md`
- 状況: P0/P1/P2/P3/P4/P5/P6/P7/P8 ✅ 完了 / Next: P9
- Next 指示書: `docs/development/current/main/phases/phase-29ao/P9-VALUEJOIN-MINIMAL-WIRE-INSTRUCTIONS.md`
- 状況: P0/P1/P2/P3/P4/P5/P6/P7/P8/P9 ✅ 完了 / Next: P10TBD
- Next 指示書: `docs/development/current/main/phases/phase-29ao/P9-VALUEJOIN-MINIMAL-WIRE-INSTRUCTIONS.md` の Next を参照
- **Phase 29af✅ COMPLETE: Boundary hygiene / regression entrypoint / carrier layout SSOT**
- 入口: `docs/development/current/main/phases/phase-29af/README.md`

View File

@ -63,7 +63,12 @@ GateSSOT:
- 指示書: `docs/development/current/main/phases/phase-29ao/P8-VALUEJOIN-EDGEARGS-COMPOSE-PRESERVE-INSTRUCTIONS.md`
- ねらい: compose::seq/if_/cleanup が EdgeArgs(layout+values) を保持することをテストで固定
## P9: ValueJoin minimal wireBlockParams 足場 + strict/dev Fail-Fast
- 指示書: `docs/development/current/main/phases/phase-29ao/P9-VALUEJOIN-MINIMAL-WIRE-INSTRUCTIONS.md`
- ねらい: EdgeCFG の block params 足場と strict/dev verify を追加し、join 受け口の整合を Fail-Fast で固定
## Nextplanned
- P9: ValueJoin 最小 wireblock params 足場 + strict/dev Fail-Fast
- 指示書: `docs/development/current/main/phases/phase-29ao/P9-VALUEJOIN-MINIMAL-WIRE-INSTRUCTIONS.md`
- P10: ValueJoin 最小配線join block/PHI の 1 ケースだけ接続、SSOT verify を維持
- 方向性: `docs/development/current/main/phases/phase-29ao/P9-VALUEJOIN-MINIMAL-WIRE-INSTRUCTIONS.md` の Next を参照

View File

@ -0,0 +1,9 @@
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::ValueId;
/// Join block params (same layout as EdgeArgs).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockParams {
pub layout: JumpArgsLayout,
pub params: Vec<ValueId>,
}

View File

@ -59,18 +59,34 @@ pub(crate) fn cleanup(
let mut exits = BTreeMap::new();
let mut wires = Vec::new();
let mut branches = Vec::new();
let mut block_params = main.block_params;
let Frag {
block_params: cleanup_block_params,
exits: cleanup_exits,
wires: cleanup_wires,
branches: cleanup_branches,
..
} = cleanup_frag;
if let Err(message) = super::merge_block_params(
&mut block_params,
cleanup_block_params,
"compose::cleanup",
) {
return Err(message);
}
// Validate cleanup_frag structure (only exits allowed, no wires/branches)
if !cleanup_frag.wires.is_empty() || !cleanup_frag.branches.is_empty() {
if !cleanup_wires.is_empty() || !cleanup_branches.is_empty() {
return Err(format!(
"compose::cleanup() Phase 281 P3: cleanup_frag must have empty wires/branches (only exits allowed), found {} wires, {} branches",
cleanup_frag.wires.len(),
cleanup_frag.branches.len()
cleanup_wires.len(),
cleanup_branches.len()
));
}
// Validate cleanup_frag exits (only Normal + Return allowed in P3)
for (kind, _) in &cleanup_frag.exits {
for (kind, _) in &cleanup_exits {
match kind {
ExitKind::Normal | ExitKind::Return => {} // OK
_ => {
@ -83,7 +99,7 @@ pub(crate) fn cleanup(
}
// Process cleanup Normal exits
if let Some(normal_stubs) = cleanup_frag.exits.get(&ExitKind::Normal) {
if let Some(normal_stubs) = cleanup_exits.get(&ExitKind::Normal) {
for mut stub in normal_stubs.clone() {
match normal_target {
Some(target_bb) => {
@ -101,7 +117,7 @@ pub(crate) fn cleanup(
}
// Process cleanup Return exits
if let Some(return_stubs) = cleanup_frag.exits.get(&ExitKind::Return) {
if let Some(return_stubs) = cleanup_exits.get(&ExitKind::Return) {
for mut stub in return_stubs.clone() {
match ret_target {
Some(target_bb) => {
@ -128,6 +144,7 @@ pub(crate) fn cleanup(
Ok(Frag {
entry: main.entry, // Entry = main entry (header_bb)
block_params,
exits,
wires,
branches,
@ -165,6 +182,7 @@ mod tests {
);
let cleanup_frag = Frag {
entry: cleanup_entry,
block_params: BTreeMap::new(),
exits,
wires: vec![],
branches: vec![],

View File

@ -69,6 +69,23 @@ pub(crate) fn if_(
let mut exits = BTreeMap::new();
let mut wires = Vec::new();
let mut block_params = t.block_params;
let join_entry = join_frag.entry;
if let Err(message) = super::merge_block_params(
&mut block_params,
e.block_params,
"compose::if_/else",
) {
panic!("{}", message);
}
if let Err(message) = super::merge_block_params(
&mut block_params,
join_frag.block_params,
"compose::if_/join",
) {
panic!("{}", message);
}
// then の全 exit を処理
for (kind, stubs) in t.exits {
@ -78,7 +95,7 @@ pub(crate) fn if_(
let wired_stubs: Vec<EdgeStub> = stubs
.into_iter()
.map(|mut stub| {
stub.target = Some(join_frag.entry);
stub.target = Some(join_entry);
stub
})
.collect();
@ -101,7 +118,7 @@ pub(crate) fn if_(
let wired_stubs: Vec<EdgeStub> = stubs
.into_iter()
.map(|mut stub| {
stub.target = Some(join_frag.entry);
stub.target = Some(join_entry);
stub
})
.collect();
@ -132,6 +149,7 @@ pub(crate) fn if_(
Frag {
entry: header, // if の入口は header
block_params,
exits, // t/e の非 Normal + join_frag.exits
wires, // t/e.Normal → join_frag.entry + t/e/join の wires
branches, // Phase 267 P0: header の BranchStub + t/e/join の branches
@ -185,6 +203,7 @@ mod tests {
);
let then_frag = Frag {
entry: then_entry,
block_params: BTreeMap::new(),
exits: then_exits,
wires: vec![],
branches: vec![],
@ -202,6 +221,7 @@ mod tests {
);
let else_frag = Frag {
entry: else_entry,
block_params: BTreeMap::new(),
exits: else_exits,
wires: vec![],
branches: vec![],

View File

@ -53,6 +53,7 @@ pub(crate) fn loop_(
// Phase 265 P2: exit 集合の配線処理wires/exits 分離)
let mut exits = BTreeMap::new();
let mut wires = Vec::new(); // Phase 265 P2: 配線済み内部配線
let block_params = body.block_params;
for (kind, stubs) in body.exits {
match kind {
@ -95,6 +96,7 @@ pub(crate) fn loop_(
Frag {
entry: header, // ループの入口
block_params,
exits, // Normal, Return, Unwind のみ(未配線)
wires, // Continue → header, Break → after配線済み
branches, // Phase 267 P0: body の branches

View File

@ -64,6 +64,12 @@
* - Phase 280: SSOT positioning (composition as pattern absorption destination)
*/
use std::collections::BTreeMap;
use crate::config::env;
use crate::mir::basic_block::BasicBlockId;
use crate::mir::builder::control_flow::edgecfg::api::block_params::BlockParams;
mod cleanup;
mod if_;
mod loop_;
@ -78,6 +84,27 @@ pub(crate) use loop_::loop_;
#[allow(unused_imports)]
pub(crate) use seq::seq;
pub(super) fn merge_block_params(
target: &mut BTreeMap<BasicBlockId, BlockParams>,
incoming: BTreeMap<BasicBlockId, BlockParams>,
context: &str,
) -> Result<(), String> {
let strict = env::joinir_strict_enabled() || env::joinir_dev_enabled();
for (block, params) in incoming {
if target.contains_key(&block) {
if strict {
return Err(format!(
"[{}] duplicate block_params for {:?}",
context, block
));
}
continue;
}
target.insert(block, params);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{cleanup, if_, loop_, seq};
@ -110,6 +137,7 @@ mod tests {
let body_frag = Frag {
entry: body_entry,
block_params: BTreeMap::new(),
exits: body_exits,
wires: vec![],
branches: vec![],
@ -145,6 +173,7 @@ mod tests {
let body_frag = Frag {
entry: body_entry,
block_params: BTreeMap::new(),
exits: body_exits,
wires: vec![],
branches: vec![],
@ -199,6 +228,7 @@ mod tests {
);
let body_frag = Frag {
entry: body,
block_params: BTreeMap::new(),
exits: body_exits,
wires: vec![],
branches: vec![],
@ -233,6 +263,7 @@ mod tests {
);
let body_frag = Frag {
entry: body,
block_params: BTreeMap::new(),
exits: body_exits,
wires: vec![],
branches: vec![],
@ -267,6 +298,7 @@ mod tests {
);
let body_frag = Frag {
entry: body,
block_params: BTreeMap::new(),
exits: body_exits,
wires: vec![],
branches: vec![],
@ -297,6 +329,7 @@ mod tests {
);
let a_frag = Frag {
entry: a_entry,
block_params: BTreeMap::new(),
exits: a_exits,
wires: vec![],
branches: vec![],
@ -309,6 +342,7 @@ mod tests {
);
let b_frag = Frag {
entry: b_entry,
block_params: BTreeMap::new(),
exits: b_exits,
wires: vec![],
branches: vec![],
@ -352,6 +386,7 @@ mod tests {
);
let a_frag = Frag {
entry: a_entry,
block_params: BTreeMap::new(),
exits: a_exits,
wires: vec![],
branches: vec![],
@ -364,6 +399,7 @@ mod tests {
);
let b_frag = Frag {
entry: b_entry,
block_params: BTreeMap::new(),
exits: b_exits,
wires: vec![],
branches: vec![],
@ -404,6 +440,7 @@ mod tests {
);
let then_frag = Frag {
entry: then_entry,
block_params: BTreeMap::new(),
exits: then_exits,
wires: vec![],
branches: vec![],
@ -416,6 +453,7 @@ mod tests {
);
let else_frag = Frag {
entry: else_entry,
block_params: BTreeMap::new(),
exits: else_exits,
wires: vec![],
branches: vec![],
@ -428,6 +466,7 @@ mod tests {
);
let join_frag = Frag {
entry: join_entry,
block_params: BTreeMap::new(),
exits: join_exits,
wires: vec![],
branches: vec![],
@ -487,6 +526,7 @@ mod tests {
);
let then_frag = Frag {
entry: then_entry,
block_params: BTreeMap::new(),
exits: then_exits,
wires: vec![],
branches: vec![],
@ -503,6 +543,7 @@ mod tests {
);
let else_frag = Frag {
entry: else_entry,
block_params: BTreeMap::new(),
exits: else_exits,
wires: vec![],
branches: vec![],
@ -510,6 +551,7 @@ mod tests {
let join_frag = Frag {
entry: join_entry,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],
@ -551,6 +593,7 @@ mod tests {
// Main Frag: empty (no exits)
let main_frag = Frag {
entry: main_entry,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],
@ -559,6 +602,7 @@ mod tests {
// Cleanup Frag: Return exit
let cleanup_frag = Frag {
entry: cleanup_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::from([(
ExitKind::Return,
vec![EdgeStub {
@ -600,6 +644,7 @@ mod tests {
// Main Frag: empty
let main_frag = Frag {
entry: main_entry,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],
@ -608,6 +653,7 @@ mod tests {
// Cleanup Frag: Return exit
let cleanup_frag = Frag {
entry: cleanup_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::from([(
ExitKind::Return,
vec![EdgeStub {
@ -649,6 +695,7 @@ mod tests {
// Main Frag: empty
let main_frag = Frag {
entry: main_entry,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],
@ -657,6 +704,7 @@ mod tests {
// Cleanup Frag: Normal exit
let cleanup_frag = Frag {
entry: cleanup_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::from([(
ExitKind::Normal,
vec![EdgeStub {
@ -698,6 +746,7 @@ mod tests {
// Main Frag: empty
let main_frag = Frag {
entry: main_entry,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],
@ -706,6 +755,7 @@ mod tests {
// Cleanup Frag: Normal exit
let cleanup_frag = Frag {
entry: cleanup_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::from([(
ExitKind::Normal,
vec![EdgeStub {

View File

@ -41,6 +41,16 @@ use crate::mir::builder::control_flow::edgecfg::api::frag::Frag;
pub(crate) fn seq(a: Frag, b: Frag) -> Frag {
let mut exits = BTreeMap::new();
let mut wires = Vec::new();
let mut block_params = a.block_params;
let b_entry = b.entry;
if let Err(message) = super::merge_block_params(
&mut block_params,
b.block_params,
"compose::seq",
) {
panic!("{}", message);
}
// a の全 exit を処理
for (kind, stubs) in a.exits {
@ -50,7 +60,7 @@ pub(crate) fn seq(a: Frag, b: Frag) -> Frag {
let wired_stubs: Vec<EdgeStub> = stubs
.into_iter()
.map(|mut stub| {
stub.target = Some(b.entry);
stub.target = Some(b_entry);
stub
})
.collect();
@ -82,6 +92,7 @@ pub(crate) fn seq(a: Frag, b: Frag) -> Frag {
Frag {
entry: a.entry, // seq の入口は a の入口
block_params,
exits, // a の非 Normal + b の全 exit
wires, // a.Normal → b.entry + a.wires + b.wires
branches, // Phase 267 P0: a.branches + b.branches
@ -92,6 +103,7 @@ pub(crate) fn seq(a: Frag, b: Frag) -> Frag {
mod tests {
use super::seq;
use crate::mir::basic_block::{BasicBlockId, EdgeArgs};
use crate::mir::builder::control_flow::edgecfg::api::block_params::BlockParams;
use crate::mir::builder::control_flow::edgecfg::api::edge_stub::EdgeStub;
use crate::mir::builder::control_flow::edgecfg::api::exit_kind::ExitKind;
use crate::mir::builder::control_flow::edgecfg::api::frag::Frag;
@ -117,6 +129,7 @@ mod tests {
exits.insert(ExitKind::Normal, vec![stub]);
let a = Frag {
entry: a_entry,
block_params: BTreeMap::new(),
exits,
wires: vec![],
branches: vec![],
@ -127,4 +140,23 @@ mod tests {
assert_eq!(composed.wires[0].target, Some(b_entry));
assert_eq!(composed.wires[0].args, args);
}
#[test]
fn seq_preserves_block_params() {
let a_entry = BasicBlockId::new(1);
let b_entry = BasicBlockId::new(2);
let join_bb = BasicBlockId::new(3);
let a = Frag::new(a_entry);
let mut b = Frag::new(b_entry);
b.block_params.insert(
join_bb,
BlockParams {
layout: JumpArgsLayout::ExprResultPlusCarriers,
params: vec![ValueId(42)],
},
);
let composed = seq(a, b);
assert!(composed.block_params.contains_key(&join_bb));
}
}

View File

@ -405,6 +405,7 @@ mod tests {
let frag = Frag {
entry: header,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: vec![],
branches: vec![branch],
@ -489,6 +490,7 @@ mod tests {
let frag = Frag {
entry: bb0,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: vec![wire],
branches: vec![branch],
@ -516,6 +518,7 @@ mod tests {
let then_frag = Frag {
entry: then_entry,
block_params: BTreeMap::new(),
exits: {
let mut exits = BTreeMap::new();
exits.insert(
@ -530,6 +533,7 @@ mod tests {
let else_frag = Frag {
entry: else_entry,
block_params: BTreeMap::new(),
exits: {
let mut exits = BTreeMap::new();
exits.insert(
@ -544,6 +548,7 @@ mod tests {
let join_frag = Frag {
entry: join_entry,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],

View File

@ -7,6 +7,7 @@
use std::collections::BTreeMap;
use crate::mir::basic_block::BasicBlockId;
use super::block_params::BlockParams;
use super::exit_kind::ExitKind;
use super::edge_stub::EdgeStub;
use super::branch_stub::BranchStub;
@ -39,6 +40,9 @@ pub struct Frag {
/// 断片の入口ブロック
pub entry: BasicBlockId,
/// 断片の block paramsjoin 受け口)
pub block_params: BTreeMap<BasicBlockId, BlockParams>,
/// 断片からの未配線脱出エッジExitKind → EdgeStub のリスト)
///
/// Phase 265 P2: target = None のみ(外へ出る exit
@ -64,6 +68,7 @@ impl Frag {
pub fn new(entry: BasicBlockId) -> Self {
Self {
entry,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: vec![], // Phase 265 P2: 配線済み内部配線
branches: vec![], // Phase 267 P0: 配線済み分岐
@ -76,6 +81,7 @@ impl Frag {
exits.insert(stub.kind, vec![stub]);
Self {
entry,
block_params: BTreeMap::new(),
exits,
wires: vec![], // Phase 265 P2: 配線済み内部配線
branches: vec![], // Phase 267 P0: 配線済み分岐

View File

@ -17,6 +17,7 @@
pub mod exit_kind;
pub mod edge_stub;
pub mod block_params;
pub mod frag;
pub mod compose;
pub mod verify;
@ -26,6 +27,8 @@ pub mod branch_stub; // Phase 267 P0: 追加
// 公開型(安定)
pub use exit_kind::ExitKind;
pub use edge_stub::EdgeStub;
#[allow(unused_imports)]
pub use block_params::BlockParams;
pub use frag::Frag;
pub use branch_stub::BranchStub; // Phase 267 P0: 追加

View File

@ -110,6 +110,8 @@ pub fn verify_frag_invariants(frag: &Frag) -> Result<(), String> {
/// - 新規に `verify_frag_invariants_strict()` を追加し、P266 の PoC/emit 側だけ strict を使う
pub fn verify_frag_invariants_strict(frag: &Frag) -> Result<(), String> {
use super::exit_kind::ExitKind;
use crate::mir::basic_block::{BasicBlockId, EdgeArgs};
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
// 1. exits と wires の両方が空の場合は警告(非致命的)
if frag.exits.is_empty() && frag.wires.is_empty() {
@ -145,6 +147,53 @@ pub fn verify_frag_invariants_strict(frag: &Frag) -> Result<(), String> {
}
}
if crate::config::env::joinir_strict_enabled() || crate::config::env::joinir_dev_enabled() {
let check_edge_args = |target: Option<BasicBlockId>,
args: &EdgeArgs,
context: &str|
-> Result<(), String> {
if args.layout != JumpArgsLayout::ExprResultPlusCarriers {
return Ok(());
}
let target = target.ok_or_else(|| {
format!(
"[verify_frag_strict] ExprResultPlusCarriers requires target block (context={})",
context
)
})?;
let params = frag.block_params.get(&target).ok_or_else(|| {
format!(
"[verify_frag_strict] Missing block_params for target {:?} (context={})",
target, context
)
})?;
if params.layout != args.layout {
return Err(format!(
"[verify_frag_strict] BlockParams layout mismatch at {:?} (context={}, block={:?}, edge={:?})",
target, context, params.layout, args.layout
));
}
if params.params.len() != args.values.len() {
return Err(format!(
"[verify_frag_strict] BlockParams length mismatch at {:?} (context={}, params={}, args={})",
target,
context,
params.params.len(),
args.values.len()
));
}
Ok(())
};
for stub in &frag.wires {
check_edge_args(stub.target, &stub.args, "wire")?;
}
for branch in &frag.branches {
check_edge_args(Some(branch.then_target), &branch.then_args, "branch/then")?;
check_edge_args(Some(branch.else_target), &branch.else_args, "branch/else")?;
}
}
Ok(())
}
@ -152,7 +201,13 @@ pub fn verify_frag_invariants_strict(frag: &Frag) -> Result<(), String> {
mod tests {
use super::*;
use std::collections::BTreeMap;
use crate::mir::basic_block::BasicBlockId;
use crate::mir::basic_block::{BasicBlockId, EdgeArgs};
use crate::mir::builder::control_flow::edgecfg::api::block_params::BlockParams;
use crate::mir::builder::control_flow::edgecfg::api::edge_stub::EdgeStub;
use crate::mir::builder::control_flow::edgecfg::api::exit_kind::ExitKind;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::ValueId;
use std::env;
#[test]
fn test_verify_frag_basic() {
@ -162,6 +217,7 @@ mod tests {
let frag = Frag {
entry: header,
block_params: BTreeMap::new(),
exits,
wires: vec![],
branches: vec![],
@ -170,4 +226,77 @@ mod tests {
// P1: Always returns Ok(())
assert!(verify_frag_invariants(&frag).is_ok());
}
fn strict_env_guard() -> impl Drop {
env::set_var("NYASH_JOINIR_STRICT", "1");
struct Guard;
impl Drop for Guard {
fn drop(&mut self) {
let _ = env::remove_var("NYASH_JOINIR_STRICT");
}
}
Guard
}
#[test]
fn strict_requires_block_params_for_expr_result_layout() {
let _guard = strict_env_guard();
let entry = BasicBlockId(1);
let target = BasicBlockId(2);
let args = EdgeArgs {
layout: JumpArgsLayout::ExprResultPlusCarriers,
values: vec![ValueId(10), ValueId(11)],
};
let frag = Frag {
entry,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: vec![EdgeStub {
from: entry,
kind: ExitKind::Normal,
target: Some(target),
args,
}],
branches: vec![],
};
let err = verify_frag_invariants_strict(&frag).unwrap_err();
assert!(err.contains("Missing block_params"), "unexpected err: {}", err);
}
#[test]
fn strict_accepts_matching_block_params_layout_and_len() {
let _guard = strict_env_guard();
let entry = BasicBlockId(1);
let target = BasicBlockId(2);
let args = EdgeArgs {
layout: JumpArgsLayout::ExprResultPlusCarriers,
values: vec![ValueId(10), ValueId(11)],
};
let mut block_params = BTreeMap::new();
block_params.insert(
target,
BlockParams {
layout: JumpArgsLayout::ExprResultPlusCarriers,
params: vec![ValueId(100), ValueId(101)],
},
);
let frag = Frag {
entry,
block_params,
exits: BTreeMap::new(),
wires: vec![EdgeStub {
from: entry,
kind: ExitKind::Normal,
target: Some(target),
args,
}],
branches: vec![],
};
assert!(verify_frag_invariants_strict(&frag).is_ok());
}
}

View File

@ -26,6 +26,7 @@ pub(super) fn normal_exit_frag(
);
Frag {
entry,
block_params: BTreeMap::new(),
exits,
wires: vec![],
branches: vec![],
@ -45,6 +46,7 @@ pub(super) fn build_body_if_frag(
let step_frag = Frag {
entry: step_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],

View File

@ -144,6 +144,7 @@ impl super::PlanNormalizer {
let frag = Frag {
entry: header_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires,
branches,

View File

@ -259,6 +259,7 @@ impl super::PlanNormalizer {
let frag = Frag {
entry: header_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires,
branches,

View File

@ -247,6 +247,7 @@ impl super::PlanNormalizer {
let frag = Frag {
entry: header_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires,
branches,

View File

@ -259,6 +259,7 @@ impl super::PlanNormalizer {
let frag = Frag {
entry: header_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires,
branches,

View File

@ -263,6 +263,7 @@ impl super::PlanNormalizer {
let frag = Frag {
entry: header_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires,
branches,
@ -502,6 +503,7 @@ impl super::PlanNormalizer {
let frag = Frag {
entry: header_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires,
branches,

View File

@ -239,6 +239,7 @@ impl super::PlanNormalizer {
let main_frag = Frag {
entry: header_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: main_wires,
branches: main_branches,
@ -261,6 +262,7 @@ impl super::PlanNormalizer {
let cleanup_frag = Frag {
entry: found_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::from([(ExitKind::Return, cleanup_exits)]),
wires: vec![],
branches: vec![],

View File

@ -174,6 +174,7 @@ impl super::PlanNormalizer {
let frag = Frag {
entry: header_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires,
branches,

View File

@ -330,6 +330,7 @@ impl super::PlanNormalizer {
let main_frag = Frag {
entry: header_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: main_wires,
branches: main_branches,
@ -344,6 +345,7 @@ impl super::PlanNormalizer {
let cleanup_frag = Frag {
entry: found_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::from([(ExitKind::Return, cleanup_exits)]),
wires: vec![],
branches: vec![],

View File

@ -328,10 +328,18 @@ impl super::PlanNormalizer {
else_args: empty_args.clone(),
}];
branches.extend(body_if_frag.branches);
let Frag {
block_params,
branches: body_branches,
wires: body_wires,
exits: body_exits,
..
} = body_if_frag;
branches.extend(body_branches);
let mut wires = Vec::new();
wires.extend(body_if_frag.wires);
wires.extend(body_wires);
wires.push(EdgeStub {
from: step_bb,
@ -341,12 +349,13 @@ impl super::PlanNormalizer {
});
let mut exits = BTreeMap::new();
for (kind, stubs) in body_if_frag.exits {
for (kind, stubs) in body_exits {
exits.insert(kind, stubs);
}
let frag = Frag {
entry: header_bb,
block_params,
exits,
wires,
branches,

View File

@ -124,6 +124,7 @@ impl super::PlanNormalizer {
let frag = Frag {
entry: header_bb,
block_params: BTreeMap::new(),
exits: build_exitmap_from_presence(
&facts.exit_kinds_present,
&facts.cleanup_kinds_present,

View File

@ -700,6 +700,7 @@ mod tests {
}],
frag: Frag {
entry: header_bb,
block_params: BTreeMap::new(),
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],