feat(llvm): Phase 131-13/14 - MIR JSON順序修正 & 2パスsnapshot解決

## Phase 131-13: MIR JSON 命令順序修正
- copy 遅延ロジック削除(~80行)
- MIR の def→use 順序をそのまま出力(SSOT)
- PHI 先頭集約のみ維持

## Phase 131-14: jump-only block 2パス snapshot 解決
- Pass A: jump-only block はメタ記録のみ
- Pass B: resolve_jump_only_snapshots() で CFG ベース解決
- path compression で連鎖を効率的に解決
- サイクル検出で Fail-Fast

## 結果
-  STRICT モードでエラーなし
-  bb7 が bb5 の snapshot を正しく継承
-  ループが正しく動作(1, 2 出力確認)
- ⚠️ print/concat で segfault(別問題、次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:
nyash-codex
2025-12-15 00:39:43 +09:00
parent eb70dfc5bb
commit 7f57a1bb05
7 changed files with 559 additions and 113 deletions

View File

@ -0,0 +1,189 @@
# Phase 131-13: MIR JSON Instruction Order Fix - COMPLETED ✅
Status: **RESOLVED** (2025-12-14)
Scope: Rust 側の `MIR → JSON (harness)` 出力で、ブロック内の命令順序が崩れて Python LLVM backend が Fail-Fast する問題。
Related:
- SSOT (LLVM棚卸し): `docs/development/current/main/phase131-3-llvm-lowering-inventory.md`
- Case C: `docs/development/current/main/phase131-11-case-c-summary.md`
## Problem Summary
**Issue**: LLVM Stage-3 Case C (loop control) failed with undefined value error
- Test: `apps/tests/llvm_stage3_loop_only.hako`
- Symptom: `binop dst=19 lhs=17 rhs=18` used undefined v17 and v18
- Root cause: JSON emitter reordered instructions, breaking def-use chain
### Original JSON Order (Broken)
```json
{
"id": 3,
"instructions": [
{"op": "const", "dst": 16},
{"op": "binop", "dst": 19, "lhs": 17, "rhs": 18}, // ❌ Use v17, v18 before def
{"op": "copy", "dst": 17, "src": 16}, // ⚠️ Define v17 AFTER use
{"op": "copy", "dst": 18, "src": 1}, // ⚠️ Define v18 AFTER use
{"op": "copy", "dst": 20, "src": 19}
]
}
```
## Root Cause Analysis
`src/runner/mir_json_emit.rs` が "use-before-def copy 回避" を目的に **copy の遅延/再配置**を行っており、
依存copy dst を参照する binop 等)まで含めた正しいスケジューリングになっていなかった。
この層JSON emitterは optimizer/scheduler ではないため、順序修正を試みるより **Fail-Fast で upstream を炙り出す**のが筋。
### Responsible Code (Lines 193-266, 652-710)
```rust
// Pre-scan: collect values defined anywhere in this block (to delay use-before-def copies)
let mut block_defines: std::collections::HashSet<u32> = ...;
let mut emitted_defs: std::collections::HashSet<u32> = ...;
let mut delayed_copies: Vec<(u32, u32)> = Vec::new();
// Delay copies if source will be defined later
if block_defines.contains(&s) && !emitted_defs.contains(&s) {
delayed_copies.push((d, s)); // ❌ Reordering!
}
// Emit delayed copies after sources should be available
for (d, s) in delayed_copies {
insts.push(json!({"op":"copy","dst": d, "src": s}));
}
```
## Solution: SSOT Principle ✅
**Box Theory Diagnosis**: Responsibility boundary violation
- **JSON Emitter responsibility**: Output MIR instructions in original order
- **Builder/Optimizer/Verifier responsibility**: Ensure correct def-use order upstream
### SSOT: 「順序を直す場所」を固定する
- ✅ JSON emitter は "順序を変えない"MIR の命令列をそのまま出力する)
- ✅ もし MIR 自体に use-before-def があるなら、builder/optimizer/verifier 側で直す(またはそこで Fail-Fast
### Fix Implementation
**P0-1: Remove all reordering logic** (except PHI consolidation)
```rust
// Phase 131-13: Emit all instructions in MIR order (SSOT principle)
// No reordering except PHI consolidation at block start (LLVM constraint)
// Step 1: Emit all PHI instructions first (LLVM requirement)
for inst in &bb.instructions {
if let I::Phi { ... } => { insts.push(phi_inst); }
}
// Step 2: Emit all non-PHI instructions in MIR order (no reordering!)
for inst in &bb.instructions {
match inst {
I::Phi { .. } => continue, // Already emitted
I::Copy { dst, src } => {
insts.push(json!({"op":"copy","dst": dst.as_u32(), "src": src.as_u32()}));
}
// ... other instructions in order
}
}
```
### Emit 規約 (Updated)
- ✅ PHI は block の先頭LLVM 制約。JSON 側で先頭に集約済み。
- ✅ 非PHIは MIR の順序を保持して出力する(並べ替えない)。
- ✅ Terminator は最後。
**Changes**:
- ✅ Removed `block_defines` HashSet
- ✅ Removed `emitted_defs` HashSet
- ✅ Removed `delayed_copies` Vec
- ✅ Removed all `emitted_defs.insert()` calls
- ✅ Removed delayed copy emission loop
- ✅ Applied fix to both `emit_mir_json_for_harness` (lib) and `emit_mir_json_for_harness_bin` (bin)
### Fixed JSON Order ✅
```json
{
"id": 3,
"instructions": [
{"op": "phi", "dst": 1},
{"op": "const", "dst": 16},
{"op": "copy", "dst": 17, "src": 16}, // ✅ Define v17 first
{"op": "copy", "dst": 18, "src": 1}, // ✅ Define v18 second
{"op": "binop", "dst": 19, "lhs": 17, "rhs": 18}, // ✅ Use v17, v18 after def
{"op": "copy", "dst": 20, "src": 19},
{"op": "mir_call", ...},
{"op": "const", "dst": 21},
{"op": "ret"}
]
}
```
## Verification ✅
### Test Execution
```bash
# Generate fixed JSON
NYASH_DISABLE_PLUGINS=1 ./target/release/hakorune --backend mir \
--emit-mir-json /tmp/fixed.json apps/tests/llvm_stage3_loop_only.hako
# Output: MIR JSON written: /tmp/fixed.json
# Verify instruction order
cat /tmp/fixed.json | jq '.functions[] | select(.name == "main") | .blocks[1].instructions'
# ✅ Correct: copy dst=17, copy dst=18, binop lhs=17 rhs=18
```
### Build Status
```bash
cargo build --release
# ✅ 0 errors, 0 warnings
```
## Done 条件 ✅
- ✅ Case C の JSON 出力で def→use 順序が正しい
- ✅ LLVM harness が正しく型解決できるuse-before-def エラーなし)
- ✅ ビルド成功0 errors, 0 warnings
## Fail-Fast チェックFuture Work
JSON emit 時に、同一ブロック内の use-before-def を検出したら明示エラーにする("直そうとしない")。
**P1: Add use-before-def detector** (NYASH_MIR_STRICT mode) - TODO
```rust
fn check_use_before_def(block: &BasicBlock) -> Result<(), String> {
let mut defined: HashSet<ValueId> = HashSet::new();
for inst in &block.instructions {
// Check all operands are defined
for operand in inst.operands() {
if !defined.contains(&operand) && !is_phi(inst) && !is_block_param(operand) {
if strict_mode() {
return Err(format!("Use-before-def: v{}", operand.as_u32()));
} else {
eprintln!("[WARN] Use-before-def: v{}", operand.as_u32());
}
}
}
if let Some(dst) = inst.dst() {
defined.insert(dst);
}
}
Ok(())
}
```
## Files Modified
- `src/runner/mir_json_emit.rs`: Core fix (removed ~80 lines of reordering logic)
## Box Theory Insights
**Fail-Fast Principle**: Instead of hiding errors with reordering, expose them early
- ❌ Old approach: Emitter tries to fix broken MIR (scheduler role)
- ✅ New approach: Emitter outputs MIR as-is, builder ensures correctness
**SSOT Boundary**:
- **Upstream** (builder.rs): Responsible for def-use order
- **Emitter** (mir_json_emit.rs): Responsible for faithful output
- **Downstream** (LLVM harness): Expects correct order from JSON

View File

@ -211,6 +211,11 @@ VM and MIR look correct, but LLVM output does not match expected result for Case
- `return counter` (no string concat) - `return counter` (no string concat)
- `print(counter)` (no `"Result: " + ...`) - `print(counter)` (no `"Result: " + ...`)
- Compare with VM and inspect the IR use-sites. - Compare with VM and inspect the IR use-sites.
**Update (Phase 131-13)**:
- snapshot-only + strict resolver により、Case C の不一致が “LLVM の値解決バグ” ではなく
“Rust の MIR→JSON emit が block 内命令順序を崩している” 問題として顕在化した。
- Investigation note: `docs/development/current/main/investigations/phase131-13-mir-json-instruction-order.md`
- Add `is_infinite_loop: bool` feature to `LoopFeatures` (detect `loop(true)`). - Add `is_infinite_loop: bool` feature to `LoopFeatures` (detect `loop(true)`).
- Fix classification so `has_break && has_continue` does not route to Pattern 4. - Fix classification so `has_break && has_continue` does not route to Pattern 4.
- Introduce a dedicated pattern kind + lowerer for **infinite loop + early-exit (+ optional continue)**: - Introduce a dedicated pattern kind + lowerer for **infinite loop + early-exit (+ optional continue)**:

View File

@ -1,9 +1,51 @@
from typing import Dict, Any, List, Tuple, NamedTuple from typing import Dict, Any, List, Tuple, NamedTuple
import os
import sys
from llvmlite import ir from llvmlite import ir
from trace import debug as trace_debug from trace import debug as trace_debug
from trace import phi_json as trace_phi_json from trace import phi_json as trace_phi_json
def is_jump_only_block(block_info: Dict) -> bool:
"""Phase 131-14-B: Detect pure jump-only blocks (trampoline blocks).
A pure jump-only block has:
- NO PHI instructions (PHI blocks do meaningful work - value merging)
- NO other instructions except a single terminator (jump/branch/ret)
- Acts as a pure trampoline/routing block
Blocks with PHI instructions are NOT jump-only because they perform value
merging and must compute their own snapshots.
"""
instructions = block_info.get("instructions", [])
# Check if block has any PHI instructions
has_phi = any(i.get("op") == "phi" for i in instructions)
if has_phi:
# PHI blocks are NOT jump-only - they do value merging
return False
# Check if block has only terminator instructions
non_term = [
i for i in instructions
if i.get("op") not in ("ret", "jump", "branch")
]
return len(non_term) == 0
def get_predecessors(bid: int, preds: Dict[int, List[int]]) -> List[int]:
"""Phase 131-14 P0-3: Get predecessors for a block.
Args:
bid: Block ID
preds: Predecessor map (bid -> [predecessor_bids])
Returns:
List of predecessor block IDs
"""
return preds.get(bid, [])
class DeferredTerminator(NamedTuple): class DeferredTerminator(NamedTuple):
"""Phase 131-12-P1: Deferred terminator with vmap snapshot. """Phase 131-12-P1: Deferred terminator with vmap snapshot.
@ -15,12 +57,138 @@ class DeferredTerminator(NamedTuple):
vmap_snapshot: Dict[int, ir.Value] vmap_snapshot: Dict[int, ir.Value]
def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]]):
"""Phase 131-14-B P0-2: Resolve jump-only block snapshots (Pass B).
This function runs AFTER all blocks have been lowered (Pass A) but BEFORE
PHI finalization. It resolves snapshots for jump-only blocks by following
the CFG to find the nearest non-jump-only predecessor.
Uses path compression to efficiently handle chains of jump-only blocks.
SSOT: Snapshots are based on CFG structure, not processing order.
"""
import sys
strict_mode = os.environ.get('NYASH_LLVM_STRICT') == '1'
trace_vmap = os.environ.get('NYASH_LLVM_TRACE_VMAP') == '1'
jump_only = getattr(builder, '_jump_only_blocks', {})
if not jump_only:
if trace_vmap:
print("[vmap/resolve/passB] No jump-only blocks to resolve", file=sys.stderr)
return
if trace_vmap:
print(f"[vmap/resolve/passB] Resolving {len(jump_only)} jump-only blocks: {sorted(jump_only.keys())}", file=sys.stderr)
resolved = {} # bid -> snapshot dict
def resolve(bid: int, visited: set | None = None) -> Dict[int, Any]:
"""Recursively resolve snapshot for a block, with cycle detection."""
if visited is None:
visited = set()
# Cycle detection
if bid in visited:
if strict_mode:
raise RuntimeError(
f"[LLVM_PY/STRICT] Phase 131-14-B: Cycle detected in jump-only chain: "
f"{visited} -> {bid}"
)
if trace_vmap:
print(f"[vmap/resolve/passB] WARNING: Cycle at bb{bid}, returning empty", file=sys.stderr)
return {}
visited.add(bid)
# Already resolved (path compression cache)
if bid in resolved:
if trace_vmap:
print(f"[vmap/resolve/passB] bb{bid} already resolved (cached)", file=sys.stderr)
return resolved[bid]
# Normal block - already has snapshot from Pass A
if bid in builder.block_end_values:
snapshot = builder.block_end_values[bid]
if trace_vmap:
print(
f"[vmap/resolve/passB] bb{bid} is normal block with snapshot "
f"({len(snapshot)} values)",
file=sys.stderr
)
return snapshot
# Jump-only block - resolve from predecessor
if bid in jump_only:
pred_bid = jump_only[bid]
if trace_vmap:
print(f"[vmap/resolve/passB] bb{bid} is jump-only, resolving from pred bb{pred_bid}", file=sys.stderr)
# Recursively resolve predecessor
pred_snapshot = resolve(pred_bid, visited)
if not pred_snapshot:
if strict_mode:
raise RuntimeError(
f"[LLVM_PY/STRICT] Phase 131-14-B: jump-only block bb{bid} "
f"cannot resolve snapshot from predecessor bb{pred_bid} "
f"(predecessor has no snapshot)"
)
if trace_vmap:
print(
f"[vmap/resolve/passB] WARNING: bb{bid} pred bb{pred_bid} has no snapshot, "
f"using empty dict",
file=sys.stderr
)
pred_snapshot = {}
# Cache the result (path compression)
resolved[bid] = dict(pred_snapshot)
if trace_vmap:
print(
f"[vmap/resolve/passB] bb{bid} resolved from bb{pred_bid}: "
f"{len(resolved[bid])} values",
file=sys.stderr
)
return resolved[bid]
# Unknown block (should not happen if Pass A worked correctly)
if strict_mode:
raise RuntimeError(
f"[LLVM_PY/STRICT] Phase 131-14-B: block bb{bid} is neither normal "
f"nor jump-only (invalid state)"
)
if trace_vmap:
print(f"[vmap/resolve/passB] WARNING: bb{bid} unknown state, returning empty", file=sys.stderr)
return {}
# Resolve all jump-only blocks
for bid in sorted(jump_only.keys()):
snapshot = resolve(bid)
builder.block_end_values[bid] = snapshot
if trace_vmap:
print(
f"[vmap/resolve/passB] ✅ bb{bid} final snapshot: "
f"{len(snapshot)} values, keys={sorted(snapshot.keys())[:10]}",
file=sys.stderr
)
if trace_vmap:
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):
"""Lower blocks in multi-pass to ensure PHIs are always before terminators. """Lower blocks in multi-pass to ensure PHIs are always before terminators.
Phase 131-4: Multi-pass block lowering architecture Phase 131-4: Multi-pass block lowering architecture
Phase 131-14-B: Two-pass snapshot resolution
- Pass A: Lower non-terminator instructions only (terminators deferred) - 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 - Pass B: PHI finalization happens in function_lower.py
- resolve_jump_only_snapshots() called BEFORE PHI finalization
- Pass C: Lower terminators (happens after PHI finalization) - Pass C: Lower terminators (happens after PHI finalization)
This ensures LLVM IR invariant: PHI nodes must be at block head before any This ensures LLVM IR invariant: PHI nodes must be at block head before any
@ -278,7 +446,78 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An
except Exception: except Exception:
pass pass
# End-of-block snapshot # End-of-block snapshot
# Phase 131-14-B P0-1: Jump-only blocks - record metadata only (Pass A)
strict_mode = os.environ.get('NYASH_LLVM_STRICT') == '1'
trace_vmap = os.environ.get('NYASH_LLVM_TRACE_VMAP') == '1'
# Initialize jump_only_blocks dict if not exists
if not hasattr(builder, '_jump_only_blocks'):
builder._jump_only_blocks = {}
is_jump_only = is_jump_only_block(block_data)
if trace_vmap:
print(
f"[vmap/snapshot] bb{bid} is_jump_only={is_jump_only} "
f"instructions={[i.get('op') for i in block_data.get('instructions', [])]}",
file=sys.stderr
)
if is_jump_only:
# Phase 131-14-B: Jump-only blocks - record metadata, defer snapshot resolution to Pass B
preds_list = get_predecessors(bid, builder.preds)
if len(preds_list) == 0:
# No predecessors - error in STRICT mode
if strict_mode:
raise RuntimeError(
f"[LLVM_PY/STRICT] Phase 131-14-B: jump-only block bb{bid} "
f"has no predecessors (orphan trampoline)"
)
# Non-STRICT: use current vmap_cur (defensive fallback)
snap = dict(vmap_cur) snap = dict(vmap_cur)
if trace_vmap:
print(
f"[vmap/snapshot] bb{bid} jump-only with 0 preds: "
f"using vmap_cur keys={sorted(snap.keys())}",
file=sys.stderr
)
elif len(preds_list) == 1:
# Single predecessor - record metadata for Pass B resolution
pred_bid = preds_list[0]
builder._jump_only_blocks[bid] = pred_bid
# DO NOT create snapshot here - will be resolved in Pass B
# Set snap to None to indicate "skip storing in block_end_values"
snap = None
if trace_vmap:
print(
f"[vmap/snapshot/passA] bb{bid} jump-only: recorded pred=bb{pred_bid}, "
f"snapshot deferred to Pass B",
file=sys.stderr
)
else:
# Multiple predecessors - error in STRICT mode (merge rules not yet defined)
if strict_mode:
raise RuntimeError(
f"[LLVM_PY/STRICT] Phase 131-14-B: jump-only block bb{bid} "
f"has multiple predecessors: {preds_list} "
f"(merge propagation not implemented)"
)
# Non-STRICT: use current vmap_cur (defensive fallback)
snap = dict(vmap_cur)
if trace_vmap:
print(
f"[vmap/snapshot] bb{bid} jump-only with multiple preds {preds_list}: "
f"using vmap_cur keys={sorted(snap.keys())}",
file=sys.stderr
)
else:
# Normal block: use its own vmap_cur
snap = dict(vmap_cur)
# Phase 131-14-B: Only store snapshot if not deferred (snap is not None)
if snap is not None:
try: try:
keys = sorted(list(snap.keys())) keys = sorted(list(snap.keys()))
except Exception: except Exception:
@ -288,6 +527,13 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An
if vid in vmap_cur: if vid in vmap_cur:
builder.def_blocks.setdefault(vid, set()).add(block_data.get("id", 0)) builder.def_blocks.setdefault(vid, set()).add(block_data.get("id", 0))
builder.block_end_values[bid] = snap builder.block_end_values[bid] = snap
else:
# Jump-only block with deferred snapshot - don't store yet
if trace_vmap:
print(
f"[vmap/snapshot/passA] bb{bid} snapshot deferred (not stored in block_end_values)",
file=sys.stderr
)
try: try:
delattr(builder, '_current_vmap') delattr(builder, '_current_vmap')
except Exception: except Exception:

View File

@ -279,6 +279,10 @@ def lower_function(builder, func_data: Dict[str, Any]):
from builders.block_lower import lower_blocks as _lower_blocks 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)
# Phase 131-14-B Pass B: Resolve jump-only block snapshots (BEFORE PHI finalization)
from builders.block_lower import resolve_jump_only_snapshots as _resolve_jump_only_snapshots
_resolve_jump_only_snapshots(builder, block_by_id)
# Optional: capture lowering ctx for downstream helpers # Optional: capture lowering ctx for downstream helpers
try: try:
builder.ctx = dict( builder.ctx = dict(
@ -300,7 +304,7 @@ def lower_function(builder, func_data: Dict[str, Any]):
except Exception: except Exception:
pass pass
# Phase 131-4 Pass B: Finalize PHIs (wires incoming edges) # Phase 131-4 Pass B (now Pass B2): Finalize PHIs (wires incoming edges)
_finalize_phis(builder) _finalize_phis(builder)
# Phase 131-4 Pass C: Lower deferred terminators (after PHIs are placed) # Phase 131-4 Pass C: Lower deferred terminators (after PHIs are placed)

View File

@ -202,9 +202,8 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in
if original_vs != vs: if original_vs != vs:
trace({"phi": "wire_replaced_src", "original": original_vs, "replaced": vs}) trace({"phi": "wire_replaced_src", "original": original_vs, "replaced": vs})
try: try:
val = builder.resolver._value_at_end_i64( # P0-4: Use resolve_incoming for PHI incoming values
vs, pred_match, builder.preds, builder.block_end_values, builder.vmap, builder.bb_map val = builder.resolver.resolve_incoming(pred_match, vs)
)
trace({"phi": "wire_resolved", "vs": vs, "pred": pred_match, "val_type": type(val).__name__}) trace({"phi": "wire_resolved", "vs": vs, "pred": pred_match, "val_type": type(val).__name__})
except Exception as e: except Exception as e:
trace({"phi": "wire_resolve_fail", "vs": vs, "pred": pred_match, "error": str(e)}) trace({"phi": "wire_resolve_fail", "vs": vs, "pred": pred_match, "error": str(e)})

View File

@ -3,7 +3,7 @@ Resolver API (Python version)
Based on src/backend/llvm/compiler/codegen/instructions/resolver.rs Based on src/backend/llvm/compiler/codegen/instructions/resolver.rs
""" """
from typing import Dict, Optional, Any, Tuple from typing import Dict, Optional, Any, Tuple, Set
import os import os
from trace import phi as trace_phi from trace import phi as trace_phi
from trace import values as trace_values from trace import values as trace_values
@ -63,6 +63,8 @@ class Resolver:
self.block_phi_incomings = {} self.block_phi_incomings = {}
# P0-1: SSOT for end-of-block values (snapshots) # P0-1: SSOT for end-of-block values (snapshots)
self.block_end_values = {} self.block_end_values = {}
# P0-3: Circular reference detection (hang prevention)
self._visited: Set[Tuple[int, int]] = set()
def mark_string(self, value_id: int) -> None: def mark_string(self, value_id: int) -> None:
try: try:
@ -76,6 +78,80 @@ class Resolver:
except Exception: except Exception:
return False return False
def _check_cycle(self, block_id: int, value_id: int):
"""P0-3: Circular reference detection (hang prevention)"""
key = (block_id, value_id)
if key in self._visited:
raise RuntimeError(
f"[LLVM_PY] Circular reference detected: bb{block_id} v{value_id}"
)
self._visited.add(key)
def resolve_cur(self, block_id: int, value_id: int, vmap_cur: Dict[int, ir.Value]) -> ir.Value:
"""P0-1: Same-block instruction lowering (vmap_cur as primary source)
Used for lowering instructions within the same basic block where the value
is defined and used. Checks vmap_cur first, then applies fail-fast checks.
Args:
block_id: Current basic block ID
value_id: Value ID to resolve
vmap_cur: Current block's value map (def->use tracking)
Returns:
LLVM IR value (i64)
"""
# 1. Check vmap_cur first
val = vmap_cur.get(value_id)
if val is not None:
return val
# 2. Fail-Fast: def_blocks has bb but vmap_cur doesn't → lowerer bug
if value_id in self.def_blocks and block_id in self.def_blocks[value_id]:
if os.environ.get('NYASH_LLVM_STRICT') == '1':
raise RuntimeError(
f"[LLVM_PY/STRICT] resolve_cur: v{value_id} defined in bb{block_id} "
f"but not in vmap_cur. Lowerer order bug?"
)
# 3. vmap_cur miss → undefined error
if os.environ.get('NYASH_LLVM_STRICT') == '1':
raise RuntimeError(
f"[LLVM_PY/STRICT] resolve_cur: v{value_id} not found in bb{block_id} vmap_cur. "
f"Available: {sorted(vmap_cur.keys())}"
)
# 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:
"""P0-2: PHI incoming resolution (snapshot-only reference)
Used for resolving PHI incoming values from predecessor blocks.
Only looks at block_end_values snapshot, never vmap_cur.
Args:
pred_block_id: Predecessor block ID
value_id: Value ID to resolve from predecessor
Returns:
LLVM IR value (i64)
"""
snapshot = self.block_end_values.get(pred_block_id, {})
val = snapshot.get(value_id)
if val is not None:
return val
# 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"Available: {sorted(snapshot.keys())}"
)
# Non-STRICT: fallback to 0
return ir.Constant(ir.IntType(64), 0)
def resolve_i64( def resolve_i64(
self, self,
value_id: int, value_id: int,

View File

@ -190,43 +190,11 @@ pub fn emit_mir_json_for_harness(
for bid in ids { for bid in ids {
if let Some(bb) = f.blocks.get(&bid) { if let Some(bb) = f.blocks.get(&bid) {
let mut insts = Vec::new(); let mut insts = Vec::new();
// Pre-scan: collect values defined anywhere in this block (to delay use-before-def copies) // Phase 131-13: Emit all instructions in MIR order (SSOT principle)
let mut block_defines: std::collections::HashSet<u32> = // No reordering except PHI consolidation at block start (LLVM constraint)
std::collections::HashSet::new();
// Step 1: Emit all PHI instructions first (LLVM requirement)
for inst in &bb.instructions { for inst in &bb.instructions {
match inst {
I::UnaryOp { dst, .. }
| I::Const { dst, .. }
| I::BinOp { dst, .. }
| I::Compare { dst, .. }
| I::Call { dst: Some(dst), .. }
| I::ExternCall { dst: Some(dst), .. }
| I::BoxCall { dst: Some(dst), .. }
| I::NewBox { dst, .. }
| I::Phi { dst, .. } => {
block_defines.insert(dst.as_u32());
}
_ => {}
}
}
// Track which values have been emitted (to order copies after their sources)
let mut emitted_defs: std::collections::HashSet<u32> =
std::collections::HashSet::new();
// PHI firstオプション
for inst in &bb.instructions {
if let I::Copy { dst, src } = inst {
// For copies whose source will be defined later in this block, delay emission
let s = src.as_u32();
if block_defines.contains(&s) && !emitted_defs.contains(&s) {
// delayed; will be emitted after non-PHI pass
} else {
insts.push(
json!({"op":"copy","dst": dst.as_u32(), "src": src.as_u32()}),
);
emitted_defs.insert(dst.as_u32());
}
continue;
}
if let I::Phi { dst, inputs, .. } = inst { if let I::Phi { dst, inputs, .. } = inst {
let incoming: Vec<_> = inputs let incoming: Vec<_> = inputs
.iter() .iter()
@ -250,20 +218,16 @@ pub fn emit_mir_json_for_harness(
insts.push(phi_inst); insts.push(phi_inst);
} }
} }
// Non-PHI
// Non-PHI // Step 2: Emit all non-PHI instructions in MIR order (no reordering!)
let mut delayed_copies: Vec<(u32, u32)> = Vec::new();
for inst in &bb.instructions { for inst in &bb.instructions {
match inst { match inst {
I::Copy { dst, src } => { I::Phi { .. } => {
let d = dst.as_u32(); // Already emitted in step 1
let s = src.as_u32(); continue;
if block_defines.contains(&s) && !emitted_defs.contains(&s) {
delayed_copies.push((d, s));
} else {
insts.push(json!({"op":"copy","dst": d, "src": s}));
emitted_defs.insert(d);
} }
I::Copy { dst, src } => {
insts.push(json!({"op":"copy","dst": dst.as_u32(), "src": src.as_u32()}));
} }
I::UnaryOp { dst, op, operand } => { I::UnaryOp { dst, op, operand } => {
let kind = match op { let kind = match op {
@ -322,7 +286,6 @@ pub fn emit_mir_json_for_harness(
"dst": dst.as_u32(), "dst": dst.as_u32(),
"target_type": ty_s, "target_type": ty_s,
})); }));
emitted_defs.insert(dst.as_u32());
} }
I::BinOp { dst, op, lhs, rhs } => { I::BinOp { dst, op, lhs, rhs } => {
let op_s = match op { let op_s = match op {
@ -456,7 +419,6 @@ pub fn emit_mir_json_for_harness(
} }
insts.push(obj); insts.push(obj);
if let Some(d) = dst.map(|v| v.as_u32()) { if let Some(d) = dst.map(|v| v.as_u32()) {
emitted_defs.insert(d);
} }
} }
_ => { _ => {
@ -531,7 +493,6 @@ pub fn emit_mir_json_for_harness(
} }
insts.push(obj); insts.push(obj);
if let Some(d) = dst.map(|v| v.as_u32()) { if let Some(d) = dst.map(|v| v.as_u32()) {
emitted_defs.insert(d);
} }
} }
I::NewBox { I::NewBox {
@ -541,7 +502,6 @@ pub fn emit_mir_json_for_harness(
} => { } => {
let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect(); let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
insts.push(json!({"op":"newbox","type": box_type, "args": args_a, "dst": dst.as_u32()})); insts.push(json!({"op":"newbox","type": box_type, "args": args_a, "dst": dst.as_u32()}));
emitted_defs.insert(dst.as_u32());
} }
I::Branch { I::Branch {
condition, condition,
@ -559,10 +519,7 @@ pub fn emit_mir_json_for_harness(
_ => { /* skip non-essential ops for initial harness */ } _ => { /* skip non-essential ops for initial harness */ }
} }
} }
// Emit delayed copies now (sources should be available) // Phase 131-13: Terminator emitted inline (no delayed copies)
for (d, s) in delayed_copies {
insts.push(json!({"op":"copy","dst": d, "src": s}));
}
if let Some(term) = &bb.terminator { if let Some(term) = &bb.terminator {
match term { match term {
I::Return { value } => insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())})), I::Return { value } => insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())})),
@ -650,27 +607,10 @@ pub fn emit_mir_json_for_harness_bin(
for bid in ids { for bid in ids {
if let Some(bb) = f.blocks.get(&bid) { if let Some(bb) = f.blocks.get(&bid) {
let mut insts = Vec::new(); let mut insts = Vec::new();
// Pre-scan to collect values defined in this block // Phase 131-13: Emit all instructions in MIR order (SSOT principle)
let mut block_defines: std::collections::HashSet<u32> = // No reordering except PHI consolidation at block start (LLVM constraint)
std::collections::HashSet::new();
for inst in &bb.instructions { // Step 1: Emit all PHI instructions first (LLVM requirement)
match inst {
I::Copy { dst, .. }
| I::Const { dst, .. }
| I::BinOp { dst, .. }
| I::Compare { dst, .. }
| I::Call { dst: Some(dst), .. }
| I::ExternCall { dst: Some(dst), .. }
| I::BoxCall { dst: Some(dst), .. }
| I::NewBox { dst, .. }
| I::Phi { dst, .. } => {
block_defines.insert(dst.as_u32());
}
_ => {}
}
}
let mut emitted_defs: std::collections::HashSet<u32> =
std::collections::HashSet::new();
for inst in &bb.instructions { for inst in &bb.instructions {
if let I::Phi { dst, inputs, .. } = inst { if let I::Phi { dst, inputs, .. } = inst {
let incoming: Vec<_> = inputs let incoming: Vec<_> = inputs
@ -693,21 +633,18 @@ pub fn emit_mir_json_for_harness_bin(
} }
} }
insts.push(phi_inst); insts.push(phi_inst);
emitted_defs.insert(dst.as_u32());
} }
} }
let mut delayed_copies: Vec<(u32, u32)> = Vec::new();
// Step 2: Emit all non-PHI instructions in MIR order (no reordering!)
for inst in &bb.instructions { for inst in &bb.instructions {
match inst { match inst {
I::Copy { dst, src } => { I::Phi { .. } => {
let d = dst.as_u32(); // Already emitted in step 1
let s = src.as_u32(); continue;
if block_defines.contains(&s) && !emitted_defs.contains(&s) {
delayed_copies.push((d, s));
} else {
insts.push(json!({"op":"copy","dst": d, "src": s}));
emitted_defs.insert(d);
} }
I::Copy { dst, src } => {
insts.push(json!({"op":"copy","dst": dst.as_u32(), "src": src.as_u32()}));
} }
I::Const { dst, value } => { I::Const { dst, value } => {
match value { match value {
@ -734,7 +671,6 @@ pub fn emit_mir_json_for_harness_bin(
insts.push(json!({"op":"const","dst": dst.as_u32(), "value": {"type": "void", "value": 0}})); insts.push(json!({"op":"const","dst": dst.as_u32(), "value": {"type": "void", "value": 0}}));
} }
} }
emitted_defs.insert(dst.as_u32());
} }
I::BinOp { dst, op, lhs, rhs } => { I::BinOp { dst, op, lhs, rhs } => {
let op_s = match op { let op_s = match op {
@ -772,7 +708,6 @@ pub fn emit_mir_json_for_harness_bin(
} }
} }
insts.push(obj); insts.push(obj);
emitted_defs.insert(dst.as_u32());
} }
I::Compare { dst, op, lhs, rhs } => { I::Compare { dst, op, lhs, rhs } => {
let op_s = match op { let op_s = match op {
@ -784,7 +719,6 @@ pub fn emit_mir_json_for_harness_bin(
C::Ge => ">=", C::Ge => ">=",
}; };
insts.push(json!({"op":"compare","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()})); insts.push(json!({"op":"compare","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()}));
emitted_defs.insert(dst.as_u32());
} }
I::Call { I::Call {
dst, dst,
@ -853,7 +787,6 @@ pub fn emit_mir_json_for_harness_bin(
} }
insts.push(obj); insts.push(obj);
if let Some(d) = dst.map(|v| v.as_u32()) { if let Some(d) = dst.map(|v| v.as_u32()) {
emitted_defs.insert(d);
} }
} }
_ => { _ => {
@ -889,7 +822,6 @@ pub fn emit_mir_json_for_harness_bin(
} }
insts.push(obj); insts.push(obj);
if let Some(d) = dst.map(|v| v.as_u32()) { if let Some(d) = dst.map(|v| v.as_u32()) {
emitted_defs.insert(d);
} }
} }
I::BoxCall { I::BoxCall {
@ -921,7 +853,6 @@ pub fn emit_mir_json_for_harness_bin(
} }
insts.push(obj); insts.push(obj);
if let Some(d) = dst.map(|v| v.as_u32()) { if let Some(d) = dst.map(|v| v.as_u32()) {
emitted_defs.insert(d);
} }
} }
I::NewBox { I::NewBox {
@ -931,7 +862,6 @@ pub fn emit_mir_json_for_harness_bin(
} => { } => {
let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect(); let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
insts.push(json!({"op":"newbox","type": box_type, "args": args_a, "dst": dst.as_u32()})); insts.push(json!({"op":"newbox","type": box_type, "args": args_a, "dst": dst.as_u32()}));
emitted_defs.insert(dst.as_u32());
} }
I::Branch { I::Branch {
condition, condition,
@ -949,10 +879,7 @@ pub fn emit_mir_json_for_harness_bin(
_ => {} _ => {}
} }
} }
// Append delayed copies after their sources // Phase 131-13: Terminator emitted inline (no delayed copies)
for (d, s) in delayed_copies {
insts.push(json!({"op":"copy","dst": d, "src": s}));
}
if let Some(term) = &bb.terminator { if let Some(term) = &bb.terminator {
match term { match term {
I::Return { value } => insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())})), I::Return { value } => insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())})),