现场(位拷贝早期实现写成 `load i64, ptr %xv`):
697:16: error: use of value '%x' expects different type than prior uses: 'i64' vs 'f64'
llvm.store %x, %0 : i64, !llvm.ptr
—— T2 转发是纯**按名字**替换的:把 `store double %x, ptr %xv` 的 %x 顶进了那条 i64 行。
- 扫描时同时记录 store 存值的**类型文本**(括号配平读)与每个 load 的**声明类型文本**
(同样括号配平,读到第一个顶层 ',' 或空格);
- 仅当**所有 load 的声明类型都与存值类型一致**时才转发,否则整槽放弃(保守,IR 始终合法);
- 同类型槽(标量/指针/聚合的常见情形)转发不受影响:聚合两侧读法同口径,不会因文本截断白丢。
4097 lines
163 KiB
Plaintext
4097 lines
163 KiB
Plaintext
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 novirtual, 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 novirtual (same reason as Line).
|
||
# ============================================================
|
||
novirtual class BasicBlock(GSListNode[BasicBlock]):
|
||
Name: str # block name (e.g. "entry"/"if.then")
|
||
Lines: 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 novirtual (same reason as Line).
|
||
# ============================================================
|
||
novirtual class Param(GSListNode[Param]):
|
||
Ty: 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 novirtual (same reason as Line).
|
||
# ============================================================
|
||
novirtual class Function(GSListNode[Function]):
|
||
Name: str # function name
|
||
RetTy: LLVMType* # return type
|
||
Params: GSList[Param]* # parameter list container
|
||
Blocks: 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); rendered as MLIR passthrough = [...]
|
||
AttrGroup: str # string attr with a value, e.g. '"section"=".text.startup"'; rendered inline as `section = "..."` (None=none)
|
||
AttrGroupId: int # llvmlite #N group id (unused by MLIR printing; the attr is inlined instead)
|
||
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")
|
||
PeepDone: int # 1=local peephole already applied to this body (runs once; see FunctionPrint)
|
||
|
||
|
||
# ============================================================
|
||
# 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) -> Line*:
|
||
"""Create an instruction line, copying text into a newly allocated buffer"""
|
||
ptr: 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) -> rawptr:
|
||
"""Allocate and zero-init a GSList container from pool (Head/Tail/Count all zero)"""
|
||
gsl: rawptr = 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) -> BasicBlock*:
|
||
"""Create a basic block"""
|
||
ptr: 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: LLVMType*,
|
||
name: str) -> Param*:
|
||
"""Create a function parameter"""
|
||
ptr: 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) + PeepDone(8) = 96 bytes (same layout as llvmlite)
|
||
define FUNC_SIZE = 96
|
||
|
||
def new_function(pool: memhub.MemBuddy*, name: str,
|
||
ret_ty: LLVMType*) -> Function*:
|
||
"""Create a function"""
|
||
ptr: 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
|
||
ptr.PeepDone = 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: Function*, param: 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: GSList[Param]* = func.Params
|
||
if params is None: return
|
||
params.append(param)
|
||
|
||
|
||
def function_add_block(func: Function*, block: BasicBlock*):
|
||
"""Append a basic block to the function's block list"""
|
||
if func is None or block is None: return
|
||
blocks: GSList[BasicBlock]* = func.Blocks
|
||
if blocks is None: return
|
||
blocks.append(block)
|
||
|
||
|
||
def function_move_block_to_end(func: Function*, block: 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: 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: 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: BasicBlock*, line: Line*):
|
||
"""Append an instruction line to the basic block"""
|
||
if block is None or line is None: return
|
||
lines: GSList[Line]* = block.Lines
|
||
if lines is None: return
|
||
lines.append(line)
|
||
|
||
|
||
def block_set_terminated(block: 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: 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: BasicBlock*,
|
||
text: str):
|
||
"""Create an instruction line and append it to the basic block"""
|
||
if block is None or text is None: return
|
||
line: 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: 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: 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: 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: 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: 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: 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: 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: 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.
|
||
# => VPC'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 render: empty symbol name")
|
||
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
|
||
# - ViperC 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 uint64(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 int*(uint64), then dereferences with defef.
|
||
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: int* = 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 int*(uint64) and assigns through defefas.
|
||
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: int* = int*(byte_off)
|
||
*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: 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: GSList[BasicBlock]* = func.Blocks
|
||
blk: 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] cyclic list: func blocks (func name=%s, ssa scan aborted)\n", func.Name)
|
||
stdlib.exit(1)
|
||
if blk.Lines is not None:
|
||
lines: GSList[Line]* = blk.Lines
|
||
line: 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] cyclic list: block lines (func name=%s, block=%s, ssa scan aborted)\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: 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: GSList[BasicBlock]* = func.Blocks
|
||
blk: 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] cyclic list: func blocks (func name=%s, ssa renumber aborted)\n", func.Name)
|
||
stdlib.exit(1)
|
||
if blk.Lines is not None:
|
||
lines: GSList[Line]* = blk.Lines
|
||
line: 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] cyclic list: block lines (func name=%s, block=%s, ssa renumber aborted)\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, int(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] cyclic list: func blocks (func name=%s, ssa replace aborted)\n", func.Name)
|
||
stdlib.exit(1)
|
||
if blk.Lines is not None:
|
||
lines: GSList[Line]* = blk.Lines
|
||
line: 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] cyclic list: block lines (func name=%s, block=%s, ssa replace aborted)\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 = 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 = 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(int(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 = 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: 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: GSList[BasicBlock]* = func.Blocks
|
||
blk: 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: Line* = None
|
||
if blk_t1.Lines is not None:
|
||
t1_lines: 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: BasicBlock* = blk
|
||
if entry_blk is None:
|
||
return
|
||
entry_lines: GSList[Line]* = entry_blk.Lines
|
||
if entry_lines is None:
|
||
return
|
||
e_line: 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
|
||
# store 存值的**类型文本**:T2 只做"同类型转发"(见下方 Criteria 前的类型检查)
|
||
st_val_ty: str = None
|
||
st_ok: int = 0
|
||
st_count: int = 0
|
||
st_line_order: int = -1
|
||
load_ltoks: str = pool.alloc(8 * 64)
|
||
# 每个 load 的**声明类型文本**(与 load_ltoks 同索引的并行槽)
|
||
load_types: 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: Line* = None
|
||
if blk_s.Lines is not None:
|
||
scan_lines: 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:
|
||
# store 行是 `store <val_ty> <val>, <ptr_ty> <ptr>`。值类型可能是聚合
|
||
# (如 `!llvm.struct<"n", (i32, i32)>`),其括号内含 ", ";直接 strstr(", ")
|
||
# 会在类型内部切断,把类型前缀当值转发出去(畸形 IR:`store <t> !llvm.struct<"n" , …`)。
|
||
# 先按括号配平跳过值类型,再取真正的值记号。
|
||
vp: str = sb + 6
|
||
if string.strncmp(vp, "volatile ", 9) == 0: vp = vp + 9
|
||
vty0: str = vp
|
||
vdepth: int = 0
|
||
while vp[0] != '\0':
|
||
vch: int = vp[0]
|
||
if vch == '<' or vch == '(' or vch == '[' or vch == '{':
|
||
vdepth += 1
|
||
elif vch == '>' or vch == ')' or vch == ']' or vch == '}':
|
||
if vdepth > 0: vdepth -= 1
|
||
elif vdepth == 0 and vch == ' ':
|
||
break
|
||
vp = vp + 1
|
||
# 存值类型文本 = [vty0, 该空格):T2 的类型检查基准
|
||
vty_len: sizet = sizet(uint64(vp) - uint64(vty0))
|
||
vty_buf: str = pool.alloc(vty_len + 1)
|
||
if vty_buf is not None:
|
||
string.strncpy(vty_buf, vty0, vty_len)
|
||
vty_buf[vty_len] = '\0'
|
||
st_val_ty = vty_buf
|
||
else: unsafe = 1
|
||
while vp[0] == ' ': vp = vp + 1
|
||
vcomma: str = string.strstr(vp, ", ")
|
||
if vcomma is not None:
|
||
vlen: sizet = sizet(uint64(vcomma) - uint64(vp))
|
||
if vlen > 0:
|
||
vbuf: str = pool.alloc(vlen + 1)
|
||
if vbuf is not None:
|
||
string.strncpy(vbuf, vp, 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 的**声明类型文本**(括号配平读到第一个顶层 ','),
|
||
# 与 load_ltoks 同索引:T2 只做同类型转发(类型不一致 ⇒ 放弃)。
|
||
# 配平口径与 store 侧读值类型一致,聚合类型两端文本才会相等。
|
||
lty0: str = string.strstr(sb, " = load ") + 8
|
||
lty_p2: str = lty0
|
||
lty_depth: int = 0
|
||
while lty_p2[0] != '\0':
|
||
lch: int = lty_p2[0]
|
||
if lch == '<' or lch == '(' or lch == '[' or lch == '{':
|
||
lty_depth += 1
|
||
elif lch == '>' or lch == ')' or lch == ']' or lch == '}':
|
||
if lty_depth > 0: lty_depth -= 1
|
||
elif lty_depth == 0 and (lch == ',' or lch == ' '):
|
||
break
|
||
lty_p2 = lty_p2 + 1
|
||
lty_len: sizet = sizet(uint64(lty_p2) - uint64(lty0))
|
||
lty_buf: str = pool.alloc(lty_len + 1)
|
||
if lty_buf is not None:
|
||
string.strncpy(lty_buf, lty0, lty_len)
|
||
lty_buf[lty_len] = '\0'
|
||
sloty: str* = (str*)(uint64(load_types) + load_cnt * 8)
|
||
sloty[0] = lty_buf
|
||
else: unsafe = 1
|
||
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
|
||
# T2 类型感知:仅当**每个 load 的声明类型都与 store 存值的类型一致**时才转发。
|
||
# 缺这条时,跨类型 load(例如位拷贝曾写成 `load i64, ptr %xv`)会被按名字替换成
|
||
# f64 的存值,产出非法 IR:
|
||
# use of value '%x' expects different type than prior uses: 'i64' vs 'f64'
|
||
# 类型不一致 ⇒ 整个槽放弃转发(保守,IR 始终合法;只是少做一次转发优化)。
|
||
if unsafe == 0:
|
||
if st_val_ty is None or load_types is None: unsafe = 1
|
||
else:
|
||
lty_i: int = 0
|
||
while lty_i < load_cnt:
|
||
lty_p: str* = (str*)(uint64(load_types) + lty_i * 8)
|
||
lty_s: str = lty_p[0]
|
||
if lty_s is None or string.strcmp(lty_s, st_val_ty) != 0: unsafe = 1
|
||
lty_i += 1
|
||
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(int(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: Line* = None
|
||
if blk_r.Lines is not None:
|
||
repl_lines: 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: Line* = None
|
||
if blk_d.Lines is not None:
|
||
kill_lines: 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: 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: BasicBlock* = func.Blocks.Head
|
||
while blk is not None:
|
||
ln: Line* = None
|
||
if blk.Lines is not None:
|
||
ln = blk.Lines.Head
|
||
while ln is not None:
|
||
nxt: 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: BasicBlock* = func.Blocks.Head
|
||
while blk_r is not None:
|
||
if blk_r.Lines is not None:
|
||
ln2: 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: Function* = None
|
||
_MLIR_SrcBlk: BasicBlock* = None
|
||
|
||
# Constant table: (MLIR type text, constant text) -> materialized SSA name
|
||
_MLIR_ConstTy: rawptr = None
|
||
_MLIR_ConstVal: rawptr = None
|
||
_MLIR_ConstName: rawptr = None
|
||
_MLIR_ConstCount: int = 0
|
||
|
||
# Next display number for auto constant SSA names (%c<seq>): decoupled from the table index
|
||
# _MLIR_ConstCount so the number can skip user SSA names literally named c0/c1/... (see
|
||
# _MlirConstGet / _MlirCollectUsedCNames), while identical constants still share one name.
|
||
_MLIR_ConstNameSeq: int = 0
|
||
# Used-name table (i32[MLIR_CONST_CAP]): slot d = 1 iff the current function already uses an SSA
|
||
# name c<d> (param or local), so the constant namer must skip d.
|
||
_MLIR_UsedC: rawptr = None
|
||
|
||
# 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: rawptr = None
|
||
_MLIR_AliasTo: rawptr = 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 print window and reused across every FunctionPrint
|
||
# in it (the tables/scratch are ~150 KiB with buddy rounding, so allocating them per function filled
|
||
# the 1 GB main pool for function-heavy modules; see _MlirTablesInit / MlirTablesReset). 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
|
||
_SC_Attr: str = None
|
||
|
||
# Reuse guard: 1 once the tables/scratch above are live for _MlirTablesPool. _MlirTablesInit
|
||
# allocates only when the guard is clear; every FunctionPrint in the same print window then reuses
|
||
# the buffers (logical state is reset per function). MlirTablesReset clears the guard at PrintModule
|
||
# entry so a window whose pool was reset_to'd reallocates from the current pool (a cached pointer
|
||
# into a reclaimed window would dangle).
|
||
_MlirTablesValid: int = 0
|
||
_MlirTablesPool: memhub.MemBuddy* = None
|
||
|
||
# Live capacities of the constant/alias tables: start at the define defaults and grow on demand
|
||
# (_MlirAliasGrow / _MlirConstGrow), instead of hard-failing the whole compile when a big function
|
||
# needs more entries than the fixed caps allow. _MLIR_ConstCap also bounds the auto constant name
|
||
# space (%c<seq>) and the used-name table, which used to be nailed to MLIR_CONST_CAP.
|
||
_MLIR_AliasCap: int = MLIR_ALIAS_CAP
|
||
_MLIR_ConstCap: int = MLIR_CONST_CAP
|
||
|
||
|
||
def MlirTablesReset():
|
||
"""Invalidate the parse-time tables/scratch so the next FunctionPrint reallocates from the pool.
|
||
|
||
Mirrors the global-region scratch pattern (MlirGvResetScratch): a print window may run after the
|
||
pool was reset_to'd, so cached pointers must not be reused across windows. Called at PrintModule
|
||
entry; FunctionPrint called standalone keeps its buffers (its caller owns the pool lifetime).
|
||
"""
|
||
global _MlirTablesValid, _SC_Op
|
||
_MlirTablesValid = 0
|
||
_SC_Op = None
|
||
|
||
|
||
def _mlir_slotp(buf: rawptr, 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 render: text dup allocation failed")
|
||
string.strncpy(b, s, n)
|
||
b[n] = '\0'
|
||
return b
|
||
|
||
|
||
def _MlirTablesInit(pool: memhub.MemBuddy*):
|
||
"""Initialize (or reuse) the constant/alias tables and the parse scratch buffers.
|
||
|
||
The buffers live for the whole print window: the first FunctionPrint allocates them and every
|
||
later one reuses them, resetting only the logical state (counts + used-name table). Re-allocating
|
||
the ~150 KiB set per function (once per function, per dump mode) exhausted the 1 GB main pool on
|
||
function-heavy modules; the equivalent global-region scratch (MlirGvEnsureScratch) already reuses
|
||
this way. Callers that may reset the pool between prints must call MlirTablesReset first.
|
||
"""
|
||
global _MLIR_Pool, _MLIR_Pass, _MLIR_ConstTy, _MLIR_ConstVal, _MLIR_ConstName, _MLIR_ConstCount, _MLIR_ConstNameSeq, _MLIR_UsedC, _MLIR_ConstCap, _MLIR_AliasFrom, _MLIR_AliasTo, _MLIR_AliasCount, _MLIR_AliasCap, _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, _SC_Attr, _MlirTablesValid, _MlirTablesPool
|
||
if pool is None:
|
||
MlirFail("MLIR render: memory pool is null")
|
||
if _MlirTablesValid != 0 and _MlirTablesPool == pool:
|
||
# Reuse the live buffers: reset the per-function logical state. Entries past the reset counts
|
||
# are unreachable (all lookups scan [0, count)), so no table body needs clearing; only the
|
||
# used-name bitmap must be zeroed because _MlirCollectUsedCNames only ever sets bits.
|
||
_MLIR_Pool = pool
|
||
_MLIR_Pass = 0
|
||
_MLIR_ConstCount = 0
|
||
_MLIR_ConstNameSeq = 0
|
||
_MLIR_AliasCount = 0
|
||
string.memset(_MLIR_UsedC, 0, _MLIR_ConstCap * 4)
|
||
return
|
||
_MLIR_Pool = pool
|
||
_MLIR_Pass = 0
|
||
_MLIR_ConstCount = 0
|
||
_MLIR_ConstNameSeq = 0
|
||
_MLIR_AliasCount = 0
|
||
_MLIR_ConstCap = MLIR_CONST_CAP
|
||
_MLIR_ConstTy = pool.alloc(_MLIR_ConstCap * 8)
|
||
_MLIR_ConstVal = pool.alloc(_MLIR_ConstCap * 8)
|
||
_MLIR_ConstName = pool.alloc(_MLIR_ConstCap * 8)
|
||
_MLIR_UsedC = pool.alloc(_MLIR_ConstCap * 4)
|
||
_MLIR_AliasCap = MLIR_ALIAS_CAP
|
||
_MLIR_AliasFrom = pool.alloc(_MLIR_AliasCap * 8)
|
||
_MLIR_AliasTo = pool.alloc(_MLIR_AliasCap * 8)
|
||
if _MLIR_ConstTy is None or _MLIR_ConstVal is None or _MLIR_ConstName is None \
|
||
or _MLIR_UsedC is None or _MLIR_AliasFrom is None or _MLIR_AliasTo is None:
|
||
MlirFail("MLIR render: constant/alias table allocation failed")
|
||
string.memset(_MLIR_ConstTy, 0, _MLIR_ConstCap * 8)
|
||
string.memset(_MLIR_ConstVal, 0, _MLIR_ConstCap * 8)
|
||
string.memset(_MLIR_ConstName, 0, _MLIR_ConstCap * 8)
|
||
string.memset(_MLIR_UsedC, 0, _MLIR_ConstCap * 4)
|
||
string.memset(_MLIR_AliasFrom, 0, _MLIR_AliasCap * 8)
|
||
string.memset(_MLIR_AliasTo, 0, _MLIR_AliasCap * 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)
|
||
_SC_Attr = pool.alloc(128)
|
||
if _SC_Op is None or _SC_Ty is None or _SC_Mt is None or _SC_Tmp is None:
|
||
MlirFail("MLIR render: parse scratch allocation failed")
|
||
_SC_Op[0] = '\0'
|
||
_SC_Ty[0] = '\0'
|
||
_SC_Mt[0] = '\0'
|
||
_SC_Tmp[0] = '\0'
|
||
_MlirTablesPool = pool
|
||
_MlirTablesValid = 1
|
||
|
||
|
||
# ============================================================
|
||
# 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(int(_MLIR_AliasCount)):
|
||
sp: str* = _mlir_slotp(_MLIR_AliasFrom, int(i))
|
||
if sp[0] is not None and string.strcmp(sp[0], name) == 0:
|
||
st: str* = _mlir_slotp(_MLIR_AliasTo, int(i))
|
||
return st[0]
|
||
return None
|
||
|
||
|
||
def _MlirAliasGrow():
|
||
"""扩容 alias 表:池内分配新块并拷贝旧内容,容量翻倍(旧块留在池里,不回收)。
|
||
|
||
原实现把上限写死为 MLIR_ALIAS_CAP(512),超出直接 MlirFail 中断整次编译——
|
||
这是资源限制,不该让编译失败。大函数里 no-op bitcast(别名)条数会超过它。
|
||
"""
|
||
global _MLIR_AliasFrom, _MLIR_AliasTo, _MLIR_AliasCap
|
||
old_cap: int = _MLIR_AliasCap
|
||
nb_from: bytes = _MLIR_Pool.alloc(old_cap * 16)
|
||
nb_to: bytes = _MLIR_Pool.alloc(old_cap * 16)
|
||
if nb_from is None or nb_to is None:
|
||
MlirFail("MLIR render: alias table growth allocation failed")
|
||
string.memcpy(nb_from, _MLIR_AliasFrom, old_cap * 8)
|
||
string.memcpy(nb_to, _MLIR_AliasTo, old_cap * 8)
|
||
string.memset(nb_from + old_cap * 8, 0, old_cap * 8)
|
||
string.memset(nb_to + old_cap * 8, 0, old_cap * 8)
|
||
_MLIR_AliasFrom = nb_from
|
||
_MLIR_AliasTo = nb_to
|
||
_MLIR_AliasCap = old_cap * 2
|
||
|
||
|
||
def _MlirConstGrow():
|
||
"""扩容常量表 + 自动常量名空间(%c<seq>)+ 已用名表:池内分配翻倍新块并拷贝旧内容。
|
||
|
||
原实现三处都钉死在 MLIR_CONST_CAP(512):常量表满 → MlirFail("constant table overflow")、
|
||
%c0..%c511 用尽 → MlirFail、以及 _MlirMarkUsedCName 里"更大的后缀不可能冲突"的假设。
|
||
这些只是资源限制,不该中断编译,故统一按需扩容。
|
||
"""
|
||
global _MLIR_ConstTy, _MLIR_ConstVal, _MLIR_ConstName, _MLIR_UsedC, _MLIR_ConstCap
|
||
old_cap: int = _MLIR_ConstCap
|
||
nb_ty: bytes = _MLIR_Pool.alloc(old_cap * 16)
|
||
nb_val: bytes = _MLIR_Pool.alloc(old_cap * 16)
|
||
nb_nm: bytes = _MLIR_Pool.alloc(old_cap * 16)
|
||
nb_used: bytes = _MLIR_Pool.alloc(old_cap * 8)
|
||
if nb_ty is None or nb_val is None or nb_nm is None or nb_used is None:
|
||
MlirFail("MLIR render: constant table growth allocation failed")
|
||
string.memcpy(nb_ty, _MLIR_ConstTy, old_cap * 8)
|
||
string.memcpy(nb_val, _MLIR_ConstVal, old_cap * 8)
|
||
string.memcpy(nb_nm, _MLIR_ConstName, old_cap * 8)
|
||
string.memcpy(nb_used, _MLIR_UsedC, old_cap * 4)
|
||
string.memset(nb_ty + old_cap * 8, 0, old_cap * 8)
|
||
string.memset(nb_val + old_cap * 8, 0, old_cap * 8)
|
||
string.memset(nb_nm + old_cap * 8, 0, old_cap * 8)
|
||
string.memset(nb_used + old_cap * 4, 0, old_cap * 4)
|
||
_MLIR_ConstTy = nb_ty
|
||
_MLIR_ConstVal = nb_val
|
||
_MLIR_ConstName = nb_nm
|
||
_MLIR_UsedC = nb_used
|
||
_MLIR_ConstCap = old_cap * 2
|
||
|
||
|
||
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_AliasCap:
|
||
_MlirAliasGrow()
|
||
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 _MlirIsSsaNameChar(ch: char) -> int:
|
||
"""Return 1 if ch may appear in a bare SSA/symbol name [A-Za-z0-9_$.] (mirrors _ll_name_needs_quote)."""
|
||
if ch >= 'a' and ch <= 'z': return 1
|
||
if ch >= 'A' and ch <= 'Z': return 1
|
||
if ch >= '0' and ch <= '9': return 1
|
||
if ch == '_' or ch == '$' or ch == '.': return 1
|
||
return 0
|
||
|
||
|
||
def _usedc_set(idx: int, val: int):
|
||
"""Write the used-name table slot (_MLIR_UsedC is an i32[MLIR_CONST_CAP] via explicit byte offset)."""
|
||
if _MLIR_UsedC is None:
|
||
return
|
||
elem_ptr: int* = int*(uint64(_MLIR_UsedC) + idx * 4)
|
||
*elem_ptr = val
|
||
|
||
|
||
def _usedc_get(idx: int) -> int:
|
||
"""Read the used-name table slot (explicit byte offset, same convention as _map_get_i32)."""
|
||
if _MLIR_UsedC is None:
|
||
return 0
|
||
elem_ptr: int* = int*(uint64(_MLIR_UsedC) + idx * 4)
|
||
return *elem_ptr
|
||
|
||
|
||
def _MlirMarkUsedCName(name: str):
|
||
"""Mark name (an SSA name, with or without a leading '%') as used iff it is exactly c<digits>.
|
||
|
||
自动常量名 %c<seq> 的编号空间会按需扩容(见 _MlirConstGrow),所以这里记录**任意** c<digits>
|
||
后缀:越界的编号先扩容已用名表再置位,否则扩出来的编号可能与该函数自带的 c<N> 撞名。
|
||
"""
|
||
if name is None:
|
||
return
|
||
p: str = name
|
||
if p[0] == '%':
|
||
p = p + 1
|
||
if p[0] != 'c':
|
||
return
|
||
q: str = p + 1
|
||
d: int = 0
|
||
nd: int = 0
|
||
while _is_digit_ch(q[0]) != 0:
|
||
if d < MLIR_CONST_CAP:
|
||
d = d * 10 + (int(q[0]) - 48)
|
||
nd += 1
|
||
q = q + 1
|
||
if nd == 0:
|
||
return
|
||
if _MlirIsSsaNameChar(q[0]) != 0:
|
||
return
|
||
if d < MLIR_CONST_CAP:
|
||
_usedc_set(d, 1)
|
||
|
||
|
||
def _MlirScanUsedCText(text: str):
|
||
"""Mark every user SSA name of the form %c<digits> occurring in one intermediate line."""
|
||
if text is None:
|
||
return
|
||
p: str = text
|
||
while p[0] != '\0':
|
||
if p[0] == '%':
|
||
cs: str = p + 1
|
||
if cs[0] == 'c':
|
||
e: str = p + 2
|
||
d: int = 0
|
||
nd: int = 0
|
||
while _is_digit_ch(e[0]) != 0:
|
||
# 上限只用于防溢出:记录任何 c<digits>(名字空间会按需扩容,
|
||
# 不能再假设"更大的后缀不会与自动名冲突")
|
||
if d < 100000000:
|
||
d = d * 10 + (int(e[0]) - 48)
|
||
nd += 1
|
||
e = e + 1
|
||
if nd > 0 and _MlirIsSsaNameChar(e[0]) == 0:
|
||
if d >= _MLIR_ConstCap:
|
||
_MlirConstGrow()
|
||
_usedc_set(d, 1)
|
||
p = e
|
||
continue
|
||
p = p + 1
|
||
|
||
|
||
def _MlirCollectUsedCNames(func: Function*):
|
||
"""Mark all user SSA names of the form c<digits> in func (params + every intermediate line).
|
||
|
||
The constant materializer auto-names constants %c<seq>; a param or local literally named c1/c2
|
||
(legal Viper identifiers) would otherwise collide with it and mlir-opt reports
|
||
"redefinition of SSA value '%c1'". This pass records the exact used names so _MlirConstGet skips
|
||
those numbers; user names are never rewritten and there is no silent fallback (fail-fast below).
|
||
"""
|
||
if func is None:
|
||
return
|
||
params: GSList[Param]* = func.Params
|
||
if params is not None:
|
||
cur: Param* = params.Head
|
||
steps: int = 0
|
||
while cur is not None:
|
||
steps += 1
|
||
if steps > IR_DUMP_CHAIN_LIMIT:
|
||
MlirFail("MLIR render: parameter chain may be cyclic")
|
||
_MlirMarkUsedCName(cur.Name)
|
||
cur = cur.Next
|
||
blocks: GSList[BasicBlock]* = func.Blocks
|
||
if blocks is None:
|
||
return
|
||
blk: BasicBlock* = blocks.Head
|
||
blk_steps: int = 0
|
||
while blk is not None:
|
||
blk_steps += 1
|
||
if blk_steps > IR_DUMP_CHAIN_LIMIT:
|
||
MlirFail("MLIR render: function block chain may be cyclic")
|
||
if blk.Lines is not None:
|
||
lines: GSList[Line]* = blk.Lines
|
||
ln: Line* = lines.Head
|
||
ln_steps: int = 0
|
||
while ln is not None:
|
||
ln_steps += 1
|
||
if ln_steps > IR_DUMP_CHAIN_LIMIT:
|
||
MlirFail("MLIR render: block instruction chain may be cyclic")
|
||
_MlirScanUsedCText(ln.Buf)
|
||
ln = ln.Next
|
||
blk = blk.Next
|
||
|
||
|
||
def _MlirConstGet(tytext: str, valtext: str) -> str:
|
||
"""Get (or create) the constant's materialized name %cN"""
|
||
global _MLIR_ConstCount, _MLIR_ConstNameSeq
|
||
for i in range(int(_MLIR_ConstCount)):
|
||
sp: str* = _mlir_slotp(_MLIR_ConstTy, int(i))
|
||
if sp[0] is not None and string.strcmp(sp[0], tytext) == 0:
|
||
sv: str* = _mlir_slotp(_MLIR_ConstVal, int(i))
|
||
if sv[0] is not None and string.strcmp(sv[0], valtext) == 0:
|
||
sn: str* = _mlir_slotp(_MLIR_ConstName, int(i))
|
||
return sn[0]
|
||
if _MLIR_Pass == 2:
|
||
# pass1 already scanned all instruction lines; no new constant should appear here
|
||
MlirFail("MLIR render: unmaterialized constant in emit pass (scan missed it)")
|
||
if _MLIR_ConstCount >= _MLIR_ConstCap:
|
||
_MlirConstGrow()
|
||
nm: str = _MLIR_Pool.alloc(32)
|
||
if nm is None:
|
||
MlirFail("MLIR render: constant name allocation failed")
|
||
# Auto name %c<seq>: skip numbers already used by the function's own SSA names (c0/c1/...).
|
||
# seq is strictly increasing, so names stay unique; it is decoupled from the table index, and the
|
||
# (type,value) cache above still makes identical constants reuse the same name.
|
||
seq: int = _MLIR_ConstNameSeq
|
||
while seq < _MLIR_ConstCap and _usedc_get(seq) != 0:
|
||
seq += 1
|
||
if seq >= _MLIR_ConstCap:
|
||
# 名字空间同样按需扩容:原来钉死在 %c0..%c511,用尽就 MlirFail
|
||
_MlirConstGrow()
|
||
while seq < _MLIR_ConstCap and _usedc_get(seq) != 0:
|
||
seq += 1
|
||
_MLIR_ConstNameSeq = seq + 1
|
||
viperlib.snprintf(nm, 32, "%%c%d", seq)
|
||
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 render: cmpxchg result name allocation failed")
|
||
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 render: hex float literal type is none of 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:
|
||
# 报文带上 (MLIR 类型, 常量文本, 实际位数):仅说"宽度不匹配"无法定位是哪条常量
|
||
# (此前现场只能靠二分源码找);与 _MlirConstFail 同风格。
|
||
vlen: sizet = string.strlen(val)
|
||
tlen: sizet = string.strlen(mt)
|
||
dbg: str = _MLIR_Pool.alloc(vlen + tlen + 128)
|
||
if dbg is not None:
|
||
viperlib.snprintf(dbg, vlen + tlen + 128, "MLIR render: float bit-pattern width mismatches type (type '%s' needs %d hex digits, value '%s' has %d)", mt, want, val, nd)
|
||
MlirFail(dbg)
|
||
MlirFail("MLIR render: float bit-pattern width mismatches type (f16/f32/f64/f128 need 4/8/16/32 hex digits respectively)")
|
||
return val
|
||
# Already decimal text: use as-is
|
||
return val
|
||
|
||
|
||
def _MlirConstFail(tytext: str, valtext: str):
|
||
"""Unsupported constant form -> loud failure including the type and value.
|
||
|
||
MlirFail gives no backtrace, so print (MLIR 类型, 常量文本) — otherwise an
|
||
"unsupported constant operand form" is unattributable (which pointer/aggregate constant,
|
||
and from which operand shape). Supports i/f literals, true/false, null/zeroinitializer and
|
||
`@sym`; anything else (e.g. a pointer/aggregate-typed literal or a constant expression)
|
||
lands here and must be lowered explicitly.
|
||
"""
|
||
tlen: sizet = 0
|
||
vlen: sizet = 0
|
||
if tytext is not None: tlen = string.strlen(tytext)
|
||
if valtext is not None: vlen = string.strlen(valtext)
|
||
bsize: sizet = tlen + vlen + 160
|
||
buf: str = _MLIR_Pool.alloc(bsize)
|
||
if buf is None:
|
||
MlirFail("MLIR render: unsupported constant operand form")
|
||
viperlib.snprintf(buf, bsize, "MLIR render: unsupported constant operand form (type '%s', value '%s')", tytext, valtext)
|
||
MlirFail(buf)
|
||
|
||
|
||
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':
|
||
_MlirConstFail(mt, val)
|
||
_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(int(_MLIR_ConstCount)):
|
||
st: str* = _mlir_slotp(_MLIR_ConstTy, int(i))
|
||
sv: str* = _mlir_slotp(_MLIR_ConstVal, int(i))
|
||
sn: str* = _mlir_slotp(_MLIR_ConstName, int(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] = char(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] = char(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] = char(ch2)
|
||
n += 1
|
||
p = p + 1
|
||
d2 -= 1
|
||
if d2 <= 0:
|
||
break
|
||
continue
|
||
if n + 1 < outsz:
|
||
out[n] = char(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: Function*, name: str) -> 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: GSList[BasicBlock]* = func.Blocks
|
||
blk: BasicBlock* = blocks.Head
|
||
steps: int = 0
|
||
while blk is not None:
|
||
steps += 1
|
||
if steps > IR_DUMP_CHAIN_LIMIT:
|
||
MlirFail("MLIR render: block chain may be cyclic")
|
||
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 render: empty operand")
|
||
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 render: too many phi incoming edges")
|
||
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: 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 render: too many phi incoming edges")
|
||
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: 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: GSList[Line]* = blk.Lines
|
||
ln: 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: BasicBlock*, src: 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: GSList[Line]* = target.Lines
|
||
ln: 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 render: target block has phi but no incoming edge matches the source block")
|
||
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 render: target block has phi but no block arguments were generated")
|
||
_append_cstr(out, outsz, "(")
|
||
_append_cstr(out, outsz, tmp)
|
||
_append_cstr(out, outsz, ")")
|
||
|
||
|
||
def _MlirEmitBlockHeader(out: str, outsz: sizet, blk: 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: GSList[Line]* = blk.Lines
|
||
ln: 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 render: failed to parse leading 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: BasicBlock*):
|
||
"""Emit in-block instructions (skip leading phis, which became block params)"""
|
||
if blk is None or blk.Lines is None:
|
||
return
|
||
lines: GSList[Line]* = blk.Lines
|
||
ln: Line* = lines.Head
|
||
skipping: int = 1
|
||
steps: int = 0
|
||
while ln is not None:
|
||
steps += 1
|
||
if steps > IR_DUMP_CHAIN_LIMIT:
|
||
MlirFail("MLIR render: block instruction chain may be cyclic")
|
||
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: BasicBlock*):
|
||
"""entry block must not have phis (no predecessor, cannot become block params)"""
|
||
if _MlirBlockHasPhi(blk) != 0:
|
||
MlirFail("MLIR render: entry block must not have phi (MLIR entry block takes no block arguments)")
|
||
|
||
|
||
# ============================================================
|
||
# 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 render: alloca only supports the align attribute (unknown attribute)")
|
||
p = _mlir_ws(p + 5)
|
||
nd: int = 0
|
||
while p[0] >= '0' and p[0] <= '9':
|
||
if nd + 1 >= 512:
|
||
MlirFail("MLIR render: alloca alignment value too long (out of range)")
|
||
aligntxt[nd] = p[0]
|
||
nd += 1
|
||
p = p + 1
|
||
aligntxt[nd] = '\0'
|
||
p = _mlir_ws(p)
|
||
if p[0] != '\0':
|
||
MlirFail("MLIR render: extra token after alloca align")
|
||
if nd == 0:
|
||
MlirFail("MLIR render: alloca alignment must be a constant (decimal constant, not SSA)")
|
||
if nd > 10:
|
||
MlirFail("MLIR render: alloca alignment out of range (must be a power of 2 and <= 2^30)")
|
||
av: int = string.atoi(aligntxt)
|
||
if av <= 0 or av > 1073741824:
|
||
MlirFail("MLIR render: alloca alignment out of range (must be a power of 2 and <= 2^30)")
|
||
if (av & (av - 1)) != 0:
|
||
MlirFail("MLIR render: alloca alignment must be a power of 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 render: too many function type params in variadic 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 render: call missing argument list")
|
||
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 render: too many call arguments")
|
||
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 ")
|
||
# callee 也必须过 alias 解析:no-op bitcast(含函数指针 bitcast)被省略后,间接调用点
|
||
# 必须改写成别名目标,否则 %N 悬空 → mlir-opt 报 use of undeclared SSA value
|
||
# / definition has type 'i8' but previously used here with type '!llvm.ptr'。
|
||
calb_r: str = _MlirResolve(calb)
|
||
if calb_r is None: calb_r = calb
|
||
_append_cstr(out, outsz, calb_r)
|
||
_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_r[0] != '@':
|
||
# Indirect call: the callee is a !llvm.ptr operand
|
||
# (用解析后的名字判断:别名解析可能把 callee 指到一个 @符号,那就成了直接调用)
|
||
_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 "...", "..."()`
|
||
VPC 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: VPC'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 (VPC'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 render: inline_asm missing asm text (or contains an unknown keyword)")
|
||
p = _mlir_ws(p)
|
||
if p[0] != ',':
|
||
MlirFail("MLIR render: inline_asm missing constraint string (must be \"asm\", \"constraints\")")
|
||
p = _mlir_ws(p + 1)
|
||
conb: str = _SC_Args
|
||
p = _mlir_read_value(p, conb, 2048)
|
||
if conb[0] != '"':
|
||
MlirFail("MLIR render: inline_asm constraint string must be a string literal")
|
||
p = _mlir_ws(p)
|
||
if p[0] != '(':
|
||
MlirFail("MLIR render: inline_asm missing argument list '(...)'")
|
||
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 render: too many inline_asm arguments")
|
||
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 render: extra token after inline_asm argument list")
|
||
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 render: inline_asm has a return value but no result name (%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 render: no-op bitcast missing result name")
|
||
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 render: too many GEP indices")
|
||
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 render: GEP missing index")
|
||
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 render: too many switch branches")
|
||
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 render: switch case value must be a constant (SSA value not supported)")
|
||
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 render: cmpxchg missing result name")
|
||
# 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 render: va_arg missing result name")
|
||
# 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: VPC'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 render: too many extractvalue indices")
|
||
p = _mlir_ws(p + 1)
|
||
ib: str = _SC_IdxT
|
||
p = _mlir_read_value(p, ib, 512)
|
||
if ib[0] == '\0':
|
||
MlirFail("MLIR render: extractvalue missing index")
|
||
if ib[0] == '%':
|
||
MlirFail("MLIR render: extractvalue index must be a constant (SSA index not supported)")
|
||
if nidx > 0:
|
||
_append_cstr(idxs, 1024, ", ")
|
||
_append_cstr(idxs, 1024, ib)
|
||
nidx += 1
|
||
if nidx == 0:
|
||
MlirFail("MLIR render: extractvalue missing index")
|
||
if p[0] != '\0':
|
||
MlirFail("MLIR render: extra token after extractvalue index")
|
||
# 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 render: double-result prefix (%a, %b =) missing '='")
|
||
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 render: empty instruction")
|
||
if resname2 is not None and string.strcmp(ob, "cmpxchg") != 0:
|
||
MlirFail("MLIR render: double-result prefix (%a, %b =) only supports 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 render: phi is not at block head (v1 only supports leading consecutive phis -> block arguments)")
|
||
_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: VPC'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 render: unknown instruction opcode")
|
||
|
||
|
||
# ============================================================
|
||
# Function signature and body
|
||
# ============================================================
|
||
def _MlirFuncSig(buf: str, size: sizet,
|
||
func: 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: GSList[Param]* = func.Params
|
||
cur: 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 render: parameter chain may be cyclic")
|
||
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)
|
||
# Function attributes: MLIR carries them as an `attributes { ... }` clause right after the
|
||
# signature (see _MlirFuncAttrs). Placed here so both define and declare lines get them.
|
||
_MlirFuncAttrs(buf, size, func)
|
||
|
||
|
||
# ============================================================
|
||
# Function attribute rendering
|
||
# ============================================================
|
||
# MLIR (llvm dialect) expresses LLVM function attributes as an `attributes { ... }` clause on
|
||
# `llvm.func`. Measured against mlir-opt 22.1.8 + mlir-translate --mlir-to-llvmir:
|
||
# - keyword / quoted-keyword attrs (noinline / "stackrealign") -> `passthrough = ["<name>", ...]`
|
||
# (a token wrapped in LLVM quotes is a string attr; the surrounding quotes are stripped once)
|
||
# - a string attr with a value (section) -> `<name> = "<value>"`
|
||
# e.g. `llvm.func @f() attributes {section = ".text.startup", passthrough = ["noinline", "stackrealign"]}`
|
||
# lowers to `define ... #0 section ".text.startup"` + `attributes #0 = { noinline "stackrealign" }`.
|
||
def _MlirFuncAttrs(buf: str, size: sizet, func: Function*):
|
||
"""Append ` attributes { ... }` for func's function attrs, or nothing when none are set."""
|
||
has_attrs: int = 0
|
||
if func.Attrs is not None and func.Attrs[0] != '\0': has_attrs = 1
|
||
has_section: int = 0
|
||
if func.AttrGroup is not None: has_section = 1
|
||
if has_attrs == 0 and has_section == 0: return
|
||
_append_cstr(buf, size, " attributes {")
|
||
wrote: int = 0
|
||
if has_section != 0:
|
||
wrote = _MlirSectionAttr(buf, size, func.AttrGroup)
|
||
if has_attrs != 0:
|
||
if wrote != 0: _append_cstr(buf, size, ", ")
|
||
_append_cstr(buf, size, "passthrough = [")
|
||
_MlirPassthroughList(buf, size, func.Attrs)
|
||
_append_cstr(buf, size, "]")
|
||
_append_cstr(buf, size, "}")
|
||
|
||
|
||
def _MlirSectionAttr(buf: str, size: sizet, group: str) -> int:
|
||
"""Render the string attr group `"section"=".text.startup"` as MLIR `section = ".text.startup"`.
|
||
|
||
Returns 1 when an entry was written, 0 on an unrecognized/oversized group (caller skips it).
|
||
"""
|
||
if group is None: return 0
|
||
sep: str = string.strstr(group, "\"=\"")
|
||
if sep is None: return 0
|
||
# name = text between the leading quote and the separator's quote (e.g. section)
|
||
name_len: sizet = uint64(sep) - uint64(group) - 1
|
||
if name_len == 0 or name_len >= 120: return 0
|
||
name_buf: str = _SC_Attr
|
||
if name_buf is None: return 0
|
||
string.strncpy(name_buf, group + 1, name_len)
|
||
name_buf[name_len] = '\0'
|
||
_append_cstr(buf, size, name_buf)
|
||
_append_cstr(buf, size, " = ")
|
||
# value already carries its surrounding quotes: sep points at the '"' of '"="', so sep+2 is '"value"'
|
||
_append_cstr(buf, size, sep + 2)
|
||
return 1
|
||
|
||
|
||
def _MlirPassthroughList(buf: str, size: sizet, attrs: str):
|
||
"""Emit MLIR passthrough entries `"a", "b"` from a space-separated list of LLVM attr tokens."""
|
||
if attrs is None: return
|
||
n: sizet = string.strlen(attrs)
|
||
i: sizet = 0
|
||
first: int = 1
|
||
tok: str = _SC_Attr
|
||
if tok is None: return
|
||
while i < n:
|
||
while i < n and attrs[i] == ' ': i += 1
|
||
if i >= n: break
|
||
start: sizet = i
|
||
while i < n and attrs[i] != ' ': i += 1
|
||
end: sizet = i
|
||
# LLVM string attr: strip one surrounding pair of double quotes ("stackrealign" -> stackrealign)
|
||
if end - start >= 2 and attrs[start] == '"' and attrs[end - 1] == '"':
|
||
start += 1
|
||
end -= 1
|
||
k: sizet = 0
|
||
while start < end and k < 120:
|
||
tok[k] = attrs[start]
|
||
k += 1
|
||
start += 1
|
||
tok[k] = '\0'
|
||
if first == 0: _append_cstr(buf, size, ", ")
|
||
_append_cstr(buf, size, "\"")
|
||
_append_cstr(buf, size, tok)
|
||
_append_cstr(buf, size, "\"")
|
||
first = 0
|
||
|
||
|
||
def FunctionPrint(buf: str, size: sizet,
|
||
func: 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).
|
||
# Run at most ONCE per body: the module is printed several times (STUB/TEXT/FULL, see PrintModule)
|
||
# and the T2 forwarding is NOT idempotent — a second pass over the already-optimized text can
|
||
# drop an entry alloca whose use was only reachable through a line the first pass had removed,
|
||
# leaving dangling SSA names (mlir-opt: "op using value defined outside the region").
|
||
if func.IsDeclared == 0 and G_PeepEnable != 0 and func.PeepDone == 0:
|
||
_peephole_opt(func, pool)
|
||
func.PeepDone = 1
|
||
RenumberSSA(func, pool)
|
||
|
||
_MlirTablesInit(pool)
|
||
_MLIR_Func = func
|
||
# Record the function's own SSA names of the form c0/c1/... so the constant namer never picks a
|
||
# colliding %c<seq> (a user param/local literally named c1 would clash with a materialized const).
|
||
_MlirCollectUsedCNames(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: 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: BasicBlock* = blocks.Head
|
||
blk_steps: int = 0
|
||
while blk is not None:
|
||
blk_steps += 1
|
||
if blk_steps > IR_DUMP_CHAIN_LIMIT:
|
||
MlirFail("MLIR render: function block chain may be cyclic")
|
||
_MLIR_SrcBlk = blk
|
||
if blk.Lines is not None:
|
||
lines: GSList[Line]* = blk.Lines
|
||
ln: Line* = lines.Head
|
||
ln_steps: int = 0
|
||
while ln is not None:
|
||
ln_steps += 1
|
||
if ln_steps > IR_DUMP_CHAIN_LIMIT:
|
||
MlirFail("MLIR render: block instruction chain may be cyclic")
|
||
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 render: function block chain may be cyclic")
|
||
_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 render: output text truncated by buffer overflow (increase the dump_ir/FunctionPrint buffer)")
|
||
|
||
|
||
# ============================================================
|
||
# 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 ViperC 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 ViperC'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 debug record: function nesting depth exceeds limit (DBGC_STACK_MAX)")
|
||
if _DbgEnsureStack() != 0:
|
||
MlirFail("MLIR debug record: context stack allocation failed")
|
||
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 debug record: F record buffer allocation failed")
|
||
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 debug record: C record buffer allocation failed")
|
||
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: Function*) -> str:
|
||
"""Get the function name"""
|
||
if func is None:
|
||
return None
|
||
return func.Name
|
||
|
||
|
||
def function_get_name_len(func: 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: Function*) -> LLVMType*:
|
||
"""Get the function return type"""
|
||
if func is None:
|
||
return None
|
||
return func.RetTy
|
||
|
||
|
||
def function_get_params(func: Function*) -> GSList[Param]*:
|
||
"""Get the function parameter list container"""
|
||
if func is None:
|
||
return None
|
||
return func.Params
|
||
|
||
|
||
def function_get_param_head(func: Function*) -> Param*:
|
||
"""Get the function parameter list head (one step, avoiding GSList.Head access by callers)"""
|
||
if func is None:
|
||
return None
|
||
params: GSList[Param]* = func.Params
|
||
if params is None:
|
||
return None
|
||
return params.Head
|
||
|
||
|
||
def function_get_next(func: Function*) -> Function*:
|
||
"""Get the next function in the list"""
|
||
if func is None:
|
||
return None
|
||
return func.Next
|
||
|
||
|
||
def function_is_declared(func: 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: Param*) -> str:
|
||
"""Get the parameter name"""
|
||
if param is None:
|
||
return None
|
||
return param.Name
|
||
|
||
|
||
def param_get_ty(param: Param*) -> LLVMType*:
|
||
"""Get the parameter type"""
|
||
if param is None:
|
||
return None
|
||
return param.Ty
|
||
|
||
|
||
def param_get_next(param: Param*) -> Param*:
|
||
"""Get the next parameter in the list"""
|
||
if param is None:
|
||
return None
|
||
return param.Next
|