fix(joinir): Phase 177-3 ValueId collision fix for multi-carrier loops
Root cause: JoinIR ValueId collision between function parameters and condition bindings - Same ValueId used for both `result_init` (carrier param) and `limit` (condition var) - Phase 33-21 was overwriting condition bindings when remapping carrier PHIs Fix implemented (Option B - immediate protection): 1. Phase 177-3: Protect condition-only variables from Phase 33-21 override - Collect condition_bindings that are NOT carriers (by checking exit_bindings) - Skip remapping for these protected ValueIds 2. Phase 177-3-B: Handle body-only carriers explicitly - Carriers that appear in condition_bindings (added by Phase 176-5) - Map them to correct PHI dsts by name lookup Investigation tools added: - [DEBUG-177] trace logs for remapper state tracking - Phase 177-3 protection logging - BoundaryInjector PHI collision detection Test results: - ✅ Integer multi-carrier test: Output 3 (expected) - ⚠️ String test: RC=0 but empty output (separate issue - string concat emit) Design docs created: - phase177-parse-string-design.md: _parse_string loop analysis - phase177-carrier-evolution.md: Carrier progression Phase 174-179 Next: Investigate string concatenation emit for full _parse_string support
This commit is contained in:
165
docs/development/current/main/phase177-carrier-evolution.md
Normal file
165
docs/development/current/main/phase177-carrier-evolution.md
Normal file
@ -0,0 +1,165 @@
|
||||
# Phase 177: Carrier Evolution - min から Production へ
|
||||
|
||||
## 視覚的比較: ループ構造の進化
|
||||
|
||||
### Phase 174: min(1-carrier)
|
||||
```
|
||||
Input: "hello world""
|
||||
^start ^target
|
||||
|
||||
loop(pos < len) {
|
||||
ch = s[pos]
|
||||
if ch == '"' → break ✓
|
||||
else → pos++
|
||||
}
|
||||
|
||||
Carriers: 1
|
||||
- pos (ループ変数)
|
||||
|
||||
Pattern: 4 (Loop + If PHI)
|
||||
Exit: pos = 11
|
||||
```
|
||||
|
||||
### Phase 175/176: min2(2-carrier)
|
||||
```
|
||||
Input: "hello world""
|
||||
^start ^target
|
||||
|
||||
loop(pos < len) {
|
||||
ch = s[pos]
|
||||
if ch == '"' → break ✓
|
||||
else → result += ch
|
||||
pos++
|
||||
}
|
||||
|
||||
Carriers: 2
|
||||
- pos (ループ変数)
|
||||
- result (バッファ)
|
||||
|
||||
Pattern: 4 (Loop + If PHI)
|
||||
Exit: pos = 11, result = "hello world"
|
||||
```
|
||||
|
||||
### Phase 177-A: Simple Case(2-carrier, Production-like)
|
||||
```
|
||||
Input: "hello world""
|
||||
^start ^target
|
||||
|
||||
loop(p < len) {
|
||||
ch = s[p]
|
||||
if ch == '"' → break ✓
|
||||
else → str += ch
|
||||
p++
|
||||
}
|
||||
|
||||
Carriers: 2
|
||||
- p (ループ変数)
|
||||
- str (バッファ)
|
||||
|
||||
Pattern: 4 (Loop + If PHI)
|
||||
Exit: p = 11, str = "hello world"
|
||||
```
|
||||
|
||||
### Phase 178: Escape Handling(2-carrier + continue)
|
||||
```
|
||||
Input: "hello \"world\"""
|
||||
^start ^escape ^target
|
||||
|
||||
loop(p < len) {
|
||||
ch = s[p]
|
||||
if ch == '"' → break ✓
|
||||
if ch == '\\' → str += ch
|
||||
p++
|
||||
str += s[p]
|
||||
p++
|
||||
continue ←← 新要素
|
||||
else → str += ch
|
||||
p++
|
||||
}
|
||||
|
||||
Carriers: 2 (変わらず)
|
||||
- p (ループ変数)
|
||||
- str (バッファ)
|
||||
|
||||
Pattern: 4? (continue 対応は未検証)
|
||||
Exit: p = 15, str = "hello \"world\""
|
||||
```
|
||||
|
||||
### Phase 179: Full Production(2-carrier + early return)
|
||||
```
|
||||
Input: "hello \"world\"""
|
||||
^start ^escape ^target
|
||||
|
||||
loop(p < len) {
|
||||
ch = s[p]
|
||||
if ch == '"' → return MapBox {...} ✓ ←← early return
|
||||
if ch == '\\' → if p+1 >= len → return null ←← error
|
||||
str += ch
|
||||
p++
|
||||
str += s[p]
|
||||
p++
|
||||
continue
|
||||
else → str += ch
|
||||
p++
|
||||
}
|
||||
return null ←← ループ終端エラー
|
||||
|
||||
Carriers: 2 (変わらず)
|
||||
- p (ループ変数)
|
||||
- str (バッファ)
|
||||
|
||||
Pattern: 4? (early return 対応は未検証)
|
||||
Exit: 正常: MapBox, 異常: null
|
||||
```
|
||||
|
||||
## Carrier 安定性分析
|
||||
|
||||
### 重要な発見
|
||||
**Phase 174 → 179 を通じて Carrier 数は安定(1 → 2)**
|
||||
|
||||
| Phase | Carriers | 新要素 | P5昇格候補 |
|
||||
|-------|----------|--------|-----------|
|
||||
| 174 | 1 (pos) | - | なし |
|
||||
| 175/176 | 2 (pos + result) | バッファ追加 | なし |
|
||||
| 177-A | 2 (p + str) | - | なし |
|
||||
| 178 | 2 (p + str) | continue | なし(確認中) |
|
||||
| 179 | 2 (p + str) | early return | なし(確認中) |
|
||||
|
||||
### P5昇格候補の不在
|
||||
**Trim と異なり、`is_ch_match` 相当は不要**
|
||||
|
||||
理由:
|
||||
- Trim: `is_ch_match` が「次も空白か?」を決定(**次の判断に影響**)
|
||||
- _parse_string: `ch == '"'` は「今終了か?」のみ(**次に影響しない**)
|
||||
|
||||
```
|
||||
Trim の制御フロー:
|
||||
is_ch_match = (ch == ' ')
|
||||
if is_ch_match → pos++ → 次も is_ch_match を評価 ←← 連鎖
|
||||
|
||||
_parse_string の制御フロー:
|
||||
if ch == '"' → break → 終了
|
||||
else → str += ch, p++ → 次は独立判断 ←← 連鎖なし
|
||||
```
|
||||
|
||||
## JoinIR Pattern 対応予測
|
||||
|
||||
| Phase | Pattern 候補 | 理由 |
|
||||
|-------|-------------|------|
|
||||
| 177-A | Pattern4 | Loop + If PHI + break(実装済み) |
|
||||
| 178 | Pattern4? | continue は Pattern4-with-continue(要実装確認) |
|
||||
| 179 | Pattern5? | early return は新パターン候補(要設計) |
|
||||
|
||||
## まとめ
|
||||
|
||||
### 段階的検証戦略
|
||||
1. **Phase 177-A**: min2 と同型 → P5 安定性確認
|
||||
2. **Phase 178**: continue 追加 → JoinIR 拡張必要性評価
|
||||
3. **Phase 179**: early return 追加 → Pattern5 設計判断
|
||||
|
||||
### Carrier 設計の教訓
|
||||
- **最小構成で開始**: 1-carrier (Phase 174)
|
||||
- **段階的拡張**: 2-carrier (Phase 175/176)
|
||||
- **Production 適用**: 構造は変えず、制御フローのみ追加(Phase 177+)
|
||||
|
||||
→ **「Carrier 数を固定して制御フローを段階的に複雑化」が正解**
|
||||
178
docs/development/current/main/phase177-parse-string-design.md
Normal file
178
docs/development/current/main/phase177-parse-string-design.md
Normal file
@ -0,0 +1,178 @@
|
||||
# Phase 177: _parse_string 本体 JoinIR 適用設計
|
||||
|
||||
## 目的
|
||||
Production `JsonParserBox._parse_string` メソッドに JoinIR P5 パイプラインを適用する。
|
||||
Phase 174/175/176 で確立した「1-carrier → 2-carrier」検証の成果を、実際の JSON パーサーに展開する。
|
||||
|
||||
## 本番ループ構造(lines 144-181)
|
||||
|
||||
### 前提条件
|
||||
- 開始位置 `pos` は `"` を指している(line 145 でチェック)
|
||||
- ループ開始時 `p = pos + 1`(引用符の次の文字から開始)
|
||||
|
||||
### ループ本体(lines 150-178)
|
||||
```hako
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
|
||||
if ch == '"' {
|
||||
// End of string (lines 153-160)
|
||||
local result = new MapBox()
|
||||
result.set("value", me._unescape_string(str))
|
||||
result.set("pos", p + 1)
|
||||
result.set("type", "string")
|
||||
return result // ← Early return (通常の exit)
|
||||
}
|
||||
|
||||
if ch == "\\" {
|
||||
// Escape sequence (lines 162-174)
|
||||
local has_next = 0
|
||||
if p + 1 < s.length() { has_next = 1 }
|
||||
|
||||
if has_next == 0 { return null } // ← Early return (エラー)
|
||||
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
str = str + s.substring(p, p+1)
|
||||
p = p + 1
|
||||
continue // ← ループ継続
|
||||
}
|
||||
|
||||
str = str + ch // 通常文字の追加
|
||||
p = p + 1
|
||||
}
|
||||
|
||||
return null // ← ループ終端に到達(エラー)
|
||||
```
|
||||
|
||||
### 制御フロー分岐
|
||||
1. **終端クォート検出** (`ch == '"'`): 成功 return
|
||||
2. **エスケープ検出** (`ch == "\\"`) + continue: 2文字消費してループ継続
|
||||
3. **通常文字**: バッファ蓄積 + 位置進行
|
||||
4. **ループ終端**: 引用符が閉じていない(エラー)
|
||||
|
||||
## Carriers 分析
|
||||
|
||||
| 変数 | 初期値 | 役割 | 更新式 | P5 昇格対象? | 備考 |
|
||||
|------|--------|------|--------|---------------|------|
|
||||
| `p` | `pos + 1` | カウンタ(位置) | `p = p + 1` | **N(ループ変数)** | 条件式に使用 |
|
||||
| `str` | `""` | バッファ(蓄積) | `str = str + ch` | **N(通常キャリア)** | エスケープ時2回更新 |
|
||||
| `has_next` | - | 一時変数(フラグ) | - | - | ループ内のみ有効 |
|
||||
| `is_escape` | - | (潜在的フラグ) | - | - | 明示的変数なし |
|
||||
|
||||
### 重要な発見
|
||||
- **エスケープ処理は continue 経由**: `p` と `str` を 2 回更新してから continue
|
||||
- **Early return が 2 箇所**: 成功 return (line 159) とエラー return (line 167)
|
||||
- **通常キャリアのみ**: P5 昇格対象(`is_ch_match` 相当)は**不要**
|
||||
|
||||
## min ケースとの差分
|
||||
|
||||
### 共通点
|
||||
| 項目 | min (Phase 174) | min2 (Phase 175) | 本番 (Phase 177) |
|
||||
|------|-----------------|------------------|------------------|
|
||||
| ループ変数 | `pos` | `pos` | `p` |
|
||||
| バッファ | なし | `result` | `str` |
|
||||
| 終端条件 | `ch == '"'` → break | `ch == '"'` → break | `ch == '"'` → return |
|
||||
| 通常処理 | `pos++` | `result += ch; pos++` | `str += ch; p++` |
|
||||
|
||||
### 差分
|
||||
| 項目 | min/min2 | 本番 |
|
||||
|------|----------|------|
|
||||
| 終端処理 | `break` のみ | Early `return` (MapBox 返却) |
|
||||
| エスケープ処理 | なし | `continue` を使った複雑な分岐 |
|
||||
| エラー処理 | なし | ループ内・外に `return null` |
|
||||
|
||||
## 方針決定: 段階的アプローチ
|
||||
|
||||
### Phase 177-A: Simple Case(今回)
|
||||
**対象**: エスケープ**なし**・終端クォート検出のみ
|
||||
- **ループ構造**: min2 と完全同型(`p` + `str` の 2-carrier)
|
||||
- **終端処理**: `break` → 直後に MapBox 構築(return 代替)
|
||||
- **目的**: P5 パイプラインが「2-carrier + break」で動作することを確認
|
||||
|
||||
```hako
|
||||
// Simplified for Phase 177-A
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == '"' {
|
||||
break // P5: Pattern4 対応(Loop+If PHI merge)
|
||||
} else {
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
}
|
||||
}
|
||||
// break 後: MapBox 構築
|
||||
```
|
||||
|
||||
### Phase 178: Escape Handling(次回)
|
||||
**追加要素**:
|
||||
- `continue` 分岐(エスケープ処理)
|
||||
- Early return(エラーハンドリング)
|
||||
- P5 昇格キャリア候補の検討(`is_escape` フラグ?)
|
||||
|
||||
### Phase 179: Full Production(最終)
|
||||
**統合**:
|
||||
- `_unescape_string()` 呼び出し
|
||||
- 完全なエラーハンドリング
|
||||
- 本番同等の MapBox 返却
|
||||
|
||||
## Phase 177-A 実装計画
|
||||
|
||||
### Test Case: `test_jsonparser_parse_string_simple.hako`
|
||||
```hako
|
||||
// Phase 177-A: Production-like simple case
|
||||
static box JsonParserStringTest3 {
|
||||
parse_string_simple() {
|
||||
local s = "hello world\""
|
||||
local p = 0 // pos + 1 相当(簡略化のため 0 から開始)
|
||||
local str = ""
|
||||
local len = s.length()
|
||||
|
||||
// 2-carrier loop (min2 と同型)
|
||||
loop(p < len) {
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == "\"" {
|
||||
break
|
||||
} else {
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Post-loop: 結果出力(MapBox 構築の代替)
|
||||
print("Parsed string: ")
|
||||
print(str)
|
||||
print(", final pos: ")
|
||||
print(p)
|
||||
}
|
||||
|
||||
main() {
|
||||
me.parse_string_simple()
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 期待される MIR 構造
|
||||
- **Pattern4 検出**: Loop + If PHI merge(Phase 170 実装済み)
|
||||
- **2 carriers**: `p` (ループ変数) + `str` (バッファ)
|
||||
- **Exit PHI**: ループ後の `p` と `str` が正しく伝播
|
||||
|
||||
### 検証項目
|
||||
1. ✅ P5 パイプライン通過(JoinIR → MIR)
|
||||
2. ✅ 2-carrier の正しい伝播(`p` と `str`)
|
||||
3. ✅ `break` 後の変数値が正しく使用可能
|
||||
|
||||
## 成功基準
|
||||
- [ ] `test_jsonparser_parse_string_simple.hako` が実行成功
|
||||
- [ ] MIR ダンプで Pattern4 検出確認
|
||||
- [ ] 出力: `Parsed string: hello world, final pos: 11`
|
||||
|
||||
## 次のステップ(Phase 178)
|
||||
- `continue` 分岐の追加(エスケープ処理)
|
||||
- P5 昇格キャリア候補の検討(必要性を再評価)
|
||||
- Early return 対応(JoinIR での処理検討)
|
||||
|
||||
## まとめ
|
||||
**Phase 177-A では、min2 と同型の Simple Case を Production 環境に適用する。**
|
||||
エスケープ処理は Phase 178 以降に回し、まず「2-carrier + break」の動作確認を優先する。
|
||||
@ -12,15 +12,20 @@ use super::super::trace;
|
||||
/// Phase 1: Allocate new block IDs for ALL functions (Phase 189)
|
||||
///
|
||||
/// DETERMINISM: Sort functions and blocks by name/ID to ensure consistent iteration order
|
||||
///
|
||||
/// Returns: (JoinIrIdRemapper, exit_block_id)
|
||||
pub(super) fn allocate_blocks(
|
||||
builder: &mut crate::mir::builder::MirBuilder,
|
||||
mir_module: &MirModule,
|
||||
debug: bool,
|
||||
) -> Result<JoinIrIdRemapper, String> {
|
||||
) -> Result<(JoinIrIdRemapper, crate::mir::BasicBlockId), String> {
|
||||
let mut remapper = JoinIrIdRemapper::new();
|
||||
|
||||
// Create exit block for Return conversion (single for all functions)
|
||||
let _exit_block_id = builder.block_gen.next();
|
||||
// Phase 177-3: Allocate exit block FIRST to ensure it doesn't conflict with JoinIR blocks
|
||||
// This exit_block_id will be returned and used by instruction_rewriter and exit_phi_builder
|
||||
let exit_block_id = builder.block_gen.next();
|
||||
|
||||
eprintln!("[cf_loop/joinir/block_allocator] Phase 177-3: Allocated exit_block_id = {:?}", exit_block_id);
|
||||
|
||||
// Phase 195: Use unified trace
|
||||
trace::trace().blocks("allocator", "Phase 189: Allocating block IDs for all functions");
|
||||
@ -61,5 +66,6 @@ pub(super) fn allocate_blocks(
|
||||
);
|
||||
}
|
||||
|
||||
Ok(remapper)
|
||||
// Phase 177-3: Return both remapper and exit_block_id
|
||||
Ok((remapper, exit_block_id))
|
||||
}
|
||||
|
||||
@ -30,6 +30,11 @@ use std::collections::HashMap;
|
||||
/// Uses extracted modules:
|
||||
/// - tail_call_classifier: TailCallKind classification
|
||||
/// - merge_result: MergeResult data structure
|
||||
///
|
||||
/// # Phase 177-3
|
||||
///
|
||||
/// `exit_block_id` is now passed in from block_allocator to ensure it doesn't
|
||||
/// conflict with JoinIR function blocks.
|
||||
pub(super) fn merge_and_rewrite(
|
||||
builder: &mut crate::mir::builder::MirBuilder,
|
||||
mir_module: &MirModule,
|
||||
@ -38,13 +43,11 @@ pub(super) fn merge_and_rewrite(
|
||||
function_params: &HashMap<String, Vec<ValueId>>,
|
||||
boundary: Option<&JoinInlineBoundary>,
|
||||
loop_header_phi_info: &mut LoopHeaderPhiInfo,
|
||||
exit_block_id: BasicBlockId,
|
||||
debug: bool,
|
||||
) -> Result<MergeResult, String> {
|
||||
// Create exit block for Return conversion (single for all functions)
|
||||
let exit_block_id = builder.block_gen.next();
|
||||
if debug {
|
||||
eprintln!("[cf_loop/joinir] Exit block: {:?}", exit_block_id);
|
||||
}
|
||||
)-> Result<MergeResult, String> {
|
||||
// Phase 177-3: exit_block_id is now passed in from block_allocator
|
||||
eprintln!("[cf_loop/joinir/instruction_rewriter] Phase 177-3: Using exit_block_id = {:?}", exit_block_id);
|
||||
|
||||
// Phase 189 FIX: Build set of boundary join_inputs to skip their Const initializers
|
||||
let boundary_input_set: std::collections::HashSet<ValueId> = boundary
|
||||
@ -172,8 +175,58 @@ pub(super) fn merge_and_rewrite(
|
||||
let mut found_tail_call = false;
|
||||
let mut tail_call_target: Option<(BasicBlockId, Vec<ValueId>)> = None;
|
||||
|
||||
// Phase 177-3: Check if this block is the loop header with PHI nodes
|
||||
let is_loop_header_with_phi = is_loop_entry_point && !loop_header_phi_info.carrier_phis.is_empty();
|
||||
|
||||
if is_loop_entry_point {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 177-3 DEBUG: is_loop_entry_point={}, carrier_phis.len()={}, is_loop_header_with_phi={}",
|
||||
is_loop_entry_point,
|
||||
loop_header_phi_info.carrier_phis.len(),
|
||||
is_loop_header_with_phi
|
||||
);
|
||||
}
|
||||
|
||||
// Phase 177-3: Collect PHI dst IDs that will be created for this block
|
||||
// (if this is the loop header)
|
||||
let phi_dst_ids_for_block: std::collections::HashSet<ValueId> = if is_loop_header_with_phi {
|
||||
loop_header_phi_info.carrier_phis.values()
|
||||
.map(|entry| entry.phi_dst)
|
||||
.collect()
|
||||
} else {
|
||||
std::collections::HashSet::new()
|
||||
};
|
||||
|
||||
if is_loop_header_with_phi && !phi_dst_ids_for_block.is_empty() {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 177-3: Loop header with {} PHI dsts to protect: {:?}",
|
||||
phi_dst_ids_for_block.len(),
|
||||
phi_dst_ids_for_block
|
||||
);
|
||||
}
|
||||
|
||||
// First pass: Process all instructions, identify tail calls
|
||||
for inst in &old_block.instructions {
|
||||
// Phase 177-3: Skip Copy instructions that would overwrite PHI dsts
|
||||
// The loop header PHIs will provide these values, so copies are redundant/harmful
|
||||
if is_loop_header_with_phi {
|
||||
if let MirInstruction::Copy { dst, src } = inst {
|
||||
// Check if this copy's dst is a PHI dst (after remapping)
|
||||
let dst_remapped = remapper.get_value(*dst).unwrap_or(*dst);
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 177-3 DEBUG: Copy {:?} = {:?}, dst_remapped = {:?}, in phi_dsts = {}",
|
||||
dst, src, dst_remapped, phi_dst_ids_for_block.contains(&dst_remapped)
|
||||
);
|
||||
if phi_dst_ids_for_block.contains(&dst_remapped) {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 177-3: ✅ Skipping loop header Copy to PHI dst {:?} (original {:?})",
|
||||
dst_remapped, dst
|
||||
);
|
||||
continue; // Skip - would overwrite PHI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 189: Skip Const String instructions that define function names
|
||||
if let MirInstruction::Const { dst, value } = inst {
|
||||
if let crate::mir::types::ConstValue::String(_) = value {
|
||||
@ -653,15 +706,30 @@ pub(super) fn merge_and_rewrite(
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 177-3: Collect PHI dst IDs from loop_header_phi_info
|
||||
let phi_dst_ids: std::collections::HashSet<ValueId> = loop_header_phi_info
|
||||
.carrier_phis
|
||||
.values()
|
||||
.map(|entry| entry.phi_dst)
|
||||
.collect();
|
||||
|
||||
// Use BoundaryInjector to inject Copy instructions
|
||||
// Phase 177-3 Option B: Returns reallocations map for condition_bindings with PHI collisions
|
||||
if let Some(ref mut current_func) = builder.current_function {
|
||||
BoundaryInjector::inject_boundary_copies(
|
||||
let _reallocations = BoundaryInjector::inject_boundary_copies(
|
||||
current_func,
|
||||
entry_block_remapped,
|
||||
boundary,
|
||||
&value_map_for_injector,
|
||||
&phi_dst_ids,
|
||||
debug,
|
||||
)?;
|
||||
|
||||
// Note: reallocations map is returned but not currently used.
|
||||
// If condition variables need to be referenced after this point,
|
||||
// the reallocations map would need to be applied to update references.
|
||||
// For now, condition_bindings are read-only variables used only in
|
||||
// the loop condition, so no further updates are needed.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -80,7 +80,8 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
}
|
||||
|
||||
// Phase 1: Allocate block IDs for all functions
|
||||
let mut remapper = block_allocator::allocate_blocks(builder, mir_module, debug)?;
|
||||
// Phase 177-3: block_allocator now returns exit_block_id to avoid conflicts
|
||||
let (mut remapper, exit_block_id) = block_allocator::allocate_blocks(builder, mir_module, debug)?;
|
||||
|
||||
// Phase 2: Collect values from all functions
|
||||
let (mut used_values, value_to_func_name, function_params) =
|
||||
@ -113,6 +114,30 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
// Phase 3: Remap ValueIds
|
||||
remap_values(builder, &used_values, &mut remapper, debug)?;
|
||||
|
||||
// Phase 177-3 DEBUG: Verify remapper state after Phase 3
|
||||
eprintln!("[DEBUG-177] === Remapper state after Phase 3 ===");
|
||||
eprintln!("[DEBUG-177] used_values count: {}", used_values.len());
|
||||
for value_id in &used_values {
|
||||
if let Some(remapped) = remapper.get_value(*value_id) {
|
||||
eprintln!("[DEBUG-177] JoinIR {:?} → Host {:?}", value_id, remapped);
|
||||
} else {
|
||||
eprintln!("[DEBUG-177] JoinIR {:?} → NOT FOUND ❌", value_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Check condition_bindings specifically
|
||||
if let Some(boundary) = boundary {
|
||||
eprintln!("[DEBUG-177] === Condition bindings check ===");
|
||||
for binding in &boundary.condition_bindings {
|
||||
let lookup_result = remapper.get_value(binding.join_value);
|
||||
eprintln!(
|
||||
"[DEBUG-177] '{}': JoinIR {:?} → {:?}",
|
||||
binding.name, binding.join_value, lookup_result
|
||||
);
|
||||
}
|
||||
}
|
||||
eprintln!("[DEBUG-177] ==============================");
|
||||
|
||||
// Phase 3.5: Build loop header PHIs (if loop pattern with loop_var_name)
|
||||
//
|
||||
// We need to know PHI dsts before instruction_rewriter runs, so that:
|
||||
@ -215,9 +240,47 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
// 2. loop body uses PHI result
|
||||
// 3. tail call args are correctly routed
|
||||
|
||||
// Map main's parameters
|
||||
// MIR function keys use join_func_N format from join_func_name()
|
||||
// Phase 177-3 fix: Protect condition-ONLY bindings from being overridden to PHI dsts
|
||||
//
|
||||
// Problem: condition_bindings may contain:
|
||||
// 1. True condition-only variables (e.g., 'limit' in loop(i < limit)) - NOT carriers
|
||||
// 2. Body-only carriers added by Phase 176-5 (e.g., 'result') - ARE carriers
|
||||
//
|
||||
// We must ONLY protect (1), not (2), because:
|
||||
// - Condition-only vars should keep their HOST mapping (e.g., limit = %8)
|
||||
// - Body-only carriers MUST be remapped to PHI dsts (e.g., result = %24)
|
||||
//
|
||||
// Solution: Protect condition_bindings that are NOT in exit_bindings (i.e., not carriers)
|
||||
let carrier_names: std::collections::HashSet<&str> = boundary
|
||||
.exit_bindings
|
||||
.iter()
|
||||
.map(|eb| eb.carrier_name.as_str())
|
||||
.collect();
|
||||
|
||||
let condition_binding_ids: std::collections::HashSet<ValueId> = boundary
|
||||
.condition_bindings
|
||||
.iter()
|
||||
.filter(|cb| !carrier_names.contains(cb.name.as_str()))
|
||||
.map(|cb| cb.join_value)
|
||||
.collect();
|
||||
|
||||
if !condition_binding_ids.is_empty() {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 177-3: Protected ValueIds (condition-only, not carriers): {:?}",
|
||||
condition_binding_ids
|
||||
);
|
||||
for cb in &boundary.condition_bindings {
|
||||
let is_carrier = carrier_names.contains(cb.name.as_str());
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 177-3: '{}': JoinIR {:?} (carrier={})",
|
||||
cb.name, cb.join_value, is_carrier
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let main_func_name = "join_func_0";
|
||||
let loop_step_func_name = "join_func_1";
|
||||
|
||||
if function_params.get(main_func_name).is_none() {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}",
|
||||
@ -226,30 +289,57 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
);
|
||||
}
|
||||
if let Some(main_params) = function_params.get(main_func_name) {
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 33-21: main ({}) params: {:?}",
|
||||
"[DEBUG-177] Phase 33-21: main ({}) params: {:?}",
|
||||
main_func_name, main_params
|
||||
);
|
||||
}
|
||||
eprintln!(
|
||||
"[DEBUG-177] Phase 33-21: carrier_phis count: {}, names: {:?}",
|
||||
phi_info.carrier_phis.len(),
|
||||
phi_info.carrier_phis.iter().map(|(n, _)| n.as_str()).collect::<Vec<_>>()
|
||||
);
|
||||
// Map main's parameters to header PHI dsts
|
||||
// main params: [i_init, carrier1_init, ...]
|
||||
// carrier_phis: [("i", entry), ("sum", entry), ...]
|
||||
for (idx, (carrier_name, entry)) in phi_info.carrier_phis.iter().enumerate() {
|
||||
if let Some(&main_param) = main_params.get(idx) {
|
||||
if debug {
|
||||
// Phase 177-3: Don't override condition_bindings
|
||||
if condition_binding_ids.contains(&main_param) {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 33-21: REMAP main param {:?} → {:?} ('{}')",
|
||||
main_param, entry.phi_dst, carrier_name
|
||||
"[cf_loop/joinir] Phase 177-3: Skipping override for condition_binding {:?} ('{}')",
|
||||
main_param, carrier_name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
eprintln!(
|
||||
"[DEBUG-177] Phase 33-21: REMAP main param[{}] {:?} → {:?} ('{}')",
|
||||
idx, main_param, entry.phi_dst, carrier_name
|
||||
);
|
||||
remapper.set_value(main_param, entry.phi_dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 177-3-B: Handle body-only carriers
|
||||
// These are carriers in carrier_phis that are NOT in main function params.
|
||||
// They appear in condition_bindings (added by Phase 176-5) but need PHI remapping.
|
||||
for (carrier_name, entry) in &phi_info.carrier_phis {
|
||||
// Check if this carrier has a condition_binding
|
||||
if let Some(binding) = boundary.condition_bindings.iter().find(|cb| cb.name == *carrier_name) {
|
||||
// Skip if it's a true condition-only variable (already protected above)
|
||||
if condition_binding_ids.contains(&binding.join_value) {
|
||||
continue;
|
||||
}
|
||||
// This is a body-only carrier - remap it to PHI dst
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 177-3-B: Body-only carrier '{}': JoinIR {:?} → PHI {:?}",
|
||||
carrier_name, binding.join_value, entry.phi_dst
|
||||
);
|
||||
remapper.set_value(binding.join_value, entry.phi_dst);
|
||||
}
|
||||
}
|
||||
|
||||
// Map loop_step's parameters
|
||||
let loop_step_func_name = "join_func_1";
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 33-21: function_params keys: {:?}",
|
||||
@ -275,6 +365,14 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
// carrier_phis: [("i", entry), ("sum", entry), ...]
|
||||
for (idx, (carrier_name, entry)) in phi_info.carrier_phis.iter().enumerate() {
|
||||
if let Some(&loop_step_param) = loop_step_params.get(idx) {
|
||||
// Phase 177-3: Don't override condition_bindings
|
||||
if condition_binding_ids.contains(&loop_step_param) {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 177-3: Skipping override for condition_binding {:?} ('{}')",
|
||||
loop_step_param, carrier_name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 33-21: REMAP loop_step param {:?} → {:?} ('{}')",
|
||||
@ -290,6 +388,8 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
// Fallback: Use old behavior (ValueId(0), ValueId(1), ...)
|
||||
// This handles patterns that don't have loop_step function
|
||||
if let Some(phi_dst) = phi_info.get_carrier_phi(loop_var_name) {
|
||||
// Phase 177-3: Don't override condition_bindings
|
||||
if !condition_binding_ids.contains(&ValueId(0)) {
|
||||
remapper.set_value(ValueId(0), phi_dst);
|
||||
if debug {
|
||||
eprintln!(
|
||||
@ -297,12 +397,19 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
phi_dst
|
||||
);
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 177-3 fallback: Skipping override for condition_binding ValueId(0)"
|
||||
);
|
||||
}
|
||||
}
|
||||
for (idx, (carrier_name, entry)) in phi_info.carrier_phis.iter().enumerate() {
|
||||
if carrier_name == loop_var_name {
|
||||
continue;
|
||||
}
|
||||
let join_value_id = ValueId(idx as u32);
|
||||
// Phase 177-3: Don't override condition_bindings
|
||||
if !condition_binding_ids.contains(&join_value_id) {
|
||||
remapper.set_value(join_value_id, entry.phi_dst);
|
||||
if debug {
|
||||
eprintln!(
|
||||
@ -310,8 +417,24 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
join_value_id, entry.phi_dst, carrier_name
|
||||
);
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 177-3 fallback: Skipping override for condition_binding {:?} ('{}')",
|
||||
join_value_id, carrier_name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 177-3 DEBUG: Check remapper after Phase 33-21 overrides
|
||||
eprintln!("[DEBUG-177] === Remapper state after Phase 33-21 ===");
|
||||
for binding in &boundary.condition_bindings {
|
||||
let lookup_result = remapper.get_value(binding.join_value);
|
||||
eprintln!(
|
||||
"[DEBUG-177] '{}': JoinIR {:?} → {:?} (after 33-21)",
|
||||
binding.name, binding.join_value, lookup_result
|
||||
);
|
||||
}
|
||||
|
||||
phi_info
|
||||
} else {
|
||||
@ -323,6 +446,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
|
||||
// Phase 4: Merge blocks and rewrite instructions
|
||||
// Phase 33-16: Pass mutable loop_header_phi_info for latch_incoming tracking
|
||||
// Phase 177-3: Pass exit_block_id from allocator to avoid conflicts
|
||||
let merge_result = instruction_rewriter::merge_and_rewrite(
|
||||
builder,
|
||||
mir_module,
|
||||
@ -331,6 +455,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
&function_params,
|
||||
boundary,
|
||||
&mut loop_header_phi_info,
|
||||
exit_block_id,
|
||||
debug,
|
||||
)?;
|
||||
|
||||
|
||||
@ -39,13 +39,26 @@ impl BoundaryInjector {
|
||||
/// When `boundary.loop_var_name` is set, ALL carriers (loop var + other carriers
|
||||
/// from exit_bindings) are handled by header PHIs. We skip ALL join_inputs
|
||||
/// Copy instructions to avoid overwriting the PHI results.
|
||||
///
|
||||
/// # Phase 177-3: PHI Collision Avoidance (Option B)
|
||||
///
|
||||
/// When `phi_dst_ids` contains ValueIds of existing PHI dsts in the header block,
|
||||
/// we allocate a NEW ValueId for condition_bindings instead of skipping the copy.
|
||||
/// This ensures the condition variable is available even when its remapped ValueId
|
||||
/// collides with a PHI dst.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a HashMap mapping original ValueIds to their reallocated ValueIds
|
||||
/// (only for condition_bindings that had collisions).
|
||||
pub fn inject_boundary_copies(
|
||||
func: &mut MirFunction,
|
||||
entry_block_id: BasicBlockId,
|
||||
boundary: &JoinInlineBoundary,
|
||||
value_map: &HashMap<ValueId, ValueId>,
|
||||
phi_dst_ids: &std::collections::HashSet<ValueId>,
|
||||
debug: bool,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<HashMap<ValueId, ValueId>, String> {
|
||||
// Phase 33-20: When loop_var_name is set, ALL join_inputs are handled by header PHIs
|
||||
// This includes the loop variable AND all other carriers from exit_bindings.
|
||||
// We skip ALL join_inputs Copy instructions, only condition_bindings remain.
|
||||
@ -59,7 +72,8 @@ impl BoundaryInjector {
|
||||
};
|
||||
let total_inputs = effective_join_inputs + boundary.condition_bindings.len();
|
||||
if total_inputs == 0 {
|
||||
return Ok(());
|
||||
// No inputs to process, return empty reallocations map
|
||||
return Ok(HashMap::new());
|
||||
}
|
||||
|
||||
if debug {
|
||||
@ -73,7 +87,38 @@ impl BoundaryInjector {
|
||||
);
|
||||
}
|
||||
|
||||
// Entry block を取得
|
||||
// Phase 177-3: Use PHI dst IDs passed from caller
|
||||
if debug && !phi_dst_ids.is_empty() {
|
||||
eprintln!(
|
||||
"[BoundaryInjector] Phase 177-3: Received {} PHI dst IDs to avoid: {:?}",
|
||||
phi_dst_ids.len(),
|
||||
phi_dst_ids
|
||||
);
|
||||
}
|
||||
|
||||
// Phase 177-3 Option B: First pass - allocate all new ValueIds for PHI collisions
|
||||
// We need to do this BEFORE acquiring the entry_block reference to avoid borrow conflicts
|
||||
let mut reallocations = HashMap::new();
|
||||
|
||||
for binding in &boundary.condition_bindings {
|
||||
let remapped_join = value_map.get(&binding.join_value).copied().unwrap_or(binding.join_value);
|
||||
|
||||
if phi_dst_ids.contains(&remapped_join) {
|
||||
// Collision detected! Allocate a fresh ValueId
|
||||
let fresh_dst = func.next_value_id();
|
||||
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[BoundaryInjector] Phase 177-3 Option B: PHI collision for condition binding '{}': {:?} → reallocated to {:?}",
|
||||
binding.name, remapped_join, fresh_dst
|
||||
);
|
||||
}
|
||||
|
||||
reallocations.insert(binding.join_value, fresh_dst);
|
||||
}
|
||||
}
|
||||
|
||||
// Now get entry block reference (after all ValueId allocations are done)
|
||||
let entry_block = func
|
||||
.get_block_mut(entry_block_id)
|
||||
.ok_or(format!("Entry block {:?} not found", entry_block_id))?;
|
||||
@ -110,17 +155,34 @@ impl BoundaryInjector {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 177-3 DEBUG: Check value_map in BoundaryInjector
|
||||
if debug {
|
||||
eprintln!("[DEBUG-177] === BoundaryInjector value_map ===");
|
||||
for binding in &boundary.condition_bindings {
|
||||
let lookup = value_map.get(&binding.join_value);
|
||||
eprintln!(
|
||||
"[DEBUG-177] '{}': JoinIR {:?} → {:?}",
|
||||
binding.name, binding.join_value, lookup
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 171-fix: Inject Copy instructions for condition_bindings (condition-only variables)
|
||||
// These variables are read-only and used ONLY in the loop condition.
|
||||
// Each binding explicitly specifies HOST ValueId → JoinIR ValueId mapping.
|
||||
// We inject Copy: remapped_join_value = Copy host_value
|
||||
//
|
||||
// Phase 177-3 Option B: Use pre-allocated reallocations for PHI collision cases
|
||||
for binding in &boundary.condition_bindings {
|
||||
// Look up the remapped JoinIR ValueId from value_map
|
||||
let remapped_join = value_map.get(&binding.join_value).copied().unwrap_or(binding.join_value);
|
||||
|
||||
// Copy instruction: remapped_join_value = Copy host_value
|
||||
// Phase 177-3 Option B: Check if this binding was reallocated (PHI collision case)
|
||||
let final_dst = reallocations.get(&binding.join_value).copied().unwrap_or(remapped_join);
|
||||
|
||||
// Copy instruction: final_dst = Copy host_value
|
||||
let copy_inst = MirInstruction::Copy {
|
||||
dst: remapped_join,
|
||||
dst: final_dst,
|
||||
src: binding.host_value,
|
||||
};
|
||||
|
||||
@ -128,8 +190,9 @@ impl BoundaryInjector {
|
||||
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[BoundaryInjector] Condition binding '{}': Copy {:?} = Copy {:?} (JoinIR {:?} → remapped {:?})",
|
||||
binding.name, remapped_join, binding.host_value, binding.join_value, remapped_join
|
||||
"[BoundaryInjector] Condition binding '{}': Copy {:?} = Copy {:?} (JoinIR {:?} → remapped {:?}{})",
|
||||
binding.name, final_dst, binding.host_value, binding.join_value, remapped_join,
|
||||
if final_dst != remapped_join { format!(" → reallocated {:?}", final_dst) } else { String::new() }
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -145,7 +208,8 @@ impl BoundaryInjector {
|
||||
entry_block.instruction_spans.insert(0, default_span);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Return reallocations map for condition_bindings that had PHI collisions
|
||||
Ok(reallocations)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user