181 lines
5.8 KiB
Markdown
181 lines
5.8 KiB
Markdown
|
|
# 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`
|