Files
Viper_VRTE/mlirlite/__function.vp
T
TermiNexus e71daeea0e docs(includes): R11 注释英文化——75 个文件的中文注释改写为英文短注释
按规范「注释只讲解作用,不掺杂历史原因,尽量英文」:把 includes 下 75 个 .vp 的中文 # 注释与三引号 docstring 改写为英文,每个注释块压到 2 行以内。

保留要点:作用、约束、危险警告(不得/必须/注意)、ABI 与类型约定(如 BYTEPTR | t.CPtr = i8** 的跨模块坍缩坑)、位宽/长度约定、fail-fast 要求、内存与句柄所有权。

未动:文件头注释(规范豁免,仍是中文)、运行时中文字符串常量(printf / fatal_error / MlirFail 等用户可见消息,属代码字面量)。

验证:独立脚本比对 HEAD 与工作区的代码骨架(剥离 # 注释与三引号 docstring 后的非空行)——75 个文件逐字节一致;行尾 CRLF/LF 未被改动;全文剩余中文仅 547 行头注释、0 条行尾注释。bootstrap 三重重编 SUCCESS;全量 77/77 顺序回归通过。
2026-10-05 16:18:44 +08:00

3650 lines
144 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, ParamNode, TypePrint,
MlirTypeText, MlirTypeTextIsPointer, MlirFail, MlirSetContextLine,
)
# dump sentinel: IR list traversal step cap (cycle detection, fail-fast, aligned with __module.vp).
# A broken Next cycle would hang traversal; over the cap it prints the list name and exit(1).
define IR_DUMP_CHAIN_LIMIT = 100000
# Peephole master switch (T2 forwarding/ret zeroing, T3 no-op bitcast removal).
# --no-llvmpeephole zeroes it so FunctionPrint skips local peepholes for IR diffing (RenumberSSA still runs).
G_PeepEnable: bool = True
# Function-text truncation flag: _append_cstr sets it to 1 when output hits the buffer cap.
# Checked before FunctionPrint returns and MlirFail'd: truncated MLIR must fail loudly, never silently.
_MlirOutTrunc: int = 0
# ============================================================
# Line: instruction text line
#
# One LLVM IR instruction serializes to one text line, held in a linked list.
# Extends GSListNode[Line] for typed Next: Line|CPtr
#
# NOTE: must carry @t.NoVTable, else TransPyC adds a vtable ptr (T*) but drops
# the parent GSListNode[T] Next field, misaligning the struct so field reads return garbage.
# ============================================================
novirtual class Line(GSListNode[Line]):
Buf: str # line text (NUL-terminated)
Len: sizet # text length (excluding NUL)
# ============================================================
# BasicBlock: basic block
#
# Holds name + instruction-line list + terminator flag.
# Extends GSListNode[BasicBlock] for typed Next: BasicBlock|CPtr
# Lines is a GSList[Line] pointer providing O(1) append.
#
# NOTE: must carry @t.NoVTable (same reason as Line).
# ============================================================
novirtual class BasicBlock(GSListNode[BasicBlock]):
Name: str # block name (e.g. "entry"/"if.then")
Lines: rawptr[GSList[Line]] # instruction-line list container (Head/Tail/Count)
IsTerminated: int # 1=terminator present (br/ret)
# ============================================================
# Param: function parameter
#
# Extends GSListNode[Param] for typed Next: Param|CPtr
# Attrs holds parameter attribute text (e.g. "noundef"/"nocapture"/"readonly"), None=none.
#
# NOTE: must carry @t.NoVTable (same reason as Line).
# ============================================================
novirtual class Param(GSListNode[Param]):
Ty: rawptr[LLVMType] # parameter type
Name: str # parameter name (e.g. "%0"/"%a")
Attrs: str # parameter attribute text (None=none)
# ============================================================
# Function: function
#
# Holds name + return type + parameter list + basic-block list.
# Extends GSListNode[Function] for typed Next: Function|CPtr
# Params/Blocks are GSList pointers providing O(1) append.
# Attrs holds function-level attribute text (e.g. "nounwind"/"noinline"), None=none.
#
# NOTE: must carry @t.NoVTable (same reason as Line).
# ============================================================
novirtual class Function(GSListNode[Function]):
Name: str # function name
RetTy: rawptr[LLVMType] # return type
Params: rawptr[GSList[Param]] # parameter list container
Blocks: rawptr[GSList[BasicBlock]] # basic-block list container
IsDeclared: int # 1=declaration only (declare), 0=definition (define)
IsVarArg: int # 1=variadic function (...)
Attrs: str # function attribute text (None=none)
AttrGroup: str # string attribute group content (e.g. '"section"=".text.startup"', None=none)
AttrGroupId: int # attribute group id (>0 emits #N on the function line and attributes #N at module end)
NameLen: sizet # function name length (computed once at creation, always == strlen(Name); appended last so existing field offsets stay)
DbgRef: str # DISubprogram ref (kept as-is; ignored by MLIR printing, e.g. "!3")
# ============================================================
# Factory functions
# ============================================================
# Line hardcoded size: Next(8) + Buf(8) + Len(8) = 24 bytes
define LINE_SIZE = 24
def new_line(pool: memhub.MemBuddy*, text: str) -> rawptr[Line]:
"""Create an instruction line, copying text into a newly allocated buffer"""
ptr: rawptr[Line] = pool.alloc(LINE_SIZE)
if ptr is None: return None
string.memset(ptr, 0, LINE_SIZE)
if text is not None:
slen: sizet = string.strlen(text)
buf: str = pool.alloc(slen + 1)
if buf is not None:
string.strcpy(buf, text)
ptr.Buf = buf
ptr.Len = slen
return ptr
def _new_gslist(pool: memhub.MemBuddy*, list_size: sizet) -> t.CPtr:
"""Allocate and zero-init a GSList container from pool (Head/Tail/Count all zero)"""
gsl: t.CPtr = pool.alloc(list_size)
if gsl is None: return None
string.memset(gsl, 0, list_size)
return gsl
# BasicBlock hardcoded size: Next(8) + Name(8) + Lines(8) + IsTerminated(4) = 32 bytes
define BLOCK_SIZE = 32
def new_basic_block(pool: memhub.MemBuddy*, name: str) -> rawptr[BasicBlock]:
"""Create a basic block"""
ptr: rawptr[BasicBlock] = pool.alloc(BLOCK_SIZE)
if ptr is None: return None
string.memset(ptr, 0, BLOCK_SIZE)
ptr.Name = name
ptr.Lines = _new_gslist(pool, 24) # GSList: Head+Tail+Count
ptr.IsTerminated = 0
return ptr
# Param hardcoded size: Next(8) + Ty(8) + Name(8) + Attrs(8) = 32 bytes
define PARAM_SIZE = 32
def new_param(pool: memhub.MemBuddy*, ty: rawptr[LLVMType],
name: str) -> rawptr[Param]:
"""Create a function parameter"""
ptr: rawptr[Param] = pool.alloc(PARAM_SIZE)
if ptr is None: return None
string.memset(ptr, 0, PARAM_SIZE)
ptr.Ty = ty
ptr.Name = name
ptr.Attrs = None
return ptr
# Function hardcoded size: Next(8) + Name(8) + RetTy(8) + Params(8) + Blocks(8) + IsDeclared(4) + IsVarArg(4) + Attrs(8) + AttrGroup(8) + AttrGroupId(8) + NameLen(8) + DbgRef(8) = 88 bytes (same layout as llvmlite)
define FUNC_SIZE = 88
def new_function(pool: memhub.MemBuddy*, name: str,
ret_ty: rawptr[LLVMType]) -> rawptr[Function]:
"""Create a function"""
ptr: rawptr[Function] = pool.alloc(FUNC_SIZE)
if ptr is None: return None
string.memset(ptr, 0, FUNC_SIZE)
ptr.Name = name
ptr.RetTy = ret_ty
ptr.Params = _new_gslist(pool, 24) # GSList: Head+Tail+Count
ptr.Blocks = _new_gslist(pool, 24)
ptr.IsDeclared = 0
ptr.Attrs = None
ptr.AttrGroup = None
ptr.AttrGroupId = 0
# NameLen computed once: Name never changes, so NameLen == strlen(Name) (avoids repeated strlen).
# name=None sets 0, matching new_line's null convention (explicit for fixed semantics, not allocator behavior).
if name is not None:
ptr.NameLen = string.strlen(name)
else:
ptr.NameLen = 0
return ptr
# ============================================================
# List operations (delegating to GSList.append)
# ============================================================
def function_add_param(func: rawptr[Function], param: rawptr[Param]):
"""Append a parameter to the function's parameter list"""
if func is None or param is None: return
# NOTE: use a local variable + type parameter (see function_move_block_to_end): chaining
# func.Params.append() mis-resolves to LinkedNode.append and corrupts memory 48 bytes before the container.
params: rawptr[GSList[Param]] = func.Params
if params is None: return
params.append(param)
def function_add_block(func: rawptr[Function], block: rawptr[BasicBlock]):
"""Append a basic block to the function's block list"""
if func is None or block is None: return
blocks: rawptr[GSList[BasicBlock]] = func.Blocks
if blocks is None: return
blocks.append(block)
def function_move_block_to_end(func: rawptr[Function], block: rawptr[BasicBlock]):
"""Move block to the end of the function's block list (BB text order == control-flow order).
Fixes non-monotonic SSA numbering (BBs are created in order but instructions are emitted in
control-flow order) that makes llc error; called at position_at_end so BB order matches position order.
NOTE: must use the typed GSList[BasicBlock] parameter, else TransPyC builds an incomplete struct
type and blocks.Head/Tail/Count field reads return garbage.
"""
if func is None or block is None: return
blocks: rawptr[GSList[BasicBlock]] = func.Blocks
if blocks is None: return
# Already Tail; nothing to move
if blocks.Tail is block:
return
# Remove block from the list
if blocks.Head is block:
# block is Head; advance Head directly
blocks.Head = block.Next
else:
# Walk to find the predecessor
prev: rawptr[BasicBlock] = blocks.Head
while prev is not None and prev.Next is not block:
prev = prev.Next
if prev is None:
# block not in list; append directly
blocks.append(block)
return
prev.Next = block.Next
# block.Next handled by the removal logic; reset to None
block.Next = None
# Append at the end (no blocks.append call to avoid double-incrementing Count)
if blocks.Tail is not None:
blocks.Tail.Next = block
blocks.Tail = block
def block_append_line(block: rawptr[BasicBlock], line: rawptr[Line]):
"""Append an instruction line to the basic block"""
if block is None or line is None: return
lines: rawptr[GSList[Line]] = block.Lines
if lines is None: return
lines.append(line)
def block_set_terminated(block: rawptr[BasicBlock], val: int):
"""Set the block terminator flag (cross-module call to avoid opaque chained field access)"""
if block is None: return
block.IsTerminated = val
def block_get_terminated(block: rawptr[BasicBlock]) -> int:
"""Get the block terminator flag (cross-module call to avoid opaque chained field access)"""
if block is None:
return 1
return block.IsTerminated
def block_append_text(pool: memhub.MemBuddy*, block: rawptr[BasicBlock],
text: str):
"""Create an instruction line and append it to the basic block"""
if block is None or text is None: return
line: rawptr[Line] = new_line(pool, text)
if line is not None:
block_append_line(block, line)
def block_insert_text_before_terminator(pool: memhub.MemBuddy*,
block: rawptr[BasicBlock],
text: str):
"""Create an instruction line and insert it before the block terminator.
If the block is not terminated, append at the end; if terminated (br/ret), insert before
the terminator so alloca and similar instructions do not land after it.
"""
if block is None or text is None: return
if block.Lines is None: return
line: rawptr[Line] = new_line(pool, text)
if line is None: return
# Block not terminated: append directly
if block.IsTerminated == 0:
block_append_line(block, line)
return
# Block terminated: insert before Tail (the terminator)
lines: rawptr[GSList[Line]] = block.Lines
if lines.Head is None:
block_append_line(block, line)
return
# Single node: insert before Head
if lines.Head is lines.Tail:
line.Next = lines.Head
lines.Head = line
lines.Count += 1
return
# Find the predecessor of Tail
prev: rawptr[Line] = lines.Head
while prev is not None and prev.Next is not lines.Tail:
prev = prev.Next
if prev is None:
# No predecessor found; fall back to append
block_append_line(block, line)
return
# Insert line between prev and Tail
line.Next = lines.Tail
prev.Next = line
lines.Count += 1
# ============================================================
# Attribute setters
# ============================================================
def function_set_attrs(func: rawptr[Function], attrs: str):
"""Set function-level attribute text (e.g. "nounwind"/"noinline")"""
if func is None: return
func.Attrs = attrs
_g_attr_group_counter: int = 0
def function_set_attr_group(func: rawptr[Function], group: str):
"""Set function string attribute group (e.g. '"section"=".text.startup"') and assign #N.
LLVM string attributes (with a value, e.g. section) cannot be inlined on the signature
line; they must use a group: the function line emits #N and the group is emitted at the end.
"""
global _g_attr_group_counter
if func is None or group is None:
return
func.AttrGroup = group
_g_attr_group_counter += 1
func.AttrGroupId = _g_attr_group_counter
def param_set_attrs(param: rawptr[Param], attrs: str):
"""Set parameter attribute text (e.g. "noundef"/"nocapture"/"readonly")"""
if param is None: return
param.Attrs = attrs
def function_set_dbg(func: rawptr[Function], ref: str):
"""Set the function-level debug ref (DISubprogram node ref, e.g. "!3").
Kept as-is: MLIR printing ignores it (FunctionPrint emits no !dbg), so this only
records the model without changing output. ref=None clears it.
"""
if func is None: return
func.DbgRef = ref
# ============================================================
# _ll_name_needs_quote - whether a symbol name needs @"..." quoting in MLIR
#
# WARNING: the rule differs from LLVM (measured on mlir-opt 22.1.8):
# - A bare identifier's first char must be a letter or '_': leading digit, '.', '$' must be quoted.
# => TPV's {sha1}.{name} all start with a hex digit, so all need quotes;
# => the string-constant global @.str starts with '.', so it needs quotes too.
# - Later chars allow only [A-Za-z0-9_$] and '.'.
# - '-' is illegal in a bare MLIR name (LLVM allows it); measured error "expected '('".
# - '['/']' (generic specialization names) are likewise illegal.
# This predicate takes the conservative direction: when unsure, quote (@"..." is legal for any name).
# ============================================================
def _ll_name_needs_quote(name: str) -> int:
"""MLIR bare-name predicate: returns 1 (needs quotes) unless it matches [A-Za-z_][A-Za-z0-9_$.]*"""
if name is None:
return 0
name_len: sizet = string.strlen(name)
if name_len == 0:
return 1
c0: char = name[0]
if not ((c0 >= 'a' and c0 <= 'z') or (c0 >= 'A' and c0 <= 'Z') or c0 == '_'):
return 1
i: sizet = 1
while i < name_len:
ch: char = name[i]
if not ((ch >= 'a' and ch <= 'z') or (ch >= 'A' and ch <= 'Z') or
(ch >= '0' and ch <= '9') or ch == '_' or ch == '$' or ch == '.'):
return 1
i += 1
return 0
# ============================================================
# _MlirSymRefAppend - emit a symbol reference (unified quoting rule)
#
# name does NOT include a leading '@'; an already-quoted form (first char '"') is emitted as-is.
# Used where a symbol appears as an operand (e.g. llvm.mlir.addressof), sharing the same
# predicate with _MlirFuncSig / call target / global name.
# ============================================================
def _MlirSymRefAppend(out: str, outsz: sizet, name: str):
if name is None or name[0] == '\0':
MlirFail("MLIR 渲染: 空符号名")
if name[0] == '"':
_append_cstr(out, outsz, "@")
_append_cstr(out, outsz, name)
return
if _ll_name_needs_quote(name) != 0:
_append_cstr(out, outsz, "@\"")
_append_cstr(out, outsz, name)
_append_cstr(out, outsz, "\"")
return
_append_cstr(out, outsz, "@")
_append_cstr(out, outsz, name)
# ============================================================
# RenumberSSA: renumber numeric SSA names within a function
#
# Fixes the non-monotonic SSA names in text output caused by build_alloca_at_entry
# inserting allocas before the entry block terminator (llc error:
# "instruction expected to be numbered '%N' or greater").
#
# Example: entry originally %0-%6 + br; alloca_at_entry inserts %28/%41 before br,
# so text order is entry(%0..%6, %28, %41) -> if.then(%7..%27),
# placing %28 before %7, which llc rejects.
#
# Algorithm:
# 1. Scan all BB instruction lines, collect numeric SSA names %N in text-output order
# 2. Build an old->new map by first-occurrence order (incrementing from 0)
# 3. Replace %N in every instruction line text with the new number
#
# NOTE:
# - Only pure numeric SSA names (%0, %1, ...) are renumbered
# - Named SSA names (%entry, %.tmp, %self), block labels and global symbols @N are not
# - Only called for define (with a body); declare has no body to process
# - TransPyV params are mostly named (%self/%env/%n), so no numeric param renumbering needed
# ============================================================
# Whether ch is a decimal digit
def _is_digit_ch(ch: char) -> int:
"""Return 1 if ch is '0'-'9'.
ch is a value type (char = i8); TPC has fixed is-None handling for value types
(always false, generating an i1 0), so no null deref occurs.
"""
if ch is None:
return 0
if ch >= '0' and ch <= '9':
return 1
return 0
# ============================================================
# Safe i32 array access (explicit byte offset)
#
# TransPyC's ptr[i] pointer indexing may be treated as byte indexing (i8*) instead of i32*
# indexing, corrupting memory. Must compute the byte offset explicitly: addr = base + idx * 4,
# then cast to i32*. See HandlesTranslator.py:358's t.CUInt64T(ptr) + offset * sizeof usage.
# ============================================================
def _map_get_i32(map_arr: str, idx: int) -> int:
"""Read i32 array map_arr[idx] (explicit byte offset idx*4).
Casts the byte offset to i32* via (rawptr[int])(uint64), then dereferences with c.Deref.
TPC has fixed type-union casts (_HandleTypeUnionCastLlvm fallback), so this form works.
"""
if map_arr is None:
return -1
byte_off: uint64 = uint64(map_arr) + idx * 4
elem_ptr: rawptr[int] = (rawptr[int])(byte_off)
return *elem_ptr
def _map_set_i32(map_arr: str, idx: int, val: int):
"""Write map_arr[idx] = val (i32 array, explicit byte offset idx*4).
Casts via (rawptr[int])(uint64) and assigns through c.DerefAs.
TPC has fixed type-union casts, so this form works.
"""
if map_arr is None:
return
byte_off: uint64 = uint64(map_arr) + idx * 4
elem_ptr: rawptr[int] = (rawptr[int])(byte_off)
c.DerefAs(elem_ptr, val)
def _scan_max_ssa_in_text(text: str, cur_max: int) -> int:
"""Scan text for the largest numeric %N and return max(cur_max, found).
Only matches '%' immediately followed by a digit; skips named SSA names (%entry etc.).
"""
result: int = cur_max
p: str = text
while p is not None and p[0] != '\0':
if p[0] == '%':
nxt: str = p + 1
if nxt is not None and _is_digit_ch(nxt[0]) != 0:
num: int = string.atoi(nxt)
if num > result:
result = num
p = p + 1
return result
def _find_max_ssa_number(func: rawptr[Function]) -> int:
"""Walk all BB instruction lines for the largest %N; returns -1 if no numeric SSA names"""
max_n: int = -1
if func is None or func.Blocks is None:
return max_n
blocks: rawptr[GSList[BasicBlock]] = func.Blocks
blk: rawptr[BasicBlock] = blocks.Head
blk_steps: sizet = 0
while blk is not None:
blk_steps += 1
if blk_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: func blocks(函数名=%s,ssa 扫描终止)\n", func.Name)
stdlib.exit(1)
if blk.Lines is not None:
lines: rawptr[GSList[Line]] = blk.Lines
line: rawptr[Line] = lines.Head
line_steps: sizet = 0
while line is not None:
line_steps += 1
if line_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: block lines(函数名=%s,块=%s,ssa 扫描终止)\n",
func.Name, blk.Name)
stdlib.exit(1)
if line.Buf is not None:
max_n = _scan_max_ssa_in_text(line.Buf, max_n)
line = line.Next
blk = blk.Next
return max_n
def _assign_new_numbers(text: str,
map_arr: str,
max_n: int, next_new: int) -> int:
"""Scan text; for each numeric %N, assign next_new if map_arr[N]==-1.
Returns the updated next_new. Mapping is built in first-occurrence order.
"""
result: int = next_new
p: str = text
while p is not None and p[0] != '\0':
if p[0] == '%':
nxt: str = p + 1
if nxt is not None and _is_digit_ch(nxt[0]) != 0:
num: int = string.atoi(nxt)
if num >= 0 and num <= max_n:
if _map_get_i32(map_arr, num) == -1:
_map_set_i32(map_arr, num, result)
result += 1
p = p + 1
return result
def _replace_ssa_numbers(text: str,
pool: memhub.MemBuddy*,
map_arr: str,
max_n: int) -> str:
"""Build new text with each %N replaced by its mapped new number.
Char-by-char scan: a numeric %N is looked up and substituted, others copied as-is.
Returns a newly allocated buffer (caller need not free the old Buf; pool manages it).
"""
src_len: sizet = string.strlen(text)
# New buffer: worst case each number grows in digits; allocate src_len*2 + 64
new_size: sizet = src_len * 2 + 64
new_buf: str = pool.alloc(new_size)
if new_buf is None:
return None
# Allocate the digit buffer once (reused in the loop)
num_buf: str = pool.alloc(16)
if num_buf is None:
new_buf[0] = '\0'
return new_buf
dst_idx: sizet = 0
src_p: str = text
while src_p is not None and src_p[0] != '\0':
if dst_idx + 16 >= new_size:
# Buffer too small; truncate
break
if src_p[0] == '%':
nxt: str = src_p + 1
if nxt is not None and _is_digit_ch(nxt[0]) != 0:
num: int = string.atoi(nxt)
# Range-check before reading map_arr: when num > max_n the array holds only
# (max_n+1)*4 bytes, so a read-before-check would read out of bounds.
if num >= 0 and num <= max_n:
new_num: int = _map_get_i32(map_arr, num)
if new_num != -1:
# Write '%'
new_buf[dst_idx] = '%'
dst_idx += 1
# Write the new number
num_buf[0] = '\0'
viperlib.snprintf(num_buf, 16, "%d", new_num)
k: sizet = 0
while num_buf[k] != '\0' and dst_idx + 1 < new_size:
new_buf[dst_idx] = num_buf[k]
dst_idx += 1
k += 1
# Skip the old digit chars
src_p = src_p + 1
while src_p is not None and _is_digit_ch(src_p[0]) != 0:
src_p = src_p + 1
continue
# Ordinary char: copy directly
new_buf[dst_idx] = src_p[0]
dst_idx += 1
src_p = src_p + 1
new_buf[dst_idx] = '\0'
return new_buf
def RenumberSSA(func: rawptr[Function], pool: memhub.MemBuddy*):
"""Renumber all numeric SSA names in the function so text-output order is monotonic.
Called before FunctionPrint serializes the body; only applies to define (IsDeclared==0)
functions with non-empty Blocks.
"""
if func is None or pool is None:
return
if func.IsDeclared == 1:
return # declare has no body
if func.Blocks is None:
return
# 1. First pass: find the largest SSA number
max_n: int = _find_max_ssa_number(func)
if max_n < 0:
return # no numeric SSA names
# 2. Allocate and init map_arr[0..max_n] = -1; stored as i8* (pool.alloc), accessed via
# _map_get_i32/_map_set_i32; memset to 0xFF (-1 per 4-byte i32) to avoid ptr[i] indexing issues.
map_bytes: sizet = (max_n + 1) * 4 # int = 4 bytes
map_arr: str = pool.alloc(map_bytes)
if map_arr is None:
return
string.memset(map_arr, 0xFF, map_bytes) # 0xFF * 4 = 0xFFFFFFFF = -1
# 3. Second pass: assign new numbers in text order
next_new: int = 0
blocks: rawptr[GSList[BasicBlock]] = func.Blocks
blk: rawptr[BasicBlock] = blocks.Head
blk_steps: sizet = 0
while blk is not None:
blk_steps += 1
if blk_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: func blocks(函数名=%s,ssa 重编号终止)\n", func.Name)
stdlib.exit(1)
if blk.Lines is not None:
lines: rawptr[GSList[Line]] = blk.Lines
line: rawptr[Line] = lines.Head
line_steps: sizet = 0
while line is not None:
line_steps += 1
if line_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: block lines(函数名=%s,块=%s,ssa 重编号终止)\n",
func.Name, blk.Name)
stdlib.exit(1)
if line.Buf is not None:
next_new = _assign_new_numbers(line.Buf, map_arr, max_n, next_new)
line = line.Next
blk = blk.Next
# 4. Check whether any renumbering is needed (old != new)
need_renumber: int = 0
i: int = 0
while i <= max_n:
v: int = _map_get_i32(map_arr, i)
if v != -1 and v != i:
need_renumber = 1
break
i += 1
if need_renumber == 0:
return # already monotonic; no replacement needed
# 5. Third pass: replace every instruction line text
blk = blocks.Head
blk_steps = 0
while blk is not None:
blk_steps += 1
if blk_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: func blocks(函数名=%s,ssa 替换终止)\n", func.Name)
stdlib.exit(1)
if blk.Lines is not None:
lines: rawptr[GSList[Line]] = blk.Lines
line: rawptr[Line] = lines.Head
line_steps = 0
while line is not None:
line_steps += 1
if line_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: block lines(函数名=%s,块=%s,ssa 替换终止)\n",
func.Name, blk.Name)
stdlib.exit(1)
if line.Buf is not None:
new_buf: str = _replace_ssa_numbers(
line.Buf, pool, map_arr, max_n)
if new_buf is not None:
line.Buf = new_buf
line.Len = string.strlen(new_buf)
line = line.Next
blk = blk.Next
# ============================================================
# Local peephole optimization (text layer, before output, semantics-preserving)
# T1: ret <iN> zeroinitializer -> ret <iN> 0; ret <ptr*> zeroinitializer -> null
# T2: when an entry alloca slot has exactly one store with a side-effect-free value
# (param/%SSA/int/float literal), forward store->load to that value and drop
# alloca/store/load ("virtual register optimization").
# Safety: only when the alloca and its single store are both in entry, loads anywhere,
# and no other address-taking/aliasing use.
# ============================================================
def _peep_is_startwith(s: str, pfx: str) -> int:
if s is None or pfx is None:
return 0
pl: sizet = string.strlen(pfx)
if pl == 0:
return 1
if string.strlen(s) < pl:
return 0
return 1 if string.strncmp(s, pfx, pl) == 0 else 0
def _peep_replace_token(pool: memhub.MemBuddy*,
line_buf: str, tok: str, repl: str,
tlen: sizet, rlen: sizet) -> str:
"""Replace the standalone token %<tok> in line_buf with repl; returns a new buffer (original if unchanged).
tlen/rlen = strlen(tok)/strlen(repl): computed by the caller outside the line loop
(the same ltok/safe_val applies to all lines here) to avoid recomputing lengths per line.
"""
if line_buf is None or tok is None or repl is None:
return line_buf
if tlen == 0:
return line_buf
# Pre-scan for a hit: no strlen, single NUL-bounded pass (most calls return with no hit,
# avoiding per-line strlen). Per-input equivalent: tok has no NUL, so strncmp equality
# implies the token fits; dropping the old i+tlen<=ln bound does not change hit detection.
hit: int = 0
i: sizet = 0
while line_buf[i] != '\0':
if line_buf[i] == '%' and line_buf[i + 1] == tok[0] \
and string.strncmp(line_buf + i + 1, tok, tlen) == 0:
nxt: int = 0
if line_buf[i + 1 + tlen] != '\0':
cc: int = line_buf[i + 1 + tlen]
if (cc >= 48 and cc <= 57) or (cc >= 65 and cc <= 90) or (cc >= 97 and cc <= 122) or cc == 95:
nxt = 1
if nxt == 0:
hit = 1
break
i += 1
if hit == 0:
return line_buf
ln: sizet = string.strlen(line_buf)
# Buffer size bound: at most floor(ln/2) replacements per line, each removing >=2 bytes
# (%+name) and inserting rlen; worst output <= ln + (ln/2)*rlen + NUL. ln + 1 + rlen*(ln+1)
# always covers it, preventing overflow (random memory corruption) when rlen is larger.
out: str = pool.alloc(ln + 1 + rlen * (ln + 1))
if out is None:
return line_buf
op: sizet = 0
oq: sizet = 0
i = 0
while i < ln:
if line_buf[i] == '%' and i + 1 + tlen <= ln \
and line_buf[i + 1] == tok[0] \
and string.strncmp(line_buf + i + 1, tok, tlen) == 0:
nxt2: int = 0
if i + 1 + tlen < ln:
cc2: int = line_buf[i + 1 + tlen]
if (cc2 >= 48 and cc2 <= 57) or (cc2 >= 65 and cc2 <= 90) or (cc2 >= 97 and cc2 <= 122) or cc2 == 95:
nxt2 = 1
if nxt2 == 0:
for j in range(rlen):
out[oq] = repl[j]
oq += 1
i += 1 + tlen
continue
out[oq] = line_buf[i]
oq += 1
i += 1
out[oq] = '\0'
return out
def _peep_last_ref_eq(sb: str, a_name: str, nl: sizet) -> int:
"""Whether the line's last '%name' token equals a_name (store/load pointer operand is at line end).
nl = strlen(a_name): computed by the caller outside the alloca-slot scan (a_name is
constant within one slot scan) to avoid repeated strlen per line.
"""
if sb is None or a_name is None:
return 0
# Single pass to locate the last '%' (avoids repeated strchr calls, one per '%').
r: str = None
probe: str = sb
while probe[0] != '\0':
if probe[0] == '%':
r = probe
probe += 1
if r is None:
return 0
# Compare the first char first (cheap reject); only strncmp when it matches
if r[1] == a_name[0] and string.strncmp(r + 1, a_name, nl) == 0:
nxtc: int = r[1 + nl]
if nxtc == 0 or nxtc == 32 or nxtc == 44 or nxtc == 41:
return 1
return 0
def _peep_alloc_name(eb: str, pool: memhub.MemBuddy*) -> str:
"""Extract name (without %) from '%name = alloca ...'; returns None on failure"""
if eb is None or eb[0] != '%':
return None
eqpos: str = string.strstr(eb, " = alloca ")
if eqpos is None:
return None
nlen: sizet = sizet(uint64(eqpos) - uint64(eb)) - 1
if nlen == 0:
return None
nm: str = pool.alloc(nlen + 1)
if nm is None:
return None
string.strncpy(nm, eb + 1, nlen)
nm[nlen] = '\0'
return nm
def _peep_is_alloc_of(eb: str, a_name: str, nl: sizet) -> int:
"""Whether eb is a_name's alloca definition line; equivalent to strcmp(_peep_alloc_name(eb), a_name)==0.
Compares the name first (strncmp, reject on mismatch), then locates " = alloca " only when
bytes match, reducing strstr count from O(slots*lines) to only hit lines per slot.
nl = strlen(a_name); nlen==0 (empty name) returned None before, so it is rejected here too.
Equivalence: _peep_alloc_name takes the bytes before the first " = alloca " and after '%',
so equal-length equal name <=> strncmp match and the substring starts at eb+1+nl.
"""
if eb is None or a_name is None or nl == 0 or eb[0] != '%':
return 0
# First-char prefilter: nl>=1, so strncmp equality implies equal first chars; a mismatch
# cheaply rejects, keeping most lines out of strncmp. Per-input equivalent: this branch
# rejects iff the old strncmp would too. eb[0]=='%' is non-NUL, so eb[1] is in-bounds.
if eb[1] != a_name[0]:
return 0
if string.strncmp(eb + 1, a_name, nl) != 0:
return 0
eqpos: str = string.strstr(eb, " = alloca ")
if eqpos is not None and uint64(eqpos) == uint64(eb) + 1 + nl:
return 1
return 0
def _peephole_opt(func: rawptr[Function], pool: memhub.MemBuddy*):
"""Text-layer local peephole: T1 ret zeroinitializer; T2 entry-slot store->load forwarding"""
if func is None or func.Blocks is None or pool is None:
return
# NOTE: TPC silently drops assignments for a two-level "var = ptr.field.member" auto-deref
# chain (the IR reads an uninitialized stack var -> random stack errors). Split into one level:
# fetch the field into a local rawptr, then null-check before .Head (same pattern as FunctionPrint).
func_blocks: rawptr[GSList[BasicBlock]] = func.Blocks
blk: rawptr[BasicBlock] = None
if func_blocks is not None:
blk = func_blocks.Head
# ---------- T1: ret <ty> zeroinitializer ----------
blk_t1 = blk
while blk_t1 is not None:
line1: rawptr[Line] = None
if blk_t1.Lines is not None:
t1_lines: rawptr[GSList[Line]] = blk_t1.Lines
line1 = t1_lines.Head
while line1 is not None:
if line1.Buf is not None:
zi: str = string.strstr(line1.Buf, " zeroinitializer")
if zi is not None and _peep_is_startwith(line1.Buf, "ret ") != 0:
ty_len: sizet = sizet(uint64(zi) - uint64(line1.Buf)) - 4
if ty_len > 0:
ty_buf: str = pool.alloc(ty_len + 1)
if ty_buf is not None:
string.strncpy(ty_buf, line1.Buf + 4, ty_len)
ty_buf[ty_len] = '\0'
lit: str = None
if ty_buf[0] == 'i':
lit = "0"
elif string.strchr(ty_buf, 42) is not None: # '*'
lit = "null"
if lit is not None:
nb: str = pool.alloc(ty_len + 16)
if nb is not None:
nb[0] = '\0'
string.strcat(nb, "ret ")
string.strcat(nb, ty_buf)
string.strcat(nb, " ")
string.strcat(nb, lit)
line1.Buf = nb
line1 = line1.Next
blk_t1 = blk_t1.Next
# ---------- T2: entry alloca slot forwarding ----------
entry_blk: rawptr[BasicBlock] = blk
if entry_blk is None:
return
entry_lines: rawptr[GSList[Line]] = entry_blk.Lines
if entry_lines is None:
return
e_line: rawptr[Line] = entry_lines.Head
while e_line is not None:
eb: str = e_line.Buf
if eb is not None:
a_name: str = _peep_alloc_name(eb, pool)
if a_name is not None:
# a_name is constant across the slot's full line scan; strlen computed once (not per line)
a_name_len: sizet = string.strlen(a_name)
st_val: str = None
st_ok: int = 0
st_count: int = 0
st_line_order: int = -1
load_ltoks: str = pool.alloc(8 * 64)
load_cnt: int = 0
load_entry_order: int64 = 0
alloca_order: int = -1
order: int = 0
unsafe: int = 0
# First pass: count this alloca's store/load/other refs and line order
blk_s = blk
while blk_s is not None:
l_s: rawptr[Line] = None
if blk_s.Lines is not None:
scan_lines: rawptr[GSList[Line]] = blk_s.Lines
l_s = scan_lines.Head
while l_s is not None:
sb: str = l_s.Buf
if sb is not None:
is_alloc_def: int = 0
if sb[0] == '%':
if _peep_is_alloc_of(sb, a_name, a_name_len) != 0:
is_alloc_def = 1
if is_alloc_def == 0 and _peep_last_ref_eq(sb, a_name, a_name_len) != 0:
if _peep_is_startwith(sb, "store ") != 0:
st_count += 1
if st_count == 1:
vcomma: str = string.strstr(sb + 6, ", ")
if vcomma is not None:
vlen: sizet = sizet(uint64(vcomma) - uint64(sb + 6))
if vlen > 0:
vbuf: str = pool.alloc(vlen + 1)
if vbuf is not None:
string.strncpy(vbuf, sb + 6, vlen)
vbuf[vlen] = '\0'
st_val = vbuf
st_ok = 1
else:
unsafe = 1
if blk_s == entry_blk:
st_line_order = order
elif string.strstr(sb, " = load ") is not None:
ls: str = string.strstr(sb, "%")
if ls is not None and load_cnt < 64:
lname_len: sizet = sizet(
uint64(string.strstr(sb, " = load ")) - uint64(ls))
lname: str = pool.alloc(lname_len + 1)
if lname is not None:
string.strncpy(lname, ls + 1, lname_len - 1)
lname[lname_len - 1] = '\0'
slotp: str* = (str*)(uint64(load_ltoks) + load_cnt * 8)
slotp[0] = lname
load_cnt += 1
if blk_s == entry_blk and st_line_order < 0:
load_entry_order += 1
else:
# lname alloc failed: this load won't enter load_ltoks, but the
# delete phase still removes its def line by slot name, leaving a
# dangling use. Conservatively abandon T2 forwarding for this slot.
unsafe = 1
else:
# Loads exceed record capacity (load_ltoks=8*64, cap 64) or the line has
# no '%' to parse a def name: unrecorded loads would still have their
# " = load " def lines deleted by slot name, leaving dangling uses
# (llc: "use of undefined value", e.g. strcmp short-circuit >64 branch).
# Only a fully recorded load set allows deleting all defs, so
# conservatively abandon the whole T2 forwarding for this slot (IR stays valid).
unsafe = 1
else:
unsafe = 1
if sb is not None:
order += 1
l_s = l_s.Next
blk_s = blk_s.Next
# Criteria
safe_val: str = None
if unsafe == 0 and st_count == 1 and st_ok != 0 and st_line_order >= 0 \
and load_cnt > 0 and load_entry_order == 0 and st_val is not None:
pct: str = string.strchr(st_val, '%')
if pct is None:
sp1: str = string.strchr(st_val, ' ')
if sp1 is not None and sp1[1] != 0:
safe_val = sp1 + 1
else:
safe_val = st_val
else:
if string.strchr(pct, ' ') is None and string.strchr(pct, ',') is None:
safe_val = pct
if safe_val is not None:
# Second pass: replace load-name references
# safe_val is constant during this slot's replacement; strlen computed once per slot
safe_len: sizet = string.strlen(safe_val)
for li2 in range(load_cnt):
slotsp: str* = (str*)(uint64(load_ltoks) + li2 * 8)
ltok: str = slotsp[0]
if ltok is not None:
# ltok is constant across the load name's function-wide scan; strlen computed once
ltok_len: sizet = string.strlen(ltok)
blk_r = blk
while blk_r is not None:
lr: rawptr[Line] = None
if blk_r.Lines is not None:
repl_lines: rawptr[GSList[Line]] = blk_r.Lines
lr = repl_lines.Head
while lr is not None:
if lr.Buf is not None:
nb2: str = _peep_replace_token(pool, lr.Buf, ltok, safe_val,
ltok_len, safe_len)
if nb2 is not None and nb2 is not lr.Buf:
lr.Buf = nb2
lr = lr.Next
blk_r = blk_r.Next
# Delete: alloca def / the single store / all load lines
blk_d = blk
while blk_d is not None:
ld: rawptr[Line] = None
if blk_d.Lines is not None:
kill_lines: rawptr[GSList[Line]] = blk_d.Lines
ld = kill_lines.Head
while ld is not None:
db: str = ld.Buf
if db is not None:
kill: int = 0
if db[0] == '%' and db[1] == a_name[0]:
if _peep_is_alloc_of(db, a_name, a_name_len) != 0:
kill = 1
if kill == 0 and _peep_last_ref_eq(db, a_name, a_name_len) != 0:
if _peep_is_startwith(db, "store ") != 0 \
or string.strstr(db, " = load ") is not None:
kill = 1
if kill != 0:
ld.Buf = None
ld = ld.Next
blk_d = blk_d.Next
e_line = e_line.Next
# ---------- T3: no-op bitcast removal ----------
_peep_noop_bitcast(func, pool)
def _peep_noop_bitcast(func: rawptr[Function], pool: memhub.MemBuddy*):
"""T3: remove no-op bitcast (bitcast <T> %x to <T>, same type on both sides).
The emitter (virtual calls / union width casts) produces same-type identity bitcasts
(e.g. `%6 = bitcast i8* %ok_msg to i8*`), purely redundant. Replace all uses of %x with
the source operand (%SSA/@global/constant token), then delete the line.
Conservative limit: both type and operand must be simple whitespace-free tokens.
"""
if func is None or pool is None or func.Blocks is None:
return
# Single forward pass: on a valid candidate, replace refs function-wide + delete the line,
# resuming from the next line; stop at the first non-matching bitcast. Equivalent deletion
# result to the old rescan-from-block-head approach (replacement only touches operand tokens),
# but removes the O(K*C) quadratic rescan.
blk: rawptr[BasicBlock] = func.Blocks.Head
while blk is not None:
ln: rawptr[Line] = None
if blk.Lines is not None:
ln = blk.Lines.Head
while ln is not None:
nxt: rawptr[Line] = ln.Next
if ln.Buf is not None:
eqpos: str = string.strstr(ln.Buf, " = bitcast ")
if eqpos is not None:
nlen: sizet = sizet(uint64(eqpos) - uint64(ln.Buf)) - 1
nm_buf: str = None
src_buf: str = None
if nlen > 0:
after: str = eqpos + 11 # skip " = bitcast "
sp1: str = string.strchr(after, ' ')
tpos: str = string.strstr(after, " to ")
if sp1 is not None and tpos is not None \
and uint64(sp1) < uint64(tpos):
tyA_len: sizet = sizet(uint64(sp1) - uint64(after))
src_st: str = sp1 + 1
src_len: sizet = sizet(uint64(tpos) - uint64(src_st))
tyB: str = tpos + 4
tyB_len: sizet = string.strlen(tyB)
if src_len > 0 and string.strchr(tyB, ' ') is None \
and uint64(string.strchr(src_st, ' ')) == uint64(tpos) \
and tyA_len == tyB_len \
and string.strncmp(after, tyB, tyA_len) == 0:
nm_buf = pool.alloc(nlen + 1)
if nm_buf is not None:
string.strncpy(nm_buf, ln.Buf + 1, nlen)
nm_buf[nlen] = '\0'
src_buf = pool.alloc(src_len + 1)
if src_buf is not None:
string.strncpy(src_buf, src_st, src_len)
src_buf[src_len] = '\0'
if nm_buf is None or src_buf is None:
# First non-matching bitcast: stop (same semantics as before)
return
# Replace %nm references with the source operand function-wide
# nm_buf/src_buf are constant across the scan; strlen computed once (not per line)
nm_buf_len: sizet = string.strlen(nm_buf)
src_buf_len: sizet = string.strlen(src_buf)
blk_r: rawptr[BasicBlock] = func.Blocks.Head
while blk_r is not None:
if blk_r.Lines is not None:
ln2: rawptr[Line] = blk_r.Lines.Head
while ln2 is not None:
if ln2.Buf is not None:
rb: str = _peep_replace_token(pool, ln2.Buf, nm_buf, src_buf,
nm_buf_len, src_buf_len)
if rb is not None and rb is not ln2.Buf:
ln2.Buf = rb
ln2.Len = string.strlen(rb)
ln2 = ln2.Next
blk_r = blk_r.Next
ln.Buf = None
ln = nxt
blk = blk.Next
# ============================================================
# MLIR (llvm dialect) render stage
#
# Located inside FunctionPrint. Builders stay unchanged and keep writing LLVM-syntax
# instruction text into Line; this stage re-parses that text and handles the three
# MLIR-specific differences:
# 1) constant materialization -- MLIR operands cannot be literals: non-SSA operands
# (int/float/null/zeroinitializer/@symbol) become llvm.mlir.constant / llvm.mlir.zero /
# llvm.mlir.addressof ops at the entry block head, assigned %cN (once per (type,text) per function)
# 2) phi -> block parameters -- leading phis become block params ^blk(%0: i32); phi lines are
# not emitted; br/cond_br into that block get arguments matched by source block
# 3) opaque pointers -- all pointers unify to !llvm.ptr (handled by TypePrint/MlirTypeText)
#
# Two passes: pass1 only registers (const table / bitcast no-op alias table) + shape checks;
# pass2 writes text (const table is complete, so entry-head const ops can be emitted first).
# ============================================================
# Table capacity
define MLIR_CONST_CAP = 512
define MLIR_ALIAS_CAP = 512
# Render pass: 1=scan(register) 2=emit(write text)
_MLIR_Pass: int = 0
_MLIR_Pool: memhub.MemBuddy* = None
_MLIR_Func: rawptr[Function] = None
_MLIR_SrcBlk: rawptr[BasicBlock] = None
# Constant table: (MLIR type text, constant text) -> materialized SSA name
_MLIR_ConstTy: t.CPtr = None
_MLIR_ConstVal: t.CPtr = None
_MLIR_ConstName: t.CPtr = None
_MLIR_ConstCount: int = 0
# bitcast no-op alias table + cmpxchg result binding table: result name -> source operand / extractvalue result name
# (cmpxchg's extractvalue result name is deterministically derived from the result name, not a counter; see _MlirCxResName)
_MLIR_AliasFrom: t.CPtr = None
_MLIR_AliasTo: t.CPtr = None
_MLIR_AliasCount: int = 0
# The intermediate instruction text line currently being rendered (set at _MlirRenderLine entry).
# Supplies line context to type-text conversion failures: MlirFail terminates immediately with no
# stack, so the line text is stashed in __types.vp (via MlirSetContextLine) and printed with the error.
_MLIR_CurLine: str = None
# Parse-time scratch buffers (allocated once per FunctionPrint, reused throughout;
# text that must outlive a line is dup'd on table/alias insertion, see _mlir_dup)
_SC_Op: str = None
_SC_Tb: str = None
_SC_Ty: str = None
_SC_Ty2: str = None
_SC_V1: str = None
_SC_V2: str = None
_SC_V3: str = None
_SC_Callee: str = None
_SC_Args: str = None
_SC_ArgTs: str = None
_SC_IdxV: str = None
_SC_IdxT: str = None
_SC_Mt: str = None
_SC_Tmp: str = None
_SC_Tmp2: str = None
_SC_P1: str = None
_SC_P2: str = None
_SC_B1: str = None
_SC_B2: str = None
_SC_Buf: str = None
def _mlir_slotp(buf: t.CPtr, idx: int) -> str*:
"""Table slot address (buf + idx*8)"""
return (str*)(uint64(buf) + idx * 8)
def _mlir_dup(s: str) -> str:
"""Copy text into the pool (for table entries that must outlive a line)"""
if s is None:
return None
n: sizet = string.strlen(s)
b: str = _MLIR_Pool.alloc(n + 1)
if b is None:
MlirFail("MLIR 渲染: 文本 dup 分配失败")
string.strncpy(b, s, n)
b[n] = '\0'
return b
def _MlirTablesInit(pool: memhub.MemBuddy*):
"""Initialize the two tables and the parse scratch buffers"""
global _MLIR_Pool, _MLIR_Pass, _MLIR_ConstTy, _MLIR_ConstVal, _MLIR_ConstName, _MLIR_ConstCount, _MLIR_AliasFrom, _MLIR_AliasTo, _MLIR_AliasCount, _SC_Op, _SC_Tb, _SC_Ty, _SC_Ty2, _SC_V1, _SC_V2, _SC_V3, _SC_Callee, _SC_Args, _SC_ArgTs, _SC_IdxV, _SC_IdxT, _SC_Mt, _SC_Tmp, _SC_Tmp2, _SC_P1, _SC_P2, _SC_B1, _SC_B2, _SC_Buf
if pool is None:
MlirFail("MLIR 渲染: 内存池为空")
_MLIR_Pool = pool
_MLIR_Pass = 0
_MLIR_ConstCount = 0
_MLIR_AliasCount = 0
_MLIR_ConstTy = pool.alloc(MLIR_CONST_CAP * 8)
_MLIR_ConstVal = pool.alloc(MLIR_CONST_CAP * 8)
_MLIR_ConstName = pool.alloc(MLIR_CONST_CAP * 8)
_MLIR_AliasFrom = pool.alloc(MLIR_ALIAS_CAP * 8)
_MLIR_AliasTo = pool.alloc(MLIR_ALIAS_CAP * 8)
if _MLIR_ConstTy is None or _MLIR_ConstVal is None or _MLIR_ConstName is None \
or _MLIR_AliasFrom is None or _MLIR_AliasTo is None:
MlirFail("MLIR 渲染: 常量/别名表分配失败")
string.memset(_MLIR_ConstTy, 0, MLIR_CONST_CAP * 8)
string.memset(_MLIR_ConstVal, 0, MLIR_CONST_CAP * 8)
string.memset(_MLIR_ConstName, 0, MLIR_CONST_CAP * 8)
string.memset(_MLIR_AliasFrom, 0, MLIR_ALIAS_CAP * 8)
string.memset(_MLIR_AliasTo, 0, MLIR_ALIAS_CAP * 8)
_SC_Op = pool.alloc(64)
_SC_Tb = pool.alloc(512)
_SC_Ty = pool.alloc(4096)
_SC_Ty2 = pool.alloc(4096)
_SC_V1 = pool.alloc(512)
_SC_V2 = pool.alloc(512)
_SC_V3 = pool.alloc(512)
_SC_Callee = pool.alloc(512)
_SC_Args = pool.alloc(2048)
_SC_ArgTs = pool.alloc(8192)
_SC_IdxV = pool.alloc(1024)
_SC_IdxT = pool.alloc(4096)
_SC_Mt = pool.alloc(4096)
_SC_Tmp = pool.alloc(8192)
_SC_Tmp2 = pool.alloc(4096)
_SC_P1 = pool.alloc(512)
_SC_P2 = pool.alloc(512)
_SC_B1 = pool.alloc(4096)
_SC_B2 = pool.alloc(4096)
_SC_Buf = pool.alloc(4096)
if _SC_Op is None or _SC_Ty is None or _SC_Mt is None or _SC_Tmp is None:
MlirFail("MLIR 渲染: 解析 scratch 分配失败")
_SC_Op[0] = '\0'
_SC_Ty[0] = '\0'
_SC_Mt[0] = '\0'
_SC_Tmp[0] = '\0'
# ============================================================
# Constant table / alias table
# ============================================================
def _MlirAliasLookup(name: str) -> str:
"""Look up the alias table; returns None on miss"""
if name is None:
return None
for i in range(_MLIR_AliasCount):
sp: str* = _mlir_slotp(_MLIR_AliasFrom, i)
if sp[0] is not None and string.strcmp(sp[0], name) == 0:
st: str* = _mlir_slotp(_MLIR_AliasTo, i)
return st[0]
return None
def _MlirAliasAdd(from_name: str, to_name: str):
"""Register a no-op bitcast alias (from is replaced by to)"""
global _MLIR_AliasCount
if from_name is None or to_name is None:
return
if _MLIR_AliasCount >= MLIR_ALIAS_CAP:
MlirFail("MLIR 渲染: bitcast 别名表溢出")
sp: str* = _mlir_slotp(_MLIR_AliasFrom, _MLIR_AliasCount)
st: str* = _mlir_slotp(_MLIR_AliasTo, _MLIR_AliasCount)
sp[0] = _mlir_dup(from_name)
st[0] = _mlir_dup(to_name)
_MLIR_AliasCount += 1
def _MlirResolve(name: str) -> str:
"""Resolve the alias chain (bitcast no-op result name -> real operand)"""
if name is None:
return None
cur: str = name
for g in range(64):
nx: str = _MlirAliasLookup(cur)
if nx is None:
return cur
cur = nx
return cur
def _MlirConstGet(tytext: str, valtext: str) -> str:
"""Get (or create) the constant's materialized name %cN"""
global _MLIR_ConstCount
for i in range(_MLIR_ConstCount):
sp: str* = _mlir_slotp(_MLIR_ConstTy, i)
if sp[0] is not None and string.strcmp(sp[0], tytext) == 0:
sv: str* = _mlir_slotp(_MLIR_ConstVal, i)
if sv[0] is not None and string.strcmp(sv[0], valtext) == 0:
sn: str* = _mlir_slotp(_MLIR_ConstName, i)
return sn[0]
if _MLIR_Pass == 2:
# pass1 already scanned all instruction lines; no new constant should appear here
MlirFail("MLIR 渲染: 发射阶段出现未物化常量(扫描遗漏)")
if _MLIR_ConstCount >= MLIR_CONST_CAP:
MlirFail("MLIR 渲染: 常量表溢出")
nm: str = _MLIR_Pool.alloc(32)
if nm is None:
MlirFail("MLIR 渲染: 常量名分配失败")
viperlib.snprintf(nm, 32, "%%c%d", _MLIR_ConstCount)
sp2: str* = _mlir_slotp(_MLIR_ConstTy, _MLIR_ConstCount)
sv2: str* = _mlir_slotp(_MLIR_ConstVal, _MLIR_ConstCount)
sn2: str* = _mlir_slotp(_MLIR_ConstName, _MLIR_ConstCount)
sp2[0] = _mlir_dup(tytext)
sv2[0] = _mlir_dup(valtext)
sn2[0] = nm
_MLIR_ConstCount += 1
return nm
def _MlirCxResName(resname: str, suffix: str) -> str:
"""cmpxchg's extractvalue result name: deterministically derived from the struct result name.
A bare MLIR SSA name's first char must be a letter or `_` (mlir-opt 22.1.8 measured: `%0_v`
errors "expected '=' after SSA name"), so a `_` is prefixed to the result name body:
`%0` -> `%_0_v` (old value [0]) / `%_0_ok` (success bit [1])
`%a` -> `%_a_v` / `%_a_ok`
Names map one-to-one to result names with no counter, so pass1 (alias registration) and
pass2 (emission) produce identical names and references never dangle.
"""
nm: str = _MLIR_Pool.alloc(64)
if nm is None:
MlirFail("MLIR 渲染: cmpxchg 结果名分配失败")
nm[0] = '\0'
_append_cstr(nm, 4096, "%_")
_append_cstr(nm, 4096, resname + 1)
_append_cstr(nm, 4096, suffix)
return nm
def _MlirFloatLiteral(val: str, mt: str) -> str:
"""Float constant text -> MLIR float literal text.
MLIR float literals have two forms (Builtin Dialect FloatAttr syntax:
`float-literal (':' float-type)?` | `hexadecimal-literal ':' float-type`):
- decimal float literal (e.g. `3.14 : f64`);
- hexadecimal bit pattern (e.g. `0x40091EB851EB851F : f64`, interpreted bitwise).
So the `0x...` bit pattern from ConstFloat passes through as-is (no precision loss);
converting it to `%e` (7 significant digits) would truncate and defeat the bit-pattern intent.
The pattern width must match the type (f16/f32/f64/f128 <-> 4/8/16/32 hex digits), else a loud
error, since MLIR interprets the hex value by the type width.
"""
if val[0] == '0' and (val[1] == 'x' or val[1] == 'X'):
want: int = 0
if string.strcmp(mt, "f16") == 0:
want = 4
elif string.strcmp(mt, "f32") == 0:
want = 8
elif string.strcmp(mt, "f64") == 0:
want = 16
elif string.strcmp(mt, "f128") == 0:
want = 32
if want == 0:
MlirFail("MLIR 渲染: 十六进制浮点字面量的类型既非 f16/f32/f64/f128")
nd: int = 0
p: str = val + 2
while (p[0] >= '0' and p[0] <= '9') \
or (p[0] >= 'a' and p[0] <= 'f') \
or (p[0] >= 'A' and p[0] <= 'F'):
nd += 1
p = p + 1
if nd != want:
MlirFail("MLIR 渲染: 浮点位模式位数与类型不符(f16/f32/f64/f128 依次需 4/8/16/32 位十六进制)")
return val
# Already decimal text: use as-is
return val
def _MlirEmitOneConst(out: str, outsz: sizet, mt: str, val: str, nm: str):
"""Emit one constant-materialization op"""
_append_cstr(out, outsz, " ")
_append_cstr(out, outsz, nm)
if val[0] == '@':
# Symbol reference (global/function address): use the unified quoting rule
# (LLVM and MLIR bare-name rules differ: MLIR disallows leading digit/'.' and '-')
_append_cstr(out, outsz, " = llvm.mlir.addressof ")
_MlirSymRefAppend(out, outsz, val + 1)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, mt)
elif string.strcmp(val, "null") == 0 or string.strcmp(val, "zeroinitializer") == 0:
_append_cstr(out, outsz, " = llvm.mlir.zero : ")
_append_cstr(out, outsz, mt)
else:
lit: str = val
if mt[0] == 'f':
lit = _MlirFloatLiteral(val, mt)
elif mt[0] != 'i':
MlirFail("MLIR 渲染: 不支持的常量操作数形态")
_append_cstr(out, outsz, " = llvm.mlir.constant(")
_append_cstr(out, outsz, lit)
# i1's `true`/`false` are MLIR bare boolean keywords, no type suffix allowed:
# `llvm.mlir.constant(false : i1)` makes mlir-opt error "expected ')'".
# Other scalars must be written `value : type` (otherwise treated as an untyped attr, mismatching the result type).
if string.strcmp(lit, "true") != 0 and string.strcmp(lit, "false") != 0:
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, mt)
_append_cstr(out, outsz, ") : ")
_append_cstr(out, outsz, mt)
_append_cstr(out, outsz, "\n")
def _MlirEmitConsts(out: str, outsz: sizet):
"""Emit all constant-materialization ops at the entry block head in registration order"""
for i in range(_MLIR_ConstCount):
st: str* = _mlir_slotp(_MLIR_ConstTy, i)
sv: str* = _mlir_slotp(_MLIR_ConstVal, i)
sn: str* = _mlir_slotp(_MLIR_ConstName, i)
_MlirEmitOneConst(out, outsz, st[0], sv[0], sn[0])
# ============================================================
# Parsing primitives for intermediate instruction text
# ============================================================
def _mlir_ws(p: str) -> str:
"""Skip whitespace (space/tab/newline)"""
if p is None:
return None
while p[0] == ' ' or p[0] == '\t' or p[0] == '\n' or p[0] == '\r':
p = p + 1
return p
def _mlir_delim(ch: int) -> int:
"""Whether ch ends a token"""
if ch == 0 or ch == 32 or ch == 9 or ch == 10 or ch == 13:
return 1
if ch == ',' or ch == '(' or ch == ')' or ch == '[' or ch == ']' \
or ch == ':' or ch == '=':
return 1
return 0
def _mlir_read_value(p: str, out: str, outsz: sizet) -> str:
"""Read a value token (%name / @"name" / %"name" / literal); returns the new cursor"""
p = _mlir_ws(p)
n: sizet = 0
if p[0] == '\0':
out[0] = '\0'
return p
if p[0] == '%' or p[0] == '@':
if n + 1 < outsz:
out[n] = p[0]
n += 1
p = p + 1
if p[0] == '"':
if n + 1 < outsz:
out[n] = '"'
n += 1
p = p + 1
while p[0] != '\0' and p[0] != '"':
if n + 1 < outsz:
out[n] = p[0]
n += 1
p = p + 1
if p[0] == '"':
if n + 1 < outsz:
out[n] = '"'
n += 1
p = p + 1
out[n] = '\0'
return p
while p[0] != '\0' and _mlir_delim(p[0]) == 0:
if n + 1 < outsz:
out[n] = p[0]
n += 1
p = p + 1
out[n] = '\0'
return p
def _mlir_read_type(p: str, out: str, outsz: sizet, nofunc: int = 0) -> str:
"""Read a type token (MLIR form !llvm... or scalar iN/fN, also LLVM aggregate forms).
Bracket balancing covers `[] {} () <>`: MLIR's !llvm.array<...>/!llvm.struct<(...)> contain
spaces and commas and must be read whole by depth counting; bracketless types (!llvm.ptr)
end at a top-level delimiter.
With nofunc=1, does NOT absorb the following ` (...)` function-type suffix: in a call's return
type position, `(...)` (LLVM syntax) is the callee's parameter type list, not part of the return
type, so it must be left to _MlirCall's variadic branch (see the comment there).
"""
p = _mlir_ws(p)
n: sizet = 0
if p[0] == '\0':
out[0] = '\0'
return p
c0: int = p[0]
if c0 == '!' or c0 == '[' or c0 == '{' or c0 == '(':
d: int = 0
while p[0] != '\0':
ch: int = p[0]
if ch == '[' or ch == '{' or ch == '(' or ch == '<':
d += 1
elif ch == ']' or ch == '}' or ch == ')' or ch == '>':
if n + 1 < outsz:
out[n] = ch
n += 1
p = p + 1
d -= 1
if d <= 0:
break
continue
elif d == 0 and (ch == ' ' or ch == ',' or ch == '\t'
or ch == '\n' or ch == '\r'):
# Top-level delimiter: bracketless types (e.g. !llvm.ptr) end here
break
if n + 1 < outsz:
out[n] = ch
n += 1
p = p + 1
while p[0] == '*':
if n + 1 < outsz:
out[n] = '*'
n += 1
p = p + 1
out[n] = '\0'
return p
if c0 == '%':
p = _mlir_read_value(p, out, outsz)
n = string.strlen(out)
while p[0] == '*':
if n + 1 < outsz:
out[n] = '*'
n += 1
p = p + 1
out[n] = '\0'
return p
# Ordinary word (i32 / i8* / void / double ...)
while p[0] != '\0' and _mlir_delim(p[0]) == 0:
if n + 1 < outsz:
out[n] = p[0]
n += 1
p = p + 1
# Function-type suffix " ( ... )" (not absorbed when nofunc=1, see the docstring)
q: str = _mlir_ws(p)
if nofunc == 0 and q[0] == '(':
if n + 1 < outsz:
out[n] = ' '
n += 1
p = q
d2: int = 0
while p[0] != '\0':
ch2: int = p[0]
if ch2 == '(':
d2 += 1
elif ch2 == ')':
if n + 1 < outsz:
out[n] = ch2
n += 1
p = p + 1
d2 -= 1
if d2 <= 0:
break
continue
if n + 1 < outsz:
out[n] = ch2
n += 1
p = p + 1
while p[0] == '*':
if n + 1 < outsz:
out[n] = '*'
n += 1
p = p + 1
out[n] = '\0'
return p
def _mlir_in_set(ob: str, setstr: str) -> int:
"""Whether opcode belongs to a "|a|b|c|"-form set"""
tb: str = _SC_Tb
tb[0] = '\0'
string.strcat(tb, "|")
string.strcat(tb, ob)
string.strcat(tb, "|")
if string.strstr(setstr, tb) is not None:
return 1
return 0
def _MlirBlkRef(t: str) -> str:
"""Block reference text: strip the leading '%' (MLIR blocks use ^name)"""
if t is None:
return ""
if t[0] == '%':
return t + 1
return t
def _MlirFindBlock(func: rawptr[Function], name: str) -> rawptr[BasicBlock]:
"""Find a basic block by name (without %) within the function"""
if func is None or func.Blocks is None or name is None:
return None
blocks: rawptr[GSList[BasicBlock]] = func.Blocks
blk: rawptr[BasicBlock] = blocks.Head
steps: int = 0
while blk is not None:
steps += 1
if steps > IR_DUMP_CHAIN_LIMIT:
MlirFail("MLIR 渲染: 块链疑似成环")
if blk.Name is not None and string.strcmp(blk.Name, name) == 0:
return blk
blk = blk.Next
return None
def _MlirIsPhiLine(text: str) -> int:
"""Whether the line is a phi line"""
if text is None:
return 0
if string.strstr(text, " = phi ") is not None:
return 1
return 0
# ============================================================
# Operand rendering (with constant materialization)
# ============================================================
def _MlirOperand(val: str, tyllvm: str, out: str, outsz: sizet) -> str:
"""Render one operand and return its final text.
SSA value -> %name after alias resolution;
non-SSA (literal/symbol ref) -> materialized %cN (registered during the scan pass).
"""
if val is None or val[0] == '\0':
MlirFail("MLIR 渲染: 操作数为空")
if val[0] == '%':
r: str = _MlirResolve(val)
_append_cstr(out, outsz, r)
return r
mt: str = _SC_Mt
mt[0] = '\0'
MlirTypeText(mt, 4096, tyllvm, _MLIR_Pool)
nm: str = _MlirConstGet(mt, val)
_append_cstr(out, outsz, nm)
return nm
# ============================================================
# phi parsing
# ============================================================
def _MlirPhiScan(text: str):
"""Scan a phi line: register all incoming constants (scan pass only)"""
p: str = _mlir_ws(text)
tb: str = _SC_Buf
p = _mlir_read_value(p, tb, 512)
p = _mlir_ws(p)
if p[0] == '=':
p = _mlir_ws(p + 1)
ob: str = _SC_Op
p = _mlir_read_value(p, ob, 64)
if string.strcmp(ob, "phi") != 0:
return
tyb: str = _SC_Ty
p = _mlir_read_type(p, tyb, 512)
g: int = 0
while p[0] != '\0':
g += 1
if g > 512:
MlirFail("MLIR 渲染: phi 入边过多")
p = _mlir_ws(p)
if p[0] != '[':
break
p = _mlir_ws(p + 1)
vb: str = _SC_P1
p = _mlir_read_value(p, vb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
bb: str = _SC_P2
p = _mlir_read_value(p, bb, 512)
p = _mlir_ws(p)
if p[0] == ']':
p = p + 1
p = _mlir_ws(p)
if p[0] == ',':
p = p + 1
# Register this incoming value (constants/symbol refs need materialization)
_MlirOperand(vb, tyb, None, 0)
def _MlirPhiParam(text: str) -> int:
"""Take a phi's result name and type as block params; on success writes _SC_B1(name)/_SC_B2(type) and returns 1"""
p: str = _mlir_ws(text)
nm: str = _SC_B1
p = _mlir_read_value(p, nm, 512)
p = _mlir_ws(p)
if p[0] != '=':
return 0
p = _mlir_ws(p + 1)
ob: str = _SC_Op
p = _mlir_read_value(p, ob, 64)
if string.strcmp(ob, "phi") != 0:
return 0
tyb: str = _SC_B2
_mlir_read_type(p, tyb, 512)
return 1
def _MlirPhiFindIncoming(text: str, src: rawptr[BasicBlock]) -> int:
"""Find the phi incoming edge matching the source block; on success writes _SC_B1(value)/_SC_B2(type) and returns 1"""
p: str = _mlir_ws(text)
tb: str = _SC_Buf
p = _mlir_read_value(p, tb, 512)
p = _mlir_ws(p)
if p[0] == '=':
p = _mlir_ws(p + 1)
ob: str = _SC_Op
p = _mlir_read_value(p, ob, 64)
if string.strcmp(ob, "phi") != 0:
return 0
tyb: str = _SC_Ty
p = _mlir_read_type(p, tyb, 512)
srcn: str = None
if src is not None:
srcn = src.Name
g: int = 0
while p[0] != '\0':
g += 1
if g > 512:
MlirFail("MLIR 渲染: phi 入边过多")
p = _mlir_ws(p)
if p[0] != '[':
break
p = _mlir_ws(p + 1)
vb: str = _SC_P1
p = _mlir_read_value(p, vb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
bb: str = _SC_P2
p = _mlir_read_value(p, bb, 512)
p = _mlir_ws(p)
if p[0] == ']':
p = p + 1
p = _mlir_ws(p)
if p[0] == ',':
p = p + 1
if srcn is not None:
bn: str = bb
if bn[0] == '%':
bn = bn + 1
if string.strcmp(bn, srcn) == 0:
string.strcpy(_SC_B1, vb)
string.strcpy(_SC_B2, tyb)
return 1
return 0
# ============================================================
# Block parameters / block arguments
# ============================================================
def _MlirBlockHasPhi(blk: rawptr[BasicBlock]) -> int:
"""Whether the block head has a phi (i.e. the block carries block params)"""
if blk is None or blk.Lines is None:
return 0
lines: rawptr[GSList[Line]] = blk.Lines
ln: rawptr[Line] = lines.Head
while ln is not None:
if ln.Buf is not None:
if _MlirIsPhiLine(ln.Buf) != 0:
return 1
return 0
ln = ln.Next
return 0
def _MlirBlockOperands(out: str, outsz: sizet,
target: rawptr[BasicBlock], src: rawptr[BasicBlock]):
"""Emit target's block arguments when branching to it: (%v : ty, ...).
Incoming edges are matched to phis by source block; no match -> loud error (never silently drop args).
"""
if target is None or src is None:
return
if _MlirBlockHasPhi(target) == 0:
return
tmp: str = _SC_Tmp
tmp[0] = '\0'
lines: rawptr[GSList[Line]] = target.Lines
ln: rawptr[Line] = lines.Head
cnt: int = 0
while ln is not None:
if ln.Buf is None:
ln = ln.Next
continue
if _MlirIsPhiLine(ln.Buf) == 0:
break
if _MlirPhiFindIncoming(ln.Buf, src) == 0:
MlirFail("MLIR 渲染: 跳转目标块带 phi,但找不到与源块匹配的入边")
if cnt > 0:
_append_cstr(tmp, 8192, ", ")
_MlirOperand(_SC_B1, _SC_B2, tmp, 4096)
_append_cstr(tmp, 8192, " : ")
mt: str = _SC_Mt
mt[0] = '\0'
MlirTypeText(mt, 4096, _SC_B2, _MLIR_Pool)
_append_cstr(tmp, 8192, mt)
cnt += 1
ln = ln.Next
if cnt == 0:
MlirFail("MLIR 渲染: 目标块带 phi 但未生成块实参")
_append_cstr(out, outsz, "(")
_append_cstr(out, outsz, tmp)
_append_cstr(out, outsz, ")")
def _MlirEmitBlockHeader(out: str, outsz: sizet, blk: rawptr[BasicBlock]):
"""Emit a non-entry block header: ^name(%p: ty, ...):"""
_append_cstr(out, outsz, "^")
if blk.Name is not None:
_append_cstr(out, outsz, blk.Name)
if _MlirBlockHasPhi(blk) != 0:
tmp: str = _SC_Tmp
tmp[0] = '\0'
lines: rawptr[GSList[Line]] = blk.Lines
ln: rawptr[Line] = lines.Head
cnt: int = 0
while ln is not None:
if ln.Buf is None:
ln = ln.Next
continue
if _MlirIsPhiLine(ln.Buf) == 0:
break
if _MlirPhiParam(ln.Buf) != 0:
if cnt > 0:
_append_cstr(tmp, 8192, ", ")
_append_cstr(tmp, 8192, _SC_B1)
_append_cstr(tmp, 8192, ": ")
mt: str = _SC_Mt
mt[0] = '\0'
MlirTypeText(mt, 4096, _SC_B2, _MLIR_Pool)
_append_cstr(tmp, 8192, mt)
cnt += 1
ln = ln.Next
if cnt == 0:
MlirFail("MLIR 渲染: 块首 phi 解析失败")
_append_cstr(out, outsz, "(")
_append_cstr(out, outsz, tmp)
_append_cstr(out, outsz, ")")
_append_cstr(out, outsz, ":\n")
def _MlirEmitBlockLines(out: str, outsz: sizet, blk: rawptr[BasicBlock]):
"""Emit in-block instructions (skip leading phis, which became block params)"""
if blk is None or blk.Lines is None:
return
lines: rawptr[GSList[Line]] = blk.Lines
ln: rawptr[Line] = lines.Head
skipping: int = 1
steps: int = 0
while ln is not None:
steps += 1
if steps > IR_DUMP_CHAIN_LIMIT:
MlirFail("MLIR 渲染: 块指令链疑似成环")
if ln.Buf is not None:
if skipping != 0 and _MlirIsPhiLine(ln.Buf) != 0:
ln = ln.Next
continue
skipping = 0
_MlirRenderLine(ln.Buf, out, outsz)
ln = ln.Next
def _MlirCheckEntryNoPhi(blk: rawptr[BasicBlock]):
"""entry block must not have phis (no predecessor, cannot become block params)"""
if _MlirBlockHasPhi(blk) != 0:
MlirFail("MLIR 渲染: entry 块不允许 phi(MLIR entry 块不接受块参数)")
# ============================================================
# Per-instruction MLIR rendering
# ============================================================
def _MlirEmitRes(out: str, outsz: sizet, resname: str):
"""Emit the " [%N = ]" prefix"""
_append_cstr(out, outsz, " ")
if resname is not None:
_append_cstr(out, outsz, resname)
_append_cstr(out, outsz, " = ")
def _MlirBinop(p: str, resname: str, ob: str, out: str, outsz: sizet):
"""Integer/float binary op: llvm.<op> %a, %b : ty"""
tyb: str = _SC_Ty
p = _mlir_read_type(p, tyb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
lb: str = _SC_V1
p = _mlir_read_value(p, lb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
rb: str = _SC_V2
_mlir_read_value(p, rb, 512)
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.")
_append_cstr(out, outsz, ob)
_append_cstr(out, outsz, " ")
_MlirOperand(lb, tyb, out, outsz)
_append_cstr(out, outsz, ", ")
_MlirOperand(rb, tyb, out, outsz)
mt: str = _SC_Mt
mt[0] = '\0'
MlirTypeText(mt, 4096, tyb, _MLIR_Pool)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, mt)
_append_cstr(out, outsz, "\n")
def _MlirFneg(p: str, resname: str, out: str, outsz: sizet):
"""fneg: llvm.fneg %a : ty"""
tyb: str = _SC_Ty
p = _mlir_read_type(p, tyb, 512)
vb: str = _SC_V1
_mlir_read_value(p, vb, 512)
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.fneg ")
_MlirOperand(vb, tyb, out, outsz)
mt: str = _SC_Mt
mt[0] = '\0'
MlirTypeText(mt, 4096, tyb, _MLIR_Pool)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, mt)
_append_cstr(out, outsz, "\n")
def _MlirCmp(p: str, resname: str, ob: str, out: str, outsz: sizet):
"""icmp/fcmp: llvm.icmp "sgt" %a, %b : ty
The intermediate text is LLVM syntax `icmp <pred> <type> <lhs>, <rhs>` -- predicate before type,
so the read order must be predicate -> type -> operands.
Do not read the type first: the predicate (e.g. `sgt`) would be passed as type text, triggering
"cannot convert to MLIR type text" and aborting.
"""
pb: str = _SC_P1
p = _mlir_read_value(p, pb, 512)
p = _mlir_ws(p)
tyb: str = _SC_Ty
p = _mlir_read_type(p, tyb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
lb: str = _SC_V1
p = _mlir_read_value(p, lb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
rb: str = _SC_V2
_mlir_read_value(p, rb, 512)
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.")
_append_cstr(out, outsz, ob)
_append_cstr(out, outsz, " \"")
_append_cstr(out, outsz, pb)
_append_cstr(out, outsz, "\" ")
_MlirOperand(lb, tyb, out, outsz)
_append_cstr(out, outsz, ", ")
_MlirOperand(rb, tyb, out, outsz)
mt: str = _SC_Mt
mt[0] = '\0'
MlirTypeText(mt, 4096, tyb, _MLIR_Pool)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, mt)
_append_cstr(out, outsz, "\n")
def _MlirAlloca(p: str, resname: str, out: str, outsz: sizet):
"""alloca: llvm.alloca %cN x ty {alignment = N : i64} : (i32) -> !llvm.ptr
The MLIR form of the alignment attribute was determined by measuring mlir-opt 22.1.8
(do NOT rely on memory): `{alignment = 16 : i64}` -> exit 0 (adopted);
`align(16)` / `, align 16` (LLVM syntax) -> exit 1 (expected ':'); `{alignment = 16 : i32}`
-> exit 1 (requires i64). So the intermediate `, align N` must be rewritten to `{alignment = N : i64}`.
Alignment accepts only a decimal constant that is a power of two: mlir-opt does not check
power-of-two (`{alignment = 3 : i64}` exits 0), but non-powers are illegal in LLVM semantics, so this
layer gates it: non-constant/non-power/out-of-range (> 2^30) always errors loudly.
"""
tyb: str = _SC_Ty
p = _mlir_read_type(p, tyb, 512)
p = _mlir_ws(p)
aligntxt: str = _SC_P2
aligntxt[0] = '\0'
if p[0] == ',':
# ", align N" (the intermediate-text form from build_alloca_aligned)
p = _mlir_ws(p + 1)
if string.strncmp(p, "align", 5) != 0:
MlirFail("MLIR 渲染: alloca 只支持 align 属性(未知属性)")
p = _mlir_ws(p + 5)
nd: int = 0
while p[0] >= '0' and p[0] <= '9':
if nd + 1 >= 512:
MlirFail("MLIR 渲染: alloca 对齐值过长(超范围)")
aligntxt[nd] = p[0]
nd += 1
p = p + 1
aligntxt[nd] = '\0'
p = _mlir_ws(p)
if p[0] != '\0':
MlirFail("MLIR 渲染: alloca 的 align 之后出现多余记号")
if nd == 0:
MlirFail("MLIR 渲染: alloca 对齐值必须是常量(常量十进制,非 SSA)")
if nd > 10:
MlirFail("MLIR 渲染: alloca 对齐值超范围(须为 2 的幂且 <= 2^30)")
av: int = string.atoi(aligntxt)
if av <= 0 or av > 1073741824:
MlirFail("MLIR 渲染: alloca 对齐值超范围(须为 2 的幂且 <= 2^30)")
if (av & (av - 1)) != 0:
MlirFail("MLIR 渲染: alloca 对齐值必须是 2 的幂")
mt: str = _SC_Mt
mt[0] = '\0'
MlirTypeText(mt, 4096, tyb, _MLIR_Pool)
cnt: str = _MlirConstGet("i32", "1")
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.alloca ")
_append_cstr(out, outsz, cnt)
_append_cstr(out, outsz, " x ")
_append_cstr(out, outsz, mt)
if aligntxt[0] != '\0':
_append_cstr(out, outsz, " {alignment = ")
_append_cstr(out, outsz, aligntxt)
_append_cstr(out, outsz, " : i64}")
_append_cstr(out, outsz, " : (i32) -> !llvm.ptr\n")
def _MlirLoad(p: str, resname: str, out: str, outsz: sizet):
"""load: llvm.load %p : !llvm.ptr -> ty"""
p = _mlir_ws(p)
vol: int = 0
if string.strncmp(p, "volatile ", 9) == 0:
vol = 1
p = _mlir_ws(p + 9)
tyb: str = _SC_Ty
p = _mlir_read_type(p, tyb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
ptyb: str = _SC_Ty2
p = _mlir_read_type(p, ptyb, 512)
p = _mlir_ws(p)
pb: str = _SC_V1
_mlir_read_value(p, pb, 512)
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.load ")
if vol != 0:
_append_cstr(out, outsz, "volatile ")
_MlirOperand(pb, ptyb, out, outsz)
tmt: str = _SC_Mt
tmt[0] = '\0'
MlirTypeText(tmt, 4096, tyb, _MLIR_Pool)
pmt: str = _SC_Tmp2
pmt[0] = '\0'
MlirTypeText(pmt, 4096, ptyb, _MLIR_Pool)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, pmt)
_append_cstr(out, outsz, " -> ")
_append_cstr(out, outsz, tmt)
_append_cstr(out, outsz, "\n")
def _MlirStore(p: str, out: str, outsz: sizet):
"""store: llvm.store %v, %p : ty, !llvm.ptr"""
p = _mlir_ws(p)
vol: int = 0
if string.strncmp(p, "volatile ", 9) == 0:
vol = 1
p = _mlir_ws(p + 9)
vtyb: str = _SC_Ty
p = _mlir_read_type(p, vtyb, 512)
p = _mlir_ws(p)
vb: str = _SC_V1
p = _mlir_read_value(p, vb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
ptyb: str = _SC_Ty2
p = _mlir_read_type(p, ptyb, 512)
p = _mlir_ws(p)
pb: str = _SC_V2
_mlir_read_value(p, pb, 512)
_MlirEmitRes(out, outsz, None)
_append_cstr(out, outsz, "llvm.store ")
if vol != 0:
_append_cstr(out, outsz, "volatile ")
_MlirOperand(vb, vtyb, out, outsz)
_append_cstr(out, outsz, ", ")
_MlirOperand(pb, ptyb, out, outsz)
vmt: str = _SC_Mt
vmt[0] = '\0'
MlirTypeText(vmt, 1024, vtyb, _MLIR_Pool)
pmt: str = _SC_Tmp2
pmt[0] = '\0'
MlirTypeText(pmt, 4096, ptyb, _MLIR_Pool)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, vmt)
_append_cstr(out, outsz, ", ")
_append_cstr(out, outsz, pmt)
_append_cstr(out, outsz, "\n")
def _MlirBr(p: str, out: str, outsz: sizet):
"""br: in LLVM syntax the conditional branch opcode is also `br`, dispatched by whether the first token is `label`.
br label %x -> llvm.br ^x
br i1 %c, label %a, label %b -> llvm.cond_br %c, ^a, ^b
Note: build_cond_br emits intermediate text `br i1 ...` (NOT `cond_br`), so the conditional
form must be forwarded here to _MlirCondBr; otherwise it is taken as unconditional and the
type `i1` is mistaken for a block name (producing `llvm.br ^i1` and dropping the condition).
"""
p = _mlir_ws(p)
if string.strncmp(p, "label ", 6) != 0:
# Conditional branch: `<type> <cond>, label %a, label %b`
_MlirCondBr(p, out, outsz)
return
p = _mlir_ws(p + 6)
tb: str = _SC_V1
_mlir_read_value(p, tb, 512)
br: str = _MlirBlkRef(tb)
_MlirEmitRes(out, outsz, None)
_append_cstr(out, outsz, "llvm.br ^")
_append_cstr(out, outsz, br)
_MlirBlockOperands(out, outsz, _MlirFindBlock(_MLIR_Func, br), _MLIR_SrcBlk)
_append_cstr(out, outsz, "\n")
def _MlirCondBr(p: str, out: str, outsz: sizet):
"""cond_br: llvm.cond_br %c, ^then(%v : ty), ^else(%v : ty)"""
ctyb: str = _SC_Ty
p = _mlir_read_type(p, ctyb, 512)
p = _mlir_ws(p)
cb: str = _SC_V1
p = _mlir_read_value(p, cb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
if string.strncmp(p, "label ", 6) == 0:
p = _mlir_ws(p + 6)
tb: str = _SC_V2
p = _mlir_read_value(p, tb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
if string.strncmp(p, "label ", 6) == 0:
p = _mlir_ws(p + 6)
eb: str = _SC_V3
_mlir_read_value(p, eb, 512)
_MlirEmitRes(out, outsz, None)
_append_cstr(out, outsz, "llvm.cond_br ")
_MlirOperand(cb, ctyb, out, outsz)
tr: str = _MlirBlkRef(tb)
_append_cstr(out, outsz, ", ^")
_append_cstr(out, outsz, tr)
_MlirBlockOperands(out, outsz, _MlirFindBlock(_MLIR_Func, tr), _MLIR_SrcBlk)
er: str = _MlirBlkRef(eb)
_append_cstr(out, outsz, ", ^")
_append_cstr(out, outsz, er)
_MlirBlockOperands(out, outsz, _MlirFindBlock(_MLIR_Func, er), _MLIR_SrcBlk)
_append_cstr(out, outsz, "\n")
def _MlirRet(p: str, out: str, outsz: sizet):
"""ret: llvm.return %v : ty (void omits operand and type)"""
p = _mlir_ws(p)
if string.strncmp(p, "void", 4) == 0 and (p[4] == '\0' or p[4] == ' '):
_MlirEmitRes(out, outsz, None)
_append_cstr(out, outsz, "llvm.return\n")
return
tyb: str = _SC_Ty
p = _mlir_read_type(p, tyb, 512)
vb: str = _SC_V1
_mlir_read_value(p, vb, 512)
_MlirEmitRes(out, outsz, None)
_append_cstr(out, outsz, "llvm.return ")
_MlirOperand(vb, tyb, out, outsz)
mt: str = _SC_Mt
mt[0] = '\0'
MlirTypeText(mt, 4096, tyb, _MLIR_Pool)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, mt)
_append_cstr(out, outsz, "\n")
def _MlirUnreachable(out: str, outsz: sizet):
"""unreachable: llvm.unreachable"""
_MlirEmitRes(out, outsz, None)
_append_cstr(out, outsz, "llvm.unreachable\n")
def _MlirCall(p: str, resname: str, out: str, outsz: sizet):
"""call: llvm.call @f(%a) : (i32) -> i32 / indirect llvm.call %fp(%a) : !llvm.ptr, (i32) -> i32
Variadic calls (intermediate text `call <ret> (...) @f(...)`) append `vararg(!llvm.func<...>)`.
"""
p = _mlir_ws(p)
variadic: int = 0
vftys: str = _SC_Tmp
vftys[0] = '\0'
rtyb: str = _SC_Ty
# The return type must be read with nofunc=1: in variadic-call text, the `(...)` after the
# return type is the callee's parameter type list (LLVM syntax), not part of the return type.
# Reading it as a function-type suffix would leave p at `@`/`%`, never entering the variadic
# branch, turning rtyb into `i32 (!llvm.ptr, i32, ...)`, which MlirTypeText splits on commas
# and misreports on the trailing `...`.
p = _mlir_read_type(p, rtyb, 512, 1)
p = _mlir_ws(p)
if string.strncmp(p, "asm", 3) == 0:
# Inline asm: intermediate text is `[%N = ]call <ret_ty> asm [sideeffect]
# [inteldialect] "<asm>", "<constraints>"(<ty> %v, ...)` (LLVM syntax);
# the MLIR form is rewritten by _MlirAsm.
_MlirAsm(p, resname, rtyb, out, outsz)
return
if p[0] == '(':
# Variadic call: intermediate text is `call <ret> (<arg types...>, ...) @f(<args>)`
# (builder's build_call is_variadic=1 form).
# MLIR form (mlir-opt 22.1.8 measured exit 0):
# %0 = llvm.call @printf(%p, %n) vararg(!llvm.func<i32 (!llvm.ptr, i32, ...)>)
# : (!llvm.ptr, i32) -> i32
# `vararg(...)` holds the callee's full MLIR function type (with `...`);
# for void returns that return type is `void` (writing `none` errors
# "expected var_callee_type to return void").
# Note: buffers like vftys/calb avoid _SC_Ty (rtyb holds it) and _SC_Tmp2 (used by the arg loop).
variadic = 1
p = p + 1
g0: int = 0
while p[0] != '\0':
g0 += 1
if g0 > 256:
MlirFail("MLIR 渲染: 变参 call 的函数类型参数过多")
p = _mlir_ws(p)
if p[0] == ')':
p = p + 1
break
if vftys[0] != '\0':
_append_cstr(vftys, 8192, ", ")
if p[0] == '.':
# `...` variadic token passes through (not a type; MlirTypeText rejects it)
_append_cstr(vftys, 8192, "...")
while p[0] == '.':
p = p + 1
else:
ftb: str = _SC_V2
ftb[0] = '\0'
p = _mlir_read_type(p, ftb, 512)
fmt: str = _SC_IdxT
fmt[0] = '\0'
MlirTypeText(fmt, 4096, ftb, _MLIR_Pool)
_append_cstr(vftys, 8192, fmt)
p = _mlir_ws(p)
if p[0] == ',':
p = p + 1
calb: str = _SC_Callee
p = _mlir_read_value(p, calb, 512)
p = _mlir_ws(p)
if p[0] != '(':
MlirFail("MLIR 渲染: call 缺少实参列表")
p = p + 1
args: str = _SC_Args
argts: str = _SC_ArgTs
args[0] = '\0'
argts[0] = '\0'
nargs: int = 0
g: int = 0
while p[0] != '\0':
g += 1
if g > 256:
MlirFail("MLIR 渲染: call 实参过多")
p = _mlir_ws(p)
if p[0] == ')':
break
atyb: str = _SC_Ty2
p = _mlir_read_type(p, atyb, 512)
p = _mlir_ws(p)
avb: str = _SC_V1
p = _mlir_read_value(p, avb, 512)
if nargs > 0:
_append_cstr(args, 2048, ", ")
_append_cstr(argts, 8192, ", ")
_MlirOperand(avb, atyb, args, 2048)
amt: str = _SC_Tmp2
amt[0] = '\0'
MlirTypeText(amt, 4096, atyb, _MLIR_Pool)
_append_cstr(argts, 8192, amt)
nargs += 1
p = _mlir_ws(p)
if p[0] == ',':
p = p + 1
rmt: str = _SC_Mt
rmt[0] = '\0'
MlirTypeText(rmt, 1024, rtyb, _MLIR_Pool)
# LLVM intrinsics (bare symbol @llvm.xxx, not llvm.mlir.*) must use the generic op
# `llvm.call_intrinsic "llvm.xxx"(...) : (...) -> ...`: mlir-translate mangles the intrinsic
# name by operand types (e.g. llvm.va_start -> llvm.va_start.p0). A plain llvm.call would emit
# an unmangled @llvm.va_start and LLVM verification errors
# "Intrinsic name not mangled correctly for type arguments!".
if calb[0] == '@' and string.strncmp(calb + 1, "llvm.", 5) == 0 \
and string.strncmp(calb + 1, "llvm.mlir.", 10) != 0:
if string.strcmp(rmt, "none") == 0:
_MlirEmitRes(out, outsz, None)
else:
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.call_intrinsic \"")
_append_cstr(out, outsz, calb + 1)
_append_cstr(out, outsz, "\"(")
_append_cstr(out, outsz, args)
_append_cstr(out, outsz, ") : (")
_append_cstr(out, outsz, argts)
_append_cstr(out, outsz, ") -> ")
if string.strcmp(rmt, "none") == 0:
_append_cstr(out, outsz, "()")
else:
_append_cstr(out, outsz, rmt)
_append_cstr(out, outsz, "\n")
return
if string.strcmp(rmt, "none") == 0:
_MlirEmitRes(out, outsz, None)
else:
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.call ")
_append_cstr(out, outsz, calb)
_append_cstr(out, outsz, "(")
_append_cstr(out, outsz, args)
_append_cstr(out, outsz, ")")
if variadic != 0:
# Variadic call: vararg(the callee's full MLIR function type) (includes `...`)
_append_cstr(out, outsz, " vararg(!llvm.func<")
if string.strcmp(rmt, "none") == 0:
# Void-returning variadic function type: write `void`, not `none`
_append_cstr(out, outsz, "void")
else:
_append_cstr(out, outsz, rmt)
_append_cstr(out, outsz, " (")
_append_cstr(out, outsz, vftys)
# Close three levels in order: the param list `)`, `!llvm.func<`'s `>`, `vararg(`'s `)`
# (omitting vararg's `)` made mlir-opt 22.1.8 report "expected ')'" verbatim)
_append_cstr(out, outsz, ")>)")
_append_cstr(out, outsz, " : ")
if calb[0] != '@':
# Indirect call: the callee is a !llvm.ptr operand
_append_cstr(out, outsz, "!llvm.ptr, ")
_append_cstr(out, outsz, "(")
_append_cstr(out, outsz, argts)
_append_cstr(out, outsz, ") -> ")
if string.strcmp(rmt, "none") == 0:
_append_cstr(out, outsz, "()")
else:
_append_cstr(out, outsz, rmt)
_append_cstr(out, outsz, "\n")
def _MlirAsm(p: str, resname: str, rtyb: str, out: str, outsz: sizet):
"""Inline asm: llvm.inline_asm [has_side_effects] [asm_dialect = intel] "...", "..." %a, %b : (i32, i32) -> i32
Intermediate text (LLVM syntax) has two sources:
builders' build_inline_asm -> `call <ret> asm sideeffect "...", "..."()`
TPV CallGenCBuiltin injected -> `call <ret> asm sideeffect inteldialect "...", "..."(<ty> %v, ...)`
The MLIR (llvm dialect) form was determined by measuring mlir-opt 22.1.8 (do NOT rely on memory):
* The dialect keyword is `asm_dialect = intel`: LLVM's bare `inteldialect` errors
"expected attribute value"; `asm_dialect = inteldialect` errors with the enum list [att, intel].
* Input operands are comma-separated bare operands without parens: `"...", "=r,r"(%x) : (i32) -> i32`
errors "expected ':'" (that is LLVM syntax and must be rewritten).
* No return => `: (<operand types...>) -> ()`; with return => `-> ty`.
* Multi-result inline asm is a single struct-typed result in MLIR: TPV's
`%N = call {i32, i32} asm sideeffect "...", "=r,r"()` renders as
`%N = llvm.inline_asm has_side_effects "...", "=r,r" : () -> !llvm.struct<(i32, i32)>`
(mlir-opt / mlir-translate 22.1.8 measured exit 0), then fields are extracted with
`llvm.extractvalue` (see _MlirExtractValue). No special branch is needed here: the return type
text goes through MlirTypeText (`{...}` -> `!llvm.struct<(...)>`), nested structs included.
* Measured passing combinations: has_side_effects x asm_dialect = intel x 0/1/2 operands x return/no-return.
Illegal forms always error loudly: unknown tokens besides `sideeffect`/`inteldialect`, missing
constraint string, non-string literal, unpaired args, or a return value without a result name.
"""
p = _mlir_ws(p + 3)
side: int = 0
intel: int = 0
g: int = 0
while g < 8:
g += 1
if string.strncmp(p, "sideeffect", 10) == 0 and (p[10] == ' ' or p[10] == '\t'):
side = 1
p = _mlir_ws(p + 10)
continue
if string.strncmp(p, "inteldialect", 12) == 0 and (p[12] == ' ' or p[12] == '\t'):
intel = 1
p = _mlir_ws(p + 12)
continue
break
# Asm text can reach several KB (TPV's ASM_LINE_BUF_SIZE=4096); use a 4096 buffer, not a
# small one, to avoid truncation; escape pairs (\22/\5C/\0A) pass through as MLIR accepts them.
asmb: str = _SC_Tmp
p = _mlir_read_value(p, asmb, 4096)
if asmb[0] != '"':
MlirFail("MLIR 渲染: inline_asm 缺少汇编文本(或含未知关键字)")
p = _mlir_ws(p)
if p[0] != ',':
MlirFail("MLIR 渲染: inline_asm 缺少约束串(形态须为 \"asm\", \"constraints\")")
p = _mlir_ws(p + 1)
conb: str = _SC_Args
p = _mlir_read_value(p, conb, 2048)
if conb[0] != '"':
MlirFail("MLIR 渲染: inline_asm 约束串必须是字符串字面量")
p = _mlir_ws(p)
if p[0] != '(':
MlirFail("MLIR 渲染: inline_asm 缺少实参列表 '(...)'")
p = p + 1
ops: str = _SC_ArgTs
ops[0] = '\0'
tys: str = _SC_IdxV
tys[0] = '\0'
nops: int = 0
g = 0
while p[0] != '\0':
g += 1
if g > 256:
MlirFail("MLIR 渲染: inline_asm 实参过多")
p = _mlir_ws(p)
if p[0] == ')':
break
atyb: str = _SC_Ty2
p = _mlir_read_type(p, atyb, 512)
p = _mlir_ws(p)
avb: str = _SC_V1
p = _mlir_read_value(p, avb, 512)
if nops > 0:
_append_cstr(ops, 8192, ", ")
_append_cstr(tys, 1024, ", ")
_MlirOperand(avb, atyb, ops, 2048)
amt: str = _SC_Mt
amt[0] = '\0'
MlirTypeText(amt, 4096, atyb, _MLIR_Pool)
_append_cstr(tys, 1024, amt)
nops += 1
p = _mlir_ws(p)
if p[0] == ',':
p = p + 1
if p[0] == ')':
p = p + 1
p = _mlir_ws(p)
if p[0] != '\0':
MlirFail("MLIR 渲染: inline_asm 实参列表之后出现多余记号")
rmt: str = _SC_Buf
rmt[0] = '\0'
MlirTypeText(rmt, 1024, rtyb, _MLIR_Pool)
if string.strcmp(rmt, "none") == 0:
_MlirEmitRes(out, outsz, None)
else:
if resname is None:
MlirFail("MLIR 渲染: inline_asm 有返回值但缺少结果名(%N = call …)")
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.inline_asm")
if side != 0:
_append_cstr(out, outsz, " has_side_effects")
if intel != 0:
_append_cstr(out, outsz, " asm_dialect = intel")
_append_cstr(out, outsz, " ")
_append_cstr(out, outsz, asmb)
_append_cstr(out, outsz, ", ")
_append_cstr(out, outsz, conb)
if nops > 0:
_append_cstr(out, outsz, " ")
_append_cstr(out, outsz, ops)
_append_cstr(out, outsz, " : (")
_append_cstr(out, outsz, tys)
_append_cstr(out, outsz, ") -> ")
if string.strcmp(rmt, "none") == 0:
_append_cstr(out, outsz, "()")
else:
_append_cstr(out, outsz, rmt)
_append_cstr(out, outsz, "\n")
def _MlirCast(p: str, resname: str, ob: str, out: str, outsz: sizet):
"""Cast instruction: llvm.zext %v : i32 to i64"""
ftyb: str = _SC_Ty
p = _mlir_read_type(p, ftyb, 512)
p = _mlir_ws(p)
vb: str = _SC_V1
p = _mlir_read_value(p, vb, 512)
p = _mlir_ws(p)
if string.strncmp(p, "to ", 3) == 0:
p = _mlir_ws(p + 3)
ttyb: str = _SC_Ty2
_mlir_read_type(p, ttyb, 512)
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.")
_append_cstr(out, outsz, ob)
_append_cstr(out, outsz, " ")
_MlirOperand(vb, ftyb, out, outsz)
fmt: str = _SC_Mt
fmt[0] = '\0'
MlirTypeText(fmt, 4096, ftyb, _MLIR_Pool)
tmt: str = _SC_Tmp2
tmt[0] = '\0'
MlirTypeText(tmt, 4096, ttyb, _MLIR_Pool)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, fmt)
_append_cstr(out, outsz, " to ")
_append_cstr(out, outsz, tmt)
_append_cstr(out, outsz, "\n")
def _MlirBitcast(p: str, resname: str, out: str, outsz: sizet):
"""bitcast: ptr->ptr is treated as a no-op (records an alias); otherwise llvm.bitcast %v : T to U"""
ftyb: str = _SC_Ty
p = _mlir_read_type(p, ftyb, 512)
p = _mlir_ws(p)
vb: str = _SC_V1
p = _mlir_read_value(p, vb, 512)
p = _mlir_ws(p)
if string.strncmp(p, "to ", 3) == 0:
p = _mlir_ws(p + 3)
ttyb: str = _SC_Ty2
_mlir_read_type(p, ttyb, 512)
if MlirTypeTextIsPointer(ftyb) != 0 and MlirTypeTextIsPointer(ttyb) != 0:
# With opaque pointers, a ptr->ptr bitcast is meaningless -> no-op:
# register the result name as an alias of the source operand (later refs auto-rewritten), emit nothing
if resname is None:
MlirFail("MLIR 渲染: no-op bitcast 缺少结果名")
opnd: str = _MlirOperand(vb, ftyb, None, 0)
_MlirAliasAdd(resname, opnd)
return
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.bitcast ")
_MlirOperand(vb, ftyb, out, outsz)
fmt: str = _SC_Mt
fmt[0] = '\0'
MlirTypeText(fmt, 4096, ftyb, _MLIR_Pool)
tmt: str = _SC_Tmp2
tmt[0] = '\0'
MlirTypeText(tmt, 4096, ttyb, _MLIR_Pool)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, fmt)
_append_cstr(out, outsz, " to ")
_append_cstr(out, outsz, tmt)
_append_cstr(out, outsz, "\n")
def _MlirSelect(p: str, resname: str, out: str, outsz: sizet):
"""select: llvm.select %c, %a, %b : i1, i32"""
ctyb: str = _SC_Ty
p = _mlir_read_type(p, ctyb, 512)
p = _mlir_ws(p)
cb: str = _SC_V1
p = _mlir_read_value(p, cb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
ttyb: str = _SC_Ty2
p = _mlir_read_type(p, ttyb, 512)
p = _mlir_ws(p)
tb: str = _SC_V2
p = _mlir_read_value(p, tb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
ftyb: str = _SC_Buf
p = _mlir_read_type(p, ftyb, 512)
p = _mlir_ws(p)
fb: str = _SC_V3
_mlir_read_value(p, fb, 512)
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.select ")
_MlirOperand(cb, ctyb, out, outsz)
_append_cstr(out, outsz, ", ")
_MlirOperand(tb, ttyb, out, outsz)
_append_cstr(out, outsz, ", ")
_MlirOperand(fb, ttyb, out, outsz)
cmt: str = _SC_Mt
cmt[0] = '\0'
MlirTypeText(cmt, 4096, ctyb, _MLIR_Pool)
vmt: str = _SC_Tmp2
vmt[0] = '\0'
MlirTypeText(vmt, 1024, ttyb, _MLIR_Pool)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, cmt)
_append_cstr(out, outsz, ", ")
_append_cstr(out, outsz, vmt)
_append_cstr(out, outsz, "\n")
def _MlirGep(p: str, resname: str, out: str, outsz: sizet):
"""getelementptr: llvm.getelementptr %p[i0, i1] : (!llvm.ptr, t0, t1) -> !llvm.ptr, base"""
baseb: str = _SC_Ty2
p = _mlir_read_type(p, baseb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
ptyb: str = _SC_Buf
p = _mlir_read_type(p, ptyb, 512)
p = _mlir_ws(p)
pb: str = _SC_V3
p = _mlir_read_value(p, pb, 512)
idxv: str = _SC_IdxV
idxt: str = _SC_IdxT
idxv[0] = '\0'
idxt[0] = '\0'
p = _mlir_ws(p)
nidx: int = 0
tmpv: str = _SC_Tmp
tmpv[0] = '\0'
g: int = 0
while p[0] == ',':
g += 1
if g > 32:
MlirFail("MLIR 渲染: GEP 索引过多")
p = _mlir_ws(p + 1)
ityb: str = _SC_Ty
p = _mlir_read_type(p, ityb, 512)
p = _mlir_ws(p)
ivb: str = _SC_V1
p = _mlir_read_value(p, ivb, 512)
# Constant indices must be written as integer literals (e.g. `[0, 1]`), not materialized
# as SSA operands: MLIR's llvm.getelementptr requires struct-field indices to be constant,
# else the verifier errors 'expected index N indexing a struct to be constant'
# (operand form only populates rawConstantIndices' dynamic slot). Only non-constant (SSA,
# starting with '%') indices are materialized and take a type slot; constants do not.
if nidx > 0:
_append_cstr(idxv, 1024, ", ")
if ivb[0] == '%':
_MlirOperand(ivb, ityb, tmpv, 4096)
_append_cstr(idxv, 1024, tmpv)
tmpv[0] = '\0'
imt: str = _SC_Mt
imt[0] = '\0'
MlirTypeText(imt, 4096, ityb, _MLIR_Pool)
if idxt[0] != '\0':
_append_cstr(idxt, 4096, ", ")
_append_cstr(idxt, 4096, imt)
else:
_append_cstr(idxv, 1024, ivb)
nidx += 1
p = _mlir_ws(p)
if nidx == 0:
MlirFail("MLIR 渲染: GEP 缺少索引")
bmt: str = _SC_Tmp2
bmt[0] = '\0'
MlirTypeText(bmt, 4096, baseb, _MLIR_Pool)
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.getelementptr ")
_MlirOperand(pb, ptyb, out, outsz)
_append_cstr(out, outsz, "[")
_append_cstr(out, outsz, idxv)
_append_cstr(out, outsz, "] : (!llvm.ptr")
# One type per index
_MlirAppendCommaTypes(out, outsz, idxt)
_append_cstr(out, outsz, ") -> !llvm.ptr, ")
_append_cstr(out, outsz, bmt)
_append_cstr(out, outsz, "\n")
def _MlirAppendCommaTypes(out: str, outsz: sizet, tylist: str):
"""Append ", t0, t1" to out (tylist is a comma-separated type string)"""
if tylist is None or tylist[0] == '\0':
return
_append_cstr(out, outsz, ", ")
_append_cstr(out, outsz, tylist)
def _MlirSwitch(p: str, out: str, outsz: sizet):
"""switch: llvm.switch %v : i32, ^default [0: ^case0, 1: ^case1]
The form was determined by measuring mlir-opt 22.1.8 (the design-doc form
`llvm.switch %v, ^default [0: ^case0] : i32` is REJECTED: "expected ':'"):
`llvm.switch %v : i32, ^default [0: ^c0, 1: ^c1]` -> exit 0 (adopted)
`llvm.switch %v : i32, ^default(%r : i32) [ ... ]` -> exit 0 (default block args)
`llvm.switch %v : i32, ^default [0: ^c0(%r : i32)]` -> exit 0 (case block args)
`llvm.switch %v : i64, ^default [4294967296: ^c0]` -> exit 0 (case value follows value type)
`llvm.switch %v : i32, ^default [-1: ^c0]` -> exit 0
`llvm.switch %v : i32, ^default []` -> exit 0 (empty case table)
i.e. the value type follows the value (`%v : i32,`); block args are written exactly like
br/cond_br, so _MlirBlockOperands' phi->block-arg facility is reused as-is => target blocks may
carry phis (a missing incoming edge errors loudly, never silently dropping args).
Case values must still be constants: in MLIR they are attributes (case_values), not SSA values.
"""
tyb: str = _SC_Ty
p = _mlir_read_type(p, tyb, 512)
p = _mlir_ws(p)
vb: str = _SC_V1
p = _mlir_read_value(p, vb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
if string.strncmp(p, "label ", 6) == 0:
p = _mlir_ws(p + 6)
db: str = _SC_V2
p = _mlir_read_value(p, db, 512)
p = _mlir_ws(p)
if p[0] == '[':
p = p + 1
# Materialize the value operand and its MLIR type text first: _MlirBlockOperands inside the
# case loop reuses _SC_Ty/_SC_Buf/_SC_P1/_SC_P2 scratch, so capture into a separate buffer first.
vop: str = _SC_IdxT
vop[0] = '\0'
_MlirOperand(vb, tyb, vop, 1024)
vmt: str = _SC_IdxV
vmt[0] = '\0'
MlirTypeText(vmt, 1024, tyb, _MLIR_Pool)
cases: str = _SC_Tmp2
cases[0] = '\0'
ncase: int = 0
g: int = 0
while p[0] != '\0':
g += 1
if g > 1024:
MlirFail("MLIR 渲染: switch 分支过多")
p = _mlir_ws(p)
if p[0] == ']' or p[0] == '\0':
break
ctb: str = _SC_P1
p = _mlir_read_type(p, ctb, 512)
p = _mlir_ws(p)
cvb: str = _SC_P2
p = _mlir_read_value(p, cvb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
if string.strncmp(p, "label ", 6) == 0:
p = _mlir_ws(p + 6)
cb: str = _SC_Ty2
p = _mlir_read_value(p, cb, 512)
if cvb[0] == '%':
MlirFail("MLIR 渲染: switch case 值必须是常量(SSA 值不支持)")
cbr: str = _MlirBlkRef(cb)
if ncase > 0:
_append_cstr(cases, 4096, ", ")
_append_cstr(cases, 4096, cvb)
_append_cstr(cases, 4096, ": ^")
_append_cstr(cases, 4096, cbr)
# Target block has phis => append block arguments (same facility as br/cond_br)
_MlirBlockOperands(cases, 1024, _MlirFindBlock(_MLIR_Func, cbr), _MLIR_SrcBlk)
ncase += 1
p = _mlir_ws(p)
if p[0] == ',':
p = p + 1
dr: str = _MlirBlkRef(db)
_MlirEmitRes(out, outsz, None)
_append_cstr(out, outsz, "llvm.switch ")
_append_cstr(out, outsz, vop)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, vmt)
_append_cstr(out, outsz, ", ^")
_append_cstr(out, outsz, dr)
_MlirBlockOperands(out, outsz, _MlirFindBlock(_MLIR_Func, dr), _MLIR_SrcBlk)
_append_cstr(out, outsz, " [")
_append_cstr(out, outsz, cases)
_append_cstr(out, outsz, "]\n")
def _MlirAtomicRmw(p: str, resname: str, out: str, outsz: sizet):
"""atomicrmw: llvm.atomicrmw add %p, %v monotonic : !llvm.ptr, i32"""
aop: str = _SC_P1
p = _mlir_read_value(p, aop, 512)
p = _mlir_ws(p)
ptyb: str = _SC_Ty
p = _mlir_read_type(p, ptyb, 512)
p = _mlir_ws(p)
pb: str = _SC_V1
p = _mlir_read_value(p, pb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
vtyb: str = _SC_Ty2
p = _mlir_read_type(p, vtyb, 512)
p = _mlir_ws(p)
vb: str = _SC_V2
p = _mlir_read_value(p, vb, 512)
p = _mlir_ws(p)
ordb: str = _SC_P2
_mlir_read_value(p, ordb, 512)
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.atomicrmw ")
_append_cstr(out, outsz, aop)
_append_cstr(out, outsz, " ")
_MlirOperand(pb, ptyb, out, outsz)
_append_cstr(out, outsz, ", ")
_MlirOperand(vb, vtyb, out, outsz)
_append_cstr(out, outsz, " ")
_append_cstr(out, outsz, ordb)
pmt: str = _SC_Mt
pmt[0] = '\0'
MlirTypeText(pmt, 4096, ptyb, _MLIR_Pool)
vmt: str = _SC_Tmp2
vmt[0] = '\0'
MlirTypeText(vmt, 1024, vtyb, _MLIR_Pool)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, pmt)
_append_cstr(out, outsz, ", ")
_append_cstr(out, outsz, vmt)
_append_cstr(out, outsz, "\n")
def _MlirCmpXchg(p: str, resname: str, resname2: str,
out: str, outsz: sizet):
"""cmpxchg: MLIR single result struct{T, i1} + two extractvalues for old value / success bit.
Intermediate text (LLVM syntax, two forms: builder single result / two-result prefix)
%N = cmpxchg <ptr_ty> <ptr>, <cmp_ty> <cmp>, <new_ty> <new> <succ> <fail>
%a, %b = cmpxchg ...
MLIR form (mlir-opt 22.1.8 measured exit 0; cmpxchg is a single result in MLIR, yielding
struct{T, i1}, not two SSA values):
%N = llvm.cmpxchg %p, %c, %n acq_rel monotonic : !llvm.ptr, i32
%_N_v = llvm.extractvalue %N[0] : !llvm.struct<(i32, i1)> ; old value (compared value)
%_N_ok = llvm.extractvalue %N[1] : !llvm.struct<(i32, i1)> ; i1 success bit
The two extractvalue result names are deterministically derived from the struct result name
(_MlirCxResName, e.g. `%0` -> `%_0_v`/`%_0_ok`).
Bindings: `%N` (single result) / `%a` (two-result first name) -> old value `[0]`;
two-result second name `%b` -> success bit `[1]`. Bindings use the same alias facility as
bitcast no-op (_MlirAliasAdd); later references are auto-rewritten by _MlirResolve to the
extractvalue result names. This preserves builder-side build_cmpxchg's "T-typed Value" (old value) semantics.
"""
ptyb: str = _SC_Ty
p = _mlir_read_type(p, ptyb, 512)
p = _mlir_ws(p)
pb: str = _SC_V1
p = _mlir_read_value(p, pb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
ctyb: str = _SC_Ty2
p = _mlir_read_type(p, ctyb, 512)
p = _mlir_ws(p)
cb: str = _SC_V2
p = _mlir_read_value(p, cb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
ntyb: str = _SC_B1
p = _mlir_read_type(p, ntyb, 512)
p = _mlir_ws(p)
nb: str = _SC_B2
p = _mlir_read_value(p, nb, 512)
p = _mlir_ws(p)
succ: str = _SC_P1
p = _mlir_read_value(p, succ, 512)
p = _mlir_ws(p)
fail: str = _SC_P2
_mlir_read_value(p, fail, 512)
if resname is None:
MlirFail("MLIR 渲染: cmpxchg 缺少结果名")
# The two extractvalue result names derive deterministically from the struct name (identical across both passes)
onm: str = _MlirCxResName(resname, "_v")
snm: str = _MlirCxResName(resname, "_ok")
_MlirAliasAdd(resname, onm)
if resname2 is not None:
_MlirAliasAdd(resname2, snm)
# MLIR type text (use a non-_SC_Mt buffer: _MlirOperand occupies _SC_Mt for literals)
pmt: str = _SC_Tmp2
pmt[0] = '\0'
MlirTypeText(pmt, 4096, ptyb, _MLIR_Pool)
vmt: str = _SC_IdxV
vmt[0] = '\0'
MlirTypeText(vmt, 1024, ctyb, _MLIR_Pool)
stmt: str = _SC_IdxT
stmt[0] = '\0'
_append_cstr(stmt, 4096, "!llvm.struct<(")
_append_cstr(stmt, 4096, vmt)
_append_cstr(stmt, 4096, ", i1)>")
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.cmpxchg ")
_MlirOperand(pb, ptyb, out, outsz)
_append_cstr(out, outsz, ", ")
_MlirOperand(cb, ctyb, out, outsz)
_append_cstr(out, outsz, ", ")
_MlirOperand(nb, ntyb, out, outsz)
_append_cstr(out, outsz, " ")
_append_cstr(out, outsz, succ)
_append_cstr(out, outsz, " ")
_append_cstr(out, outsz, fail)
_append_cstr(out, outsz, " : ")
_append_cstr(out, outsz, pmt)
_append_cstr(out, outsz, ", ")
_append_cstr(out, outsz, vmt)
_append_cstr(out, outsz, "\n")
_MlirEmitRes(out, outsz, onm)
_append_cstr(out, outsz, "llvm.extractvalue ")
_append_cstr(out, outsz, resname)
_append_cstr(out, outsz, "[0] : ")
_append_cstr(out, outsz, stmt)
_append_cstr(out, outsz, "\n")
_MlirEmitRes(out, outsz, snm)
_append_cstr(out, outsz, "llvm.extractvalue ")
_append_cstr(out, outsz, resname)
_append_cstr(out, outsz, "[1] : ")
_append_cstr(out, outsz, stmt)
_append_cstr(out, outsz, "\n")
def _MlirVaArg(p: str, resname: str, out: str, outsz: sizet):
"""va_arg: use the generic op syntax (deliberate choice, see below).
Intermediate text (LLVM syntax): %N = va_arg <va_list_ty> <va_list_ptr>, <arg_ty>
MLIR text: %N = "llvm.va_arg"(%ap) : (!llvm.ptr) -> i32
Why generic form is required (mlir-opt 22.1.8 measured): the custom assembly syntax
`llvm.va_arg %ap, i32` requires an explicit va_list type (in LLVM it is `ptr`), which cannot
be expressed with opaque pointers -- mlir-opt errors "expected builtin.function". So the
generic op form is used, described only by operand/result types. Down-level verification
(mlir-translate --mlir-to-llvmir) yields `%2 = va_arg ptr %0, i32`, isomorphic to LLVM's va_arg.
"""
vtyb: str = _SC_Ty
p = _mlir_read_type(p, vtyb, 512)
p = _mlir_ws(p)
vb: str = _SC_V1
p = _mlir_read_value(p, vb, 512)
p = _mlir_ws(p)
if p[0] == ',':
p = _mlir_ws(p + 1)
atyb: str = _SC_Ty2
_mlir_read_type(p, atyb, 512)
if resname is None:
MlirFail("MLIR 渲染: va_arg 缺少结果名")
# Operand/result types (_MlirOperand only uses _SC_Mt, so take separate buffers here)
omt: str = _SC_Tmp2
omt[0] = '\0'
MlirTypeText(omt, 4096, vtyb, _MLIR_Pool)
rmt: str = _SC_IdxV
rmt[0] = '\0'
MlirTypeText(rmt, 1024, atyb, _MLIR_Pool)
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "\"llvm.va_arg\"(")
_MlirOperand(vb, vtyb, out, outsz)
_append_cstr(out, outsz, ") : (")
_append_cstr(out, outsz, omt)
_append_cstr(out, outsz, ") -> ")
_append_cstr(out, outsz, rmt)
_append_cstr(out, outsz, "\n")
def _MlirExtractValue(p: str, resname: str, out: str, outsz: sizet):
"""extractvalue: llvm.extractvalue %v[i0, i1] : <aggregate type>
Intermediate text is LLVM syntax (aggregate type BEFORE the value, indices after commas):
%r = extractvalue {i32, i32} %v, 0
%r = extractvalue {i32, {i32, i32}} %v, 1, 0 ; multi-level index
MLIR form (mlir-opt 22.1.8 measured exit 0):
%r = llvm.extractvalue %v[0] : !llvm.struct<(i32, i32)>
%r = llvm.extractvalue %v[1, 0] : !llvm.struct<(i32, !llvm.struct<(i32, i32)>)>
The index list is written inside `[]` separated by ", " in MLIR (different from LLVM's
`<aggregate>, i0, i1` syntax, so it must be rewritten); the aggregate type text goes through
MlirTypeText (anonymous `{...}` -> !llvm.struct<(...)>, named `%"name"` -> inlined named body,
array `[N x T]` -> !llvm.array<N x T>, all measured exit 0).
Real source: TPV's multi-result inline asm (`%asm_result = call {i32, i32} asm ...`) followed by
per-field extraction (`%asm_out_0 = extractvalue {i32, i32} %asm_result, 0`); multi-level indices
are also supported (measured legal). Indices must be constant literals (LLVM syntax) -- SSA index => loud error.
"""
atyb: str = _SC_Ty
p = _mlir_read_type(p, atyb, 512)
p = _mlir_ws(p)
vb: str = _SC_V1
p = _mlir_read_value(p, vb, 512)
idxs: str = _SC_IdxV
idxs[0] = '\0'
nidx: int = 0
g: int = 0
# Index list: comma-separated in LLVM syntax (`<aggregate>, i0, i1`); read each and rewrite.
# Loop bound matches other parse loops in this file (bounded; exceeding errors loudly).
while g < 64:
p = _mlir_ws(p)
if p[0] != ',':
break
g += 1
if g > 32:
MlirFail("MLIR 渲染: extractvalue 索引过多")
p = _mlir_ws(p + 1)
ib: str = _SC_IdxT
p = _mlir_read_value(p, ib, 512)
if ib[0] == '\0':
MlirFail("MLIR 渲染: extractvalue 缺少索引")
if ib[0] == '%':
MlirFail("MLIR 渲染: extractvalue 索引必须是常量(SSA 索引不支持)")
if nidx > 0:
_append_cstr(idxs, 1024, ", ")
_append_cstr(idxs, 1024, ib)
nidx += 1
if nidx == 0:
MlirFail("MLIR 渲染: extractvalue 缺少索引")
if p[0] != '\0':
MlirFail("MLIR 渲染: extractvalue 索引之后出现多余记号")
# Aggregate type text goes into a separate buffer first (_MlirOperand only uses _SC_Mt)
amt: str = _SC_Tmp2
amt[0] = '\0'
MlirTypeText(amt, 4096, atyb, _MLIR_Pool)
_MlirEmitRes(out, outsz, resname)
_append_cstr(out, outsz, "llvm.extractvalue ")
_MlirOperand(vb, atyb, out, outsz)
_append_cstr(out, outsz, "[")
_append_cstr(out, outsz, idxs)
_append_cstr(out, outsz, "] : ")
_append_cstr(out, outsz, amt)
_append_cstr(out, outsz, "\n")
# ============================================================
# Instruction line -> MLIR dispatch
# ============================================================
def _MlirRenderLine(text: str, out: str, outsz: sizet):
"""Parse one intermediate instruction text and render it to MLIR (out=None: register/validate only)"""
global _MLIR_Pass, _MLIR_CurLine
if text is None:
return
# Stash the "current line" at entry: printed on type-text conversion failure (both passes
# use the same line, so pass1 failures are also locatable). text points to Line.Buf, live for the whole render.
_MLIR_CurLine = text
MlirSetContextLine(_MLIR_CurLine)
p: str = _mlir_ws(text)
if p[0] == '\0':
return
resname: str = None
resname2: str = None
if p[0] == '%':
tb: str = _SC_Tb
q: str = _mlir_read_value(p, tb, 512)
q2: str = _mlir_ws(q)
if q2[0] == '=':
resname = _mlir_dup(tb)
p = _mlir_ws(q2 + 1)
elif q2[0] == ',':
# Two-result prefix `%a, %b = <op>` (cmpxchg only): MLIR's cmpxchg is a single
# struct{T, i1} result; the two names each bind to one extractvalue (see _MlirCmpXchg).
resname = _mlir_dup(tb)
q3: str = _mlir_ws(q2 + 1)
tb2: str = _SC_V3
q3 = _mlir_read_value(q3, tb2, 512)
q4: str = _mlir_ws(q3)
if q4[0] != '=':
MlirFail("MLIR 渲染: 双结果前缀(%a, %b =)缺少 '='")
resname2 = _mlir_dup(tb2)
p = _mlir_ws(q4 + 1)
ob: str = _SC_Op
p = _mlir_read_value(p, ob, 64)
if ob[0] == '\0':
MlirFail("MLIR 渲染: 空指令")
if resname2 is not None and string.strcmp(ob, "cmpxchg") != 0:
MlirFail("MLIR 渲染: 双结果前缀(%a, %b =)只支持 cmpxchg")
if _mlir_in_set(ob, "|add|sub|mul|sdiv|udiv|srem|urem|and|or|xor|shl|lshr|ashr|") != 0:
_MlirBinop(p, resname, ob, out, outsz)
return
if _mlir_in_set(ob, "|fadd|fsub|fmul|fdiv|frem|") != 0:
_MlirBinop(p, resname, ob, out, outsz)
return
if string.strcmp(ob, "fneg") == 0:
_MlirFneg(p, resname, out, outsz)
return
if string.strcmp(ob, "icmp") == 0:
_MlirCmp(p, resname, ob, out, outsz)
return
if string.strcmp(ob, "fcmp") == 0:
_MlirCmp(p, resname, ob, out, outsz)
return
if string.strcmp(ob, "alloca") == 0:
_MlirAlloca(p, resname, out, outsz)
return
if string.strcmp(ob, "load") == 0:
_MlirLoad(p, resname, out, outsz)
return
if string.strcmp(ob, "store") == 0:
_MlirStore(p, out, outsz)
return
if string.strcmp(ob, "br") == 0:
_MlirBr(p, out, outsz)
return
if string.strcmp(ob, "ret") == 0:
_MlirRet(p, out, outsz)
return
if string.strcmp(ob, "unreachable") == 0:
_MlirUnreachable(out, outsz)
return
if string.strcmp(ob, "call") == 0:
_MlirCall(p, resname, out, outsz)
return
if string.strcmp(ob, "select") == 0:
_MlirSelect(p, resname, out, outsz)
return
if string.strcmp(ob, "phi") == 0:
if out is not None:
# Leading phis are skipped in _MlirEmitBlockLines; reaching here means phi is not at block head
MlirFail("MLIR 渲染: phi 不在块首(v1 只支持块首连续 phi → 块参数)")
_MlirPhiScan(text)
return
if string.strcmp(ob, "getelementptr") == 0:
_MlirGep(p, resname, out, outsz)
return
if string.strcmp(ob, "bitcast") == 0:
_MlirBitcast(p, resname, out, outsz)
return
if _mlir_in_set(ob, "|sext|trunc|zext|ptrtoint|inttoptr|fpext|fptrunc|fptosi|sitofp|fptoui|uitofp|") != 0:
_MlirCast(p, resname, ob, out, outsz)
return
if string.strcmp(ob, "switch") == 0:
_MlirSwitch(p, out, outsz)
return
if string.strcmp(ob, "atomicrmw") == 0:
_MlirAtomicRmw(p, resname, out, outsz)
return
if string.strcmp(ob, "cmpxchg") == 0:
_MlirCmpXchg(p, resname, resname2, out, outsz)
return
if string.strcmp(ob, "va_arg") == 0:
_MlirVaArg(p, resname, out, outsz)
return
if string.strcmp(ob, "extractvalue") == 0:
# Real source: TPV's multi-result inline-asm result extraction (see _MlirExtractValue).
# Note: other unsupported opcodes like `insertvalue` never occur in the corpus and fall
# through to the loud fallback error ("unknown instruction opcode").
_MlirExtractValue(p, resname, out, outsz)
return
# Note: inline asm's opcode in intermediate text is "call" (call <ret> asm ...); _MlirCall
# recognizes it and forwards to _MlirAsm (llvm.inline_asm).
MlirFail("MLIR 渲染: 未知指令 opcode")
# ============================================================
# Function signature and body
# ============================================================
def _MlirFuncSig(buf: str, size: sizet,
func: rawptr[Function], pool: memhub.MemBuddy*):
"""llvm.func @name(%a: i32, %b: i32) -> i32 (void return omits -> ty)"""
_append_cstr(buf, size, "llvm.func @")
if func.Name is not None:
if _ll_name_needs_quote(func.Name) != 0:
_append_cstr(buf, size, "\"")
_append_cstr(buf, size, func.Name)
_append_cstr(buf, size, "\"")
else:
_append_cstr(buf, size, func.Name)
_append_cstr(buf, size, "(")
params: rawptr[GSList[Param]] = func.Params
cur: rawptr[Param] = None
if params is not None:
cur = params.Head
first: int = 1
cur_steps: int = 0
while cur is not None:
cur_steps += 1
if cur_steps > IR_DUMP_CHAIN_LIMIT:
MlirFail("MLIR 渲染: 参数链疑似成环")
if first == 0:
_append_cstr(buf, size, ", ")
if cur.Name is not None:
if cur.Name[0] != '%':
_append_cstr(buf, size, "%")
_append_cstr(buf, size, cur.Name)
_append_cstr(buf, size, ": ")
ty_buf: str = _SC_Ty
ty_buf[0] = '\0'
TypePrint(ty_buf, 4096, cur.Ty, pool)
_append_cstr(buf, size, ty_buf)
cur = cur.Next
first = 0
# Variadic function: append `...` at the end of the parameter list (mlir-opt 22.1.8 measured exit 0:
# `llvm.func @f(%p: !llvm.ptr, ...) -> i32` / with no fixed params `llvm.func @f(...) -> i32`)
if func.IsVarArg == 1:
if params is not None and params.Head is not None:
_append_cstr(buf, size, ", ...")
else:
_append_cstr(buf, size, "...")
_append_cstr(buf, size, ")")
# Return type (Void omitted)
is_void: int = 0
match func.RetTy:
case LLVMType.Void():
is_void = 1
case _:
is_void = 0
if is_void == 0:
ret_buf: str = _SC_Ty2
ret_buf[0] = '\0'
TypePrint(ret_buf, 4096, func.RetTy, pool)
_append_cstr(buf, size, " -> ")
_append_cstr(buf, size, ret_buf)
def FunctionPrint(buf: str, size: sizet,
func: rawptr[Function], pool: memhub.MemBuddy*):
"""Serialize function func as MLIR (llvm dialect) text APPENDED to buf.
NOTE: like the llvmlite version, this appends (so the module printer can concatenate
functions); callers wanting single-function text must set buf[0]='\\0' themselves.
"""
global _MLIR_Pass, _MLIR_Func, _MLIR_SrcBlk, _MLIR_CurLine, _MlirOutTrunc
if func is None or buf is None or size == 0:
return
_MlirOutTrunc = 0
# Local peephole first (ret zeroinitializer->0/null, entry-slot store->load forwarding,
# no-op bitcast removal): peepholes match by name and delete lines by setting Buf=None.
# They act on the builders' intermediate instruction text and are orthogonal to MLIR rendering;
# RenumberSSA internally skips declare functions (IsDeclared==1).
if func.IsDeclared == 0 and G_PeepEnable != 0:
_peephole_opt(func, pool)
RenumberSSA(func, pool)
_MlirTablesInit(pool)
_MLIR_Func = func
# Clear the previous function's line context: signature/declare and other non-line-driven
# positions must not carry an unrelated stale line, avoiding misleading diagnostics.
_MLIR_CurLine = None
MlirSetContextLine(None)
if func.IsDeclared == 1:
# LLVM intrinsic declarations are NOT emitted: on the MLIR side intrinsics are expressed
# via llvm.call_intrinsic (see _MlirCall). Emitting `llvm.func @llvm.va_start(!llvm.ptr)`
# would be down-leveled to an unmangled `declare void @llvm.va_start(ptr)`, and LLVM
# verification errors "Intrinsic name not mangled correctly for type arguments!".
# Rule: name starts with "llvm." and not "llvm.mlir.*" (the latter are MLIR-internal op names).
if func.Name is not None and string.strncmp(func.Name, "llvm.", 5) == 0 \
and string.strncmp(func.Name, "llvm.mlir.", 10) != 0:
return
# declare (no body)
_MlirFuncSig(buf, size, func, pool)
_append_cstr(buf, size, "\n")
_MlirOutTruncFail()
return
blocks: rawptr[GSList[BasicBlock]] = func.Blocks
if blocks is None:
_MlirFuncSig(buf, size, func, pool)
_append_cstr(buf, size, "\n")
_MlirOutTruncFail()
return
# ---- pass1: scan (register constants/aliases + shape checks) ----
_MLIR_Pass = 1
blk: rawptr[BasicBlock] = blocks.Head
blk_steps: int = 0
while blk is not None:
blk_steps += 1
if blk_steps > IR_DUMP_CHAIN_LIMIT:
MlirFail("MLIR 渲染: 函数块链疑似成环")
_MLIR_SrcBlk = blk
if blk.Lines is not None:
lines: rawptr[GSList[Line]] = blk.Lines
ln: rawptr[Line] = lines.Head
ln_steps: int = 0
while ln is not None:
ln_steps += 1
if ln_steps > IR_DUMP_CHAIN_LIMIT:
MlirFail("MLIR 渲染: 块指令链疑似成环")
if ln.Buf is not None:
_MlirRenderLine(ln.Buf, None, 0)
ln = ln.Next
blk = blk.Next
# ---- pass2: emit ----
_MLIR_Pass = 2
_MlirFuncSig(buf, size, func, pool)
_append_cstr(buf, size, " {\n")
blk = blocks.Head
first: int = 1
blk_steps = 0
while blk is not None:
blk_steps += 1
if blk_steps > IR_DUMP_CHAIN_LIMIT:
MlirFail("MLIR 渲染: 函数块链疑似成环")
_MLIR_SrcBlk = blk
if first != 0:
# entry block has no label; constant-materialization ops go at the entry block head
# (entry dominates the function, so constants are visible everywhere without breaking the phi-at-head rule)
_MlirCheckEntryNoPhi(blk)
_MlirEmitConsts(buf, size)
else:
_MlirEmitBlockHeader(buf, size, blk)
_MlirEmitBlockLines(buf, size, blk)
blk = blk.Next
first = 0
_append_cstr(buf, size, "}\n")
_MlirOutTruncFail()
# ============================================================
# Internal helpers
# ============================================================
# _append_cstr write-position cache (perf: O(1) when appending into the same buffer,
# avoiding O(n^2) strlen(dst) rescans -- an IR-serialization hot spot).
# Falls back to strlen on buffer switch (different dst) or buffer reset (dst[0]=0 and pos>0).
_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, _MlirOutTrunc
if dst is None or src is None: return
dlen: sizet = 0
if _g_last_dst == uint64(dst):
dlen = _g_last_pos
# Buffer reset (caller reuses the same address after buf[0]=0) -> restart from head
if dlen > 0 and dst[0] == '\0':
dlen = 0
# Cache-consistency check: the cached end must still be the current text end (NUL at the
# end, non-NUL before it). If several modules write the same buf or pool reuse leaves old
# content, the cached pos goes stale and appends past the first NUL become invisible
# (LLvmLiteTest Test5/9 root cause); on failure fall back to strlen.
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 exceeds the buffer size (corrupted/dangling buffer),
# return; otherwise remain = dst_size - dlen underflows to a huge value and writes out of bounds.
if dlen >= dst_size:
_MlirOutTrunc = 1
return
slen: sizet = string.strlen(src)
remain: sizet = dst_size - dlen
if remain <= 0:
_MlirOutTrunc = 1
return
i: sizet = 0
while i < slen and i + 1 < remain:
dst[dlen + i] = src[i]
i += 1
if i < slen:
_MlirOutTrunc = 1
dst[dlen + i] = '\0'
_g_last_pos = dlen + i
def _MlirOutTruncFail():
"""Output-truncation check before printing finishes (truncation errors loudly, never emits partial MLIR silently)"""
global _MlirOutTrunc
if _MlirOutTrunc != 0:
_MlirOutTrunc = 0
MlirFail("MLIR 渲染: 输出文本超出缓冲被截断(需增大 dump_ir/FunctionPrint 的缓冲)")
# ============================================================
# Debug call-site recording (--traceback only; zero overhead when off)
#
# The MLIR backend emits no DWARF (see README 3.3): the final exe has no .debug_line records
# for our .vp functions, so runtime stackmaps cannot map a caller frame's PC (= return address)
# back to a call-site line. Here, at BUILD time, each define (F record) and each DIRECT call
# (C record) is recorded into a text buffer; after each module is translated TransPyV writes it
# to a <sha1>.vpcall side file, and BuildPipeline.WriteStackMap combines it with
# `llvm-objdump -d` for "return address -> (file, line, function)" address normalization.
#
# Record format (fields separated by \t to avoid ambiguity from spaces/backslashes in Windows paths):
# F\t<function symbol>\t<definition line>\t<absolute file path>\n
# C\t<caller symbol>\t<callee symbol>\t<call-site line>\t<absolute file path>\n
#
# Function symbol = Function.Name (no @ prefix), matching objdump labels; the callee symbol comes
# from build_call's callee (also no @); LLVM intrinsics (llvm.*) never become named callq targets and are skipped.
# ============================================================
# Master switch: mirrors TransPyV's --traceback (relayed via DebugInfoGen.SetEnabled)
_g_dbg_on: int = 0
# Text buffer (stdlib-allocated, survives pool reset; cleared after DebugTakeText hands off ownership)
_g_dbg_buf: bytes = None
_g_dbg_len: sizet = 0
_g_dbg_cap: sizet = 0
# Current function context (C records pull caller symbol/file/line from here); nested defines
# (lambdas etc.) use a fixed-size Push/Pop stack.
define DBGC_STACK_MAX = 256
_g_dbg_func: str = None
_g_dbg_file: str = None
_g_dbg_defline: int = 0
_g_dbg_line: int = 0
_g_dbg_depth: int = 0
_g_dbg_stk_func: bytes = None # 8 bytes per slot (char*)
_g_dbg_stk_file: bytes = None # 8 bytes per slot (char*)
_g_dbg_stk_def: bytes = None # 8 bytes per slot (sizet)
_g_dbg_stk_ln: bytes = None # 8 bytes per slot (sizet)
def DebugSetEnabled(flag: bool) -> int:
"""Enable/disable debug call-site recording (relayed from DebugInfoGen.SetEnabled --traceback)."""
global _g_dbg_on
if flag:
_g_dbg_on = 1
else:
_g_dbg_on = 0
return 0
def DebugIsEnabled() -> int:
return _g_dbg_on
def _DbgReserve(extra: sizet) -> int:
"""Ensure the text buffer can hold at least extra more bytes (excluding the trailing NUL)."""
global _g_dbg_buf, _g_dbg_cap
if _g_dbg_buf is None:
cap0: sizet = 262144
b: bytes = stdlib.malloc(cap0)
if b is None:
return 1
b[0] = '\0'
_g_dbg_buf = b
_g_dbg_cap = cap0
need: sizet = _g_dbg_len + extra + 1
if need <= _g_dbg_cap:
return 0
ncap: sizet = _g_dbg_cap
while ncap < need:
ncap = ncap * 2
nb: bytes = stdlib.realloc(_g_dbg_buf, ncap)
if nb is None:
return 1
_g_dbg_buf = nb
_g_dbg_cap = ncap
return 0
def _DbgEnsureStack() -> int:
"""Lazily allocate the Push/Pop context stack (only when enabled and actual nesting occurs)."""
global _g_dbg_stk_func, _g_dbg_stk_file, _g_dbg_stk_def, _g_dbg_stk_ln
if _g_dbg_stk_func is not None:
return 0
b1: bytes = stdlib.malloc(sizet(DBGC_STACK_MAX) * 8)
b2: bytes = stdlib.malloc(sizet(DBGC_STACK_MAX) * 8)
b3: bytes = stdlib.malloc(sizet(DBGC_STACK_MAX) * 8)
b4: bytes = stdlib.malloc(sizet(DBGC_STACK_MAX) * 8)
if b1 is None or b2 is None or b3 is None or b4 is None:
return 1
_g_dbg_stk_func = b1
_g_dbg_stk_file = b2
_g_dbg_stk_def = b3
_g_dbg_stk_ln = b4
return 0
def DebugNoteFunc(sym: str, file: str, line: int) -> int:
"""Enter a define: push the outer context, set as current function, and write an F record."""
global _g_dbg_func, _g_dbg_file, _g_dbg_defline, _g_dbg_line, _g_dbg_depth, _g_dbg_len
if _g_dbg_on == 0:
return 0
if _g_dbg_depth >= DBGC_STACK_MAX:
MlirFail("MLIR 调试记录: 函数嵌套深度超上限(DBGC_STACK_MAX)")
if _DbgEnsureStack() != 0:
MlirFail("MLIR 调试记录: 上下文栈分配失败")
fs: str* = (str*)(uint64(_g_dbg_stk_func) + sizet(_g_dbg_depth) * 8)
fs[0] = _g_dbg_func
ff: str* = (str*)(uint64(_g_dbg_stk_file) + sizet(_g_dbg_depth) * 8)
ff[0] = _g_dbg_file
ds: sizet* = (sizet*)(uint64(_g_dbg_stk_def) + sizet(_g_dbg_depth) * 8)
ds[0] = sizet(_g_dbg_defline)
ls: sizet* = (sizet*)(uint64(_g_dbg_stk_ln) + sizet(_g_dbg_depth) * 8)
ls[0] = sizet(_g_dbg_line)
_g_dbg_depth += 1
_g_dbg_func = sym
_g_dbg_file = file
_g_dbg_defline = line
_g_dbg_line = line
if sym is None or file is None:
return 0
need: sizet = string.strlen(sym) + string.strlen(file) + 64
if _DbgReserve(need) != 0:
MlirFail("MLIR 调试记录: F 记录缓冲分配失败")
viperlib.snprintf(_g_dbg_buf + _g_dbg_len, _g_dbg_cap - _g_dbg_len,
"F\t%s\t%d\t%s\n", sym, line, file)
_g_dbg_len = _g_dbg_len + string.strlen(_g_dbg_buf + _g_dbg_len)
return 0
def DebugClearFunc() -> int:
"""Leave a define: restore the outer context (clear if none)."""
global _g_dbg_func, _g_dbg_file, _g_dbg_defline, _g_dbg_line, _g_dbg_depth
if _g_dbg_on == 0:
return 0
if _g_dbg_depth <= 0:
_g_dbg_func = None
_g_dbg_file = None
_g_dbg_defline = 0
_g_dbg_line = 0
return 0
_g_dbg_depth -= 1
fs: str* = (str*)(uint64(_g_dbg_stk_func) + sizet(_g_dbg_depth) * 8)
_g_dbg_func = fs[0]
ff: str* = (str*)(uint64(_g_dbg_stk_file) + sizet(_g_dbg_depth) * 8)
_g_dbg_file = ff[0]
ds: sizet* = (sizet*)(uint64(_g_dbg_stk_def) + sizet(_g_dbg_depth) * 8)
_g_dbg_defline = int(ds[0])
ls: sizet* = (sizet*)(uint64(_g_dbg_stk_ln) + sizet(_g_dbg_depth) * 8)
_g_dbg_line = int(ls[0])
return 0
def DebugNoteLine(line: int) -> int:
"""Record the current statement line (used by the following build_call to write a C record)."""
global _g_dbg_line
if _g_dbg_on == 0:
return 0
if line > 0:
_g_dbg_line = line
return 0
def DebugResetFunc() -> int:
"""Clear the current function context (module-start fallback: a mid-translation failure may leave an un-Popped context)."""
global _g_dbg_func, _g_dbg_file, _g_dbg_defline, _g_dbg_line, _g_dbg_depth
if _g_dbg_on == 0:
return 0
_g_dbg_func = None
_g_dbg_file = None
_g_dbg_defline = 0
_g_dbg_line = 0
_g_dbg_depth = 0
return 0
def _DebugNoteCall(callee: str) -> int:
"""Write a C record (caller = current function, line = current statement line)."""
global _g_dbg_len
if _g_dbg_on == 0:
return 0
if _g_dbg_func is None or _g_dbg_file is None or _g_dbg_line <= 0:
return 0
if callee is None or callee[0] == '\0':
return 0
if string.strncmp(callee, "llvm.", 5) == 0:
return 0
need: sizet = string.strlen(_g_dbg_func) + string.strlen(callee) \
+ string.strlen(_g_dbg_file) + 64
if _DbgReserve(need) != 0:
MlirFail("MLIR 调试记录: C 记录缓冲分配失败")
viperlib.snprintf(_g_dbg_buf + _g_dbg_len, _g_dbg_cap - _g_dbg_len,
"C\t%s\t%s\t%d\t%s\n",
_g_dbg_func, callee, _g_dbg_line, _g_dbg_file)
_g_dbg_len = _g_dbg_len + string.strlen(_g_dbg_buf + _g_dbg_len)
return 0
def DebugTakeText() -> str:
"""Take and clear the accumulated text (once per module); buffer ownership passes to the caller."""
global _g_dbg_buf, _g_dbg_len, _g_dbg_cap
if _g_dbg_buf is None or _g_dbg_len == 0:
if _g_dbg_buf is not None:
_g_dbg_len = 0
_g_dbg_buf[0] = '\0'
return None
out: str = _g_dbg_buf
_g_dbg_buf = None
_g_dbg_len = 0
_g_dbg_cap = 0
return out
# ============================================================
# Field accessors (let other modules bypass stub type limits)
#
# Cross-module, the user module only has stub types for Function/Param (missing fields, possibly
# wrong types), so TransPyC silently skips field access (no GEP, no error) and fields stay null.
# These accessors run inside this module where the full type definitions exist, so they can access
# all fields correctly; other modules get field values via function calls, bypassing the stub limits.
# ============================================================
def function_get_name(func: rawptr[Function]) -> str:
"""Get the function name"""
if func is None:
return None
return func.Name
def function_get_name_len(func: rawptr[Function]) -> sizet:
"""Get the function name length (computed once at creation, always == strlen(Name)).
Mirrors function_get_name's cross-module stub-bypass pattern: the user module only has a stub
Function type (missing fields, possibly wrong types), so reading Name/NameLen directly is
silently skipped. This accessor reads fields inside the full-type module and returns the length
via a call, avoiding repeated strlen on the same name (a program-wide strlen hot spot).
"""
if func is None:
return 0
return func.NameLen
def function_get_ret_ty(func: rawptr[Function]) -> rawptr[LLVMType]:
"""Get the function return type"""
if func is None:
return None
return func.RetTy
def function_get_params(func: rawptr[Function]) -> rawptr[GSList[Param]]:
"""Get the function parameter list container"""
if func is None:
return None
return func.Params
def function_get_param_head(func: rawptr[Function]) -> rawptr[Param]:
"""Get the function parameter list head (one step, avoiding GSList.Head access by callers)"""
if func is None:
return None
params: rawptr[GSList[Param]] = func.Params
if params is None:
return None
return params.Head
def function_get_next(func: rawptr[Function]) -> rawptr[Function]:
"""Get the next function in the list"""
if func is None:
return None
return func.Next
def function_is_declared(func: rawptr[Function]) -> int:
"""Get whether the function is a declaration (1=declare, 0=define)"""
if func is None:
return 0
return func.IsDeclared
def param_get_name(param: rawptr[Param]) -> str:
"""Get the parameter name"""
if param is None:
return None
return param.Name
def param_get_ty(param: rawptr[Param]) -> rawptr[LLVMType]:
"""Get the parameter type"""
if param is None:
return None
return param.Ty
def param_get_next(param: rawptr[Param]) -> rawptr[Param]:
"""Get the next parameter in the list"""
if param is None:
return None
return param.Next