docs(edgecfg): Phase 280 - Frag Composition SSOT Positioning (A→B→C)
## Purpose Stop pattern number enumeration proliferation by establishing Frag composition API as the Single Source of Truth (SSOT) for structured control flow → CFG lowering. Pattern numbers (1-9+) are **symptom labels** for regression tests, NOT architectural concepts. The architectural SSOT is **Frag composition rules** (seq/if/loop/cleanup). ## Changes Summary **Phase A (Docs-only, no code)**: SSOT Positioning - edgecfg-fragments.md: Status Draft → Active SSOT (+243 lines) - Added 5 sections: Composition SSOT, Rules, Laws, Fail-Fast, Ownership - Documented 3-tier ownership model (Normalizer/Composition/Lowerer) - Established composition as pattern absorption destination - joinir-architecture-overview.md: Pattern absorption documentation (+90 lines) - Added Section 0.2: Pattern Number Absorption Destination - JoinIR vs Plan comparison (different extraction, same SSOT) - Pattern absorption status table (Pattern6/7 as Phase 280 targets) - phase-280/README.md: Full roadmap (new) **Phase B (API solidification)**: Contract Verification - compose.rs: Module-level + function-level Phase 280 docs (+149 lines) - Documented composition SSOT, ownership model, usage example - Added constraint/composition law sections to seq/if/loop/cleanup - Contract verification: All seq/if/loop contracts verified (no gaps) - Test gap analysis: No missing tests (wires/exits separation explicitly tested) **Phase C (Pattern preparation)**: Documentation-only - normalizer.rs: Pattern6/7 TODO comments (+10 lines) - Pattern6: Early exit doesn't fit compose::if_() → cleanup() target - Pattern7: 挙動不変保証難 → compose::if_() migration deferred to Phase 281 ## Impact - **Net +460 lines** (docs-heavy, minimal code) - **4 files modified**, 1 directory created - **SSOT established**: Frag composition is now THE absorption destination - **導線固定**: Clear migration path for Pattern6/7 (Phase 281+) - **No behavior change**: Documentation-only for Phase C (tests not run) ## Phase 280 Goal Achieved ✅ SSOT positioning + 導線固定 (NOT full migration - that's Phase 281) ✅ Phase A complete: Docs updated to "Active SSOT" ✅ Phase B complete: API contract verified and documented ✅ Phase C complete: Pattern6/7 hand-rolled locations documented ## Next Phase (Phase 281+) - Phase 281: Full Pattern6/7 absorption (replace hand-rolled with compose_*) - Phase 282: Router shrinkage (pattern numbers → test labels) - Phase 283+: Pattern8 and beyond 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -1,7 +1,8 @@
|
||||
# EdgeCFG Flow Fragments(Frag / ExitKind)— Structured→CFG lowering SSOT
|
||||
|
||||
Status: Draft(design SSOT candidate)
|
||||
Last updated: 2025-12-21
|
||||
Status: Active SSOT
|
||||
Last updated: 2025-12-23
|
||||
Phase: 280 (Composition SSOT Positioning)
|
||||
|
||||
Related:
|
||||
- North star(CFG/ABI): `docs/development/current/main/design/join-explicit-cfg-construction.md`
|
||||
@ -99,7 +100,232 @@ Frag = { entry_block, exits: Map<ExitKind, Vec<EdgeStub>> }
|
||||
|
||||
- pattern番号は **回帰テスト名/症状ラベル**としては残して良い
|
||||
- 実装の中心は `Frag/ExitKind/join(block params)` の合成則になる
|
||||
- 各 pattern 実装は “Extractor(形の認識)→ Frag 合成呼び出し” の薄い層へ縮退する
|
||||
- 各 pattern 実装は "Extractor(形の認識)→ Frag 合成呼び出し" の薄い層へ縮退する
|
||||
|
||||
---
|
||||
|
||||
## Composition SSOT (Phase 280)
|
||||
|
||||
**Status**: Active SSOT
|
||||
**Purpose**: Pattern number absorption destination
|
||||
**Date**: 2025-12-23
|
||||
|
||||
### Why Composition is SSOT
|
||||
|
||||
Pattern numbers (1-9+) are **symptom labels** for regression tests, NOT architectural concepts.
|
||||
The architectural SSOT is **Frag composition rules** (`seq`/`if`/`loop`/`cleanup`).
|
||||
|
||||
**Upstream (Extractor/Normalizer)**: Finish "shape recognition" and extract pattern-specific knowledge
|
||||
**Downstream (Composition)**: Use Frag composition rules to build CFG converging to SSOT
|
||||
**Terminator Generation**: `emit_frag()` as sole SSOT (Phase 267 P0)
|
||||
|
||||
### Composition Input/Output Contract
|
||||
|
||||
- **Input**: `Frag` (entry + exits + wires + branches)
|
||||
- **Output**: `Frag` (new entry + merged exits + merged wires + merged branches)
|
||||
- **Guarantee**: Composition preserves invariants (`verify_frag_invariants_strict`)
|
||||
- **No Allocation**: Caller (Normalizer) allocates `BasicBlockId`/`ValueId`
|
||||
- **Pure Transform**: Composition rearranges `exits`/`wires`/`branches` only
|
||||
|
||||
### Composition Rules (Canonical Operations)
|
||||
|
||||
#### `seq(a, b)`: Sequential composition
|
||||
|
||||
**Composition Law**:
|
||||
- `a.Normal` exits → `wires` (target = `Some(b.entry)`)
|
||||
- Non-Normal exits (Return/Break/Continue/Unwind) → propagate upward (`exits`)
|
||||
- Result: `seq.entry = a.entry`, `seq.exits = a.non-Normal + b.all`
|
||||
|
||||
**Contract**:
|
||||
- **Caller allocates**: `b.entry` (`BasicBlockId`)
|
||||
- **Composition wires**: `a.Normal` → `b.entry`
|
||||
|
||||
#### `if_(header, cond, t, e, join_frag)`: Conditional composition
|
||||
|
||||
**Composition Law**:
|
||||
- `header` → `t.entry`/`e.entry` (`BranchStub` → `branches`)
|
||||
- `t/e.Normal` → `join_frag.entry` (`EdgeStub` → `wires`)
|
||||
- Non-Normal exits → propagate upward (`exits`)
|
||||
- Result: `if.entry = header`, `if.exits = t/e.non-Normal + join_frag.all`
|
||||
|
||||
**Contract**:
|
||||
- **Caller allocates**: `header`, `t.entry`, `e.entry`, `join_frag.entry` (`BasicBlockId`), `cond` (`ValueId`)
|
||||
- **Caller provides**: `then_entry_args`, `else_entry_args` (`EdgeArgs`) - Phase 268 P1 SSOT
|
||||
- **Composition wires**: `t/e.Normal` → `join_frag.entry`
|
||||
|
||||
#### `loop_(loop_id, header, after, body)`: Loop composition
|
||||
|
||||
**Composition Law**:
|
||||
- `Continue(loop_id)` → `header` (`EdgeStub` → `wires`)
|
||||
- `Break(loop_id)` → `after` (`EdgeStub` → `wires`)
|
||||
- Normal/Return/Unwind → propagate upward (`exits`)
|
||||
- Result: `loop.entry = header`, `loop.exits = Normal/Return/Unwind only` (no Break/Continue)
|
||||
|
||||
**Contract**:
|
||||
- **Caller allocates**: `loop_id` (`LoopId`), `header`, `after` (`BasicBlockId`)
|
||||
- **Composition wires**: `Continue(loop_id)` → `header`, `Break(loop_id)` → `after`
|
||||
|
||||
#### `cleanup(body, cleanup_block)`: Cleanup composition (TODO: Phase 280+)
|
||||
|
||||
**Planned Composition Law** (Future):
|
||||
- All exits (Normal/Break/Continue/Return/Unwind) → `cleanup` (`EdgeStub` → `wires`)
|
||||
- Cleanup re-dispatches original exit (`ExitTag` + payload via block params)
|
||||
- `Invoke.err` also routed through cleanup
|
||||
|
||||
**Status**: Signature fixed, implementation TODO (Phase 280+)
|
||||
|
||||
---
|
||||
|
||||
## Composition Laws (Invariants)
|
||||
|
||||
### Wires/Exits Separation (Phase 265 P2)
|
||||
|
||||
**Invariants**:
|
||||
- **`wires`**: `target = Some(...)` only (internal wiring, resolved)
|
||||
- **`exits`**: `target = None` only (external exit, unresolved)
|
||||
- **Exception**: `Return` may have `target = None` in `wires` (`emit_wires` ignores it)
|
||||
|
||||
**Why separate?**
|
||||
- Prevents resolved wiring from being re-wired in next composition
|
||||
- Makes composition semantics clear: `wires` = done, `exits` = propagate upward
|
||||
|
||||
**Fail-Fast Enforcement**:
|
||||
- `verify_frag_invariants_strict()` checks `wires`/`exits` separation
|
||||
- Normal/Break/Continue/Unwind require `target = Some` in `wires`
|
||||
- `Return` allows `target = None` (meaningless for `Return`)
|
||||
|
||||
### Terminator Uniqueness (Phase 267 P0)
|
||||
|
||||
**Invariant**: 1 block = 1 terminator
|
||||
|
||||
**Enforcement** (`emit_frag`):
|
||||
- From grouping: ensures 1 block = 1 terminator
|
||||
- Same block cannot have both `wire` and `branch`
|
||||
- Same block cannot have multiple `wires` (from-grouping detects violation)
|
||||
|
||||
### Entry Consistency
|
||||
|
||||
**Invariants**:
|
||||
- `Frag.entry` must be a valid `BasicBlockId`
|
||||
- Composition preserves entry validity
|
||||
- Entry points to first block in composed CFG fragment
|
||||
|
||||
---
|
||||
|
||||
## Fail-Fast Invariants (Phase 266+)
|
||||
|
||||
### Pre-emission Verification (Two Levels)
|
||||
|
||||
#### `verify_frag_invariants()` (warning-only)
|
||||
|
||||
- **Purpose**: Legacy compatibility mode
|
||||
- **Behavior**: Logs warnings but doesn't fail
|
||||
- **Usage**: Used by existing code during migration
|
||||
|
||||
#### `verify_frag_invariants_strict()` (Err on violation)
|
||||
|
||||
- **Purpose**: New code enforcement
|
||||
- **Behavior**: Returns `Err` on invariant violation
|
||||
- **Usage**: Called by `emit_frag()` automatically
|
||||
- **Enforces**: wires/exits separation, target constraints
|
||||
|
||||
**Invariants checked**:
|
||||
1. **Wires/Exits Separation**:
|
||||
- wires have `target = Some` (except Return)
|
||||
- exits have `target = None`
|
||||
2. **Target Validity**:
|
||||
- Normal/Break/Continue/Unwind require `target = Some` in wires
|
||||
- Return allows `target = None`
|
||||
|
||||
### Emission-time Verification (`emit_frag` SSOT)
|
||||
|
||||
**`emit_frag()` responsibilities** (Phase 267 P0):
|
||||
1. Call `verify_frag_invariants_strict()` before emission
|
||||
2. Detect `target = None` violations (except Return)
|
||||
3. Enforce 1 block = 1 terminator (from-grouping)
|
||||
4. Detect wire/branch conflicts (same block)
|
||||
|
||||
**Terminator emission**:
|
||||
- `wires` → Jump/Return terminators (`emit_wires` internally)
|
||||
- `branches` → Branch terminators (`set_branch_with_edge_args`)
|
||||
- Phase 260 terminator API (SSOT): `set_jump_with_edge_args`, `set_branch_with_edge_args`
|
||||
|
||||
### Composition-side Invariants
|
||||
|
||||
**Assumptions**:
|
||||
- Composition functions **assume** input `Frag` is valid
|
||||
- Composition **preserves** validity (output passes `verify_frag_invariants_strict`)
|
||||
- Caller (Normalizer) responsible for initial `Frag` validity
|
||||
|
||||
---
|
||||
|
||||
## Ownership (Who Allocates What)
|
||||
|
||||
### Allocator Responsibilities (3-tier)
|
||||
|
||||
#### Tier 1: Normalizer (Pattern-Specific)
|
||||
|
||||
**Responsibilities**:
|
||||
- Allocates `BasicBlockId` (`builder.next_block_id()`)
|
||||
- Allocates `ValueId` (`builder.next_value_id()`)
|
||||
- Knows pattern semantics (scan, split, etc.)
|
||||
- Constructs initial `Frag` with valid blocks/values
|
||||
|
||||
**Example** (Pattern6 ScanWithInit):
|
||||
```rust
|
||||
let header_bb = builder.next_block_id();
|
||||
let body_bb = builder.next_block_id();
|
||||
let step_bb = builder.next_block_id();
|
||||
// ... allocate all blocks upfront
|
||||
```
|
||||
|
||||
#### Tier 2: Composition API (Pattern-Agnostic)
|
||||
|
||||
**Responsibilities**:
|
||||
- Receives pre-allocated `BasicBlockId`/`ValueId`
|
||||
- Rearranges `exits`/`wires`/`branches`
|
||||
- Pure CFG transformation (no allocation, no semantics)
|
||||
|
||||
**Example** (compose::seq):
|
||||
```rust
|
||||
pub fn seq(a: Frag, b: Frag) -> Frag {
|
||||
// Assume a.entry, b.entry are pre-allocated
|
||||
// Just rearrange exits/wires
|
||||
}
|
||||
```
|
||||
|
||||
**Why no allocation?**
|
||||
- Separation of concerns: allocation (pattern-specific) vs wiring (generic)
|
||||
- Composability: composition functions can be called in any order without ID conflicts
|
||||
- Testability: composition can be tested with fixed IDs (deterministic)
|
||||
|
||||
#### Tier 3: Lowerer (MIR Emission)
|
||||
|
||||
**Responsibilities**:
|
||||
- Calls `emit_frag()` to generate MIR terminators
|
||||
- Uses Phase 260 terminator API (`set_jump_with_edge_args`, `set_branch_with_edge_args`)
|
||||
- No allocation, no CFG construction
|
||||
|
||||
**Example** (PlanLowerer::lower_loop):
|
||||
```rust
|
||||
emit_frag(func, &loop_plan.frag)?; // Emits all terminators
|
||||
```
|
||||
|
||||
### Ownership Flow Diagram
|
||||
|
||||
```
|
||||
Pattern6/7 Normalizer
|
||||
↓ (allocate blocks/values)
|
||||
DomainPlan → CorePlan
|
||||
↓ (pre-allocated IDs)
|
||||
Composition API (seq/if/loop)
|
||||
↓ (rearranged Frag)
|
||||
PlanLowerer
|
||||
↓ (emit_frag)
|
||||
MIR Terminator Instructions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 実装の入口(SSOT API を先に作る)
|
||||
|
||||
@ -184,8 +410,17 @@ Phase 270 の “JoinIR-only minimal loop” を通すための橋渡し。将
|
||||
|
||||
## 実装入口(コード SSOT)
|
||||
|
||||
**Phase 264 で入口API を作成完了**
|
||||
**Phase 280: Composition SSOT Positioning Complete (2025-12-23)**
|
||||
|
||||
- **Status**: Active SSOT
|
||||
- **Documentation**: Full composition SSOT positioning (5 sections above)
|
||||
- **Implementation**: Composition API exists and tested (seq/if/loop)
|
||||
- **Pattern Preparation**: Pattern6/7 hand-rolled locations documented for Phase 281 migration
|
||||
|
||||
**Phase 264 (歴史/別案件)**: Entry API Creation (別スコープ: BundleResolver loop fix)
|
||||
|
||||
- Note: Phase 264 は別案件(BundleResolver loop pattern fix)
|
||||
- Composition API 入口作成は Phase 264 で完了したが、SSOT positioning は Phase 280 で確立
|
||||
- 物理配置: `src/mir/builder/control_flow/edgecfg/api/`
|
||||
- コア型: `ExitKind`, `EdgeStub`, `Frag`
|
||||
- 合成関数: `seq`, `if_`, `loop_`, `cleanup`(シグネチャのみ、中身TODO)
|
||||
|
||||
Reference in New Issue
Block a user