feat(joinir): Phase 213-2 Step 2-2 & 2-3 Data structure extensions

Extended PatternPipelineContext and CarrierUpdateInfo for Pattern 3 AST-based generalization.

Changes:
1. PatternPipelineContext:
   - Added loop_condition: Option<ASTNode>
   - Added loop_body: Option<Vec<ASTNode>>
   - Added loop_update_summary: Option<LoopUpdateSummary>
   - Updated build_pattern_context() for Pattern 3

2. CarrierUpdateInfo:
   - Added then_expr: Option<ASTNode>
   - Added else_expr: Option<ASTNode>
   - Updated analyze_loop_updates() with None defaults

Status: Phase 213-2 Steps 2-2 & 2-3 complete
Next: Create Pattern3IfAnalyzer to extract if statement and populate update summary
This commit is contained in:
nyash-codex
2025-12-10 00:01:53 +09:00
parent 577b5b01d5
commit d7805e5974
138 changed files with 3529 additions and 378 deletions

View File

@ -97,6 +97,8 @@ NYASH_ENABLE_USING=1 # using 文有効化
## 現在の JoinIR 統合状況
> Note (2025-12): 現在は LoopBuilder を物理削除し、JoinIR は常時 ONNYASH_JOINIR_CORE は deprecated/no-op。以下のコードスケッチは Phase 121 当時の歴史メモとして残しているよ。
### Loop PHI 生成(部分統合済み)
**Phase 49 Mainline Integration**:
@ -380,4 +382,3 @@ Detects unreachable basic blocks using MIR CFG information. Complements HC019 by
- **CFG Extractor:** `src/mir/cfg_extractor.rs`
- **Tests:** `apps/tests/hako_check/test_dead_blocks_*.hako`

View File

@ -62,9 +62,13 @@ JoinIR ラインで守るべきルールを先に書いておくよ:
- ExitLineReconnector は Condition 役の変数を exit_bindings から除外
- **ParamRole の分類**:
- `LoopParam`: ループ制御変数(例: `i` in `loop(i < len)`)→ header PHI + exit_bindings
- `Condition`: 条件専用変数(例: `digits` in `digits.indexOf(ch)`)→ condition_bindings のみ
- `Carrier`: 状態更新変数(例: `sum`, `count`)→ header PHI + exit_bindings
- `ExprResult`: ループ戻り値 → exit_phi_builder で処理
- `Condition`: 条件専用変数(例: `digits` in `digits.indexOf(ch)`)→ condition_bindings のみ
- `Carrier`: 状態更新変数(例: `sum`, `count`)→ header PHI + exit_bindings
- `ExprResult`: ループ戻り値 → exit_phi_builder で処理
10. **JoinIR Core は常時 ON**
- LoopBuilder は物理削除済み。JoinIR を OFF にする経路やフォールバックは存在しない。
- `NYASH_JOINIR_CORE` は deprecated0 を指定しても警告して無視。JoinIR の OFF トグルは提供しない。
---
@ -132,7 +136,44 @@ Local Region (1000+):
- `JoinValueSpace`: **lowering 内部の分離**param vs local vs PHI
- 両者は相補的な役割
詳細は `src/mir/join_ir/lowering/join_value_space.rs``phase201-join-value-space-design.md` を参照。
### 1.9.5 Phase 205: 領域契約の検証強化
**追加された Box-First 機能**:
1. **衝突検出debug-only**
- 全ての割り当てられた ValueId を追跡(`allocated_ids: HashSet<u32>`
- 重複割り当てを即座に検出し panicFail-Fast 原則)
- `check_collision()` で実装
2. **領域検証debug-only**
- `verify_region(id, expected_region)` で ValueId が期待される領域にいるか検証
- 違反時は明確なエラーメッセージと修正ヒントを提供
- 例: "ValueId(500) is in Param region, expected Local. Hint: Use alloc_local() for JoinIR values"
3. **RegionVerifier Box**
- 場所: `src/mir/builder/control_flow/joinir/merge/mod.rs::verify_valueid_regions()`
- 責務: merge 時に boundary と loop_info の ValueId 領域契約を検証
- 検証項目:
- 全ての `boundary.join_inputs` が Param 領域100-999にいる
- 全ての `condition_bindings[].join_value` が Param 領域にいる
- 全ての `carrier_phis[].phi_dst` が有効範囲(<= LOCAL_MAX
4. **明示的な領域定数**
```rust
pub const PHI_RESERVED_MIN: u32 = 0;
pub const PHI_RESERVED_MAX: u32 = 99;
pub const PARAM_MIN: u32 = 100;
pub const PARAM_MAX: u32 = 999;
pub const LOCAL_MIN: u32 = 1000;
pub const LOCAL_MAX: u32 = 100000;
```
**Fail-Fast 原則の実装**:
- 領域違反は即座に panicデバッグモード
- フォールバックやサイレント修正は一切行わない
- エラーメッセージに具体的な修正方法を含める
詳細は `src/mir/join_ir/lowering/join_value_space.rs` と `phase205-valueid-regions-design.md` を参照。
---
@ -534,12 +575,43 @@ Pattern2/4 への統合(実際に Body-local 更新を使うループを JoinI
方針:
- **ループの「形」は P1P4 から増やさない**。
- **ループの「形」は P1P4 から増やさない**。
複雑さLoopBodyLocal 条件、OR chain、continue 多用など)は BoolExprLowerer /
ContinueBranchNormalizer / TrimLoopLowerer (P5) といった補助箱側で吸収する。
- JsonParser 側の P5 適用Trim / `_skip_whitespace` / `_parse_string` 最小版)は実証済み。
- JsonParser 側の P5 適用Trim / `_skip_whitespace` / `_parse_string` 最小版)は実証済み。
残りのループは Phase 17x18x で、P1P4+P5 の組み合わせとして段階的に実装していく。
### 4.3 JsonParser 実戦カバレッジPhase 210 時点)
Phase 210 で「軽量ループ 3 本」を実戦投入し、JoinIR インフラが **本番級に動作する** ことを確認したよ:
- **実戦確認済みループ**7/13 loops ≒ 54%:
- ✅ `_skip_whitespace` (P2 + P5 Trim, Phase 173)
- ✅ `_trim` leading/trailing (P2 + P5 Trim, Phase 171/172)
- ✅ `_match_literal` 最小版 (P1 Simple, Phase 210)
- ✅ `_atoi` 最小版 (P2 Break, NumberAccumulation, Phase 210)
- ✅ `_parse_number` 最小版 (P2 Break, Multi-carrier, Phase 210)
- **Phase 210 の成果**:
- 3 本すべて JoinIR → MIR → Runtime 完全成功RC 正常)
- Pattern1 & Pattern2 自動ルーティング正常動作
- NumberAccumulation (Mul+Add 2命令), Multi-carrier, PHI Contract, ValueId Regions すべて正常
- **制約発見ゼロ** - Phase 190/201/204/205 の統合が完璧に機能
- **Phase 211/212 の発見** (2025-12-09):
- Phase 211: if-sum パターン(ループ内 if 条件付き更新)の設計完了
- Phase 212: ⚠️ **AST→MIR 層の制約発見** - ループ内 if/else が MIR に変換されない問題を検出
- JoinIR Pattern3 (IfPHI) は動作可能だが、その前段階AST→MIRで if が消失
- Phase 212.5 で AST→MIR ループ内 if 修正が必要と判明
- **残りループ** (Phase 211+ で段階的対応予定):
- `_parse_array`, `_parse_object` (MethodCall 複数)
- `_unescape_string` (複雑なキャリア処理)
- その他 6 ループPhase 195/200+ 系設計で順次対応)
**結論**: JoinIR インフラP1-P5/JoinValueSpace/PHI契約は **実戦投入可能な成熟度** に到達 ✨
**Phase 212 制約**: AST→MIR 層のループ内 if 変換修正が次の課題
---
## 5. selfhost / .hako JoinIR Frontend との関係

View File

@ -0,0 +1,557 @@
# Phase 205: ValueId Region Boundaries - Design Document
**Author**: Claude Sonnet 4.5
**Date**: 2025-12-09
**Status**: In Progress
## Overview
Phase 205 establishes strict ValueId region contracts for JoinIR lowering, completing the Box-First architecture started in Phase 201. This phase ensures that ValueId allocation is:
1. **Predictable**: Each ValueId belongs to a clearly defined region
2. **Verifiable**: Region violations are detected in debug mode
3. **Maintainable**: All allocation goes through JoinValueSpace Box
## ValueId Region Architecture
### Region Layout
```text
0 100 1000 u32::MAX
├──────────┼──────────┼──────────────────────────┤
│ PHI │ Param │ Local │
│ Reserved│ Region │ Region │
└──────────┴──────────┴──────────────────────────┘
```
### Region Definitions
| Region | Range | Purpose | Examples |
|--------|-------|---------|----------|
| **PHI Reserved** | 0-99 | LoopHeader PHI destinations | `phi_dst: ValueId(0)` |
| **Param Region** | 100-999 | Loop arguments & environment | `Condition.bool_id`, `Carrier.join_id`, `CapturedEnv` |
| **Local Region** | 1000+ | JoinIR-internal values | Const, BinOp, Load, etc. |
### Constants (Phase 205)
```rust
// Explicit region boundaries
pub const PHI_RESERVED_MIN: u32 = 0;
pub const PHI_RESERVED_MAX: u32 = 99;
pub const PARAM_MIN: u32 = 100;
pub const PARAM_MAX: u32 = 999;
pub const LOCAL_MIN: u32 = 1000;
pub const LOCAL_MAX: u32 = 100000;
```
## Box-First Design
### ValueIdAllocator Box (JoinValueSpace)
**Responsibility**: Single Source of Truth for ValueId allocation
**API**:
```rust
impl JoinValueSpace {
// Primary allocation methods
pub fn alloc_param(&mut self) -> ValueId; // Returns 100+
pub fn alloc_local(&mut self) -> ValueId; // Returns 1000+
pub fn reserve_phi(&mut self, id: ValueId); // Marks PHI dst
// Phase 205: Enhanced verification
pub fn verify_region(&self, id: ValueId, expected: Region) -> Result<(), String>;
pub fn check_collision(&self, id: ValueId, role: &str); // debug-only
}
```
**Invariants**:
1. `alloc_param()` never returns id >= 1000
2. `alloc_local()` never returns id < 1000
3. No ValueId is allocated twice
4. PHI dst always in range 0-99
### RegionVerifier Box
**Responsibility**: Verify region contracts at merge boundaries
**Location**: `src/mir/builder/control_flow/joinir/merge/mod.rs`
**API**:
```rust
#[cfg(debug_assertions)]
fn verify_valueid_regions(
boundary: &JoinInlineBoundary,
loop_info: &LoopHeaderPhiInfo,
join_value_space: &JoinValueSpace,
);
```
**Checks**:
1. All `boundary.join_inputs` are in Param region
2. All `carrier_phis[].phi_dst` are in valid range (<= LOCAL_MAX)
3. No overlap between Param and Local regions
4. PHI reservations are in PHI Reserved region
## ValueId Role Mapping
### Param Region (100-999)
| Role | Allocated By | Example |
|------|-------------|---------|
| **Condition.bool_id** | `condition_env_builder.rs` | `ValueId(100)` |
| **Carrier.join_id** | Pattern frontend (P1/P2/P3/P4) | `ValueId(101)`, `ValueId(102)` |
| **CapturedEnv vars** | Pattern frontend | `ValueId(103+)` |
| **Boundary inputs** | `common_init.rs` | `ValueId(104+)` |
### Local Region (1000+)
| Role | Allocated By | Example |
|------|-------------|---------|
| **Const values** | Lowerers (pattern1-4, trim) | `ValueId(1000)` |
| **BinOp results** | Lowerers | `ValueId(1001)` |
| **Load results** | Lowerers | `ValueId(1002)` |
| **Intermediate values** | Lowerers | `ValueId(1003+)` |
### PHI Reserved (0-99)
| Role | Allocated By | Example |
|------|-------------|---------|
| **PHI dst** | MirBuilder (host side) | `ValueId(0)`, `ValueId(1)` |
**Note**: PHI dst comes from host MirBuilder, NOT JoinValueSpace. `reserve_phi()` is for verification only.
## Current State Inventory (Task 205-2)
### Pattern 1 (Minimal)
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs`
**Status**: Fully integrated with JoinValueSpace
**Allocation Sites**:
- ConditionEnv: Uses `alloc_param()` via `condition_env_builder.rs`
- Carrier (i): Uses `alloc_param()` in frontend
- Lowerer: Uses `alloc_local()` for all JoinIR values
**Raw ValueId Usage**: None detected
### Pattern 2 (With Break)
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
**Status**: Fully integrated with JoinValueSpace
**Allocation Sites**:
- ConditionEnv: Uses `alloc_param()` via `condition_env_builder.rs`
- Carrier (v): Uses `alloc_param()` in frontend
- Lowerer: Uses `alloc_local()` for all JoinIR values
**Raw ValueId Usage**: None detected
**Historical Note**: Pattern 2 was the original motivation for Phase 201 - previously had collision between `alloc_join_value()` (param) and `alloc_value()` (local starting from 0).
### Pattern 3 (With If-PHI)
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs`
**Status**: Needs verification
**Allocation Sites**:
- ConditionEnv: Uses `alloc_param()` via `condition_env_builder.rs`
- Carriers (sum, count): Uses `alloc_param()` in frontend
- Lowerer: Uses `alloc_local()` for all JoinIR values
**Potential Issues**:
- If-PHI lowering: Need to verify all temporary values use `alloc_local()`
- ExitLine reconnection: Verify no raw `ValueId(..)` usage
**Action Required**: Task 205-5 will audit
### Pattern 4 (With Continue)
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
**Status**: Needs verification
**Allocation Sites**:
- ConditionEnv: Uses `alloc_param()` via `condition_env_builder.rs`
- Carriers: Uses `alloc_param()` in frontend
- Lowerer: Uses `alloc_local()` for all JoinIR values
**Potential Issues**:
- Continue-pattern has more complex control flow
- UpdateSummary handling: Verify all intermediate values use `alloc_local()`
**Action Required**: Task 205-5 will audit
### Trim Pattern Lowerer
**File**: `src/mir/builder/control_flow/joinir/patterns/trim_pattern_lowerer.rs`
**Status**: Needs verification
**Allocation Sites**:
- Uses `alloc_fn: &mut dyn FnMut() -> ValueId` pattern
- Should receive `space.local_allocator()` closure
**Potential Issues**:
- Multiple lowerer sites (JsonParser, other Trim use cases)
- Need to ensure all call sites pass `space.local_allocator()`
**Action Required**: Task 205-5 will audit
### ConditionEnv Builder
**File**: `src/mir/builder/control_flow/joinir/patterns/condition_env_builder.rs`
**Status**: Already uses `alloc_param()`
**Implementation**:
```rust
pub fn build_condition_env(
condition_ast: &AstNode,
join_value_space: &mut JoinValueSpace,
// ...
) -> Result<ConditionEnv, String> {
let bool_id = join_value_space.alloc_param(); // ✅ Correct
// ...
}
```
### Exit Binding & Common Init
**Files**:
- `src/mir/builder/control_flow/joinir/patterns/exit_binding.rs`
- `src/mir/builder/control_flow/joinir/patterns/common_init.rs`
**Status**: Needs verification
**Potential Issues**:
- Exit binding may create temporary ValueIds
- Common init should use `alloc_param()` for boundary inputs
**Action Required**: Task 205-5 will audit
## Implementation Plan
### Task 205-3: ValueIdAllocator Box Enhancement
**Changes to** `src/mir/join_ir/lowering/join_value_space.rs`:
```rust
// Add explicit max constants
pub const LOCAL_MAX: u32 = 100000;
// Add collision detection (debug-only)
#[cfg(debug_assertions)]
fn check_collision(&self, id: ValueId, role: &str) {
if self.allocated_ids.contains(&id) {
panic!(
"[JoinValueSpace] ValueId collision: {:?} already allocated (role: {})",
id, role
);
}
}
// Add region verification
#[cfg(debug_assertions)]
pub fn verify_region(&self, id: ValueId, expected_region: Region) -> Result<(), String> {
let actual = self.region_of(id);
if actual != expected_region {
return Err(format!(
"ValueId {:?} is in {:?} region, expected {:?}",
id, actual, expected_region
));
}
Ok(())
}
// Track allocated IDs (debug-only)
#[cfg(debug_assertions)]
allocated_ids: HashSet<u32>,
// Update alloc_param/alloc_local to track allocations
#[cfg(debug_assertions)]
pub fn alloc_param(&mut self) -> ValueId {
let id = self.next_param;
debug_assert!(id < LOCAL_BASE, "Param region overflow");
self.check_collision(ValueId(id), "param");
self.allocated_ids.insert(id);
self.next_param += 1;
ValueId(id)
}
```
### Task 205-4: RegionVerifier Box Implementation
**Location**: `src/mir/builder/control_flow/joinir/merge/mod.rs`
**Integration Point**: Add to existing `verify_joinir_contracts()` function
```rust
#[cfg(debug_assertions)]
fn verify_joinir_contracts(
func: &JoinIRFunction,
boundary: &JoinInlineBoundary,
loop_info: &LoopHeaderPhiInfo,
join_value_space: &JoinValueSpace,
) {
// Existing PHI contract verification
verify_phi_contracts(func, loop_info);
// Phase 205: Add region verification
verify_valueid_regions(boundary, loop_info, join_value_space);
}
#[cfg(debug_assertions)]
fn verify_valueid_regions(
boundary: &JoinInlineBoundary,
loop_info: &LoopHeaderPhiInfo,
join_value_space: &JoinValueSpace,
) {
// 1. Verify boundary inputs are in Param region
for join_id in &boundary.join_inputs {
let region = join_value_space.region_of(*join_id);
if region != Region::Param {
panic!(
"[RegionVerifier] Boundary input {:?} is in {:?} region, expected Param",
join_id, region
);
}
}
// 2. Verify PHI dst are in valid range
for (carrier_name, entry) in &loop_info.carrier_phis {
let region = join_value_space.region_of(entry.phi_dst);
// PHI dst may be in PHI Reserved or early Param range (depending on MirBuilder)
if entry.phi_dst.0 > LOCAL_MAX {
panic!(
"[RegionVerifier] Carrier '{}' PHI dst {:?} exceeds LOCAL_MAX",
carrier_name, entry.phi_dst
);
}
}
// 3. Verify JoinValueSpace internal consistency
if let Err(e) = join_value_space.verify_no_overlap() {
panic!("[RegionVerifier] JoinValueSpace overlap detected: {}", e);
}
}
```
### Task 205-5: Pattern Integration Audit
**Files to Audit**:
1. `pattern1_minimal.rs` - Already correct
2. `pattern2_with_break.rs` - Already correct
3. `pattern3_with_if_phi.rs` - Verify If-PHI lowering
4. `pattern4_with_continue.rs` - Verify UpdateSummary handling
5. `trim_pattern_lowerer.rs` - Verify all call sites
6. `exit_binding.rs` - Verify no raw ValueId usage
7. `common_init.rs` - Verify boundary input allocation
**Audit Checklist**:
- [ ] No raw `ValueId(..)` construction in lowerers
- [ ] All Carrier `join_id` use `alloc_param()`
- [ ] All lowerer intermediate values use `alloc_local()`
- [ ] All `alloc_fn` closures receive `space.local_allocator()`
**Fix Strategy**:
```rust
// ❌ Before (if found):
let temp = ValueId(next_id);
next_id += 1;
// ✅ After:
let temp = join_value_space.alloc_local();
```
### Task 205-6: Testing & Documentation
**Test Cases**:
1. `loop_min_while.hako` (Pattern 1)
2. `loop_with_break.hako` (Pattern 2)
3. `loop_if_phi.hako` (Pattern 3)
4. `loop_continue_pattern4.hako` (Pattern 4)
5. Trim/JsonParser representative case
**Expected Outcome**:
- All 821 tests pass
- No regression
- Debug assertions detect region violations (if any)
**Documentation Updates**:
1. `joinir-architecture-overview.md`:
- Add "ValueId Region Contract" section
- Update Box boundary diagram
- Link to this design doc
2. `CURRENT_TASK.md`:
- Mark Phase 205 complete
- Add handoff notes for Phase 206
## Fail-Fast Principles
### Region Violations
**Principle**: Detect region violations immediately, fail fast with clear error messages.
**Implementation**:
```rust
#[cfg(debug_assertions)]
fn verify_region(&self, id: ValueId, expected: Region) -> Result<(), String> {
let actual = self.region_of(id);
if actual != expected {
// ✅ Clear, actionable error message
return Err(format!(
"ValueId {:?} is in {:?} region, expected {:?}\n\
Hint: Use alloc_param() for loop arguments, alloc_local() for JoinIR values",
id, actual, expected
));
}
Ok(())
}
```
**No Fallback**: If a region violation occurs, panic immediately. Do not:
- Silently remap ValueIds
- Use fallback allocation
- Continue with corrupted state
### Collision Detection
**Principle**: Each ValueId allocated exactly once.
**Implementation**:
```rust
#[cfg(debug_assertions)]
fn check_collision(&self, id: ValueId, role: &str) {
if self.allocated_ids.contains(&id.0) {
panic!(
"[JoinValueSpace] ValueId collision detected!\n\
ID: {:?}\n\
Role: {}\n\
This indicates a bug in JoinIR lowering - contact maintainer",
id, role
);
}
}
```
## Box Boundaries
### SSOT (Single Source of Truth)
**JoinValueSpace is the SSOT for JoinIR ValueId allocation.**
**Boundary Rules**:
1. **Inside JoinIR lowering**: All ValueIds come from JoinValueSpace
2. **Outside JoinIR lowering**: MirBuilder allocates PHI dst independently
3. **Bridge**: `reserve_phi()` synchronizes PHI dst for verification
**Example**:
```rust
// ✅ Correct: JoinIR lowering
let mut join_value_space = JoinValueSpace::new();
let carrier_id = join_value_space.alloc_param(); // Inside SSOT boundary
// ✅ Correct: MirBuilder allocates PHI dst
let phi_dst = mir_builder.alloc_value(); // Outside SSOT boundary
// ⚠️ Bridge: Sync for verification
join_value_space.reserve_phi(phi_dst); // Tell JoinValueSpace about external PHI
```
### Allocator Closures
**Pattern**: Pass allocation function to lowerers
```rust
// ✅ Correct pattern:
fn lower_pattern3(
alloc_local: &mut dyn FnMut() -> ValueId, // Receives closure
// ...
) {
let const_id = alloc_local(); // ✅ Uses closure
}
// Call site:
lower_pattern3(
&mut join_value_space.local_allocator(), // ✅ Passes JoinValueSpace closure
// ...
);
```
**Benefits**:
- Lowerer doesn't need direct JoinValueSpace reference
- Maintains Box boundary
- Easy to test with mock allocators
## Success Criteria
Phase 205 is complete when:
1. Design document created (this file)
2. JoinValueSpace has collision detection & region verification (debug-only)
3. RegionVerifier integrated into merge verification
4. All patterns (P1/P2/P3/P4) audited for raw ValueId usage
5. All tests pass (821 tests, 0 regression)
6. Documentation updated (overview + CURRENT_TASK)
## Future Work (Phase 206+)
### Potential Enhancements
1. **Runtime Region Tracking** (if needed):
- Track ValueId Role mapping for better error messages
- Example: "ValueId(105) is carrier 'sum', expected local region"
2. **Region Statistics**:
- Report param/local/PHI usage per pattern
- Detect potential region exhaustion early
3. **Contract Testing**:
- Generate test cases that deliberately violate regions
- Verify debug assertions trigger correctly
4. **Allocator Modes**:
- Dense allocation (minimize gaps)
- Sparse allocation (easier debugging)
- Deterministic allocation (reproducible builds)
## References
- **Phase 201**: JoinValueSpace initial implementation
- **Phase 204**: PHI contract verification (dst overwrite, inputs sanity)
- **Box-First Principle**: CLAUDE.md Section "箱理論Box-First"
## Appendix: Region Math
### Current Capacity
| Region | Range | Capacity | Typical Usage |
|--------|-------|----------|---------------|
| PHI Reserved | 0-99 | 100 IDs | 1-5 PHIs per loop |
| Param | 100-999 | 900 IDs | 3-10 params per loop |
| Local | 1000-99999 | 99000 IDs | 10-1000 values per loop |
### Overflow Scenarios
**Param Overflow** (highly unlikely):
- Would require 900+ loop parameters
- Current max observed: ~10 params (Pattern 3)
- Debug assertion will catch at param #900
**Local Overflow** (theoretical):
- Would require 99000+ JoinIR instructions
- Current max observed: ~100 instructions (JsonParser)
- Would indicate pathological code generation
**PHI Overflow** (impossible):
- PHI dst allocated by MirBuilder, not JoinValueSpace
- JoinValueSpace only verifies PHI dst <= 99
- If violated, indicates bug in MirBuilder
## Version History
- **2025-12-09**: Initial design document (Claude Sonnet 4.5)
- **Phase 205-1**: Created as part of ValueId region boundary task

View File

@ -0,0 +1,753 @@
# Phase 210: JsonParser JoinIR ミニ統合ラウンド1
**日付**: 2025-12-09
**ゴール**: 既存JoinIRインフラP1-P5/JoinValueSpace/PHI契約で実戦ループ2〜3本を通して観測する
**制約**: 新しい箱・大リファクタは禁止。問題発見時は「どの層の制約か」を記録するまで。
---
## Section 1: 対象ループの再選定Task 210-1
### 1.1 選定基準
**Phase 210 の狙い**:
- 既存インフラで「理論上いけるはず」のループを実戦投入
- 新機能実装ではなく、既存機能の統合観測
- Fail-Fast で問題層を特定
**選定条件**:
1. ✅ Phase 190 で理論実装済みパターン (NumberAccumulation)
2. ✅ Phase 181 で棚卸し済み(ブロックなし確認済み)
3. ✅ 単純構造LoopBodyLocal なし or Trim パターンのみ)
4. ✅ 既存テスト資産が使えるphase190_*, phase183_* など)
### 1.2 選定結果: 3本のループ
#### ループ1: _atoi の最小版 (P2 Break) ⭐最優先
**理由**:
- Phase 190-impl-D で E2E 検証済み (`phase190_atoi_impl.hako` → 12 ✅)
- NumberAccumulation パターン (`v = v * 10 + digit`) 完全実装済み
- CarrierInfo, LoopUpdateAnalyzer, CarrierUpdateLowerer で全対応
- 既に JoinIR → MIR パイプライン通過確認済み
**ループ構造**:
```nyash
local result = 0
local i = 0
loop(i < n) {
local ch = s.substring(i, i+1)
local pos = digits.indexOf(ch)
if pos < 0 { break }
result = result * 10 + pos // NumberAccumulation
i = i + 1
}
```
**想定パターン**: Pattern 2 (WithBreak)
**既知の制約**:
- LoopBodyLocal (`ch`, `pos`) への代入は JoinIR 未対応Phase 186 残タスク)
- 回避策: body-local を使わず carrier のみで書ける最小版を使用
**観測ポイント**:
- `[pattern] Pattern2_WithBreak MATCHED`
- `[joinir/pattern2] Generated JoinIR`
- `[joinir/verify] all contracts satisfied`
- Runtime: 正しい整数変換結果
---
#### ループ2: _parse_number の最小版 (P2 Break)
**理由**:
- Phase 190-impl-D で E2E 検証済み (`phase190_parse_number_impl.hako` → 123 ✅)
- StringAppendChar + NumberAccumulation の組み合わせ
- Multi-carrier パターン(`p`, `num_str`)の実証
**ループ構造**:
```nyash
local num_str = ""
local p = 0
loop(p < s.length()) {
local ch = s.substring(p, p+1)
local digit_pos = digits.indexOf(ch)
if digit_pos < 0 { break }
num_str = num_str + ch // StringAppendChar
p = p + 1
}
```
**想定パターン**: Pattern 2 (WithBreak)
**既知の制約**:
- LoopBodyLocal (`ch`, `digit_pos`) への代入は JoinIR 未対応
- 回避策: body-local を読み取り専用として扱う(書き込みなし)
**観測ポイント**:
- Multi-carrier update の正しい PHI 配線
- StringAppendChar + CounterLike の組み合わせ動作
- Runtime: 正しい数値文字列抽出
---
#### ループ3: _match_literal の最小版 (P1 Simple)
**理由**:
- Phase 181 で「P1 Simple」として分類済み
- 最も単純なパターンbreak なし、continue なし)
- Pattern 1 の汎用性確認に最適
**ループ構造**:
```nyash
local i = 0
loop(i < len) {
if s.substring(pos + i, pos + i + 1) != literal.substring(i, i + 1) {
return 0
}
i = i + 1
}
return 1
```
**想定パターン**: Pattern 1 (SimpleWhile)
**既知の制約**:
- 早期 return があるLoopForm では break として扱われる可能性)
- Pattern 1 vs Pattern 2 のルーティング境界を観測
**観測ポイント**:
- Pattern 1 vs Pattern 2 の自動ルーティング
- 早期 return の JoinIR 表現
- Runtime: 文字列一致判定の正確性
---
### 1.3 除外したループ
**_skip_whitespace, _trim (leading/trailing)**:
- 既に Phase 171/173 で実装・検証済み
- Phase 210 では「新規に JoinIR ラインに乗せたいもの」を優先(指示書より)
- 比較用として残すが、今回の観測対象からは除外
**_parse_array, _parse_object, _unescape_string**:
- MethodCall 多数、複雑なキャリア処理
- Phase 183+ の対象Phase 210 の範囲外)
---
## Section 2: 最小 .hako ハーネスの設計Task 210-2
### 2.1 ハーネス設計方針
**Phase 210 の制約**:
- 既存の Phase190/200 系テストを再利用してもよい(指示書より)
- 新規に書く場合は `apps/tests/phase210_*` に配置
- RCResult Codeで結果を返すシンプル構造
**再利用候補**:
1. `apps/tests/phase190_atoi_impl.hako` (既存) ✅
2. `apps/tests/phase190_parse_number_impl.hako` (既存) ✅
3. `apps/tests/phase210_match_literal_min.hako` (新規作成予定)
---
### 2.2 ハーネス1: phase190_atoi_impl.hako (再利用)
**現状**: Phase 190-impl-D で既に検証済み
**実行コマンド**:
```bash
./target/release/hakorune apps/tests/phase190_atoi_impl.hako
```
**期待出力**:
```
12
```
**観測項目**:
- [ ] `[pattern] Pattern2_WithBreak MATCHED`
- [ ] `[joinir/pattern2] Generated JoinIR`
- [ ] `[joinir/verify] all contracts satisfied`
- [ ] Runtime: RC = 12
---
### 2.3 ハーネス2: phase190_parse_number_impl.hako (再利用)
**現状**: Phase 190-impl-D で既に検証済み
**実行コマンド**:
```bash
./target/release/hakorune apps/tests/phase190_parse_number_impl.hako
```
**期待出力**:
```
123
```
**観測項目**:
- [ ] `[pattern] Pattern2_WithBreak MATCHED`
- [ ] Multi-carrier PHI 配線確認
- [ ] StringAppendChar + CounterLike 組み合わせ動作
- [ ] Runtime: RC = 123
---
### 2.4 ハーネス3: phase210_match_literal_min.hako (新規)
**設計イメージ**:
```nyash
static box Main {
main() {
local s = "hello"
local literal = "hello"
local pos = 0
local len = 5
local i = 0
loop(i < len) {
if s.substring(pos + i, pos + i + 1) != literal.substring(i, i + 1) {
return 0
}
i = i + 1
}
return 1
}
}
```
**実行コマンド**:
```bash
./target/release/hakorune apps/tests/phase210_match_literal_min.hako
```
**期待出力**:
```
1
```
**観測項目**:
- [ ] Pattern 1 vs Pattern 2 ルーティング結果
- [ ] 早期 return の JoinIR 表現
- [ ] Runtime: RC = 1 (一致成功)
**実装タイミング**: Task 210-2 の「コード実装」フェーズで作成(今回は設計のみ)
---
## Section 3: 実行経路の確認Task 210-3
### 3.1 実行コマンド方針
**基本実行**:
```bash
./target/release/hakorune apps/tests/phase210_*.hako
```
**構造確認モード** (必要に応じて):
```bash
NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune apps/tests/phase210_*.hako
```
**詳細ログ** (問題発生時):
```bash
NYASH_CLI_VERBOSE=1 ./target/release/hakorune apps/tests/phase210_*.hako
```
---
### 3.2 期待するログのイメージ
#### Pattern 2 (WithBreak) の場合
**ルーティング段階**:
```
[trace:routing] router: function 'Main.main' - try_cf_loop_joinir called
[trace:pattern] route: Pattern2_WithBreak MATCHED
```
**JoinIR 生成段階**:
```
[joinir/pattern2] Generated JoinIR for loop
[joinir/pattern2] Carriers: result, i
[joinir/pattern2] Update kinds: NumberAccumulation(base=10), CounterLike
```
**検証段階**:
```
[joinir/verify] Verifying loop header PHIs
[joinir/verify] Verifying exit line contract
[joinir/verify] Verifying ValueId regions
[joinir/verify] all contracts satisfied
```
**MIR マージ段階**:
```
[joinir/merge] Merging JoinIR into host MIR
[joinir/merge] Reconnecting exit line
[joinir/merge] Merge complete
```
#### Pattern 1 (SimpleWhile) の場合
**ルーティング段階**:
```
[trace:routing] router: function 'Main.main' - try_cf_loop_joinir called
[trace:pattern] route: Pattern1_SimpleWhile MATCHED
```
**JoinIR 生成段階**:
```
[joinir/pattern1] Generated JoinIR for simple loop
[joinir/pattern1] Carriers: i
[joinir/pattern1] No break/continue, single exit
```
---
### 3.3 Fail-Fast 方針
**Phase 210 の鉄則**: 問題発見時は「記録するまで」に留める。修正は Phase 211+ で。
#### Fail-Fast ケース1: [joinir/freeze]
**想定エラー**:
```
[joinir/freeze] Complex carrier update detected
carrier: result
reason: MethodCall in addend
```
**対処**:
- 記録: 「CarrierUpdate 層でブロック」
- 修正: Phase 211+ で MethodCall 対応
#### Fail-Fast ケース2: Type error
**想定エラー**:
```
[ERROR] Type mismatch: expected Integer, got String
```
**対処**:
- 記録: 「ConditionEnv 層でブロック(型推論失敗)」
- 修正: Phase 211+ で型ヒント強化
#### Fail-Fast ケース3: ssa-undef-debug
**想定エラー**:
```
[ssa-undef-debug] Undefined variable: pos
at: LoopBodyLocal assignment
```
**対処**:
- 記録: 「LoopBodyLocal 層でブロックPhase 186 残タスク)」
- 回避: body-local を使わない最小版に切り替え
---
## Section 4: 観測結果の記録Task 210-4
### 4.1 記録フォーマット
**このセクションに追記する形で観測結果を記録する**
#### テストファイル一覧
| # | ファイル | ループパターン | 実行日 | 結果 |
|---|---------|--------------|-------|------|
| 1 | phase190_atoi_impl.hako | P2 Break (NumberAccumulation) | 2025-12-09 | ✅ RC=12 |
| 2 | phase190_parse_number_impl.hako | P2 Break (Multi-carrier) | 2025-12-09 | ✅ RC=123 |
| 3 | phase210_match_literal_min.hako | P1 Simple | 2025-12-09 | ✅ RC=1 |
#### 観測結果テーブル
| ループ | Pattern | JoinIR生成 | PHI契約 | MIRマージ | Runtime | エラー層 | 備考 |
|-------|---------|-----------|---------|----------|---------|---------|------|
| _atoi | P2 | ✅ | ✅ | ✅ | ✅ 12 | なし | NumberAccumulation (Mul+Add) 正常動作 |
| _parse_number | P2 | ✅ | ✅ | ✅ | ✅ 123 | なし | Multi-carrier (i, num) 正常動作 |
| _match_literal | P1 | ✅ | ✅ | ✅ | ✅ 1 | なし | Pattern1 SimpleWhile 正常動作 |
**記号**:
- ✅: 正常動作
- ⚠️: 警告あり(動作はする)
- ❌: エラーFail-Fast
- `-`: 未実行
---
### 4.2 エラー層の分類
**Phase 210 で観測する層**:
| 層 | 責任範囲 | 既知の制約 |
|----|---------|----------|
| **ConditionEnv** | 条件式の変数解決・型推論 | MethodCall in condition (Phase 171-D) |
| **LoopBodyLocal** | body-local 変数の代入 | Assignment 未対応 (Phase 186) |
| **CarrierUpdate** | Carrier 更新パターンの検出 | Complex addend (Phase 191+) |
| **MethodCall** | メソッド呼び出しの lowering | body-local の MethodCall (Phase 183+) |
| **PHI Contract** | PHI dst/inputs の検証 | Phase 204/205 で対応済み |
| **ValueId Region** | Param/Local region 分離 | Phase 205 で対応済み |
---
### 4.3 インフラ達成度マトリクス
**Phase 210 時点の達成度**:
| 機能 | Pattern1 | Pattern2 | Pattern3 | Pattern4 | Pattern5 |
|-----|----------|----------|----------|----------|----------|
| **基本 loop** | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Break** | - | ✅ | - | - | - |
| **Continue** | - | - | - | ✅ | - |
| **If-PHI** | - | - | ✅ | ✅ | - |
| **Trim (LoopBodyLocal昇格)** | - | ✅ | - | - | ✅ |
| **NumberAccumulation** | - | ✅ | - | - | - |
| **StringAppendChar** | - | ✅ | - | ✅ | - |
| **Multi-carrier** | ✅ | ✅ | ✅ | ✅ | ✅ |
| **PHI Contract** | ✅ | ✅ | ✅ | ✅ | ✅ |
| **ValueId Region** | ✅ | ✅ | ✅ | ✅ | ✅ |
**未対応機能** (Phase 211+ の課題):
- [ ] LoopBodyLocal への代入 (Phase 186)
- [ ] MethodCall in condition (Phase 171-D)
- [ ] Complex addend in NumberAccumulation (Phase 191+)
- [ ] MethodCall in body-local (Phase 183+)
---
### 4.4 詳細観測ログ (2025-12-09 実行結果)
#### ハーネス1: phase190_atoi_impl.hako ✅
**実行コマンド**:
```bash
./target/release/hakorune apps/tests/phase190_atoi_impl.hako
```
**主要ログ抽出**:
```
[pattern2/init] PatternPipelineContext: loop_var='i', loop_var_id=ValueId(4), carriers=1
[pattern2/phase201] Using JoinValueSpace: loop_var 'i' → Some(ValueId(100))
[pattern2/phase201] Allocated carrier 'result' param ID: ValueId(101)
[cf_loop/pattern2] Phase 176-3: Analyzed 1 carrier updates
[joinir/pattern2] Phase 176-3: Carrier 'result' update: ValueId(101) -> ValueId(1013)
[joinir_block] Compute instruction: Const { dst: ValueId(1011), value: Integer(10) }
[joinir_block] Compute instruction: BinOp { dst: ValueId(1012), op: Mul, lhs: ValueId(102), rhs: ValueId(1011) }
[joinir_block] Compute instruction: BinOp { dst: ValueId(1013), op: Add, lhs: ValueId(1012), rhs: ValueId(100) }
```
**観測ポイント**:
- ✅ Pattern2 ルーティング成功
- ✅ NumberAccumulation 検出: `result * 10 + i` → Mul + Add の2命令
- ✅ ValueId Regions: Param (100-101), Local (1000+) 正常分離
- ✅ PHI 契約: LoopHeader PHI (ValueId(5), ValueId(6)) + Exit PHI 正常配線
- ✅ Runtime: 出力 `12` (期待値通り)
---
#### ハーネス2: phase190_parse_number_impl.hako ✅
**実行コマンド**:
```bash
./target/release/hakorune apps/tests/phase190_parse_number_impl.hako
```
**主要ログ抽出**:
```
[pattern2/init] PatternPipelineContext: loop_var='i', loop_var_id=ValueId(4), carriers=1
[pattern2/phase201] Using JoinValueSpace: loop_var 'i' → Some(ValueId(100))
[pattern2/phase201] Allocated carrier 'num' param ID: ValueId(101)
[joinir/pattern2] Phase 176-3: Generating JoinIR for 1 carriers: ["num"]
[cf_loop/exit_line] ExitMetaCollector: Collected 'num' JoinIR ValueId(1016) → HOST ValueId(2)
[DEBUG-177] Phase 33-21: carrier_phis count: 2, names: ["i", "num"]
```
**観測ポイント**:
- ✅ Pattern2 ルーティング成功
- ✅ Multi-carrier: `i` (loop var), `num` (carrier) の2つ正常動作
- ✅ NumberAccumulation: `num * 10 + i` の Mul + Add 生成
- ✅ Exit PHI: 2つの carrier が正しく Exit block で統合
- ✅ Runtime: 出力 `123` (期待値通り)
---
#### ハーネス3: phase210_match_literal_min.hako ✅
**実行コマンド**:
```bash
./target/release/hakorune apps/tests/phase210_match_literal_min.hako
```
**主要ログ抽出**:
```
[joinir/pattern1] Generated JoinIR for Simple While Pattern
[joinir/pattern1] Functions: main, loop_step, k_exit
[DEBUG-177] Phase 33-21: carrier_phis count: 1, names: ["i"]
[cf_loop/joinir] Phase 177-3: Loop header with 1 PHI dsts to protect: {ValueId(11)}
```
**観測ポイント**:
-**Pattern1 ルーティング成功** (Simple While Pattern)
- ✅ JoinIR 生成: main, loop_step, k_exit の3関数
- ✅ Single carrier: loop var `i` のみ
- ✅ PHI 契約: LoopHeader PHI (ValueId(11)) 正常
- ✅ Runtime: 出力 `0 1 2` + RC=1 (最終return値正常)
- ⚠️ 副作用: `print(i)` が意図せず実行(テストコード設計時の残骸、動作自体は正常)
---
### 4.5 Phase 210 総合評価
**成功基準達成度**:
| 基準 | 達成 | 詳細 |
|-----|------|------|
| **最低限の成功** (1本でも通る) | ✅ | 3本すべて JoinIR → MIR → Runtime 到達 |
| **理想的な成功** (3本全て通る) | ✅ | Pattern1, Pattern2 両方で観測データ取得成功 |
| **Pattern1 動作確認** | ✅ | SimpleWhile パターン正常動作 |
| **Pattern2 動作確認** | ✅ | Break パターン正常動作 |
| **NumberAccumulation** | ✅ | Mul + Add 2命令生成確認 |
| **Multi-carrier** | ✅ | 2 carrier 同時動作確認 |
| **PHI Contract** | ✅ | LoopHeader PHI + Exit PHI 正常配線 |
| **ValueId Regions** | ✅ | Param/Local region 分離確認 |
| **Fail-Fast 発動** | ❌ | エラー0件すべて正常動作 |
**重要な発見**:
-**既存インフラは「理論上いけるはず」を超えて「実戦でも完全動作」** することを確認
- ✅ Phase 190 (NumberAccumulation), Phase 201 (JoinValueSpace), Phase 204/205 (PHI Contract) の統合が完璧に機能
- ✅ Pattern1 と Pattern2 の自動ルーティングが正常動作
- ✅ Multi-carrier パターンの PHI 配線も問題なし
-**制約発見なし** - 予想に反して、すべてのループが制約なく動作
**Phase 210 の結論**:
> JoinIR インフラP1-P5/JoinValueSpace/PHI契約は **実戦投入可能** な成熟度に達している✨
---
## Section 5: ドキュメント更新Task 210-5
### 5.1 CURRENT_TASK.md への追記
**追加内容** (Phase 210 完了時):
```markdown
### Phase 210: JsonParser JoinIR ミニ統合ラウンド1
- **ゴール**: 既存 JoinIR インフラで実戦ループ 2〜3 本を観測
- **結果**:
- _atoi (P2 Break): ✅ or ⚠️ or ❌ (詳細: phase210-jsonparser-mini-integration.md)
- _parse_number (P2 Break): ✅ or ⚠️ or ❌
- _match_literal (P1/P2): ✅ or ⚠️ or ❌
- **発見した制約**:
- [TBD: 実行後に記録]
- **次フェーズ**: Phase 211 - 発見した制約の解消
```
---
### 5.2 joinir-architecture-overview.md への追記
**追加箇所**: Section 1.10 (Coverage Snapshot) など
**追加内容**:
```markdown
#### Phase 210: JsonParser Coverage Snapshot
**実戦投入済みループ**: 3/11 loops (Phase 210 時点)
- ✅ _atoi (P2 Break, NumberAccumulation)
- ✅ _parse_number (P2 Break, Multi-carrier)
- ✅ _match_literal (P1 Simple)
**残りループ**: 8 loops
- Phase 211+: _parse_array, _parse_object (MethodCall 複数)
- Phase 212+: _unescape_string (複雑なキャリア処理)
```
---
## Section 6: 実装タスクの整理
### Task 210-1: 対象ループの再選定 ✅(このドキュメント完成で完了)
**成果物**:
- このドキュメント (phase210-jsonparser-mini-integration.md)
- 選定ループ: _atoi, _parse_number, _match_literal (3本)
- 想定パターン: P1 (SimpleWhile), P2 (WithBreak)
---
### Task 210-2: 最小 .hako ハーネス準備(次のステップ)
**実装内容**:
1. `phase190_atoi_impl.hako` の再確認(既存)
2. `phase190_parse_number_impl.hako` の再確認(既存)
3. `phase210_match_literal_min.hako` の新規作成
**実装タイミング**: Task 210-2 実行時
---
### Task 210-3: 実行経路の確認Task 210-2 の後)
**実行コマンド**:
```bash
# ハーネス1
./target/release/hakorune apps/tests/phase190_atoi_impl.hako
# ハーネス2
./target/release/hakorune apps/tests/phase190_parse_number_impl.hako
# ハーネス3
./target/release/hakorune apps/tests/phase210_match_literal_min.hako
```
**記録先**: Section 4 の観測結果テーブル
---
### Task 210-4: 観測結果の記録Task 210-3 の後)
**記録内容**:
- 実行日時
- ログ出力Pattern ルーティング、JoinIR 生成、検証、Runtime
- エラー層の分類
- インフラ達成度マトリクスの更新
**記録先**: Section 4 (このドキュメント内)
---
### Task 210-5: ドキュメント更新Task 210-4 の後)
**更新対象**:
1. `CURRENT_TASK.md` - Phase 210 の結果と次フェーズ計画
2. `joinir-architecture-overview.md` - JsonParser Coverage Snapshot 更新
---
## Section 7: 成功基準
### 7.1 Phase 210 の成功定義
**最低限の成功** (1本でも通れば成功):
- [ ] いずれか1本のループが JoinIR → MIR → Runtime まで到達
- [ ] エラーが出た場合、エラー層が明確に分類できる
**理想的な成功** (3本全て通る):
- [ ] 3本のループすべてが正常実行
- [ ] Pattern 1 と Pattern 2 の両方で観測データ取得
- [ ] Multi-carrier, NumberAccumulation, StringAppendChar の組み合わせ動作確認
---
### 7.2 Fail-Fast の成功定義
**Phase 210 は Fail-Fast が成功条件**:
- ✅ エラーが出たら即座に記録して停止(修正しない)
- ✅ エラー層を 6 つの分類ConditionEnv/LoopBodyLocal/CarrierUpdate/MethodCall/PHI/ValueIdに振り分け
- ✅ Phase 211+ の課題として整理
**失敗条件**:
- ❌ エラーを無視して進む
- ❌ エラー層が不明なまま終わる
- ❌ Phase 210 で新機能実装を始める
---
## Section 8: 次フェーズへの接続
### Phase 211: 制約解消フェーズ
**Phase 210 で発見した制約を解消する**:
1. LoopBodyLocal への代入 (Phase 186 残タスク)
2. MethodCall in condition (Phase 171-D)
3. Complex addend in NumberAccumulation (Phase 191+)
**実装戦略**:
- Phase 210 の観測結果を基に、最も影響の大きい制約から優先的に解消
- 1フェーズ1制約の原則箱理論: 小さく積む)
---
### Phase 212+: JsonParser 完全統合
**残り8ループの段階的実装**:
- Phase 212: _parse_array, _parse_object (MethodCall 複数対応)
- Phase 213: _unescape_string (複雑なキャリア処理)
- Phase 214: JsonParser 全11ループ完全動作確認
---
## Appendix A: 既存テストの確認
### A.1 phase190_atoi_impl.hako
**場所**: `apps/tests/phase190_atoi_impl.hako`
**現状**: Phase 190-impl-D で E2E 検証済み
**実行結果** (Phase 190 時点):
```
12
```
**Phase 210 での再確認ポイント**:
- [ ] Pattern2 ルーティング確認
- [ ] NumberAccumulation 検出確認
- [ ] PHI Contract 検証通過確認
---
### A.2 phase190_parse_number_impl.hako
**場所**: `apps/tests/phase190_parse_number_impl.hako`
**現状**: Phase 190-impl-D で E2E 検証済み
**実行結果** (Phase 190 時点):
```
123
```
**Phase 210 での再確認ポイント**:
- [ ] Multi-carrier (p, num_str) の PHI 配線確認
- [ ] StringAppendChar + CounterLike 組み合わせ確認
- [ ] Exit line reconnect 確認
---
## Appendix B: 参照ドキュメント
### B.1 Phase 190 関連
- **phase190-number-update-design.md** - NumberAccumulation 設計書
- **phase190-impl-D 完了報告** - _atoi, _parse_number E2E 検証結果
### B.2 Phase 181 関連
- **phase181-jsonparser-loop-roadmap.md** - JsonParser 全11ループの棚卸し
### B.3 JoinIR アーキテクチャ
- **joinir-architecture-overview.md** - JoinIR 全体設計
- **phase204-phi-contract-verifier.md** - PHI Contract 検証
- **phase205-valueid-regions-design.md** - ValueId Region 設計
---
## 改訂履歴
- **2025-12-09**: Task 210-1 完了(対象ループ再選定・設計ドキュメント作成)
- 選定ループ: _atoi, _parse_number, _match_literal (3本)
- 想定パターン: P1 (SimpleWhile), P2 (WithBreak)
- 既存テスト再利用: phase190_atoi_impl.hako, phase190_parse_number_impl.hako
- 新規ハーネス設計: phase210_match_literal_min.hako
---
**Phase 210 Status**: Task 210-1 完了 ✅ / Task 210-2〜210-5 未実行

View File

@ -0,0 +1,277 @@
# Phase 211: JsonParser 次の 1 手(中規模ループ候補選定)
**Phase**: 211
**Date**: 2025-12-09
**Status**: 🎯 設計フェーズ(コード実装なし)
**Prerequisite**: Phase 210 完了(軽量ループ 3 本実戦成功)
---
## 🎯 Phase 211 の目的
Phase 210 で「軽量ループ 3 本」が完全成功したため、次は **「中規模の複雑さを持つループ 1 本」** を選び、既存の Pattern/P5 boxes をどう組み合わせるか **設計のみ** 行う。
### 📋 作業範囲(明確化)
-**やること**: ループ 1 本選定 → Pattern/boxes マッピング → 組み合わせ戦略設計
-**やらないこと**: コード実装、ハーネス作成、テスト実行
- 🎯 **成果物**: Phase 212+ で実装する際の「設計図」
---
## Task 211-1: 中規模ループ候補の選定
### 候補 A: `_parse_string` 簡略版
**元の仕様** (Phase 181 より):
```hako
_parse_string(pos) {
local i = pos
local escaped = 0 // LoopBodyLocal (フラグ)
local buf = new ArrayBox() // Buffer構築
loop(i < len) {
local ch = s.char_at(i)
if ch == quote and escaped == 0 { break } // 終了条件
if ch == backslash {
escaped = 1 // フラグ切り替え
} else {
if escaped == 1 {
buf.append(escape_char(ch)) // エスケープ処理
escaped = 0
} else {
buf.append(ch)
}
}
i = i + 1
}
return buf.to_string()
}
```
**簡略版スコープ** (Phase 211 用):
-`escaped` フラグLoopBodyLocal の if 分岐)
-`buf` バッファ構築ArrayBox.append
-`escape_char()` 詳細処理Phase 211 では省略 → "X" で代用)
- ❌ StringBox.to_string()(単純化のため最終 return は buf のまま)
**複雑さの軸**:
- **A軸 (更新)**: `i = i + 1` Simple+ `escaped` フラグ切り替えIfPHI 必要)
- **B軸 (脱出)**: `break` Pattern 2 Break
- **C軸 (条件)**: `ch == quote and escaped == 0` Multi-condition
- **D軸 (変数)**: `i`, `escaped`, `buf` 3 carriers
### 候補 B: selfhost if-sum パターン
**元の仕様** (Phase 181 より):
```hako
// FuncScannerBox._sum_def_count() の簡略版
_sum_def_count(defs) {
local sum = 0
local i = 0
loop(i < defs.len()) {
local item = defs.get(i)
if item != null {
sum = sum + 1 // 条件付き加算
}
i = i + 1
}
return sum
}
```
**複雑さの軸**:
- **A軸 (更新)**: `sum = sum + 1` (条件内)+ `i = i + 1` (無条件)
- **B軸 (脱出)**: なし(自然終了)
- **C軸 (条件)**: `item != null` Simple
- **D軸 (変数)**: `sum`, `i` 2 carriers
---
## Task 211-2: Pattern/Boxes マッピング(候補ごと)
### 候補 A マッピング: `_parse_string` 簡略版
| 軸 | 要求 | 既存 Pattern/Box | Phase 210 時点の対応状況 |
|---|-----|----------------|----------------------|
| **A軸** | `i = i + 1` + `escaped` フラグ | Pattern 2 + IfPHI | ✅ Phase 210 で multi-carrier 確認済み |
| **B軸** | `break` | Pattern 2 Break | ✅ Phase 210 で動作確認済み |
| **C軸** | `ch == quote and escaped == 0` | ConditionLowerer + Multi-condition | ✅ Phase 169 で `and` 対応済み |
| **D軸** | 3 carriers (`i`, `escaped`, `buf`) | CarrierInfo + Multi-carrier | ✅ Phase 210 で 2-carrier 確認済み3-carrier は未テスト) |
**特殊要素**:
- **LoopBodyLocal**: `escaped` はループ内 if 分岐で更新される「状態フラグ」
- Phase 171 Trim Pattern では「ループ末尾で代入→Carrier 昇格」だったが、今回は **「if 分岐内で更新→PHI 必要」**
- 既存 IfPHI ロジックPhase 61で対応可能か要検証
- **Buffer 構築**: `buf.append(ch)` は BoxCall だが、JoinIR では BoxCall は Opaque 扱い
- Phase 210 で BoxCall 自体は問題なし(既存パターンで動作)
**Phase 211 での設計焦点**:
1. `escaped` フラグを Carrier として扱うか、LoopBodyLocal+IfPHI で扱うか
2. 3-carrier (i, escaped, buf) の PHI 配線が既存ロジックで通るか
### 候補 B マッピング: selfhost if-sum パターン
| 軸 | 要求 | 既存 Pattern/Box | Phase 210 時点の対応状況 |
|---|-----|----------------|----------------------|
| **A軸** | `sum = sum + 1` (条件内) + `i = i + 1` | Pattern 1 + IfPHI | ✅ IfPHI は Phase 61 で実装済み |
| **B軸** | なし(自然終了) | Pattern 1 Simple | ✅ Phase 210 で確認済み |
| **C軸** | `item != null` | ConditionLowerer | ✅ 比較演算子対応済み |
| **D軸** | 2 carriers (`sum`, `i`) | CarrierInfo | ✅ Phase 210 で動作確認済み |
**特殊要素**:
- **条件付き更新**: `sum = sum + 1` が if ブロック内
- Phase 61 IfPHI で対応可能(ループ内 if は Merge 経由で Carrier に PHI 接続)
**Phase 211 での設計焦点**:
1. ループ内 if の `sum` 更新が IfPHI → Loop Header PHI に正しく接続されるか確認
---
## Task 211-3: 推奨候補の選定と組み合わせ戦略
### 🎯 推奨: 候補 B (`selfhost if-sum`) を Phase 211 で選定
**理由**:
1. **既存 boxes で完全カバー可能**
- Pattern 1 Simple + IfPHI + Multi-carrierすべて Phase 210 で動作確認済み)
- 新規要素: 「ループ内 if の条件付き更新」のみ
2. **検証価値が高い**
- Phase 61 IfPHI が「ループ内 if」でも正しく動作するか実戦確認
- selfhost 実用パターン(`_sum_def_count` 等)の代表例
3. **Phase 212 実装が軽量**
- ハーネス作成が簡単ArrayBox.get + null チェック)
- デバッグが容易(条件分岐 1 箇所のみ)
**候補 A を Phase 212 以降に回す理由**:
- 3-carrier は Phase 210 で未テスト2-carrier までしか確認していない)
- `escaped` フラグの LoopBodyLocal+IfPHI 処理が複雑
- Phase 211 で「ループ内 if 更新」を先に確認してから、Phase 212+ で 3-carrier に進む方が安全
---
## Task 211-4: Boxes 組み合わせ設計(候補 B: if-sum
### 使用する既存 Boxes
| Box 名 | 役割 | Phase 210 確認状況 |
|-------|-----|------------------|
| **LoopPatternRouter** | Pattern 1 ルーティング | ✅ Phase 210 で動作確認 |
| **SimpleWhileMinimal** | Pattern 1 lowering | ✅ Phase 210 で動作確認 |
| **ConditionLowerer** | `item != null` → JoinIR | ✅ Phase 169/210 で確認 |
| **CarrierInfo** | `sum`, `i` の metadata 管理 | ✅ Phase 210 で確認 |
| **IfPhiContext** | ループ内 if の PHI 生成 | ⚠️ Phase 61 実装済みだが、ループ内 if での実戦は未確認 |
| **JoinValueSpace** | ValueId 割り当て | ✅ Phase 210 で region 分離確認 |
### 処理フロー設計Phase 212 実装時の想定)
```
1. LoopPatternRouter が Pattern 1 を検出
2. SimpleWhileMinimal が呼び出される
3. CarrierInfo が `sum`, `i` を carrier として登録
4. Loop Header PHI 生成:
- PHI(sum): entry=0, back_edge=sum_updated
- PHI(i): entry=0, back_edge=i_updated
5. ConditionLowerer が `i < defs.len()` を JoinIR に変換
6. ループ本体:
- `local item = defs.get(i)` → JoinIR BoxCall (Opaque)
- `if item != null { ... }` → IfPhiContext 起動
6a. IfPhiContext が if ブロック内の `sum = sum + 1` を処理
- then ブロック: sum_updated = sum_current + 1
- else ブロック: sum_updated = sum_current (変更なし)
- Merge 点: PHI(sum_updated) ← [then: sum+1, else: sum]
- `i = i + 1` → 無条件更新
7. Loop Back Edge:
- sum_updated → Header PHI(sum) の back_edge
- i_updated → Header PHI(i) の back_edge
8. Exit PHI:
- PHI(sum_final): loop_exit ← Header PHI(sum)
- PHI(i_final): loop_exit ← Header PHI(i)
```
### 重要な設計ポイント
**IfPhiContext の責務**:
- ループ内 if の **Merge 点で PHI 生成** → この PHI が Loop Header PHI の back_edge に接続される
- Phase 61 実装時は「ループ外 if」を想定していたが、**ループ内 if でも同じロジックが適用できる** はず
**検証ポイントPhase 212 で確認)**:
1. IfPhiContext がループ内 if を正しく検出するか
2. Merge PHI が Header PHI の back_edge に正しく接続されるか
3. `sum` の ValueId が Param region (100-999) に割り当てられるかPhase 201/205 要件)
---
## Task 211-5: Phase 212+ 実装スコープ定義
### Phase 212: if-sum ハーネス実装・実行
**スコープ**:
-`apps/tests/phase212_if_sum_min.hako` 作成
- ✅ 実行 → 観測Phase 210 と同じ Fail-Fast 戦略)
- ✅ IfPhiContext のループ内 if 動作確認
- ✅ phase212-if-sum-observation.md にログ記録
**期待される成果**:
- ループ内 if の条件付き更新が正しく動作
- IfPhiContext → Header PHI 接続が正常
-**「ループ内 if + multi-carrier」パターンが実戦確認済み** になる
### Phase 213+: 段階的拡張(候補 A 等)
**Phase 213**: 3-carrier テスト(`_parse_string` 簡略版の前段階)
- 候補: `i`, `sum`, `count` の 3-carrier ループ(ダミー処理)
- 目的: 3-carrier の PHI 配線が既存ロジックで通るか確認
**Phase 214**: `_parse_string` 簡略版(`escaped` フラグ + `buf` バッファ)
- 候補 A の実装
- 条件: Phase 213 で 3-carrier が成功していること
**Phase 215+**: 残りの JsonParser ループPhase 181 inventory より)
- `_read_array`, `_read_object` 等の再帰呼び出しパターン
- `_parse_hex` 等の特殊処理
---
## 📊 Phase 211 の成果物(このドキュメント)
### ✅ 達成したこと
1. **候補選定**: 候補 B (`selfhost if-sum`) を Phase 212 実装対象に選定
2. **Pattern/Boxes マッピング**: 既存 boxes で完全カバー可能と確認
3. **組み合わせ戦略**: IfPhiContext → Header PHI 接続フローを設計
4. **Phase 212+ スコープ**: 段階的拡張計画を定義
### 🎯 Phase 212 への引き継ぎ事項
- **実装対象**: `apps/tests/phase212_if_sum_min.hako`(条件付き加算ループ)
- **検証ポイント**: IfPhiContext のループ内 if 動作、Header PHI 接続
- **期待結果**: Phase 210 同様の完全成功Fail-Fast トリガーなし)
---
## 📝 補足: Phase 210 との差分
| 項目 | Phase 210 | Phase 211 |
|-----|----------|----------|
| **複雑さ** | 軽量Pattern 1/2 基本形) | 中規模(ループ内 if 更新) |
| **新規要素** | なし(既存確認のみ) | IfPhiContext のループ内適用 |
| **Carrier 数** | 2 まで確認 | 2Phase 213 で 3 に拡張予定) |
| **アプローチ** | 実戦観測 | 設計のみPhase 212 で実装) |
---
**Phase 211 完了条件**: ✅ このドキュメントの作成完了
**次のステップ**: Phase 212if-sum ハーネス実装・実行)

View File

@ -0,0 +1,426 @@
# Phase 212.5: ループ内 if → MIR 変換バグ修正(緊急ミニフェーズ)
**Phase**: 212.5
**Date**: 2025-12-09
**Status**: 🔧 In Progress
**Prerequisite**: Phase 212 完了(制約発見)
---
## 🎯 Phase 212.5 の目的
Phase 212 で発見した「ループ内 if/else が MIR に変換されない」問題を修正する。
**戦略**:
- JoinIR に触らないAST→MIR Builder だけを修正)
- 既存の If lowering 箱を再利用
- 最小限の変更で根治
---
## Task 212.5-1: 現状の AST / MIR を確認 ✅
### テストファイル
**`apps/tests/phase212_if_sum_min.hako`**:
```hako
static box IfSumTest {
sum_def_count(defs) {
local sum = 0
local i = 0
local len = 3
loop(i < len) {
// ← この if がループ本体に含まれるはず
if i > 0 {
sum = sum + 1 // ← 条件付き更新
}
i = i + 1
}
return sum
}
main() {
local result = IfSumTest.sum_def_count(0)
return result
}
}
```
### 期待される AST 構造
ループ本体の AST ノードには以下が含まれるはず:
```
Loop {
condition: BinaryOp(Lt, i, len),
body: Block [
// ← If ノードがここにあるはず
If {
condition: BinaryOp(Gt, i, 0),
then_block: Block [
Assignment(sum, BinOp(Add, sum, 1))
],
else_block: None
},
Assignment(i, BinOp(Add, i, 1))
]
}
```
### 実際の MIR 出力Before
```mir
define i64 @IfSumTest.sum_def_count/1(? %0) effects(read) {
bb1:
%2 = const 0 ; sum 初期化
%4 = const 0 ; i 初期化
br label bb3
bb2:
ret %2 ; return sum
bb3:
%7 = phi [%4, bb1], [%16, bb6] ; ← i の PHI のみ
br label bb4
bb4:
%12 = const 3
%13 = icmp Lt %7, %12
%14 = Not %13
br %14, label bb5, label bb6
bb5:
br label bb2
bb6:
; ← ここに if 由来の Compare / Branch が無い!
extern_call env.console.log(%7) [effects: pure|io]
%15 = const 1
%16 = %7 Add %15
%7 = copy %16
br label bb3
}
```
### 問題点の詳細
**欠落している MIR 命令**:
bb6 ループ本体ブロックには以下があるべき:
```mir
bb6:
; ← if i > 0 の条件チェック
%const_0 = const 0
%cond = icmp Gt %7, %const_0
br %cond, label bb_then, label bb_else
bb_then:
; sum = sum + 1
%sum_phi = phi [%2, bb3], [%sum_updated, bb_else] ; ← sum の PHI
%const_1 = const 1
%sum_updated = %sum_phi Add %const_1
br label bb_merge
bb_else:
br label bb_merge
bb_merge:
%sum_final = phi [%sum_updated, bb_then], [%sum_phi, bb_else]
; i = i + 1
%15 = const 1
%16 = %7 Add %15
br label bb3
```
**実際には**:
- if 由来の `Compare` / `Branch` が一切無い
- `sum` 変数に関する処理PHI・加算が完全に消失
### 仮説: どの層が壊しているか
#### ✅ Parser 層は OK
理由:
- Phase 212 で print を if 内に追加しても同じ結果
- Parser が if ードを落としているなら、syntax error になるはず
-**Parser は正しく AST を生成している可能性が高い**
#### ❌ LoopForm / control_flow builder が怪しい
**仮説 1**: ループ本体の AST ノードが **フラット化** されている
- `loop { stmt1; stmt2; }` の各 stmt を順次処理する際、
- `stmt``If` ノードの場合に **match していない** 可能性
**仮説 2**: ループ本体の `build_block()` が If を **スキップ** している
- `build_block()` が Statement を処理する際、
- `Statement::Expr(If)`**式として評価** せずに無視している可能性
**仮説 3**: If が **Dead Code Elimination (DCE)** で消えている
- `sum` の値が return で使われているから DCE で消えないはず
- でも念のため確認が必要
---
## Task 212.5-2: MIR Builder の責務位置を特定 ✅
### 確認したファイル
1.**`src/mir/builder/stmts.rs`** - Statement 処理
2.**`src/mir/builder/exprs.rs`** - Expression 処理
3.**`src/mir/builder/control_flow/mod.rs`** - cf_if(), cf_loop()
### 問題の根本原因を特定
#### 🚨 **発見した問題**
**`build_statement()` (stmts.rs:215-222)**:
```rust
pub(super) fn build_statement(&mut self, node: ASTNode) -> Result<ValueId, String> {
self.current_span = node.span();
match node {
// 将来ここに While / ForRange / Match / Using など statement 専用分岐を追加する。
other => self.build_expression(other), // ← すべて expression として処理
}
}
```
**問題点**:
- `ASTNode::If` のケースが **match に存在しない**
- すべての Statement が `other =>``build_expression()` に投げられる
- **If が式として評価される** → 値が使われない場合に最適化で消える可能性
#### If の処理フロー(現状)
```
build_statement(ASTNode::If)
match { other => build_expression(other) } ← If ケースなし
build_expression(ASTNode::If)
match { ASTNode::If { ... } => self.cf_if(...) } ← ここで処理
cf_if(condition, then_branch, else_branch)
lower_if_form(...) ← JoinIR ベースの PHI 生成
```
#### 新しい仮説
**仮説 1**: If が式として評価され、**値が使われない**ため DCE で消える
- ループ内の `if i > 0 { sum = sum + 1 }` は Statement として書かれている
- でも `build_statement()``build_expression()` に投げる
- `build_expression()` は ValueId を返すが、ループ本体では **その値を使わない**
- → 最適化 (DCE) で If ブロック全体が消える?
**仮説 2**: ループ本体の AST が JoinIR 経路で **フラット化** されている
- `cf_loop()``try_cf_loop_joinir()` の経路で
- ループ本体の AST ノードが別の形式に変換される際に If が消失
**仮説 3**: `lower_if_form()` がループ内 if を **スキップ** している
- `lower_if_form()` が「ループ外の if のみ対応」の可能性
- ループ内 if は別の処理が必要だが、その処理が未実装
### 次の調査対象
1. **DCE (Dead Code Elimination)** の動作確認
- If 式の戻り値が使われない場合に DCE で消えるか?
2. **`try_cf_loop_joinir()` の実装確認**
- ループ本体の AST がどう処理されているか
- If ノードが JoinIR 変換時に保持されているか
3. **`lower_if_form()` の実装確認**
- ループ内 if でも正しく動作するか
- ループコンテキストでの制約があるか
---
## Task 212.5-3: 小さな箱として if-lowering を足す 🔧
### 根本原因の確定
**問題**:
- `build_statement()``ASTNode::If`**expression 経路にだけ流していた**
- Statement としての If副作用のみが欲しいが expression として評価される
- → 値が使われないと最適化で消える
**対応方針**:
- **Option A** を採用: `build_statement()` に statement 用の If ケースを追加
- 既存の If lowering 箱 (`cf_if` / `lower_if_form`) を呼ぶだけ
### 設計方針
**原則**:
- 新規巨大箱は作らない
- 既存の If lowering 箱を再利用
- Statement と Expression の If を明確に分離
### 実装戦略Option A
#### 修正箇所: `src/mir/builder/stmts.rs`
**Before**:
```rust
pub(super) fn build_statement(&mut self, node: ASTNode) -> Result<ValueId, String> {
self.current_span = node.span();
match node {
// TODO: While / ForRange / Match / Using …
other => self.build_expression(other), // ← If も expression 扱い
}
}
```
**After**:
```rust
pub(super) fn build_statement(&mut self, node: ASTNode) -> Result<ValueId, String> {
self.current_span = node.span();
match node {
ASTNode::If { condition, then_body, else_body, .. } => {
// Statement としての If - 既存 If lowering を呼ぶ
self.build_if_statement(*condition, then_body, else_body)?;
// Statement なので値は使わないVoid を返す)
Ok(crate::mir::builder::emission::constant::emit_void(self))
}
// 将来: While / ForRange / Match / Using など
other => self.build_expression(other),
}
}
```
#### 新規関数: `build_if_statement()`
既存の If lowering を薄くラップする小さい箱:
```rust
/// Statement としての If 処理(副作用のみ)
///
/// ループ内 if や top-level statement if はここを通る。
/// Expression としての if値を使う場合は build_expression 経由。
pub(super) fn build_if_statement(
&mut self,
condition: ASTNode,
then_body: Vec<ASTNode>,
else_body: Option<Vec<ASTNode>>,
) -> Result<(), String> {
use crate::ast::Span;
// then_body と else_body を ASTNode::Program に変換
let then_node = ASTNode::Program {
statements: then_body,
span: Span::unknown(),
};
let else_node = else_body.map(|b| ASTNode::Program {
statements: b,
span: Span::unknown(),
});
// 既存の If lowering を呼ぶcf_if は lower_if_form を呼ぶ)
self.cf_if(condition, then_node, else_node)?;
Ok(())
}
```
### Expression vs Statement の分離
**Expression としての If** (既存のまま):
```hako
local x = if cond { 1 } else { 2 } // ← 値を使う
```
`build_expression()` 経由で処理
**Statement としての If** (今回追加):
```hako
if i > 0 { sum = sum + 1 } // ← 副作用のみ
```
`build_statement()` 経由で処理
### 重要なポイント
1. **JoinIR 側には触らない**
- 今は素の MIR だけ直す
- JoinIR Pattern3 (IfPHI) は Phase 212.5 完了後に使う
2. **既存 If lowering を再利用**
- `cf_if()``lower_if_form()` の既存パスをそのまま使う
- **ループ内 if も top-level if と同じ構造**(特別扱いしない)
3. **1 箇所だけで修正**
- `build_statement()` に If ケースを追加するだけ
- 複数箇所で同じことをしないDRY 原則)
---
## Task 212.5-4: phase212_if_sum_min.hako で再検証 🧪
### 検証手順
#### Step 1: 素の MIR ダンプ確認
```bash
./target/release/hakorune --dump-mir apps/tests/phase212_if_sum_min.hako 2>&1 | grep -A 50 "sum_def_count"
```
**期待される MIR**:
- ループ body 内に:
-`Compare` 命令: `%cond = icmp Gt %i, 0`
-`Branch` 命令: `br %cond, label bb_then, label bb_else`
- ✅ then ブロック: `sum = sum + 1` 相当の BinOp
- ✅ PHI 命令: `sum` の merge PHI
#### Step 2: JoinIR 経由の E2E テスト
```bash
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase212_if_sum_min.hako
```
**期待される結果**:
- RC: **2** (i=1, i=2 で sum が increment されるため)
- Pattern 3 (IfPHI) または Pattern 1 + IfPHI が選ばれる
- Carrier: `i``sum` の 2 つ
---
## Task 212.5-5: ドキュメント & CURRENT_TASK の更新 📝
### Before/After まとめ
**Before** (Phase 212 時点):
- ループ内 if が MIR に現れない
- `sum` 変数が carrier として認識されない
- RC: 0 (期待: 2)
**After** (Phase 212.5 完了後):
- ループ内 if が正常に MIR に変換される
- `sum``i` の 2-carrier が正常動作
- RC: 2 (正常)
### CURRENT_TASK.md 更新内容
```markdown
- [x] **Phase 212.5: ループ内 if の AST→MIR 修正** ✅ (完了: 2025-12-09)
- **目的**: Phase 212 で発見した「ループ内 if が MIR に変換されない」問題を修正
- **修正箇所**: [ファイル名・関数名]
- **修正内容**: [具体的な変更内容]
- **検証結果**: phase212_if_sum_min.hako で RC=2 を確認
- **Phase 212 BLOCKED 解消**: ループ内 if の根本問題を解決
```
---
## 📊 Phase 212.5 の進捗
- [x] Task 212.5-1: 現状確認・設計メモ作成 ✅
- [ ] Task 212.5-2: MIR Builder 責務位置特定
- [ ] Task 212.5-3: if-lowering 追加
- [ ] Task 212.5-4: 再検証
- [ ] Task 212.5-5: ドキュメント更新
**次のステップ**: Task 212.5-2ファイル読み込み・責務特定

View File

@ -0,0 +1,257 @@
# Phase 212: if-sum ミニ実装 & 実行フェーズ - 観測レポート
**Phase**: 212
**Date**: 2025-12-09
**Status**: ⚠️ **BLOCKED** - AST→MIR 変換層の制約発見
**Prerequisite**: Phase 211 完了(設計フェーズ)
---
## 🎯 Phase 212 の目的
Phase 211 で設計した「if-sum パターン」(ループ内 if での条件付き更新)を、既存 JoinIR インフラP1+P3+multi-carrierだけで実際に動かす。
**戦略**: Fail-Fast - 問題が出たら「どこで止まったか」を記録するところまでに留める。
---
## Task 212-1: .hako テスト関数の追加 ✅
**ファイル**: `apps/tests/phase212_if_sum_min.hako`
**初期実装**:
```hako
static box IfSumTest {
sum_def_count(defs) {
local sum = 0
local i = 0
local len = 3
loop(i < len) {
if i > 0 {
sum = sum + 1 // ← 条件付き更新
}
i = i + 1
}
return sum
}
main() {
local result = IfSumTest.sum_def_count(0)
return result
}
}
```
**期待結果**: RC=2 (i=1, i=2 で sum が increment されるため)
---
## Task 212-2: ルーティング条件の確認 ✅
**確認内容**:
- `loop_pattern_router.rs` (Phase 194) は構造ベースで Pattern 1-4 を自動分類
- `loop_pattern_detection::classify()` が CFG 構造から Pattern 判定
- → 名前ベースの whitelist は不要(既存ロジックで対応可能)
**結論**: 構造ベースルーティングで自動的に Pattern が選ばれるはず
---
## Task 212-3: JoinIR 経路で E2E 実行 ⚠️
### 実行コマンド
```bash
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase212_if_sum_min.hako
```
### 実行結果
```
[joinir/pattern1] Generated JoinIR for Simple While Pattern
[joinir/pattern1] Functions: main, loop_step, k_exit
...
[DEBUG-177] Phase 33-21: carrier_phis count: 1, names: ["i"]
...
RC: 0
```
**期待**: RC=2
**実際**: RC=0
### Pattern ルーティング観測
- **選ばれた Pattern**: **Pattern 1 (Simple While)**
- **Carrier 数**: **1 つのみ** (`i`)
- **欠落している Carrier**: `sum` が carrier として認識されていない
### MIR ダンプ分析
```bash
./target/release/hakorune --dump-mir apps/tests/phase212_if_sum_min.hako
```
**MIR 出力** (`IfSumTest.sum_def_count/1`):
```mir
define i64 @IfSumTest.sum_def_count/1(? %0) effects(read) {
bb1:
%2 = const 0 ; ← sum 初期化
%4 = const 0 ; ← i 初期化
br label bb3
bb2:
ret %2 ; ← return sum
bb3:
%7 = phi [%4, bb1], [%16, bb6] ; ← i の PHI のみ
br label bb4
bb4:
%12 = const 3
%13 = icmp Lt %7, %12
%14 = Not %13
br %14, label bb5, label bb6
bb5:
br label bb2
bb6:
extern_call env.console.log(%7) [effects: pure|io] ; ← print(i)
%15 = const 1
%16 = %7 Add %15 ; ← i = i + 1
%7 = copy %16
br label bb3
}
```
### 🚨 **重大な発見**
#### 現象
**ループ内 if/else ブロックが MIR に存在しない!**
- `.hako` ソースコードには `if i > 0 { sum = sum + 1 }` が書いてあるのに、
- MIR には bb6 ブロックに `print(i)``i = i + 1` しかない
- `sum` 変数に関する処理条件分岐・加算・PHI**完全に消失**
#### print 追加による検証
if ブロックが DCE で消えている可能性を考え、if 内に `print(sum)` を追加:
```hako
if i > 0 {
sum = sum + 1
print(sum) // ← Force if to stay in MIR
} else {
print(0) // ← Ensure else branch exists
}
```
**結果**: MIR は変わらず。if/else ブロック自体が MIR に現れない。
---
## Task 212-4: 観測結果の記録 ✅
### ✅ 成功した部分
1. **Pattern Routing 動作**: Pattern 1 が構造ベースで正しく選ばれた
2. **JoinIR 生成**: Pattern 1 lowerer が動作し、JoinIR 関数 (main/loop_step/k_exit) を生成
3. **Carrier 処理**: `i` carrier の PHI 配線は正常
### ❌ 失敗した部分
**Root Cause**: **AST → MIR 変換層でループ内 if/else が消失**
#### 発見した制約
| 項目 | 内容 |
|-----|-----|
| **制約層** | AST → MIR 変換Parser or MIR Builder |
| **現象** | ループ内 if/else の代入文が MIR に変換されない |
| **影響範囲** | JoinIR Pattern 3 (IfPHI) が動作する以前の問題 |
| **エラーメッセージ** | なしsilent failure |
| **再現性** | 100%print 追加でも変わらず) |
#### 詳細分析
**予想される原因**:
1. **Parser の制限**:
- ループ本体の if/else が正しく AST に変換されていない可能性
- AST ノードが生成されても、型やスコープ情報が不完全
2. **MIR Builder の制限**:
- `build_block()` がループ本体の if を処理する際に、条件付き代入を無視
- ループ内 if の Merge PHI 生成ロジックが未実装
3. **変数スコープ問題**:
- `sum` がループ外で `local` 宣言されているが、ループ内 if での更新が「新しい定義」として認識されない
- ループ内変数更新が SSA 形式に変換されない
**確認が必要な層**:
- `src/mir/builder/control_flow/if_form.rs` - if 式の MIR 変換ロジック
- `src/mir/builder/control_flow/loop_form.rs` - ループ本体の処理
- `src/mir/builder/build_block.rs` - ブロック構築ロジック
- `src/parser/` - AST 生成の正確性
### JoinIR インフラへの影響
**Phase 212 の結論**:
- ✅ JoinIR Pattern Routing 自体は正常動作
- ✅ Pattern 1 (Simple While) の carrier 処理は完璧
-**ループ内 if の AST→MIR 変換が Phase 212 のブロッカー**
**Phase 213 への影響**:
- Phase 213 (3-carrier テスト) も同じ問題に遭遇する可能性が高い
- **先に AST→MIR 層の修正が必要**
---
## 📊 Phase 212 Overall Evaluation
### 成果
1. **Fail-Fast 成功**: Phase 211 の設計段階では見えなかった制約を 1 回の実行で発見
2. **制約の層を特定**: JoinIR ではなく **AST→MIR 変換層** の問題と判明
3. **再現性確認**: MIR ダンプで問題を可視化・記録
### 次のステップ
**Phase 212.5 (緊急対応)**: AST→MIR ループ内 if 変換の調査・修正
**調査項目**:
1. ループ内 if の AST ノードが正しく生成されているか確認
2. `build_block()` がループ本体の if をどう処理しているか追跡
3. ループ内変数更新の SSA 変換ロジックを確認
**実装方針**:
- Phase 212 は「観測フェーズ」として完了
- Phase 212.5 で AST→MIR 修正(別タスク)
- Phase 213 以降は Phase 212.5 完了後に再開
---
## 📝 参考情報
### 関連ドキュメント
- Phase 211 設計: `docs/development/current/main/phase211-loop-candidate-selection.md`
- JoinIR アーキテクチャ: `docs/development/current/main/joinir-architecture-overview.md`
- Pattern Routing: `src/mir/join_ir/lowering/loop_pattern_router.rs`
### 関連コード
- テストファイル: `apps/tests/phase212_if_sum_min.hako`
- Pattern 1 Lowerer: `src/mir/join_ir/lowering/loop_patterns/simple_while.rs`
- Pattern 3 Lowerer: `src/mir/join_ir/lowering/loop_patterns/with_if_phi.rs`
---
**Phase 212 ステータス**: ⚠️ BLOCKEDAST→MIR 層の制約により中断)
**次のアクション**: Phase 212.5AST→MIR ループ内 if 修正)

View File

@ -0,0 +1,411 @@
# Phase 213: Pattern3 Lowerer 汎用化if-sum minimal
**Phase**: 213
**Date**: 2025-12-09
**Status**: 🚧 In Progress
**Prerequisite**: Phase 212.5 完了(構造ベース if 検出 + Pattern 3 routing
---
## 🎯 Phase 213 の目的
Phase 212.5 で正しく Pattern 3 にルーティングされるようになった `phase212_if_sum_min.hako` を、JoinIR Pattern 3If-PHIで正しく実行できるようにする。
**問題**: 現在の Pattern 3 lowerer は **test-only PoC 実装**
- Loop condition: `i <= 5` (hardcoded)
- If condition: `i % 2 == 1` (hardcoded)
- Update logic: `sum + i` (hardcoded)
**目標**: AST-based 汎用 Pattern 3 lowerer の実装
- LoopUpdateSummary / CarrierInfo / BoolExprLowerer ベースの汎用実装
- `phase212_if_sum_min.hako` で RC=2 達成
- 既存パターン(`loop_if_phi.hako` 等)の後方互換維持
---
## 📋 現状の Pattern 3 実装の問題点
### 1. ハードコードされた条件・更新式
**Loop condition** (`loop_with_if_phi_minimal.rs`):
```rust
// Hardcoded: i <= 5
let loop_cond_value = /* ... */;
```
**If condition**:
```rust
// Hardcoded: i % 2 == 1
let if_cond = /* modulo operation */;
```
**Update expressions**:
```rust
// Hardcoded: sum = sum + i, count = count + 1
let sum_update = /* sum + i */;
let count_update = /* count + 1 */;
```
### 2. テスト専用の ValueId マッピング
```rust
const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(24);
const PATTERN3_K_EXIT_COUNT_FINAL_ID: ValueId = ValueId(25);
```
これらは特定のテストケース用に固定されており、異なる carrier 構成には対応できない。
### 3. 汎用性の欠如
- `phase212_if_sum_min.hako` のような実際の if-sum パターンが動かない
- Carrier 構成が変わると動作しない
- If 条件が変わると対応できない
---
## 🏗️ 新しい入力情報アーキテクチャ
### 入力: PatternPipelineContext
Phase 213 では、以下の情報を利用して汎用的な lowering を実現:
**1. LoopFeatures** (from pattern_pipeline.rs)
- `has_if`: Loop body に if 文が存在するか
- `has_if_else_phi`: PHI merge が必要な if-else か
- `carrier_count`: Carrier 変数の数
**2. CarrierInfo**
- Carrier 変数のリスト名前、host_id、join_id
- 各 carrier の UpdateKindCounterLike, AccumulationLike, etc.
**3. LoopUpdateSummary**
```rust
pub struct LoopUpdateSummary {
pub updates: Vec<CarrierUpdateInfo>, // 各 carrier の更新情報
}
pub struct CarrierUpdateInfo {
pub carrier_name: String,
pub update_kind: UpdateKind,
pub then_expr: Option<ASTNode>, // then branch update
pub else_expr: Option<ASTNode>, // else branch update
}
```
**4. BoolExprLowerer / condition_to_joinir**
- 任意の bool 条件を JoinIR に変換
- 既存の `condition_to_joinir()` 関数を活用
**5. ConditionEnv / JoinValueSpace**
- Variable → ValueId マッピング
- ValueId allocation 管理
---
## 🔄 目標となる変換フロー
### Phase 213 汎用 Lowering Pipeline
```
Input: PatternPipelineContext
├─ loop_condition: ASTNode (e.g., "i < 3")
├─ loop_body: Vec<ASTNode> (contains if statement)
├─ CarrierInfo (e.g., [i, sum])
└─ LoopUpdateSummary (e.g., sum: then=sum+1, else=sum+0)
Step 1: Loop Condition Lowering
loop_condition AST → BoolExprLowerer
→ JoinIR loop_cond: ValueId
Step 2: Extract If Statement from Loop Body
Find ASTNode::If in loop_body
→ if_condition: ASTNode (e.g., "i > 0")
→ then_body: Vec<ASTNode>
→ else_body: Option<Vec<ASTNode>>
Step 3: If Condition Lowering
if_condition AST → BoolExprLowerer
→ JoinIR if_cond: ValueId
Step 4: Carrier Update Lowering (from LoopUpdateSummary)
For each carrier in CarrierInfo:
- Get then_expr from LoopUpdateSummary
- Get else_expr from LoopUpdateSummary
- Lower then_expr → JoinIR then_value: ValueId
- Lower else_expr → JoinIR else_value: ValueId
- Generate PHI: carrier_new = phi [then_value, else_value]
Step 5: JoinIR Function Generation
- entry(): Initialize carriers
- loop_step(i, carrier1, carrier2, ...):
if if_cond:
then_branch → update carriers (then values)
else:
else_branch → update carriers (else values)
PHI merge → carrier_new values
next iteration or exit
- k_exit(carrier1_final, carrier2_final, ...): Return final values
Step 6: ExitMeta Construction
ExitMeta {
carriers: [
{ name: "sum", join_id: ValueId(X), host_slot: ValueId(Y) },
...
]
}
Output: (JoinModule, ExitMeta)
```
---
## 🚨 Fail-Fast ポリシー
### 対応外パターンの明示的エラー
Pattern 3 lowerer は以下の場合に **明示的にエラー**を返す:
**1. LoopUpdateSummary 不整合**
```rust
if carrier.then_expr.is_none() || carrier.else_expr.is_none() {
return Err(JoinIrError::UnsupportedPattern {
reason: format!("Carrier '{}' missing then/else update", carrier.name)
});
}
```
**2. UpdateKind 未対応**
```rust
match carrier.update_kind {
UpdateKind::Complex | UpdateKind::Unknown => {
return Err(JoinIrError::UnsupportedPattern {
reason: format!("Carrier '{}' has unsupported UpdateKind: {:?}",
carrier.name, carrier.update_kind)
});
}
_ => { /* OK */ }
}
```
**3. If 構造不整合**
```rust
if loop_body.iter().filter(|n| matches!(n, ASTNode::If { .. })).count() != 1 {
return Err(JoinIrError::UnsupportedPattern {
reason: "Pattern 3 requires exactly one if statement in loop body".to_string()
});
}
```
**禁止事項**:
- ❌ Silent fallback to Pattern 1
- ❌ Default values for missing updates
- ❌ Ignoring unsupported UpdateKind
**原則**: **すべての制約は明示的エラーで通知**Fail-Fast
---
## 📐 設計の核心アイデア
### 1. 入力を「箱」として分離
**現状**: ハードコードされた値が scattered
**Phase 213**: 入力情報を構造化された箱から取得
```rust
// Before (Phase 195)
const LOOP_BOUND: i64 = 5; // Hardcoded
const IF_MODULO: i64 = 2; // Hardcoded
// After (Phase 213)
let loop_cond = ctx.loop_condition; // From PatternPipelineContext
let if_cond = extract_if_condition(&ctx.loop_body)?; // From AST
let updates = ctx.loop_update_summary; // From LoopUpdateSummary
```
### 2. Lowering を既存箱に委譲
**BoolExprLowerer**: Bool condition → JoinIR
```rust
let loop_cond_value = condition_to_joinir(
loop_cond,
&condition_env,
&mut join_value_space
)?;
```
**CarrierUpdateEmitter**: Update expression → JoinIR
```rust
let then_value = emit_carrier_update_with_env(
carrier.then_expr,
&update_env,
&mut join_value_space
)?;
```
### 3. ExitMeta で複数 Carrier を統一的に扱う
**現状**: 固定 ValueId の const 定義
**Phase 213**: ExitMeta に動的登録
```rust
// Before
exit_bindings.push(LoopExitBinding {
carrier_name: "sum".to_string(),
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // Hardcoded!
host_slot: sum_var_id,
});
// After
for carrier in carrier_info.carriers.iter() {
exit_bindings.push(LoopExitBinding {
carrier_name: carrier.name.clone(),
join_exit_value: carrier.join_final_id, // From JoinIR generation
host_slot: carrier.host_id,
});
}
```
---
## 🔧 実装の構造
### Target Files
**1. JoinIR Lowerer**
- `src/mir/join_ir/lowering/loop_with_if_phi_minimal.rs`
- **変更内容**:
- ハードコード削除
- PatternPipelineContext からの入力受け取り
- BoolExprLowerer / CarrierUpdateEmitter への委譲
**2. Pattern 3 Entry Point**
- `src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs`
- **変更内容**:
- PatternPipelineContext の構築
- ExitMeta の動的構築
- Fail-Fast エラーハンドリング
### Signature Changes
**Before (Phase 195)**:
```rust
pub fn lower_loop_with_if_phi_pattern(
scope: LoopScopeShape,
join_value_space: &mut JoinValueSpace,
) -> Option<JoinModule>
```
**After (Phase 213)**:
```rust
pub fn lower_loop_with_if_phi_pattern(
ctx: &PatternPipelineContext,
join_value_space: &mut JoinValueSpace,
) -> Result<(JoinModule, ExitMeta), JoinIrError>
```
**変更点**:
1. 入力: `LoopScopeShape``PatternPipelineContext`
2. 戻り値: `Option<JoinModule>``Result<(JoinModule, ExitMeta), JoinIrError>`
3. ExitMeta を返して動的 exit binding を可能に
---
## ✅ 検証計画
### Test Case 1: phase212_if_sum_min.hako主目標
**Input**:
```nyash
loop(i < 3) {
if i > 0 {
sum = sum + 1
}
i = i + 1
}
```
**Expected**:
- RC: **2**
- Pattern: Pattern 3 (If-Else PHI)
- Carriers: `i` (CounterLike), `sum` (AccumulationLike)
- Trace: `[joinir/pattern3] Generated JoinIR for Loop with If-Else PHI`
### Test Case 2: loop_if_phi.hako後方互換
既存の Phase 195 テストケース:
```nyash
loop(i < 5) {
if i % 2 == 1 {
sum = sum + i
} else {
sum = sum + 0
}
i = i + 1
}
```
**Expected**:
- 既存と同じ出力・RC
- Regression なし
### Test Case 3: Multi-carrier Phase 195 tests
Phase 195 で追加された multi-carrier tests:
- sum + count の 2-carrier
- sum + count + index の 3-carrier (if exists)
**Expected**:
- 既存と同じ挙動
- ExitMeta が複数 carrier を正しく処理
---
## 📊 Phase 213 タスクチェックリスト
- [ ] Task 213-1: 設計ドキュメント作成 ✅ (this file)
- [ ] Task 213-2: Pattern3 Lowerer 本体リファクタリング
- [ ] Step 2-1: ハードコード削除
- [ ] Step 2-2: 入力を Context ベースに変更
- [ ] Step 2-3: 条件 lowering を BoolExprLowerer に委譲
- [ ] Step 2-4: キャリア更新の一般化
- [ ] Step 2-5: PHI 生成
- [ ] Step 2-6: 戻り値と boundary 連携
- [ ] Task 213-3: Fail-Fast 条件の明確化
- [ ] Task 213-4: テスト & 検証
- [ ] phase212_if_sum_min.hako → RC=2
- [ ] loop_if_phi.hako → Regression check
- [ ] Multi-carrier tests → Regression check
- [ ] Task 213-5: ドキュメント更新
- [ ] phase212-if-sum-impl.md
- [ ] joinir-architecture-overview.md
- [ ] CURRENT_TASK.md
---
## 🎯 Success Criteria
**Phase 213 is complete when**:
1.`phase212_if_sum_min.hako` produces RC=2
2. ✅ All existing Pattern 3 tests pass (no regression)
3. ✅ No hardcoded conditions/updates in Pattern 3 lowerer
4. ✅ Fail-Fast errors for unsupported patterns
5. ✅ Documentation updated (3 files)
**Commit message format**:
```
feat(joinir): Phase 213 Pattern3 AST-based generalization
Phase 213 で Pattern3 lowerer を AST-based 汎用実装に書き換え。
phase212_if_sum_min.hako が RC=2 で正常動作。
- Removed hardcoded conditions/updates
- Integrated BoolExprLowerer for dynamic condition lowering
- Generalized carrier update via LoopUpdateSummary
- Dynamic ExitMeta construction for multi-carrier support
- Fail-Fast for unsupported patterns
```
---
**Phase 213: READY TO START** 🚀

View File

@ -122,6 +122,8 @@
### 2.1 `NYASH_JOINIR_CORE`
> 2025-12 現在: JoinIR は常時 ON。`NYASH_JOINIR_CORE` は警告のみで無視されるLoopBuilder 削除済み、config/env で no-op
**使用箇所総数**: 9箇所
#### カテゴリ別内訳