Files
Viper_VRTE/mlirlite/__builder.vp
T
TermiNexus 0055ab7ea7 refactor: rawptr[T] -> T* 与 t.CConst 前缀迁移(R14/R16)
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 顺序回归通过。
2026-10-05 17:38:21 +08:00

1890 lines
75 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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