Files
TermiNexus 1b21d58be3 fix(mlirlite): 消除 IR 文本缓冲与解析器的静默截断
构建器:_append_cstr 装不下时改调 MlirFail(原先静默截断会让 switch 少 case、asm 半截、call 少参数);新增 _line_cap_for 按实际片段长度分配行缓冲,覆盖 load/store、12 种转换指令、gep/gep_array/gep_struct、phi、switch、select、atomicrmw、cmpxchg、va_arg、inline asm;build_call 的 callee 不再按 128 截断,参数槽从 32 放宽到 64。解析器:_mlir_read_value/_mlir_read_type 超限即 MlirFail,不再悄悄少写;所有值/类型/符号 scratch 由 512 提到 4096,调用点容量同步;_replace_ssa_numbers 改为两遍精确分配(原 2x 估算会半行截断);全局初始化器的聚合 token 与 addressof 符号名同样改为 4096 + fail-loud;TypePrint 的 "..." 占位现在会置 _MlirTypeTrunc,由 TypePrint 统一报错。
2026-10-17 17:57:40 +08:00

2013 lines
81 KiB
Plaintext

from stdint import *
import string
import viperlib
import memhub
import stdio
import stdlib
from linkedlist import GSListNode, GSList
from .__types import LLVMType, TypePrint, MlirFail
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: cursor-style IR instruction emitter
#
# Tracks the current BasicBlock and appends instruction text to the block's instruction list.
# Maintains an incrementing SSA name counter (%0, %1, %2, ...).
#
# Usage:
# 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)
#
# Note: add novirtual to stay consistent with the hardcoded BUILDER_SIZE=40
# (methods are implemented as instance methods, with no vtable; the same-named top-level function at the end of the file is a one-line delegate).
# ============================================================
# Instruction text buffer sizing
#
# An MLIR-form type text **inlines the full field body of a named struct**
# (e.g. `!llvm.struct<"sha1.Cls", (i32, !llvm.ptr, ...)>`), so a single type can reach hundreds of bytes,
# far longer than the LLVM-form `%"sha1.Cls"` reference (a dozen-odd bytes). Lines that embed two or
# more type texts are therefore sized from the actual parts with _line_cap_for(); _LINE_CAP is only
# the fixed size for single-type-text lines (binop/alloca/icmp/ret/br).
# The rendering side (TypePrint/MlirTypeText) already has an overflow guard: any truncation errors loudly instead of silently
# producing malformed MLIR (historical failure: the alloca line type was truncated by a 128-byte buffer -> mlir-opt reported
# "unbalanced '(' character in pretty dialect name").
# ============================================================
# Capacity of a single type text (types embedded by the builders are rendered to MLIR form by TypePrint).
# An inlined named struct expands its **full field body** at the use site, so a type text can reach hundreds of bytes (ViperC
# class names / nesting are deeper) => 512 is too small and would trigger TypePrint's "type text exceeds the output buffer and is
# truncated" loud error. Raised to 2048; note that arg_cap is derived from this constant
# ((arg_count+1)*(_TY_TEXT_CAP+64)), so a too-high value makes a single multi-arg call allocation approach the module pool's per-block limit.
_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_tagged(_TY_TEXT_CAP, memhub.DETSITE_BUILDER_73)
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: corrupted LLVMType object ty=0x%llx first char=%d output='%s'\n",
uint64(ty), c0, buf)
stdlib.exit(1)
return None
return buf
def _line_cap_for(self, a: str, b: str, c: str, d: str) -> sizet:
"""Exact capacity for one IR line assembled from up to four text parts (+ syntax slack).
Builders embed MLIR type texts that inline full struct bodies (_TY_TEXT_CAP each), so a
fixed _LINE_CAP buffer silently truncated any line holding two or more of them. Sizing
from the actual parts keeps the line complete and allocates no more than needed.
"""
cap: sizet = 96
if a is not None: cap = cap + string.strlen(a)
if b is not None: cap = cap + string.strlen(b)
if c is not None: cap = cap + string.strlen(c)
if d is not None: cap = cap + string.strlen(d)
return cap
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__())
*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__())
*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__())
*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)
cap: sizet = self._line_cap_for(name, ty_s, ptr_ty_s, ptr.Name)
line: str = pool.alloc(cap)
if line is None: return None
if is_volatile != 0:
viperlib.snprintf(line, cap, "%s = load volatile %s, %s %s", name, ty_s, ptr_ty_s, ptr.Name)
else:
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(val_ty_s, val.Name, ptr_ty_s, ptr.Name)
line: str = pool.alloc(cap)
if line is None: return
if is_volatile != 0:
viperlib.snprintf(line, cap, "store volatile %s %s, %s %s", val_ty_s, val.Name, ptr_ty_s, ptr.Name)
else:
viperlib.snprintf(line, cap, "store %s %s, %s %s", val_ty_s, val.Name, ptr_ty_s, ptr.Name)
self._emit(line)
def build_store_at_entry(self, val: Value*, ptr: Value*,
is_volatile: int = 0):
"""store [volatile ]<val_ty> <val>, <ptr_ty> <ptr>, emitted in the **entry block**
(inserted before the entry terminator) so the write dominates every path.
Used to zero/null-initialize a local slot at function entry: a slot that is not
assigned on some path must still hold a safe value, e.g. the M3 owned-slife guard —
the exit-time `free(load slot)` must be `free(NULL)` (a no-op) instead of freeing
uninitialized stack garbage. Mirrors build_alloca_at_entry's insertion strategy.
"""
if self is None or val is None or ptr is None: return
pool: memhub.MemBuddy* = self.Pool
func: Function* = self.Func
if func is None or func.Blocks is None:
self.build_store(val, ptr, is_volatile)
return
blocks: GSList[BasicBlock]* = func.Blocks
entry_blk: BasicBlock* = blocks.Head
if entry_blk is None:
self.build_store(val, ptr, is_volatile)
return
val_ty_s: str = self._type_str(val.Ty)
ptr_ty_s: str = self._type_str(ptr.Ty)
cap: sizet = self._line_cap_for(val_ty_s, val.Name, ptr_ty_s, ptr.Name)
line: str = pool.alloc(cap)
if line is None: return
if is_volatile != 0:
viperlib.snprintf(line, cap, "store volatile %s %s, %s %s", val_ty_s, val.Name, ptr_ty_s, ptr.Name)
else:
viperlib.snprintf(line, cap, "store %s %s, %s %s", val_ty_s, val.Name, ptr_ty_s, ptr.Name)
block_insert_text_before_terminator(pool, entry_blk, 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__())
*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__())
*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 + 64) + 32
args_buf: str = pool.alloc_tagged(arg_cap, memhub.DETSITE_BUILDER_527)
if args_buf is None: return None
args_buf[0] = '\0'
func_ty_buf: str = pool.alloc_tagged(arg_cap, memhub.DETSITE_BUILDER_530)
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).
# Exact capacity: these appends used to pass 128 while the buffer was _LINE_CAP, so a long
# symbol name was silently cut into a call to a nonexistent symbol.
callee_cap: sizet = string.strlen(callee) + 3
callee_buf: str = pool.alloc(callee_cap)
if callee_buf is None: return None
callee_buf[0] = '\0'
if _ll_name_needs_quote(callee) != 0:
_append_cstr(callee_buf, callee_cap, "\"")
_append_cstr(callee_buf, callee_cap, callee)
_append_cstr(callee_buf, callee_cap, "\"")
else:
_append_cstr(callee_buf, callee_cap, 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 + 64) + 32
args_buf: str = pool.alloc_tagged(arg_cap2, memhub.DETSITE_BUILDER_612)
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)
cap: sizet = self._line_cap_for(name, from_ty_s, to_ty_s, val.Name)
line: str = pool.alloc(cap)
if line is None: return None
if val.Name is None:
return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, from_ty_s, to_ty_s, val.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, from_ty_s, to_ty_s, val.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, from_ty_s, to_ty_s, val.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, from_ty_s, to_ty_s, val.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, from_ty_s, to_ty_s, val.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, from_ty_s, to_ty_s, val.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, from_ty_s, to_ty_s, val.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, from_ty_s, to_ty_s, val.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, from_ty_s, to_ty_s, val.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, from_ty_s, to_ty_s, val.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, from_ty_s, to_ty_s, val.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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__())
*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)
cap: sizet = self._line_cap_for(name, elem_ty_s, ptr_ty_s, idx_ty_s)
if ptr.Name is not None: cap = cap + string.strlen(ptr.Name)
if idx.Name is not None: cap = cap + string.strlen(idx.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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__())
*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)
cap: sizet = self._line_cap_for(name, array_ty_s, ptr_ty_s, idx_ty_s)
if ptr.Name is not None: cap = cap + string.strlen(ptr.Name)
if idx.Name is not None: cap = cap + string.strlen(idx.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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__())
*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)
cap: sizet = self._line_cap_for(name, struct_ty_s, ptr_ty_s, ptr.Name)
# %d for the field index: a 32-bit int is at most 11 characters.
cap = cap + 16
line: str = pool.alloc(cap)
if line is None:
stdio.printf("[BGS] line alloc None\n")
return None
viperlib.snprintf(line, cap, "%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__())
*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)
# Size the incoming list from the real edge list: a fixed 4096-byte buffer silently
# dropped whole edges (later renders as "phi without a matching incoming edge").
parts_cap: sizet = 16
cur0: PhiIncoming* = incoming_head
while cur0 is not None:
iv0: Value* = cur0.Val
if iv0 is not None and iv0.Name is not None: parts_cap = parts_cap + string.strlen(iv0.Name)
ib0: BasicBlock* = cur0.Block
if ib0 is not None and ib0.Name is not None: parts_cap = parts_cap + string.strlen(ib0.Name)
parts_cap = parts_cap + 8
cur0 = cur0.Next
parts: str = pool.alloc(parts_cap)
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, parts_cap, ", ")
_append_cstr(parts, parts_cap, "[")
# 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, parts_cap, inc_val_name)
_append_cstr(parts, parts_cap, ", %")
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, parts_cap, inc_blk_name)
_append_cstr(parts, parts_cap, "]")
cur = cur.Next
first = 0
line_cap: sizet = self._line_cap_for(name, ty_s, parts, None)
line: str = pool.alloc(line_cap)
if line is None: return None
viperlib.snprintf(line, line_cap, "%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)
# Size the switch text from the real case list. A fixed 2560-byte line silently dropped
# whole cases while still emitting a valid switch -> wrong dispatch with no diagnostic.
total: sizet = self._line_cap_for(ty_s, val.Name, default_blk.Name, None)
case_max: sizet = 0
cur0: SwitchCase* = cases_head
while cur0 is not None:
cv0: Value* = cur0.CaseVal
cb0: BasicBlock* = cur0.Block
if cv0 is not None and cb0 is not None and cv0.Name is not None and cb0.Name is not None:
need: sizet = self._line_cap_for(ty_s, cv0.Name, cb0.Name, None)
total = total + need
if need > case_max: case_max = need
cur0 = cur0.Next
if case_max < 64: case_max = 64
line: str = pool.alloc(total)
if line is None: return
viperlib.snprintf(line, total, "switch %s %s, label %%%s",
ty_s, val.Name, default_blk.Name)
_append_cstr(line, total, " [")
_append_cstr(line, total, "\n")
# iterate the case list
case_line: str = pool.alloc(case_max)
if case_line is None: return
cur: SwitchCase* = cases_head
while cur 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, case_max, " %s %s, label %%%s\n",
ty_s, cv_name, cb_name)
_append_cstr(line, total, case_line)
cur = cur.Next
_append_cstr(line, total, "]")
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)
cap: sizet = self._line_cap_for(name, ty_s, cond.Name, true_val.Name)
if false_val.Name is not None: cap = cap + string.strlen(false_val.Name)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, ptr_ty_s, val_ty_s, op_s)
if ptr.Name is not None: cap = cap + string.strlen(ptr.Name)
if val.Name is not None: cap = cap + string.strlen(val.Name)
if ord_s is not None: cap = cap + string.strlen(ord_s)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, ptr_ty_s, cmp_ty_s, new_ty_s)
if ptr.Name is not None: cap = cap + string.strlen(ptr.Name)
if cmp_val.Name is not None: cap = cap + string.strlen(cmp_val.Name)
if new_val.Name is not None: cap = cap + string.strlen(new_val.Name)
if succ_s is not None: cap = cap + string.strlen(succ_s)
if fail_s is not None: cap = cap + string.strlen(fail_s)
line: str = pool.alloc(cap)
if line is None: return None
viperlib.snprintf(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)
cap: sizet = self._line_cap_for(name, va_list_ty_s, va_list_ptr.Name, arg_ty_s)
line: str = pool.alloc(cap)
if line is None: return None
if va_list_ptr.Name is None:
return None
viperlib.snprintf(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)
# Size by the actual asm text: templates are often multi-KB, and the old fixed
# _LINE_CAP buffer cut them mid-instruction (silent until llc/verifier complains).
cap: sizet = self._line_cap_for(ret_ty_s, asm_text, constraints, name)
line: str = pool.alloc(cap)
if line is None: return None
if has_side_effects != 0:
if is_void == 1:
viperlib.snprintf(line, cap, "call %s asm sideeffect \"%s\", \"%s\"()",
ret_ty_s, asm_text, constraints)
else:
viperlib.snprintf(line, cap, "%s = call %s asm sideeffect \"%s\", \"%s\"()",
name, ret_ty_s, asm_text, constraints)
else:
if is_void == 1:
viperlib.snprintf(line, cap, "call %s asm \"%s\", \"%s\"()",
ret_ty_s, asm_text, constraints)
else:
viperlib.snprintf(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)
def build_store_at_entry(builder: IRBuilder*, val: Value*, ptr: Value*,
is_volatile: int = 0):
"""store [volatile ]<val_ty> <val>, <ptr_ty> <ptr> emitted in the entry block
(before the entry terminator) so the write dominates every path; used for
null-initializing a local slot at function entry (M3 owned-slife guard)."""
builder.build_store_at_entry(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: novirtual 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: novirtual 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)
# fail-loud: a truncated IR line can stay syntactically plausible (a switch with fewer
# cases, a phi missing its last edge, an asm string cut mid-instruction), so it must never
# be emitted silently. Builders size their lines from the actual parts; reaching this
# branch means a builder under-sized its buffer.
slen: sizet = string.strlen(src)
if dlen >= dst_size or dlen + slen + 1 > dst_size:
_g_last_pos = 0
_g_last_dst = uint64(dst)
MlirFail("MLIR render: instruction text exceeds the builder line buffer while appending (dlen + src + NUL > capacity); a builder under-sized its line buffer")
i: sizet = 0
while i < slen:
dst[dlen + i] = src[i]
i += 1
dst[dlen + i] = '\0'
_g_last_pos = dlen + i