phase29ao(p10): emit block params as phis (valuejoin wiring)

This commit is contained in:
2025-12-30 06:58:50 +09:00
parent a8bdc0e587
commit cd2a7b9fe7
6 changed files with 257 additions and 10 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 P10)**
`docs/development/current/main/phases/phase-29ao/P10-VALUEJOIN-BLOCKPARAMS-PHI-INSERTION-INSTRUCTIONS.md`
**Next implementation (Phase 29ao P11, TBD)**
`docs/development/current/main/phases/phase-29ao/README.md`
**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 P10ValueJoin: block_params → PHI wiring
指示書: `docs/development/current/main/phases/phase-29ao/P10-VALUEJOIN-BLOCKPARAMS-PHI-INSERTION-INSTRUCTIONS.md`
Next: Phase 29ao P11ValueJoin first use-case, TBD
指示書: `docs/development/current/main/phases/phase-29ao/README.md`
運用ルール: integration filter で phase143_* は回さないJoinIR 回帰は phase29ae pack のみ)
運用ルール: phase286_pattern9_* は legacy pack (SKIP) を使う
移行道筋 SSOT: `docs/development/current/main/design/coreplan-migration-roadmap-ssot.md`
@ -58,6 +58,11 @@ Next: Phase 29ao P10ValueJoin: block_params → PHI wiring
- 変更: `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-30: Phase 29ao P10 完了**
- 目的: `Frag.block_params``emit_frag()` で PHI に落とす唯一の接続点を追加(未接続のまま)
- 変更: `src/mir/builder/control_flow/edgecfg/api/emit.rs` / `src/mir/builder/control_flow/edgecfg/api/verify.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/P9 ✅ 完了 / Next: P10TBD
- Next 指示書: `docs/development/current/main/phases/phase-29ao/P10-VALUEJOIN-BLOCKPARAMS-PHI-INSERTION-INSTRUCTIONS.md`
- 状況: P0/P1/P2/P3/P4/P5/P6/P7/P8/P9/P10 ✅ 完了 / Next: P11TBD
- Next 指示書: `docs/development/current/main/phases/phase-29ao/README.md`
- **Phase 29af✅ COMPLETE: Boundary hygiene / regression entrypoint / carrier layout SSOT**
- 入口: `docs/development/current/main/phases/phase-29af/README.md`

View File

@ -68,7 +68,11 @@ GateSSOT:
- 指示書: `docs/development/current/main/phases/phase-29ao/P9-VALUEJOIN-MINIMAL-WIRE-INSTRUCTIONS.md`
- ねらい: EdgeCFG の block params 足場と strict/dev verify を追加し、join 受け口の整合を Fail-Fast で固定
## P10: ValueJoin minimal wiringblock_params → MIR PHI
- 指示書: `docs/development/current/main/phases/phase-29ao/P10-VALUEJOIN-BLOCKPARAMS-PHI-INSERTION-INSTRUCTIONS.md`
- ねらい: `Frag.block_params``emit_frag()` で PHI に落とす唯一の接続点を追加(未接続のまま)
## Nextplanned
- P10: ValueJoin 最小配線block_params → MIR PHI の 1 箇所接続
- 指示書: `docs/development/current/main/phases/phase-29ao/P10-VALUEJOIN-BLOCKPARAMS-PHI-INSERTION-INSTRUCTIONS.md`
- P11: ValueJoin の最初の実使用Normalizer で block_params を生成し、1ケースだけ回帰

View File

@ -100,6 +100,97 @@ pub fn emit_wires(
Ok(())
}
fn emit_block_params_as_phis(
function: &mut crate::mir::MirFunction,
frag: &super::frag::Frag,
) -> Result<(), String> {
use crate::ast::Span;
use crate::mir::basic_block::{BasicBlockId, EdgeArgs};
use crate::mir::ssot::cf_common::insert_phi_at_head_spanned;
use std::collections::BTreeSet;
if frag.block_params.is_empty() {
return Ok(());
}
let strict = crate::config::env::joinir_strict_enabled()
|| crate::config::env::joinir_dev_enabled();
let mut incoming: BTreeMap<BasicBlockId, Vec<(BasicBlockId, EdgeArgs)>> = BTreeMap::new();
for stub in &frag.wires {
if let Some(target) = stub.target {
incoming
.entry(target)
.or_default()
.push((stub.from, stub.args.clone()));
}
}
for branch in &frag.branches {
incoming
.entry(branch.then_target)
.or_default()
.push((branch.from, branch.then_args.clone()));
incoming
.entry(branch.else_target)
.or_default()
.push((branch.from, branch.else_args.clone()));
}
for (target, params) in &frag.block_params {
let edges = incoming.get(target).cloned().unwrap_or_default();
if edges.is_empty() {
if strict {
return Err(format!(
"[emit_frag] BlockParams target {:?} has no incoming edges",
target
));
}
continue;
}
if strict {
let mut seen = BTreeSet::new();
for (pred, _) in &edges {
if !seen.insert(*pred) {
return Err(format!(
"[emit_frag] Duplicate incoming edge {:?}->{:?}",
pred, target
));
}
}
}
for (index, dst) in params.params.iter().enumerate() {
let mut inputs = Vec::with_capacity(edges.len());
for (pred, args) in &edges {
match args.values.get(index) {
Some(value) => inputs.push((*pred, *value)),
None => {
if strict {
return Err(format!(
"[emit_frag] Missing edge arg for block_params {:?} index {}",
target, index
));
}
}
}
}
if inputs.is_empty() {
if strict {
return Err(format!(
"[emit_frag] BlockParams target {:?} has no inputs for index {}",
target, index
));
}
continue;
}
insert_phi_at_head_spanned(function, *target, *dst, inputs, Span::unknown());
}
}
Ok(())
}
/// Frag を MIR に emitPhase 267 P0: SSOT
///
/// # 責務
@ -124,6 +215,9 @@ pub fn emit_frag(
// Step 0: verify_frag_invariants_strict() で事前検証SSOT
super::verify::verify_frag_invariants_strict(frag)?;
// Step 0.5: block_params → PHI 挿入ValueJoin wiring SSOT
emit_block_params_as_phis(function, frag)?;
// Step 1: branches を from ごとにグループ化1本だけ許可
let mut branches_by_block: BTreeMap<BasicBlockId, Vec<&BranchStub>> = BTreeMap::new();
for branch in &frag.branches {
@ -449,6 +543,86 @@ mod tests {
assert!(block.successors.contains(&else_bb));
}
#[test]
fn test_emit_frag_block_params_inserts_phi() {
use super::super::block_params::BlockParams;
use super::super::frag::Frag;
use std::collections::BTreeMap;
let mut function = create_test_function();
let pred1 = BasicBlockId(1);
let pred2 = BasicBlockId(2);
let target = BasicBlockId(3);
function.add_block(BasicBlock::new(pred1));
function.add_block(BasicBlock::new(pred2));
function.add_block(BasicBlock::new(target));
let args1 = EdgeArgs {
layout: JumpArgsLayout::ExprResultPlusCarriers,
values: vec![ValueId(10), ValueId(11)],
};
let args2 = EdgeArgs {
layout: JumpArgsLayout::ExprResultPlusCarriers,
values: vec![ValueId(12), ValueId(13)],
};
let wires = vec![
EdgeStub {
from: pred1,
kind: ExitKind::Normal,
target: Some(target),
args: args1,
},
EdgeStub {
from: pred2,
kind: ExitKind::Normal,
target: Some(target),
args: args2,
},
];
let mut block_params = BTreeMap::new();
block_params.insert(
target,
BlockParams {
layout: JumpArgsLayout::ExprResultPlusCarriers,
params: vec![ValueId(100), ValueId(101)],
},
);
let frag = Frag {
entry: pred1,
block_params,
exits: BTreeMap::new(),
wires,
branches: vec![],
};
emit_frag(&mut function, &frag).expect("emit_frag should succeed");
let block = function.get_block(target).unwrap();
match &block.instructions[0] {
MirInstruction::Phi { dst, inputs, .. } => {
assert_eq!(*dst, ValueId(100));
assert_eq!(
inputs,
&vec![(pred1, ValueId(10)), (pred2, ValueId(12))]
);
}
other => panic!("Expected Phi at head, got {:?}", other),
}
match &block.instructions[1] {
MirInstruction::Phi { dst, inputs, .. } => {
assert_eq!(*dst, ValueId(101));
assert_eq!(
inputs,
&vec![(pred1, ValueId(11)), (pred2, ValueId(13))]
);
}
other => panic!("Expected second Phi, got {:?}", other),
}
}
#[test]
fn test_emit_frag_branch_wire_conflict_fails() {
use super::super::branch_stub::BranchStub;
@ -583,4 +757,3 @@ mod tests {
assert_eq!(branch.else_target, else_entry);
}
}

View File

@ -6,6 +6,7 @@
*/
use super::frag::Frag;
use std::collections::BTreeMap;
/// Frag の不変条件を検証Phase 265 P2: wires/exits 分離契約)
///
@ -112,6 +113,7 @@ 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;
use std::collections::BTreeSet;
// 1. exits と wires の両方が空の場合は警告(非致命的)
if frag.exits.is_empty() && frag.wires.is_empty() {
@ -147,7 +149,10 @@ pub fn verify_frag_invariants_strict(frag: &Frag) -> Result<(), String> {
}
}
if crate::config::env::joinir_strict_enabled() || crate::config::env::joinir_dev_enabled() {
let strict = crate::config::env::joinir_strict_enabled()
|| crate::config::env::joinir_dev_enabled();
if strict {
let check_edge_args = |target: Option<BasicBlockId>,
args: &EdgeArgs,
context: &str|
@ -194,6 +199,66 @@ pub fn verify_frag_invariants_strict(frag: &Frag) -> Result<(), String> {
}
}
if strict && !frag.block_params.is_empty() {
let mut incoming: BTreeMap<BasicBlockId, Vec<(BasicBlockId, &EdgeArgs)>> = BTreeMap::new();
for stub in &frag.wires {
if let Some(target) = stub.target {
incoming
.entry(target)
.or_default()
.push((stub.from, &stub.args));
}
}
for branch in &frag.branches {
incoming
.entry(branch.then_target)
.or_default()
.push((branch.from, &branch.then_args));
incoming
.entry(branch.else_target)
.or_default()
.push((branch.from, &branch.else_args));
}
for (target, params) in &frag.block_params {
let Some(edges) = incoming.get(target) else {
return Err(format!(
"[verify_frag_strict] BlockParams target {:?} has no incoming edges",
target
));
};
if edges.is_empty() {
return Err(format!(
"[verify_frag_strict] BlockParams target {:?} has no incoming edges",
target
));
}
let mut seen = BTreeSet::new();
for (pred, args) in edges {
if !seen.insert(*pred) {
return Err(format!(
"[verify_frag_strict] Duplicate incoming edge {:?}->{:?}",
pred, target
));
}
if args.layout != params.layout {
return Err(format!(
"[verify_frag_strict] BlockParams layout mismatch at {:?} (block={:?}, edge={:?})",
target, params.layout, args.layout
));
}
if args.values.len() != params.params.len() {
return Err(format!(
"[verify_frag_strict] BlockParams length mismatch at {:?} (params={}, args={})",
target,
params.params.len(),
args.values.len()
));
}
}
}
}
Ok(())
}