Phase 114完了に伴うドキュメント更新: - 30-Backlog.md: Phase 114の次候補から完了済みに移行 - design/control-tree.md: Phase 110-112の進捗反映 - design/README.md, joinir-design-map.md, phases/README.md: マイナー更新 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
17 KiB
JoinIR Design Map(現役の地図)
Status: SSOT(navigation)
Scope: JoinIR の「Loop/If を JoinIR 化して MIR に統合する」導線(検出→shape guard→lower→merge→契約検証)
Related:
- SSOT:
docs/development/current/main/joinir-architecture-overview.md - SSOT:
docs/development/current/main/loop_pattern_space.md - SSOT:
docs/development/current/main/joinir-boundary-builder-pattern.md - SSOT:
docs/development/current/main/design/loop-canonicalizer.md
このドキュメントは Phase ログではなく、「JoinIR を触る人が迷子にならず、どこを直すべきかが一発で分かる」ための設計図(地図)です。
詳細な経緯・作業ログは docs/development/current/main/phases/ と docs/development/current/main/investigations/ に分離します。
役割分担(joinir-architecture-overview との分離)
このファイルは「実装導線の地図」の SSOT です(navigation SSOT)。
意味論・契約・不変条件の本文(normative)は docs/development/current/main/joinir-architecture-overview.md を SSOT とします。
使い分け:
- 「JoinIR が何を保証し、何を Fail-Fast で落とすべきか」→
joinir-architecture-overview.md - 「どのファイルを触るべきか」「入口はどこか」「追加手順は?」→ この
joinir-design-map.md - 「経緯/ログ/切り分け」→
docs/development/current/main/phases/とdocs/development/current/main/investigations/
Design Notes(箱理論の“効いてるところ”)
このプロジェクトで JoinIR が効いている理由は、「PHI/CFG を一枚岩にせず、“意味の境界”を箱で分けている」点にある。
- スコープ解決の SSOT を固定する(検索順):
ConditionEnv → LoopBodyLocalEnv → CapturedEnv → CarrierInfo- 「なぜこの変数が見える/見えないか」を、層の契約として説明できるようにする
- ループは “形(pattern)” を言語化して段階投入する(fixture + shape guard + Fail-Fast)
- pattern を増やす代わりに、policy(family)で “同型” を吸収する
- Capability は “解禁の順序” を SSOT 化する(最小形→回帰で積み上げ)
- 未対応は best-effort で誤魔化さず、Fail-Fast で理由を固定する
最近の改善(完了):
- policy Reject の "hint" を
error_tagsに集約して、修正方針を 1 行で出せるようにした(Phase 109) - 構造SSOT(LoopSkeleton + StepTree)へ寄せて、policy/step箱の増殖先を “構造” に集約する足場を追加した(Phase 110–112)
Error Tags with Hints (Phase 109)
SSOT: error_tags is the single source for "tag + message + hint" errors.
Policy:
- policy/validator/merge use error_tags (no raw strings)
- hint is "1-line fix suggestion" only (no long explanations)
- Format:
[joinir/<category>/<tag>] <message> Hint: <hint>
Examples:
[joinir/phase107/balanced_depth_scan/missing_tail_inc] ... Hint: add 'i = i + 1' at top-level[joinir/phase100/pinned/reassigned] ... Hint: remove reassign, or promote to carrier
1枚図: レイヤー(AST → JoinIR → MIR → Backend)
flowchart LR
A[AST] -->|Frontend lowering| J1[JoinIR (Structured)]
J1 -->|Normalize / Shape guard| J2[JoinIR (Normalized)]
J2 -->|JoinIR → MIR bridge| M1[MIR Module]
M1 -->|Merge into host function| M2[MIR (in builder)]
M2 --> B[Backend\n(VM / LLVM / Cranelift)]
subgraph Frontend
A
J1
end
subgraph JoinIR Core
J2
end
subgraph MIR Builder
M1
M2
end
読み方:
- 「Loop/If の形が認識されない」: Pattern 検出(Feature/Kind)と shape guard を見る
- 「JoinIR は生成できるが統合で壊れる」: Merge(ValueId/PHI/ExitLine/Boundary)と契約検証を見る
- 「なぜこのエラータグが出たか」: ErrorTags(SSOT)を起点に呼び出し元へ辿る
“箱”の責務マップ(担当境界)
| 領域 | 役割(何を決めるか) | 主な入口/箱(SSOT寄り) | 主な出力 | Fail-Fast(典型) |
|---|---|---|---|---|
| Pattern検出 | ループ形を分類し、どの lowerer に渡すか決める | src/mir/builder/control_flow/joinir/patterns/router.rs, src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs, src/mir/builder/control_flow/joinir/patterns/pattern2_inputs_facts_box.rs, src/mir/builder/control_flow/joinir/patterns/policies/, src/mir/loop_pattern_detection/mod.rs |
LoopPatternKind / Pattern 選択 |
「分類不能」→ 明示的に Err(サイレントな非JoinIR退避は禁止) |
| shape guard | 「この shape なら lower/merge 契約が成立する」を保証する | src/mir/join_ir/normalized/shape_guard.rs, src/mir/builder/control_flow/joinir/patterns/*_validator.rs |
shape OK / 詳細診断 | shape 不一致を握りつぶさず Err |
| lowering | JoinIR(Structured/Normalized)を生成する | src/mir/join_ir/lowering/mod.rs, src/mir/builder/control_flow/joinir/patterns/pattern2_lowering_orchestrator.rs, src/mir/builder/control_flow/joinir/patterns/pattern*_*.rs |
JoinModule |
未対応の構造は error_tags::freeze(...) 等で Err |
| merge | JoinIR→MIR 変換後、ホスト関数に統合する | src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs, src/mir/builder/control_flow/joinir/merge/mod.rs |
ホスト MIR のブロック/ValueId 更新 | ValueId 競合、ExitLine 未接続、PHI 破綻を Err |
| ExitMeta | 「出口でどの carrier をどの host slot に戻すか」のメタ | src/mir/join_ir/lowering/carrier_info.rs, src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs |
ExitMeta / exit_bindings |
carrier 不整合(不足/過剰)を Err |
| CarrierInit | carrier 初期化の SSOT(FromHost/Const/LoopLocal) | src/mir/builder/control_flow/joinir/merge/carrier_init_builder.rs, src/mir/join_ir/lowering/carrier_info.rs |
初期値 ValueId |
初期化経路の分岐が散らばらない(SSOT を使う) |
| ErrorTags | エラータグ整形の SSOT(検索性・一貫性) | src/mir/join_ir/lowering/error_tags.rs |
文字列タグ | 文字列ハードコードを避け、タグを一元化 |
注:
- Pattern 検出は「関数名 by-name 分岐」に依存しない(構造で決める)。必要なら dev/診断限定のガードに閉じ込める。
- shape guard は「OK なら後工程が前提にできる契約」を固定する場所で、曖昧な許容をしない。
入口(コード側のエントリポイント)
Loop(builder 側の導線)
- Router(builder 入口):
src/mir/builder/control_flow/joinir/routing.rsMirBuilder::try_cf_loop_joinir(...)(JoinIR ルートへ入る最初の関数)MirBuilder::cf_loop_joinir_impl(...)(pattern router → legacy binding の順)
- Pattern router(テーブル駆動):
src/mir/builder/control_flow/joinir/patterns/router.rs - Feature extraction:
src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs - Pattern 実体(代表):
src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rssrc/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rssrc/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rssrc/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rssrc/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs
- 変換パイプライン(JoinIR→MIR→Merge の統一導線):
- Merge(統合の本体):
src/mir/builder/control_flow/joinir/merge/mod.rs- ExitLine:
src/mir/builder/control_flow/joinir/merge/exit_line/mod.rs - Merge の契約検証(debug):
src/mir/builder/control_flow/joinir/merge/contract_checks.rs
- ExitLine:
JoinIR(IR/正規化/ブリッジ)
- JoinIR 定義・入口:
src/mir/join_ir/mod.rs - Normalized / shape guard:
src/mir/join_ir/normalized/shape_guard.rs - JoinIR → MIR bridge:
src/mir/join_ir_vm_bridge/mod.rs
共通(診断とタグ)
- Trace(JoinIR ルートの統一トレース):
src/mir/builder/control_flow/joinir/trace.rs - Error tags(SSOT):
src/mir/join_ir/lowering/error_tags.rs - Loop Canonicalizer(前処理 SSOT):
src/mir/loop_canonicalizer/mod.rs - ConditionOnly Derived Slot(Phase 93):
src/mir/join_ir/lowering/common/condition_only_emitter.rs - BodyLocalDerived Slot(Phase 94 / P5b):
src/mir/join_ir/lowering/common/body_local_derived_emitter.rs
不変条件(Fail-Fast)
JoinIR を触るときは、次を破ったら「即エラーで止める」前提で設計・実装する。
形状(shape)
- Pattern は「認識できる shape だけ」を通し、曖昧な許容をしない。
- 形状が合わないときは
Ok(None)で静かに進めない(非JoinIRへの退避を作らない)。- 例外: 明確な “routing” で「別 JoinIR 経路」を選ぶのは可(同一層内での選択)。
- depth-scan(Phase 107:
find_balanced_*)は Pattern2 の policy で受理し、break 条件は “derived 値(depth_delta/depth_next)” から合成して SSOT 化する(by-name 分岐は禁止)。
ValueId / PHI / Boundary の世界
- JoinIR 内部(JoinValueSpace 等)と host MIR builder の ValueId を混ぜない。
- Boundary は JoinIR↔host の橋渡し契約:
join_inputsとhost_inputsの対応が明示される- Exit 側は “carrier 名” をキーにして reconnection される(ExitMeta/exit_bindings)
- PHI は「誰が確保するか」を固定し、衝突を許さない(PHI dst の予約・再利用禁止)。
ExitLine 契約
- ExitLine は「出口へ集約する」ための契約であり、未接続の経路を残さない。
- carrier/slot の不足・余剰・不整合は
error_tags::exit_line_contract(...)等で即エラーにする。
Allocator SSOT(Phase 135)
- 原則: すべての ValueId 発行は単一の allocator(
ConditionContext.alloc_value)を経由する - 禁止事項: ConditionLoweringBox / ExprLowerer での内部カウンタ使用
- 理由: JoinIR params (ValueId(1000+)) と衝突し、merge 時に header PHI dst を上書きする
- 検出:
--verifyで "Value %N defined multiple times" エラー - 修正例: Phase 135 P0 - ConditionLoweringBox が
&mut ConditionContextを受け取り、alloc_value を必ず使用
Boundary Injection SSA(Phase 135)
- 原則: condition_bindings は alias を許すが、注入 Copy の dst は重複させない
- Fail-Fast: 異なる source が同一 dst に来る場合は即座にエラー
- 理由: MIR SSA を破壊し、VM/LLVM で未定義動作を引き起こす
- 検出:
joinir_inline_boundary_injector.rsの重複排除ロジック - 修正例: Phase 135 P0 - Boundary Copy deduplication by dst
Box 実装チェックリスト
Box を新規実装・変更した際は以下を必ず確認:
- ✅
--verifyで契約違反が検出されるか(SSA 破綻・ValueId 衝突) - ✅ smoke test で退行が出ないか(phase132/133/135 など)
- ✅ allocator を bypass していないか(ConditionContext.alloc_value を使っているか)
- ✅ boundary injection で dst 重複を防いでいるか
追加手順チェックリスト(新しいループ形を飲み込む最小手順)
「新しいループ形」を JoinIR で扱えるようにするときの最小手順。
- Fixture を追加(再現可能に固定)
apps/tests/またはapps/smokes/に最小の.hakoを追加(対象形が一目で分かるもの)
- Pattern/feature を追加(検出)
src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs(必要なら feature 抽出を拡張)src/mir/loop_pattern_detection/(分類/補助解析が必要ならここに追加)
- shape guard を追加(契約の固定)
- 形状・前提条件を validator として分離し、失敗は Err にする
- lower を追加(JoinIR を生成)
- 既存 pattern のコピーではなく、「今回の shape が要求する最小構成」にする
- エラーは
src/mir/join_ir/lowering/error_tags.rsを使いタグを固定する
- merge/ExitLine を接続(契約が満たされるように)
- carrier/ExitMeta/Boundary が揃っているか確認する
- Tests を追加(仕様固定)
- unit: pattern/validator/merge の局所テスト
- smoke:
tools/smokes/v2/の profile に軽いケースを追加(quick を重くしない)
- Docs を更新(地図を更新)
docs/development/current/main/loop_pattern_space.md(パターン空間に追記が必要なら)docs/development/current/main/joinir-architecture-overview.md(箱/契約が増えたなら)- 本ファイル(入口・責務マップの更新)
Smoke(LLVM EXE)SSOT(integration)
LLVM EXE の integration smoke は、原則として共通ヘルパーに寄せる(重複禁止 / SKIP 規約の統一)。
- SSOT helper:
tools/smokes/v2/lib/llvm_exe_runner.sh- LLVM 前提チェック(
llvm-config-18/llvmlite/--backend llvm) - 必須プラグインの dlopen gating + 必要時だけ
tools/plugins/build-all.sh - build → run → 数値行だけ抽出して比較(デバッグログ混入耐性)
- LLVM 前提チェック(
スコープ解決の SSOT(Pinned Read‑Only Captures)
JoinIR lowering では「ループ内で参照される値」を次の層で解決する(探索順 SSOT):
ConditionEnv(条件式に必要な値)LoopBodyLocalEnv(ループ body 内で初期化される一時変数)CapturedEnv(ループ外から入ってくる read‑only 入力)
CapturedEnv は “読み取り専用入力” の SSOT として扱い、内部で区別する:
Explicit(従来の capture)Pinned(ループ外 local を loop 内 receiver として参照するための read‑only capture)
Fail‑Fast(Pinned):
- loop body 内で再代入される変数は pinned 禁止
- loop entry 時点で host 側 ValueId が無い場合は拒否(黙って skip しない)
設計メモ: docs/development/current/main/phases/phase-100/README.md