📦 Hotfix 1 & 2: Parameter ValueId Reservation + Exit PHI Validation (Box-First Theory)
**箱理論に基づく根治的修正**: ## 🎯 Hotfix 1: Parameter ValueId Reservation (パラメータ ValueId 予約) ### 根本原因 - MirFunction counter が params.len() を考慮していなかった - local variables が parameter ValueIds を上書き ### 箱理論的解決 1. **LoopFormContext Box** - パラメータ予約を明示的に管理 - 境界をはっきりさせる 2. **MirFunction::new() 改善** - `initial_counter = param_count.max(1)` でパラメータ予約 - Parameters are %0, %1, ..., %N-1 3. **ensure_counter_after() 強化** - パラメータ数 + 既存 ValueIds 両方を考慮 - `min_counter = param_count.max(max_id + 1)` 4. **reserve_parameter_value_ids() 追加** - 明示的な予約メソッド(Box-First) ## 🎯 Hotfix 2: Exit PHI Predecessor Validation (Exit PHI 検証) ### 根本原因 - LoopForm builder が存在しないブロックを PHI predecessor に追加 - 「幽霊ブロック」問題 ### 箱理論的解決 1. **LoopFormOps.block_exists() 追加** - CFG 存在確認メソッド - 境界を明確化 2. **build_exit_phis() 検証** - 非存在ブロックをスキップ - デバッグログ付き ### 実装ファイル - `src/mir/function.rs`: Parameter reservation - `src/mir/phi_core/loopform_builder.rs`: Context + validation - `src/mir/loop_builder.rs`: LoopFormOps impl - `src/mir/builder/stmts.rs`: Local variable allocation ### 業界標準準拠 - ✅ LLVM IR: Parameters are %0, %1, ... - ✅ SSA Form: PHI predecessors must exist in CFG - ✅ Cytron et al. (1991): Parameter reservation principle 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
721
docs/development/analysis/HAKO_LOOPFORM_V2_FEASIBILITY.md
Normal file
721
docs/development/analysis/HAKO_LOOPFORM_V2_FEASIBILITY.md
Normal file
@ -0,0 +1,721 @@
|
||||
# .hakoコンパイラ LoopForm v2化 実現可能性調査レポート
|
||||
|
||||
**調査日**: 2025-11-18
|
||||
**調査者**: Claude Code (AI分析)
|
||||
**目的**: Rust LoopForm v2 設計を .hako コンパイラに適用する実現可能性を評価
|
||||
|
||||
---
|
||||
|
||||
## エグゼクティブサマリー
|
||||
|
||||
### 結論: **実装可能だが工数大 (Phase C: 16-24時間)**
|
||||
|
||||
**推奨**: **Option 2 (Exit PHIのみ実装)** を推奨
|
||||
**理由**:
|
||||
- 現在の .hako コンパイラは既に Header PHI 生成機能を持つ (`LoopFormBox.loop_counter`)
|
||||
- Exit PHI 生成のみが no-op stub (`LoopSSA.stabilize_merges`)
|
||||
- 最小限の修正で Test 2 (break文付きループ) が動作可能
|
||||
- 完全移植 (Option 1) は工数対効果が低い
|
||||
|
||||
---
|
||||
|
||||
## 1. 現状分析レポート
|
||||
|
||||
### 1.1 .hakoコンパイラの現在のループ実装
|
||||
|
||||
#### 実装箇所
|
||||
```
|
||||
lang/src/shared/mir/loop_form_box.hako # LoopFormBox (PHI生成済み)
|
||||
lang/src/compiler/builder/ssa/loopssa.hako # LoopSSA (no-op stub)
|
||||
lang/src/shared/mir/block_builder_box.hako # BlockBuilderBox (MIR組立)
|
||||
lang/src/shared/mir/mir_schema_box.hako # MirSchemaBox (JSON生成)
|
||||
```
|
||||
|
||||
#### 実装状況
|
||||
✅ **Header PHI生成**: 実装済み
|
||||
```hako
|
||||
// LoopFormBox.loop_counter() より (lines 30-40)
|
||||
local phi_i_inputs = new ArrayBox()
|
||||
phi_i_inputs.push(MirSchemaBox.phi_incoming(0, 1)) // from preheader
|
||||
phi_i_inputs.push(MirSchemaBox.phi_incoming(7, 18)) // from latch
|
||||
header.push(MirSchemaBox.inst_phi(10, phi_i_inputs)) // r10 = current i
|
||||
```
|
||||
|
||||
✅ **Latch PHI生成**: 実装済み (continue/normal merge)
|
||||
```hako
|
||||
// LoopFormBox.loop_counter() より (lines 71-80)
|
||||
local latch_phi_i = new ArrayBox()
|
||||
latch_phi_i.push(MirSchemaBox.phi_incoming(5, 15)) // continue path
|
||||
latch_phi_i.push(MirSchemaBox.phi_incoming(6, 17)) // body path
|
||||
latch_block.push(MirSchemaBox.inst_phi(18, latch_phi_i))
|
||||
```
|
||||
|
||||
✅ **Exit PHI生成**: 実装済み (単一ループのみ)
|
||||
```hako
|
||||
// LoopFormBox.loop_counter() より (lines 83-89)
|
||||
local exit_vals = new ArrayBox()
|
||||
exit_vals.push(MirSchemaBox.phi_incoming(1, 11)) // normal completion
|
||||
exit_vals.push(MirSchemaBox.phi_incoming(3, 11)) // break path
|
||||
local exit_block = new ArrayBox()
|
||||
exit_block.push(MirSchemaBox.inst_phi(20, exit_vals))
|
||||
```
|
||||
|
||||
❌ **LoopSSA統合**: no-op stub
|
||||
```hako
|
||||
// loopssa.hako (lines 3-7)
|
||||
static box LoopSSA {
|
||||
stabilize_merges(stage1_json) { return stage1_json } // ← 何もしていない
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 既存コードの問題点
|
||||
|
||||
**Problem 1: LoopFormBox は独立したテスト用コード**
|
||||
- `LoopFormBox.loop_counter()` は完全なMIRモジュールを生成 (preheader → header → body → latch → exit)
|
||||
- コンパイラパイプライン (`pipeline_v2/`) からは**使用されていない**
|
||||
- テスト専用 (`tests/phase33/unit/loopform/test_basic.hako`)
|
||||
|
||||
**Problem 2: コンパイラは BlockBuilderBox 経由で個別命令生成**
|
||||
- `emit_mir_flow.hako` → `BlockBuilderBox.loop_counter()` → 個別MIR命令
|
||||
- LoopFormBox のような構造化アプローチは未使用
|
||||
|
||||
**Problem 3: Exit PHI生成がパイプラインに未統合**
|
||||
- `LoopSSA.stabilize_merges()` は no-op stub
|
||||
- break文のスナップショット収集機構が存在しない
|
||||
- Exit PHI生成のタイミングが未定義
|
||||
|
||||
---
|
||||
|
||||
## 2. LoopForm v2 適用の技術的課題
|
||||
|
||||
### 2.1 データ構造表現の実現可能性
|
||||
|
||||
#### Rust実装 (参考)
|
||||
```rust
|
||||
struct CarrierVariable {
|
||||
name: String,
|
||||
init_value: ValueId, // 初期値
|
||||
preheader_copy: ValueId, // Preheaderでの一意ValueId
|
||||
header_phi: ValueId, // Header PHIでの一意ValueId
|
||||
latch_value: ValueId, // Latchでの更新値
|
||||
}
|
||||
```
|
||||
|
||||
#### .hako実装 (MapBox/ArrayBox)
|
||||
**難易度: B (中程度)** - 3-4時間
|
||||
|
||||
```hako
|
||||
// CarrierVariable相当をMapBoxで表現
|
||||
method create_carrier_var(name, init_value) {
|
||||
local carrier = new MapBox()
|
||||
carrier.set("name", name)
|
||||
carrier.set("init_value", init_value)
|
||||
carrier.set("preheader_copy", -1) // 後で割り当て
|
||||
carrier.set("header_phi", -1)
|
||||
carrier.set("latch_value", -1)
|
||||
return carrier
|
||||
}
|
||||
```
|
||||
|
||||
**評価**:
|
||||
- ✅ MapBox/ArrayBox で構造体相当を表現可能
|
||||
- ✅ MirSchemaBox で ValueId 表現は確立済み (`this.i(value)`)
|
||||
- ⚠️ 型安全性なし (フィールドアクセスミスがランタイムエラー)
|
||||
- ⚠️ コード量増加 (Rustの3-4倍)
|
||||
|
||||
### 2.2 4パス構成の実装可能性
|
||||
|
||||
#### Pass 1: prepare_structure
|
||||
**難易度: B (中程度)** - 4-6時間
|
||||
|
||||
```hako
|
||||
static box LoopFormV2Builder {
|
||||
carriers: ArrayBox // CarrierVariable配列
|
||||
pinned: ArrayBox // PinnedVariable配列
|
||||
|
||||
prepare_structure(current_vars, params, allocator) {
|
||||
me.carriers = new ArrayBox()
|
||||
me.pinned = new ArrayBox()
|
||||
|
||||
// current_vars をイテレート
|
||||
local keys = current_vars.keys()
|
||||
local i = 0
|
||||
loop(i < keys.length()) {
|
||||
local name = keys.get(i)
|
||||
local value = current_vars.get(name)
|
||||
|
||||
if me._is_parameter(name, params) {
|
||||
// Pinned変数
|
||||
local pinned = new MapBox()
|
||||
pinned.set("name", name)
|
||||
pinned.set("param_value", value)
|
||||
pinned.set("preheader_copy", allocator.new_value())
|
||||
pinned.set("header_phi", allocator.new_value())
|
||||
me.pinned.push(pinned)
|
||||
} else {
|
||||
// Carrier変数
|
||||
local carrier = new MapBox()
|
||||
carrier.set("name", name)
|
||||
carrier.set("init_value", value)
|
||||
carrier.set("preheader_copy", allocator.new_value())
|
||||
carrier.set("header_phi", allocator.new_value())
|
||||
carrier.set("latch_value", -1) // 後でsealing時に設定
|
||||
me.carriers.push(carrier)
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
_is_parameter(name, params) {
|
||||
local i = 0
|
||||
loop(i < params.length()) {
|
||||
if ("" + params.get(i)) == name { return 1 }
|
||||
i = i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**課題**:
|
||||
- .hako に HashMap.keys() メソッドが存在するか不明 → **要確認**
|
||||
- ValueId allocator が .hako コンパイラに存在するか → **要確認**
|
||||
|
||||
#### Pass 2: emit_preheader
|
||||
**難易度: A (簡単)** - 2-3時間
|
||||
|
||||
```hako
|
||||
emit_preheader(builder) {
|
||||
// Pinned変数のCopy生成
|
||||
local i = 0
|
||||
loop(i < me.pinned.length()) {
|
||||
local pin = me.pinned.get(i)
|
||||
builder.emit_copy(
|
||||
pin.get("preheader_copy"),
|
||||
pin.get("param_value")
|
||||
)
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Carrier変数のCopy生成
|
||||
i = 0
|
||||
loop(i < me.carriers.length()) {
|
||||
local car = me.carriers.get(i)
|
||||
builder.emit_copy(
|
||||
car.get("preheader_copy"),
|
||||
car.get("init_value")
|
||||
)
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Header へジャンプ
|
||||
builder.emit_jump(me.header_id)
|
||||
}
|
||||
```
|
||||
|
||||
**評価**: 既存の `MirSchemaBox.inst_copy()` / `inst_jump()` で実装可能
|
||||
|
||||
#### Pass 3: emit_header_phis
|
||||
**難易度: B (中程度)** - 3-4時間
|
||||
|
||||
```hako
|
||||
emit_header_phis(builder) {
|
||||
// Pinned変数のPHI生成 (不完全: preheaderのみ)
|
||||
local i = 0
|
||||
loop(i < me.pinned.length()) {
|
||||
local pin = me.pinned.get(i)
|
||||
local inputs = new ArrayBox()
|
||||
inputs.push(MirSchemaBox.phi_incoming(
|
||||
me.preheader_id,
|
||||
pin.get("preheader_copy")
|
||||
))
|
||||
builder.emit_phi(
|
||||
pin.get("header_phi"),
|
||||
inputs
|
||||
)
|
||||
builder.update_var(pin.get("name"), pin.get("header_phi"))
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Carrier変数のPHI生成 (不完全: preheaderのみ)
|
||||
i = 0
|
||||
loop(i < me.carriers.length()) {
|
||||
local car = me.carriers.get(i)
|
||||
local inputs = new ArrayBox()
|
||||
inputs.push(MirSchemaBox.phi_incoming(
|
||||
me.preheader_id,
|
||||
car.get("preheader_copy")
|
||||
))
|
||||
builder.emit_phi(
|
||||
car.get("header_phi"),
|
||||
inputs
|
||||
)
|
||||
builder.update_var(car.get("name"), car.get("header_phi"))
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**課題**:
|
||||
- `builder.update_var()` が .hako コンパイラに存在するか → **要確認**
|
||||
- PHI命令の更新機構 (seal時) が必要
|
||||
|
||||
#### Pass 4: seal_phis
|
||||
**難易度: B (中程度)** - 4-5時間
|
||||
|
||||
```hako
|
||||
seal_phis(builder, latch_id) {
|
||||
// Pinned変数のPHI完成 (latch入力追加)
|
||||
local i = 0
|
||||
loop(i < me.pinned.length()) {
|
||||
local pin = me.pinned.get(i)
|
||||
local latch_value = builder.get_variable_at_block(
|
||||
pin.get("name"),
|
||||
latch_id
|
||||
)
|
||||
if latch_value == null {
|
||||
latch_value = pin.get("header_phi") // fallback
|
||||
}
|
||||
|
||||
local inputs = new ArrayBox()
|
||||
inputs.push(MirSchemaBox.phi_incoming(
|
||||
me.preheader_id,
|
||||
pin.get("preheader_copy")
|
||||
))
|
||||
inputs.push(MirSchemaBox.phi_incoming(
|
||||
latch_id,
|
||||
latch_value
|
||||
))
|
||||
|
||||
builder.update_phi_inputs(
|
||||
me.header_id,
|
||||
pin.get("header_phi"),
|
||||
inputs
|
||||
)
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Carrier変数のPHI完成 (同様の処理)
|
||||
// ... (省略)
|
||||
}
|
||||
```
|
||||
|
||||
**課題**:
|
||||
- `builder.update_phi_inputs()` メソッドが必要 → **新規実装必要**
|
||||
- JSONベースのMIR構築で「既存PHI命令の更新」をどう実現するか
|
||||
|
||||
### 2.3 Exit PHI生成
|
||||
|
||||
**難易度: C (難しい)** - 8-10時間
|
||||
|
||||
```hako
|
||||
build_exit_phis(builder, exit_id, exit_snapshots) {
|
||||
// exit_snapshots: [(block_id, { var_name: value_id }), ...]
|
||||
|
||||
local all_vars = new MapBox() // var_name => [(block_id, value_id), ...]
|
||||
|
||||
// Header fallthrough値を追加
|
||||
local i = 0
|
||||
loop(i < me.pinned.length()) {
|
||||
local pin = me.pinned.get(i)
|
||||
local name = pin.get("name")
|
||||
local inputs = new ArrayBox()
|
||||
inputs.push(new ArrayBox().push(me.header_id).push(pin.get("header_phi")))
|
||||
all_vars.set(name, inputs)
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Carrier変数も同様
|
||||
// ...
|
||||
|
||||
// break snapshots を統合
|
||||
i = 0
|
||||
loop(i < exit_snapshots.length()) {
|
||||
local snap = exit_snapshots.get(i)
|
||||
local block_id = snap.get(0)
|
||||
local vars_map = snap.get(1)
|
||||
|
||||
local var_names = vars_map.keys()
|
||||
local j = 0
|
||||
loop(j < var_names.length()) {
|
||||
local var_name = var_names.get(j)
|
||||
local value_id = vars_map.get(var_name)
|
||||
|
||||
local existing = all_vars.get(var_name)
|
||||
if existing == null {
|
||||
existing = new ArrayBox()
|
||||
all_vars.set(var_name, existing)
|
||||
}
|
||||
existing.push(new ArrayBox().push(block_id).push(value_id))
|
||||
|
||||
j = j + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// PHI生成
|
||||
local var_names = all_vars.keys()
|
||||
i = 0
|
||||
loop(i < var_names.length()) {
|
||||
local var_name = var_names.get(i)
|
||||
local inputs = all_vars.get(var_name)
|
||||
|
||||
// 重複除去 (sanitize_phi_inputs相当)
|
||||
inputs = me._sanitize_phi_inputs(inputs)
|
||||
|
||||
if inputs.length() == 0 {
|
||||
// スキップ
|
||||
} else if inputs.length() == 1 {
|
||||
// 単一入力: 直接バインド
|
||||
builder.update_var(var_name, inputs.get(0).get(1))
|
||||
} else {
|
||||
// 複数入力: PHI生成
|
||||
local phi_id = builder.new_value()
|
||||
local phi_inputs = new ArrayBox()
|
||||
local j = 0
|
||||
loop(j < inputs.length()) {
|
||||
local inp = inputs.get(j)
|
||||
phi_inputs.push(MirSchemaBox.phi_incoming(
|
||||
inp.get(0), // block_id
|
||||
inp.get(1) // value_id
|
||||
))
|
||||
j = j + 1
|
||||
}
|
||||
builder.emit_phi(phi_id, phi_inputs)
|
||||
builder.update_var(var_name, phi_id)
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
_sanitize_phi_inputs(inputs) {
|
||||
// 重複除去: block_idでグループ化し、最後の値を採用
|
||||
local map = new MapBox()
|
||||
local i = 0
|
||||
loop(i < inputs.length()) {
|
||||
local inp = inputs.get(i)
|
||||
local bb = "" + inp.get(0) // block_idを文字列化
|
||||
map.set(bb, inp)
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// MapBox → ArrayBox変換
|
||||
local result = new ArrayBox()
|
||||
local keys = map.keys()
|
||||
i = 0
|
||||
loop(i < keys.length()) {
|
||||
result.push(map.get(keys.get(i)))
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
**最大の課題**:
|
||||
- **break文検出とスナップショット収集**をどこで行うか?
|
||||
- コンパイラパイプラインの改修が必要
|
||||
|
||||
### 2.4 ValueId事前割り当て
|
||||
|
||||
**現状調査結果**:
|
||||
- .hako コンパイラのValueId生成器が不明
|
||||
- `MirSchemaBox.i(value)` は既存値をラップするのみ
|
||||
|
||||
**必要機能**:
|
||||
```hako
|
||||
static box ValueIdAllocator {
|
||||
next_id: IntegerBox
|
||||
|
||||
birth() {
|
||||
me.next_id = 100 // 初期値
|
||||
}
|
||||
|
||||
new_value() {
|
||||
local id = me.next_id
|
||||
me.next_id = me.next_id + 1
|
||||
return id
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**難易度: A (簡単)** - 1-2時間
|
||||
|
||||
---
|
||||
|
||||
## 3. 実装難易度・工数見積もり
|
||||
|
||||
### 3.1 難易度評価
|
||||
|
||||
| コンポーネント | 難易度 | 工数 | 理由 |
|
||||
|---------------|--------|------|------|
|
||||
| データ構造設計 | B | 3-4h | MapBox/ArrayBox で実装可能だが冗長 |
|
||||
| Pass 1: prepare_structure | B | 4-6h | HashMap.keys() 存在確認が必要 |
|
||||
| Pass 2: emit_preheader | A | 2-3h | 既存APIで実装可能 |
|
||||
| Pass 3: emit_header_phis | B | 3-4h | PHI更新機構が必要 |
|
||||
| Pass 4: seal_phis | B | 4-5h | update_phi_inputs実装が必要 |
|
||||
| Exit PHI: build_exit_phis | C | 8-10h | break検出機構の実装が必要 |
|
||||
| ValueId allocator | A | 1-2h | シンプルなカウンター |
|
||||
| テスト・デバッグ | B | 4-6h | MIR JSON検証 |
|
||||
|
||||
**総工数見積もり**:
|
||||
- **最小 (MVP)**: 20-26時間
|
||||
- **推奨 (完全版)**: 30-40時間
|
||||
|
||||
### 3.2 段階的実装計画
|
||||
|
||||
#### Phase A (MVP): 最小動作版
|
||||
**目標**: Test 2でheader PHI生成を確認
|
||||
**工数**: 10-12時間
|
||||
|
||||
**タスク**:
|
||||
1. ValueIdAllocator実装 (1-2h)
|
||||
2. LoopFormV2Builder骨格作成 (2-3h)
|
||||
3. Pass 1-3実装 (header PHI生成のみ) (6-7h)
|
||||
4. テスト実行 (1h)
|
||||
|
||||
**成果物**:
|
||||
- 単純ループ (break/continueなし) で header PHI 生成
|
||||
- `LoopFormBox.loop_count()` と同等の機能
|
||||
|
||||
#### Phase B (Exit PHI): break対応
|
||||
**目標**: Test 2で単一break文のループが動作
|
||||
**工数**: 12-16時間
|
||||
|
||||
**タスク**:
|
||||
1. Pass 4 (seal_phis) 実装 (4-5h)
|
||||
2. break文検出機構 (5-6h)
|
||||
3. build_exit_phis実装 (8-10h)
|
||||
4. テスト・デバッグ (3-4h)
|
||||
|
||||
**成果物**:
|
||||
- break文付きループで exit PHI 生成
|
||||
- `LoopFormBox.loop_counter()` と同等の機能
|
||||
|
||||
#### Phase C (完全版): 全機能
|
||||
**目標**: Test 2完全パス
|
||||
**工数**: 8-12時間
|
||||
|
||||
**タスク**:
|
||||
1. continue対応 (latch PHI) (3-4h)
|
||||
2. ネストループ対応 (4-5h)
|
||||
3. 複数break対応 (1-2h)
|
||||
4. 総合テスト (2-3h)
|
||||
|
||||
**成果物**:
|
||||
- Rust LoopForm v2 と同等の完全実装
|
||||
|
||||
**総工数 (Phase A-C)**: **30-40時間**
|
||||
|
||||
---
|
||||
|
||||
## 4. 代替案の検討
|
||||
|
||||
### Option 1: LoopForm v2 完全移植
|
||||
**メリット**:
|
||||
- ✅ Rustと同等の完全性
|
||||
- ✅ 将来的な拡張性
|
||||
|
||||
**デメリット**:
|
||||
- ❌ 実装工数大 (30-40時間)
|
||||
- ❌ .hakoコンパイラのメンテナンス負荷増
|
||||
- ❌ 型安全性なし (ランタイムエラーリスク)
|
||||
|
||||
**評価**: **非推奨** (工数対効果が低い)
|
||||
|
||||
### Option 2: Exit PHIのみ実装 ⭐推奨
|
||||
**メリット**:
|
||||
- ✅ 最小限の修正でTest 2パス
|
||||
- ✅ 既存の `LoopFormBox` を活用
|
||||
- ✅ 工数削減 (12-16時間)
|
||||
|
||||
**デメリット**:
|
||||
- ⚠️ Header PHI生成は既存ロジック依存
|
||||
- ⚠️ LoopForm v2の核心思想 (事前ValueId割り当て) を完全には反映しない
|
||||
|
||||
**実装方針**:
|
||||
1. `LoopSSA.stabilize_merges()` を実装
|
||||
2. break文検出とスナップショット収集
|
||||
3. Exit PHI生成 (`build_exit_phis` 相当)
|
||||
|
||||
**評価**: **強く推奨** (実用性と工数のバランス)
|
||||
|
||||
### Option 3: Rustコンパイラに統一
|
||||
**メリット**:
|
||||
- ✅ .hakoコンパイラのメンテナンス不要
|
||||
- ✅ LoopForm v2の恩恵を直接享受
|
||||
|
||||
**デメリット**:
|
||||
- ❌ Selfhostの意義喪失
|
||||
- ❌ Phase 15目標に反する
|
||||
|
||||
**評価**: **非推奨** (プロジェクト方針に反する)
|
||||
|
||||
---
|
||||
|
||||
## 5. 推奨事項
|
||||
|
||||
### 推奨Option: **Option 2 (Exit PHIのみ実装)**
|
||||
|
||||
**理由**:
|
||||
1. **最小限の修正で目標達成**: Test 2 (break文付きループ) が動作
|
||||
2. **既存実装を活用**: `LoopFormBox.loop_counter()` で Header/Latch PHI は既に実装済み
|
||||
3. **工数削減**: 12-16時間 (vs 完全移植30-40時間)
|
||||
4. **段階的移行**: 将来的に完全移植も可能 (Phase Cへの道筋)
|
||||
|
||||
### 実装計画 (Option 2)
|
||||
|
||||
**Step 1: break文検出機構 (4-5時間)**
|
||||
```hako
|
||||
// lang/src/compiler/pipeline_v2/stmt_break_detector_box.hako (新規)
|
||||
static box StmtBreakDetectorBox {
|
||||
break_snapshots: ArrayBox // [(block_id, var_snapshot), ...]
|
||||
|
||||
birth() {
|
||||
me.break_snapshots = new ArrayBox()
|
||||
}
|
||||
|
||||
capture_snapshot(block_id, var_map) {
|
||||
local snap = new ArrayBox()
|
||||
snap.push(block_id)
|
||||
snap.push(var_map)
|
||||
me.break_snapshots.push(snap)
|
||||
}
|
||||
|
||||
get_snapshots() {
|
||||
return me.break_snapshots
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Exit PHI生成 (6-8時間)**
|
||||
```hako
|
||||
// lang/src/compiler/builder/ssa/loopssa.hako (修正)
|
||||
using lang.compiler.pipeline_v2.stmt_break_detector_box as BreakDetector
|
||||
|
||||
static box LoopSSA {
|
||||
stabilize_merges(stage1_json, break_detector, header_vars) {
|
||||
// break文が存在しない場合はスキップ
|
||||
local snaps = break_detector.get_snapshots()
|
||||
if snaps.length() == 0 { return stage1_json }
|
||||
|
||||
// Exit PHI生成
|
||||
local exit_phis = me._build_exit_phis(
|
||||
header_vars,
|
||||
snaps
|
||||
)
|
||||
|
||||
// stage1_jsonに追加
|
||||
// ... (JSON操作)
|
||||
|
||||
return stage1_json
|
||||
}
|
||||
|
||||
_build_exit_phis(header_vars, exit_snapshots) {
|
||||
// Option 2の build_exit_phis 実装
|
||||
// ... (上記セクション2.3参照)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: パイプライン統合 (2-3時間)**
|
||||
- `emit_mir_flow.hako` から `LoopSSA.stabilize_merges()` を呼び出し
|
||||
- break文処理時に `BreakDetector.capture_snapshot()` を呼び出し
|
||||
|
||||
**Step 4: テスト・デバッグ (2-3時間)**
|
||||
- Test 2 (break文付きループ) で動作確認
|
||||
- MIR JSON 検証
|
||||
|
||||
**総工数**: **14-19時間**
|
||||
|
||||
### リスク評価
|
||||
|
||||
**技術リスク: 中**
|
||||
- .hako コンパイラのアーキテクチャが JSON ベースであり、Rust の命令型アプローチと異なる
|
||||
- PHI命令の更新 (seal時) が JSON 操作で複雑化する可能性
|
||||
|
||||
**メンテナンスリスク: 低**
|
||||
- Option 2 は既存の `LoopFormBox` を活用するため、新規コードは最小限
|
||||
- `LoopSSA.stabilize_merges()` のみの実装で、他への影響が少ない
|
||||
|
||||
**互換性リスク: 低**
|
||||
- 既存のループ処理は変更せず、exit PHI 生成のみ追加
|
||||
- 後方互換性が保たれる
|
||||
|
||||
---
|
||||
|
||||
## 6. 期待される成果物
|
||||
|
||||
### 1. 現状分析レポート ✅
|
||||
**内容**: 本ドキュメント セクション1
|
||||
|
||||
### 2. 実装可能性評価 ✅
|
||||
**内容**: 本ドキュメント セクション2
|
||||
|
||||
### 3. 工数見積もり ✅
|
||||
**内容**: 本ドキュメント セクション3
|
||||
|
||||
### 4. 実装計画案 ✅
|
||||
**内容**: 本ドキュメント セクション5
|
||||
|
||||
### 5. 推奨事項 ✅
|
||||
**結論**: **Option 2 (Exit PHIのみ実装)** を推奨
|
||||
|
||||
**根拠**:
|
||||
- 最小工数 (14-19時間) で目標達成
|
||||
- 既存実装を活用し、リスク低減
|
||||
- 段階的移行の道筋が明確
|
||||
|
||||
**次のアクション**:
|
||||
1. ユーザーに Option 2 を提案
|
||||
2. 承認後、実装開始 (Step 1-4)
|
||||
3. Test 2 パス確認
|
||||
4. 必要に応じて Phase C (完全版) への移行を検討
|
||||
|
||||
---
|
||||
|
||||
## 7. 調査手法
|
||||
|
||||
### 実施した調査
|
||||
1. ✅ コード精読: `lang/src/compiler/` 配下の関連ファイル
|
||||
- `loopssa.hako`, `loop_form_box.hako`, `block_builder_box.hako`, `mir_schema_box.hako`
|
||||
2. ✅ Rust実装との比較: `src/mir/phi_core/loopform_builder.rs` との対比
|
||||
3. ✅ アーキテクチャ分析: .hakoのMIR生成パイプライン理解
|
||||
- JSON ベースのMIR構築方式を確認
|
||||
4. ✅ 実装シミュレーション: 疑似コードレベルでの実装可能性検証
|
||||
|
||||
### 未確認項目 (要調査)
|
||||
- [ ] .hako の `MapBox.keys()` メソッド存在確認
|
||||
- [ ] .hako コンパイラの ValueId 生成器の実装箇所
|
||||
- [ ] `builder.update_var()` / `builder.get_variable_at_block()` の存在確認
|
||||
- [ ] JSON操作によるPHI命令更新の実装詳細
|
||||
|
||||
---
|
||||
|
||||
## 付録A: Rust vs .hako データ構造対応表
|
||||
|
||||
| Rust型 | .hako表現 | 実装例 |
|
||||
|--------|-----------|--------|
|
||||
| `struct CarrierVariable` | `MapBox` | `local car = new MapBox(); car.set("name", "i")` |
|
||||
| `Vec<CarrierVariable>` | `ArrayBox` | `local carriers = new ArrayBox(); carriers.push(car)` |
|
||||
| `HashMap<String, ValueId>` | `MapBox` | `local vars = new MapBox(); vars.set("i", 10)` |
|
||||
| `ValueId` | `IntegerBox` | `local vid = 10` (整数で表現) |
|
||||
| `BasicBlockId` | `IntegerBox` | `local bid = 5` (整数で表現) |
|
||||
|
||||
**注意点**:
|
||||
- .hako は型安全性がないため、フィールドアクセスミスがランタイムエラーになる
|
||||
- Rustの3-4倍のコード量が必要
|
||||
|
||||
---
|
||||
|
||||
## 付録B: 参考実装コード (Option 2)
|
||||
|
||||
**完全な実装例は本ドキュメント セクション5参照**
|
||||
|
||||
---
|
||||
|
||||
**レポート作成日**: 2025-11-18
|
||||
**次回レビュー**: 実装開始前 (ユーザー承認後)
|
||||
**ドキュメント状態**: READY FOR REVIEW ✅
|
||||
198
docs/development/investigation/mir_locals_copy_investigation.md
Normal file
198
docs/development/investigation/mir_locals_copy_investigation.md
Normal file
@ -0,0 +1,198 @@
|
||||
# MIR Locals Copy Instruction Investigation
|
||||
|
||||
## Summary
|
||||
|
||||
**Status**: ✅ **RESOLVED** - Copy instructions ARE being emitted correctly
|
||||
|
||||
**Investigation Date**: 2025-11-18
|
||||
|
||||
**Root Cause**: Misunderstanding of debug output - the implementation was already correct.
|
||||
|
||||
## Background
|
||||
|
||||
The user reported that `build_local_statement` was modified to emit Copy instructions for local variable initializations, but the Copy instructions weren't appearing in the MIR output, and SSA violations remained.
|
||||
|
||||
## Investigation Process
|
||||
|
||||
### 1. Initial Hypothesis
|
||||
|
||||
The hypothesis was that Copy instructions weren't being emitted due to:
|
||||
- Old binaries being used
|
||||
- Wrong code path being taken
|
||||
- Optimization passes removing the Copy instructions
|
||||
- Value generator issues
|
||||
|
||||
### 2. Test Implementation
|
||||
|
||||
Created a comprehensive Rust test (`src/tests/mir_locals_ssa.rs`) to verify Copy instruction emission:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn mir_locals_copy_instructions_emitted() {
|
||||
// Test source with 3 local variables
|
||||
let src = r#"
|
||||
static box TestLocals {
|
||||
main() {
|
||||
local a = 1
|
||||
local b = 2
|
||||
local c = new ArrayBox()
|
||||
return 0
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
// Compile and verify Copy instructions exist
|
||||
// Verify no SSA violations
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Findings
|
||||
|
||||
#### Actual MIR Output (CORRECT):
|
||||
|
||||
```mir
|
||||
define i64 @TestLocals.main/0() effects(read) {
|
||||
bb2:
|
||||
0: %0 = const 1
|
||||
1: %4 = copy %0 ← Copy instruction present!
|
||||
2: %1 = const 2
|
||||
3: %5 = copy %1 ← Copy instruction present!
|
||||
4: %6 = new ArrayBox()
|
||||
5: %7 = copy %6 ← Copy instruction present!
|
||||
6: %2 = const 0
|
||||
7: ret %2
|
||||
}
|
||||
```
|
||||
|
||||
**Analysis**:
|
||||
- ✅ All 3 Copy instructions are present
|
||||
- ✅ No SSA violations (each register defined exactly once)
|
||||
- ✅ Proper SSA form maintained
|
||||
|
||||
#### Debug Output Confusion
|
||||
|
||||
The initial debug output showed:
|
||||
```
|
||||
[DEBUG/local] init_val = %0
|
||||
[DEBUG/local] Allocating new var_id = %0
|
||||
```
|
||||
|
||||
This appeared to be an SSA violation, but it was actually from **different functions** being compiled (main, TestLocals.main/0, condition_fn), each with their own value generator instance.
|
||||
|
||||
### 4. Code Verification
|
||||
|
||||
The current implementation in `src/mir/builder/stmts.rs` line 188:
|
||||
|
||||
```rust
|
||||
pub(super) fn build_local_statement(
|
||||
&mut self,
|
||||
variables: Vec<String>,
|
||||
initial_values: Vec<Option<Box<ASTNode>>>,
|
||||
) -> Result<ValueId, String> {
|
||||
let mut last_value = None;
|
||||
for (i, var_name) in variables.iter().enumerate() {
|
||||
let var_id = if i < initial_values.len() && initial_values[i].is_some() {
|
||||
let init_expr = initial_values[i].as_ref().unwrap();
|
||||
let init_val = self.build_expression(*init_expr.clone())?;
|
||||
|
||||
// FIX: Allocate a new ValueId for this local variable
|
||||
// and emit a Copy instruction to establish SSA form
|
||||
let var_id = self.value_gen.next();
|
||||
|
||||
self.emit_instruction(crate::mir::MirInstruction::Copy {
|
||||
dst: var_id,
|
||||
src: init_val
|
||||
})?;
|
||||
|
||||
// Propagate metadata (type/origin) from initializer to variable
|
||||
crate::mir::builder::metadata::propagate::propagate(self, init_val, var_id);
|
||||
|
||||
var_id
|
||||
} else {
|
||||
// Create a concrete register for uninitialized locals (Void)
|
||||
crate::mir::builder::emission::constant::emit_void(self)
|
||||
};
|
||||
|
||||
self.variable_map.insert(var_name.clone(), var_id);
|
||||
last_value = Some(var_id);
|
||||
}
|
||||
Ok(last_value.unwrap_or_else(|| self.value_gen.next()))
|
||||
}
|
||||
```
|
||||
|
||||
**This implementation is CORRECT and working as intended.**
|
||||
|
||||
### 5. MIR Printer Verification
|
||||
|
||||
The Copy instruction is properly handled in `src/mir/printer_helpers.rs`:
|
||||
|
||||
```rust
|
||||
MirInstruction::Copy { dst, src } => {
|
||||
format!("{} copy {}", format_dst(dst, types), src)
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
### What Was Working All Along
|
||||
|
||||
1. ✅ `build_local_statement` correctly emits Copy instructions
|
||||
2. ✅ Value generator properly allocates unique ValueIds
|
||||
3. ✅ MIR printer correctly displays Copy instructions
|
||||
4. ✅ No SSA violations in the output
|
||||
5. ✅ Metadata propagation works correctly
|
||||
|
||||
### Why The Confusion
|
||||
|
||||
1. Debug output showed register reuse, but this was from **different compilation contexts** (different functions)
|
||||
2. Each function has its own value generator starting from %0
|
||||
3. The test compiles multiple functions (main, TestLocals.main/0, condition_fn), each showing their own register allocation
|
||||
|
||||
### Test Results
|
||||
|
||||
```
|
||||
test tests::mir_locals_ssa::mir_locals_copy_instructions_emitted ... ok
|
||||
```
|
||||
|
||||
- Copy instructions: 3 found (expected 3)
|
||||
- SSA violations: 0 (expected 0)
|
||||
- All assertions passed
|
||||
|
||||
## Recommendations
|
||||
|
||||
### For Future Investigation
|
||||
|
||||
1. When debugging MIR output, always identify which function you're looking at
|
||||
2. Remember that value generators are per-function, not global
|
||||
3. Use `--dump-mir` or `--emit-mir-json` flags for reliable MIR inspection
|
||||
4. Test with fresh builds to avoid stale binary issues
|
||||
|
||||
### Code Quality
|
||||
|
||||
The current implementation:
|
||||
- Follows SSA principles correctly
|
||||
- Has proper error handling
|
||||
- Includes metadata propagation
|
||||
- Is well-commented
|
||||
|
||||
**No changes needed** - the implementation is correct.
|
||||
|
||||
## Related Files
|
||||
|
||||
- **Implementation**: `src/mir/builder/stmts.rs:188`
|
||||
- **Test**: `src/tests/mir_locals_ssa.rs`
|
||||
- **Printer**: `src/mir/printer_helpers.rs:294`
|
||||
- **Instruction Definition**: `src/mir/instruction.rs:193`
|
||||
|
||||
## Verification Commands
|
||||
|
||||
```bash
|
||||
# Run the test
|
||||
cargo test mir_locals_copy_instructions_emitted -- --nocapture
|
||||
|
||||
# Check MIR output for any program
|
||||
./target/release/hakorune --dump-mir your_program.hako
|
||||
|
||||
# Emit MIR as JSON
|
||||
./target/release/hakorune --emit-mir-json output.json your_program.hako
|
||||
```
|
||||
@ -2,6 +2,26 @@
|
||||
|
||||
Status: design+partial implementation(Stage1 ビルド導線の初期版まで)
|
||||
|
||||
## 25.1 サブフェーズの整理(a〜e 概要)
|
||||
|
||||
- **25.1a — Stage1 Build Hotfix(配線)**
|
||||
- ねらい: `.hako → Program(JSON v0) → MIR(JSON)` の Rust/provider 経路をまず安定させる。
|
||||
- 担当: `compiler_stageb.hako` + `tools/hakorune_emit_mir.sh` 系の導線修復(Stage‑B emit を「実際に動く状態」に戻す)。
|
||||
- **25.1b — Selfhost MirBuilder Parity(selfhost-first 設計)**
|
||||
- ねらい: Rust の `env.mirbuilder.emit` を「オラクル」として、Hakorune 側 `MirBuilderBox` を同じ意味論まで引き上げる。
|
||||
- 担当: `.hako → Program(JSON v0) → MIR(JSON)` のうち「Program→MIR」を selfhost builder だけでも成立させる準備。
|
||||
- **25.1c — Env/Extern/Stage‑B 構造整理**
|
||||
- ねらい: `env.*` / `hostbridge.*` / `env.box_introspect.*` の責務と Stage‑B Main を箱単位で整理し、入口を一つに揃える。
|
||||
- 担当: Stage‑B を `StageBArgsBox` / `StageBBodyExtractorBox` / `StageBDriverBox` / `Stage1UsingResolverBox` に分解しつつ、挙動は変えない構造リファクタ。
|
||||
- **25.1d — Rust MIR SSA/PHI Smokes**
|
||||
- ねらい: Rust 側 `MirBuilder + LoopBuilder + IfForm` の SSA/PHI バグを、小さな Rust テスト(Hako→AST→MirCompiler→MirVerifier)で炙り出して潰す。
|
||||
- 担当: Stage‑B/Stage‑1/selfhost で見えている Undefined Value / non‑dominating use を、まず Rust 階層だけで止血する。
|
||||
- **25.1e — LoopForm PHI v2 Migration(Rust)**
|
||||
- ねらい: ループの PHI 生成の「SSOT」を LoopForm v2 + `phi_core` に寄せ、Legacy LoopBuilder 経路との二重管理を解消する。
|
||||
- 担当: `NYASH_LOOPFORM_PHI_V2=1` を使って Stage‑1 / Stage‑B 代表ループ(`_find_from` や stageb_min)を通し、`phi pred mismatch` / ValueId 二重定義を構造的に解消する。
|
||||
|
||||
ざっくりとした進行順は「25.1a/c で配線と箱分割 → 25.1d/e で Rust MIR/LoopForm を根治 → その結果を踏まえて 25.1b(selfhost MirBuilder/LoopSSA)側に寄せていく」というイメージだよ。
|
||||
|
||||
## ゴール
|
||||
|
||||
- Rust 製 `hakorune` を **Stage0 ブートストラップ**と位置付け、Hakorune コード(.hako)で構成された **Stage1 バイナリ**を明確に分離する。
|
||||
|
||||
259
docs/development/roadmap/phases/phase-25.1e/README.md
Normal file
259
docs/development/roadmap/phases/phase-25.1e/README.md
Normal file
@ -0,0 +1,259 @@
|
||||
# Phase 25.1e — LoopForm PHI v2 Migration (Rust MIR)
|
||||
|
||||
Status: planning(LoopForm/PHI 正規化フェーズ・挙動は変えない/Rust側のみ)
|
||||
|
||||
## ゴール
|
||||
|
||||
- ループまわりの SSA / PHI 生成の「SSOT(単一の正)」を **LoopForm v2 + phi_core** に寄せて、Legacy 経路との二重管理を解消する。
|
||||
- Stage‑1 / Stage‑B / selfhost で見えている以下の問題を、LoopForm 側の設計として整理して直す:
|
||||
- 複雑ループ(`Stage1UsingResolverFull._find_from/3` など)での「同一 ValueId の二重定義(PHI vs 既存値)」。
|
||||
- Merge block(ヘッダ/合流ブロック)で、predecessor 定義値を PHI なしで読むことによる non‑dominating use。
|
||||
- pinned 受信箱(`__pin$*@recv`)や Loop carrier 変数の PHI 対象範囲が曖昧で、legacy/local_ssa/LoopForm の責務が重なっている問題。
|
||||
- 既存テスト/Stage‑B 最小ハーネス/selfhost CLI から見える「SSA/PHI 赤ログ」を、LoopForm v2 経路を正とすることで構造的に潰す。
|
||||
|
||||
## 前提 / これまでにやったこと(25.1d まで)
|
||||
|
||||
- Local 変数の SSA 化:
|
||||
- `build_local_statement` を修正し、`local a = expr` ごとに新しい ValueId を払い出して `Copy` 命令で初期化値をコピーするように統一。
|
||||
- `src/tests/mir_locals_ssa.rs` で `local a,b,c` パターンを固定し、Const/NewBox とローカル変数レジスタが分離されることを確認済み。
|
||||
- Callee 解決/ガード:
|
||||
- `CalleeBoxKind` / `CalleeResolverBox` / `CalleeGuardBox` の導入により、Stage‑B / Stage‑1 の static compiler Box と runtime Box の混線を構造的に防止。
|
||||
- `StageBArgsBox.resolve_src/1` 内の `args.get(i)` が `Stage1UsingResolverBox.get` に化ける問題は解消済み。
|
||||
- Loop/PHI まわりの scaffolding:
|
||||
- `phi_core::loop_phi::{prepare_loop_variables_with, seal_incomplete_phis_with, build_exit_phis_with}` と `LoopPhiOps` を導入し、LoopBuilder から PHI 生成を委譲可能な構造は整備済み。
|
||||
- LoopForm v2 (`LoopFormBuilder` + `LoopFormOps`) は導線のみ実装済みで、既定では legacy 経路(`build_loop_legacy`)が使われている。
|
||||
|
||||
残っている問題は、主に legacy LoopBuilder / loop_phi / LoopForm v2 の責務が重なっているところだよ。
|
||||
|
||||
## 方針(25.1e)
|
||||
|
||||
- **SSOT を決める**:
|
||||
- ループの PHI 生成の「正」は LoopForm v2 (`LoopFormBuilder` + `LoopFormOps` + `phi_core::loop_phi/if_phi`) に置く。
|
||||
- `build_loop_legacy` + `prepare_loop_variables_with` は「互換レイヤ/移行レイヤ」と位置づけ、最終的には LoopForm v2 の薄いラッパに縮退させる。
|
||||
- **Feature Flag で段階導入**:
|
||||
- `NYASH_LOOPFORM_PHI_V2=1` のときだけ LoopForm v2 経路を使い、当面は Stage‑B / Stage‑1 / selfhost 用テストの中でピンポイントに有効化する。
|
||||
- デフォルト挙動は変えない(フラグ未設定時はこれまでどおり legacy 経路)。
|
||||
- **1 バグ 1 パターンで前進**:
|
||||
- `mir_stage1_using_resolver_full_collect_entries_verifies` や Stage‑B 最小ハーネスで見えている赤ログは、それぞれ最小 Hako に絞って LoopForm v2 側で再現・修正する。
|
||||
- 同時に legacy 側からは同じ責務(PHI 生成ロジック)を抜いていき、二重管理を減らす。
|
||||
|
||||
## タスク粒度(やることリスト)
|
||||
|
||||
### 1. LoopForm v2 の足場をテストで固める
|
||||
|
||||
1.1 LoopForm v2 専用テストモードの追加
|
||||
- `mir_stage1_using_resolver_full_collect_entries_verifies` をベースに、`NYASH_LOOPFORM_PHI_V2=1` を立てた状態でのみ走るサブテストを追加。
|
||||
- 目的: v2 経路で `_find_from` ループの SSA/PHI が整合していることを検証する足場を作る(まだ赤でもよい)。
|
||||
|
||||
1.2 Stage‑B 最小ハーネス用ループ抜き出しテスト
|
||||
- `lang/src/compiler/tests/stageb_min_sample.hako` から、代表的なループだけを抜き出した Hako を作り、LoopForm v2 経路(`NYASH_LOOPFORM_PHI_V2=1`)で `MirVerifier` を通すテストを追加。
|
||||
- 目的: Stage‑B / selfhost CLI で見えているループ系のバグを、純粋な LoopForm/PHI 問題として Rust テストに落とし込む。
|
||||
|
||||
### 2. Legacy / v2 の責務切り分け
|
||||
|
||||
2.1 prepare_loop_variables の責務縮小(実施中の変更の整備)
|
||||
- 既に導入したフィルタリング:
|
||||
- `prepare_loop_variables` が preheader の `variable_map` 全体ではなく、「ループキャリア変数(body で再代入されるもの)+ pinned 変数(`__pin$*`)」だけを PHI 対象にするように変更。
|
||||
- 効果: `text_len` / `pattern_len` などループ不変なローカルに PHI を張らないことで、ValueId の二重定義/UseBeforeDef が起きにくくなる。
|
||||
- 25.1e では、この変更を LoopForm v2 側の設計として明文化し、legacy 側のコメントやドキュメントもそれに揃える。
|
||||
|
||||
2.2 LoopForm v2 での PHI 生成を SSOT にする
|
||||
- `LoopFormBuilder::prepare_structure` / `emit_preheader` / `emit_header_phis` の挙動をドキュメント化し、「どの変数に PHI を張るか」のルールを固定:
|
||||
- 関数パラメータ + 明示的なループキャリア変数(body 内で再代入される)+ pinned 変数のみ。
|
||||
- ループ不変変数は preheader の値をそのまま使い、PHI を作らない。
|
||||
- `build_loop_legacy` の PHI 補助ロジック(header/exit での snapshot + PHI 生成)は、LoopForm v2 のロジックと重複しないように段階的に削る。
|
||||
|
||||
### 3. mir_stage1_using_resolver_full_collect_entries の赤ログ解消
|
||||
|
||||
- 現状の代表的なエラー:
|
||||
- `Value %24 / %25 / %26 defined multiple times (bb53 vs bb54)`
|
||||
- `Merge block bb54 uses predecessor-defined value %28/%29/%27 from bb59/bb61 without Phi`
|
||||
- タスク:
|
||||
1. `_find_from` ループに対応する Hako 断片を LoopForm v2 経路でミニテスト化。
|
||||
2. LoopForm v2 側で:
|
||||
- preheader/header/latch/exit の各ブロックに対して、どの変数が carrier/pinned なのかを明示的に計算。
|
||||
- PHI dst の ValueId 割り当てを `MirFunction::next_value_id` に完全委譲し、既存 SSA 値と衝突しないようにする。
|
||||
- header での PHI 再定義(`%24` copy + `phi %24` のような形)を避けるため、古い値と新しい値のバインディングを LoopForm 内部で完結させる。
|
||||
3. 修正後、`mir_stage1_using_resolver_full_collect_entries_verifies` が LoopForm v2 経路で緑になることを確認。
|
||||
|
||||
### 4. Stage‑B / selfhost CLI への波及
|
||||
|
||||
- Stage‑B 最小ハーネス(`tools/test_stageb_min.sh`)と selfhost CLI スモークを、LoopForm v2 経路で試験的に実行:
|
||||
- `NYASH_LOOPFORM_PHI_V2=1` にした状態で Test2(`compiler_stageb.hako` 経由)と selfhost CLI を実行し、ValueId 未定義や PHI 不整合が減っていることを確認。
|
||||
- 25.1e のスコープでは、「v2 経路での挙動が legacy より悪化しない(少なくとも同程度、可能なら改善)」ことを目標に、必要最小限の修正に留める。
|
||||
|
||||
## 設計図 — LoopForm v2 Scope モデル
|
||||
|
||||
25.1e では「すべてのループ/if/else を LoopForm v2 のスコープとして見る」という前提で設計を固める。
|
||||
ここでは **スコープ単位の入出力と break/continue の扱い** を明文化する。
|
||||
|
||||
### 1. 基本モデル(LoopScope / IfScope)
|
||||
|
||||
- すべての制御構造は「スコープ」として扱う:
|
||||
- `LoopScope`: `while/loop` 相当(Nyash の `loop (cond) { ... }`)。
|
||||
- `IfScope`: `if (cond) { then } else { else }`。
|
||||
- 各スコープは次の情報を持つ:
|
||||
- 入力: `Env_in` … スコープ入口時点の `variable_map`(名前→ValueId)。
|
||||
- 出力: `Env_out` … スコープ出口時点の `variable_map`。
|
||||
- 内部状態:
|
||||
- `Carriers`: ループ本体で再代入される変数名の集合。
|
||||
- `Pinned`: ループをまたいで保持する必要がある値(`__pin$*@recv` や `me` の一部など)。
|
||||
- `BreakSnaps`: break 到達時の `Env` スナップショットの集合。
|
||||
- `ContinueSnaps`: continue 到達時の `Env` スナップショットの集合。
|
||||
|
||||
LoopForm v2 は「各スコープの `Env_in` と `Env_out` を定義し、SSA/PHI をその範囲で完結させる」ことを目標にする。
|
||||
|
||||
### 2. LoopScope の形状とブロック
|
||||
|
||||
LoopScope は LLVM の canonical form に従う:
|
||||
|
||||
```text
|
||||
preheader → header (PHI) → body → latch → header
|
||||
↘ exit
|
||||
```
|
||||
|
||||
- preheader:
|
||||
- ループに入る直前のブロック。
|
||||
- `Env_in(loop)` をそのまま保持し、loop entry 用の Copy をここで emit する(Carrier/Pinned のみ)。
|
||||
- header:
|
||||
- ループ条件・合流点。
|
||||
- Entry 時点では preheader からの Copy を入力に PHI を seed するだけ(latch/continue は後で seal)。
|
||||
- body:
|
||||
- ループ本体。`Carriers` に対する再代入や break/continue が発生する。
|
||||
- latch:
|
||||
- body の末尾ブロック(continue でヘッダに戻る前に通る最後のブロック)。
|
||||
- `Env_latch` として LoopForm v2 に引き継がれ、header PHI の backedge 値に使われる。
|
||||
- exit:
|
||||
- ループ脱出ブロック。`BreakSnaps` と header fall-through をマージして exit PHI を作る。
|
||||
|
||||
### 3. 変数分類 — Carrier / Pinned / Invariant
|
||||
|
||||
- Carrier:
|
||||
- ループ本体(body)で **再代入される** 変数。
|
||||
- 例: `i`, `a`, `b` などのインデックスや累積値。
|
||||
- LoopScope では:
|
||||
- header entry で `phi(entry, latch)` を必ず持つ。
|
||||
- exit でも header 値と break 値を PHI でマージする。
|
||||
- Pinned:
|
||||
- `me` レシーバや `__pin$*@recv` のように、ループをまたいで同じ Box を指し続ける必要がある値。
|
||||
- ループ内の再代入はほとんどないが、「PHI に乗せておかないと次のイテレーションで UseBeforeDef になる」種類の値。
|
||||
- LoopScope では:
|
||||
- header PHI の入力として preheader の Copy を用意する(`prepare_structure` 相当)。
|
||||
- break/continue/exit でも pinned 値が破綻しないように header/exit PHI に含める。
|
||||
- Invariant:
|
||||
- ループ内で再代入されない、純粋な不変ローカル・パラメータ。
|
||||
- 例: `text_len`, `pattern_len` のような長さ。
|
||||
- LoopScope では:
|
||||
- preheader 値をそのまま使い、PHI には乗せない(ValueId の二重定義を避ける)。
|
||||
|
||||
LoopForm v2 のルール:
|
||||
- **PHI の対象は Carrier + Pinned のみ**。Invariant は preheader の値を直接参照する。
|
||||
|
||||
### 4. break / continue の扱い
|
||||
|
||||
#### 4.1 continue
|
||||
|
||||
- continue は「現在のループの latch ブロックにジャンプして header に戻る」。
|
||||
- LoopScope では:
|
||||
- `ContinueSnaps` に `(block_id, VarSnapshot)` を記録する(block_id は continue が現れたブロック)。
|
||||
- `seal_phis` 実行時に:
|
||||
- `ContinueSnaps` のスナップショットから Carrier/Pinned 変数の値を集め、
|
||||
- header の IncompletePhi(`IncompletePhi { var_name, phi_id, known_inputs }`)に `(continue_block, value)` を追加する。
|
||||
- 条件: continue は「現在のループスコープ」からのみ脱出し、外側のスコープには影響しない。
|
||||
|
||||
#### 4.2 break
|
||||
|
||||
- break は「現在のループを脱出し、exit ブロックへ遷移する」。
|
||||
- LoopScope では:
|
||||
- `BreakSnaps` に `(block_id, VarSnapshot)` を記録する。
|
||||
- `build_exit_phis` 実行時に:
|
||||
- header fall-through の Snapshot (header_exit_snapshot)と `BreakSnaps` をマージし、
|
||||
- exit ブロックで PHI を生成する:
|
||||
- 1 predecessor のみ → 直接 bind。
|
||||
- 2 つ以上 → `phi(header, break1, break2, ...)` を作る。
|
||||
- ここでも PHI 対象は Carrier + Pinned のみ。Invariant は preheader/header の値で十分。
|
||||
|
||||
### 5. スコープ入出力と変数の「渡し方」
|
||||
|
||||
#### 5.1 LoopScope の Env 入出力
|
||||
|
||||
- 入力: `Env_in(loop)` = ループ直前(preheader 手前)の `variable_map`。
|
||||
- LoopForm v2 はここから:
|
||||
- Carrier/Pinned を抽出して PHI 用の構造を準備。
|
||||
- preheader に必要な Copy を emit。
|
||||
- 出力: `Env_out(loop)` = exit ブロック直後の `variable_map`。
|
||||
- Carrier/Pinned は exit PHI の結果に更新される。
|
||||
- Invariant は `Env_in(loop)` の値をそのまま引き継ぐ。
|
||||
|
||||
LoopScope の契約:
|
||||
- 「ループの外側から見える変数」は Carrier/Pinned に限らず全部だが、
|
||||
- ループ内で変わり得るのは Carrier/Pinned。
|
||||
- ループ内で決して変わらないものは Envin と Envout で同じ ValueId になる。
|
||||
|
||||
#### 5.2 IfScope の Env 入出力
|
||||
|
||||
- IfScope も同様に:
|
||||
- `Env_in(if)` = pre-if スナップショット。
|
||||
- `Env_then_end`, `Env_else_end` を計算し、
|
||||
- 変化した変数についてのみ merge PHI を生成(`phi_core::if_phi::merge_modified_at_merge_with`)。
|
||||
- LoopScope と組み合わせる場合(loop header 内の if など)は:
|
||||
- LoopForm が header/body/latch の枠を作り、
|
||||
- その中の if は IfScope として φ を 張る。
|
||||
- LoopScope は「if によって更新された Carrier/Pinned の最終値」を snapshot として扱い、次の header/latch PHI の入力に使う。
|
||||
|
||||
### 6. break/continue を含む複雑パターン
|
||||
|
||||
代表的な難パターン:
|
||||
- ループ内 if の中で break/continue が出るケース:
|
||||
|
||||
```hako
|
||||
loop (i < n) {
|
||||
local ch = text.substring(i, i+1)
|
||||
if ch == " " {
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if ch == "," {
|
||||
break
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
|
||||
LoopForm v2 での扱い:
|
||||
- Carrier: `i`
|
||||
- Pinned: `text`, `n`、必要に応じて pinned recv(`__pin$*@recv`)。
|
||||
- IfScope 内で:
|
||||
- `continue` → `ContinueSnaps` に i/text/n のスナップショットを保存。
|
||||
- `break` → `BreakSnaps` に同様のスナップショットを保存。
|
||||
- seal/build_exit 時:
|
||||
- header PHI: `i` と pinned recv のみを対象に、preheader/latch/continue からの値を統合。
|
||||
- exit PHI: `i` や pinned recv を header fall-through と break ブロックから統合。
|
||||
|
||||
これにより、「どのスコープからどの変数が外に出るか」「break/continue でどの値が生き残るか」が LoopForm v2 の規則で明示される。
|
||||
|
||||
### 7. この設計図の適用範囲
|
||||
|
||||
- 対象:
|
||||
- Rust MIR builder (`MirBuilder` + `LoopBuilder` + `LoopFormBuilder`) が生成するすべての loop/if/else 構造。
|
||||
- JSON v0 Bridge の loop lowering(Bridge 側にも LoopFormOps 実装を追加して同じアルゴリズムを使う)。
|
||||
- スコープ外(25.1e 時点):
|
||||
- Nyash `.hako` 側 MirBuilder(selfhost builder)の loop/if 実装。
|
||||
- try/catch/finally の完全な LoopForm への統合(現状は独自の cf_try_catch ルールで SSA を保っている)。
|
||||
|
||||
25.1e では、この設計図をベースに「_find_from ループ」「Stage‑B 最小ループ」などの代表ケースから LoopForm v2 に寄せていき、
|
||||
legacy LoopBuilder 側から重複ロジックを削っていくのが次のステップになる。
|
||||
|
||||
## スコープ外 / 後続フェーズ候補
|
||||
|
||||
- Nyash 側 MirBuilder(.hako 実装)の LoopForm 対応:
|
||||
- ここでは Rust MIR builder 側の LoopForm/PHI を整えることに集中し、`.hako` 側 MirBuilder への LoopForm 移植は Phase 25.1f 以降のタスクとする。
|
||||
- ループ最適化(unrolling / strength reduction など):
|
||||
- 25.1e はあくまで「正しい SSA/PHI を作る」のが目的であり、性能最適化は Phase 26+ で扱う。
|
||||
|
||||
## メモ(現状の観測ログ)
|
||||
|
||||
- `mir_stage1_using_resolver_full_collect_entries_verifies` 実行時:
|
||||
- `_find_from` / `collect_entries` 内で多数の PHI が生成されているが、header/merge ブロックで既存の ValueId と衝突して `Value %N defined multiple times` が発生。
|
||||
- merge ブロック(bb54 など)で predecessor(bb59, bb61)由来の値を PHI なしで読んでいる箇所があり、`non‑dominating use` / `Merge block uses predecessor-defined value without Phi` がレポートされている。
|
||||
- `LoopBuilder::prepare_loop_variables` による「全変数 PHI 化」は、LocalSSA+LoopForm の両方が入った状態では過剰であり、キャリア+pinned のみに制限する必要があることが分かっている。
|
||||
Reference in New Issue
Block a user