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:
1 parent
45e78ad107
commit
76d1ee6076
173 files changed
+1104
-3090
No files matched your search
@@ -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__())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user