llvm(py): introduce BuildCtx + trace hub; refactor if-merge prepass + PHI wiring into module; unify logs; ctx-enable compare/ret/call/boxcall/externcall/typeop/newbox/safepoint; curated smoke option for if-merge; README updates; keep behavior stable
This commit is contained in:
@ -13,12 +13,35 @@ ChatGPTが設計した`docs/design/LLVM_LAYER_OVERVIEW.md`の設計原則に従
|
||||
## 📂 構造
|
||||
```
|
||||
llvm_py/
|
||||
├── README.md # このファイル
|
||||
├── mir_reader.py # MIR JSON読み込み
|
||||
├── llvm_builder.py # メインのLLVM IR生成
|
||||
├── resolver.py # Resolver API(Python版)
|
||||
├── types.py # 型変換ユーティリティ
|
||||
└── test_simple.py # 基本テスト
|
||||
├── README.md # このファイル
|
||||
├── llvm_builder.py # メインのLLVM IR生成(パスのオーケストレーション)
|
||||
├── mir_reader.py # MIR(JSON) ローダ
|
||||
├── resolver.py # 値解決(SSA/PHIの局所化とキャッシュ)
|
||||
├── utils/
|
||||
│ └── values.py # 同一ブロック優先の解決などの共通ポリシー
|
||||
├── cfg/
|
||||
│ └── utils.py # CFG ビルド(pred/succ)
|
||||
├── prepass/
|
||||
│ ├── loops.py # ループ検出(while 形)
|
||||
│ └── if_merge.py # if-merge(ret-merge)前処理(PHI前宣言プラン)
|
||||
├── instructions/
|
||||
│ ├── controlflow/
|
||||
│ │ ├── branch.py # 条件分岐
|
||||
│ │ ├── jump.py # 無条件ジャンプ
|
||||
│ │ └── while_.py # 通常の while 降下(LoopForm 失敗時のフォールバック)
|
||||
│ ├── binop.py # 2項演算
|
||||
│ ├── compare.py # 比較演算(i1生成)
|
||||
│ ├── const.py # 定数
|
||||
│ ├── copy.py # Copy(MIR13 PHI-off の合流表現)
|
||||
│ ├── call.py # Ny 関数呼び出し
|
||||
│ ├── boxcall.py # Box メソッド呼び出し
|
||||
│ ├── externcall.py # 外部呼び出し
|
||||
│ ├── newbox.py # Box 生成
|
||||
│ ├── ret.py # return 降下(if-merge の前宣言PHIを優先)
|
||||
│ ├── typeop.py # 型変換
|
||||
│ ├── safepoint.py # safepoint
|
||||
│ └── barrier.py # メモリバリア
|
||||
└── test_simple.py # 基本テスト
|
||||
```
|
||||
|
||||
## 🚀 使い方
|
||||
@ -30,18 +53,40 @@ python src/llvm_py/llvm_builder.py input.mir.json -o output.o
|
||||
NYASH_LLVM_USE_HARNESS=1 ./target/release/nyash program.nyash
|
||||
```
|
||||
|
||||
## 🔧 開発用フラグ(プリパス/トレース)
|
||||
- `NYASH_LLVM_USE_HARNESS=1` … Rust 実行から llvmlite ハーネスへ委譲
|
||||
- `NYASH_LLVM_PREPASS_LOOP=1` … ループ検出プリパスON(while 形を構造化)
|
||||
- `NYASH_LLVM_PREPASS_IFMERGE=1` … if-merge(ret-merge)プリパスON(ret値 PHI を前宣言)
|
||||
- `NYASH_LLVM_TRACE_PHI=1` … PHI 配線と end-of-block 解決の詳細トレース
|
||||
- `NYASH_CLI_VERBOSE=1` … 降下やスナップショットの詳細ログ
|
||||
- `NYASH_MIR_NO_PHI=1` … MIR13(PHI-off)を明示(既定1)
|
||||
- `NYASH_VERIFY_ALLOW_NO_PHI=1` … PHI-less を検証で許容(既定1)
|
||||
|
||||
## 📋 設計原則(LLVM_LAYER_OVERVIEWに準拠)
|
||||
1. **Resolver-only reads** - 直接vmapアクセス禁止
|
||||
2. **Localize at block start** - BB先頭でPHI生成
|
||||
3. **Sealed SSA** - snapshot経由の配線
|
||||
4. **BuilderCursor相当** - 挿入位置の厳格管理
|
||||
1. Resolver-only reads(原則): 直接の cross-block vmap 参照は避け、resolver 経由で取得
|
||||
2. Localize at block start: ブロック先頭で PHI を作る(if-merge は prepass で前宣言)
|
||||
3. Sealed SSA: ブロック末 snapshot を用いた finalize_phis 配線
|
||||
4. Builder cursor discipline: 生成位置の厳格化(terminator 後に emit しない)
|
||||
|
||||
## 🎨 実装状況
|
||||
- [ ] 基本構造(MIR読み込み)
|
||||
- [ ] Core-14命令の実装
|
||||
- [ ] Resolver API
|
||||
- [ ] LoopForm対応
|
||||
- [ ] テストスイート
|
||||
- [x] ControlFlow 分離(branch/jump/while_regular)
|
||||
- [x] CFG/Prepass 分離(cfg/utils.py, prepass/loops.py, prepass/if_merge.py)
|
||||
- [x] if-merge(ret-merge)の PHI 前宣言(ゲート: NYASH_LLVM_PREPASS_IFMERGE=1)
|
||||
- [x] ループプリパス(ゲート: NYASH_LLVM_PREPASS_LOOP=1)
|
||||
- [ ] 追加命令/Stage-3 の持続的整備
|
||||
|
||||
## ✅ テスト・検証
|
||||
- パリティ(llvmlite vs PyVM。既定は終了コードのみ比較)
|
||||
- `./tools/pyvm_vs_llvmlite.sh apps/tests/ternary_nested.nyash`
|
||||
- 代表例(プリパス有効):
|
||||
- `NYASH_LLVM_PREPASS_IFMERGE=1 ./tools/pyvm_vs_llvmlite.sh apps/tests/ternary_nested.nyash`
|
||||
- `NYASH_LLVM_PREPASS_LOOP=1 ./tools/pyvm_vs_llvmlite.sh apps/tests/loop_if_phi.nyash`
|
||||
- 厳密比較(標準出力+終了コード)
|
||||
- `CMP_STRICT=1 ./tools/pyvm_vs_llvmlite.sh <file.nyash>`
|
||||
- まとまったスモーク(PHI-off 既定)
|
||||
- `tools/smokes/curated_llvm.sh`
|
||||
- PHI-on 検証(実験的): `tools/smokes/curated_llvm.sh --phi-on`
|
||||
|
||||
## 📊 予想行数
|
||||
- 全体: 800-1000行
|
||||
|
||||
34
src/llvm_py/build_ctx.py
Normal file
34
src/llvm_py/build_ctx.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""
|
||||
Build context for instruction lowering and helpers.
|
||||
|
||||
This structure aggregates frequently passed references so call sites can
|
||||
remain concise as we gradually refactor instruction signatures.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional
|
||||
import llvmlite.ir as ir
|
||||
|
||||
@dataclass
|
||||
class BuildCtx:
|
||||
# Core IR handles
|
||||
module: ir.Module
|
||||
i64: ir.IntType
|
||||
i32: ir.IntType
|
||||
i8: ir.IntType
|
||||
i1: ir.IntType
|
||||
i8p: ir.PointerType
|
||||
|
||||
# SSA maps and CFG
|
||||
vmap: Dict[int, ir.Value]
|
||||
bb_map: Dict[int, ir.Block]
|
||||
preds: Dict[int, List[int]]
|
||||
block_end_values: Dict[int, Dict[int, ir.Value]]
|
||||
|
||||
# Resolver (value queries, casts, string-ish tags)
|
||||
resolver: Any
|
||||
|
||||
# Optional diagnostics toggles (read from env by the builder)
|
||||
trace_phi: bool = False
|
||||
verbose: bool = False
|
||||
|
||||
36
src/llvm_py/cfg/utils.py
Normal file
36
src/llvm_py/cfg/utils.py
Normal file
@ -0,0 +1,36 @@
|
||||
"""
|
||||
CFG utilities
|
||||
Build predecessor/successor maps and simple helpers.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Any, Tuple
|
||||
|
||||
def build_preds_succs(block_by_id: Dict[int, Dict[str, Any]]) -> Tuple[Dict[int, List[int]], Dict[int, List[int]]]:
|
||||
"""Construct predecessor and successor maps from MIR(JSON) blocks."""
|
||||
succs: Dict[int, List[int]] = {}
|
||||
preds: Dict[int, List[int]] = {}
|
||||
for b in block_by_id.values():
|
||||
bid = int(b.get('id', 0))
|
||||
preds.setdefault(bid, [])
|
||||
for b in block_by_id.values():
|
||||
src = int(b.get('id', 0))
|
||||
for inst in b.get('instructions', []) or []:
|
||||
op = inst.get('op')
|
||||
if op == 'jump':
|
||||
t = inst.get('target')
|
||||
if t is not None:
|
||||
t = int(t)
|
||||
succs.setdefault(src, []).append(t)
|
||||
preds.setdefault(t, []).append(src)
|
||||
elif op == 'branch':
|
||||
th = inst.get('then'); el = inst.get('else')
|
||||
if th is not None:
|
||||
th = int(th)
|
||||
succs.setdefault(src, []).append(th)
|
||||
preds.setdefault(th, []).append(src)
|
||||
if el is not None:
|
||||
el = int(el)
|
||||
succs.setdefault(src, []).append(el)
|
||||
preds.setdefault(el, []).append(src)
|
||||
return preds, succs
|
||||
|
||||
@ -7,8 +7,9 @@ Each instruction has its own file, following Rust structure
|
||||
from .const import lower_const
|
||||
from .binop import lower_binop
|
||||
from .compare import lower_compare
|
||||
from .jump import lower_jump
|
||||
from .branch import lower_branch
|
||||
# controlflow
|
||||
from .controlflow.jump import lower_jump
|
||||
from .controlflow.branch import lower_branch
|
||||
from .ret import lower_return
|
||||
from .phi import lower_phi
|
||||
from .call import lower_call
|
||||
@ -29,4 +30,4 @@ __all__ = [
|
||||
'lower_externcall', 'lower_typeop', 'lower_safepoint',
|
||||
'lower_barrier', 'lower_newbox',
|
||||
'LoopFormContext', 'lower_while_loopform'
|
||||
]
|
||||
]
|
||||
|
||||
@ -5,6 +5,7 @@ Handles +, -, *, /, %, &, |, ^, <<, >>
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, Optional, Any
|
||||
from utils.values import resolve_i64_strict
|
||||
from .compare import lower_compare
|
||||
import llvmlite.ir as ir
|
||||
|
||||
@ -38,12 +39,12 @@ def lower_binop(
|
||||
"""
|
||||
# Resolve operands as i64 (using resolver when available)
|
||||
# For now, simple vmap lookup
|
||||
if resolver is not None and preds is not None and block_end_values is not None:
|
||||
lhs_val = resolver.resolve_i64(lhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
rhs_val = resolver.resolve_i64(rhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
else:
|
||||
lhs_val = vmap.get(lhs, ir.Constant(ir.IntType(64), 0))
|
||||
rhs_val = vmap.get(rhs, ir.Constant(ir.IntType(64), 0))
|
||||
lhs_val = resolve_i64_strict(resolver, lhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
rhs_val = resolve_i64_strict(resolver, rhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
if lhs_val is None:
|
||||
lhs_val = ir.Constant(ir.IntType(64), 0)
|
||||
if rhs_val is None:
|
||||
rhs_val = ir.Constant(ir.IntType(64), 0)
|
||||
|
||||
# Relational/equality operators delegate to compare
|
||||
if op in ('==','!=','<','>','<=','>='):
|
||||
@ -60,6 +61,7 @@ def lower_binop(
|
||||
preds=preds,
|
||||
block_end_values=block_end_values,
|
||||
bb_map=bb_map,
|
||||
ctx=getattr(resolver, 'ctx', None),
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ Core of Nyash's "Everything is Box" philosophy
|
||||
"""
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
def _declare(module: ir.Module, name: str, ret, args):
|
||||
for f in module.functions:
|
||||
@ -47,7 +47,8 @@ def lower_boxcall(
|
||||
resolver=None,
|
||||
preds=None,
|
||||
block_end_values=None,
|
||||
bb_map=None
|
||||
bb_map=None,
|
||||
ctx: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Lower MIR BoxCall instruction
|
||||
@ -68,10 +69,43 @@ def lower_boxcall(
|
||||
i8 = ir.IntType(8)
|
||||
i8p = i8.as_pointer()
|
||||
|
||||
# Short-hands with ctx (backward-compatible fallback)
|
||||
r = resolver
|
||||
p = preds
|
||||
bev = block_end_values
|
||||
bbm = bb_map
|
||||
if ctx is not None:
|
||||
try:
|
||||
r = getattr(ctx, 'resolver', r)
|
||||
p = getattr(ctx, 'preds', p)
|
||||
bev = getattr(ctx, 'block_end_values', bev)
|
||||
bbm = getattr(ctx, 'bb_map', bbm)
|
||||
except Exception:
|
||||
pass
|
||||
def _res_i64(vid: int):
|
||||
if r is not None and p is not None and bev is not None and bbm is not None:
|
||||
try:
|
||||
return r.resolve_i64(vid, builder.block, p, bev, vmap, bbm)
|
||||
except Exception:
|
||||
return None
|
||||
return vmap.get(vid)
|
||||
|
||||
# If BuildCtx is provided, prefer its maps for consistency.
|
||||
if ctx is not None:
|
||||
try:
|
||||
if getattr(ctx, 'resolver', None) is not None:
|
||||
resolver = ctx.resolver
|
||||
if getattr(ctx, 'preds', None) is not None and preds is None:
|
||||
preds = ctx.preds
|
||||
if getattr(ctx, 'block_end_values', None) is not None and block_end_values is None:
|
||||
block_end_values = ctx.block_end_values
|
||||
if getattr(ctx, 'bb_map', None) is not None and bb_map is None:
|
||||
bb_map = ctx.bb_map
|
||||
except Exception:
|
||||
pass
|
||||
# Receiver value
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
recv_val = resolver.resolve_i64(box_vid, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
else:
|
||||
recv_val = _res_i64(box_vid)
|
||||
if recv_val is None:
|
||||
recv_val = vmap.get(box_vid, ir.Constant(i64, 0))
|
||||
|
||||
# Minimal method bridging for strings and console
|
||||
@ -96,11 +130,11 @@ def lower_boxcall(
|
||||
if method_name == "substring":
|
||||
# substring(start, end)
|
||||
# If receiver is a handle (i64), use handle-based helper; else pointer-based API
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
s = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if args else ir.Constant(i64, 0)
|
||||
e = resolver.resolve_i64(args[1], builder.block, preds, block_end_values, vmap, bb_map) if len(args) > 1 else ir.Constant(i64, 0)
|
||||
else:
|
||||
s = _res_i64(args[0]) if args else ir.Constant(i64, 0)
|
||||
if s is None:
|
||||
s = vmap.get(args[0], ir.Constant(i64, 0)) if args else ir.Constant(i64, 0)
|
||||
e = _res_i64(args[1]) if len(args) > 1 else ir.Constant(i64, 0)
|
||||
if e is None:
|
||||
e = vmap.get(args[1], ir.Constant(i64, 0)) if len(args) > 1 else ir.Constant(i64, 0)
|
||||
if hasattr(recv_val, 'type') and isinstance(recv_val.type, ir.IntType):
|
||||
# handle-based
|
||||
@ -191,9 +225,8 @@ def lower_boxcall(
|
||||
# ArrayBox.get(index) → nyash.array.get_h(handle, idx)
|
||||
# MapBox.get(key) → nyash.map.get_hh(handle, key_any)
|
||||
recv_h = _ensure_handle(builder, module, recv_val)
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
k = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if args else ir.Constant(i64, 0)
|
||||
else:
|
||||
k = _res_i64(args[0]) if args else ir.Constant(i64, 0)
|
||||
if k is None:
|
||||
k = vmap.get(args[0], ir.Constant(i64, 0)) if args else ir.Constant(i64, 0)
|
||||
callee_map = _declare(module, "nyash.map.get_hh", i64, [i64, i64])
|
||||
res = builder.call(callee_map, [recv_h, k], name="map_get_hh")
|
||||
@ -204,9 +237,8 @@ def lower_boxcall(
|
||||
if method_name == "push":
|
||||
# ArrayBox.push(val) → nyash.array.push_h(handle, val)
|
||||
recv_h = _ensure_handle(builder, module, recv_val)
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
v0 = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if args else ir.Constant(i64, 0)
|
||||
else:
|
||||
v0 = _res_i64(args[0]) if args else ir.Constant(i64, 0)
|
||||
if v0 is None:
|
||||
v0 = vmap.get(args[0], ir.Constant(i64, 0)) if args else ir.Constant(i64, 0)
|
||||
callee = _declare(module, "nyash.array.push_h", i64, [i64, i64])
|
||||
res = builder.call(callee, [recv_h, v0], name="arr_push_h")
|
||||
@ -217,11 +249,11 @@ def lower_boxcall(
|
||||
if method_name == "set":
|
||||
# MapBox.set(key, val) → nyash.map.set_hh(handle, key_any, val_any)
|
||||
recv_h = _ensure_handle(builder, module, recv_val)
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
k = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if len(args) > 0 else ir.Constant(i64, 0)
|
||||
v = resolver.resolve_i64(args[1], builder.block, preds, block_end_values, vmap, bb_map) if len(args) > 1 else ir.Constant(i64, 0)
|
||||
else:
|
||||
k = _res_i64(args[0]) if len(args) > 0 else ir.Constant(i64, 0)
|
||||
if k is None:
|
||||
k = vmap.get(args[0], ir.Constant(i64, 0)) if len(args) > 0 else ir.Constant(i64, 0)
|
||||
v = _res_i64(args[1]) if len(args) > 1 else ir.Constant(i64, 0)
|
||||
if v is None:
|
||||
v = vmap.get(args[1], ir.Constant(i64, 0)) if len(args) > 1 else ir.Constant(i64, 0)
|
||||
callee = _declare(module, "nyash.map.set_hh", i64, [i64, i64, i64])
|
||||
res = builder.call(callee, [recv_h, k, v], name="map_set_hh")
|
||||
@ -232,9 +264,8 @@ def lower_boxcall(
|
||||
if method_name == "has":
|
||||
# MapBox.has(key) → nyash.map.has_hh(handle, key_any)
|
||||
recv_h = _ensure_handle(builder, module, recv_val)
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
k = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if args else ir.Constant(i64, 0)
|
||||
else:
|
||||
k = _res_i64(args[0]) if args else ir.Constant(i64, 0)
|
||||
if k is None:
|
||||
k = vmap.get(args[0], ir.Constant(i64, 0)) if args else ir.Constant(i64, 0)
|
||||
callee = _declare(module, "nyash.map.has_hh", i64, [i64, i64])
|
||||
res = builder.call(callee, [recv_h, k], name="map_has_hh")
|
||||
|
||||
@ -4,7 +4,8 @@ Handles regular function calls (not BoxCall or ExternCall)
|
||||
"""
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Dict, List, Optional, Any
|
||||
from trace import debug as trace_debug
|
||||
|
||||
def lower_call(
|
||||
builder: ir.IRBuilder,
|
||||
@ -16,7 +17,8 @@ def lower_call(
|
||||
resolver=None,
|
||||
preds=None,
|
||||
block_end_values=None,
|
||||
bb_map=None
|
||||
bb_map=None,
|
||||
ctx: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Lower MIR Call instruction
|
||||
@ -30,6 +32,50 @@ def lower_call(
|
||||
vmap: Value map
|
||||
resolver: Optional resolver for type handling
|
||||
"""
|
||||
# If BuildCtx is provided, prefer its maps for consistency.
|
||||
if ctx is not None:
|
||||
try:
|
||||
if getattr(ctx, 'resolver', None) is not None:
|
||||
resolver = ctx.resolver
|
||||
if getattr(ctx, 'preds', None) is not None and preds is None:
|
||||
preds = ctx.preds
|
||||
if getattr(ctx, 'block_end_values', None) is not None and block_end_values is None:
|
||||
block_end_values = ctx.block_end_values
|
||||
if getattr(ctx, 'bb_map', None) is not None and bb_map is None:
|
||||
bb_map = ctx.bb_map
|
||||
except Exception:
|
||||
pass
|
||||
# Short-hands with ctx (backward-compatible fallback)
|
||||
r = resolver
|
||||
p = preds
|
||||
bev = block_end_values
|
||||
bbm = bb_map
|
||||
if ctx is not None:
|
||||
try:
|
||||
r = getattr(ctx, 'resolver', r)
|
||||
p = getattr(ctx, 'preds', p)
|
||||
bev = getattr(ctx, 'block_end_values', bev)
|
||||
bbm = getattr(ctx, 'bb_map', bbm)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Resolver helpers (prefer resolver when available)
|
||||
def _res_i64(vid: int):
|
||||
if r is not None and p is not None and bev is not None and bbm is not None:
|
||||
try:
|
||||
return r.resolve_i64(vid, builder.block, p, bev, vmap, bbm)
|
||||
except Exception:
|
||||
return None
|
||||
return vmap.get(vid)
|
||||
|
||||
def _res_ptr(vid: int):
|
||||
if r is not None and p is not None and bev is not None:
|
||||
try:
|
||||
return r.resolve_ptr(vid, builder.block, p, bev, vmap)
|
||||
except Exception:
|
||||
return None
|
||||
return vmap.get(vid)
|
||||
|
||||
# Resolve function: accepts string name or value-id referencing a string literal
|
||||
actual_name = func_name
|
||||
if not isinstance(func_name, str):
|
||||
@ -58,11 +104,10 @@ def lower_call(
|
||||
arg_val = None
|
||||
if i < len(func.args):
|
||||
expected_type = func.args[i].type
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
if hasattr(expected_type, 'is_pointer') and expected_type.is_pointer:
|
||||
arg_val = resolver.resolve_ptr(arg_id, builder.block, preds, block_end_values, vmap)
|
||||
else:
|
||||
arg_val = resolver.resolve_i64(arg_id, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
if hasattr(expected_type, 'is_pointer') and expected_type.is_pointer:
|
||||
arg_val = _res_ptr(arg_id)
|
||||
else:
|
||||
arg_val = _res_i64(arg_id)
|
||||
if arg_val is None:
|
||||
arg_val = vmap.get(arg_id)
|
||||
if arg_val is None:
|
||||
@ -88,13 +133,8 @@ def lower_call(
|
||||
# Make the call
|
||||
result = builder.call(func, call_args, name=f"call_{func_name}")
|
||||
# Optional trace for final debugging
|
||||
try:
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_TRACE_FINAL') == '1' and isinstance(actual_name, str):
|
||||
if actual_name in ("Main.node_json/3", "Main.esc_json/1", "main"):
|
||||
print(f"[TRACE] call {actual_name} args={len(call_args)}", flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
if isinstance(actual_name, str) and actual_name in ("Main.node_json/3", "Main.esc_json/1", "main"):
|
||||
trace_debug(f"[TRACE] call {actual_name} args={len(call_args)}")
|
||||
|
||||
# Store result if needed
|
||||
if dst_vid is not None:
|
||||
|
||||
@ -5,7 +5,9 @@ Handles comparison operations (<, >, <=, >=, ==, !=)
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, Optional, Any
|
||||
from utils.values import resolve_i64_strict
|
||||
from .externcall import lower_externcall
|
||||
from trace import values as trace_values
|
||||
|
||||
def lower_compare(
|
||||
builder: ir.IRBuilder,
|
||||
@ -20,6 +22,7 @@ def lower_compare(
|
||||
block_end_values=None,
|
||||
bb_map=None,
|
||||
meta: Optional[Dict[str, Any]] = None,
|
||||
ctx: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Lower MIR Compare instruction
|
||||
@ -32,15 +35,23 @@ def lower_compare(
|
||||
dst: Destination value ID
|
||||
vmap: Value map
|
||||
"""
|
||||
# If BuildCtx is provided, prefer its maps for consistency.
|
||||
if ctx is not None:
|
||||
try:
|
||||
if getattr(ctx, 'resolver', None) is not None:
|
||||
resolver = ctx.resolver
|
||||
if getattr(ctx, 'preds', None) is not None and preds is None:
|
||||
preds = ctx.preds
|
||||
if getattr(ctx, 'block_end_values', None) is not None and block_end_values is None:
|
||||
block_end_values = ctx.block_end_values
|
||||
if getattr(ctx, 'bb_map', None) is not None and bb_map is None:
|
||||
bb_map = ctx.bb_map
|
||||
except Exception:
|
||||
pass
|
||||
# Get operands
|
||||
# Prefer same-block SSA from vmap; fallback to resolver for cross-block dominance
|
||||
lhs_val = vmap.get(lhs)
|
||||
rhs_val = vmap.get(rhs)
|
||||
if (lhs_val is None or rhs_val is None) and resolver is not None and preds is not None and block_end_values is not None and current_block is not None:
|
||||
if lhs_val is None:
|
||||
lhs_val = resolver.resolve_i64(lhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
if rhs_val is None:
|
||||
rhs_val = resolver.resolve_i64(rhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
lhs_val = resolve_i64_strict(resolver, lhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
rhs_val = resolve_i64_strict(resolver, rhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
|
||||
i64 = ir.IntType(64)
|
||||
i8p = ir.IntType(8).as_pointer()
|
||||
@ -63,12 +74,7 @@ def lower_compare(
|
||||
except Exception:
|
||||
pass
|
||||
if force_string or lhs_tag or rhs_tag:
|
||||
try:
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_TRACE_VALUES') == '1':
|
||||
print(f"[compare] string-eq path: lhs={lhs} rhs={rhs} force={force_string} tagL={lhs_tag} tagR={rhs_tag}", flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
trace_values(f"[compare] string-eq path: lhs={lhs} rhs={rhs} force={force_string} tagL={lhs_tag} tagR={rhs_tag}")
|
||||
# Prefer same-block SSA (vmap) since string handles are produced in-place; fallback to resolver
|
||||
lh = lhs_val if lhs_val is not None else (
|
||||
resolver.resolve_i64(lhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
@ -78,14 +84,7 @@ def lower_compare(
|
||||
resolver.resolve_i64(rhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
if (resolver is not None and preds is not None and block_end_values is not None and current_block is not None) else ir.Constant(i64, 0)
|
||||
)
|
||||
try:
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_TRACE_VALUES') == '1':
|
||||
lz = isinstance(lh, ir.Constant) and getattr(getattr(lh,'constant',None),'constant',None) == 0
|
||||
rz = isinstance(rh, ir.Constant) and getattr(getattr(rh,'constant',None),'constant',None) == 0
|
||||
print(f"[compare] string-eq args: lh_is_const={isinstance(lh, ir.Constant)} rh_is_const={isinstance(rh, ir.Constant)}", flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
trace_values(f"[compare] string-eq args: lh_is_const={isinstance(lh, ir.Constant)} rh_is_const={isinstance(rh, ir.Constant)}")
|
||||
eqf = None
|
||||
for f in builder.module.functions:
|
||||
if f.name == 'nyash.string.eq_hh':
|
||||
@ -117,12 +116,11 @@ def lower_compare(
|
||||
# Perform signed comparison using canonical predicates ('<','>','<=','>=','==','!=')
|
||||
pred = op if op in ('<','>','<=','>=','==','!=') else '=='
|
||||
cmp_result = builder.icmp_signed(pred, lhs_val, rhs_val, name=f"cmp_{dst}")
|
||||
|
||||
# Convert i1 to i64 (0 or 1)
|
||||
result = builder.zext(cmp_result, i64, name=f"cmp_i64_{dst}")
|
||||
|
||||
# Store result
|
||||
vmap[dst] = result
|
||||
# Store the canonical i1 compare result. Consumers that require i64
|
||||
# should explicitly cast at their use site (e.g., via resolver or
|
||||
# instruction-specific lowering) to avoid emitting casts after
|
||||
# terminators when used as branch conditions.
|
||||
vmap[dst] = cmp_result
|
||||
|
||||
def lower_fcmp(
|
||||
builder: ir.IRBuilder,
|
||||
|
||||
@ -5,6 +5,7 @@ Conditional branch based on condition value
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict
|
||||
from utils.values import resolve_i64_strict
|
||||
|
||||
def lower_branch(
|
||||
builder: ir.IRBuilder,
|
||||
@ -28,22 +29,22 @@ def lower_branch(
|
||||
vmap: Value map
|
||||
bb_map: Block map
|
||||
"""
|
||||
# Get condition value
|
||||
if resolver is not None and preds is not None and block_end_values is not None:
|
||||
cond = resolver.resolve_i64(cond_vid, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
else:
|
||||
cond = vmap.get(cond_vid)
|
||||
if not cond:
|
||||
# Get condition value with preference to same-block SSA
|
||||
cond = resolve_i64_strict(resolver, cond_vid, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
if cond is None:
|
||||
# Default to false if missing
|
||||
cond = ir.Constant(ir.IntType(1), 0)
|
||||
|
||||
# Convert to i1 if needed
|
||||
if hasattr(cond, 'type'):
|
||||
if cond.type == ir.IntType(64):
|
||||
# If we already have an i1 (canonical compare result), use it directly.
|
||||
if isinstance(cond.type, ir.IntType) and cond.type.width == 1:
|
||||
pass
|
||||
elif isinstance(cond.type, ir.IntType) and cond.type.width == 64:
|
||||
# i64 to i1: compare != 0
|
||||
zero = ir.Constant(ir.IntType(64), 0)
|
||||
cond = builder.icmp_unsigned('!=', cond, zero, name="cond_i1")
|
||||
elif cond.type == ir.IntType(8).as_pointer():
|
||||
elif isinstance(cond.type, ir.PointerType):
|
||||
# Pointer to i1: compare != null
|
||||
null = ir.Constant(cond.type, None)
|
||||
cond = builder.icmp_unsigned('!=', cond, null, name="cond_p1")
|
||||
@ -21,4 +21,5 @@ def lower_jump(
|
||||
"""
|
||||
target_bb = bb_map.get(target_bid)
|
||||
if target_bb:
|
||||
builder.branch(target_bb)
|
||||
builder.branch(target_bb)
|
||||
|
||||
80
src/llvm_py/instructions/controlflow/while_.py
Normal file
80
src/llvm_py/instructions/controlflow/while_.py
Normal file
@ -0,0 +1,80 @@
|
||||
"""
|
||||
Lowering helpers for while-control flow (regular structured)
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Any
|
||||
import llvmlite.ir as ir
|
||||
|
||||
def lower_while_regular(
|
||||
builder: ir.IRBuilder,
|
||||
func: ir.Function,
|
||||
cond_vid: int,
|
||||
body_insts: List[Dict[str, Any]],
|
||||
loop_id: int,
|
||||
vmap: Dict[int, ir.Value],
|
||||
bb_map: Dict[int, ir.Block],
|
||||
resolver,
|
||||
preds,
|
||||
block_end_values,
|
||||
):
|
||||
"""Create a minimal while in IR: cond -> body -> cond, with exit.
|
||||
The body instructions are lowered using the caller's dispatcher.
|
||||
"""
|
||||
i1 = ir.IntType(1)
|
||||
i64 = ir.IntType(64)
|
||||
|
||||
# Create basic blocks: cond -> body -> cond, and exit
|
||||
cond_bb = func.append_basic_block(name=f"while{loop_id}_cond")
|
||||
body_bb = func.append_basic_block(name=f"while{loop_id}_body")
|
||||
exit_bb = func.append_basic_block(name=f"while{loop_id}_exit")
|
||||
|
||||
# Jump from current to cond
|
||||
builder.branch(cond_bb)
|
||||
|
||||
# Cond block
|
||||
cbuild = ir.IRBuilder(cond_bb)
|
||||
try:
|
||||
# Resolve against the condition block to localize dominance
|
||||
cond_val = resolver.resolve_i64(cond_vid, cond_bb, preds, block_end_values, vmap, bb_map)
|
||||
except Exception:
|
||||
cond_val = vmap.get(cond_vid)
|
||||
if cond_val is None:
|
||||
cond_val = ir.Constant(i1, 0)
|
||||
# Normalize to i1
|
||||
if hasattr(cond_val, 'type'):
|
||||
if isinstance(cond_val.type, ir.IntType) and cond_val.type.width == 64:
|
||||
zero64 = ir.Constant(i64, 0)
|
||||
cond_val = cbuild.icmp_unsigned('!=', cond_val, zero64, name="while_cond_i1")
|
||||
elif isinstance(cond_val.type, ir.PointerType):
|
||||
nullp = ir.Constant(cond_val.type, None)
|
||||
cond_val = cbuild.icmp_unsigned('!=', cond_val, nullp, name="while_cond_p1")
|
||||
elif isinstance(cond_val.type, ir.IntType) and cond_val.type.width == 1:
|
||||
# already i1
|
||||
pass
|
||||
else:
|
||||
# Fallback: treat as false
|
||||
cond_val = ir.Constant(i1, 0)
|
||||
else:
|
||||
cond_val = ir.Constant(i1, 0)
|
||||
|
||||
cbuild.cbranch(cond_val, body_bb, exit_bb)
|
||||
|
||||
# Body block
|
||||
bbuild = ir.IRBuilder(body_bb)
|
||||
# The caller must provide a dispatcher to lower body_insts; do a simple inline here.
|
||||
# We expect the caller to have a method lower_instruction(builder, inst, func).
|
||||
lower_instruction = getattr(resolver, '_owner_lower_instruction', None)
|
||||
if lower_instruction is None:
|
||||
raise RuntimeError('resolver._owner_lower_instruction not set (needs NyashLLVMBuilder.lower_instruction)')
|
||||
for sub in body_insts:
|
||||
if bbuild.block.terminator is not None:
|
||||
cont = func.append_basic_block(name=f"cont_bb_{bbuild.block.name}")
|
||||
bbuild.position_at_end(cont)
|
||||
lower_instruction(bbuild, sub, func)
|
||||
# Ensure terminator: if not terminated, branch back to cond
|
||||
if bbuild.block.terminator is None:
|
||||
bbuild.branch(cond_bb)
|
||||
|
||||
# Continue at exit
|
||||
builder.position_at_end(exit_bb)
|
||||
|
||||
46
src/llvm_py/instructions/copy.py
Normal file
46
src/llvm_py/instructions/copy.py
Normal file
@ -0,0 +1,46 @@
|
||||
"""
|
||||
Copy instruction lowering
|
||||
MIR13 PHI-off uses explicit copies along edges/blocks to model merges.
|
||||
"""
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, Optional, Any
|
||||
from utils.values import resolve_i64_strict
|
||||
|
||||
def lower_copy(
|
||||
builder: ir.IRBuilder,
|
||||
dst: int,
|
||||
src: int,
|
||||
vmap: Dict[int, ir.Value],
|
||||
resolver=None,
|
||||
current_block=None,
|
||||
preds=None,
|
||||
block_end_values=None,
|
||||
bb_map=None,
|
||||
ctx: Optional[Any] = None,
|
||||
):
|
||||
"""Lower a copy by mapping dst to src value in the current block scope.
|
||||
|
||||
Prefer same-block SSA from vmap; fallback to resolver to preserve
|
||||
dominance and to localize values across predecessors.
|
||||
"""
|
||||
# If BuildCtx is provided, prefer its maps for consistency.
|
||||
if ctx is not None:
|
||||
try:
|
||||
if getattr(ctx, 'resolver', None) is not None:
|
||||
resolver = ctx.resolver
|
||||
if getattr(ctx, 'vmap', None) is not None and vmap is None:
|
||||
vmap = ctx.vmap
|
||||
if getattr(ctx, 'preds', None) is not None and preds is None:
|
||||
preds = ctx.preds
|
||||
if getattr(ctx, 'block_end_values', None) is not None and block_end_values is None:
|
||||
block_end_values = ctx.block_end_values
|
||||
if getattr(ctx, 'bb_map', None) is not None and bb_map is None:
|
||||
bb_map = ctx.bb_map
|
||||
except Exception:
|
||||
pass
|
||||
# Prefer local SSA; resolve otherwise to preserve dominance
|
||||
val = resolve_i64_strict(resolver, src, current_block, preds, block_end_values, vmap, bb_map)
|
||||
if val is None:
|
||||
val = ir.Constant(ir.IntType(64), 0)
|
||||
vmap[dst] = val
|
||||
@ -4,7 +4,7 @@ Minimal mapping for NyRT-exported symbols (console/log family等)
|
||||
"""
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
def lower_externcall(
|
||||
builder: ir.IRBuilder,
|
||||
@ -16,7 +16,8 @@ def lower_externcall(
|
||||
resolver=None,
|
||||
preds=None,
|
||||
block_end_values=None,
|
||||
bb_map=None
|
||||
bb_map=None,
|
||||
ctx: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Lower MIR ExternCall instruction
|
||||
@ -30,6 +31,19 @@ def lower_externcall(
|
||||
vmap: Value map
|
||||
resolver: Optional resolver for type handling
|
||||
"""
|
||||
# If BuildCtx is provided, prefer its maps for consistency.
|
||||
if ctx is not None:
|
||||
try:
|
||||
if getattr(ctx, 'resolver', None) is not None:
|
||||
resolver = ctx.resolver
|
||||
if getattr(ctx, 'preds', None) is not None and preds is None:
|
||||
preds = ctx.preds
|
||||
if getattr(ctx, 'block_end_values', None) is not None and block_end_values is None:
|
||||
block_end_values = ctx.block_end_values
|
||||
if getattr(ctx, 'bb_map', None) is not None and bb_map is None:
|
||||
bb_map = ctx.bb_map
|
||||
except Exception:
|
||||
pass
|
||||
# Accept full symbol names (e.g., "nyash.console.log", "nyash.string.len_h").
|
||||
llvm_name = func_name
|
||||
|
||||
@ -83,13 +97,17 @@ def lower_externcall(
|
||||
call_args: List[ir.Value] = []
|
||||
for i, arg_id in enumerate(args):
|
||||
orig_arg_id = arg_id
|
||||
# Prefer resolver
|
||||
# Prefer resolver/ctx
|
||||
aval = None
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
if len(func.args) > i and isinstance(func.args[i].type, ir.PointerType):
|
||||
aval = resolver.resolve_ptr(arg_id, builder.block, preds, block_end_values, vmap)
|
||||
else:
|
||||
aval = resolver.resolve_i64(arg_id, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
else:
|
||||
try:
|
||||
if len(func.args) > i and isinstance(func.args[i].type, ir.PointerType):
|
||||
aval = resolver.resolve_ptr(arg_id, builder.block, preds, block_end_values, vmap)
|
||||
else:
|
||||
aval = resolver.resolve_i64(arg_id, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
except Exception:
|
||||
aval = None
|
||||
if aval is None:
|
||||
aval = vmap.get(arg_id)
|
||||
if aval is None:
|
||||
# Default guess
|
||||
|
||||
@ -123,7 +123,10 @@ def lower_while_loopform(
|
||||
lf.tag_phi = tag_phi
|
||||
lf.payload_phi = payload_phi
|
||||
|
||||
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
|
||||
print(f"[LoopForm] Created loop structure (id={loop_id})")
|
||||
try:
|
||||
from trace import debug as trace_debug
|
||||
trace_debug(f"[LoopForm] Created loop structure (id={loop_id})")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
@ -4,7 +4,7 @@ Handles box creation (new StringBox(), new IntegerBox(), etc.)
|
||||
"""
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
def lower_newbox(
|
||||
builder: ir.IRBuilder,
|
||||
@ -13,7 +13,8 @@ def lower_newbox(
|
||||
args: List[int],
|
||||
dst_vid: int,
|
||||
vmap: Dict[int, ir.Value],
|
||||
resolver=None
|
||||
resolver=None,
|
||||
ctx: Optional[Any] = None
|
||||
) -> None:
|
||||
"""
|
||||
Lower MIR NewBox instruction
|
||||
|
||||
@ -134,12 +134,15 @@ def lower_phi(
|
||||
import os
|
||||
if used_default_zero and os.environ.get('NYASH_LLVM_PHI_STRICT') == '1':
|
||||
raise RuntimeError(f"[LLVM_PY] PHI dst={dst_vid} used synthesized zero; check preds/incoming")
|
||||
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
|
||||
try:
|
||||
from trace import phi as trace_phi
|
||||
try:
|
||||
blkname = str(current_block.name)
|
||||
except Exception:
|
||||
blkname = '<blk>'
|
||||
print(f"[PHI] {blkname} v{dst_vid} incoming={len(incoming_pairs)} zero={1 if used_default_zero else 0}")
|
||||
trace_phi(f"[PHI] {blkname} v{dst_vid} incoming={len(incoming_pairs)} zero={1 if used_default_zero else 0}")
|
||||
except Exception:
|
||||
pass
|
||||
# Propagate string-ness: if any incoming value-id is tagged string-ish, mark dst as string-ish.
|
||||
try:
|
||||
if resolver is not None and hasattr(resolver, 'is_stringish') and hasattr(resolver, 'mark_string'):
|
||||
|
||||
@ -4,7 +4,7 @@ Handles void and value returns
|
||||
"""
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, Optional
|
||||
from typing import Dict, Optional, Any
|
||||
|
||||
def lower_return(
|
||||
builder: ir.IRBuilder,
|
||||
@ -14,7 +14,8 @@ def lower_return(
|
||||
resolver=None,
|
||||
preds=None,
|
||||
block_end_values=None,
|
||||
bb_map=None
|
||||
bb_map=None,
|
||||
ctx: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Lower MIR Return instruction
|
||||
@ -25,6 +26,19 @@ def lower_return(
|
||||
vmap: Value map
|
||||
return_type: Expected return type
|
||||
"""
|
||||
# Prefer BuildCtx maps if provided
|
||||
if ctx is not None:
|
||||
try:
|
||||
if getattr(ctx, 'resolver', None) is not None:
|
||||
resolver = ctx.resolver
|
||||
if getattr(ctx, 'preds', None) is not None and preds is None:
|
||||
preds = ctx.preds
|
||||
if getattr(ctx, 'block_end_values', None) is not None and block_end_values is None:
|
||||
block_end_values = ctx.block_end_values
|
||||
if getattr(ctx, 'bb_map', None) is not None and bb_map is None:
|
||||
bb_map = ctx.bb_map
|
||||
except Exception:
|
||||
pass
|
||||
if value_id is None:
|
||||
# Void return
|
||||
builder.ret_void()
|
||||
@ -33,6 +47,53 @@ def lower_return(
|
||||
ret_val = None
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
try:
|
||||
# If this block has a declared PHI for the return value, force using the
|
||||
# local PHI placeholder to ensure dominance and let finalize_phis wire it.
|
||||
try:
|
||||
block_name = builder.block.name
|
||||
cur_bid = int(str(block_name).replace('bb',''))
|
||||
except Exception:
|
||||
cur_bid = -1
|
||||
try:
|
||||
bm = getattr(resolver, 'block_phi_incomings', {}) or {}
|
||||
except Exception:
|
||||
bm = {}
|
||||
if isinstance(value_id, int) and isinstance(bm.get(cur_bid), dict) and value_id in bm.get(cur_bid):
|
||||
# Reuse predeclared ret-phi when available
|
||||
cur = None
|
||||
try:
|
||||
rp = getattr(resolver, 'ret_phi_map', {}) or {}
|
||||
key = (int(cur_bid), int(value_id))
|
||||
if key in rp:
|
||||
cur = rp[key]
|
||||
except Exception:
|
||||
cur = None
|
||||
if cur is None:
|
||||
btop = ir.IRBuilder(builder.block)
|
||||
try:
|
||||
btop.position_at_start(builder.block)
|
||||
except Exception:
|
||||
pass
|
||||
# Reuse existing local phi if present; otherwise create
|
||||
cur = vmap.get(value_id)
|
||||
need_new = True
|
||||
try:
|
||||
need_new = not (cur is not None and hasattr(cur, 'add_incoming') and getattr(getattr(cur, 'basic_block', None), 'name', None) == builder.block.name)
|
||||
except Exception:
|
||||
need_new = True
|
||||
if need_new:
|
||||
cur = btop.phi(ir.IntType(64), name=f"phi_ret_{value_id}")
|
||||
# Bind to maps
|
||||
vmap[value_id] = cur
|
||||
try:
|
||||
if hasattr(resolver, 'global_vmap') and isinstance(resolver.global_vmap, dict):
|
||||
resolver.global_vmap[value_id] = cur
|
||||
except Exception:
|
||||
pass
|
||||
ret_val = cur
|
||||
if ret_val is not None:
|
||||
builder.ret(ret_val)
|
||||
return
|
||||
if isinstance(return_type, ir.PointerType):
|
||||
ret_val = resolver.resolve_ptr(value_id, builder.block, preds, block_end_values, vmap)
|
||||
else:
|
||||
|
||||
@ -4,7 +4,7 @@ GC safepoints where runtime can safely collect garbage
|
||||
"""
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
def lower_safepoint(
|
||||
builder: ir.IRBuilder,
|
||||
@ -15,7 +15,8 @@ def lower_safepoint(
|
||||
resolver=None,
|
||||
preds=None,
|
||||
block_end_values=None,
|
||||
bb_map=None
|
||||
bb_map=None,
|
||||
ctx: Optional[Any] = None
|
||||
) -> None:
|
||||
"""
|
||||
Lower MIR Safepoint instruction
|
||||
@ -53,8 +54,18 @@ def lower_safepoint(
|
||||
|
||||
# Store each live value
|
||||
for i, vid in enumerate(live_values):
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
val = resolver.resolve_i64(vid, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
# Prefer BuildCtx if provided
|
||||
r = resolver; p = preds; bev = block_end_values; bbm = bb_map
|
||||
if ctx is not None:
|
||||
try:
|
||||
r = getattr(ctx, 'resolver', r)
|
||||
p = getattr(ctx, 'preds', p)
|
||||
bev = getattr(ctx, 'block_end_values', bev)
|
||||
bbm = getattr(ctx, 'bb_map', bbm)
|
||||
except Exception:
|
||||
pass
|
||||
if r is not None and p is not None and bev is not None and bbm is not None:
|
||||
val = r.resolve_i64(vid, builder.block, p, bev, vmap, bbm)
|
||||
else:
|
||||
val = vmap.get(vid, ir.Constant(i64, 0))
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ Handles type conversions and type checks
|
||||
"""
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, Optional
|
||||
from typing import Dict, Optional, Any
|
||||
|
||||
def lower_typeop(
|
||||
builder: ir.IRBuilder,
|
||||
@ -16,7 +16,8 @@ def lower_typeop(
|
||||
resolver=None,
|
||||
preds=None,
|
||||
block_end_values=None,
|
||||
bb_map=None
|
||||
bb_map=None,
|
||||
ctx: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Lower MIR TypeOp instruction
|
||||
@ -35,6 +36,19 @@ def lower_typeop(
|
||||
vmap: Value map
|
||||
resolver: Optional resolver for type handling
|
||||
"""
|
||||
# Prefer BuildCtx maps when provided
|
||||
if ctx is not None:
|
||||
try:
|
||||
if getattr(ctx, 'resolver', None) is not None:
|
||||
resolver = ctx.resolver
|
||||
if getattr(ctx, 'preds', None) is not None and preds is None:
|
||||
preds = ctx.preds
|
||||
if getattr(ctx, 'block_end_values', None) is not None and block_end_values is None:
|
||||
block_end_values = ctx.block_end_values
|
||||
if getattr(ctx, 'bb_map', None) is not None and bb_map is None:
|
||||
bb_map = ctx.bb_map
|
||||
except Exception:
|
||||
pass
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
src_val = resolver.resolve_i64(src_vid, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
else:
|
||||
@ -83,7 +97,8 @@ def lower_convert(
|
||||
resolver=None,
|
||||
preds=None,
|
||||
block_end_values=None,
|
||||
bb_map=None
|
||||
bb_map=None,
|
||||
ctx: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Lower type conversion between primitive types
|
||||
@ -96,6 +111,18 @@ def lower_convert(
|
||||
to_type: Target type
|
||||
vmap: Value map
|
||||
"""
|
||||
if ctx is not None:
|
||||
try:
|
||||
if getattr(ctx, 'resolver', None) is not None:
|
||||
resolver = ctx.resolver
|
||||
if getattr(ctx, 'preds', None) is not None and preds is None:
|
||||
preds = ctx.preds
|
||||
if getattr(ctx, 'block_end_values', None) is not None and block_end_values is None:
|
||||
block_end_values = ctx.block_end_values
|
||||
if getattr(ctx, 'bb_map', None) is not None and bb_map is None:
|
||||
bb_map = ctx.bb_map
|
||||
except Exception:
|
||||
pass
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
# Choose resolution based on from_type
|
||||
if from_type == "ptr":
|
||||
|
||||
@ -15,9 +15,10 @@ import llvmlite.binding as llvm
|
||||
from instructions.const import lower_const
|
||||
from instructions.binop import lower_binop
|
||||
from instructions.compare import lower_compare
|
||||
from instructions.jump import lower_jump
|
||||
from instructions.branch import lower_branch
|
||||
from instructions.controlflow.jump import lower_jump
|
||||
from instructions.controlflow.branch import lower_branch
|
||||
from instructions.ret import lower_return
|
||||
from instructions.copy import lower_copy
|
||||
# PHI are deferred; finalize_phis wires incoming edges after snapshots
|
||||
from instructions.call import lower_call
|
||||
from instructions.boxcall import lower_boxcall
|
||||
@ -27,6 +28,13 @@ from instructions.newbox import lower_newbox
|
||||
from instructions.safepoint import lower_safepoint, insert_automatic_safepoint
|
||||
from instructions.barrier import lower_barrier
|
||||
from instructions.loopform import lower_while_loopform
|
||||
from instructions.controlflow.while_ import lower_while_regular
|
||||
from phi_wiring import setup_phi_placeholders as _setup_phi_placeholders, finalize_phis as _finalize_phis
|
||||
from trace import debug as trace_debug
|
||||
from trace import phi as trace_phi
|
||||
from prepass.loops import detect_simple_while
|
||||
from prepass.if_merge import plan_ret_phi_predeclare
|
||||
from build_ctx import BuildCtx
|
||||
|
||||
from resolver import Resolver
|
||||
from mir_reader import MIRReader
|
||||
@ -71,6 +79,8 @@ class NyashLLVMBuilder:
|
||||
# Heuristics for minor gated fixes
|
||||
self.current_function_name: Optional[str] = None
|
||||
self._last_substring_vid: Optional[int] = None
|
||||
# Map of (block_id, value_id) -> predeclared PHI for ret-merge if-merge prepass
|
||||
self.predeclared_ret_phis: Dict[Tuple[int, int], ir.Instruction] = {}
|
||||
|
||||
def build_from_mir(self, mir_json: Dict[str, Any]) -> str:
|
||||
"""Build LLVM IR from MIR JSON"""
|
||||
@ -177,11 +187,12 @@ class NyashLLVMBuilder:
|
||||
os.makedirs(os.path.dirname(dump_path), exist_ok=True)
|
||||
with open(dump_path, 'w') as f:
|
||||
f.write(ir_text)
|
||||
elif os.environ.get('NYASH_CLI_VERBOSE') == '1':
|
||||
else:
|
||||
# Default dump location when verbose and not explicitly set
|
||||
os.makedirs('tmp', exist_ok=True)
|
||||
with open('tmp/nyash_harness.ll', 'w') as f:
|
||||
f.write(ir_text)
|
||||
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
|
||||
os.makedirs('tmp', exist_ok=True)
|
||||
with open('tmp/nyash_harness.ll', 'w') as f:
|
||||
f.write(ir_text)
|
||||
except Exception:
|
||||
pass
|
||||
return ir_text
|
||||
@ -322,13 +333,176 @@ class NyashLLVMBuilder:
|
||||
# Prepass: collect producer stringish hints and PHI metadata for all blocks
|
||||
# and create placeholders at each block head so that resolver can safely
|
||||
# return existing PHIs without creating new ones.
|
||||
self.setup_phi_placeholders(blocks)
|
||||
|
||||
_setup_phi_placeholders(self, blocks)
|
||||
|
||||
# Optional: if-merge prepass → predeclare PHI for return-merge blocks
|
||||
# Gate with NYASH_LLVM_PREPASS_IFMERGE=1
|
||||
try:
|
||||
if os.environ.get('NYASH_LLVM_PREPASS_IFMERGE') == '1':
|
||||
plan = plan_ret_phi_predeclare(block_by_id)
|
||||
if plan:
|
||||
# Ensure block_phi_incomings map exists
|
||||
if not hasattr(self, 'block_phi_incomings') or self.block_phi_incomings is None:
|
||||
self.block_phi_incomings = {}
|
||||
for bbid, ret_vid in plan.items():
|
||||
# Create a placeholder PHI at block head if missing
|
||||
bb0 = self.bb_map.get(bbid)
|
||||
if bb0 is not None:
|
||||
b0 = ir.IRBuilder(bb0)
|
||||
try:
|
||||
b0.position_at_start(bb0)
|
||||
except Exception:
|
||||
pass
|
||||
cur = self.vmap.get(ret_vid)
|
||||
need_new = True
|
||||
try:
|
||||
need_new = not (cur is not None and hasattr(cur, 'add_incoming'))
|
||||
except Exception:
|
||||
need_new = True
|
||||
if need_new:
|
||||
ph = b0.phi(self.i64, name=f"phi_ret_{ret_vid}")
|
||||
self.vmap[ret_vid] = ph
|
||||
else:
|
||||
ph = cur
|
||||
# Record for later unify
|
||||
try:
|
||||
self.predeclared_ret_phis[(int(bbid), int(ret_vid))] = ph
|
||||
except Exception:
|
||||
pass
|
||||
# Record declared incoming metadata using the same value-id
|
||||
# for each predecessor; finalize_phis will resolve per-pred end values.
|
||||
try:
|
||||
preds_raw = [p for p in self.preds.get(bbid, []) if p != bbid]
|
||||
except Exception:
|
||||
preds_raw = []
|
||||
# Dedup while preserving order
|
||||
seen = set()
|
||||
preds_list = []
|
||||
for p in preds_raw:
|
||||
if p not in seen:
|
||||
preds_list.append(p)
|
||||
seen.add(p)
|
||||
try:
|
||||
# finalize_phis reads pairs as (decl_b, v_src) and maps to nearest predecessor.
|
||||
# We provide (bb_pred, ret_vid) for all preds.
|
||||
self.block_phi_incomings.setdefault(int(bbid), {})[int(ret_vid)] = [
|
||||
(int(p), int(ret_vid)) for p in preds_list
|
||||
]
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
trace_debug(f"[prepass] if-merge: predeclare PHI at bb{bbid} for v{ret_vid} preds={preds_list}")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Optional: simple loop prepass → synthesize a structured while body
|
||||
loop_plan = None
|
||||
try:
|
||||
if os.environ.get('NYASH_LLVM_PREPASS_LOOP') == '1':
|
||||
loop_plan = detect_simple_while(block_by_id)
|
||||
if loop_plan is not None:
|
||||
trace_debug(f"[prepass] detect loop header=bb{loop_plan['header']} then=bb{loop_plan['then']} latch=bb{loop_plan['latch']} exit=bb{loop_plan['exit']}")
|
||||
except Exception:
|
||||
loop_plan = None
|
||||
|
||||
# Provide predeclared ret-phi map to resolver for ret lowering to reuse
|
||||
try:
|
||||
self.resolver.ret_phi_map = self.predeclared_ret_phis
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Now lower blocks
|
||||
skipped: set[int] = set()
|
||||
if loop_plan is not None:
|
||||
try:
|
||||
for bskip in loop_plan.get('skip_blocks', []):
|
||||
if bskip != loop_plan.get('header'):
|
||||
skipped.add(int(bskip))
|
||||
except Exception:
|
||||
pass
|
||||
for bid in order:
|
||||
block_data = block_by_id.get(bid)
|
||||
if block_data is None:
|
||||
continue
|
||||
# If loop prepass applies, lower while once at header and skip loop-internal blocks
|
||||
if loop_plan is not None and bid == loop_plan.get('header'):
|
||||
bb = self.bb_map[bid]
|
||||
builder = ir.IRBuilder(bb)
|
||||
try:
|
||||
self.resolver.builder = builder
|
||||
self.resolver.module = self.module
|
||||
except Exception:
|
||||
pass
|
||||
# Lower while via loopform (if enabled) or regular fallback
|
||||
self.loop_count += 1
|
||||
body_insts = loop_plan.get('body_insts', [])
|
||||
cond_vid = loop_plan.get('cond')
|
||||
from instructions.loopform import lower_while_loopform
|
||||
ok = False
|
||||
try:
|
||||
# Use a clean per-while vmap context seeded from global placeholders
|
||||
self._current_vmap = dict(self.vmap)
|
||||
ok = lower_while_loopform(builder, func, cond_vid, body_insts,
|
||||
self.loop_count, self.vmap, self.bb_map,
|
||||
self.resolver, self.preds, self.block_end_values)
|
||||
except Exception:
|
||||
ok = False
|
||||
if not ok:
|
||||
# Prepare resolver backref for instruction dispatcher
|
||||
try:
|
||||
self.resolver._owner_lower_instruction = self.lower_instruction
|
||||
except Exception:
|
||||
pass
|
||||
lower_while_regular(builder, func, cond_vid, body_insts,
|
||||
self.loop_count, self.vmap, self.bb_map,
|
||||
self.resolver, self.preds, self.block_end_values)
|
||||
# Clear while vmap context
|
||||
try:
|
||||
delattr(self, '_current_vmap')
|
||||
except Exception:
|
||||
pass
|
||||
# Mark blocks to skip
|
||||
for bskip in loop_plan.get('skip_blocks', []):
|
||||
skipped.add(bskip)
|
||||
# Ensure skipped original blocks have a valid terminator: branch to while exit
|
||||
try:
|
||||
exit_name = f"while{self.loop_count}_exit"
|
||||
exit_bb = None
|
||||
for bbf in func.blocks:
|
||||
try:
|
||||
if str(bbf.name) == exit_name:
|
||||
exit_bb = bbf
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if exit_bb is not None:
|
||||
# Connect while exit to original exit block if available
|
||||
try:
|
||||
orig_exit_bb = self.bb_map.get(loop_plan.get('exit'))
|
||||
if orig_exit_bb is not None and exit_bb.terminator is None:
|
||||
ibx = ir.IRBuilder(exit_bb)
|
||||
ibx.branch(orig_exit_bb)
|
||||
except Exception:
|
||||
pass
|
||||
for bskip in loop_plan.get('skip_blocks', []):
|
||||
if bskip == loop_plan.get('header'):
|
||||
continue
|
||||
bb_skip = self.bb_map.get(bskip)
|
||||
if bb_skip is None:
|
||||
continue
|
||||
try:
|
||||
if bb_skip.terminator is None:
|
||||
ib = ir.IRBuilder(bb_skip)
|
||||
ib.branch(exit_bb)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
continue
|
||||
if bid in skipped:
|
||||
continue
|
||||
bb = self.bb_map[bid]
|
||||
self.lower_block(bb, block_data, func)
|
||||
|
||||
@ -337,10 +511,32 @@ class NyashLLVMBuilder:
|
||||
self.resolver.def_blocks = self.def_blocks
|
||||
# Provide phi metadata for this function to resolver
|
||||
self.resolver.block_phi_incomings = getattr(self, 'block_phi_incomings', {})
|
||||
# Attach a BuildCtx object for future refactors (non-breaking)
|
||||
try:
|
||||
self.ctx = BuildCtx(
|
||||
module=self.module,
|
||||
i64=self.i64,
|
||||
i32=self.i32,
|
||||
i8=self.i8,
|
||||
i1=self.i1,
|
||||
i8p=self.i8p,
|
||||
vmap=self.vmap,
|
||||
bb_map=self.bb_map,
|
||||
preds=self.preds,
|
||||
block_end_values=self.block_end_values,
|
||||
resolver=self.resolver,
|
||||
trace_phi=os.environ.get('NYASH_LLVM_TRACE_PHI') == '1',
|
||||
verbose=os.environ.get('NYASH_CLI_VERBOSE') == '1',
|
||||
)
|
||||
# Also expose via resolver for convenience until migration completes
|
||||
self.resolver.ctx = self.ctx
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
# Finalize PHIs for this function now that all snapshots for it exist
|
||||
self.finalize_phis()
|
||||
_finalize_phis(self)
|
||||
|
||||
|
||||
def setup_phi_placeholders(self, blocks: List[Dict[str, Any]]):
|
||||
"""Predeclare PHIs and collect incoming metadata for finalize_phis.
|
||||
@ -439,8 +635,17 @@ class NyashLLVMBuilder:
|
||||
pass
|
||||
|
||||
def lower_block(self, bb: ir.Block, block_data: Dict[str, Any], func: ir.Function):
|
||||
"""Lower a single basic block"""
|
||||
"""Lower a single basic block.
|
||||
|
||||
Emit all non-terminator ops first, then control-flow terminators
|
||||
(branch/jump/ret). This avoids generating IR after a terminator.
|
||||
"""
|
||||
builder = ir.IRBuilder(bb)
|
||||
try:
|
||||
import os
|
||||
trace_debug(f"[llvm-py] === lower_block bb{block_data.get('id')} ===")
|
||||
except Exception:
|
||||
pass
|
||||
# Provide builder/module to resolver for PHI/casts insertion
|
||||
try:
|
||||
self.resolver.builder = builder
|
||||
@ -448,54 +653,187 @@ class NyashLLVMBuilder:
|
||||
except Exception:
|
||||
pass
|
||||
instructions = block_data.get("instructions", [])
|
||||
# Lower non-PHI instructions strictly in original program order.
|
||||
# Reordering here can easily introduce use-before-def within the same
|
||||
# basic block (e.g., string ops that depend on prior me.* calls).
|
||||
# Ensure JSON-declared PHIs are materialized at block start before any terminator
|
||||
try:
|
||||
phi_insts = [inst for inst in (instructions or []) if inst.get('op') == 'phi']
|
||||
if phi_insts:
|
||||
btop = ir.IRBuilder(bb)
|
||||
btop.position_at_start(bb)
|
||||
for pinst in phi_insts:
|
||||
dstp = pinst.get('dst')
|
||||
if isinstance(dstp, int):
|
||||
cur = self.vmap.get(dstp)
|
||||
need_new = True
|
||||
try:
|
||||
need_new = not (cur is not None and hasattr(cur, 'add_incoming'))
|
||||
except Exception:
|
||||
need_new = True
|
||||
if need_new:
|
||||
phi = btop.phi(self.i64, name=f"phi_{dstp}")
|
||||
self.vmap[dstp] = phi
|
||||
except Exception:
|
||||
pass
|
||||
# Partition into body ops and terminators
|
||||
body_ops: List[Dict[str, Any]] = []
|
||||
term_ops: List[Dict[str, Any]] = []
|
||||
for inst in (instructions or []):
|
||||
opx = inst.get("op")
|
||||
if opx in ("branch", "jump", "ret"):
|
||||
term_ops.append(inst)
|
||||
elif opx == "phi":
|
||||
continue
|
||||
else:
|
||||
body_ops.append(inst)
|
||||
# Per-block SSA map (avoid cross-block vmap pollution)
|
||||
# Seed with non-PHI globals and PHIs that belong to this block only.
|
||||
vmap_cur: Dict[int, ir.Value] = {}
|
||||
try:
|
||||
for _vid, _val in (self.vmap or {}).items():
|
||||
keep = True
|
||||
try:
|
||||
if hasattr(_val, 'add_incoming'):
|
||||
bb_of = getattr(getattr(_val, 'basic_block', None), 'name', None)
|
||||
keep = (bb_of == bb.name)
|
||||
except Exception:
|
||||
keep = False
|
||||
if keep:
|
||||
vmap_cur[_vid] = _val
|
||||
except Exception:
|
||||
vmap_cur = dict(self.vmap)
|
||||
# Expose to lower_instruction users (e.g., while_ regular lowering)
|
||||
self._current_vmap = vmap_cur
|
||||
created_ids: List[int] = []
|
||||
non_phi_insts = [inst for inst in instructions if inst.get("op") != "phi"]
|
||||
for inst in non_phi_insts:
|
||||
# Stop if a terminator has already been emitted for this block
|
||||
# Compute ids defined in this block to help with copy/PHI decisions
|
||||
defined_here_all: set = set()
|
||||
for _inst in body_ops:
|
||||
try:
|
||||
d = _inst.get('dst')
|
||||
if isinstance(d, int):
|
||||
defined_here_all.add(d)
|
||||
except Exception:
|
||||
pass
|
||||
# Keep PHI synthesis on-demand in resolver; avoid predeclaring here to reduce clashes.
|
||||
# Lower body ops first in-order
|
||||
for i_idx, inst in enumerate(body_ops):
|
||||
try:
|
||||
import os
|
||||
trace_debug(f"[llvm-py] body op: {inst.get('op')} dst={inst.get('dst')} cond={inst.get('cond')}")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if bb.terminator is not None:
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
builder.position_at_end(bb)
|
||||
self.lower_instruction(builder, inst, func)
|
||||
# Special-case copy: avoid forward self-block dependencies only when src is defined later in this block
|
||||
if inst.get('op') == 'copy':
|
||||
src_i = inst.get('src')
|
||||
skip_now = False
|
||||
if isinstance(src_i, int):
|
||||
try:
|
||||
# Check if src will be defined in a subsequent instruction
|
||||
for _rest in body_ops[i_idx+1:]:
|
||||
try:
|
||||
if int(_rest.get('dst')) == int(src_i):
|
||||
skip_now = True
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
if skip_now:
|
||||
# Skip now; a later copy will remap after src becomes available
|
||||
pass
|
||||
else:
|
||||
self.lower_instruction(builder, inst, func)
|
||||
else:
|
||||
self.lower_instruction(builder, inst, func)
|
||||
# Sync per-block vmap snapshot with any new definitions that were
|
||||
# written into the global vmap by lowering routines (e.g., copy)
|
||||
try:
|
||||
dst = inst.get("dst")
|
||||
if isinstance(dst, int) and dst not in created_ids and dst in self.vmap:
|
||||
created_ids.append(dst)
|
||||
if isinstance(dst, int):
|
||||
if dst in self.vmap:
|
||||
_gval = self.vmap[dst]
|
||||
# Avoid syncing PHIs that belong to other blocks (placeholders)
|
||||
try:
|
||||
if hasattr(_gval, 'add_incoming'):
|
||||
bb_of = getattr(getattr(_gval, 'basic_block', None), 'name', None)
|
||||
if bb_of == bb.name:
|
||||
vmap_cur[dst] = _gval
|
||||
else:
|
||||
vmap_cur[dst] = _gval
|
||||
except Exception:
|
||||
vmap_cur[dst] = _gval
|
||||
if dst not in created_ids and dst in vmap_cur:
|
||||
created_ids.append(dst)
|
||||
except Exception:
|
||||
pass
|
||||
# Ret-phi proactive insertion removed; resolver handles ret localization as needed.
|
||||
|
||||
# Lower terminators at end, preserving order
|
||||
for inst in term_ops:
|
||||
try:
|
||||
import os
|
||||
trace_debug(f"[llvm-py] term op: {inst.get('op')} dst={inst.get('dst')} cond={inst.get('cond')}")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if bb.terminator is not None:
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
builder.position_at_end(bb)
|
||||
# (if-merge handled by resolver + finalize_phis)
|
||||
self.lower_instruction(builder, inst, func)
|
||||
# Sync back local PHIs created in this block into the global vmap so that
|
||||
# finalize_phis targets the same SSA nodes as terminators just used.
|
||||
try:
|
||||
for vid in created_ids:
|
||||
val = vmap_cur.get(vid)
|
||||
if val is not None and hasattr(val, 'add_incoming'):
|
||||
try:
|
||||
if getattr(getattr(val, 'basic_block', None), 'name', None) == bb.name:
|
||||
self.vmap[vid] = val
|
||||
except Exception:
|
||||
self.vmap[vid] = val
|
||||
except Exception:
|
||||
pass
|
||||
# Snapshot end-of-block values for sealed PHI wiring
|
||||
bid = block_data.get("id", 0)
|
||||
# Robust snapshot: clone the entire vmap at block end so that
|
||||
# values that were not redefined in this block (but remain live)
|
||||
# are available to PHI finalize wiring. This avoids omissions of
|
||||
# phi-dst/cyclic and carry-over values.
|
||||
snap: Dict[int, ir.Value] = dict(self.vmap)
|
||||
snap: Dict[int, ir.Value] = dict(vmap_cur)
|
||||
try:
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
|
||||
keys = sorted(list(snap.keys()))
|
||||
print(f"[builder] snapshot bb{bid} keys={keys[:20]}...", flush=True)
|
||||
keys = sorted(list(snap.keys()))
|
||||
trace_phi(f"[builder] snapshot bb{bid} keys={keys[:20]}...")
|
||||
except Exception:
|
||||
pass
|
||||
# Record block-local definitions for lifetime hinting
|
||||
for vid in created_ids:
|
||||
if vid in self.vmap:
|
||||
if vid in vmap_cur:
|
||||
self.def_blocks.setdefault(vid, set()).add(block_data.get("id", 0))
|
||||
self.block_end_values[bid] = snap
|
||||
# Clear current vmap context
|
||||
try:
|
||||
delattr(self, '_current_vmap')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def lower_instruction(self, builder: ir.IRBuilder, inst: Dict[str, Any], func: ir.Function):
|
||||
"""Dispatch instruction to appropriate handler"""
|
||||
op = inst.get("op")
|
||||
# Pick current vmap context
|
||||
vmap_ctx = getattr(self, '_current_vmap', self.vmap)
|
||||
|
||||
if op == "const":
|
||||
dst = inst.get("dst")
|
||||
value = inst.get("value")
|
||||
lower_const(builder, self.module, dst, value, self.vmap, self.resolver)
|
||||
lower_const(builder, self.module, dst, value, vmap_ctx, self.resolver)
|
||||
|
||||
elif op == "binop":
|
||||
operation = inst.get("operation")
|
||||
@ -504,23 +842,28 @@ class NyashLLVMBuilder:
|
||||
dst = inst.get("dst")
|
||||
dst_type = inst.get("dst_type")
|
||||
lower_binop(builder, self.resolver, operation, lhs, rhs, dst,
|
||||
self.vmap, builder.block, self.preds, self.block_end_values, self.bb_map,
|
||||
vmap_ctx, builder.block, self.preds, self.block_end_values, self.bb_map,
|
||||
dst_type=dst_type)
|
||||
|
||||
elif op == "jump":
|
||||
target = inst.get("target")
|
||||
lower_jump(builder, target, self.bb_map)
|
||||
|
||||
elif op == "copy":
|
||||
dst = inst.get("dst")
|
||||
src = inst.get("src")
|
||||
lower_copy(builder, dst, src, vmap_ctx, self.resolver, builder.block, self.preds, self.block_end_values, self.bb_map, getattr(self, 'ctx', None))
|
||||
|
||||
elif op == "branch":
|
||||
cond = inst.get("cond")
|
||||
then_bid = inst.get("then")
|
||||
else_bid = inst.get("else")
|
||||
lower_branch(builder, cond, then_bid, else_bid, self.vmap, self.bb_map, self.resolver, self.preds, self.block_end_values)
|
||||
lower_branch(builder, cond, then_bid, else_bid, vmap_ctx, self.bb_map, self.resolver, self.preds, self.block_end_values)
|
||||
|
||||
elif op == "ret":
|
||||
value = inst.get("value")
|
||||
lower_return(builder, value, self.vmap, func.function_type.return_type,
|
||||
self.resolver, self.preds, self.block_end_values, self.bb_map)
|
||||
lower_return(builder, value, vmap_ctx, func.function_type.return_type,
|
||||
self.resolver, self.preds, self.block_end_values, self.bb_map, getattr(self, 'ctx', None))
|
||||
|
||||
elif op == "phi":
|
||||
# No-op here: PHIはメタのみ(resolverがon‑demand生成)
|
||||
@ -533,15 +876,16 @@ class NyashLLVMBuilder:
|
||||
rhs = inst.get("rhs")
|
||||
dst = inst.get("dst")
|
||||
cmp_kind = inst.get("cmp_kind")
|
||||
lower_compare(builder, operation, lhs, rhs, dst, self.vmap,
|
||||
lower_compare(builder, operation, lhs, rhs, dst, vmap_ctx,
|
||||
self.resolver, builder.block, self.preds, self.block_end_values, self.bb_map,
|
||||
meta={"cmp_kind": cmp_kind} if cmp_kind else None)
|
||||
meta={"cmp_kind": cmp_kind} if cmp_kind else None,
|
||||
ctx=getattr(self, 'ctx', None))
|
||||
|
||||
elif op == "call":
|
||||
func_name = inst.get("func")
|
||||
args = inst.get("args", [])
|
||||
dst = inst.get("dst")
|
||||
lower_call(builder, self.module, func_name, args, dst, self.vmap, self.resolver, self.preds, self.block_end_values, self.bb_map)
|
||||
lower_call(builder, self.module, func_name, args, dst, vmap_ctx, self.resolver, self.preds, self.block_end_values, self.bb_map, getattr(self, 'ctx', None))
|
||||
|
||||
elif op == "boxcall":
|
||||
box_vid = inst.get("box")
|
||||
@ -549,7 +893,7 @@ class NyashLLVMBuilder:
|
||||
args = inst.get("args", [])
|
||||
dst = inst.get("dst")
|
||||
lower_boxcall(builder, self.module, box_vid, method, args, dst,
|
||||
self.vmap, self.resolver, self.preds, self.block_end_values, self.bb_map)
|
||||
vmap_ctx, self.resolver, self.preds, self.block_end_values, self.bb_map, getattr(self, 'ctx', None))
|
||||
# Optional: honor explicit dst_type for tagging (string handle)
|
||||
try:
|
||||
dst_type = inst.get("dst_type")
|
||||
@ -571,14 +915,14 @@ class NyashLLVMBuilder:
|
||||
args = inst.get("args", [])
|
||||
dst = inst.get("dst")
|
||||
lower_externcall(builder, self.module, func_name, args, dst,
|
||||
self.vmap, self.resolver, self.preds, self.block_end_values, self.bb_map)
|
||||
vmap_ctx, self.resolver, self.preds, self.block_end_values, self.bb_map, getattr(self, 'ctx', None))
|
||||
|
||||
elif op == "newbox":
|
||||
box_type = inst.get("type")
|
||||
args = inst.get("args", [])
|
||||
dst = inst.get("dst")
|
||||
lower_newbox(builder, self.module, box_type, args, dst,
|
||||
self.vmap, self.resolver)
|
||||
vmap_ctx, self.resolver, getattr(self, 'ctx', None))
|
||||
|
||||
elif op == "typeop":
|
||||
operation = inst.get("operation")
|
||||
@ -586,13 +930,14 @@ class NyashLLVMBuilder:
|
||||
dst = inst.get("dst")
|
||||
target_type = inst.get("target_type")
|
||||
lower_typeop(builder, operation, src, dst, target_type,
|
||||
self.vmap, self.resolver, self.preds, self.block_end_values, self.bb_map)
|
||||
vmap_ctx, self.resolver, self.preds, self.block_end_values, self.bb_map, getattr(self, 'ctx', None))
|
||||
|
||||
elif op == "safepoint":
|
||||
live = inst.get("live", [])
|
||||
lower_safepoint(builder, self.module, live, self.vmap,
|
||||
lower_safepoint(builder, self.module, live, vmap_ctx,
|
||||
resolver=self.resolver, preds=self.preds,
|
||||
block_end_values=self.block_end_values, bb_map=self.bb_map)
|
||||
block_end_values=self.block_end_values, bb_map=self.bb_map,
|
||||
ctx=getattr(self, 'ctx', None))
|
||||
|
||||
elif op == "barrier":
|
||||
barrier_type = inst.get("type", "memory")
|
||||
@ -606,11 +951,16 @@ class NyashLLVMBuilder:
|
||||
if not lower_while_loopform(builder, func, cond, body,
|
||||
self.loop_count, self.vmap, self.bb_map,
|
||||
self.resolver, self.preds, self.block_end_values):
|
||||
# Fallback to regular while
|
||||
self._lower_while_regular(builder, inst, func)
|
||||
# Fallback to regular while (structured)
|
||||
try:
|
||||
self.resolver._owner_lower_instruction = self.lower_instruction
|
||||
except Exception:
|
||||
pass
|
||||
lower_while_regular(builder, func, cond, body,
|
||||
self.loop_count, self.vmap, self.bb_map,
|
||||
self.resolver, self.preds, self.block_end_values)
|
||||
else:
|
||||
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
|
||||
print(f"[Python LLVM] Unknown instruction: {op}")
|
||||
trace_debug(f"[Python LLVM] Unknown instruction: {op}")
|
||||
# Record per-inst definition for lifetime hinting as soon as available
|
||||
try:
|
||||
dst_maybe = inst.get("dst")
|
||||
@ -642,7 +992,8 @@ class NyashLLVMBuilder:
|
||||
# Cond block
|
||||
cbuild = ir.IRBuilder(cond_bb)
|
||||
try:
|
||||
cond_val = self.resolver.resolve_i64(cond_vid, builder.block, self.preds, self.block_end_values, self.vmap, self.bb_map)
|
||||
# Resolve against the condition block to localize dominance
|
||||
cond_val = self.resolver.resolve_i64(cond_vid, cbuild.block, self.preds, self.block_end_values, self.vmap, self.bb_map)
|
||||
except Exception:
|
||||
cond_val = self.vmap.get(cond_vid)
|
||||
if cond_val is None:
|
||||
@ -697,6 +1048,7 @@ class NyashLLVMBuilder:
|
||||
for fr in from_list:
|
||||
succs.setdefault(fr, []).append(to_bid)
|
||||
for block_id, dst_map in (getattr(self, 'block_phi_incomings', {}) or {}).items():
|
||||
trace_phi(f"[finalize] bb{block_id} dsts={list(dst_map.keys())}")
|
||||
bb = self.bb_map.get(block_id)
|
||||
if bb is None:
|
||||
continue
|
||||
@ -706,15 +1058,34 @@ class NyashLLVMBuilder:
|
||||
except Exception:
|
||||
pass
|
||||
for dst_vid, incoming in (dst_map or {}).items():
|
||||
trace_phi(f"[finalize] dst v{dst_vid} incoming={incoming}")
|
||||
# Ensure placeholder exists at block head
|
||||
phi = self.vmap.get(dst_vid)
|
||||
try:
|
||||
is_phi = hasattr(phi, 'add_incoming')
|
||||
except Exception:
|
||||
is_phi = False
|
||||
if not is_phi:
|
||||
phi = b.phi(self.i64, name=f"phi_{dst_vid}")
|
||||
# Prefer predeclared ret-phi when available and force using it.
|
||||
predecl = getattr(self, 'predeclared_ret_phis', {}) if hasattr(self, 'predeclared_ret_phis') else {}
|
||||
phi = predecl.get((int(block_id), int(dst_vid))) if predecl else None
|
||||
if phi is not None:
|
||||
# Bind as canonical target
|
||||
self.vmap[dst_vid] = phi
|
||||
else:
|
||||
phi = self.vmap.get(dst_vid)
|
||||
# Ensure we target a PHI belonging to the current block; if a
|
||||
# global mapping points to a PHI in another block (due to
|
||||
# earlier localization), create/replace with a local PHI.
|
||||
need_local_phi = False
|
||||
try:
|
||||
if not (phi is not None and hasattr(phi, 'add_incoming')):
|
||||
need_local_phi = True
|
||||
else:
|
||||
bb_of_phi = getattr(getattr(phi, 'basic_block', None), 'name', None)
|
||||
if bb_of_phi != bb.name:
|
||||
need_local_phi = True
|
||||
except Exception:
|
||||
need_local_phi = True
|
||||
if need_local_phi:
|
||||
phi = b.phi(self.i64, name=f"phi_{dst_vid}")
|
||||
self.vmap[dst_vid] = phi
|
||||
n = getattr(phi, 'name', b'').decode() if hasattr(getattr(phi, 'name', None), 'decode') else str(getattr(phi, 'name', ''))
|
||||
trace_phi(f"[finalize] target phi={n}")
|
||||
# Wire incoming per CFG predecessor; map src_vid when provided
|
||||
preds_raw = [p for p in self.preds.get(block_id, []) if p != block_id]
|
||||
# Deduplicate while preserving order
|
||||
@ -820,6 +1191,10 @@ class NyashLLVMBuilder:
|
||||
if pred_bb is None:
|
||||
continue
|
||||
phi.add_incoming(val, pred_bb)
|
||||
try:
|
||||
trace_phi(f"[finalize] add incoming: bb{pred_bid} -> v{dst_vid}")
|
||||
except Exception:
|
||||
pass
|
||||
# Tag dst as string-ish if any declared source was string-ish (post-lowering info)
|
||||
try:
|
||||
if hasattr(self.resolver, 'is_stringish') and hasattr(self.resolver, 'mark_string'):
|
||||
|
||||
200
src/llvm_py/phi_wiring.py
Normal file
200
src/llvm_py/phi_wiring.py
Normal file
@ -0,0 +1,200 @@
|
||||
"""
|
||||
PHI wiring helpers
|
||||
|
||||
- setup_phi_placeholders: Predeclare PHIs and collect incoming metadata
|
||||
- finalize_phis: Wire PHI incomings using end-of-block snapshots and resolver
|
||||
|
||||
These operate on the NyashLLVMBuilder instance to keep changes minimal.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Any
|
||||
import llvmlite.ir as ir
|
||||
|
||||
def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
"""Predeclare PHIs and collect incoming metadata for finalize_phis.
|
||||
|
||||
This pass is function-local and must be invoked after basic blocks are
|
||||
created and before lowering individual blocks. It also tags string-ish
|
||||
values eagerly to help downstream resolvers choose correct intrinsics.
|
||||
"""
|
||||
try:
|
||||
# Pass A: collect producer stringish hints per value-id
|
||||
produced_str: Dict[int, bool] = {}
|
||||
for block_data in blocks:
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
try:
|
||||
opx = inst.get("op")
|
||||
dstx = inst.get("dst")
|
||||
if dstx is None:
|
||||
continue
|
||||
is_str = False
|
||||
if opx == "const":
|
||||
v = inst.get("value", {}) or {}
|
||||
t = v.get("type")
|
||||
if t == "string" or (isinstance(t, dict) and t.get("kind") in ("handle","ptr") and t.get("box_type") == "StringBox"):
|
||||
is_str = True
|
||||
elif opx in ("binop","boxcall","externcall"):
|
||||
t = inst.get("dst_type")
|
||||
if isinstance(t, dict) and t.get("kind") == "handle" and t.get("box_type") == "StringBox":
|
||||
is_str = True
|
||||
if is_str:
|
||||
produced_str[int(dstx)] = True
|
||||
except Exception:
|
||||
pass
|
||||
# Pass B: materialize PHI placeholders and record incoming metadata
|
||||
builder.block_phi_incomings = {}
|
||||
for block_data in blocks:
|
||||
bid0 = block_data.get("id", 0)
|
||||
bb0 = builder.bb_map.get(bid0)
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
if inst.get("op") == "phi":
|
||||
try:
|
||||
dst0 = int(inst.get("dst"))
|
||||
incoming0 = inst.get("incoming", []) or []
|
||||
except Exception:
|
||||
dst0 = None; incoming0 = []
|
||||
if dst0 is None:
|
||||
continue
|
||||
# Record incoming metadata for finalize_phis
|
||||
try:
|
||||
builder.block_phi_incomings.setdefault(bid0, {})[dst0] = [
|
||||
(int(b), int(v)) for (v, b) in incoming0
|
||||
]
|
||||
except Exception:
|
||||
pass
|
||||
# Ensure placeholder exists at block head
|
||||
if bb0 is not None:
|
||||
b0 = ir.IRBuilder(bb0)
|
||||
try:
|
||||
b0.position_at_start(bb0)
|
||||
except Exception:
|
||||
pass
|
||||
existing = builder.vmap.get(dst0)
|
||||
is_phi = False
|
||||
try:
|
||||
is_phi = hasattr(existing, 'add_incoming')
|
||||
except Exception:
|
||||
is_phi = False
|
||||
if not is_phi:
|
||||
ph0 = b0.phi(builder.i64, name=f"phi_{dst0}")
|
||||
builder.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")
|
||||
mark_str = isinstance(dst_type0, dict) and dst_type0.get("kind") == "handle" and dst_type0.get("box_type") == "StringBox"
|
||||
if not mark_str:
|
||||
for (_b_decl_i, v_src_i) in incoming0:
|
||||
try:
|
||||
if produced_str.get(int(v_src_i)):
|
||||
mark_str = True; break
|
||||
except Exception:
|
||||
pass
|
||||
if mark_str and hasattr(builder.resolver, 'mark_string'):
|
||||
builder.resolver.mark_string(int(dst0))
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def finalize_phis(builder):
|
||||
"""Finalize PHIs declared in JSON by wiring incoming edges at block heads.
|
||||
Uses resolver._value_at_end_i64 to materialize values at predecessor ends,
|
||||
ensuring casts/boxing are inserted in predecessor blocks (dominance-safe)."""
|
||||
# Build succ map for nearest-predecessor mapping
|
||||
succs: Dict[int, List[int]] = {}
|
||||
for to_bid, from_list in (builder.preds or {}).items():
|
||||
for fr in from_list:
|
||||
succs.setdefault(fr, []).append(to_bid)
|
||||
for block_id, dst_map in (getattr(builder, 'block_phi_incomings', {}) or {}).items():
|
||||
bb = builder.bb_map.get(block_id)
|
||||
if bb is None:
|
||||
continue
|
||||
b = ir.IRBuilder(bb)
|
||||
try:
|
||||
b.position_at_start(bb)
|
||||
except Exception:
|
||||
pass
|
||||
for dst_vid, incoming in (dst_map or {}).items():
|
||||
# Ensure placeholder exists at block head
|
||||
# Prefer predeclared ret-phi when available and force using it.
|
||||
predecl = getattr(builder, 'predeclared_ret_phis', {}) if hasattr(builder, 'predeclared_ret_phis') else {}
|
||||
phi = predecl.get((int(block_id), int(dst_vid))) if predecl else None
|
||||
if phi is not None:
|
||||
builder.vmap[dst_vid] = phi
|
||||
else:
|
||||
phi = builder.vmap.get(dst_vid)
|
||||
need_local_phi = False
|
||||
try:
|
||||
if not (phi is not None and hasattr(phi, 'add_incoming')):
|
||||
need_local_phi = True
|
||||
else:
|
||||
bb_of_phi = getattr(getattr(phi, 'basic_block', None), 'name', None)
|
||||
if bb_of_phi != bb.name:
|
||||
need_local_phi = True
|
||||
except Exception:
|
||||
need_local_phi = True
|
||||
if need_local_phi:
|
||||
phi = b.phi(builder.i64, name=f"phi_{dst_vid}")
|
||||
builder.vmap[dst_vid] = phi
|
||||
# Wire incoming per CFG predecessor; map src_vid when provided
|
||||
preds_raw = [p for p in builder.preds.get(block_id, []) if p != block_id]
|
||||
# Deduplicate while preserving order
|
||||
seen = set()
|
||||
preds_list: List[int] = []
|
||||
for p in preds_raw:
|
||||
if p not in seen:
|
||||
preds_list.append(p)
|
||||
seen.add(p)
|
||||
# Helper: find the nearest immediate predecessor on a path decl_b -> ... -> block_id
|
||||
def nearest_pred_on_path(decl_b: int):
|
||||
from collections import deque
|
||||
q = deque([decl_b])
|
||||
visited = set([decl_b])
|
||||
parent: Dict[int, Any] = {decl_b: None}
|
||||
while q:
|
||||
cur = q.popleft()
|
||||
if cur == block_id:
|
||||
par = parent.get(block_id)
|
||||
return par if par in preds_list else None
|
||||
for nx in succs.get(cur, []):
|
||||
if nx not in visited:
|
||||
visited.add(nx)
|
||||
parent[nx] = cur
|
||||
q.append(nx)
|
||||
return None
|
||||
# Precompute a non-self initial source (if present) to use for self-carry cases
|
||||
init_src_vid = None
|
||||
for (b_decl0, v_src0) in incoming:
|
||||
try:
|
||||
vs0 = int(v_src0)
|
||||
except Exception:
|
||||
continue
|
||||
if vs0 != int(dst_vid):
|
||||
init_src_vid = vs0
|
||||
break
|
||||
# Pre-resolve declared incomings to nearest immediate predecessors
|
||||
chosen: Dict[int, ir.Value] = {}
|
||||
for (b_decl, v_src) in incoming:
|
||||
try:
|
||||
bd = int(b_decl); vs = int(v_src)
|
||||
except Exception:
|
||||
continue
|
||||
pred_match = nearest_pred_on_path(bd)
|
||||
if pred_match is None:
|
||||
continue
|
||||
if vs == int(dst_vid) and init_src_vid is not None:
|
||||
vs = int(init_src_vid)
|
||||
try:
|
||||
val = builder.resolver._value_at_end_i64(vs, pred_match, builder.preds, builder.block_end_values, builder.vmap, builder.bb_map)
|
||||
except Exception:
|
||||
val = None
|
||||
if val is None:
|
||||
# As a last resort, zero
|
||||
val = ir.Constant(builder.i64, 0)
|
||||
chosen[pred_match] = val
|
||||
# Finally add incomings
|
||||
for pred_bid, val in chosen.items():
|
||||
pred_bb = builder.bb_map.get(pred_bid)
|
||||
if pred_bb is None:
|
||||
continue
|
||||
phi.add_incoming(val, pred_bb)
|
||||
35
src/llvm_py/prepass/if_merge.py
Normal file
35
src/llvm_py/prepass/if_merge.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""
|
||||
If-merge prepass utilities
|
||||
For blocks that end with return and have multiple predecessors, plan PHI predeclare for return value ids.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Optional
|
||||
from cfg.utils import build_preds_succs
|
||||
|
||||
def plan_ret_phi_predeclare(block_by_id: Dict[int, Dict[str, Any]]) -> Optional[Dict[int, int]]:
|
||||
"""Return a map {block_id: value_id} for blocks that end with ret <value>
|
||||
and have multiple predecessors. The caller can predeclare a PHI for value_id
|
||||
at the block head to ensure dominance for the return.
|
||||
"""
|
||||
preds, _ = build_preds_succs(block_by_id)
|
||||
plan: Dict[int, int] = {}
|
||||
for bid, blk in block_by_id.items():
|
||||
term = None
|
||||
if blk.get('instructions'):
|
||||
last = blk.get('instructions')[-1]
|
||||
if last.get('op') in ('jump','branch','ret'):
|
||||
term = last
|
||||
if term is None and 'terminator' in blk:
|
||||
t = blk['terminator']
|
||||
if t and t.get('op') in ('jump','branch','ret'):
|
||||
term = t
|
||||
if not term or term.get('op') != 'ret':
|
||||
continue
|
||||
val = term.get('value')
|
||||
if not isinstance(val, int):
|
||||
continue
|
||||
pred_list = [p for p in preds.get(int(bid), []) if p != int(bid)]
|
||||
if len(pred_list) > 1:
|
||||
plan[int(bid)] = int(val)
|
||||
return plan or None
|
||||
|
||||
106
src/llvm_py/prepass/loops.py
Normal file
106
src/llvm_py/prepass/loops.py
Normal file
@ -0,0 +1,106 @@
|
||||
"""
|
||||
Loop prepass utilities
|
||||
Detect simple while-shaped loops in MIR(JSON) and return a lowering plan.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Any, Optional
|
||||
from cfg.utils import build_preds_succs
|
||||
|
||||
def detect_simple_while(block_by_id: Dict[int, Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
||||
"""Detect a simple loop pattern: header(branch cond → then/else),
|
||||
a latch that jumps back to header reachable from then, and exit on else.
|
||||
Returns a plan dict or None.
|
||||
"""
|
||||
# Build succ and pred maps from JSON quickly
|
||||
preds, succs = build_preds_succs(block_by_id)
|
||||
# Find a header with a branch terminator and else leading to a ret (direct)
|
||||
for b in block_by_id.values():
|
||||
bid = int(b.get('id', 0))
|
||||
term = None
|
||||
if b.get('instructions'):
|
||||
last = b.get('instructions')[-1]
|
||||
if last.get('op') in ('jump','branch','ret'):
|
||||
term = last
|
||||
if term is None and 'terminator' in b:
|
||||
t = b['terminator']
|
||||
if t and t.get('op') in ('jump','branch','ret'):
|
||||
term = t
|
||||
if not term or term.get('op') != 'branch':
|
||||
continue
|
||||
then_bid = int(term.get('then'))
|
||||
else_bid = int(term.get('else'))
|
||||
cond_vid = int(term.get('cond')) if term.get('cond') is not None else None
|
||||
if cond_vid is None:
|
||||
continue
|
||||
# Quick check: else block ends with ret
|
||||
else_blk = block_by_id.get(else_bid)
|
||||
has_ret = False
|
||||
if else_blk is not None:
|
||||
insts = else_blk.get('instructions', [])
|
||||
if insts and insts[-1].get('op') == 'ret':
|
||||
has_ret = True
|
||||
elif else_blk.get('terminator', {}).get('op') == 'ret':
|
||||
has_ret = True
|
||||
if not has_ret:
|
||||
continue
|
||||
# Find a latch that jumps back to header reachable from then
|
||||
latch = None
|
||||
visited = set()
|
||||
stack = [then_bid]
|
||||
while stack:
|
||||
cur = stack.pop()
|
||||
if cur in visited:
|
||||
continue
|
||||
visited.add(cur)
|
||||
cur_blk = block_by_id.get(cur)
|
||||
if cur_blk is None:
|
||||
continue
|
||||
for inst in cur_blk.get('instructions', []) or []:
|
||||
if inst.get('op') == 'jump' and int(inst.get('target')) == bid:
|
||||
latch = cur
|
||||
break
|
||||
if latch is not None:
|
||||
break
|
||||
for nx in succs.get(cur, []) or []:
|
||||
if nx not in visited and nx != else_bid:
|
||||
stack.append(nx)
|
||||
if latch is None:
|
||||
continue
|
||||
# Compose body_insts: collect insts along then-branch region up to latch (inclusive),
|
||||
# excluding any final jump back to header to prevent double edges.
|
||||
collect_order: List[int] = []
|
||||
visited2 = set()
|
||||
stack2 = [then_bid]
|
||||
while stack2:
|
||||
cur = stack2.pop()
|
||||
if cur in visited2 or cur == bid or cur == else_bid:
|
||||
continue
|
||||
visited2.add(cur)
|
||||
collect_order.append(cur)
|
||||
if cur == latch:
|
||||
continue
|
||||
for nx in succs.get(cur, []) or []:
|
||||
if nx not in visited2 and nx != else_bid:
|
||||
stack2.append(nx)
|
||||
body_insts: List[Dict[str, Any]] = []
|
||||
for bbid in collect_order:
|
||||
blk = block_by_id.get(bbid)
|
||||
if blk is None:
|
||||
continue
|
||||
for inst in blk.get('instructions', []) or []:
|
||||
if inst.get('op') == 'jump' and int(inst.get('target', -1)) == bid:
|
||||
continue
|
||||
body_insts.append(inst)
|
||||
skip_blocks = set(collect_order)
|
||||
skip_blocks.add(bid)
|
||||
return {
|
||||
'header': bid,
|
||||
'then': then_bid,
|
||||
'else': else_bid,
|
||||
'latch': latch,
|
||||
'exit': else_bid,
|
||||
'cond': cond_vid,
|
||||
'body_insts': body_insts,
|
||||
'skip_blocks': list(skip_blocks),
|
||||
}
|
||||
return None
|
||||
@ -112,8 +112,17 @@ class PyVM:
|
||||
cur = min(fn.blocks.keys())
|
||||
prev: Optional[int] = None
|
||||
|
||||
# Simple block execution loop
|
||||
# Simple block execution loop with step budget to avoid infinite hangs
|
||||
max_steps = 0
|
||||
try:
|
||||
max_steps = int(os.environ.get("NYASH_PYVM_MAX_STEPS", "200000"))
|
||||
except Exception:
|
||||
max_steps = 200000
|
||||
steps = 0
|
||||
while True:
|
||||
steps += 1
|
||||
if max_steps and steps > max_steps:
|
||||
raise RuntimeError(f"pyvm: max steps exceeded ({max_steps}) in function {fn.name}")
|
||||
block = fn.blocks.get(cur)
|
||||
if block is None:
|
||||
raise RuntimeError(f"block not found: {cur}")
|
||||
@ -226,18 +235,28 @@ class PyVM:
|
||||
a = self._read(regs, inst.get("lhs"))
|
||||
b = self._read(regs, inst.get("rhs"))
|
||||
res: bool
|
||||
if operation == "==":
|
||||
# For ordering comparisons, be robust to None by coercing to ints
|
||||
if operation in ("<", "<=", ">", ">="):
|
||||
try:
|
||||
ai = 0 if a is None else (int(a) if not isinstance(a, str) else 0)
|
||||
except Exception:
|
||||
ai = 0
|
||||
try:
|
||||
bi = 0 if b is None else (int(b) if not isinstance(b, str) else 0)
|
||||
except Exception:
|
||||
bi = 0
|
||||
if operation == "<":
|
||||
res = ai < bi
|
||||
elif operation == "<=":
|
||||
res = ai <= bi
|
||||
elif operation == ">":
|
||||
res = ai > bi
|
||||
else:
|
||||
res = ai >= bi
|
||||
elif operation == "==":
|
||||
res = (a == b)
|
||||
elif operation == "!=":
|
||||
res = (a != b)
|
||||
elif operation == "<":
|
||||
res = (a < b)
|
||||
elif operation == "<=":
|
||||
res = (a <= b)
|
||||
elif operation == ">":
|
||||
res = (a > b)
|
||||
elif operation == ">=":
|
||||
res = (a >= b)
|
||||
else:
|
||||
raise RuntimeError(f"unsupported compare: {operation}")
|
||||
# VM convention: booleans are i64 0/1
|
||||
@ -287,6 +306,12 @@ class PyVM:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if op == "copy":
|
||||
src = self._read(regs, inst.get("src"))
|
||||
self._set(regs, inst.get("dst"), src)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if op == "boxcall":
|
||||
recv = self._read(regs, inst.get("box"))
|
||||
method = inst.get("method")
|
||||
|
||||
@ -5,6 +5,8 @@ Based on src/backend/llvm/compiler/codegen/instructions/resolver.rs
|
||||
|
||||
from typing import Dict, Optional, Any, Tuple
|
||||
import os
|
||||
from trace import phi as trace_phi
|
||||
from trace import values as trace_values
|
||||
import llvmlite.ir as ir
|
||||
|
||||
class Resolver:
|
||||
@ -26,6 +28,13 @@ class Resolver:
|
||||
# Legacy constructor (vmap, bb_map) — builder/module will be set later when available
|
||||
self.builder = None
|
||||
self.module = None
|
||||
try:
|
||||
# Keep references to global maps when provided
|
||||
self.global_vmap = a if isinstance(a, dict) else None
|
||||
self.global_bb_map = b if isinstance(b, dict) else None
|
||||
except Exception:
|
||||
self.global_vmap = None
|
||||
self.global_bb_map = None
|
||||
|
||||
# Caches: (block_name, value_id) -> llvm value
|
||||
self.i64_cache: Dict[Tuple[str, int], ir.Value] = {}
|
||||
@ -95,9 +104,35 @@ class Resolver:
|
||||
bmap = self.block_phi_incomings.get(block_id)
|
||||
if isinstance(bmap, dict) and value_id in bmap:
|
||||
existing_cur = vmap.get(value_id)
|
||||
if existing_cur is not None and hasattr(existing_cur, 'add_incoming'):
|
||||
# Use placeholder only if it belongs to the current block; otherwise
|
||||
# create/ensure a local PHI at the current block head to dominate uses.
|
||||
is_phi_here = False
|
||||
try:
|
||||
is_phi_here = (
|
||||
existing_cur is not None
|
||||
and hasattr(existing_cur, 'add_incoming')
|
||||
and getattr(getattr(existing_cur, 'basic_block', None), 'name', None) == current_block.name
|
||||
)
|
||||
except Exception:
|
||||
is_phi_here = False
|
||||
if is_phi_here:
|
||||
self.i64_cache[cache_key] = existing_cur
|
||||
return existing_cur
|
||||
# Materialize a local PHI placeholder at block start and bind to vmap
|
||||
b = ir.IRBuilder(current_block)
|
||||
try:
|
||||
b.position_at_start(current_block)
|
||||
except Exception:
|
||||
pass
|
||||
phi_local = b.phi(self.i64, name=f"phi_{value_id}")
|
||||
vmap[value_id] = phi_local
|
||||
try:
|
||||
if isinstance(getattr(self, 'global_vmap', None), dict):
|
||||
self.global_vmap[value_id] = phi_local
|
||||
except Exception:
|
||||
pass
|
||||
self.i64_cache[cache_key] = phi_local
|
||||
return phi_local
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -116,8 +151,7 @@ class Resolver:
|
||||
if defined_here:
|
||||
existing = vmap.get(value_id)
|
||||
if existing is not None and hasattr(existing, 'type') and isinstance(existing.type, ir.IntType) and existing.type.width == 64:
|
||||
if os.environ.get('NYASH_LLVM_TRACE_VALUES') == '1':
|
||||
print(f"[resolve] local reuse: bb{bid} v{value_id}", flush=True)
|
||||
trace_values(f"[resolve] local reuse: bb{bid} v{value_id}")
|
||||
self.i64_cache[cache_key] = existing
|
||||
return existing
|
||||
else:
|
||||
@ -131,8 +165,7 @@ class Resolver:
|
||||
base_val = vmap.get(value_id)
|
||||
if base_val is None:
|
||||
result = ir.Constant(self.i64, 0)
|
||||
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
|
||||
print(f"[resolve] bb{bid} v{value_id} entry/no-preds → 0", flush=True)
|
||||
trace_phi(f"[resolve] bb{bid} v{value_id} entry/no-preds → 0")
|
||||
else:
|
||||
# If pointer string, box to handle in current block (use local builder)
|
||||
if hasattr(base_val, 'type') and isinstance(base_val.type, ir.PointerType) and self.module is not None:
|
||||
@ -185,8 +218,7 @@ class Resolver:
|
||||
declared = False
|
||||
if declared:
|
||||
# Return existing placeholder if present; do not create a new PHI here.
|
||||
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
|
||||
print(f"[resolve] use placeholder PHI: bb{cur_bid} v{value_id}", flush=True)
|
||||
trace_phi(f"[resolve] use placeholder PHI: bb{cur_bid} v{value_id}")
|
||||
placeholder = vmap.get(value_id)
|
||||
result = placeholder if (placeholder is not None and hasattr(placeholder, 'add_incoming')) else ir.Constant(self.i64, 0)
|
||||
else:
|
||||
@ -258,19 +290,14 @@ class Resolver:
|
||||
bb_map: Optional[Dict[int, ir.Block]] = None,
|
||||
_vis: Optional[set] = None) -> ir.Value:
|
||||
"""Resolve value as i64 at the end of a given block by traversing predecessors if needed."""
|
||||
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
|
||||
try:
|
||||
print(f"[resolve] end_i64 enter: bb{block_id} v{value_id}", flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
trace_phi(f"[resolve] end_i64 enter: bb{block_id} v{value_id}")
|
||||
key = (block_id, value_id)
|
||||
if key in self._end_i64_cache:
|
||||
return self._end_i64_cache[key]
|
||||
if _vis is None:
|
||||
_vis = set()
|
||||
if key in _vis:
|
||||
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
|
||||
print(f"[resolve] cycle detected at end_i64(bb{block_id}, v{value_id}) → 0", flush=True)
|
||||
trace_phi(f"[resolve] cycle detected at end_i64(bb{block_id}, v{value_id}) → 0")
|
||||
return ir.Constant(self.i64, 0)
|
||||
_vis.add(key)
|
||||
|
||||
@ -285,16 +312,21 @@ class Resolver:
|
||||
is_phi_val = hasattr(val, 'add_incoming')
|
||||
except Exception:
|
||||
is_phi_val = False
|
||||
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
|
||||
try:
|
||||
ty = 'phi' if is_phi_val else ('ptr' if hasattr(val, 'type') and isinstance(val.type, ir.PointerType) else ('i'+str(getattr(val.type,'width','?')) if hasattr(val,'type') and isinstance(val.type, ir.IntType) else 'other'))
|
||||
print(f"[resolve] snap hit: bb{block_id} v{value_id} type={ty}", flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
ty = 'phi' if is_phi_val else ('ptr' if hasattr(val, 'type') and isinstance(val.type, ir.PointerType) else ('i'+str(getattr(val.type,'width','?')) if hasattr(val,'type') and isinstance(val.type, ir.IntType) else 'other'))
|
||||
trace_phi(f"[resolve] snap hit: bb{block_id} v{value_id} type={ty}")
|
||||
except Exception:
|
||||
pass
|
||||
if is_phi_val:
|
||||
# Using a dominating PHI placeholder as incoming is valid for finalize_phis
|
||||
self._end_i64_cache[key] = val
|
||||
return val
|
||||
# Accept PHI only when it belongs to the same block (dominates end-of-block).
|
||||
try:
|
||||
belongs_here = (getattr(getattr(val, 'basic_block', None), 'name', b'').decode() if hasattr(getattr(val, 'basic_block', None), 'name') else str(getattr(getattr(val, 'basic_block', None), 'name', ''))) == f"bb{block_id}"
|
||||
except Exception:
|
||||
belongs_here = False
|
||||
if belongs_here:
|
||||
self._end_i64_cache[key] = val
|
||||
return val
|
||||
# Otherwise ignore and try predecessors to avoid self-carry from foreign PHI
|
||||
coerced = self._coerce_in_block_to_i64(val, block_id, bb_map)
|
||||
self._end_i64_cache[key] = coerced
|
||||
return coerced
|
||||
@ -310,9 +342,8 @@ class Resolver:
|
||||
# Do not use global vmap here; if not materialized by end of this block
|
||||
# (or its preds), bail out with zero to preserve dominance.
|
||||
|
||||
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
|
||||
preds_s = ','.join(str(x) for x in pred_ids)
|
||||
print(f"[resolve] end_i64 miss: bb{block_id} v{value_id} preds=[{preds_s}] → 0", flush=True)
|
||||
preds_s = ','.join(str(x) for x in pred_ids)
|
||||
trace_phi(f"[resolve] end_i64 miss: bb{block_id} v{value_id} preds=[{preds_s}] → 0")
|
||||
z = ir.Constant(self.i64, 0)
|
||||
self._end_i64_cache[key] = z
|
||||
return z
|
||||
|
||||
@ -1,51 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple test for Nyash LLVM Python backend
|
||||
Tests basic MIR -> LLVM compilation
|
||||
Simple smoke for Nyash LLVM Python backend
|
||||
Generates a minimal MIR(JSON) in the current schema and compiles it.
|
||||
"""
|
||||
|
||||
import json
|
||||
from llvm_builder import NyashLLVMBuilder
|
||||
|
||||
# Simple MIR test case: function that returns 42
|
||||
test_mir = {
|
||||
"functions": {
|
||||
"main": {
|
||||
# Minimal MIR(JSON): main() { ret 42 }
|
||||
TEST_MIR = {
|
||||
"functions": [
|
||||
{
|
||||
"name": "main",
|
||||
"params": [],
|
||||
"return_type": "i64",
|
||||
"entry_block": 0,
|
||||
"blocks": {
|
||||
"0": {
|
||||
"blocks": [
|
||||
{
|
||||
"id": 0,
|
||||
"instructions": [
|
||||
{
|
||||
"kind": "Const",
|
||||
"dst": 0,
|
||||
"value": {"type": "i64", "value": 42}
|
||||
}
|
||||
],
|
||||
"terminator": {
|
||||
"kind": "Return",
|
||||
"value": 0
|
||||
}
|
||||
{"op": "const", "dst": 0, "value": {"type": "i64", "value": 42}},
|
||||
{"op": "ret", "value": 0}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def test_basic():
|
||||
"""Test basic MIR -> LLVM compilation"""
|
||||
builder = NyashLLVMBuilder()
|
||||
|
||||
# Generate LLVM IR
|
||||
llvm_ir = builder.build_from_mir(test_mir)
|
||||
print("Generated LLVM IR:")
|
||||
print(llvm_ir)
|
||||
|
||||
# Compile to object file
|
||||
ir = builder.build_from_mir(TEST_MIR)
|
||||
print("Generated LLVM IR (truncated):\n", ir.splitlines()[0:8])
|
||||
builder.compile_to_object("test_simple.o")
|
||||
print("\nCompiled to test_simple.o")
|
||||
print("Compiled to test_simple.o")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_basic()
|
||||
test_basic()
|
||||
|
||||
41
src/llvm_py/trace.py
Normal file
41
src/llvm_py/trace.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""
|
||||
Lightweight tracing helpers for the LLVM Python backend.
|
||||
|
||||
Environment flags (string '1' to enable):
|
||||
- NYASH_CLI_VERBOSE: general lowering/debug logs
|
||||
- NYASH_LLVM_TRACE_PHI: PHI resolution/snapshot wiring logs
|
||||
- NYASH_LLVM_TRACE_VALUES: value resolution logs
|
||||
|
||||
Import and use:
|
||||
from trace import debug, phi, values
|
||||
debug("message")
|
||||
phi("phi message")
|
||||
values("values message")
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
def _enabled(env_key: str) -> bool:
|
||||
return os.environ.get(env_key) == '1'
|
||||
|
||||
def debug(msg: str) -> None:
|
||||
if _enabled('NYASH_CLI_VERBOSE'):
|
||||
try:
|
||||
print(msg, flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def phi(msg: str) -> None:
|
||||
if _enabled('NYASH_LLVM_TRACE_PHI'):
|
||||
try:
|
||||
print(msg, flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def values(msg: str) -> None:
|
||||
if _enabled('NYASH_LLVM_TRACE_VALUES'):
|
||||
try:
|
||||
print(msg, flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
31
src/llvm_py/utils/values.py
Normal file
31
src/llvm_py/utils/values.py
Normal file
@ -0,0 +1,31 @@
|
||||
"""
|
||||
Value resolution helpers
|
||||
Centralize policies like "prefer same-block SSA; otherwise resolve with dominance".
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
import llvmlite.ir as ir
|
||||
|
||||
def resolve_i64_strict(
|
||||
resolver,
|
||||
value_id: int,
|
||||
current_block: ir.Block,
|
||||
preds: Dict[int, list],
|
||||
block_end_values: Dict[int, Dict[int, Any]],
|
||||
vmap: Dict[int, Any],
|
||||
bb_map: Optional[Dict[int, ir.Block]] = None,
|
||||
*,
|
||||
prefer_local: bool = True,
|
||||
) -> ir.Value:
|
||||
"""Resolve i64 under policies:
|
||||
- If prefer_local and vmap has a same-block definition, reuse it.
|
||||
- Otherwise, delegate to resolver to localize with PHI/casts as needed.
|
||||
"""
|
||||
# Prefer current vmap SSA first (block-local map is passed in vmap)
|
||||
val = vmap.get(value_id)
|
||||
if prefer_local and val is not None:
|
||||
return val
|
||||
# Fallback to resolver
|
||||
if resolver is None:
|
||||
return ir.Constant(ir.IntType(64), 0)
|
||||
return resolver.resolve_i64(value_id, current_block, preds, block_end_values, vmap, bb_map)
|
||||
Reference in New Issue
Block a user