R14:rawptr[X] -> X*(41 文件 / 2037 行改动),X 含模块限定名与嵌套下标(rawptr[GSList[NameEntry]] -> GSList[NameEntry]*)。保留 6 处 rawptr[t.CPtr](规范例外:存纯裸指针);# 注释与字符串里的 rawptr[...] 属元语法描述,未动。 R16(保守子集):t.CConst | str/bytes -> const str/bytes,t.CConst | char | t.CPtr -> const str;typedef 右值位置豁免(const str 在该处无法注册,与 R1 同源)。 验证:TPC 自举三重重编 SUCCESS;TransPyV.exe 全量 77/77 顺序回归通过。
1890 lines
75 KiB
Plaintext
1890 lines
75 KiB
Plaintext
import t, c
|
||
from stdint import *
|
||
import string
|
||
import viperlib
|
||
import memhub
|
||
import stdio
|
||
import stdlib
|
||
from linkedlist import GSListNode, GSList
|
||
from .__types import LLVMType, TypePrint
|
||
from .__values import Value, new_value, SSAValue
|
||
from .__function import Function, BasicBlock, block_append_text, block_set_terminated, block_get_terminated, function_move_block_to_end, _ll_name_needs_quote, block_insert_text_before_terminator
|
||
|
||
# ============================================================
|
||
# IRBuilder: 游标式 IR 指令发射器
|
||
#
|
||
# 跟踪当前 BasicBlock,将指令文本追加到块的指令链表。
|
||
# 维护递增的 SSA 名计数器(%0, %1, %2, ...)。
|
||
#
|
||
# 用法:
|
||
# builder = new_builder(pool, func)
|
||
# builder.PositionAtEnd(entry_block)
|
||
# a = builder.build_alloca(i32_ty)
|
||
# b = builder.build_alloca(i32_ty)
|
||
# builder.build_store(ConstInt(42), a)
|
||
# r = builder.build_load(i32_ty, a)
|
||
# builder.build_ret(r)
|
||
#
|
||
# 注意: 加 @t.NoVTable 以与硬编码 BUILDER_SIZE=40 保持一致
|
||
# (方法以实例方法实现,无虚表;文件末尾的同名顶层函数为一行委托)。
|
||
# ============================================================
|
||
# 指令文本缓冲尺寸
|
||
#
|
||
# MLIR 形态的类型文本会**内联具名结构体的完整字段体**
|
||
# (如 `!llvm.struct<"sha1.Cls", (i32, !llvm.ptr, …)>`),单个类型可达数百字节,
|
||
# 远长于 LLVM 形态的 `%"sha1.Cls"` 引用(十几字节)。故指令行缓冲必须按
|
||
# "一行最多内嵌几个类型文本"给足:_LINE_CAP 覆盖 5 个类型文本 + 固定部分。
|
||
# 渲染侧 TypePrint/MlirTypeText 已加溢出护栏:一旦截断即响亮报错,不再静默
|
||
# 产出畸形 MLIR(历史故障:alloca 行类型被 128 字节缓冲截断 → mlir-opt 报
|
||
# "unbalanced '(' character in pretty dialect name")。
|
||
# ============================================================
|
||
# 单个类型文本的容量(builders 内嵌类型经 TypePrint 渲染为 MLIR 形态)。
|
||
# 内联具名结构体在使用处展开**完整字段体**,类型文本可达数百字节(TransPyV
|
||
# 自身类名/嵌套更深)⇒ 512 偏小会触发 TypePrint 的"类型文本超出输出缓冲被
|
||
# 截断"响亮报错。提到 2048;注意 arg_cap 由本常量推导
|
||
# ((arg_count+1)*(_TY_TEXT_CAP+32)),过高会让多参调用单次分配逼近模块池单块上限。
|
||
_TY_TEXT_CAP: sizet = 2048
|
||
_LINE_CAP: sizet = 2560
|
||
|
||
novirtual class IRBuilder:
|
||
Pool: memhub.MemBuddy* # Memory pool
|
||
Func: Function* # Owning function
|
||
CurBlock: BasicBlock* # Current insertion point
|
||
Counter: int # SSA name counter
|
||
DbgLoc: str # current DILocation ref (kept same-shaped; MLIR printing ignores it, no !dbg suffix)
|
||
|
||
# ============================================================
|
||
# Internal helpers
|
||
# ============================================================
|
||
def _alloc_ssa_name(self) -> str:
|
||
"""Allocate an SSA name string "%N" and increment the counter"""
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
buf: str = pool.alloc(16)
|
||
if buf is None: return None
|
||
viperlib.snprintf(buf, 16, "%%%d", self.Counter)
|
||
self.Counter += 1
|
||
return buf
|
||
|
||
|
||
def _type_str(self, ty: LLVMType*) -> str:
|
||
"""Get the MLIR text of a type (allocates a _TY_TEXT_CAP-byte buffer from pool).
|
||
MLIR type text inlines full struct bodies; overflow makes TypePrint fail-fast loudly.
|
||
"""
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
buf: str = pool.alloc(_TY_TEXT_CAP)
|
||
if buf is None: return None
|
||
buf[0] = '\0'
|
||
TypePrint(buf, _TY_TEXT_CAP, ty, pool)
|
||
# [IR-FAIL] guard: LLVMType may be overwritten/dangling (pool reuse after reset), making the
|
||
# first char garbage/empty; fail-fast. Legal first chars: i/f/!/[/{/%/" and 'n'.
|
||
c0: int = buf[0]
|
||
if c0 == 0 or (c0 != 'i' and c0 != 'f' and c0 != '!' and c0 != 'n' and
|
||
c0 != '[' and c0 != '{' and c0 != '%' and c0 != '"'):
|
||
stdio.printf("[IR-FAIL] _type_str: LLVMType 对象已损坏 ty=0x%llx 首字符=%d 输出='%s'\n",
|
||
uint64(ty), c0, buf)
|
||
stdlib.exit(1)
|
||
return None
|
||
return buf
|
||
|
||
|
||
def _emit(self, text: str):
|
||
"""Append instruction text to the current block. Unlike llvmlite, MLIR has no ", !dbg !N"
|
||
suffix, so DbgLoc is deliberately ignored (set_debug_location only records the model)."""
|
||
if self is None or self.CurBlock is None: return
|
||
block_append_text(self.Pool, self.CurBlock, text)
|
||
|
||
|
||
def set_debug_location(self, ref: str):
|
||
"""Set the current DILocation ref (kept same-shaped; MLIR only records it, no output effect).
|
||
ref=None clears it.
|
||
"""
|
||
if self is None: return
|
||
self.DbgLoc = ref
|
||
|
||
def clear_debug_location(self):
|
||
"""Clear the current DILocation ref (kept same-shaped; MLIR only records it)"""
|
||
if self is None: return
|
||
self.DbgLoc = None
|
||
|
||
|
||
def _mark_terminated(self):
|
||
"""Mark the current block as terminated"""
|
||
if self is None or self.CurBlock is None: return
|
||
block_set_terminated(self.CurBlock, 1)
|
||
|
||
|
||
def _build_binop(self, op: str,
|
||
lhs: Value*, rhs: Value*) -> Value*:
|
||
"""Shared implementation of binary operations"""
|
||
if self is None or lhs is None or rhs is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
ty_s: str = self._type_str(lhs.Ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = %s %s %s, %s", name, op, ty_s, lhs.Name, rhs.Name)
|
||
self._emit(line)
|
||
return SSAValue(pool, lhs.Ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# Block positioning
|
||
# ============================================================
|
||
def position_at_end(self, block: BasicBlock*):
|
||
"""Set the insertion point to the end of block and move block to the end of the
|
||
function's block list, keeping text order consistent with control-flow order (else
|
||
non-monotonic SSA names break llc)."""
|
||
if self is None: return
|
||
self.CurBlock = block
|
||
if self.Func is not None:
|
||
function_move_block_to_end(self.Func, block)
|
||
|
||
|
||
def builder_cur_block_is_terminated(self) -> int:
|
||
"""Check the current block's IsTerminated flag via block_get_terminated instead of the
|
||
chained self.CurBlock.IsTerminated, which silently skips the GEP when BasicBlock is opaque."""
|
||
if self is None or self.CurBlock is None:
|
||
return 1
|
||
return block_get_terminated(self.CurBlock)
|
||
|
||
|
||
# ============================================================
|
||
# Memory instructions
|
||
# ============================================================
|
||
def build_alloca(self, ty: LLVMType*) -> Value*:
|
||
"""%N = alloca <ty>
|
||
|
||
Returns an SSA value of type ty* (default alignment, backend-decided).
|
||
No default parameter: TPC parallel compilation does not pre-register include defaults
|
||
(argN fallback); for explicit alignment use build_alloca_aligned.
|
||
"""
|
||
if self is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
ty_s: str = self._type_str(ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = alloca %s", name, ty_s)
|
||
self._emit(line)
|
||
|
||
# result type is ty*
|
||
result_ty: LLVMType* = LLVMType.Ptr(ty)
|
||
result_ty_ptr: LLVMType* = pool.alloc(LLVMType.__sizeof__())
|
||
if result_ty_ptr is not None:
|
||
string.memset(result_ty_ptr, 0, LLVMType.__sizeof__())
|
||
c.DerefAs(result_ty_ptr, result_ty)
|
||
return SSAValue(pool, result_ty_ptr, name)
|
||
|
||
|
||
def build_alloca_aligned(self, ty: LLVMType*,
|
||
align: int) -> Value*:
|
||
"""%N = alloca <ty>, align N. Returns an SSA value of type ty* with explicit alignment
|
||
(e.g. va_list 24-byte buffer needs 16). align has no default; callers must pass it."""
|
||
if self is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
ty_s: str = self._type_str(ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = alloca %s, align %d", name, ty_s, align)
|
||
self._emit(line)
|
||
|
||
# result type is ty*
|
||
result_ty: LLVMType* = LLVMType.Ptr(ty)
|
||
result_ty_ptr: LLVMType* = pool.alloc(LLVMType.__sizeof__())
|
||
if result_ty_ptr is not None:
|
||
string.memset(result_ty_ptr, 0, LLVMType.__sizeof__())
|
||
c.DerefAs(result_ty_ptr, result_ty)
|
||
return SSAValue(pool, result_ty_ptr, name)
|
||
|
||
|
||
def build_alloca_at_entry(self, ty: LLVMType*) -> Value*:
|
||
"""%N = alloca <ty>, emitted in the function entry block to ensure dominance.
|
||
Inserts before the entry terminator; alloca inside a conditional branch violates dominance."""
|
||
if self is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
func: Function* = self.Func
|
||
if func is None or func.Blocks is None:
|
||
return self.build_alloca(ty)
|
||
blocks: GSList[BasicBlock]* = func.Blocks
|
||
entry_blk: BasicBlock* = blocks.Head
|
||
if entry_blk is None:
|
||
return self.build_alloca(ty)
|
||
name: str = self._alloc_ssa_name()
|
||
ty_s: str = self._type_str(ty)
|
||
line_text: str = pool.alloc(_LINE_CAP)
|
||
if line_text is None: return None
|
||
viperlib.snprintf(line_text, _LINE_CAP, "%s = alloca %s", name, ty_s)
|
||
# Insert before the entry terminator: if the entry block already ends in br/ret, appending
|
||
# would put alloca after the terminator (invalid IR); insert-before-terminator handles both.
|
||
block_insert_text_before_terminator(pool, entry_blk, line_text)
|
||
# result type is ty*
|
||
result_ty: LLVMType* = LLVMType.Ptr(ty)
|
||
result_ty_ptr: LLVMType* = pool.alloc(LLVMType.__sizeof__())
|
||
if result_ty_ptr is not None:
|
||
string.memset(result_ty_ptr, 0, LLVMType.__sizeof__())
|
||
c.DerefAs(result_ty_ptr, result_ty)
|
||
return SSAValue(pool, result_ty_ptr, name)
|
||
|
||
|
||
def build_load(self, ty: LLVMType*,
|
||
ptr: Value*,
|
||
is_volatile: int = 0) -> Value*:
|
||
"""%N = load [volatile ]<ty>, <ptr_ty> <ptr>
|
||
is_volatile=0 (default) emits the plain form; =1 adds the volatile qualifier so the
|
||
optimizer cannot hoist/merge the access (e.g. spinlocks)."""
|
||
if self is None or ptr is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
ty_s: str = self._type_str(ty)
|
||
ptr_ty_s: str = self._type_str(ptr.Ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
if is_volatile != 0:
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = load volatile %s, %s %s", name, ty_s, ptr_ty_s, ptr.Name)
|
||
else:
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = load %s, %s %s", name, ty_s, ptr_ty_s, ptr.Name)
|
||
self._emit(line)
|
||
return SSAValue(pool, ty, name)
|
||
|
||
|
||
def build_store(self, val: Value*, ptr: Value*,
|
||
is_volatile: int = 0):
|
||
"""store [volatile ]<val_ty> <val>, <ptr_ty> <ptr>
|
||
is_volatile=0 (default) emits the plain form; =1 adds the volatile qualifier
|
||
(`store volatile i32 %v, i32* %p`)."""
|
||
if self is None or val is None or ptr is None: return
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
val_ty_s: str = self._type_str(val.Ty)
|
||
ptr_ty_s: str = self._type_str(ptr.Ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return
|
||
if is_volatile != 0:
|
||
viperlib.snprintf(line, _LINE_CAP, "store volatile %s %s, %s %s", val_ty_s, val.Name, ptr_ty_s, ptr.Name)
|
||
else:
|
||
viperlib.snprintf(line, _LINE_CAP, "store %s %s, %s %s", val_ty_s, val.Name, ptr_ty_s, ptr.Name)
|
||
self._emit(line)
|
||
|
||
|
||
# ============================================================
|
||
# Binary operation instructions
|
||
# ============================================================
|
||
def build_add(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = add <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("add", lhs, rhs)
|
||
|
||
|
||
def build_sub(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = sub <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("sub", lhs, rhs)
|
||
|
||
|
||
def build_mul(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = mul <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("mul", lhs, rhs)
|
||
|
||
|
||
def build_sdiv(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = sdiv <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("sdiv", lhs, rhs)
|
||
|
||
|
||
def build_udiv(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = udiv <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("udiv", lhs, rhs)
|
||
|
||
|
||
def build_srem(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = srem <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("srem", lhs, rhs)
|
||
|
||
|
||
def build_urem(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = urem <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("urem", lhs, rhs)
|
||
|
||
|
||
def build_and(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = and <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("and", lhs, rhs)
|
||
|
||
|
||
def build_or(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = or <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("or", lhs, rhs)
|
||
|
||
|
||
def build_xor(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = xor <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("xor", lhs, rhs)
|
||
|
||
|
||
def build_shl(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = shl <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("shl", lhs, rhs)
|
||
|
||
|
||
def build_lshr(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = lshr <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("lshr", lhs, rhs)
|
||
|
||
|
||
def build_ashr(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = ashr <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("ashr", lhs, rhs)
|
||
|
||
|
||
# ============================================================
|
||
# Float binary operation instructions
|
||
# ============================================================
|
||
def build_fadd(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = fadd <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("fadd", lhs, rhs)
|
||
|
||
|
||
def build_fsub(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = fsub <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("fsub", lhs, rhs)
|
||
|
||
|
||
def build_fmul(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = fmul <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("fmul", lhs, rhs)
|
||
|
||
|
||
def build_fdiv(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = fdiv <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("fdiv", lhs, rhs)
|
||
|
||
|
||
def build_frem(self, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = frem <ty> <lhs>, <rhs>"""
|
||
return self._build_binop("frem", lhs, rhs)
|
||
|
||
|
||
def build_fneg(self, val: Value*) -> Value*:
|
||
"""%N = fneg <ty> <val>"""
|
||
if self is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
ty_s: str = self._type_str(val.Ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = fneg %s %s", name, ty_s, val.Name)
|
||
self._emit(line)
|
||
return SSAValue(pool, val.Ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# Comparison instructions
|
||
# ============================================================
|
||
def build_icmp(self, pred: int,
|
||
lhs: Value*, rhs: Value*) -> Value*:
|
||
"""%N = icmp <pred> <ty> <lhs>, <rhs>
|
||
Returns an SSA value of type i1."""
|
||
if self is None or lhs is None or rhs is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
ty_s: str = self._type_str(lhs.Ty)
|
||
pred_s: str = _icmp_pred_str(pred)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = icmp %s %s %s, %s", name, pred_s, ty_s, lhs.Name, rhs.Name)
|
||
self._emit(line)
|
||
|
||
# result type is i1
|
||
i1_ty: LLVMType* = pool.alloc(LLVMType.__sizeof__())
|
||
if i1_ty is not None:
|
||
string.memset(i1_ty, 0, LLVMType.__sizeof__())
|
||
c.DerefAs(i1_ty, LLVMType.Int(1))
|
||
return SSAValue(pool, i1_ty, name)
|
||
|
||
|
||
def build_fcmp(self, pred: int,
|
||
lhs: Value*, rhs: Value*) -> Value*:
|
||
"""%N = fcmp <pred> <ty> <lhs>, <rhs>
|
||
Returns an SSA value of type i1."""
|
||
if self is None or lhs is None or rhs is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
ty_s: str = self._type_str(lhs.Ty)
|
||
pred_s: str = _fcmp_pred_str(pred)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = fcmp %s %s %s, %s", name, pred_s, ty_s, lhs.Name, rhs.Name)
|
||
self._emit(line)
|
||
|
||
# result type is i1
|
||
i1_ty: LLVMType* = pool.alloc(LLVMType.__sizeof__())
|
||
if i1_ty is not None:
|
||
string.memset(i1_ty, 0, LLVMType.__sizeof__())
|
||
c.DerefAs(i1_ty, LLVMType.Int(1))
|
||
return SSAValue(pool, i1_ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# Terminator instructions
|
||
# ============================================================
|
||
def build_br(self, target: BasicBlock*):
|
||
"""br label %<target>"""
|
||
if self is None or target is None: return
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
line: str = pool.alloc(64)
|
||
if line is None: return
|
||
viperlib.snprintf(line, 64, "br label %%%s", target.Name)
|
||
self._emit(line)
|
||
self._mark_terminated()
|
||
|
||
|
||
def build_cond_br(self, cond: Value*,
|
||
then_blk: BasicBlock*, else_blk: BasicBlock*):
|
||
"""br i1 <cond>, label %<then>, label %<else_>"""
|
||
if self is None or cond is None or then_blk is None or else_blk is None: return
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return
|
||
viperlib.snprintf(line, _LINE_CAP, "br i1 %s, label %%%s, label %%%s",
|
||
cond.Name, then_blk.Name, else_blk.Name)
|
||
self._emit(line)
|
||
self._mark_terminated()
|
||
|
||
|
||
def build_ret(self, val: Value*):
|
||
"""ret <ty> <val>"""
|
||
if self is None or val is None: return
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
ty_s: str = self._type_str(val.Ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return
|
||
viperlib.snprintf(line, _LINE_CAP, "ret %s %s", ty_s, val.Name)
|
||
self._emit(line)
|
||
self._mark_terminated()
|
||
|
||
|
||
def build_ret_void(self):
|
||
"""ret void"""
|
||
if self is None: return
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
line: str = pool.alloc(16)
|
||
if line is None: return
|
||
string.strcpy(line, "ret void")
|
||
self._emit(line)
|
||
self._mark_terminated()
|
||
|
||
|
||
# ============================================================
|
||
# Call instructions
|
||
# ============================================================
|
||
def build_call(self, callee: str,
|
||
args: Value*, arg_count: int,
|
||
ret_ty: LLVMType*,
|
||
is_variadic: int = 0) -> Value*:
|
||
"""%N = call <ret_ty> @<callee>(<args>)
|
||
args is the Value list head, arg_count the argument count. If ret_ty is void no SSA name is
|
||
allocated (plain call); is_variadic=1 emits the function type signature (e.g. i32 (i8*, ...))."""
|
||
if self is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
|
||
# --traceback: record this direct call's call site (build time; no-op unless enabled and
|
||
# inside a DebugInfoGen define context). See the debug call-site section in __function.vp.
|
||
_DebugNoteCall(callee)
|
||
|
||
# check whether the return type is void
|
||
is_void: int = 0
|
||
match ret_ty:
|
||
case LLVMType.Void():
|
||
is_void = 1
|
||
case _:
|
||
is_void = 0
|
||
|
||
name: str = None
|
||
if is_void == 0:
|
||
name = self._alloc_ssa_name()
|
||
|
||
ret_ty_s: str = self._type_str(ret_ty)
|
||
|
||
# Build the argument list text + variadic function type signature; size by argument count
|
||
# (struct type text can be _TY_TEXT_CAP each; a fixed 1024 would silently truncate).
|
||
arg_cap: sizet = sizet(arg_count + 1) * (_TY_TEXT_CAP + 32) + 32
|
||
args_buf: str = pool.alloc(arg_cap)
|
||
if args_buf is None: return None
|
||
args_buf[0] = '\0'
|
||
func_ty_buf: str = pool.alloc(arg_cap)
|
||
if func_ty_buf is None: return None
|
||
func_ty_buf[0] = '\0'
|
||
cur: Value* = args
|
||
first: int = 1
|
||
while cur is not None:
|
||
if first == 0:
|
||
_append_cstr(args_buf, arg_cap, ", ")
|
||
_append_cstr(func_ty_buf, arg_cap, ", ")
|
||
arg_ty_s: str = self._type_str(cur.Ty)
|
||
_append_cstr(args_buf, arg_cap, arg_ty_s)
|
||
_append_cstr(args_buf, arg_cap, " ")
|
||
if cur.Name is not None:
|
||
_append_cstr(args_buf, arg_cap, cur.Name)
|
||
_append_cstr(func_ty_buf, arg_cap, arg_ty_s)
|
||
cur = cur.Next
|
||
first = 0
|
||
if is_variadic == 1:
|
||
if first == 0:
|
||
_append_cstr(func_ty_buf, arg_cap, ", ")
|
||
_append_cstr(func_ty_buf, arg_cap, "...")
|
||
|
||
# Build the quoted callee name (SHA1-prefixed names with '.' or a leading digit need quotes)
|
||
callee_buf: str = pool.alloc(_LINE_CAP)
|
||
if callee_buf is None: return None
|
||
callee_buf[0] = '\0'
|
||
if _ll_name_needs_quote(callee) != 0:
|
||
_append_cstr(callee_buf, 128, "\"")
|
||
_append_cstr(callee_buf, 128, callee)
|
||
_append_cstr(callee_buf, 128, "\"")
|
||
else:
|
||
_append_cstr(callee_buf, 128, callee)
|
||
|
||
line_cap: sizet = string.strlen(ret_ty_s) + string.strlen(args_buf) \
|
||
+ string.strlen(func_ty_buf) + string.strlen(callee_buf) + 64
|
||
line: str = pool.alloc(line_cap)
|
||
if line is None: return None
|
||
if is_variadic == 1:
|
||
if is_void == 1:
|
||
viperlib.snprintf(line, line_cap, "call %s (%s) @%s(%s)", ret_ty_s, func_ty_buf, callee_buf, args_buf)
|
||
else:
|
||
viperlib.snprintf(line, line_cap, "%s = call %s (%s) @%s(%s)", name, ret_ty_s, func_ty_buf, callee_buf, args_buf)
|
||
else:
|
||
if is_void == 1:
|
||
viperlib.snprintf(line, line_cap, "call %s @%s(%s)", ret_ty_s, callee_buf, args_buf)
|
||
else:
|
||
viperlib.snprintf(line, line_cap, "%s = call %s @%s(%s)", name, ret_ty_s, callee_buf, args_buf)
|
||
self._emit(line)
|
||
|
||
if is_void == 1:
|
||
return None
|
||
return SSAValue(pool, ret_ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# Indirect call instruction: call <ret_ty> %callee(<args>)
|
||
#
|
||
# callee_val is a function-pointer SSA value (must already be a pointer type, e.g. i32(i8*)*)
|
||
# ============================================================
|
||
def build_call_indirect(self, callee_val: Value*,
|
||
args: Value*, arg_count: int,
|
||
ret_ty: LLVMType*) -> Value*:
|
||
"""%N = call <ret_ty> %callee(<args>)
|
||
Indirect call: callee_val is a function-pointer SSA value."""
|
||
if self is None or callee_val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
|
||
is_void: int = 0
|
||
match ret_ty:
|
||
case LLVMType.Void():
|
||
is_void = 1
|
||
case _:
|
||
is_void = 0
|
||
|
||
name: str = None
|
||
if is_void == 0:
|
||
name = self._alloc_ssa_name()
|
||
|
||
ret_ty_s: str = self._type_str(ret_ty)
|
||
|
||
# Build the argument list text (sized by argument count, same reason as build_call)
|
||
arg_cap2: sizet = sizet(arg_count + 1) * (_TY_TEXT_CAP + 32) + 32
|
||
args_buf: str = pool.alloc(arg_cap2)
|
||
if args_buf is None: return None
|
||
args_buf[0] = '\0'
|
||
cur: Value* = args
|
||
first: int = 1
|
||
while cur is not None:
|
||
if first == 0:
|
||
_append_cstr(args_buf, arg_cap2, ", ")
|
||
arg_ty_s: str = self._type_str(cur.Ty)
|
||
_append_cstr(args_buf, arg_cap2, arg_ty_s)
|
||
_append_cstr(args_buf, arg_cap2, " ")
|
||
if cur.Name is not None:
|
||
_append_cstr(args_buf, arg_cap2, cur.Name)
|
||
cur = cur.Next
|
||
first = 0
|
||
|
||
line_cap2: sizet = string.strlen(ret_ty_s) + string.strlen(args_buf) \
|
||
+ string.strlen(callee_val.Name) + 64
|
||
line: str = pool.alloc(line_cap2)
|
||
if line is None: return None
|
||
if is_void == 1:
|
||
viperlib.snprintf(line, line_cap2, "call %s %s(%s)", ret_ty_s, callee_val.Name, args_buf)
|
||
else:
|
||
viperlib.snprintf(line, line_cap2, "%s = call %s %s(%s)", name, ret_ty_s, callee_val.Name, args_buf)
|
||
self._emit(line)
|
||
|
||
if is_void == 1:
|
||
return None
|
||
return SSAValue(pool, ret_ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# Type conversion instructions
|
||
# ============================================================
|
||
def build_bitcast(self, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = bitcast <from_ty> <val> to <to_ty>"""
|
||
if self is None or val is None:
|
||
return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
from_ty_s: str = self._type_str(val.Ty)
|
||
to_ty_s: str = self._type_str(to_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
if val.Name is None:
|
||
return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = bitcast %s %s to %s", name, from_ty_s, val.Name, to_ty_s)
|
||
self._emit(line)
|
||
ret: Value* = SSAValue(pool, to_ty, name)
|
||
return ret
|
||
|
||
|
||
def build_sext(self, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = sext <from_ty> <val> to <to_ty>"""
|
||
if self is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
from_ty_s: str = self._type_str(val.Ty)
|
||
to_ty_s: str = self._type_str(to_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = sext %s %s to %s", name, from_ty_s, val.Name, to_ty_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, to_ty, name)
|
||
|
||
|
||
def build_trunc(self, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = trunc <from_ty> <val> to <to_ty>"""
|
||
if self is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
from_ty_s: str = self._type_str(val.Ty)
|
||
to_ty_s: str = self._type_str(to_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = trunc %s %s to %s", name, from_ty_s, val.Name, to_ty_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, to_ty, name)
|
||
|
||
|
||
def build_zext(self, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = zext <from_ty> <val> to <to_ty>"""
|
||
if self is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
from_ty_s: str = self._type_str(val.Ty)
|
||
to_ty_s: str = self._type_str(to_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = zext %s %s to %s", name, from_ty_s, val.Name, to_ty_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, to_ty, name)
|
||
|
||
|
||
def build_ptrtoint(self, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = ptrtoint <from_ty> <val> to <to_ty>"""
|
||
if self is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
from_ty_s: str = self._type_str(val.Ty)
|
||
to_ty_s: str = self._type_str(to_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = ptrtoint %s %s to %s", name, from_ty_s, val.Name, to_ty_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, to_ty, name)
|
||
|
||
|
||
def build_inttoptr(self, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = inttoptr <from_ty> <val> to <to_ty>"""
|
||
if self is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
from_ty_s: str = self._type_str(val.Ty)
|
||
to_ty_s: str = self._type_str(to_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = inttoptr %s %s to %s", name, from_ty_s, val.Name, to_ty_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, to_ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# Float conversion instructions
|
||
# ============================================================
|
||
def build_fpext(self, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = fpext <from_ty> <val> to <to_ty>"""
|
||
if self is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
from_ty_s: str = self._type_str(val.Ty)
|
||
to_ty_s: str = self._type_str(to_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = fpext %s %s to %s", name, from_ty_s, val.Name, to_ty_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, to_ty, name)
|
||
|
||
|
||
def build_fptrunc(self, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = fptrunc <from_ty> <val> to <to_ty>"""
|
||
if self is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
from_ty_s: str = self._type_str(val.Ty)
|
||
to_ty_s: str = self._type_str(to_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = fptrunc %s %s to %s", name, from_ty_s, val.Name, to_ty_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, to_ty, name)
|
||
|
||
|
||
def build_fp2si(self, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = fptosi <from_ty> <val> to <to_ty>"""
|
||
if self is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
from_ty_s: str = self._type_str(val.Ty)
|
||
to_ty_s: str = self._type_str(to_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = fptosi %s %s to %s", name, from_ty_s, val.Name, to_ty_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, to_ty, name)
|
||
|
||
|
||
def build_si2fp(self, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = sitofp <from_ty> <val> to <to_ty>"""
|
||
if self is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
from_ty_s: str = self._type_str(val.Ty)
|
||
to_ty_s: str = self._type_str(to_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = sitofp %s %s to %s", name, from_ty_s, val.Name, to_ty_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, to_ty, name)
|
||
|
||
|
||
def build_fp2ui(self, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = fptoui <from_ty> <val> to <to_ty>"""
|
||
if self is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
from_ty_s: str = self._type_str(val.Ty)
|
||
to_ty_s: str = self._type_str(to_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = fptoui %s %s to %s", name, from_ty_s, val.Name, to_ty_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, to_ty, name)
|
||
|
||
|
||
def build_ui2fp(self, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = uitofp <from_ty> <val> to <to_ty>"""
|
||
if self is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
from_ty_s: str = self._type_str(val.Ty)
|
||
to_ty_s: str = self._type_str(to_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = uitofp %s %s to %s", name, from_ty_s, val.Name, to_ty_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, to_ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# _gep_index_zext - zero-extend narrow integer GEP indices
|
||
#
|
||
# LLVM sign-extends narrow GEP indices (i8 0xE2 = -30); Viper indices are non-negative, so all
|
||
# <32-bit indices are zext'd to i32. build_gep/build_gep_array handle this internally.
|
||
# ============================================================
|
||
def _gep_index_zext(self, idx: Value*) -> Value*:
|
||
"""Zero-extend a <32-bit integer index to i32; other types are returned unchanged"""
|
||
if self is None or idx is None or idx.Ty is None:
|
||
return idx
|
||
bits: int = 0
|
||
match idx.Ty:
|
||
case LLVMType.Int(b):
|
||
bits = b
|
||
case _:
|
||
bits = 0
|
||
if bits == 0 or bits >= 32:
|
||
return idx
|
||
i32_ty: LLVMType* = self.Pool.alloc(LLVMType.__sizeof__())
|
||
if i32_ty is None:
|
||
return idx
|
||
string.memset(i32_ty, 0, LLVMType.__sizeof__())
|
||
c.DerefAs(i32_ty, LLVMType.Int(32))
|
||
z: Value* = self.build_zext(idx, i32_ty)
|
||
return z if z is not None else idx
|
||
|
||
|
||
# ============================================================
|
||
# GEP instructions
|
||
# ============================================================
|
||
def build_gep(self, elem_ty: LLVMType*,
|
||
ptr: Value*, idx: Value*) -> Value*:
|
||
"""%N = getelementptr <elem_ty>, <ptr_ty> <ptr>, <idx_ty> <idx>"""
|
||
if self is None or ptr is None or idx is None: return None
|
||
# Defensive check: ptr.Ty must be a pointer type, else llc errors ("base of getelementptr
|
||
# must be a pointer") when translate_value returns a non-pointer (e.g. i32 constant 0).
|
||
if _is_ptr_type(ptr.Ty) == 0:
|
||
return None
|
||
idx = self._gep_index_zext(idx)
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
elem_ty_s: str = self._type_str(elem_ty)
|
||
ptr_ty_s: str = self._type_str(ptr.Ty)
|
||
idx_ty_s: str = self._type_str(idx.Ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = getelementptr %s, %s %s, %s %s",
|
||
name, elem_ty_s, ptr_ty_s, ptr.Name, idx_ty_s, idx.Name)
|
||
self._emit(line)
|
||
|
||
# GEP result type is elem_ty*
|
||
result_ty: LLVMType* = pool.alloc(LLVMType.__sizeof__())
|
||
if result_ty is not None:
|
||
string.memset(result_ty, 0, LLVMType.__sizeof__())
|
||
c.DerefAs(result_ty, LLVMType.Ptr(elem_ty))
|
||
return SSAValue(pool, result_ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# build_gep_array - two-index array GEP: getelementptr [N x ty], [N x ty]* %ptr, i32 0, i32 %idx
|
||
# ============================================================
|
||
def build_gep_array(self, array_ty: LLVMType*,
|
||
elem_ty: LLVMType*,
|
||
ptr: Value*, idx: Value*) -> Value*:
|
||
"""%N = getelementptr [N x elem_ty], [N x elem_ty]* %ptr, i32 0, i32 %idx
|
||
Result type is elem_ty*."""
|
||
if self is None or ptr is None or idx is None: return None
|
||
idx = self._gep_index_zext(idx)
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
array_ty_s: str = self._type_str(array_ty)
|
||
ptr_ty_s: str = self._type_str(ptr.Ty)
|
||
idx_ty_s: str = self._type_str(idx.Ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = getelementptr %s, %s %s, i32 0, %s %s",
|
||
name, array_ty_s, ptr_ty_s, ptr.Name, idx_ty_s, idx.Name)
|
||
self._emit(line)
|
||
|
||
# GEP result type is elem_ty*
|
||
result_ty: LLVMType* = pool.alloc(LLVMType.__sizeof__())
|
||
if result_ty is not None:
|
||
string.memset(result_ty, 0, LLVMType.__sizeof__())
|
||
c.DerefAs(result_ty, LLVMType.Ptr(elem_ty))
|
||
return SSAValue(pool, result_ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# build_gep_struct - struct-field GEP: getelementptr struct_ty, struct_ty* %ptr, i32 0, i32 %field_idx
|
||
# ============================================================
|
||
def build_gep_struct(self, struct_ty: LLVMType*,
|
||
field_ty: LLVMType*,
|
||
ptr: Value*, field_idx: int) -> Value*:
|
||
"""%N = getelementptr struct_ty, struct_ty* %ptr, i32 0, i32 %field_idx
|
||
Result type is field_ty*."""
|
||
if self is None or ptr is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
struct_ty_s: str = self._type_str(struct_ty)
|
||
ptr_ty_s: str = self._type_str(ptr.Ty)
|
||
line: str = pool.alloc(2048)
|
||
if line is None:
|
||
stdio.printf("[BGS] line alloc None\n")
|
||
return None
|
||
viperlib.snprintf(line, 2048, "%s = getelementptr %s, %s %s, i32 0, i32 %d",
|
||
name, struct_ty_s, ptr_ty_s, ptr.Name, field_idx)
|
||
self._emit(line)
|
||
|
||
# GEP result type is field_ty*
|
||
result_ty: LLVMType* = pool.alloc(LLVMType.__sizeof__())
|
||
if result_ty is None:
|
||
stdio.printf("[BGS] result_ty alloc None\n")
|
||
else:
|
||
string.memset(result_ty, 0, LLVMType.__sizeof__())
|
||
c.DerefAs(result_ty, LLVMType.Ptr(field_ty))
|
||
rv: Value* = SSAValue(pool, result_ty, name)
|
||
if rv is None:
|
||
stdio.printf("[BGS] SSAValue None\n")
|
||
return rv
|
||
|
||
|
||
# ============================================================
|
||
# Phi instruction
|
||
# ============================================================
|
||
def build_phi(self, ty: LLVMType*,
|
||
incoming_head: PhiIncoming*, count: int) -> Value*:
|
||
"""%N = phi <ty> [val1, %blk1], [val2, %blk2], ...
|
||
incoming_head is the PhiIncoming list head; count is the incoming-edge count."""
|
||
if self is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
ty_s: str = self._type_str(ty)
|
||
|
||
# Build the incoming list text [val, %blk], ...; there can be many edges (e.g. a 30+ or-chain),
|
||
# so a large buffer is required.
|
||
parts: str = pool.alloc(4096)
|
||
if parts is None: return None
|
||
parts[0] = '\0'
|
||
cur: PhiIncoming* = incoming_head
|
||
first: int = 1
|
||
while cur is not None:
|
||
if first == 0:
|
||
_append_cstr(parts, 4096, ", ")
|
||
_append_cstr(parts, 4096, "[")
|
||
# use an intermediate var; chained struct member access would be dropped by the compiler
|
||
inc_val: Value* = cur.Val
|
||
if inc_val is not None:
|
||
inc_val_name: str = inc_val.Name
|
||
if inc_val_name is not None:
|
||
_append_cstr(parts, 4096, inc_val_name)
|
||
_append_cstr(parts, 4096, ", %")
|
||
inc_blk: BasicBlock* = cur.Block
|
||
if inc_blk is not None:
|
||
inc_blk_name: str = inc_blk.Name
|
||
if inc_blk_name is not None:
|
||
_append_cstr(parts, 4096, inc_blk_name)
|
||
_append_cstr(parts, 4096, "]")
|
||
cur = cur.Next
|
||
first = 0
|
||
|
||
line: str = pool.alloc(8192)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, 8192, "%s = phi %s %s", name, ty_s, parts)
|
||
self._emit(line)
|
||
return SSAValue(pool, ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# Switch instruction
|
||
# ============================================================
|
||
def build_switch(self, val: Value*,
|
||
default_blk: BasicBlock*,
|
||
cases_head: SwitchCase*, case_count: int):
|
||
"""switch <ty> <val>, label %<default> [<case_val>, label %<blk> ...]"""
|
||
if self is None or val is None or default_blk is None: return
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
ty_s: str = self._type_str(val.Ty)
|
||
|
||
# build body: switch <ty> <val>, label %default
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return
|
||
viperlib.snprintf(line, _LINE_CAP, "switch %s %s, label %%%s",
|
||
ty_s, val.Name, default_blk.Name)
|
||
_append_cstr(line, _LINE_CAP, " [")
|
||
_append_cstr(line, _LINE_CAP, "\n")
|
||
|
||
# iterate the case list
|
||
cur: SwitchCase* = cases_head
|
||
case_ty_s: str = self._type_str(val.Ty)
|
||
while cur is not None:
|
||
case_line: str = pool.alloc(_LINE_CAP)
|
||
if case_line is not None:
|
||
# use an intermediate var; chained struct member access would be dropped by the compiler
|
||
cv: Value* = cur.CaseVal
|
||
cb: BasicBlock* = cur.Block
|
||
if cv is not None and cb is not None:
|
||
cv_name: str = cv.Name
|
||
cb_name: str = cb.Name
|
||
if cv_name is not None and cb_name is not None:
|
||
viperlib.snprintf(case_line, _LINE_CAP, " %s %s, label %%%s\n",
|
||
case_ty_s, cv_name, cb_name)
|
||
_append_cstr(line, _LINE_CAP, case_line)
|
||
cur = cur.Next
|
||
|
||
_append_cstr(line, _LINE_CAP, "]")
|
||
self._emit(line)
|
||
self._mark_terminated()
|
||
|
||
|
||
# ============================================================
|
||
# Select instruction
|
||
# ============================================================
|
||
def build_select(self, cond: Value*,
|
||
true_val: Value*, false_val: Value*) -> Value*:
|
||
"""%N = select i1 <cond>, <ty> <true_val>, <ty> <false_val>"""
|
||
if self is None or cond is None or true_val is None or false_val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
ty_s: str = self._type_str(true_val.Ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = select i1 %s, %s %s, %s %s",
|
||
name, cond.Name, ty_s, true_val.Name, ty_s, false_val.Name)
|
||
self._emit(line)
|
||
return SSAValue(pool, true_val.Ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# Unreachable instruction
|
||
# ============================================================
|
||
def build_unreachable(self):
|
||
"""unreachable"""
|
||
if self is None: return
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
line: str = pool.alloc(16)
|
||
if line is None: return
|
||
string.strcpy(line, "unreachable")
|
||
self._emit(line)
|
||
self._mark_terminated()
|
||
|
||
|
||
# ============================================================
|
||
# Atomic instructions (Phase 3)
|
||
# ============================================================
|
||
def build_atomicrmw(self, op: int,
|
||
ptr: Value*, val: Value*,
|
||
order: int) -> Value*:
|
||
"""%N = atomicrmw <op> <ptr_ty> <ptr>, <val_ty> <val> <ordering>"""
|
||
if self is None or ptr is None or val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
ptr_ty_s: str = self._type_str(ptr.Ty)
|
||
val_ty_s: str = self._type_str(val.Ty)
|
||
op_s: str = _atomic_op_str(op)
|
||
ord_s: str = _atomic_order_str(order)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = atomicrmw %s %s %s, %s %s %s",
|
||
name, op_s, ptr_ty_s, ptr.Name, val_ty_s, val.Name, ord_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, val.Ty, name)
|
||
|
||
|
||
def build_cmpxchg(self, ptr: Value*,
|
||
cmp_val: Value*, new_val: Value*,
|
||
succ_order: int, fail_order: int) -> Value*:
|
||
"""%N = cmpxchg <ptr_ty> <ptr>, <cmp_ty> <cmp>, <new_ty> <new> <succ_order> <fail_order>
|
||
Returns a {<ty>, i1} SSA value (simplified to an i64 representing the pair)."""
|
||
if self is None or ptr is None or cmp_val is None or new_val is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
ptr_ty_s: str = self._type_str(ptr.Ty)
|
||
cmp_ty_s: str = self._type_str(cmp_val.Ty)
|
||
new_ty_s: str = self._type_str(new_val.Ty)
|
||
succ_s: str = _atomic_order_str(succ_order)
|
||
fail_s: str = _atomic_order_str(fail_order)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = cmpxchg %s %s, %s %s, %s %s %s %s",
|
||
name, ptr_ty_s, ptr.Name, cmp_ty_s, cmp_val.Name,
|
||
new_ty_s, new_val.Name, succ_s, fail_s)
|
||
self._emit(line)
|
||
# return the original value's type
|
||
return SSAValue(pool, new_val.Ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# va_arg instruction: read the next variadic argument from va_list
|
||
# ============================================================
|
||
def build_va_arg(self,
|
||
va_list_ptr: Value*,
|
||
arg_ty: LLVMType*) -> Value*:
|
||
"""%N = va_arg <va_list_ty> <va_list_ptr>, <arg_ty>"""
|
||
if self is None or va_list_ptr is None or arg_ty is None:
|
||
return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
name: str = self._alloc_ssa_name()
|
||
va_list_ty_s: str = self._type_str(va_list_ptr.Ty)
|
||
arg_ty_s: str = self._type_str(arg_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
if va_list_ptr.Name is None:
|
||
return None
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = va_arg %s %s, %s",
|
||
name, va_list_ty_s, va_list_ptr.Name, arg_ty_s)
|
||
self._emit(line)
|
||
return SSAValue(pool, arg_ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# Inline assembly (Phase 3)
|
||
# ============================================================
|
||
def build_inline_asm(self, ret_ty: LLVMType*,
|
||
asm_text: str, constraints: str,
|
||
has_side_effects: int) -> Value*:
|
||
"""%N = call <ret_ty> asm [sideeffect] "" "<constraints>"()
|
||
Simplified inline-assembly call, no-argument version."""
|
||
if self is None: return None
|
||
pool: memhub.MemBuddy* = self.Pool
|
||
|
||
# check whether the return type is void
|
||
is_void: int = 0
|
||
match ret_ty:
|
||
case LLVMType.Void():
|
||
is_void = 1
|
||
case _:
|
||
is_void = 0
|
||
|
||
name: str = None
|
||
if is_void == 0:
|
||
name = self._alloc_ssa_name()
|
||
|
||
ret_ty_s: str = self._type_str(ret_ty)
|
||
line: str = pool.alloc(_LINE_CAP)
|
||
if line is None: return None
|
||
|
||
if has_side_effects != 0:
|
||
if is_void == 1:
|
||
viperlib.snprintf(line, _LINE_CAP, "call %s asm sideeffect \"%s\", \"%s\"()",
|
||
ret_ty_s, asm_text, constraints)
|
||
else:
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = call %s asm sideeffect \"%s\", \"%s\"()",
|
||
name, ret_ty_s, asm_text, constraints)
|
||
else:
|
||
if is_void == 1:
|
||
viperlib.snprintf(line, _LINE_CAP, "call %s asm \"%s\", \"%s\"()",
|
||
ret_ty_s, asm_text, constraints)
|
||
else:
|
||
viperlib.snprintf(line, _LINE_CAP, "%s = call %s asm \"%s\", \"%s\"()",
|
||
name, ret_ty_s, asm_text, constraints)
|
||
self._emit(line)
|
||
|
||
if is_void == 1:
|
||
return None
|
||
return SSAValue(pool, ret_ty, name)
|
||
|
||
|
||
# ============================================================
|
||
# Factory functions
|
||
# ============================================================
|
||
# IRBuilder hardcoded size: Pool(8) + Func(8) + CurBlock(8) + Counter(4) + DbgLoc(8) = 40 bytes (same layout as llvmlite)
|
||
define BUILDER_SIZE = 40
|
||
|
||
def new_builder(pool: memhub.MemBuddy*, func: Function*) -> IRBuilder*:
|
||
"""Create an IRBuilder associated with function func."""
|
||
ptr: IRBuilder* = pool.alloc(BUILDER_SIZE)
|
||
if ptr is None: return None
|
||
string.memset(ptr, 0, BUILDER_SIZE)
|
||
ptr.Pool = pool
|
||
ptr.Func = func
|
||
ptr.CurBlock = None
|
||
ptr.Counter = 0
|
||
ptr.DbgLoc = None
|
||
return ptr
|
||
|
||
|
||
# ============================================================
|
||
# Block positioning
|
||
# ============================================================
|
||
def position_at_end(builder: IRBuilder*, block: BasicBlock*):
|
||
"""Set the insertion point to the end of block and move block to the end of the
|
||
function's block list, keeping text order consistent with control-flow order (else
|
||
non-monotonic SSA names break llc)."""
|
||
builder.position_at_end(block)
|
||
|
||
|
||
# ============================================================
|
||
# Internal helpers
|
||
# ============================================================
|
||
def _alloc_ssa_name(builder: IRBuilder*) -> str:
|
||
"""Allocate an SSA name string "%N" and increment the counter"""
|
||
return builder._alloc_ssa_name()
|
||
|
||
|
||
def _type_str(builder: IRBuilder*, ty: LLVMType*) -> str:
|
||
"""Get the IR text of a type (allocates a buffer from pool). Complex struct types (e.g. a
|
||
13-method vtable) can exceed 64 bytes; too small a buffer truncates '}' and breaks the IR."""
|
||
return builder._type_str(ty)
|
||
|
||
|
||
def _emit(builder: IRBuilder*, text: str):
|
||
"""Append instruction text to the current block."""
|
||
builder._emit(text)
|
||
|
||
|
||
def _mark_terminated(builder: IRBuilder*):
|
||
"""Mark the current block as terminated"""
|
||
builder._mark_terminated()
|
||
|
||
|
||
def builder_cur_block_is_terminated(builder: IRBuilder*) -> int:
|
||
"""Check the current block's IsTerminated flag via block_get_terminated instead of the
|
||
chained builder.CurBlock.IsTerminated, which silently skips the GEP when BasicBlock is opaque."""
|
||
return builder.builder_cur_block_is_terminated()
|
||
|
||
|
||
# ============================================================
|
||
# Debug location (kept same-shaped; ignored by MLIR printing)
|
||
# ============================================================
|
||
def builder_set_debug_location(builder: IRBuilder*, ref: str):
|
||
"""Set the current DILocation ref (kept same-shaped; MLIR only records it, no output effect)"""
|
||
builder.set_debug_location(ref)
|
||
|
||
|
||
def builder_clear_debug_location(builder: IRBuilder*):
|
||
"""Clear the current DILocation ref (kept same-shaped; MLIR only records it)"""
|
||
builder.clear_debug_location()
|
||
|
||
|
||
# ============================================================
|
||
# Memory instructions
|
||
# ============================================================
|
||
def build_alloca(builder: IRBuilder*, ty: LLVMType*) -> Value*:
|
||
"""%N = alloca <ty>
|
||
|
||
Returns an SSA value of type ty* (default alignment, backend-decided).
|
||
No default parameter: TPC parallel compilation does not pre-register include defaults
|
||
(argN fallback); for explicit alignment use build_alloca_aligned.
|
||
"""
|
||
return builder.build_alloca(ty)
|
||
|
||
|
||
def build_alloca_aligned(builder: IRBuilder*, ty: LLVMType*,
|
||
align: int) -> Value*:
|
||
"""%N = alloca <ty>, align N. Returns an SSA value of type ty* with explicit alignment
|
||
(e.g. va_list 24-byte buffer needs 16). align has no default; callers must pass it."""
|
||
return builder.build_alloca_aligned(ty, align)
|
||
|
||
|
||
def build_alloca_at_entry(builder: IRBuilder*, ty: LLVMType*) -> Value*:
|
||
"""%N = alloca <ty>, emitted in the function entry block to ensure dominance.
|
||
Inserts before the entry terminator; alloca inside a conditional branch violates dominance."""
|
||
return builder.build_alloca_at_entry(ty)
|
||
|
||
|
||
def build_load(builder: IRBuilder*, ty: LLVMType*,
|
||
ptr: Value*,
|
||
is_volatile: int = 0) -> Value*:
|
||
"""%N = load [volatile ]<ty>, <ptr_ty> <ptr>"""
|
||
return builder.build_load(ty, ptr, is_volatile)
|
||
|
||
|
||
def build_store(builder: IRBuilder*, val: Value*, ptr: Value*,
|
||
is_volatile: int = 0):
|
||
"""store [volatile ]<val_ty> <val>, <ptr_ty> <ptr>"""
|
||
builder.build_store(val, ptr, is_volatile)
|
||
|
||
|
||
# ============================================================
|
||
# Binary operation instructions
|
||
# ============================================================
|
||
def build_add(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = add <ty> <lhs>, <rhs>"""
|
||
return builder.build_add(lhs, rhs)
|
||
|
||
|
||
def build_sub(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = sub <ty> <lhs>, <rhs>"""
|
||
return builder.build_sub(lhs, rhs)
|
||
|
||
|
||
def build_mul(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = mul <ty> <lhs>, <rhs>"""
|
||
return builder.build_mul(lhs, rhs)
|
||
|
||
|
||
def build_sdiv(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = sdiv <ty> <lhs>, <rhs>"""
|
||
return builder.build_sdiv(lhs, rhs)
|
||
|
||
|
||
def build_udiv(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = udiv <ty> <lhs>, <rhs>"""
|
||
return builder.build_udiv(lhs, rhs)
|
||
|
||
|
||
def build_srem(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = srem <ty> <lhs>, <rhs>"""
|
||
return builder.build_srem(lhs, rhs)
|
||
|
||
|
||
def build_urem(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = urem <ty> <lhs>, <rhs>"""
|
||
return builder.build_urem(lhs, rhs)
|
||
|
||
|
||
def build_and(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = and <ty> <lhs>, <rhs>"""
|
||
return builder.build_and(lhs, rhs)
|
||
|
||
|
||
def build_or(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = or <ty> <lhs>, <rhs>"""
|
||
return builder.build_or(lhs, rhs)
|
||
|
||
|
||
def build_xor(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = xor <ty> <lhs>, <rhs>"""
|
||
return builder.build_xor(lhs, rhs)
|
||
|
||
|
||
def build_shl(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = shl <ty> <lhs>, <rhs>"""
|
||
return builder.build_shl(lhs, rhs)
|
||
|
||
|
||
def build_lshr(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = lshr <ty> <lhs>, <rhs>"""
|
||
return builder.build_lshr(lhs, rhs)
|
||
|
||
|
||
def build_ashr(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = ashr <ty> <lhs>, <rhs>"""
|
||
return builder.build_ashr(lhs, rhs)
|
||
|
||
|
||
# ============================================================
|
||
# Float binary operation instructions
|
||
# ============================================================
|
||
def build_fadd(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = fadd <ty> <lhs>, <rhs>"""
|
||
return builder.build_fadd(lhs, rhs)
|
||
|
||
|
||
def build_fsub(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = fsub <ty> <lhs>, <rhs>"""
|
||
return builder.build_fsub(lhs, rhs)
|
||
|
||
|
||
def build_fmul(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = fmul <ty> <lhs>, <rhs>"""
|
||
return builder.build_fmul(lhs, rhs)
|
||
|
||
|
||
def build_fdiv(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = fdiv <ty> <lhs>, <rhs>"""
|
||
return builder.build_fdiv(lhs, rhs)
|
||
|
||
|
||
def build_frem(builder: IRBuilder*, lhs: Value*,
|
||
rhs: Value*) -> Value*:
|
||
"""%N = frem <ty> <lhs>, <rhs>"""
|
||
return builder.build_frem(lhs, rhs)
|
||
|
||
|
||
def build_fneg(builder: IRBuilder*, val: Value*) -> Value*:
|
||
"""%N = fneg <ty> <val>"""
|
||
return builder.build_fneg(val)
|
||
|
||
|
||
def _build_binop(builder: IRBuilder*, op: str,
|
||
lhs: Value*, rhs: Value*) -> Value*:
|
||
"""Shared implementation of binary operations"""
|
||
return builder._build_binop(op, lhs, rhs)
|
||
|
||
|
||
# ============================================================
|
||
# Comparison instructions
|
||
# ============================================================
|
||
# ICMP predicate constants
|
||
define ICMP_EQ = 0
|
||
define ICMP_NE = 1
|
||
define ICMP_SGT = 2
|
||
define ICMP_SGE = 3
|
||
define ICMP_SLT = 4
|
||
define ICMP_SLE = 5
|
||
define ICMP_UGT = 6
|
||
define ICMP_UGE = 7
|
||
define ICMP_ULT = 8
|
||
define ICMP_ULE = 9
|
||
|
||
# FCMP predicate constants
|
||
define FCMP_FALSE = 0
|
||
define FCMP_OEQ = 1
|
||
define FCMP_OGT = 2
|
||
define FCMP_OGE = 3
|
||
define FCMP_OLT = 4
|
||
define FCMP_OLE = 5
|
||
define FCMP_ONE = 6
|
||
define FCMP_ORD = 7
|
||
define FCMP_UNO = 8
|
||
define FCMP_UEQ = 9
|
||
define FCMP_UGT = 10
|
||
define FCMP_UGE = 11
|
||
define FCMP_ULT = 12
|
||
define FCMP_ULE = 13
|
||
define FCMP_UNE = 14
|
||
define FCMP_TRUE = 15
|
||
|
||
|
||
def _icmp_pred_str(pred: int) -> str:
|
||
if pred == ICMP_EQ: return "eq"
|
||
if pred == ICMP_NE: return "ne"
|
||
if pred == ICMP_SGT: return "sgt"
|
||
if pred == ICMP_SGE: return "sge"
|
||
if pred == ICMP_SLT: return "slt"
|
||
if pred == ICMP_SLE: return "sle"
|
||
if pred == ICMP_UGT: return "ugt"
|
||
if pred == ICMP_UGE: return "uge"
|
||
if pred == ICMP_ULT: return "ult"
|
||
if pred == ICMP_ULE: return "ule"
|
||
return "eq"
|
||
|
||
|
||
def _fcmp_pred_str(pred: int) -> str:
|
||
if pred == FCMP_FALSE: return "false"
|
||
if pred == FCMP_OEQ: return "oeq"
|
||
if pred == FCMP_OGT: return "ogt"
|
||
if pred == FCMP_OGE: return "oge"
|
||
if pred == FCMP_OLT: return "olt"
|
||
if pred == FCMP_OLE: return "ole"
|
||
if pred == FCMP_ONE: return "one"
|
||
if pred == FCMP_ORD: return "ord"
|
||
if pred == FCMP_UNO: return "uno"
|
||
if pred == FCMP_UEQ: return "ueq"
|
||
if pred == FCMP_UGT: return "ugt"
|
||
if pred == FCMP_UGE: return "uge"
|
||
if pred == FCMP_ULT: return "ult"
|
||
if pred == FCMP_ULE: return "ule"
|
||
if pred == FCMP_UNE: return "une"
|
||
if pred == FCMP_TRUE: return "true"
|
||
return "oeq"
|
||
|
||
|
||
def build_icmp(builder: IRBuilder*, pred: int,
|
||
lhs: Value*, rhs: Value*) -> Value*:
|
||
"""%N = icmp <pred> <ty> <lhs>, <rhs>
|
||
Returns an SSA value of type i1."""
|
||
return builder.build_icmp(pred, lhs, rhs)
|
||
|
||
|
||
def build_fcmp(builder: IRBuilder*, pred: int,
|
||
lhs: Value*, rhs: Value*) -> Value*:
|
||
"""%N = fcmp <pred> <ty> <lhs>, <rhs>
|
||
Returns an SSA value of type i1."""
|
||
return builder.build_fcmp(pred, lhs, rhs)
|
||
|
||
|
||
# ============================================================
|
||
# Terminator instructions
|
||
# ============================================================
|
||
def build_br(builder: IRBuilder*, target: BasicBlock*):
|
||
"""br label %<target>"""
|
||
builder.build_br(target)
|
||
|
||
|
||
def build_cond_br(builder: IRBuilder*, cond: Value*,
|
||
then_blk: BasicBlock*, else_blk: BasicBlock*):
|
||
"""br i1 <cond>, label %<then>, label %<else_>"""
|
||
builder.build_cond_br(cond, then_blk, else_blk)
|
||
|
||
|
||
def build_ret(builder: IRBuilder*, val: Value*):
|
||
"""ret <ty> <val>"""
|
||
builder.build_ret(val)
|
||
|
||
|
||
def build_ret_void(builder: IRBuilder*):
|
||
"""ret void"""
|
||
builder.build_ret_void()
|
||
|
||
|
||
# ============================================================
|
||
# Call instructions
|
||
# ============================================================
|
||
def build_call(builder: IRBuilder*, callee: str,
|
||
args: Value*, arg_count: int,
|
||
ret_ty: LLVMType*,
|
||
is_variadic: int = 0) -> Value*:
|
||
"""%N = call <ret_ty> @<callee>(<args>)
|
||
args is the Value list head, arg_count the argument count. If ret_ty is void no SSA name is
|
||
allocated (plain call); is_variadic=1 emits the function type signature (e.g. i32 (i8*, ...))."""
|
||
return builder.build_call(callee, args, arg_count, ret_ty, is_variadic)
|
||
|
||
|
||
# ============================================================
|
||
# Indirect call instruction: call <ret_ty> %callee(<args>)
|
||
#
|
||
# callee_val is a function-pointer SSA value (must already be a pointer type, e.g. i32(i8*)*)
|
||
# ============================================================
|
||
def build_call_indirect(builder: IRBuilder*, callee_val: Value*,
|
||
args: Value*, arg_count: int,
|
||
ret_ty: LLVMType*) -> Value*:
|
||
"""%N = call <ret_ty> %callee(<args>)
|
||
Indirect call: callee_val is a function-pointer SSA value."""
|
||
return builder.build_call_indirect(callee_val, args, arg_count, ret_ty)
|
||
|
||
|
||
# ============================================================
|
||
# Type conversion instructions
|
||
# ============================================================
|
||
def build_bitcast(builder: IRBuilder*, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = bitcast <from_ty> <val> to <to_ty>"""
|
||
return builder.build_bitcast(val, to_ty)
|
||
|
||
|
||
def build_sext(builder: IRBuilder*, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = sext <from_ty> <val> to <to_ty>"""
|
||
return builder.build_sext(val, to_ty)
|
||
|
||
|
||
def build_trunc(builder: IRBuilder*, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = trunc <from_ty> <val> to <to_ty>"""
|
||
return builder.build_trunc(val, to_ty)
|
||
|
||
|
||
def build_zext(builder: IRBuilder*, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = zext <from_ty> <val> to <to_ty>"""
|
||
return builder.build_zext(val, to_ty)
|
||
|
||
|
||
def build_ptrtoint(builder: IRBuilder*, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = ptrtoint <from_ty> <val> to <to_ty>"""
|
||
return builder.build_ptrtoint(val, to_ty)
|
||
|
||
|
||
def build_inttoptr(builder: IRBuilder*, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = inttoptr <from_ty> <val> to <to_ty>"""
|
||
return builder.build_inttoptr(val, to_ty)
|
||
|
||
|
||
# ============================================================
|
||
# Float conversion instructions
|
||
# ============================================================
|
||
def build_fpext(builder: IRBuilder*, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = fpext <from_ty> <val> to <to_ty>"""
|
||
return builder.build_fpext(val, to_ty)
|
||
|
||
|
||
def build_fptrunc(builder: IRBuilder*, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = fptrunc <from_ty> <val> to <to_ty>"""
|
||
return builder.build_fptrunc(val, to_ty)
|
||
|
||
|
||
def build_fp2si(builder: IRBuilder*, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = fptosi <from_ty> <val> to <to_ty>"""
|
||
return builder.build_fp2si(val, to_ty)
|
||
|
||
|
||
def build_si2fp(builder: IRBuilder*, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = sitofp <from_ty> <val> to <to_ty>"""
|
||
return builder.build_si2fp(val, to_ty)
|
||
|
||
|
||
def build_fp2ui(builder: IRBuilder*, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = fptoui <from_ty> <val> to <to_ty>"""
|
||
return builder.build_fp2ui(val, to_ty)
|
||
|
||
|
||
def build_ui2fp(builder: IRBuilder*, val: Value*,
|
||
to_ty: LLVMType*) -> Value*:
|
||
"""%N = uitofp <from_ty> <val> to <to_ty>"""
|
||
return builder.build_ui2fp(val, to_ty)
|
||
|
||
|
||
# ============================================================
|
||
# GEP instructions
|
||
# ============================================================
|
||
def _is_ptr_type(ty: LLVMType*) -> int:
|
||
"""Check whether ty is a Ptr type (standalone function to avoid the nested-match compiler bug)"""
|
||
if ty is None:
|
||
return 0
|
||
match ty:
|
||
case LLVMType.Ptr(pointee):
|
||
return 1
|
||
case _:
|
||
return 0
|
||
|
||
|
||
def build_gep(builder: IRBuilder*, elem_ty: LLVMType*,
|
||
ptr: Value*, idx: Value*) -> Value*:
|
||
"""%N = getelementptr <elem_ty>, <ptr_ty> <ptr>, <idx_ty> <idx>"""
|
||
return builder.build_gep(elem_ty, ptr, idx)
|
||
|
||
|
||
# ============================================================
|
||
# build_gep_array - two-index array GEP: getelementptr [N x ty], [N x ty]* %ptr, i32 0, i32 %idx
|
||
# ============================================================
|
||
def build_gep_array(builder: IRBuilder*, array_ty: LLVMType*,
|
||
elem_ty: LLVMType*,
|
||
ptr: Value*, idx: Value*) -> Value*:
|
||
"""%N = getelementptr [N x elem_ty], [N x elem_ty]* %ptr, i32 0, i32 %idx
|
||
Result type is elem_ty*."""
|
||
return builder.build_gep_array(array_ty, elem_ty, ptr, idx)
|
||
|
||
|
||
# ============================================================
|
||
# build_gep_struct - struct-field GEP: getelementptr struct_ty, struct_ty* %ptr, i32 0, i32 %field_idx
|
||
# ============================================================
|
||
def build_gep_struct(builder: IRBuilder*, struct_ty: LLVMType*,
|
||
field_ty: LLVMType*,
|
||
ptr: Value*, field_idx: int) -> Value*:
|
||
"""%N = getelementptr struct_ty, struct_ty* %ptr, i32 0, i32 %field_idx
|
||
Result type is field_ty*."""
|
||
return builder.build_gep_struct(struct_ty, field_ty, ptr, field_idx)
|
||
|
||
|
||
# ============================================================
|
||
# PhiIncoming: a phi incoming edge (value, block) pair
|
||
# inherit GSListNode[PhiIncoming] for a strongly-typed Next
|
||
#
|
||
# Note: @t.NoVTable is required (rationale in the Line comment in __function.py).
|
||
# ============================================================
|
||
novirtual class PhiIncoming(GSListNode[PhiIncoming]):
|
||
Val: Value*
|
||
Block: BasicBlock*
|
||
|
||
|
||
# PhiIncoming hardcoded size: Next(8) + Val(8) + Block(8) = 24 bytes
|
||
define PHIINC_SIZE = 24
|
||
|
||
def new_phi_incoming(pool: memhub.MemBuddy*,
|
||
val: Value*, block: BasicBlock*) -> PhiIncoming*:
|
||
ptr: PhiIncoming* = pool.alloc(PHIINC_SIZE)
|
||
if ptr is None: return None
|
||
string.memset(ptr, 0, PHIINC_SIZE)
|
||
ptr.Val = val
|
||
ptr.Block = block
|
||
return ptr
|
||
|
||
|
||
# ============================================================
|
||
# Phi instruction
|
||
# ============================================================
|
||
def build_phi(builder: IRBuilder*, ty: LLVMType*,
|
||
incoming_head: PhiIncoming*, count: int) -> Value*:
|
||
"""%N = phi <ty> [val1, %blk1], [val2, %blk2], ...
|
||
incoming_head is the PhiIncoming list head; count is the incoming-edge count."""
|
||
return builder.build_phi(ty, incoming_head, count)
|
||
|
||
|
||
# ============================================================
|
||
# SwitchCase: a case branch of the switch instruction
|
||
#
|
||
# Note: @t.NoVTable is required (rationale in the Line comment in __function.py).
|
||
# ============================================================
|
||
novirtual class SwitchCase(GSListNode[SwitchCase]):
|
||
CaseVal: Value*
|
||
Block: BasicBlock*
|
||
|
||
|
||
# SwitchCase hardcoded size: Next(8) + CaseVal(8) + Block(8) = 24 bytes
|
||
define SWCASE_SIZE = 24
|
||
|
||
def new_switch_case(pool: memhub.MemBuddy*,
|
||
case_val: Value*, block: BasicBlock*) -> SwitchCase*:
|
||
ptr: SwitchCase* = pool.alloc(SWCASE_SIZE)
|
||
if ptr is None: return None
|
||
string.memset(ptr, 0, SWCASE_SIZE)
|
||
ptr.CaseVal = case_val
|
||
ptr.Block = block
|
||
return ptr
|
||
|
||
|
||
# ============================================================
|
||
# Switch instruction
|
||
# ============================================================
|
||
def build_switch(builder: IRBuilder*, val: Value*,
|
||
default_blk: BasicBlock*,
|
||
cases_head: SwitchCase*, case_count: int):
|
||
"""switch <ty> <val>, label %<default> [<case_val>, label %<blk> ...]"""
|
||
builder.build_switch(val, default_blk, cases_head, case_count)
|
||
|
||
|
||
# ============================================================
|
||
# Select instruction
|
||
# ============================================================
|
||
def build_select(builder: IRBuilder*, cond: Value*,
|
||
true_val: Value*, false_val: Value*) -> Value*:
|
||
"""%N = select i1 <cond>, <ty> <true_val>, <ty> <false_val>"""
|
||
return builder.build_select(cond, true_val, false_val)
|
||
|
||
|
||
# ============================================================
|
||
# Unreachable instruction
|
||
# ============================================================
|
||
def build_unreachable(builder: IRBuilder*):
|
||
"""unreachable"""
|
||
builder.build_unreachable()
|
||
|
||
|
||
# ============================================================
|
||
# Atomic instructions (Phase 3)
|
||
# ============================================================
|
||
# AtomicRMW operation constants
|
||
define ATOMIC_XCHG = 0
|
||
define ATOMIC_ADD = 1
|
||
define ATOMIC_SUB = 2
|
||
define ATOMIC_AND = 3
|
||
define ATOMIC_NAND = 4
|
||
define ATOMIC_OR = 5
|
||
define ATOMIC_XOR = 6
|
||
define ATOMIC_MAX = 7
|
||
define ATOMIC_MIN = 8
|
||
define ATOMIC_UMAX = 9
|
||
define ATOMIC_UMIN = 10
|
||
|
||
# Memory ordering constants
|
||
define ATOMIC_ORDER_NOTATOMIC = 0
|
||
define ATOMIC_ORDER_UNORDERED = 1
|
||
define ATOMIC_ORDER_MONOTONIC = 2
|
||
define ATOMIC_ORDER_ACQUIRE = 3
|
||
define ATOMIC_ORDER_RELEASE = 4
|
||
define ATOMIC_ORDER_ACQ_REL = 5
|
||
define ATOMIC_ORDER_SEQ_CST = 6
|
||
|
||
|
||
def _atomic_op_str(op: int) -> str:
|
||
if op == ATOMIC_XCHG: return "xchg"
|
||
if op == ATOMIC_ADD: return "add"
|
||
if op == ATOMIC_SUB: return "sub"
|
||
if op == ATOMIC_AND: return "and"
|
||
if op == ATOMIC_NAND: return "nand"
|
||
if op == ATOMIC_OR: return "or"
|
||
if op == ATOMIC_XOR: return "xor"
|
||
if op == ATOMIC_MAX: return "max"
|
||
if op == ATOMIC_MIN: return "min"
|
||
if op == ATOMIC_UMAX: return "umax"
|
||
if op == ATOMIC_UMIN: return "umin"
|
||
return "xchg"
|
||
|
||
|
||
def _atomic_order_str(ord: int) -> str:
|
||
if ord == ATOMIC_ORDER_UNORDERED: return "unordered"
|
||
if ord == ATOMIC_ORDER_MONOTONIC: return "monotonic"
|
||
if ord == ATOMIC_ORDER_ACQUIRE: return "acquire"
|
||
if ord == ATOMIC_ORDER_RELEASE: return "release"
|
||
if ord == ATOMIC_ORDER_ACQ_REL: return "acq_rel"
|
||
if ord == ATOMIC_ORDER_SEQ_CST: return "seq_cst"
|
||
return "monotonic"
|
||
|
||
|
||
def build_atomicrmw(builder: IRBuilder*, op: int,
|
||
ptr: Value*, val: Value*,
|
||
order: int) -> Value*:
|
||
"""%N = atomicrmw <op> <ptr_ty> <ptr>, <val_ty> <val> <ordering>"""
|
||
return builder.build_atomicrmw(op, ptr, val, order)
|
||
|
||
|
||
def build_cmpxchg(builder: IRBuilder*, ptr: Value*,
|
||
cmp_val: Value*, new_val: Value*,
|
||
succ_order: int, fail_order: int) -> Value*:
|
||
"""%N = cmpxchg <ptr_ty> <ptr>, <cmp_ty> <cmp>, <new_ty> <new> <succ_order> <fail_order>
|
||
Returns a {<ty>, i1} SSA value (simplified to an i64 representing the pair)."""
|
||
return builder.build_cmpxchg(ptr, cmp_val, new_val, succ_order, fail_order)
|
||
|
||
|
||
# ============================================================
|
||
# va_arg instruction: read the next variadic argument from va_list
|
||
# ============================================================
|
||
def build_va_arg(builder: IRBuilder*,
|
||
va_list_ptr: Value*,
|
||
arg_ty: LLVMType*) -> Value*:
|
||
"""%N = va_arg <va_list_ty> <va_list_ptr>, <arg_ty>"""
|
||
return builder.build_va_arg(va_list_ptr, arg_ty)
|
||
|
||
|
||
# ============================================================
|
||
# Inline assembly (Phase 3)
|
||
# ============================================================
|
||
def build_inline_asm(builder: IRBuilder*, ret_ty: LLVMType*,
|
||
asm_text: str, constraints: str,
|
||
has_side_effects: int) -> Value*:
|
||
"""%N = call <ret_ty> asm [sideeffect] "" "<constraints>"()
|
||
Simplified inline-assembly call, no-argument version."""
|
||
return builder.build_inline_asm(ret_ty, asm_text, constraints, has_side_effects)
|
||
|
||
|
||
# ============================================================
|
||
# Internal helpers
|
||
# ============================================================
|
||
# _append_cstr write-position cache: O(1) positioning for repeated appends to the same buffer,
|
||
# avoiding O(n^2) strlen rescans (IR serialization hotspot); falls back to strlen on buffer switch/reset.
|
||
_g_last_dst: uint64 = 0
|
||
_g_last_pos: sizet = 0
|
||
|
||
def _append_cstr(dst: str, dst_size: sizet, src: str):
|
||
"""Append C string src to the end of dst."""
|
||
global _g_last_dst, _g_last_pos
|
||
if dst is None or src is None: return
|
||
dlen: sizet = 0
|
||
if _g_last_dst == uint64(dst):
|
||
dlen = _g_last_pos
|
||
# buffer was reset (caller reused the same address after buf[0]=0) -> back to the head
|
||
if dlen > 0 and dst[0] == '\0':
|
||
dlen = 0
|
||
# Cache consistency check (same as __module/__function): a stale pos appends past the first
|
||
# NUL and hides content; on failure fall back to strlen (hot path stays O(1)).
|
||
if dlen == 0:
|
||
if dst[0] != '\0':
|
||
dlen = string.strlen(dst)
|
||
elif dst[dlen] != '\0' or dst[dlen - 1] == '\0':
|
||
dlen = string.strlen(dst)
|
||
else:
|
||
dlen = string.strlen(dst)
|
||
_g_last_dst = uint64(dst)
|
||
# Unsigned-underflow guard: if dlen >= buffer size (buffer corrupt/dangling) return early,
|
||
# else remain = dst_size - dlen underflows and dst[dlen+i] would write past the buffer.
|
||
if dlen >= dst_size:
|
||
return
|
||
slen: sizet = string.strlen(src)
|
||
remain: sizet = dst_size - dlen
|
||
if remain <= 0: return
|
||
i: sizet = 0
|
||
while i < slen and i + 1 < remain:
|
||
dst[dlen + i] = src[i]
|
||
i += 1
|
||
dst[dlen + i] = '\0'
|
||
_g_last_pos = dlen + i
|