Files
Viper_VRTE/llvmlite/__function.vp
T
2026-09-16 16:07:35 +08:00

1421 lines
62 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import t, c
from stdint import *
import string
import viperlib
import memhub
import stdio
import stdlib
from linkedlist import GSListNode, GSList
from .__types import LLVMType, ParamNode, TypePrint
# dump 哨兵:IR 链表遍历步数上限(成环检测,fail-fast,与 __module.vp 对齐)。
# 函数/基本块/指令行链的 Next 被越界写破坏成环时,遍历无限循环挂起;
# 超过上限即打印链表名并 exit(1),把"卡死"变成"确定性报错定位"。
IR_DUMP_CHAIN_LIMIT: t.CDefine = 100000
# 窥孔优化总开关(T2 转发/ret 归零、T3 no-op bitcast 消除)。
# TPV 命令行 --no-llvmpeephole 会将其置 0:FunctionPrint 序列化时跳过全部本地窥孔,
# 便于与关闭窥孔的 IR 对比调试(RenumberSSA 仍保留,保证输出 SSA 编号合法)。
G_PeepEnable: bool = True
# ============================================================
# Line: 指令文本行
#
# 每条 LLVM IR 指令序列化为一行文本,通过链表组织。
# 继承 GSListNode[Line] 获取强类型 Next: Line|CPtr
#
# 注意: 必须加 @t.NoVTable。否则 TransPyC 为子类添加 vtable 指针(T*),
# 但完全丢失父类 GSListNode[T] 的 Next 字段,导致 struct 布局错位,
# 所有字段访问读取垃圾值(见续 XV 会话根因分析)。
# ============================================================
@t.NoVTable
class Line(GSListNode[Line]):
Buf: str # 行文本(NUL 结尾)
Len: t.CSizeT # 文本长度(不含 NUL)
# ============================================================
# BasicBlock: 基本块
#
# 持有名称 + 指令行链表 + 终止指令标志。
# 继承 GSListNode[BasicBlock] 获取强类型 Next: BasicBlock|CPtr
# Lines 为 GSList[Line] 指针,提供 O(1) 追加。
#
# 注意: 必须加 @t.NoVTable(理由同 Line)。
# ============================================================
@t.NoVTable
class BasicBlock(GSListNode[BasicBlock]):
Name: str # 块名(如 "entry"/"if.then")
Lines: rawptr[GSList[Line]] # 指令行链表容器(Head/Tail/Count)
IsTerminated: int # 1=已有终止指令(br/ret)
# ============================================================
# Param: 函数参数
#
# 继承 GSListNode[Param] 获取强类型 Next: Param|CPtr
# Attrs 存储参数属性文本(如 "noundef"/"nocapture"/"readonly"),None=无属性
#
# 注意: 必须加 @t.NoVTable(理由同 Line)。
# ============================================================
@t.NoVTable
class Param(GSListNode[Param]):
Ty: rawptr[LLVMType] # 参数类型
Name: str # 参数名(如 "%0"/"%a")
Attrs: str # 参数属性文本(None=无属性)
# ============================================================
# Function: 函数
#
# 持有名称 + 返回类型 + 参数链表 + 基本块链表。
# 继承 GSListNode[Function] 获取强类型 Next: Function|CPtr
# Params/Blocks 为 GSList 指针,提供 O(1) 追加。
# Attrs 存储函数级属性文本(如 "nounwind"/"noinline"),None=无属性
#
# 注意: 必须加 @t.NoVTable(理由同 Line)。
# ============================================================
@t.NoVTable
class Function(GSListNode[Function]):
Name: str # 函数名
RetTy: rawptr[LLVMType] # 返回类型
Params: rawptr[GSList[Param]] # 参数链表容器
Blocks: rawptr[GSList[BasicBlock]] # 基本块链表容器
IsDeclared: int # 1=仅声明(declare), 0=定义(define)
IsVarArg: int # 1=变参函数(...)
Attrs: str # 函数属性文本(None=无属性)
AttrGroup: str # 字符串属性组内容(如 '"section"=".text.startup"',None=无)
AttrGroupId: int # 属性组编号(>0 时函数行输出 #N,模块尾输出 attributes #N)
NameLen: t.CSizeT # 函数名长度(创建时一次算好,恒等于 strlen(Name);追加在末尾,既有字段偏移不变)
# ============================================================
# 工厂函数
# ============================================================
# Line 硬编码大小: Next(8) + Buf(8) + Len(8) = 24 字节
LINE_SIZE: t.CDefine = 24
def new_line(pool: rawptr[memhub.MemBuddy], text: str) -> rawptr[Line]:
"""创建一个指令行,复制 text 到新分配的缓冲区"""
ptr: rawptr[Line] = pool.alloc(LINE_SIZE)
if ptr is None: return None
string.memset(ptr, 0, LINE_SIZE)
if text is not None:
slen: t.CSizeT = 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: rawptr[memhub.MemBuddy], list_size: t.CSizeT) -> t.CPtr:
"""从 pool 分配并零初始化一个 GSList 容器(Head/Tail/Count 全零)"""
gsl: t.CPtr = pool.alloc(list_size)
if gsl is None: return None
string.memset(gsl, 0, list_size)
return gsl
# BasicBlock 硬编码大小: Next(8) + Name(8) + Lines(8) + IsTerminated(4) = 32 字节
BLOCK_SIZE: t.CDefine = 32
def new_basic_block(pool: rawptr[memhub.MemBuddy], name: str) -> rawptr[BasicBlock]:
"""创建一个基本块"""
ptr: rawptr[BasicBlock] = pool.alloc(BLOCK_SIZE)
if ptr is None: return None
string.memset(ptr, 0, BLOCK_SIZE)
ptr.Name = name
ptr.Lines = _new_gslist(pool, 24) # GSList: Head+Tail+Count
ptr.IsTerminated = 0
return ptr
# Param 硬编码大小: Next(8) + Ty(8) + Name(8) + Attrs(8) = 32 字节
PARAM_SIZE: t.CDefine = 32
def new_param(pool: rawptr[memhub.MemBuddy], ty: rawptr[LLVMType],
name: str) -> rawptr[Param]:
"""创建一个函数参数"""
ptr: rawptr[Param] = pool.alloc(PARAM_SIZE)
if ptr is None: return None
string.memset(ptr, 0, PARAM_SIZE)
ptr.Ty = ty
ptr.Name = name
ptr.Attrs = None
return ptr
# Function 硬编码大小: Next(8) + Name(8) + RetTy(8) + Params(8) + Blocks(8) + IsDeclared(4) + IsVarArg(4) + Attrs(8) + AttrGroup(8) + AttrGroupId(8) + NameLen(8) = 80 字节
FUNC_SIZE: t.CDefine = 80
def new_function(pool: rawptr[memhub.MemBuddy], name: str,
ret_ty: rawptr[LLVMType]) -> rawptr[Function]:
"""创建一个函数"""
ptr: rawptr[Function] = pool.alloc(FUNC_SIZE)
if ptr is None: return None
string.memset(ptr, 0, FUNC_SIZE)
ptr.Name = name
ptr.RetTy = ret_ty
ptr.Params = _new_gslist(pool, 24) # GSList: Head+Tail+Count
ptr.Blocks = _new_gslist(pool, 24)
ptr.IsDeclared = 0
ptr.Attrs = None
ptr.AttrGroup = None
ptr.AttrGroupId = 0
# NameLen 创建时一次算好:Name 终生不变(本文件仅此处赋值 ptr.Name),
# 故 NameLen 恒等于 strlen(Name),可替代对函数名的反复 strlen 热点。
# name=None 时置 0,与 new_line 的 "text is not None" 判空约定一致
# (memset 已归零,此处显式赋值以固定语义、不依赖分配器行为)。
if name is not None:
ptr.NameLen = string.strlen(name)
else:
ptr.NameLen = 0
return ptr
# ============================================================
# 链表操作(委托 GSList.append)
# ============================================================
def function_add_param(func: rawptr[Function], param: rawptr[Param]):
"""将参数追加到函数参数链表"""
if func is None or param is None: return
# 注意:必须先用局部变量 + 类型参数(对齐 function_move_block_to_end 注释),
# 链式 func.Params.append() 会被编译器解析成 LinkedNode.append 且 this 前移
# sizeof(LinkedNode)(兄弟 Mixin 兜底误匹配),写穿容器前 48 字节破坏数据结构。
params: rawptr[GSList[Param]] = func.Params
if params is None: return
params.append(param)
def function_add_block(func: rawptr[Function], block: rawptr[BasicBlock]):
"""将基本块追加到函数块链表"""
if func is None or block is None: return
blocks: rawptr[GSList[BasicBlock]] = func.Blocks
if blocks is None: return
blocks.append(block)
def function_move_block_to_end(func: rawptr[Function], block: rawptr[BasicBlock]):
"""将 block 移动到函数块链表末尾(保证 BB 文本顺序与控制流顺序一致)。
用于解决 SSA 名非单调问题:BB 按创建顺序排列,但指令按控制流顺序生成,
导致 SSA 编号在文本输出时非单调递增,llc 报错。
在 position_at_end 时调用此函数,使 BB 顺序与 position 顺序一致。
注意:必须使用 GSList[BasicBlock] 带类型参数,否则 TransPyC 创建不完整
struct 类型,导致 blocks.Head/Tail/Count 字段访问读取垃圾值。
"""
if func is None or block is None: return
blocks: rawptr[GSList[BasicBlock]] = func.Blocks
if blocks is None: return
# 已经是 Tail,无需移动
if blocks.Tail is block:
return
# 从链表中移除 block
if blocks.Head is block:
# block 是 Head,直接后移 Head
blocks.Head = block.Next
else:
# 遍历找前驱
prev: rawptr[BasicBlock] = blocks.Head
while prev is not None and prev.Next is not block:
prev = prev.Next
if prev is None:
# block 不在链表中,直接 append
blocks.append(block)
return
prev.Next = block.Next
# block.Next 由移除逻辑处理,重置为 None
block.Next = None
# append 到末尾(不调用 blocks.append 以避免 Count 重复递增)
if blocks.Tail is not None:
blocks.Tail.Next = block
blocks.Tail = block
def block_append_line(block: rawptr[BasicBlock], line: rawptr[Line]):
"""将指令行追加到基本块"""
if block is None or line is None: return
lines: rawptr[GSList[Line]] = block.Lines
if lines is None: return
lines.append(line)
def block_set_terminated(block: rawptr[BasicBlock], val: int):
"""设置块的终止标志(供跨模块调用,避免链式属性访问的 opaque 类型问题)"""
if block is None: return
block.IsTerminated = val
def block_get_terminated(block: rawptr[BasicBlock]) -> int:
"""获取块的终止标志(供跨模块调用,避免链式属性访问的 opaque 类型问题)"""
if block is None:
return 1
return block.IsTerminated
def block_append_text(pool: rawptr[memhub.MemBuddy], block: rawptr[BasicBlock],
text: str):
"""创建指令行并追加到基本块"""
if block is None or text is None: return
line: rawptr[Line] = new_line(pool, text)
if line is not None:
block_append_line(block, line)
def block_insert_text_before_terminator(pool: rawptr[memhub.MemBuddy],
block: rawptr[BasicBlock],
text: str):
"""创建指令行并插入到基本块终止指令之前
如果块未终止,追加到末尾。
如果块已终止(有 br/ret),在终止指令之前插入,确保 alloca 等指令
不出现在终止指令之后。
"""
if block is None or text is None: return
if block.Lines is None: return
line: rawptr[Line] = new_line(pool, text)
if line is None: return
# 块未终止:直接追加
if block.IsTerminated == 0:
block_append_line(block, line)
return
# 块已终止:在 Tail(终止指令)之前插入
lines: rawptr[GSList[Line]] = block.Lines
if lines.Head is None:
block_append_line(block, line)
return
# 只有一个节点:在 Head 之前插入
if lines.Head is lines.Tail:
line.Next = lines.Head
lines.Head = line
lines.Count += 1
return
# 找到 Tail 的前驱节点
prev: rawptr[Line] = lines.Head
while prev is not None and prev.Next is not lines.Tail:
prev = prev.Next
if prev is None:
# 找不到前驱,回退到追加
block_append_line(block, line)
return
# 在 prev 和 Tail 之间插入 line
line.Next = lines.Tail
prev.Next = line
lines.Count += 1
# ============================================================
# 属性设置
# ============================================================
def function_set_attrs(func: rawptr[Function], attrs: str):
"""设置函数级属性文本(如 "nounwind"/"noinline")"""
if func is None: return
func.Attrs = attrs
_g_attr_group_counter: int = 0
def function_set_attr_group(func: rawptr[Function], group: str):
"""设置函数字符串属性组(如 '"section"=".text.startup"')并分配组编号 #N。
LLVM 字符串属性(带值的,如 section)不能内联在函数签名行,
必须用属性组:函数行输出 #N,组定义输出在函数尾。
"""
global _g_attr_group_counter
if func is None or group is None:
return
func.AttrGroup = group
_g_attr_group_counter += 1
func.AttrGroupId = _g_attr_group_counter
def param_set_attrs(param: rawptr[Param], attrs: str):
"""设置参数属性文本(如 "noundef"/"nocapture"/"readonly")"""
if param is None: return
param.Attrs = attrs
# ============================================================
# _ll_name_needs_quote - 检查 LLVM 标识符是否需要加引号
#
# LLVM IR 规范:标识符合法字符集为 [-a-zA-Z$._][-a-zA-Z$._0-9]*
# 以数字开头或含非合法字符(如 '['、']'、' '、'@' 等)时必须用双引号包围。
# '.' 是合法字符,不需要加引号(如 @.str、@llvm.memcpy 都不加引号)。
# SHA1 命名空间的函数名(如 "4dad2ba72ed7ba09.GSListNode[Param]")含 '['、']',
# 需要加引号;但纯 hex+点 的名字(如 "abc.def")不需要。
# ============================================================
def _ll_name_needs_quote(name: str) -> int:
"""检查 LLVM 标识符是否需要加引号(以数字开头或含非合法字符)
LLVM IR 合法字符:a-z, A-Z, 0-9, -, $, ., _
首字符是数字时需要加引号(LLVM 要求局部/全局标识符首字符非数字,除非加引号)。
含其他字符(如 '['、']'、空格)时需要加引号。
"""
if name is None:
return 0
c0: t.CChar = name[0]
if c0 >= '0' and c0 <= '9':
return 1
name_len: t.CSizeT = string.strlen(name)
i: t.CSizeT = 0
while i < name_len:
ch: t.CChar = name[i]
# 合法字符:a-z, A-Z, 0-9, -, $, ., _
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 == '.' or ch == '_'):
return 1
i += 1
return 0
# ============================================================
# RenumberSSA: 重编号函数内数字 SSA 名
#
# 解决 build_alloca_at_entry 在入口块终止指令前插入 alloca 导致
# SSA 名在文本输出中非单调递增的问题(llc 报错:
# "instruction expected to be numbered '%N' or greater")。
#
# 现象: entry 块原指令 %0-%6 + br,alloca_at_entry 插入 %28/%41 到 br 之前,
# 文本输出顺序为 entry(%0..%6, %28, %41) → if.then(%7..%27),
# 导致 %28 出现在 %7 之前,llc 拒绝编译。
#
# 算法:
# 1. 扫描所有 BB 指令行,按文本输出顺序收集数字 SSA 名 %N
# 2. 按首次出现顺序建立 old→new 映射(从 0 递增)
# 3. 替换所有指令行文本中的 %N 为新编号
#
# 注意:
# - 仅重编号纯数字 SSA 名(%0, %1, ...)
# - 命名 SSA 名(%entry, %.tmp, %self)、块标签、全局符号 @N 不重编号
# - 仅对 define(有函数体)调用,declare 无函数体无需处理
# - 当前 TransPyV 参数多为命名(%self/%env/%n),非数字,无需重编号参数
# ============================================================
# 判定字符是否为十进制数字
def _is_digit_ch(ch: t.CChar) -> int:
"""判断字符是否是 '0'-'9'
ch 是值类型 (t.CChar = i8),TPC 已修复值类型 is None 处理:
值类型 is None 恒为 false(生成 i1 0 常量),不会解引用空指针。
"""
if ch is None:
return 0
if ch >= '0' and ch <= '9':
return 1
return 0
# ============================================================
# i32 数组的安全访问(显式字节偏移)
#
# TransPyC 的 ptr[i] 指针索引可能被当作字节索引(i8*)而非 i32* 索引,
# 导致内存损坏。必须显式计算字节偏移:addr = base + idx * 4,再转型为 i32*。
# 参见 HandlesTranslator.py:358 的 t.CUInt64T(ptr) + offset * sizeof 用法。
# ============================================================
def _map_get_i32(map_arr: str, idx: int) -> int:
"""读取 i32 数组 map_arr[idx](显式字节偏移 idx*4)
使用 (rawptr[int])(uint64) 强转将字节偏移转为 i32*,再用 c.Deref 解引用。
TPC 已修复类型联合强转(_HandleTypeUnionCastLlvm 添加 fallback),此写法可用。
"""
if map_arr is None:
return -1
byte_off: t.CUInt64T = t.CUInt64T(map_arr) + idx * 4
elem_ptr: rawptr[int] = (rawptr[int])(byte_off)
return c.Deref(elem_ptr)
def _map_set_i32(map_arr: str, idx: int, val: int):
"""写入 map_arr[idx] = val(i32 数组,显式字节偏移 idx*4)
使用 (rawptr[int])(uint64) 强转 + c.DerefAs 解引用赋值。
TPC 已修复类型联合强转,此写法可用。
"""
if map_arr is None:
return
byte_off: t.CUInt64T = t.CUInt64T(map_arr) + idx * 4
elem_ptr: rawptr[int] = (rawptr[int])(byte_off)
c.DerefAs(elem_ptr, val)
def _scan_max_ssa_in_text(text: str, cur_max: int) -> int:
"""扫描文本,找出 %N(N 为数字)中最大的 N,与 cur_max 比较返回更大值
仅匹配 '%' 后紧跟数字的情况,跳过命名 SSA 名(%entry 等)。
"""
result: int = cur_max
p: str = text
while p is not None and p[0] != '\0':
if p[0] == '%':
nxt: str = p + 1
if nxt is not None and _is_digit_ch(nxt[0]) != 0:
num: int = string.atoi(nxt)
if num > result:
result = num
p = p + 1
return result
def _find_max_ssa_number(func: rawptr[Function]) -> int:
"""遍历所有 BB 指令行,找出最大的 %N 数字;无数字 SSA 名返回 -1"""
max_n: int = -1
if func is None or func.Blocks is None:
return max_n
blocks: rawptr[GSList[BasicBlock]] = func.Blocks
blk: rawptr[BasicBlock] = blocks.Head
blk_steps: t.CSizeT = 0
while blk is not None:
blk_steps += 1
if blk_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: func blocks(函数名=%s,ssa 扫描终止)\n", func.Name)
stdlib.exit(1)
if blk.Lines is not None:
lines: rawptr[GSList[Line]] = blk.Lines
line: rawptr[Line] = lines.Head
line_steps: t.CSizeT = 0
while line is not None:
line_steps += 1
if line_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: block lines(函数名=%s,块=%s,ssa 扫描终止)\n",
func.Name, blk.Name)
stdlib.exit(1)
if line.Buf is not None:
max_n = _scan_max_ssa_in_text(line.Buf, max_n)
line = line.Next
blk = blk.Next
return max_n
def _assign_new_numbers(text: str,
map_arr: str,
max_n: int, next_new: int) -> int:
"""扫描文本,对每个 %N(数字),若 map_arr[N]==-1 则分配 next_new
返回更新后的 next_new。按文本首次出现顺序建立映射。
"""
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: rawptr[memhub.MemBuddy],
map_arr: str,
max_n: int) -> str:
"""构建新文本,将 %N 替换为映射后的新编号
逐字符扫描:遇到 %N(数字)查映射表替换,其他字符原样复制。
返回新分配的缓冲区(调用方无需释放旧 Buf,pool 统一管理)。
"""
src_len: t.CSizeT = string.strlen(text)
# 新缓冲区:最坏情况每个数字变长(位数增加),分配 src_len*2 + 64
new_size: t.CSizeT = src_len * 2 + 64
new_buf: str = pool.alloc(new_size)
if new_buf is None:
return None
# 一次性分配数字缓冲区(循环中复用)
num_buf: str = pool.alloc(16)
if num_buf is None:
new_buf[0] = '\0'
return new_buf
dst_idx: t.CSizeT = 0
src_p: str = text
while src_p is not None and src_p[0] != '\0':
if dst_idx + 16 >= new_size:
# 缓冲区不足,截断
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)
# 先做范围检查再读 map_arr:num 超 max_n 时 map_arr 只有
# (max_n+1)*4 字节,先读后查会越界读远端内存
if num >= 0 and num <= max_n:
new_num: int = _map_get_i32(map_arr, num)
if new_num != -1:
# 写入 '%'
new_buf[dst_idx] = '%'
dst_idx += 1
# 写入新编号
num_buf[0] = '\0'
viperlib.snprintf(num_buf, 16, "%d", new_num)
k: t.CSizeT = 0
while num_buf[k] != '\0' and dst_idx + 1 < new_size:
new_buf[dst_idx] = num_buf[k]
dst_idx += 1
k += 1
# 跳过旧数字字符
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
# 普通字符:直接复制
new_buf[dst_idx] = src_p[0]
dst_idx += 1
src_p = src_p + 1
new_buf[dst_idx] = '\0'
return new_buf
def RenumberSSA(func: rawptr[Function], pool: rawptr[memhub.MemBuddy]):
"""重编号函数内所有数字 SSA 名,确保按文本输出顺序单调递增
在 FunctionPrint 序列化函数体之前调用。
仅对 define(IsDeclared==0)且 Blocks 非空 的函数生效。
"""
if func is None or pool is None:
return
if func.IsDeclared == 1:
return # declare 无函数体
if func.Blocks is None:
return
# 1. 第一遍扫描:找出最大 SSA 编号
max_n: int = _find_max_ssa_number(func)
if max_n < 0:
return # 无数字 SSA 名
# 2. 分配并初始化映射数组 map_arr[0..max_n] = -1
# 用 i8* 存储(pool.alloc 返回 i8*),通过 _map_get_i32/_map_set_i32 访问。
# 初始化为 -1:每个 i32 元素 4 字节,0xFFFFFFFF = -1(有符号 i32)。
# 用 memset 一次性填充,避免循环内 ptr[i] 指针索引问题。
map_bytes: t.CSizeT = (max_n + 1) * 4 # int = 4 字节
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. 第二遍扫描:按文本顺序分配新编号
next_new: int = 0
blocks: rawptr[GSList[BasicBlock]] = func.Blocks
blk: rawptr[BasicBlock] = blocks.Head
blk_steps: t.CSizeT = 0
while blk is not None:
blk_steps += 1
if blk_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: func blocks(函数名=%s,ssa 重编号终止)\n", func.Name)
stdlib.exit(1)
if blk.Lines is not None:
lines: rawptr[GSList[Line]] = blk.Lines
line: rawptr[Line] = lines.Head
line_steps: t.CSizeT = 0
while line is not None:
line_steps += 1
if line_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: block lines(函数名=%s,块=%s,ssa 重编号终止)\n",
func.Name, blk.Name)
stdlib.exit(1)
if line.Buf is not None:
next_new = _assign_new_numbers(line.Buf, map_arr, max_n, next_new)
line = line.Next
blk = blk.Next
# 4. 检查是否有需要重编号的(old != new)
need_renumber: int = 0
i: int = 0
while i <= max_n:
v: int = _map_get_i32(map_arr, i)
if v != -1 and v != i:
need_renumber = 1
break
i += 1
if need_renumber == 0:
return # 编号已单调,无需替换
# 5. 第三遍扫描:替换所有指令行文本
blk = blocks.Head
blk_steps = 0
while blk is not None:
blk_steps += 1
if blk_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: func blocks(函数名=%s,ssa 替换终止)\n", func.Name)
stdlib.exit(1)
if blk.Lines is not None:
lines: rawptr[GSList[Line]] = blk.Lines
line: rawptr[Line] = lines.Head
line_steps = 0
while line is not None:
line_steps += 1
if line_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: block lines(函数名=%s,块=%s,ssa 替换终止)\n",
func.Name, blk.Name)
stdlib.exit(1)
if line.Buf is not None:
new_buf: str = _replace_ssa_numbers(
line.Buf, pool, map_arr, max_n)
if new_buf is not None:
line.Buf = new_buf
line.Len = string.strlen(new_buf)
line = line.Next
blk = blk.Next
# ============================================================
# 本地窥孔优化(文本层,输出前执行,保持语义)
# T1: ret <iN> zeroinitializer → ret <iN> 0;ret <ptr*> zeroinitializer → null
# T2: 入口 alloca 槽:唯一一次 store 且值无副作用(形参/%SSA/整数/浮点字面量)
# 时,把 store→load 转发为直接使用该值,删除 alloca/store/load 冗余行
# ("虚拟寄存器优化",如 printf(i8* %msg) 前不再 alloca+store+load)。
# 安全性:仅当 alloca 与唯一 store 都在 entry、loads 任意、无其它取址/别名用途。
# ============================================================
def _peep_is_startwith(s: str, pfx: str) -> int:
if s is None or pfx is None:
return 0
pl: t.CSizeT = 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: rawptr[memhub.MemBuddy],
line_buf: str, tok: str, repl: str,
tlen: t.CSizeT, rlen: t.CSizeT) -> str:
"""将 line_buf 中独立记号 %<tok> 替换为 repl,返回新缓冲(无变化返回原指针)
tlen/rlen = strlen(tok)/strlen(repl):由调用方在行循环外预先求好(同一
ltok/safe_val 会作用于本函数处理的所有行),避免每行重复求长。
"""
if line_buf is None or tok is None or repl is None:
return line_buf
if tlen == 0:
return line_buf
# 预扫是否命中:不依赖 strlen,以 NUL 为界单趟扫描(绝大多数调用无命中即返回,
# 从而省去逐行 strlen)。逐输入等价:tok 无 NUL(tlen=strlen(tok)),故
# strncmp 判等必蕴含记号完整落在串内(i+tlen 处仍非 NUL);凡 i+tlen>ln 的
# 位置 strncmp 必先遇到 line_buf 的 NUL 而与 tok 的非 NUL 字节不等,故原
# 上界 i+tlen<=ln 从未排除任一可能命中,去掉后命中判定不变。
hit: int = 0
i: t.CSizeT = 0
while line_buf[i] != '\0':
if line_buf[i] == '%' and line_buf[i + 1] == tok[0] \
and string.strncmp(line_buf + i + 1, tok, tlen) == 0:
nxt: int = 0
if line_buf[i + 1 + tlen] != '\0':
cc: int = line_buf[i + 1 + tlen]
if (cc >= 48 and cc <= 57) or (cc >= 65 and cc <= 90) or (cc >= 97 and cc <= 122) or cc == 95:
nxt = 1
if nxt == 0:
hit = 1
break
i += 1
if hit == 0:
return line_buf
ln: t.CSizeT = string.strlen(line_buf)
# 缓冲大小上界:单行最多 ⌊ln/2⌋ 次替换,每次去掉 ≥2 字节(%+名)换入 rlen 字节;
# 最坏输出 ≤ ln + (ln/2)·rlen + NUL。ln + 1 + rlen·(ln+1) 恒覆盖该上界,
# 避免 K≥2 且替换串更长时把新缓冲写穿(随机内存破坏)。
out: str = pool.alloc(ln + 1 + rlen * (ln + 1))
if out is None:
return line_buf
op: t.CSizeT = 0
oq: t.CSizeT = 0
i = 0
while i < ln:
if line_buf[i] == '%' and i + 1 + tlen <= ln \
and line_buf[i + 1] == tok[0] \
and string.strncmp(line_buf + i + 1, tok, tlen) == 0:
nxt2: int = 0
if i + 1 + tlen < ln:
cc2: int = line_buf[i + 1 + tlen]
if (cc2 >= 48 and cc2 <= 57) or (cc2 >= 65 and cc2 <= 90) or (cc2 >= 97 and cc2 <= 122) or cc2 == 95:
nxt2 = 1
if nxt2 == 0:
j: t.CSizeT = 0
while j < rlen:
out[oq] = repl[j]
oq += 1
j += 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: t.CSizeT) -> int:
"""行内最后一个 '%name' 记号是否等于 a_name(store/load 的指针操作数在行尾)
nl = strlen(a_name):由调用方在 alloca 槽全量行扫描外预先求好(a_name 在
同一次槽扫描中不变),避免每行调用重复 strlen。
"""
if sb is None or a_name is None:
return 0
# 单趟定位最后一个 '%'(原实现反复 strchr 逐个跳转,每个 '%' 一次函数调用)
r: str = None
probe: str = sb
while probe[0] != '\0':
if probe[0] == '%':
r = probe
probe += 1
if r is None:
return 0
# 先比首字符(廉价拒绝),首字符相同才进入 strncmp
if r[1] == a_name[0] and string.strncmp(r + 1, a_name, nl) == 0:
nxtc: int = r[1 + nl]
if nxtc == 0 or nxtc == 32 or nxtc == 44 or nxtc == 41:
return 1
return 0
def _peep_alloc_name(eb: str, pool: rawptr[memhub.MemBuddy]) -> str:
"""从 '%name = alloca ...' 提取 name(不含 %),失败返回 None"""
if eb is None or eb[0] != '%':
return None
eqpos: str = string.strstr(eb, " = alloca ")
if eqpos is None:
return None
nlen: t.CSizeT = t.CSizeT(t.CUInt64T(eqpos) - t.CUInt64T(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: t.CSizeT) -> int:
"""判断 eb 是否为 a_name 的 alloca 定义行,等价于对 _peep_alloc_name(eb) 与
a_name 做 strcmp 是否为 0(不再分配名字缓冲)。
先比名字(strncmp,名字不同立即拒绝),仅当名字逐字节一致时才做
" = alloca " 子串定位——把原实现「每一个 '%' 行都调一次 strstr」的
O(槽数×行数) 次 strstr 降为每槽仅命中行才调用。
nl = strlen(a_name);nlen==0(名字为空)在原实现返回 None,故此处同样拒绝。
等价性:_peep_alloc_name 取第一个 " = alloca " 之前、'%' 之后的字节为名字,
故名字等长且一致 ⟺ strncmp 相同且该子串恰起于 eb+1+nl。
"""
if eb is None or a_name is None or nl == 0 or eb[0] != '%':
return 0
# 首字符预筛:nl>=1,strncmp 判等必然蕴含首字符相等;首字符不同即可廉价拒绝,
# 避免绝大多数行进入 strncmp 调用。逐输入等价:本分支拒绝当且仅当原 strncmp
# 也必返回非 0(首字节不同)。eb[0]=='%' 非 NUL ⇒ eb[1] 读在串内(最多为 NUL)。
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 t.CUInt64T(eqpos) == t.CUInt64T(eb) + 1 + nl:
return 1
return 0
def _peephole_opt(func: rawptr[Function], pool: rawptr[memhub.MemBuddy]):
"""文本层本地窥孔:T1 ret zeroinitializer;T2 入口槽 store→load 转发"""
if func is None or func.Blocks is None or pool is None:
return
# 注:TPC 对 "变量 = 指针.字段.成员" 两级自动解引用链会静默丢弃赋值
# (编译出的 IR 直接读未初始化栈变量 → 随机栈错误)。必须拆成单级:
# 先取字段到局部 rawptr,再判空后取 .Head(与 FunctionPrint 同一模式)。
func_blocks: rawptr[GSList[BasicBlock]] = func.Blocks
blk: rawptr[BasicBlock] = None
if func_blocks is not None:
blk = func_blocks.Head
# ---------- T1: ret <ty> zeroinitializer ----------
blk_t1 = blk
while blk_t1 is not None:
line1: rawptr[Line] = None
if blk_t1.Lines is not None:
t1_lines: rawptr[GSList[Line]] = blk_t1.Lines
line1 = t1_lines.Head
while line1 is not None:
if line1.Buf is not None:
zi: str = string.strstr(line1.Buf, " zeroinitializer")
if zi is not None and _peep_is_startwith(line1.Buf, "ret ") != 0:
ty_len: t.CSizeT = t.CSizeT(t.CUInt64T(zi) - t.CUInt64T(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 槽转发 ----------
entry_blk: rawptr[BasicBlock] = blk
if entry_blk is None:
return
entry_lines: rawptr[GSList[Line]] = entry_blk.Lines
if entry_lines is None:
return
e_line: rawptr[Line] = entry_lines.Head
while e_line is not None:
eb: str = e_line.Buf
if eb is not None:
a_name: str = _peep_alloc_name(eb, pool)
if a_name is not None:
# a_name 在本槽的全量行扫描中不变,strlen 只求一次(原每行各求一次)
a_name_len: t.CSizeT = string.strlen(a_name)
st_val: str = None
st_ok: int = 0
st_count: int = 0
st_line_order: int = -1
load_ltoks: str = pool.alloc(8 * 64)
load_cnt: int = 0
load_entry_order: t.CInt64T = 0
alloca_order: int = -1
order: int = 0
unsafe: int = 0
# 第一遍:统计本 alloca 的 store/load/其它引用及行序
blk_s = blk
while blk_s is not None:
l_s: rawptr[Line] = None
if blk_s.Lines is not None:
scan_lines: rawptr[GSList[Line]] = blk_s.Lines
l_s = scan_lines.Head
while l_s is not None:
sb: str = l_s.Buf
if sb is not None:
is_alloc_def: int = 0
if sb[0] == '%':
if _peep_is_alloc_of(sb, a_name, a_name_len) != 0:
is_alloc_def = 1
if is_alloc_def == 0 and _peep_last_ref_eq(sb, a_name, a_name_len) != 0:
if _peep_is_startwith(sb, "store ") != 0:
st_count += 1
if st_count == 1:
vcomma: str = string.strstr(sb + 6, ", ")
if vcomma is not None:
vlen: t.CSizeT = t.CSizeT(t.CUInt64T(vcomma) - t.CUInt64T(sb + 6))
if vlen > 0:
vbuf: str = pool.alloc(vlen + 1)
if vbuf is not None:
string.strncpy(vbuf, sb + 6, vlen)
vbuf[vlen] = '\0'
st_val = vbuf
st_ok = 1
else:
unsafe = 1
if blk_s == entry_blk:
st_line_order = order
elif string.strstr(sb, " = load ") is not None:
ls: str = string.strstr(sb, "%")
if ls is not None and load_cnt < 64:
lname_len: t.CSizeT = t.CSizeT(
t.CUInt64T(string.strstr(sb, " = load ")) - t.CUInt64T(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 | t.CPtr = t.CPtr(t.CUInt64T(load_ltoks) + load_cnt * 8)
slotp[0] = lname
load_cnt += 1
if blk_s == entry_blk and st_line_order < 0:
load_entry_order += 1
else:
# lname 分配失败:该 load 不会进 load_ltoks,但删除阶段
# 仍会按槽名删其 def 行,use 无法改写为 store 值 → 悬空。
# 保守放弃本 alloca 槽的 T2 转发。
unsafe = 1
else:
# 本 alloca 的 load 超过记录容量(load_ltoks=8*64,上限 64)
# 或行内无 '%' 无法解析 def 名:超出容量的 load 不会被记录,
# 但删除阶段按槽名删除全部 " = load " def 行 → 未记录 load 的
# use 未改写为 store 值,将悬空引用已删 def(llc:
# "use of undefined value",见 strcmp 短路链 >64 分支场景)。
# 只有 load 全集都被记录时才能安全删全量 def,故保守放弃
# 本 alloca 槽的整个 T2 转发(保留 alloca/store/load,IR 仍合法)。
unsafe = 1
else:
unsafe = 1
if sb is not None:
order += 1
l_s = l_s.Next
blk_s = blk_s.Next
# 判据
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:
# 第二遍:替换 load 名引用
# safe_val 在本槽替换阶段不变,strlen 只求一次(原每行各求一次)
safe_len: t.CSizeT = string.strlen(safe_val)
li2: int = 0
while li2 < load_cnt:
slotsp: str | t.CPtr = t.CPtr(t.CUInt64T(load_ltoks) + li2 * 8)
ltok: str = slotsp[0]
if ltok is not None:
# ltok 在该 load 名的全函数行扫描中不变,strlen 只求一次
ltok_len: t.CSizeT = string.strlen(ltok)
blk_r = blk
while blk_r is not None:
lr: rawptr[Line] = None
if blk_r.Lines is not None:
repl_lines: rawptr[GSList[Line]] = blk_r.Lines
lr = repl_lines.Head
while lr is not None:
if lr.Buf is not None:
nb2: str = _peep_replace_token(pool, lr.Buf, ltok, safe_val,
ltok_len, safe_len)
if nb2 is not None and nb2 is not lr.Buf:
lr.Buf = nb2
lr = lr.Next
blk_r = blk_r.Next
li2 += 1
# 删除:alloca def / 唯一 store / 全部 load 行
blk_d = blk
while blk_d is not None:
ld: rawptr[Line] = None
if blk_d.Lines is not None:
kill_lines: rawptr[GSList[Line]] = blk_d.Lines
ld = kill_lines.Head
while ld is not None:
db: str = ld.Buf
if db is not None:
kill: int = 0
if db[0] == '%' and db[1] == a_name[0]:
if _peep_is_alloc_of(db, a_name, a_name_len) != 0:
kill = 1
if kill == 0 and _peep_last_ref_eq(db, a_name, a_name_len) != 0:
if _peep_is_startwith(db, "store ") != 0 \
or string.strstr(db, " = load ") is not None:
kill = 1
if kill != 0:
ld.Buf = None
ld = ld.Next
blk_d = blk_d.Next
e_line = e_line.Next
# ---------- T3: no-op bitcast 消除 ----------
_peep_noop_bitcast(func, pool)
def _peep_noop_bitcast(func: rawptr[Function], pool: rawptr[memhub.MemBuddy]):
"""T3: 消除 no-op bitcast(bitcast <T> %x to <T>,两侧类型相同)。
生成端(虚调用/联合位宽转换)会产生同类型恒等 bitcast(如
`%6 = bitcast i8* %ok_msg to i8*`),纯冗余指令。把 %x 的所有引用
替换为源操作数(%SSA/@global/常量 token)后删除该行。
保守限制:类型与操作数均为不含空格的简单 token。
"""
if func is None or pool is None or func.Blocks is None:
return
# 单趟前向扫描:命中合法候选 → 全函数替换引用 + 删行,游标从下一行续扫;
# 遇到首个不满足条件的 bitcast 即停止。与原实现"每删一行后从块头整函数
# 重扫、遇无效候选即 return"的删除结果逐一等价(替换只改操作数 token,
# 不会把游标之前的非候选行变成候选),只是消除了 O(K·C) 的重扫二次方。
blk: rawptr[BasicBlock] = func.Blocks.Head
while blk is not None:
ln: rawptr[Line] = None
if blk.Lines is not None:
ln = blk.Lines.Head
while ln is not None:
nxt: rawptr[Line] = ln.Next
if ln.Buf is not None:
eqpos: str = string.strstr(ln.Buf, " = bitcast ")
if eqpos is not None:
nlen: t.CSizeT = t.CSizeT(t.CUInt64T(eqpos) - t.CUInt64T(ln.Buf)) - 1
nm_buf: str = None
src_buf: str = None
if nlen > 0:
after: str = eqpos + 11 # 跳过 " = bitcast "
sp1: str = string.strchr(after, ' ')
tpos: str = string.strstr(after, " to ")
if sp1 is not None and tpos is not None \
and t.CUInt64T(sp1) < t.CUInt64T(tpos):
tyA_len: t.CSizeT = t.CSizeT(t.CUInt64T(sp1) - t.CUInt64T(after))
src_st: str = sp1 + 1
src_len: t.CSizeT = t.CSizeT(t.CUInt64T(tpos) - t.CUInt64T(src_st))
tyB: str = tpos + 4
tyB_len: t.CSizeT = string.strlen(tyB)
if src_len > 0 and string.strchr(tyB, ' ') is None \
and t.CUInt64T(string.strchr(src_st, ' ')) == t.CUInt64T(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:
# 首个不满足条件的 bitcast:停止(与原语义一致)
return
# 全函数把 %nm 的引用替换为源操作数
# nm_buf/src_buf 在全函数行扫描中不变,strlen 只求一次(原每行各求一次)
nm_buf_len: t.CSizeT = string.strlen(nm_buf)
src_buf_len: t.CSizeT = string.strlen(src_buf)
blk_r: rawptr[BasicBlock] = func.Blocks.Head
while blk_r is not None:
if blk_r.Lines is not None:
ln2: rawptr[Line] = blk_r.Lines.Head
while ln2 is not None:
if ln2.Buf is not None:
rb: str = _peep_replace_token(pool, ln2.Buf, nm_buf, src_buf,
nm_buf_len, src_buf_len)
if rb is not None and rb is not ln2.Buf:
ln2.Buf = rb
ln2.Len = string.strlen(rb)
ln2 = ln2.Next
blk_r = blk_r.Next
ln.Buf = None
ln = nxt
blk = blk.Next
# ============================================================
# FunctionPrint: 将函数序列化为 IR 文本
#
# 格式:
# define <ret_ty> @<name>(<params>) {
# <block_name>:
# <instruction>
# <instruction>
# <block_name>:
# <instruction>
# }
# ============================================================
def FunctionPrint(buf: str, size: t.CSizeT,
func: rawptr[Function], pool: rawptr[memhub.MemBuddy]):
"""将函数 func 序列化为 IR 文本写入 buf"""
if func is None or buf is None or size == 0: return
# 先本地窥孔(ret zeroinitializer→0/null、入口槽 store→load 转发、
# no-op bitcast 消除):窥孔按"名称一致性"匹配、删除行置 Buf=None;
# RenumberSSA 遍历时已跳过 Buf=None 行 → 窥孔之后再重编号,输出 SSA
# 编号连续(窥孔删行后不再出现 %1→%5 空洞,文件更紧凑)。
# RenumberSSA 内部会跳过 declare 函数(IsDeclared==1),无需在此判断。
if func.IsDeclared == 0 and G_PeepEnable != 0:
_peephole_opt(func, pool)
RenumberSSA(func, pool)
ty_buf: str = pool.alloc(128)
if ty_buf is None: return
ty_buf[0] = '\0'
# define <ret_ty> @<name>(<params>)
if func.IsDeclared == 1:
_append_cstr(buf, size, "declare ")
else:
# 泛型特化类方法(如 GSList[NameEntry].__before_init__):多个模块各自
# 特化生成同名定义 → ld.lld duplicate symbol warning。特化方法代码由
# 相同模板+类型参数生成,定义相同,输出 linkonce_odr 让链接器合并。
if func.Name is not None and string.strchr(func.Name, 91) is not None:
_append_cstr(buf, size, "define linkonce_odr ")
else:
_append_cstr(buf, size, "define ")
ty_buf[0] = '\0'
TypePrint(ty_buf, 128, func.RetTy, pool)
_append_cstr(buf, size, ty_buf)
_append_cstr(buf, size, " @")
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, "(")
# 参数列表(从 GSList[Param].Head 遍历)
params: rawptr[GSList[Param]] = func.Params
cur: rawptr[Param] = None
if params is not None:
cur = params.Head
first: int = 1
cur_steps: t.CSizeT = 0
while cur is not None:
cur_steps += 1
if cur_steps > IR_DUMP_CHAIN_LIMIT:
if params is not None:
stdio.printf("[IR-DUMP] 链表异常: func params(函数名=%s,Params.Count=%zu,遍历超 %d 步,print 终止)\n",
func.Name, params.Count, IR_DUMP_CHAIN_LIMIT)
else:
stdio.printf("[IR-DUMP] 链表异常: func params(函数名=%s,Params=None,遍历超 %d 步,print 终止)\n",
func.Name, IR_DUMP_CHAIN_LIMIT)
stdlib.exit(1)
if first == 0:
_append_cstr(buf, size, ", ")
# 参数属性(如 "noundef"/"nocapture")在类型之前
if cur.Attrs is not None:
_append_cstr(buf, size, cur.Attrs)
_append_cstr(buf, size, " ")
ty_buf[0] = '\0'
TypePrint(ty_buf, 128, cur.Ty, pool)
_append_cstr(buf, size, ty_buf)
# declare 和 define 都打印参数名(如果有)
if cur.Name is not None:
_append_cstr(buf, size, " ")
# LLVM IR 要求参数名以 % 开头
if cur.Name[0] != '%':
_append_cstr(buf, size, "%")
_append_cstr(buf, size, cur.Name)
cur = cur.Next
first = 0
# 变参函数追加 ...
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, ")")
# 函数级属性(如 "nounwind"/"noinline")在 ) 之后
if func.Attrs is not None:
_append_cstr(buf, size, " ")
_append_cstr(buf, size, func.Attrs)
# B5:字符串属性(section 等)必须用属性组 #N 引用(LLVM 不接受内联
# section="..."),函数行输出 #N,组定义在函数尾/模块中输出。
if func.AttrGroup is not None and func.AttrGroupId > 0:
ag_ref: str = pool.alloc(32)
if ag_ref is not None:
viperlib.snprintf(ag_ref, 32, " #%d", func.AttrGroupId)
_append_cstr(buf, size, ag_ref)
if func.IsDeclared == 1:
_append_cstr(buf, size, "\n")
_append_attr_group_def(buf, size, pool, func)
return
# 函数体
_append_cstr(buf, size, " {\n")
blocks: rawptr[GSList[BasicBlock]] = func.Blocks
blk: rawptr[BasicBlock] = None
if blocks is not None:
blk = blocks.Head
blk_steps: t.CSizeT = 0
while blk is not None:
blk_steps += 1
if blk_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: func blocks(函数名=%s,print 终止)\n", func.Name)
stdlib.exit(1)
# 块标签
if blk.Name is not None:
_append_cstr(buf, size, blk.Name)
_append_cstr(buf, size, ":\n")
# 指令行(从 GSList[Line].Head 遍历)
line: rawptr[Line] = None
if blk.Lines is not None:
lines: rawptr[GSList[Line]] = blk.Lines
line = lines.Head
has_lines: int = 0
line_steps: t.CSizeT = 0
while line is not None:
line_steps += 1
if line_steps > IR_DUMP_CHAIN_LIMIT:
stdio.printf("[IR-DUMP] 链表成环: block lines(函数名=%s,块=%s,print 终止)\n",
func.Name, blk.Name)
stdlib.exit(1)
if line.Buf is not None:
_append_cstr(buf, size, " ")
_append_cstr(buf, size, line.Buf)
_append_cstr(buf, size, "\n")
has_lines = 1
line = line.Next
# 块未终止兜底:LLVM IR 要求每个基本块以终止指令(br/ret/unreachable)结尾。
# 两种情况需要补 unreachable:
# 1. 空块(has_lines==0):标签后无任何指令
# 2. 有指令但未终止(IsTerminated==0):如 break/continue 后的 dead.N 块
# 被后续语句填入指令但缺少终止符,导致下一个标签处报
# "expected instruction opcode"
if has_lines == 0 or blk.IsTerminated == 0:
_append_cstr(buf, size, " unreachable\n")
blk = blk.Next
_append_cstr(buf, size, "}\n")
_append_attr_group_def(buf, size, pool, func)
def _append_attr_group_def(buf: str, size: t.CSizeT,
pool: rawptr[memhub.MemBuddy],
func: rawptr[Function]):
"""输出字符串属性组定义: attributes #N = { "section"="..." }"""
if func is None or func.AttrGroup is None or func.AttrGroupId <= 0:
return
ag_buf: str = pool.alloc(192)
if ag_buf is None:
return
viperlib.snprintf(ag_buf, 192, "attributes #%d = { %s }\n",
func.AttrGroupId, func.AttrGroup)
_append_cstr(buf, size, ag_buf)
# ============================================================
# 内部辅助
# ============================================================
# _append_cstr 写入位置缓存(性能优化:同一缓冲连续追加时 O(1) 定位,
# 避免每次 strlen(dst) 从头扫描导致的 O(n²) —— IR 序列化热点)。
# 缓冲切换(dst 不同)或缓冲被重置(dst[0]=0 且 pos>0)时自动回退 strlen。
_g_last_dst: t.CUInt64T = 0
_g_last_pos: t.CSizeT = 0
def _append_cstr(dst: str, dst_size: t.CSizeT, src: str):
"""将 C 字符串 src 追加到 dst 末尾"""
global _g_last_dst, _g_last_pos
if dst is None or src is None: return
dlen: t.CSizeT = 0
if _g_last_dst == t.CUInt64T(dst):
dlen = _g_last_pos
# 缓冲被重置(调用方 buf[0]=0 后复用同一地址)→ 回到头部
if dlen > 0 and dst[0] == '\0':
dlen = 0
# 缓存一致性校验:缓存结尾必须仍是当前文本末端(末端 NUL 且前一字符非 NUL)。
# 多个模块轮流写同一 buf、或 pool 复用地址残留旧内容时缓存 pos 会过期,
# 追加落到首个 NUL 之后内容"看不见"(LLvmLiteTest Test5/9 根因)。失败回退 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 = t.CUInt64T(dst)
# 无符号下溢防护:dlen 超过缓冲大小时(缓冲已被破坏/悬空)直接返回,
# 否则 remain = dst_size - dlen 下溢成巨值会把 dst[dlen+i] 写穿到缓冲外
if dlen >= dst_size:
return
slen: t.CSizeT = string.strlen(src)
remain: t.CSizeT = dst_size - dlen
if remain <= 0: return
i: t.CSizeT = 0
while i < slen and i + 1 < remain:
dst[dlen + i] = src[i]
i += 1
dst[dlen + i] = '\0'
_g_last_pos = dlen + i
# ============================================================
# 字段访问器(供其他模块绕过 stub 类型限制使用)
#
# 跨模块编译时,使用方模块只有 Function/Param 的 stub 类型
# (字段不足且字段类型可能错误),导致 TransPyC 静默跳过
# 字段访问(不生成 GEP,不报错),字段值永远为 null。
#
# 这些访问器在拥有完整类型定义的本模块内执行,能正确访问
# 所有字段。其他模块通过函数调用获取字段值,绕过 stub 限制。
# ============================================================
def function_get_name(func: rawptr[Function]) -> str:
"""获取函数名"""
if func is None:
return None
return func.Name
def function_get_name_len(func: rawptr[Function]) -> t.CSizeT:
"""获取函数名长度(创建时一次算好,恒等于 strlen(Name))
对齐 function_get_name 的跨模块 stub 绕过模式:使用方模块只有 Function
的 stub 类型(字段不足且字段类型可能错误),直接读 Name/NameLen 字段会被
TransPyC 静默跳过;本访问器在拥有完整类型定义的本模块内取字段,跨模块经
函数调用取得长度,避免对同一函数名反复 strlen(全程序 strlen 热点)。
"""
if func is None:
return 0
return func.NameLen
def function_get_ret_ty(func: rawptr[Function]) -> rawptr[LLVMType]:
"""获取函数返回类型"""
if func is None:
return None
return func.RetTy
def function_get_params(func: rawptr[Function]) -> rawptr[GSList[Param]]:
"""获取函数参数链表容器"""
if func is None:
return None
return func.Params
def function_get_param_head(func: rawptr[Function]) -> rawptr[Param]:
"""获取函数参数链表头节点(一步到位,避免调用方访问 GSList.Head)"""
if func is None:
return None
params: rawptr[GSList[Param]] = func.Params
if params is None:
return None
return params.Head
def function_get_next(func: rawptr[Function]) -> rawptr[Function]:
"""获取链表下一个函数"""
if func is None:
return None
return func.Next
def function_is_declared(func: rawptr[Function]) -> int:
"""获取函数是否为声明(1=declare 仅声明, 0=define 定义)"""
if func is None:
return 0
return func.IsDeclared
def param_get_name(param: rawptr[Param]) -> str:
"""获取参数名"""
if param is None:
return None
return param.Name
def param_get_ty(param: rawptr[Param]) -> rawptr[LLVMType]:
"""获取参数类型"""
if param is None:
return None
return param.Ty
def param_get_next(param: rawptr[Param]) -> rawptr[Param]:
"""获取链表下一个参数"""
if param is None:
return None
return param.Next