Files
hakorune/docs/development/current/main/investigations/phase131-12-p1-trace-summary.md
nyash-codex 7dfd6ff1d9 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>
2025-12-14 21:28:41 +09:00

198 lines
6.6 KiB
Markdown

# 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.