feat(sidecar): meta 增写 STRUCT/VTABLE,REPL 布局恢复改读 meta

- WriteModuleFuncMeta 更名 WriteModuleMeta:追加 STRUCT <name> FIELDS n PACKED 0|1
  (opaque → STRUCT <name> OPAQUE)与 VTABLE <class> SLOTS n;数据源改为
  Module.NamedTypeHead / GlobalHead,不再有任何 IR 文本反推
- VpiReader REPL 布局改读 {sha1}.meta:新增 _meta_atoi / _repl_meta_scan_layout
  按前缀精确匹配解析 STRUCT/VTABLE 行
- 删除旧 .stub.ll 文本解析辅助 _repl_stub_take_name / _repl_stub_body_count
  及其 scratch 缓冲 ReplStubNameBuf / _REPL_STUB_NAME_CAP

去 IR 化步骤③;未编译验证。
This commit is contained in:
TermiNexus committed 2026-10-03 08:18:15 +08:00
1 parent 78d3941f7d
commit 35f2070d3b
4 files changed
+123 -169

No files matched your search

+52 -154
View File
@@ -29,7 +29,6 @@ import lib.Projectrans.Config as Config
_REPL_STUB_CLS_MAX: t.CDefine = 1024
_REPL_STUB_FIELD_MAX: int = 128
_REPL_STUB_VT_MAX: int = 128
_REPL_STUB_NAME_CAP: sizet = 256
_REPL_STUB_TXT_CAP: sizet = 4194304
_REPL_STUB_WARN_MAX: int = 10
_REPL_STUB_FINISH_PASSES: int = 8
@@ -3686,7 +3685,6 @@ ReplStubWarnLeft: int = 0
ReplStubTmpCount: int = -1
ReplStubTmpPacked: int = 0
ReplStubTmpArity: int = 0
ReplStubNameBuf: str = None
def _repl_stub_dupstr(pool: memhub.MemBuddy*, src: str) -> str:
@@ -3761,105 +3759,31 @@ def _repl_stub_readtext(path: str) -> str:
return buf
# 从 txt[pos] 起取一个标识符(跳过 '%' / '@' 前缀与可选引号)写进 ReplStubNameBuf;
# 返回名字之后的位置(0 = 失败/超长;合法调用至少前进 2 字符,故 0 可作失败哨兵)
def _repl_stub_take_name(txt: str, pos: sizet) -> sizet:
global ReplStubNameBuf
if txt is None or ReplStubNameBuf is None:
return 0
n: sizet = string.strlen(txt)
i: sizet = pos
if i < n and (txt[i] == '%' or txt[i] == '@'):
# 从 txt[start, end) 解析十进制整数(跳过前导空格;无数字返回 0)
def _meta_atoi(txt: str, start: sizet, end: sizet) -> int:
i: sizet = start
while i < end and (txt[i] == ' ' or txt[i] == '\t'):
i += 1
if i >= n:
return 0
if txt[i] == '\"':
neg: int = 0
if i < end and txt[i] == '-':
neg = 1
i += 1
k: sizet = 0
while i < n and txt[i] != '\"':
if k + 1 >= _REPL_STUB_NAME_CAP:
return 0
ReplStubNameBuf[k] = txt[i]
k += 1
i += 1
if i >= n:
return 0
ReplStubNameBuf[k] = 0
return i + 1
k2: sizet = 0
while i < n:
ch: char = txt[i]
if ch == ' ' or ch == '\t' or ch == '=' or ch == ',' or ch == '{' or ch == '}':
break
if ch == '[' or ch == ']' or ch == '(' or ch == ')' or ch == '*' or ch == '<' or ch == '>':
break
if k2 + 1 >= _REPL_STUB_NAME_CAP:
return 0
ReplStubNameBuf[k2] = ch
k2 += 1
v: int = 0
while i < end and txt[i] >= '0' and txt[i] <= '9':
v = v * 10 + (int(txt[i]) - 48)
i += 1
if k2 == 0:
return 0
ReplStubNameBuf[k2] = 0
return i
if neg != 0:
return -v
return v
# 从 txt[pos](应指向 '{')起数顶层字段数:{} / [] / () / <> / "..." 内的逗号不计;
# 返回字段数(-1 = 未闭合/格式异常)
def _repl_stub_body_count(txt: str, pos: sizet) -> int:
if txt is None:
return -1
n: sizet = string.strlen(txt)
i: sizet = pos
if i >= n or txt[i] != '{':
return -1
i += 1
depth: int = 0
in_str: int = 0
cnt: int = 0
has: int = 0
while i < n:
ch: char = txt[i]
if in_str != 0:
if ch == '\"':
in_str = 0
i += 1
continue
if ch == '\"':
in_str = 1
has = 1
i += 1
continue
if ch == 0:
return -1
if ch == ',':
if depth == 0:
cnt += 1
i += 1
continue
if ch == '{' or ch == '[' or ch == '(' or ch == '<':
depth += 1
has = 1
i += 1
continue
if ch == '}' or ch == ']' or ch == ')' or ch == '>':
if ch == '}' and depth == 0:
if has == 0:
return 0
return cnt + 1
depth -= 1
i += 1
continue
if ch != ' ' and ch != '\t' and ch != '\n' and ch != '\r':
has = 1
i += 1
return -1
# 扫描存根文本,取 `%"<key>" = type ...` 与 `@...<key>.vtable = ...` 两条元数据,
# 结果写全局 scratch:ReplStubTmpCount(字段数 / -1 无此类 / -2 opaque)、
# ReplStubTmpPacked、ReplStubTmpArity(.vtable 槽数,0 = 无该全局)
def _repl_stub_scan_meta(txt: str, key: str) -> None:
# 扫描 {sha1}.meta 文本,取 `STRUCT <key> FIELDS <n> PACKED <0|1>`(或 `STRUCT <key> OPAQUE`)
# 与 `VTABLE <key> SLOTS <n>` 两条元数据,结果写全局 scratch:
# ReplStubTmpCount(字段数 / -1 无此类 / -2 opaque)、ReplStubTmpPacked、
# ReplStubTmpArity(虚表槽数,0 = 无该虚表)。key = "{sha1}.{类名}"。
# 逐行、按前缀 strncmp 精确匹配,且 name 之后必须紧跟空格(避免前缀误命中);
# 不走 take_name —— 类名可能含泛型下标 [ ] 等字符。
def _repl_meta_scan_layout(txt: str, key: str) -> None:
global ReplStubTmpCount, ReplStubTmpPacked, ReplStubTmpArity
ReplStubTmpCount = -1
ReplStubTmpPacked = 0
@@ -3868,6 +3792,8 @@ def _repl_stub_scan_meta(txt: str, key: str) -> None:
return
n: sizet = string.strlen(txt)
key_len: sizet = string.strlen(key)
if key_len == 0:
return
i: sizet = 0
while i < n:
ls: sizet = i
@@ -3876,61 +3802,37 @@ def _repl_stub_scan_meta(txt: str, key: str) -> None:
le: sizet = i
if i < n:
i += 1
p: sizet = ls
while p < le and (txt[p] == ' ' or txt[p] == '\t' or txt[p] == '\r'):
p += 1
if p >= le:
continue
c0: char = txt[p]
if c0 == '%':
# 类型定义行
q: sizet = _repl_stub_take_name(txt, p)
if q == 0 or q > le:
if le > ls and txt[le - 1] == '\r':
le -= 1
if ls + 7 <= le and string.strncmp(txt + ls, "STRUCT ", 7) == 0:
ns: sizet = ls + 7
ne: sizet = ns + key_len
if ne > le or string.strncmp(txt + ns, key, key_len) != 0:
continue
if string.strcmp(ReplStubNameBuf, key) != 0:
if ne >= le or txt[ne] != ' ':
continue
# 找 "= type"
j: sizet = q
while j + 6 < le and string.strncmp(txt + j, "= type", 6) != 0:
j += 1
if j + 6 >= le:
continue
j += 6
while j < le and (txt[j] == ' ' or txt[j] == '\t'):
j += 1
if j + 6 <= le and string.strncmp(txt + j, "opaque", 6) == 0:
# OPAQUE
if ne + 7 <= le and string.strncmp(txt + ne + 1, "OPAQUE", 6) == 0:
ReplStubTmpCount = -2
continue
if j < le and txt[j] == '<':
ReplStubTmpPacked = 1
j += 1
if j < le and txt[j] == '{':
ReplStubTmpCount = _repl_stub_body_count(txt, j)
# FIELDS <n> PACKED <0|1>
if ne + 8 <= le and string.strncmp(txt + ne + 1, "FIELDS ", 7) == 0:
ReplStubTmpCount = _meta_atoi(txt, ne + 8, le)
ps: sizet = ne + 8
while ps < le and txt[ps] >= '0' and txt[ps] <= '9':
ps += 1
if ps + 8 <= le and string.strncmp(txt + ps + 1, "PACKED ", 7) == 0:
ReplStubTmpPacked = _meta_atoi(txt, ps + 8, le)
continue
if c0 == '@':
# 全局变量行:只关心 <key>.vtable
q2: sizet = _repl_stub_take_name(txt, p)
if q2 == 0 or q2 > le:
if ls + 7 <= le and string.strncmp(txt + ls, "VTABLE ", 7) == 0:
vs: sizet = ls + 7
ve: sizet = vs + key_len
if ve > le or string.strncmp(txt + vs, key, key_len) != 0:
continue
nm_len: sizet = string.strlen(ReplStubNameBuf)
if nm_len != key_len + 7:
if ve >= le or txt[ve] != ' ':
continue
if string.strncmp(ReplStubNameBuf, key, key_len) != 0:
continue
if string.strncmp(ReplStubNameBuf + key_len, ".vtable", 7) != 0:
continue
j2: sizet = q2
while j2 < le and txt[j2] != '=':
j2 += 1
if j2 >= le:
continue
j2 += 1
while j2 < le and txt[j2] != '{':
j2 += 1
if j2 < le and txt[j2] == '{':
ar: int = _repl_stub_body_count(txt, j2)
if ar > 0:
ReplStubTmpArity = ar
if ve + 7 <= le and string.strncmp(txt + ve + 1, "SLOTS ", 6) == 0:
ReplStubTmpArity = _meta_atoi(txt, ve + 7, le)
continue
return
@@ -3939,7 +3841,7 @@ def ReplStubLayoutBegin(temp_dir: str) -> int:
"""开启会话级"存根布局恢复"(Repl.vp 在 includes 类注册前调用一次);0=成功"""
global ReplStubLayoutMode, ReplStubPool, ReplStubTempDir, ReplStubTable
global ReplStubCount, ReplStubOk, ReplStubVtOk, ReplStubRefused, ReplStubNoStub
global ReplStubWarnLeft, ReplStubNameBuf
global ReplStubWarnLeft
global ReplStubRefusedNoStub, ReplStubRefusedNoClass, ReplStubRefusedOpaque, ReplStubRefusedParent
pool: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if pool is None or temp_dir is None:
@@ -3952,10 +3854,6 @@ def ReplStubLayoutBegin(temp_dir: str) -> int:
if ReplStubTable is None:
return 1
string.memset(ReplStubTable, 0, 8 * _REPL_STUB_CLS_MAX)
ReplStubNameBuf = pool.alloc(_REPL_STUB_NAME_CAP)
if ReplStubNameBuf is None:
return 1
ReplStubNameBuf[0] = 0
ReplStubCount = 0
ReplStubOk = 0
ReplStubVtOk = 0
@@ -4508,7 +4406,7 @@ def _repl_stub_add_class(pool: memhub.MemBuddy*,
# 存根元数据(缺存根时 LayoutCount 保持"未找到"哨兵;两种成因由 have_stub 区分)
e.LayoutCount = _REPL_STUB_NOCOUNT_NOCLASS
if txt is not None:
_repl_stub_scan_meta(txt, key)
_repl_meta_scan_layout(txt, key)
e.LayoutCount = ReplStubTmpCount
e.VtArity = ReplStubTmpArity
if ReplStubTmpPacked != 0:
@@ -4564,9 +4462,9 @@ def _repl_stub_collect_module(pool: memhub.MemBuddy*,
return 0
if src_sha1 is None or src_sha1[0] == '\0' or ReplStubTable is None:
return 0
# 存根文本(缺失:带父类/虚表的类将因"数据缺失"被明确拒绝,逐个可见)
# 布局元数据({sha1}.meta;缺失:带父类/虚表的类将因"数据缺失"被明确拒绝,逐个可见)
txt: str = None
sp: str = _repl_stub_path(src_sha1, "stub.ll")
sp: str = _repl_stub_path(src_sha1, "meta")
if sp is not None:
txt = _repl_stub_readtext(sp)
stdlib.free(sp)
@@ -4577,8 +4475,8 @@ def _repl_stub_collect_module(pool: memhub.MemBuddy*,
ReplStubNoStub += 1
if ReplStubWarnLeft > 0:
ReplStubWarnLeft -= 1
stdio.printf("includes 类布局: 模块 %s 无 .stub.ll 存根(数据缺失)—— 该模块"
"带父类/虚表的类将逐个按『数据缺失』明确拒绝(读不到存根 ⇒ 无"
stdio.printf("includes 类布局: 模块 %s 无 .meta 存根(数据缺失)—— 该模块"
"带父类/虚表的类将逐个按『数据缺失』明确拒绝(读不到布局元数据 ⇒ 无"
"字段数可比,不是字段数不符)。用同一工程先跑一次批处理生成存根,"
"再 `--project .\\project.vpj --repl`(TransPyV 目录)即可复用\n",
src_sha1)
+65 -9
View File
@@ -31,21 +31,25 @@ import lib.Stub.StubMerger as StubMerger
# ============================================================
# ============================================================
# WriteModuleFuncMeta - 落盘 {sha1}.meta 的函数定义清单(结构化 sidecar)
# WriteModuleMeta - 落盘 {sha1}.meta(模块结构化 sidecar:函数清单 + 结构体布局 + 虚表)
#
# 每行 "FUNC <mangled> DEF|DECL":DEF = 本模块定义了该符号(有函数体,等同 .text.ll 的 define 行),
# DECL = 仅声明(等同 .stub.ll 的 declare)。数据源为翻译期模型(Module.FuncHead 链表 +
# Function.IsDeclared),与 dump_ir 的 stub/text 判据同源(STUB=IsDeclared==1、TEXT==0),
# 取代「从 IR 文本反推 declare-vs-define」。
# 行格式(每行一条,纯文本、无嵌套括号):
# FUNC <mangled> DEF|DECL DEF=本模块定义了该符号(有函数体,等同 .text.ll 的 define 行)
# STRUCT <sha1>.<类名> FIELDS <n> PACKED <0|1> 命名结构体字段数/是否 packed
# STRUCT <sha1>.<类名> OPAQUE 前向声明(opaque,无 body)
# VTABLE <sha1>.<类名> SLOTS <n> 虚表槽数(全局 @<类名>.vtable 的元素数)
# 数据源均为翻译期模型(Module.FuncHead / NamedTypeHead / GlobalHead),与 dump_ir 同源,
# 取代「从 IR 文本反推 declare-vs-define / 结构体布局 / 虚表槽数」。
#
# 写不出(路径/打开失败)即 fail-fast:缺 meta 会使 Phase B+/C 的「声明文件判定」失效。
# 写不出(路径/打开失败)即 fail-fast:缺 meta 会使 Phase B+/C 的声明文件判定与
# REPL 的布局恢复失效。
#
# Args: mod 已翻译完成的 LLVM 模块;temp_dir 基础目录;td_len temp_dir 长度;sha1 模块 SHA1
# Returns: 0=成功(失败经 VLogger.error 终止进程)
# ============================================================
def WriteModuleFuncMeta(mod: llvmlite.LLVMModule*, temp_dir: str,
td_len: sizet, sha1: str) -> int:
"""把本模块的函数定义清单写入 {sha1}.meta"""
def WriteModuleMeta(mod: llvmlite.LLVMModule*, temp_dir: str,
td_len: sizet, sha1: str) -> int:
"""把本模块的函数定义/结构体布局/虚表清单写入 {sha1}.meta"""
if mod is None or temp_dir is None or sha1 is None:
VLogger.error("meta 写出:参数为空", "Meta")
return 1
@@ -78,6 +82,58 @@ def WriteModuleFuncMeta(mod: llvmlite.LLVMModule*, temp_dir: str,
mf.write(line, string.strlen(line))
stdlib.free(line)
cur = llvmlite.function_get_next(cur)
# 2) 结构体布局:STRUCT <name> FIELDS <n> PACKED <0|1>(opaque → STRUCT <name> OPAQUE)
nt: llvmlite.NamedTypeNode* = mod.NamedTypeHead
nt_steps: sizet = 0
while nt is not None:
nt_steps += 1
if nt_steps > 100000:
VLogger.error("meta 写出:命名类型链表成环/超限", "Meta")
break
tn: str = llvmlite.get_struct_name(nt.Ty)
if tn is not None and tn[0] != '\0':
tl: sizet = string.strlen(tn)
lb: bytes = stdlib.malloc(tl + 48)
if lb is None:
VLogger.error("meta 写出:行缓冲分配失败", "Meta")
break
fc: int = llvmlite.get_struct_fcount(nt.Ty)
if fc < 0:
viperlib.snprintf(lb, tl + 48, "STRUCT %s OPAQUE\n", tn)
else:
viperlib.snprintf(lb, tl + 48, "STRUCT %s FIELDS %d PACKED %d\n", tn, fc, nt.IsPacked)
mf.write(lb, string.strlen(lb))
stdlib.free(lb)
nt = nt.Next
# 3) 虚表槽数:VTABLE <class> SLOTS <n>(全局名 <class>.vtable,Ty 匿名结构体 fcount=槽数)
gv: llvmlite.GlobalVariable* = mod.GlobalHead
gv_steps: sizet = 0
while gv is not None:
gv_steps += 1
if gv_steps > 100000:
VLogger.error("meta 写出:全局变量链表成环/超限", "Meta")
break
gn: str = gv.Name
if gn is not None:
gnl: sizet = string.strlen(gn)
if gnl > 7 and string.strcmp(gn + gnl - 7, ".vtable") == 0:
base_len: sizet = gnl - 7
base: bytes = stdlib.malloc(base_len + 1)
if base is not None:
string.strncpy(base, gn, base_len)
base[base_len] = '\0'
ar: int = llvmlite.get_struct_fcount(gv.Ty)
if ar < 0:
ar = 0
lb2: bytes = stdlib.malloc(base_len + 40)
if lb2 is None:
VLogger.error("meta 写出:行缓冲分配失败", "Meta")
else:
viperlib.snprintf(lb2, base_len + 40, "VTABLE %s SLOTS %d\n", base, ar)
mf.write(lb2, string.strlen(lb2))
stdlib.free(lb2)
stdlib.free(base)
gv = gv.Next
mf.close()
stdlib.free(mpath)
return 0