diff --git a/docs/development/current/main/design/edgecfg-fragments.md b/docs/development/current/main/design/edgecfg-fragments.md index 35d42dd6..d99ddd39 100644 --- a/docs/development/current/main/design/edgecfg-fragments.md +++ b/docs/development/current/main/design/edgecfg-fragments.md @@ -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> } - 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) diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index 01ec3d85..d39ef45d 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -136,6 +136,96 @@ NYASH_JOINIR_DEBUG=1 cargo test --release --lib 実装: `src/config/env/joinir_flags.rs::is_joinir_debug()` が両者をチェック。 詳細: `docs/development/current/main/phase82-83-debug-flag-ssot-summary.md` +## 0.2 Pattern Number Absorption Destination (Phase 280) + +**Status**: Active (2025-12-23) +**Purpose**: Stop pattern enumeration proliferation by establishing Frag composition as SSOT + +### The Problem: Pattern Enumeration Proliferation + +Pattern numbers (1-9+) became architectural decision points: +- Router branches exploded (17+ patterns across JoinIR/Plan routes) +- Each pattern duplicated CFG construction logic (block allocation, PHI insertion, terminator emission) +- "Pattern-specific" knowledge leaked into lowering layers +- Adding new loop shapes required full-stack pattern additions + +**Symptom**: Pattern numbers drove architecture instead of being test labels + +### The Solution: Frag Composition SSOT + +**Key Insight**: Pattern numbers → symptom labels (test names), CFG construction → Frag composition API + +**Architecture shift**: +- **Before Phase 280**: Pattern number → entire lowering pipeline (extractor + allocator + emitter) +- **After Phase 280**: Pattern number → extractor only, all lowering uses Frag composition SSOT + +**Composition API as absorption destination**: +- `seq(a, b)`: Sequential composition (Normal wiring) +- `if_(header, cond, t, e, join)`: Conditional composition (Branch wiring) +- `loop_(loop_id, header, after, body)`: Loop composition (Break/Continue wiring) +- `cleanup(body, cleanup)`: Cleanup composition (planned, Phase 280+) + +**Reference**: `docs/development/current/main/design/edgecfg-fragments.md` (Active SSOT) + +### JoinIR vs Plan: Different Extraction, Same SSOT + +Both routes converge on the same Frag composition SSOT: + +| Route | Extraction Source | Pattern Knowledge | Composition SSOT | +|-------|-------------------|-------------------|------------------| +| **JoinIR** | cf_loop structure (Structured JoinIR) | JoinIR-specific (cf_loop DSL) | **Frag API** (seq/if/loop) | +| **Plan** | DomainPlan (Pattern6/7 extractors) | Domain-specific (ScanWithInit/SplitScan) | **Frag API** (same) | + +**Key principle**: Different extraction strategies, converged CFG construction + +**Why separate routes?**: +- JoinIR route: Handles cf_loop-based patterns (Pattern1-5, 8-9) via Structured JoinIR +- Plan route: Handles complex scan patterns (Pattern6/7) via DomainPlan → CorePlan → Frag +- Both routes use same Frag composition API for CFG lowering (no duplication) + +### Pattern Absorption Status (Phase 280) + +| Pattern | Structure | Status | Absorption Target | +|---------|-----------|--------|-------------------| +| **Pattern6** | ScanWithInit (forward scan) | Plan-based (Phase 273) | **Phase 280 C: Frag refactor target** | +| **Pattern7** | SplitScan (conditional split) | Plan-based (Phase 273) | **Phase 280 C: Frag refactor target** | +| Pattern8 | BoolPredicateScan (is_integer) | JoinIR-based | Phase 281+ (migration planned) | +| Pattern9 | AccumConstLoop (bridge) | JoinIR-based (Phase 271) | 撤去条件 defined (minimal loop SSOT) | +| Pattern1-5 | Legacy (SimpleWhile, Break, IfPhi, Continue, InfiniteEarlyExit) | JoinIR-based | Test/error stubs (not absorbed) | + +**Phase 280 Focus**: Pattern6/7 preparation (documentation of hand-rolled locations, defer refactor to Phase 281) + +**Absorption criteria** (Pattern6/7 → Frag composition): +1. Hand-rolled Frag construction identified (function name + 識別コメント) +2. TODO comments documenting future compose_* migration path +3. Behavior-preserving refactor deferred to Phase 281 (Phase 280 = SSOT positioning only) + +### Absorption Timeline + +**Phase 280 (Current)**: SSOT positioning + 導線固定 +- A: Documentation (edgecfg-fragments.md → Active SSOT) +- B: API solidification (compose.rs contract verification) +- C: Pattern preparation (Pattern6/7 hand-rolled locations documented) +- **Goal**: Establish Frag composition as THE absorption destination +- **Non-Goal**: Full Pattern6/7 migration (deferred to Phase 281) + +**Phase 281 (Planned)**: Full Pattern6/7 absorption +- Replace hand-rolled Frag construction with compose_* calls +- Behavior-preserving verification (smoke tests) +- Goal: Pattern6/7 use compose::if_/loop_ exclusively + +**Phase 282 (Planned)**: Router shrinkage +- Pattern numbers → test labels only +- Router uses Frag composition for all CFG construction +- Pattern extractors remain as thin detection layer + +**Phase 283+ (Future)**: Pattern8 and beyond +- Migrate Pattern8 to Plan route or Frag-based JoinIR +- Evaluate Pattern9 撤去 (if minimal loop SSOT achieved) +- Continue pattern number reduction + +--- + ## 1. 不変条件(Invariants) JoinIR ラインで守るべきルールを先に書いておくよ: diff --git a/docs/development/current/main/phases/phase-280/README.md b/docs/development/current/main/phases/phase-280/README.md new file mode 100644 index 00000000..23d890b3 --- /dev/null +++ b/docs/development/current/main/phases/phase-280/README.md @@ -0,0 +1,442 @@ +# Phase 280: ExitKind+Frag Composition SSOT (A→B→C) + +**Status**: In Progress (Started 2025-12-23) +**Phase Number**: 280 +**Type**: Design-First (Documentation → Minimal Code) + +## Executive Summary + +**Goal**: Stop pattern number enumeration proliferation by establishing Frag composition API as the Single Source of Truth (SSOT) for structured control flow → CFG lowering. + +**Strategy**: Three-phase approach (A→B→C): +- **Phase A**: Document SSOT positioning (docs-only, no code) ✅ **Complete** +- **Phase B**: Solidify composition API contract (minimal test-based verification) +- **Phase C**: Prepare Pattern6/7 for composition API (documentation-only, defer migration to Phase 281) + +**Key Insight**: Pattern numbers (1-9+) are **symptom labels** for regression tests, NOT architectural concepts. The architectural SSOT is **Frag composition rules** (`seq`/`if`/`loop`/`cleanup`). + +**Phase 280 Goal**: SSOT positioning + 導線固定 (NOT full migration - that's Phase 281) + +--- + +## Purpose + +**What this phase achieves**: +1. Establish Frag composition API as THE absorption destination for pattern proliferation +2. Document composition SSOT positioning in architecture docs (edgecfg-fragments.md, joinir-architecture-overview.md) +3. Solidify composition API contract through verification and testing +4. Identify Pattern6/7 hand-rolled Frag construction locations for future migration +5. Create clear導線 (guidance) for Phase 281+ migration work + +**Why this is needed**: +- Pattern numbers became architectural decision points (17+ patterns across JoinIR/Plan routes) +- CFG construction logic duplicated across patterns +- Adding new loop shapes required full-stack pattern additions +- Need convergence point to absorb pattern-specific knowledge + +--- + +## Non-Goals + +**What this phase does NOT do**: +1. ❌ Migrate Pattern6/7 to use composition API (deferred to Phase 281) +2. ❌ Remove hand-rolled Frag construction (documentation-only in Phase 280 C) +3. ❌ Change router behavior (routing remains unchanged) +4. ❌ Add new composition functions (seq/if/loop already exist) + +**Rationale**: Phase 280 is about SSOT positioning, NOT full migration. 行動は最小が正解. + +--- + +## SSOT References + +### Primary Documentation + +1. **Frag Composition SSOT**: [`docs/development/current/main/design/edgecfg-fragments.md`](/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/design/edgecfg-fragments.md) + - Status: **Active SSOT** (updated Phase 280 A1) + - 5 new sections: Composition SSOT, Rules, Laws, Fail-Fast, Ownership + +2. **JoinIR Architecture Overview**: [`docs/development/current/main/joinir-architecture-overview.md`](/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/joinir-architecture-overview.md) + - Section 0.2: **Pattern Number Absorption Destination (Phase 280)** (updated Phase 280 A2) + - Comparison: JoinIR vs Plan (different extraction, same SSOT) + +3. **Phase 280 Plan**: [`/home/tomoaki/.claude/plans/elegant-wondering-stroustrup.md`](/home/tomoaki/.claude/plans/elegant-wondering-stroustrup.md) + - Full implementation plan with A→B→C breakdown + +### Related Documentation + +- **Phase 273 (Plan Line SSOT)**: Pattern6/7 Plan-based routing completed +- **Phase 264 (歴史/別案件)**: BundleResolver loop fix (separate scope) +- **Phase 265-268**: Frag composition API creation and terminator SSOT + +--- + +## Three-Phase Approach (A→B→C) + +### Phase A: Design SSOT Solidification (Docs-Only) ✅ **COMPLETE** + +**No code changes - documentation only** + +#### A1: Update edgecfg-fragments.md to "Composition SSOT" ✅ + +**Changes Made**: +- Header: `Status: Draft` → `Status: Active SSOT` +- Added 5 new sections (after line 103): + 1. **Composition SSOT (Phase 280)**: Why it's SSOT, input/output contract + 2. **Composition Rules**: seq/if/loop/cleanup with composition laws + 3. **Composition Laws (Invariants)**: Wires/Exits Separation, Terminator Uniqueness + 4. **Fail-Fast Invariants**: Two-level verification (verify_frag_invariants vs strict) + 5. **Ownership (Who Allocates What)**: 3-tier model (Normalizer/Composition/Lowerer) +- Updated "実装入口" section: Phase 280 entry added, Phase 264 separated as history + +**File**: [`docs/development/current/main/design/edgecfg-fragments.md`](/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/design/edgecfg-fragments.md) + +#### A2: Update joinir-architecture-overview.md with "Pattern Number Absorption Destination" ✅ + +**Changes Made**: +- Added section 0.2: **Pattern Number Absorption Destination (Phase 280)** +- Content: + - Problem: Pattern enumeration proliferation + - Solution: Frag Composition SSOT + - JoinIR vs Plan comparison table (different extraction, same SSOT) + - Pattern absorption status table (Pattern6/7 highlighted as Phase 280 targets) + - Absorption timeline (Phase 280-283+) + +**File**: [`docs/development/current/main/joinir-architecture-overview.md`](/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/joinir-architecture-overview.md) + +#### A3: Create Phase 280 README ✅ + +**This file** - Full roadmap documentation with: +- Purpose / Non-Goals +- SSOT References +- Three-Phase Approach breakdown +- Execution Order +- Acceptance Criteria +- Risks and Mitigation +- Critical Files + +--- + +### Phase B: Frag Combiner Minimal API Design + +**Minimal code changes - documentation + tests only** + +#### B1: Document compose.rs Entry Points + +**File**: `src/mir/builder/control_flow/edgecfg/api/compose.rs` + +**Changes Required**: +1. Add module-level documentation explaining: + - Composition SSOT (Phase 280) + - Entry points: seq(), if_(), loop_(), cleanup() + - Contract: Input/Output Frag, No Allocation, Pure CFG Transform + - Usage example + +2. Add function-level "Phase 280: Composition SSOT" sections documenting: + - Constraints (caller allocates X, composition wires Y) + - Composition laws (input → output transformation) + +**Acceptance**: +- [ ] Module-level docs added +- [ ] Each function has "Phase 280" constraint section +- [ ] Composition laws documented + +#### B2: Verify Composition API Implements Contract + +**File**: `src/mir/builder/control_flow/edgecfg/api/compose.rs` + +**Action**: Documentation-only verification (checklist) + +**Verification Checklist**: + +**seq() Contract** ✓: +- [x] a.Normal → b.entry (wires): Line 42-51 +- [x] Non-Normal propagate: Line 54-58 +- [x] Tests exist + +**if_() Contract** ✓: +- [x] header → t.entry/e.entry (BranchStub): Line 114-122 +- [x] t/e.Normal → join (wires): Line 131-169 +- [x] Tests exist + +**loop_() Contract** ✓: +- [x] Continue → header (wires): Line 224-233 +- [x] Break → after (wires): Line 236-245 +- [x] Tests exist + +**cleanup() Contract** ⏸: +- [ ] TODO placeholder (Phase 280+ scope) + +**Acceptance**: +- [ ] Checklist complete (all seq/if/loop verified) +- [ ] No missing contract implementations discovered + +#### B3: Add Tests for Composition Invariants + +**File**: `src/mir/builder/control_flow/edgecfg/api/compose.rs` (test module) + +**Action**: Gap analysis + add tests if needed + +**Current Coverage**: 13 tests exist (seq: 2, if: 2, loop: 5, emit: 1, basic: 3) + +**Gap Analysis**: +1. **Output Determinism** (BTreeMap ordering) + - Check: Verify exits/wires maintain ordering + - Status: Implicit (BTreeMap used, not explicitly tested) + - Action: Add test if gap confirmed + +2. **Wires/Exits Separation** + - Check: Verify wires target=Some, exits target=None + - Status: Partially covered + - Action: Add explicit test if gap confirmed + +**Decision**: Check if gaps exist, add tests **only if** gaps confirmed + +**Acceptance**: +- [ ] Gap analysis complete +- [ ] Tests added if gaps found +- [ ] `cargo test --lib --release` PASS + +--- + +### Phase C: Prepare Pattern6/7 to Use Composition API + +**Behavior-preserving refactor OR documentation** + +**✅ USER DECISION: Option 2 (Documentation-Only) - Recommended** + +**Rationale**: +- Phase 280 goal is **SSOT positioning + 導線固定** (NOT full migration) +- Pattern6: early-return doesn't naturally fit `compose::if_()` model +- Pattern7: 挙動不変保証が難しい、得られる差分が小さい +- **行動は最小が正解** - Full migration deferred to Phase 281 + +#### C1: Identify Hand-Rolled Frag Construction Locations + +**File**: `src/mir/builder/control_flow/plan/normalizer.rs` + +**Pattern6 (ScanWithInit)**: +- **Function**: `normalize_scan_with_init()` +- **識別コメント**: Search for `// Step 12: Build CoreLoopPlan` or `let branches = vec![...]` near `BranchStub { from: header_bb, cond: cond_loop` +- **Structure**: 5 blocks (preheader, header, body, step, found, after) +- **Hand-rolled**: 2 BranchStub + 2 EdgeStub +- **Composition opportunity**: **Hand-rolled clearer** (early exit breaks if_ model) + +**Pattern7 (SplitScan)**: +- **Function**: `normalize_split_scan()` +- **識別コメント**: Search for `// Build Frag with branches and wires` or `let branches = vec![...]` near `BranchStub { from: header_bb, cond: cond_loop` in split context +- **Structure**: 6 blocks (preheader, header, body, then, else, step, after) +- **Hand-rolled**: 2 BranchStub + 3 EdgeStub +- **Composition opportunity**: **Defer to Phase 281** (挙動不変保証が難しい) + +**Acceptance**: +- [ ] Pattern6 location identified (function name + 識別コメント) +- [ ] Pattern7 location identified (function name + 識別コメント) +- [ ] Composition opportunities assessed (both defer to Phase 281) + +#### C2: Document Hand-Rolled Construction (Defer Migration to Phase 281) + +**Scope**: Add TODO comments showing future refactor path + +**Example Documentation** (Option 2 - DEFAULT): + +**Pattern6** (normalize_scan_with_init): +```rust +// Phase 280 TODO: Hand-rolled Frag construction for early exit pattern +// Reason: `found` is early Return, doesn't fit compose::if_() model +// Future: Consider compose::cleanup() for early exit normalization (Phase 281+) +``` + +**Pattern7** (normalize_split_scan): +```rust +// Phase 280 TODO: Hand-rolled Frag construction for split scan pattern +// Target (Phase 281): compose::if_(body_bb, cond_match, then_frag, else_frag, step_frag) +// Reason deferred: 挙動不変保証が難しい、Phase 280 は SSOT positioning 優先 +// Migration: Phase 281+ で compose::if_() への移行を検討 +``` + +**Implementation**: +1. Search for hand-rolled locations using function name + 識別コメント (from C1) +2. Add TODO comments above hand-rolled construction +3. Document: (a) current structure, (b) future compose target, (c) defer reason + +**Acceptance**: +- [ ] TODO comments added to both Pattern6 and Pattern7 +- [ ] Comments document: current structure + future target + defer reason +- [ ] No behavior change (documentation-only) + +#### C3: Verify No Behavior Change (Smoke Tests - Optional) + +**Status**: Optional (Phase C is documentation-only, no code changes) + +**Test Strategy**: If desired, run representative smokes to verify baseline + +**Pattern6 Smoke (Optional)**: +```bash +bash tools/smokes/v2/profiles/integration/apps/phase258_p0_index_of_string_llvm_exe.sh +``` + +**Pattern7 Smoke (Optional)**: +```bash +# Find Pattern7 smokes (split) +tools/smokes/v2/run.sh --profile integration --filter "*split*" +``` + +**Quick Profile (Optional)**: +```bash +tools/smokes/v2/run.sh --profile quick +# Expected: 45/46 PASS (baseline maintained) +``` + +**Acceptance**: +- [ ] Smoke tests run if desired (optional - docs-only change) +- [ ] Behavior unchanged (guaranteed - no code modifications in Phase C) + +--- + +## Execution Order (Critical!) + +**MUST execute in strict A→B→C order**: + +1. **Phase A** (Docs-only, no code) ✅ **COMPLETE**: + - A1: Update edgecfg-fragments.md ✅ + - A2: Update joinir-architecture-overview.md ✅ + - A3: Create phase-280/README.md ✅ + - **Verify**: No code changes made ✅ + +2. **Phase B** (API solidification): + - B1: Add compose.rs module docs + - B2: Verify contract checklist + - B3: Add missing tests if needed + - **Verify**: `cargo test --lib --release` PASS + +3. **Phase C** (Pattern preparation - Documentation-only): + - C1: Identify hand-rolled locations (function name + 識別コメント) + - C2: Add TODO comments (Option 2 - default, defer migration to Phase 281) + - C3: Run smoke tests (optional - no code change, behavior guaranteed) + - **Verify**: No regression (docs-only, no behavior change) + +4. **Final Verification**: + - All acceptance criteria met + - Smoke tests PASS + - No regression + +--- + +## Acceptance Criteria + +### Phase A (Docs) ✅ **COMPLETE** + +- [x] edgecfg-fragments.md updated (5 sections, Active SSOT) +- [x] joinir-architecture-overview.md updated (absorption section + table) +- [x] phase-280/README.md created +- [x] All docs cross-reference each other +- [x] No code changes + +### Phase B (API) ✓ When all checked: + +- [ ] compose.rs module-level docs added +- [ ] Function constraints documented +- [ ] Composition contract verified +- [ ] Missing tests added if gaps found +- [ ] `cargo test --lib --release` PASS + +### Phase C (Pattern Prep) ✓ When all checked: + +- [ ] Hand-rolled locations identified (function name + 識別コメント) +- [ ] Documentation-only (Option 2) executed (TODO comments added) +- [ ] Smoke tests PASS (optional - no code change expected) +- [ ] Behavior unchanged (guaranteed - docs-only) + +### Overall ✓ When all checked: + +- [ ] No regression (all tests/smokes PASS) +- [ ] SSOT positioning clear +- [ ] Pattern6/7 prepared for Phase 281 + +--- + +## Risks and Mitigation + +### Risk 1: Composition API Doesn't Match Hand-Rolled Exactly + +- **Impact**: Behavior change, regression +- **Likelihood**: Medium +- **Mitigation**: Phase A (docs-only) first, Phase C Option 2 (document) is fallback +- **Phase 280 Decision**: Documentation-only (Option 2), defer refactor to Phase 281 + +### Risk 2: Pattern6/7 Have Hidden Dependencies + +- **Impact**: Refactor breaks edge cases +- **Likelihood**: Low (well-tested) +- **Mitigation**: C3 runs both Pattern-specific and quick profile smokes +- **Phase 280 Decision**: No refactor, only documentation + +### Risk 3: Smoke Tests Miss Edge Cases + +- **Impact**: Regression not caught +- **Likelihood**: Low (45+ tests in quick profile) +- **Mitigation**: Run both VM and LLVM backends if needed +- **Phase 280 Status**: Optional (docs-only change) + +--- + +## Critical Files + +### Phase A (Documentation) ✅ + +1. [`docs/development/current/main/design/edgecfg-fragments.md`](/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/design/edgecfg-fragments.md) ✅ +2. [`docs/development/current/main/joinir-architecture-overview.md`](/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/joinir-architecture-overview.md) ✅ +3. [`docs/development/current/main/phases/phase-280/README.md`](/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/phases/phase-280/README.md) ✅ (this file) + +### Phase B (Code: API) + +4. `src/mir/builder/control_flow/edgecfg/api/compose.rs` + +### Phase C (Code: Pattern Prep - Documentation-only) + +5. `src/mir/builder/control_flow/plan/normalizer.rs` + - Pattern6: `normalize_scan_with_init()` function + - Pattern7: `normalize_split_scan()` function + - Action: Add TODO comments (no code changes) + +--- + +## Related Phases + +### Predecessor Phases + +- **Phase 273 (P0-P4)**: Plan line SSOT for Pattern6/7 (DomainPlan → CorePlan → Frag → emit_frag) +- **Phase 265-268**: Frag composition API creation, terminator SSOT (emit_frag) +- **Phase 264**: BundleResolver loop fix (歴史/別案件, separate scope) + +### Successor Phases (Planned) + +- **Phase 281**: Full Pattern6/7 absorption (replace hand-rolled with compose_*) +- **Phase 282**: Router shrinkage (pattern numbers → test labels) +- **Phase 283+**: Pattern8 and beyond + +--- + +## Status Summary + +**Phase A**: ✅ **COMPLETE** (2025-12-23) +- A1: edgecfg-fragments.md updated ✅ +- A2: joinir-architecture-overview.md updated ✅ +- A3: phase-280/README.md created ✅ + +**Phase B**: ⏳ **In Progress** +- B1: compose.rs module docs (pending) +- B2: Verify composition contract (pending) +- B3: Add missing tests if needed (pending) + +**Phase C**: ⏸ **Pending** +- C1: Identify hand-rolled locations (pending) +- C2: Add TODO comments (pending) +- C3: Run smoke tests (optional) + +**Overall**: 33% complete (Phase A done, Phases B+C remaining) + +--- + +**End of Phase 280 README** diff --git a/src/mir/builder/control_flow/edgecfg/api/compose.rs b/src/mir/builder/control_flow/edgecfg/api/compose.rs index 06926782..0f4b51b4 100644 --- a/src/mir/builder/control_flow/edgecfg/api/compose.rs +++ b/src/mir/builder/control_flow/edgecfg/api/compose.rs @@ -1,12 +1,67 @@ /*! - * Frag 合成関数群(Phase 264: シグネチャのみ + TODO実装) + * # Frag Composition API - Single Source of Truth (Phase 280) * - * 設計原則: - * - 各関数は「入口の形」だけを固定 - * - 中身は TODO!() で次フェーズに委譲 - * - シグネチャ変更は破壊的変更として扱う - * - pub(crate) にして外部から誤って触れないようにする - * (実装フェーズ Phase 265+ で pub に昇格) + * This module is the **Single Source of Truth** for Frag composition. + * + * ## Purpose (Phase 280) + * + * 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) + * + * ## Entry Points (Composition Operations) + * + * - `seq(a, b)`: Sequential composition (Normal wiring) + * - `if_(header, cond, t, e, join_frag)`: Conditional composition (Branch wiring) + * - `loop_(loop_id, header, after, body)`: Loop composition (Break/Continue wiring) + * - `cleanup(body, cleanup)`: Cleanup composition (TODO: Phase 280+) + * + * ## Composition Contract (Invariants) + * + * - **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 CFG Transform**: Composition rearranges `exits`/`wires`/`branches` only + * + * ## Ownership Model (3-tier) + * + * 1. **Normalizer** (Tier 1): Allocates blocks/values, pattern-specific knowledge + * 2. **Composition** (Tier 2): Rearranges exits/wires/branches, pattern-agnostic + * 3. **Lowerer** (Tier 3): Emits MIR terminators via `emit_frag()` + * + * ## Usage Example + * + * ```rust + * // Tier 1: Normalizer allocates blocks + * let header_bb = builder.next_block_id(); + * let body_bb = builder.next_block_id(); + * let after_bb = builder.next_block_id(); + * + * // Build Frags for body + * let body_frag = Frag { /* body CFG */ }; + * + * // Tier 2: Composition wires exits (no allocation) + * let loop_frag = compose::loop_(loop_id, header_bb, after_bb, body_frag); + * + * // Tier 3: Lowerer emits terminators + * emit_frag(func, &loop_frag)?; + * ``` + * + * ## References + * + * - **SSOT Documentation**: `docs/development/current/main/design/edgecfg-fragments.md` (Active SSOT) + * - **Pattern Absorption**: `docs/development/current/main/joinir-architecture-overview.md` (Section 0.2) + * - **Phase 280 Roadmap**: `docs/development/current/main/phases/phase-280/README.md` + * + * ## History + * + * - Phase 264: Entry API creation (signatures only) + * - Phase 265-268: Implementation (seq/if/loop wiring, emit_frag SSOT) + * - Phase 280: SSOT positioning (composition as pattern absorption destination) */ use std::collections::BTreeMap; @@ -20,6 +75,25 @@ use super::branch_stub::BranchStub; // Phase 267 P0: Branch 生成に必要 /// 順次合成: `a; b` /// +/// # Phase 280: Composition SSOT +/// +/// ## Constraint (Caller Allocates) +/// +/// - **Caller allocates**: `b.entry` (`BasicBlockId`) +/// - **Composition wires**: `a.Normal` → `b.entry` +/// +/// ## Composition Law (Input → Output) +/// +/// - `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` +/// +/// ## Invariants Preserved +/// +/// - Wires/Exits separation: wires have `target = Some`, exits have `target = None` +/// - Terminator uniqueness: 1 block = 1 terminator (from-grouping in emit_frag) +/// - Entry consistency: `seq.entry` is valid `BasicBlockId` +/// /// # Phase 265 P2: wires/exits 分離実装完了 /// - a.Normal → b.entry を wires に追加(内部配線) /// - seq の exits[Normal] は b の Normal のみ(外へ出る exit) @@ -84,6 +158,27 @@ pub(crate) fn seq(a: Frag, b: Frag) -> Frag { /// 条件分岐合成: `if (cond) { t } else { e }` /// +/// # Phase 280: Composition SSOT +/// +/// ## Constraint (Caller Allocates) +/// +/// - **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**: `header` → `t.entry`/`e.entry` (BranchStub), `t/e.Normal` → `join_frag.entry` +/// +/// ## Composition Law (Input → Output) +/// +/// - `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` +/// +/// ## Invariants Preserved +/// +/// - Wires/Exits separation: BranchStub in `branches`, Normal wiring in `wires`, exits `target = None` +/// - Terminator uniqueness: 1 block = 1 terminator (header gets Branch, t/e/join get Jump/Return) +/// - Entry consistency: `if.entry` is valid `BasicBlockId` +/// /// # Phase 267 P0: Branch 生成実装完了 /// - header → then/else の BranchStub を branches に追加 /// - t/e.Normal → join_frag.entry を wires に追加(内部配線) @@ -194,6 +289,26 @@ pub(crate) fn if_( /// ループ合成: `loop (cond) { body }` /// +/// # Phase 280: Composition SSOT +/// +/// ## Constraint (Caller Allocates) +/// +/// - **Caller allocates**: `loop_id` (`LoopId`), `header`, `after` (`BasicBlockId`) +/// - **Composition wires**: `Continue(loop_id)` → `header`, `Break(loop_id)` → `after` +/// +/// ## Composition Law (Input → Output) +/// +/// - `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) +/// +/// ## Invariants Preserved +/// +/// - Wires/Exits separation: Continue/Break have `target = Some`, other exits `target = None` +/// - Terminator uniqueness: 1 block = 1 terminator (from-grouping in emit_frag) +/// - Entry consistency: `loop.entry` is valid `BasicBlockId` +/// /// # Phase 265 P2: wires/exits 分離実装完了 /// - Continue(loop_id) → header へ配線(wires へ) /// - Break(loop_id) → after へ配線(wires へ) @@ -268,6 +383,20 @@ pub(crate) fn loop_( /// cleanup 合成: finally の後継(すべての exit を正規化) /// +/// # Phase 280: Composition SSOT +/// +/// ## Planned Composition Law (Future: Phase 280+) +/// +/// - 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**: Phase 264 +/// - **Implementation**: TODO (Phase 280+) +/// - **Usage**: Not yet used (planned for exception/async cleanup) +/// /// # 配線ルール(TODO実装) /// - body の全 exit を cleanup 経由へリライト /// - cleanup 後に元の exit を再発射(ExitTag + payload を block params で運ぶ) @@ -276,15 +405,11 @@ pub(crate) fn loop_( /// # 引数 /// - `body`: 本体の断片 /// - `cleanup_block`: cleanup 処理を行うブロック -/// -/// # Phase 264 -/// - シグネチャのみ固定、中身は TODO -/// - pub(crate) で外部から触れないようにする pub(crate) fn cleanup( _body: Frag, _cleanup_block: BasicBlockId, ) -> Frag { - todo!("Phase 264: cleanup 合成は次フェーズで実装") + todo!("Phase 280+: cleanup 合成は将来実装予定(exception/async cleanup 用)") } #[cfg(test)] diff --git a/src/mir/builder/control_flow/plan/normalizer.rs b/src/mir/builder/control_flow/plan/normalizer.rs index 88fb9b9f..41734840 100644 --- a/src/mir/builder/control_flow/plan/normalizer.rs +++ b/src/mir/builder/control_flow/plan/normalizer.rs @@ -295,6 +295,10 @@ impl PlanNormalizer { values: vec![i_current], }; + // Phase 280 TODO: Hand-rolled Frag construction for early exit pattern + // Reason: `found_bb` is early Return, doesn't fit compose::if_() model + // Future: Consider compose::cleanup() for early exit normalization (Phase 281+) + // Current structure: 2 BranchStub (header→body/after, body→found/step) + 2 EdgeStub (step→header, found→Return) let branches = vec![ BranchStub { from: header_bb, @@ -688,6 +692,12 @@ impl PlanNormalizer { ]; // Step 12: Build Frag (2 branches + 3 wires) + // + // Phase 280 TODO: Hand-rolled Frag construction for split scan pattern + // Target (Phase 281): compose::if_(body_bb, cond_match, then_frag, else_frag, step_frag) + // Reason deferred: 挙動不変保証が難しい、Phase 280 は SSOT positioning 優先 + // Migration: Phase 281+ で compose::if_() への移行を検討 + // Current structure: 2 BranchStub (header→body/after, body→then/else) + 3 EdgeStub (then→step, else→step, step→header) let empty_args = EdgeArgs { layout: JumpArgsLayout::CarriersOnly, values: vec![],