按规范「注释只讲解作用,不掺杂历史原因,尽量英文」:把 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 顺序回归通过。
3650 lines
144 KiB
Plaintext
3650 lines
144 KiB
Plaintext
import t, c
|
||
from stdint import *
|
||
import string
|
||
import viperlib
|
||
import memhub
|
||
import stdio
|
||
import stdlib
|
||
from linkedlist import GSListNode, GSList
|
||
from .__types import (
|
||
LLVMType, 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
|