fix(link): 为 weak/linkonce 函数与全局补 COMDAT,修复 COFF 下跨模块重复符号

COFF 下无 COMDAT 的 weak/linkonce 定义会被 llc 展开成强符号 .weak.<name>.default(weak external 实现副本)。rowptr 新一代 includes 让泛型特化(list[rowptr[AST]].__before_init__)与跨模块共享常量(PYI_BUF_SIZE)在多个模块重复定义 → ld.lld duplicate symbol。

在 Phase2Translator 三处 dso_local 后处理簇后调用 _AddWeakComdat:函数定义补 ' comdat'、全局补 ', comdat'(置于 align/section 前),并在模块头加 comdat any 声明。llc/ld.lld 端到端已验证(双 obj 同符号链接无重复报错)。
This commit is contained in:
TermiNexus committed 2026-09-05 12:02:48 +08:00
1 parent cb8eef3316
commit b02bedae9a
1 file changed
+62
+62
View File
@@ -501,6 +501,65 @@ def _count_braces_outside_strings(text: str) -> int:
return count
def _AddWeakComdat(ll_content: str) -> str:
"""为 weak/linkonce 系列 函数/全局 定义补 COMDAT(COFF/PE 必需)。
COFF 下无 COMDAT 的 weak/linkonce 定义会被 llc 展开成强符号
`.weak.<name>.default`(weak external 的实现副本)。同一弱定义出现在
多个模块时(泛型特化方法如 `e94ef9e139461915.list[rowptr[AST]].
__before_init__`、跨模块共享常量如 PYI_BUF_SIZE),副本同名 →
ld.lld 报 duplicate symbol。补 COMDAT(any) 后 llc 输出单一 COMDAT
成员,链接器自动去重(llc/ld.lld 端到端已实验验证)。
"""
if (' linkonce ' not in ll_content and ' linkonce_odr ' not in ll_content
and ' weak_odr ' not in ll_content and ' weak ' not in ll_content):
return ll_content
weak_links = 'linkonce|linkonce_odr|weak|weak_odr|extern_weak'
comdat_names: list[str] = []
# 1) 函数定义:define <linkage> [dso_local] ... @name(...) { → @name(...) comdat {
def _fn_repl(m: re.Match) -> str:
comdat_names.append(m.group(3))
return m.group(1) + ' comdat ' + m.group(4)
ll_content = re.sub(
r'^(define\s+(?:dso_local\s+)?(?:%s)\s+[^@]*@("?)([^"\s\(]+)\2[^{]*)(\{)' % weak_links,
_fn_repl, ll_content, flags=re.MULTILINE)
# 2) 全局定义:@name = <linkage> [dso_local] (global|constant) <init> → 值后补 ', comdat'
# (comdat 需位于 align/section 等尾部属性之前,符合 LLVM IR 文法)
def _g_repl(m: re.Match) -> str:
line = m.group(0)
if ' comdat' in line:
return line
comdat_names.append(m.group(3))
ins = re.search(r',\s*(?:align|section|partition|code_model)\b', line)
if ins:
return line[:ins.start()] + ', comdat' + line[ins.start():]
return line + ', comdat'
ll_content = re.sub(
r'^(@("?)([^"=\s]+)\2\s*=\s*(?:%s)\s+(?:dso_local\s+)?(?:global|constant)[^\n]*)' % weak_links,
_g_repl, ll_content, flags=re.MULTILINE)
if not comdat_names:
return ll_content
decls: list[str] = []
for name in dict.fromkeys(comdat_names):
if re.match(r'^[0-9A-Za-z_$.-]+$', name):
decls.append(f'${name} = comdat any')
else:
decls.append(f'$"{name}" = comdat any')
comdat_block = '\n'.join(decls) + '\n'
lines = ll_content.splitlines(True)
header_end = 0
for i, line in enumerate(lines):
stripped = line.strip()
if stripped.startswith(';') or stripped.startswith('target ') or stripped.startswith('source_filename') or not stripped:
header_end = i + 1
continue
break
return ''.join(lines[:header_end]) + comdat_block + ''.join(lines[header_end:])
class Phase2Translator:
"""阶段二:使用声明接口翻译源文件"""
@@ -1812,6 +1871,7 @@ class Phase2Translator:
opt_content = re.sub(r'(@[\w.]+)\s*=\s*external\s+global\s+(\w[\w*]*)\s+(-?\d+|null|zeroinitializer|true|false)$', r'\1 = linkonce global \2 \3', opt_content, flags=re.MULTILINE)
opt_content = re.sub(r'(@[\w.]+)\s*=\s*(private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr)\s+(global|constant)', r'\1 = \2 dso_local \3', opt_content)
opt_content = re.sub(r'(@[\w.]+)\s*=\s+(?!private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr|dso_local)(global|constant)', r'\1 = dso_local \2', opt_content)
opt_content = _AddWeakComdat(opt_content)
with open(optimized_ll, 'w', encoding='utf-8') as f:
f.write(opt_content)
merged_obj = os.path.join(self.output_dir, '_merged.obj')
@@ -2045,6 +2105,7 @@ class Phase2Translator:
ll_content = re.sub(r'^(@[\w.]+)\s*=\s*external\s+global\s+(\w[\w*]*)\s+(-?\d+|null|zeroinitializer|true|false)$', r'\1 = linkonce global \2 \3', ll_content, flags=re.MULTILINE)
ll_content = re.sub(r'^(@[\w.]+)\s*=\s*(private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr)\s+(global|constant)', r'\1 = \2 dso_local \3', ll_content, flags=re.MULTILINE)
ll_content = re.sub(r'^(@[\w.]+)\s*=\s+(?!private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr|dso_local)(global|constant)', r'\1 = dso_local \2', ll_content, flags=re.MULTILINE)
ll_content = _AddWeakComdat(ll_content)
# LLVM 22+: 移除同一模块中已有 define 的 declare 语句(LLVM 22 将 declare+define 视为重复定义)
defined_funcs: set[str] = set()
for m in re.finditer(r'^define\s+(?:dso_local\s+)?[^@]*@\"?([^"\s\(]+)\"?\s*\(', ll_content, re.MULTILINE):
@@ -2795,6 +2856,7 @@ class Phase2Translator:
ll_content = re.sub(r'^(@[\w.]+)\s*=\s*external\s+global\s+(\w[\w*]*)\s+(-?\d+|null|zeroinitializer|true|false)$', r'\1 = linkonce global \2 \3', ll_content, flags=re.MULTILINE)
ll_content = re.sub(r'^(@[\w.]+)\s*=\s*(private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr)\s+(global|constant)', r'\1 = \2 dso_local \3', ll_content, flags=re.MULTILINE)
ll_content = re.sub(r'^(@[\w.]+)\s*=\s+(?!private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr|dso_local)(global|constant)', r'\1 = dso_local \2', ll_content, flags=re.MULTILINE)
ll_content = _AddWeakComdat(ll_content)
# stub_decls 注入(复用预收集的 stub_decls)
if stub_decls: