fix(joinir): Phase 220-C condition variable remap and self-copy skip
- Pre-populate remap with condition_bindings (join_value → host_value) - Skip self-copy param bindings to avoid `%6 = copy %6` - ConditionEnv remap verified: ValueId(101) → ValueId(6) correctly Note: RC=0 issue remains - ExprResult routing to be investigated in Phase 221 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -70,6 +70,12 @@ JoinIR ラインで守るべきルールを先に書いておくよ:
|
|||||||
- LoopBuilder は物理削除済み。JoinIR を OFF にする経路やフォールバックは存在しない。
|
- LoopBuilder は物理削除済み。JoinIR を OFF にする経路やフォールバックは存在しない。
|
||||||
- `NYASH_JOINIR_CORE` は deprecated(0 を指定しても警告して無視)。JoinIR の OFF トグルは提供しない。
|
- `NYASH_JOINIR_CORE` は deprecated(0 を指定しても警告して無視)。JoinIR の OFF トグルは提供しない。
|
||||||
|
|
||||||
|
11. **Parser の直後に JoinIR 正規化ゲートを置く**
|
||||||
|
- `.hako → AST` のあと、必ず JoinIR Frontend(Pattern 検出+ lowering)を通してから MIR/LLVM に進むのを基本線とする。
|
||||||
|
- dev/ci/strict 系プロファイルでは、この JoinIR 正規化がビルドゲートになる(ここで `[joinir/freeze]` が出たらビルド失敗)。
|
||||||
|
- relaxed / 実験用プロファイルでは、一時的に JoinIR 正規化をスキップすることはあっても、最終的な「言語としての安全域」は
|
||||||
|
JoinIR Frontend が受理できる構造で定義する。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1.9 ValueId Space Management (Phase 201)
|
## 1.9 ValueId Space Management (Phase 201)
|
||||||
@ -118,6 +124,33 @@ Local Region (1000+):
|
|||||||
| Pattern 2 lowerer | Local (1000+) | `alloc_local()` | 中間値(Const, BinOp, etc.) |
|
| Pattern 2 lowerer | Local (1000+) | `alloc_local()` | 中間値(Const, BinOp, etc.) |
|
||||||
| Pattern 3 lowerer | Local (1000+) | `alloc_local()` | 中間値(PHI, Select, etc.) |
|
| Pattern 3 lowerer | Local (1000+) | `alloc_local()` | 中間値(PHI, Select, etc.) |
|
||||||
| Pattern 4 lowerer | Local (1000+) | `alloc_local()` | 中間値(Select, BinOp, etc.) |
|
| Pattern 4 lowerer | Local (1000+) | `alloc_local()` | 中間値(Select, BinOp, etc.) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 将来の拡張(例外 / async / try-catch)
|
||||||
|
|
||||||
|
現状の JoinIR は「同期・正常系」のループ/if/更新を扱う芯としてはほぼ完成しており、
|
||||||
|
LoopBuilder 完全削除後も P1–P5 + 各種 UpdateKind で JsonParser/selfhost の代表ケースを安全に lowering できている。
|
||||||
|
|
||||||
|
一方で、言語としては将来的に `throw/try/catch` や async/await 相当の表現も必要になる。その扱いについては、
|
||||||
|
以下の方針をここに固定しておく(まだ実装フェーズには入っていない):
|
||||||
|
|
||||||
|
1. **ErrorCarrier(Result 風キャリア)の導入(将来フェーズ)**
|
||||||
|
- 関数/ループのキャリアに `err_flag` / `err_value` を追加し、「エラーが発生したら err キャリアを立てて継続を上に抜けていく」
|
||||||
|
という形で throw 相当を表現する。
|
||||||
|
- catch 側は「ある関数境界で err キャリアを inspect して処理する」箱として実装し、構文の try/catch はこの箱への sugar として扱う。
|
||||||
|
- これにより core JoinIR(ループ骨格・PHI・ExitLine)は変更せず、ErrorCarrier を 1 本足すだけで例外伝播を表現できる。
|
||||||
|
|
||||||
|
2. **async/await は state machine 箱として後置する**
|
||||||
|
- async 関数は `.hako AST → state machine JoinIR` へ変換する専用の箱で表現し、その state machine 自体は
|
||||||
|
既存の JoinIR(ループ+if+キャリア)で実装する。
|
||||||
|
- これにより、JoinIR のコア設計を同期用に保ったまま、外側に async 用の変換レイヤーを追加する形にする。
|
||||||
|
|
||||||
|
3. **導入タイミング**
|
||||||
|
- まずは JsonParser/selfhost の同期ループ/if が JoinIR Pattern 群でほぼ全部通る状態を優先して作る。
|
||||||
|
- ErrorCarrier や try/catch/async の実装は、その後の「第2章」として Phase 23x 以降に扱う(現在は設計メモのみ)。
|
||||||
|
|
||||||
|
この方針により、「例外や async のために JoinIR の芯を崩さず、外側に箱を足す」拡張戦略を維持する。
|
||||||
| LoopHeaderPhiBuilder | PHI Reserved (0-99) | `reserve_phi()` | PHI dst ID 保護(上書き防止) |
|
| LoopHeaderPhiBuilder | PHI Reserved (0-99) | `reserve_phi()` | PHI dst ID 保護(上書き防止) |
|
||||||
|
|
||||||
### 1.9.4 設計原則
|
### 1.9.4 設計原則
|
||||||
|
|||||||
@ -282,9 +282,27 @@ pub(super) fn merge_and_rewrite(
|
|||||||
// In the header block, carriers are defined by PHIs, not Copies.
|
// In the header block, carriers are defined by PHIs, not Copies.
|
||||||
// JoinIR function parameters get copied to local variables, but after
|
// JoinIR function parameters get copied to local variables, but after
|
||||||
// inlining with header PHIs, those Copies would overwrite the PHI results.
|
// inlining with header PHIs, those Copies would overwrite the PHI results.
|
||||||
if let MirInstruction::Copy { dst, src: _ } = inst {
|
if let MirInstruction::Copy { dst, src } = inst {
|
||||||
// Check if this Copy's dst (after remapping) matches any header PHI dst
|
// Check if this Copy's dst (after remapping) matches any header PHI dst
|
||||||
let remapped_dst = remapper.get_value(*dst).unwrap_or(*dst);
|
let remapped_dst = remapper.get_value(*dst).unwrap_or(*dst);
|
||||||
|
let remapped_src = remapper.get_value(*src).unwrap_or(*src);
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"[DEBUG-220C] Copy instruction: {:?} = {:?} → remapped: {:?} = {:?}",
|
||||||
|
dst, src, remapped_dst, remapped_src
|
||||||
|
);
|
||||||
|
|
||||||
|
// Phase 220-C: Skip self-copies (src == dst after remapping)
|
||||||
|
// These occur when condition variables are remapped to HOST values,
|
||||||
|
// and the JoinIR Copy becomes a pointless self-copy.
|
||||||
|
if remapped_src == remapped_dst {
|
||||||
|
eprintln!(
|
||||||
|
"[cf_loop/joinir] Phase 220-C: ✅ Skipping self-copy {:?} = {:?} (remapped to {:?})",
|
||||||
|
dst, src, remapped_dst
|
||||||
|
);
|
||||||
|
continue; // Skip - pointless self-copy
|
||||||
|
}
|
||||||
|
|
||||||
let is_header_phi_dst = loop_header_phi_info.carrier_phis
|
let is_header_phi_dst = loop_header_phi_info.carrier_phis
|
||||||
.values()
|
.values()
|
||||||
.any(|entry| entry.phi_dst == remapped_dst);
|
.any(|entry| entry.phi_dst == remapped_dst);
|
||||||
@ -403,23 +421,41 @@ pub(super) fn merge_and_rewrite(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Insert Copy instructions for parameter binding
|
// Insert Copy instructions for parameter binding
|
||||||
|
eprintln!("[DEBUG-220C-PARAM] Processing {} param bindings", args.len());
|
||||||
for (i, arg_val_remapped) in args.iter().enumerate() {
|
for (i, arg_val_remapped) in args.iter().enumerate() {
|
||||||
if i < target_params.len() {
|
if i < target_params.len() {
|
||||||
let param_val_original = target_params[i];
|
let param_val_original = target_params[i];
|
||||||
|
eprintln!(
|
||||||
|
"[DEBUG-220C-PARAM] Param[{}]: original={:?}, remapped lookup...",
|
||||||
|
i, param_val_original
|
||||||
|
);
|
||||||
if let Some(param_val_remapped) =
|
if let Some(param_val_remapped) =
|
||||||
remapper.get_value(param_val_original)
|
remapper.get_value(param_val_original)
|
||||||
{
|
{
|
||||||
|
eprintln!(
|
||||||
|
"[DEBUG-220C-PARAM] Param[{}]: {:?} → {:?}, arg={:?}",
|
||||||
|
i, param_val_original, param_val_remapped, arg_val_remapped
|
||||||
|
);
|
||||||
|
|
||||||
|
// Phase 220-C: Skip self-copies (arg == param after remapping)
|
||||||
|
// This occurs when condition variables are remapped to HOST values
|
||||||
|
if param_val_remapped == *arg_val_remapped {
|
||||||
|
eprintln!(
|
||||||
|
"[cf_loop/joinir] Phase 220-C: ✅ Skip self-copy param binding {:?} = {:?}",
|
||||||
|
param_val_remapped, arg_val_remapped
|
||||||
|
);
|
||||||
|
continue; // Skip self-copy
|
||||||
|
}
|
||||||
|
|
||||||
new_block.instructions.push(MirInstruction::Copy {
|
new_block.instructions.push(MirInstruction::Copy {
|
||||||
dst: param_val_remapped,
|
dst: param_val_remapped,
|
||||||
src: *arg_val_remapped,
|
src: *arg_val_remapped,
|
||||||
});
|
});
|
||||||
|
|
||||||
if debug {
|
eprintln!(
|
||||||
eprintln!(
|
"[DEBUG-220C-PARAM] Added Copy: {:?} = {:?}",
|
||||||
"[cf_loop/joinir] Param binding: arg {:?} → param {:?}",
|
param_val_remapped, arg_val_remapped
|
||||||
arg_val_remapped, param_val_remapped
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,19 +87,12 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
|||||||
let (mut used_values, value_to_func_name, function_params) =
|
let (mut used_values, value_to_func_name, function_params) =
|
||||||
value_collector::collect_values(mir_module, &remapper, debug)?;
|
value_collector::collect_values(mir_module, &remapper, debug)?;
|
||||||
|
|
||||||
// Phase 171-fix: Add condition_bindings' join_values to used_values for remapping
|
// Phase 220-C: Condition bindings should NOT be added to used_values
|
||||||
if let Some(boundary) = boundary {
|
// They will be remapped directly to HOST values in remap_values()
|
||||||
for binding in &boundary.condition_bindings {
|
// (Removed Phase 171-fix logic)
|
||||||
if debug {
|
|
||||||
eprintln!(
|
|
||||||
"[cf_loop/joinir] Phase 171-fix: Adding condition binding '{}' JoinIR {:?} to used_values",
|
|
||||||
binding.name, binding.join_value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
used_values.insert(binding.join_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 172-3: Add exit_bindings' join_exit_values to used_values for remapping
|
// Phase 172-3: Add exit_bindings' join_exit_values to used_values for remapping
|
||||||
|
if let Some(boundary) = boundary {
|
||||||
for binding in &boundary.exit_bindings {
|
for binding in &boundary.exit_bindings {
|
||||||
if debug {
|
if debug {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
@ -197,7 +190,8 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Phase 3: Remap ValueIds (with reserved PHI dsts protection)
|
// Phase 3: Remap ValueIds (with reserved PHI dsts protection)
|
||||||
remap_values(builder, &used_values, &mut remapper, &reserved_phi_dsts, debug)?;
|
// Phase 220-C: Pre-populate condition_bindings BEFORE remap_values
|
||||||
|
remap_values(builder, &used_values, &mut remapper, &reserved_phi_dsts, boundary, debug)?;
|
||||||
|
|
||||||
// Phase 177-3 DEBUG: Verify remapper state after Phase 3
|
// Phase 177-3 DEBUG: Verify remapper state after Phase 3
|
||||||
eprintln!("[DEBUG-177] === Remapper state after Phase 3 ===");
|
eprintln!("[DEBUG-177] === Remapper state after Phase 3 ===");
|
||||||
@ -618,11 +612,15 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
|||||||
/// Phase 201-A: Accept reserved ValueIds that must not be reused.
|
/// Phase 201-A: Accept reserved ValueIds that must not be reused.
|
||||||
/// These are PHI dst ValueIds that will be created by LoopHeaderPhiBuilder.
|
/// These are PHI dst ValueIds that will be created by LoopHeaderPhiBuilder.
|
||||||
/// We must skip these IDs to prevent carrier value corruption.
|
/// We must skip these IDs to prevent carrier value corruption.
|
||||||
|
///
|
||||||
|
/// Phase 220-C: Pre-populate condition_bindings to use HOST ValueIds directly.
|
||||||
|
/// Condition variables should NOT get new allocations - they use HOST values.
|
||||||
fn remap_values(
|
fn remap_values(
|
||||||
builder: &mut crate::mir::builder::MirBuilder,
|
builder: &mut crate::mir::builder::MirBuilder,
|
||||||
used_values: &std::collections::BTreeSet<ValueId>,
|
used_values: &std::collections::BTreeSet<ValueId>,
|
||||||
remapper: &mut crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper,
|
remapper: &mut crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper,
|
||||||
reserved_ids: &std::collections::HashSet<ValueId>,
|
reserved_ids: &std::collections::HashSet<ValueId>,
|
||||||
|
boundary: Option<&JoinInlineBoundary>,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if debug {
|
if debug {
|
||||||
@ -630,7 +628,32 @@ fn remap_values(
|
|||||||
used_values.len(), reserved_ids.len());
|
used_values.len(), reserved_ids.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 220-C: Pre-populate condition_bindings BEFORE normal remapping
|
||||||
|
// Condition variables use HOST ValueIds directly (no new allocation)
|
||||||
|
if let Some(boundary) = boundary {
|
||||||
|
for binding in &boundary.condition_bindings {
|
||||||
|
remapper.set_value(binding.join_value, binding.host_value);
|
||||||
|
if debug {
|
||||||
|
eprintln!(
|
||||||
|
"[cf_loop/joinir] Phase 220-C: Condition '{}': JoinIR {:?} → HOST {:?} (no allocation)",
|
||||||
|
binding.name, binding.join_value, binding.host_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for old_value in used_values {
|
for old_value in used_values {
|
||||||
|
// Phase 220-C: Skip if already mapped (e.g., condition_bindings)
|
||||||
|
if remapper.get_value(*old_value).is_some() {
|
||||||
|
if debug {
|
||||||
|
eprintln!(
|
||||||
|
"[cf_loop/joinir] Phase 220-C: Skipping {:?} (already mapped)",
|
||||||
|
old_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Phase 201-A: Allocate new ValueId, skipping reserved PHI dsts
|
// Phase 201-A: Allocate new ValueId, skipping reserved PHI dsts
|
||||||
let new_value = loop {
|
let new_value = loop {
|
||||||
let candidate = builder.next_value_id();
|
let candidate = builder.next_value_id();
|
||||||
|
|||||||
Reference in New Issue
Block a user