diff --git a/docs/development/current/main/investigations/phase-259-block-parameterized-cfg-consult.md b/docs/development/current/main/investigations/phase-259-block-parameterized-cfg-consult.md new file mode 100644 index 00000000..1bdb78fc --- /dev/null +++ b/docs/development/current/main/investigations/phase-259-block-parameterized-cfg-consult.md @@ -0,0 +1,167 @@ +# Phase 259: Block-Parameterized CFG / Join-Explicit CFG — ChatGPT Pro 相談パケット + +目的: ChatGPT Pro に「正規化(normalized)をどう設計し、小さい強い箱(Box)で結び、block-parameterized CFG へ段階移行するか」を相談するためのコピペ用 SSOT を用意する。 + +更新日: 2025-12-20 + +--- + +## 1) ChatGPT Pro へ投げる文章(コピペ用) + +以下の条件で、段階移行できる設計を提案してください。 + +### コンテキスト + +Nyash/Hakorune の JoinIR→MIR 経路で、`Jump/continuation/params/edge-args` が「暗黙 ABI(順序/長さ/役割/識別)」として散在し、パターン追加のたびに merge/optimizer/verify が連鎖で壊れました。最近は SSOT 化と Fail-Fast を進め、`JumpArgsLayout` を boundary に持たせて推測を排除しましたが、最終的には **block-parameterized CFG**(edge-args を第一級に持つ CFG)に収束させたいです。 + +北極星(north star): “Join-Explicit CFG Construction” + +### ゴール(最終形) + +- `Jump/continuation/params/edge-args` を第一級(explicit)として扱う +- JoinIR↔MIR 間の暗黙 ABI を消し、変換を「意味解釈」ではなく「写像(mapping)」に縮退する +- 長期的には JoinIR を builder DSL に降格できる状態にする(削除は急がない) + +### 制約 / 方針 + +- フォールバックより Fail-Fast(prod/CI 既定で安全) +- by-name ハードコード分岐は禁止(特定 Box 名での条件分岐など) +- 新しい環境変数の増殖は禁止(既存 debug gate の範囲で) +- 段階導入(Strangler)で、差分を小さく・可逆に + +### 質問(回答してほしいこと) + +1. **Normalized JoinIR の最小語彙**は何がよいか? + - `Jump(args)` / `Branch(then_args, else_args)` / `Return` / `Call` のセット + - `cond 付き Jump` を禁止し、`Branch` に寄せるべきか +2. **ABI(役割・順序)SSOT をどこに置くべきか?** + - `JoinAbi`(sig/roles/special conts/alias)を新設するか、`JoinModule` に持たせるか + - boundary の `join_inputs/host_inputs` を Vec 順序のまま段階移行する案(最小差分)と、`ParamId→ValueId` 束縛にする案(最終形) +3. **block-parameterized CFG 移行の最小ステップ**を、Phase で刻んで提示してほしい + - `jump_args` を BasicBlock メタから terminator operand に埋め込む手順(併存→移行→削除) + - verify / optimizer / printer / builder への影響の最小化 +4. **箱(Box)分割**は最小でどう切るべきか? + - “推測禁止” と “Fail-Fast” をどの箱に閉じ込めるか + - 例: `AbiBox`, `BoundaryContractCheckBox`, `ExitArgsPlumbingBox`, `CfgSuccessorSyncBox` +5. **不変条件(invariants)**と検証地点の設計 + - Normalizer直後 / merge直前 / `--verify` の各地点で、何を verify するのが最短で強いか + +### 期待する出力(形式) + +- 「いきなり最終形」ではなく、**段階移行(1フェーズ=小差分)**のマイルストーンを提示 +- 各フェーズの **受け入れ基準**(smoke/verify/contract)を明文化 +- 新規箱は最小(2〜3個から開始)で、増やす判断基準も書く + +--- + +## 2) リポジトリ現状(SSOTメモ) + +### 最近の到達点(コミット) + +- `73ddc5f58` feat(joinir): Phase 257 P1.1/P1.2/P1.3(Pattern6 SSOT / PHI predecessor verify / LoopHeaderPhi 修正) +- `23531bf64` feat(joinir): Phase 258 P0(index_of_string dynamic needle window scan) +- `e4f57ea83` docs: Phase 257-259 SSOT 更新 + +### 現在の “意味データが IR 外” の例 + +- `jump_args` が `BasicBlock` のメタとして存在(DCE/verify が追う必要がある) +- spans が `instructions` と並行 Vec(同期漏れで SPAN MISMATCH が起きる) + +--- + +## 3) 最低限のソースコード断片(Pro に見せる用) + +### 3.1 `BasicBlock`(jump_args と span の現状) + +`src/mir/basic_block.rs:45` + +```rust +pub struct BasicBlock { + pub id: BasicBlockId, + pub instructions: Vec, + pub instruction_spans: Vec, + pub terminator: Option, + pub terminator_span: Option, + pub predecessors: BTreeSet, + pub successors: BTreeSet, + // ... + pub jump_args: Option>, // Phase 246-EX: Jump args metadata +} +``` + +### 3.2 `JumpArgsLayout`(推測排除の SSOT) + +`src/mir/join_ir/lowering/inline_boundary.rs:107` + +```rust +pub enum JumpArgsLayout { + CarriersOnly, + ExprResultPlusCarriers, +} +``` + +### 3.3 Exit args collection(layout に従うだけ) + +`src/mir/builder/control_flow/joinir/merge/exit_args_collector.rs:94` + +```rust +pub fn collect( + &self, + exit_bindings: &[LoopExitBinding], + remapped_args: &[ValueId], + block_id: BasicBlockId, + strict_exit: bool, + layout: JumpArgsLayout, +) -> Result +``` + +### 3.4 DCE が jump_args を use として数える(現状の暫定対応) + +`src/mir/passes/dce.rs:40` + +```rust +if let Some(args) = &block.jump_args { + for &u in args { + used_values.insert(u); + } +} +``` + +--- + +## 4) 相談したい設計上の痛点(要約) + +- `jump_args` がメタなので、最適化/検証/表示/CFG更新が「忘れると壊れる」になりやすい +- continuation の識別が ID/名前/legacy alias で揺れると merge が壊れる +- “順序” が暗黙だと、expr_result と carrier が同一 ValueId のときにズレて誤配線になりやすい +- spans が並行 Vec だと、パスが1箇所でも同期を忘れると壊れる +- `this`/`me` の表面名と内部 `variable_map` キーがズレると、Pattern 側で receiver を取り違えやすい(SSOT不足) +- `Branch` が入ると “edge-args の参照点” が曖昧になりやすい(then/else のどちらかだけ見て事故る) + +--- + +## 5) 移行の北極星(既存 SSOT へのリンク) + +- North Star: `docs/development/current/main/design/join-explicit-cfg-construction.md` +- Phase 256 で露出した契約論点: `docs/development/current/main/investigations/phase-256-joinir-contract-questions.md` + +## 6) 追加質問(receiver SSOT) + +`me` receiver の host ValueId を Pattern 側が直接 `"me"`/`"this"` で参照しないように、API/Box として封印したい。 + +- どの層に置くべきか?(例: `joinir/api/receiver.rs`) +- Fail-Fast の位置(builder で未登録なら即死?pattern detect の時点で弾く?) +- `this`/`me` の将来拡張(Stage-3/4)に耐える最小設計は? + +--- + +## 7) ChatGPT Pro 追記(設計レビュー観点) + +段階移行ロードマップの細部で、次の2点を優先して “迷子防止” を強化したい。 + +1. **edge-args の参照 API は Branch 前提にする** + - `edge_args()` 単発は曖昧になりやすい + - 推奨: `out_edges()` / `edge_args_to(target)` のように「edge を列挙」できる形を SSOT にする +2. **terminator operand の edge-args は “意味付き” にする** + - `Vec` だけだと layout(`CarriersOnly` / `ExprResultPlusCarriers`)の推測が残る + - 最小: `EdgeArgs { layout: JumpArgsLayout, values: Vec }` を同梱(将来は `ContSigId` に置換) diff --git a/docs/development/current/main/phases/phase-260/README.md b/docs/development/current/main/phases/phase-260/README.md new file mode 100644 index 00000000..f9684dae --- /dev/null +++ b/docs/development/current/main/phases/phase-260/README.md @@ -0,0 +1,92 @@ +# Phase 260: Block-Parameterized CFG(edge-args)段階導入 + +Status: Planned +Last updated: 2025-12-20 + +## 目的(P0) + +JoinIR→MIR の暗黙 ABI(jump_args / carriers / expr_result slot / successors)を減らし、将来的な **block-parameterized CFG**(edge-args を第一級に持つCFG)へ収束するための「大工事パート」を開始する。 + +このフェーズは “一括置換” ではなく、**併存導入(Strangler)**で可逆に進める。 + +## 背景(Phase 256-259 で露出した型) + +- `jump_args` が IR 外メタとして存在すると、DCE/verify/CFG 更新が「忘れると壊れる」になる +- spans が並行 Vec だと、最適化や変換で同期漏れが起きやすい +- continuation / entry / exit の識別と args 順序が散在すると、推測・補正が増殖する + +North Star: `docs/development/current/main/design/join-explicit-cfg-construction.md` + +## 方針(2段正規化) + +- **Semantic Normalization(意味SSOT)**: terminator 語彙の固定(例: cond付きJumpを正規形から禁止しBranchへ) +- **Plumbing Normalization(配線SSOT)**: edge-args / CFG successor / spans を IR 構造に閉じ込め、写像に縮退 + +## スコープ(Phase 260) + +### In scope + +- MIR に「edge-args を持つ terminator 表現」を **併存導入**する(旧 `BasicBlock.jump_args` は残す) +- “読む側” を単一APIに寄せる(`Branch` を含むので “複数 edge” 前提で一本化する) + - 例: `block.out_edges()` / `block.edge_args_to(target)` +- 互換期間は **一致検証を Fail-Fast**(両方ある場合は矛盾で即死) + +### Out of scope(P0ではやらない) + +- `BasicBlock.jump_args` の削除(削除は Phase 261+) +- spans の内部表現を `Vec>` に一気に切替(Phase 261+ で段階導入) +- JoinIR を削除する(builder DSL 降格は長期) + +## 実装タスク(P0) + +1. `MirTerminator`(または既存 terminator に edge-args を持てる variant)を追加(併存導入) + - `Jump` だけでなく `Branch` を含むため、API は “複数 edge” を前提にする +2. bridge が `Jump/Branch` の edge-args を terminator operand としてもセット(旧jump_argsも併記してよい) +3. merge/ExitLine/DCE/verify/printer が参照する入口を一本化(読む側の Strangler) + - 推奨: `block.out_edges()` / `block.edge_args_to(target)` のような API(`edge_args()` 単発は Branch で曖昧) +4. Fail-Fast 契約チェック(`--verify` 時に必須) + - “両方ある場合は一致” を verify で保証 + - 追加: “terminator から計算した successors” と “block.successors キャッシュ” の一致も verify で保証(同期漏れを即死) + +## 受け入れ基準(P0) + +- `cargo build --release` が通る +- `./tools/smokes/v2/run.sh --profile quick` が少なくとも悪化しない(same first FAIL 以上) +- `--verify` の既存テストが壊れない(PHI/CFG検証が健全) +- legacy 依存の “推測” が増えていない(新規の env var 追加なし) +- `rg "jump_args"` を走らせて、移行コードと API 以外に参照が増えていない(読む側の寄せ漏れを検出) +- DCE 回帰が 1 本以上あり、「edge-args だけで使われる値」が消されないことを固定できている + +## ロードマップ(P0→P3) + +### P0(併存導入の芯) + +- **単一参照API**を作る(Branch を含むので “複数 edge” 前提) + - 例: `BasicBlock::out_edges()` / `BasicBlock::edge_args_to(target)` +- MIR terminator に edge-args を持てる表現を追加(旧 `jump_args` と **併存**) + - 推奨: `EdgeArgs { layout: JumpArgsLayout, values: Vec }` のように “意味(layout)” も同梱する +- bridge が edge-args を terminator operand に必ず埋める(旧jump_argsも同内容でセットしてよい) +- merge/ExitLine/DCE/verify/printer は参照点を `out_edges()`/`edge_args_to(...)` に寄せる +- 両方ある場合の **一致検証を Fail-Fast**(`--verify` で必須) + +### P1(切替) + +- `jump_args` を読む経路を段階的に減らす(参照点は `out_edges()`/`edge_args_to(...)` のみ) +- terminator 更新の **API一本化**(successors/preds の同期漏れを構造で潰す) + - 読む側だけでなく、書く側(terminator 設定/edge-args 設定)も API 経由に寄せる +- DCE/verify が terminator operand から自然に use/pred を追えることを固定する + +### P2(削除) + +- `BasicBlock.jump_args` を削除(併存チェックも撤去) +- `jump_args` 特例の DCE/verify コードを削除(terminator operand が SSOT) + +### P3(spans 収束) + +- `instructions` + `instruction_spans` の並行 Vec を段階導入で廃止 + - 先に編集APIを一本化 → 最終的に `Vec>` へ + +## メモ(設計SSOT) + +- 相談パケット: `docs/development/current/main/investigations/phase-259-block-parameterized-cfg-consult.md` +- decisions: `docs/development/current/main/20-Decisions.md` diff --git a/src/backend/mir_interpreter/exec.rs b/src/backend/mir_interpreter/exec.rs index 8f9949a1..a91d9835 100644 --- a/src/backend/mir_interpreter/exec.rs +++ b/src/backend/mir_interpreter/exec.rs @@ -394,7 +394,7 @@ impl MirInterpreter { }; Ok(BlockOutcome::Return(result)) } - Some(MirInstruction::Jump { target }) => Ok(BlockOutcome::Next { + Some(MirInstruction::Jump { target, .. }) => Ok(BlockOutcome::Next { target: *target, predecessor: block.id, }), @@ -402,6 +402,7 @@ impl MirInterpreter { condition, then_bb, else_bb, + .. }) => { // Dev counter: count branch terminators actually evaluated self.branch_count = self.branch_count.saturating_add(1); diff --git a/src/backend/wasm/codegen.rs b/src/backend/wasm/codegen.rs index 4648ae94..deb08fce 100644 --- a/src/backend/wasm/codegen.rs +++ b/src/backend/wasm/codegen.rs @@ -388,7 +388,7 @@ impl WasmCodegen { } // Control Flow Instructions (Critical for loops and conditions) - MirInstruction::Jump { target } => { + MirInstruction::Jump { target, .. } => { // Unconditional jump to target basic block // Use WASM br instruction to break to the target block Ok(vec![format!("br $block_{}", target.as_u32())]) @@ -398,6 +398,7 @@ impl WasmCodegen { condition, then_bb, else_bb, + .. } => { // Conditional branch based on condition value // Load condition value and branch accordingly diff --git a/src/mir/basic_block.rs b/src/mir/basic_block.rs index bcae9234..7f04ea09 100644 --- a/src/mir/basic_block.rs +++ b/src/mir/basic_block.rs @@ -6,6 +6,7 @@ use super::{EffectMask, MirInstruction, SpannedInstRef, SpannedInstruction, ValueId}; use crate::ast::Span; +use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; use crate::runtime::get_global_ring0; use std::collections::BTreeSet; // Phase 69-3: HashSet → BTreeSet for determinism use std::fmt; @@ -42,6 +43,20 @@ impl fmt::Display for BasicBlockId { } } +/// Edge arguments for CFG edges (Phase 260 P0) +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EdgeArgs { + pub layout: JumpArgsLayout, + pub values: Vec, +} + +/// Outgoing edge from a basic block +#[derive(Debug, Clone)] +pub struct OutEdge { + pub target: BasicBlockId, + pub args: Option, +} + /// A basic block in SSA form #[derive(Debug, Clone)] pub struct BasicBlock { @@ -82,6 +97,8 @@ pub struct BasicBlock { /// all the Jump args (not just the first one) so that exit PHI can correctly /// merge carrier values from multiple exit paths. pub jump_args: Option>, + /// Phase 260 P0: Layout for legacy jump_args (for consistency checks) + pub jump_args_layout: Option, } impl BasicBlock { @@ -99,6 +116,7 @@ impl BasicBlock { reachable: false, sealed: false, jump_args: None, // Phase 246-EX: No jump args by default + jump_args_layout: None, // Phase 260 P0: Unknown by default } } @@ -161,18 +179,23 @@ impl BasicBlock { /// Update successors based on the terminator instruction fn update_successors_from_terminator(&mut self) { - self.successors.clear(); + self.successors = self.successors_from_terminator(); + } + + /// Compute successors from the terminator (SSOT for CFG verification) + pub fn successors_from_terminator(&self) -> BTreeSet { + let mut successors = BTreeSet::new(); if let Some(ref terminator) = self.terminator { match terminator { MirInstruction::Branch { then_bb, else_bb, .. } => { - self.successors.insert(*then_bb); - self.successors.insert(*else_bb); + successors.insert(*then_bb); + successors.insert(*else_bb); } - MirInstruction::Jump { target } => { - self.successors.insert(*target); + MirInstruction::Jump { target, .. } => { + successors.insert(*target); } MirInstruction::Return { .. } => { // No successors for return @@ -184,6 +207,140 @@ impl BasicBlock { _ => unreachable!("Non-terminator instruction in terminator position"), } } + + successors + } + + /// Enumerate all outgoing CFG edges + pub fn out_edges(&self) -> Vec { + match self.terminator { + Some(MirInstruction::Branch { + then_bb, + else_bb, + ref then_edge_args, + ref else_edge_args, + .. + }) => vec![ + OutEdge { + target: then_bb, + args: then_edge_args.clone(), + }, + OutEdge { + target: else_bb, + args: else_edge_args.clone(), + }, + ], + Some(MirInstruction::Jump { + target, + ref edge_args, + .. + }) => vec![OutEdge { + target, + args: edge_args.clone().or_else(|| self.legacy_edge_args()), + }], + _ => Vec::new(), + } + } + + /// Get edge args for a specific target (if present) + pub fn edge_args_to(&self, target: BasicBlockId) -> Option { + self.out_edges() + .into_iter() + .find(|edge| edge.target == target) + .and_then(|edge| edge.args) + } + + /// Set legacy jump args metadata (migration helper) + pub fn set_legacy_jump_args(&mut self, values: Vec, layout: Option) { + self.jump_args = Some(values); + self.jump_args_layout = layout; + } + + /// Clear legacy jump args metadata (migration helper) + pub fn clear_legacy_jump_args(&mut self) { + self.jump_args = None; + self.jump_args_layout = None; + } + + /// Check if legacy jump args metadata exists + pub fn has_legacy_jump_args(&self) -> bool { + self.jump_args.is_some() + } + + /// Access legacy jump args values (read-only, migration helper) + pub fn legacy_jump_args_values(&self) -> Option<&[ValueId]> { + self.jump_args.as_deref() + } + + /// Access legacy jump args layout (read-only, migration helper) + pub fn legacy_jump_args_layout(&self) -> Option { + self.jump_args_layout + } + + /// Build edge args from legacy values with an explicit layout (migration helper) + pub fn legacy_edge_args_with_layout(&self, layout: JumpArgsLayout) -> Option { + self.jump_args.as_ref().map(|values| EdgeArgs { + layout, + values: values.clone(), + }) + } + + /// Set jump terminator with edge args and legacy metadata (SSOT write helper) + pub fn set_jump_with_edge_args(&mut self, target: BasicBlockId, edge_args: Option) { + let terminator = MirInstruction::Jump { + target, + edge_args: edge_args.clone(), + }; + if !self.is_terminator(&terminator) { + panic!("Instruction is not a valid terminator: {:?}", terminator); + } + + self.effects = self.effects | terminator.effects(); + self.terminator = Some(terminator); + self.terminator_span = Some(Span::unknown()); + if let Some(args) = edge_args { + self.jump_args = Some(args.values.clone()); + self.jump_args_layout = Some(args.layout); + } else { + self.clear_legacy_jump_args(); + } + self.update_successors_from_terminator(); + } + + /// Set branch terminator with per-edge args (clears legacy metadata) + pub fn set_branch_with_edge_args( + &mut self, + condition: ValueId, + then_bb: BasicBlockId, + then_edge_args: Option, + else_bb: BasicBlockId, + else_edge_args: Option, + ) { + let terminator = MirInstruction::Branch { + condition, + then_bb, + else_bb, + then_edge_args, + else_edge_args, + }; + if !self.is_terminator(&terminator) { + panic!("Instruction is not a valid terminator: {:?}", terminator); + } + + self.effects = self.effects | terminator.effects(); + self.terminator = Some(terminator); + self.terminator_span = Some(Span::unknown()); + self.clear_legacy_jump_args(); + self.update_successors_from_terminator(); + } + + fn legacy_edge_args(&self) -> Option { + let values = self.jump_args.as_ref()?; + let layout = self.jump_args_layout?; + Some(EdgeArgs { + layout, + values: values.clone(), + }) } /// Add a predecessor @@ -540,6 +697,8 @@ mod tests { condition: ValueId::new(0), then_bb, else_bb, + then_edge_args: None, + else_edge_args: None, }; bb.add_instruction(branch_inst); diff --git a/src/mir/builder.rs b/src/mir/builder.rs index 7aa9041d..47130a0b 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -670,7 +670,7 @@ impl MirBuilder { MirInstruction::Branch { then_bb, else_bb, .. } => (Some(*then_bb), Some(*else_bb), None), - MirInstruction::Jump { target } => (None, None, Some(*target)), + MirInstruction::Jump { target, .. } => (None, None, Some(*target)), _ => (None, None, None), }; @@ -784,8 +784,9 @@ impl MirBuilder { condition, then_bb, else_bb, + .. } => format!("br {}, {}, {}", condition, then_bb, else_bb), - MirInstruction::Jump { target } => format!("br {}", target), + MirInstruction::Jump { target, .. } => format!("br {}", target), _ => format!("{:?}", instruction), } ); diff --git a/src/mir/builder/control_flow/joinir/api/entry.rs b/src/mir/builder/control_flow/joinir/api/entry.rs new file mode 100644 index 00000000..69a7f55b --- /dev/null +++ b/src/mir/builder/control_flow/joinir/api/entry.rs @@ -0,0 +1,14 @@ +//! Entry function helpers (SSOT) + +use crate::mir::join_ir::{JoinFunction, JoinModule}; + +/// Extract entry function from JoinModule (SSOT). +/// +/// Priority: `join_module.entry` → fallback to `"main"`. +pub(in crate::mir::builder) fn get_entry_function<'a>( + join_module: &'a JoinModule, + context: &str, +) -> Result<&'a JoinFunction, String> { + // Re-exported from patterns/common as the SSOT entry point. + super::super::patterns::common::get_entry_function(join_module, context) +} diff --git a/src/mir/builder/control_flow/joinir/api/mod.rs b/src/mir/builder/control_flow/joinir/api/mod.rs new file mode 100644 index 00000000..9f569273 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/api/mod.rs @@ -0,0 +1,14 @@ +//! JoinIR integration API (SSOT entry points) +//! +//! Purpose: provide a small, stable surface for pattern lowerers and merge code. +//! This reduces "where should I call this from?" drift and avoids re-implementing +//! contract logic (SSOT, fail-fast checks) in each pattern. +//! +//! Policy: +//! - Prefer SSOT helpers over ad-hoc logic in patterns. +//! - Avoid guessing (order/layout/name) in callers; callers pass explicit intent. +//! - Keep this module thin: mostly wrappers/re-exports with clear naming. + +pub(in crate::mir::builder) mod entry; +pub(in crate::mir::builder) mod pipeline_contracts; +pub(in crate::mir::builder) mod receiver; diff --git a/src/mir/builder/control_flow/joinir/api/pipeline_contracts.rs b/src/mir/builder/control_flow/joinir/api/pipeline_contracts.rs new file mode 100644 index 00000000..1b8e0d2d --- /dev/null +++ b/src/mir/builder/control_flow/joinir/api/pipeline_contracts.rs @@ -0,0 +1,16 @@ +//! Pipeline contract helpers (SSOT) + +use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; +use crate::mir::join_ir::JoinModule; + +/// Run all JoinIR pipeline contract checks (SSOT). +/// +/// This is the preferred entry point for patterns that need to validate boundary +/// assumptions before bridge/merge. +pub(in crate::mir::builder) fn run_all_pipeline_checks( + join_module: &JoinModule, + boundary: &JoinInlineBoundary, +) -> Result<(), String> { + super::super::merge::contract_checks::run_all_pipeline_checks(join_module, boundary) +} + diff --git a/src/mir/builder/control_flow/joinir/api/receiver.rs b/src/mir/builder/control_flow/joinir/api/receiver.rs new file mode 100644 index 00000000..a124885f --- /dev/null +++ b/src/mir/builder/control_flow/joinir/api/receiver.rs @@ -0,0 +1,20 @@ +//! Receiver helpers (SSOT) +//! +//! Motivation: surface syntax uses `this` / `me`, but the MIR builder's `variable_map` +//! registers the receiver under a specific key. Patterns must not guess that key. + +use crate::mir::builder::MirBuilder; +use crate::mir::ValueId; + +/// Return the host ValueId for `me` receiver (SSOT). +/// +/// Patterns should use this instead of hardcoding `"me"` / `"this"`. +pub(in crate::mir::builder) fn get_me_host_id(builder: &MirBuilder, context: &str) -> Result { + builder + .variable_ctx + .variable_map + .get("me") + .copied() + .ok_or_else(|| format!("[{}] receiver 'me' not found in variable_map", context)) +} + diff --git a/src/mir/builder/control_flow/joinir/merge/contract_checks.rs b/src/mir/builder/control_flow/joinir/merge/contract_checks.rs index 6c6936a3..200388ab 100644 --- a/src/mir/builder/control_flow/joinir/merge/contract_checks.rs +++ b/src/mir/builder/control_flow/joinir/merge/contract_checks.rs @@ -33,7 +33,7 @@ pub(super) fn verify_all_terminator_targets_exist( let Some(term) = &block.terminator else { continue }; match term { - MirInstruction::Jump { target } => { + MirInstruction::Jump { target, .. } => { if !func.blocks.contains_key(target) && !contracts.allowed_missing_jump_targets.contains(target) { @@ -749,13 +749,21 @@ mod tests { let bb2 = BasicBlockId(2); let func = create_test_function(vec![ - (bb0, Some(MirInstruction::Jump { target: bb1 })), + ( + bb0, + Some(MirInstruction::Jump { + target: bb1, + edge_args: None, + }), + ), ( bb1, Some(MirInstruction::Branch { condition: ValueId(0), then_bb: bb2, else_bb: bb2, + then_edge_args: None, + else_edge_args: None, }), ), (bb2, Some(MirInstruction::Return { value: None })), @@ -775,7 +783,13 @@ mod tests { let bb0 = BasicBlockId(0); let bb99 = BasicBlockId(99); // Missing block - let func = create_test_function(vec![(bb0, Some(MirInstruction::Jump { target: bb99 }))]); + let func = create_test_function(vec![( + bb0, + Some(MirInstruction::Jump { + target: bb99, + edge_args: None, + }), + )]); let contracts = MergeContracts { allowed_missing_jump_targets: vec![], @@ -795,7 +809,13 @@ mod tests { let bb0 = BasicBlockId(0); let bb_exit = BasicBlockId(100); // Missing but allowed - let func = create_test_function(vec![(bb0, Some(MirInstruction::Jump { target: bb_exit }))]); + let func = create_test_function(vec![( + bb0, + Some(MirInstruction::Jump { + target: bb_exit, + edge_args: None, + }), + )]); let contracts = MergeContracts { allowed_missing_jump_targets: vec![bb_exit], diff --git a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs index 6a7ab16c..38d96620 100644 --- a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs +++ b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs @@ -391,7 +391,7 @@ pub(super) fn merge_and_rewrite( // We skip merging continuation functions, so any tail-call to k_exit must be // lowered as an exit jump to `exit_block_id` (and contribute exit values). let mut k_exit_lowering_decision: Option = None; - let mut k_exit_jump_args: Option> = None; + let mut k_exit_edge_args: Option = None; // Phase 177-3: Check if this block is the loop header with PHI nodes let is_loop_header_with_phi = @@ -488,7 +488,10 @@ pub(super) fn merge_and_rewrite( if let Some(decision) = tail_call_policy.classify_tail_call(callee_name, &remapped_args) { // This is a k_exit tail call - policy says normalize to exit jump k_exit_lowering_decision = Some(decision); - k_exit_jump_args = old_block.jump_args.clone(); + if let Some(b) = boundary { + k_exit_edge_args = + old_block.legacy_edge_args_with_layout(b.jump_args_layout); + } found_tail_call = true; if debug { log!( @@ -554,6 +557,8 @@ pub(super) fn merge_and_rewrite( condition, then_bb, else_bb, + then_edge_args, + else_edge_args, } => { let remapped_then = skipped_entry_redirects .get(&then_bb) @@ -569,6 +574,8 @@ pub(super) fn merge_and_rewrite( condition, then_bb: remapped_then, else_bb: remapped_else, + then_edge_args, + else_edge_args, } } MirInstruction::Phi { dst, inputs, type_hint } => { @@ -950,8 +957,9 @@ pub(super) fn merge_and_rewrite( } }; - new_block.terminator = Some(MirInstruction::Jump { + new_block.set_terminator(MirInstruction::Jump { target: actual_target, + edge_args: None, }); // DEBUG: Print final state after adding parameter bindings @@ -979,7 +987,18 @@ pub(super) fn merge_and_rewrite( // Remap terminator (convert Return → Jump to exit) if not already set by tail call if !found_tail_call { if let Some(ref term) = old_block.terminator { - let remapped_term = match term { + let remap_edge_args = |edge_args: &Option| { + edge_args.as_ref().map(|args| crate::mir::EdgeArgs { + layout: args.layout, + values: args + .values + .iter() + .map(|&v| remapper.remap_value(v)) + .collect(), + }) + }; + let mut remapped_term: Option = None; + match term { MirInstruction::Return { value } => { // Convert Return to Jump to exit block // All functions return to same exit block (Phase 189) @@ -988,43 +1007,54 @@ pub(super) fn merge_and_rewrite( // // The JoinIR Jump instruction passes ALL carrier values in its args, // but the JoinIR→MIR conversion in joinir_block_converter only preserved - // the first arg in the Return value. We now use the jump_args metadata + // the first arg in the Return value. We now use the legacy jump_args metadata // to recover all the original Jump args. - // - if let Some(_ret_val) = value { - // Phase 246-EX: Check if this block has jump_args metadata - if let Some(ref jump_args) = old_block.jump_args { - log!( - verbose, - "[DEBUG-177] Phase 246-EX: Block {:?} has jump_args metadata: {:?}", - old_block.id, jump_args - ); + let mut exit_edge_args: Option = None; + if value.is_some() { + if let Some(b) = boundary { + // Phase 246-EX: Check if this block has legacy jump_args metadata + if let Some(edge_args) = + old_block.legacy_edge_args_with_layout(b.jump_args_layout) + { + log!( + verbose, + "[DEBUG-177] Phase 246-EX: Block {:?} has legacy jump_args metadata: {:?}", + old_block.id, edge_args.values + ); - // The jump_args are in JoinIR value space, remap them to HOST - let remapped_args: Vec = jump_args - .iter() - .map(|&arg| remapper.remap_value(arg)) - .collect(); + // The jump_args are in JoinIR value space, remap them to HOST + let remapped_args: Vec = edge_args + .values + .iter() + .map(|&arg| remapper.remap_value(arg)) + .collect(); - log!( - verbose, - "[DEBUG-177] Phase 246-EX: Remapped jump_args: {:?}", - remapped_args - ); + log!( + verbose, + "[DEBUG-177] Phase 246-EX: Remapped jump_args: {:?}", + remapped_args + ); + + // Phase 118 P2: Use ExitArgsCollectorBox to collect exit values + let edge_args = crate::mir::EdgeArgs { + layout: edge_args.layout, + values: remapped_args, + }; + exit_edge_args = Some(edge_args.clone()); - // Phase 118 P2: Use ExitArgsCollectorBox to collect exit values - if let Some(b) = boundary { let collector = ExitArgsCollectorBox::new(); let collection_result = collector.collect( &b.exit_bindings, - &remapped_args, + &edge_args.values, new_block_id, strict_exit, - b.jump_args_layout, + edge_args.layout, )?; // Add expr_result to exit_phi_inputs (if present) - if let Some(expr_result_val) = collection_result.expr_result_value { + if let Some(expr_result_val) = + collection_result.expr_result_value + { exit_phi_inputs.push((new_block_id, expr_result_val)); log!( verbose, @@ -1034,7 +1064,9 @@ pub(super) fn merge_and_rewrite( } // Add carrier values to carrier_inputs - for (carrier_name, (block_id, value_id)) in collection_result.carrier_values { + for (carrier_name, (block_id, value_id)) in + collection_result.carrier_values + { carrier_inputs .entry(carrier_name.clone()) .or_insert_with(Vec::new) @@ -1045,16 +1077,14 @@ pub(super) fn merge_and_rewrite( carrier_name, block_id, value_id ); } - } - } else { - // Fallback: Use header PHI dst (old behavior for blocks without jump_args) - log!( - verbose, - "[DEBUG-177] Phase 246-EX: Block {:?} has NO jump_args, using header PHI fallback", - old_block.id - ); + } else { + // Fallback: Use header PHI dst (old behavior for blocks without jump_args) + log!( + verbose, + "[DEBUG-177] Phase 246-EX: Block {:?} has NO jump_args, using header PHI fallback", + old_block.id + ); - if let Some(b) = boundary { if let Some(loop_var_name) = &b.loop_var_name { if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(loop_var_name) @@ -1102,11 +1132,16 @@ pub(super) fn merge_and_rewrite( } } - MirInstruction::Jump { - target: exit_block_id, + if let Some(edge_args) = exit_edge_args { + new_block.set_jump_with_edge_args(exit_block_id, Some(edge_args)); + } else { + new_block.set_terminator(MirInstruction::Jump { + target: exit_block_id, + edge_args: None, + }); } } - MirInstruction::Jump { target } => { + MirInstruction::Jump { target, edge_args } => { // Phase 189 FIX: Remap block ID for Jump // Phase 259 P0 FIX: Check skipped_entry_redirects first (for k_exit blocks) let remapped_target = skipped_entry_redirects @@ -1114,14 +1149,17 @@ pub(super) fn merge_and_rewrite( .or_else(|| local_block_map.get(target)) .copied() .unwrap_or(*target); - MirInstruction::Jump { + remapped_term = Some(MirInstruction::Jump { target: remapped_target, - } + edge_args: remap_edge_args(edge_args), + }); } MirInstruction::Branch { condition, then_bb, else_bb, + then_edge_args, + else_edge_args, } => { // Phase 189 FIX: Remap block IDs AND condition ValueId for Branch // Phase 259 P0 FIX: Check skipped_entry_redirects first (for k_exit blocks) @@ -1135,15 +1173,46 @@ pub(super) fn merge_and_rewrite( .or_else(|| local_block_map.get(else_bb)) .copied() .unwrap_or(*else_bb); - MirInstruction::Branch { + remapped_term = Some(MirInstruction::Branch { condition: remapper.remap_value(*condition), then_bb: remapped_then, else_bb: remapped_else, - } + then_edge_args: remap_edge_args(then_edge_args), + else_edge_args: remap_edge_args(else_edge_args), + }); } - _ => remapper.remap_instruction(term), - }; - new_block.terminator = Some(remapped_term); + _ => remapped_term = Some(remapper.remap_instruction(term)), + } + if let Some(term) = remapped_term { + match term { + MirInstruction::Jump { target, edge_args } => { + if edge_args.is_some() { + new_block.set_jump_with_edge_args(target, edge_args); + } else { + new_block.set_terminator(MirInstruction::Jump { + target, + edge_args: None, + }); + } + } + MirInstruction::Branch { + condition, + then_bb, + then_edge_args, + else_bb, + else_edge_args, + } => { + new_block.set_branch_with_edge_args( + condition, + then_bb, + then_edge_args, + else_bb, + else_edge_args, + ); + } + _ => new_block.set_terminator(term), + } + } } } @@ -1153,23 +1222,31 @@ pub(super) fn merge_and_rewrite( match decision { LoweringDecision::NormalizeToExitJump { args } => { // Collect exit values from k_exit arguments + let mut exit_edge_args: Option = None; if let Some(b) = boundary { let collector = ExitArgsCollectorBox::new(); - let exit_args: Vec = if let Some(ref jump_args) = k_exit_jump_args { - jump_args - .iter() - .map(|&arg| remapper.remap_value(arg)) - .collect() - } else { - args.clone() + let exit_values: Vec = + if let Some(ref edge_args) = k_exit_edge_args { + edge_args + .values + .iter() + .map(|&arg| remapper.remap_value(arg)) + .collect() + } else { + args.clone() + }; + let edge_args = crate::mir::EdgeArgs { + layout: b.jump_args_layout, + values: exit_values, }; + exit_edge_args = Some(edge_args.clone()); let collection_result = collector.collect( &b.exit_bindings, - &exit_args, + &edge_args.values, new_block_id, strict_exit, - b.jump_args_layout, + edge_args.layout, )?; if let Some(expr_result_value) = collection_result.expr_result_value { exit_phi_inputs.push((collection_result.block_id, expr_result_value)); @@ -1189,8 +1266,17 @@ pub(super) fn merge_and_rewrite( } // Generate exit jump using policy box - let exit_jump = tail_call_policy.rewrite_to_exit_jump(exit_block_id); - new_block.terminator = Some(exit_jump.clone()); + let exit_jump = if let Some(edge_args) = exit_edge_args { + new_block.set_jump_with_edge_args(exit_block_id, Some(edge_args)); + new_block.terminator.clone().unwrap_or(MirInstruction::Jump { + target: exit_block_id, + edge_args: None, + }) + } else { + let exit_jump = tail_call_policy.rewrite_to_exit_jump(exit_block_id); + new_block.set_terminator(exit_jump.clone()); + exit_jump + }; // Strict mode: verify the generated terminator matches contract if strict_exit { diff --git a/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs b/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs index 51d27939..dd902cd7 100644 --- a/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs +++ b/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs @@ -361,7 +361,7 @@ impl LoopHeaderPhiBuilder { ); } } - crate::mir::MirInstruction::Jump { target } => { + crate::mir::MirInstruction::Jump { target, .. } => { block.successors.insert(*target); if dev_debug { trace.stderr_if( diff --git a/src/mir/builder/control_flow/joinir/merge/tail_call_lowering_policy.rs b/src/mir/builder/control_flow/joinir/merge/tail_call_lowering_policy.rs index e642fe7c..1d83164d 100644 --- a/src/mir/builder/control_flow/joinir/merge/tail_call_lowering_policy.rs +++ b/src/mir/builder/control_flow/joinir/merge/tail_call_lowering_policy.rs @@ -96,6 +96,7 @@ impl TailCallLoweringPolicyBox { pub fn rewrite_to_exit_jump(&self, exit_block_id: BasicBlockId) -> MirInstruction { MirInstruction::Jump { target: exit_block_id, + edge_args: None, } } @@ -121,8 +122,8 @@ impl TailCallLoweringPolicyBox { exit_block_id: BasicBlockId, ) -> Result<(), String> { match terminator { - MirInstruction::Jump { target } if *target == exit_block_id => Ok(()), - MirInstruction::Jump { target } => Err(crate::mir::join_ir::lowering::error_tags::freeze_with_hint( + MirInstruction::Jump { target, .. } if *target == exit_block_id => Ok(()), + MirInstruction::Jump { target, .. } => Err(crate::mir::join_ir::lowering::error_tags::freeze_with_hint( "phase131/k_exit/wrong_jump_target", &format!( "skippable continuation tail call lowered to Jump {:?}, expected exit_block_id {:?}", @@ -184,7 +185,7 @@ mod tests { let jump = policy.rewrite_to_exit_jump(exit_block); assert!(matches!( jump, - MirInstruction::Jump { target } if target == exit_block + MirInstruction::Jump { target, .. } if target == exit_block )); } @@ -194,6 +195,7 @@ mod tests { let exit_block = BasicBlockId(42); let jump = MirInstruction::Jump { target: exit_block, + edge_args: None, }; let result = policy.verify_exit_jump(&jump, exit_block); @@ -206,6 +208,7 @@ mod tests { let exit_block = BasicBlockId(42); let wrong_jump = MirInstruction::Jump { target: BasicBlockId(99), + edge_args: None, }; let result = policy.verify_exit_jump(&wrong_jump, exit_block); diff --git a/src/mir/builder/control_flow/joinir/merge/tests/continuation_contract.rs b/src/mir/builder/control_flow/joinir/merge/tests/continuation_contract.rs index d4120bbd..5ecc659b 100644 --- a/src/mir/builder/control_flow/joinir/merge/tests/continuation_contract.rs +++ b/src/mir/builder/control_flow/joinir/merge/tests/continuation_contract.rs @@ -33,7 +33,7 @@ fn case_a_pure_k_exit_return_is_skippable() { let mut func = make_function("join_func_2"); let block = func.blocks.get_mut(&func.entry_block).unwrap(); block.instructions.clear(); - block.terminator = Some(MirInstruction::Return { value: None }); + block.set_terminator(MirInstruction::Return { value: None }); assert!(is_skippable_continuation(&func)); } @@ -52,8 +52,9 @@ fn case_b_k_exit_tailcall_post_k_is_not_skippable() { args: vec![], effects: EffectMask::CONTROL, }); - block.terminator = Some(MirInstruction::Jump { + block.set_terminator(MirInstruction::Jump { target: BasicBlockId(1), + edge_args: None, }); assert!(!is_skippable_continuation(&func)); } @@ -67,14 +68,15 @@ fn case_c_multi_block_continuation_is_not_skippable() { // Add a second block let second_block_id = BasicBlockId(1); let mut second_block = crate::mir::BasicBlock::new(second_block_id); - second_block.terminator = Some(MirInstruction::Return { value: None }); + second_block.set_terminator(MirInstruction::Return { value: None }); func.blocks.insert(second_block_id, second_block); // Entry block jumps to second block let entry_block = func.blocks.get_mut(&func.entry_block).unwrap(); entry_block.instructions.clear(); - entry_block.terminator = Some(MirInstruction::Jump { + entry_block.set_terminator(MirInstruction::Jump { target: second_block_id, + edge_args: None, }); assert!(!is_skippable_continuation(&func)); @@ -91,6 +93,6 @@ fn case_d_continuation_with_instructions_is_not_skippable() { dst: ValueId(0), value: crate::mir::types::ConstValue::Integer(42), }); - block.terminator = Some(MirInstruction::Return { value: None }); + block.set_terminator(MirInstruction::Return { value: None }); assert!(!is_skippable_continuation(&func)); } diff --git a/src/mir/builder/control_flow/joinir/mod.rs b/src/mir/builder/control_flow/joinir/mod.rs index 9da34838..accbb0e4 100644 --- a/src/mir/builder/control_flow/joinir/mod.rs +++ b/src/mir/builder/control_flow/joinir/mod.rs @@ -10,6 +10,7 @@ //! - Control tree capability guard (control_tree_capability_guard.rs) ✅ Phase 112 pub(in crate::mir::builder) mod control_tree_capability_guard; +pub(in crate::mir::builder) mod api; pub(in crate::mir::builder) mod legacy; // Phase 132-R0 Task 4: Legacy routing isolation pub(in crate::mir::builder) mod loop_context; pub(in crate::mir::builder) mod merge; diff --git a/src/mir/builder/emission/branch.rs b/src/mir/builder/emission/branch.rs index 7351b015..a3610a27 100644 --- a/src/mir/builder/emission/branch.rs +++ b/src/mir/builder/emission/branch.rs @@ -18,6 +18,8 @@ pub fn emit_conditional( condition: cond, then_bb, else_bb, + then_edge_args: None, + else_edge_args: None, }) } } @@ -28,6 +30,9 @@ pub fn emit_jump(b: &mut MirBuilder, target: BasicBlockId) -> Result<(), String> crate::mir::ssot::cf_common::set_jump(func, cur_bb, target); Ok(()) } else { - b.emit_instruction(MirInstruction::Jump { target }) + b.emit_instruction(MirInstruction::Jump { + target, + edge_args: None, + }) } } diff --git a/src/mir/builder/joinir_id_remapper.rs b/src/mir/builder/joinir_id_remapper.rs index 1d66cbb3..8387deb9 100644 --- a/src/mir/builder/joinir_id_remapper.rs +++ b/src/mir/builder/joinir_id_remapper.rs @@ -102,7 +102,25 @@ impl JoinIrIdRemapper { vals.extend(args.iter().copied()); vals } - Branch { condition, .. } => vec![*condition], + Branch { + condition, + then_edge_args, + else_edge_args, + .. + } => { + let mut vals = vec![*condition]; + if let Some(args) = then_edge_args { + vals.extend(args.values.iter().copied()); + } + if let Some(args) = else_edge_args { + vals.extend(args.values.iter().copied()); + } + vals + } + Jump { edge_args, .. } => edge_args + .as_ref() + .map(|args| args.values.clone()) + .unwrap_or_default(), Return { value } => value.iter().copied().collect(), Phi { dst, inputs, .. } => { let mut vals = vec![*dst]; @@ -177,8 +195,15 @@ impl JoinIrIdRemapper { /// 命令を新しい ID空間にリマップ pub fn remap_instruction(&self, inst: &MirInstruction) -> MirInstruction { use crate::mir::MirInstruction::*; + use crate::mir::EdgeArgs; let remap = |v: ValueId| self.value_map.get(&v).copied().unwrap_or(v); + let remap_edge_args = |edge_args: &Option| { + edge_args.as_ref().map(|args| EdgeArgs { + layout: args.layout, + values: args.values.iter().map(|&v| remap(v)).collect(), + }) + }; match inst { Const { dst, value } => Const { @@ -431,8 +456,28 @@ impl JoinIrIdRemapper { then_val: remap(*then_val), else_val: remap(*else_val), }, - // Pass through unchanged (Branch/Jump/Return handled separately) - Branch { .. } | Jump { .. } | Return { .. } | Nop | Safepoint => inst.clone(), + Branch { + condition, + then_bb, + else_bb, + then_edge_args, + else_edge_args, + } => Branch { + condition: remap(*condition), + then_bb: *then_bb, + else_bb: *else_bb, + then_edge_args: remap_edge_args(then_edge_args), + else_edge_args: remap_edge_args(else_edge_args), + }, + Jump { target, edge_args } => Jump { + target: *target, + edge_args: remap_edge_args(edge_args), + }, + Return { value } => Return { + value: value.map(remap), + }, + // Pass through unchanged + Nop | Safepoint => inst.clone(), } } diff --git a/src/mir/control_tree/normalized_shadow/loop_true_break_once.rs b/src/mir/control_tree/normalized_shadow/loop_true_break_once.rs index 80a23437..406cefe9 100644 --- a/src/mir/control_tree/normalized_shadow/loop_true_break_once.rs +++ b/src/mir/control_tree/normalized_shadow/loop_true_break_once.rs @@ -728,7 +728,7 @@ mod tests { "k_exit must return Some(value)" ); - // Bridge sanity: tail-call blocks must carry jump_args metadata for merge collection. + // Bridge sanity: tail-call blocks must carry legacy jump_args metadata for merge collection. // This is required for DirectValue mode (no PHI) to reconnect carriers safely. let mir_module = crate::mir::join_ir_vm_bridge::bridge_joinir_to_mir(&module) .expect("bridge_joinir_to_mir failed"); @@ -744,8 +744,8 @@ mod tests { .get(&entry) .expect("missing loop_body entry block"); assert!( - entry_block.jump_args.is_some(), - "loop_body entry block must have jump_args metadata in bridged MIR" + entry_block.has_legacy_jump_args(), + "loop_body entry block must have legacy jump_args metadata in bridged MIR" ); // Loop-only (the routing path in real lowering): still must encode loop_step as a tail-call. diff --git a/src/mir/function.rs b/src/mir/function.rs index d37bd904..9e90c4d7 100644 --- a/src/mir/function.rs +++ b/src/mir/function.rs @@ -335,7 +335,10 @@ impl MirFunction { ) -> Result<(), String> { if let Some(bb) = self.get_block_mut(bb_id) { if !bb.is_terminated() { - bb.set_terminator(MirInstruction::Jump { target }); + bb.set_terminator(MirInstruction::Jump { + target, + edge_args: None, + }); } Ok(()) } else { @@ -357,6 +360,8 @@ impl MirFunction { condition, then_bb, else_bb, + then_edge_args: None, + else_edge_args: None, }); Ok(()) } else { diff --git a/src/mir/function_emission.rs b/src/mir/function_emission.rs index 3d8a988f..da27a7da 100644 --- a/src/mir/function_emission.rs +++ b/src/mir/function_emission.rs @@ -63,6 +63,9 @@ pub fn emit_return_value(f: &mut MirFunction, bb: BasicBlockId, value: ValueId) #[inline] pub fn emit_jump(f: &mut MirFunction, bb: BasicBlockId, target: BasicBlockId) { if let Some(block) = f.get_block_mut(bb) { - block.add_instruction(MirInstruction::Jump { target }); + block.add_instruction(MirInstruction::Jump { + target, + edge_args: None, + }); } } diff --git a/src/mir/instruction.rs b/src/mir/instruction.rs index 14abdf4e..9a397244 100644 --- a/src/mir/instruction.rs +++ b/src/mir/instruction.rs @@ -4,7 +4,7 @@ * SSA-form instructions with effect tracking for optimization */ -use super::{EffectMask, ValueId}; +use super::{EdgeArgs, EffectMask, ValueId}; use crate::mir::definitions::Callee; // Import Callee from unified definitions use crate::mir::types::{ BarrierOp, BinaryOp, CompareOp, ConstValue, MirType, TypeOpKind, UnaryOp, WeakRefOp, @@ -116,11 +116,19 @@ pub enum MirInstruction { condition: ValueId, then_bb: super::BasicBlockId, else_bb: super::BasicBlockId, + /// Optional edge args for then branch (Phase 260 P0) + then_edge_args: Option, + /// Optional edge args for else branch (Phase 260 P0) + else_edge_args: Option, }, /// Unconditional jump /// `jmp %target_bb` - Jump { target: super::BasicBlockId }, + Jump { + target: super::BasicBlockId, + /// Optional edge args for jump (Phase 260 P0) + edge_args: Option, + }, /// Return from function /// `ret %value` or `ret void` diff --git a/src/mir/instruction/methods.rs b/src/mir/instruction/methods.rs index bec6ecc5..098a594d 100644 --- a/src/mir/instruction/methods.rs +++ b/src/mir/instruction/methods.rs @@ -176,6 +176,31 @@ impl MirInstruction { return used; } + match self { + MirInstruction::Branch { + condition, + then_edge_args, + else_edge_args, + .. + } => { + let mut used = vec![*condition]; + if let Some(args) = then_edge_args { + used.extend(args.values.iter().copied()); + } + if let Some(args) = else_edge_args { + used.extend(args.values.iter().copied()); + } + return used; + } + MirInstruction::Jump { edge_args, .. } => { + return edge_args + .as_ref() + .map(|args| args.values.clone()) + .unwrap_or_default(); + } + _ => {} + } + if let Some(used) = inst_meta::used_via_meta(self) { return used; } diff --git a/src/mir/join_ir/lowering/if_merge.rs b/src/mir/join_ir/lowering/if_merge.rs index a95eb525..ddcad92b 100644 --- a/src/mir/join_ir/lowering/if_merge.rs +++ b/src/mir/join_ir/lowering/if_merge.rs @@ -132,6 +132,7 @@ impl IfMergeLowerer { condition, then_bb, else_bb, + .. } => IfBranch { cond: *condition, then_block: *then_bb, diff --git a/src/mir/join_ir/lowering/if_select.rs b/src/mir/join_ir/lowering/if_select.rs index 63ef086c..24824b2c 100644 --- a/src/mir/join_ir/lowering/if_select.rs +++ b/src/mir/join_ir/lowering/if_select.rs @@ -142,6 +142,7 @@ impl IfSelectLowerer { condition, then_bb, else_bb, + .. } => IfBranch { cond: *condition, then_block: *then_bb, @@ -263,13 +264,13 @@ impl IfSelectLowerer { ) -> Option { // then が Jump で終わるか確認 let then_target = match then_block.terminator.as_ref()? { - MirInstruction::Jump { target } => *target, + MirInstruction::Jump { target, .. } => *target, _ => return None, }; // else が Jump で終わるか確認 let else_target = match else_block.terminator.as_ref()? { - MirInstruction::Jump { target } => *target, + MirInstruction::Jump { target, .. } => *target, _ => return None, }; @@ -312,7 +313,7 @@ impl IfSelectLowerer { // then ブロックが Jump で終わるか確認 let merge_block_id = match then_block.terminator.as_ref()? { - MirInstruction::Jump { target } => *target, + MirInstruction::Jump { target, .. } => *target, _ => return None, }; @@ -339,7 +340,7 @@ impl IfSelectLowerer { // else ブロックも同じ merge ブロックに Jump するか確認 let else_merge = match else_block.terminator.as_ref()? { - MirInstruction::Jump { target } => *target, + MirInstruction::Jump { target, .. } => *target, _ => return None, }; diff --git a/src/mir/join_ir_vm_bridge/joinir_block_converter.rs b/src/mir/join_ir_vm_bridge/joinir_block_converter.rs index dd35de4b..7e1712e7 100644 --- a/src/mir/join_ir_vm_bridge/joinir_block_converter.rs +++ b/src/mir/join_ir_vm_bridge/joinir_block_converter.rs @@ -268,6 +268,8 @@ impl JoinIrBlockConverter { condition: *cond, then_bb: then_block, else_bb: else_block, + then_edge_args: None, + else_edge_args: None, }; Self::finalize_block( mir_func, @@ -290,8 +292,9 @@ impl JoinIrBlockConverter { effects: EffectMask::WRITE, }); then_block_obj.instruction_spans.push(Span::unknown()); - then_block_obj.terminator = Some(MirInstruction::Jump { + then_block_obj.set_terminator(MirInstruction::Jump { target: merge_block, + edge_args: None, }); mir_func.blocks.insert(then_block, then_block_obj); @@ -302,8 +305,9 @@ impl JoinIrBlockConverter { src: *receiver, }); else_block_obj.instruction_spans.push(Span::unknown()); - else_block_obj.terminator = Some(MirInstruction::Jump { + else_block_obj.set_terminator(MirInstruction::Jump { target: merge_block, + edge_args: None, }); mir_func.blocks.insert(else_block, else_block_obj); @@ -428,8 +432,8 @@ impl JoinIrBlockConverter { // Without this, tail-call blocks look like "no args", forcing fallbacks that can // produce undefined ValueIds in DirectValue mode. if let Some(block) = mir_func.blocks.get_mut(&self.current_block_id) { - if block.jump_args.is_none() { - block.jump_args = Some(args.to_vec()); + if !block.has_legacy_jump_args() { + block.set_legacy_jump_args(args.to_vec(), None); } } @@ -484,6 +488,8 @@ impl JoinIrBlockConverter { condition: *cond_var, then_bb: exit_block_id, else_bb: continue_block_id, + then_edge_args: None, + else_edge_args: None, }; Self::finalize_block( @@ -497,7 +503,7 @@ impl JoinIrBlockConverter { let mut exit_block = crate::mir::BasicBlock::new(exit_block_id); // Phase 246-EX: Store Jump args in metadata for exit PHI construction - exit_block.jump_args = Some(args.to_vec()); + exit_block.set_legacy_jump_args(args.to_vec(), None); // Phase 256 P1.9: Generate tail call to continuation exit_block.instructions.push(MirInstruction::Const { @@ -513,7 +519,7 @@ impl JoinIrBlockConverter { effects: EffectMask::PURE, }); exit_block.instruction_spans.push(Span::unknown()); - exit_block.terminator = Some(MirInstruction::Return { value: Some(call_result_id) }); + exit_block.set_terminator(MirInstruction::Return { value: Some(call_result_id) }); mir_func.blocks.insert(exit_block_id, exit_block); // Continue block @@ -539,8 +545,8 @@ impl JoinIrBlockConverter { // Preserve jump args as metadata (SSOT for ExitLine/jump_args wiring). if let Some(block) = mir_func.blocks.get_mut(&self.current_block_id) { - if block.jump_args.is_none() { - block.jump_args = Some(args.to_vec()); + if !block.has_legacy_jump_args() { + block.set_legacy_jump_args(args.to_vec(), None); } } @@ -631,6 +637,8 @@ impl JoinIrBlockConverter { condition: *cond, then_bb: then_block, else_bb: else_block, + then_edge_args: None, + else_edge_args: None, }; Self::finalize_block( mir_func, @@ -648,8 +656,9 @@ impl JoinIrBlockConverter { }); then_block_obj.instruction_spans.push(Span::unknown()); } - then_block_obj.terminator = Some(MirInstruction::Jump { + then_block_obj.set_terminator(MirInstruction::Jump { target: merge_block, + edge_args: None, }); mir_func.blocks.insert(then_block, then_block_obj); @@ -662,8 +671,9 @@ impl JoinIrBlockConverter { }); else_block_obj.instruction_spans.push(Span::unknown()); } - else_block_obj.terminator = Some(MirInstruction::Jump { + else_block_obj.set_terminator(MirInstruction::Jump { target: merge_block, + edge_args: None, }); mir_func.blocks.insert(else_block, else_block_obj); @@ -752,6 +762,8 @@ impl JoinIrBlockConverter { condition: *cond_var, then_bb: next_true_block, else_bb: final_else_block, + then_edge_args: None, + else_edge_args: None, }; if level == 0 { @@ -775,8 +787,9 @@ impl JoinIrBlockConverter { }); then_block_obj.instruction_spans.push(Span::unknown()); } - then_block_obj.terminator = Some(MirInstruction::Jump { + then_block_obj.set_terminator(MirInstruction::Jump { target: merge_block, + edge_args: None, }); mir_func.blocks.insert(then_block, then_block_obj); @@ -789,8 +802,9 @@ impl JoinIrBlockConverter { }); else_block_obj.instruction_spans.push(Span::unknown()); } - else_block_obj.terminator = Some(MirInstruction::Jump { + else_block_obj.set_terminator(MirInstruction::Jump { target: merge_block, + edge_args: None, }); mir_func.blocks.insert(final_else_block, else_block_obj); @@ -842,7 +856,7 @@ impl JoinIrBlockConverter { block.instructions = instructions; block.instruction_spans = vec![Span::unknown(); inst_count]; } - block.terminator = Some(terminator); + block.set_terminator(terminator); } } diff --git a/src/mir/join_ir_vm_bridge/normalized_bridge/direct.rs b/src/mir/join_ir_vm_bridge/normalized_bridge/direct.rs index 8cb87e3f..6a70234c 100644 --- a/src/mir/join_ir_vm_bridge/normalized_bridge/direct.rs +++ b/src/mir/join_ir_vm_bridge/normalized_bridge/direct.rs @@ -140,32 +140,40 @@ fn lower_normalized_function_direct( let merge_bb = BasicBlockId(next_block_id); next_block_id += 1; - finalize_block( - &mut mir_func, - current_block_id, - mem::take(&mut current_insts), - MirInstruction::Branch { - condition: cond, - then_bb, - else_bb, - }, - None, - ); + finalize_block( + &mut mir_func, + current_block_id, + mem::take(&mut current_insts), + MirInstruction::Branch { + condition: cond, + then_bb, + else_bb, + then_edge_args: None, + else_edge_args: None, + }, + None, + ); - finalize_block( - &mut mir_func, - then_bb, - Vec::new(), - MirInstruction::Jump { target: merge_bb }, - None, - ); - finalize_block( - &mut mir_func, - else_bb, - Vec::new(), - MirInstruction::Jump { target: merge_bb }, - None, - ); + finalize_block( + &mut mir_func, + then_bb, + Vec::new(), + MirInstruction::Jump { + target: merge_bb, + edge_args: None, + }, + None, + ); + finalize_block( + &mut mir_func, + else_bb, + Vec::new(), + MirInstruction::Jump { + target: merge_bb, + edge_args: None, + }, + None, + ); current_block_id = merge_bb; current_insts = vec![MirInstruction::Phi { @@ -240,6 +248,8 @@ fn lower_normalized_function_direct( condition: cond_remapped, then_bb, else_bb, + then_edge_args: None, + else_edge_args: None, }, None, ); @@ -298,7 +308,7 @@ fn lower_normalized_function_direct( block.instruction_spans = vec![Span::unknown(); block.instructions.len()]; } if block.terminator.is_none() { - block.terminator = Some(MirInstruction::Return { value: None }); + block.set_terminator(MirInstruction::Return { value: None }); } } @@ -398,7 +408,7 @@ fn build_exit_or_tail_branch( .or_insert_with(|| BasicBlock::new(block_id)); block.instructions = insts; block.instruction_spans = vec![Span::unknown(); block.instructions.len()]; - block.terminator = Some(term); + block.set_terminator(term); return Ok(()); } @@ -410,8 +420,8 @@ fn build_exit_or_tail_branch( .or_insert_with(|| BasicBlock::new(block_id)); block.instructions.clear(); block.instruction_spans.clear(); - block.terminator = Some(MirInstruction::Return { value: ret_val }); - block.jump_args = Some(env.to_vec()); + block.set_terminator(MirInstruction::Return { value: ret_val }); + block.set_legacy_jump_args(env.to_vec(), None); Ok(()) } @@ -455,6 +465,10 @@ fn finalize_block( .or_insert_with(|| BasicBlock::new(block_id)); block.instructions = instructions; block.instruction_spans = vec![Span::unknown(); block.instructions.len()]; - block.terminator = Some(terminator); - block.jump_args = jump_args; + block.set_terminator(terminator); + if let Some(args) = jump_args { + block.set_legacy_jump_args(args, None); + } else { + block.clear_legacy_jump_args(); + } } diff --git a/src/mir/loop_api.rs b/src/mir/loop_api.rs index b63e7cbb..d6def64e 100644 --- a/src/mir/loop_api.rs +++ b/src/mir/loop_api.rs @@ -55,7 +55,10 @@ pub fn build_simple_loop( let after = lb.new_block(); // Jump to header - lb.emit(MirInstruction::Jump { target: header })?; + lb.emit(MirInstruction::Jump { + target: header, + edge_args: None, + })?; // Header: branch on provided condition lb.start_new_block(header)?; @@ -63,12 +66,17 @@ pub fn build_simple_loop( condition, then_bb: body, else_bb: after, + then_edge_args: None, + else_edge_args: None, })?; // Body lb.start_new_block(body)?; build_body(lb)?; - lb.emit(MirInstruction::Jump { target: header })?; + lb.emit(MirInstruction::Jump { + target: header, + edge_args: None, + })?; // After: return void value lb.start_new_block(after)?; diff --git a/src/mir/mod.rs b/src/mir/mod.rs index 0bcc8b21..5a5eb473 100644 --- a/src/mir/mod.rs +++ b/src/mir/mod.rs @@ -53,7 +53,7 @@ pub mod verification; pub mod verification_types; // extracted error types // Optimization subpasses (e.g., type_hints) // Phase 25.1f: Loop/If 共通ビュー(ControlForm) // Re-export main types for easy access -pub use basic_block::{BasicBlock, BasicBlockId, BasicBlockIdGenerator}; +pub use basic_block::{BasicBlock, BasicBlockId, BasicBlockIdGenerator, EdgeArgs, OutEdge}; pub use binding_id::BindingId; // Phase 74: BindingId infrastructure pub use builder::MirBuilder; diff --git a/src/mir/optimizer.rs b/src/mir/optimizer.rs index 92ae72a6..22c5b331 100644 --- a/src/mir/optimizer.rs +++ b/src/mir/optimizer.rs @@ -524,7 +524,7 @@ mod tests { } #[test] - fn test_dce_keeps_jump_args_values() { + fn test_dce_keeps_edge_args_values() { let signature = FunctionSignature { name: "main".to_string(), params: vec![], @@ -533,6 +533,7 @@ mod tests { }; let mut func = MirFunction::new(signature, BasicBlockId::new(0)); let bb0 = BasicBlockId::new(0); + let bb1 = BasicBlockId::new(1); let mut b0 = BasicBlock::new(bb0); let v0 = ValueId::new(0); let v1 = ValueId::new(1); @@ -541,9 +542,17 @@ mod tests { value: ConstValue::Integer(1), }); b0.add_instruction(MirInstruction::Copy { dst: v1, src: v0 }); - b0.add_instruction(MirInstruction::Return { value: None }); - b0.jump_args = Some(vec![v1]); + b0.set_jump_with_edge_args( + bb1, + Some(crate::mir::EdgeArgs { + layout: crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout::CarriersOnly, + values: vec![v1], + }), + ); func.add_block(b0); + let mut exit_block = BasicBlock::new(bb1); + exit_block.set_terminator(MirInstruction::Return { value: None }); + func.add_block(exit_block); let mut module = MirModule::new("test".to_string()); module.add_function(func); @@ -555,7 +564,7 @@ mod tests { .instructions .iter() .any(|inst| matches!(inst, MirInstruction::Copy { .. })); - assert!(has_copy, "Copy used only by jump_args should not be eliminated"); + assert!(has_copy, "Copy used only by edge args should not be eliminated"); } #[test] diff --git a/src/mir/passes/dce.rs b/src/mir/passes/dce.rs index 96c3dd87..045f9a14 100644 --- a/src/mir/passes/dce.rs +++ b/src/mir/passes/dce.rs @@ -37,9 +37,11 @@ fn eliminate_dead_code_in_function(function: &mut MirFunction) -> usize { used_values.insert(u); } } - if let Some(args) = &block.jump_args { - for &u in args { - used_values.insert(u); + for edge in block.out_edges() { + if let Some(args) = edge.args { + for u in args.values { + used_values.insert(u); + } } } } @@ -108,7 +110,7 @@ mod tests { use crate::mir::{BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirInstruction, MirType}; #[test] - fn test_dce_keeps_jump_args_values() { + fn test_dce_keeps_edge_args_values() { let mut module = MirModule::new("dce_test".to_string()); let sig = FunctionSignature { @@ -122,6 +124,7 @@ mod tests { let v1 = ValueId(1); let v2 = ValueId(2); let v_dead = ValueId(3); + let bb1 = BasicBlockId(1); { let bb0 = func.blocks.get_mut(&BasicBlockId(0)).unwrap(); @@ -141,10 +144,20 @@ mod tests { }); bb0.instruction_spans.push(Span::unknown()); - // SSOT: jump_args is a semantic use-site (ExitLine, continuation args). - bb0.jump_args = Some(vec![v2]); + // SSOT: edge args are semantic use-sites (ExitLine, continuation args). + bb0.set_jump_with_edge_args( + bb1, + Some(crate::mir::EdgeArgs { + layout: crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout::CarriersOnly, + values: vec![v2], + }), + ); } + let mut exit_block = crate::mir::BasicBlock::new(bb1); + exit_block.set_terminator(MirInstruction::Return { value: None }); + func.add_block(exit_block); + module.add_function(func); let eliminated = eliminate_dead_code(&mut module); @@ -156,7 +169,7 @@ mod tests { // Contract: spans must stay aligned with instructions. assert_eq!(bb0.instructions.len(), bb0.instruction_spans.len()); - // Contract: values that appear only in jump_args must be kept (and their deps). + // Contract: values that appear only in edge args must be kept (and their deps). assert!(bb0 .instructions .iter() diff --git a/src/mir/printer_helpers.rs b/src/mir/printer_helpers.rs index 896e7f5e..b53baf39 100644 --- a/src/mir/printer_helpers.rs +++ b/src/mir/printer_helpers.rs @@ -232,11 +232,12 @@ pub fn format_instruction( condition, then_bb, else_bb, + .. } => { format!("br {}, label {}, label {}", condition, then_bb, else_bb) } - MirInstruction::Jump { target } => { + MirInstruction::Jump { target, .. } => { format!("br label {}", target) } diff --git a/src/mir/ssot/cf_common.rs b/src/mir/ssot/cf_common.rs index 9f4bc950..f19815fb 100644 --- a/src/mir/ssot/cf_common.rs +++ b/src/mir/ssot/cf_common.rs @@ -30,6 +30,8 @@ pub fn set_branch( condition, then_bb, else_bb, + then_edge_args: None, + else_edge_args: None, }); } if let Some(tb) = f.get_block_mut(then_bb) { @@ -43,7 +45,10 @@ pub fn set_branch( /// Set an unconditional jump terminator and register predecessor on target block. pub fn set_jump(f: &mut MirFunction, cur_bb: BasicBlockId, target: BasicBlockId) { if let Some(bb) = f.get_block_mut(cur_bb) { - bb.set_terminator(MirInstruction::Jump { target }); + bb.set_terminator(MirInstruction::Jump { + target, + edge_args: None, + }); } if let Some(tb) = f.get_block_mut(target) { tb.add_predecessor(cur_bb); diff --git a/src/mir/utils/control_flow.rs b/src/mir/utils/control_flow.rs index 6b68aa42..1e23df6a 100644 --- a/src/mir/utils/control_flow.rs +++ b/src/mir/utils/control_flow.rs @@ -63,6 +63,7 @@ pub fn capture_actual_predecessor_and_jump( // 既存control_flowモジュールと同じパターンを使用 builder.emit_instruction(super::super::MirInstruction::Jump { target: target_block, + edge_args: None, })?; Ok(Some(cur_id)) } else { diff --git a/src/mir/verification/cfg.rs b/src/mir/verification/cfg.rs index 2c70bdbd..c4d5bb2a 100644 --- a/src/mir/verification/cfg.rs +++ b/src/mir/verification/cfg.rs @@ -1,13 +1,23 @@ use crate::mir::function::MirFunction; use crate::mir::verification::utils; use crate::mir::verification_types::VerificationError; -use crate::mir::{BasicBlockId, ValueId}; +use crate::mir::{BasicBlockId, MirInstruction, ValueId}; use std::collections::{HashMap, HashSet}; /// Verify CFG references and reachability pub fn check_control_flow(function: &MirFunction) -> Result<(), Vec> { let mut errors = Vec::new(); for (block_id, block) in &function.blocks { + let expected_successors = block.successors_from_terminator(); + if expected_successors != block.successors { + errors.push(VerificationError::ControlFlowError { + block: *block_id, + reason: format!( + "Successors cache mismatch: cached={:?}, expected={:?}", + block.successors, expected_successors + ), + }); + } for successor in &block.successors { if !function.blocks.contains_key(successor) { errors.push(VerificationError::ControlFlowError { @@ -16,6 +26,62 @@ pub fn check_control_flow(function: &MirFunction) -> Result<(), Vec { + if block.has_legacy_jump_args() { + let Some(legacy_layout) = block.legacy_jump_args_layout() else { + errors.push(VerificationError::ControlFlowError { + block: *block_id, + reason: "Legacy jump_args layout missing with edge-args present" + .to_string(), + }); + continue; + }; + let legacy_values = block.legacy_jump_args_values().unwrap_or_default(); + if edge_args.values.as_slice() != legacy_values { + errors.push(VerificationError::ControlFlowError { + block: *block_id, + reason: format!( + "Edge-args values mismatch: edge_args={:?}, legacy={:?}", + edge_args.values, legacy_values + ), + }); + } + if edge_args.layout != legacy_layout { + errors.push(VerificationError::ControlFlowError { + block: *block_id, + reason: format!( + "Edge-args layout mismatch: edge_args={:?}, legacy={:?}", + edge_args.layout, legacy_layout + ), + }); + } + } + } + MirInstruction::Branch { + then_edge_args, + else_edge_args, + .. + } => { + if block.has_legacy_jump_args() + && (then_edge_args.is_some() || else_edge_args.is_some()) + { + errors.push(VerificationError::ControlFlowError { + block: *block_id, + reason: "Legacy jump_args present on multi-edge terminator with edge-args" + .to_string(), + }); + } + } + _ => {} + } + } } // Unreachable blocks are allowed in MIR. // They are created intentionally by break/continue/return statements via diff --git a/src/runner/json_v1_bridge.rs b/src/runner/json_v1_bridge.rs index 385ab271..017f95b5 100644 --- a/src/runner/json_v1_bridge.rs +++ b/src/runner/json_v1_bridge.rs @@ -233,12 +233,15 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { condition: ValueId::new(cond), then_bb: BasicBlockId::new(then_bb), else_bb: BasicBlockId::new(else_bb), + then_edge_args: None, + else_edge_args: None, }); } "jump" => { let target = require_u64(inst, "target", "jump target")? as u32; block_ref.add_instruction(MirInstruction::Jump { target: BasicBlockId::new(target), + edge_args: None, }); } "phi" => { diff --git a/src/runner/mir_json_emit.rs b/src/runner/mir_json_emit.rs index 21854ff1..8c886796 100644 --- a/src/runner/mir_json_emit.rs +++ b/src/runner/mir_json_emit.rs @@ -512,10 +512,11 @@ pub fn emit_mir_json_for_harness( condition, then_bb, else_bb, + .. } => { insts.push(json!({"op":"branch","cond": condition.as_u32(), "then": then_bb.as_u32(), "else": else_bb.as_u32()})); } - I::Jump { target } => { + I::Jump { target, .. } => { insts.push(json!({"op":"jump","target": target.as_u32()})); } I::Return { value } => { @@ -528,8 +529,8 @@ pub fn emit_mir_json_for_harness( if let Some(term) = &bb.terminator { match term { I::Return { value } => insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())})), - I::Jump { target } => insts.push(json!({"op":"jump","target": target.as_u32()})), - I::Branch { condition, then_bb, else_bb } => insts.push(json!({"op":"branch","cond": condition.as_u32(), "then": then_bb.as_u32(), "else": else_bb.as_u32()})), + I::Jump { target, .. } => insts.push(json!({"op":"jump","target": target.as_u32()})), + I::Branch { condition, then_bb, else_bb, .. } => insts.push(json!({"op":"branch","cond": condition.as_u32(), "then": then_bb.as_u32(), "else": else_bb.as_u32()})), _ => {} } } @@ -874,10 +875,11 @@ pub fn emit_mir_json_for_harness_bin( condition, then_bb, else_bb, + .. } => { insts.push(json!({"op":"branch","cond": condition.as_u32(), "then": then_bb.as_u32(), "else": else_bb.as_u32()})); } - I::Jump { target } => { + I::Jump { target, .. } => { insts.push(json!({"op":"jump","target": target.as_u32()})); } I::Return { value } => { @@ -890,8 +892,8 @@ pub fn emit_mir_json_for_harness_bin( if let Some(term) = &bb.terminator { match term { I::Return { value } => insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())})), - I::Jump { target } => insts.push(json!({"op":"jump","target": target.as_u32()})), - I::Branch { condition, then_bb, else_bb } => insts.push(json!({"op":"branch","cond": condition.as_u32(), "then": then_bb.as_u32(), "else": else_bb.as_u32()})), + I::Jump { target, .. } => insts.push(json!({"op":"jump","target": target.as_u32()})), + I::Branch { condition, then_bb, else_bb, .. } => insts.push(json!({"op":"branch","cond": condition.as_u32(), "then": then_bb.as_u32(), "else": else_bb.as_u32()})), _ => {} } } blocks.push(json!({"id": bid.as_u32(), "instructions": insts})); diff --git a/src/runner/mir_json_v0.rs b/src/runner/mir_json_v0.rs index 8bd4e264..111a6be7 100644 --- a/src/runner/mir_json_v0.rs +++ b/src/runner/mir_json_v0.rs @@ -144,12 +144,15 @@ pub fn parse_mir_v0_to_module(json: &str) -> Result { condition: ValueId::new(cond), then_bb: BasicBlockId::new(then_bb), else_bb: BasicBlockId::new(else_bb), + then_edge_args: None, + else_edge_args: None, }); } "jump" => { let target = require_u64(inst, "target", "jump target")? as u32; block_ref.add_instruction(MirInstruction::Jump { target: BasicBlockId::new(target), + edge_args: None, }); } "phi" => { diff --git a/src/tests/mir_joinir_if_select.rs b/src/tests/mir_joinir_if_select.rs index 382a3683..d9b14ce6 100644 --- a/src/tests/mir_joinir_if_select.rs +++ b/src/tests/mir_joinir_if_select.rs @@ -30,17 +30,19 @@ mod tests { // Entry block (bb0): branch on cond let mut entry = BasicBlock::new(BasicBlockId::new(0)); - entry.terminator = Some(MirInstruction::Branch { + entry.set_terminator(MirInstruction::Branch { condition: ValueId(0), // cond parameter then_bb: BasicBlockId::new(1), else_bb: BasicBlockId::new(2), + then_edge_args: None, + else_edge_args: None, }); blocks.insert(BasicBlockId::new(0), entry); // Then block (bb1): return 10 // NOTE: Pattern matcher expects empty blocks (Return only) let mut then_block = BasicBlock::new(BasicBlockId::new(1)); - then_block.terminator = Some(MirInstruction::Return { + then_block.set_terminator(MirInstruction::Return { value: Some(ValueId(1)), // Assumes ValueId(1) is const 10 }); blocks.insert(BasicBlockId::new(1), then_block); @@ -48,7 +50,7 @@ mod tests { // Else block (bb2): return 20 // NOTE: Pattern matcher expects empty blocks (Return only) let mut else_block = BasicBlock::new(BasicBlockId::new(2)); - else_block.terminator = Some(MirInstruction::Return { + else_block.set_terminator(MirInstruction::Return { value: Some(ValueId(2)), // Assumes ValueId(2) is const 20 }); blocks.insert(BasicBlockId::new(2), else_block); @@ -78,10 +80,12 @@ mod tests { // Entry block (bb0): branch on cond let mut entry = BasicBlock::new(BasicBlockId::new(0)); - entry.terminator = Some(MirInstruction::Branch { + entry.set_terminator(MirInstruction::Branch { condition: ValueId(0), // cond then_bb: BasicBlockId::new(1), else_bb: BasicBlockId::new(2), + then_edge_args: None, + else_edge_args: None, }); blocks.insert(BasicBlockId::new(0), entry); @@ -92,8 +96,9 @@ mod tests { dst: ValueId(3), // x src: ValueId(10), // Assumes ValueId(10) is const 100 }); - then_block.terminator = Some(MirInstruction::Jump { + then_block.set_terminator(MirInstruction::Jump { target: BasicBlockId::new(3), + edge_args: None, }); blocks.insert(BasicBlockId::new(1), then_block); @@ -104,14 +109,15 @@ mod tests { dst: ValueId(3), // x src: ValueId(20), // Assumes ValueId(20) is const 200 }); - else_block.terminator = Some(MirInstruction::Jump { + else_block.set_terminator(MirInstruction::Jump { target: BasicBlockId::new(3), + edge_args: None, }); blocks.insert(BasicBlockId::new(2), else_block); // Merge block (bb3): return x let mut merge_block = BasicBlock::new(BasicBlockId::new(3)); - merge_block.terminator = Some(MirInstruction::Return { + merge_block.set_terminator(MirInstruction::Return { value: Some(ValueId(3)), }); blocks.insert(BasicBlockId::new(3), merge_block); @@ -408,10 +414,12 @@ mod tests { // Entry block (bb0): branch on cond let mut entry = BasicBlock::new(BasicBlockId::new(0)); - entry.terminator = Some(MirInstruction::Branch { + entry.set_terminator(MirInstruction::Branch { condition: ValueId(0), // cond then_bb: BasicBlockId::new(1), else_bb: BasicBlockId::new(2), + then_edge_args: None, + else_edge_args: None, }); blocks.insert(BasicBlockId::new(0), entry); @@ -425,7 +433,7 @@ mod tests { dst: ValueId(4), // y = 2 value: crate::mir::ConstValue::Integer(2), }); - then_block.terminator = Some(MirInstruction::Return { + then_block.set_terminator(MirInstruction::Return { value: Some(ValueId(10)), // result (x + y computed elsewhere) }); blocks.insert(BasicBlockId::new(1), then_block); @@ -440,7 +448,7 @@ mod tests { dst: ValueId(4), // y = 4 (same dst as then!) value: crate::mir::ConstValue::Integer(4), }); - else_block.terminator = Some(MirInstruction::Return { + else_block.set_terminator(MirInstruction::Return { value: Some(ValueId(20)), // result (x + y computed elsewhere) }); blocks.insert(BasicBlockId::new(2), else_block); @@ -470,10 +478,12 @@ mod tests { // Entry block (bb0): branch on cond let mut entry = BasicBlock::new(BasicBlockId::new(0)); - entry.terminator = Some(MirInstruction::Branch { + entry.set_terminator(MirInstruction::Branch { condition: ValueId(0), // cond then_bb: BasicBlockId::new(1), else_bb: BasicBlockId::new(2), + then_edge_args: None, + else_edge_args: None, }); blocks.insert(BasicBlockId::new(0), entry); @@ -491,7 +501,7 @@ mod tests { dst: ValueId(5), // z = 30 value: crate::mir::ConstValue::Integer(30), }); - then_block.terminator = Some(MirInstruction::Return { + then_block.set_terminator(MirInstruction::Return { value: Some(ValueId(10)), // result (x + y + z computed elsewhere) }); blocks.insert(BasicBlockId::new(1), then_block); @@ -510,7 +520,7 @@ mod tests { dst: ValueId(5), // z = 60 (same dst as then!) value: crate::mir::ConstValue::Integer(60), }); - else_block.terminator = Some(MirInstruction::Return { + else_block.set_terminator(MirInstruction::Return { value: Some(ValueId(20)), // result (x + y + z computed elsewhere) }); blocks.insert(BasicBlockId::new(2), else_block); @@ -632,23 +642,25 @@ mod tests { dst: ValueId(2), value: crate::mir::ConstValue::Integer(20), }); - entry.terminator = Some(MirInstruction::Branch { + entry.set_terminator(MirInstruction::Branch { condition: ValueId(0), // cond parameter then_bb: BasicBlockId::new(1), else_bb: BasicBlockId::new(2), + then_edge_args: None, + else_edge_args: None, }); blocks.insert(BasicBlockId::new(0), entry); // Then block (bb1): return 10 let mut then_block = BasicBlock::new(BasicBlockId::new(1)); - then_block.terminator = Some(MirInstruction::Return { + then_block.set_terminator(MirInstruction::Return { value: Some(ValueId(1)), // const 10 }); blocks.insert(BasicBlockId::new(1), then_block); // Else block (bb2): return 20 let mut else_block = BasicBlock::new(BasicBlockId::new(2)); - else_block.terminator = Some(MirInstruction::Return { + else_block.set_terminator(MirInstruction::Return { value: Some(ValueId(2)), // const 20 }); blocks.insert(BasicBlockId::new(2), else_block); diff --git a/src/tests/phase67_generic_type_resolver.rs b/src/tests/phase67_generic_type_resolver.rs index 0579e4a5..e492dea8 100644 --- a/src/tests/phase67_generic_type_resolver.rs +++ b/src/tests/phase67_generic_type_resolver.rs @@ -95,10 +95,12 @@ fn phase67_ab_test_resolve_from_phi_equivalence() { dst: cond, value: ConstValue::Bool(true), }); - f.get_block_mut(entry).unwrap().terminator = Some(MirInstruction::Branch { + f.get_block_mut(entry).unwrap().set_terminator(MirInstruction::Branch { condition: cond, then_bb, else_bb, + then_edge_args: None, + else_edge_args: None, }); // Then: v2 = 42 @@ -109,7 +111,10 @@ fn phase67_ab_test_resolve_from_phi_equivalence() { dst: v2, value: ConstValue::Integer(42), }); - f.get_block_mut(then_bb).unwrap().terminator = Some(MirInstruction::Jump { target: merge_bb }); + f.get_block_mut(then_bb).unwrap().set_terminator(MirInstruction::Jump { + target: merge_bb, + edge_args: None, + }); // Else: v3 = 0 let v3 = f.next_value_id(); @@ -119,7 +124,10 @@ fn phase67_ab_test_resolve_from_phi_equivalence() { dst: v3, value: ConstValue::Integer(0), }); - f.get_block_mut(else_bb).unwrap().terminator = Some(MirInstruction::Jump { target: merge_bb }); + f.get_block_mut(else_bb).unwrap().set_terminator(MirInstruction::Jump { + target: merge_bb, + edge_args: None, + }); // Merge: v4 = phi(v2 from then, v3 from else) let v4 = f.next_value_id(); diff --git a/tests/mir_verification_unit.rs b/tests/mir_verification_unit.rs index dccf83a9..27fdb57d 100644 --- a/tests/mir_verification_unit.rs +++ b/tests/mir_verification_unit.rs @@ -130,6 +130,8 @@ fn test_merge_use_before_phi_detected() { condition: cond, then_bb, else_bb, + then_edge_args: None, + else_edge_args: None, }); } @@ -139,7 +141,10 @@ fn test_merge_use_before_phi_detected() { dst: v1, value: ConstValue::String("A".to_string()), }); - b1.add_instruction(MirInstruction::Jump { target: merge_bb }); + b1.add_instruction(MirInstruction::Jump { + target: merge_bb, + edge_args: None, + }); f.add_block(b1); let v2 = f.next_value_id(); // %2 @@ -148,7 +153,10 @@ fn test_merge_use_before_phi_detected() { dst: v2, value: ConstValue::String("B".to_string()), }); - b2.add_instruction(MirInstruction::Jump { target: merge_bb }); + b2.add_instruction(MirInstruction::Jump { + target: merge_bb, + edge_args: None, + }); f.add_block(b2); let mut b3 = BasicBlock::new(merge_bb); diff --git a/tests/wasm_string_constants.rs b/tests/wasm_string_constants.rs index 55964cfe..3239b651 100644 --- a/tests/wasm_string_constants.rs +++ b/tests/wasm_string_constants.rs @@ -146,7 +146,7 @@ fn build_string_const_mir_module() -> MirModule { }); // return %str - block.terminator = Some(MirInstruction::Return { + block.set_terminator(MirInstruction::Return { value: Some(str_value), }); @@ -193,7 +193,7 @@ fn build_multiple_string_const_mir_module() -> MirModule { }); // return %str1 - block.terminator = Some(MirInstruction::Return { + block.set_terminator(MirInstruction::Return { value: Some(str1_value), });