feat(llvm): Phase 131-11-H/12 - ループキャリアPHI型修正 & vmap snapshot SSOT
## Phase 131-11-H: ループキャリアPHI型修正 - PHI生成時に初期値(entry block)の型のみ使用 - backedge の値を型推論に使わない(循環依存回避) - NYASH_CARRIER_PHI_DEBUG=1 でトレース ## Phase 131-12-P0: def_blocks 登録 & STRICT エラー化 - safe_vmap_write() で PHI 上書き保護 - resolver miss を STRICT でエラー化(フォールバック 0 禁止) - def_blocks 自動登録 ## Phase 131-12-P1: vmap_cur スナップショット実装 - DeferredTerminator 構造体(block, term_ops, vmap_snapshot) - Pass A で vmap_cur をスナップショット - Pass C でスナップショット復元(try-finally) - STRICT モード assert ## 結果 - ✅ MIR PHI型: Integer(正しい) - ✅ VM: Result: 3 - ✅ vmap snapshot 機構: 動作確認 - ⚠️ LLVM: Result: 0(別のバグ、次Phase で調査) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -0,0 +1,197 @@
|
||||
# Phase 131-12-P1: vmap Object Identity Trace - Summary
|
||||
|
||||
## Status: ✅ Root Cause Identified
|
||||
|
||||
**Date**: 2025-12-14
|
||||
**Investigation**: vmap_cur object identity issue causing wrong values in LLVM backend
|
||||
**Result**: **Hypothesis C confirmed** - Object identity problem in Pass A→C temporal coupling
|
||||
|
||||
## Critical Discovery
|
||||
|
||||
### The Smoking Gun
|
||||
|
||||
```python
|
||||
# Pass A (block_lower.py line 168)
|
||||
builder._current_vmap = vmap_cur # ← Create per-block vmap
|
||||
|
||||
# Pass A (block_lower.py line 240)
|
||||
builder._deferred_terminators[bid] = (bb, term_ops) # ← Defer terminators
|
||||
|
||||
# Pass A (block_lower.py line 265)
|
||||
delattr(builder, '_current_vmap') # ← DELETE vmap_cur ❌
|
||||
|
||||
# Pass C (lower_terminators, line 282)
|
||||
# When lowering deferred terminators:
|
||||
vmap_ctx = getattr(owner, '_current_vmap', owner.vmap) # ← Falls back to global vmap! ❌
|
||||
```
|
||||
|
||||
**Problem**: Pass A deletes `_current_vmap` before Pass C runs, causing terminators to use the wrong vmap object.
|
||||
|
||||
### Trace Evidence
|
||||
|
||||
```
|
||||
bb1 block creation: vmap_ctx id=140506427346368 ← Creation
|
||||
bb1 const instruction: vmap_ctx id=140506427346368 ← Same (good)
|
||||
bb1 ret terminator: vmap_ctx id=140506427248448 ← DIFFERENT (bad!)
|
||||
^^^^^^^^^^^^^^
|
||||
This is owner.vmap, not vmap_cur!
|
||||
```
|
||||
|
||||
**Impact**: Values written to `vmap_cur` in Pass A are invisible to terminators in Pass C.
|
||||
|
||||
## The Bug Flow
|
||||
|
||||
1. **Pass A**: Create `vmap_cur` for block
|
||||
2. **Pass A**: Lower body instructions → writes go to `vmap_cur`
|
||||
3. **Pass A**: Store terminators for later
|
||||
4. **Pass A**: **Delete `_current_vmap`** ← THE BUG
|
||||
5. **Pass C**: Lower terminators → fallback to `owner.vmap` (different object!)
|
||||
6. **Result**: Terminators read from wrong vmap, missing all Pass A writes
|
||||
|
||||
## Proof: Per-Block vs Global vmap
|
||||
|
||||
### Expected (Per-Block Context)
|
||||
```python
|
||||
vmap_cur = {...} # Block-local SSA values
|
||||
builder._current_vmap = vmap_cur
|
||||
# All instructions in this block use the SAME object
|
||||
```
|
||||
|
||||
### Actual (Broken State)
|
||||
```python
|
||||
vmap_cur = {...} # Block-local SSA values
|
||||
builder._current_vmap = vmap_cur # Pass A body instructions use this
|
||||
|
||||
# Pass A ends
|
||||
delattr(builder, '_current_vmap') # DELETED!
|
||||
|
||||
# Pass C starts
|
||||
vmap_ctx = owner.vmap # Falls back to GLOBAL vmap (different object!)
|
||||
# Terminators see different data than body instructions! ❌
|
||||
```
|
||||
|
||||
## Fix Options (Recommended: Option 3)
|
||||
|
||||
### Option 1: Don't Delete Until Pass C Completes
|
||||
- Quick fix but creates temporal coupling
|
||||
- Harder to reason about state lifetime
|
||||
|
||||
### Option 2: Read from block_end_values SSOT
|
||||
- Good: Uses snapshot as source of truth
|
||||
- Issue: Requires restoring to builder state
|
||||
|
||||
### Option 3: Store vmap_cur in Deferred Data (RECOMMENDED)
|
||||
```python
|
||||
# Pass A (line 240)
|
||||
builder._deferred_terminators[bid] = (bb, term_ops, vmap_cur) # ← Add vmap_cur
|
||||
|
||||
# Pass C (line 282)
|
||||
for bid, (bb, term_ops, vmap_ctx) in deferred.items():
|
||||
builder._current_vmap = vmap_ctx # ← Restore exact context
|
||||
# Lower terminators with correct vmap
|
||||
```
|
||||
|
||||
**Why Option 3?**
|
||||
- Explicit ownership: vmap_cur is passed through deferred tuple
|
||||
- No temporal coupling: Pass C gets exact context from Pass A
|
||||
- SSOT principle: One source of vmap per block
|
||||
- Fail-Fast: Type error if tuple structure changes
|
||||
|
||||
## Architecture Impact
|
||||
|
||||
### Current Problem
|
||||
- **Temporal Coupling**: Pass C depends on Pass A's ephemeral state
|
||||
- **Silent Fallback**: Wrong vmap used without error
|
||||
- **Hidden Sharing**: Global vmap shared across blocks
|
||||
|
||||
### Fixed Architecture (Box-First)
|
||||
```
|
||||
Pass A: Create vmap_cur (per-block "box")
|
||||
↓
|
||||
Store in deferred tuple (explicit ownership transfer)
|
||||
↓
|
||||
Pass C: Restore vmap_cur from tuple (unpack "box")
|
||||
↓
|
||||
Use exact same object (SSOT)
|
||||
```
|
||||
|
||||
**Aligns with CLAUDE.md principles**:
|
||||
- ✅ Box-First: vmap_cur is a "box" passed between passes
|
||||
- ✅ SSOT: One vmap per block, explicit transfer
|
||||
- ✅ Fail-Fast: Type error if deferred tuple changes
|
||||
|
||||
## Test Commands
|
||||
|
||||
### Verify Fix
|
||||
```bash
|
||||
# Before fix: Shows different IDs for terminator
|
||||
NYASH_LLVM_VMAP_TRACE=1 NYASH_LLVM_USE_HARNESS=1 \
|
||||
./target/release/hakorune --backend llvm apps/tests/llvm_stage3_loop_only.hako 2>&1 | \
|
||||
grep "\[vmap/id\]"
|
||||
|
||||
# After fix: Should show SAME ID throughout block
|
||||
```
|
||||
|
||||
### Full Verification
|
||||
```bash
|
||||
# Check full execution
|
||||
NYASH_LLVM_VMAP_TRACE=1 NYASH_LLVM_USE_HARNESS=1 \
|
||||
./target/release/hakorune --backend llvm apps/tests/llvm_stage3_loop_only.hako
|
||||
|
||||
# Expected: Result: 3 (matching VM)
|
||||
```
|
||||
|
||||
## Files Modified
|
||||
|
||||
### Trace Implementation (Phase 131-12-P1)
|
||||
- `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/builders/block_lower.py`
|
||||
- `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/builders/instruction_lower.py`
|
||||
- `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/instructions/const.py`
|
||||
- `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/instructions/copy.py`
|
||||
- `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/instructions/binop.py`
|
||||
- `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/utils/values.py`
|
||||
|
||||
### Fix Target (Next Phase)
|
||||
- `/home/tomoaki/git/hakorune-selfhost/src/llvm_py/builders/block_lower.py` (Option 3)
|
||||
|
||||
## Related Documents
|
||||
|
||||
- Investigation: `/docs/development/current/main/investigations/phase131-12-case-c-llvm-wrong-result.md`
|
||||
- Detailed Analysis: `/docs/development/current/main/investigations/phase131-12-p1-vmap-identity-analysis.md`
|
||||
- LLVM Inventory: `/docs/development/current/main/phase131-3-llvm-lowering-inventory.md`
|
||||
- Environment Variables: `/docs/reference/environment-variables.md`
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Implement Option 3 fix** (store vmap_cur in deferred tuple)
|
||||
2. **Add Fail-Fast check** in instruction_lower.py (detect missing _current_vmap)
|
||||
3. **Verify with trace** (consistent IDs across Pass A→C)
|
||||
4. **Run full test suite** (ensure VM/LLVM parity)
|
||||
5. **Document pattern** (for future multi-pass architectures)
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
### Box-First Principle Application
|
||||
- Mutable builder state (`_current_vmap`) should be **explicitly passed** through phases
|
||||
- Don't rely on `getattr` fallbacks - they hide bugs
|
||||
- Per-block context is a "box" - treat it as first-class data
|
||||
|
||||
### Fail-Fast Opportunity
|
||||
```python
|
||||
# BEFORE (silent fallback)
|
||||
vmap_ctx = getattr(owner, '_current_vmap', owner.vmap) # Wrong vmap silently used
|
||||
|
||||
# AFTER (fail-fast)
|
||||
vmap_ctx = getattr(owner, '_current_vmap', None)
|
||||
if vmap_ctx is None:
|
||||
raise RuntimeError("Pass A/C timing bug: _current_vmap not set")
|
||||
```
|
||||
|
||||
### SSOT Enforcement
|
||||
- `block_end_values` is snapshot SSOT
|
||||
- `_current_vmap` is working buffer
|
||||
- Pass C should **restore** working buffer from SSOT or deferred data
|
||||
|
||||
---
|
||||
|
||||
**Investigation Complete**: Root cause identified with high confidence. Ready for fix implementation.
|
||||
Reference in New Issue
Block a user