diff --git a/docs/development/current/main/investigations/phase132-case-c-llvm-exe.md b/docs/development/current/main/investigations/phase132-case-c-llvm-exe.md new file mode 100644 index 00000000..255f27a4 --- /dev/null +++ b/docs/development/current/main/investigations/phase132-case-c-llvm-exe.md @@ -0,0 +1,169 @@ +# Phase 132-P0: Case C (Infinite Loop with Early Exit) - LLVM EXE Investigation + +## Date +2025-12-15 + +## Status +🔴 **FAILED** - LLVM executable returns wrong result + +## Summary +Testing `apps/tests/llvm_stage3_loop_only.hako` (Pattern 5: InfiniteEarlyExit) in LLVM EXE mode reveals a critical exit PHI usage bug. + +## Test File +`apps/tests/llvm_stage3_loop_only.hako` +```nyash +static box Main { + main() { + local counter = 0 + loop (true) { + counter = counter + 1 + if counter == 3 { break } + continue + } + print("Result: " + counter) + return 0 + } +} +``` + +## Expected Behavior +- **VM execution**: `Result: 3` ✅ +- **LLVM EXE**: `Result: 3` (should match VM) + +## Actual Behavior +- **VM execution**: `Result: 3` ✅ +- **LLVM EXE**: `Result: 0` ❌ + +## Root Cause Analysis + +### MIR Structure (Correct) +```mir +bb3: ; Exit block + 1: %1: Integer = phi [%8, bb6] ; ✅ PHI correctly receives counter + 1: %16: String = const "Result: " + 1: %17: String = copy %16 + 1: %18: Integer = copy %1 ; ✅ MIR uses %1 (PHI result) + 1: %19: Box("StringBox") = %17 Add %18 + 1: %20: Box("StringBox") = copy %19 + 1: call_global print(%20) + 1: %21: Integer = const 0 + 1: ret %21 +``` + +**MIR is correct**: The exit block (bb3) has a PHI node that receives the counter value from bb6, and subsequent instructions correctly use `%1`. + +### LLVM IR (BUG) +```llvm +bb3: + %"phi_1" = phi i64 [%"add_8", %"bb6"] ; ✅ PHI created correctly + %".2" = getelementptr inbounds [9 x i8], [9 x i8]* @".str.main.16", i32 0, i32 0 + %"const_str_h_16" = call i64 @"nyash.box.from_i8_string"(i8* %".2") + %"bin_h2p_r_19" = call i8* @"nyash.string.to_i8p_h"(i64 0) ; ❌ Uses 0 instead of %"phi_1" + %"concat_is_19" = call i8* @"nyash.string.concat_is"(i64 0, i8* %"bin_h2p_r_19") ; ❌ Uses 0 + %"concat_box_19" = call i64 @"nyash.box.from_i8_string"(i8* %"concat_is_19") + call void @"ny_check_safepoint"() + %"unified_global_print" = call i64 @"print"(i64 0) ; ❌ Uses 0 + ret i64 0 +``` + +**Bug identified**: The PHI node `%"phi_1"` is created correctly and receives the counter value from `%"add_8"`. However, **all subsequent uses of ValueId(1) are hardcoded to `i64 0` instead of using `%"phi_1"`**. + +### Hypothesis +The Python LLVM builder is not correctly resolving ValueId(1) when lowering instructions in bb3. Possible causes: + +1. **vmap issue**: The PHI node is created and stored in `self.vmap[1]` during `setup_phi_placeholders`, but when lowering instructions in bb3, `vmap_cur` may not contain the PHI. + +2. **Resolution fallback**: When `resolve_i64_strict` fails to find ValueId(1), it falls back to `ir.Constant(i64, 0)`. + +3. **Block-local vmap initialization**: `vmap_cur` is initialized with `dict(builder.vmap)` at the start of each block, but something may be preventing the PHI from being included. + +## Investigation Steps + +### Step 1: Verify PHI Creation +✅ **Confirmed**: PHI is created in LLVM IR at bb3 + +### Step 2: Check MIR Exit PHI Generation +✅ **Confirmed**: MIR has correct exit PHI with debug logs: +``` +[DEBUG-177] Phase 246-EX: Block BasicBlockId(1) has jump_args metadata: [ValueId(1004)] +[DEBUG-177] Phase 246-EX: Remapped jump_args: [ValueId(8)] +[DEBUG-177] Phase 246-EX: exit_phi_inputs from jump_args[0]: (BasicBlockId(6), ValueId(8)) +[DEBUG-177] Phase 246-EX-P5: Added loop_var 'counter' to carrier_inputs: (BasicBlockId(6), ValueId(8)) +[DEBUG-177] Exit block PHI (carrier 'counter'): ValueId(1) = phi [(BasicBlockId(6), ValueId(8))] +``` + +### Step 3: Trace vmap Resolution +🔄 **In progress**: Running with `NYASH_LLVM_VMAP_TRACE=1` to see if PHI is in vmap_cur + +## Code Locations + +### Python LLVM Builder +- PHI placeholder creation: `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/llvm_builder.py:276-368` + - Line 343: `self.vmap[dst0] = ph0` - PHI stored in global vmap +- Block lowering: `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/builders/block_lower.py` + - Line 335: `vmap_cur = dict(builder.vmap)` - Copy global vmap to block-local +- Instruction lowering: `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/builders/instruction_lower.py` + - Line 8: `vmap_ctx = getattr(owner, '_current_vmap', owner.vmap)` - Use block-local vmap +- Value resolution: `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/utils/values.py:11-56` + - `resolve_i64_strict` - Checks vmap, global_vmap, then resolver + - Falls back to `ir.Constant(i64, 0)` if all fail (line 53) + +### Rust MIR Generation +- Exit PHI generation: `src/mir/join_ir/lowering/simple_while_minimal.rs` + - Pattern 5 (InfiniteEarlyExit) lowering + +## Root Cause Identified + +### Bug Location +`/home/tomoaki/git/hakorune-selfhost/src/llvm_py/llvm_builder.py:342-343` + +```python +ph0 = b0.phi(self.i64, name=f"phi_{dst0}") +self.vmap[dst0] = ph0 +# ❌ MISSING: self.phi_manager.register_phi(bid0, dst0, ph0) +``` + +### Failure Chain + +1. **PHI Creation** (line 342-343): PHI is created and stored in `self.vmap[1]` ✅ +2. **PHI Registration** (MISSING): PHI is **never registered** via `phi_manager.register_phi()` ❌ +3. **Block Lowering** (block_lower.py:325): `filter_vmap_preserve_phis` is called +4. **PHI Filtering** (phi_manager.py:filter_vmap_preserve_phis): Checks `is_phi_owned(3, 1)` +5. **Ownership Check** (phi_manager.py:is_phi_owned): Looks for `(3, 1)` in `predeclared` dict +6. **Not Found** ❌: PHI was never registered, so `(3, 1)` is not in `predeclared` +7. **PHI Filtered Out**: PHI is removed from `vmap_cur` +8. **Value Resolution Fails**: Instructions can't find ValueId(1), fall back to `ir.Constant(i64, 0)` + +### Fix Strategy + +**Option A: Add PHI Registration** (Recommended) + +Add `self.phi_manager.register_phi(bid0, dst0, ph0)` after line 343 in `llvm_builder.py:setup_phi_placeholders`: + +```python +if not is_phi: + ph0 = b0.phi(self.i64, name=f"phi_{dst0}") + self.vmap[dst0] = ph0 + # ✅ FIX: Register PHI for filter_vmap_preserve_phis + self.phi_manager.register_phi(int(bid0), int(dst0), ph0) +``` + +This ensures PHIs are included in `vmap_cur` when lowering their defining block. + +### Verification Plan + +1. Add `register_phi` call in `setup_phi_placeholders` +2. Rebuild and test: `NYASH_LLVM_STRICT=1 tools/build_llvm.sh apps/tests/llvm_stage3_loop_only.hako -o /tmp/case_c` +3. Execute: `/tmp/case_c` should output `Result: 3` +4. Check LLVM IR: Should use `%"phi_1"` instead of `0` + +## Acceptance Criteria + +- ✅ LLVM EXE output: `Result: 3` +- ✅ LLVM IR uses `%"phi_1"` instead of `0` +- ✅ STRICT mode passes without fallback warnings + +## Related Documents +- [Phase 132 Plan](/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/phase132-plan.md) +- [Phase 131-3 LLVM Lowering Inventory](/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/phase131-3-llvm-lowering-inventory.md) +- [Simple While Minimal Lowering](/home/tomoaki/git/hakorune-selfhost/src/mir/join_ir/lowering/simple_while_minimal.rs) diff --git a/docs/development/current/main/investigations/phase132-p0-case-c-root-cause.md b/docs/development/current/main/investigations/phase132-p0-case-c-root-cause.md new file mode 100644 index 00000000..7ef074da --- /dev/null +++ b/docs/development/current/main/investigations/phase132-p0-case-c-root-cause.md @@ -0,0 +1,149 @@ +# Phase 132-P0 Case C Root Cause Investigation + +**Date**: 2025-12-15 +**Status**: Root cause identified, fix designed +**Priority**: P0 (blocks LLVM EXE execution) + +## Problem Statement + +Case C (Pattern 5 + print concat) LLVM EXE fails with domination error: + +``` +RuntimeError: Instruction does not dominate all uses! + %phi_1 = phi i64 [ %add_8, %bb6 ] + %phi_3 = phi i64 [ %phi_1, %bb0 ], [ %add_8, %bb7 ] +``` + +`%phi_3` in bb4 uses `%phi_1` from bb0 edge, but `%phi_1` is defined in bb3 which doesn't dominate bb0. + +## Investigation Process + +### Step 0: IR Dump Confirmation + +Generated IR shows: +```llvm +bb0: + br label %bb4 + +bb3: + %phi_1 = phi i64 [%add_8, %bb6] ; Defined in bb3 + ... + +bb4: + %phi_3 = phi i64 [%phi_1, %bb0], [%add_8, %bb7] ; Uses %phi_1 from bb0! +``` + +MIR shows correct structure: +- bb0: `ValueId(1) = const 0` +- bb3: `ValueId(1) = PHI [bb6, ValueId(8)]` +- bb4: `ValueId(3) = PHI [(bb0, ValueId(2)), (bb7, ValueId(8))]` + +**Key observation**: Same ValueId(1) used in different blocks is normal (SSA allows this), but LLVM builder is confusing them! + +### Step 1: VMAP Trace Analysis + +VMAP trace showed: +``` +[vmap/id] Pass A bb0 snapshot id=139925440649984 keys=[0, 1] +[vmap/id] Pass A bb0 snapshot id=139925440650112 keys=[1, 2, 3] +``` + +Two different bb0 snapshots! But only one bb0 in `main` function. + +### Root Cause Discovery + +Checked all functions in MIR JSON: +```json +{ + "name": "condition_fn", + "blocks": [0] +}, +{ + "name": "main", + "blocks": [0, 3, 4, 5, 6, 7] +} +``` + +**BINGO**: Two functions have bb0! +- `condition_fn` has bb0 (first snapshot) +- `main` has bb0 (second snapshot, overwrites first) + +### Root Cause + +**`block_end_values` uses `block_id` as key instead of `(function_name, block_id)` tuple** + +**Problem flow**: +1. Process `condition_fn` bb0 → `block_end_values[0] = {0: ..., 1: ...}` +2. Process `main` bb0 → `block_end_values[0] = {1: ..., 2: ..., 3: ...}` (OVERWRITES!) +3. Process `main` bb4's PHI → resolve incoming ValueId(1) from bb0 +4. `resolve_incoming(pred_block_id=0, value_id=1)` looks up `block_end_values[0][1]` +5. Gets `main`'s bb0 ValueId(1) (which is copy of PHI) instead of const 0! + +**Result**: bb4's PHI gets `%phi_1` (bb3's PHI) instead of `i64 0` (bb0's const), causing domination error. + +## Solution Design + +### Change 1: Tuple-Key block_end_values + +**Old**: +```python +block_end_values: Dict[int, Dict[int, ir.Value]] = {} +block_end_values[bid] = snap +``` + +**New**: +```python +block_end_values: Dict[Tuple[str, int], Dict[int, ir.Value]] = {} +block_end_values[(func_name, bid)] = snap +``` + +### Change 2: Thread function name through call chain + +**Files to modify**: +1. `llvm_builder.py` - Type annotation +2. `function_lower.py` - Pass `func.name` to `lower_blocks` +3. `block_lower.py` - Accept `func_name` parameter, use tuple keys +4. `resolver.py` - Update `resolve_incoming` to accept `func_name` +5. `phi_wiring/wiring.py` - Update `wire_incomings` to use tuple keys + +### Change 3: Verifier (STRICT mode) + +Add collision detection: +```python +if key in block_end_values and STRICT: + existing_func = find_function_for_key(key) + if existing_func != current_func: + raise RuntimeError( + f"Block ID collision: bb{bid} exists in both " + f"{existing_func} and {current_func}" + ) +``` + +## Acceptance Criteria + +1. **Pattern 1** (Phase 132): Still passes (regression test) +2. **Case C** (Pattern 5): Builds and executes correctly +3. **VM/LLVM parity**: Both produce same result +4. **STRICT mode**: No collisions, no fallback to 0 + +## Implementation Status + +- [x] Root cause identified +- [x] Solution designed +- [ ] Tuple-key implementation +- [ ] STRICT verifier +- [ ] Acceptance testing +- [ ] Documentation update + +## Related Documents + +- Task: `/home/tomoaki/git/hakorune-selfhost/CURRENT_TASK.md` (Phase 132-P0) +- Inventory: `/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/phase131-3-llvm-lowering-inventory.md` + +## Key Insight + +**"Same block ID in different functions is a FEATURE, not a bug"** + +MIR reuses block IDs across functions (bb0 is common entry block). The LLVM builder MUST namespace block_end_values by function to avoid collisions. + +This is a **Box-First principle violation**: `block_end_values` should have been scoped per-function from the start (encapsulation boundary). diff --git a/docs/development/current/main/investigations/phase132-p0-tuple-key-implementation.md b/docs/development/current/main/investigations/phase132-p0-tuple-key-implementation.md new file mode 100644 index 00000000..092e4490 --- /dev/null +++ b/docs/development/current/main/investigations/phase132-p0-tuple-key-implementation.md @@ -0,0 +1,180 @@ +# Phase 132-P0: block_end_values Tuple-Key Implementation + +**Date**: 2025-12-15 +**Status**: ✅ Implementation Complete +**Related**: phase132-p0-case-c-root-cause.md + +## Summary + +Implemented tuple-key `(func_name, block_id)` for `block_end_values` to prevent cross-function block ID collisions in LLVM backend. + +## Root Cause (from investigation) + +```python +# ❌ Before: Block ID collision across functions +block_end_values: Dict[int, Dict[int, ir.Value]] +# main:bb0 and condition_fn:bb0 collide! + +# ✅ After: Function-scoped keys +block_end_values: Dict[Tuple[str, int], Dict[int, ir.Value]] +# ("main", 0) and ("condition_fn", 0) are distinct +``` + +## Implementation (5 files modified) + +### 1. `src/llvm_py/llvm_builder.py` (Type annotation) +```python +# Line 116: Updated type annotation +self.block_end_values: Dict[Tuple[str, int], Dict[int, ir.Value]] = {} +``` + +### 2. `src/llvm_py/builders/function_lower.py` (Pass func_name) +```python +# Line 303: Pass func_name to lower_blocks +_lower_blocks(builder, func, block_by_id, order, loop_plan, func_name=name) + +# Line 308: Pass func_name to resolve_jump_only_snapshots +_resolve_jump_only_snapshots(builder, block_by_id, func_name=name) + +# Line 333: Pass func_name to finalize_phis +_finalize_phis(builder, func_name=name) +``` + +### 3. `src/llvm_py/builders/block_lower.py` (Tuple-key usage) +```python +# Line 184: Accept func_name parameter +def lower_blocks(..., func_name: str = "unknown"): + +# Line 61: Accept func_name parameter +def resolve_jump_only_snapshots(..., func_name: str = "unknown"): + +# Line 118-119: Use tuple-key for read +if (func_name, bid) in builder.block_end_values: + snapshot = builder.block_end_values[(func_name, bid)] + +# Line 177: Use tuple-key for write (Pass B) +builder.block_end_values[(func_name, bid)] = snapshot + +# Line 529: Use tuple-key for write (Pass A) +builder.block_end_values[(func_name, bid)] = snap +``` + +### 4. `src/llvm_py/resolver.py` (resolve_incoming) +```python +# Line 127: Accept func_name parameter +def resolve_incoming(self, pred_block_id: int, value_id: int, func_name: str = "unknown"): + +# Line 143: Use tuple-key for snapshot lookup +snapshot = self.block_end_values.get((func_name, pred_block_id), {}) +``` + +### 5. `src/llvm_py/phi_wiring/wiring.py` (PHI wiring) +```python +# Line 242: Accept func_name parameter in finalize_phis +def finalize_phis(builder, func_name: str = "unknown"): + +# Line 140: Accept func_name parameter in wire_incomings +def wire_incomings(builder, ..., func_name: str = "unknown"): + +# Line 155: Use tuple-key for PHI lookup +cur = (snap.get((func_name, int(block_id)), {}) or {}).get(int(dst_vid)) + +# Line 219: Pass func_name to resolve_incoming +val = builder.resolver.resolve_incoming(pred_match, vs, func_name=func_name) + +# Line 256: Pass func_name to wire_incomings +wired = wire_incomings(builder, ..., func_name=func_name) +``` + +## Testing Results + +### ✅ Pattern 1 (Phase 132 regression check) +```bash +# Test file: /tmp/p1_return_i.hako +static box Main { + main() { + local i = 0 + loop(i < 3) { i = i + 1 } + return i + } +} + +# VM Result: RC: 3 ✅ +# LLVM Result: Result: 3 ✅ (without STRICT mode) +# LLVM STRICT: ValueId collision error (separate issue) +``` + +**Status**: ✅ No regression - Pattern 1 still works correctly + +### ⚠️ Case C (Pattern 5) - Dominance Error Persists +```bash +# Test file: apps/tests/llvm_stage3_loop_only.hako +# VM Result: Result: 3 ✅ +# LLVM Result: PHI dominance error ❌ + +Error: Instruction does not dominate all uses! + %phi_1 = phi i64 [ %add_8, %bb6 ] + %phi_3 = phi i64 [ %phi_1, %bb0 ], [ %add_8, %bb7 ] +``` + +**Analysis**: The dominance error is NOT caused by block_end_values collision. +It's a different issue related to PHI node placement and control flow structure. + +### Verification Logs +```bash +# Pass B resolution working correctly: +[vmap/resolve/passB] Resolving 2 jump-only blocks: [6, 7] +[vmap/resolve/passB] bb6 is jump-only, resolving from pred bb5 +[vmap/resolve/passB] bb5 is normal block with snapshot (5 values) +[vmap/resolve/passB] bb6 resolved from bb5: 5 values +[vmap/resolve/passB] ✅ bb6 final snapshot: 5 values, keys=[3, 7, 8, 9, 10] +``` + +## Design Principles Applied + +### Box-First (SSOT) +- Each function is an independent Box +- `block_end_values` keys are scoped to function +- `(func_name, block_id)` is the SSOT identifier + +### Fail-Fast +- STRICT mode detects collisions immediately +- Updated error messages include `func_name` context + +## Conclusion + +### ✅ Implementation Complete +- All 5 files updated with tuple-key logic +- Type annotations consistent +- Function signatures updated +- All call sites pass `func_name` + +### ✅ Regression Prevention +- Pattern 1 still works correctly +- VM/LLVM parity maintained for simple cases + +### ⚠️ Case C Needs Further Investigation +The dominance error in Case C is **not fixed** by this change. +**Root cause**: Different issue - likely related to: +- PHI node placement in complex control flow (break/continue) +- Block ordering or dominator tree structure +- Need separate investigation (Phase 132-P1?) + +## Next Steps + +1. **Accept tuple-key fix**: Merge this implementation (prevents future collisions) +2. **Investigate Case C separately**: Create Phase 132-P1 for dominance error +3. **Add tuple-key validation**: Optional STRICT check that all lookups use tuple-key + +## Files Modified + +1. `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/llvm_builder.py` +2. `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/builders/function_lower.py` +3. `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/builders/block_lower.py` +4. `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/resolver.py` +5. `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/phi_wiring/wiring.py` + +## References + +- Phase 132 Inventory: `docs/development/current/main/phase131-3-llvm-lowering-inventory.md` +- Root Cause Analysis: `docs/development/current/main/investigations/phase132-p0-case-c-root-cause.md` diff --git a/src/llvm_py/builders/block_lower.py b/src/llvm_py/builders/block_lower.py index 92754208..15e6bab6 100644 --- a/src/llvm_py/builders/block_lower.py +++ b/src/llvm_py/builders/block_lower.py @@ -58,8 +58,9 @@ class DeferredTerminator(NamedTuple): vmap_snapshot: Dict[int, ir.Value] -def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]]): +def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]], func_name: str = "unknown"): """Phase 131-14-B P0-2: Resolve jump-only block snapshots (Pass B). + Phase 132-P0: Use tuple-key (func_name, block_id) to prevent cross-function collision. This function runs AFTER all blocks have been lowered (Pass A) but BEFORE PHI finalization. It resolves snapshots for jump-only blocks by following @@ -68,6 +69,9 @@ def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]]) Uses path compression to efficiently handle chains of jump-only blocks. SSOT: Snapshots are based on CFG structure, not processing order. + + Args: + func_name: Function name for tuple-key (func_name, block_id) in block_end_values """ import sys @@ -110,8 +114,9 @@ def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]]) return resolved[bid] # Normal block - already has snapshot from Pass A - if bid in builder.block_end_values: - snapshot = builder.block_end_values[bid] + # Phase 132-P0: Use tuple-key (func_name, block_id) + if (func_name, bid) in builder.block_end_values: + snapshot = builder.block_end_values[(func_name, bid)] if trace_vmap: print( f"[vmap/resolve/passB] bb{bid} is normal block with snapshot " @@ -166,9 +171,10 @@ def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]]) return {} # Resolve all jump-only blocks + # Phase 132-P0: Use tuple-key (func_name, block_id) for bid in sorted(jump_only.keys()): snapshot = resolve(bid) - builder.block_end_values[bid] = snapshot + builder.block_end_values[(func_name, bid)] = snapshot if trace_vmap: print( @@ -181,11 +187,12 @@ def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]]) print(f"[vmap/resolve/passB] Pass B complete: resolved {len(jump_only)} jump-only blocks", file=sys.stderr) -def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, Any]], order: List[int], loop_plan: Dict[str, Any] | None): +def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, Any]], order: List[int], loop_plan: Dict[str, Any] | None, func_name: str = "unknown"): """Lower blocks in multi-pass to ensure PHIs are always before terminators. Phase 131-4: Multi-pass block lowering architecture Phase 131-14-B: Two-pass snapshot resolution + Phase 132-P0: Tuple-key (func_name, block_id) to prevent cross-function collision - Pass A: Lower non-terminator instructions only (terminators deferred) - jump-only blocks: record metadata only, NO snapshot resolution - Pass B: PHI finalization happens in function_lower.py @@ -194,6 +201,9 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An This ensures LLVM IR invariant: PHI nodes must be at block head before any other instructions, and terminators must be last. + + Args: + func_name: Function name for tuple-key (func_name, block_id) in block_end_values """ skipped: set[int] = set() if loop_plan is not None: @@ -506,6 +516,7 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An snap = dict(vmap_cur) # Phase 131-14-B: Only store snapshot if not deferred (snap is not None) + # Phase 132-P0: Use tuple-key (func_name, block_id) if snap is not None: try: keys = sorted(list(snap.keys())) @@ -515,7 +526,7 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An for vid in created_ids: if vid in vmap_cur: builder.def_blocks.setdefault(vid, set()).add(block_data.get("id", 0)) - builder.block_end_values[bid] = snap + builder.block_end_values[(func_name, bid)] = snap else: # Jump-only block with deferred snapshot - don't store yet if trace_vmap: diff --git a/src/llvm_py/builders/function_lower.py b/src/llvm_py/builders/function_lower.py index de40f84b..4b87449e 100644 --- a/src/llvm_py/builders/function_lower.py +++ b/src/llvm_py/builders/function_lower.py @@ -49,6 +49,12 @@ def lower_function(builder, func_data: Dict[str, Any]): builder.bb_map.clear() except Exception: builder.bb_map = {} + # Phase 132-P0: Clear phi_manager per-function to avoid ValueId collisions + try: + if hasattr(builder, 'phi_manager') and hasattr(builder.phi_manager, 'predeclared'): + builder.phi_manager.predeclared.clear() + except Exception: + pass try: # Reset resolver caches keyed by block names builder.resolver.i64_cache.clear() @@ -292,12 +298,14 @@ def lower_function(builder, func_data: Dict[str, Any]): loop_plan = None # Phase 131-4 Pass A: Lower non-terminator instructions (terminators deferred) + # Phase 132-P0: Pass func.name for tuple-key (func_name, block_id) from builders.block_lower import lower_blocks as _lower_blocks - _lower_blocks(builder, func, block_by_id, order, loop_plan) + _lower_blocks(builder, func, block_by_id, order, loop_plan, func_name=name) # Phase 131-14-B Pass B: Resolve jump-only block snapshots (BEFORE PHI finalization) + # Phase 132-P0: Pass func_name for tuple-key from builders.block_lower import resolve_jump_only_snapshots as _resolve_jump_only_snapshots - _resolve_jump_only_snapshots(builder, block_by_id) + _resolve_jump_only_snapshots(builder, block_by_id, func_name=name) # Optional: capture lowering ctx for downstream helpers try: @@ -321,7 +329,8 @@ def lower_function(builder, func_data: Dict[str, Any]): pass # Phase 131-4 Pass B (now Pass B2): Finalize PHIs (wires incoming edges) - _finalize_phis(builder) + # Phase 132-P0: Pass func_name for tuple-key resolution + _finalize_phis(builder, func_name=name) # Phase 131-4 Pass C: Lower deferred terminators (after PHIs are placed) from builders.block_lower import lower_terminators as _lower_terminators diff --git a/src/llvm_py/llvm_builder.py b/src/llvm_py/llvm_builder.py index 466889e2..acbcdbbb 100644 --- a/src/llvm_py/llvm_builder.py +++ b/src/llvm_py/llvm_builder.py @@ -112,7 +112,8 @@ class NyashLLVMBuilder: self.phi_deferrals: List[Tuple[int, int, List[Tuple[int, int]]]] = [] # Predecessor map and per-block end snapshots self.preds: Dict[int, List[int]] = {} - self.block_end_values: Dict[int, Dict[int, ir.Value]] = {} + # Phase 132-P0: Tuple-key (func_name, block_id) to prevent cross-function collision + self.block_end_values: Dict[Tuple[str, int], Dict[int, ir.Value]] = {} # Definition map: value_id -> set(block_id) where the value is defined # Used as a lightweight lifetime hint to avoid over-localization self.def_blocks: Dict[int, set] = {} @@ -340,7 +341,13 @@ class NyashLLVMBuilder: is_phi = False if not is_phi: ph0 = b0.phi(self.i64, name=f"phi_{dst0}") - self.vmap[dst0] = ph0 + # Phase 132-P0: Store PHI in phi_manager ONLY, not in global vmap + # This prevents ValueId collisions when different blocks use the same ValueId + try: + self.phi_manager.register_phi(int(bid0), int(dst0), ph0) + except Exception: + # Fallback: store in global vmap (legacy behavior) + self.vmap[dst0] = ph0 # Tag propagation: if explicit dst_type marks string or any incoming was produced as string-ish, tag dst try: dst_type0 = inst.get("dst_type") diff --git a/src/llvm_py/phi_manager.py b/src/llvm_py/phi_manager.py index de2e84e3..4b59f259 100644 --- a/src/llvm_py/phi_manager.py +++ b/src/llvm_py/phi_manager.py @@ -25,6 +25,7 @@ class PhiManager: """Filter vmap while preserving owned PHIs SSOT: PHIs in vmap are the single source of truth + Phase 132-P0: Also add PHIs from predeclared registry """ result = {} for vid, val in vmap.items(): @@ -33,6 +34,12 @@ class PhiManager: result[vid] = val else: result[vid] = val + + # Phase 132-P0: Add PHIs from predeclared that aren't in vmap yet + for (bid, vid), phi_val in self.predeclared.items(): + if bid == target_bid and vid not in result: + result[vid] = phi_val + return result def sync_protect_phis(self, target_vmap: dict, source_vmap: dict): diff --git a/src/llvm_py/phi_wiring/wiring.py b/src/llvm_py/phi_wiring/wiring.py index 6b2bd024..313ddc35 100644 --- a/src/llvm_py/phi_wiring/wiring.py +++ b/src/llvm_py/phi_wiring/wiring.py @@ -137,8 +137,13 @@ def nearest_pred_on_path( return None -def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[int, int]]): - """Wire PHI incoming edges for (block_id, dst_vid) using declared (decl_b, v_src) pairs.""" +def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[int, int]], func_name: str = "unknown"): + """Wire PHI incoming edges for (block_id, dst_vid) using declared (decl_b, v_src) pairs. + Phase 132-P0: Accept func_name for tuple-key (func_name, block_id) resolution. + + Args: + func_name: Function name for tuple-key (func_name, block_id) in block_end_values + """ bb = builder.bb_map.get(block_id) if bb is None: return @@ -146,7 +151,8 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in phi = None try: snap = getattr(builder, 'block_end_values', {}) or {} - cur = (snap.get(int(block_id), {}) or {}).get(int(dst_vid)) + # Phase 132-P0: Use tuple-key (func_name, block_id) + cur = (snap.get((func_name, int(block_id)), {}) or {}).get(int(dst_vid)) if cur is not None and hasattr(cur, 'add_incoming'): # Ensure it belongs to the same block cur_bb_name = getattr(getattr(cur, 'basic_block', None), 'name', None) @@ -210,7 +216,8 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in trace({"phi": "wire_replaced_src", "original": original_vs, "replaced": vs}) try: # P0-4: Use resolve_incoming for PHI incoming values - val = builder.resolver.resolve_incoming(pred_match, vs) + # Phase 132-P0: Pass func_name for tuple-key resolution + val = builder.resolver.resolve_incoming(pred_match, vs, func_name=func_name) trace({"phi": "wire_resolved", "vs": vs, "pred": pred_match, "val_type": type(val).__name__}) except Exception as e: trace({"phi": "wire_resolve_fail", "vs": vs, "pred": pred_match, "error": str(e)}) @@ -239,7 +246,13 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in return wired -def finalize_phis(builder): +def finalize_phis(builder, func_name: str = "unknown"): + """Finalize PHI nodes by wiring their incoming edges. + Phase 132-P0: Pass func_name for tuple-key (func_name, block_id) resolution. + + Args: + func_name: Function name for tuple-key (func_name, block_id) in block_end_values + """ total_blocks = 0 total_dsts = 0 total_wired = 0 @@ -247,7 +260,7 @@ def finalize_phis(builder): total_blocks += 1 for dst_vid, incoming in (dst_map or {}).items(): total_dsts += 1 - wired = wire_incomings(builder, int(block_id), int(dst_vid), incoming) + wired = wire_incomings(builder, int(block_id), int(dst_vid), incoming, func_name=func_name) total_wired += int(wired or 0) trace({"phi": "finalize", "block": int(block_id), "dst": int(dst_vid), "wired": int(wired or 0)}) trace({"phi": "finalize_summary", "blocks": int(total_blocks), "dsts": int(total_dsts), "incoming_wired": int(total_wired)}) diff --git a/src/llvm_py/resolver.py b/src/llvm_py/resolver.py index 00f302fb..43a7f654 100644 --- a/src/llvm_py/resolver.py +++ b/src/llvm_py/resolver.py @@ -124,8 +124,9 @@ class Resolver: # Non-STRICT: fallback to 0 return ir.Constant(ir.IntType(64), 0) - def resolve_incoming(self, pred_block_id: int, value_id: int) -> ir.Value: + def resolve_incoming(self, pred_block_id: int, value_id: int, func_name: str = "unknown") -> ir.Value: """P0-2: PHI incoming resolution (snapshot-only reference) + Phase 132-P0: Use tuple-key (func_name, block_id) to prevent cross-function collision Used for resolving PHI incoming values from predecessor blocks. Only looks at block_end_values snapshot, never vmap_cur. @@ -133,11 +134,13 @@ class Resolver: Args: pred_block_id: Predecessor block ID value_id: Value ID to resolve from predecessor + func_name: Function name for tuple-key (func_name, block_id) in block_end_values Returns: LLVM IR value (i64) """ - snapshot = self.block_end_values.get(pred_block_id, {}) + # Phase 132-P0: Use tuple-key (func_name, block_id) + snapshot = self.block_end_values.get((func_name, pred_block_id), {}) val = snapshot.get(value_id) if val is not None: return val @@ -145,7 +148,7 @@ class Resolver: # Fail-Fast: snapshot miss → structural bug if os.environ.get('NYASH_LLVM_STRICT') == '1': raise RuntimeError( - f"[LLVM_PY/STRICT] resolve_incoming: v{value_id} not in bb{pred_block_id} snapshot. " + f"[LLVM_PY/STRICT] resolve_incoming: v{value_id} not in {func_name}:bb{pred_block_id} snapshot. " f"Available: {sorted(snapshot.keys())}" ) diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs b/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs index 0d5dc849..39a14d79 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs @@ -57,7 +57,7 @@ impl MirBuilder { trace::trace().varmap("pattern1_start", &self.variable_map); // Phase 202-A: Create JoinValueSpace for unified ValueId allocation - // Pattern 1 uses Local region only (no Param region needed - no ConditionEnv) + // Pattern 1 uses Param region for boundary input slots (loop var) and Local region for temps. use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; let mut join_value_space = JoinValueSpace::new(); @@ -80,6 +80,7 @@ impl MirBuilder { use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder; use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding; use crate::mir::join_ir::lowering::carrier_info::CarrierRole; + use crate::mir::join_ir::lowering::join_value_space::PARAM_MIN; // Phase 132-Post: Extract k_exit's parameter ValueId from join_module (Box-First) let k_exit_func = join_module.require_function("k_exit", "Pattern 1"); @@ -96,7 +97,7 @@ impl MirBuilder { let boundary = JoinInlineBoundaryBuilder::new() .with_inputs( - vec![ValueId(0)], // JoinIR's main() parameter (loop variable) + vec![ValueId(PARAM_MIN)], // JoinIR's main() parameter (loop variable, Param region) vec![ctx.loop_var_id], // Host's loop variable ) .with_exit_bindings(vec![exit_binding]) // Phase 132: Enable exit PHI & variable_map update diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs b/src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs index de84bbde..f855b0cd 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs @@ -188,19 +188,11 @@ impl PatternPipelineContext { // Complex conditions (e.g., i % 2 == 1) → fallback to legacy mode if let Some(ASTNode::If { condition, .. }) = if_stmt { use crate::mir::join_ir::lowering::condition_pattern::{ - analyze_condition_pattern, normalize_comparison, ConditionPattern, + analyze_condition_capability, ConditionCapability, }; - // (a) Pattern check: must be SimpleComparison - let pattern = analyze_condition_pattern(condition); - if pattern != ConditionPattern::SimpleComparison { - // Complex condition → legacy mode (PoC lowering) - return false; - } - - // (b) Normalization check: must be normalizable - if normalize_comparison(condition).is_none() { - // Normalization failed → legacy mode + // Capability check: if-sum lowerer が扱える比較か + if analyze_condition_capability(condition) != ConditionCapability::IfSumComparable { return false; } } diff --git a/src/mir/builder/lifecycle.rs b/src/mir/builder/lifecycle.rs index 7b8f8417..f6996521 100644 --- a/src/mir/builder/lifecycle.rs +++ b/src/mir/builder/lifecycle.rs @@ -389,6 +389,11 @@ impl super::MirBuilder { // After PHI types are corrected, re-infer BinOp result types self.repropagate_binop_types(&mut function); + // Phase 84-5 guard hardening: ensure call/await results are registered in `value_types` + // before return type inference. This avoids "impossible" debug panics when the builder + // emitted a value-producing instruction without annotating its dst type. + self.annotate_missing_result_types_from_calls_and_await(&function, &module); + // Phase 131-9: Update function metadata with corrected types // MUST happen after PHI type correction above AND BinOp re-propagation function.metadata.value_types = self.value_types.clone(); @@ -600,6 +605,69 @@ impl super::MirBuilder { Ok(module) } + fn annotate_missing_result_types_from_calls_and_await( + &mut self, + function: &super::MirFunction, + module: &MirModule, + ) { + use crate::mir::definitions::Callee; + use crate::mir::MirInstruction; + + for (_bid, bb) in function.blocks.iter() { + for inst in bb.instructions.iter() { + match inst { + MirInstruction::Await { dst, future } => { + if self.value_types.contains_key(dst) { + continue; + } + let inferred = match self.value_types.get(future) { + Some(MirType::Future(inner)) => (**inner).clone(), + _ => MirType::Unknown, + }; + self.value_types.insert(*dst, inferred); + } + MirInstruction::Call { + dst: Some(dst), + callee: Some(callee), + .. + } => { + if self.value_types.contains_key(dst) { + continue; + } + let inferred = match callee { + Callee::Global(name) => module + .functions + .get(name) + .map(|f| f.signature.return_type.clone()) + .or_else(|| { + crate::mir::builder::types::annotation::annotate_from_function( + self, *dst, name, + ); + self.value_types.get(dst).cloned() + }) + .unwrap_or(MirType::Unknown), + Callee::Constructor { box_type } => { + let ret = MirType::Box(box_type.clone()); + self.value_origin_newbox.insert(*dst, box_type.clone()); + ret + } + _ => MirType::Unknown, + }; + self.value_types.insert(*dst, inferred); + } + MirInstruction::ExternCall { dst: Some(dst), .. } + | MirInstruction::BoxCall { dst: Some(dst), .. } + | MirInstruction::PluginInvoke { dst: Some(dst), .. } => { + if !self.value_types.contains_key(dst) { + self.value_types.insert(*dst, MirType::Unknown); + } + } + _ => {} + } + } + } + } + // Phase 131-11-E: Re-propagate BinOp result types after PHI resolution // This fixes cases where BinOp instructions were created before PHI types were known fn repropagate_binop_types(&mut self, function: &mut super::MirFunction) { diff --git a/src/mir/builder/stmts.rs b/src/mir/builder/stmts.rs index 1d61ba1e..622af497 100644 --- a/src/mir/builder/stmts.rs +++ b/src/mir/builder/stmts.rs @@ -1,4 +1,4 @@ -use super::{Effect, EffectMask, MirInstruction, ValueId}; +use super::{Effect, EffectMask, MirInstruction, MirType, ValueId}; use crate::ast::{ASTNode, CallExpr}; use crate::mir::utils::is_current_block_terminated; use crate::mir::TypeOpKind; @@ -424,6 +424,10 @@ impl super::MirBuilder { args: arg_vals, effects: crate::mir::effect::EffectMask::PURE.add(crate::mir::effect::Effect::Io), })?; + // Future spawn returns a Future; the inner type is not statically known here. + // Register at least Future to avoid later fail-fast type inference panics. + self.value_types + .insert(future_id, MirType::Future(Box::new(MirType::Unknown))); self.variable_map.insert(variable.clone(), future_id); if let Some(reg) = self.current_slot_registry.as_mut() { reg.ensure_slot(&variable, None); @@ -436,6 +440,13 @@ impl super::MirBuilder { dst: future_id, value: expression_value, })?; + let inner = self + .value_types + .get(&expression_value) + .cloned() + .unwrap_or(MirType::Unknown); + self.value_types + .insert(future_id, MirType::Future(Box::new(inner))); self.variable_map.insert(variable.clone(), future_id); if let Some(reg) = self.current_slot_registry.as_mut() { reg.ensure_slot(&variable, None); @@ -455,6 +466,11 @@ impl super::MirBuilder { dst: result_id, future: future_value, })?; + let result_type = match self.value_types.get(&future_value) { + Some(MirType::Future(inner)) => (**inner).clone(), + _ => MirType::Unknown, + }; + self.value_types.insert(result_id, result_type); self.emit_instruction(MirInstruction::Safepoint)?; Ok(result_id) } diff --git a/src/mir/join_ir/lowering/condition_pattern.rs b/src/mir/join_ir/lowering/condition_pattern.rs index c19f896b..608ceb11 100644 --- a/src/mir/join_ir/lowering/condition_pattern.rs +++ b/src/mir/join_ir/lowering/condition_pattern.rs @@ -14,7 +14,8 @@ //! ## 解決策 //! //! ConditionPatternBox を導入し、if条件が「単純比較」かどうかを判定する。 -//! AST-based lowerer は単純比較のみ処理可能とし、複雑条件はlegacy modeへフォールバック。 +//! ただし「単純/複雑/legacy」の語彙は混線しやすいので、routing 用には +//! `ConditionCapability` を使って「どの経路で扱うか」を明示する。 //! //! Phase 222: 左右反転(literal on left → var on left)と変数同士の比較をサポート。 //! @@ -35,6 +36,64 @@ use crate::ast::{ASTNode, BinaryOperator, LiteralValue}; use crate::mir::CompareOp; +/// ConditionCapability: 条件式をどの戦略で扱えるか(routing 用) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ConditionCapability { + /// Pattern3 if-sum AST-based lowerer で比較として扱える + IfSumComparable, + /// 上記以外(caller が別経路を選ぶ) + Unsupported, +} + +fn is_if_sum_value_expr(expr: &ASTNode) -> bool { + match expr { + ASTNode::Variable { .. } | ASTNode::Literal { .. } => true, + ASTNode::BinaryOp { + operator, left, right, .. + } => matches!( + operator, + BinaryOperator::Add + | BinaryOperator::Subtract + | BinaryOperator::Multiply + | BinaryOperator::Divide + | BinaryOperator::Modulo + ) && is_if_sum_value_expr(left.as_ref()) + && is_if_sum_value_expr(right.as_ref()), + _ => false, + } +} + +/// 条件式の“能力”を判定(routing のための入口) +pub fn analyze_condition_capability(cond: &ASTNode) -> ConditionCapability { + match cond { + ASTNode::BinaryOp { + operator, + left, + right, + .. + } => { + let is_comparison = matches!( + operator, + BinaryOperator::Equal + | BinaryOperator::NotEqual + | BinaryOperator::Less + | BinaryOperator::Greater + | BinaryOperator::LessEqual + | BinaryOperator::GreaterEqual + ); + if !is_comparison { + return ConditionCapability::Unsupported; + } + if is_if_sum_value_expr(left.as_ref()) && is_if_sum_value_expr(right.as_ref()) { + ConditionCapability::IfSumComparable + } else { + ConditionCapability::Unsupported + } + } + _ => ConditionCapability::Unsupported, + } +} + /// if条件のパターン種別 #[derive(Debug, Clone, PartialEq, Eq)] pub enum ConditionPattern { @@ -176,8 +235,8 @@ pub fn analyze_condition_pattern(cond: &ASTNode) -> ConditionPattern { /// // i > 0 → true /// assert!(is_simple_comparison(&simple_condition)); /// -/// // i % 2 == 1 → false -/// assert!(!is_simple_comparison(&complex_condition)); +/// // i % 2 == 1 → true(Phase 242-EX-A で比較のオペランドに算術式を許可) +/// assert!(is_simple_comparison(&complex_condition)); /// ``` pub fn is_simple_comparison(cond: &ASTNode) -> bool { analyze_condition_pattern(cond) == ConditionPattern::SimpleComparison @@ -605,4 +664,51 @@ mod tests { ); assert!(is_simple_comparison(&cond)); } + + // ======================================================================== + // ConditionCapability (routing) Tests + // ======================================================================== + + #[test] + fn test_capability_if_sum_comparable_simple() { + let cond = binop(BinaryOperator::Greater, var("i"), int_lit(0)); + assert_eq!( + analyze_condition_capability(&cond), + ConditionCapability::IfSumComparable + ); + } + + #[test] + fn test_capability_if_sum_comparable_binop_operand() { + let lhs = binop(BinaryOperator::Modulo, var("i"), int_lit(2)); + let cond = binop(BinaryOperator::Equal, lhs, int_lit(1)); + assert_eq!( + analyze_condition_capability(&cond), + ConditionCapability::IfSumComparable + ); + } + + #[test] + fn test_capability_rejects_logical_and() { + let cond = binop(BinaryOperator::And, var("a"), var("b")); + assert_eq!( + analyze_condition_capability(&cond), + ConditionCapability::Unsupported + ); + } + + #[test] + fn test_capability_rejects_method_call_operand() { + let method_call = ASTNode::MethodCall { + object: Box::new(var("obj")), + method: "get".to_string(), + arguments: vec![], + span: Span::unknown(), + }; + let cond = binop(BinaryOperator::Greater, method_call, int_lit(0)); + assert_eq!( + analyze_condition_capability(&cond), + ConditionCapability::Unsupported + ); + } } diff --git a/src/mir/join_ir/lowering/inline_boundary.rs b/src/mir/join_ir/lowering/inline_boundary.rs index 406bde1d..723f890e 100644 --- a/src/mir/join_ir/lowering/inline_boundary.rs +++ b/src/mir/join_ir/lowering/inline_boundary.rs @@ -10,8 +10,9 @@ //! //! ## Design Philosophy //! -//! The JoinIR lowerer should work with **local ValueIds** (0, 1, 2, ...) without -//! knowing anything about the host function's ValueId space. This ensures: +//! The JoinIR lowerer should work with **JoinIR-side ValueIds** allocated via +//! `JoinValueSpace` (Param: 100-999, Local: 1000+) without knowing anything about +//! the host function's ValueId space. This ensures: //! //! 1. **Modularity**: JoinIR lowerers are pure transformers //! 2. **Reusability**: Same lowerer can be used in different contexts @@ -26,13 +27,13 @@ //! Host Function: //! ValueId(4) = Const 0 // i = 0 in host //! -//! JoinIR Fragment (uses local IDs 0, 1, 2, ...): -//! ValueId(0) = param // i_param (local to JoinIR) -//! ValueId(1) = Const 3 -//! ValueId(2) = Compare ... +//! JoinIR Fragment: +//! ValueId(100) = param // i_param (JoinIR Param region) +//! ValueId(1000) = Const 3 +//! ValueId(1001) = Compare ... //! //! Boundary: -//! join_inputs: [ValueId(0)] // JoinIR's param slot +//! join_inputs: [ValueId(100)] // JoinIR's param slot (Param region) //! host_inputs: [ValueId(4)] // Host's `i` variable //! //! Merged MIR (with Copy injection): @@ -115,11 +116,11 @@ pub struct LoopExitBinding { pub struct JoinInlineBoundary { /// JoinIR-local ValueIds that act as "input slots" /// - /// These are the ValueIds used **inside** the JoinIR fragment to refer - /// to values that come from the host. They should be small sequential - /// IDs (0, 1, 2, ...) since JoinIR lowerers allocate locally. +/// These are the ValueIds used **inside** the JoinIR fragment to refer +/// to values that come from the host. They should be in the JoinValueSpace +/// Param region (100-999). (They are typically allocated sequentially.) /// - /// Example: For a loop variable `i`, JoinIR uses ValueId(0) as the parameter. + /// Example: For a loop variable `i`, JoinIR uses ValueId(100) as the parameter. pub join_inputs: Vec, /// Host-function ValueIds that provide the input values diff --git a/src/mir/join_ir/lowering/loop_pattern_router.rs b/src/mir/join_ir/lowering/loop_pattern_router.rs index 2d82fecd..fdae7f02 100644 --- a/src/mir/join_ir/lowering/loop_pattern_router.rs +++ b/src/mir/join_ir/lowering/loop_pattern_router.rs @@ -40,7 +40,7 @@ //! # Integration Points //! //! Called from: -//! - `loop_to_join.rs::LoopToJoinLowerer::lower_loop()` +//! - `loop_to_join::LoopToJoinLowerer::lower_loop()` //! - `loop_form_intake.rs::handle_loop_form()` use crate::mir::join_ir::JoinInst; @@ -94,7 +94,7 @@ use crate::mir::loop_form::LoopForm; /// # Integration Point /// /// This function should be called from loop lowering entry points: -/// - `loop_to_join.rs::LoopToJoinLowerer::lower_loop()` +/// - `loop_to_join::LoopToJoinLowerer::lower_loop()` /// - `loop_form_intake.rs::handle_loop_form()` /// /// # Example Usage diff --git a/src/mir/join_ir/lowering/loop_to_join.rs b/src/mir/join_ir/lowering/loop_to_join.rs deleted file mode 100644 index 22d43a77..00000000 --- a/src/mir/join_ir/lowering/loop_to_join.rs +++ /dev/null @@ -1,297 +0,0 @@ -//! Phase 31: LoopToJoinLowerer - 統一 Loop→JoinIR 変換箱 -//! -//! このモジュールは MIR の LoopForm を JoinIR に変換する統一インターフェースを提供する。 -//! -//! ## 設計思想(Phase 33-23 責務分離完了) -//! -//! - **単一エントリポイント**: `LoopToJoinLowerer::lower()` ですべてのループを処理 -//! - **責務分離**: 検証・選択・調整を専用Boxに委譲 -//! - `LoopPatternValidator`: 構造検証(180行) -//! - `LoopViewBuilder`: Lowering選択(343行) -//! - `LoopToJoinLowerer`: コーディネーター(本ファイル) -//! - **既存コード再利用**: generic_case_a の `_with_scope` 関数を内部で呼び出し -//! -//! ## 責務分離(Phase 33-9.1) -//! -//! **LoopToJoinLowerer の責務**: -//! - MirQuery/LoopFormIntake/LoopScopeShape構築 -//! - Validator/Builder呼び出し調整 -//! - Strict modeエラーハンドリング -//! -//! **非責務**: -//! - 構造検証(→ LoopPatternValidator) -//! - Lowering選択(→ LoopViewBuilder) -//! - if/else の PHI には触らない(If lowering の責務) -//! -//! ## 使用例 -//! -//! ```ignore -//! let lowerer = LoopToJoinLowerer::new(); -//! let join_module = lowerer.lower(func, &loop_form, &query)?; -//! ``` - -use crate::mir::control_form::LoopId; -use crate::mir::join_ir::lowering::loop_form_intake::intake_loop_form; -use crate::mir::join_ir::lowering::loop_pattern_validator::LoopPatternValidator; -use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; -use crate::mir::join_ir::lowering::loop_view_builder::LoopViewBuilder; -use crate::mir::join_ir::JoinModule; -use crate::mir::loop_form::LoopForm; -use crate::mir::query::MirQueryBox; -use crate::mir::MirFunction; - -/// Phase 32 L-1.2: 汎用 Case-A lowering が有効かどうか -/// -/// `NYASH_JOINIR_LOWER_GENERIC=1` の場合、関数名フィルタを外して -/// 構造ベースで Case-A ループを拾う。 -fn generic_case_a_enabled() -> bool { - crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") -} - -/// Loop→JoinIR 変換の統一箱(Phase 33-23 コーディネーター) -/// -/// Phase 31 で導入された統一インターフェース。 -/// Phase 33-23 で責務分離完了(Validator/Builder委譲)。 -pub struct LoopToJoinLowerer { - /// デバッグモード(詳細ログ出力) - debug: bool, - /// 構造検証箱(Phase 33-23) - validator: LoopPatternValidator, - /// Lowering選択箱(Phase 33-23) - builder: LoopViewBuilder, -} - -impl Default for LoopToJoinLowerer { - fn default() -> Self { - Self::new() - } -} - -impl LoopToJoinLowerer { - /// 新しい LoopToJoinLowerer を作成(Phase 33-23 Boxインスタンス化) - pub fn new() -> Self { - let debug = std::env::var("NYASH_LOOPTOJOIN_DEBUG") - .map(|v| v == "1") - .unwrap_or(false); - Self { - debug, - validator: LoopPatternValidator::new(), - builder: LoopViewBuilder::new(), - } - } - - /// MIR LoopForm を JoinIR に変換 - /// - /// # Arguments - /// - /// - `func`: MIR 関数 - /// - `loop_form`: 変換対象の LoopForm - /// - `func_name`: 関数名(オプション、ルーティング用) - /// - /// # Returns - /// - /// - `Some(JoinModule)`: 変換成功 - /// - `None`: 変換失敗(フォールバック経路へ) - pub fn lower( - &self, - func: &MirFunction, - loop_form: &LoopForm, - func_name: Option<&str>, - ) -> Option { - let strict_on = crate::config::env::joinir_strict_enabled(); - let is_minimal_target = func_name - .map(super::loop_scope_shape::is_case_a_minimal_target) - .unwrap_or(false); - if self.debug { - eprintln!( - "[LoopToJoinLowerer] lower() called for {:?}", - func_name.unwrap_or("") - ); - } - - // Phase 32 L-1.2: 早期フィルタは削除。構造チェック後に条件付きで適用する。 - - // Step 1: MirQuery を構築 - let query = MirQueryBox::new(func); - - // Step 2: LoopFormIntake を構築 - // Phase 70-2: var_classes 引数削除完了(Trio 依存ゼロ) - let intake = intake_loop_form(loop_form, &query, func)?; - - // Step 3: LoopScopeShape を構築 - // Phase 48-2: from_loop_form() で Trio を内部化(LoopExitLivenessBox 依存削除) - let scope = LoopScopeShape::from_loop_form(loop_form, &intake, &query, func_name)?; - - if self.debug { - eprintln!( - "[LoopToJoinLowerer] LoopScopeShape built: pinned={:?}, carriers={:?}, exit_live={:?}", - scope.pinned, scope.carriers, scope.exit_live - ); - } - - // Phase 32 Step 3-C: View メソッドで構造情報を取得(常に実行) - let loop_id = LoopId(0); // 単一ループの場合は 0 - let region = loop_form.to_region_view(loop_id); - let _control = loop_form.to_control_view(loop_id); - let exit_edges = loop_form.to_exit_edges(loop_id); - - // Debug: view ベースの情報をログ - if self.debug { - eprintln!( - "[LoopToJoinLowerer] Phase 32 views: func={:?} loop_id={:?} header={:?} exits={:?}", - func_name.unwrap_or(""), - loop_id, - region.header, - exit_edges.iter().map(|e| e.to).collect::>() - ); - } - - // Phase 33-23: Validator箱に検証を委譲 - if !self - .validator - .is_supported_case_a(func, ®ion, &exit_edges, &scope) - { - if self.debug { - eprintln!( - "[LoopToJoinLowerer] rejected by validator: {:?}", - func_name.unwrap_or("") - ); - } - if strict_on && is_minimal_target { - panic!( - "[joinir/loop] strict mode: validator rejected {}", - func_name.unwrap_or("") - ); - } - return None; - } - - // Phase 32 L-1.2: 環境変数で分岐 - // OFF(既定): 従来の minimal 4 本だけ - // ON: 構造だけで通す(関数名フィルタを外す) - if !generic_case_a_enabled() { - if !func_name.map_or(false, super::loop_scope_shape::is_case_a_minimal_target) { - if self.debug { - eprintln!( - "[LoopToJoinLowerer] rejected by name filter (generic disabled): {:?}", - func_name.unwrap_or("") - ); - } - if strict_on && is_minimal_target { - panic!( - "[joinir/loop] strict mode: name filter rejected {}", - func_name.unwrap_or("") - ); - } - return None; - } - } else if self.debug { - eprintln!( - "[LoopToJoinLowerer] generic Case-A enabled, allowing {:?}", - func_name.unwrap_or("") - ); - } - - // Phase 33-23: Builder箱にlowering選択を委譲 - let out = self.builder.build(scope, func_name); - if out.is_none() && strict_on && is_minimal_target { - panic!( - "[joinir/loop] strict mode: lowering failed for {}", - func_name.unwrap_or("") - ); - } - out - } - - // Phase 33-23: Validation/Lowering選択ロジックはValidator/Builderに移動 - // - is_supported_case_a_loop_view() → LoopPatternValidator::is_supported_case_a() - // - lower_with_scope() → LoopViewBuilder::build() - // - has_safe_progress() → LoopPatternValidator::validate_progress_carrier() - - // ======================================== - // Case-A helpers for specific function patterns - // ======================================== - - /// Case-A 汎用 lowerer の「Main.skip/1 用」薄いラッパー。 - /// 実際のロジックは `lower` に集約されている。 - pub fn lower_case_a_for_skip_ws( - &self, - func: &MirFunction, - loop_form: &LoopForm, - ) -> Option { - self.lower(func, loop_form, Some("Main.skip/1")) - } - - /// Case-A 汎用 lowerer の「FuncScannerBox.trim/1 用」薄いラッパー。 - /// 実際のロジックは `lower` に集約されている。 - pub fn lower_case_a_for_trim( - &self, - func: &MirFunction, - loop_form: &LoopForm, - ) -> Option { - self.lower(func, loop_form, Some("FuncScannerBox.trim/1")) - } - - /// Case-A 汎用 lowerer の「FuncScannerBox.append_defs/2 用」薄いラッパー。 - /// 実際のロジックは `lower` に集約されている。 - pub fn lower_case_a_for_append_defs( - &self, - func: &MirFunction, - loop_form: &LoopForm, - ) -> Option { - self.lower(func, loop_form, Some("FuncScannerBox.append_defs/2")) - } - - /// Case-A 汎用 lowerer の「Stage1UsingResolverBox.resolve_for_source/5 用」薄いラッパー。 - /// 実際のロジックは `lower` に集約されている。 - pub fn lower_case_a_for_stage1_resolver( - &self, - func: &MirFunction, - loop_form: &LoopForm, - ) -> Option { - self.lower( - func, - loop_form, - Some("Stage1UsingResolverBox.resolve_for_source/5"), - ) - } - - /// Case-A 汎用 lowerer の「StageBBodyExtractorBox.build_body_src/2 用」薄いラッパー。 - /// 実際のロジックは `lower` に集約されている。 - pub fn lower_case_a_for_stageb_body( - &self, - func: &MirFunction, - loop_form: &LoopForm, - ) -> Option { - self.lower( - func, - loop_form, - Some("StageBBodyExtractorBox.build_body_src/2"), - ) - } - - /// Case-A 汎用 lowerer の「StageBFuncScannerBox.scan_all_boxes/1 用」薄いラッパー。 - /// 実際のロジックは `lower` に集約されている。 - pub fn lower_case_a_for_stageb_funcscanner( - &self, - func: &MirFunction, - loop_form: &LoopForm, - ) -> Option { - self.lower( - func, - loop_form, - Some("StageBFuncScannerBox.scan_all_boxes/1"), - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_lowerer_creation() { - let lowerer = LoopToJoinLowerer::new(); - assert!(!lowerer.debug || lowerer.debug); // Just check it compiles - } -} diff --git a/src/mir/join_ir/lowering/loop_to_join/case_a_entrypoints.rs b/src/mir/join_ir/lowering/loop_to_join/case_a_entrypoints.rs new file mode 100644 index 00000000..c3c64e5f --- /dev/null +++ b/src/mir/join_ir/lowering/loop_to_join/case_a_entrypoints.rs @@ -0,0 +1,73 @@ +use super::LoopToJoinLowerer; +use crate::mir::join_ir::JoinModule; +use crate::mir::loop_form::LoopForm; +use crate::mir::MirFunction; + +impl LoopToJoinLowerer { + /// Case-A 汎用 lowerer の「Main.skip/1 用」薄いラッパー。 + pub fn lower_case_a_for_skip_ws( + &self, + func: &MirFunction, + loop_form: &LoopForm, + ) -> Option { + self.lower(func, loop_form, Some("Main.skip/1")) + } + + /// Case-A 汎用 lowerer の「FuncScannerBox.trim/1 用」薄いラッパー。 + pub fn lower_case_a_for_trim( + &self, + func: &MirFunction, + loop_form: &LoopForm, + ) -> Option { + self.lower(func, loop_form, Some("FuncScannerBox.trim/1")) + } + + /// Case-A 汎用 lowerer の「FuncScannerBox.append_defs/2 用」薄いラッパー。 + pub fn lower_case_a_for_append_defs( + &self, + func: &MirFunction, + loop_form: &LoopForm, + ) -> Option { + self.lower(func, loop_form, Some("FuncScannerBox.append_defs/2")) + } + + /// Case-A 汎用 lowerer の「Stage1UsingResolverBox.resolve_for_source/5 用」薄いラッパー。 + pub fn lower_case_a_for_stage1_resolver( + &self, + func: &MirFunction, + loop_form: &LoopForm, + ) -> Option { + self.lower( + func, + loop_form, + Some("Stage1UsingResolverBox.resolve_for_source/5"), + ) + } + + /// Case-A 汎用 lowerer の「StageBBodyExtractorBox.build_body_src/2 用」薄いラッパー。 + pub fn lower_case_a_for_stageb_body( + &self, + func: &MirFunction, + loop_form: &LoopForm, + ) -> Option { + self.lower( + func, + loop_form, + Some("StageBBodyExtractorBox.build_body_src/2"), + ) + } + + /// Case-A 汎用 lowerer の「StageBFuncScannerBox.scan_all_boxes/1 用」薄いラッパー。 + pub fn lower_case_a_for_stageb_funcscanner( + &self, + func: &MirFunction, + loop_form: &LoopForm, + ) -> Option { + self.lower( + func, + loop_form, + Some("StageBFuncScannerBox.scan_all_boxes/1"), + ) + } +} + diff --git a/src/mir/join_ir/lowering/loop_to_join/core.rs b/src/mir/join_ir/lowering/loop_to_join/core.rs new file mode 100644 index 00000000..5d641600 --- /dev/null +++ b/src/mir/join_ir/lowering/loop_to_join/core.rs @@ -0,0 +1,166 @@ +use crate::mir::control_form::LoopId; +use crate::mir::join_ir::lowering::loop_form_intake::intake_loop_form; +use crate::mir::join_ir::lowering::loop_pattern_validator::LoopPatternValidator; +use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; +use crate::mir::join_ir::lowering::loop_view_builder::LoopViewBuilder; +use crate::mir::join_ir::JoinModule; +use crate::mir::loop_form::LoopForm; +use crate::mir::query::MirQueryBox; +use crate::mir::MirFunction; + +fn generic_case_a_enabled() -> bool { + crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") +} + +/// Loop→JoinIR 変換の統一箱(coordinator) +/// +/// - MirQuery/Intake/LoopScopeShape の構築 +/// - Validator/Builder 呼び出しの調整 +/// - strict mode の fail-fast(対象関数のみ) +pub struct LoopToJoinLowerer { + debug: bool, + validator: LoopPatternValidator, + builder: LoopViewBuilder, +} + +impl Default for LoopToJoinLowerer { + fn default() -> Self { + Self::new() + } +} + +impl LoopToJoinLowerer { + pub fn new() -> Self { + let debug = std::env::var("NYASH_LOOPTOJOIN_DEBUG") + .map(|v| v == "1") + .unwrap_or(false); + Self { + debug, + validator: LoopPatternValidator::new(), + builder: LoopViewBuilder::new(), + } + } + + /// MIR LoopForm を JoinIR (JoinModule) に変換 + /// + /// - `Some(JoinModule)`: 変換成功 + /// - `None`: 未サポート(上位のフォールバックへ) + pub fn lower( + &self, + func: &MirFunction, + loop_form: &LoopForm, + func_name: Option<&str>, + ) -> Option { + let strict_on = crate::config::env::joinir_strict_enabled(); + let is_minimal_target = func_name + .map(super::super::loop_scope_shape::is_case_a_minimal_target) + .unwrap_or(false); + + if self.debug { + eprintln!( + "[LoopToJoinLowerer] lower() called for {:?}", + func_name.unwrap_or("") + ); + } + + let query = MirQueryBox::new(func); + let intake = intake_loop_form(loop_form, &query, func)?; + let scope = LoopScopeShape::from_loop_form(loop_form, &intake, &query, func_name)?; + + if self.debug { + eprintln!( + "[LoopToJoinLowerer] LoopScopeShape built: pinned={:?}, carriers={:?}, exit_live={:?}", + scope.pinned, scope.carriers, scope.exit_live + ); + } + + let loop_id = LoopId(0); + let region = loop_form.to_region_view(loop_id); + let exit_edges = loop_form.to_exit_edges(loop_id); + + if self.debug { + eprintln!( + "[LoopToJoinLowerer] views: func={:?} loop_id={:?} header={:?} exits={:?}", + func_name.unwrap_or(""), + loop_id, + region.header, + exit_edges.iter().map(|e| e.to).collect::>() + ); + } + + if !self + .validator + .is_supported_case_a(func, ®ion, &exit_edges, &scope) + { + if self.debug { + eprintln!( + "[LoopToJoinLowerer] rejected by validator: {:?}", + func_name.unwrap_or("") + ); + } + if strict_on && is_minimal_target { + panic!( + "[joinir/loop] strict mode: validator rejected {}", + func_name.unwrap_or("") + ); + } + return None; + } + + if !generic_case_a_enabled() { + if !func_name + .map_or(false, super::super::loop_scope_shape::is_case_a_minimal_target) + { + if self.debug { + eprintln!( + "[LoopToJoinLowerer] rejected by name filter (generic disabled): {:?}", + func_name.unwrap_or("") + ); + } + if strict_on && is_minimal_target { + panic!( + "[joinir/loop] strict mode: name filter rejected {}", + func_name.unwrap_or("") + ); + } + return None; + } + } else if self.debug { + eprintln!( + "[LoopToJoinLowerer] generic Case-A enabled, allowing {:?}", + func_name.unwrap_or("") + ); + } + + let out = self.builder.build(scope, func_name); + if out.is_none() && strict_on && is_minimal_target { + panic!( + "[joinir/loop] strict mode: lowering failed for {}", + func_name.unwrap_or("") + ); + } + out + } + + /// 旧コメント/ドキュメントとの整合のための別名(導線の明確化) + pub fn lower_loop( + &self, + func: &MirFunction, + loop_form: &LoopForm, + func_name: Option<&str>, + ) -> Option { + self.lower(func, loop_form, func_name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_lowerer_creation() { + let lowerer = LoopToJoinLowerer::new(); + assert!(!lowerer.debug || lowerer.debug); + } +} + diff --git a/src/mir/join_ir/lowering/loop_to_join/mod.rs b/src/mir/join_ir/lowering/loop_to_join/mod.rs new file mode 100644 index 00000000..209f4f90 --- /dev/null +++ b/src/mir/join_ir/lowering/loop_to_join/mod.rs @@ -0,0 +1,19 @@ +//! Phase 31/33-23: Loop→JoinIR lowering entry (LoopToJoinLowerer) +//! +//! このモジュールは「LoopForm を JoinIR (JoinModule) に変換する統一エントリ」を提供する。 +//! 本体ロジックは coordinator に集約し、責務を分割して保守性を上げる。 +//! +//! ## 責務境界(Box化) +//! - `LoopPatternValidator`(`../loop_pattern_validator.rs`): 構造検証(shape guard) +//! - `LoopViewBuilder`(`../loop_view_builder.rs`): lowerer 選択(routing) +//! - `LoopToJoinLowerer`(このモジュール): intake/scope 構築と strict ハンドリング(coordinator) +//! +//! ## 注意 +//! - Phase 進捗ログはここに混ぜない(現役の導線のみ)。 +//! - “とりあえず通す”フォールバックは増やさない。失敗は `None` で上位に返す。 + +mod core; +mod case_a_entrypoints; + +pub use core::LoopToJoinLowerer; + diff --git a/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs b/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs index 5589ce4e..2708d56c 100644 --- a/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs +++ b/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs @@ -34,7 +34,7 @@ use crate::mir::join_ir::lowering::condition_env::ConditionEnv; use crate::mir::join_ir::lowering::condition_lowerer::lower_value_expression; #[cfg(debug_assertions)] use crate::mir::join_ir::lowering::condition_pattern::{ - analyze_condition_pattern, ConditionPattern, + analyze_condition_capability, ConditionCapability, }; use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; use crate::mir::join_ir::{ @@ -71,11 +71,11 @@ pub fn lower_if_sum_pattern( #[cfg(debug_assertions)] if let ASTNode::If { condition, .. } = if_stmt { - let pattern = analyze_condition_pattern(condition); + let capability = analyze_condition_capability(condition); debug_assert!( - matches!(pattern, ConditionPattern::SimpleComparison), - "[if-sum] Unexpected complex condition pattern passed to AST-based lowerer: {:?}", - pattern + matches!(capability, ConditionCapability::IfSumComparable), + "[if-sum] Unsupported condition passed to AST-based lowerer: {:?}", + capability ); } diff --git a/src/mir/join_ir/lowering/simple_while_minimal.rs b/src/mir/join_ir/lowering/simple_while_minimal.rs index b4f7270f..d25ac8a2 100644 --- a/src/mir/join_ir/lowering/simple_while_minimal.rs +++ b/src/mir/join_ir/lowering/simple_while_minimal.rs @@ -82,15 +82,16 @@ use crate::mir::join_ir::{ /// # Boundary Contract /// /// This function returns a JoinModule with: -/// - **Input slot**: ValueId(0) in loop_step function represents the loop variable -/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueId(0) to host's loop var +/// - **Input slot**: main() の param(JoinValueSpace の Param region)でループ変数を受け取る +/// - **Caller responsibility**: Create JoinInlineBoundary to map that param ValueId to host's loop var pub(crate) fn lower_simple_while_minimal( _scope: LoopScopeShape, join_value_space: &mut JoinValueSpace, ) -> Option { - // Phase 202-A: Use JoinValueSpace for Local region allocation (1000+) - // This ensures no collision with Param region (100-999) used by ConditionEnv/CarrierInfo - let mut alloc_value = || join_value_space.alloc_local(); + // Phase 202-A/Phase 205: Use JoinValueSpace for Param/Local region allocation. + // - Params: boundary join_inputs (host loop var wiring) + // - Locals: constants, intermediate values + // NOTE: Avoid holding multiple closures borrowing join_value_space mutably at once. let mut join_module = JoinModule::new(); @@ -102,36 +103,34 @@ pub(crate) fn lower_simple_while_minimal( let k_exit_id = JoinFuncId::new(2); // ================================================================== - // ValueId allocation (Phase 188-Impl-3: Sequential local IDs) + // ValueId allocation // ================================================================== - // main() locals - let i_init = alloc_value(); // ValueId(0) - loop init value - let loop_result = alloc_value(); // ValueId(1) - result from loop_step - let const_0_main = alloc_value(); // ValueId(2) - return value + // main() params/locals + let i_main_param = join_value_space.alloc_param(); // boundary input (host loop var → this param) + let loop_result = join_value_space.alloc_local(); // result from loop_step + let const_0_main = join_value_space.alloc_local(); // return value - // loop_step locals - let i_param = alloc_value(); // ValueId(3) - parameter - let const_3 = alloc_value(); // ValueId(4) - comparison constant - let cmp_lt = alloc_value(); // ValueId(5) - i < 3 - let exit_cond = alloc_value(); // ValueId(6) - !(i < 3) - let const_1 = alloc_value(); // ValueId(7) - increment constant - let i_next = alloc_value(); // ValueId(8) - i + 1 + // loop_step params/locals + let i_step_param = join_value_space.alloc_param(); // loop_step parameter + let const_3 = join_value_space.alloc_local(); // comparison constant + let cmp_lt = join_value_space.alloc_local(); // i < 3 + let exit_cond = join_value_space.alloc_local(); // !(i < 3) + let const_1 = join_value_space.alloc_local(); // increment constant + let i_next = join_value_space.alloc_local(); // i + 1 - // k_exit locals - // Phase 132: i_exit receives loop variable from Jump - let i_exit = alloc_value(); // ValueId(9) - exit parameter (loop variable) + // k_exit params + let i_exit_param = join_value_space.alloc_param(); // exit parameter (loop variable) // ================================================================== // main() function // ================================================================== - // Phase 188-Impl-3: main() takes i as a parameter (boundary input) - // The host will inject a Copy instruction: i_init_local = Copy host_i - let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![i_init]); + // main() takes loop var as a param (boundary input) + let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![i_main_param]); - // result = loop_step(i_init) + // result = loop_step(i_main_param) main_func.body.push(JoinInst::Call { func: loop_step_id, - args: vec![i_init], + args: vec![i_main_param], k_next: None, dst: Some(loop_result), }); @@ -152,7 +151,7 @@ pub(crate) fn lower_simple_while_minimal( // loop_step(i) function // ================================================================== let mut loop_step_func = - JoinFunction::new(loop_step_id, "loop_step".to_string(), vec![i_param]); + JoinFunction::new(loop_step_id, "loop_step".to_string(), vec![i_step_param]); // exit_cond = !(i < 3) // Step 1: const 3 @@ -169,7 +168,7 @@ pub(crate) fn lower_simple_while_minimal( .push(JoinInst::Compute(MirLikeInst::Compare { dst: cmp_lt, op: CompareOp::Lt, - lhs: i_param, + lhs: i_step_param, rhs: const_3, })); @@ -186,7 +185,7 @@ pub(crate) fn lower_simple_while_minimal( // Pass loop variable to exit continuation for return value parity loop_step_func.body.push(JoinInst::Jump { cont: k_exit_id.as_cont(), - args: vec![i_param], + args: vec![i_step_param], cond: Some(exit_cond), }); @@ -194,7 +193,7 @@ pub(crate) fn lower_simple_while_minimal( // Phase 188-Impl-1-E: Use Print instruction loop_step_func .body - .push(JoinInst::Compute(MirLikeInst::Print { value: i_param })); + .push(JoinInst::Compute(MirLikeInst::Print { value: i_step_param })); // i_next = i + 1 // Step 1: const 1 @@ -211,7 +210,7 @@ pub(crate) fn lower_simple_while_minimal( .push(JoinInst::Compute(MirLikeInst::BinOp { dst: i_next, op: BinOpKind::Add, - lhs: i_param, + lhs: i_step_param, rhs: const_1, })); @@ -228,12 +227,12 @@ pub(crate) fn lower_simple_while_minimal( // ================================================================== // k_exit(i_exit) function - Phase 132: receives loop variable // ================================================================== - let mut k_exit_func = JoinFunction::new(k_exit_id, "k_exit".to_string(), vec![i_exit]); + let mut k_exit_func = JoinFunction::new(k_exit_id, "k_exit".to_string(), vec![i_exit_param]); // Phase 132: return i_exit (loop variable at exit) // This ensures VM/LLVM parity for `return i` after loop k_exit_func.body.push(JoinInst::Ret { - value: Some(i_exit), + value: Some(i_exit_param), }); join_module.add_function(k_exit_func);