Files
ViperC/App/lib/Stub/StubMerger/StubMergerBuild.vp
T
TermiNexus 825fe6d326 fix(TransPyV): 指针 cast 的跨层 idiom 修正:(str | t.CPtr)(x) → rawptr(x)
c 类(源码违规):(str | t.CPtr)(x) 按既定语义 = i8**(char**,指针的指针),

而这些位置要的是 i8*。TPC 的 return/赋值检查现在按声明精确校验(本次即

TypeGenResolve.vp:1221 `return (str | t.CPtr)(v)` 声明返回 i8* 却 return i8** 被抳下),

该 idiom 在旧的无条件 bitcast 下一直不可见。

改法:86 处 (str | t.CPtr)(X) → rawptr(X)(rawptr 是 t.CPtr 的内置名,同为 i8*;

整数操作数走 inttoptr,指针操作数走 bitcast)。不用 str(X):整数操作数会编译成 itoa

(Gen/FuncGenMixin.vp:1159 注释已记录该陷阱),精确类型仍由使用处的 `: str` 标注承担。

保留 4 处本就该是 i8** 的用例(未改):Converter.vp:820(_g_pyi_cdef_names 声明为

str | t.CPtr 的指针数组)、CallGenCBuiltin.vp:1194/1215(声明即 str | t.CPtr)、

1411(先 cast 再 [0] 取首元素)。

另:Gen/Type/TypeGenResolve.vp:1221 手工改为 return t.CPtr(v)(该文件含你的未提交改动,未一并提交)。
2026-09-28 18:43:59 +08:00

1917 lines
94 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 lib.core.Gen.GenApi as GenApi
import t, c
from stdint import *
import memhub
import string
import stdlib
import llvmlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import viperlib
import sys
import ast
import lib.core.VLogger as VLogger
import lib.core.IncludesScanner as IncludesScanner
import lib.core.Gen.Translator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.Stub.StubMerger.StubMergerTopo as StubMergerTopo
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
import lib.Stub.StubMerger as StubMerger
# ============================================================
# 已见符号表(BuildCombinedIR 去重 O(n²)→O(1))
#
# 用定长槽位开放寻址哈希表记录 out_buf 中已活跃的 declare/global/type 行,替代全量扫描去重(O(1) 查找与定位)。
# slot 布局(步长 _SEEN_SLOT):{u64 hash} {u64 pos} {u8 flags} {name[_SEEN_NAME_MAX]} {u8 nul};空槽判定 hash==0(memset 全零初始化)。
# flags(仅类型表)bit0=1 表示活跃行为完整定义(= type { / = type <),bit0=0 表示仅 opaque 声明;函数/全局表恒为 0。
# pos 为活跃行在 out_buf 中的起始位置(注释操作直接 out_buf[pos]=';');注释行不从表中删除——注释后必然立即追加新行(更新 pos),表保持「有活跃行」不变量。
# 超长名(>= _SEEN_NAME_MAX)不登记,防御性回退;表满(count >= cap)fail-fast 报错:静默丢弃登记会让去重失效,重复行追加 → llc redefinition。
# ============================================================
_SEEN_NAME_MAX: t.CDefine = 256
_SEEN_SLOT: t.CDefine = 274 # 8(hash)+8(pos)+1(flags)+256(name)+1(nul)
_SEEN_FUNC_CAP: t.CDefine = 16384
_SEEN_GLOBAL_CAP: t.CDefine = 16384
_SEEN_TYPE_CAP: t.CDefine = 4096
_SEEN_FNV_OFFSET: uint64 = 0xcbf29ce484222325
_SEEN_FNV_PRIME: uint64 = 0x100000001b3
_seen_funcs: bytes # 已见函数签名表(@"sha1.name"( 等)
_seen_funcs_count: int
_seen_globals: bytes # 已见全局变量名表(@name)
_seen_globals_count: int
_seen_types: bytes # 已见类型名表(%name)
_seen_types_count: int
_seen_key_scratch: bytes # 注册键暂存(跨 truncation 窗口复制)
def _SeenHash(key: str) -> uint64:
"""FNV-1a 64 位哈希(对齐 includes/hashtable.vp)"""
h: uint64 = _SEEN_FNV_OFFSET
p: str = key
while p[0] != 0:
h = h ^ uint64(p[0])
h = h * _SEEN_FNV_PRIME
p += 1
if h < 2:
h = 2
return h
def _SeenFind(tbl: bytes, cap: sizet, key: str, key_hash: uint64) -> sizet:
"""已见表槽位查找:命中返回槽位下标;未命中返回 cap(哨兵)"""
if tbl is None or key is None:
return cap
mask: sizet = cap - 1
idx: sizet = sizet(key_hash) & mask
i: sizet = 0
while i < cap:
off: sizet = idx * _SEEN_SLOT
hp: uint64 | t.CPtr = (t.CPtr)(uint64(tbl) + off)
sh: uint64 = hp[0]
if sh == 0:
return cap
if sh == key_hash:
nm: str = tbl + off + 17
if string.strcmp(nm, key) == 0:
return idx
idx = (idx + 1) & mask
i += 1
return cap
def _SeenPresent(tbl: bytes, cap: sizet, key: str, key_hash: uint64) -> int:
"""已见表查找:返回 0=不存在;>0=存在(返回值 = flags+1)"""
si: sizet = _SeenFind(tbl, cap, key, key_hash)
if si >= cap:
return 0
return int(tbl[si * _SEEN_SLOT + 16]) + 1
def _SeenInsert(tbl: bytes, cap: sizet, count: int, key: str, key_hash: uint64,
flags: int, pos: sizet) -> int:
"""已见表插入/更新:key 不存在则新登记(返回 count+1);已存在则 OR flags、更新 pos"""
if tbl is None or key is None:
return count
kl: sizet = string.strlen(key)
if kl >= _SEEN_NAME_MAX:
return count
if count >= cap:
VLogger.error(f"已见符号表满(容量 {cap}),新增符号登记被丢弃——去重失效,必须增大容量", "StubMerger")
return count
mask: sizet = cap - 1
idx: sizet = sizet(key_hash) & mask
i: sizet = 0
while i < cap:
off: sizet = idx * _SEEN_SLOT
hp: uint64 | t.CPtr = (t.CPtr)(uint64(tbl) + off)
sh: uint64 = hp[0]
if sh == 0:
hp[0] = key_hash
pp: uint64 | t.CPtr = (t.CPtr)(uint64(tbl) + off + 8)
pp[0] = uint64(pos)
tbl[off + 16] = char(flags)
string.strcpy(tbl + off + 17, key)
return count + 1
if sh == key_hash:
nm: str = tbl + off + 17
if string.strcmp(nm, key) == 0:
pp: uint64 | t.CPtr = (t.CPtr)(uint64(tbl) + off + 8)
pp[0] = uint64(pos)
tbl[off + 16] = char(int(tbl[off + 16]) | flags)
return count
idx = (idx + 1) & mask
i += 1
return count
def _SeenCommentOut(tbl: bytes, cap: sizet, key: str, key_hash: uint64,
out_buf: bytes) -> int:
"""注释 out_buf 中该符号的活跃行(行首改 ';'),返回 1=已注释 0=无"""
si: sizet = _SeenFind(tbl, cap, key, key_hash)
if si >= cap:
return 0
if int(tbl[si * _SEEN_SLOT + 16]) != 0:
return 0 # 类型表 flags=1:活跃行为完整定义,无可注释的 opaque
pp: uint64 | t.CPtr = (t.CPtr)(uint64(tbl) + si * _SEEN_SLOT + 8)
p: uint64 = pp[0]
if out_buf is not None:
out_buf[sizet(p)] = ';'
return 1
def _SeenGetPos(tbl: bytes, cap: sizet, key: str, key_hash: uint64) -> sizet:
"""读取已见表条目记录的活跃行位置;不存在返回 cap(哨兵)"""
si: sizet = _SeenFind(tbl, cap, key, key_hash)
if si >= cap:
return cap
pp: uint64 | t.CPtr = (t.CPtr)(uint64(tbl) + si * _SEEN_SLOT + 8)
return sizet(pp[0])
def _RegisterStubLines(buf: bytes, buf_len: sizet, base_pos: sizet):
"""把本地 stub(整体复制进 out_buf)中的 declare/global/type 全部登记到已见表
本地 stub.ll 在 BuildCombinedIR 第 1 步整体 strcpy 进 out_buf,不做逐行
去重处理,若不同步登记,后续依赖的同名行查表未命中会重复追加 →
llc "redefinition of type/global"(7871cc1a... ll:758 根因)。
行位置 = base_pos + 行内偏移(复制是 1:1 保序),供注释操作定位。
同名重复行(如 61d41cf89...stub.ll 的 _g_gen 出现两次)在复制前将
行首改为 ';'(注释掉),表只指向首个活跃行(61d41cf89... ll:9853 根因);
若先 opaque 后完整定义,则注释旧行、表升级指向完整定义行。
"""
global _seen_funcs_count, _seen_globals_count, _seen_types_count
if buf is None:
return
pos: sizet = 0
while pos < buf_len:
ls: sizet = pos
while pos < buf_len:
if buf[pos] == '\n':
break
pos += 1
ll: sizet = pos - ls
if pos < buf_len:
pos += 1
line_pos: sizet = base_pos + ls
# declare → 函数签名 @"name"(
if ll >= 8 and string.strncmp(buf + ls, "declare ", 8) == 0:
at: sizet = ls + 8
while at < ls + ll:
if buf[at] == '@':
break
at += 1
if at < ls + ll:
lp: sizet = at + 1
while lp < ls + ll:
if buf[lp] == '(':
break
lp += 1
if lp < ls + ll:
sv_f: char = buf[lp + 1]
buf[lp + 1] = '\0'
kh_f: uint64 = _SeenHash(buf + at)
if _SeenPresent(_seen_funcs, _SEEN_FUNC_CAP, buf + at, kh_f) == 0:
_seen_funcs_count = _SeenInsert(_seen_funcs, _SEEN_FUNC_CAP, _seen_funcs_count, buf + at, kh_f, 0, line_pos)
else:
buf[ls] = ';'
buf[lp + 1] = sv_f
continue
# 全局 @name =(键 @name,截断于 '=' 前)
if ll > 0 and buf[ls] == '@':
# 字符串常量 @.str.{sha1}.{N}:本地 stub 的 external 声明是死代码
# (本地 text.ll 有 internal constant 定义),注释掉避免与定义重复
if ll >= 5 and buf[ls + 1] == '.' and buf[ls + 2] == 's' and buf[ls + 3] == 't' and buf[ls + 4] == 'r':
buf[ls] = ';'
continue
eq: sizet = ls + 1
while eq < ls + ll:
if buf[eq] == '=':
break
eq += 1
if eq < ls + ll:
sv_g: char = buf[eq]
buf[eq] = '\0'
kh_g: uint64 = _SeenHash(buf + ls)
if _SeenPresent(_seen_globals, _SEEN_GLOBAL_CAP, buf + ls, kh_g) == 0:
_seen_globals_count = _SeenInsert(_seen_globals, _SEEN_GLOBAL_CAP, _seen_globals_count, buf + ls, kh_g, 0, line_pos)
else:
buf[ls] = ';'
buf[eq] = sv_g
continue
# 类型 %name = type ...(完整定义 flags=1,opaque flags=0)
if ll > 0 and buf[ls] == '%':
eq: sizet = ls + 1
while eq < ls + ll:
if buf[eq] == '=':
break
eq += 1
if eq < ls + ll:
sv_t: char = buf[eq]
buf[eq] = '\0'
kh_t: uint64 = _SeenHash(buf + ls)
is_full: int = 0
if eq + 8 <= ls + ll:
if buf[eq + 2] == 't' and buf[eq + 3] == 'y' and buf[eq + 4] == 'p' and buf[eq + 5] == 'e' and buf[eq + 6] == ' ':
if buf[eq + 7] == '{' or buf[eq + 7] == '<':
is_full = 1
prev_t: int = _SeenPresent(_seen_types, _SEEN_TYPE_CAP, buf + ls, kh_t)
if prev_t == 0:
_seen_types_count = _SeenInsert(_seen_types, _SEEN_TYPE_CAP, _seen_types_count, buf + ls, kh_t, is_full, line_pos)
elif is_full == 1 and prev_t == 1:
# 先 opaque 后完整定义:注释旧行,表升级指向新完整定义行
old_pos: sizet = _SeenGetPos(_seen_types, _SEEN_TYPE_CAP, buf + ls, kh_t)
if old_pos >= base_pos and old_pos < base_pos + buf_len:
buf[old_pos - base_pos] = ';'
_seen_types_count = _SeenInsert(_seen_types, _SEEN_TYPE_CAP, _seen_types_count, buf + ls, kh_t, 1, line_pos)
else:
buf[ls] = ';'
buf[eq] = sv_t
continue
# ============================================================
# StubMergerBuild - BuildCombinedIR:组合本地 stub + 依赖 stubs + 本地 text
#
# 函数实现直接位于本模块,调用方经本模块前缀访问;全局变量跨模块经 StubMerger._xxx 访问。
# ============================================================
# ============================================================
# _LoadAndAppendStub - 加载依赖 stub 并追加到 out_buf
#
# 读取 {temp_dir}/{sha1}.stub.ll,跳过 header(;, target, source_filename),
# 去重 declare/global,追加到 out_buf。返回新 out_pos。
# ============================================================
def _LoadAndAppendStub(temp_dir: str, td_len: sizet, dep_sha1: str,
dep_buf: bytes, out_buf: bytes, out_size: sizet,
out_pos: sizet) -> sizet:
"""加载依赖 stub 并追加(跳过 header,去重)"""
global _seen_funcs_count, _seen_globals_count, _seen_types_count
if temp_dir is None or dep_sha1 is None or dep_buf is None or out_buf is None:
return out_pos
dep_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, dep_sha1, "stub.ll")
if dep_path is None:
return out_pos
df_ls: rawptr[fileio.File] = fileio.File(dep_path, fileio.MODE.R)
if df_ls.closed:
stdlib.free(dep_path)
# 依赖 stub 缺失:includes 映射内的模块缺失属编译错误(fail-fast),
# 非映射模块(如 t/c 内部库、用户文件)按设计跳过
if StubMergerStore.LookupSha1RelPath(dep_sha1) is not None:
VLogger.error(f"依赖 stub 缺失: {dep_sha1}", "StubMerger")
return out_pos
dep_br_ls: int64 = df_ls.read_all(dep_buf, StubMerger.STUB_READ_BUF_SIZE)
df_ls.close()
stdlib.free(dep_path)
if dep_br_ls <= 0:
# 依赖 stub 为空:纯声明/纯常量模块(如 stdio.vp 全 pass、CDefine 模块)
# 无可声明符号,stub.ll 为空属合法形态,静默跳过
return out_pos
if dep_br_ls < StubMerger.STUB_READ_BUF_SIZE:
dep_buf[dep_br_ls] = '\0'
# 注释掉依赖 stub 中的字符串常量行(模块内部的,不需要)
# 简单方案:搜索 "= external unnamed_addr constant" 并将行首的 @ 改为 ;@
cfs_pos: sizet = 0
while cfs_pos + 30 < sizet(dep_br_ls):
ext_p: t.CPtr = string.strstr(dep_buf + cfs_pos, "= external unnamed_addr constant")
if ext_p is None:
break
ext_off2: sizet = sizet(uint64(ext_p) - uint64(dep_buf))
# 向前查找行首的 @
ls: sizet = ext_off2
while ls > 0:
if dep_buf[ls] == '\n':
ls += 1
break
ls -= 1
if ls < ext_off2 and dep_buf[ls] == '@':
dep_buf[ls] = ';'
cfs_pos = ext_off2 + 30
# 跳过 header,追加内容(去重 declare/global/type,已见符号表 O(1) 去重)
dep_pos: sizet = 0
skipping: int = 1
while dep_pos < dep_br_ls:
line_start: sizet = dep_pos
while dep_pos < dep_br_ls:
if dep_buf[dep_pos] == '\n':
break
dep_pos += 1
line_len: sizet = dep_pos - line_start
if dep_pos < dep_br_ls:
dep_pos += 1
if skipping != 0:
if line_len == 0:
continue
ch0: int = dep_buf[line_start]
if ch0 == ';':
continue
if ch0 == 't' and line_len >= 6:
if string.strncmp(dep_buf + line_start, "target", 6) == 0:
continue
if ch0 == 's' and line_len >= 15:
if string.strncmp(dep_buf + line_start, "source_filename", 15) == 0:
continue
skipping = 0
# 注册暂存(append 成功后才登记;reg_key 指向 scratch,insert 当轮完成)
reg_tbl: int = 0
reg_key: str = None
reg_hash: uint64 = 0
reg_flags: int = 0
# 去重:declare(用已见函数表,键 @"name"(,避免误匹配 call 语句)
if line_len >= 8 and string.strncmp(dep_buf + line_start, "declare ", 8) == 0:
at: sizet = line_start + 8
while at < line_start + line_len:
if dep_buf[at] == '@':
break
at += 1
if at < line_start + line_len:
lp: sizet = at + 1
while lp < line_start + line_len:
if dep_buf[lp] == '(':
break
lp += 1
if lp < line_start + line_len:
saved: char = dep_buf[lp + 1]
dep_buf[lp + 1] = '\0'
reg_hash = _SeenHash(dep_buf + at)
if _SeenPresent(_seen_funcs, _SEEN_FUNC_CAP, dep_buf + at, reg_hash) != 0:
dep_buf[lp + 1] = saved
continue
string.strcpy(_seen_key_scratch, dep_buf + at)
reg_tbl = 1
reg_key = _seen_key_scratch
reg_flags = 0
dep_buf[lp + 1] = saved
# 去重:external global(键 @name,截断于 '=' 前)
if line_len > 0 and dep_buf[line_start] == '@':
# 字符串常量 @.str.{sha1}.{N}:依赖 stub 的 external 声明是死代码
# (定义在模块自身 text.ll,合并 IR 中依赖方无需声明),跳过
if line_len >= 5 and dep_buf[line_start + 1] == '.' and dep_buf[line_start + 2] == 's' \
and dep_buf[line_start + 3] == 't' and dep_buf[line_start + 4] == 'r':
continue
eq: sizet = line_start + 1
while eq < line_start + line_len:
if dep_buf[eq] == '=':
break
eq += 1
if eq < line_start + line_len:
saved: char = dep_buf[eq]
dep_buf[eq] = '\0'
reg_hash = _SeenHash(dep_buf + line_start)
if _SeenPresent(_seen_globals, _SEEN_GLOBAL_CAP, dep_buf + line_start, reg_hash) != 0:
dep_buf[eq] = saved
continue
string.strcpy(_seen_key_scratch, dep_buf + line_start)
reg_tbl = 2
reg_key = _seen_key_scratch
reg_flags = 0
dep_buf[eq] = saved
# 去重:type 定义(如 %"sha1.Point" = type {i32, i32})
# 区分完整定义(= type { ... } / = type < ... >)和 opaque 声明(= type opaque)
# 当本地 stub 有 opaque 声明而依赖 stub 有完整定义时,注释掉 opaque 行,
# 让完整定义被追加,避免 LLC "Cannot allocate unsized type" 错误。
if line_len > 0 and dep_buf[line_start] == '%':
eq2: sizet = line_start + 1
while eq2 < line_start + line_len:
if dep_buf[eq2] == '=':
break
eq2 += 1
if eq2 < line_start + line_len:
saved2: char = dep_buf[eq2 + 1]
dep_buf[eq2 + 1] = '\0'
# 检查依赖行是否为完整定义(= type { 或 = type <)
dep_is_full_def: int = 0
if eq2 + 8 <= line_start + line_len:
if dep_buf[eq2 + 2] == 't' and dep_buf[eq2 + 3] == 'y' and dep_buf[eq2 + 4] == 'p' and dep_buf[eq2 + 5] == 'e' and dep_buf[eq2 + 6] == ' ':
if dep_buf[eq2 + 7] == '{' or dep_buf[eq2 + 7] == '<':
dep_is_full_def = 1
saved_eq: char = dep_buf[eq2]
dep_buf[eq2] = '\0'
reg_hash = _SeenHash(dep_buf + line_start)
if dep_is_full_def == 1:
# 1. 注释掉 out_buf 中的 opaque 声明(如果有)
_SeenCommentOut(_seen_types, _SEEN_TYPE_CAP, dep_buf + line_start, reg_hash, out_buf)
# 2. 检查是否已有完整定义(避免重复)
if _SeenPresent(_seen_types, _SEEN_TYPE_CAP, dep_buf + line_start, reg_hash) == 2:
dep_buf[eq2] = saved_eq
dep_buf[eq2 + 1] = saved2
continue
# 3. 追加完整定义
string.strcpy(_seen_key_scratch, dep_buf + line_start)
reg_tbl = 3
reg_key = _seen_key_scratch
reg_flags = 1
else:
# opaque 声明:已存在同名(任意形态)则跳过
if _SeenPresent(_seen_types, _SEEN_TYPE_CAP, dep_buf + line_start, reg_hash) != 0:
dep_buf[eq2] = saved_eq
dep_buf[eq2 + 1] = saved2
continue
string.strcpy(_seen_key_scratch, dep_buf + line_start)
reg_tbl = 3
reg_key = _seen_key_scratch
reg_flags = 0
dep_buf[eq2] = saved_eq
dep_buf[eq2 + 1] = saved2
# 追加(append 成功后才登记已见表)
if out_pos + line_len + 2 < out_size:
reg_pos: sizet = out_pos
string.strncpy(out_buf + out_pos, dep_buf + line_start, line_len)
out_pos += line_len
out_buf[out_pos] = '\n'
out_pos += 1
out_buf[out_pos] = '\0'
if reg_tbl == 1:
_seen_funcs_count = _SeenInsert(_seen_funcs, _SEEN_FUNC_CAP, _seen_funcs_count, reg_key, reg_hash, reg_flags, reg_pos)
elif reg_tbl == 2:
_seen_globals_count = _SeenInsert(_seen_globals, _SEEN_GLOBAL_CAP, _seen_globals_count, reg_key, reg_hash, reg_flags, reg_pos)
elif reg_tbl == 3:
_seen_types_count = _SeenInsert(_seen_types, _SEEN_TYPE_CAP, _seen_types_count, reg_key, reg_hash, reg_flags, reg_pos)
return out_pos
# ============================================================
# _LoadAndAppendTextDeclares - 从依赖 text.ll 提取 declare 追加到 out_buf
#
# 读取 {temp_dir}/{sha1}.text.ll,将 define 行转为 declare(提取函数签名),
# 跳过函数体,去重后追加到 out_buf。返回新 out_pos。
# ============================================================
def _LoadAndAppendTextDeclares(temp_dir: str, td_len: sizet, dep_sha1: str,
dep_buf: bytes, out_buf: bytes, out_size: sizet,
out_pos: sizet) -> sizet:
"""从依赖 text.ll 提取 declare(define 转 declare),追加到 out_buf"""
global _seen_funcs_count, _seen_globals_count, _seen_types_count
if temp_dir is None or dep_sha1 is None or dep_buf is None or out_buf is None:
return out_pos
text_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, dep_sha1, "text.ll")
if text_path is None:
return out_pos
tf_lt: rawptr[fileio.File] = fileio.File(text_path, fileio.MODE.R)
if tf_lt.closed:
stdlib.free(text_path)
# 依赖 text 缺失:includes 映射内的模块缺失属编译错误(fail-fast),
# 非映射模块(如 t/c 内部库、用户文件)按设计跳过
if StubMergerStore.LookupSha1RelPath(dep_sha1) is not None:
VLogger.error(f"依赖 text 缺失: {dep_sha1}", "StubMerger")
return out_pos
text_br: int64 = tf_lt.read_all(dep_buf, StubMerger.STUB_READ_BUF_SIZE)
tf_lt.close()
stdlib.free(text_path)
if text_br <= 0:
# 依赖 text 为空:纯 stub 模块(如 stdio.vp 全 pass 声明)无函数体,
# text.ll 为空属合法形态,无可提取 declare,静默跳过
return out_pos
if text_br < StubMerger.STUB_READ_BUF_SIZE:
dep_buf[text_br] = '\0'
dep_pos_lt: sizet = 0
in_body: int = 0
while dep_pos_lt < text_br:
ls: sizet = dep_pos_lt
while dep_pos_lt < text_br:
if dep_buf[dep_pos_lt] == '\n':
break
dep_pos_lt += 1
ll: sizet = dep_pos_lt - ls
if dep_pos_lt < text_br:
dep_pos_lt += 1
# 函数体跳过:直到单独的 } 行
if in_body != 0:
if ll == 1 and dep_buf[ls] == '}':
in_body = 0
continue
# 跳过空行和 header
if ll == 0:
continue
c0: int = dep_buf[ls]
if c0 == ';':
continue
if c0 == 't' and ll >= 6 and string.strncmp(dep_buf + ls, "target", 6) == 0:
continue
if c0 == 's' and ll >= 15 and string.strncmp(dep_buf + ls, "source_filename", 15) == 0:
continue
# 处理 define 行:转为 declare
if ll >= 7 and string.strncmp(dep_buf + ls, "define ", 7) == 0:
# 提取 @funcname( 用于去重
at_lt: sizet = ls + 7
while at_lt < ls + ll:
if dep_buf[at_lt] == '@':
break
at_lt += 1
if at_lt >= ls + ll:
in_body = 1
continue
lp_lt: sizet = at_lt + 1
while lp_lt < ls + ll:
if dep_buf[lp_lt] == '(':
break
lp_lt += 1
if lp_lt >= ls + ll:
in_body = 1
continue
# 去重检查:用已见函数表(键 @"name"(,避免误匹配 call 语句)
saved_lt: char = dep_buf[lp_lt + 1]
dep_buf[lp_lt + 1] = '\0'
fh_lt: uint64 = _SeenHash(dep_buf + at_lt)
if _SeenPresent(_seen_funcs, _SEEN_FUNC_CAP, dep_buf + at_lt, fh_lt) != 0:
dep_buf[lp_lt + 1] = saved_lt
in_body = 1
continue
string.strcpy(_seen_key_scratch, dep_buf + at_lt)
dep_buf[lp_lt + 1] = saved_lt
# 找行尾的 ' {' 并截断
decl_end: sizet = ls + ll
if decl_end > ls + 1:
if dep_buf[decl_end - 1] == '{' and dep_buf[decl_end - 2] == ' ':
decl_end -= 2
# 剥掉行尾的**函数级元数据附加**(`!dbg !71`;可多组如 `!dbg !71 !prof !72`)。
# declare **不接受**函数级元数据附加:llc 直接报 "expected '='"(实证
# LLvmLiteTest 合并产物 949685246c8e241c.ll:898:104 —
# `declare void @"…GSList[Param].__init__"(…) !dbg !71`)。这条残留正是
# "文本降级 define→declare"自身带出来的:源 define 头行形如
# `define linkonce_odr void @"…"(…) !dbg !71 {`,上面只删掉了尾部 `" {"`,
# 其后的 ` !dbg !71` 被原样抄下。declare 无函数体,!dbg 本就无意义
# (FunctionPrint 对 declare 从不打印 !dbg,见 llvmlite/__function.vp 的
# IsDeclared==1 提前返回分支),故在此直接截断。
# 判据:参数表之后、括号深度 0、且不在双引号内的 ` !`(元数据附加恒在行尾,
# 裸标识符不允许含 '!',唯一可能含 '!' 的是带引号的符号名 → 用引号态排除)。
meta_pos_lt: sizet = ls
meta_depth_lt: int = 0
meta_in_str_lt: int = 0
meta_cut_lt: sizet = 0
while meta_pos_lt < decl_end:
meta_ch_lt: int = dep_buf[meta_pos_lt]
if meta_ch_lt == '"':
meta_in_str_lt = 1 - meta_in_str_lt
elif meta_in_str_lt == 0:
if meta_ch_lt == '(' or meta_ch_lt == '[' or meta_ch_lt == '{':
meta_depth_lt += 1
elif meta_ch_lt == ')' or meta_ch_lt == ']' or meta_ch_lt == '}':
if meta_depth_lt > 0:
meta_depth_lt -= 1
elif meta_ch_lt == '!' and meta_depth_lt == 0 and meta_pos_lt > ls \
and dep_buf[meta_pos_lt - 1] <= ' ':
meta_cut_lt = meta_pos_lt
break
meta_pos_lt += 1
if meta_cut_lt != 0:
# 连同前导空白一起截掉,避免残留行尾空白
# (`<= ' '` 同时覆盖空格与制表符,且不会误吃 ')' 等非空白字符)
while meta_cut_lt > ls and dep_buf[meta_cut_lt - 1] <= ' ':
meta_cut_lt -= 1
decl_end = meta_cut_lt
# 输出: declare + content(跳过 "define " 及其后所有 linkage 词,至多到 decl_end)。
# 泛型特化方法 define 名为 "define linkonce_odr ..."(名含 '['):只跳 "define " 会拼出 "declare linkonce_odr ..." →
# llc 报 "invalid linkage for function declaration"(declare 只能 external),故循环剥掉全部 linkage 词。
c_start_lt: sizet = ls + 7
skipped_lt: int = 1
while skipped_lt != 0:
skipped_lt = 0
if c_start_lt + 13 <= decl_end and \
string.strncmp(dep_buf + c_start_lt, "linkonce_odr ", 13) == 0:
c_start_lt += 13
skipped_lt = 1
elif c_start_lt + 10 <= decl_end and \
string.strncmp(dep_buf + c_start_lt, "weak_odr ", 9) == 0:
c_start_lt += 9
skipped_lt = 1
elif c_start_lt + 10 <= decl_end and \
string.strncmp(dep_buf + c_start_lt, "linkonce ", 9) == 0:
c_start_lt += 9
skipped_lt = 1
elif c_start_lt + 6 <= decl_end and \
string.strncmp(dep_buf + c_start_lt, "weak ", 5) == 0:
c_start_lt += 5
skipped_lt = 1
elif c_start_lt + 10 <= decl_end and \
string.strncmp(dep_buf + c_start_lt, "internal ", 9) == 0:
c_start_lt += 9
skipped_lt = 1
elif c_start_lt + 9 <= decl_end and \
string.strncmp(dep_buf + c_start_lt, "private ", 8) == 0:
c_start_lt += 8
skipped_lt = 1
copy_len_lt: sizet = decl_end - c_start_lt
if out_pos + 8 + copy_len_lt + 2 < out_size:
decl_pos_lt: sizet = out_pos
string.strncpy(out_buf + out_pos, "declare ", 8)
out_pos += 8
string.strncpy(out_buf + out_pos, dep_buf + c_start_lt, copy_len_lt)
out_pos += copy_len_lt
out_buf[out_pos] = '\n'
out_pos += 1
out_buf[out_pos] = '\0'
_seen_funcs_count = _SeenInsert(_seen_funcs, _SEEN_FUNC_CAP, _seen_funcs_count, _seen_key_scratch, fh_lt, 0, decl_pos_lt)
in_body = 1
continue
# 提取类型定义(%... = type { ... } 或 %... = type < ... >)
# stub.ll 中只有 opaque 声明,需要从 text.ll 中提取完整定义
# 用于 alloca 和 getelementptr 等需要完整类型的指令
if ll > 0 and dep_buf[ls] == '%':
eq_td: sizet = ls + 1
while eq_td < ls + ll:
if dep_buf[eq_td] == '=':
break
eq_td += 1
if eq_td < ls + ll:
# 检查是否为完整定义(= type { 或 = type <)
dep_is_full_td: int = 0
if eq_td + 8 <= ls + ll:
if dep_buf[eq_td + 2] == 't' and dep_buf[eq_td + 3] == 'y' \
and dep_buf[eq_td + 4] == 'p' and dep_buf[eq_td + 5] == 'e' \
and dep_buf[eq_td + 6] == ' ':
if dep_buf[eq_td + 7] == '{' or dep_buf[eq_td + 7] == '<':
dep_is_full_td = 1
if dep_is_full_td == 1:
# 临时截断到 '=' 之前(不含 '='),键统一为 %name
saved_eq_td: char = dep_buf[eq_td]
dep_buf[eq_td] = '\0'
th_td: uint64 = _SeenHash(dep_buf + ls)
# 1. 注释掉 out_buf 中的 opaque 声明(如果有)
_SeenCommentOut(_seen_types, _SEEN_TYPE_CAP, dep_buf + ls, th_td, out_buf)
# 2. 检查是否已有完整定义(避免重复)
if _SeenPresent(_seen_types, _SEEN_TYPE_CAP, dep_buf + ls, th_td) == 2:
dep_buf[eq_td] = saved_eq_td
continue
string.strcpy(_seen_key_scratch, dep_buf + ls)
dep_buf[eq_td] = saved_eq_td
# 追加完整类型定义
if out_pos + ll + 2 < out_size:
td_pos: sizet = out_pos
string.strncpy(out_buf + out_pos, dep_buf + ls, ll)
out_pos += ll
out_buf[out_pos] = '\n'
out_pos += 1
out_buf[out_pos] = '\0'
_seen_types_count = _SeenInsert(_seen_types, _SEEN_TYPE_CAP, _seen_types_count, _seen_key_scratch, th_td, 1, td_pos)
continue
# 提取全局变量定义(@... = ...)
# 依赖 text.ll 中的全局变量带初值定义(如 @StubMerger._mbuddy = global ...)
# stub.ll 中只有 external 声明,需要从 text.ll 提取完整定义
# 注释掉 out_buf 中的同名行(external 声明或旧定义),追加 text.ll 中的完整定义
if ll > 0 and dep_buf[ls] == '@':
# 字符串常量 @.str.{sha1}.{N}:依赖 text.ll 中的 internal 定义是死代码
# (定义模块自身 .obj 已有,合并 IR 中依赖方无需重复),跳过
if ll >= 5 and dep_buf[ls + 1] == '.' and dep_buf[ls + 2] == 's' and dep_buf[ls + 3] == 't' \
and dep_buf[ls + 4] == 'r':
continue
eq_gv: sizet = ls + 1
while eq_gv < ls + ll:
if dep_buf[eq_gv] == '=':
break
eq_gv += 1
if eq_gv < ls + ll:
# 截断到 '=' 之前(不含 '='),键统一为 @name
saved_gv: char = dep_buf[eq_gv]
dep_buf[eq_gv] = '\0'
gh_gv: uint64 = _SeenHash(dep_buf + ls)
# 依赖方已有同名全局(stub 注入的 external 声明或先前依赖的定义)时跳过:跨模块全局(如 MemBuddy.vtable)
# 只需一份强定义(在定义模块的 text.ll 中),复制进每个依赖方会导致 ld.lld duplicate symbol。
# 仅当依赖方完全没有该全局(既无 external 也无定义)时才需要补充。
if _SeenPresent(_seen_globals, _SEEN_GLOBAL_CAP, dep_buf + ls, gh_gv) == 0:
# 追加完整定义(无同名行时注释旧行是 no-op)
string.strcpy(_seen_key_scratch, dep_buf + ls)
dep_buf[eq_gv] = saved_gv
if out_pos + ll + 2 < out_size:
gv_pos: sizet = out_pos
string.strncpy(out_buf + out_pos, dep_buf + ls, ll)
out_pos += ll
out_buf[out_pos] = '\n'
out_pos += 1
out_buf[out_pos] = '\0'
_seen_globals_count = _SeenInsert(_seen_globals, _SEEN_GLOBAL_CAP, _seen_globals_count, _seen_key_scratch, gh_gv, 0, gv_pos)
else:
dep_buf[eq_gv] = saved_gv
continue
# 跳过其他行
return out_pos
# ============================================================
# _IsAppModule - 模块名是否为 App 源文件(store 条目 incdir 为空)
#
# 依赖图 BFS 中识别 App 中间节点:App 文件不入 reachable_set(Phase B
# 已编译其 .obj),但其 includes 依赖必须继续展开。App 条目由 App 扫描
# 阶段用 AppendToSha1MapStore 登记(incdir 留空);includes 条目用
# AddIncludeToSha1MapStore 登记(incdir 非空)。
# ============================================================
def _IsAppModule(mod_name: str) -> int:
"""mod_name 是否为 App 源文件(store 命中且 incdir 为空)"""
if mod_name is None:
return 0
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None:
return 0
for i in range(StubMerger._sha1_store_count):
midx: sizet = sizet(i) * 64
if string.strcmp(StubMerger._sha1_store_mod + midx, mod_name) == 0:
iidx: sizet = sizet(i) * StubMerger.MAX_INC_DIR_LEN
if StubMerger._sha1_store_incdir[iidx] == '\0':
return 1
return 0
return 0
# ============================================================
# _AppendDepsToWorklist - 追加空格分隔模块名到 worklist(逐模块去重)
#
# worklist 是 BFS 队列(空格分隔);deps_str 中的模块若已在 worklist/processed 中则不重复追加,
# 否则 BFS 沿共享依赖(builtins/stdlib 等)反复累积会致 REACH_WORKLIST_BUF 溢出。
#
# Args: worklist 队列缓冲(原地修改);wl_len 当前长度(值);deps_str 要追加的模块名(空格分隔);processed 已处理集合(空格分隔,查重)
# Returns: 追加后的新长度(>=0);溢出/参数错误返回 -1(调用方 fail-fast)
# ============================================================
def _AppendDepsToWorklist(worklist: bytes, wl_len: sizet,
deps_str: str, processed: str) -> int:
if worklist is None or deps_str is None:
return -1
cur_len: sizet = wl_len
dl: sizet = string.strlen(deps_str)
dpos: sizet = 0
while dpos < dl:
dstart: sizet = dpos
while dpos < dl and deps_str[dpos] != ' ':
dpos += 1
dlen: sizet = dpos - dstart
if dpos < dl:
dpos += 1
if dlen == 0:
continue
dmod: bytes = stdlib.malloc(dlen + 1)
if dmod is None:
continue
string.strncpy(dmod, deps_str + dstart, dlen)
dmod[dlen] = '\0'
in_wl: int = 0
if cur_len > 0:
if GenApi.is_module_imported(worklist, dmod) != 0:
in_wl = 1
if in_wl == 0 and processed is not None:
if GenApi.is_module_imported(processed, dmod) != 0:
in_wl = 1
if in_wl == 0:
if cur_len + dlen + 2 < StubMerger.REACH_WORKLIST_BUF:
if cur_len > 0 and worklist[cur_len - 1] != ' ':
worklist[cur_len] = ' '
cur_len = cur_len + 1
string.strcpy(worklist + cur_len, dmod)
cur_len = cur_len + dlen
worklist[cur_len] = ' '
cur_len = cur_len + 1
worklist[cur_len] = '\0'
else:
stdlib.free(dmod)
VLogger.error("依赖 worklist 溢出", "StubMerger")
return -1
stdlib.free(dmod)
return int(cur_len)
# ============================================================
# _BuildReachableSha1Set - 构建可达 SHA1 集合(依赖图按需扫描;未引用的 includes 从不读取)
#
# App 优先模型:从 app_deps 出发 BFS,对每个模块名按文件系统解析到 includes 文件(ResolveModuleToPath),只对可达 includes
# 读文件 + 算 SHA1 + 解析 import,补充依赖图并登记 Sha1Store/deps 存储器/写 deps.txt;Phase 1b / Phase B+ 只处理可达集合中的文件。
#
# 算法(工作列表 BFS):1) App 扫描(app_deps 为空时)递归扫 source_dir,declare_only=2 解析 import(相对导入 resolve 成绝对模块名)
# + 容器注入(_list/_dict)→ worklist 初始队列,App 文件登记 store(incdir 空)+ 填 deps;2) builtins 预加入队列;3) BFS 直到闭包。
#
# Args: mb 内存池;source_dir App 源文件目录(Config.SourceDir);temp_dir 临时目录(deps.txt 所在位置);reachable_set 输出参数,可达 SHA1 集合缓冲区(MAX_INCLUDES_SHA1 * 17 字节);app_deps App 源文件直接依赖模块名(空格分隔),非空则跳过 App 扫描;includes_dirs includes 根目录(空格分隔)
# Returns: 可达 SHA1 数量(>0 成功;0 无 includes 依赖;<0 失败)
# ============================================================
def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str,
temp_dir: str,
reachable_set: str,
app_deps: str = None,
includes_dirs: str = None) -> int:
"""构建可达 SHA1 集合(依赖图按需扫描,App 优先)"""
if source_dir is None or temp_dir is None or reachable_set is None:
return -1
if includes_dirs is None:
VLogger.error("includes_dirs 缺失,无法按需解析依赖图", "Reachable")
return -1
SRC_BUF_SIZE_R: sizet = 1048576
# 工作列表(空格分隔的模块名)
worklist: bytes = stdlib.malloc(StubMerger.REACH_WORKLIST_BUF)
if worklist is None:
return -1
worklist[0] = '\0'
wl_len: sizet = 0
# 1. App 扫描(app_deps 非空时跳过,直接用调用方提供的初始队列)
if app_deps is None or app_deps[0] == '\0':
VLogger.info("App 扫描:解析 source_dir 的 import 依赖", "Reachable")
scan_res_r: rawptr[IncludesScanner.ScanResult] = IncludesScanner.scan_includes(mb, source_dir)
if scan_res_r is not None:
es_r: sizet = IncludesScanner.FileEntry.__sizeof__()
for e_i_r in range(scan_res_r.Count):
e_r: rawptr[IncludesScanner.FileEntry] = rawptr[IncludesScanner.FileEntry](uint64(scan_res_r.Entries) + uint64(e_i_r) * es_r)
if e_r is None or e_r.Path is None:
continue
# 文件级 mark:parse 完立即回收 AST(App 全量 parse 驻留会 OOM)
app_mark: sizet = mb.mark()
sbuf: bytes = Utils.read_file_to_buf(e_r.Path, SRC_BUF_SIZE_R)
if sbuf is None:
mb.reset_to(app_mark)
continue
lx: ast.Lexer* = ast.new_lexer(mb)
tree: ast.AST* = None
if lx is not None:
ast._lexer_init(lx, sbuf, mb)
tokens: ast.Token* = ast.tokenize(lx)
tree = ast.parse_tokens(mb, tokens)
if tree is not None:
tr: rawptr[HandlesTranslator.Translator] = HandlesTranslator.Translator()
if tr is not None:
tr._declare_only = 2
# 源文件包名:用 RelPath(相对 source_dir,支持子目录),
# 保证 from .xxx import 相对导入解析正确
tr.CurrentPackage = GenApi.compute_package_from_relpath(mb, e_r.RelPath)
GenApi.set_current_file_type(e_r.Path)
GenApi.set_current_file_imports(e_r.Path)
GenApi.set_current_module_sha1(e_r.Sha1)
GenApi.clear_cdefine_constants()
GenApi.clear_ctypedefs()
GenApi.reset_visible_structs(mb, 0)
ret_a: int = tr.translate(tree)
if ret_a == 0:
# 容器使用检测:App 文件用 list/dict(裸名注解/构造)
# 时注入 _list/_dict 依赖(对齐原实现)
tr._inject_auto_container_visibility(mb, tree)
# App 文件登记 store(incdir 空)+ 填 deps:
# BFS 展开 App 中间节点的 includes 依赖时经
# LookupIncludesDeps(app_sha1) 读取
if e_r.Sha1 is not None:
StubMergerStore.AppendToSha1MapStore(e_r.Sha1, e_r.RelPath)
StubMergerStore.PopulateIncludesDeps(e_r.Sha1, tr._imported_modules)
# 收集 deps 到初始队列(逐模块去重)
if tr._imported_modules is not None:
im_len_a: sizet = string.strlen(tr._imported_modules)
im_pos_a: sizet = 0
while im_pos_a < im_len_a:
im_start_a: sizet = im_pos_a
while im_pos_a < im_len_a and tr._imported_modules[im_pos_a] != ' ':
im_pos_a += 1
im_mod_len: sizet = im_pos_a - im_start_a
if im_pos_a < im_len_a:
im_pos_a += 1
if im_mod_len == 0:
continue
im_buf_a: bytes = stdlib.malloc(im_mod_len + 1)
if im_buf_a is None:
continue
string.strncpy(im_buf_a, tr._imported_modules + im_start_a, im_mod_len)
im_buf_a[im_mod_len] = '\0'
in_wl_a: int = 0
if wl_len > 0 and GenApi.is_module_imported(worklist, im_buf_a) != 0:
in_wl_a = 1
if in_wl_a == 0:
if wl_len + im_mod_len + 2 < StubMerger.REACH_WORKLIST_BUF:
if wl_len > 0:
worklist[wl_len] = ' '
wl_len += 1
string.strcpy(worklist + wl_len, im_buf_a)
wl_len += im_mod_len
worklist[wl_len] = ' '
wl_len += 1
worklist[wl_len] = '\0'
stdlib.free(im_buf_a)
# 释放 Translator 资源
if tr._global_names is not None:
stdlib.free(tr._global_names)
if tr._nonlocal_names is not None:
stdlib.free(tr._nonlocal_names)
stdlib.free(sbuf)
mb.reset_to(app_mark)
else:
# app_deps 已提供(Phase2 场景):直接作为初始队列
ad_len: sizet = string.strlen(app_deps)
if ad_len > 0 and ad_len < StubMerger.REACH_WORKLIST_BUF:
string.strcpy(worklist, app_deps)
wl_len = ad_len
else:
VLogger.error("app_deps 过长,无法构建可达集合", "StubMerger")
stdlib.free(worklist)
return -1
# 去掉末尾多余空格
if wl_len > 0 and worklist[wl_len - 1] == ' ':
worklist[wl_len - 1] = '\0'
wl_len -= 1
# 2. 预加入基础模块(builtins)
# builtins(内置函数模块)始终纳入编译链。_list/_dict 仅在显式
# list/dict 注解/构造时经 _inject_auto_container_visibility 注入
# (list 字面量赋给 t.CArray 走数组初始化,不触发 _list)。
# processed 尚未分配,此处传 None 仅按 worklist 查重。
wl_len = _AppendDepsToWorklist(worklist, wl_len, "builtins", None)
if wl_len < 0:
stdlib.free(worklist)
return -1
# 2b. --traceback: force the traceback runtime module into the reachable set
# (TPC: used_includes.add('traceback')). Its Install() symbol and the
# @llvm.global_ctors ctor are only produced by compiling this module, so a
# missing file is fail-fast rather than a silent "feature off" build.
if BuildPipeline._traceback_mode != 0:
tb_abs: t.CPtr = stdlib.malloc(8)
tb_rel: t.CPtr = stdlib.malloc(8)
tb_inc: t.CPtr = stdlib.malloc(8)
tb_found: int = -1
if tb_abs is not None and tb_rel is not None and tb_inc is not None:
tb_abs_slot: sizet* = (sizet*)(tb_abs)
tb_rel_slot: sizet* = (sizet*)(tb_rel)
tb_inc_slot: sizet* = (sizet*)(tb_inc)
tb_abs_slot[0] = 0
tb_rel_slot[0] = 0
tb_inc_slot[0] = 0
tb_found = IncludesScanner.ResolveModuleToPath(
includes_dirs, "traceback", tb_abs, tb_rel, tb_inc)
if tb_abs is not None:
stdlib.free(tb_abs)
if tb_rel is not None:
stdlib.free(tb_rel)
if tb_inc is not None:
stdlib.free(tb_inc)
if tb_found != 0:
stdlib.free(worklist)
VLogger.error(f"--traceback: 在 includes 目录中找不到 traceback.vp,无法编译并链接 Install()(请确认 project.vpj 的 includes 包含仓库 includes 目录;已扫描: {includes_dirs})", "Traceback")
return -1
tb_wl: int = _AppendDepsToWorklist(worklist, wl_len, "traceback", None)
if tb_wl < 0:
stdlib.free(worklist)
return -1
wl_len = sizet(tb_wl)
# 3. 工作列表 BFS:模块名 → 文件系统解析 → 读+hash+parse import
processed: bytes = stdlib.malloc(StubMerger.REACH_WORKLIST_BUF)
if processed is None:
stdlib.free(worklist)
return -1
processed[0] = '\0'
proc_len: sizet = 0
reachable_count: int = 0
wl_pos: t.CPtr = stdlib.malloc(8)
if wl_pos is None:
stdlib.free(worklist)
stdlib.free(processed)
return -1
wl_pos_slot: sizet* = (sizet*)(wl_pos)
wl_pos_slot[0] = 0
while wl_pos_slot[0] < wl_len:
pos: sizet = wl_pos_slot[0]
# 提取一个模块名(到空格或末尾)
name_start: sizet = pos
while pos < wl_len and worklist[pos] != ' ':
pos += 1
name_len: sizet = pos - name_start
if pos < wl_len:
pos += 1
wl_pos_slot[0] = pos
if name_len == 0:
continue
# 复制模块名
mod_buf: bytes = stdlib.malloc(name_len + 1)
if mod_buf is None:
continue
string.strncpy(mod_buf, worklist + name_start, name_len)
mod_buf[name_len] = '\0'
# 已处理去重
if GenApi.is_module_imported(processed, mod_buf) != 0:
stdlib.free(mod_buf)
continue
# 加入 processed
if proc_len + name_len + 1 < StubMerger.REACH_WORKLIST_BUF:
string.strcpy(processed + proc_len, mod_buf)
proc_len = proc_len + name_len
processed[proc_len] = ' '
proc_len = proc_len + 1
processed[proc_len] = '\0'
else:
VLogger.error("依赖 processed 溢出", "StubMerger")
stdlib.free(mod_buf)
continue
# App 中间节点:只展开其 includes 依赖,不入 reachable_set
# (Phase B 已编译 App 的 .obj,加入会导致 Phase B+ 重复编译 → 重复符号)
if _IsAppModule(mod_buf) != 0:
app_sha1: str = StubMergerStore.LookupSha1ByModName(mod_buf)
if app_sha1 is not None:
app_deps_str: str = StubMergerStore.LookupIncludesDeps(app_sha1)
if app_deps_str is not None:
ret_ap: int = _AppendDepsToWorklist(worklist, wl_len, app_deps_str, processed)
if ret_ap < 0:
stdlib.free(mod_buf)
stdlib.free(worklist)
stdlib.free(processed)
stdlib.free(wl_pos)
return -1
wl_len = sizet(ret_ap)
stdlib.free(mod_buf)
continue
# includes:按文件系统按需解析(未引用的 includes 根本不触碰)
out_abs: t.CPtr = stdlib.malloc(8)
out_rel: t.CPtr = stdlib.malloc(8)
out_inc: t.CPtr = stdlib.malloc(8)
if out_abs is None or out_rel is None or out_inc is None:
if out_abs is not None:
stdlib.free(out_abs)
if out_rel is not None:
stdlib.free(out_rel)
if out_inc is not None:
stdlib.free(out_inc)
stdlib.free(mod_buf)
continue
# 输出槽防御性初始化(成功路径必全写;未找到/未命中时仅 free 槽本身)
# 槽联合必须用 (sizet*):t.CPtr|t.CPtr 会被折叠成 t.CPtr,
# [0] 变成 1 字节访问 → 指针被截断(0x2e 崩溃根因)。CSizeT 成员为
# 8 字节:写侧 sizet(ptr),读侧必须用 rawptr(slot[0]) 做 u64→
# 指针转换;str(slot[0]) 是内置"数字转字符串"(snprintf 到栈缓冲并返回
# 该栈地址),拿到的是十进制文本+栈指针 → free 即 bad-free。
abs_slot: sizet* = (sizet*)(out_abs)
rel_slot: sizet* = (sizet*)(out_rel)
inc_slot: sizet* = (sizet*)(out_inc)
abs_slot[0] = 0
rel_slot[0] = 0
inc_slot[0] = 0
rcode: int = IncludesScanner.ResolveModuleToPath(
includes_dirs, mod_buf, out_abs, out_rel, out_inc)
if rcode != 0:
# 内建模块(t/c/builtins 等内部库)或未找到:静默跳过
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(mod_buf)
continue
abs_path: str = rawptr(abs_slot[0])
rel_path: str = rawptr(rel_slot[0])
inc_dir: str = rawptr(inc_slot[0])
# 读文件 + 计算 SHA1(CRLF→LF,与 IncludesScanner 一致)
sha1_r2: str = IncludesScanner.compute_file_sha1(mb, abs_path)
if sha1_r2 is None:
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(mod_buf)
continue
# 已在可达集合(前序 BFS 已 parse 过):只展开其 deps
if StubMergerCollect._is_in_sha1_set(sha1_r2, reachable_set, reachable_count) != 0:
deps_ex: str = StubMergerStore.LookupIncludesDeps(sha1_r2)
if deps_ex is not None:
ret_ex: int = _AppendDepsToWorklist(worklist, wl_len, deps_ex, processed)
if ret_ex < 0:
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(mod_buf)
stdlib.free(worklist)
stdlib.free(processed)
stdlib.free(wl_pos)
return -1
wl_len = sizet(ret_ex)
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(mod_buf)
continue
# 加入可达集合 + 登记 Sha1Store(仅可达 includes)
reachable_count = StubMergerIR._AddSha1ToSet(reachable_set, reachable_count, sha1_r2)
StubMergerStore.AddIncludeToSha1MapStore(sha1_r2, rel_path, inc_dir, mod_buf)
# 文件级 mark:parse import 后立即回收 AST(可达 includes 全量
# parse 的 AST 若驻留 → 主池 OOM)
scan_mark_r: sizet = mb.mark()
sbuf_r: bytes = Utils.read_file_to_buf(abs_path, SRC_BUF_SIZE_R)
if sbuf_r is None:
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(mod_buf)
mb.reset_to(scan_mark_r)
continue
lx_r: ast.Lexer* = ast.new_lexer(mb)
tree_r: ast.AST* = None
if lx_r is not None:
ast._lexer_init(lx_r, sbuf_r, mb)
tokens_r: ast.Token* = ast.tokenize(lx_r)
tree_r = ast.parse_tokens(mb, tokens_r)
if tree_r is None:
# parse_tokens keeps the error detail (line/col/message) in the global
# channel only, so print the unified parse traceback before the one-line
# log; without it a reachable-includes syntax error is undiagnosable.
GenApi.dump_parse_traceback(abs_path)
VLogger.error(f"可达 includes 解析失败: {abs_path}", "Reachable")
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(sbuf_r)
stdlib.free(mod_buf)
stdlib.free(worklist)
stdlib.free(processed)
stdlib.free(wl_pos)
mb.reset_to(scan_mark_r)
return -1
tr_r2: rawptr[HandlesTranslator.Translator] = HandlesTranslator.Translator()
if tr_r2 is None:
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(sbuf_r)
stdlib.free(mod_buf)
mb.reset_to(scan_mark_r)
continue
tr_r2.ModuleSha1 = sha1_r2
tr_r2._declare_only = 2
# 包名:相对 includes 根目录的 RelPath(支持 from .xxx import 相对导入)
tr_r2.CurrentPackage = GenApi.compute_package_from_relpath(mb, rel_path)
GenApi.set_current_file_type(abs_path)
GenApi.set_current_file_imports(abs_path)
GenApi.set_current_module_sha1(sha1_r2)
GenApi.clear_cdefine_constants()
GenApi.clear_ctypedefs()
GenApi.reset_visible_structs(mb, 0)
ret_r2: int = tr_r2.translate(tree_r)
if ret_r2 != 0:
VLogger.error(f"import 依赖扫描失败(可达 includes): {abs_path}", "Reachable")
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(sbuf_r)
if tr_r2._global_names is not None:
stdlib.free(tr_r2._global_names)
if tr_r2._nonlocal_names is not None:
stdlib.free(tr_r2._nonlocal_names)
stdlib.free(mod_buf)
stdlib.free(worklist)
stdlib.free(processed)
stdlib.free(wl_pos)
mb.reset_to(scan_mark_r)
return -1
# 容器按需注入(includes 用 list/dict 泛型时追加 _list/_dict 到依赖,
# 对齐 App 层 _inject_auto_container_visibility;内联破环防 _list/_dict 环)
tr_r2._inject_auto_container_visibility(mb, tree_r)
# 填内存依赖 + 写 deps.txt(供依赖图展开与 Phase B 按需加载)
deps_r2: str = tr_r2._imported_modules
StubMergerStore.PopulateIncludesDeps(sha1_r2, deps_r2)
if deps_r2 is not None:
td_len_w: sizet = string.strlen(temp_dir)
deps_path_w: str = StubMergerCollect._sliced_path(temp_dir, td_len_w, sha1_r2, "deps.txt")
if deps_path_w is not None:
df_w: rawptr[fileio.File] = fileio.File(deps_path_w, fileio.MODE.W)
if not df_w.closed:
dl_w: sizet = string.strlen(deps_r2)
if dl_w > 0:
df_w.write(deps_r2, dl_w)
df_w.close()
stdlib.free(deps_path_w)
# 追加依赖到 worklist(去重防溢出)
ret_w: int = _AppendDepsToWorklist(worklist, wl_len, deps_r2, processed)
if ret_w < 0:
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(sbuf_r)
stdlib.free(mod_buf)
stdlib.free(worklist)
stdlib.free(processed)
stdlib.free(wl_pos)
mb.reset_to(scan_mark_r)
return -1
wl_len = sizet(ret_w)
else:
VLogger.debug(f"无依赖: {rel_path}", "Reachable")
# 释放 Translator 的 C malloc 资源 + 回收本文件 AST
if tr_r2._global_names is not None:
stdlib.free(tr_r2._global_names)
if tr_r2._nonlocal_names is not None:
stdlib.free(tr_r2._nonlocal_names)
stdlib.free(sbuf_r)
mb.reset_to(scan_mark_r)
stdlib.free(abs_path)
stdlib.free(rel_path)
stdlib.free(inc_dir)
stdlib.free(out_abs)
stdlib.free(out_rel)
stdlib.free(out_inc)
stdlib.free(mod_buf)
stdlib.free(worklist)
stdlib.free(processed)
stdlib.free(wl_pos)
return reachable_count
# ============================================================
# 类型定义前移(llc: "invalid type for function argument")
#
# by-value 结构体参数引用的具名类型必须在 declare 点之前定义,否则 llc 报 invalid type for function argument。
# 本遍是纯置换(长度不变、行内容逐字不变):合并阶段 _seen_types(本文件 L1646-1667)已按类型名保证「活跃定义唯一」,重复行不会进入 out_buf。
# ============================================================
def _IsTypeDefLine(buf: bytes, ls: sizet, le: sizet) -> int:
"""buf[ls, le)(不含换行)是否为 `%name = type ...` 类型定义行
对齐 TPC L3064 的 `stripped.startswith('%') and '= type' in stripped`,
但把裸子串判定收紧为词判定:'= type' 也会出现在函数体字符串常量里
(如 c"= type\\00"),裸子串判定会把普通指令行当类型定义搬走 → 破坏 IR。
"""
k: sizet = ls
while k < le and (buf[k] == ' ' or buf[k] == '\t'):
k += 1
if k >= le or buf[k] != '%':
return 0
j: sizet = k
while j + 6 <= le:
if string.strncmp(buf + j, "= type", 6) == 0:
if j + 6 == le:
return 1
e: char = buf[j + 6]
# 'type' 后只能是空白 / '{' / '<'(= type { ... } / opaque / <{...}>)
if e == ' ' or e == '\t' or e == '{' or e == '<':
return 1
j += 1
return 0
def _IsIrHeaderLine(buf: bytes, ls: sizet, le: sizet) -> int:
"""buf[ls, le)(不含换行)是否属于 ll header(类型定义插在其后)
对齐 TPC L3076 的 header 判据:空行 / ';' 注释 / 'target ' /
'source_filename' / '!' 元数据(行首空白先跳过,等价 TPC 的 stripped)。
"""
k: sizet = ls
while k < le and (buf[k] == ' ' or buf[k] == '\t' or buf[k] == '\r'):
k += 1
if k >= le:
return 1
if buf[k] == ';' or buf[k] == '!':
return 1
if le - k >= 7 and string.strncmp(buf + k, "target ", 7) == 0:
return 1
if le - k >= 15 and string.strncmp(buf + k, "source_filename", 15) == 0:
return 1
return 0
def HoistTypeDefsBeforeDeclares(buf: bytes, length: sizet) -> sizet:
"""把所有 `%name = type ...` 行上移到 IR header 之后(见上方注释)
纯文本行级重排:行内容逐字不变、类型定义之间相对顺序不变、非类型定义行
相对顺序不变;插入点与 TPC 一致(header 之后第一行;若首个非 header 行
就在文件首行或无 header 行,按 TPC 同规则追加末尾)。
幂等:已是"定义在 header 之后"时结果与输入逐字节相同。
返回重排后的长度(有类型定义行时恒为 length)。
"""
if buf is None or length == 0:
return length
# 1. 统计类型定义行总长(整行含换行)
types_len: sizet = 0
p: sizet = 0
while p < length:
le: sizet = p
while le < length and buf[le] != '\n':
le += 1
step: sizet = le - p
if le < length:
step += 1
if _IsTypeDefLine(buf, p, le) != 0:
types_len += step
p += step
if types_len == 0:
return length
tmp: bytes = stdlib.malloc(types_len + 1)
if tmp is None:
# VLogger.error 直接终止进程(不静默降级为"未前移")
VLogger.error("类型定义前移暂存缓冲分配失败", "StubMerger")
return length
# 2. 类型定义行抽到 tmp、其余行原地压缩(只删行不增行,w <= p 恒成立)
tmp_pos: sizet = 0
w: sizet = 0
p = 0
while p < length:
le = p
while le < length and buf[le] != '\n':
le += 1
step = le - p
if le < length:
step += 1
if _IsTypeDefLine(buf, p, le) != 0:
string.memmove(tmp + tmp_pos, buf + p, step)
tmp_pos += step
else:
if w != p:
string.memmove(buf + w, buf + p, step)
w += step
p += step
tmp[tmp_pos] = '\0'
# 3. 求插入点:过滤后文本里 header 之后的首行起始偏移
ins: sizet = 0
hit: int = 0
q: sizet = 0
while q < w:
qe: sizet = q
while qe < w and buf[qe] != '\n':
qe += 1
if _IsIrHeaderLine(buf, q, qe) == 0:
ins = q
hit = 1
break
if qe >= w:
break
q = qe + 1
if hit == 0 or ins == 0:
# 对齐 TPC L3079-3080:insert_pos == len(other_lines)
ins = w
# 4. 腾出 tmp_pos 字节空隙并拷入(长度不变;右移用 memmove 保证重叠安全)
tail: sizet = w - ins
if tail > 0:
string.memmove(buf + ins + tmp_pos, buf + ins, tail)
string.memmove(buf + ins, tmp, tmp_pos)
new_len: sizet = w + tmp_pos
buf[new_len] = '\0'
stdlib.free(tmp)
return new_len
# ============================================================
# BuildCombinedIR - 组合本地 stub + 依赖 stubs + 本地 text → 完整 IR
#
# 按需加载:读取 deps.txt 获取导入模块名,通过内存存储器查找 SHA1,
# 仅加载实际使用的依赖 stub(而非全部扫描)。
# _sha1_map.txt 仅作为人类可读输出,程序内部不读取。
# ============================================================
def BuildCombinedIR(temp_dir: str, local_sha1: str,
out_buf: bytes, out_size: sizet) -> sizet:
"""组合本地 stub + 依赖 stubs + 本地 text → out_buf(按需加载)"""
global _seen_funcs, _seen_funcs_count, _seen_globals, _seen_globals_count
global _seen_types, _seen_types_count, _seen_key_scratch
if temp_dir is None or local_sha1 is None or out_buf is None or out_size == 0:
return 0
td_len: sizet = string.strlen(temp_dir)
out_buf[0] = '\0'
out_pos: sizet = 0
# 已见符号表重置(跨构建复用缓冲,仅清零计数 + 全槽位清零)
if _seen_funcs is None:
_seen_funcs = stdlib.malloc(_SEEN_FUNC_CAP * _SEEN_SLOT)
if _seen_funcs is None:
VLogger.error("已见函数表分配失败", "StubMerger")
return 0
if _seen_globals is None:
_seen_globals = stdlib.malloc(_SEEN_GLOBAL_CAP * _SEEN_SLOT)
if _seen_globals is None:
VLogger.error("已见全局表分配失败", "StubMerger")
return 0
if _seen_types is None:
_seen_types = stdlib.malloc(_SEEN_TYPE_CAP * _SEEN_SLOT)
if _seen_types is None:
VLogger.error("已见类型表分配失败", "StubMerger")
return 0
if _seen_key_scratch is None:
_seen_key_scratch = stdlib.malloc(_SEEN_NAME_MAX + 1)
if _seen_key_scratch is None:
VLogger.error("已见表键暂存分配失败", "StubMerger")
return 0
string.memset(_seen_funcs, 0, _SEEN_FUNC_CAP * _SEEN_SLOT)
string.memset(_seen_globals, 0, _SEEN_GLOBAL_CAP * _SEEN_SLOT)
string.memset(_seen_types, 0, _SEEN_TYPE_CAP * _SEEN_SLOT)
_seen_funcs_count = 0
_seen_globals_count = 0
_seen_types_count = 0
# 1. 读取并追加本地 stub.ll(含 header)
stub_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, local_sha1, "stub.ll")
if stub_path is None:
return 0
sf: rawptr[fileio.File] = fileio.File(stub_path, fileio.MODE.R)
if sf.closed:
stdlib.free(stub_path)
return 0
stub_content: bytes = stdlib.malloc(StubMerger.STUB_READ_BUF_SIZE)
if stub_content is None:
sf.close()
stdlib.free(stub_path)
return 0
stub_br: int64 = sf.read_all(stub_content, StubMerger.STUB_READ_BUF_SIZE)
sf.close()
stdlib.free(stub_path)
if stub_br <= 0:
stdlib.free(stub_content)
return 0
if stub_br < StubMerger.STUB_READ_BUF_SIZE:
stub_content[stub_br] = '\0'
slen: sizet = stub_br
# 修复本地 stub.ll 中字符串常量的非法 IR:
# llvmlite OUTPUT_STUB 模式输出 "= external unnamed_addr constant" 的非法形式。
# 简单方案:搜索 "= external unnamed_addr constant" 并将 external 替换为 8 个空格。
fix_pos: sizet = 0
while fix_pos + 30 < slen:
ext_ptr: t.CPtr = string.strstr(stub_content + fix_pos, "= external unnamed_addr constant")
if ext_ptr is None:
break
# ext_ptr 指向 "= external ..." 中的 '='
# 'external' 开始于 ext_ptr + 2
ext_off: sizet = sizet(uint64(ext_ptr) - uint64(stub_content)) + 2
if ext_off + 8 <= slen:
stub_content[ext_off] = ' '
stub_content[ext_off + 1] = ' '
stub_content[ext_off + 2] = ' '
stub_content[ext_off + 3] = ' '
stub_content[ext_off + 4] = ' '
stub_content[ext_off + 5] = ' '
stub_content[ext_off + 6] = ' '
stub_content[ext_off + 7] = ' '
fix_pos = ext_off + 8
# 直接复制修复后的 stub 内容(复制前先登记其 declare/global/type,
# 保持已见表与 out_buf 一致,避免后续依赖重复追加 redefinition)
if out_pos + slen + 2 < out_size:
_RegisterStubLines(stub_content, slen, out_pos)
string.strcpy(out_buf + out_pos, stub_content)
out_pos += slen
if out_buf[out_pos - 1] != '\n':
out_buf[out_pos] = '\n'
out_pos += 1
out_buf[out_pos] = '\0'
stdlib.free(stub_content)
# 2. 按需加载依赖 stubs(根据 deps.txt 过滤,而非扫描全部)
# 先加载依赖 stub(含 type 定义),确保 type 定义在本地 text.ll 的 define 块之前
# 2a. 构建 includes SHA1 → module_name 映射(从全局内存存储器)
sha1_arr: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 17)
mod_arr: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 64)
inc_count: int = 0
if sha1_arr is not None and mod_arr is not None:
inc_count = StubMergerIR._BuildIncludesSha1Map(temp_dir, td_len, sha1_arr, mod_arr)
# 2b. 读取 deps.txt 获取导入模块名集合
deps_buf: str = None
deps_loaded: int = 0
deps_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, local_sha1, "deps.txt")
if deps_path is not None:
df_deps: rawptr[fileio.File] = fileio.File(deps_path, fileio.MODE.R)
if not df_deps.closed:
deps_buf = stdlib.malloc(4096)
if deps_buf is not None:
deps_br: int64 = df_deps.read_all(deps_buf, 4096)
if deps_br > 0:
if deps_br < 4096:
deps_buf[deps_br] = '\0'
deps_loaded = 1
elif deps_br == 0:
# 空 deps.txt:无 import 的包标记文件(如纯注释的 lib/core/.../__init__.vp)合法——deps_loaded 置 1 视为「已加载且无依赖」,
# 避免下方「deps.txt 缺失」误报;deps_buf 是 malloc 未初始化内存,必须显式置空串,否则后续 strlen 读到垃圾长度遍历越界。
deps_buf[0] = '\0'
deps_loaded = 1
df_deps.close()
stdlib.free(deps_path)
# deps.txt 缺失但存在 includes 映射:依赖无法按需过滤,fail-fast 终止
if deps_loaded == 0 and inc_count > 0:
VLogger.error("依赖文件 deps.txt 缺失,无法合并 includes stub", "StubMerger")
# 2c. 递归扫描 temp_dir 中所有 .stub.ll,按需加载
# 全目录扫描结果按 temp_dir 缓存(Phase B 对每个源文件调用一次
# BuildCombinedIR,每次递归扫盘 O(N×S) 是 Phase B 慢的根因之一;
# 首次扫描后复用缓存,temp_dir 变化时自动重建)
cache_valid_sc: int = 0
if StubMerger._stub_scan_cache_arr is not None and StubMerger._stub_scan_cache_dir is not None:
if string.strcmp(StubMerger._stub_scan_cache_dir, temp_dir) == 0:
cache_valid_sc = 1
if cache_valid_sc == 0:
new_scan_arr: str = stdlib.malloc(StubMerger.MAX_INCLUDES_SHA1 * 17)
if new_scan_arr is not None:
string.memset(new_scan_arr, 0, StubMerger.MAX_INCLUDES_SHA1 * 17)
new_scan_count: int = StubMergerCollect._collect_stub_sha1s(
temp_dir, td_len, Config.Sha1SliceLevel, new_scan_arr, 0, StubMerger.MAX_INCLUDES_SHA1)
if new_scan_count > 0:
# 缓存目录键(stdlib 深拷贝,跨调用存活)
dir_buf_sc: str = stdlib.malloc(td_len + 1)
if dir_buf_sc is not None:
string.strcpy(dir_buf_sc, temp_dir)
if StubMerger._stub_scan_cache_arr is not None:
stdlib.free(StubMerger._stub_scan_cache_arr)
StubMerger._stub_scan_cache_arr = None
if StubMerger._stub_scan_cache_dir is not None:
stdlib.free(StubMerger._stub_scan_cache_dir)
StubMerger._stub_scan_cache_dir = None
StubMerger._stub_scan_cache_arr = new_scan_arr
StubMerger._stub_scan_cache_dir = dir_buf_sc
StubMerger._stub_scan_cache_count = new_scan_count
else:
stdlib.free(new_scan_arr)
else:
stdlib.free(new_scan_arr)
all_sha1_arr: str = StubMerger._stub_scan_cache_arr
all_count: int = StubMerger._stub_scan_cache_count
if all_sha1_arr is not None and all_count > 0:
dep_buf: bytes = stdlib.malloc(StubMerger.STUB_READ_BUF_SIZE)
if dep_buf is not None:
si: int = 0
while si < all_count:
dep_sha1: str = all_sha1_arr + sizet(si) * 17
if string.strcmp(dep_sha1, local_sha1) == 0:
si += 1
continue
should_load: int = 0
# 检查是否在 includes 映射中
is_include: int = 0
if inc_count > 0:
for ii in range(inc_count):
idx_ii: sizet = sizet(ii) * 17
if string.strcmp(sha1_arr + idx_ii, dep_sha1) != 0:
continue
is_include = 1
if deps_loaded != 0:
idx_mi: sizet = sizet(ii) * 64
dep_mod_name: str = mod_arr + idx_mi
if GenApi.is_module_imported(deps_buf, dep_mod_name) != 0:
should_load = 1
elif StubMergerIR._is_submodule_of_imported(deps_buf, dep_mod_name) != 0:
# 传递性依赖:deps.txt 只记录直接导入(如 ast),但包的 __init__.vp 内部 from .lexer import Lexer
# 会让 IR 引用 ast.lexer 子模块类型,故还需检查 dep_mod_name 是否为已导入包的子模块。
should_load = 1
else:
# 基名匹配:store 键是全路径模块名(Kernel.drivers.fs.fat32.fat32_types),而 deps.txt 记录 import 短名(fat32_types)
# → 精确/子模块均不命中 → 依赖 stub/text 不加载 → 跨模块类完整定义缺失(值类型 alloca 报 llc "Cannot allocate unsized type")。
# 故取最后一个 '.' 之后的基名与 deps 词边界匹配(多加载无害,去重表兜底)。
last_dot_bm: str = string.strrchr(dep_mod_name, '.')
if last_dot_bm is not None and last_dot_bm[1] != '\0':
if GenApi.is_module_imported(deps_buf, last_dot_bm + 1) != 0:
should_load = 1
break
if is_include == 0:
# 非 includes stub(用户文件),总是加载
should_load = 1
if should_load != 0:
out_pos = _LoadAndAppendStub(temp_dir, td_len, dep_sha1, dep_buf, out_buf, out_size, out_pos)
out_pos = _LoadAndAppendTextDeclares(temp_dir, td_len, dep_sha1, dep_buf, out_buf, out_size, out_pos)
si += 1
stdlib.free(dep_buf)
# all_sha1_arr 是模块级缓存(StubMerger._stub_scan_cache_arr),跨调用共享,不释放
# 2d. 验证 deps.txt 中所有依赖模块的 stub 文件都存在(fail-fast)
# 避免到 llc 才报 undefined value 错误。
# 仅验证在 includes 映射中找到的模块(t/c 等内部库无 SHA1 映射,自动跳过)。
if deps_loaded != 0 and inc_count > 0:
dep_total: sizet = string.strlen(deps_buf)
dp: sizet = 0
while dp < dep_total:
# 跳过前导空格
while dp < dep_total:
if deps_buf[dp] != ' ':
break
dp += 1
if dp >= dep_total:
break
# 提取模块名(到下一个空格或末尾)
ms: sizet = dp
while dp < dep_total:
if deps_buf[dp] == ' ':
break
dp += 1
ml: sizet = dp - ms
if ml == 0:
continue
# 复制模块名到临时缓冲区并 null 终止
mod_nm: str = stdlib.malloc(ml + 1)
if mod_nm is None:
continue
string.strncpy(mod_nm, deps_buf + ms, ml)
mod_nm[ml] = '\0'
# 在 includes 映射中查找模块对应的 SHA1
found_s: str = None
for mi2 in range(inc_count):
idx_m2: sizet = sizet(mi2) * 64
if string.strcmp(mod_arr + idx_m2, mod_nm) == 0:
idx_s2: sizet = sizet(mi2) * 17
found_s = sha1_arr + idx_s2
break
if found_s is not None:
# 检查 stub 文件是否存在
chk_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, found_s, "stub.ll")
if chk_path is not None:
chk_f: rawptr[fileio.File] = fileio.File(chk_path, fileio.MODE.R)
if chk_f.closed:
VLogger.error(f"依赖模块 '{mod_nm}' (sha1={found_s}) 的 stub 文件不存在: {chk_path},立即终止编译", "BuildCombinedIR")
chk_f.close()
stdlib.free(chk_path)
stdlib.free(mod_nm)
# 3. 读取并追加本地 text.ll(行级去重,跳过 stub.ll 中已存在的定义)
text_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, local_sha1, "text.ll")
if text_path is None:
return out_pos
tf: rawptr[fileio.File] = fileio.File(text_path, fileio.MODE.R)
if tf.closed:
# 本地 text.ll 缺失:合并 IR 无法生成定义块,fail-fast 终止
VLogger.error(f"本地 text.ll 缺失: {text_path}", "StubMerger")
stdlib.free(text_path)
else:
text_content: bytes = stdlib.malloc(StubMerger.STUB_READ_BUF_SIZE)
if text_content is None:
tf.close()
stdlib.free(text_path)
else:
text_br: int64 = tf.read_all(text_content, StubMerger.STUB_READ_BUF_SIZE)
tf.close()
stdlib.free(text_path)
if text_br > 0:
if text_br < StubMerger.STUB_READ_BUF_SIZE:
text_content[text_br] = '\0'
# 行级去重追加(与 _LoadAndAppendStub 相同逻辑)
tx_pos: sizet = 0
tx_skip: int = 1
while tx_pos < text_br:
tx_ls: sizet = tx_pos
while tx_pos < text_br:
if text_content[tx_pos] == '\n':
break
tx_pos += 1
tx_ll: sizet = tx_pos - tx_ls
if tx_pos < text_br:
tx_pos += 1
if tx_skip != 0:
if tx_ll == 0:
continue
tx_c0: int = text_content[tx_ls]
if tx_c0 == ';':
continue
if tx_c0 == 't' and tx_ll >= 6:
if string.strncmp(text_content + tx_ls, "target", 6) == 0:
continue
if tx_c0 == 's' and tx_ll >= 15:
if string.strncmp(text_content + tx_ls, "source_filename", 15) == 0:
continue
tx_skip = 0
# 注册暂存(append 成功后才登记;tx_key 指向 scratch,insert 当轮完成)
tx_reg: int = 0
tx_key: str = None
tx_hash: uint64 = 0
tx_flags: int = 0
# 去重:declare(用已见函数表,键 @"name"(,避免误匹配 call 语句)
if tx_ll >= 8 and string.strncmp(text_content + tx_ls, "declare ", 8) == 0:
tx_at: sizet = tx_ls + 8
while tx_at < tx_ls + tx_ll:
if text_content[tx_at] == '@':
break
tx_at += 1
if tx_at < tx_ls + tx_ll:
tx_lp: sizet = tx_at + 1
while tx_lp < tx_ls + tx_ll:
if text_content[tx_lp] == '(':
break
tx_lp += 1
if tx_lp < tx_ls + tx_ll:
tx_sv: char = text_content[tx_lp + 1]
text_content[tx_lp + 1] = '\0'
tx_hash = _SeenHash(text_content + tx_at)
if _SeenPresent(_seen_funcs, _SEEN_FUNC_CAP, text_content + tx_at, tx_hash) != 0:
text_content[tx_lp + 1] = tx_sv
continue
string.strcpy(_seen_key_scratch, text_content + tx_at)
tx_reg = 1
tx_key = _seen_key_scratch
tx_flags = 0
text_content[tx_lp + 1] = tx_sv
# 去重:define(注释 out_buf 中已有 declare,避免 declare/define 冲突)
if tx_ll >= 7 and string.strncmp(text_content + tx_ls, "define ", 7) == 0:
tx_at_d: sizet = tx_ls + 7
while tx_at_d < tx_ls + tx_ll:
if text_content[tx_at_d] == '@':
break
tx_at_d += 1
if tx_at_d < tx_ls + tx_ll:
tx_lp_d: sizet = tx_at_d + 1
while tx_lp_d < tx_ls + tx_ll:
if text_content[tx_lp_d] == '(':
break
tx_lp_d += 1
if tx_lp_d < tx_ls + tx_ll:
tx_sv_d: char = text_content[tx_lp_d + 1]
text_content[tx_lp_d + 1] = '\0'
tx_hash = _SeenHash(text_content + tx_at_d)
_SeenCommentOut(_seen_funcs, _SEEN_FUNC_CAP, text_content + tx_at_d, tx_hash, out_buf)
string.strcpy(_seen_key_scratch, text_content + tx_at_d)
tx_reg = 1
tx_key = _seen_key_scratch
tx_flags = 0
text_content[tx_lp_d + 1] = tx_sv_d
# 处理:@global = ...
# stub 中可能是 external 声明或完整定义,以 text 中的定义为准
# 注释 out_buf 中同名全局行,然后追加 text 中的定义(键 @name,截断于 '=' 前)
if tx_ll > 0 and text_content[tx_ls] == '@':
tx_eq: sizet = tx_ls + 1
while tx_eq < tx_ls + tx_ll:
if text_content[tx_eq] == '=':
break
tx_eq += 1
if tx_eq < tx_ls + tx_ll:
tx_sv2: char = text_content[tx_eq]
text_content[tx_eq] = '\0'
tx_hash = _SeenHash(text_content + tx_ls)
_SeenCommentOut(_seen_globals, _SEEN_GLOBAL_CAP, text_content + tx_ls, tx_hash, out_buf)
string.strcpy(_seen_key_scratch, text_content + tx_ls)
tx_reg = 2
tx_key = _seen_key_scratch
tx_flags = 0
text_content[tx_eq] = tx_sv2
# 去重:%type = ...(键 %name,截断于 '=' 前)
if tx_ll > 0 and text_content[tx_ls] == '%':
tx_eq2: sizet = tx_ls + 1
while tx_eq2 < tx_ls + tx_ll:
if text_content[tx_eq2] == '=':
break
tx_eq2 += 1
if tx_eq2 < tx_ls + tx_ll:
tx_sv3: char = text_content[tx_eq2]
text_content[tx_eq2] = '\0'
tx_is_full: int = 0
if tx_eq2 + 8 <= tx_ls + tx_ll:
if text_content[tx_eq2 + 2] == 't' and text_content[tx_eq2 + 3] == 'y' \
and text_content[tx_eq2 + 4] == 'p' and text_content[tx_eq2 + 5] == 'e' \
and text_content[tx_eq2 + 6] == ' ':
if text_content[tx_eq2 + 7] == '{' or text_content[tx_eq2 + 7] == '<':
tx_is_full = 1
tx_hash = _SeenHash(text_content + tx_ls)
if _SeenPresent(_seen_types, _SEEN_TYPE_CAP, text_content + tx_ls, tx_hash) != 0:
text_content[tx_eq2] = tx_sv3
continue
string.strcpy(_seen_key_scratch, text_content + tx_ls)
tx_reg = 3
tx_key = _seen_key_scratch
tx_flags = tx_is_full
text_content[tx_eq2] = tx_sv3
# 追加(append 成功后才登记已见表)
if out_pos + tx_ll + 2 < out_size:
tx_reg_pos: sizet = out_pos
string.strncpy(out_buf + out_pos, text_content + tx_ls, tx_ll)
out_pos += tx_ll
out_buf[out_pos] = '\n'
out_pos += 1
out_buf[out_pos] = '\0'
if tx_reg == 1:
_seen_funcs_count = _SeenInsert(_seen_funcs, _SEEN_FUNC_CAP, _seen_funcs_count, tx_key, tx_hash, tx_flags, tx_reg_pos)
elif tx_reg == 2:
_seen_globals_count = _SeenInsert(_seen_globals, _SEEN_GLOBAL_CAP, _seen_globals_count, tx_key, tx_hash, tx_flags, tx_reg_pos)
elif tx_reg == 3:
_seen_types_count = _SeenInsert(_seen_types, _SEEN_TYPE_CAP, _seen_types_count, tx_key, tx_hash, tx_flags, tx_reg_pos)
stdlib.free(text_content)
if deps_buf is not None:
stdlib.free(deps_buf)
if sha1_arr is not None:
stdlib.free(sha1_arr)
if mod_arr is not None:
stdlib.free(mod_arr)
# 类型定义前移:llc 要求 by-value 结构体参数引用的具名类型在 declare 点之前定义(否则 "invalid type for function argument")。
# 落点:Phase2 的 write_ir_to_file 逐字节写本缓冲区(BuildPipeline.vp L289-314,无文本加工),故在此完成即覆盖 llc 的全部输入。
out_pos = HoistTypeDefsBeforeDeclares(out_buf, out_pos)
return out_pos
# ============================================================
# LoadDepVpiIntoModule - 加载依赖模块的 .vpi 文件,注入 declare 到当前模块
#
# 遍历 imported_modules(空格分隔的模块名):LookupSha1ByModName 取 SHA1 → _sliced_path 构造 .vpi 路径 → VpiReader.ParseVpiIntoModule 解析并注入 declare。
#
# 必须在 _translate_module_level 中、Import 处理完成后、translate_children 之前调用,确保跨模块函数调用时 declare 已存在。
#
# Args: pool 内存池;mod 当前 LLVM 模块;imported_modules 已导入模块名字符串(空格分隔);from_imports from-import 映射字符串("name:module name:module" 格式);temp_dir temp 目录路径;trans 翻译器对象
# Returns: 成功加载的 .vpi 文件数
# ============================================================
def LoadDepVpiIntoModule(pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
imported_modules: str,
from_imports: str,
temp_dir: str,
trans: rawptr[HandlesTranslator.Translator]) -> int:
"""加载依赖模块的 .vpi 文件,将函数声明注入到当前模块"""
if pool is None or mod is None or temp_dir is None or trans is None:
return 0
if imported_modules is None:
return 0
# 每个模块的 stub 注入前重置 typedef 递归注册的防环标记:
# CTypedef 表每文件隔离(translate 前 clear),visited 若跨模块残留会
# 导致 stdint 等 typedef 只注册一次、后续模块注入再次解析时表里为空
# → 参数类型退化 i8_ptr_ty → 整数实参误报 CastError(FuncTest 根因)。
VpiReader.ResetVpiTypedefVisited()
# 按需注入防重入表同周期重置:注入目标 mod 每模块不同,跨模块沿用会让
# 后续模块命中缓存、拿不到本模块需要的 declare(包重导出按需注入用)。
VpiReader.ResetVpiInjectVisited()
td_len: sizet = string.strlen(temp_dir)
total_len: sizet = string.strlen(imported_modules)
success_count: int = 0
# 遍历 imported_modules 中的每个模块名(空格分隔)
ci: sizet = 0
while ci < total_len:
# 跳过前导空格
while ci < total_len and imported_modules[ci] == ' ':
ci += 1
if ci >= total_len:
break
# 提取模块名(到下一个空格或字符串末尾)
word_start: sizet = ci
while ci < total_len and imported_modules[ci] != ' ':
ci += 1
word_len: sizet = ci - word_start
if word_len == 0:
continue
# 复制模块名到临时缓冲区并 null 终止
mod_nm_buf: str = stdlib.malloc(word_len + 1)
if mod_nm_buf is None:
continue
string.strncpy(mod_nm_buf, imported_modules + word_start, word_len)
mod_nm_buf[word_len] = '\0'
# 用 LookupSha1ByModName 查找模块的 SHA1
sha1_val: str = StubMergerStore.LookupSha1ByModName(mod_nm_buf)
# 改名别名(import X as Y,如 HT→lib.core.Handles.HandlesTranslator):
# 精确/包名回退均失败时从 from_imports 查别名映射再查 SHA1,
# 否则该类模块的 vpi/stub 不注入 → 跨模块符号解析失败
if sha1_val is None and from_imports is not None:
sha1_val = StubMergerStore.LookupSha1ByAlias(from_imports, mod_nm_buf)
# 如果精确查找失败,尝试包名回退(如 "ast" → "ast.__init__")
# LookupSha1ByModName 内部已处理包名回退,此处无需重复
if sha1_val is not None:
# 构造 .vpi 文件路径
vpi_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1_val, "vpi")
if vpi_path is not None:
# 检查 .vpi 文件是否存在(可读)
vf: rawptr[fileio.File] = fileio.File(vpi_path, fileio.MODE.R)
if not vf.closed:
vf.close()
# 调用 VpiReader 解析 .vpi 并注入 declare
# (错误上下文帧由调用方 HandlesTranslator 维护)
ret: int = VpiReader.ParseVpiIntoModule(pool, mod, vpi_path, trans)
if ret == 1:
success_count += 1
stdlib.free(vpi_path)
# sha1_val 指向内部存储,无需 free
stdlib.free(mod_nm_buf)
return success_count