phase29ao(p10): emit block params as phis (valuejoin wiring)
This commit is contained in:
@ -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 移行の土台を作った。
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
## Current Focus: Phase 29ao(CorePlan composition)
|
||||
|
||||
Next: Phase 29ao P10(ValueJoin: block_params → PHI wiring)
|
||||
指示書: `docs/development/current/main/phases/phase-29ao/P10-VALUEJOIN-BLOCKPARAMS-PHI-INSERTION-INSTRUCTIONS.md`
|
||||
Next: Phase 29ao P11(ValueJoin 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 P10(ValueJoin: 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 完了** ✅
|
||||
- 目的: P0–P14 の成果を 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`
|
||||
|
||||
@ -15,8 +15,8 @@ Related:
|
||||
|
||||
- **Phase 29ao(active): 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: P10(TBD)
|
||||
- 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: P11(TBD)
|
||||
- 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`
|
||||
|
||||
@ -68,7 +68,11 @@ Gate(SSOT):
|
||||
- 指示書: `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 wiring(block_params → MIR PHI)✅
|
||||
|
||||
- 指示書: `docs/development/current/main/phases/phase-29ao/P10-VALUEJOIN-BLOCKPARAMS-PHI-INSERTION-INSTRUCTIONS.md`
|
||||
- ねらい: `Frag.block_params` を `emit_frag()` で PHI に落とす唯一の接続点を追加(未接続のまま)
|
||||
|
||||
## Next(planned)
|
||||
|
||||
- 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ケースだけ回帰)
|
||||
|
||||
@ -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 に emit(Phase 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user