fix(compiler,peephole): TPC 属性链解析与窥孔输出瘦身

- TPC(lib/core): 任意级属性链按链前向类解析恢复 stub 退化字段; cast 局部 rowptr[X] 登记回填; 通用指针槽按静态类名 bitcast 兜底; 赋值 RHS 求值失败 fail-fast 防未初始化变量; 报错行按源码文本锚定并附 ATTR-DIAG 现场
- 窥孔(includes/llvmlite/__function.vp): 成员访问单级链模式; 窥孔移至 Renumber 前使 SSA 编号连续; T3 no-op bitcast 消除; 替换缓冲按最坏上界分配
- __builder/__module/string.vp 配套; 清理 Test 构建产物与 PROF 杂项
This commit is contained in:
TermiNexus committed 2026-09-08 11:02:37 +08:00
1 parent 45e78ad107
commit 76d1ee6076
173 files changed
+1104 -3090

No files matched your search

+6 -2
View File
@@ -201,8 +201,12 @@ class IRBuilder:
line_text: str = pool.alloc(128)
if line_text is None: return None
viperlib.snprintf(line_text, 128, "%s = alloca %s", name, ty_s)
# 在入口块终止指令之前插入
block_append_text(pool, entry_blk, line_text)
# 在入口块终止指令之前插入:若入口块已以 br/ret 终止(如嵌套循环内
# 再分配循环变量),block_append_text 会把 alloca 追加到终止指令之后,
# 生成 "br 之后不可达指令" 的非法 IR(llc: "instruction expected to be
# numbered '%N' or greater")。block_insert_text_before_terminator
# 在块未终止时等同追加,已终止时插到终止指令之前。
block_insert_text_before_terminator(pool, entry_blk, line_text)
# 结果类型是 ty*
result_ty: rowptr[LLVMType] = LLVMType.Ptr(ty)
result_ty_ptr: rowptr[LLVMType] = pool.alloc(LLVMType.__sizeof__())
+381 -3
View File
@@ -658,6 +658,380 @@ def RenumberSSA(func: rowptr[Function], pool: rowptr[memhub.MemBuddy]):
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: rowptr[memhub.MemBuddy],
line_buf: str, tok: str, repl: str) -> str:
"""将 line_buf 中独立记号 %<tok> 替换为 repl,返回新缓冲(无变化返回原指针)"""
if line_buf is None or tok is None or repl is None:
return line_buf
tlen: t.CSizeT = string.strlen(tok)
if tlen == 0:
return line_buf
rlen: t.CSizeT = string.strlen(repl)
ln: t.CSizeT = string.strlen(line_buf)
# 预扫是否命中
hit: int = 0
i: t.CSizeT = 0
while i + tlen <= ln:
if line_buf[i] == '%' and string.strncmp(line_buf + i + 1, tok, tlen) == 0:
nxt: int = 0
if i + 1 + tlen < ln:
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/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 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) -> int:
"""行内最后一个 '%name' 记号是否等于 a_name(store/load 的指针操作数在行尾)"""
if sb is None or a_name is None:
return 0
r: str = None
probe: str = sb
while True:
hit: str = string.strchr(probe, '%')
if hit is None:
break
r = hit
probe = hit + 1
if r is None:
return 0
nl: t.CSizeT = string.strlen(a_name)
if 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: rowptr[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 _peephole_opt(func: rowptr[Function], pool: rowptr[memhub.MemBuddy]):
"""文本层本地窥孔:T1 ret zeroinitializer;T2 入口槽 store→load 转发"""
if func is None or func.Blocks is None or pool is None:
return
# 注:TPC 对 "变量 = 指针.字段.成员" 两级自动解引用链会静默丢弃赋值
# (编译出的 IR 直接读未初始化栈变量 → 随机栈错误)。必须拆成单级:
# 先取字段到局部 rowptr,再判空后取 .Head(与 FunctionPrint 同一模式)。
func_blocks: rowptr[GSList[BasicBlock]] = func.Blocks
blk: rowptr[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: rowptr[Line] = None
if blk_t1.Lines is not None:
t1_lines: rowptr[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: rowptr[BasicBlock] = blk
if entry_blk is None:
return
entry_lines: rowptr[GSList[Line]] = entry_blk.Lines
if entry_lines is None:
return
e_line: rowptr[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:
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: rowptr[Line] = None
if blk_s.Lines is not None:
scan_lines: rowptr[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] == '%':
tmp_nm: str = _peep_alloc_name(sb, pool)
if tmp_nm is not None and string.strcmp(tmp_nm, a_name) == 0:
is_alloc_def = 1
if is_alloc_def == 0 and _peep_last_ref_eq(sb, a_name) != 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:
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 名引用
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:
blk_r = blk
while blk_r is not None:
lr: rowptr[Line] = None
if blk_r.Lines is not None:
repl_lines: rowptr[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)
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: rowptr[Line] = None
if blk_d.Lines is not None:
kill_lines: rowptr[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
dnm: str = None
if db[0] == '%':
dnm = _peep_alloc_name(db, pool)
if dnm is not None and string.strcmp(dnm, a_name) == 0:
kill = 1
if kill == 0 and _peep_last_ref_eq(db, a_name) != 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: rowptr[Function], pool: rowptr[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
again: int = 1
while again != 0:
again = 0
hit_line: rowptr[Line] = None
nm_buf: str = None
src_buf: str = None
blk: rowptr[BasicBlock] = func.Blocks.Head
while blk is not None and hit_line is None:
if blk.Lines is not None:
ln: rowptr[Line] = blk.Lines.Head
while ln is not None and hit_line is None:
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
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'
hit_line = ln
ln = ln.Next
blk = blk.Next
if hit_line is None or nm_buf is None or src_buf is None:
return
# 全函数把 %nm 的引用替换为源操作数
blk = func.Blocks.Head
while blk is not None:
if blk.Lines is not None:
ln2: rowptr[Line] = blk.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)
if rb is not None and rb is not ln2.Buf:
ln2.Buf = rb
ln2.Len = string.strlen(rb)
ln2 = ln2.Next
blk = blk.Next
hit_line.Buf = None
again = 1
# ============================================================
# FunctionPrint: 将函数序列化为 IR 文本
#
@@ -675,10 +1049,14 @@ def FunctionPrint(buf: str, size: t.CSizeT,
"""将函数 func 序列化为 IR 文本写入 buf"""
if func is None or buf is None or size == 0: return
# 在序列化前重编号数字 SSA 名,确保按文本输出顺序单调递增。
# 解决 build_alloca_at_entry 在入口块终止指令前插入 alloca 导致
# SSA 名非单调(llc 报错 "instruction expected to be numbered '%N' or greater")。
# 先本地窥孔(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:
_peephole_opt(func, pool)
RenumberSSA(func, pool)
ty_buf: str = pool.alloc(128)
+2 -1
View File
@@ -367,9 +367,10 @@ class LLVMModule:
stdlib.exit(1)
if mode == OUTPUT_STUB:
# stub 模式:仅输出 declare(IsDeclared==1)
# FunctionPrint 的 declare 分支已以 "\n" 收尾(L768),这里不再
# 补换行,否则每个 declare 之间多出一个空行(美观/文件瘦身)。
if func.IsDeclared == 1:
FunctionPrint(buf, size, func, pool)
_append_cstr(buf, size, "\n")
elif mode == OUTPUT_TEXT:
# text 模式:仅输出 define(IsDeclared==0)
if func.IsDeclared == 0:
+1 -1
View File
@@ -167,7 +167,7 @@ def strspn(s: str, skip: str = " ") -> int:
return si
# Memory set function
export def memset(ptr: t.CVoid | t.CPtr, value: int, num: t.CSizeT) -> t.CVoid | t.CPtr:
export def memset(ptr: t.CPtr, value: int, num: t.CSizeT) -> t.CPtr:
# Original implementation:
# p: BYTEPTR = BYTEPTR(ptr)
# i: t.CSizeT