docs: finalize loop canonicalizer design (P0 implementation-ready)
## Summary Loop Canonicalizer の設計を「実装可能」レベルまで固めた。 ## P0: 設計詳細化 ### LoopSkeleton の最小フィールド確定 - LoopSkeleton struct(steps/carriers/exits/captured) - SkeletonStep enum(HeaderCond/BreakCheck/ContinueCheck/Update/Body) - UpdateKind enum(ConstStep/Conditional/Arbitrary) - ExitContract, CarrierSlot, CarrierRole の定義 - SSOT 境界の原則(入力: AST、出力: Skeleton のみ) ### Capability の語彙固定(Fail-Fast reason タグ) - CAP_MISSING_* プレフィックスで統一 - 8 つの Capability を定義(ConstStepIncrement, SingleBreakPoint 等) - 新規追加時は設計書を先に更新するルール ### RoutingDecision の出力先決定 - error_tags: Fail-Fast エラーメッセージ - contract_checks: Phase 135 P1 verifier に統合 - NYASH_LOOP_ROUTING_TRACE: 開発時のルーティング追跡 - ErrorTagCollector を使用(新規 Box は作らない) ### 最初の対象ループ(skip_whitespace)の受け入れ基準 - Skeleton 差分を表形式で明示 - 必要 Capability を列挙 - 受け入れ基準 4 項目を定義 ## P1/P2: 確認完了 - joinir-design-map.md に loop-canonicalizer.md へのリンク済み - quick を重くしない運用は joinir-design-map.md に記載済み 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -72,10 +72,201 @@ Canonicalizer は “できない理由” を機械的に返す。
|
||||
- 非 JoinIR への退避(prohibited fallback)は導入しない。
|
||||
- 既定挙動は変えない(必要なら dev-only で段階投入する)。
|
||||
|
||||
## LoopSkeleton の最小フィールド(SSOT 境界)
|
||||
|
||||
Canonicalizer の出力は以下のフィールドに限定する(これ以上細かくしない):
|
||||
|
||||
```rust
|
||||
/// ループの骨格(Canonicalizer の唯一の出力)
|
||||
pub struct LoopSkeleton {
|
||||
/// ステップの列(HeaderCond, BodyInit, BreakCheck, Updates, Tail)
|
||||
pub steps: Vec<SkeletonStep>,
|
||||
|
||||
/// キャリア(ループ変数・更新規則・境界通過の契約)
|
||||
pub carriers: Vec<CarrierSlot>,
|
||||
|
||||
/// 出口契約(break/continue/return の有無と payload)
|
||||
pub exits: ExitContract,
|
||||
|
||||
/// 外部キャプチャ(省略可: 外側変数の取り込み)
|
||||
pub captured: Option<Vec<CapturedSlot>>,
|
||||
}
|
||||
|
||||
/// ステップの種類(最小限)
|
||||
pub enum SkeletonStep {
|
||||
/// ループ継続条件(loop(cond) の cond)
|
||||
HeaderCond { expr: AstExpr },
|
||||
|
||||
/// 早期終了チェック(if cond { break })
|
||||
BreakCheck { cond: AstExpr, has_value: bool },
|
||||
|
||||
/// スキップチェック(if cond { continue })
|
||||
ContinueCheck { cond: AstExpr },
|
||||
|
||||
/// キャリア更新(i = i + 1 など)
|
||||
Update { carrier_name: String, update_kind: UpdateKind },
|
||||
|
||||
/// 本体(その他の命令)
|
||||
Body { stmts: Vec<AstStmt> },
|
||||
}
|
||||
|
||||
/// キャリアの更新種別
|
||||
pub enum UpdateKind {
|
||||
/// 定数ステップ(i = i + const)
|
||||
ConstStep { delta: i64 },
|
||||
|
||||
/// 条件付き更新(if cond { x = a } else { x = b })
|
||||
Conditional { then_value: AstExpr, else_value: AstExpr },
|
||||
|
||||
/// 任意更新(上記以外)
|
||||
Arbitrary,
|
||||
}
|
||||
|
||||
/// 出口契約
|
||||
pub struct ExitContract {
|
||||
pub has_break: bool,
|
||||
pub has_continue: bool,
|
||||
pub has_return: bool,
|
||||
pub break_has_value: bool,
|
||||
}
|
||||
|
||||
/// キャリアスロット
|
||||
pub struct CarrierSlot {
|
||||
pub name: String,
|
||||
pub role: CarrierRole,
|
||||
pub update_kind: UpdateKind,
|
||||
}
|
||||
|
||||
/// キャリアの役割
|
||||
pub enum CarrierRole {
|
||||
/// ループカウンタ(i < n の i)
|
||||
Counter,
|
||||
/// アキュムレータ(sum += x の sum)
|
||||
Accumulator,
|
||||
/// 条件変数(while(is_valid) の is_valid)
|
||||
ConditionVar,
|
||||
/// 派生値(digit_pos 等)
|
||||
Derived,
|
||||
}
|
||||
```
|
||||
|
||||
### SSOT 境界の原則
|
||||
|
||||
- **入力**: AST(LoopExpr)
|
||||
- **出力**: LoopSkeleton のみ(JoinIR は生成しない)
|
||||
- **禁止**: Skeleton に JoinIR 固有の情報を含めない(BlockId, ValueId 等)
|
||||
|
||||
---
|
||||
|
||||
## Capability の語彙(Fail-Fast reason タグ)
|
||||
|
||||
Skeleton を生成できても lower/merge できるとは限らない。以下の Capability で判定する:
|
||||
|
||||
| Capability | 説明 | 未達時の理由タグ |
|
||||
|--------------------------|------------------------------------------|-------------------------------------|
|
||||
| `ConstStepIncrement` | キャリア更新が定数ステップ(i=i+const) | `CAP_MISSING_CONST_STEP` |
|
||||
| `SingleBreakPoint` | break が単一箇所のみ | `CAP_MISSING_SINGLE_BREAK` |
|
||||
| `SingleContinuePoint` | continue が単一箇所のみ | `CAP_MISSING_SINGLE_CONTINUE` |
|
||||
| `NoSideEffectInHeader` | ループ条件に副作用がない | `CAP_MISSING_PURE_HEADER` |
|
||||
| `OuterLocalCondition` | 条件変数が外側スコープで定義済み | `CAP_MISSING_OUTER_LOCAL_COND` |
|
||||
| `ExitBindingsComplete` | 境界へ渡す値が過不足ない | `CAP_MISSING_EXIT_BINDINGS` |
|
||||
| `CarrierPromotion` | LoopBodyLocal を昇格可能 | `CAP_MISSING_CARRIER_PROMOTION` |
|
||||
| `BreakValueConsistent` | break 値の型が一貫 | `CAP_MISSING_BREAK_VALUE_TYPE` |
|
||||
|
||||
### 語彙の安定性
|
||||
|
||||
- reason タグは `CAP_MISSING_*` プレフィックスで統一
|
||||
- 新規追加時は `loop-canonicalizer.md` に先に追記してからコード実装
|
||||
- ログ / 統計 / error_tags で集約可能
|
||||
|
||||
---
|
||||
|
||||
## RoutingDecision の出力先
|
||||
|
||||
Canonicalizer の判定結果は `RoutingDecision` に集約し、以下に流す:
|
||||
|
||||
```rust
|
||||
pub struct RoutingDecision {
|
||||
/// 選択された Pattern(None = Fail-Fast)
|
||||
pub chosen: Option<LoopPattern>,
|
||||
|
||||
/// 不足している Capability のリスト
|
||||
pub missing_caps: Vec<&'static str>,
|
||||
|
||||
/// 選択理由(デバッグ用)
|
||||
pub notes: Vec<String>,
|
||||
|
||||
/// error_tags への追記(contract_checks 用)
|
||||
pub error_tags: Vec<ErrorTag>,
|
||||
}
|
||||
```
|
||||
|
||||
### 出力先マッピング
|
||||
|
||||
| 出力先 | 条件 | 用途 |
|
||||
|----------------------------|-------------------------------|-----------------------------------|
|
||||
| `error_tags` | `chosen.is_none()` | Fail-Fast のエラーメッセージ |
|
||||
| `contract_checks` | debug build + 契約違反時 | Phase 135 P1 の verifier に統合 |
|
||||
| `NYASH_LOOP_ROUTING_TRACE` | 環境変数 ON 時 | 開発時のルーティング追跡 |
|
||||
| 統計 JSON | 将来拡張 | Corpus 分析(Skeleton Signature) |
|
||||
|
||||
### error_tags との統合
|
||||
|
||||
- `RoutingDecision.error_tags` は `src/mir/builder/control_flow/joinir/error_tags.rs` に追記
|
||||
- 既存の `ErrorTagCollector` を使用(新規 Box は作らない)
|
||||
|
||||
---
|
||||
|
||||
## 最初の対象ループ: skip_whitespace(受け入れ基準)
|
||||
|
||||
### 対象ファイル
|
||||
|
||||
`tools/selfhost/test_pattern3_skip_whitespace.hako`
|
||||
|
||||
```hako
|
||||
loop(p < len) {
|
||||
local ch = s.substring(p, p + 1)
|
||||
local is_ws = ... // whitespace 判定
|
||||
if is_ws == 1 {
|
||||
p = p + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Skeleton 差分
|
||||
|
||||
| フィールド | 値 |
|
||||
|-------------------|-------------------------------------------|
|
||||
| `steps[0]` | `HeaderCond { expr: p < len }` |
|
||||
| `steps[1]` | `Body { ... }` (ch, is_ws 計算) |
|
||||
| `steps[2]` | `BreakCheck { cond: is_ws == 0 }` |
|
||||
| `steps[3]` | `Update { carrier: "p", ConstStep(1) }` |
|
||||
| `carriers[0]` | `{ name: "p", role: Counter, ConstStep }` |
|
||||
| `exits` | `{ has_break: true, break_has_value: false }` |
|
||||
|
||||
### 必要 Capability
|
||||
|
||||
- ✅ `ConstStepIncrement` (p = p + 1)
|
||||
- ✅ `SingleBreakPoint` (else { break } のみ)
|
||||
- ✅ `OuterLocalCondition` (p, len は外側定義)
|
||||
- ✅ `ExitBindingsComplete` (p を境界に渡す)
|
||||
|
||||
### 受け入れ基準
|
||||
|
||||
1. `LoopCanonicalizer::canonicalize(ast)` が上記 Skeleton を返す
|
||||
2. `RoutingDecision.chosen == Some(Pattern3)`
|
||||
3. `RoutingDecision.missing_caps == []`
|
||||
4. 既存 smoke `phase135_trim_mir_verify.sh` が退行しない
|
||||
|
||||
---
|
||||
|
||||
## 追加・変更チェックリスト
|
||||
|
||||
- [ ] 追加するループ形を最小 fixture に落とす(再現固定)
|
||||
- [ ] LoopSkeleton の差分(steps/exits/carriers)を明示する
|
||||
- [ ] 必要 Capability を列挙し、未達は Fail-Fast(理由が出る)
|
||||
- [ ] 既存 smoke/verify が退行しない(quick は重くしない)
|
||||
- [ ] 新規 Capability は先に `loop-canonicalizer.md` に追記してから実装
|
||||
|
||||
|
||||
Reference in New Issue
Block a user