fix(sizeof,parse): 尺寸链去静默兜底,解析报错可定位,补 C 解析器缺口

StructGen: _get_struct_size/_get_align 去 opaque→8/ptr_size、未知→4 兜底并补 VectorType;LiteralStructType.fields→.elements(llvmlite 无 fields,必然 AttributeError)

HandlesClassDef: union 载荷尺寸经不存在的 ir.DataLayout 静默退化成 8 字节;HandlesExprCall/ExprBuiltin: sizeof 各静默 0 分支改 fail-fast,数组元素 sizeof 统一走 _get_struct_size

HandlesFor: nonlocal struct/array 堆分配不再退化成 malloc(8)

vast/vast_c: 补一元 * / & 解析,装饰器后 extern/export 分派修复,C 侧报错带 token 与行号;StubGen.Converter: 宏展开丢尾随空行修 .pyi 缩进非法

其余为 rawptr 迁移 / fail-fast 口径等历史改动一并入库
This commit is contained in:
TermiNexus committed 2026-09-26 19:27:22 +08:00
1 parent 53a345a59a
commit c83fc752c6
53 files changed
+6238 -887

No files matched your search

+152
View File
@@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
"""只读审计:全仓"把源码交给 Python 解析器"的调用点是否都过了 Viper 软关键字层。
背景(为什么需要这个门禁)
Viper 前端 = TPC 自研 vast(`lib/vast`)+ **唯一展开点**
`lib/core/SymbolUtils.ExpandSoftKeywords`(`fn/fun/func/function` → `def`;
`enum X:` 块 → `class X(t.CEnum):`,两者均同行替换、行号不变)。
凡把 **Viper 源码文本**交给 `ast.parse` / `vast.parse` 的入口都必须先过这一层;
漏过者的失败形态是**静默降级**,不是报错:`includes/ast/base.vp:168` 的
`enum ASTFlag:` 曾让整个 base.vp 的声明(含顶层 `_init_ast`)一个都不注册,
下游只报 `Undefined function: '_init_ast'`(浪费一轮排查,见
`Debugger/enum-semantics.md` §13)。判据 = `Grep "ast\\.parse\\("` 逐个确认
上游是否已有展开层,或该输入**必然无糖**。
分类(逐调用点,见 `Classify`)
PASS 首实参文本含 `ExpandSoftKeywords`(同一表达式内已展开)
EVAL `mode='eval'`:注解 / 表达式的**片段**解析,不是整份源码 ⇒ 无需
EXEMPT 命中下方冻结豁免表:输入必然是 `.vpi`/`.pyi` 无糖产物,或同一函数
上游(紧邻几行)已展开 ⇒ 已人工审计确认
SUSPECT 其余 ⇒ **需人工判定**;`--strict` 下 exit 1(门禁失败)
覆盖范围:仓库内全部 `*.py`(含根目录脚本、`MCP/**`、`TransPyC/Tools/**`),
跳过 `.git`/`__pycache__`/`output`/`temp`/`.tpv_build`/`dlls` 与解析器自身
(`lib/vast`、`lib/vast_c` 是前端**实现**,不是前端**消费者**)。
TPV 侧源码(`TransPyV/**` 的 `.vp`)跑自己的前端,不在本脚本范围内。
用法(**只读,不写任何文件**)
python TransPyC/Tools/AuditViperParseEntries.py # 打印审计表
python TransPyC/Tools/AuditViperParseEntries.py --strict # 有 SUSPECT 则 exit 1
"""
import ast as pyast
import os
import sys
ScriptDir = os.path.dirname(os.path.abspath(__file__))
RepoRoot = os.path.dirname(os.path.dirname(ScriptDir))
SkipDirs = {'__pycache__', '.git', '.tpv_build', 'dlls', 'output', 'temp', 'node_modules'}
# 前端实现(vast 解析器本体;它自己就是"唯一许可入口"的实现层)
SkipRelPrefixes = ('TransPyC/lib/vast/', 'TransPyC/lib/vast_c/')
# ---- 冻结豁免表:(仓库相对路径, 首实参文本归一化) -> 豁免理由 ------------------
# 新增合法调用点时必须在此登记原因;否则 --strict 会把新调用点当作 SUSPECT 拦下。
# 表来源:本轮全仓穷举审计(见最终报告 B 节审计表)。
Exempt = {
('TransPyC/lib/Projectrans/Phase2Translator.py', 'f.read()'):
'.vpi/.pyi 无糖产物(_ScanPyiFiles / _PyiCacheWorkerInitializer 仅扫 .vpi/.pyi)',
('TransPyC/lib/Projectrans/Phase2Translator.py', 'sig_content'):
'sig_files 全部来自 temp/*.vpi|*.pyi(Phase2Translator._LoadSha1Map:759)',
('TransPyC/lib/Projectrans/DeclarationGenerator.py', 'pyi_content'):
'generate(pyi_content, ...) 的入参来自 Phase1Generator 读 temp/{sha1}.vpi',
('TransPyC/lib/StubGen/Converter.py', 'PyContent'):
'convert() 首行 PyContent = ExpandSoftKeywords(PyContent)(Converter.py:68)',
('TransPyC/lib/Shell/TransPyC.py', 'content'):
'同函数 :100 content = ExpandSoftKeywords(content)',
('TransPyC/lib/Shell/TransPyC.py', 'Content'):
'同函数 :445 Content = ExpandSoftKeywords(Content)',
('TransPyC/lib/Shell/TransPyC.py', 'self.code'):
'Convert() :522 self.code = ExpandSoftKeywords(self.code)',
('TransPyC/lib/core/Translator/PythonParser.py', 'content'):
'ParsePythonFile() :76 content = ExpandSoftKeywords(content)',
('TransPyC/lib/core/Translator/AnnotationLoader.py', 'ann_content'):
'三处调用点各自紧邻上一行 ann_content = ExpandSoftKeywords(ann_content)',
('TransPyC/lib/core/SymbolExtractor.py', 'code'):
'extract(FilePath) 的 FilePath 全部来自 LoadModuleSymbols → .vpi/.pyi',
('TransPyC/lib/core/Handles/HandlesImports.py', '_fh0.read()'):
'.vpi(_TryLoadClassMembersFromPyi guard 分支补解析 class_parents)',
('TransPyC/lib/core/Handles/HandlesImports.py', 'f.read()'):
'.vpi(_TryLoadClassMembersFromPyi 主路径 _pyi_cache 填充)',
('TransPyC/lib/core/Handles/HandlesExprCall.py', 'pyi_code'):
'.vpi(_vtable_pyi_cache 填充,路径固定 temp/{sha1}.vpi)',
}
def Classify(Rel, Arg, Mode):
"""→ (类别, 理由)。"""
if 'ExpandSoftKeywords' in Arg:
return 'PASS', '首实参已过 ExpandSoftKeywords'
if Mode.strip('\'"') == 'eval':
return 'EVAL', "mode='eval'(注解/表达式片段,非整份源码)"
Hit = Exempt.get((Rel, Arg))
if Hit:
return 'EXEMPT', Hit
return 'SUSPECT', '未过展开层且不在冻结豁免表中(需人工判定该输入是否必然无糖)'
def IterCallSites():
"""遍历仓库 .py,产出 (rel, line, arg, mode);无法按 CPython 解析的文件记入 Unparsed。"""
for Root, Dirs, Files in os.walk(RepoRoot):
Dirs[:] = sorted(d for d in Dirs if d not in SkipDirs)
for Name in sorted(Files):
if not Name.endswith('.py'):
continue
Path = os.path.join(Root, Name)
Rel = os.path.relpath(Path, RepoRoot).replace(os.sep, '/')
if Rel.startswith(SkipRelPrefixes):
continue
try:
with open(Path, encoding='utf-8') as Fh:
Src = Fh.read()
Tree = pyast.parse(Src, Rel)
except Exception as Err:
yield ('<unparsed>', 0, Rel, type(Err).__name__)
continue
for Node in pyast.walk(Tree):
if not isinstance(Node, pyast.Call):
continue
Func = Node.func
if not isinstance(Func, pyast.Attribute) or Func.attr != 'parse':
continue
if not (isinstance(Func.value, pyast.Name) and Func.value.id in ('ast', 'vast')):
continue
Arg = pyast.get_source_segment(Src, Node.args[0]) if Node.args else ''
Mode = ''
for Kw in Node.keywords:
if Kw.arg == 'mode':
Mode = pyast.get_source_segment(Src, Kw.value) or ''
yield ('site', Node.lineno, Rel, ' '.join((Arg or '').split()), ' '.join(Mode.split()))
def Main(Strict):
Rows = []
Unparsed = []
for Item in IterCallSites():
if Item[0] == '<unparsed>':
Unparsed.append((Item[2], Item[3]))
continue
_, Line, Rel, Arg, Mode = Item
Kind, Why = Classify(Rel, Arg, Mode)
Rows.append((Kind, Rel, Line, Arg, Why))
Order = {'SUSPECT': 0, 'PASS': 1, 'EVAL': 2, 'EXEMPT': 3}
Rows.sort(key=lambda R: (Order[R[0]], R[1], R[2]))
Count = {}
for Kind, _, _, _, _ in Rows:
Count[Kind] = Count.get(Kind, 0) + 1
for Kind, Rel, Line, Arg, Why in Rows:
print('%-8s %s:%d arg=%s — %s' % (Kind, Rel, Line, (Arg or '')[:60], Why))
print('-' * 100)
print('sites=%d PASS=%d EVAL=%d EXEMPT=%d SUSPECT=%d unparsed=%d' % (
len(Rows), Count.get('PASS', 0), Count.get('EVAL', 0),
Count.get('EXEMPT', 0), Count.get('SUSPECT', 0), len(Unparsed)))
if Unparsed:
print('无法按 CPython 解析(未审计): %s' % Unparsed)
if Strict and Count.get('SUSPECT'):
sys.stderr.write('[strict] 门禁失败: 有 %d 个未过展开层且未登记的解析入口\n'
% Count['SUSPECT'])
return 1
return 0
if __name__ == '__main__':
sys.exit(Main('--strict' in sys.argv[1:]))
+840
View File
@@ -0,0 +1,840 @@
# -*- coding: utf-8 -*-
"""作用域感知静态扫描: `rawptr[<族>.<X>]` 变量上访问 <X> 不存在的字段(fast-fail 潜在点)。
族(Family)= 一个"类名限定前缀 + 类定义来源"。默认族表见 BuildFamilies;
`rawptr[ast.X]` / `rawptr[llvmlite.X]` / `rawptr[GenTypes.X]` / `rawptr[HT.X]` /
`rawptr[memhub.X]` 均可解析。另加载 linkedlist.vp 作为**支撑族**(空前缀 ''),
供 `class ParamNode(GSListNode[ParamNode])` 这类**裸基类名**解析(否则 `.Next` 会误报)。
相对旧版的关键修正(旧版按“变量名全文件聚合 + 正则硬匹配”导致大量误报):
1) 作用域建模: 为 模块 / 函数(方法/嵌套函数) / lambda 建立独立作用域,
类体作用域按 Python 语义对方法“穿透”; 属性访问按“该访问点所在作用域链”找
**最近一次、且位置在访问点之前** 的声明(不是“全文件第一条”)。
2) 形参(含 self 与 rawptr[X] 形参)计入声明; 无注解形参视为“遮蔽”外层同名变量。
3) 重新赋值/重声明: AnnAssign / Assign(cast 形态) / 赋值继承(Name RHS) 都参与;
非指针赋值记录为“已解析但非指针”=> 不再继承更早的类。
4) 跳过非代码: 解析源码后, 注释/字符串/docstring 内容天然不产生 Attribute 节点
(如 "%s.__iter__" 之类的字符串与三引号注释块不再命中)。
5) 类成员表按族隔离: 基类先在**本族**找, 再找**支撑族**(''), 最后全局唯一名;
同一基类名跨族重复时报 AMBIG(宁可 UNRESOLVED 也不猜)。
6) `t.REnum`(tagged union)派生类: 变体字段合并进父类成员集
(TPV 允许在父类指针上访问变体字段, 不合并会产生大量误报)。
前端 = **TPC 自研 vast**(`lib/vast`,与 StubGen/Converter.py 用的 `import vast as ast`
是同一源文件; 不可用 CPython `ast`, 否则含 `list[X*]` 等 vast 专有写法的文件会整体
SyntaxError 被跳过)。vast 与 CPython ast 的差异及本脚本的适配:
A) `X*` / `X**` 被 vast 脱糖成 `X | t.CPtr`(`**` 加两层), 其中合成的
`t.CPtr` Attribute 节点**连 lineno 属性都没有**(编译器内部脱糖产物, 不是源码里的
访问; 全树实测 555 个, 全部是 ('t','CPtr'))=> 计入 Uses 时用
`getattr(Node,'lineno',None) is not None` 排除。
B) vast 的 SyntaxError 可能不带 lineno(C 路径)=> 从报文 `at line N` 兜底提取。
C) vast 的装饰器 `@t.NoVTable` 的 col_offset 是 `@` 的位置(CPython 是其后 1 列);
该 Attribute 的接收者 `t` 不是族指针, 判据不受影响。
D) 其余节点类名/属性名/walk/iter_child_nodes 与 CPython ast 同构(已逐节点对照)。
输出: 文件:行号 / 变量 / 推导出的类 / 字段 / 声明来源行 / 判定, 并统计
NOWHERE / OTHER / RESOLVED / UNRESOLVED, 并按族(ast/llvmlite/其它)分桶。
路径: 全部按**脚本自身位置**推导(ScriptDir = <RepoRoot>/TransPyC/Tools),
不依赖 cwd;可用 --app-root / --includes / --out 覆盖。
用法:
python TransPyC/Tools/ScanAstFieldMiss.py [--app-root DIR] [--includes DIR]
[--out FILE] [--verbose] [--strict]
python TransPyC/Tools/ScanAstFieldMiss.py --selftest [--includes DIR] [--out FILE]
默认值:
--app-root <RepoRoot>/TransPyV/App
--includes <RepoRoot>/includes
--out <RepoRoot>/Debugger/ScanAstFieldMiss.txt
退出码:
0 非 --strict 的正常扫描; 或 --strict 下 NOWHERE / OTHER / parsefail 三者全 0
1 --strict 下 有 NOWHERE/OTHER 命中, **或 parsefail > 0**
(parsefail 也算门禁失败: 文件被前端静默跳过 = 假绿, 见下)
2 参数错误
--selftest: 0 = 断言全通过; 1 = 有断言失败
--selftest 为什么必须有(历史教训):
上一版门禁的前端是 CPython `ast`, 对含 `list[X*]` / `rawptr[X]*` 等 vast 专有写法的
文件整体 SyntaxError 跳过 ⇒ 曾出现 "OTHER=0 全绿" 而工作树里实际有 **18 处**
`rawptr[llvmlite.Function].Ty` 真错(整批文件根本没被检查)。
故 --selftest 内置合成用例(生成到临时目录、跑完即删), 断言"**报出集合 == 预期集合**",
覆盖 4 类正例(含 vast 专有 `rawptr[X]*` / `rawptr[X]**`)与 4 类负例
(合法字段 / 跨函数同名各自声明 / docstring 与注释 / 返回值推断生效),
用来证明扫描器不是空转。
"""
import glob
import os
import re
import shutil
import sys
import tempfile
# ============================================================
# 0. 路径推导(全部按脚本自身位置, 不依赖 cwd)
# ScriptDir = <RepoRoot>/TransPyC/Tools
# TpcRoot = <RepoRoot>/TransPyC (TransPyC 仓库根)
# RepoRoot = <RepoRoot> (工作区根)
# ============================================================
ScriptDir = os.path.dirname(os.path.abspath(__file__))
TpcRoot = os.path.dirname(ScriptDir)
RepoRoot = os.path.dirname(TpcRoot)
DefaultAppRoot = os.path.join(RepoRoot, 'TransPyV', 'App')
DefaultIncludesDir = os.path.join(RepoRoot, 'includes')
DefaultOutFile = os.path.join(RepoRoot, 'Debugger', 'ScanAstFieldMiss.txt')
# ---- 前端: TPC 自研 vast(非 CPython ast)。需要 TransPyC 包根在 sys.path,
# lib._bootstrap 会再补齐 lib/ 与 lib/includes(使 `import vast` 可用)。
if TpcRoot not in sys.path:
sys.path.insert(0, TpcRoot)
from lib import vast as ast # noqa: E402
from lib.core.SymbolUtils import ExpandSoftKeywords # noqa: E402
SupportPrefix = ''
# ============================================================
# 1. 类成员表: 字段 / 方法 / 基类(按族隔离;每次扫描重建)
# ============================================================
FamilyTable = []
SupportFiles = []
FamilyNames = set()
ClassFields = {}
ClassMethods = {}
ClassBase = {}
ClassLoc = {} # 裸类名 -> [族, ...](用于跨族基类解析)
EnumClass = {} # (族, 类名) -> 1 表示 t.REnum/t.CEnum 派生(变体字段合并)
REnumClass = {} # (族, 类名) -> 1 表示 t.REnum 派生(额外有编译器内建 .kind())
MemberCache = {}
FamilyFields = {} # 族 -> 该族所有字段并集(OTHER/NOWHERE 判定用)
AmbiguousBases = set()
Uses = [] # [(AttrNode, Scope)];Walk 现场填充, 每个文件扫描前 clear
# ---- 返回注解表(同文件内 def f(...) -> rawptr[族.X]):供 x = f() 形态的类型推导。
# 仅补充覆盖(收紧),不改变任何既有判据:名字在文件内返回注解不一致时报歧义并放弃。
FuncRet = {}
def ResetTables():
"""清空全部类表/缓存(供同一进程内多轮扫描复用, 如 --selftest)。"""
global FamilyTable, SupportFiles, FamilyNames
global ClassFields, ClassMethods, ClassBase, ClassLoc, EnumClass, REnumClass
global MemberCache, FamilyFields, AmbiguousBases
FamilyTable = []
SupportFiles = []
FamilyNames = set()
ClassFields = {}
ClassMethods = {}
ClassBase = {}
ClassLoc = {}
EnumClass = {}
REnumClass = {}
MemberCache = {}
FamilyFields = {}
AmbiguousBases = set()
FuncRet.clear()
def AddClass(Fam, Name, Bases):
ClassFields.setdefault((Fam, Name), set())
ClassMethods.setdefault((Fam, Name), set())
ClassLoc.setdefault(Name, [])
if Fam not in ClassLoc[Name]:
ClassLoc[Name].append(Fam)
if Bases:
ClassBase.setdefault((Fam, Name), set()).update(Bases)
def BaseName(Node):
if isinstance(Node, ast.Name):
return Node.id
if isinstance(Node, ast.Attribute):
return Node.attr
if isinstance(Node, ast.Subscript): # GSListNode[ParamNode] -> GSListNode
return BaseName(Node.value)
return None
def CollectBody(Fam, Name, Body, Enum):
"""收集类体成员;Enum=True 时把嵌套变体类的字段并入本类(REnum 语义)。"""
for Item in Body:
if isinstance(Item, ast.AnnAssign) and isinstance(Item.target, ast.Name):
ClassFields[(Fam, Name)].add(Item.target.id)
elif isinstance(Item, (ast.FunctionDef, ast.AsyncFunctionDef)):
ClassMethods[(Fam, Name)].add(Item.name)
elif Enum and isinstance(Item, ast.ClassDef):
CollectBody(Fam, Name, Item.body, Enum) # 变体字段并入父类
def RegisterClass(Fam, Node):
Bases = set()
for B in Node.bases:
Nm = BaseName(B)
if Nm:
Bases.add(Nm)
AddClass(Fam, Node.name, Bases)
Enum = 1 if (Bases & {'REnum', 'CEnum'}) else 0
if Enum:
EnumClass[(Fam, Node.name)] = 1
if 'REnum' in Bases:
# REnum.kind() 是编译器内建(HandlesExprCall.py:3126 'kind' → __tag 读取)
REnumClass[(Fam, Node.name)] = 1
CollectBody(Fam, Node.name, Node.body, Enum)
def ParseSrc(Src, Path):
"""vast 前端解析入口(全脚本单一前端; 失败抛异常, 由调用方记账)。
`.vp` 源码须先过 Viper 软关键字层(`lib/core/SymbolUtils.ExpandSoftKeywords`:
`fn/fun/func/function` → `def`、`enum X:` → `class X(t.CEnum):`),与
`StubGen/Converter.py` / `Shell/TransPyC.py` 等既有前端入口同口径。
否则含 `enum` 的 `.vp`(如 `includes/ast/base.vp:168`)会在此整体 SyntaxError,
被 `LoadInclude` 的正则兜底或 `RunScan` 的 `parsefail` 记成"解析失败"——两者
都会**静默漏掉该文件的类/字段**(本脚本的假绿来源)。`.py` 无糖,展开为空操作
(`'enum' in Code` 门禁 + 行首锚定正则)。
"""
return ast.parse(ExpandSoftKeywords(Src), Path)
def ErrLine(E):
"""vast 的 SyntaxError 可能无 lineno(C 路径)=> 从报文 `at line N` 兜底提取。"""
L = getattr(E, 'lineno', None)
if L:
return L
M = re.search(r'at line (\d+)', str(E))
return int(M.group(1)) if M else -1
def LoadIncludeRegex(Src, Fam):
"""ast.parse 失败(docstring 含 \\xNN 等)时的回退:缩进感知的类/字段/方法收集。"""
Indent = {}
Enum = {}
for Ln in Src.splitlines():
M = re.match(r'(\s*)class (\w+)(?:\[[^\]]*\])?(?:\(([^)]*)\))?\s*:', Ln)
if M:
Depth = len(M.group(1))
Name = M.group(2)
Bases = set()
if M.group(3):
for B in M.group(3).split(','):
B = B.strip().split('.')[-1].split('[')[0].strip()
if B:
Bases.add(B)
AddClass(Fam, Name, Bases)
Indent[Name] = Depth
Enum[Name] = 1 if (Bases & {'REnum', 'CEnum'}) else 0
if Enum[Name]:
EnumClass[(Fam, Name)] = 1
continue
M2 = re.match(r'(\s+)(\w+)\s*:\s', Ln)
if M2 and Indent:
Depth = len(M2.group(1))
Owner = [N for N, D in Indent.items() if D < Depth]
if Owner:
P = max(Owner, key=lambda N: Indent[N])
ClassFields[(Fam, P)].add(M2.group(2))
continue
M3 = re.match(r'(\s+)def (\w+)\(', Ln)
if M3 and Indent:
Depth = len(M3.group(1))
Owner = [N for N, D in Indent.items() if D < Depth]
if Owner:
P = max(Owner, key=lambda N: Indent[N])
ClassMethods[(Fam, P)].add(M3.group(2))
def LoadInclude(Path, Fam):
try:
Src = open(Path, encoding='utf-8').read()
except Exception:
return
try:
Tree = ParseSrc(Src, Path)
except Exception: # noqa: BLE001 (vast 失败可能是 SyntaxError/ValueError)
LoadIncludeRegex(Src, Fam)
return
for Node in Tree.body:
if isinstance(Node, ast.ClassDef):
RegisterClass(Fam, Node)
def BuildFamilies(AppRoot, IncludesDir):
"""按 AppRoot/IncludesDir 建族表并加载类定义(含支撑族)。"""
global FamilyTable, SupportFiles, FamilyNames
FamilyTable = [
('ast', [os.path.join(IncludesDir, 'ast')]),
('llvmlite', [os.path.join(IncludesDir, 'llvmlite')]),
('GenTypes', [os.path.join(AppRoot, 'lib', 'core', 'Gen', 'GenTypes.vp')]),
('HT', [os.path.join(AppRoot, 'lib', 'core', 'Gen', 'Translator.vp')]),
('memhub', [os.path.join(IncludesDir, 'memhub.vp')]),
]
SupportFiles = [os.path.join(IncludesDir, 'linkedlist.vp')]
FamilyNames = set(P for P, _ in FamilyTable) | {''}
for Prefix, Paths in FamilyTable:
for P in Paths:
if os.path.isdir(P):
for F in sorted(glob.glob(os.path.join(P, '*.vp'))):
LoadInclude(F, Prefix)
else:
LoadInclude(P, Prefix)
for F in SupportFiles:
LoadInclude(F, SupportPrefix)
def ComputeFamilyFields():
global FamilyFields
FamilyFields = {}
for (_F, _N) in ClassFields:
FamilyFields.setdefault(_F, set()).update(ClassFields[(_F, _N)])
def FindClass(Name, Prefer):
"""裸类名 -> (族, 类名)。优先本族, 再支撑族, 最后全局唯一名。"""
if (Prefer, Name) in ClassFields:
return (Prefer, Name)
if (SupportPrefix, Name) in ClassFields:
return (SupportPrefix, Name)
Locs = ClassLoc.get(Name, [])
if len(Locs) == 1:
return (Locs[0], Name)
if len(Locs) > 1:
AmbiguousBases.add(Name)
return None
def Members(Fam, Name, Seen=None):
if Seen is None:
Seen = set()
Key = (Fam, Name)
if Key in Seen:
return set()
Seen.add(Key)
Out = set(ClassFields.get(Key, ())) | set(ClassMethods.get(Key, ()))
if Key in REnumClass:
Out.add('kind')
for B in ClassBase.get(Key, ()):
Loc = FindClass(B, Fam)
if Loc is not None:
if Loc in REnumClass:
Out.add('kind')
Out |= Members(Loc[0], Loc[1], Seen)
return Out
def Member(Fam, Name):
Key = (Fam, Name)
if Key not in MemberCache:
MemberCache[Key] = Members(Fam, Name)
return MemberCache[Key]
# ============================================================
# 2. 类型推导: 从注解 / cast 调用里取 (族, 类名)
# ============================================================
def AnnClass(Ann):
"""注解里若为 rawptr[<族>.<X>](可被 `| t.CPtr` 包裹)返回 (族, X), 否则 None。"""
if Ann is None:
return None
if isinstance(Ann, ast.BinOp) and isinstance(Ann.op, ast.BitOr):
return AnnClass(Ann.left) or AnnClass(Ann.right)
if isinstance(Ann, ast.Subscript):
if isinstance(Ann.value, ast.Name) and Ann.value.id == 'rawptr':
Sl = Ann.slice
if isinstance(Sl, ast.Attribute) and isinstance(Sl.value, ast.Name) \
and Sl.value.id in FamilyNames and Sl.value.id != '':
return (Sl.value.id, Sl.attr)
return None
def CastClass(Val):
"""`rawptr[<族>.<X>](...)` 形态的 cast 调用返回 (族, X), 否则 None。"""
if isinstance(Val, ast.Call) and isinstance(Val.func, ast.Subscript):
return AnnClass(Val.func)
return None
def CollectFuncRets(Tree):
FuncRet.clear()
Ambig = set()
for Node in ast.walk(Tree):
if isinstance(Node, (ast.FunctionDef, ast.AsyncFunctionDef)):
C = AnnClass(Node.returns)
if Node.name in FuncRet and FuncRet[Node.name] != C:
Ambig.add(Node.name)
else:
FuncRet[Node.name] = C
for N in Ambig:
FuncRet.pop(N, None)
# ============================================================
# 3. 作用域模型
# ============================================================
class Scope:
__slots__ = ('Kind', 'Parent', 'Decls')
def __init__(self, Kind, Parent):
self.Kind = Kind # 'module' / 'func' / 'class'
self.Parent = Parent
self.Decls = {} # name -> [(Lineno, Col, Cls, DeclLine)]
def Add(self, Name, Lineno, Col, Cls):
self.Decls.setdefault(Name, []).append((Lineno, Col, Cls, Lineno))
def Resolve(Sc, Name, Lineno, Col):
"""沿作用域链找 Name 在 (Lineno, Col) 之前最近一次的声明。
返回 (Cls, DeclLine); Cls 为 None 表示“已解析但非族指针”或“未找到”。
类体作用域对嵌套函数穿透(Python 语义)。
"""
Cur = Sc
First = True
while Cur is not None:
if Cur.Kind != 'class' or First:
if Name in Cur.Decls:
Best = None
for D in Cur.Decls[Name]:
if (D[0], D[1]) <= (Lineno, Col):
if Best is None or (D[0], D[1]) > (Best[0], Best[1]):
Best = D
if Best is not None:
return Best[2], Best[3]
First = False
Cur = Cur.Parent
return None, None
def Walk(Node, Sc):
if Node is None:
return
if isinstance(Node, (ast.FunctionDef, ast.AsyncFunctionDef)):
for D in Node.decorator_list:
Walk(D, Sc)
A = Node.args
Formals = list(A.posonlyargs) + list(A.args) + list(A.kwonlyargs)
for P in Formals:
Walk(P.annotation, Sc)
for D in A.defaults:
Walk(D, Sc)
for D in A.kw_defaults:
Walk(D, Sc)
if A.vararg is not None:
Walk(A.vararg.annotation, Sc)
if A.kwarg is not None:
Walk(A.kwarg.annotation, Sc)
Walk(Node.returns, Sc)
FS = Scope('func', Sc)
for P in Formals:
FS.Add(P.arg, Node.lineno, 0, AnnClass(P.annotation))
if A.vararg is not None:
FS.Add(A.vararg.arg, Node.lineno, 0, AnnClass(A.vararg.annotation))
if A.kwarg is not None:
FS.Add(A.kwarg.arg, Node.lineno, 0, AnnClass(A.kwarg.annotation))
for S in Node.body:
Walk(S, FS)
return
if isinstance(Node, ast.Lambda):
A = Node.args
Formals = list(A.posonlyargs) + list(A.args) + list(A.kwonlyargs)
for P in Formals:
Walk(P.annotation, Sc)
for D in A.defaults:
Walk(D, Sc)
for D in A.kw_defaults:
Walk(D, Sc)
LS = Scope('func', Sc)
for P in Formals:
LS.Add(P.arg, Node.lineno, 0, AnnClass(P.annotation))
Walk(Node.body, LS)
return
if isinstance(Node, ast.ClassDef):
for D in Node.decorator_list:
Walk(D, Sc)
for B in Node.bases:
Walk(B, Sc)
for K in Node.keywords:
Walk(K.value, Sc)
CS = Scope('class', Sc)
for S in Node.body:
Walk(S, CS)
return
if isinstance(Node, ast.Attribute):
# 只收源码里真实存在的属性访问: vast 脱糖 `X*` 会合成 `t.CPtr` Attribute,
# 该节点**连 lineno 属性都没有**(全树 555 个, 全为 ('t','CPtr')), 不是源码
# 访问、也不能参与位置比较 => 用 getattr 显式排除。
if isinstance(Node.value, ast.Name) and getattr(Node, 'lineno', None) is not None:
Uses.append((Node, Sc))
Walk(Node.value, Sc)
return
if isinstance(Node, ast.AnnAssign):
if isinstance(Node.target, ast.Name):
Sc.Add(Node.target.id, Node.target.lineno, Node.target.col_offset,
AnnClass(Node.annotation))
elif isinstance(Node.target, ast.Attribute):
Walk(Node.target, Sc) # `x.y: T = ...` 的属性访问
Walk(Node.annotation, Sc)
Walk(Node.value, Sc)
return
if isinstance(Node, ast.Assign):
Cls = None
V = Node.value
if isinstance(V, ast.Call):
Cls = CastClass(V)
if Cls is None and isinstance(V.func, ast.Name):
Cls = FuncRet.get(V.func.id)
elif isinstance(V, ast.Name):
Cls, _ = Resolve(Sc, V.id, V.lineno, V.col_offset)
for T in Node.targets:
if isinstance(T, ast.Name):
Sc.Add(T.id, T.lineno, T.col_offset, Cls)
# 赋值**目标**位置的属性访问也要计入(如 `vtable_node.Ty = ...`)
Walk(T, Sc)
Walk(Node.value, Sc)
return
if isinstance(Node, ast.AugAssign):
if isinstance(Node.target, ast.Name):
Sc.Add(Node.target.id, Node.target.lineno, Node.target.col_offset, None)
Walk(Node.target, Sc)
Walk(Node.value, Sc)
return
if isinstance(Node, (ast.For, ast.AsyncFor)):
for T in ast.walk(Node.target):
if isinstance(T, ast.Name):
Sc.Add(T.id, Node.lineno, 0, None)
Walk(Node.iter, Sc)
for S in Node.body:
Walk(S, Sc)
for S in Node.orelse:
Walk(S, Sc)
return
if isinstance(Node, ast.NamedExpr):
if isinstance(Node.target, ast.Name):
Sc.Add(Node.target.id, Node.target.lineno, Node.target.col_offset, None)
Walk(Node.value, Sc)
return
for Ch in ast.iter_child_nodes(Node):
Walk(Ch, Sc)
# ============================================================
# 4. 扫描(一轮 = ResetTables + BuildFamilies + 遍历 AppRoot)
# ============================================================
def BucketOf(Fam):
return Fam if Fam in ('ast', 'llvmlite') else 'OTHER:' + (Fam or 'support')
def Bump(D, Key):
D[Key] = D.get(Key, 0) + 1
def RunScan(AppRoot, IncludesDir, OutFile, Verbose):
"""跑一轮扫描: 写报告文件 + 返回统计/命中集合(不打印)。"""
ResetTables()
BuildFamilies(AppRoot, IncludesDir)
ComputeFamilyFields()
Nowhere = []
Other = []
ResolvedCount = 0
UnresolvedCount = 0
Files = sorted(glob.glob(os.path.join(AppRoot, '**', '*.vp'), recursive=True))
ParseFail = []
for Path in Files:
try:
Src = open(Path, encoding='utf-8').read()
except Exception:
continue
try:
Tree = ParseSrc(Src, Path)
except Exception as E: # noqa: BLE001 (vast 失败可能是 SyntaxError/ValueError)
ParseFail.append((os.path.relpath(Path, AppRoot), ErrLine(E), str(E)[:80]))
continue
RawLines = Src.splitlines()
Uses.clear()
CollectFuncRets(Tree)
MS = Scope('module', None)
for S in Tree.body:
Walk(S, MS)
for AttrNode, Sc in Uses:
Var = AttrNode.value.id
Field = AttrNode.attr
Cls, DeclLine = Resolve(Sc, Var, AttrNode.lineno, AttrNode.col_offset)
if Cls is None:
UnresolvedCount += 1
continue
Fam, CName = Cls
if (Fam, CName) not in ClassFields:
UnresolvedCount += 1
continue
ResolvedCount += 1
if Field in Member(Fam, CName):
continue
Rel = os.path.relpath(Path, AppRoot)
SrcLine = RawLines[AttrNode.lineno - 1].strip()[:96] if AttrNode.lineno <= len(RawLines) else ''
Row = (Rel, AttrNode.lineno, Var, CName, Field, DeclLine, SrcLine, Fam)
if Field in FamilyFields.get(Fam, set()):
Other.append(Row)
else:
Nowhere.append(Row)
# 稳定排序(便于 diff / 门禁)
SortKey = lambda R: (R[0], R[1], R[2], R[4], R[7], R[3], R[5])
Nowhere.sort(key=SortKey)
Other.sort(key=SortKey)
BucketsN = {}
BucketsO = {}
for R in Nowhere:
Bump(BucketsN, BucketOf(R[7]))
for R in Other:
Bump(BucketsO, BucketOf(R[7]))
ParsedN = len(Files) - len(ParseFail)
UsesN = ResolvedCount + UnresolvedCount
ClassLine = ' '.join('%s=%d' % (f, sum(1 for (x, _n) in ClassFields if x == f))
for f in sorted(set(x for x, _n in ClassFields)))
def W(Fh, Rows, Tag):
for R in Rows:
Fh.write('%-46s:%-5d var=%s cls=%s.%s field=%s decl@%d 判定=%s %s\n' %
(R[0], R[1], R[2], R[7], R[3], R[4], R[5], Tag, R[6]))
OutDir = os.path.dirname(os.path.abspath(OutFile))
if OutDir:
os.makedirs(OutDir, exist_ok=True)
with open(OutFile, 'w', encoding='utf-8') as Fh:
Fh.write('files=%d parsed=%d parsefail=%d uses=%d NOWHERE=%d OTHER=%d RESOLVED=%d UNRESOLVED=%d\n' %
(len(Files), ParsedN, len(ParseFail), UsesN,
len(Nowhere), len(Other), ResolvedCount, UnresolvedCount))
Fh.write('classes: %s\n' % ClassLine)
Fh.write('NOWHERE buckets: %s\n' % sorted(BucketsN.items()))
Fh.write('OTHER buckets: %s\n' % sorted(BucketsO.items()))
if AmbiguousBases:
Fh.write('AMBIGUOUS BASES (跨族重名, 已不解析): %s\n' % sorted(AmbiguousBases))
Fh.write('\n=== NOWHERE (本族任何类都没有该字段) ===\n')
W(Fh, Nowhere, 'NOWHERE')
Fh.write('\n=== OTHER (字段只存在于本族别的类) ===\n')
W(Fh, Other, 'OTHER')
if ParseFail:
Fh.write('\n=== PARSE FAIL (vast 无法解析, 已跳过) ===\n')
for P in ParseFail:
Fh.write('%s:%s %s\n' % P)
return {
'Files': len(Files), 'Parsed': ParsedN, 'ParseFail': ParseFail,
'Uses': UsesN, 'Nowhere': Nowhere, 'Other': Other,
'Resolved': ResolvedCount, 'Unresolved': UnresolvedCount,
'BucketsN': BucketsN, 'BucketsO': BucketsO,
'Ambiguous': sorted(AmbiguousBases), 'Classes': ClassLine,
'OutFile': OutFile,
}
def PrintSummary(Stats, Verbose):
print('files=%d parsed=%d parsefail=%d uses=%d NOWHERE=%d OTHER=%d RESOLVED=%d UNRESOLVED=%d -> %s' %
(Stats['Files'], Stats['Parsed'], len(Stats['ParseFail']), Stats['Uses'],
len(Stats['Nowhere']), len(Stats['Other']),
Stats['Resolved'], Stats['Unresolved'], Stats['OutFile']))
print('NOWHERE buckets: %s' % sorted(Stats['BucketsN'].items()))
print('OTHER buckets: %s' % sorted(Stats['BucketsO'].items()))
if Stats['Ambiguous']:
print('AMBIGUOUS BASES: %s' % Stats['Ambiguous'])
if Stats['ParseFail']:
print('PARSE FAIL: %s' % Stats['ParseFail'])
Limit = 25 if not Verbose else 100000
for R in Stats['Nowhere'][:Limit]:
print('N %-40s:%-5d %s.%s.%s [decl@%d]' % (R[0], R[1], R[7], R[3], R[4], R[5]))
for R in Stats['Other'][:Limit]:
print('O %-40s:%-5d %s.%s.%s [decl@%d]' % (R[0], R[1], R[7], R[3], R[4], R[5]))
# ============================================================
# 5. --selftest: 内置合成用例(临时目录生成, 跑完即删)
# 断言"报出集合 == 预期集合";正例 6 条(含 vast 专有写法)、负例 4 个文件。
# ============================================================
SelftestCases = [
('CaseP1AstNameOther.vp', """import ast
def ProbeP1(x_r: rawptr[ast.AST]) -> int:
return x_r.name
"""),
('CaseP2LlvFunctionTyOther.vp', """import llvmlite
def ProbeP2(x_r: rawptr[llvmlite.Function]) -> int:
return x_r.Ty
"""),
('CaseP3AstNowhere.vp', """import ast
def ProbeP3(x_r: rawptr[ast.AST]) -> int:
return x_r.zzz_not_a_field
"""),
('CaseP4AstStarOther.vp', """import ast
def ProbeP4(x_r: rawptr[ast.AST]*) -> int:
return x_r.name
"""),
('CaseP5AstStarStarOther.vp', """import ast
def ProbeP5(x_r: rawptr[ast.AST]**) -> int:
return x_r.name
"""),
('CaseP6ReturnInferOther.vp', """import llvmlite
def MkP6(x_r: rawptr[llvmlite.Function]) -> rawptr[llvmlite.Function]:
return x_r
def ProbeP6(x_r: rawptr[llvmlite.Function]) -> int:
y_r = MkP6(x_r)
return y_r.Ty
"""),
('CaseN1ParamNodeTy.vp', """import llvmlite
def ProbeN1(x_r: rawptr[llvmlite.ParamNode]) -> int:
return x_r.Ty
"""),
('CaseN2SameNameScopes.vp', """import llvmlite
def ProbeN2A(x_r: rawptr[llvmlite.ParamNode]) -> int:
return x_r.Ty
def ProbeN2B(x_r: rawptr[llvmlite.Function]) -> int:
return x_r.Ty
"""),
('CaseN3DocComment.vp', """import ast
def ProbeN3(x_r: rawptr[ast.AST]) -> int:
\"\"\"docstring: x_r.name / x_r.zzz_not_a_field 只是说明文字, 不是代码。\"\"\"
# 注释: x_r.name / x_r.zzz_not_a_field
return 0
"""),
('CaseN4ReturnInferOk.vp', """import llvmlite
def MkN4(x_r: rawptr[llvmlite.ParamNode]) -> rawptr[llvmlite.ParamNode]:
return x_r
def ProbeN4(x_r: rawptr[llvmlite.ParamNode]) -> int:
y_r = MkN4(x_r)
return y_r.Ty
"""),
]
# (文件, 变量, 族.类, 字段) -> 判定
SelftestExpected = {
('CaseP1AstNameOther.vp', 'x_r', 'ast.AST', 'name'): 'OTHER',
('CaseP2LlvFunctionTyOther.vp', 'x_r', 'llvmlite.Function', 'Ty'): 'OTHER',
('CaseP3AstNowhere.vp', 'x_r', 'ast.AST', 'zzz_not_a_field'): 'NOWHERE',
('CaseP4AstStarOther.vp', 'x_r', 'ast.AST', 'name'): 'OTHER',
('CaseP5AstStarStarOther.vp', 'x_r', 'ast.AST', 'name'): 'OTHER',
('CaseP6ReturnInferOther.vp', 'y_r', 'llvmlite.Function', 'Ty'): 'OTHER',
('CaseN2SameNameScopes.vp', 'x_r', 'llvmlite.Function', 'Ty'): 'OTHER',
}
def SelfTest(IncludesDir, OutFile):
AppRoot = tempfile.mkdtemp(prefix='ScanAstFieldMissSelftest.')
try:
for Name, Src in SelftestCases:
with open(os.path.join(AppRoot, Name), 'w', encoding='utf-8', newline='\n') as Fh:
Fh.write(Src)
Report = OutFile or os.path.join(AppRoot, 'Selftest.txt')
Stats = RunScan(AppRoot, IncludesDir, Report, False)
Reported = {}
for R in Stats['Nowhere']:
Reported[(R[0], R[2], '%s.%s' % (R[7], R[3]), R[4])] = 'NOWHERE'
for R in Stats['Other']:
Reported[(R[0], R[2], '%s.%s' % (R[7], R[3]), R[4])] = 'OTHER'
Ok = True
if Stats['ParseFail']:
Ok = False
print('[selftest] 解析失败(预期 parsefail=0):')
for P in Stats['ParseFail']:
print(' %s:%s %s' % P)
Missing = sorted(set(SelftestExpected) - set(Reported))
Extra = sorted(set(Reported) - set(SelftestExpected))
TagDiff = sorted(K for K in set(Reported) & set(SelftestExpected)
if Reported[K] != SelftestExpected[K])
for K in Missing:
Ok = False
print('[selftest] 漏报: %s -> 预期 %s' % (K, SelftestExpected[K]))
for K in Extra:
Ok = False
print('[selftest] 误报: %s -> %s' % (K, Reported[K]))
for K in TagDiff:
Ok = False
print('[selftest] 判定不符: %s -> %s(预期 %s)' % (K, Reported[K], SelftestExpected[K]))
print('[selftest] files=%d parsed=%d parsefail=%d NOWHERE=%d OTHER=%d 报出=%d 预期=%d' %
(Stats['Files'], Stats['Parsed'], len(Stats['ParseFail']),
len(Stats['Nowhere']), len(Stats['Other']), len(Reported), len(SelftestExpected)))
print('[selftest] %s' % ('PASS' if Ok else 'FAIL'))
return 0 if Ok else 1
finally:
shutil.rmtree(AppRoot, ignore_errors=True)
# ============================================================
# 6. CLI
# ============================================================
def ParseArgs(Argv):
Opt = {'AppRoot': DefaultAppRoot, 'Includes': DefaultIncludesDir, 'Out': None,
'Strict': False, 'Verbose': False, 'SelfTest': False}
Need = {'--app-root': 'AppRoot', '--includes': 'Includes', '--out': 'Out'}
Flags = {'--strict': 'Strict', '--verbose': 'Verbose', '--selftest': 'SelfTest'}
I = 0
while I < len(Argv):
A = Argv[I]
if A in Need:
if I + 1 >= len(Argv):
sys.stderr.write('参数缺值: %s\n' % A)
return None
Opt[Need[A]] = Argv[I + 1]
I += 2
continue
if A in Flags:
Opt[Flags[A]] = True
I += 1
continue
sys.stderr.write('未知参数: %s(用法见本脚本 docstring)\n' % A)
return None
return Opt
def main():
Opt = ParseArgs(sys.argv[1:])
if Opt is None:
return 2
if Opt['SelfTest']:
return SelfTest(Opt['Includes'], Opt['Out'])
Stats = RunScan(Opt['AppRoot'], Opt['Includes'], Opt['Out'] or DefaultOutFile, Opt['Verbose'])
PrintSummary(Stats, Opt['Verbose'])
if Opt['Strict'] and (Stats['Nowhere'] or Stats['Other'] or Stats['ParseFail']):
sys.stderr.write('[strict] 门禁失败: NOWHERE=%d OTHER=%d parsefail=%d\n' %
(len(Stats['Nowhere']), len(Stats['Other']), len(Stats['ParseFail'])))
return 1
return 0
if __name__ == '__main__':
sys.exit(main())
+8 -8
View File
@@ -149,7 +149,7 @@ def main() -> None:
parser.add_argument('--clear-cache', action='store_true', help='清除 .transpyc_cache 全局缓存(.vpi/.stub.ll/.doc.json)')
parser.add_argument('--asan', action='store_true', help='ASan 模式:对全部 .ll 做 opt -passes=asan 插桩,并显式链接 clang64 的 ASan 运行库(mingw64 的 clang 没有 x86_64-w64-windows-gnu 目标的 ASan runtime,不能用 -fsanitize=address)')
parser.add_argument('--dwarf', action='store_true', help='函数级 DWARF 调试元数据:每模块一份 DICompileUnit/DIFile,每个 define 一份 DISubprogram 并挂 !dbg,函数入口第一条指令挂 DILocation。使 ASan/llvm-symbolizer 能把崩溃地址反查为 "<文件>.vp:函数起始行"(默认关闭;--asan 时自动开启)。开启时强制重译 App 模块(不复用不含 !llvm.dbg.cu 的旧 .ll/.obj),.ll/.obj 体积与编译时间略增')
parser.add_argument('--ctrlc-stack', action='store_true', help='Ctrl+C 调用栈打印(仅 Windows/PE):链接完成后用 llvm-dwarfdump 把最终 exe 的 .debug_line/.debug_info 导出为 "<exe>.stackmap" 地址→源行旁文件(小端定长格式),供 exe 内运行时在 Ctrl+C/Ctrl+Break 时把 CRT 到当前的返回地址逐帧翻译为 "<文件>.vp:行/函数"。会自动开启 DWARF(同 --asan,否则行表为空);找不到 llvm-dwarfdump / 调用失败 / 解析不到任何行记录 / 目标不是 Windows PE 时直接报错终止编译')
parser.add_argument('--traceback', action='store_true', help='Ctrl+C 调用栈打印(仅 Windows/PE):链接完成后用 llvm-dwarfdump 把最终 exe 的 .debug_line/.debug_info 导出为 "<exe>.stackmap" 地址→源行旁文件(小端定长格式),供 exe 内运行时在 Ctrl+C/Ctrl+Break 时把 CRT 到当前的返回地址逐帧翻译为 "<文件>.vp:行/函数"。会自动开启 DWARF(同 --asan,否则行表为空);找不到 llvm-dwarfdump / 调用失败 / 解析不到任何行记录 / 目标不是 Windows PE 时直接报错终止编译')
parser.add_argument('--nowarn-register', action='store_true', help='抑制"外部函数返回类型未登记"类 WARN 输出(透传给自举编译的 TransPyV.exe)')
parser.add_argument('--hotspot-sites', nargs='?', const='', default=None, help='调用点热点统计:为每个匹配白名单的直接 call 点插入计数器,运行时 Ctrl+C 各模块按真实次数降序写入 <path>(默认为 hotspot_sites.txt);同时为每个函数定义加计时器,按自身耗时(self)降序写入同目录 hotspot_time.txt,行为 `<函数名> <self> <inc> <进入次数>`(单位 tick,x86_64 上为序列化 lfence; rdtsc);self 已扣除被插桩子调用的 inc,但不含未插桩被调方(间接调用、strcmp/memcpy 等无定义外部函数),它们计入调用者的 self')
parser.add_argument('--hotspot-callees', default=None, help='--hotspot-sites 的白名单被调函数基名(逗号分隔),默认内存/字符串族 strcmp,strncmp,strchr,strlen,strstr,memcpy,memset,memmove,strcpy,strcat')
@@ -163,20 +163,20 @@ def main() -> None:
if args.asan:
_vlog().info('ASan 模式已启用(opt -passes=asan + clang64 libclang_rt.asan_dynamic*)', 'asan')
# Ctrl+C 调用栈(--ctrlc-stack):链接后用 llvm-dwarfdump 导出 <exe>.stackmap。
# Ctrl+C 调用栈(--traceback):链接后用 llvm-dwarfdump 导出 <exe>.stackmap。
# 不在此处做工具/平台校验——真正的 fail-fast 落在链接成功后的旁文件导出步骤
# (那里才有最终 exe 路径可判 PE)。此处仅提示已启用。
if args.ctrlc_stack:
_vlog().info('Ctrl+C 调用栈模式已启用(链接后用 llvm-dwarfdump 导出 <exe>.stackmap)', 'ctrlc-stack')
if args.traceback:
_vlog().info('Ctrl+C 调用栈模式已启用(链接后用 llvm-dwarfdump 导出 <exe>.stackmap)', 'traceback')
# 函数级 DWARF 调试元数据(--dwarf):默认关闭(硬要求——普通构建不承担
# 任何风险,生成的 .ll 与改动前逐字节一致)。--asan 时自动开启:ASan 报告
# 本身就需要行号才能定位,且 ASan 构建已是显式选择的非默认路径。
# --ctrlc-stack 同理自动开启:旁文件的"地址→源行"完全来自 DWARF 行表,
# --traceback 同理自动开启:旁文件的"地址→源行"完全来自 DWARF 行表,
# 没有 DI 元数据时 .debug_line 为空、旁文件必为空。
dwarf_enabled: bool = bool(args.dwarf or args.asan or args.ctrlc_stack)
dwarf_enabled: bool = bool(args.dwarf or args.asan or args.traceback)
if dwarf_enabled:
_auto_src: str = '--asan' if args.asan else ('--ctrlc-stack' if args.ctrlc_stack else '')
_auto_src: str = '--asan' if args.asan else ('--traceback' if args.traceback else '')
_vlog().info(
'函数级 DWARF 调试元数据已启用:每个 .vp 模块一份 DICompileUnit + 每函数 DISubprogram/DILocation'
+ (f'(由 {_auto_src} 自动开启)' if _auto_src and not args.dwarf else ''),
@@ -349,7 +349,7 @@ def main() -> None:
except:
pass
_vlog().info(f"includes.binary: 已清空 {precompiled_dir} ({cleared} 个文件)")
trans: Phase2Translator = Phase2Translator(src, temp_dir, output_dir, compile_cmd=cc_cmd, compile_flags=cc_flags, linker_cmd=linker_cmd, linker_flags=linker_flags, linker_output=linker_output, static_libs=static_libs, dynamic_libs=dynamic_libs, include_dirs=project_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout, force_recompile_includes=force_recompile_includes, asan_mode=args.asan, dwarf_enabled=dwarf_enabled, ctrlc_stack_enabled=args.ctrlc_stack)
trans: Phase2Translator = Phase2Translator(src, temp_dir, output_dir, compile_cmd=cc_cmd, compile_flags=cc_flags, linker_cmd=linker_cmd, linker_flags=linker_flags, linker_output=linker_output, static_libs=static_libs, dynamic_libs=dynamic_libs, include_dirs=project_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout, force_recompile_includes=force_recompile_includes, asan_mode=args.asan, dwarf_enabled=dwarf_enabled, traceback_enabled=args.traceback)
trans.sha1_map = Load_sha1_map(temp_dir)
trans.slice_level = slice_level
# 调用点热点统计与每函数计时(--hotspot-sites):None=未启用(零影响)
+47 -11
View File
@@ -10,7 +10,8 @@ from lib.includes import t
from lib.includes.t import CTypeRegistry
from lib.core.SymbolUtils import (
IsListAnnotation, AnnotationContainsName, IsCPtrSubscriptNode, IsCPtrNode,
IsRawPtrSubscriptNode, IsPtrSubscriptNode, ResolveGenericTypeArgStr)
IsRawPtrSubscriptNode, IsPtrSubscriptNode, ResolveGenericTypeArgStr,
EnumClassIntWidth, ClassHasFuncLevelCVTable)
class DeclarationGenerator:
@@ -55,7 +56,9 @@ class DeclarationGenerator:
t.configure_platform(self.target_triple)
def generate(self, pyi_content: str, src_path: str) -> str:
tree: ast.Module = ast.parse(pyi_content)
# 传 src_path:解析失败时报错/回退诊断才带得上来源(否则 vast 记为 <unknown>,
# 既无法定位文件,也无法知道是哪份 .pyi 出问题)
tree: ast.Module = ast.parse(pyi_content, src_path)
self._DefineConstants: dict[str, int | str] = {}
self._pyi_tree = tree
@@ -255,9 +258,17 @@ class DeclarationGenerator:
if isinstance(elt, ast.Subscript) and isinstance(elt.value, ast.Attribute) and isinstance(elt.value.value, ast.Name) and elt.value.value.id == 't' and elt.value.attr == 'Callable':
return None
VarType: str = self._get_type_str(node.annotation)
if VarType.startswith('[0 x ') and node.value and isinstance(node.value, ast.List):
if VarType.startswith('[0 x ') and node.value:
elem_type: str = VarType[5:-1]
actual_count: int = len(node.value.elts)
actual_count: int = 0
if isinstance(node.value, ast.List):
actual_count = len(node.value.elts)
elif isinstance(node.value, ast.Constant) and isinstance(node.value.value, str):
# 字符串字面量:与 HandlesBase.ParseArrayCount(:731-732) / HandlesAssign
# 全局路径同口径(len(str)+1,含结尾 NUL)。此前只修 List 值 → CArray
# 字符串全局的 stub 声明停留在 [0 x i8],与定义端 [N x i8] 长度不一致
# (跨模块 type mismatch / llc 拒绝)。
actual_count = len(node.value.value) + 1
if actual_count > 0:
VarType = f'[{actual_count} x {elem_type}]'
return f'@{var_name} = external global {VarType}'
@@ -350,6 +361,10 @@ class DeclarationGenerator:
if is_enum:
enum_values: dict[str, int] = {}
next_value: int = 0
# 枚举存储宽度 = 最宽成员类型(与定义端 HandlesClassDef / 跨模块读取端
# 同一规则源 EnumClassIntWidth;此处无 SymbolTable,走 t 类名 / builtins /
# 库 typedef 的表)。声明端的全局类型必须与定义端运行时常量同宽(ABI)。
enum_width: int = EnumClassIntWidth(node.body)
for item in node.body:
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
var_name: str = item.target.id
@@ -367,7 +382,7 @@ class DeclarationGenerator:
enum_values[var_name] = next_value
next_value += 1
for var_name, value in enum_values.items():
decls.append(f'@__config_{class_name}_{var_name} = external global i32')
decls.append(f'@__config_{class_name}_{var_name} = external global i{enum_width}')
return decls
if is_renum:
# REnum 布局: { i32 __tag, <max_variant_payload> }
@@ -473,6 +488,14 @@ class DeclarationGenerator:
# (@t.NoVTable 标记的类及其子类不使用 vtable,字段展平嵌入)
if has_parent_class and not is_cvtable and not is_novtable and not parent_is_novtable:
is_cvtable = True
# 方法级 virtual(`virtual def m(...)` ⇒ 方法装饰器 @t.CVTable)触发类级 vtable:
# 与 HandlesClassDef._resolve_class_flags / TPV _detect_vtable_status 的
# `_has_any_func_cvtable` 分支逐条对齐(类级 NoVTable / 无真实父类)。
# 必须在此同步:定义模块按 IsCVTable 在 struct 首部放 vptr,若 stub 不同步,
# 跨模块使用该类的 struct 布局会比定义模块少一个字段 ⇒ 字段整体错位。
if not is_cvtable and ClassHasFuncLevelCVTable(node):
if is_novtable or not has_parent_class:
is_cvtable = True
base_has_vtable: bool = False
for base in node.bases:
base_name: str | None = None
@@ -847,6 +870,7 @@ class DeclarationGenerator:
if has_methods and not is_cpython_object:
is_cpython_object = True
base_has_vtable: bool = False
has_real_parent: bool = False
for base in node.bases:
base_name: str | None = None
if isinstance(base, ast.Attribute):
@@ -859,6 +883,7 @@ class DeclarationGenerator:
elif isinstance(base.value, ast.Name):
base_name = base.value.id
if base_name and not self._is_marker_base(base_name):
has_real_parent = True
_base_node: ast.ClassDef | None = None
for n in ast.iter_child_nodes(self._pyi_tree):
if isinstance(n, ast.ClassDef) and n.name == base_name:
@@ -874,6 +899,11 @@ class DeclarationGenerator:
base_has_vtable = True
if base_has_vtable:
break
# 方法级 virtual 触发类级 vtable(与上方 _generate_class_decl 同一判据,
# 保持在同一个类里两处口径一致;详见该处注释)
if not is_cvtable and not base_has_vtable and ClassHasFuncLevelCVTable(node):
if is_novtable or not has_real_parent:
is_cvtable = True
return has_methods and is_cvtable and not base_has_vtable and not is_novtable
def _FlattenInheritedDecl(self, cls_name: str, seen_cls: set, member_types: list[str],
@@ -1156,7 +1186,7 @@ class DeclarationGenerator:
return _dg_llvm
_dg_node: ast.AST | None = None
try:
_dg_node = ast.parse(_dg_bv, mode='eval').body
_dg_node = ast.parse(_dg_bv, '<DeclarationGenerator:type_expr>', mode='eval').body
except Exception:
_dg_node = None
if _dg_node is not None:
@@ -1291,11 +1321,13 @@ class DeclarationGenerator:
return f'[{self._get_const_int(annotation.slice)} x i8]'
if isinstance(annotation.slice, ast.Constant) and isinstance(annotation.slice.value, int):
return f'[{annotation.slice.value} x {base}]'
# 处理 t.CArray[elem_type, count] / rawarray[elem_type, count] 注解 → [count x elem_type]
# 支持 t.CArray (ast.Attribute)、CArray/rawarray (ast.Name) 三种写法
# 处理 t.CArray[elem_type, count] / rawarray[elem_type, count] / array[elem_type, count]
# 注解 → [count x elem_type]
# 支持 t.CArray (ast.Attribute)、CArray/rawarray/array (ast.Name) 三种写法
# (array 为本轮新增的内建软关键字,与 t.CArray 逐点等价)
ValueIsCArray: bool = (
(isinstance(annotation.value, ast.Attribute) and annotation.value.attr == 'CArray')
or (isinstance(annotation.value, ast.Name) and annotation.value.id in ('CArray', 'rawarray'))
or (isinstance(annotation.value, ast.Name) and annotation.value.id in ('CArray', 'rawarray', 'array'))
)
if ValueIsCArray:
SliceNode: ast.AST = annotation.slice
@@ -1307,9 +1339,13 @@ class DeclarationGenerator:
# count 为 None 或 0 → 零长度数组(外部符号)
return f'[0 x {ElemType}]'
elif isinstance(SliceNode, (ast.Attribute, ast.Name, ast.Subscript)):
# 单参数 t.CArray[elem_type] → 指针模式
# 单参数 t.CArray[elem_type]:与 t.CArray[elem_type, None] 语义统一为
# "长度自动推导数组"。此处无初始化字面量可推导 → 与 ,None 分支同用
# [0 x elem] 占位,真实长度由 _generate_global_decl 按初始化字面量
# (List 长度 / str 长度+1) 修正。
# 参数位置经 _decay_array_to_ptr(:23-28) 退化为 elem*,与旧指针行为一致。
ElemType: str = self._get_type_str(SliceNode, embedded=True)
return f'{ElemType}*'
return f'[0 x {ElemType}]'
if isinstance(annotation.value, ast.Name) and annotation.value.id == 'tuple':
slice_node: ast.AST = annotation.slice
elem_types: list[str]
+14 -1
View File
@@ -9,6 +9,7 @@ import traceback
from lib.Projectrans.Utils import compute_sha1, get_file_dependencies, find_reachable_files_from_entries, parse_python_file
from lib.Projectrans.DeclarationGenerator import DeclarationGenerator
from lib.core.VLogger import get_logger as _vlog
from lib.core.SymbolUtils import ExpandSoftKeywords
from lib.constants.config import mode as _ConfigMode
from lib.includes import t
from StubGen import PythonToStubConverter
@@ -383,6 +384,11 @@ class Phase1Generator:
用于 .stub.ll 缓存依赖校验:当依赖的 include SHA1 变化时,
当前 include 的 .stub.ll 缓存应失效(因为类型引用的 SHA1 前缀会变化)。
签名同时纳入 includes/builtins.vp:它是注解类型解析的隐式输入
(不在任何模块的 import 闭包,但其 CTypedef 参与签名/字段类型解析),
其内容变化必须让所有 .stub.ll 失效——否则旧声明(如 sizet 曾被解析成
%"{mod}.sizet"* 假结构体)会被缓存继续引用,源码修复不生效。
"""
if not self._include_dep_graph or not self._include_file_map:
return ''
@@ -400,6 +406,11 @@ class Phase1Generator:
if DepSha1:
Result.add(DepSha1)
Queue.extend(self._include_dep_graph.get(DepMod, set()))
BuiltinsInfo: tuple[str, str, str] | None = self._include_file_map.get('builtins')
if BuiltinsInfo is not None:
BuiltinsSha1: str = self._get_sha1(BuiltinsInfo[0])
if BuiltinsSha1:
Result.add(BuiltinsSha1)
return '|'.join(sorted(Result))
def _ReadStubDep(self, stub_path: str) -> str:
@@ -510,7 +521,9 @@ class Phase1Generator:
"""
result: dict[str, str] = {}
try:
tree = ast.parse(content)
# `.vp` 源码须先过 Viper 软关键字层(`enum X:` / `fn` 等)再解析,
# 否则 SyntaxError 被吞成"无 docstring",与主翻译路径口径不一致。
tree = ast.parse(ExpandSoftKeywords(content), '<Phase1Generator:_extract_docstrings>')
except SyntaxError:
return result
for node in tree.body:
+94 -94
View File
@@ -31,11 +31,11 @@ from lib.core.LLVMCG.IRMetadataFix import SanitizeIRMetadata
_FILE_STAT_CACHE: dict[str, bool] = {}
# --ctrlc-stack(Ctrl+C 调用栈)强制纳入编译+链接的 include 模块源文件名。
# --traceback(Ctrl+C 调用栈)强制纳入编译+链接的 include 模块源文件名。
# 该模块对外只有 Install() 入口,且无任何用户代码 import 它 —— 若不强制纳入,
# 产物里既没有 Install 符号、也没有调用它的启动期构造器,功能静默失效。
# .vp 优先,.py 仅为向后兼容的回退名。
_CTRLC_STACK_MODULE_FILES: tuple[str, ...] = ('dbgstack.vp', 'dbgstack.py')
_TRACEBACK_MODULE_FILES: tuple[str, ...] = ('traceback.vp', 'traceback.py')
def _CachedIsFile(path: str) -> bool:
@@ -80,9 +80,9 @@ def _FindAsanRuntimeDir() -> str | None:
return found[-1]
# ---------- --ctrlc-stack:地址→源行 旁文件(<exe>.stackmap)----------
# ---------- --traceback:地址→源行 旁文件(<exe>.stackmap)----------
#
# 开启 --ctrlc-stack 时,链接成功后用 llvm-dwarfdump 解析最终 PE 的
# 开启 --traceback 时,链接成功后用 llvm-dwarfdump 解析最终 PE 的
# .debug_line/.debug_info,把已有的"函数级/语句级 DWARF 行标记"导出成
# <exe>.stackmap,供 exe 内运行时处理器在 Ctrl+C/Ctrl+Break 时把 CRT 到当前
# 的返回地址逐帧翻译回 ".vp:行/函数"。以下函数只在开关开启时被调用;未开启时
@@ -95,7 +95,7 @@ def _ReadPEImageLayout(exe_path: str) -> tuple[int, int, dict[str, tuple[int, in
"""读取 PE 的 (ImageBase, SizeOfImage, 节表) ;非 PE / 解析失败返回 None。
节表为 {节名: (节 RVA, 节 VirtualSize)}。写端需要这三样才能把 DWARF 地址严格
归一到"运行期 RVA"(运行期 dbgstack.vp 用 `帧地址 − GetModuleHandleA(NULL)`):
归一到"运行期 RVA"(运行期 traceback.vp 用 `帧地址 − GetModuleHandleA(NULL)`):
* 合法行 ⇔ 地址落在映像 VA 区间 [ImageBase, ImageBase + SizeOfImage);
* "运行期可能出现的 RVA 区间"的粗判据 = [.text 节 RVA, SizeOfImage)。
(原 _ReadPEImageBase 只返回 ImageBase,不足以做上述判定,故扩展为本函数。)
@@ -413,7 +413,7 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp
hotspot_sites_file: str | None = None,
hotspot_sites_all: bool = False,
hotspot_callees: list[str] | None = None,
ctrlc_stack_enabled: bool = False,
traceback_enabled: bool = False,
dwarf_enabled: bool = False) -> tuple[str, str, str]:
# 注意:不可跨任务复用 Phase2Translator/共享符号表!_translate_file 编译过程中
# 会向共享 SymbolTable 写入符号(import insert/类注册等),复用导致前文件的
@@ -437,10 +437,10 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp
trans.hotspot_sites_file = hotspot_sites_file
trans.hotspot_sites_all = hotspot_sites_all
trans.hotspot_callees = hotspot_callees
# Ctrl+C 调用栈(--ctrlc-stack):worker 子进程无父进程属性,须显式透传
# (False=未启用,零影响);dbgstack 模块据此注入 Install() 启动期构造器
trans.ctrlc_stack_enabled = ctrlc_stack_enabled
# 函数级 DWARF(--ctrlc-stack 时 include 模块也必须带行表,否则栈里落到
# Ctrl+C 调用栈(--traceback):worker 子进程无父进程属性,须显式透传
# (False=未启用,零影响);traceback 模块据此注入 Install() 启动期构造器
trans.traceback_enabled = traceback_enabled
# 函数级 DWARF(--traceback 时 include 模块也必须带行表,否则栈里落到
# includes/* 的帧取不到源行):worker 子进程无父进程属性,须显式透传
# (False=未启用,零影响),仅用于 _translate_file 的 dwarf_enabled 形参
trans.dwarf_enabled = dwarf_enabled
@@ -736,7 +736,7 @@ def _BuildPyiCacheFile(temp_dir: str) -> str | None:
continue
try:
with open(p, 'r', encoding='utf-8') as f:
cache[p] = ast.parse(f.read())
cache[p] = ast.parse(f.read(), p)
except Exception:
cache[p] = None
try:
@@ -776,7 +776,7 @@ def _PyiCacheWorkerInitializer(temp_dir: str) -> None:
for p in _ScanPyiFiles(temp_dir):
try:
with open(p, 'r', encoding='utf-8') as f:
new_cache[p] = ast.parse(f.read())
new_cache[p] = ast.parse(f.read(), p)
except Exception:
new_cache[p] = None
if new_cache:
@@ -914,7 +914,7 @@ class Phase2Translator:
_include_sha1_cache: dict[str, str] = {}
_include_exist_cache: dict[str, bool] = {}
def __init__(self, src_root: str, temp_dir: str, output_dir: str, compile_cmd: str = 'llc', compile_flags: list[str] | None = None, linker_cmd: str | None = None, linker_flags: list[str] | None = None, linker_output: str | None = None, static_libs: list[str] | None = None, dynamic_libs: list[str] | None = None, include_dirs: list[str] | None = None, entry_files: list[str] | None = None, target_triple: str | None = None, target_datalayout: str | None = None, force_recompile_includes: bool = False, build_pyi_cache: bool = True, asan_mode: bool = False, dwarf_enabled: bool = False, ctrlc_stack_enabled: bool = False) -> None:
def __init__(self, src_root: str, temp_dir: str, output_dir: str, compile_cmd: str = 'llc', compile_flags: list[str] | None = None, linker_cmd: str | None = None, linker_flags: list[str] | None = None, linker_output: str | None = None, static_libs: list[str] | None = None, dynamic_libs: list[str] | None = None, include_dirs: list[str] | None = None, entry_files: list[str] | None = None, target_triple: str | None = None, target_datalayout: str | None = None, force_recompile_includes: bool = False, build_pyi_cache: bool = True, asan_mode: bool = False, dwarf_enabled: bool = False, traceback_enabled: bool = False) -> None:
self.src_root = os.path.abspath(src_root)
self.temp_dir = os.path.abspath(temp_dir)
self.output_dir = os.path.abspath(output_dir)
@@ -950,12 +950,12 @@ class Phase2Translator:
# !llvm.dbg.cu + 每函数 DISubprogram + 入口 DILocation,供 ASan /
# llvm-symbolizer 反查 ".vp:函数起始行"。
self.dwarf_enabled: bool = dwarf_enabled
# Ctrl+C 调用栈(--ctrlc-stack):False=未启用(零影响,不调用任何外部
# Ctrl+C 调用栈(--traceback):False=未启用(零影响,不调用任何外部
# 工具、不写任何旁文件);True=链接成功后额外用 llvm-dwarfdump 把最终
# exe 的 DWARF 行表导出为 "<exe>.stackmap"(地址→源行旁文件,供 exe 内
# 运行时在 Ctrl+C/Ctrl+Break 时逐帧翻译返回地址)。要求 --dwarf 已开启
# (Config 侧联动,否则行表为空),且目标为 Windows PE。
self.ctrlc_stack_enabled: bool = ctrlc_stack_enabled
self.traceback_enabled: bool = traceback_enabled
# 调用点热点统计(--hotspot-sites):None=未启用(零影响);str=调用点计数
# 落盘文件路径(每函数计时落同目录 hotspot_time.txt)。各模块运行时自带
# Ctrl+C 处理器、统一以追加方式写该路径;含 main 的模块在 main 入口清空一次
@@ -1173,9 +1173,9 @@ class Phase2Translator:
# 行为与改动前完全一致(零影响)。
# --dwarf(函数级 DWARF)同理:缓存 .ll/.obj 不含 !llvm.dbg.cu,必须一并
# 强制重译,否则开启开关后仍复用旧产物、看不到任何 .vp 行号。
# --ctrlc-stack 依赖同一份 .debug_line 行表(Config 已联动开启 --dwarf),
# --traceback 依赖同一份 .debug_line 行表(Config 已联动开启 --dwarf),
# 这里显式纳入,避免将来联动改动后静默复用无 DI 的旧产物。
hotspot_on: bool = bool(self.hotspot_sites_file) or bool(self.dwarf_enabled) or bool(self.ctrlc_stack_enabled)
hotspot_on: bool = bool(self.hotspot_sites_file) or bool(self.dwarf_enabled) or bool(self.traceback_enabled)
for i, src_path in enumerate(py_files, 1):
rel = os.path.relpath(src_path, self.src_root)
# 复用 parse_python_file 的 AST 缓存(get_file_dependencies 已 parse 过)
@@ -1786,7 +1786,7 @@ class Phase2Translator:
# `.vp` 源码须先过 Viper 软关键字层(`enum X:` / `fn` 等)再解析:
# 否则此处 SyntaxError 会静默 return 原 code(自动注入 import _list/_dict
# 失效),与主翻译路径(Shell/TransPyC 已展开)口径不一致。
tree = ast.parse(ExpandSoftKeywords(code))
tree = ast.parse(ExpandSoftKeywords(code), src_path)
except SyntaxError:
return code
@@ -1851,10 +1851,10 @@ class Phase2Translator:
"""翻译单个源文件,嵌入相关 .stub.ll 声明
dwarf_enabled: 函数级 DWARF 调试元数据(--dwarf),默认 False(零影响)。
App 源模块在 --dwarf/--asan/--ctrlc-stack 时传 True。include 文件默认
App 源模块在 --dwarf/--asan/--traceback 时传 True。include 文件默认
传 False(其 .obj 会落进跨项目共享的 includes.binary 预编译缓存,注入 DI
会污染该缓存);仅 --ctrlc-stack 时传 True——此时 include 的 .obj 改落
隔离目录(includes.binary_ctrlcstack,见 _get_precompiled_obj_path),
会污染该缓存);仅 --traceback 时传 True——此时 include 的 .obj 改落
隔离目录(includes.binary_traceback,见 _get_precompiled_obj_path),
不会污染共享缓存。
"""
_t0: float = time.time()
@@ -1891,15 +1891,15 @@ class Phase2Translator:
# 未启用时不设置 → 与改动前逐字节一致(零影响)
if dwarf_enabled:
trans.translator.DwarfEnabled = True
# Ctrl+C 调用栈(--ctrlc-stack):仅当本文件就是 dbgstack 模块时置位——
# Ctrl+C 调用栈(--traceback):仅当本文件就是 traceback 模块时置位——
# 只有该模块需要注入 @llvm.global_ctors 构造器调用自己的 Install()
# (见 BaseGen._RegisterDbgStackInstallViaCtor)。其它模块不设置该属性
# → Gen.ctrlc_stack_ctor 保持 False,IR 与改动前逐字节一致(零影响)。
_ctrlc_stack_ctor: bool = bool(
self.ctrlc_stack_enabled
and os.path.basename(src_path) in _CTRLC_STACK_MODULE_FILES)
if _ctrlc_stack_ctor:
trans.translator.CtrlcStackCtor = True
# (见 BaseGen._RegisterTracebackInstallViaCtor)。其它模块不设置该属性
# → Gen.traceback_ctor 保持 False,IR 与改动前逐字节一致(零影响)。
_traceback_ctor: bool = bool(
self.traceback_enabled
and os.path.basename(src_path) in _TRACEBACK_MODULE_FILES)
if _traceback_ctor:
trans.translator.TracebackCtor = True
trans.SliceLevel = self.slice_level
trans.translator.SliceLevel = self.slice_level
trans.translator.SliceCount = 0
@@ -1953,7 +1953,7 @@ class Phase2Translator:
try:
with open(sig_path, 'r', encoding='utf-8') as f:
sig_content = f.read()
sig_tree = ast.parse(sig_content)
sig_tree = ast.parse(sig_content, sig_path)
for node in ast.iter_child_nodes(sig_tree):
if isinstance(node, ast.FunctionDef):
if _check_annotation_for_export(node.returns):
@@ -2097,13 +2097,13 @@ class Phase2Translator:
out_dir = os.path.dirname(out_path)
if out_dir:
os.makedirs(out_dir, exist_ok=True)
# --ctrlc-stack:核对注入钩子确实生效——本模块要求注入 @llvm.global_ctors
# --traceback:核对注入钩子确实生效——本模块要求注入 @llvm.global_ctors
# 构造器(StructGen.finalize 里落成 .ll 文本之前),若产物中没有该全局
# 说明钩子不可用(如序列化路径被改动绕过),显式报错终止,不静默产出
# 缺注册的产物(那会让整个功能静默失效)。
if _ctrlc_stack_ctor and 'llvm.global_ctors' not in result:
if _traceback_ctor and 'llvm.global_ctors' not in result:
raise RuntimeError(
'--ctrlc-stack: dbgstack 模块未注入 @llvm.global_ctors '
'--traceback: traceback 模块未注入 @llvm.global_ctors '
'(模块级收尾钩子未生效,不静默降级)')
# 修复 LLVM 22+: external global 不能有初值,改为 linkonce
result = re.sub(r'^(@[\w.]+)\s*=\s*external\s+global\s+(\w[\w*]*)\s+(-?\d+|null|zeroinitializer|true|false)$', r'\1 = linkonce global \2 \3', result, flags=re.MULTILINE)
@@ -2218,8 +2218,8 @@ class Phase2Translator:
return opt_r
compile_input = asan_ll
cmd = [self.compile_cmd] + self.compile_flags + ['-o', obj_path, compile_input]
if self.ctrlc_stack_enabled:
# --ctrlc-stack:让 llc 保留帧指针(-frame-pointer=all)。dbghelp 主路径
if self.traceback_enabled:
# --traceback:让 llc 保留帧指针(-frame-pointer=all)。dbghelp 主路径
# 不依赖帧指针,但符号表不可用时要靠 RBP 链兜底——llc 默认
# omit-frame-pointer 会让 RBP 链只走出 1~2 帧(实测"共 2 帧")。
# 只在开启该开关时加,关闭时命令与改前逐字一致。
@@ -2786,14 +2786,14 @@ class Phase2Translator:
def _scan_include_libraries(self) -> None:
"""扫描 includes 目录,检测被使用的模块对应的库文件和源文件"""
# --ctrlc-stack:强制纳入 dbgstack 模块 —— 无任何用户代码 import 它,
# --traceback:强制纳入 traceback 模块 —— 无任何用户代码 import 它,
# 不纳入就不会被翻译 ⇒ 产物没有 Install 符号可注入启动期构造器。
# 直接借用既有的 used_includes 收集路径(下面按同一套 py_index /
# 传递依赖逻辑把它加入 extra_py_files,再交给 _compile_include_py_files
# 编译并把 .obj 追加进 extra_link_files)。开关关闭时不触碰 used_includes
# → 本函数行为与改动前逐字一致(零影响)。
if self.ctrlc_stack_enabled:
self.used_includes.add('dbgstack')
if self.traceback_enabled:
self.used_includes.add('traceback')
if not self.used_includes:
return
@@ -2845,16 +2845,16 @@ class Phase2Translator:
_vlog().info(f"\n[includes] 需要链接的库文件: {len(self.extra_link_files)} 个")
if self.extra_compile_files:
_vlog().info(f"[includes] 需要编译的源文件: {len(self.extra_compile_files)} 个")
# --ctrlc-stack:断言 dbgstack 源文件确实被纳入待编译集合。找不到就必然
# --traceback:断言 traceback 源文件确实被纳入待编译集合。找不到就必然
# 得不到 Install 符号、构造器无处注入(功能静默失效)——显式报错终止,
# 不静默跳过(多半是 project.json 的 includes 未包含仓库的 includes 目录)。
if self.ctrlc_stack_enabled:
_dbgstack_found: bool = any(
os.path.basename(_p) in _CTRLC_STACK_MODULE_FILES
if self.traceback_enabled:
_traceback_found: bool = any(
os.path.basename(_p) in _TRACEBACK_MODULE_FILES
for _p in self.extra_py_files)
if not _dbgstack_found:
if not _traceback_found:
raise RuntimeError(
'--ctrlc-stack: 在 includes 目录中找不到 dbgstack.vp/.py,'
'--traceback: 在 includes 目录中找不到 traceback.vp/.py,'
'无法编译并链接 Install()(请确认 project.json 的 includes '
f'包含仓库 includes 目录;已扫描: {self.include_dirs})')
@@ -3104,14 +3104,14 @@ class Phase2Translator:
预编译目录位于 include 目录同级,命名为 {basename}.binary/。
文件命名为 {sha1}.{triple_safe}.{rel_model}.obj,区分目标平台和重定位模型。
--ctrlc-stack:include 模块也带 DWARF 行表(栈帧要能落到 includes/* 的源行),
--traceback:include 模块也带 DWARF 行表(栈帧要能落到 includes/* 的源行),
此类 .obj 不得回写进跨项目共享的 includes.binary(会污染其它项目的预编译
缓存),改落**兄弟目录** {basename}.binary + 后缀(不新增层级,形态与
ASan 变体目录 includes.binary_asan 一致)。
关闭时后缀为空 → 路径与改动前逐字一致(含 --dwarf/--asan 单独开启的情形)。
--asan 与 --ctrlc-stack 同开:ASan 插桩也会改变 include obj 内容(见
--asan 与 --traceback 同开:ASan 插桩也会改变 include obj 内容(见
_llc_compile),两者产物不可互相复用,故再叠加 _asan 后缀区分
(优先级:是否隔离由 --ctrlc-stack 决定,--asan 只决定后缀再叠加 _asan)。
(优先级:是否隔离由 --traceback 决定,--asan 只决定后缀再叠加 _asan)。
"""
AbsSrcPath: str = os.path.abspath(src_path)
RelModel: str = 'static'
@@ -3128,8 +3128,8 @@ class Phase2Translator:
inc_parent: str = os.path.dirname(abs_inc)
inc_base: str = os.path.basename(abs_inc)
_iso_suffix: str = ''
if self.ctrlc_stack_enabled:
_iso_suffix = '_ctrlcstack_asan' if self.asan_mode else '_ctrlcstack'
if self.traceback_enabled:
_iso_suffix = '_traceback_asan' if self.asan_mode else '_traceback'
precompiled_dir: str = os.path.join(inc_parent, inc_base + '.binary' + _iso_suffix)
triple_safe: str = (self.triple or 'default').replace(os.sep, '_').replace('/', '_').replace('\\', '_').replace(':', '_')
return os.path.join(precompiled_dir, f"{sha1}.{triple_safe}.{RelModel}.obj")
@@ -3155,18 +3155,18 @@ class Phase2Translator:
# 本函数行为与改动前逐字节一致(零影响)。
hotspot_on: bool = bool(self.hotspot_sites_file)
# --ctrlc-stack:include 模块也必须带 DWARF 行表(否则运行时 Ctrl+C 的栈里
# --traceback:include 模块也必须带 DWARF 行表(否则运行时 Ctrl+C 的栈里
# 落在 includes/*(memhub/string/viperlib/w32…)的帧取不到源行,只能落到
# 邻行或 "+0x…(unknown)")。为此翻译时给 include 透传 dwarf_enabled=True
# (见下方 worker/串行四处的 dwarf_enabled=self.ctrlc_stack_enabled),并把
# 旧目标文件(output 目录 {sha1}.obj 及隔离缓存 includes.binary_ctrlcstack
# (见下方 worker/串行四处的 dwarf_enabled=self.traceback_enabled),并把
# 旧目标文件(output 目录 {sha1}.obj 及隔离缓存 includes.binary_traceback
# 内的旧 .obj)一并失效重译——复用上一次无 DI 的产物会让行表静默缺失。
# 与 hotspot_on 处理方式一致;同时覆盖 dbgstack.vp(其 .obj 还须含
# 与 hotspot_on 处理方式一致;同时覆盖 traceback.vp(其 .obj 还须含
# @llvm.global_ctors 启动期构造器)。取舍:开启本开关时 include 目标文件每
# 次构建都重译(放弃隔离缓存的复用,换取"必然带 DWARF"的正确性保证),
# 仅调试模式可接受。开关关闭时 include_dwarf=False,本函数行为与改动前
# 逐字节一致(零影响,含 --dwarf/--asan 单独开启的情形)。
include_dwarf: bool = bool(self.ctrlc_stack_enabled)
include_dwarf: bool = bool(self.traceback_enabled)
# 构建 include 文件的模块 SHA1 映射(预计算所有文件,构建完整映射)
include_ModuleSha1Map = self._build_current_ModuleSha1Map(None)
@@ -3206,7 +3206,7 @@ class Phase2Translator:
self._collect_inline_symbols(src_path)
continue
# --hotspot-sites / --ctrlc-stack:include 的 .py 未变时下面两处会因旧
# --hotspot-sites / --traceback:include 的 .py 未变时下面两处会因旧
# .obj 存在而直接跳过,复用上一次(未插桩 / 不含 DI)的目标文件。
# 启用时先让这两类旧 .obj 一并失效,使其必然走入 need_translate 重新
# 翻译并嵌入热点计数/计时 IR 或 DWARF 行表。
@@ -3351,8 +3351,8 @@ class Phase2Translator:
hotspot_sites_file=self.hotspot_sites_file,
hotspot_sites_all=self.hotspot_sites_all,
hotspot_callees=self.hotspot_callees,
ctrlc_stack_enabled=self.ctrlc_stack_enabled,
dwarf_enabled=self.ctrlc_stack_enabled
traceback_enabled=self.traceback_enabled,
dwarf_enabled=self.traceback_enabled
)
in_progress[future] = item
@@ -3372,8 +3372,8 @@ class Phase2Translator:
hotspot_sites_file=self.hotspot_sites_file,
hotspot_sites_all=self.hotspot_sites_all,
hotspot_callees=self.hotspot_callees,
ctrlc_stack_enabled=self.ctrlc_stack_enabled,
dwarf_enabled=self.ctrlc_stack_enabled
traceback_enabled=self.traceback_enabled,
dwarf_enabled=self.traceback_enabled
)
in_progress[future] = item
remaining.clear()
@@ -3417,12 +3417,12 @@ class Phase2Translator:
for item in need_translate:
src_path, sha1, module_name, ll_path, obj_path, precompiled_obj = item
_vlog().info(f" 翻译: {module_name}.py -> {sha1}.ll")
self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.ctrlc_stack_enabled)
self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.traceback_enabled)
else:
item = need_translate[0]
src_path, sha1, module_name, ll_path, obj_path, precompiled_obj = item
_vlog().info(f" 翻译: {module_name}.py -> {sha1}.ll")
self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.ctrlc_stack_enabled)
self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.traceback_enabled)
# ========== 阶段2:串行后处理(dso_local + stub_decls 注入)==========
compile_tasks = [] # [(dso_ll_path, obj_path, src_path, sha1, module_name, ll_path, precompiled_obj, ll_content)]
@@ -3435,16 +3435,16 @@ class Phase2Translator:
with open(ll_path, 'r', encoding='utf-8') as f:
ll_content = f.read()
# --ctrlc-stack 提醒:include 模块若发射了 define 却不带 DI(!llvm.dbg.cu),
# --traceback 提醒:include 模块若发射了 define 却不带 DI(!llvm.dbg.cu),
# 其 .debug_line 行为空 ⇒ 运行时 Ctrl+C 栈里该 include 帧取不到源行
# (会退化为 +0x… (unknown))。
# **只警告、不报错**:像 includes/stdarg.vp 这类模块的 define 是"合成"的
# (由 extern/sugar 派生、无 .vp 函数体),本就不会有源级 DI —— 按 fail-fast
# 报错会把这类模块全部误杀;而"行号缺失"只降低可读性,不是编译正确性问题。
if self.ctrlc_stack_enabled and re.search(r'^define ', ll_content, re.MULTILINE) \
if self.traceback_enabled and re.search(r'^define ', ll_content, re.MULTILINE) \
and '!llvm.dbg.cu' not in ll_content:
_vlog().warning(
f"--ctrlc-stack: include 模块 {module_name}.py 发射了 define 但 IR 未携带 "
f"--traceback: include 模块 {module_name}.py 发射了 define 但 IR 未携带 "
f"DWARF (!llvm.dbg.cu)——该模块内联帧在 Ctrl+C 栈里将只显示地址: {ll_path}")
# dso_local 正则标注(使用 ^ + MULTILINE 避免匹配字符串常量内容中的 define/declare)
@@ -3602,8 +3602,8 @@ class Phase2Translator:
result = compile_results.get(task)
if result and result.returncode == 0:
self.extra_link_files.append(obj_path)
# 复制到 includes.binary 预编译目录(--ctrlc-stack 时 precompiled_obj
# 指向隔离目录 includes.binary_ctrlcstack,不会污染共享缓存)
# 复制到 includes.binary 预编译目录(--traceback 时 precompiled_obj
# 指向隔离目录 includes.binary_traceback,不会污染共享缓存)
if precompiled_obj:
precompiled_dir = os.path.dirname(precompiled_obj)
try:
@@ -3611,12 +3611,12 @@ class Phase2Translator:
os.makedirs(precompiled_dir, exist_ok=True)
shutil.copy2(obj_path, precompiled_obj)
except Exception as _e:
# --ctrlc-stack fail-fast:隔离目录创建 / 写回失败必须显式终止,
# --traceback fail-fast:隔离目录创建 / 写回失败必须显式终止,
# 不静默降级(否则本次构建的 include obj 拿不到、或落到共享缓存
# 之外的位置,DWARF 行表与后续复用的前提都被破坏)。
if self.ctrlc_stack_enabled:
if self.traceback_enabled:
raise RuntimeError(
f"--ctrlc-stack: include 预编译缓存写入隔离目录失败: "
f"--traceback: include 预编译缓存写入隔离目录失败: "
f"{precompiled_obj}: {_e}") from _e
if _config_mode == "strict":
_vlog().warning(f"预编译缓存写入失败: {_e}")
@@ -4186,7 +4186,7 @@ class Phase2Translator:
break
def _WriteStackMap(self, exe_path: str) -> None:
"""--ctrlc-stack:链接成功后导出"地址→源行"旁文件 <exe>.stackmap。
"""--traceback:链接成功后导出"地址→源行"旁文件 <exe>.stackmap。
用 llvm-dwarfdump 解析最终 PE 的 .debug_line(地址→文件/行)与
.debug_info(DISubprogram 的 low/high_pc + DW_AT_name → 函数名),
@@ -4198,7 +4198,7 @@ class Phase2Translator:
rva(u64) + file_off(u32) + line(u32) + func_off(u32)
字符串池 str_bytes 字节(紧接记录区,UTF-8,全部以 '\\0' 结尾)
口径(必须与运行期严格一致):运行期 dbgstack.vp 取
口径(必须与运行期严格一致):运行期 traceback.vp 取
`rva = 帧地址 − GetModuleHandleA(NULL)`,即"链接期 ImageBase 之上的偏移",
故写端唯一正确口径是 **`RVA = dwarfdump 打印的地址 − PE.ImageBase`**。
@@ -4214,27 +4214,27 @@ class Phase2Translator:
的记录保留(RVA = 地址 − ImageBase);其余(未重定位的 0、越界值)一律**丢弃并
统计**(不静默留垃圾条目)。同一 RVA 只保留行号最大的一条。
写出前自检(任一不满足即 fail-fast 抛异常,遵循 --ctrlc-stack 既有风格):
写出前自检(任一不满足即 fail-fast 抛异常,遵循 --traceback 既有风格):
记录数 / min~max RVA 与 SizeOfImage 的关系 / 所有 RVA ∈ [0, SizeOfImage) /
RVA 严格升序 / 至少若干条落在运行期可能出现的 RVA 区间
(粗判据 [.text 节 RVA, SizeOfImage))。
任何一步失败都直接抛异常终止编译(--ctrlc-stack 的硬要求,无兜底)。
任何一步失败都直接抛异常终止编译(--traceback 的硬要求,无兜底)。
"""
import struct
import bisect
def _Fail(msg: str) -> None:
full: str = msg + '(这是 --ctrlc-stack 的要求)'
full: str = msg + '(这是 --traceback 的要求)'
_vlog().error(full)
raise RuntimeError(full)
layout = _ReadPEImageLayout(exe_path)
if layout is None:
_Fail(f"--ctrlc-stack 只支持 Windows PE 产物,但 {exe_path} 不是有效 PE")
_Fail(f"--traceback 只支持 Windows PE 产物,但 {exe_path} 不是有效 PE")
image_base, size_of_image, sections = layout
if size_of_image <= 0:
_Fail(f"--ctrlc-stack 读到的 PE.SizeOfImage 非正({size_of_image}),无法界定 RVA 域")
_Fail(f"--traceback 读到的 PE.SizeOfImage 非正({size_of_image}),无法界定 RVA 域")
img_lo: int = image_base
img_hi: int = image_base + size_of_image
# 运行期可能出现的 RVA 区间的**粗判据**下界:.text 节 RVA(缺失时退化为最小节 RVA)
@@ -4245,22 +4245,22 @@ class Phase2Translator:
dwarfdump: str | None = shutil.which('llvm-dwarfdump') or shutil.which('llvm-dwarfdump.exe')
if not dwarfdump:
_Fail("--ctrlc-stack 找不到 llvm-dwarfdump(请确认 PATH 内含 LLVM 工具)")
_Fail("--traceback 找不到 llvm-dwarfdump(请确认 PATH 内含 LLVM 工具)")
try:
proc = subprocess.run(
[dwarfdump, '--debug-line', '--debug-info', exe_path],
capture_output=True)
except Exception as e:
_Fail(f"--ctrlc-stack 调用 llvm-dwarfdump 失败: {e}")
_Fail(f"--traceback 调用 llvm-dwarfdump 失败: {e}")
if proc.returncode != 0:
_Fail(f"--ctrlc-stack 的 llvm-dwarfdump 返回 {proc.returncode}: "
_Fail(f"--traceback 的 llvm-dwarfdump 返回 {proc.returncode}: "
+ proc.stderr.decode('utf-8', errors='replace').strip())
dump_text: str = proc.stdout.decode('utf-8', errors='replace')
rows: list[tuple[int, str, int]] = _ParseDwarfLineRows(dump_text)
if not rows:
_Fail(f"--ctrlc-stack 未能从 {exe_path} 解析出任何 DWARF 行记录(.debug_line 为空?)")
_Fail(f"--traceback 未能从 {exe_path} 解析出任何 DWARF 行记录(.debug_line 为空?)")
# ---- 行记录:逐条归一化为"运行期 RVA";不能归一的一律丢弃并统计 ----
dropped_rows: int = 0
@@ -4278,12 +4278,12 @@ class Phase2Translator:
best[rva] = (path, line)
if dropped_rows:
_vlog().warning(
f" [--ctrlc-stack] 丢弃 {dropped_rows} 条无法归一化的 DWARF 行记录"
f" [--traceback] 丢弃 {dropped_rows} 条无法归一化的 DWARF 行记录"
f"(地址不在映像 VA 区间 [0x{img_lo:x}, 0x{img_hi:x}) 内;判定原因:多为被连接器"
f" COMDAT 去重丢弃的泛型副本,其地址恰为 0)示例地址: "
+ ", ".join(f"0x{a:x}" for a in dropped_row_samples))
if not best:
_Fail(f"--ctrlc-stack 从 {exe_path} 得到的行记录全部无法归一到运行期 RVA 域")
_Fail(f"--traceback 从 {exe_path} 得到的行记录全部无法归一到运行期 RVA 域")
# ---- 子程序(函数名):同样逐条归一化;不能归一的丢弃 ----
funcs: list[tuple[int, int, str]] = _ParseDwarfSubprograms(dump_text)
@@ -4302,7 +4302,7 @@ class Phase2Translator:
func_ranges.append((lo_rva, hi_rva, name))
if dropped_funcs:
_vlog().warning(
f" [--ctrlc-stack] 丢弃 {dropped_funcs} 个无法归一化的 DWARF 子程序"
f" [--traceback] 丢弃 {dropped_funcs} 个无法归一化的 DWARF 子程序"
f"(low_pc 不在映像 VA 区间 [0x{img_lo:x}, 0x{img_hi:x}) 内;同上为去重副本)")
func_ranges.sort(key=lambda f: f[0])
func_lows: list[int] = [f[0] for f in func_ranges]
@@ -4341,19 +4341,19 @@ class Phase2Translator:
max_rva: int = rvas[-1]
for i in range(1, len(rvas)):
if rvas[i] <= rvas[i - 1]:
_Fail(f"--ctrlc-stack 自检失败:记录 RVA 非严格升序"
_Fail(f"--traceback 自检失败:记录 RVA 非严格升序"
f"(第 {i} 条 0x{rvas[i]:x} <= 前一条 0x{rvas[i - 1]:x},会破坏运行期二分查找)")
outside: int = sum(1 for r in rvas if not (0 <= r < size_of_image))
if outside:
_Fail(f"--ctrlc-stack 自检失败:有 {outside} 条记录的 RVA 落在 [0, SizeOfImage=0x{size_of_image:x}) 之外")
_Fail(f"--traceback 自检失败:有 {outside} 条记录的 RVA 落在 [0, SizeOfImage=0x{size_of_image:x}) 之外")
# 运行期可能出现的 RVA 区间(粗判据):[.text 节 RVA, SizeOfImage)。一条都没有 ⇒ 口径又错了。
plausible: int = sum(1 for r in rvas if text_rva <= r < size_of_image)
if plausible <= 0:
_Fail(f"--ctrlc-stack 自检失败:没有任何记录的 RVA 落在运行期可能出现的区间"
_Fail(f"--traceback 自检失败:没有任何记录的 RVA 落在运行期可能出现的区间"
f" [0x{text_rva:x}(.text RVA), 0x{size_of_image:x}(SizeOfImage)) 内"
f"(RVA 口径疑似又错了)")
_vlog().info(
f" [--ctrlc-stack] 自检: 记录={len(records)} RVA范围=[0x{min_rva:x}, 0x{max_rva:x}]"
f" [--traceback] 自检: 记录={len(records)} RVA范围=[0x{min_rva:x}, 0x{max_rva:x}]"
f" PE.SizeOfImage=0x{size_of_image:x}({size_of_image})"
f" 各节最高结束 RVA=0x{max_sec_end:x}"
f" | 全部∈[0,SizeOfImage)=是 严格升序=是"
@@ -4371,9 +4371,9 @@ class Phase2Translator:
with open(map_path, 'wb') as f:
f.write(out)
except Exception as e:
_Fail(f"--ctrlc-stack 写出旁文件失败 {map_path}: {e}")
_Fail(f"--traceback 写出旁文件失败 {map_path}: {e}")
_vlog().success(
f" [--ctrlc-stack] 已写出地址→源行旁文件: {map_path}"
f" [--traceback] 已写出地址→源行旁文件: {map_path}"
f"({len(records)} 条记录,{len(out)} 字节)")
def _link_obj_files(self, active_sha1s: set[str]) -> None:
@@ -4504,13 +4504,13 @@ SECTIONS {
lib_flags.append('-L' + self.asan_rt_dir)
lib_flags.append('-l:libclang_rt.asan_dynamic-x86_64.dll.a')
lib_flags.append('-l:libclang_rt.asan_dynamic_runtime_thunk-x86_64.a')
# Ctrl+C 栈转储(--ctrlc-stack):includes/dbgstack.vp 的主回溯路径用
# Ctrl+C 栈转储(--traceback):includes/traceback.vp 的主回溯路径用
# StackWalk64 / SymInitialize / SymFunctionTableAccess64 / SymGetModuleBase64
# (均在 dbghelp),故开启该开关时必须显式链接 -ldbghelp,否则链接期报未定义符号。
# 与 ASan 同理:库要排在目标文件之后 ⇒ 追加到 lib_flags。
# (若确实无法链 dbghelp:把 includes/dbgstack.vp 的 DBGSTACK_USE_DBGHELP 置 0,
# (若确实无法链 dbghelp:把 includes/traceback.vp 的 TRACEBACK_USE_DBGHELP 置 0,
# 退化为纯 RBP 链回溯,此时不该再带本 flag。)
if self.ctrlc_stack_enabled:
if self.traceback_enabled:
lib_flags.append('-ldbghelp')
cmd = [self.linker_cmd] + non_lib_flags + ['-o', link_output] + all_link_files + lib_flags
_vlog().info(f" 执行: {' '.join(cmd)}")
@@ -4564,8 +4564,8 @@ SECTIONS {
_vlog().error(f"\n链接异常,立即终止编译。")
sys.exit(1)
# --ctrlc-stack:链接成功后导出"地址→源行"旁文件(<exe>.stackmap)。
# --traceback:链接成功后导出"地址→源行"旁文件(<exe>.stackmap)。
# 刻意放在上面的链接 try/except 之外,使导出阶段的 fail-fast RuntimeError
# 直接向上抛出终止编译,而不被"链接异常兜底"吞掉。
if self.ctrlc_stack_enabled:
if self.traceback_enabled:
self._WriteStackMap(exe_path)
+3 -2
View File
@@ -78,10 +78,11 @@ def parse_python_file(filepath: str) -> tuple[str, ast.Module] | tuple[str, None
except Exception:
return ('', None)
try:
# 软关键字展开:fn/fun/func/function -> def(行号不变),供 ast.parse 使用。
# 软关键字/语法糖展开:fn/fun/func/function -> def、enum 块 -> class X(t.CEnum)
# (均同行替换,行号不变),供 ast.parse 使用。
# 注意 content 保持原始文本返回(compute_sha1 必须与 Phase1 一致),
# 仅 tree 基于展开后的代码解析。
tree: ast.Module = ast.parse(ExpandSoftKeywords(content))
tree: ast.Module = ast.parse(ExpandSoftKeywords(content), filepath)
except SyntaxError:
return (content, None)
_FILE_AST_CACHE[norm_path] = (mtime, content, tree)
+5 -5
View File
@@ -110,7 +110,7 @@ class TransPyC:
f.write('')
# 解析AST并提取符号
tree: ast.Module = ast.parse(content)
tree: ast.Module = ast.parse(content, SymbolFile.FilePath or '<TransPyC:PreProcessSymbol>')
if debug:
with open(DebugFile, 'a', encoding=SymbolFile.encoding) as f:
@@ -449,8 +449,8 @@ class TransPyC:
self.translator.OriginalLines = Content.split('\n')
self.translator.Content = Content
# 解析Python代码为AST
Tree: ast.Module = ast.parse(Content)
# 解析Python代码为AST
Tree: ast.Module = ast.parse(Content, InputFile)
# 解析文件以提取类型信息(用于 EmbeddedAssignments)
self.translator.ParsePythonFile(InputFile, encoding)
@@ -538,13 +538,13 @@ class TransPyC:
self.translator.OriginalLines = self.code.split('\n')
# 解析Python代码为AST
Tree: ast.Module = ast.parse(self.code)
Tree: ast.Module = ast.parse(self.code, SourceFilename or '<TransPyC:Convert>')
# 从源代码直接预扫描 import 语句,注册别名
# 注意:不能依赖 Tree 中的 import 节点,因为 ParsePythonFile 可能修改了 AST
if not getattr(self.translator, '_ImportedModules', None):
self.translator._ImportedModules = set()
_prescan_tree: ast.Module = ast.parse(self.code)
_prescan_tree: ast.Module = ast.parse(self.code, SourceFilename or '<TransPyC:Convert>')
for _node in ast.iter_child_nodes(_prescan_tree):
if isinstance(_node, ast.Import):
for _alias in _node.names:
+53 -7
View File
@@ -88,7 +88,8 @@ class PythonToStubConverter:
HasImportT: bool = False
HasImportC: bool = False
try:
tree: ast.Module = ast.parse(PyContent)
# 传来源名:解析失败时 vast 的报错/回退诊断才带得上模块名(否则记为 <unknown>)
tree: ast.Module = ast.parse(PyContent, f'{ModuleName}.vp')
for node in tree.body:
if isinstance(node, ast.Import):
for alias in node.names:
@@ -127,7 +128,7 @@ class PythonToStubConverter:
try:
if tree is None:
# 第一遍解析失败(非语法类异常):重试一次,保持原"主循环重新解析"语义
tree = ast.parse(PyContent)
tree = ast.parse(PyContent, f'{ModuleName}.vp')
# 第一遍扫描:收集 Postdefinition 变量
PostdefVars: dict[str, list[tuple[str, str, ast.AnnAssign]]] = {} # {ClassName: [(VarName, TypeStr, node), ...]}
@@ -492,12 +493,23 @@ class PythonToStubConverter:
if SourceLines:
StartLine: int = node.lineno - 1
EndLine: int = node.end_lineno if hasattr(node, 'end_lineno') and node.end_lineno else StartLine + 1
for i in range(StartLine, min(EndLine, len(SourceLines))):
lines.append(SourceLines[i])
Copied: list[str] = [SourceLines[i]
for i in range(StartLine, min(EndLine, len(SourceLines)))]
# 丢弃尾随空行:解析器的 end_lineno 会被紧随的空行 NL 顶到下一行
# (vast Parser._ParseSuite 单行套件分支返回前 `_SkipNL()` 吞掉空行 NL,
# 调用方 _FixLoc 取 _Prev() 即该 NL)。若不丢弃,`define f(...): return x`
# 这类内联体宏末尾会多出空白行,使下方"末行是否已有内联体"判据落空,
# 凭空补一行 ` pass` → 存根缩进非法(hashlib.__sha512 第 29 行)。
while Copied and not Copied[-1].strip():
Copied.pop()
lines.extend(Copied)
if lines and lines[-1].strip() and not lines[-1].rstrip().endswith(':'):
pass
else:
elif lines:
lines.append(f'{IndentStr} pass')
else:
# 源区间为空(理论不可达):产出合法占位,绝不落出 ` pass` 这类非法缩进
lines.append(f'{IndentStr}pass')
return lines
# 处理函数装饰器
@@ -661,7 +673,17 @@ class PythonToStubConverter:
@staticmethod
def _GetTypeString(annotation: ast.AST) -> str:
"""获取类型字符串"""
"""获取类型字符串
支持集与 TPV(TransPyV/App/lib/Stub/StubGen/Converter.vp 的 _PyiTypeStr)
等价:Name/Attribute/Subscript/BinOp/UnaryOp/Compare/BoolOp/Constant/
List/Call(TPV 另支持裸 Tuple,本侧仅在 Subscript.slice 位置接受 Tuple)。
未支持的节点种类与运算符在此 fail-fast(不自造 't.CVoid' 回落)。
"""
# 无注解(None):调用方未提供注解,沿用原语义返回 't.CVoid';
# 这不是"未知节点"——未知节点走函数尾 fail-fast。
if annotation is None:
return 't.CVoid'
if isinstance(annotation, ast.Name):
return annotation.id
elif isinstance(annotation, ast.Attribute):
@@ -706,6 +728,15 @@ class PythonToStubConverter:
return f'{LeftStr} / {RightStr}'
elif isinstance(annotation.op, ast.Mod):
return f'{LeftStr} % {RightStr}'
# 位与/右移:条件宏实参(如 c.CIf(MASK_BYTE & 0x0F)、
# c.CIf((MAX_SIZE >> SHIFT_BITS) > 0))。两运算符必须与 TPV 的
# _PyiBinOpOpText 保持一致,否则同一份源码两侧写出的 vpi 文本不同。
elif isinstance(annotation.op, ast.BitAnd):
return f'{LeftStr} & {RightStr}'
elif isinstance(annotation.op, ast.RShift):
return f'{LeftStr} >> {RightStr}'
# 其余二元运算符(LShift/BitXor/FloorDiv/Pow/MatMult 等)不支持:
# 落到函数尾 fail-fast(与 TPV 同语义)
elif isinstance(annotation, ast.UnaryOp):
if isinstance(annotation.op, ast.Not):
operand: str = PythonToStubConverter._GetTypeString(annotation.operand)
@@ -740,6 +771,11 @@ class PythonToStubConverter:
for i, op in enumerate(ops):
result += f' {op} {comparators[i]}'
return result
elif isinstance(annotation, ast.BoolOp):
# 布尔运算:条件宏实参(如 c.CIf(ENABLED and MAX_SIZE > 0))。
# 文本与 TPV 的 BoolOp 分支一致:and → ' and '、or → ' or '。
Joiner: str = ' or ' if isinstance(annotation.op, ast.Or) else ' and '
return Joiner.join([PythonToStubConverter._GetTypeString(v) for v in annotation.values])
elif isinstance(annotation, ast.Constant):
return repr(annotation.value)
elif isinstance(annotation, ast.Index):
@@ -765,7 +801,17 @@ class PythonToStubConverter:
return f'{FuncStr}({keywords_str})'
else:
return f'{FuncStr}({ArgsStr})'
return 't.CVoid'
# 未知/未支持的注解节点 fail-fast(与 TPV StubGen 的 _PyiTypeStr 同语义、
# 同文案、同类型):静默回落 't.CVoid' 会把错误类型写进 stub 签名——实例:
# 源里的 t.Callable[[], int] 曾写成 t.Callable[int, int](见
# ViperApps/ViperOS/VKernel/.tpv_build/temp/0b/0bb176432facf634.vpi:23),
# 下游按错误签名解析且无任何提示。
# 归因信息:节点 kind + 行号;文件归因由调用方在日志里带上
# (Generator.GenerateStub → "Failed to process <文件>",
# Phase1Generator → "处理 include 文件失败 <相对路径>")。
KindName: str = type(annotation).__name__
LineNo: int = getattr(annotation, 'lineno', 0)
raise TypeError(f'vpi 类型注解节点类型未知: {KindName} (line {LineNo})')
@staticmethod
def _ExtractPostdefClass(annotation: ast.AST) -> str | None:
+84 -13
View File
@@ -9,7 +9,10 @@ from lib.core.Handles.HandlesBase import BaseHandle, CTypeInfo
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _config_mode
from lib.includes import t
from lib.core.SymbolUtils import IsListAnnotation, ParseListAnnotation, FindStructNameInAnnotationWithContext, AnnotationContainsName, IsCPtrNode
from lib.core.SymbolUtils import (
IsListAnnotation, ParseListAnnotation, FindStructNameInAnnotationWithContext,
AnnotationContainsName, IsCPtrNode, RequireDefineConstStorable,
)
def _IsGenericTpName(name: str) -> bool:
@@ -52,6 +55,26 @@ class AnnAssignHandle(BaseHandle):
Gen._store(Value, NewVar)
Gen.variables[VarName] = NewVar
@staticmethod
def _RhsEndsWithVoidCall(Gen: LlvmCodeGenerator, BlockBefore: ir.Block | None, InsnStart: int) -> bool:
"""判定刚求值的带注解赋值 RHS 是否"按类型即无值"(以返回 void 的调用收尾)。
判据完全取自**实际发射的指令**(不依赖 RHS 的 AST 形态白名单):仅当求值
发生在同一基本块、且新增指令的最后一条是返回 void 的 call 时,才认为该
表达式语义上本就无值(如返回 void 的内建/库调用),此时"无值可存"合法。
其余情况——无任何指令产出(类型下标被当乘法 ⇒ 整条 RHS 未生成任何东西)
或只产出 load/gep 等非收尾指令后求值失败(属性链解析失败,实测同块新增
1 条 load)——都属编译期求值失败,必须 fail-fast。
"""
BlockAfter: ir.Block | None = Gen.builder.block if Gen.builder is not None else None
if InsnStart < 0 or BlockAfter is None or BlockAfter is not BlockBefore:
return False
NewInstrs: list[ir.Instruction] = BlockAfter.instructions[InsnStart:]
if not NewInstrs:
return False
Last: ir.Instruction = NewInstrs[-1]
return isinstance(Last, ir.CallInstr) and isinstance(Last.type, ir.VoidType)
def _HandleAttributeStoreLlvm(self, Target: ast.Attribute, Value: ir.Value) -> None:
Gen: LlvmCodeGenerator = self.Trans.LlvmGen
AttrName: str = Target.attr
@@ -210,7 +233,16 @@ class AnnAssignHandle(BaseHandle):
elif isinstance(TargetType, ir.PointerType) and isinstance(Value.type, ir.IntType):
Value = Gen.builder.inttoptr(Value, TargetType, name="int_to_ptr")
elif isinstance(TargetType, ir.IntType) and isinstance(Value.type, ir.PointerType):
Value = Gen.builder.ptrtoint(Value, TargetType, name="ptr_to_int")
if isinstance(Value, ir.Constant) and Value.constant is None:
# null 指针常量赋给整数字段 → 0(C 空指针常量语义)
Value = ir.Constant(TargetType, 0)
else:
# 指针 → 整数:隐式转换一律禁止(与 TransPyV coerce_to_type 同口径)
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"指针隐式转整数已禁止:不能把指针类型 {Value.type} 赋给整数类型 {TargetType};"
f"如需转换请显式 cast(如 t.CUInt64T(p) / t.CInt(p))"
)
Gen._store(Value, Ptr)
def _HandleGenericListLiteralAssign(self, VarName: str, Node: ast.AnnAssign, Gen: LlvmCodeGenerator, list_sub: ast.Subscript) -> None:
@@ -356,6 +388,9 @@ class AnnAssignHandle(BaseHandle):
def _HandleAnnAssignLlvmInner(self, Node: ast.AnnAssign) -> None:
Gen: LlvmCodeGenerator = self.Trans.LlvmGen
# 内建定长数组构造子 array 的注解校验(误用即 fail-fast,含文件:行:列):
# 局部变量/self 字段注解不经过 ResolveAnnotationType,故在此显式校验。
self._CheckFixedArrayKeyword(Node.annotation)
# M1 协程:记录 async 函数调用赋值的变量名(coro.coro_run 内联翻译需要)
if isinstance(Node.target, ast.Name) and Node.value is not None:
if isinstance(Node.value, ast.Call) and isinstance(Node.value.func, ast.Name):
@@ -373,6 +408,10 @@ class AnnAssignHandle(BaseHandle):
IsPtr: bool = TypeInfo.IsPtr
if TypeInfo.IsDefine:
if Node.value and isinstance(Node.value, ast.Constant):
# fail-fast:常量值必须能落进声明类型(define 带类型形态)/ 64 位存储
# (自动推导形态)。唯一判据见 SymbolUtils.RequireDefineIntStorable。
RequireDefineConstStorable(Gen, self.Trans, Node, TypeInfo.BaseType,
Node.annotation, Node.value.value, VarName)
if not hasattr(Gen, '_define_constants'):
Gen._define_constants = {}
Gen._define_constants[VarName] = Node.value.value
@@ -387,6 +426,8 @@ class AnnAssignHandle(BaseHandle):
ctx = EvalContext(Gen=Gen, symtab=self.Trans.SymbolTable, translator=self.Trans)
eval_val = ConstEvaluator.eval_full(Node.value, ctx)
if eval_val is not None and isinstance(eval_val, (int, float)):
RequireDefineConstStorable(Gen, self.Trans, Node, TypeInfo.BaseType,
Node.annotation, eval_val, VarName)
if not hasattr(Gen, '_define_constants'):
Gen._define_constants = {}
Gen._define_constants[VarName] = eval_val
@@ -491,7 +532,13 @@ class AnnAssignHandle(BaseHandle):
and 'list' in self.Trans.ClassHandler._generic_class_templates):
self._HandleGenericListLiteralAssign(VarName, Node, Gen, list_sub)
return
if TypeInfo.IsVoid and isinstance(Node.annotation, ast.BinOp):
# Only fall back to the left operand when the union neither resolved a base
# type nor accumulated multi-level pointers. A pointer-marker based union
# (`t.CPtr*` = `t.CPtr | t.CPtr` => CVoid/2) is already a fully resolved
# multi-level pointer; falling back to its left operand (`t.CPtr` => 1 layer)
# used to silently degrade it to i8*. Keep PtrCount > 1 untouched.
if (TypeInfo.IsVoid and TypeInfo.PtrCount <= 1
and isinstance(Node.annotation, ast.BinOp)):
found: str | None = FindStructNameInAnnotationWithContext(
Node.annotation, self.Trans, Gen)
if found:
@@ -513,6 +560,11 @@ class AnnAssignHandle(BaseHandle):
and Node.annotation.id not in Gen.class_vtable):
TypeInfo.PtrCount = 0
IsPtr = False
# const / volatile 局部变量登记(统一登记入口)。放在此处:TypeInfo 的
# PtrCount/IsStruct 已定稿(上方 526-547),且早于本函数所有后续分支与
# 提前 return(数组/list/_reg_values/已存在 alloca 等),保证「局部变量」
# 这一登记点只有一处、无遗漏。
Gen.RegisterVarQualifiers(VarName, TypeInfo, Node.annotation, IsGlobal=False)
VarType: ir.Type = Gen._ctype_to_llvm(TypeInfo)
if not IsPtr and TypeInfo.IsStruct and TypeInfo.Name:
stripped: str = TypeInfo.Name
@@ -653,11 +705,13 @@ class AnnAssignHandle(BaseHandle):
elif isinstance(Node.value, ast.Attribute):
# 防静默:带值带注解属性链求值失败禁止跳过(见 __function.vp
# 两级链 blk = func.Blocks.Head 编译产物 store 丢失根因)。
self._RaiseUnresolvedExpr("带注解赋值", Node.value, Gen)
self._RaiseUnresolvedExpr("带注解赋值", Node.value, Gen, Node.annotation, VarName)
Gen._record_var_signedness(VarName, IsUnsigned)
return
del Gen._reg_values[VarName]
InitValue: ir.Value | None = None
RhsBlock: ir.Block | None = Gen.builder.block if Gen.builder is not None else None
RhsInsnStart: int = len(RhsBlock.instructions) if RhsBlock is not None else -1
if Node.value:
InitValue = self.HandleExprLlvm(Node.value, VarType=VarType)
# 闭包变量追踪:当赋值右侧是返回闭包的函数调用时,记录变量为闭包类型
@@ -665,10 +719,14 @@ class AnnAssignHandle(BaseHandle):
CalledName: str = Node.value.func.id
if CalledName in Gen._closure_return_types:
Gen._closure_var_types[VarName] = Gen._closure_return_types[CalledName]
if InitValue is None and Node.value is not None and isinstance(Node.value, ast.Attribute):
# 防静默:带值带注解的属性链求值失败(返回 None)必须报编译错误,
# 否则变量 alloca 已创建却无 store → 后续 load 未初始化栈变量。
self._RaiseUnresolvedExpr("带注解赋值", Node.value, Gen)
if InitValue is None and Node.value is not None:
# 防静默(通用判据,不再只认 ast.Attribute):带值带注解赋值 RHS
# 求值为 None 且非"按类型即无值"(未以返回 void 的调用收尾)时,
# 变量 alloca 已创建却无 store → 后续 load 读未初始化栈变量
# (运行期随机崩溃;如类型下标被当乘法的 list[X*]*(...) 整条 RHS
# 不产任何指令)。判据取自实际发射的指令,见 _RhsEndsWithVoidCall。
if not self._RhsEndsWithVoidCall(Gen, RhsBlock, RhsInsnStart):
self._RaiseUnresolvedExpr("带注解赋值", Node.value, Gen, Node.annotation, VarName)
if VarName in Gen._reg_values:
del Gen._reg_values[VarName]
if VarName in Gen.variables and Gen.variables[VarName] is not None:
@@ -844,11 +902,21 @@ class AnnAssignHandle(BaseHandle):
elif isinstance(VarType, ir.PointerType) and isinstance(InitValue.type, ir.IntType):
InitValue = Gen.builder.inttoptr(InitValue, VarType, name=f"int2ptr_{VarName}")
elif isinstance(VarType, ir.IntType) and isinstance(InitValue.type, ir.PointerType):
InitValue = Gen.builder.ptrtoint(InitValue, VarType, name=f"ptr2int_{VarName}")
if isinstance(InitValue, ir.Constant) and InitValue.constant is None:
# null 指针常量初始化整数变量 → 0(C 空指针常量语义)
InitValue = ir.Constant(VarType, 0)
else:
# 指针 → 整数:隐式转换一律禁止(与 TransPyV coerce_to_type 同口径)
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"指针隐式转整数已禁止:不能用指针类型 {InitValue.type} 初始化整数变量 "
f"{VarName}: {VarType};如需转换请显式 cast(如 t.CUInt64T(p) / t.CInt(p))"
)
# 大端局部变量:存储时 bswap
InitValue = Gen._apply_bswap_if_big(InitValue, TypeInfo.ByteOrder, f"bswap_store_{VarName}")
Gen._store(InitValue, var)
except Exception as _e:
except (TypeError, ValueError) as _e:
# 仅回退 llvmlite 自身的类型/值错误;FailFastError 等一律上抛(不吞)
if _config_mode == "strict":
self.Trans.LogWarning(f"异常被忽略: {_e}")
Gen.variables[VarName] = var
@@ -955,12 +1023,15 @@ class AnnAssignHandle(BaseHandle):
if _config_mode == "strict":
raise
_vlog().warning(f"解析类型失败: {_e}", "Exception")
_RhsBlock: ir.Block | None = Gen.builder.block if Gen.builder is not None else None
_RhsInsnStart: int = len(_RhsBlock.instructions) if _RhsBlock is not None else -1
Value: ir.Value | None = self.HandleExprLlvm(Node.value, VarType=AttrVarType)
if Value:
self._HandleAttributeStoreLlvm(Node.target, Value)
elif isinstance(Node.value, ast.Attribute):
# 防静默:self 字段带注解赋值右侧属性链求值失败必须报错
self._RaiseUnresolvedExpr("self 字段带注解赋值", Node.value, Gen)
elif not self._RhsEndsWithVoidCall(Gen, _RhsBlock, _RhsInsnStart):
# 防静默(通用判据,不再只认 ast.Attribute):self 字段带注解
# 赋值右侧求值失败(None)必须报错,否则字段写被静默跳过。
self._RaiseUnresolvedExpr("self 字段带注解赋值", Node.value, Gen, Node.annotation, Node.target.attr)
if Value:
AttrName: str = Node.target.attr
len_name: str = f'{AttrName}__len'
+298 -28
View File
@@ -31,8 +31,9 @@ class AssignHandle(BaseHandle):
return any(AssignHandle._contains_identified_struct(e) for e in var_type.elements)
return False
def HandleExprLlvm(self, Node: ast.expr, VarType: ir.Type | None = None) -> ir.Value | None:
return self.Trans.ExprHandler.HandleExprLlvm(Node, VarType)
def HandleExprLlvm(self, Node: ast.expr, VarType: ir.Type | None = None,
ConstWidthHint: int | None = None) -> ir.Value | None:
return self.Trans.ExprHandler.HandleExprLlvm(Node, VarType, ConstWidthHint)
def _HandleAssignLlvm(self, Node: ast.Assign) -> None:
Gen: "Translator.LlvmGen" = self.Trans.LlvmGen
@@ -41,9 +42,8 @@ class AssignHandle(BaseHandle):
Target: ast.expr = Node.targets[0]
if isinstance(Target, ast.Name):
VarName: str = Target.id
if VarName in Gen.var_const_flags:
src_info: str = Gen._get_node_info()
_vlog().error(f"不能对 const 变量 '{VarName}' 赋值{src_info}")
if Gen._IsVarConst(VarName):
_vlog().error(Gen._ConstAssignMessage("赋值", VarName))
sys.exit(1)
if isinstance(Node.value, ast.Attribute) and isinstance(Node.value.value, ast.Name) and IsTModule(Node.value.value.id, self.Trans.SymbolTable):
AttrName: str = Node.value.attr
@@ -698,8 +698,9 @@ class AssignHandle(BaseHandle):
val: ir.Value | None = self.HandleExprLlvm(kw.value)
if val:
member_values[kw.arg] = val
has_vtable: bool = ClassName in Gen.class_vtable or ClassName in Gen._cross_module_vtable_classes
base: int = 1 if has_vtable else 0
# base(vtable 占位槽数)统一走 Gen._member_base(唯一真源),
# 不再直查 class_vtable/_cross_module_vtable_classes(会漏掉跨模块 AST 节点类)。
base: int = Gen._member_base(ClassName)
for i, (member_name, member_type) in enumerate(members):
if isinstance(member_type, ir.VoidType):
continue
@@ -719,6 +720,15 @@ class AssignHandle(BaseHandle):
def _HandleAttributeStoreLlvm(self, Target: ast.Attribute, Value: ir.Value) -> None:
Gen: "Translator.LlvmGen" = self.Trans.LlvmGen
AttrName: str = Target.attr
# const 类字段:给 `obj.字段 = ...` / `self.字段 = ...` 直接赋值同样拒绝
# (判据 = 字段自身 const,见 BaseGen._IsFieldConst / Debugger/type-descriptors.md)。
# 类名能解析时按 `类名.字段名` 精确判定;解析不到时退化裸名(保守报错)。
_TargetCN: str | None = None
if isinstance(Target.value, ast.Name):
_TargetCN = self.Trans.ExprHandler._get_var_class(Target.value, Gen)
if Gen._IsFieldConst(_TargetCN, AttrName):
_vlog().error(Gen._ConstAssignMessage("字段赋值", AttrName))
sys.exit(1)
if isinstance(Target.value, ast.Name) and Target.value.id == 'self':
ClassName: str | None = self.Trans._CurrentCpythonObjectClass
if ClassName and ClassName in Gen.structs:
@@ -1256,15 +1266,20 @@ class AssignHandle(BaseHandle):
def _HandleSubscriptStoreLlvm(self, Target: ast.Subscript, Value: ir.Value) -> None:
Gen: "Translator.LlvmGen" = self.Trans.LlvmGen
# pointee-volatile 基址名后缀(与 HandlesExprAttr._HandleSubscriptLlvm 同款);
# 空后缀 ⇒ 下标 GEP 名仍是 "subscript",既有代码 IR 逐字节不变。
_PVSuffix: str = Gen._SubscriptSuffix(Target.value)
# 查询数组元素字节序
arr_byte_order: str = ''
if isinstance(Target.value, ast.Name) and Target.value.id in Gen.local_var_byteorders:
arr_byte_order = Gen.local_var_byteorders[Target.value.id]
if isinstance(Target.value, ast.Name):
SubVarName: str = Target.value.id
if SubVarName in Gen.var_const_flags:
src_info: str = Gen._get_node_info()
self.Trans.LogError(f"不能对 const 变量 '{SubVarName}' 的元素赋值{src_info}")
if Gen._IsVarConst(SubVarName):
# 与 Name 赋值/增量赋值同一条 fail-fast(原先此处 LogError 后继续,
# 属静默继续;现统一 error + exit,判据与报文两侧一致)。
_vlog().error(Gen._ConstAssignMessage("的元素赋值", SubVarName))
sys.exit(1)
ClassName: str | None = self.Trans.ExprHandler._get_var_class(Target.value, Gen)
if ClassName and Gen._has_function(f'{ClassName}.__setitem__'):
obj_val: ir.Value | None = self.Trans.ExprHandler.HandleExprLlvm(Target.value)
@@ -1306,7 +1321,7 @@ class AssignHandle(BaseHandle):
PtrToStruct: ir.Type = Value.type.pointee
BaseAsPP: ir.Value = Gen.builder.bitcast(ValueVal,
ir.PointerType(ir.PointerType(PtrToStruct)), name="cast_base_pp")
ElemPtr: ir.Value = Gen.builder.gep(BaseAsPP, [IndexVal], name="subscript")
ElemPtr: ir.Value = Gen.builder.gep(BaseAsPP, [IndexVal], name=f"subscript{_PVSuffix}")
Gen.builder.store(Value, ElemPtr)
return
if BaseHandle._is_char_pointer(IndexVal):
@@ -1324,7 +1339,7 @@ class AssignHandle(BaseHandle):
if isinstance(ValueVal.type, ir.PointerType) and isinstance(ValueVal.type.pointee, ir.PointerType):
if isinstance(IndexVal.type, ir.IntType) and IndexVal.type.width < 64:
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(64), name="zext_ptr_index_store")
ElemPtr: ir.Value = Gen.builder.gep(ValueVal, [IndexVal], name="subscript")
ElemPtr: ir.Value = Gen.builder.gep(ValueVal, [IndexVal], name=f"subscript{_PVSuffix}")
self._StoreArrElemWithCoerceBswap(Value, ElemPtr, arr_byte_order)
return
if isinstance(ValueVal.type, ir.PointerType):
@@ -1336,7 +1351,7 @@ class AssignHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr: ir.Value = Gen.builder.gep(ValueVal, [zero, IndexVal], name="subscript")
ElemPtr: ir.Value = Gen.builder.gep(ValueVal, [zero, IndexVal], name=f"subscript{_PVSuffix}")
elif (isinstance(pointee, ir.IntType) and pointee.width == 8
and isinstance(Target.value, ast.Name)
and (Gen.var_ptr_element.get(Target.value.id, False)
@@ -1346,11 +1361,17 @@ class AssignHandle(BaseHandle):
CastedPtrPtr: ir.Value = Gen.builder.bitcast(ValueVal, ir.PointerType(ir.PointerType(ir.IntType(8))), name="cast_pp_str_store")
if isinstance(IndexVal.type, ir.IntType) and IndexVal.type.width < 64:
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(64), name="zext_pp_str_index_store")
ElemPtr = Gen.builder.gep(CastedPtrPtr, [IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(CastedPtrPtr, [IndexVal], name=f"subscript{_PVSuffix}")
self._StoreArrElemWithCoerceBswap(Value, ElemPtr, arr_byte_order)
return
else:
ElemPtr: ir.Value = Gen.builder.gep(ValueVal, [IndexVal], name="subscript")
ElemPtr: ir.Value = Gen.builder.gep(ValueVal, [IndexVal], name=f"subscript{_PVSuffix}")
# fail-fast:rawptr[T](基座是指针存储)的元素为对象 T、而 RHS 是指向 T
# 的指针时,下游 _StoreWithCoerce/_coerce_value 会静默 load 整对象再按
# sizeof(T) 存储(写 sizeof(T) 而非 8 字节指针)。数组槽(t.CArray[T,N])
# 本就按 sizeof(T) 布局,整对象赋值合法 ⇒ 由 IsArrayBase 排除。
self._RejectWholeObjectCopyIntoPtrSlot(
Target, Value, ElemPtr, isinstance(pointee, ir.ArrayType))
self._StoreArrElemWithCoerceBswap(Value, ElemPtr, arr_byte_order)
elif isinstance(ValueVal.type, ir.ArrayType):
zero: ir.Constant = ir.Constant(ir.IntType(32), 0)
@@ -1364,7 +1385,7 @@ class AssignHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr: ir.Value = Gen.builder.gep(VarPtr, [zero, IndexVal], name="subscript")
ElemPtr: ir.Value = Gen.builder.gep(VarPtr, [zero, IndexVal], name=f"subscript{_PVSuffix}")
self._StoreArrElemWithCoerceBswap(Value, ElemPtr, arr_byte_order)
return
if isinstance(Target.value, ast.Subscript):
@@ -1375,7 +1396,7 @@ class AssignHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr: ir.Value = Gen.builder.gep(InnerPtr, [zero, IndexVal], name="subscript")
ElemPtr: ir.Value = Gen.builder.gep(InnerPtr, [zero, IndexVal], name=f"subscript{_PVSuffix}")
self._StoreArrElemWithCoerceBswap(Value, ElemPtr, arr_byte_order)
return
if isinstance(Target.value, ast.Attribute):
@@ -1386,12 +1407,12 @@ class AssignHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr: ir.Value = Gen.builder.gep(AttrPtr, [zero, IndexVal], name="subscript")
ElemPtr: ir.Value = Gen.builder.gep(AttrPtr, [zero, IndexVal], name=f"subscript{_PVSuffix}")
self._StoreArrElemWithCoerceBswap(Value, ElemPtr, arr_byte_order)
return
arr_alloc: ir.AllocaInstr = Gen._allocaEntry(ValueVal.type, name="arr_subscript_tmp")
Gen._store(ValueVal, arr_alloc)
ElemPtr: ir.Value = Gen.builder.gep(arr_alloc, [zero, IndexVal], name="subscript")
ElemPtr: ir.Value = Gen.builder.gep(arr_alloc, [zero, IndexVal], name=f"subscript{_PVSuffix}")
self._StoreArrElemWithCoerceBswap(Value, ElemPtr, arr_byte_order)
ModifiedArr: ir.Value = Gen._load(arr_alloc, name="modified_arr")
if isinstance(Target.value, ast.Subscript):
@@ -1403,6 +1424,71 @@ class AssignHandle(BaseHandle):
if OuterPtr:
Gen._store(ModifiedArr, OuterPtr)
@staticmethod
def _StructTypeKey(ty: ir.Type) -> str:
"""结构体类型的稳定标识(identified 用 name,literal 用类型文本)"""
if isinstance(ty, ir.IdentifiedStructType):
return ty.name
return str(ty)
# ============================================================
# _RejectWholeObjectCopyIntoPtrSlot - 下标赋值的"整对象拷贝"fail-fast
#
# 目标形态(唯一判据):赋值左值是 rawptr[T] 下标(元素 = 对象/结构体 T,
# 基座为指针存储),而 RHS 的值类型是指向 T 的指针。此时 _StoreWithCoerce /
# _coerce_value 的"指针 → 非指针值: load 解引用"分支会生成
# %v = load %T, %T* %rhs
# store %T %v, %T* %elem (写 sizeof(T) 字节)
# 而该槽只该写 8 字节指针(槽是按 8 字节排布的缓冲/空闲链表)⇒ 每个元素
# 越界 sizeof(T)-8 字节,写穿相邻块(MemBuddy 空闲链表被写穿的静默降级根因)。
#
# 不拦(判据刻意收窄,避免误伤):
# · 数组槽 t.CArray[T,N](IsArrayBase=True):元素槽本就按 sizeof(T) 布局,
# `v[i] = T(...)`(构造函数返回临时对象地址)是合法的整对象复制;
# · 元素是标量(rawptr[int]):ElemPtr 元素不是结构体;
# · 元素本身是指针(rawptr[rawptr[T]] / X|t.CPtr):元素类型是 Ptr;
# · RHS 所指类型与元素类型不一致(非本形态)。
# 与 TPV AssignGenMixin._reject_whole_object_copy_into_ptr_slot 同口径。
# ============================================================
def _RejectWholeObjectCopyIntoPtrSlot(self, Target: ast.expr, Value: ir.Value,
ElemPtr: ir.Value, IsArrayBase: bool) -> None:
"""rawptr[T] 下标赋值 + RHS 为指向 T 的指针 ⇒ fail-fast(整对象拷贝)"""
Gen: "Translator.LlvmGen" = self.Trans.LlvmGen
if IsArrayBase:
return
if ElemPtr is None or not isinstance(ElemPtr.type, ir.PointerType):
return
ElemTy: ir.Type = ElemPtr.type.pointee
if not isinstance(ElemTy, (ir.IdentifiedStructType, ir.LiteralStructType)):
return
if Value is None or not isinstance(Value.type, ir.PointerType):
return
RhsInner: ir.Type = Value.type.pointee
if not isinstance(RhsInner, (ir.IdentifiedStructType, ir.LiteralStructType)):
return
ElemKey: str = AssignHandle._StructTypeKey(ElemTy)
RhsKey: str = AssignHandle._StructTypeKey(RhsInner)
if ElemKey is None or RhsKey is None:
return
if ElemKey != RhsKey:
return
ElemSize: int = Gen._get_struct_size(ElemTy)
PtrSize: int = 8
Over: int = ElemSize - PtrSize
if Over < 0:
Over = 0
SrcInfo: str = Gen._get_node_info()
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"下标赋值的左值元素是对象 '{ElemKey}'({ElemSize} 字节),"
f"右值却是指向 '{RhsKey}' 的指针:编译器会静默生成整对象拷贝"
f"(load {ElemKey} + store {ElemKey},写 {ElemSize} 字节),"
f"而不是按指针写 {PtrSize} 字节。若该地址实际按 8 字节槽存放指针"
f"(如 rawptr[T] 缓冲/空闲链表),每写一个元素就越界 {Over} 字节,"
f"直接写穿相邻块。修法:把指针槽的基座类型写成指针元素形态"
f"(`rawptr[T] | t.CPtr` 或 `rawptr[rawptr[T]]`)后再赋值;"
f"或把基座显式转为 t.CPtr(无类型指针)后按 8 字节指针语义写入。{SrcInfo}")
def _StoreWithCoerce(self, Value: ir.Value, Ptr: ir.Value) -> None:
Gen: "Translator.LlvmGen" = self.Trans.LlvmGen
if isinstance(Ptr.type, ir.PointerType):
@@ -1456,12 +1542,21 @@ class AssignHandle(BaseHandle):
return None
def _EmitGlobalAnnAssignLlvm(self, Node: ast.AnnAssign, Gen: "Translator.LlvmGen") -> None:
# 内建定长数组构造子 array 的注解校验(误用即 fail-fast,含文件:行:列):
# 模块级全局注解不经过 ResolveAnnotationType,故在此显式校验。
self._CheckFixedArrayKeyword(Node.annotation)
if isinstance(Node.target, ast.Name):
VarName: str = Node.target.id
Info: "SymbolTable.SymbolInfo | None" = self.Trans.SymbolTable.lookup(VarName)
if Info:
if Info.IsTypedef:
return
# 模块级全局 t.attr.* 属性(注解联合形态 `T | t.attr.section("...")`):
# 先解析(空/缺段名、非正整数字面量 aligned → fail-fast 终止),再在各
# 全局创建点用 _ApplyGlobalAttrs 施加。此前 TPC 全局路径完全没有
# section/align/weak 代码(函数侧 section 只来自装饰器,见
# HandlesFunctions.py:1361-1362 → :1767)。
GlobalAttrs: dict[str, Any] = self._ExtractGlobalAttrs(Node, Gen)
parse_result = ParseListAnnotation(Node.annotation)
if parse_result:
elem_type_node: ast.expr = parse_result.elem_type_node
@@ -1470,15 +1565,30 @@ class AssignHandle(BaseHandle):
check_func_ptr=True, void_fallback_width=32, fallback_to_cint=True,
)
if parse_result.is_pointer:
# 单参数 list[type] → 指针
VarType: ir.Type = ir.PointerType(ElemType)
if VarName not in Gen.variables and VarName not in Gen.module.globals:
GlobalVar: ir.GlobalVariable = ir.GlobalVariable(Gen.module, VarType, name=VarName)
GlobalVar.initializer = ir.Constant(VarType, None)
Gen.variables[VarName] = GlobalVar
if isinstance(elem_type_node, ast.Name) and elem_type_node.id in Gen.structs:
Gen.global_struct_class[VarName] = elem_type_node.id
return
# 单参数 t.CArray[T]:与 t.CArray[T, None] 统一为"长度自动推导数组"。
# 有可推导初值(列表字面量 / 字符串字面量)时不再生成裸指针,而是落入
# 下方数组路径:由 ParseArrayCount(count_node=None) 从字面量推导长度、
# 字节内联到变量自身(与类成员 DeclarationGenerator.py:775-784、
# 局部 HandlesAnnAssign.py:446-489 同一口径;用户语义:单参数即
# "长度自动推导",不再是隐式指针)。
# 无可推导初值(纯声明 / 真正的指针用法)时保持既有指针语义逐字不变。
HasDerivableInit: bool = bool(
Node.value and (
isinstance(Node.value, ast.List)
or (isinstance(Node.value, ast.Constant) and isinstance(Node.value.value, str))
)
)
if not HasDerivableInit:
VarType: ir.Type = ir.PointerType(ElemType)
if VarName not in Gen.variables and VarName not in Gen.module.globals:
GlobalVar: ir.GlobalVariable = ir.GlobalVariable(Gen.module, VarType, name=VarName)
GlobalVar.initializer = ir.Constant(VarType, None)
self._ApplyGlobalAttrs(GlobalVar, GlobalAttrs)
Gen.variables[VarName] = GlobalVar
if isinstance(elem_type_node, ast.Name) and elem_type_node.id in Gen.structs:
Gen.global_struct_class[VarName] = elem_type_node.id
return
# 有初值 → 落入下方数组路径(count_node=None → 从字面量推导长度)
count_node: ast.expr | None = parse_result.count_node
ArrayCount: int = self.ParseArrayCount(count_node, Gen, Node.value, mode='global')
VarType = ir.ArrayType(ElemType, ArrayCount)
@@ -1506,6 +1616,7 @@ class AssignHandle(BaseHandle):
GlobalVar.linkage = 'common'
else:
GlobalVar.initializer = ir.Constant(VarType, [ir.Constant(ElemType, ir.Undefined)] * ArrayCount)
self._ApplyGlobalAttrs(GlobalVar, GlobalAttrs)
Gen.variables[VarName] = GlobalVar
if isinstance(elem_type_node, ast.Name) and elem_type_node.id in Gen.structs:
Gen.global_struct_class[VarName] = elem_type_node.id
@@ -1523,6 +1634,7 @@ class AssignHandle(BaseHandle):
GlobalVar.initializer = ir.Constant(VarType, None)
else:
GlobalVar.initializer = ir.Constant(VarType, None)
self._ApplyGlobalAttrs(GlobalVar, GlobalAttrs)
Gen.variables[VarName] = GlobalVar
Gen.global_struct_class[VarName] = StructName
return
@@ -1557,6 +1669,12 @@ class AssignHandle(BaseHandle):
VarType: ir.Type = Gen._ctype_to_llvm(TypeInfo)
if isinstance(VarType, ir.VoidType):
VarType = ir.IntType(32)
# const / volatile 模块级全局登记(统一登记入口,持久层 global_*)。
# 局限(明确记录,见 Debugger/type-descriptors.md 已知差异):本分支只覆盖
# 标量/指针型全局;数组(t.CArray[...])与结构体型全局在更早的分支
# (上方 1568-1627)已 return,不在此登记 —— 全仓普查无这类带 const/
# volatile 注解的全局,故无可观测差异。
Gen.RegisterVarQualifiers(VarName, TypeInfo, Node.annotation, IsGlobal=True)
# 记录模块级注解类型:供函数内 global 变量的 += (HandlesAugAssign)参考,
# 避免用 RHS 字面量类型(i32)误建 i32 全局导致 i64/i32 类型不匹配。
if not hasattr(Gen, '_module_ann_types'):
@@ -1593,6 +1711,7 @@ class AssignHandle(BaseHandle):
if isinstance(VarType, (ir.IdentifiedStructType, ir.LiteralStructType)):
GlobalVar: ir.GlobalVariable = ir.GlobalVariable(Gen.module, VarType, name=VarName)
GlobalVar.initializer = ir.Constant(VarType, None)
self._ApplyGlobalAttrs(GlobalVar, GlobalAttrs)
Gen.variables[VarName] = GlobalVar
if TypeInfo and TypeInfo.IsStruct and TypeInfo.Name:
Gen.global_struct_class[VarName] = TypeInfo.Name
@@ -1666,8 +1785,159 @@ class AssignHandle(BaseHandle):
GlobalVar.linkage = 'common'
else:
GlobalVar.initializer = Gen._zero_value_for_type(VarType)
# 属性最后施加:weak 需覆盖分支内可能设过的 'common'(对齐 TransPyV
# TranslatorGenMixin.vp:926-929 先清 Linkage 再设 weak 的语义)。
self._ApplyGlobalAttrs(GlobalVar, GlobalAttrs)
Gen.variables[VarName] = GlobalVar
# ======================================================================
# 模块级全局的 t.attr.* 属性(注解联合形态 `T | t.attr.section("...")`)
#
# 镜像 TransPyV(Viper 自举侧)已有实现:
# TranslatorGenMixin._scan_global_tattr / _extract_global_attr_section /
# _extract_global_attr_aligned / _extract_global_attr_weak(.vp:590-651)
# + ClassGenBase._tattr_name(.vp:341-358)+ _class_has_tattr 的
# `c.Attribute(t.attr.xxx)` 解包(.vp:359-394)。
# TPC(Python 侧)此前完全没有全局 section/align/weak 代码。
# ======================================================================
def _TAttrName(self, node: ast.AST | None) -> str | None:
"""从 t.attr.<name> / t.attr.<name>(args) 节点提取最外层属性名。
对应 TransPyV ClassGenBase._tattr_name:Call → 递归取 func;
Attribute → 取 attr。
"""
if isinstance(node, ast.Call):
return self._TAttrName(node.func)
if isinstance(node, ast.Attribute):
return node.attr
return None
def _ScanGlobalTAttr(self, node: ast.AST | None, want_name: str) -> ast.AST | None:
"""在全局变量注解中查找 t.attr.<want_name> 节点,命中返回该节点。
注解形态是 BinOp(BitOr) 左深链(`A | B | C` = BinOp(BinOp(A,B),C)),
递归左右子节点。命中节点形态:Call(t.attr.aligned(16))或
Attribute(t.attr.weak)。同时兼容包装写法
`T | c.Attribute(t.attr.section("..."))`(解包参考 TransPyV
ClassGenBase._class_has_tattr,.vp:375-389)。
"""
if node is None:
return None
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
hit_left: ast.AST | None = self._ScanGlobalTAttr(node.left, want_name)
if hit_left is not None:
return hit_left
return self._ScanGlobalTAttr(node.right, want_name)
# 包装写法:c.Attribute(t.attr.xxx / t.attr.xxx(...)) → 拆 args 再扫
# (与 HandlesFunctions.py:1354 装饰器侧同一判据:func.attr=='Attribute'
# 且 func.value 为 Name('c'))
if (isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)
and node.func.attr == 'Attribute'
and isinstance(node.func.value, ast.Name) and node.func.value.id == 'c'):
for arg in node.args:
hit_arg: ast.AST | None = self._ScanGlobalTAttr(arg, want_name)
if hit_arg is not None:
return hit_arg
return None
if self._TAttrName(node) == want_name:
return node
return None
@staticmethod
def _TAttrArgStr(node: ast.AST | None) -> str | None:
"""取 t.attr.<name>("...") 的字符串字面量参数(缺参/非字面量返回 None)。"""
if isinstance(node, ast.Call) and node.args:
arg0: ast.AST = node.args[0]
if isinstance(arg0, ast.Constant) and isinstance(arg0.value, str):
return arg0.value
return None
@staticmethod
def _TAttrAlignInt(node: ast.AST | None) -> int | None:
"""取 t.attr.aligned(N) 的整数字面量 N(缺参/非字面量返回 None)。
对应 TransPyV ClassGenBase._tattr_aligned_int(.vp:395-415)。
"""
if isinstance(node, ast.Call) and node.args:
arg0: ast.AST = node.args[0]
if (isinstance(arg0, ast.Constant) and isinstance(arg0.value, int)
and not isinstance(arg0.value, bool)):
return arg0.value
return None
def _RaiseGlobalAttrError(self, Msg: str, Node: ast.AST, Gen: "Translator.LlvmGen", VarName: str) -> None:
"""fail-fast:全局 t.attr.* 属性非法(缺/空段名、非正整数字面量 aligned)。
定位串(文件:行:列)与报错出口对齐 HandlesBase._RaiseUnresolvedExpr
(HandlesBase.py:552-615)——错误信息含源码位置后 sys.exit(1)。
"""
raw_lineno: int = getattr(Node, 'lineno', 0) or 0
lineno: int = raw_lineno or getattr(Gen, '_current_lineno', 0) or 0
col: int = getattr(Node, 'col_offset', -1)
src_file: str = getattr(Gen, '_current_source_file', '') or ''
# 注入行偏移还原:AST 行号含 _inject_auto_imports 在文件头注入的行数
_line_offset: int = int(getattr(getattr(self, 'Trans', None), '_line_offset', 0) or 0)
if _line_offset > 0 and lineno > _line_offset:
lineno -= _line_offset
loc: str = src_file
if lineno > 0:
loc += f":{lineno}"
if col >= 0:
loc += f":{col}"
_vlog().compile_error(
f"模块级变量 '{VarName}' 的 {Msg}(位置 {loc})", src_file, lineno, '')
sys.exit(1)
def _ExtractGlobalAttrs(self, Node: ast.AnnAssign, Gen: "Translator.LlvmGen") -> dict[str, Any]:
"""从模块级全局注解提取 t.attr.section/aligned/weak。
返回 {'section': str|None, 'align': int, 'weak': bool}。
非法值(空/缺段名、非正整数 aligned)→ fail-fast 终止(对齐 TransPyV
TranslatorGenMixin._extract_global_attr_section/aligned 的致命错误语义)。
"""
Attrs: dict[str, Any] = {'section': None, 'align': 0, 'weak': False}
Ann: ast.AST = Node.annotation
VarName: str = Node.target.id if isinstance(Node.target, ast.Name) else '<anonymous>'
sec_node: ast.AST | None = self._ScanGlobalTAttr(Ann, 'section')
if sec_node is not None:
sec_val: str | None = self._TAttrArgStr(sec_node)
if not sec_val:
self._RaiseGlobalAttrError(
"t.attr.section(...) 段名缺失或为空(空段名会生成非法的 section \"\")",
sec_node, Gen, VarName)
Attrs['section'] = sec_val
align_node: ast.AST | None = self._ScanGlobalTAttr(Ann, 'aligned')
if align_node is not None:
align_val: int | None = self._TAttrAlignInt(align_node)
if align_val is None or align_val <= 0:
self._RaiseGlobalAttrError(
"t.attr.aligned(N) 需要正整数字面量 N", align_node, Gen, VarName)
Attrs['align'] = align_val
if self._ScanGlobalTAttr(Ann, 'weak') is not None:
Attrs['weak'] = True
return Attrs
@staticmethod
def _ApplyGlobalAttrs(GlobalVar: ir.GlobalVariable, Attrs: dict[str, Any]) -> None:
"""把提取出的 section/align/weak 施加到已创建的 LLVM 全局变量上。
llvmlite 口径(llvmlite/ir/values.py):
- section 写**裸**段名(序列化时自动加引号,:843-844);
- align 需 int(:846-847);GlobalVariable.align 默认 None(:807);
- weak 用 linkage='weak'(GlobalVariable 无 weak 属性,对照函数侧
HandlesFunctions.py:1772)。
"""
if not Attrs:
return
sec: str | None = Attrs.get('section')
if sec:
GlobalVar.section = sec
align_val: int = int(Attrs.get('align') or 0)
if align_val > 0:
GlobalVar.align = align_val
if Attrs.get('weak'):
GlobalVar.linkage = 'weak'
def _eval_global_const(self, value_node: ast.expr, var_type: ir.Type, Gen: "Translator.LlvmGen") -> ir.Constant | None:
if isinstance(value_node, ast.Constant):
if isinstance(value_node.value, bool):
+60 -10
View File
@@ -303,9 +303,14 @@ class AsyncFunctionHandle(BaseHandle):
if not any(isinstance(n, ast.Await) for n in ast.walk(stmt)):
continue # 无 await:委托 ForHandler,不需额外槽
if not self._IsRangeFor(stmt):
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_lineno: int = int(getattr(stmt, 'lineno', 0) or 0)
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and _lineno > _line_offset:
_lineno -= _line_offset
raise SyntaxError(
f"[coro-M3] 协程内 For 仅支持 range 迭代(含 await)"
f"(line {getattr(stmt, 'lineno', 0)})"
f"(line {_lineno})"
)
slots.append(f'__f_idx_{for_counter}')
slots.append(f'__f_len_{for_counter}')
@@ -551,9 +556,14 @@ class AsyncFunctionHandle(BaseHandle):
if isinstance(stmt, ast.Expr):
self.Trans.ExprHandler.HandleExprLlvm(stmt.value)
return
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_lineno: int = int(getattr(stmt, 'lineno', 0) or 0)
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and _lineno > _line_offset:
_lineno -= _line_offset
raise SyntaxError(
f"[coro-M2] async 函数体暂不支持语句: {type(stmt).__name__} "
f"(line {getattr(stmt, 'lineno', 0)})"
f"(line {_lineno})"
)
# ------------------------------------------------------------------
@@ -640,9 +650,14 @@ class AsyncFunctionHandle(BaseHandle):
return
async_name: str | None = self._GetAsyncCallName(call) if isinstance(call, ast.Call) else None
if not async_name:
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_lineno: int = int(getattr(await_node, 'lineno', 0) or 0)
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and _lineno > _line_offset:
_lineno -= _line_offset
raise SyntaxError(
f"[coro-M3] await 目标必须是 async 函数调用或 coro_sleep(ms) "
f"(line {getattr(await_node, 'lineno', 0)})"
f"(line {_lineno})"
)
self.Trans.ExprHandler.HandleExprLlvm(await_node) # → _HandleAwaitLlvm
@@ -1002,8 +1017,13 @@ class AsyncFunctionHandle(BaseHandle):
def _HandleCoroIf(self, Node: ast.If, Gen: LlvmCodeGenerator) -> None:
if any(isinstance(n, ast.Await) for n in ast.walk(Node.test)):
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_lineno: int = int(getattr(Node, 'lineno', 0) or 0)
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and _lineno > _line_offset:
_lineno -= _line_offset
raise SyntaxError(
f"[coro-M2] if 条件中不支持 await (line {getattr(Node, 'lineno', 0)})"
f"[coro-M2] if 条件中不支持 await (line {_lineno})"
)
Cond: ir.Value | None = self.Trans.ExprHandler.HandleExprLlvm(Node.test)
if not Cond:
@@ -1034,8 +1054,13 @@ class AsyncFunctionHandle(BaseHandle):
def _HandleCoroWhile(self, Node: ast.While, Gen: LlvmCodeGenerator) -> None:
if any(isinstance(n, ast.Await) for n in ast.walk(Node.test)):
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_lineno: int = int(getattr(Node, 'lineno', 0) or 0)
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and _lineno > _line_offset:
_lineno -= _line_offset
raise SyntaxError(
f"[coro-M2] while 条件中不支持 await (line {getattr(Node, 'lineno', 0)})"
f"[coro-M2] while 条件中不支持 await (line {_lineno})"
)
CondBB: ir.Block = Gen.func.append_basic_block(name='coro_wcond')
BodyBB: ir.Block = Gen.func.append_basic_block(name='coro_wbody')
@@ -1076,12 +1101,22 @@ class AsyncFunctionHandle(BaseHandle):
def _HandleCoroTry(self, Node: ast.Try, Gen: LlvmCodeGenerator) -> None:
"""协程内 try/except:捕获 raise 与 await 传播异常(M6:多 except 类型匹配;Exception/裸 except 捕获所有)。"""
if not Node.handlers:
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_lineno: int = int(getattr(Node, 'lineno', 0) or 0)
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and _lineno > _line_offset:
_lineno -= _line_offset
raise SyntaxError(
f"[coro-M6] 协程内 try 需要 except 子句 (line {getattr(Node, 'lineno', 0)})"
f"[coro-M6] 协程内 try 需要 except 子句 (line {_lineno})"
)
if Node.finalbody:
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_lineno = int(getattr(Node, 'lineno', 0) or 0)
_line_offset = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and _lineno > _line_offset:
_lineno -= _line_offset
raise SyntaxError(
f"[coro-M6] 协程内暂不支持 try/finally (line {getattr(Node, 'lineno', 0)})"
f"[coro-M6] 协程内暂不支持 try/finally (line {_lineno})"
)
except_bb: ir.Block = Gen.func.append_basic_block('coro_try_except')
merge_bb: ir.Block = Gen.func.append_basic_block('coro_try_merge')
@@ -1159,8 +1194,13 @@ class AsyncFunctionHandle(BaseHandle):
"""协程内 try/finally(无 except):正常/异常路径都执行 fbody;
异常时 finally 后重新传播(done=1 + br exit);finally 内暂不支持 await。"""
if any(isinstance(n, ast.Await) for s in Node.finalbody for n in ast.walk(s)):
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_lineno: int = int(getattr(Node, 'lineno', 0) or 0)
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and _lineno > _line_offset:
_lineno -= _line_offset
raise SyntaxError(
f"[coro-M7] finally 内暂不支持 await (line {getattr(Node, 'lineno', 0)})"
f"[coro-M7] finally 内暂不支持 await (line {_lineno})"
)
fin_bb: ir.Block = Gen.func.append_basic_block('coro_fin')
fin_exc_bb: ir.Block = Gen.func.append_basic_block('coro_fin_exc')
@@ -1201,15 +1241,25 @@ class AsyncFunctionHandle(BaseHandle):
def _HandleCoroFor(self, Node: ast.For, Gen: LlvmCodeGenerator) -> None:
if any(isinstance(n, ast.Await) for n in ast.walk(Node)):
if not self._IsRangeFor(Node):
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_lineno: int = int(getattr(Node, 'lineno', 0) or 0)
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and _lineno > _line_offset:
_lineno -= _line_offset
raise SyntaxError(
f"[coro-M3] 协程内 For 仅支持 range 迭代(含 await)"
f"(line {getattr(Node, 'lineno', 0)})"
f"(line {_lineno})"
)
self._HandleCoroForRange(Node, Gen)
return
if any(isinstance(n, ast.Return) for st in Node.body for n in ast.walk(st)):
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_lineno: int = int(getattr(Node, 'lineno', 0) or 0)
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and _lineno > _line_offset:
_lineno -= _line_offset
raise SyntaxError(
f"[coro-M2] For 循环体内暂不支持 return(M3 实现)(line {getattr(Node, 'lineno', 0)})"
f"[coro-M2] For 循环体内暂不支持 return(M3 实现)(line {_lineno})"
)
# 无 await/return:委托现有 ForHandler(迭代索引等 alloca 不跨挂起,安全)
self.Trans.ForHandler._HandleForLlvm(Node)
+8 -9
View File
@@ -65,9 +65,8 @@ class AugAssignHandle(BaseHandle):
src_info: str = Gen._get_node_info()
_vlog().error(f"变量 '{VarName}' 未声明,不能用于增量赋值(需用 global 声明或先声明局部变量){src_info}")
sys.exit(1)
if VarName in Gen.var_const_flags:
src_info: str = Gen._get_node_info()
_vlog().error(f"不能对 const 变量 '{VarName}' 增量赋值{src_info}")
if Gen._IsVarConst(VarName):
_vlog().error(Gen._ConstAssignMessage("增量赋值", VarName))
sys.exit(1)
ClassName: str | None = Gen.var_struct_class.get(VarName)
iop_name: str | None = self._AUGOP_MAP.get(type(Node.op))
@@ -118,7 +117,7 @@ class AugAssignHandle(BaseHandle):
GVar = Gen.module.globals[VarName]
Gen.variables[VarName] = GVar
OldVal: ir.Value = Gen._load(GVar, name=VarName)
NewVal: ir.Value = Gen.emit_binary_op(Op, OldVal, Value, is_unsigned=is_unsigned)
NewVal: ir.Value = Gen.emit_binary_op(Op, OldVal, Value, is_unsigned=is_unsigned, is_aug=True)
Gen._store(NewVal, GVar)
return
if VarName in Gen._reg_values:
@@ -156,7 +155,7 @@ class AugAssignHandle(BaseHandle):
OldVal = Gen.builder.zext(OldVal, Value.type, name="zext_left")
elif OldVal.type.width > Value.type.width:
Value = Gen.builder.zext(Value, OldVal.type, name="zext_right")
NewVal = Gen.emit_binary_op(Op, OldVal, Value, is_unsigned=is_unsigned)
NewVal = Gen.emit_binary_op(Op, OldVal, Value, is_unsigned=is_unsigned, is_aug=True)
if isinstance(NewVal.type, ir.IntType) and isinstance(orig_type, ir.IntType):
if NewVal.type.width > orig_type.width:
NewVal = Gen.builder.trunc(NewVal, orig_type, name="trunc_result")
@@ -179,7 +178,7 @@ class AugAssignHandle(BaseHandle):
NewVal = Gen.builder.gep(OldVal, [Value], name="ptr_add_aug")
Gen._store(NewVal, store_ptr)
return
NewVal: ir.Value = Gen.emit_binary_op(Op, OldVal, Value, is_unsigned=is_unsigned)
NewVal: ir.Value = Gen.emit_binary_op(Op, OldVal, Value, is_unsigned=is_unsigned, is_aug=True)
if isinstance(store_ptr.type, ir.PointerType):
TargetType: ir.Type = store_ptr.type.pointee
if NewVal.type != TargetType:
@@ -191,7 +190,7 @@ class AugAssignHandle(BaseHandle):
var: ir.AllocaInstr = Gen._allocaEntry(Value.type, name=VarName)
Gen.variables[VarName] = var
OldVal: ir.Value = Gen._load(var, name=VarName)
NewVal: ir.Value = Gen.emit_binary_op(Op, OldVal, Value, is_unsigned=is_unsigned)
NewVal: ir.Value = Gen.emit_binary_op(Op, OldVal, Value, is_unsigned=is_unsigned, is_aug=True)
Gen._store(NewVal, var)
elif isinstance(Node.target, ast.Attribute):
AttrName: str = Node.target.attr
@@ -210,7 +209,7 @@ class AugAssignHandle(BaseHandle):
OldVal = Gen.builder.zext(OldVal, Value.type, name="zext_left_attr")
elif OldVal.type.width > Value.type.width:
Value = Gen.builder.zext(Value, OldVal.type, name="zext_right_attr")
NewVal: ir.Value = Gen.emit_binary_op(Op, OldVal, Value, is_unsigned=is_unsigned)
NewVal: ir.Value = Gen.emit_binary_op(Op, OldVal, Value, is_unsigned=is_unsigned, is_aug=True)
self.Trans.AssignHandler._HandleAttributeStoreLlvm(Node.target, NewVal)
elif isinstance(Node.target, ast.Subscript):
SubTarget: ast.Subscript = Node.target
@@ -236,7 +235,7 @@ class AugAssignHandle(BaseHandle):
NewVal = Gen.builder.gep(OldVal, [Value], name="ptr_add_sub")
Gen._store(NewVal, ElemPtr)
return
NewVal: ir.Value = Gen.emit_binary_op(Op, OldVal, Value, is_unsigned=is_unsigned)
NewVal: ir.Value = Gen.emit_binary_op(Op, OldVal, Value, is_unsigned=is_unsigned, is_aug=True)
if isinstance(NewVal.type, ir.IntType) and isinstance(ElemPtr.type.pointee, ir.IntType):
if NewVal.type.width > ElemPtr.type.pointee.width:
NewVal = Gen.builder.trunc(NewVal, ElemPtr.type.pointee, name="trunc_sub_result")
+122 -20
View File
@@ -13,7 +13,8 @@ from lib.constants import config as _config
from lib.includes import t
from lib.includes.t import CTypeRegistry
from lib.core.TypeSpec import TypeSpec, SymbolMeta, FIELD_ROUTES
from lib.core.SymbolUtils import IsPtrSubscriptNode, IsListAnnotation
from lib.core.SymbolUtils import (
IsPtrSubscriptNode, IsListAnnotation, FixedArrayKeywordAnnotationError)
from lib.core.VLogger import get_logger as _vlog
@@ -523,12 +524,13 @@ class BaseHandle:
pointee: _ir.Type = val.type.pointee
return isinstance(pointee, _ir.IntType) and pointee.width == 8
def HandleExprLlvm(self, Node: ast.AST, VarType: _ir.Type | str | None = None) -> _ir.Value | None:
return self.Trans.ExprHandler.HandleExprLlvm(Node, VarType)
def HandleExprLlvm(self, Node: ast.AST, VarType: _ir.Type | str | None = None,
ConstWidthHint: int | None = None) -> _ir.Value | None:
return self.Trans.ExprHandler.HandleExprLlvm(Node, VarType, ConstWidthHint)
@staticmethod
def _ExprText(Node: ast.AST) -> str:
"""把属性链/名字节点渲染成源码文本(如 func.Blocks.Head),供错误信息使用"""
"""把表达式节点渲染成源码文本(如 func.Blocks.Head、list[X*] * uint64(v)),供错误信息使用"""
if isinstance(Node, ast.Attribute):
parts: list[str] = []
cur: ast.AST = Node
@@ -541,32 +543,97 @@ class BaseHandle:
return '.'.join(reversed(parts))
if isinstance(Node, ast.Name):
return Node.id
try:
rendered: str = ast.unparse(Node)
if rendered:
return rendered
except Exception:
pass
return getattr(Node, 'attr', '') or '<expr>'
def _RaiseUnresolvedExpr(self, What: str, Node: ast.AST, Gen: Any) -> None:
"""fail-fast:属性/成员访问表达式在编译期求值失败(返回 None)时终止。
def _FailFastFixedArrayKeyword(self, Node: ast.AST, Msg: str) -> None:
"""内建定长数组构造子 array 误用的 fail-fast 出口。
属性链(含两级自动解引用链,如 blk = func.Blocks.Head)若解析不出
目标类型,_handle_attr_nested 会返回 None;赋值调用方若静默跳过
store,目标 alloca 永不初始化 → 运行期读未初始化栈变量(随机崩溃)。
此处用 VLogger 输出 文件名:行:列 + 表达式源码后退出(严格模式语义)。
报错含:文件:行:列 + 源码片段 + 期望形态(array[E, N])+ 正确示例
(消息文本由 SymbolUtils.FixedArrayKeywordAnnotationError 给出)。
"""
import sys
Gen: Any = getattr(self.Trans, 'LlvmGen', None)
raw_lineno: int = getattr(Node, 'lineno', 0) or 0
lineno: int = raw_lineno or getattr(Gen, '_current_lineno', 0) or 0
col: int = getattr(Node, 'col_offset', -1)
src_file: str = getattr(Gen, '_current_source_file', '') or ''
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and lineno > _line_offset:
lineno -= _line_offset
code_line: str = ''
if lineno > 0 and Gen is not None and hasattr(Gen, '_get_source_line'):
try:
code_line = (Gen._get_source_line(lineno) or '').strip()
except Exception:
code_line = ''
loc: str = src_file
if lineno > 0:
loc += f":{lineno}"
if col >= 0:
loc += f":{col}"
full: str = f"{Msg}。位置 {loc}" + (f",源码: {code_line}" if code_line else '')
_vlog().compile_error(full, src_file, lineno, code_line)
sys.exit(1)
def _CheckFixedArrayKeyword(self, Annotation: ast.AST) -> None:
"""注解入口统一校验内建定长数组构造子 array(误用即 fail-fast)。
只校验带位置的注解入口(本类的 ResolveAnnotationType / CollectAnnAssignMember
+ HandlesAnnAssign 局部 / HandlesAssign 全局显式调用),**不**在通用表达式
解析路径上调用——以保证 array 在非类型位置仍是普通标识符(软关键字)。
"""
if Annotation is None:
return
Msg: str | None = FixedArrayKeywordAnnotationError(Annotation, self.Trans.SymbolTable)
if Msg:
self._FailFastFixedArrayKeyword(Annotation, Msg)
def _RaiseUnresolvedExpr(self, What: str, Node: ast.AST, Gen: Any, AnnNode: ast.AST | None = None,
VarName: str | None = None) -> None:
"""fail-fast:带值表达式在编译期求值失败(返回 None 且无值可存)时终止。
属性链(含两级自动解引用链,如 blk = func.Blocks.Head)解析不出目标
类型时 _handle_attr_nested 会返回 None;类型下标被当乘法的畸形写法
(如 list[X*]*(...))会让整条 RHS 不产任何指令而求值 None。赋值调用方
若静默跳过 store,目标 alloca 永不初始化 → 运行期读未初始化栈变量(随机
崩溃)。此处用 VLogger 输出 目标变量 + 注解文本 + 表达式源码与形态 +
文件名:行:列 后退出(严格模式语义)。
"""
import sys
ann_text: str = ''
if AnnNode is not None:
try:
ann_text = ast.unparse(AnnNode)
except Exception:
ann_text = ''
# RHS 形态 = AST 节点类名(Name/Subscript/BinOp/Call/Attribute/…),
# 便于报错可直接按形态分类,不必再从表达式文本反推。
rhs_kind: str = type(Node).__name__
raw_lineno: int = getattr(Node, 'lineno', 0) or 0
lineno: int = raw_lineno or getattr(Gen, '_current_lineno', 0) or 0
col: int = getattr(Node, 'col_offset', -1)
src_file: str = getattr(Gen, '_current_source_file', '') or ''
expr_text: str = self._ExprText(Node)
# 注入行偏移还原:AST 行号含 _inject_auto_imports 在文件头注入的 N 行,
# 先按精确偏移减回磁盘真实行号(N 由 Phase2Translator 计算并挂到 translator)。
_line_offset: int = int(getattr(getattr(self, 'Trans', None), '_line_offset', 0) or 0)
if _line_offset > 0 and lineno > _line_offset:
lineno -= _line_offset
code_line: str = ''
if lineno > 0 and src_file:
# 行号基准修正(vasts AST 行号 vs 磁盘原文件行号偏差):
# 行号基准修正(vast AST 行号 vs 磁盘原文件行号偏差):
# Phase2Translator._inject_auto_imports 会在解析前向源码头部注入
# 1~3 行 "import _list/_dict/json",使 AST 节点 lineno 比磁盘原文件
# 真实行号大 N(实测 CallGenCBuiltin.vp 真实 1409 报 1410、
# FuncGenMixin.vp 真实 514 报 515)。而 _get_source_line 基于
# _current_source_lines(原文件),导致报错行号偏大且"代码行"
# 取错行。修正:以表达式文本为锚,从原始行号向上(至多 8 行)
# 回溯取"最近且包含该表达式文本"的源码行作为真实行号。
# FuncGenMixin.vp 真实 514 报 515)。上面已按 _line_offset 精确还原;
# 此处再以表达式文本为锚(从原始行号向上至多 8 行)回溯校正
# 属性链(其 expr_text 逐字出现在磁盘行中)可能残留的偏差。
if expr_text and '.' in expr_text and hasattr(Gen, '_get_source_line'):
_src_lines: Any = getattr(Gen, '_current_source_lines', None)
if isinstance(_src_lines, list) and _src_lines:
@@ -583,8 +650,11 @@ class BaseHandle:
loc += f":{lineno}"
if col >= 0:
loc += f":{col}"
msg: str = (f"{What}表达式 '{expr_text}' 编译期求值为空(属性访问未能解析出"
f"目标类型/成员),赋值将被跳过、变量保持未初始化。位置 {loc}"
msg: str = (f"{What}表达式 '{expr_text}'({rhs_kind} 形态)编译期求值为空("
f"表达式未解析出目标、无值可存;且其求值未以返回 void 的调用收尾),"
f"赋值将被跳过、变量保持未初始化"
f"{(',目标变量 ' + repr(VarName)) if VarName else ''}"
f"{(',注解 ' + ann_text) if ann_text else ''}。位置 {loc}"
f"{(',源码: ' + code_line) if code_line else ''}")
_vlog().compile_error(msg, src_file, lineno, code_line)
sys.exit(1)
@@ -767,6 +837,9 @@ class BaseHandle:
CTypeInfo.FromNode 把容器折叠成 i8*,字段类型与 declare/stub 端
的特化名指针不一致(同一结构体在定义端/声明端布局描述不同)。
"""
# 内建定长数组构造子 array 的注解校验(误用即 fail-fast):
# 本方法覆盖函数参数/返回注解与类字段(CollectAnnAssignMember 亦经此)。
self._CheckFixedArrayKeyword(annotation)
if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
merger: Any = getattr(self.Trans, 'TypeMergeHandler', None)
if merger:
@@ -943,6 +1016,15 @@ class BaseHandle:
if isinstance(MemberType, _ir.VoidType):
MemberType = _ir.PointerType(_ir.IntType(8))
return VarName, MemberType, TypeInfo
except RecursionError:
# RecursionError 不是"可忽略异常":它是类型解析/声明发射链**成环**的
# 真缺陷信号。此前被下方 `except Exception` 吞成一行
# `WARN: HandlesBase: 忽略异常 maximum recursion depth exceeded` 并静默
# 退化为 i32 ⇒ 真正的环(如跨模块类声明发射重入
# _EmitExternalClassDeclLlvm ↔ _TryLoadStructFromStub ↔ _TryLoadClassMembersFromPyi)
# 被掩盖,下游只看到类型解析静默退化。此处 fail-fast 上抛(保留原始栈),
# 让环路根因暴露在错误栈顶,不再被 WARN 掩埋。
raise
except Exception as e:
_vlog().warning(f"HandlesBase: 忽略异常 {e}", exc_info=e)
return VarName, _ir.IntType(32), None
@@ -995,15 +1077,35 @@ class BaseHandle:
ret_type = inferred
else:
ret_type = Gen._CType2LLVM('i32', False)
param_type_infos = [pt for _, pt in (sym_info.FuncPtrParams or [])]
param_type_infos = list(sym_info.FuncPtrParams or [])
if isinstance(ret_type, _ir.VoidType):
ret_type = _ir.IntType(32)
llvm_param_types: list[Any] = []
for pt in param_type_infos:
for ParamIdx, (ParamName, pt) in enumerate(param_type_infos):
if isinstance(pt, CTypeInfo) and pt.BaseType:
lp: Any = pt.ToLLVM(Gen)
elif isinstance(pt, str):
lp = Gen._CType2LLVM(pt, '*' in pt)
else:
lp = Gen._CType2LLVM(pt if isinstance(pt, str) else 'i32', '*' in pt if isinstance(pt, str) else False)
# fast-fail:形参类型未解析出 BaseType 时不再硬编码 'i32' 造形参。
# 旧回退会把"类型解析失败"伪装成 llvmlite 的
# Type of #{N} arg mismatch: {expected} != {actual}
# (形参被静默写成 i32 ⇒ 报错方向与真因相反,且不含符号/位置信息,
# 如 "Type of #2 arg mismatch: i32 != %"<sha1>.SymbolTable"*"),
# 这里直接抛出带上下文的编译错误暴露真实缺失。
if isinstance(pt, CTypeInfo):
_PtDiag: str = (
f"CTypeInfo(BaseType=None, Name={pt.Name!r}, PtrCount={pt.PtrCount},"
f" IsTypedef={pt.IsTypedef}, IsFuncPtr={pt.IsFuncPtr}, IsState={pt.IsState},"
f" OriginalType={pt.OriginalType!r})")
else:
_PtDiag = f"{type(pt).__name__}({pt!r})"
raise TypeError(
f"[fast-fail] 函数 '{sym_info.Name}' 第 {ParamIdx + 1} 个形参 '{ParamName}' 的"
f"类型未解析出 BaseType,无法构建 LLVM 形参类型({_PtDiag});"
f"定义位置={getattr(sym_info, 'file', None)!r}:{sym_info.Lineno},"
f" IsVariadic={is_variadic}, mangled_name={mangled_name!r}"
)
if isinstance(lp, _ir.VoidType):
lp = _ir.IntType(8).as_pointer()
llvm_param_types.append(lp)
+9 -2
View File
@@ -62,6 +62,12 @@ class BodyHandle(BaseHandle):
self.Trans.DeleteHandler._HandleDeleteLlvm(Node)
except Exception as e:
lineno: int = getattr(Node, 'lineno', 0)
# 注入行偏移还原:AST 行号含 _inject_auto_imports 在文件头注入的 N 行,
# 先按精确偏移减回磁盘真实行号(N 由 Phase2Translator 计算并挂到 translator);
# 报文行号与源码行必须同用还原后的 lineno,二者一致(否则会显示下一行源码)。
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and lineno > _line_offset:
lineno -= _line_offset
source_line: str | None = Gen._get_source_line(lineno)
src_file: str = Gen._current_source_file or ''
src_path: str = src_file if src_file else 'unknown'
@@ -73,5 +79,6 @@ class BodyHandle(BaseHandle):
if Gen.builder and not Gen.builder.block.is_terminated:
Gen._EmitTempFrees()
def HandleExprLlvm(self, Node: ast.expr, VarType: ir.Type | None = None) -> ir.Value | None:
return self.Trans.ExprHandler.HandleExprLlvm(Node, VarType)
def HandleExprLlvm(self, Node: ast.expr, VarType: ir.Type | None = None,
ConstWidthHint: int | None = None) -> ir.Value | None:
return self.Trans.ExprHandler.HandleExprLlvm(Node, VarType, ConstWidthHint)
+182 -20
View File
@@ -9,12 +9,22 @@ import llvmlite.ir as ir
from lib.core.Exportable import EnumMember as ExportEnumMember
from lib.core.Handles.HandlesBase import BaseHandle, CTypeInfo, FuncMeta
from lib.core.SymbolUtils import (
IsTModule, FindStructNameInAnnotationWithContext, MangleTypeArgByRegistry)
IsTModule, FindStructNameInAnnotationWithContext, MangleTypeArgByRegistry,
EnumClassIntWidth,
EnumBodyMemberNames, EnumMemberCountMismatchError,
ClassHasFuncLevelCVTable, IsVtableSlotMethod)
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _config_mode
from lib.includes import t
from lib.includes.t import CTypeRegistry
# 枚举成员不允许的指针形态名:t.CPtr 家族(CPtr/CNeedPtr/CAutoPtr,后两者继承
# CPtr)、内建 rawptr(= t.CPtr)、str/bytes(= i8*)。枚举值是整数常量,
# 指针成员语义不成立,命中即明确报错(不静默当 int)。
_ENUM_PTR_ANNOTATION_NAMES: frozenset[str] = frozenset({
'CPtr', 'CNeedPtr', 'CAutoPtr', 'rawptr', 'str', 'bytes',
})
class ClassHandle(BaseHandle):
def _is_exception_class(self, Node: ast.ClassDef) -> bool:
@@ -438,11 +448,15 @@ class ClassHandle(BaseHandle):
return 'struct'
return None
def _resolve_class_flags(self, Node: ast.ClassDef, Gen: LlvmGeneratorMixin) -> tuple[bool, bool, str | None, bool]:
def _resolve_class_flags(self, Node: ast.ClassDef, Gen: LlvmGeneratorMixin) -> tuple[bool, bool, str | None, bool, bool]:
"""解析类的 Object/CVTable 标志和父类信息
Returns:
tuple: (IsCpythonObject, IsCVTable, ParentClassName, IsNoVTable)
tuple: (IsCpythonObject, IsCVTable, ParentClassName, IsNoVTable,
VtableByClassLevel)
VtableByClassLevel:**忽略方法级 CVTable 标记**算出的 vtable 状态(= 类级
CVTable / 继承链判定),供 vtable 槽筛选(SymbolUtils.IsVtableSlotMethod)
使用——与 TPV _should_method_be_virtual 第 3/4 条的判据一致。
"""
ClassName: str = Node.name
IsCpythonObject: bool = False
@@ -536,6 +550,15 @@ class ClassHandle(BaseHandle):
or ParentClassName in Gen.structs)
if parent_has_vtable or not parent_known:
IsCVTable = True
# 方法级 virtual(`virtual def m(...)` ⇒ 方法装饰器 @t.CVTable)触发类级 vtable:
# 与 TPV ClassGenBase._detect_vtable_status 的三条 `_has_any_func_cvtable`
# 分支逐条对齐(类级 NoVTable / 父类 NoVTable / 无真实父类)。
# 注意"父类已知且无 vtable" **不**触发(TPV 同分支返回 0)——
# 父类按无 vptr 布局而子类插 vptr 会让继承方法字段错位。
VtableByClassLevel: bool = IsCVTable
if (not VtableByClassLevel and ClassHasFuncLevelCVTable(Node)
and (IsNoVTable or ParentIsNoVTable or not HasParentClass)):
IsCVTable = True
if IsCVTable:
Gen.class_vtable.add(ClassName)
# 沿继承链向上传播,将所有祖先类加入 vtable
@@ -557,7 +580,7 @@ class ClassHandle(BaseHandle):
else:
# 类图中没有,尝试从 class_parent 查询
ancestor = Gen.class_parent.get(ancestor)
return IsCpythonObject, IsCVTable, ParentClassName, IsNoVTable
return IsCpythonObject, IsCVTable, ParentClassName, IsNoVTable, VtableByClassLevel
def _RegisterGenericClassTemplate(self, Node: ast.ClassDef) -> None:
"""注册泛型类模板到 `_generic_class_templates`(判据唯一:`_is_generic_class`)。
@@ -651,7 +674,8 @@ class ClassHandle(BaseHandle):
IsCVTable: bool
ParentClassName: str | None
IsNoVTable: bool
IsCpythonObject, IsCVTable, ParentClassName, IsNoVTable = self._resolve_class_flags(Node, Gen)
VtableByClassLevel: bool
IsCpythonObject, IsCVTable, ParentClassName, IsNoVTable, VtableByClassLevel = self._resolve_class_flags(Node, Gen)
# 跟踪正在发射的类,防止递归特化时 _generate_structs 过早为空成员类设置 body
Gen._classes_in_progress.add(ClassName)
# 标记正在收集字段,阻止继承处理(_ResolveGenericSlice → _specialize_generic_class)
@@ -685,7 +709,9 @@ class ClassHandle(BaseHandle):
_ck = (id(Gen), ClassName)
if _ck in _ih._struct_Load_cache:
del _ih._struct_Load_cache[_ck]
Gen.class_members[ClassName] = []
Gen.class_members[ClassName] = []
# 本地定义是权威重建点:先撤销"已展平"登记,重建完成(且父类齐全)后再登记。
Gen._members_flattened.discard(ClassName)
Gen.class_member_defaults[ClassName] = {}
Gen.class_member_signeds[ClassName] = {}
Gen.class_member_bitfields[ClassName] = {}
@@ -693,6 +719,9 @@ class ClassHandle(BaseHandle):
Gen.class_member_bitoffsets[ClassName] = {}
ParentClass: str | None = None
ParentClasses: list[str] = []
# 本类展平是否完整(父类若在此刻尚未就绪会被跳过 ⇒ 不登记已展平,
# 留待查询侧 HandlesImports.EnsureClassMembersFlattened 补齐,顺序无关)。
_local_flat_complete: bool = True
if Node.bases:
class_graph: dict = Gen._class_graph
for base in Node.bases:
@@ -784,6 +813,9 @@ class ClassHandle(BaseHandle):
_flat: list[tuple] = []
for ParentClass in ParentClasses:
if ParentClass not in class_graph and ParentClass not in Gen.class_members:
# 父类尚未就绪(跨模块父类在此刻未加载 / 特化表未生成)⇒
# 本类展平不完整,不登记 _members_flattened。
_local_flat_complete = False
continue
_FlattenInherited(ParentClass, _seen_cls, _flat, Gen)
if _flat:
@@ -871,6 +903,10 @@ class ClassHandle(BaseHandle):
if parsed:
VarName, MemberType, TypeInfo = parsed
Gen.class_members[ClassName].append((VarName, MemberType))
# const / volatile 类字段登记(统一登记入口,见 BaseGen)。
# 键 `类名.字段名` + 裸字段名;与 TransPyV 侧 ClassGenDef 的
# FieldEntry.IsConst/IsVolatile/PointeeVolatile 同口径。
Gen.RegisterFieldQualifiers(ClassName, VarName, TypeInfo, item.annotation)
if TypeInfo:
if isinstance(MemberType, ir.PointerType) and isinstance(MemberType.pointee, ir.IntType) and MemberType.pointee.width == 8:
if isinstance(item.annotation, ast.BinOp) and isinstance(item.annotation.op, ast.BitOr):
@@ -1025,14 +1061,30 @@ class ClassHandle(BaseHandle):
# 构造函数(__new__/__init__)不放入 vtable,避免子类与基类
# vtable 大小不一致导致虚分派索引偏移。__before_init__ 由 TPC
# 自动生成,不会出现在 FunctionDef 中,此处一并防御。
#
# 方法级修饰符(`novirtual def m(...)` ⇒ @t.NoVTable /
# `virtual def m(...)` ⇒ @t.CVTable):是否占槽按
# SymbolUtils.IsVtableSlotMethod 判定——与 TPV
# ClassGenBase._should_method_be_virtual 同判据(**唯一规则源**),
# 故 TPC/TPV 的虚表槽位顺序与数量逐槽一致(含"类级 NoVTable +
# 方法级 CVTable ⇒ 仍生成虚表且只有该方法进槽"这一组合形态)。
# 仅在本类确有 vtable(IsCVTable)时筛选:无 vtable 的类不进
# Vtables,class_methods 仍保留全部方法供静态解析/继承包装使用。
if MethodName not in ('__new__', '__init__', '__before_init__'):
if FullMethodName not in Gen.class_methods[ClassName]:
_IsSlot: bool = (not IsCVTable) or IsVtableSlotMethod(item, VtableByClassLevel)
if _IsSlot and FullMethodName not in Gen.class_methods[ClassName]:
Gen.class_methods[ClassName].append(FullMethodName)
Gen.register_method(ClassName, FullMethodName)
Gen.register_method(ClassName, FullMethodName, _IsSlot)
else:
Gen.register_method(ClassName, FullMethodName)
if IsCVTable:
Gen.class_vtable.add(ClassName)
Gen._generate_structs()
Gen._create_Vtable_globals()
if _local_flat_complete:
# 登记"该表已按父类展平":供 _TryLoadClassMembersFromPyi 的 guard 与
# FuncGen._member_base 的查询前补齐使用(与跨模块路径同一口径)。
Gen._members_flattened.add(ClassName)
# _create_Vtable_globals 会跳过正在发射中的类(方法列表可能不完整),
# 当前类的方法已全部注册(L559-571),此处显式创建其 vtable
if IsCVTable:
@@ -1333,11 +1385,10 @@ class ClassHandle(BaseHandle):
if union_member_types:
max_size: int = 0
for member_type in union_member_types:
try:
size: int = member_type.get_abi_size(ir.DataLayout(Gen.module.data_layout))
except Exception as e: # 回退:获取类型大小失败时使用默认值 8
_vlog().warning(f"HandlesClassDef: 忽略异常 {e}", exc_info=e)
size = 8
# 统一走 Gen._get_struct_size(与 __sizeof__ / memhub alloc 同源)。
# llvmlite 并无 ir.DataLayout 类,原先那行必然抛 AttributeError,
# 再被 except 吞成 size=8 —— 静默少算,union 载荷区被开得过小。
size: int = Gen._get_struct_size(member_type)
max_size = max(max_size, size)
union_type: ir.IdentifiedStructType = ir.IdentifiedStructType(Gen.module, ClassName)
union_type.set_body(ir.ArrayType(ir.IntType(8), max_size))
@@ -1498,19 +1549,94 @@ class ClassHandle(BaseHandle):
# 更新预注册的 REnum 类型信息(填充变体列表)
PreTypeInfo.RenumVariants = variant_names
def _RegisterEnumMembers(self, Node: ast.ClassDef) -> None:
ClassName: str = Node.name
EnumTypeInfo: CTypeInfo = CTypeInfo()
EnumTypeInfo.Name = ClassName
EnumTypeInfo.BaseType = t.CEnum(ClassName)
EnumTypeInfo.IsEnum = True
def _EnumMemberAnnotationIsPtr(self, annotation: ast.AST | None) -> bool:
"""枚举成员注解是否含指针形态(逐层下标 + 联合类型逐边),True=含指针
与 TPV 侧 ClassGenEnum._enum_member_annotation_is_ptr 同口径。枚举值是
整数常量,指针成员语义不成立,必须明确报错(fail-fast)而非静默接受:
此前 TPC 完全忽略成员注解,`Name: t.CPtr` 会被当成普通 i32 成员放行。
指针标记既按名字匹配(含 str/bytes = i8*、rawptr = t.CPtr),也按
from t import CPtr as P 这类别名经 _t_type_symbols 还原后判定。
"""
if annotation is None:
return False
if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
return (self._EnumMemberAnnotationIsPtr(annotation.left)
or self._EnumMemberAnnotationIsPtr(annotation.right))
if isinstance(annotation, ast.Subscript):
return self._EnumMemberAnnotationIsPtr(annotation.value)
name: str | None = None
if isinstance(annotation, ast.Attribute):
name = annotation.attr
elif isinstance(annotation, ast.Name):
name = annotation.id
if name is None:
return False
if name in _ENUM_PTR_ANNOTATION_NAMES:
return True
symtab: object = self.Trans.SymbolTable
resolved: type | None = None
if hasattr(symtab, '_t_type_symbols'):
resolved = symtab._t_type_symbols.get(name)
if resolved is None:
resolved = getattr(t, name, None)
if isinstance(resolved, type) and issubclass(resolved, t.CPtr):
return True
return False
def _EnumClassIntWidth(self, Node: ast.ClassDef) -> int:
"""按"最宽成员类型"推导枚举存储宽度(bit)。
规则本体唯一:SymbolUtils.EnumClassIntWidth(定义端 / 跨模块 `.vpi` 读取端 /
符号表提取端 / 声明端共用同一函数),此处只注入本端 SymbolTable
(覆盖库内自定义 typedef 与 builtins 表)。无任何可解析为整数的成员
→ 默认 32——既有枚举全部落在此支,与改前的恒 i32 逐字一致(零行为变化)。
"""
return EnumClassIntWidth(Node.body, self.Trans.SymbolTable)
def _EnumSiteLoc(self, Node: ast.AST) -> str:
"""枚举 fail-fast 站点:`文件:行[:列]`(行号经 `_line_offset` 还原)+ 源码片段。
口径与 `_FailFastFixedArrayKeyword` / `SymbolUtils._DefineSiteInfo` 一致。
"""
Gen: object = getattr(self.Trans, 'LlvmGen', None)
lineno: int = int(getattr(Node, 'lineno', 0) or 0)
Offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if Offset > 0 and lineno > Offset:
lineno -= Offset
col: int = int(getattr(Node, 'col_offset', -1))
Loc: str = getattr(Gen, '_current_source_file', '') or ''
Loc += f":{lineno}"
if col >= 0:
Loc += f":{col}"
Source: str = ''
if lineno > 0 and Gen is not None and hasattr(Gen, '_get_source_line'):
try:
Source = (Gen._get_source_line(lineno) or '').strip()
except Exception:
Source = ''
return Loc + (f",源码: {Source}" if Source else '')
def _RegisterEnumMembers(self, Node: ast.ClassDef) -> None:
ClassName: str = Node.name
# 枚举存储宽度 = 最宽成员类型(先推导再登记;成员注册顺序无关)。
# 登记到枚举条目与每个成员条目上,供常量发射点与类型解析点同点读取
# (两侧 ABI:常量宽度必须与枚举类型宽度一致)。
EnumWidth: int = self._EnumClassIntWidth(Node)
EnumTypeInfo: CTypeInfo = CTypeInfo()
EnumTypeInfo.Name = ClassName
EnumTypeInfo.BaseType = t.CEnum(ClassName)
EnumTypeInfo.IsEnum = True
EnumTypeInfo.set('enum_width', EnumWidth)
self.Trans.SymbolTable.insert(ClassName, EnumTypeInfo)
enum_export = self.Trans.Exportable.add_enum(
name=ClassName,
lineno=Node.lineno,
is_public=True
)
next_enum_value: int = 0
next_enum_value: int = 0
# 已登记成员名(用于"可见名数 vs 登记数"兜底判据,见 SymbolUtils.EnumBodyMemberNames)
RegisteredNames: list[str] = []
for item in Node.body:
if isinstance(item, ast.Assign):
for target in item.targets:
@@ -1541,7 +1667,9 @@ class ClassHandle(BaseHandle):
MemberNode.EnumName = ClassName
MemberNode.Lineno = item.lineno
MemberNode.IsEnumMember = True
MemberNode.set('enum_width', EnumWidth)
self.Trans.SymbolTable.insert(VarName, MemberNode)
RegisteredNames.append(VarName)
enum_export.members.append(ExportEnumMember(
name=VarName,
value=value,
@@ -1550,6 +1678,19 @@ class ClassHandle(BaseHandle):
elif isinstance(item, ast.AnnAssign):
if isinstance(item.target, ast.Name):
VarName = item.target.id
# [枚举规格 fail-fast] 成员不允许指针类型(t.CPtr 家族 /
# rawptr[...] / str / bytes / 联合里夹带指针)——此前成员注解
# 被整体忽略,指针成员被静默当 i32 放行。
if self._EnumMemberAnnotationIsPtr(item.annotation):
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_lineno: int = int(getattr(item, 'lineno', 0) or 0)
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and _lineno > _line_offset:
_lineno -= _line_offset
raise TypeError(
f"[fast-fail] 枚举 '{ClassName}' 成员 '{VarName}' 不能是指针类型"
f"(枚举值只支持整数成员类型),line {_lineno}"
)
value = None
if item.value:
if isinstance(item.value, ast.Constant):
@@ -1575,12 +1716,33 @@ class ClassHandle(BaseHandle):
MemberNode.EnumName = ClassName
MemberNode.Lineno = item.lineno
MemberNode.IsEnumMember = True
MemberNode.set('enum_width', EnumWidth)
self.Trans.SymbolTable.insert(VarName, MemberNode)
RegisteredNames.append(VarName)
enum_export.members.append(ExportEnumMember(
name=VarName,
value=value,
lineno=item.lineno
))
# [兜底判据 fail-fast] "源码里可见的成员名字数" vs "实际登记成员数"。
# 任何注册器不认的成员写法(逗号组带值 `A, B = 1`、逗号组带注解
# `A, B: T`、单名尾随逗号 `A,`,以及未来出现的新形态)都会让可见名数 >
# 登记数——此前是**静默丢成员**(ASTKind 71→3、OpKind 32→2;与
# Debugger/enum-semantics.md §15、rawptr-migration-and-fastfail.md §27
# 同族,这是第三次复发),此处一律 fail-fast,不再静默跳过。
# 判据口径与 TPV ClassGenEnum._translate_enum_def 完全一致(同一规则源)。
Visible: list[tuple[str, object]] = EnumBodyMemberNames(Node.body)
if len(Visible) != len(RegisteredNames):
Rest: list[str] = list(RegisteredNames)
BadNode: object = Node
for Nm, Nd in Visible:
if Nm in Rest:
Rest.remove(Nm)
elif BadNode is Node:
BadNode = Nd
raise TypeError(EnumMemberCountMismatchError(
ClassName, Visible, RegisteredNames, self._EnumSiteLoc(BadNode),
int(getattr(self.Trans, '_line_offset', 0) or 0)))
def _EmitNewFunctionLlvm(self, ClassName: str, Gen: LlvmGeneratorMixin, IsCVTable: bool = False, IsCpythonObject: bool = False) -> None:
NewFuncName: str = f'{ClassName}.__before_init__'
+88 -19
View File
@@ -9,6 +9,19 @@ import llvmlite.ir as ir
from lib.core.Handles.HandlesBase import BaseHandle
from lib.constants.config import mode as _config_mode
from lib.includes.t import CTypeRegistry as _CR
from lib.core.SymbolUtils import RequireDefineUseWidthAt, RequireIntLiteralStorable, RequireNameDefined
def DefineUseWidthFromLLVMType(Ty: object) -> int | None:
"""LLVM 类型 → CDefine 常量的"使用处位宽"提示;其余 → None。
只认 i8/i16/i32/i64(与 SymbolUtils.DEFINE_WIDTH_LADDER 同集合):
i1(比较/逻辑结果)与指针不构成"整数使用处宽度",必须落回"取不到 ⇒
默认 32/64",否则 `C == 0` 之类会拿到位宽 1 而误报"使用处宽度不足"。
"""
if isinstance(Ty, ir.IntType) and Ty.width in (8, 16, 32, 64):
return int(Ty.width)
return None
class ExprHandle(BaseHandle):
@@ -45,14 +58,18 @@ class ExprHandle(BaseHandle):
def _get_llvm_member_offset(self, field_name: str, ClassName: str, Gen: LlvmCodeGenerator) -> int | None:
return self.Trans.ExprAttrHandle._get_llvm_member_offset(field_name, ClassName, Gen)
def HandleExprLlvm(self, Node: ast.AST, VarType: ir.Type | str | None = None) -> ir.Value | None:
def HandleExprLlvm(self, Node: ast.AST, VarType: ir.Type | str | None = None,
ConstWidthHint: int | None = None) -> ir.Value | None:
Gen: LlvmCodeGenerator = self.Trans.LlvmGen
if not Gen or not Gen.builder:
return None
if isinstance(Node, ast.Constant):
return self._HandleConstantLlvm(Node, VarType)
elif isinstance(Node, ast.Name):
return self._HandleNameLlvm(Node, VarType)
# ConstWidthHint 是**窄通道**:只有 Name(CDefine 常量物化)读取它,
# 其余分支(字面量/列表/运算…)逐字沿用既有 VarType 语义 ⇒ 非常量
# 路径的既有 IR 不变。
return self._HandleNameLlvm(Node, VarType, ConstWidthHint)
elif isinstance(Node, ast.BinOp):
return self.Trans.ExprOpsHandle._HandleBinOpLlvm(Node, VarType)
elif isinstance(Node, ast.BoolOp):
@@ -161,25 +178,30 @@ class ExprHandle(BaseHandle):
elif elem_type == 'double':
byte_offset = i * 8
ptr_as_int: Any = Gen.builder.ptrtoint(cast_ptr, ir.IntType(64))
new_ptr_val: Any = Gen.builder.add(ptr_as_int, ir.Constant(ir.IntType(64), byte_offset))
# 元素地址:以 i8* 基址按字节 GEP,再转成元素指针类型
# (不再 ptr→uint64 手动加偏移再 inttoptr,见新规范第 10 条)
elem_addr: Any = Gen.builder.gep(cast_ptr, [ir.Constant(ir.IntType(64), byte_offset)], name=f"elem_addr_{i}")
if elem_type == 'i32':
elem_ptr: Any = Gen.builder.inttoptr(new_ptr_val, ir.PointerType(ir.IntType(32)))
elem_ptr: Any = Gen.builder.bitcast(elem_addr, ir.PointerType(ir.IntType(32)))
elif elem_type == 'i16':
elem_ptr = Gen.builder.inttoptr(new_ptr_val, ir.PointerType(ir.IntType(16)))
elem_ptr = Gen.builder.bitcast(elem_addr, ir.PointerType(ir.IntType(16)))
elif elem_type == 'float':
elem_ptr = Gen.builder.inttoptr(new_ptr_val, ir.PointerType(ir.FloatType()))
elem_ptr = Gen.builder.bitcast(elem_addr, ir.PointerType(ir.FloatType()))
elif elem_type == 'double':
elem_ptr = Gen.builder.inttoptr(new_ptr_val, ir.PointerType(ir.DoubleType()))
elem_ptr = Gen.builder.bitcast(elem_addr, ir.PointerType(ir.DoubleType()))
else:
elem_ptr = Gen.builder.inttoptr(new_ptr_val, ir.PointerType(ir.IntType(8)))
elem_ptr = elem_addr
if elem_type == 'i8':
if isinstance(elem_val.type, ir.IntType) and elem_val.type.width > 8:
elem_val = Gen.builder.trunc(elem_val, ir.IntType(8), name=f"trunc_elem_{i}")
elif isinstance(elem_val.type, ir.PointerType):
elem_val = Gen.builder.ptrtoint(elem_val, ir.IntType(64), name=f"ptrtoint_elem_{i}")
elem_val = Gen.builder.trunc(elem_val, ir.IntType(8), name=f"trunc_elem_{i}")
# 指针 → 整数:隐式转换一律禁止(元素值是指针、元素类型是整数)
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"指针隐式转整数已禁止:数组字面量第 {i} 个元素是指针类型 {elem_val.type},"
f"但目标元素类型是 i8;如需转换请显式 cast(如 t.CUInt64T(p) / t.CInt(p))"
)
elif elem_type == 'i32':
if isinstance(elem_val.type, ir.IntType):
if elem_val.type.width < 32:
@@ -187,7 +209,12 @@ class ExprHandle(BaseHandle):
elif elem_val.type.width > 32:
elem_val = Gen.builder.trunc(elem_val, ir.IntType(32), name=f"trunc_elem_{i}")
elif isinstance(elem_val.type, ir.PointerType):
elem_val = Gen.builder.ptrtoint(elem_val, ir.IntType(32), name=f"ptrtoint_elem_{i}")
# 指针 → 整数:隐式转换一律禁止(ptrtoint 到 i32 还会截断地址)
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"指针隐式转整数已禁止:数组字面量第 {i} 个元素是指针类型 {elem_val.type},"
f"但目标元素类型是 i32;如需转换请显式 cast(如 t.CUInt64T(p) / t.CInt(p))"
)
elif elem_type == 'i16':
if isinstance(elem_val.type, ir.IntType):
if elem_val.type.width < 16:
@@ -221,6 +248,10 @@ class ExprHandle(BaseHandle):
elif isinstance(Value, bool):
return ir.Constant(ir.IntType(32), 1 if Value else 0)
elif isinstance(Value, int):
# 整数字面量越界 fail-fast(字面量本身无符号,0..2^64-1):与 TPV 词法层
# `_lex_int_exceeds_u64` 同口径。负数侧由"一元减 + 字面量"的联合判定负责
# (HandlesExprOps._HandleUnaryOpLlvm),此处只判字面量本身。
RequireIntLiteralStorable(Gen, self.Trans, Node, Value)
if VarType is not None:
if isinstance(VarType, ir.IntType):
return ir.Constant(VarType, Value & ((1 << VarType.width) - 1))
@@ -327,7 +358,8 @@ class ExprHandle(BaseHandle):
return ptr
return ir.Constant(ir.IntType(32), 0)
def _HandleNameLlvm(self, Node: ast.Name, VarType: ir.Type | str | None = None) -> ir.Value:
def _HandleNameLlvm(self, Node: ast.Name, VarType: ir.Type | str | None = None,
ConstWidthHint: int | None = None) -> ir.Value:
Gen: LlvmCodeGenerator = self.Trans.LlvmGen
VarName: str = Node.id
# 治本修复:不把 module.globals 的 initializer 注入 _define_constants。
@@ -343,6 +375,9 @@ class ExprHandle(BaseHandle):
# 跳过 _define_constants 查询,直接走下方 variables/global_vars/module.globals 的 load。
if VarName in define_constants and not (hasattr(Gen, 'module') and VarName in Gen.module.globals):
val: Any = define_constants[VarName]
# CDefine 常量注册值(用于使用处宽度判据;下方 _gvar 覆盖是"同名全局变量"
# 的既有特例,不应把变量值当常量做宽度判据)
_cdefine_val: Any = val
# 治本修复:本模块全局变量初始化值优先,避免跨模块同名 CDefine 通过
# 共享 _define_constants 覆盖本模块常量
# (VpiReader.SHA1_LEN=16 被 Phase1.SHA1_LEN=17 覆盖导致双点符号 bug)
@@ -357,9 +392,28 @@ class ExprHandle(BaseHandle):
if isinstance(_init, ir.Constant) and isinstance(_init.constant, (int, float, str)):
val = _init.constant
if isinstance(val, int):
# Use VarType hint if available to avoid unnecessary type mismatch
if VarType is not None and isinstance(VarType, ir.IntType):
return ir.Constant(VarType, val & ((1 << VarType.width) - 1))
# 使用处宽度(唯一判据 SymbolUtils.RequireDefineUseWidth):
# 上下文(赋值目标 / 形参 / 返回注解 / 运算对象…)给出的位宽更宽 ⇒
# 就地按该宽度物化(立即数升宽无损);更窄 ⇒ fail-fast(替换此前的
# `val & mask` 静默截断)。
# 宽度来源优先级:**窄通道 ConstWidthHint**(仅常量物化读取,由调用
# 装配处按形参/返回类型下传)> 既有尽宽通道 VarType(赋值目标 /
# 二元运算对象)。两者都取不到 ⇒ 落到下方既有 32/64 二值口径。
_use_ty: ir.Type | None = None
# 报文的"使用处"标签按**通道**取真实站点种类(不再写笼统的
# "由调用方下传的类型提示"):窄通道 ConstWidthHint 只由
# 调用装配处(形参)与返回处下传;既有 VarType 通道承载的是
# 赋值目标 / 二元运算对象类型。
_use_ctx: str = '赋值目标 / 运算对象类型'
if ConstWidthHint in (8, 16, 32, 64):
_use_ty = ir.IntType(int(ConstWidthHint))
_use_ctx = '形参 / 返回注解'
elif VarType is not None and isinstance(VarType, ir.IntType):
_use_ty = VarType
if isinstance(_use_ty, ir.IntType):
RequireDefineUseWidthAt(Gen, self.Trans, Node, _cdefine_val,
int(_use_ty.width), VarName, _use_ctx)
return ir.Constant(_use_ty, val & ((1 << _use_ty.width) - 1))
if VarName in Gen.var_signedness and Gen.var_signedness[VarName]:
if val < -(1 << 31) or val > 0xFFFFFFFF:
return ir.Constant(ir.IntType(64), val & 0xFFFFFFFFFFFFFFFF)
@@ -384,8 +438,20 @@ class ExprHandle(BaseHandle):
if VarName in all_dc:
val: Any = all_dc[VarName]
if isinstance(val, int):
if VarType is not None and isinstance(VarType, ir.IntType):
return ir.Constant(VarType, val & ((1 << VarType.width) - 1))
# 跨模块 CDefine 常量同口径:窄通道 ConstWidthHint(形参/返回
# 类型下传)优先,其次既有 VarType;更宽 ⇒ 按该宽度物化,
# 更窄 ⇒ fail-fast(判据唯一源 SymbolUtils.RequireDefineUseWidth)
_use_ty2: ir.Type | None = None
_use_ctx2: str = '赋值目标 / 运算对象类型'
if ConstWidthHint in (8, 16, 32, 64):
_use_ty2 = ir.IntType(int(ConstWidthHint))
_use_ctx2 = '形参 / 返回注解'
elif VarType is not None and isinstance(VarType, ir.IntType):
_use_ty2 = VarType
if isinstance(_use_ty2, ir.IntType):
RequireDefineUseWidthAt(Gen, self.Trans, Node, val,
int(_use_ty2.width), VarName, _use_ctx2)
return ir.Constant(_use_ty2, val & ((1 << _use_ty2.width) - 1))
if val < -(1 << 31) or val > 0xFFFFFFFF:
return ir.Constant(ir.IntType(64), val & 0xFFFFFFFFFFFFFFFF)
return ir.Constant(ir.IntType(32), val & 0xFFFFFFFF)
@@ -460,7 +526,7 @@ class ExprHandle(BaseHandle):
SymInfo: Any = self.Trans.SymbolTable.lookup(VarName)
if SymInfo:
if SymInfo.IsEnumMember and isinstance(SymInfo.value, int):
return ir.Constant(ir.IntType(32), SymInfo.value)
return ir.Constant(Gen._enum_int_type(SymInfo.EnumName, SymInfo), SymInfo.value)
if SymInfo.IsFunction or SymInfo.IsFuncPtr:
MangledName: str = Gen._mangle_func_name(VarName)
if MangledName in Gen.module.globals:
@@ -487,4 +553,7 @@ class ExprHandle(BaseHandle):
)
ast.copy_location(virtual_attr, Node)
return self.Trans.ExprAttrHandle._HandleAttributeLlvm(virtual_attr)
# fail-fast: 未定义名称禁止静默物化为 i32 0(会把常量名写错 / define 丢失 /
# 变量漏声明这类上游缺陷藏到运行期,见 RequireNameDefined 的头注释)。
RequireNameDefined(Gen, self.Trans, Node, VarName)
return ir.Constant(ir.IntType(32), 0)
+93 -65
View File
@@ -201,6 +201,34 @@ class ExprAsmHandle(BaseHandle):
stripped.append('')
return '\n'.join([first_line] + stripped)
@staticmethod
def _intrinsic_marker(expr: ast.AST) -> str | None:
"""识别内建操作数标记调用,返回规范名或 None
支持两种等价写法(裸名写法省去 c. 前缀):
- 内联汇编:c.AsmInp / c.AsmOut ↔ asminp / asmout
- LLVM IR:c.LInp / c.LOut ↔ irinp / irout
"""
if not isinstance(expr, ast.Call):
return None
func: ast.AST = expr.func
if isinstance(func, ast.Name):
if func.id == 'asminp':
return 'AsmInp'
if func.id == 'asmout':
return 'AsmOut'
if func.id == 'irinp':
return 'LInp'
if func.id == 'irout':
return 'LOut'
return None
if isinstance(func, ast.Attribute):
if isinstance(func.value, ast.Name) and func.value.id == 'c':
if func.attr in ('AsmInp', 'AsmOut', 'LInp', 'LOut'):
return func.attr
return None
return None
def _process_joined_str_template(self, node: ast.AST, out_input_ops: list[Any], out_input_constraints: list[str],
out_output_ops: list[Any], out_output_constraints: list[str]) -> str:
if not isinstance(node, ast.JoinedStr):
@@ -214,39 +242,40 @@ class ExprAsmHandle(BaseHandle):
raw_parts.append(('text', value.value))
elif isinstance(value, ast.FormattedValue):
expr: ast.AST = value.value
if isinstance(expr, ast.Call) and isinstance(expr.func, ast.Attribute):
if isinstance(expr.func.value, ast.Name) and expr.func.value.id == 'c':
if expr.func.attr == 'AsmInp':
if expr.args:
v: Any = self.HandleExprLlvm(expr.args[0])
if v:
c: str = 'r'
if len(expr.args) >= 2:
c = self._parse_asm_descr(expr.args[1])
if not c:
c = 'r'
out_input_ops.append(v)
out_input_constraints.append(c)
raw_parts.append(('input', len(out_input_ops) - 1))
elif expr.func.attr == 'AsmOut':
if expr.args:
target_ptr: Any = self._get_var_ptr_for_asm(expr.args[0])
if target_ptr:
v = target_ptr
else:
v = self.HandleExprLlvm(expr.args[0])
if v:
c: str = '=r'
if len(expr.args) >= 2:
c = self._parse_asm_descr(expr.args[1])
if not c:
c = '=r'
if not c.startswith('='):
c = '=' + c
out_output_ops.append(v)
out_output_constraints.append(c)
raw_parts.append(('output', len(out_output_ops) - 1))
else:
_marker: str | None = self._intrinsic_marker(expr)
if _marker == 'AsmInp':
if expr.args:
v: Any = self.HandleExprLlvm(expr.args[0])
if v:
c: str = 'r'
if len(expr.args) >= 2:
c = self._parse_asm_descr(expr.args[1])
if not c:
c = 'r'
out_input_ops.append(v)
out_input_constraints.append(c)
raw_parts.append(('input', len(out_input_ops) - 1))
elif _marker == 'AsmOut':
if expr.args:
target_ptr: Any = self._get_var_ptr_for_asm(expr.args[0])
if target_ptr:
v = target_ptr
else:
v = self.HandleExprLlvm(expr.args[0])
if v:
c: str = '=r'
if len(expr.args) >= 2:
c = self._parse_asm_descr(expr.args[1])
if not c:
c = '=r'
if not c.startswith('='):
c = '=' + c
out_output_ops.append(v)
out_output_constraints.append(c)
raw_parts.append(('output', len(out_output_ops) - 1))
elif not (isinstance(expr, ast.Call) and isinstance(expr.func, ast.Attribute)):
# 非 c.* 属性调用的表达式按普通输入操作数处理(保持原行为:
# c.* 属性调用中非 AsmInp/AsmOut 的形态不收集)
fallback: Any = self.HandleExprLlvm(expr)
if fallback and isinstance(fallback.type, ir.IntType):
out_input_ops.append(fallback)
@@ -316,25 +345,24 @@ class ExprAsmHandle(BaseHandle):
if output_arg and isinstance(output_arg, (ast.List, ast.Tuple)):
for item in output_arg.elts:
if isinstance(item, ast.Call) and isinstance(item.func, ast.Attribute):
if item.func.value.id == 'c' and item.func.attr == 'AsmOut':
if len(item.args) >= 1:
target_ptr: Any = self._get_var_ptr_for_asm(item.args[0])
if target_ptr:
output_ops.append(target_ptr)
else:
value: Any = self.HandleExprLlvm(item.args[0])
if value:
output_ops.append(value)
constraint: str = '=r'
if len(item.args) >= 2:
base_constraint: str = self._parse_asm_descr(item.args[1])
if base_constraint:
if base_constraint.startswith('='):
constraint = base_constraint
else:
constraint = '=' + base_constraint
output_constraints.append(constraint)
if self._intrinsic_marker(item) == 'AsmOut':
if len(item.args) >= 1:
target_ptr: Any = self._get_var_ptr_for_asm(item.args[0])
if target_ptr:
output_ops.append(target_ptr)
else:
value: Any = self.HandleExprLlvm(item.args[0])
if value:
output_ops.append(value)
constraint: str = '=r'
if len(item.args) >= 2:
base_constraint: str = self._parse_asm_descr(item.args[1])
if base_constraint:
if base_constraint.startswith('='):
constraint = base_constraint
else:
constraint = '=' + base_constraint
output_constraints.append(constraint)
raw_template: str
if isinstance(asm_template_arg, ast.JoinedStr):
@@ -360,18 +388,17 @@ class ExprAsmHandle(BaseHandle):
if input_arg and isinstance(input_arg, (ast.List, ast.Tuple)):
for item in input_arg.elts:
if isinstance(item, ast.Call) and isinstance(item.func, ast.Attribute):
if item.func.value.id == 'c' and item.func.attr == 'AsmInp':
if len(item.args) >= 1:
value: Any = self.HandleExprLlvm(item.args[0])
if value:
input_ops.append(value)
constraint: str = 'r'
if len(item.args) >= 2:
constraint = self._parse_asm_descr(item.args[1])
if not constraint:
constraint = 'r'
input_constraints.append(constraint)
if self._intrinsic_marker(item) == 'AsmInp':
if len(item.args) >= 1:
value: Any = self.HandleExprLlvm(item.args[0])
if value:
input_ops.append(value)
constraint: str = 'r'
if len(item.args) >= 2:
constraint = self._parse_asm_descr(item.args[1])
if not constraint:
constraint = 'r'
input_constraints.append(constraint)
elif isinstance(item, (ast.List, ast.Tuple)) and len(item.elts) >= 2:
value_node: ast.AST = item.elts[0]
descr_node: ast.AST = item.elts[1]
@@ -565,7 +592,8 @@ class ExprAsmHandle(BaseHandle):
expr.value.attr == 'ASM_DESCR'):
AttrName: str = expr.attr
return getattr(t.ASM_DESCR, AttrName, "")
elif isinstance(expr.value, ast.Name) and IsTModule(expr.value.id, self.Trans.SymbolTable):
elif isinstance(expr.value, ast.Name) and (expr.value.id == 'ASM_DESCR' or IsTModule(expr.value.id, self.Trans.SymbolTable)):
# 裸名 ASM_DESCR.XXX:省去 t. 前缀的等价写法
AttrName: str = expr.attr
return getattr(t.ASM_DESCR, AttrName, "")
+63 -29
View File
@@ -199,6 +199,32 @@ class ExprAttrHandle(BaseHandle):
return self._make_define_constant(Gen, define_constants[key])
return None
def _NormalizeModuleGlobalValue(self, Gen: LlvmCodeGenerator, Val: Any, AttrName: str) -> Any:
"""模块限定全局读取归一化:类指针全局的「多一层」元素类型还原为对象指针
`Mod.g`(g 为模块级全局)的取值统一经 _lookup_module_global:注解为
`X | t.CPtr` 的模块级全局(如 GenApi._g_gen: rawptr[Generator.Generator]、
VLogger.logger: Logger | t.CPtr)在本编译器里物化成「比对象多一层」的
元素类型 T**(写入端见 main.vp `GenApi._g_gen = gen`:bitcast T*→T** 后
直接存值,未取地址)⇒ 其位形就是对象指针 T*,静态类型多了一层。
裸名形态(`g.Field`)由 _handle_attr_name 的既有同款补偿 bitcast 处理;
模块限定形态(`Mod.g.Field`)此前无补偿 ⇒ 载入值停在 T**,其 pointee 是
T*(指针而非结构体)⇒ _handle_attr_nested 的 char* 分支与 struct 分支都
不命中而返回 None ⇒ 带注解赋值 RHS 求值为空 fail-fast
(App/lib/core/Repl.vp:763 `GenApi._g_gen.TypeGenCDef.CDefineNames`)、
调用接收者 None 而静默丢调用(VpiReader.vp:4066
`GenApi._g_gen.ClassGenBase._collect_parents(...)`)。此处做同款逆运算:
只改静态类型、不改位形(bitcast 不产指令)。
判据限定「pointee 仍是指向结构体的指针」⇒ `str | t.CPtr` 一类真双层指针
全局(@_argv / @_g_pyi_cdef_names = i8**,实参直传 i8** 形参)不受影响。
"""
if not isinstance(Val, ir.Value) or not isinstance(Val.type, ir.PointerType):
return Val
Inner: Any = Val.type.pointee
if isinstance(Inner, ir.PointerType) and isinstance(Inner.pointee, (ir.IdentifiedStructType, ir.LiteralStructType)):
return Gen.builder.bitcast(Val, Inner, name=f"cast_{AttrName}_global")
return Val
def _lookup_module_global(self, Gen: LlvmCodeGenerator, VarName: str, AttrName: str) -> ir.Value | None:
"""在模块全局变量中查找 import 的属性"""
imported_modules: Any = getattr(self.Trans, '_ImportedModules', None)
@@ -220,7 +246,7 @@ class ExprAttrHandle(BaseHandle):
return GVar
if isinstance(GVar.type, ir.PointerType) and isinstance(GVar.type.pointee, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)):
return GVar
return Gen._load(GVar, name=AttrName)
return self._NormalizeModuleGlobalValue(Gen, Gen._load(GVar, name=AttrName), AttrName)
# SHA1 前缀查找
ModuleSha1Map: Any = getattr(Gen, 'ModuleSha1Map', {})
sha1_candidates: list[str] = [VarName, resolved_mod]
@@ -238,7 +264,7 @@ class ExprAttrHandle(BaseHandle):
return GVar2
if isinstance(GVar2.type, ir.PointerType) and isinstance(GVar2.type.pointee, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)):
return GVar2
return Gen._load(GVar2, name=AttrName)
return self._NormalizeModuleGlobalValue(Gen, Gen._load(GVar2, name=AttrName), AttrName)
if AttrName in Gen.module.globals:
gv: Any = Gen.module.globals[AttrName]
if AttrName in Gen.variables and Gen.variables[AttrName] is None:
@@ -246,7 +272,7 @@ class ExprAttrHandle(BaseHandle):
if str_gv_name in Gen.module.globals:
str_gv: Any = Gen.module.globals[str_gv_name]
return Gen.builder.gep(str_gv, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)], name=f"{AttrName}_str_ptr")
return Gen.builder.load(gv, name=AttrName)
return self._NormalizeModuleGlobalValue(Gen, Gen.builder.load(gv, name=AttrName), AttrName)
return None
def _resolve_var_classname(self, Gen: LlvmCodeGenerator, VarName: str) -> str | None:
@@ -373,6 +399,7 @@ class ExprAttrHandle(BaseHandle):
def _handle_attr_enum(self, Node: ast.Attribute, VarName: str) -> ir.Value | None:
"""处理枚举成员访问 (VarName 是枚举类型名)"""
Gen: LlvmCodeGenerator = self.Trans.LlvmGen
SymInfo: Any = self.Trans.SymbolTable.lookup(VarName)
if not SymInfo or not SymInfo.IsEnum:
return None
@@ -387,10 +414,10 @@ class ExprAttrHandle(BaseHandle):
for key, info in self.Trans.SymbolTable.items():
if info.IsEnumMember and info.EnumName == VarName and key == Node.attr:
if isinstance(info.value, int):
return ir.Constant(ir.IntType(32), info.value)
return ir.Constant(Gen._enum_int_type(VarName, info), info.value)
return None
if isinstance(MemberInfo.value, int):
return ir.Constant(ir.IntType(32), MemberInfo.value)
return ir.Constant(Gen._enum_int_type(VarName, MemberInfo), MemberInfo.value)
return None
def _handle_attr_name(self, Node: ast.Attribute, VarName: str) -> ir.Value | None:
@@ -520,12 +547,12 @@ class ExprAttrHandle(BaseHandle):
for qname in (f"{ClassName}.{AttrName}", f"{ClassName}_{AttrName}"):
info: Any = self.Trans.SymbolTable.lookup(qname)
if info and info.IsEnumMember and isinstance(info.value, int):
return ir.Constant(ir.IntType(32), info.value)
return ir.Constant(Gen._enum_int_type(ClassName, info), info.value)
for key, info in self.Trans.SymbolTable.items():
if key == AttrName:
if info.IsEnumMember and info.EnumName == ClassName:
if isinstance(info.value, int):
return ir.Constant(ir.IntType(32), info.value)
return ir.Constant(Gen._enum_int_type(ClassName, info), info.value)
break
# CUnion 处理
@@ -1105,6 +1132,11 @@ class ExprAttrHandle(BaseHandle):
def _HandleSubscriptLlvm(self, Node: ast.Subscript) -> ir.Value | None:
Gen: LlvmCodeGenerator = self.Trans.LlvmGen
# pointee-volatile(rawptr[t.CVolatile | T])基址:下标 GEP 名带基址名,
# 顶层 _load/_store 经 BaseGen._IsPtrVolatile 回查登记名,使其 load/store
# 带 LLVM volatile。基址名取不到时后缀为空 ⇒ GEP 名仍是 "subscript",
# 既有代码 IR 逐字节不变。
_PVSuffix: str = Gen._SubscriptSuffix(Node.value)
# 查询数组元素字节序
arr_byte_order: str = ''
if isinstance(Node.value, ast.Name) and Node.value.id in Gen.local_var_byteorders:
@@ -1178,7 +1210,7 @@ class ExprAttrHandle(BaseHandle):
if isinstance(ValueVal.type, ir.PointerType) and isinstance(ValueVal.type.pointee, ir.PointerType):
if isinstance(IndexVal.type, ir.IntType) and IndexVal.type.width < 64:
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(64), name="zext_ptr_index")
ElemPtr: Any = Gen.builder.gep(ValueVal, [IndexVal], name="subscript")
ElemPtr: Any = Gen.builder.gep(ValueVal, [IndexVal], name=f"subscript{_PVSuffix}")
if isinstance(ValueVal.type.pointee.pointee, ir.IntType) and ValueVal.type.pointee.pointee.width == 8:
return self._load_arr_elem_with_bswap(Gen, ElemPtr, arr_byte_order)
return self._load_arr_elem_with_bswap(Gen, ElemPtr, arr_byte_order)
@@ -1191,7 +1223,7 @@ class ExprAttrHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr = Gen.builder.gep(ValueVal, [zero, IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(ValueVal, [zero, IndexVal], name=f"subscript{_PVSuffix}")
elem_type: Any = pointee.element
if isinstance(elem_type, (ir.IdentifiedStructType, ir.LiteralStructType)):
return ElemPtr
@@ -1210,14 +1242,14 @@ class ExprAttrHandle(BaseHandle):
CastedPtrPtr: Any = Gen.builder.bitcast(ValueVal, ir.PointerType(ir.PointerType(ir.IntType(8))), name="cast_pp_str")
if isinstance(IndexVal.type, ir.IntType) and IndexVal.type.width < 64:
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(64), name="zext_pp_str_index")
ElemPtr = Gen.builder.gep(CastedPtrPtr, [IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(CastedPtrPtr, [IndexVal], name=f"subscript{_PVSuffix}")
return Gen._load(ElemPtr, name="str_ptr_load")
elem_class: str | None = self._infer_element_struct_class(Node.value, Gen)
if elem_class and elem_class in Gen.structs:
CastedPtr: Any = Gen.builder.bitcast(ValueVal, ir.PointerType(Gen.structs[elem_class]), name=f"cast_{elem_class}_arr")
if isinstance(IndexVal.type, ir.IntType) and IndexVal.type.width < 64:
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(64), name="zext_arr_index")
ElemPtr = Gen.builder.gep(CastedPtr, [IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(CastedPtr, [IndexVal], name=f"subscript{_PVSuffix}")
if isinstance(pointee, ir.IntType):
return self._load_arr_elem_with_bswap(Gen, ElemPtr, arr_byte_order)
return ElemPtr
@@ -1230,7 +1262,7 @@ class ExprAttrHandle(BaseHandle):
CastedPtrPtr = Gen.builder.bitcast(ValueVal, ir.PointerType(ir.PointerType(ir.IntType(8))), name="cast_pp_str")
if isinstance(IndexVal.type, ir.IntType) and IndexVal.type.width < 64:
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(64), name="zext_pp_str_index")
ElemPtr = Gen.builder.gep(CastedPtrPtr, [IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(CastedPtrPtr, [IndexVal], name=f"subscript{_PVSuffix}")
return Gen._load(ElemPtr, name="str_ptr_load")
arr_type: Any = self._infer_array_member_type(Node.value, Gen)
if arr_type and isinstance(arr_type, ir.ArrayType):
@@ -1241,12 +1273,12 @@ class ExprAttrHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr = Gen.builder.gep(arr_ptr, [zero, IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(arr_ptr, [zero, IndexVal], name=f"subscript{_PVSuffix}")
arr_elem_type: Any = arr_type.element
if isinstance(arr_elem_type, (ir.IdentifiedStructType, ir.LiteralStructType)):
return ElemPtr
return self._load_arr_elem_with_bswap(Gen, ElemPtr, arr_byte_order)
ElemPtr = Gen.builder.gep(ValueVal, [IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(ValueVal, [IndexVal], name=f"subscript{_PVSuffix}")
if isinstance(pointee, (ir.IdentifiedStructType, ir.LiteralStructType)):
return ElemPtr
return self._load_arr_elem_with_bswap(Gen, ElemPtr, arr_byte_order)
@@ -1262,7 +1294,7 @@ class ExprAttrHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr = Gen.builder.gep(VarPtr, [zero, IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(VarPtr, [zero, IndexVal], name=f"subscript{_PVSuffix}")
var_arr_elem_type: Any = VarPtr.type.pointee.element
if isinstance(var_arr_elem_type, (ir.IdentifiedStructType, ir.LiteralStructType)):
return ElemPtr
@@ -1275,7 +1307,7 @@ class ExprAttrHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr = Gen.builder.gep(AttrPtr, [zero, IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(AttrPtr, [zero, IndexVal], name=f"subscript{_PVSuffix}")
attr_arr_elem_type: Any = AttrPtr.type.pointee.element
if isinstance(attr_arr_elem_type, (ir.IdentifiedStructType, ir.LiteralStructType)):
return ElemPtr
@@ -1290,14 +1322,14 @@ class ExprAttrHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr = Gen.builder.gep(InnerPtr, [zero, IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(InnerPtr, [zero, IndexVal], name=f"subscript{_PVSuffix}")
inner_elem_type: Any = inner_pointee.element
if isinstance(inner_elem_type, (ir.IdentifiedStructType, ir.LiteralStructType)):
return ElemPtr
return self._load_arr_elem_with_bswap(Gen, ElemPtr, arr_byte_order)
arr_alloc: Any = Gen._allocaEntry(ValueVal.type, name="arr_subscript_tmp")
Gen._store(ValueVal, arr_alloc)
ElemPtr = Gen.builder.gep(arr_alloc, [zero, IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(arr_alloc, [zero, IndexVal], name=f"subscript{_PVSuffix}")
alloc_arr_elem_type: Any = ValueVal.type.element
if isinstance(alloc_arr_elem_type, (ir.IdentifiedStructType, ir.LiteralStructType)):
return ElemPtr
@@ -1306,6 +1338,8 @@ class ExprAttrHandle(BaseHandle):
def HandleSubscriptPtrLlvm(self, Node: ast.Subscript) -> ir.Value | None:
Gen: LlvmCodeGenerator = self.Trans.LlvmGen
# 同 _HandleSubscriptLlvm:pointee-volatile 基址名后缀(空后缀 ⇒ 名不变)
_PVSuffix: str = Gen._SubscriptSuffix(Node.value)
ValueVal: Any = self.HandleExprLlvm(Node.value)
if not ValueVal:
return None
@@ -1324,12 +1358,12 @@ class ExprAttrHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr: Any = Gen.builder.gep(ValueVal, [zero, IndexVal], name="subscript_ptr")
ElemPtr: Any = Gen.builder.gep(ValueVal, [zero, IndexVal], name=f"subscript_ptr{_PVSuffix}")
return ElemPtr
elif isinstance(pointee, ir.PointerType):
if isinstance(IndexVal.type, ir.IntType) and IndexVal.type.width < 64:
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(64), name="zext_ptr_index_ptr")
ElemPtr = Gen.builder.gep(ValueVal, [IndexVal], name="subscript_ptr")
ElemPtr = Gen.builder.gep(ValueVal, [IndexVal], name=f"subscript_ptr{_PVSuffix}")
return ElemPtr
else:
# 指针到 str/bytes 的变量 (BinOp 注解如 str | t.CPtr, bytes | t.CPtr)
@@ -1341,11 +1375,11 @@ class ExprAttrHandle(BaseHandle):
CastedPtrPtr: Any = Gen.builder.bitcast(ValueVal, ir.PointerType(ir.PointerType(ir.IntType(8))), name="cast_pp_str_ptr")
if isinstance(IndexVal.type, ir.IntType) and IndexVal.type.width < 64:
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(64), name="zext_pp_str_index_ptr")
ElemPtr = Gen.builder.gep(CastedPtrPtr, [IndexVal], name="subscript_ptr")
ElemPtr = Gen.builder.gep(CastedPtrPtr, [IndexVal], name=f"subscript_ptr{_PVSuffix}")
return ElemPtr
if isinstance(IndexVal.type, ir.IntType) and IndexVal.type.width < 64:
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(64), name="zext_ptr_index")
ElemPtr = Gen.builder.gep(ValueVal, [IndexVal], name="subscript_ptr")
ElemPtr = Gen.builder.gep(ValueVal, [IndexVal], name=f"subscript_ptr{_PVSuffix}")
return ElemPtr
elif isinstance(ValueVal.type, ir.IntType):
var_ptr: Any = self._get_int_ptr(Node.value)
@@ -1367,7 +1401,7 @@ class ExprAttrHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr = Gen.builder.gep(VarPtr, [zero, IndexVal], name="subscript_ptr")
ElemPtr = Gen.builder.gep(VarPtr, [zero, IndexVal], name=f"subscript_ptr{_PVSuffix}")
return ElemPtr
if isinstance(Node.value, ast.Subscript):
InnerPtr: Any = self.HandleSubscriptPtrLlvm(Node.value)
@@ -1377,7 +1411,7 @@ class ExprAttrHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr = Gen.builder.gep(InnerPtr, [zero, IndexVal], name="subscript_ptr")
ElemPtr = Gen.builder.gep(InnerPtr, [zero, IndexVal], name=f"subscript_ptr{_PVSuffix}")
return ElemPtr
if isinstance(Node.value, ast.Attribute):
AttrPtr: Any = self._get_attr_ptr(Node.value)
@@ -1387,11 +1421,11 @@ class ExprAttrHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr = Gen.builder.gep(AttrPtr, [zero, IndexVal], name="subscript_ptr")
ElemPtr = Gen.builder.gep(AttrPtr, [zero, IndexVal], name=f"subscript_ptr{_PVSuffix}")
return ElemPtr
arr_alloc: Any = Gen._allocaEntry(ValueVal.type, name="arr_subscript_tmp")
Gen._store(ValueVal, arr_alloc)
ElemPtr = Gen.builder.gep(arr_alloc, [zero, IndexVal], name="subscript_ptr")
ElemPtr = Gen.builder.gep(arr_alloc, [zero, IndexVal], name=f"subscript_ptr{_PVSuffix}")
return ElemPtr
return None
@@ -1705,17 +1739,17 @@ class ExprAttrHandle(BaseHandle):
for qname in (f"{enum_key}.{member_name}", f"{enum_key}_{member_name}"):
info: Any = self.Trans.SymbolTable.lookup(qname)
if info and info.IsEnumMember and isinstance(info.value, int):
return ir.Constant(ir.IntType(32), info.value)
return ir.Constant(Gen._enum_int_type(enum_key, info), info.value)
for key, info in self.Trans.SymbolTable.items():
if info.IsEnumMember and info.EnumName == enum_key and key == member_name:
if isinstance(info.value, int):
return ir.Constant(ir.IntType(32), info.value)
return ir.Constant(Gen._enum_int_type(enum_key, info), info.value)
for key, info in self.Trans.SymbolTable.items():
if info.IsEnumMember and key == member_name:
if isinstance(info.value, int):
for enum_key in enum_keys:
if info.EnumName == enum_key:
return ir.Constant(ir.IntType(32), info.value)
return ir.Constant(Gen._enum_int_type(enum_key, info), info.value)
return None
def _lookup_member_elem_class(self, Gen: LlvmCodeGenerator, OwnerClass: str | None, FieldName: str) -> str | None:
+13 -6
View File
@@ -727,7 +727,12 @@ class ExprBuiltinHandle(BaseHandle):
elif isinstance(Node, ast.Attribute):
type_name = Node.attr
else:
return ir.Constant(ir.IntType(64), 0)
# 非 名称/属性 的 sizeof 目标(如 sizeof(a + b)、sizeof(t.CArray[int,4])):
# 曾静默返回 0 → 下游 alloc(0) 返回 NULL。fail-fast。
raise ValueError(
f"[FATAL][__sizeof__] sizeof 的目标 {type(Node).__name__}"
f"({ast.unparse(Node)})不是类型名(Name/Attribute),拒绝静默返回 0"
)
if type_name not in Gen.structs:
# str/bytes 是指针类型 (char*/void*),sizeof = 指针大小
@@ -758,7 +763,11 @@ class ExprBuiltinHandle(BaseHandle):
if _config_mode == "strict":
raise
_vlog().warning(f"解析类型失败: {_e}", "Exception")
return ir.Constant(ir.IntType(64), 0)
# 所有查找策略均失败:曾静默返回 0 → 下游 elem_size=0 / alloc(0)。fail-fast。
raise ValueError(
f"[FATAL][__sizeof__] 类型 '{type_name}' 无法解析"
f"(不在 Gen.structs、CTypeRegistry 也找不到),拒绝静默返回 0"
)
struct_type = Gen.structs[type_name]
if isinstance(struct_type, ir.PointerType):
@@ -768,10 +777,8 @@ class ExprBuiltinHandle(BaseHandle):
struct_type = Gen.structs.get(type_name, struct_type)
# 递归解析所有嵌套的 opaque struct,确保 sizeof 计算正确
self._resolve_nested_opaque_structs(struct_type, Gen)
size = Gen._get_struct_size(struct_type)
if size > 0:
return ir.Constant(ir.IntType(64), size)
return ir.Constant(ir.IntType(64), 0)
# _get_struct_size 已在尺寸不可得时 fail-fast(不返回 0),此处直接采用。
return ir.Constant(ir.IntType(64), Gen._get_struct_size(struct_type))
def _HandleAbsLlvm(self, arg_node: ast.AST) -> ir.Value | None:
Gen: LlvmGeneratorMixin = self.Trans.LlvmGen
+495 -162
View File
@@ -14,7 +14,9 @@ from lib.includes import t
from lib.includes.t import CTypeRegistry
from lib.core.SymbolUtils import (
IsTModule, ExtractTypeNameFromBinOp, IsPtrSubscriptNode, DesugarPtrSubscriptToUnion,
ResolveTypeArgStr, GenericArgRawName)
ResolveTypeArgStr, GenericArgRawName,
ClassHasFuncLevelCVTable, IsVtableSlotMethod)
from lib.core.Handles.HandlesExpr import DefineUseWidthFromLLVMType
def _safe_ptr_type(ty: ir.Type) -> ir.Type:
@@ -236,6 +238,71 @@ class ExprCallHandle(BaseHandle):
except (AssertionError, TypeError):
CallArgs.append(ir.Constant(ir.IntType(32), 0))
def _LookupGlobalDefaults(self, func_name: str | None) -> list | None:
"""按函数名查询全局默认参数表(跨模块调用点用)。
查找顺序:精确键 → 去 mangled 前缀后的裸名 → 以 '.<裸名>' 结尾的键
(如 {sha1}.name)。精确键优先,避免"同名不同模块"的默认值互相乱配。
"""
if not func_name:
return None
table: dict = getattr(self.Trans, '_global_function_default_args', None) or {}
if not table:
return None
hit = table.get(func_name)
if hit:
return hit
base: str = func_name.split('.')[-1]
if base != func_name:
hit = table.get(base)
if hit:
return hit
for key, value in table.items():
if key.endswith('.' + base):
return value
return None
def _LookupGlobalParamNames(self, func_name: str | None) -> list | None:
"""按函数名查询全局参数名表;查找顺序同 _LookupGlobalDefaults。"""
if not func_name:
return None
table: dict = getattr(self.Trans, '_global_function_param_names', None) or {}
if not table:
return None
hit = table.get(func_name)
if hit:
return hit
base: str = func_name.split('.')[-1]
if base != func_name:
hit = table.get(base)
if hit:
return hit
for key, value in table.items():
if key.endswith('.' + base):
return value
return None
@staticmethod
def _ConstantToIrValue(raw: object, param_type: ir.Type, Gen: LlvmGeneratorMixin) -> ir.Value | None:
"""把全局默认参数表里的 Python 常量转成形参类型的 IR 常量。
只处理整数/布尔(跨模块默认值里最常见,如 cap: t.CSizeT = 4096)。字符串等
常量需要在调用点引用定义模块的全局对象,无法安全重建 → 返回 None,调用点
退回原有零值填充(与修复前行为一致)。
"""
if isinstance(param_type, ir.IntType):
if isinstance(raw, bool):
raw = 1 if raw else 0
if isinstance(raw, int):
max_val: int = (1 << param_type.width) - 1
if raw > max_val or raw < 0:
raw = raw & max_val
return ir.Constant(param_type, raw)
return ir.Constant(param_type, 0)
if isinstance(param_type, ir.PointerType):
return ir.Constant(param_type, None)
return None
def _ApplyKeywordArgs(self, Node: ast.Call, CallArgs: list[ir.Value], func: ir.Function | None, func_name: str | None, self_offset: int = 1) -> None:
"""在 _fill_default_args 之后调用:用 keyword args 按 param_names 覆盖 CallArgs 对应位置。
self_offset=1 表示 CallArgs[0] 是 self,参数从 CallArgs[1] 开始(__init__/__new__/__call__/method)。
@@ -274,6 +341,108 @@ class ExprCallHandle(BaseHandle):
if 0 <= target_idx < len(CallArgs) - self_offset:
CallArgs[self_offset + target_idx] = ArgVal
def _CheckFuncPtrArity(self, Node: ast.Call, FnPtr: ir.Value, Args: list[ir.Value],
FuncName: str, Gen: LlvmGeneratorMixin) -> None:
"""函数指针间接调用前的 arity 校验(fail-fast,紧邻 builder.call 之前)。
期望形参个数取自 FnPtr 的静态 LLVM 函数类型的形参表:来源是函数指针槽
(模块级/局部 t.Callable[...] 注解)的 CTypeInfo.FuncPtrParams →
CTypeInfo.ToLLVM/_ctype_to_llvm → ir.FunctionType(ret, params);变参位由
FunctionType.var_arg 承载。不校验时,实参个数少于形参个数会让 llvmlite
CallInstr 按形参个数索引 args[i] 抛裸 `IndexError: list index out of range`
(无函数名、无源位置,栈顶落在被调函数 docstring 行,无法定位真因)。
变参函数(var_arg=True,如 C printf 形态)只要求实参不少于固定形参个数:
多出的实参按变参传递,不报错;仅 len(Args) < 固定形参个数 时报错。
"""
FnType: ir.Type = FnPtr.type.pointee if isinstance(FnPtr.type, ir.PointerType) else FnPtr.type
if not isinstance(FnType, ir.FunctionType):
return
Expected: int = len(FnType.args)
Actual: int = len(Args)
IsVariadic: bool = bool(getattr(FnType, 'var_arg', False))
if IsVariadic:
if Actual >= Expected:
return
elif Actual == Expected:
return
# 源位置:按本文件既有写法取 Node 自身的 lineno/col_offset(不用
# _current_node_info/_current_lineno —— 二者可能停留在上一条语句),
# 源文件取 Gen._current_source_file,源码行取 Gen._get_source_line。
Lineno: int = getattr(Node, 'lineno', 0) or 0
# 注入行偏移还原:AST 行号含 _inject_auto_imports 在文件头注入的 N 行,
# 先按精确偏移减回磁盘真实行号(N 由 Phase2Translator 计算并挂到 translator);
# 报文行号与源码行必须同用还原后的 Lineno,二者一致(否则会显示下一行源码)。
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and Lineno > _line_offset:
Lineno -= _line_offset
Col: int = getattr(Node, 'col_offset', -1)
SrcFile: str = getattr(Gen, '_current_source_file', '') or 'unknown'
Loc: str = f"{SrcFile}:{Lineno}"
if Col >= 0:
Loc += f":{Col}"
SourceLine: str = ''
if Lineno > 0 and hasattr(Gen, '_get_source_line'):
SourceLine = (Gen._get_source_line(Lineno) or '').strip()
ExpectText: str = (f"至少 {Expected} 个形参(变参函数,仅固定形参)"
if IsVariadic else f" {Expected} 个形参")
raise Exception(
f"[TransPyC] 函数指针调用实参个数不匹配: '{FuncName}' 期望{ExpectText},"
f"实际传入 {Actual} 个实参;请检查该函数指针(t.Callable[...] 注解槽)的"
f"形参表是否与调用处实参个数一致。位置 {Loc}"
f"{(',源码: ' + SourceLine) if SourceLine else ''}"
)
def _RequireArgValue(self, ArgVal: ir.Value | None, Callee: str, ArgIndex: int,
ArgNode: ast.AST, CallNode: ast.Call) -> ir.Value:
"""fail-fast:调用实参编译期求值为空(`HandleExprLlvm` 返回 None)时终止装配。
调用装配处原写作 `ArgVal = self.HandleExprLlvm(arg); if ArgVal: 追加(ArgVal)`:
实参求值为 None 时它被**静默整条丢弃**,导致后续实参整体前移一格,最终由
`FuncGen._adjust_args` 报出误导性的"指针类型不能隐式转换为整数参数"(真因是
"少了一个实参")。实例:`ast.OpKind.BitOr` 因枚举成员未登记而求值 None ⇒
`ast.BinOp(...)` 的 4 个实参退化为 3 个 ⇒ `AST*` 落到 `op: i32`(见
`Debugger/rawptr-migration-and-fastfail.md` §27、`Debugger/enum-semantics.md`
的「逗号分组成员被静默丢弃」一节)。
`HandleExprLlvm` 返回 None 只有一种语义——该表达式编译期**求值失败**
(名称/属性链/枚举成员未登记、跨模块符号查找失败、类构造在声明期结构体不完整等);
"按类型本无值"的形态(以返回 void 的调用收尾)返回的是 CallInstr 对象而非 None,
故不存在需要跳过的合法实参。此处改为当场终止,报文含被调者、实参序号与源表达式、
根因提示与源码位置;**不丢弃该实参**。
"""
if ArgVal is not None:
return ArgVal
Gen: LlvmGeneratorMixin = self.Trans.LlvmGen
expr_text: str = self._ExprText(ArgNode)
ArgKind: str = type(ArgNode).__name__
raw_lineno: int = getattr(ArgNode, 'lineno', 0) or getattr(CallNode, 'lineno', 0) or 0
lineno: int = raw_lineno
# 行号还原:`_inject_auto_imports` 会在解析前往文件头注入 import 行,AST 行号
# 比磁盘真实行号大 N;与 HandlesBase._RaiseUnresolvedExpr 同口径。
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and lineno > _line_offset:
lineno -= _line_offset
col: int = getattr(ArgNode, 'col_offset', -1)
src_file: str = getattr(Gen, '_current_source_file', '') or 'unknown'
code_line: str = ''
if lineno > 0 and hasattr(Gen, '_get_source_line'):
try:
code_line = (Gen._get_source_line(lineno) or '').strip()
except Exception:
code_line = ''
loc: str = f"{src_file}:{lineno}"
if col >= 0:
loc += f":{col}"
raise Exception(
f"[TransPyC] 调用实参编译期求值为空: 被调者 '{Callee}' 的第 {ArgIndex} 个实参 "
f"'{expr_text}'({ArgKind} 形态)求值失败;已终止编译,不丢弃该实参"
f"(丢弃会使后续实参前移一格,报出与真因无关的类型不匹配)。"
f"常见根因:枚举成员/名称未登记、跨模块符号查找失败、类定义在声明期结构体不完整。"
f"位置 {loc}"
f"{(',源码: ' + code_line) if code_line else ''}"
)
def _HandleCallLlvm(self, Node: ast.Call) -> ir.Value:
Gen: LlvmGeneratorMixin = self.Trans.LlvmGen
# 协程:coro.coro_run(帧) 内联翻译为 resume 循环
@@ -300,6 +469,15 @@ class ExprCallHandle(BaseHandle):
ModulePath: str | None = None
if isinstance(Node.func, ast.Name):
FuncName = Node.func.id
# __asm__(...) 语法糖:等价 c.Asm(...)(裸名调用,无需 import c)
if FuncName == '__asm__':
return self.Trans.ExprAsmHandle._HandleAsmLlvm(Node)
# __llvmir__(...) 语法糖:等价 c.LLVMIR(...)
if FuncName == '__llvmir__':
return self._HandleLLVMIRLlvm(Node)
# irinp/irout 等价 c.LInp/c.LOut(仅作模板占位符,独立调用返回 0)
if FuncName in ('irinp', 'irout'):
return ir.Constant(ir.IntType(32), 0)
# M1 协程:普通函数调用 async 函数 → create_<name>()(返回帧指针)
if hasattr(self.Trans, 'AsyncHandler') and self.Trans.AsyncHandler._IsAsyncFunc(FuncName):
return self.Trans.AsyncHandler._HandleAsyncCreateCallLlvm(Node, FuncName)
@@ -316,16 +494,19 @@ class ExprCallHandle(BaseHandle):
# 若该名字同时是类(在 Gen.structs 中或存在 __before_init__),优先作为类构造函数处理
IsAlsoClass: bool = (FuncName in Gen.structs) or Gen._has_function(f'{FuncName}.__before_init__')
if SymInfo.IsEnum and not (IsAlsoClass and getattr(SymInfo, 'IsEnumMember', False)):
# 枚举类型转换 `EnumName(v)`:结果宽度 = 该枚举的存储宽度
# (最宽成员类型),与成员常量宽度同点(ABI 一致)。
_enum_ty: ir.IntType = Gen._enum_int_type(FuncName, SymInfo)
if Node.args:
arg_val = self.HandleExprLlvm(Node.args[0])
if arg_val:
if isinstance(arg_val.type, ir.IntType) and arg_val.type.width != 32:
if arg_val.type.width < 32:
arg_val = Gen.builder.zext(arg_val, ir.IntType(32), name="zext_enum_arg")
if isinstance(arg_val.type, ir.IntType) and arg_val.type.width != _enum_ty.width:
if arg_val.type.width < _enum_ty.width:
arg_val = Gen.builder.zext(arg_val, _enum_ty, name="zext_enum_arg")
else:
arg_val = Gen.builder.trunc(arg_val, ir.IntType(32), name="trunc_enum_arg")
arg_val = Gen.builder.trunc(arg_val, _enum_ty, name="trunc_enum_arg")
return arg_val
return ir.Constant(ir.IntType(32), 0)
return ir.Constant(_enum_ty, 0)
if SymInfo.IsTypedef:
return self._HandleTypedefCastLlvm(Node, FuncName)
NewFuncName = f'{FuncName}.__before_init__'
@@ -370,6 +551,7 @@ class ExprCallHandle(BaseHandle):
ArgVal = self.HandleExprLlvm(arg)
if ArgVal:
args.append(ArgVal)
self._CheckFuncPtrArity(Node, fn_ptr, args, FuncName, Gen)
return Gen.builder.call(fn_ptr, args, name=f"call_{FuncName}")
closure_ptr = Gen._load(VarPtr, name=f"Load_closure_{FuncName}")
if isinstance(closure_ptr.type, ir.PointerType) and isinstance(closure_ptr.type.pointee, ir.FunctionType):
@@ -379,6 +561,7 @@ class ExprCallHandle(BaseHandle):
ArgVal = self.HandleExprLlvm(arg)
if ArgVal:
args.append(ArgVal)
self._CheckFuncPtrArity(Node, fn_ptr, args, FuncName, Gen)
return Gen.builder.call(fn_ptr, args, name=f"call_{FuncName}")
# 处理 t.CPtr (i8*) 函数指针调用
# 当函数被赋值给 t.CPtr 变量时,原始函数类型退化为 i8*
@@ -449,13 +632,39 @@ class ExprCallHandle(BaseHandle):
safe_env_type: ir.Type = _safe_ptr_type(expected_fn_type.args[0])
call_args[0] = Gen.builder.bitcast(call_args[0], safe_env_type, name=f"cast_env_{FuncName}")
return Gen.builder.call(fn_val, call_args, name=f"call_{FuncName}")
# 编译器内置裸指针强转 rawptr(x):rawptr 是 TPC/TPV 双端**语法保留字**
# (无 SymbolTable typedef 条目、无泛型模板;builtins.vp 已不再声明它,
# 见 TypeAnnotationResolver._BUILTIN_ROWPTR_NAME 与 TPV 同口径内置分支
# TypeGenResolve.vp:921)。落到下方 typedef 表判据必然 miss ⇒ 误报
# "Undefined function: 'rawptr'"(TransPyV/App/lib/core/Gen/
# StructGenMixin.vp `vt_ptr: rawptr = rawptr(*(uint64*)(default_val))` 根因)。
# 单独 rawptr 等价 t.CPtr(void* → i8*):目标类型取内置 rawptr 的权威
# CTypeInfo(_builtin_rawptr_info,与注解侧同源),转换复用 (rawptr[X])(x)
# / (t.CPtr)(x) 的 _emit_rawptr_cast——不新造第二套 rawptr 表示。
# 不照抄 uchar 的做法(往 builtins.vp 加 `uchar: t.CTypedef = ...`):
# rawptr 不是普通 typedef 名而是内置语法,TPV register_ctypedef 显式拒绝
# 重定义(TypeGenCType.vp:157-160)、TPC builtins_typedef_value 亦硬编码
# 返回内置值(TypeAnnotationResolver.py:313-314)⇒ 该声明不生效。
# 本分支置于下方 try 之外:解析/转换异常必须外抛,不得被吞成
# "Undefined function: 'rawptr'" 的误导报文。
from lib.core.TypeAnnotationResolver import TypeAnnotationResolver as _TAR_RP
if FuncName == _TAR_RP._BUILTIN_ROWPTR_NAME:
_rawptr_target: ir.Type = Gen._ctype_to_llvm(_TAR_RP._builtin_rawptr_info())
return self._emit_rawptr_cast(Node, Gen, _rawptr_target)
# builtins 隐式 CTypedef 作为类型转换调用(uint64(x)/int64(x)/size_t(x) 等,
# builtins.vp 声明、无需 import,不注册进 SymbolTable):常规 miss 后在此
# 尝试 typedef cast(uint64(buf) = t.CUInt64T(buf)),否则报
# "Undefined function: 'uint64'"(includes/ast/base.vp bootstrap 根因)。
_td_diag: str = ''
try:
from lib.core.TypeAnnotationResolver import TypeAnnotationResolver as _TAR_TD
if FuncName in _TAR_TD._scan_builtins()['typedefs']:
_td_table: dict = _TAR_TD._scan_builtins()['typedefs']
if FuncName not in _td_table:
# 典型成因:includes/builtins.vp 未定位到(PROJECT_INCLUDE_DIRS 尚未
# 装配)、或声明写法未被文本扫描识别 ⇒ 整表为空/缺项。
_td_diag = (f"(builtins typedef 表 {len(_td_table)} 项,无 '{FuncName}';"
f"includes 目录={_TAR_TD._builtins_include_dirs()})")
if FuncName in _td_table:
# from_type_name 对裸 typedef 名(uint64 等)解析不出真实类型类
# (BuiltinTypeMap 无此键,CreateFromTypeName 落空成空 CType),
# 因此改走权威 typedef 值:'t.CUInt64T' → CTypeRegistry.NameToLLVM
@@ -487,9 +696,11 @@ class ExprCallHandle(BaseHandle):
_tg_ptr_td: ir.Type = ir.PointerType(_tg_td)
return self._emit_rawptr_cast(Node, Gen, _tg_ptr_td)
return self._emit_rawptr_cast(Node, Gen, _tg_td)
except Exception:
pass
raise Exception(f"Undefined function: '{FuncName}'")
except Exception as _e_td:
# 不再静默:兜底链任何一步抛错都要带出来(此前 pass 让真正的根因
# 只剩一句误导性的 "Undefined function")。
_td_diag = _td_diag + f"(typedef cast 求值异常: {type(_e_td).__name__}: {_e_td})"
raise Exception(f"Undefined function: '{FuncName}'{_td_diag}")
if isinstance(Node.func, ast.Subscript):
# Handle list[int](mb) syntax — generic class constructor with explicit type args
# Handle xxx[T](x) syntax — generic function call with explicit type args
@@ -558,39 +769,12 @@ class ExprCallHandle(BaseHandle):
if ObjVal and isinstance(ObjVal.type, ir.PointerType):
pointee = ObjVal.type.pointee
if isinstance(pointee, ir.ArrayType):
# Calculate element size in bytes
elem = pointee.element
if isinstance(elem, ir.IntType):
elem_size = elem.width // 8
elif isinstance(elem, ir.PointerType):
elem_size = 8 # 64-bit pointers
elif isinstance(elem, (ir.LiteralStructType, ir.IdentifiedStructType)):
# Approximate: sum of field sizes
elem_size = 0
for field in getattr(elem, 'elements', []):
if isinstance(field, ir.IntType):
elem_size += field.width // 8
elif isinstance(field, ir.PointerType):
elem_size += 8
elif isinstance(field, ir.ArrayType):
if isinstance(field.element, ir.IntType):
elem_size += field.count * (field.element.width // 8)
else:
elem_size += field.count * 8
else:
elem_size += 8
elif isinstance(elem, ir.ArrayType):
if isinstance(elem.element, ir.IntType):
elem_size = elem.count * (elem.element.width // 8)
else:
elem_size = elem.count * 8
elif isinstance(elem, ir.FloatType):
elem_size = 4
elif isinstance(elem, ir.DoubleType):
elem_size = 8
else:
elem_size = 8 # fallback
return ir.Constant(ir.IntType(64), pointee.count * elem_size)
# 元素尺寸统一走 Gen._get_struct_size(含字段对齐/填充,与 __sizeof__ /
# memhub alloc 同源):_get_struct_size(ArrayType) = 元素尺寸 × 元素数。
# 原实现手写「字段宽度直接相加 + 未知类型兜底 8」,会漏算 struct 的填充
# ({i8,i64} 算成 9 而非 16),sizeof 偏小 → 调用方按过小尺寸 alloc →
# 越界写坏邻居块头(与 1 GiB 主池漏满同一根因链)。
return ir.Constant(ir.IntType(64), Gen._get_struct_size(pointee))
elif isinstance(pointee, (ir.LiteralStructType, ir.IdentifiedStructType)):
# 结构体实例: 直接计算大小
self._resolve_opaque_struct_members(pointee, Gen)
@@ -649,6 +833,13 @@ class ExprCallHandle(BaseHandle):
or _base_name in Gen.global_vars)
):
_is_chained_instance = True
elif self._IsModuleQualifiedGlobalChain(_base_name, Node.func.value):
# 模块限定全局变量链:`Module.Global.field.method(...)`
# (如 GenApi._g_gen.ClassGenBase._collect_parents(...))。
# 链根是模块别名、次段是该模块的全局变量 ⇒ 接收者是对象,
# 与 self/局部变量/全局变量同一条"实例链"判据(详见
# _IsModuleQualifiedGlobalChain 的说明)。
_is_chained_instance = True
if (is_instance_var or _is_chained_instance) and not is_class_name:
return self._HandleMethodCallLlvm(Node)
FuncAttr = Node.func.attr
@@ -681,6 +872,10 @@ class ExprCallHandle(BaseHandle):
if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
msg = arg.value
lineno = getattr(Node, 'lineno', 0)
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and lineno > _line_offset:
lineno -= _line_offset
raise Exception(f"#error: {msg} (line {lineno})")
elif FuncAttr in ('AsmInp', 'AsmOut'):
return ir.Constant(ir.IntType(32), 0)
@@ -1025,8 +1220,17 @@ class ExprCallHandle(BaseHandle):
if is_classmethod_call and ClassName in Gen.structs:
ClsPtr = Gen._alloca(Gen.structs[ClassName], name=f"cls_{ClassName}")
CallArgs.append(ClsPtr)
for arg in Node.args:
ArgVal = self.HandleExprLlvm(arg)
# 形参类型下传(窄通道):仅影响 CDefine 常量实参的物化宽度
# (更宽 ⇒ 升宽物化;更窄 ⇒ fail-fast);非常量实参走原路径。
_arg_off: int = len(CallArgs)
for _ai, arg in enumerate(Node.args):
_pt: ir.Type | None = None
if func is not None:
_ft_args: list[ir.Type] = func.function_type.args
if _ai + _arg_off < len(_ft_args):
_pt = _ft_args[_ai + _arg_off]
ArgVal = self.HandleExprLlvm(
arg, ConstWidthHint=DefineUseWidthFromLLVMType(_pt))
if ArgVal:
CallArgs.append(ArgVal)
if (ClassName not in Gen.class_vtable
@@ -1087,15 +1291,22 @@ class ExprCallHandle(BaseHandle):
if ClassName in Gen.structs:
ObjVal = Gen.builder.bitcast(ObjVal, ir.PointerType(Gen.structs[ClassName]), name=f"cast_{ClassName}")
CallArgs = [ObjVal]
for arg in Node.args:
ArgVal = self.HandleExprLlvm(arg)
if ArgVal:
CallArgs.append(ArgVal)
CallFuncName = f'{ClassName}.__call__'
struct_sha1_map = getattr(Gen, '_struct_sha1_map', {})
class_sha1 = struct_sha1_map.get(ClassName)
if class_sha1:
CallFuncName = f'{class_sha1}.{ClassName}.__call__'
# 形参类型下传(窄通道):仅影响 CDefine 常量实参的物化宽度。
# CallArgs[0] 为 self ⇒ 第 i 个用户实参对应形参 i+1。
_cfunc: ir.Function | None = (Gen._get_function(CallFuncName)
if Gen._has_function(CallFuncName) else None)
_ptypes: list[ir.Type] = _cfunc.function_type.args if _cfunc is not None else []
for _ai, arg in enumerate(Node.args):
_pt: ir.Type | None = _ptypes[_ai + 1] if _ai + 1 < len(_ptypes) else None
ArgVal = self.HandleExprLlvm(
arg, ConstWidthHint=DefineUseWidthFromLLVMType(_pt))
if ArgVal:
CallArgs.append(ArgVal)
if not Gen._has_function(CallFuncName):
raise Exception(f"Class '{ClassName}' has no __call__ method")
func = Gen._get_function(CallFuncName)
@@ -1131,6 +1342,66 @@ class ExprCallHandle(BaseHandle):
parts.reverse()
return '.'.join(parts) if parts else None
def _IsModuleQualifiedGlobalChain(self, base_name: str, receiver: ast.AST) -> bool:
"""判定"模块限定全局变量"接收者链(链根=模块别名、次段=该模块全局变量)。
与调用点既有的"链根是已知变量(self / 局部变量 / 全局变量 / struct 变量)"
判据互补,补的是**模块限定全局变量**这一形态:
`import lib.core.Gen.GenApi as GenApi` 后写
`GenApi._g_gen.ClassGenBase._collect_parents(...)`——链根 `GenApi` 是模块
别名(不在 var_struct_class / variables / global_vars 中),次段 `_g_gen`
是该模块的全局变量。缺此判定时该接收者被当作"模块.类"引用,落到下方
`inner_attr in Gen.structs` 分支走 _HandleStaticMethodCallLlvm——该路径按
静态方法口径处理(**不前置接收者 self**),实参整体错位一格
(trans: Translator* 对上 max_parents: i32),触发 fail-fast
"指针类型 ... 不能隐式转换为整数参数 i32";不报错的同形调用(实参全为指针)
则静默生成缺 self 的错误调用。
判据只有两段:链根是模块(SymbolTable.import_aliases / IsModuleAlias 符号 /
ModuleSha1Map / _ImportedModules 任一命中),且**紧邻根名的那一段属性**不是
类型/函数类符号(类 / 枚举 / REnum / 枚举成员 / 函数 / CDefine / 异常类 /
模块别名)——即该段是"对象"(全局变量;跨模块全局在本符号表里登记为
IsTypedef 的空类型条目,故 IsTypedef 不在排除集内)。紧随段是类
(`Module.Class.static_method(...)`)时返回 False,静态方法口径保持不变。
"""
symbol_table: Any = self.Trans.SymbolTable
# 紧邻根名的那一段属性:GenApi._g_gen.ClassGenBase → '_g_gen'
first_attr: str | None = None
cursor: ast.AST = receiver
while isinstance(cursor, ast.Attribute):
first_attr = cursor.attr
cursor = cursor.value
if not first_attr or not isinstance(cursor, ast.Name):
return False
# 链根是不是模块:优先别名表(import ... as X 的 X → 模块全名),
# 其次符号表里的模块别名标记,最后模块表(其键含短名)兜底。
module_path: str = symbol_table.resolve_alias(base_name)
if module_path != base_name:
is_module: bool = True
else:
alias_entry: Any = symbol_table.lookup(base_name)
if alias_entry is not None and getattr(alias_entry, 'IsModuleAlias', False):
module_path = getattr(alias_entry, 'ResolvedModule', '') or ''
is_module = bool(module_path)
else:
is_module = (
base_name in getattr(self.Trans.LlvmGen, 'ModuleSha1Map', {})
or base_name in getattr(self.Trans, '_ImportedModules', set()))
if not is_module:
return False
segment: Any = symbol_table.lookup(f'{module_path}.{first_attr}') if module_path else None
if segment is None:
segment = symbol_table.lookup(first_attr)
if segment is None:
return False
# 该段是"类型/函数类"符号 ⇒ 属于 `Module.Class...` 引用,不按实例链处理
# (同名枚举成员/异常类在符号表里与类名冲突,一并排除,避免误判)。
for _type_flag in ('IsStruct', 'IsEnum', 'IsRenum', 'IsUnion', 'IsEnumMember',
'IsFunction', 'IsDefine', 'IsExceptionClass', 'IsModuleAlias'):
if getattr(segment, _type_flag, False):
return False
return True
def _HandleLLVMIRLlvm(self, Node: ast.Call) -> ir.Value:
Gen: LlvmGeneratorMixin = self.Trans.LlvmGen
if not Node.args:
@@ -1158,36 +1429,32 @@ class ExprCallHandle(BaseHandle):
parts.append(value.value)
elif isinstance(value, ast.FormattedValue):
expr = value.value
if isinstance(expr, ast.Call) and isinstance(expr.func, ast.Attribute):
if isinstance(expr.func.value, ast.Name) and expr.func.value.id == 'c':
if expr.func.attr == 'LInp':
if expr.args:
val = self.HandleExprLlvm(expr.args[0])
if val:
input_ops.append(val)
all_ops.append(('inp', len(input_ops) - 1))
parts.append(f'%__OP{operand_seq}__')
operand_seq += 1
elif expr.func.attr == 'LOut':
if expr.args:
target_ptr = self.Trans.ExprAsmHandle._get_var_ptr_for_asm(expr.args[0])
if target_ptr:
val = target_ptr
else:
val = self.HandleExprLlvm(expr.args[0])
if val:
output_targets.append(val)
all_ops.append(('out', len(output_targets) - 1))
parts.append(f'%__OP{operand_seq}__')
operand_seq += 1
else:
fallback = self.HandleExprLlvm(expr)
if fallback:
input_ops.append(fallback)
# c.LInp/c.LOut 与裸名 irinp/irout 等价(见 _intrinsic_marker)
_marker: str | None = self.Trans.ExprAsmHandle._intrinsic_marker(expr)
_is_c_qualified: bool = (isinstance(expr, ast.Call) and isinstance(expr.func, ast.Attribute)
and isinstance(expr.func.value, ast.Name) and expr.func.value.id == 'c')
if _marker == 'LInp':
if expr.args:
val = self.HandleExprLlvm(expr.args[0])
if val:
input_ops.append(val)
all_ops.append(('inp', len(input_ops) - 1))
parts.append(f'%__OP{operand_seq}__')
operand_seq += 1
else:
elif _marker == 'LOut':
if expr.args:
target_ptr = self.Trans.ExprAsmHandle._get_var_ptr_for_asm(expr.args[0])
if target_ptr:
val = target_ptr
else:
val = self.HandleExprLlvm(expr.args[0])
if val:
output_targets.append(val)
all_ops.append(('out', len(output_targets) - 1))
parts.append(f'%__OP{operand_seq}__')
operand_seq += 1
elif not _is_c_qualified:
# c.* 中非 LInp/LOut 的形态不收集(保持原行为)
fallback = self.HandleExprLlvm(expr)
if fallback:
input_ops.append(fallback)
@@ -1551,6 +1818,14 @@ class ExprCallHandle(BaseHandle):
if sym_info_exact:
exact_params = sym_info_exact.FuncPtrParams
exact_param_names = [pn for pn, _ in exact_params]
# stub/LLVM 侧形参名多为 arg0/arg1 占位(SymbolTable._InsertFuncSymbol),
# 用它们无法把命名实参 `f(cap = 8, x = 1)` 绑到正确位置,会误报"缺少必需
# 参数"。优先替换为源码/vpi 扫描得到的语义参数名(长度一致才替换,
# 避免 shape 不符时错配)。
semantic_names = (self._LookupGlobalParamNames(mangled_name)
or self._LookupGlobalParamNames(func_name))
if semantic_names and len(semantic_names) == len(exact_param_names):
exact_param_names = semantic_names
if exact_param_names:
provided = len(Node.args)
kw_provided = set()
@@ -1558,17 +1833,9 @@ class ExprCallHandle(BaseHandle):
if kw.arg and kw.arg in exact_param_names:
kw_provided.add(exact_param_names.index(kw.arg))
# 获取默认参数信息,计算必需参数数量(与 _fill_default_args 一致的查找逻辑)
global_defaults: dict = self.Trans._global_function_default_args
func_defaults: list | None = None
if mangled_name in global_defaults:
func_defaults = global_defaults[mangled_name]
elif func_name in global_defaults:
func_defaults = global_defaults[func_name]
else:
for gk, gv in global_defaults.items():
if func_name in gk or gk.endswith(func_name) or gk.endswith('.' + func_name):
func_defaults = gv
break
func_defaults: list | None = self._LookupGlobalDefaults(mangled_name)
if func_defaults is None:
func_defaults = self._LookupGlobalDefaults(func_name)
total_params: int = len(exact_param_names)
if func_defaults is None:
# 默认参数信息缺失(跨模块 stub 加载:vpi/stub 只含参数
@@ -1578,6 +1845,9 @@ class ExprCallHandle(BaseHandle):
# err_type/col_start/col_end 默认值未同步时误报"缺 arg3")。
# 同文件函数的参数检查由 FunctionDef 路径(L1983)覆盖。
pass
elif len(func_defaults) > total_params:
# 表里的条目与当前函数长度不符(同名不同模块)→ 不做严格检查
pass
else:
num_defaults: int = len(func_defaults)
min_args: int = total_params - num_defaults
@@ -1645,6 +1915,10 @@ class ExprCallHandle(BaseHandle):
if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
msg = arg.value
lineno = getattr(Node, 'lineno', 0)
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and lineno > _line_offset:
lineno -= _line_offset
raise Exception(f"#error: {msg} (line {lineno})")
elif func_name in ('AsmInp', 'AsmOut'):
return ir.Constant(ir.IntType(32), 0)
@@ -2124,7 +2398,10 @@ class ExprCallHandle(BaseHandle):
if not is_variadic and len(Node.args) > regular_param_count:
raise Exception(f"调用 {FuncName}() 传入了 {len(Node.args)} 个参数,但函数只需要 {regular_param_count} 个参数")
max_args = len(Node.args) if is_variadic else regular_param_count
# 变参函数:固定形参(含带默认值的)之外才是可变部分。被省略的固定形参
# 也必须补进调用实参,否则实参个数少于固定形参 → 被调函数读到寄存器/栈
# 垃圾(VET json.write 缺省 cap 读成指针量级 → pool.alloc 巨量 → OOM 的形态)。
max_args = max(regular_param_count, len(Node.args)) if is_variadic else regular_param_count
resolved = [None] * max_args
for i, arg in enumerate(Node.args):
@@ -2153,31 +2430,44 @@ class ExprCallHandle(BaseHandle):
resolved[idx] = ArgVal
FuncDef_args_defaults = getattr(getattr(FuncDef, 'args', None), 'defaults', []) if FuncDef else []
if FuncDef_args_defaults:
defaults = FuncDef.args.defaults
num_defaults = len(defaults)
if num_defaults > 0:
min_args = regular_param_count - num_defaults
for i in range(regular_param_count):
if resolved[i] is None and i >= min_args:
default_idx = i - min_args
if default_idx < len(defaults):
default_node = defaults[default_idx]
default_val = self.HandleExprLlvm(default_node)
if default_val:
param_type = func.function_type.args[i]
if default_val.type != param_type:
if isinstance(param_type, ir.IntType) and isinstance(default_val.type, ir.IntType):
if default_val.type.width < param_type.width:
default_val = Gen.builder.zext(default_val, param_type, name=f"zext_default_{i}")
elif default_val.type.width > param_type.width:
default_val = Gen.builder.trunc(default_val, param_type, name=f"trunc_default_{i}")
elif isinstance(param_type, ir.PointerType) and isinstance(default_val.type, ir.IntType):
default_val = Gen.builder.inttoptr(default_val, param_type, name=f"inttoptr_default_{i}")
resolved[i] = default_val
# 跨模块调用(被调函数不在本模块 FunctionDefCache:其它 App 模块 / includes
# 模块 / .vpi 声明)时 FuncDef 为 None,本模块没有被调方 AST,AST 默认值不可用。
# 此时从全局默认参数表(Phase2 预扫描 App 与 includes 源码得到)取值,按
# "尾部默认参数"语义补齐被省略的实参;缺失该表时保持原有零值填充。
Global_defaults_vals: list | None = None
if not FuncDef_args_defaults:
Global_defaults_vals = self._LookupGlobalDefaults(FuncName)
if Global_defaults_vals is None and func is not None:
Global_defaults_vals = self._LookupGlobalDefaults(getattr(func, 'name', None))
Num_defaults: int = len(FuncDef_args_defaults) if FuncDef_args_defaults else (
len(Global_defaults_vals) if Global_defaults_vals else 0)
if Num_defaults > 0:
Min_args: int = regular_param_count - Num_defaults
for i in range(regular_param_count):
if resolved[i] is not None or i < Min_args:
continue
default_idx = i - Min_args
if default_idx >= Num_defaults:
continue
if FuncDef_args_defaults:
default_val = self.HandleExprLlvm(FuncDef_args_defaults[default_idx])
else:
default_val = self._ConstantToIrValue(Global_defaults_vals[default_idx],
func.function_type.args[i], Gen)
if default_val:
param_type = func.function_type.args[i]
if default_val.type != param_type:
if isinstance(param_type, ir.IntType) and isinstance(default_val.type, ir.IntType):
if default_val.type.width < param_type.width:
default_val = Gen.builder.zext(default_val, param_type, name=f"zext_default_{i}")
elif default_val.type.width > param_type.width:
default_val = Gen.builder.trunc(default_val, param_type, name=f"trunc_default_{i}")
elif isinstance(param_type, ir.PointerType) and isinstance(default_val.type, ir.IntType):
default_val = Gen.builder.inttoptr(default_val, param_type, name=f"inttoptr_default_{i}")
resolved[i] = default_val
if FuncDef and param_names:
min_args = regular_param_count - num_defaults if FuncDef_args_defaults else regular_param_count
min_args = regular_param_count - Num_defaults
provided = len(Node.args)
kw_provided = set()
for kw in Node.keywords:
@@ -2361,7 +2651,7 @@ class ExprCallHandle(BaseHandle):
return cmap
with open(pyi_path, 'r', encoding='utf-8') as f:
pyi_code = f.read()
tree = ast.parse(pyi_code)
tree = ast.parse(pyi_code, pyi_path)
cmap: dict[str, ast.ClassDef] = {}
for node in ast.iter_child_nodes(tree):
if isinstance(node, ast.ClassDef):
@@ -2397,17 +2687,18 @@ class ExprCallHandle(BaseHandle):
is_cvtable = self._has_cvtable_decorator(target_node)
is_novtable = self._has_novtable_decorator(target_node)
# 收集自身方法
self_methods: list[str] = []
# 收集自身方法(方法级修饰符的槽位过滤放在父类状态确定之后:
# 判据 SymbolUtils.IsVtableSlotMethod 需要 VtableByClassLevel)
self_method_pairs: list[tuple] = []
for item in target_node.body:
if isinstance(item, ast.FunctionDef):
method_name = item.name
if method_name in ('__new__', '__init__', '__before_init__'):
continue
if method_name == '__call__':
self_methods.append(f"{class_name}.__call__")
self_method_pairs.append((item, f"{class_name}.__call__"))
else:
self_methods.append(f"{class_name}.{method_name}")
self_method_pairs.append((item, f"{class_name}.{method_name}"))
# 收集基类方法(多继承:遍历所有父类,声明顺序,短名去重,先声明父类优先)
parent_methods: list[str] = []
@@ -2466,6 +2757,24 @@ class ExprCallHandle(BaseHandle):
if first_parent_vtable:
is_cvtable = True
# VtableByClassLevel:**忽略方法级 CVTable 标记**算出的 vtable 状态
# (= 类级 CVTable / 继承链判定)——供槽位筛选与"方法级触发"共用。
_VtByClassLevel: bool = is_cvtable
# 方法级修饰符筛选:`novirtual def`(⇒ @t.NoVTable)不进槽;
# `virtual def`(⇒ @t.CVTable)进槽。判据唯一源 = IsVtableSlotMethod
# (与定义端 HandlesClassDef / TPV _should_method_be_virtual 同口径),
# 故跨模块虚表槽序与定义模块逐槽一致。
self_methods: list[str] = []
for _nd, _full in self_method_pairs:
if (not is_cvtable) or IsVtableSlotMethod(_nd, _VtByClassLevel):
self_methods.append(_full)
# 方法级 virtual 触发类级 vtable(与 HandlesClassDef._resolve_class_flags /
# TPV _detect_vtable_status 的 `_has_any_func_cvtable` 分支对齐:
# 类级 NoVTable / 无真实父类这两种可判定形态;父类 vtable 已在上面处理)
if not is_cvtable and ClassHasFuncLevelCVTable(target_node):
if is_novtable or parent_name is None:
is_cvtable = True
# 合并方法列表:基类方法在前,子类方法在后
# 子类覆盖的方法放在父类的位置;
# 父类独有方法直接用父类方法原名(实际定义类.方法,与 TPV 一致)
@@ -2603,9 +2912,9 @@ class ExprCallHandle(BaseHandle):
if st is None or not isinstance(st, ir.IdentifiedStructType) or st.elements is None:
return None
members: list = Gen.class_members.get(lookup_cls, [])
has_vtable: bool = (ClassName in Gen.class_vtable
or ClassName in Gen._cross_module_vtable_classes)
base: int = 1 if has_vtable else 0
# base(vtable 占位槽数)统一走 Gen._member_base(唯一真源),
# 不再直查 class_vtable/_cross_module_vtable_classes(会漏掉跨模块 AST 节点类)。
base: int = Gen._member_base(ClassName)
idx: int | None = None
for i, (name, _) in enumerate(members):
if name == field_name:
@@ -2628,7 +2937,7 @@ class ExprCallHandle(BaseHandle):
try:
with open(r'd:\Users\TermiNexus\Desktop\TransPyC\_diag_offset.log', 'a', encoding='utf-8') as _f:
_f.write(f"{lookup_cls}|{field_name}|members={len(members)}|struct={len(st.elements)}"
f"|vtable={int(has_vtable)}|idx={idx}|diff={diff_elems}\n")
f"|base={base}|idx={idx}|diff={diff_elems}\n")
except Exception:
pass
offset: int = 0
@@ -2870,6 +3179,10 @@ class ExprCallHandle(BaseHandle):
raise
_vlog().warning(f"处理函数调用失败: {_e}", "Exception")
lineno = getattr(Node, 'lineno', 0)
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and lineno > _line_offset:
lineno -= _line_offset
raise Exception(f"#error: {msg} (line {lineno})")
elif MethodName in ('CElse', 'CEndif', 'CPragma', 'CUndef'):
return ir.Constant(ir.IntType(1), 1)
@@ -2904,12 +3217,16 @@ class ExprCallHandle(BaseHandle):
if (ModuleName in imported or actual_module in imported) and ModuleName not in Gen.var_struct_class:
mangled_name = Gen._mangle_func_name(MethodName, actual_module)
if Gen._has_function(mangled_name):
func = Gen._get_function(mangled_name)
CallArgs = []
for arg in Node.args:
ArgVal = self.HandleExprLlvm(arg)
# 形参类型下传(窄通道):仅影响 CDefine 常量实参的物化宽度。
_ptypes: list[ir.Type] = func.function_type.args if func is not None else []
for _ai, arg in enumerate(Node.args):
_pt: ir.Type | None = _ptypes[_ai] if _ai < len(_ptypes) else None
ArgVal = self.HandleExprLlvm(
arg, ConstWidthHint=DefineUseWidthFromLLVMType(_pt))
if ArgVal:
CallArgs.append(ArgVal)
func = Gen._get_function(mangled_name)
self._fill_default_args(CallArgs, func, MethodName)
func_ft = getattr(func, 'function_type', None)
is_variadic = getattr(func_ft, 'var_arg', False) if func_ft else False
@@ -2961,8 +3278,12 @@ class ExprCallHandle(BaseHandle):
_mf = Gen._get_function(MethodName)
if '.' not in _mf.name:
CallArgs = []
for arg in Node.args:
ArgVal = self.HandleExprLlvm(arg)
# 形参类型下传(窄通道):仅影响 CDefine 常量实参的物化宽度。
_ptypes: list[ir.Type] = _mf.function_type.args
for _ai, arg in enumerate(Node.args):
_pt: ir.Type | None = _ptypes[_ai] if _ai < len(_ptypes) else None
ArgVal = self.HandleExprLlvm(
arg, ConstWidthHint=DefineUseWidthFromLLVMType(_pt))
if ArgVal:
CallArgs.append(ArgVal)
func = _mf
@@ -3026,8 +3347,12 @@ class ExprCallHandle(BaseHandle):
if stub_func_type:
func = self.GetOrCreateStubFuncDecl(mangled_name, stub_func_type, Gen)
CallArgs = []
for arg in Node.args:
ArgVal = self.HandleExprLlvm(arg)
# 形参类型下传(窄通道):仅影响 CDefine 常量实参的物化宽度。
_ptypes: list[ir.Type] = func.function_type.args if func is not None else []
for _ai, arg in enumerate(Node.args):
_pt: ir.Type | None = _ptypes[_ai] if _ai < len(_ptypes) else None
ArgVal = self.HandleExprLlvm(
arg, ConstWidthHint=DefineUseWidthFromLLVMType(_pt))
if ArgVal:
CallArgs.append(ArgVal)
self._fill_default_args(CallArgs, func, mangled_name)
@@ -3419,8 +3744,8 @@ class ExprCallHandle(BaseHandle):
member_idx = mi
break
if member_idx >= 0 and isinstance(ObjVal.type, ir.PointerType):
has_vtable = ClassName in Gen.class_vtable or ClassName in Gen._cross_module_vtable_classes
base = 1 if has_vtable else 0
# base(vtable 占位槽数)统一走 Gen._member_base(唯一真源)。
base = Gen._member_base(ClassName)
member_ptr = Gen.builder.gep(ObjVal, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), base + member_idx)], name=f"funcptr_{MethodName}_ptr")
raw_ptr = Gen._load(member_ptr, name=f"funcptr_{MethodName}_raw")
func_ptr_type = ir.PointerType(ir.FunctionType(ir.IntType(32), [i8ptr] + [a.type for a in CallArgs]))
@@ -3845,9 +4170,19 @@ class ExprCallHandle(BaseHandle):
# 所有元素存取同槽(get(0)==get(1),list[对象] join 误报根因)。
if _BinOpContainsCPtr(Node):
return ir.Constant(ir.IntType(64), 8)
return ir.Constant(ir.IntType(64), 0)
# 不带 t.CPtr 的联合(A | B)无法确定尺寸:曾静默返回 0 →
# 下游 elem_size=0 → alloc(0) 返回 NULL / 所有元素存取同槽。fail-fast。
raise ValueError(
f"[FATAL][__sizeof__] 联合类型 '{ast.unparse(Node)}' 不含 t.CPtr,"
f"无法确定尺寸,拒绝静默返回 0(会导致 alloc(0)/元素同槽)"
)
else:
return ir.Constant(ir.IntType(64), 0)
# 非 名称/属性/联合 的 sizeof 目标(如表达式、调用):曾静默返回 0。fail-fast。
raise ValueError(
f"[FATAL][__sizeof__] 不支持的 sizeof 目标 {type(Node).__name__}"
f"({ast.unparse(Node)}),"
f"拒绝静默返回 0(会导致 alloc(0))"
)
if type_name not in Gen.structs:
# SHA1 前缀查找:如 ClassDef → 657e182b27c2a022.ClassDef
@@ -3941,10 +4276,8 @@ class ExprCallHandle(BaseHandle):
Gen.var_signedness = _saved_var_signedness
Gen.global_vars = _saved_global_vars
self.Trans.VarScopes = _saved_var_scopes
size = Gen._get_struct_size(struct_type)
if size > 0:
return ir.Constant(ir.IntType(64), size)
return ir.Constant(ir.IntType(64), 0)
# _get_struct_size 已在尺寸不可得时 fail-fast(不返回 0),此处直接采用。
return ir.Constant(ir.IntType(64), Gen._get_struct_size(struct_type))
def _HandleAbsLlvm(self, arg_node: ast.AST) -> ir.Value | None:
Gen = self.Trans.LlvmGen
@@ -4034,7 +4367,8 @@ class ExprCallHandle(BaseHandle):
if enum_member_found:
enum_type_name = enum_class_name
if enum_type_name:
type_str = f"<enum '{enum_type_name}' size=4 signed=true, ptr=false>"
_enum_bits: int = self.Trans.LlvmGen._enum_int_type(enum_type_name).width
type_str = f"<enum '{enum_type_name}' size={_enum_bits // 8} signed=true, ptr=false>"
else:
llvm_type = self.Trans.ExprUtils._infer_expr_llvm_type_full(arg)
if isinstance(llvm_type, ir.PointerType):
@@ -4382,8 +4716,8 @@ class ExprCallHandle(BaseHandle):
if not find_func:
return
members = Gen.class_members.get(ClassName, [])
has_vtable = ClassName in Gen.class_vtable or ClassName in Gen._cross_module_vtable_classes
base = 1 if has_vtable else 0
# base(vtable 占位槽数)统一走 Gen._member_base(唯一真源)。
base = Gen._member_base(ClassName)
field_map: dict[str, tuple[int, ir.Type]] = {}
for i, (mname, mtype) in enumerate(members):
if mname in requires_all:
@@ -4447,10 +4781,9 @@ class ExprCallHandle(BaseHandle):
NewMethodFunc = Gen._find_function(NewMethodFuncName)
if NewMethodFunc:
NewMethodArgs = [result]
for arg in Node.args:
ArgVal = self.HandleExprLlvm(arg)
if ArgVal:
NewMethodArgs.append(ArgVal)
for ArgIdx, arg in enumerate(Node.args, start=1):
ArgVal = self._RequireArgValue(self.HandleExprLlvm(arg), f"{ClassName}.__new__", ArgIdx, arg, Node)
NewMethodArgs.append(ArgVal)
self._fill_default_args(NewMethodArgs, NewMethodFunc, NewMethodFuncName)
self._ApplyKeywordArgs(Node, NewMethodArgs, NewMethodFunc, NewMethodFuncName, self_offset=1)
NewMethodArgs = Gen._apply_auto_addr(NewMethodFuncName, NewMethodArgs)
@@ -4479,10 +4812,9 @@ class ExprCallHandle(BaseHandle):
if Gen._has_function(InitFuncName):
InitFunc = Gen._get_function(InitFuncName)
InitArgs = [result]
for arg in Node.args:
ArgVal = self.HandleExprLlvm(arg)
if ArgVal:
InitArgs.append(ArgVal)
for ArgIdx, arg in enumerate(Node.args, start=1):
ArgVal = self._RequireArgValue(self.HandleExprLlvm(arg), f"{ClassName}.__init__", ArgIdx, arg, Node)
InitArgs.append(ArgVal)
self._fill_default_args(InitArgs, InitFunc, InitFuncName)
self._ApplyKeywordArgs(Node, InitArgs, InitFunc, InitFuncName, self_offset=1)
self._append_eh_msg_out_arg(InitArgs, InitFunc, Gen)
@@ -4522,10 +4854,9 @@ class ExprCallHandle(BaseHandle):
NewMethodFunc = Gen._find_function(NewMethodFuncName)
if NewMethodFunc:
NewMethodArgs = [result]
for arg in Node.args:
ArgVal = self.HandleExprLlvm(arg)
if ArgVal:
NewMethodArgs.append(ArgVal)
for ArgIdx, arg in enumerate(Node.args, start=1):
ArgVal = self._RequireArgValue(self.HandleExprLlvm(arg), f"{ClassName}.__new__", ArgIdx, arg, Node)
NewMethodArgs.append(ArgVal)
self._fill_default_args(NewMethodArgs, NewMethodFunc, NewMethodFuncName)
self._ApplyKeywordArgs(Node, NewMethodArgs, NewMethodFunc, NewMethodFuncName, self_offset=1)
NewMethodArgs = Gen._apply_auto_addr(NewMethodFuncName, NewMethodArgs)
@@ -4563,9 +4894,8 @@ class ExprCallHandle(BaseHandle):
init_param_types = InitFunc.function_type.args if hasattr(InitFunc, 'function_type') else []
for i, arg in enumerate(Node.args):
arg_var_type = init_param_types[i + 1] if i + 1 < len(init_param_types) else None
ArgVal = self.HandleExprLlvm(arg, VarType=arg_var_type)
if ArgVal:
InitArgs.append(ArgVal)
ArgVal = self._RequireArgValue(self.HandleExprLlvm(arg, VarType=arg_var_type), f"{ClassName}.__init__", i + 1, arg, Node)
InitArgs.append(ArgVal)
self._fill_default_args(InitArgs, InitFunc, InitFuncName)
self._ApplyKeywordArgs(Node, InitArgs, InitFunc, InitFuncName, self_offset=1)
self._append_eh_msg_out_arg(InitArgs, InitFunc, Gen)
@@ -4613,8 +4943,8 @@ class ExprCallHandle(BaseHandle):
return
if not isinstance(StructType, ir.IdentifiedStructType):
return
has_vtable = ClassName in Gen.class_vtable or ClassName in Gen._cross_module_vtable_classes
base = 1 if has_vtable else 0
# base(vtable 占位槽数)统一走 Gen._member_base(唯一真源)。
base = Gen._member_base(ClassName)
for i, (member_name, member_type) in enumerate(members):
if member_name in defaults:
default_val = defaults[member_name]
@@ -4627,8 +4957,8 @@ class ExprCallHandle(BaseHandle):
if not isinstance(StructType, ir.IdentifiedStructType):
return
members = Gen.class_members.get(ClassName, [])
has_vtable = ClassName in Gen.class_vtable or ClassName in Gen._cross_module_vtable_classes
base = 1 if has_vtable else 0
# base(vtable 占位槽数)统一走 Gen._member_base(唯一真源)。
base = Gen._member_base(ClassName)
for i, arg in enumerate(Node.args):
if i >= len(members):
break
@@ -4674,6 +5004,9 @@ class ExprCallHandle(BaseHandle):
def _HandleSubscriptPtrLlvm(self, Node: ast.Subscript) -> ir.Value | None:
Gen: LlvmGeneratorMixin = self.Trans.LlvmGen
# pointee-volatile 基址名后缀(同 HandlesExprAttr._HandleSubscriptLlvm);空后缀
# ⇒ 下标 GEP 名仍是 "subscript",既有代码 IR 逐字节不变。
_PVSuffix: str = Gen._SubscriptSuffix(Node.value)
ClassName: str | None = self.Trans.ExprHandler._get_var_class(Node.value, Gen)
if ClassName and ClassName in Gen.structs and Gen._has_function(f'{ClassName}.__getitem__'):
obj_val = self.HandleExprLlvm(Node.value)
@@ -4706,7 +5039,7 @@ class ExprCallHandle(BaseHandle):
if isinstance(ValueVal.type, ir.PointerType) and isinstance(ValueVal.type.pointee, ir.PointerType):
LoadedValueVal = Gen._load(ValueVal, name="Load_subscript")
if isinstance(LoadedValueVal.type, ir.PointerType):
ElemPtr = Gen.builder.gep(LoadedValueVal, [IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(LoadedValueVal, [IndexVal], name=f"subscript{_PVSuffix}")
return ElemPtr
if isinstance(ValueVal.type, ir.PointerType):
pointee = ValueVal.type.pointee
@@ -4717,12 +5050,12 @@ class ExprCallHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr = Gen.builder.gep(ValueVal, [zero, IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(ValueVal, [zero, IndexVal], name=f"subscript{_PVSuffix}")
return Gen._load(ElemPtr, name="arr_subscript_val")
else:
if isinstance(IndexVal.type, ir.IntType) and IndexVal.type.width < 64:
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(64), name="zext_ptr_index")
ElemPtr = Gen.builder.gep(ValueVal, [IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(ValueVal, [IndexVal], name=f"subscript{_PVSuffix}")
if isinstance(pointee, ir.IntType):
return Gen._load(ElemPtr, name="subscript_val")
return ElemPtr
@@ -4738,11 +5071,11 @@ class ExprCallHandle(BaseHandle):
IndexVal = Gen.builder.zext(IndexVal, ir.IntType(32), name="zext_arr_index")
else:
IndexVal = Gen.builder.trunc(IndexVal, ir.IntType(32), name="trunc_arr_index")
ElemPtr = Gen.builder.gep(VarPtr, [zero, IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(VarPtr, [zero, IndexVal], name=f"subscript{_PVSuffix}")
return ElemPtr
arr_alloc = Gen._allocaEntry(ValueVal.type, name="arr_subscript_tmp")
Gen._store(ValueVal, arr_alloc)
ElemPtr = Gen.builder.gep(arr_alloc, [zero, IndexVal], name="subscript")
ElemPtr = Gen.builder.gep(arr_alloc, [zero, IndexVal], name=f"subscript{_PVSuffix}")
return ElemPtr
return None
+20 -4
View File
@@ -194,8 +194,18 @@ class ExprLambdaHandle(BaseHandle):
result_type = ir.IntType(8)
# 重新处理 Else 分支的类型转换
elif isinstance(BodyVal.type, ir.PointerType) and isinstance(result_type, ir.IntType):
# 指针转整数
BodyVal = Gen.builder.ptrtoint(BodyVal, result_type, name="ifexp_body_ptr2int")
if isinstance(BodyVal.type.pointee, ir.IntType) and BodyVal.type.pointee.width == 8:
# 1 字符字符串字面量(i8*)与 char(i8)混用(如
# `p if p is not None else '?'`):取首字符。与 else 侧对称
# (旧代码仅 else 侧这样处理,body 侧 ptrtoint 截断地址是错的)
BodyVal = Gen._load(BodyVal, name="ifexp_body_Load_char")
else:
# 其余指针 → 整数:隐式转换一律禁止(两分支类型不一致须显式 cast)
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"指针隐式转整数已禁止:条件表达式两分支类型不一致({BodyVal.type} / {result_type});"
f"如需转换请显式 cast(如 t.CUInt64T(p) / t.CInt(p))"
)
Gen.builder.branch(MergeBB)
# 在 ElseBB 末尾添加类型转换(如果需要)和分支
if not ElseEndBB.is_terminated:
@@ -213,11 +223,17 @@ class ExprLambdaHandle(BaseHandle):
result_type = ir.IntType(8)
# 重新处理 BodyVal 分支的类型转换(已经在上面处理过了)
elif isinstance(OrelseVal.type, ir.PointerType) and isinstance(result_type, ir.IntType):
# 如果指针指向 i8,加载字符值
if isinstance(OrelseVal.type.pointee, ir.IntType) and OrelseVal.type.pointee.width == 8:
# 1 字符字符串字面量(i8*)与 char(i8)混用(原语义,保留):
# 取首字符;只有非 i8* 的指针转整数才 fail-fast
OrelseVal = Gen._load(OrelseVal, name="ifexp_orelse_Load_char")
else:
OrelseVal = Gen.builder.ptrtoint(OrelseVal, result_type, name="ifexp_orelse_ptr2int")
# 其余指针 → 整数:隐式转换一律禁止(两分支类型不一致须显式 cast)
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"指针隐式转整数已禁止:条件表达式两分支类型不一致({OrelseVal.type} / {result_type});"
f"如需转换请显式 cast(如 t.CUInt64T(p) / t.CInt(p))"
)
Gen.builder.branch(MergeBB)
Gen.builder.position_at_start(MergeBB)
result_phi: Any = Gen.builder.phi(result_type, name="ifexp.result")
+11
View File
@@ -6,6 +6,7 @@ if TYPE_CHECKING:
from lib.core.Handles.HandlesBase import BaseHandle
import vast as ast
import llvmlite.ir as ir
from lib.core.SymbolUtils import RequireIntLiteralStorable
class ExprOpsHandle(BaseHandle):
@@ -108,6 +109,10 @@ class ExprOpsHandle(BaseHandle):
vname: str = Node.left.id
if vname in Gen.var_signedness and Gen.var_signedness[vname]:
is_unsigned = True
# 双指针算术 fail-fast 已**下沉到单一收敛点** `Gen.emit_binary_op`
# (LLVMCG/ExprGen.py):BinOp 与 AugAssign(+=、-=、*= …)都经该函数,
# 判据(运算符属算术/位运算/移位集合 且 两操作数都是 LLVM 指针)与报文
# 在那里一次收口。此处不再保留副本,避免两处判据/报文分叉。
result = Gen.emit_binary_op(Op, LeftVal, RightVal, is_unsigned=is_unsigned)
return result
@@ -244,6 +249,12 @@ class ExprOpsHandle(BaseHandle):
elif OpSymbol == '+':
return OperandVal
elif OpSymbol == '-':
# 一元减 + 整数字面量的**联合越界判定**(与 TPV `_parse_factor` 同口径):
# `-` 不属于字面量,幅值合法(≤ 2^64-1)的字面量取负后可能 < -2^63。
if isinstance(Node.operand, ast.Constant) \
and isinstance(Node.operand.value, int) \
and not isinstance(Node.operand.value, bool):
RequireIntLiteralStorable(Gen, self.Trans, Node, -Node.operand.value)
if isinstance(OperandVal.type, ir.IntType):
return Gen.builder.neg(OperandVal, name="neg_result")
elif isinstance(OperandVal.type, (ir.FloatType, ir.DoubleType)):
+3 -1
View File
@@ -339,7 +339,9 @@ class ExprUtils:
attr_name: str = node.attr
AttrInfo: Any = self.Trans.SymbolTable.lookup(attr_name)
if AttrInfo and getattr(AttrInfo, 'IsEnumMember', None):
return ir.IntType(32)
# 枚举成员表达式的 LLVM 类型 = 该枚举的存储宽度(最宽成员类型)
return self.Trans.LlvmGen._enum_int_type(
getattr(AttrInfo, 'EnumName', None), AttrInfo)
return ir.IntType(32)
elif isinstance(node, ast.BinOp):
left_type: ir.Type = self._infer_expr_llvm_type_full(node.left)
+4 -10
View File
@@ -360,16 +360,10 @@ class ForHandle(BaseHandle):
old_var: ir.Value = Gen.variables[var_name]
if isinstance(old_var.type, ir.PointerType):
elem_type: ir.Type = old_var.type.pointee
# 计算元素大小(字节)
elem_size: int = 8 # 默认指针大小
if isinstance(elem_type, ir.IntType):
elem_size = max(1, elem_type.width // 8)
elif isinstance(elem_type, ir.FloatType):
elem_size = 4
elif isinstance(elem_type, ir.DoubleType):
elem_size = 8
elif isinstance(elem_type, ir.PointerType):
elem_size = 8
# 尺寸统一走 Gen._get_struct_size(与 __sizeof__ / alloc 同源,含对齐/填充):
# 原实现只认 Int/Float/Double/Pointer,struct/array 落到「默认 8」,
# 于是 malloc(8) 后整块 store → 堆越界写坏相邻块。
elem_size: int = Gen._get_struct_size(elem_type)
# malloc 堆存储
heap_raw: ir.Value = Gen.builder.call(malloc_func, [ir.Constant(ir.IntType(64), elem_size)], name=f"heap_{var_name}")
heap_ptr: ir.Value = Gen.builder.bitcast(heap_raw, ir.PointerType(elem_type), name=f"heap_{var_name}_ptr")
+10 -4
View File
@@ -898,7 +898,7 @@ class FunctionHandle(BaseHandle):
if '[' in replacement:
SubNode: ast.AST | None = None
try:
SubNode = ast.parse(replacement, mode='eval').body
SubNode = ast.parse(replacement, '<stub:type_expr>', mode='eval').body
except Exception:
SubNode = None
if not isinstance(SubNode, ast.Subscript):
@@ -936,7 +936,7 @@ class FunctionHandle(BaseHandle):
slice_node = self._make_replacement_node(inner_name)
else:
try:
slice_node = ast.parse(inner_name, mode='eval').body
slice_node = ast.parse(inner_name, '<stub:type_expr>', mode='eval').body
except Exception:
slice_node = self._make_name_node(inner_name)
if slice_node is None:
@@ -1627,8 +1627,10 @@ class FunctionHandle(BaseHandle):
if Arg.annotation:
try:
PInfo = self.ResolveAnnotationType(Arg.annotation)
if PInfo and (PInfo.DataConst or PInfo.VarConst):
Gen.var_const_flags[Arg.arg] = True
# const/volatile 形参登记(统一登记入口)。const 判据沿用形参历史
# 条件(IsParam=True,见 BaseGen.RegisterVarQualifiers 说明);
# volatile / pointee-volatile 与 TransPyV 侧同口径。
Gen.RegisterVarQualifiers(Arg.arg, PInfo, Arg.annotation, IsParam=True)
except Exception as _e:
if _config_mode == "strict":
self.Trans.LogWarning(f"异常被忽略: {_e}")
@@ -2140,6 +2142,10 @@ class FunctionHandle(BaseHandle):
Gen.var_struct_class = {}
Gen.var_ptr_element = {}
Gen.var_const_flags = {}
# const/volatile 的**函数作用域层**随函数出栈清空(模块级层 global_* 与
# 类字段层 field_* 是持久的,不在此清;见 BaseGen 的登记说明)。
Gen.var_volatile_flags = {}
Gen.var_pointee_volatile_flags = {}
# 恢复保存的 var_type_info
Gen.var_type_info = saved_var_type_info
Gen.var_type_assignments = saved_var_type_assignments
+369 -20
View File
@@ -14,7 +14,9 @@ from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _config_mode
from lib.includes import t
from lib.includes.t import CTypeRegistry
from lib.core.SymbolUtils import ParseListAnnotation, ExtractTypeNameFromBinOp, AnnotationContainsName, FindStructNameInAnnotationWithContext
from lib.core.SymbolUtils import ParseListAnnotation, ExtractTypeNameFromBinOp, AnnotationContainsName, FindStructNameInAnnotationWithContext
from lib.core.SymbolUtils import EnumClassIntWidth, ExpandSoftKeywords
from lib.core.SymbolUtils import ClassHasFuncLevelCVTable, IsVtableSlotMethod
# 注解里的存储类 / 限定符标记:这些名字**不是类名**(联合注解里与指针类混写,
@@ -25,6 +27,107 @@ _MARKER_ANNOT_NAMES: frozenset[str] = frozenset({
'State', 'TLS',
})
# 标记基类名(不是真实父类):与 HandlesClassDef._EmitClassLlvm 的 base 跳过表同口径。
_MARKER_BASE_NAMES: frozenset[str] = frozenset({
'Object', 'CVTable', 'NoVTable', 'CStruct', 'CUnion', 'CEnum', 'REnum',
'Exception', 'Enum',
})
def _HasRealParentClass(Node: ast.ClassDef) -> bool:
"""类是否声明了**真实父类**(排除 Object/CVTable/NoVTable/CEnum/... 标记基类)。"""
for base in getattr(Node, 'bases', None) or []:
base_name: str | None = None
if hasattr(base, 'id'):
base_name = base.id
elif hasattr(base, 'attr'):
base_name = base.attr
elif isinstance(base, ast.Subscript):
if hasattr(base.value, 'id'):
base_name = base.value.id
elif hasattr(base.value, 'attr'):
base_name = base.value.attr
if base_name and base_name not in _MARKER_BASE_NAMES:
return True
return False
def _VtableByClassLevelForImport(Node: ast.ClassDef, Gen: object) -> bool:
"""跨模块导入类的 VtableByClassLevel(忽略方法级 CVTable 标记的 vtable 状态)。
与 HandlesClassDef._resolve_class_flags 的口径一致:类级 @t.CVTable 或
"有真实父类且父类不是 NoVTable" ⇒ True。供 SymbolUtils.IsVtableSlotMethod
判定方法是否占虚表槽(唯一的判据源),使导入侧的虚表槽序与定义模块逐槽相同。
"""
if getattr(Node, 'decorator_list', None):
for decorator in Node.decorator_list:
if isinstance(decorator, ast.Attribute) and decorator.attr == 'CVTable':
return True
if isinstance(decorator, ast.Name) and decorator.id == 'CVTable':
return True
if not _HasRealParentClass(Node):
return False
_novtable_set: set = getattr(Gen, '_cross_module_novtable', None) or set()
for base in getattr(Node, 'bases', None) or []:
base_name: str | None = None
if hasattr(base, 'id'):
base_name = base.id
elif hasattr(base, 'attr'):
base_name = base.attr
if base_name and base_name not in _MARKER_BASE_NAMES:
if base_name in _novtable_set:
return False
return True
def _ParseFailureSite(Exc: BaseException) -> str:
"""解析异常 → `行[:列]` 文本;定位信息完全取不到时返回 `'?'`。
CPython/vast 的 SyntaxError 若按 (msg, (file, lineno, offset, text)) 构造则带
`.lineno`/`.offset`;而 TPC 自研 vast 的 Parser/ExprParser 只把行号写进报文
(`f"... at line {tok.Line}"`,见 lib/vast/Parser.py:826、ExprParser.py:640),
字段为空 ⇒ 必须正则兜底,否则诊断退化成"无行号",无法直接跳到失败行。
"""
Line: int = int(getattr(Exc, 'lineno', 0) or 0)
Col: int = int(getattr(Exc, 'offset', 0) or 0)
if not Line:
Matched: re.Match | None = re.search(r'at line (\d+)', str(Exc))
if Matched:
Line = int(Matched.group(1))
if not Line:
return '?'
return f"{Line}:{Col}" if Col else str(Line)
def _ReportDeclarationParseFailure(FilePath: str, Exc: BaseException, Consequence: str) -> None:
"""声明文件"读 → parse"失败的统一诊断口径(strict fail-fast / 非严格告警)。
诊断必带四要素:**文件路径**、**失败行[:列]**、**异常类型与文本**、**后果说明**
(后果由调用方按点位给出,如"该模块声明将全部缺失,后续可能表现为
`Undefined function`")。原实现(`_LoadModuleDeclarationsFromFile:364-367`)只在
strict 模式调 `Trans.LogWarning`——而 `Trans.Warnings` 全仓只被 append、无人消费
(BaseTranslator.py:55/137),**连 strict 模式都是无声的**;非严格模式更是直接
`return`。于是 `includes/ast/base.vp:168` 的 `enum ASTFlag:` 让整个 base.vp 的
声明(含顶层 `_init_ast`)一个都不注册,下游只报 `Undefined function: '_init_ast'`,
浪费了一轮排查。
fail-fast 依据:仓库有严格模式开关 `lib/constants/config.py:59 mode = "strict"`,
且既有多处同口径 `if _config_mode == "strict": raise`(本文件
`_RegisterSubModuleSha1:547`、`_build_stub_index:2443`、`_TryLoadStructFromStub:2539`
等)——本函数与它们一致,未放宽任何既有 fail-fast。
`FileNotFoundError` 例外:属"isfile 与 open 之间文件被删/被 `--clean` 清掉"的
竞态(先例见本文件 `_build_stub_index:2439-2441`),不是"源码解析失败",
故只告警、不触发 fail-fast。
"""
Site: str = _ParseFailureSite(Exc)
Message: str = (
f"声明解析失败 {FilePath}:{Site} — {type(Exc).__name__}: {Exc};{Consequence}"
)
if _config_mode == "strict" and not isinstance(Exc, FileNotFoundError):
raise ImportError(Message) from Exc
_vlog().warning(Message, "Exception")
class ImportHandle(BaseHandle):
_struct_Load_cache = {}
@@ -353,10 +456,21 @@ class ImportHandle(BaseHandle):
try:
with open(pyi_path, 'r', encoding='utf-8') as f:
ModuleCode = f.read()
ModuleTree = ast.parse(ModuleCode)
# Viper 软关键字层(与 TPC 其余前端入口同一层):`.vp` 源码可含
# `enum X:` / `fn` 等语法糖,直接 vast.parse 会在糖行 SyntaxError →
# 被下面的 except 吞掉并 return → **该模块的全部声明(函数/类/全局)
# 一个都不注册**,调用方随后报 "Undefined function"
# (includes/ast/base.vp 的 `enum ASTFlag:` → 六处 `from .base import …`
# 全部拿不到 `_init_ast`,即本次现场)。`.vpi`/`.pyi` 无糖,展开是空操作。
ModuleTree = ast.parse(ExpandSoftKeywords(ModuleCode), pyi_path)
except Exception as _e:
if _config_mode == "strict":
self.Trans.LogWarning(f"异常被忽略: {_e}")
# 统一诊断口径(strict fail-fast / 非严格告警),必带文件+行号+异常+后果。
# 原实现"吞异常 + return"(strict 也只写进无人消费的 Trans.Warnings),
# 是本次 `Undefined function: '_init_ast'` 被伪装成下游症状的直接原因。
_ReportDeclarationParseFailure(
pyi_path, _e,
"该模块的全部声明(函数/类/全局/枚举)不会注册,"
"后续引用方可能表现为 Undefined function / Undefined struct")
return
# 计算源模块名(从 .pyi 文件路径推导)。
@@ -715,7 +829,9 @@ class ImportHandle(BaseHandle):
try:
with open(FullModulePath, 'r', encoding='utf-8') as f:
ModuleCode = f.read()
ModuleTree = ast.parse(ModuleCode)
# 同上:`.vp` 源码须先过 Viper 软关键字层再 vast.parse(否则含
# `enum X:` / `fn` 的模块声明整体丢失,落到 except 的 stub 兜底)。
ModuleTree = ast.parse(ExpandSoftKeywords(ModuleCode), FullModulePath)
if module_name == 'pyzlib':
func_names = [n.name for n in ModuleTree.body if isinstance(n, ast.FunctionDef)]
has_functions_or_classes = False
@@ -854,6 +970,21 @@ class ImportHandle(BaseHandle):
elif 'includes' in FullModulePath and module_name:
self._LoadDeclarationsFromStubLlvm(module_name, Gen)
except Exception as e:
# 同族口径:读/parse 失败必须显式(文件+行号+异常+后果),不允许静默降级。
# 非严格模式仍保留既有 stub 兜底(StubGen 产物声明仍可用);strict 模式
# 由 _ReportDeclarationParseFailure 直接 fail-fast,与 :412 主点一致。
#
# **限定条件**:只有"确实有源码文件却读/解析失败"才算声明解析失败。本函数
# 的 `FullModulePath` 可为 None——TPV 自举工程 includes 为合并形态,源码模块
# (如 lib.Stub.StubMerger 包)不提供裸 .vp 文件,声明由上方
# `_LoadDeclarationsFromStubLlvm` 从 temp/*.stub.ll 加载,属**正常路径**
# (见本函数 :750 附近的长注释);此时 `open(None)` 的 TypeError 与"文件恰好
# 不在"都不应触发 fail-fast。
if FullModulePath and os.path.isfile(FullModulePath):
_ReportDeclarationParseFailure(
FullModulePath, e,
"该模块的源码声明全部未注册,"
"仅能依赖 stub 兜底(非严格模式);声明可能与源码不一致")
if module_name:
self._LoadDeclarationsFromStubLlvm(module_name, Gen)
@@ -1597,6 +1728,13 @@ class ImportHandle(BaseHandle):
if isinstance(arg.value, ast.Attribute):
if getattr(arg.value.value, 'id', None) == 't' and arg.value.attr == 'attr' and arg.attr == 'packed':
IsPacked = True
# 方法级 virtual(`virtual def m(...)` ⇒ 方法装饰器 @t.CVTable)触发类级 vtable:
# 与 HandlesClassDef._resolve_class_flags / TPV _detect_vtable_status 的
# `_has_any_func_cvtable` 分支对齐(跨模块这里只能判"类级 NoVTable /
# 无真实父类"两种可判定形态;父类 vtable 状态由 .pyi 按需注册路径解析)。
if not IsCVTable and ClassHasFuncLevelCVTable(Node):
if IsNoVTable or not _HasRealParentClass(Node):
IsCVTable = True
return (IsGenericClass, IsCenum, IsRenum, IsCpythonObject,
IsCVTable, IsNoVTable, IsPacked)
@@ -1642,6 +1780,14 @@ class ImportHandle(BaseHandle):
EnumTypeNode.Name = ClassName
EnumTypeNode.BaseType = t.CEnum(ClassName)
EnumTypeNode.IsEnum = True
# [跨模块枚举宽度] 读侧重推导:.vpi/.pyi 保留成员注解(`Name: T`),
# 故在本处按"最宽成员类型"重算(与定义端 HandlesClassDef 同一规则源
# EnumClassIntWidth),使跨模块使用同一枚举时宽度一致。本函数会以短名
# 覆盖共享符号表(_shared_sym.json pickle)中的同名条目,若不在此登记
# 宽度,覆盖后即退化为默认 i32(= 上一轮遗留缺口)。旧产物缺注解时
# 自然回落 32(Default),不崩。
EnumWidth: int = EnumClassIntWidth(Node.body, self.Trans.SymbolTable)
EnumTypeNode.set('enum_width', EnumWidth)
self.Trans.SymbolTable.insert(ClassName, EnumTypeNode)
next_enum_value: int = 0
for item in Node.body:
@@ -1672,6 +1818,7 @@ class ImportHandle(BaseHandle):
MemberNode.EnumName = ClassName
MemberNode.Lineno = item.lineno
MemberNode.IsEnumMember = True
MemberNode.set('enum_width', EnumWidth)
self.Trans.SymbolTable.insert(VarName, MemberNode)
self.Trans.SymbolTable.insert(f"{ClassName}.{VarName}", MemberNode)
self.Trans.SymbolTable.insert(f"{ClassName}_{VarName}", MemberNode)
@@ -1776,6 +1923,26 @@ class ImportHandle(BaseHandle):
next_tag = value + 1
RenumTypeNode.RenumVariants = variant_names
return
# 【声明发射重入标记】本函数主体会重入自身:成员收集(CollectAnnAssignMember
# → ResolveAnnotationType → 泛型特化 list[T]/GSListNode[T] → _EmitClassLlvm
# → StructGen._generate_structs → _TryLoadStructFromStub(X)
# → _TryLoadClassMembersFromPyi(X) → 本函数(X))。首字段即指针/容器的类必现
# (llvmlite.Param 的 Attrs、match.MatchOr 的 patterns、stmts.Interactive 的
# body…),且每轮仅 4 帧:本函数 → _TryLoadStructFromStub → 成员加载 → 本函数。
# 两条加载路径的缓存键(带/不带 source_sha1 前缀)互不相同,失败时键还会被
# 清除(_TryLoadStructFromStub 的"仍 opaque ⇒ 删键重试"),缓存守卫挡不住
# ⇒ 无限递归(RecursionError 被上层 except 吞掉,表现为类型解析静默退化)。
# 登记 (ClassName, source_sha1);由 `_TryLoadStructFromStub` 里的"顺带加载成员"
# 那一步短路(该边是递归环的唯一闭合边:结构体 body 加载会连带成员加载 → 又回到
# 本函数);本函数自身**不**短路——重入的成员收集是"把表填起来"的唯一途径,
# 消费者(重入编译的其他模块方法体按名查字段)必须能拿到它。
# 放在 CEnum/REnum 早退(上方 return)之后:那两个分支不加载成员/结构体。
_emit_inprog: set | None = getattr(Gen, '_extern_decl_emit_in_progress', None)
if _emit_inprog is None:
_emit_inprog = set()
Gen._extern_decl_emit_in_progress = _emit_inprog
_emit_key: tuple = (ClassName, source_sha1)
_emit_inprog.add(_emit_key)
# 先初始化 class_methods/class_members 等容器,确保 _TryLoadStructFromStub
# 和 _get_or_create_struct 中的 IsEnum 守卫(clean_name not in class_methods)
# 能正确识别 AST 节点类名(避免 ASTKind 枚举名冲突时把节点类当枚举返回 i32)
@@ -1814,6 +1981,11 @@ class ImportHandle(BaseHandle):
# 会被解析为 opaque struct,LLVM 报 "invalid type for function argument"。
# 字段(AnnAssign)仍正常加载,确保继承字段(如 GSListNode.Next)可被展平。
if not IsGenericClass:
# 虚表槽筛选:方法级 `novirtual def`(⇒ @t.NoVTable)不进槽、方法级
# `virtual def`(⇒ @t.CVTable)进槽——判据为 SymbolUtils.IsVtableSlotMethod
# (唯一规则源,与定义端 HandlesClassDef / TPV _should_method_be_virtual 同口径),
# 保证跨模块虚表槽序与定义模块逐槽一致。
_VtByClassLevel: bool = _VtableByClassLevelForImport(Node, Gen)
for _item in Node.body:
if isinstance(_item, ast.FunctionDef):
_has_methods_prescan = True
@@ -1825,7 +1997,8 @@ class ImportHandle(BaseHandle):
if _MethodName in ('__new__', '__init__', '__before_init__'):
continue
_FuncFullName = f"{ClassName}.__init__" if _MethodName == "__init__" else f"{ClassName}.__call__" if _MethodName == "__call__" else f"{ClassName}.{_MethodName}"
if _FuncFullName not in Gen.class_methods[ClassName]:
_IsSlot: bool = (not IsCVTable) or IsVtableSlotMethod(_item, _VtByClassLevel)
if _IsSlot and _FuncFullName not in Gen.class_methods[ClassName]:
Gen.class_methods[ClassName].append(_FuncFullName)
if _has_methods_prescan and IsCVTable:
Gen._cross_module_vtable_classes.add(ClassName)
@@ -2016,10 +2189,14 @@ class ImportHandle(BaseHandle):
# 构造函数(__new__/__init__/__before_init__)不放入 vtable,
# 避免子类与基类 vtable 大小不一致导致虚分派索引偏移。
# register_method 内部也会过滤,此处仅跳过 class_methods.append。
# 方法级修饰符(virtual def/novirtual def)⇒ 是否占槽按
# SymbolUtils.IsVtableSlotMethod(唯一规则源)判定,与定义模块逐槽一致。
_IsSlot2: bool = (not IsCVTable) or IsVtableSlotMethod(
item, _VtableByClassLevelForImport(Node, Gen))
if MethodName not in ('__new__', '__init__', '__before_init__'):
if FuncFullName not in Gen.class_methods[ClassName]:
if _IsSlot2 and FuncFullName not in Gen.class_methods[ClassName]:
Gen.class_methods[ClassName].append(FuncFullName)
Gen.register_method(ClassName, FuncFullName)
Gen.register_method(ClassName, FuncFullName, _IsSlot2)
is_item_static = any(isinstance(d, ast.Name) and d.id == 'staticmethod' for d in item.decorator_list)
is_item_property = any(isinstance(d, ast.Name) and d.id == 'property' for d in item.decorator_list)
is_item_classmethod = any(isinstance(d, ast.Name) and d.id == 'classmethod' for d in item.decorator_list)
@@ -2158,6 +2335,13 @@ class ImportHandle(BaseHandle):
# 同时处理父类 class_members 全为占位符的情况(清除后重新加载)。
_any_parent_loaded: bool = False
_all_inherited: list[tuple] = []
# 展平完整性判定(供 `_members_flattened` 登记):仅当"每个声明父类都已在
# class_members 中就绪且其自身已展平",或"占位符路径已按 struct 槽位补齐
# 继承前缀"时才为 True。未完成(典型:声明期 struct 尚 opaque / 父类所在
# 模块 stub 未就绪)时不登记,后续查询会重跑本节把继承前缀补齐
# —— 使展平结果与"父类/struct 就绪的先后顺序"无关。
_flat_complete: bool = not _ParentClasses
_placeholder_applied: bool = False
# 按继承源去重的跨父类共享 seen 集合(必须在 for 循环外,跨所有父类
# 共享——否则每个父类新建 seen,菱形祖先(如 ClassGenBase)被每个
# 继承它的 ClassGen* 父类各展平一次 → 重复(Generator 曾 707 字段)
@@ -2270,6 +2454,14 @@ class ImportHandle(BaseHandle):
_inherited_ph: list[tuple] = [(f'__inherited_{i}', _st.elements[i]) for i in range(_num_inherited)]
# 占位符路径同样整体前置(继承字段在 struct 头部),与主路径保持一致
Gen.class_members[ClassName] = _inherited_ph + Gen.class_members[ClassName]
_placeholder_applied = True
if _ParentClasses:
# 完整展平的判据:占位符路径已按 struct 槽位补齐继承前缀,或
# 每个声明父类都已在 class_members 中就绪且其自身已展平。
_flat_complete = _placeholder_applied or all(
(_pc in Gen.class_members and _pc in Gen._members_flattened)
for _pc in _ParentClasses if _pc
)
# 成员收集 + 继承展平完成 → 解除收集中保护,并用完整 class_members 设置 body。
# 必须在此显式触发一次 _generate_structs():保护期内(上方 add)所有重入的
# _generate_structs 都跳过了本类,若此处不再触发,本类 struct 将保持 opaque
@@ -2277,6 +2469,10 @@ class ImportHandle(BaseHandle):
# 本保护要消除的目标。
Gen._collecting_members_set.discard(ClassName)
Gen._generate_structs()
if _flat_complete:
# 登记"该表已按父类展平",供 _TryLoadClassMembersFromPyi 的 guard 与
# _member_base 的查询前补齐使用。未登记 ⇒ 后续任一查询都可重跑本节补齐。
Gen._members_flattened.add(ClassName)
# ============================================================
# 跨模块 vtable 槽布局与定义模块一致:父类方法在前(子类覆盖放父类槽位),
# 子类新增方法在后。定义模块(HandlesClassDef)已按此合并,跨模块若只
@@ -2301,12 +2497,18 @@ class ImportHandle(BaseHandle):
for _m in Gen.class_methods.get(ClassName, []):
_mn: str = _m.split('.')[-1] if '.' in _m else _m
_child_method_map[_mn] = _m
# 从 .vpi 的 FunctionDef 补充子类方法映射(覆盖方法可能尚未注册)
# 从 .vpi 的 FunctionDef 补充子类方法映射(覆盖方法可能尚未注册)。
# 方法级 `novirtual def`(⇒ @t.NoVTable)不占槽,**不得**用它覆盖父类槽位
# ——与 TPV _collect_vtable_methods 的 child_nvt 分支同口径(子类标记
# NoVTable 时槽位仍指向实际定义类的实现)。
_VtByClassLevelC: bool = _VtableByClassLevelForImport(Node, Gen)
for _item_m in getattr(Node, 'body', []):
if isinstance(_item_m, ast.FunctionDef):
_mn2: str = _item_m.name
if _mn2 in ('__new__', '__init__', '__before_init__'):
continue
if not IsVtableSlotMethod(_item_m, _VtByClassLevelC):
continue
_child_method_map[_mn2] = f"{ClassName}.{_mn2}"
_new_methods: list[str] = []
_covered_names: set[str] = set()
@@ -2348,6 +2550,8 @@ class ImportHandle(BaseHandle):
bw = bitfields.get(name, 0)
Gen.class_member_bitoffsets[ClassName][name] = current_bit_offset
current_bit_offset += bw
# 声明发射结束:解除重入标记(与上方 add 配对)。
_emit_inprog.discard(_emit_key)
def _build_stub_index(self) -> None:
"""扫描所有 .stub.ll 文件构建 class_name → 条目列表索引
@@ -2408,6 +2612,28 @@ class ImportHandle(BaseHandle):
_vlog().warning(f"构建 stub 索引失败: {_e}", "Exception")
self._stub_index_scanned.add(stub_path)
@staticmethod
def _ExternDeclEmitting(Gen: LlvmGeneratorMixin, class_name: str, source_sha1: str | None) -> bool:
"""该类(同名同源)的跨模块声明发射是否**正在进行中**(递归重入判定)。
标记由 `_EmitExternalClassDeclLlvm` 主体开头登记、末尾解除(键
`(ClassName, source_sha1)`)。定义模块 sha1 未知(None)时按类名保守判定:
None 与 '<sha1>' 两种形态可能指同一个类,宁可本次漏加载(由最外层收尾)
也不递归——`_EmitExternalClassDeclLlvm` → `_TryLoadStructFromStub(X)`
→ `_TryLoadClassMembersFromPyi(X)` → `_EmitExternalClassDeclLlvm(X)`
每轮仅 4 帧,且两条加载路径的缓存键(带/不带 sha1 前缀)互不相同、
失败时键还会被清除("仍 opaque ⇒ 删键重试")⇒ 缓存守卫挡不住,
会一直递归到 RecursionError(被上层 except 吞掉 ⇒ 类型解析静默退化)。
"""
_inprog: set | None = getattr(Gen, '_extern_decl_emit_in_progress', None)
if not _inprog:
return False
if (class_name, source_sha1) in _inprog:
return True
if source_sha1 is None:
return any(_k[0] == class_name for _k in _inprog)
return False
def _TryLoadStructFromStub(self, class_name: str, Gen: LlvmGeneratorMixin, source_sha1: str | None = None) -> None:
# 缓存键包含 Gen 实例标识,确保每个模块的 Gen 都能独立加载 struct 定义,
# 同时仍能防止同一 Gen 内的递归重入。原先使用纯 class_name 作为键会导致
@@ -2503,7 +2729,17 @@ class ImportHandle(BaseHandle):
raise
_vlog().warning(f"处理导入失败: {_e}", "Exception")
stub_filename = actual_sha1 + '.stub.ll'
self._TryLoadClassMembersFromPyi(class_name, stub_filename, Gen)
# 【递归环闭合边 · 重入短路】该类的声明发射尚未结束(`_EmitExternalClassDeclLlvm`
# 正在跑:见其主体开头的 `_extern_decl_emit_in_progress` 登记)⇒ 本调用是环内一环:
# 发射(X) → _TryLoadStructFromStub(X) →(此处)成员加载(X) → 发射(X) …
# 每轮仅 4 帧,且两条加载路径的缓存键(带/不带 source_sha1 前缀)互不相同、
# 失败时键还会被清除("仍 opaque ⇒ 删键重试")⇒ 缓存守卫挡不住 ⇒ 无限递归。
# 短路对象**只限"顺带加载成员"这一步**:上面的 struct body 解析照常执行
# (发射主体依赖它提前 set_body —— 否则 llvmlite `__len__` 的
# `assert self.elements is not None` 直接炸,见 _EmitExternalFuncDeclLlvm
# 的 `if _new_struct:`);成员由外层发射收尾统一收集。
if not ImportHandle._ExternDeclEmitting(Gen, class_name, source_sha1):
self._TryLoadClassMembersFromPyi(class_name, stub_filename, Gen)
# 根本修复:加载后 struct 仍为 opaque 时(如选中 'type opaque' 占位条目、
# 或 set_body 未生效),不保留成功缓存。保留会导致同 Gen 内后续调用永久
# 跳过补载,opaque 状态被固化,vtable 分派 GEP 直接崩溃。
@@ -2515,6 +2751,82 @@ class ImportHandle(BaseHandle):
except KeyError:
pass
def EnsureClassMembersFlattened(self, class_name: str, Gen: LlvmGeneratorMixin) -> None:
"""查询前补齐:确保 `class_members[class_name]` 已按父类展平(幂等、顺序无关)。
背景:`_EmitExternalClassDeclLlvm` 的展平只在"父类成员表 + 声明期 struct
body 都已就绪"时才能算出继承前缀;若当时 struct 仍 opaque(定义模块 stub
尚未生成)或父类未就绪,表就停在"自有字段",而长度随之与 struct 元素数
不再自洽(delta = 缺失槽数)。此后 `_TryLoadClassMembersFromPyi` 的原
guard(`len>0` 即短路)会把这份残缺表永久冻结 ⇒ 该类**自有字段**的 GEP
下标缺了继承前缀而整体前移(`ExceptHandler.type` 返回 1 而非 8)。
本方法是该冻结的解除点(与 `FuncGen._member_base` 的查询侧调用配套):
只有"未登记已展平 且 该类有父类 且 struct 与成员表长度不自洽(含 struct
尚未登记)"才重跑展平;对已展平类、无父类类(含 REnum 变体等非继承键)
是纯内存判断直接返回,真正重跑的类另按依赖签名去重(见下)。
以 `(struct 元素数, 成员表长度)` 作为"依赖状态签名":同一签名下补齐无效
时不再重复尝试(避免依赖长期不就绪时在热路径反复重建);依赖一旦变化
(struct 被 `_generate_structs` 修正、或表被别处改动)签名变化即重试。
"""
if Gen is None:
return
_flat: set | None = getattr(Gen, '_members_flattened', None)
if _flat is None or class_name in _flat:
return
_inprog: set | None = getattr(Gen, '_members_flatten_in_progress', None)
if _inprog is None or class_name in _inprog:
return
if class_name in (getattr(Gen, '_collecting_members_set', None) or ()):
# 该类的字段正在收集中(重入路径)⇒ 不在此处重跑展平,
# 交由收集完成后的 `_EmitExternalClassDeclLlvm` 正常收尾。
return
_parents: list = (getattr(Gen, 'class_parents', {}) or {}).get(class_name) or []
if not _parents:
# 无父类 ⇒ 展平平凡成立。表非空时登记(后续查询直接短路);表为空时
# 不登记、也不在此补齐(那是"表未加载",由 _get_member_offset 既有的
# 懒加载路径负责,不在本专项范围内)。
if Gen.class_members.get(class_name):
_flat.add(class_name)
return
_tbl: list = Gen.class_members.get(class_name) or []
_st = Gen.structs.get(class_name)
_num_elements: int = -1
if isinstance(_st, ir.IdentifiedStructType) and _st.elements is not None:
_num_elements = len(_st.elements)
if (_num_elements - len(_tbl)) in (0, 1):
# struct 布局与成员表长度自洽(无 vtable 槽 / 恰一个 vtable 槽)
# ⇒ 已展平:登记,后续查询直接短路。
_flat.add(class_name)
return
_sig: tuple[int, int] = (_num_elements, len(_tbl))
_sigs: dict = getattr(Gen, '_members_flatten_repair_sig', None)
if _sigs is None:
_sigs = {}
Gen._members_flatten_repair_sig = _sigs
if _sigs.get(class_name) == _sig:
# 同一依赖状态下补齐无效(父类/struct 仍未就绪)⇒ 不重复尝试。
return
_sigs[class_name] = _sig
if not hasattr(self, 'Trans') or self.Trans is None:
return
_sha1: str | None = (getattr(Gen, 'class_sha1_map', {}) or {}).get(class_name)
_cands: list[str] = [f"{_sha1}.stub.ll"] if _sha1 else []
if not _cands:
_sha1_map: dict = getattr(Gen, 'ModuleSha1Map', {}) or {}
_cands = [f"{_s}.stub.ll" for _s in _sha1_map.values()]
_inprog.add(class_name)
try:
for _stub in _cands:
try:
if self._TryLoadClassMembersFromPyi(class_name, _stub, Gen):
break
except Exception:
continue
finally:
_inprog.discard(class_name)
def _TryLoadClassMembersFromPyi(self, class_name: str, stub_filename: str, Gen: LlvmGeneratorMixin,
force: bool = False) -> bool:
"""从 .vpi/.pyi 声明文件加载类成员。返回 True 表示在 pyi 中找到该类并完成处理。
@@ -2526,19 +2838,42 @@ class ImportHandle(BaseHandle):
"""
ProjectRoot = self._find_project_root()
temp_dir = os.path.join(ProjectRoot, 'temp')
if (not force) and class_name in Gen.class_members and len(Gen.class_members[class_name]) > 0:
# guard 命中:class_members 已存在(可能从别处预置、不完整),但
# class_parents 可能未设置 → 多继承展平(_FlattenExt)的递归/anc_names
# guard(2026-xx 收紧):只在"表非空 **且已登记为已展平**"时短路。原实现
# 只看 `len>0`,于是"停在自有字段"的残缺表(展平未完成)一旦生成就再也不能
# 补齐(顺序敏感)——这正是自有字段下标缺继承前缀的成因。未展平的表允许
# 重跑 `_EmitExternalClassDeclLlvm`(其自有字段收集按名去重、继承前缀按名
# 去重,重跑幂等),从而与父类/struct 的就绪顺序无关。
# 【重入短路 · 与成员表长度解耦】该类**正在**被 `_EmitExternalClassDeclLlvm`
# 收集字段(`_collecting_members_set` 置位于本文件 :2054、解除于 :2481)时,
# 本次调用必是递归重入,直接返回、交由最外层收尾。
# 为什么判据不能带 `len(class_members)>0`:类内**第一个**字段的注解解析就会
# 重入本方法 —— CollectAnnAssignMember → ResolveAnnotationType → 泛型特化
# (list[T] 等)→ _EmitClassLlvm → StructGen._generate_structs
# → _TryLoadStructFromStub(X) → 本方法(X)。此刻 class_members[X] 仍为空
# (第一个字段尚未收集完),旧判据(要求 len>0)放行 ⇒ 无限递归(每轮 4 帧):
# _TryLoadClassMembersFromPyi(X) → _EmitExternalClassDeclLlvm(X)
# → _TryLoadStructFromStub(X) → _TryLoadClassMembersFromPyi(X) …
# 现场(2026-09-24 深度探针):match.MatchOr 的 patterns、stmts.Interactive 的
# body 等"首字段即指针/容器"的类;RecursionError 被上层 except 吞掉后表现为
# `WARN: … 忽略异常 maximum recursion depth exceeded` + 类型解析静默退化
# (失败的 typedef 报文、注解赋值 RHS 求值为空等次生现象)。
_collecting: set | None = getattr(Gen, '_collecting_members_set', None)
if ((not force) and (
(_collecting is not None and class_name in _collecting)
or (class_name in Gen.class_members
and len(Gen.class_members[class_name]) > 0
and class_name in Gen._members_flattened))):
# guard 命中:class_members 已存在(可能从别处预置、不完整)或该类正在
# 收集中;但 class_parents 可能未设置 → 多继承展平(_FlattenExt)的递归/anc_names
# 失效 → 菱形去重失败 → class_members 不完整(Generator 89/83 vs 114)
# → this_adjust 错位崩溃。此处补解析 .vpi 设置 class_parents(不重载
# class_members,避免覆盖可能更完整的已有数据)。
_path0: str = os.path.join(temp_dir, stub_filename.replace('.stub.ll', '.vpi'))
try:
_pyi_b0: str = stub_filename.replace('.stub.ll', '.vpi')
_path0: str = os.path.join(temp_dir, _pyi_b0)
_tree0: ast.AST | None = getattr(self, '_pyi_cache', {}).get(_path0)
if _tree0 is None and os.path.isfile(_path0):
with open(_path0, 'r', encoding='utf-8') as _fh0:
_tree0 = ast.parse(_fh0.read())
_tree0 = ast.parse(_fh0.read(), _path0)
getattr(self, '_pyi_cache', {})[_path0] = _tree0
if _tree0 is not None:
for _n0 in _tree0.body:
@@ -2553,8 +2888,14 @@ class ImportHandle(BaseHandle):
Gen.class_parents = {}
Gen.class_parents[class_name] = _b0
break
except Exception:
pass
except Exception as _e0:
# 同族口径:此处原为 `except Exception: pass`(静默)。class_parents
# 未回填 ⇒ 多继承/菱形展平不完整 ⇒ class_members 长度不确定 ⇒
# this_adjust 错位。改为显式诊断(strict fail-fast / 非严格告警)。
_ReportDeclarationParseFailure(
_path0, _e0,
f"类 '{class_name}' 的 class_parents 未能回填,"
"多继承展平可能不完整(字段下标/this_adjust 可能错位)")
return False
pyi_basename = stub_filename.replace('.stub.ll', '.vpi')
pyi_path = os.path.join(temp_dir, pyi_basename)
@@ -2576,9 +2917,15 @@ class ImportHandle(BaseHandle):
return False
try:
with open(pyi_path, 'r', encoding='utf-8') as f:
self._pyi_cache[pyi_path] = ast.parse(f.read())
self._pyi_cache[pyi_path] = ast.parse(f.read(), pyi_path)
except Exception as e:
_vlog().warning(f"HandlesImports: 忽略异常 {e}", exc_info=e)
# 同族口径:原告警不带文件名("忽略异常"),改为显式诊断
# (strict fail-fast / 非严格告警)。.vpi 是编译器无糖产物,
# 解析失败属内部不变量被破坏,非严格模式仍缓存 None 并返回 False。
_ReportDeclarationParseFailure(
pyi_path, e,
f"类 '{class_name}' 的成员/父类信息不会加载,"
"class_members 可能残缺(字段下标/this_adjust 可能错位)")
self._pyi_cache[pyi_path] = None
return False
@@ -2621,6 +2968,8 @@ class ImportHandle(BaseHandle):
# 强制重载:清空按类缓存的成员数据,重建出与定义模块一致的完整布局。
# 不清空则 dedup 只在末尾追加缺失字段 → 字段顺序与 struct elements 错位。
Gen.class_members[class_name] = []
# 同步撤销"已展平"登记:重建若因依赖未就绪而不完整,须允许后续查询再补齐。
Gen._members_flattened.discard(class_name)
for _gd_attr in ('class_member_defaults', 'class_member_signeds', 'class_member_bitfields',
'class_member_byteorders', 'class_member_bitoffsets'):
_gd: dict | None = getattr(Gen, _gd_attr, None)
+11 -3
View File
@@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING:
from lib.core.translator import Translator
from lib.core.Handles.HandlesBase import BaseHandle, CTypeInfo
from lib.core.Handles.HandlesExpr import DefineUseWidthFromLLVMType
import vast as ast
import llvmlite.ir as ir
@@ -96,7 +97,11 @@ class ReturnHandle(BaseHandle):
IsRetPtr = True
ExpectedType: ir.Type = ReturnTypeInfo.ToLLVM(Gen) if ReturnTypeInfo and ReturnTypeInfo.BaseType else Gen._CType2LLVM('int', IsRetPtr)
expected_types.append(ExpectedType)
Val: ir.Value | None = self.HandleExprLlvm(val)
# 返回注解即"使用处宽度":CDefine 常量在此按返回注解的位宽物化
# (更宽 ⇒ 升宽物化;更窄 ⇒ fail-fast);非常量返回值不受影响
# (ConstWidthHint 只被常量物化分支读取)。
Val: ir.Value | None = self.HandleExprLlvm(
val, ConstWidthHint=DefineUseWidthFromLLVMType(ExpectedType))
if Val is None:
if isinstance(ExpectedType, ir.PointerType):
Val = ir.Constant(ExpectedType, None)
@@ -131,9 +136,12 @@ class ReturnHandle(BaseHandle):
Gen.emit_return(result)
return
if Node.value:
Val: ir.Value | None = self.HandleExprLlvm(Node.value)
# 本函数返回类型即"使用处宽度":先取回返回类型再翻译值,供 CDefine 常量
# 按该位宽物化(更宽 ⇒ 升宽物化;更窄 ⇒ fail-fast)。
ret_type: ir.Type | None = getattr(Gen.func, 'ftype', None) and Gen.func.ftype.return_type if getattr(Gen, 'func', None) else None
Val: ir.Value | None = self.HandleExprLlvm(
Node.value, ConstWidthHint=DefineUseWidthFromLLVMType(ret_type))
if Val:
ret_type: ir.Type | None = getattr(Gen.func, 'ftype', None) and Gen.func.ftype.return_type if getattr(Gen, 'func', None) else None
if isinstance(ret_type, ir.IdentifiedStructType) and isinstance(Val, ir.Constant) and isinstance(Val.type, ir.PointerType) and Val.constant is None:
if ret_type.elements:
zero_val: ir.Constant = ir.Constant(ret_type, [
+7 -90
View File
@@ -5,37 +5,13 @@ import llvmlite.ir as ir
if TYPE_CHECKING:
from lib.core.translator import Translator
from lib.core.Translator.LlvmGenerator import LlvmGeneratorMixin
from lib.core.Handles.HandlesBase import BaseHandle, BuiltinTypeMap
from lib.core.Handles.HandlesBase import BaseHandle
from lib.includes.t import CTypeRegistry
from lib.core.SymbolUtils import IsTModule
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _config_mode
def _IsTypeExpr(node: ast.AST | None) -> bool:
"""判断 AST 节点是否为类型表达式(类型转换的目标,如 Name/Attribute/Subscript/BinOp|)"""
return (
isinstance(node, ast.Name)
or isinstance(node, ast.Attribute)
or isinstance(node, ast.Subscript)
or (isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr))
)
def _IsCastWrappingDeref(rhs: ast.AST | None, deref_node: ast.Call) -> bool:
"""判断 rhs 是否为"类型转换链包裹 c.Deref"(如 (T | t.CPtr)(t.CPtr(c.Deref(p))))。
允许在 c.Deref 处于类型转换调用链中时仍从赋值注解推断加载类型;
ifexp/compare/binop 包裹(非转换调用)不放行,避免把 i8* 错 bitcast 成
其他位宽加载(见 _infer_deref_target_type 文档)。
"""
if rhs is deref_node:
return True
if isinstance(rhs, ast.Call) and len(rhs.args) == 1 and _IsTypeExpr(rhs.func):
return _IsCastWrappingDeref(rhs.args[0], deref_node)
return False
class CSpecialCallHandle(BaseHandle):
def _HandleCIfLlvm(self, Node: ast.Call) -> ir.Value:
Gen: LlvmGeneratorMixin = self.Trans.LlvmGen
@@ -251,7 +227,6 @@ class CSpecialCallHandle(BaseHandle):
if arg_val is None:
return ir.Constant(ir.IntType(32), 0)
if isinstance(arg_val.type, ir.PointerType):
pointee: ir.Type = arg_val.type.pointee
# 嵌套 Deref 的内层:加载为指针 (i8*),不推断目标类型
# 因为结果会被外层 Deref 再次解引用
NestedLoadPtr: bool = getattr(Gen, '_nested_deref_load_ptr', False)
@@ -261,75 +236,17 @@ class CSpecialCallHandle(BaseHandle):
Loaded = Gen._load(casted, name="deref")
if Loaded is not None:
return Loaded
# void* (i8*) 需要根据赋值目标类型推断加载类型
elif isinstance(pointee, ir.IntType) and pointee.width == 8:
target_type: ir.Type | None = self._infer_deref_target_type(Gen, Node)
if target_type is not None and not isinstance(target_type, ir.IntType):
# bitcast i8* -> target_type* then load
casted: ir.Value = Gen.builder.bitcast(arg_val, ir.PointerType(target_type), name="deref_cast")
Loaded = Gen._load(casted, name="deref")
if Loaded is not None:
return Loaded
elif target_type is not None and isinstance(target_type, ir.IntType):
# 目标是整数类型 (如 i32/i64): bitcast 并加载
if target_type.width != 8:
casted: ir.Value = Gen.builder.bitcast(arg_val, ir.PointerType(target_type), name="deref_cast")
Loaded = Gen._load(casted, name="deref")
if Loaded is not None:
return Loaded
# 读取宽度只由指针的 pointee 类型决定(_load 按 arg_val.type.pointee
# 加载一个元素):pointee 是 i8(t.CPtr / void* / str / bytes)即恒读
# 1 字节,不再按赋值目标注解反推宽度——那会让同一处 c.Deref(i8*) 的
# 读取宽度取决于它是否恰好出现在带注解赋值的 RHS 里。需要 4/8 字节等
# 更宽读取时,由调用点显式给出元素类型指针后再解引用
# (c.Deref((int | t.CPtr)(p)) / c.Deref(t.CUInt64T(p, t.CPtr)))。
Loaded = Gen._load(arg_val, name="deref")
if Loaded is not None:
return Loaded
return arg_val
def _infer_deref_target_type(self, Gen: LlvmGeneratorMixin, DerefNode: ast.Call | None = None) -> ir.Type | None:
"""从当前赋值上下文推断 c.Deref 的目标加载类型。
当用户写 v: int = c.Deref(ptr) 时,从赋值注解推断目标类型。
注意:仅当 c.Deref 是赋值的直接 RHS,或 RHS 是类型转换链包裹
(如 `(T | t.CPtr)(t.CPtr(c.Deref(p)))`)时才推断;若 c.Deref 嵌套在
ifexp/compare/binop 中(如 `a = 1 if c.Deref(p) == 65 else 0`),
不应推断,否则会把 i8* 错误 bitcast 为 i32* 加载 4 字节。"""
assign_node: ast.AST | None = getattr(self.Trans, '_current_assign_node', None)
if not assign_node:
return None
# 仅当 c.Deref 是赋值的直接 RHS 或处于类型转换链中时才推断类型
if DerefNode is not None:
rhs: ast.AST | None = getattr(assign_node, 'value', None)
if rhs is not DerefNode and not _IsCastWrappingDeref(rhs, DerefNode):
return None
ann: ast.AST | None = getattr(assign_node, 'annotation', None)
if not ann:
return None
# 收集候选类型名:Name / Attribute / 联合 BinOp| 的所有 side。
# 逐个尝试解析,取第一个能解析为 LLVM 类型的(如
# `vthreading.Thread | t.CPtr` → 'Thread' 未注册 → 'CPtr' → i8*)。
candidates: list[str] = []
if isinstance(ann, ast.Name):
candidates.append(ann.id)
elif isinstance(ann, ast.Attribute):
candidates.append(ann.attr)
elif isinstance(ann, ast.BinOp) and isinstance(ann.op, ast.BitOr):
# 处理 int | t.CPtr 形式的联合类型注解
for side in (ann.left, ann.right):
if isinstance(side, ast.Name):
candidates.append(side.id)
elif isinstance(side, ast.Attribute):
candidates.append(side.attr)
for ann_name in candidates:
# 使用 BuiltinTypeMap 统一查询(同时支持 Python 类名和字符串速记)
entry: tuple[type, int] | None = BuiltinTypeMap.Get(ann_name)
if entry is not None:
ctype_cls: type = entry[0]
llvm_str: str | None = CTypeRegistry.CTypeToLLVM(ctype_cls)
if llvm_str:
resolved: ir.Type | None = Gen._type_str_to_llvm(llvm_str)
if resolved:
return resolved
# 检查是否是已注册的结构体类型
if ann_name in Gen.structs:
return Gen.structs[ann_name]
return None
def _HandleCPtrToIntLlvm(self, Node: ast.Call) -> ir.Value:
Gen: LlvmGeneratorMixin = self.Trans.LlvmGen
if not Node.args:
+6 -1
View File
@@ -141,8 +141,13 @@ class WithHandle(BaseHandle):
current_depth: int = sum(len(pl) for pl in Gen._with_provides_stack)
new_fields: int = len(Gen.class_provides[ClassName])
if current_depth + new_fields > 32:
# 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行
_lineno: int = int(getattr(Node, 'lineno', 0) or 0)
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and _lineno > _line_offset:
_lineno -= _line_offset
raise SyntaxError(
f"[TransPyC] with provider stack overflow at line {Node.lineno}: "
f"[TransPyC] with provider stack overflow at line {_lineno}: "
f"current depth {current_depth} + new {new_fields} fields > max 32. "
f"Class '{ClassName}' provides too many fields or with nesting too deep."
)
+12 -12
View File
@@ -257,10 +257,10 @@ class BaseGenMixin:
# True=每个 define 发 DISubprogram + 入口首条指令发 DILocation。
self.dwarf_enabled: bool = False
self.DebugInfo: Any = None
# Ctrl+C 调用栈(--ctrlc-stack):仅在翻译 includes/dbgstack.vp 这一个模块时
# Ctrl+C 调用栈(--traceback):仅在翻译 includes/traceback.vp 这一个模块时
# 置 True —— 落成 .ll 文本前为本模块注入 @llvm.global_ctors 构造器调用其
# Install()(见 _RegisterDbgStackInstallViaCtor)。默认 False(零影响)。
self.ctrlc_stack_ctor: bool = False
# Install()(见 _RegisterTracebackInstallViaCtor)。默认 False(零影响)。
self.traceback_ctor: bool = False
pi: dict[str, bool | int] = self._platform_info
# 根据目标平台设置宏(声明式,从三元组推导)
if pi['is_windows']:
@@ -2100,12 +2100,12 @@ class BaseGenMixin:
gv.linkage = 'appending'
gv.initializer = ir.Constant(arr_ty, [entry_val])
def _RegisterDbgStackInstallViaCtor(self) -> None:
"""用 @llvm.global_ctors 在进程启动期调用本模块的 Install()(--ctrlc-stack)。
def _RegisterTracebackInstallViaCtor(self) -> None:
"""用 @llvm.global_ctors 在进程启动期调用本模块的 Install()(--traceback)。
仅在"本翻译单元就是 includes/dbgstack.vp"时由 StructGen.finalize() 调用
(ctrlc_stack_ctor 由 Phase2Translator._translate_file 按源文件名置位)。
生成 internal void @__dbgstack_install_ctor()(entry 块直接 call 本模块的
仅在"本翻译单元就是 includes/traceback.vp"时由 StructGen.finalize() 调用
(traceback_ctor 由 Phase2Translator._translate_file 按源文件名置位)。
生成 internal void @__traceback_install_ctor()(entry 块直接 call 本模块的
Install() 并忽略其 int 返回值),再写入 appending linkage 的
@llvm.global_ctors —— 每模块一份,链接器按 appending 语义拼进 .init_array,
进程启动时执行一次。
@@ -2121,22 +2121,22 @@ class BaseGenMixin:
install_fn: ir.Function | None = self._find_function('Install')
if install_fn is None:
raise RuntimeError(
'--ctrlc-stack: 在 dbgstack 模块内找不到 Install() 函数,'
'--traceback: 在 traceback 模块内找不到 Install() 函数,'
'无法注入启动期构造器(不静默降级)')
i32: ir.IntType = ir.IntType(32)
i8p: ir.PointerType = ir.PointerType(ir.IntType(8))
void_fnptr: ir.PointerType = ir.PointerType(ir.FunctionType(ir.VoidType(), []))
ctor: ir.Function = ir.Function(
self.module, ir.FunctionType(ir.VoidType(), []),
name=self._UniqueGlobalName('__dbgstack_install_ctor'))
name=self._UniqueGlobalName('__traceback_install_ctor'))
ctor.linkage = 'internal'
cb: ir.IRBuilder = ir.IRBuilder(ctor.append_basic_block(name='entry'))
cb.call(install_fn, []) # Install() 无参;返回值(int)有意忽略
cb.ret_void()
if 'llvm.global_ctors' in self.module.globals:
raise RuntimeError(
'--ctrlc-stack: 模块内已存在 @llvm.global_ctors,'
'无法注入 dbgstack 启动期构造器(不静默降级)')
'--traceback: 模块内已存在 @llvm.global_ctors,'
'无法注入 traceback 启动期构造器(不静默降级)')
elem_ty: ir.LiteralStructType = ir.LiteralStructType([i32, void_fnptr, i8p])
arr_ty: ir.ArrayType = ir.ArrayType(elem_ty, 1)
entry_val: ir.Constant = ir.Constant(elem_ty, [
+54 -6
View File
@@ -46,7 +46,7 @@ class ExprGenMixin:
return self.builder.bitcast(global_var, ir.PointerType(ir.IntType(8)), name="str")
return ir.Constant(ir.IntType(32), int(value))
def emit_binary_op(self, op: str, left: ir.Value, right: ir.Value, is_unsigned: bool = False) -> ir.Value:
def emit_binary_op(self, op: str, left: ir.Value, right: ir.Value, is_unsigned: bool = False, is_aug: bool = False) -> ir.Value:
if isinstance(left.type, ir.PointerType) and isinstance(right.type, ir.IntType):
# 数组指针需要先 decay 为元素指针,否则 GEP 会以整个数组为步长
# 例如 [64 x i8]* + 1 会前进 64 字节而非 1 字节
@@ -65,6 +65,46 @@ class ExprGenMixin:
left = self.builder.zext(left, ir.IntType(64), name="int2ptrint2")
if op == '+' or op == 'Add':
return self.builder.gep(right, [left], name="ptr_add_rev")
# ============================================================
# 双指针算术全域 fail-fast(**单一收敛点**)
#
# BinOp(HandlesExprOps._HandleBinOpLlvm)与 AugAssign
# (HandlesAugAssign._HandleAugAssignLlvm,+=、-=、*= …)都经本函数,
# 故在此一次收口,避免多点各写一份判据/报文。
# 判据:运算符属二元算术/位运算/移位集合,且**两操作数都是 LLVM 指针**。
# 本语言的 str 是 i8*,没有 `+` 拼接语义;指针 ± 指针也不是 GEP 语义
# (GEP 只接受「一指针一整数」,已在上方两个 ptr/int 分支处理并返回)。
# 落到下方 ops/shift_ops/bit_ops 会直出非法 IR(`add i8*, i8*` ⇒ opt 报
# invalid operand type for instruction),故在 IR 生成前 fail-fast。
# 判据与报文逐句对齐 TPV ExprGenOps.translate_binop / AssignGenMixin._apply_aug_op。
# ============================================================
if op in ('+', 'Add', '-', 'Sub', '*', 'Mult', '/', 'Div', '//', 'FloorDiv',
'%', 'Mod', '**', 'Pow', '&', 'BitAnd', '|', 'BitOr', '^', 'BitXor',
'<<', 'LShift', '>>', 'RShift') \
and isinstance(left.type, ir.PointerType) \
and isinstance(right.type, ir.PointerType):
LeftTy: str = str(left.type)
RightTy: str = str(right.type)
if LeftTy == 'i8*':
LeftTy = 'str(i8*)'
if RightTy == 'i8*':
RightTy = 'str(i8*)'
OpCh: str = op
AugNote: str = ''
if is_aug:
# 增量赋值(+= 等)要能自报家门,便于定位(不写成裸 '+')
OpCh = f'{op}='
AugNote = '(增量赋值)'
raise TypeError(
f"[TransPyC] 双指针运算类型非法: op = '{OpCh}'{AugNote},"
f'左操作数类型 = {LeftTy},右操作数类型 = {RightTy}。'
f'本语言的 str 是 i8*,没有 `+` 拼接语义(`str + str` 与指针 ± 指针'
f'都会退化成裸指针 add/sub ⇒ 非法 IR: opt 报 '
f'invalid operand type for instruction)。'
f'禁止静默降级,已终止编译。'
f'修法: 用 f-string 插值、string.strcat 或 viperlib.snprintf 构建字符串。'
+ self._get_node_info()
)
is_float: bool = isinstance(left.type, (ir.FloatType, ir.DoubleType)) or \
isinstance(right.type, (ir.FloatType, ir.DoubleType))
if is_float:
@@ -291,10 +331,14 @@ class ExprGenMixin:
if isinstance(ret_type, ir.PointerType) and isinstance(value.type, ir.PointerType):
value = self.builder.bitcast(value, ret_type, name="ret_cast")
elif isinstance(value.type, ir.PointerType) and not isinstance(ret_type, ir.PointerType):
if isinstance(value.type.pointee, ir.IntType) and isinstance(ret_type, ir.IntType) and value.type.pointee.width == ret_type.width:
value = self._load(value, name="ret_Load")
elif isinstance(ret_type, ir.IntType):
value = self.builder.ptrtoint(value, ret_type, name="ret_ptrtoint")
if isinstance(ret_type, ir.IntType):
# 指针 → 整数:隐式转换一律禁止(与 TransPyV coerce_to_type 同口径)。
# 原先"同宽 load 所指物 / 其余 ptrtoint"都是静默转换。
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"指针隐式转整数已禁止:函数 {self.func.name} 声明返回 {ret_type},"
f"但 return 的是指针类型 {value.type};如需转换请显式 cast(如 t.CUInt64T(p) / t.CInt(p))"
)
elif isinstance(value.type.pointee, ret_type.__class__) and not isinstance(value.type.pointee, ir.IntType):
value = self._load(value, name="ret_Load")
elif isinstance(value.type.pointee, ir.PointerType):
@@ -302,7 +346,11 @@ class ExprGenMixin:
if Loaded.type == ret_type:
value = Loaded
elif isinstance(ret_type, ir.IntType):
value = self.builder.ptrtoint(Loaded, ret_type, name="ret_ptrtoint")
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"指针隐式转整数已禁止:函数 {self.func.name} 声明返回 {ret_type},"
f"但 return 的是指针类型 {Loaded.type};如需转换请显式 cast(如 t.CUInt64T(p) / t.CInt(p))"
)
elif isinstance(ret_type, ir.IntType) and isinstance(value.type, ir.IntType):
if value.type.width < ret_type.width:
if value.type.width == 1:
+106 -62
View File
@@ -21,14 +21,14 @@ import re
# `!N = ` 形式的编号元数据定义行
_Definition = re.compile(r'^!(\d+) = ')
# 编号元数据引用
_Reference = re.compile(r'!(\d+)')
# `!name = ...` 形式的命名元数据(首字符为字母,故不会命中 `!N = `)
_NamedDefinition = re.compile(r'^!([A-Za-z][\w.]*)\s*=\s*(.*)$')
# `!{ ... }` 列表形式
_ListForm = re.compile(r'^!\{(.*)\}$', re.DOTALL)
# 纯 id 引用 `!N`
_PureId = re.compile(r'^!(\d+)$')
# Decimal digit set for the no-regex fast rewrite path.
_DIGITS: frozenset[str] = frozenset('0123456789')
def _SplitTopLevel(Content: str) -> list[str]:
@@ -78,47 +78,69 @@ def _ListItems(Rhs: str) -> list[str] | None:
return _SplitTopLevel(m.group(1))
def _RewriteReferences(Line: str, Mapping: dict[int, int]) -> str:
"""改写一行里对编号元数据的引用,跳过字符串字面量与 `;` 注释。"""
if '!' not in Line:
def _RewritePlain(Line: str, StrMap: dict[str, str]) -> str:
"""Rewrite `!N` references in a line that has no `"` and no `;`.
Equivalent to a regex substitution of `!` + digits but without the regex
engine callback: split on '!' (C-level), map each digit run by full token,
keep unknown ids verbatim.
"""
Parts: list[str] = Line.split('!')
if len(Parts) == 1:
return Line
if '"' not in Line and ';' not in Line:
# 元数据行绝大多数不含字符串/注释:整体正则替换,避免逐字符扫描
return _Reference.sub(
lambda m: '!' + str(Mapping.get(int(m.group(1)), int(m.group(1)))), Line)
Out: list[str] = []
Out: list[str] = [Parts[0]]
Ap = Out.append
Get = StrMap.get
for Part in Parts[1:]:
d: int = 0
n: int = len(Part)
while d < n and Part[d] in _DIGITS:
d += 1
if not d:
Ap('!')
Ap(Part)
continue
Digits: str = Part[:d]
# Leading zeros normalise the way int()/str() did in the old path.
Tok: str = '!' + Digits if (d == 1 or Digits[0] != '0') else '!' + str(int(Digits))
New: str | None = Get(Tok)
Ap(Tok if New is None else New)
Ap(Part[d:])
return ''.join(Out)
def _RewriteQuoted(Line: str, StrMap: dict[str, str]) -> str:
"""Rewrite `!N` refs in a line that contains `"` and/or `;`.
String literals and the trailing `;` comment are copied verbatim; the
remaining (quote/comment free) chunks go through `_RewritePlain`. Uses
C-level str.find instead of the old per-character Python scan.
"""
Out: list[str] = []; Ap = Out.append
i: int = 0
n: int = len(Line)
while i < n:
Ch: str = Line[i]
if Ch == '"':
j: int = i + 1
while j < n:
if Line[j] == '\\':
j += 2
continue
if Line[j] == '"':
j += 1
break
j += 1
Out.append(Line[i:j])
i = j
continue
if Ch == ';':
Out.append(Line[i:])
q: int = Line.find('"', i)
s: int = Line.find(';', i, q if q >= 0 else n)
if s >= 0:
Ap(_RewritePlain(Line[i:s], StrMap))
Ap(Line[s:]) # ';' comment: copy verbatim to end of line
break
if Ch == '!':
m = _Reference.match(Line, i)
if m:
New: int | None = Mapping.get(int(m.group(1)))
Out.append('!' + str(New) if New is not None else m.group(0))
i = m.end()
if q < 0:
Ap(_RewritePlain(Line[i:], StrMap))
break
Ap(_RewritePlain(Line[i:q], StrMap))
j: int = q + 1
while j < n:
if Line[j] == '\\':
j += 2
continue
Out.append(Ch)
i += 1
continue
Out.append(Ch)
i += 1
if Line[j] == '"':
j += 1
break
j += 1
Ap(Line[q:j]) # string literal: copy verbatim
i = j
return ''.join(Out)
@@ -139,7 +161,7 @@ def SanitizeIRMetadata(text: str) -> str:
i: int = 0
while i < LineCount:
if Segments[i].startswith('!'):
j: int = i
j: int = i + 1
while j < LineCount and Segments[j].startswith('!'):
j += 1
Blocks.append((i, j))
@@ -161,7 +183,13 @@ def SanitizeIRMetadata(text: str) -> str:
# 3) 块内按定义出现顺序重新编号(局部 id -> 全局 id)
Lines: list[str] = list(Segments)
BlockMaps: list[dict[int, int]] = []
BlockStrMaps: list[dict[str, str]] = []
BlockIsIdentity: list[bool] = []
# Definition line index -> (new global id, length of its rewritten "!N = " prefix).
# Lets step 4/5 skip a per-line regex to re-detect definition lines.
DefInfo: dict[int, tuple[int, int]] = {}
# Named metadata lines are collected here (only block lines can start with '!').
NamedLines: dict[str, list[int]] = {}
Counter: int = 0
for Start, End in Blocks:
Mapping: dict[int, int] = {}
@@ -169,14 +197,21 @@ def SanitizeIRMetadata(text: str) -> str:
for k in range(Start, End):
m = _Definition.match(Lines[k])
if not m:
nm = _NamedDefinition.match(Lines[k])
if nm is not None:
NamedLines.setdefault(nm.group(1), []).append(k)
continue
Local: int = int(m.group(1))
Mapping[Local] = Counter
if Local != Counter:
Identity = False
Lines[k] = '!' + str(Counter) + ' = ' + Lines[k][m.end():]
Prefix: str = '!' + str(Counter) + ' = '
DefInfo[k] = (Counter, len(Prefix))
Lines[k] = Prefix + Lines[k][m.end():]
Counter += 1
BlockMaps.append(Mapping)
BlockStrMaps.append({'!' + str(LocalId): '!' + str(GlobId)
for LocalId, GlobId in Mapping.items()})
BlockIsIdentity.append(Identity)
# 4) 改写全文引用(定义体右值、define/指令行的 !dbg、命名元数据列表)
@@ -185,36 +220,40 @@ def SanitizeIRMetadata(text: str) -> str:
# 旧 id 843 → Mapping[843]=1686),一旦改写左侧就会出现两个定义共用同一
# id,opt/llc 随即报 "Metadata id is already used"(本 bug 实测残留 276 个
# 重复 id)。故定义行只改写 '=' 之后的右值。
GetDef = DefInfo.get
for k in range(LineCount):
Index: int = Owner[k]
if Index < 0 or BlockIsIdentity[Index] or not BlockMaps[Index]:
continue
Line: str = Lines[k]
Head: re.Match | None = _Definition.match(Line)
if Head:
Fixed: str = Line[:Head.end()] + _RewriteReferences(Line[Head.end():], BlockMaps[Index])
if '!' not in Line:
continue
StrMap: dict[str, str] = BlockStrMaps[Index]
Def: tuple[int, int] | None = GetDef(k)
if Def is not None:
Split: int = Def[1]
Tail: str = Line[Split:]
Fixed: str = Line[:Split] + (_RewritePlain(Tail, StrMap)
if ('"' not in Tail and ';' not in Tail)
else _RewriteQuoted(Tail, StrMap))
else:
Fixed = _RewriteReferences(Line, BlockMaps[Index])
Fixed = (_RewritePlain(Line, StrMap)
if ('"' not in Line and ';' not in Line)
else _RewriteQuoted(Line, StrMap))
if Fixed != Line:
Lines[k] = Fixed
# 5) 定义体索引(新 id -> 右值文本),供命名元数据判重
# 5) 定义体索引(新 id -> 右值文本),供命名元数据判重。
# 只遍历定义行(步骤 3 已登记),不再逐行做正则匹配。
BodyOf: dict[int, str] = {}
for k in range(LineCount):
m = _Definition.match(Lines[k])
if m:
BodyOf[int(m.group(1))] = Lines[k][m.end():].strip()
for k, (NewId, Split) in DefInfo.items():
BodyOf[NewId] = Lines[k][Split:].strip()
# 6) 命名元数据:同名多份合并成一份。
# - !llvm.dbg.cu:全部条目聚合(每个 DICompileUnit 都必须被列出,不能去重)
# - 其它(!llvm.module.flags、!llvm.ident ...):条目内容(id 引用解析成
# 节点正文)完全相同的多份只留一份,不同则聚合。
NamedLines: dict[str, list[int]] = {}
for k in range(LineCount):
m = _NamedDefinition.match(Lines[k])
if m:
NamedLines.setdefault(m.group(1), []).append(k)
# NamedLines 已在步骤 3 随块扫描一并收集(命名行的名字不受引用改写影响)。
Dropped: set[int] = set()
for Name, Indices in NamedLines.items():
if len(Indices) == 1 and Name != 'llvm.dbg.cu':
@@ -225,20 +264,25 @@ def SanitizeIRMetadata(text: str) -> str:
SeenItems: set[str] = set()
SeenBodies: set[tuple[str, ...]] = set()
Single: bool = True
IsDbgCu: bool = Name == 'llvm.dbg.cu'
for k in Indices:
Rhs: str = _NamedDefinition.match(Lines[k]).group(2).strip()
NamedMatch = _NamedDefinition.match(Lines[k])
Rhs: str = (NamedMatch.group(2) if NamedMatch is not None else '').strip()
ListItems: list[str] | None = _ListItems(Rhs)
if ListItems is None:
Single = False
break
Bodies: tuple[str, ...] = tuple(
BodyOf.get(int(_PureId.match(Item).group(1)), Item) if _PureId.match(Item) else Item
for Item in ListItems
)
if Name != 'llvm.dbg.cu':
if Bodies in SeenBodies:
if not IsDbgCu:
# !llvm.dbg.cu never de-dupes by body, so skip resolving bodies for it.
Bodies: list[str] = []
for Item in ListItems:
PureMatch = _PureId.match(Item)
Bodies.append(BodyOf.get(int(PureMatch.group(1)), Item)
if PureMatch else Item)
Key: tuple[str, ...] = tuple(Bodies)
if Key in SeenBodies:
continue
SeenBodies.add(Bodies)
SeenBodies.add(Key)
for Item in ListItems:
if Item not in SeenItems:
SeenItems.add(Item)
+93 -17
View File
@@ -2,6 +2,50 @@ from __future__ import annotations
import llvmlite.ir as ir
class _VolatileLoadInstr(ir.LoadInstr):
"""带 LLVM `volatile` 限定符的 load 指令。
llvmlite 0.47 的 `ir.LoadInstr` / `ir.StoreInstr` 不支持 volatile(无该属性,
`descr` 也不输出该关键字),故按本仓库既有做法(BaseGen.VaArgInstruction
自定义 `descr`)派生子类,只把 `load` 关键字扩成 `load volatile`。
输出格式与 llvmlite 原始实现逐字段一致(类型、指针、align、元数据),
仅多一个 `volatile` 关键字;语义与 TransPyV 侧 `build_load(..., vol)` 对齐。
"""
def descr(self, buf: list[str]) -> None:
[val] = self.operands
if self.align is not None:
align = ', align %d' % (self.align)
else:
align = ''
buf.append("load volatile {0}, {1} {2}{3}{4}\n".format(
self.type,
val.type,
val.get_reference(),
align,
self._stringify_metadata(leading_comma=True),
))
class _VolatileStoreInstr(ir.StoreInstr):
"""带 LLVM `volatile` 限定符的 store 指令(与 _VolatileLoadInstr 同款理由)。"""
def descr(self, buf: list[str]) -> None:
val, ptr = self.operands
if self.align is not None:
align = ', align %d' % (self.align)
else:
align = ''
buf.append("store volatile {0} {1}, {2} {3}{4}{5}\n".format(
val.type,
val.get_reference(),
ptr.type,
ptr.get_reference(),
align,
self._stringify_metadata(leading_comma=True),
))
class MemoryOpsMixin:
def _register_local_heap_ptr(self, val: ir.Value, ClassName: str | None = None, VarName: str | None = None) -> None:
@@ -126,12 +170,42 @@ class MemoryOpsMixin:
self.builder.position_at_end(saved_block)
return instr
def _load(self, ptr: ir.Value, name: str = '', align: int | None = None) -> ir.LoadInstr:
def _EmitLoad(self, ptr: ir.Value, name: str = '', align: int | None = None,
volatile: bool = False) -> ir.Value:
"""发射一条 load;volatile=True 时走 _VolatileLoadInstr(LLVM `load volatile`)。
类型推断完全交给 LoadInstr 自身(与 llvmlite 原始路径同一套逻辑),
不额外兜底:推不出类型时与原生 `builder.load` 一样直接抛错。
"""
if not volatile:
return self.builder.load(ptr, name=name, align=align)
instr: ir.LoadInstr = _VolatileLoadInstr(self.builder.block, ptr, name=name)
instr.align = align
self.builder._insert(instr)
return instr
def _EmitStore(self, val: ir.Value, ptr: ir.Value, align: int | None = None,
volatile: bool = False) -> ir.StoreInstr:
"""发射一条 store;volatile=True 时走 _VolatileStoreInstr(LLVM `store volatile`)。"""
if not volatile:
return self.builder.store(val, ptr, align=align)
instr: ir.StoreInstr = _VolatileStoreInstr(self.builder.block, val, ptr)
instr.align = align
self.builder._insert(instr)
return instr
def _load(self, ptr: ir.Value, name: str = '', align: int | None = None,
volatile: bool | None = None) -> ir.LoadInstr:
if align is None:
if isinstance(ptr.type, ir.PointerType):
align = self._get_align(ptr.type.pointee)
else:
align = 0
# volatile=None ⇒ 按登记名自动判定(变量自身/所指物/字段,见 _IsPtrVolatile);
# 显式传入 True/False 时以调用方为准。判定必须先于下方 bitcast(bitcast
# 结果的名字不再是变量名,会丢登记名)。
if volatile is None:
volatile = self._IsPtrVolatile(ptr)
if isinstance(ptr.type, ir.PointerType):
pointee_type: ir.Type = ptr.type.pointee
if isinstance(pointee_type, ir.IdentifiedStructType) and pointee_type.name in self.typedef_int_widths:
@@ -139,8 +213,8 @@ class MemoryOpsMixin:
if width > 0:
actual_type: ir.IntType = ir.IntType(width)
bitcast_ptr: ir.Value = self.builder.bitcast(ptr, ir.PointerType(actual_type), name="typedef_bitcast_Load")
return self.builder.load(bitcast_ptr, name=name, align=align)
return self.builder.load(ptr, name=name, align=align)
return self._EmitLoad(bitcast_ptr, name=name, align=align, volatile=volatile)
return self._EmitLoad(ptr, name=name, align=align, volatile=volatile)
def _Load_var(self, name: str) -> ir.Value | None:
if name in self._direct_values:
@@ -199,17 +273,14 @@ class MemoryOpsMixin:
# (此前 pointee.width == target.width 时直接 load null,
# 生成 `load i32, i32* null` → ASAN access-violation)
return ir.Constant(target_type, 0)
if isinstance(val.type.pointee, ir.IntType) and val.type.pointee.width == target_type.width:
return self.builder.load(val, name="load_ptr_as_int_store")
if target_type.width <= 8 and isinstance(val.type.pointee, ir.IntType):
Loaded: ir.Value = self.builder.load(val, name="load_ptr_byte_store")
if Loaded.type != target_type:
if isinstance(Loaded.type, ir.IntType) and isinstance(target_type, ir.IntType):
if target_type.width < Loaded.type.width:
return self.builder.trunc(Loaded, target_type, name="trunc_ptr_byte_store")
return self.builder.zext(Loaded, target_type, name="zext_ptr_byte_store")
return Loaded
return self.builder.ptrtoint(val, target_type, name="ptr_to_int_store")
# 指针 → 整数:隐式转换一律禁止(与 TransPyV coerce_to_type 同口径)。
# 原先三条路径都是静默的:同宽直接 load 指针所指物的位模式、≤8 位 load
# 首字节、其余 ptrtoint 截断地址——把"类型写错"藏到运行期。
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"指针隐式转整数已禁止:不能把指针类型 {val.type} 赋给整数类型 {target_type};"
f"如需转换请显式 cast(如 t.CUInt64T(p) / t.CInt(p))"
)
if isinstance(target_type, ir.FloatType) and isinstance(val.type, ir.DoubleType):
return self.builder.fptrunc(val, target_type, name="fptrunc_store")
if isinstance(target_type, ir.DoubleType) and isinstance(val.type, ir.FloatType):
@@ -242,12 +313,17 @@ class MemoryOpsMixin:
return Loaded
return None
def _store(self, val: ir.Value, ptr: ir.Value, align: int | None = None) -> ir.StoreInstr:
def _store(self, val: ir.Value, ptr: ir.Value, align: int | None = None,
volatile: bool | None = None) -> ir.StoreInstr:
if align is None:
if isinstance(ptr.type, ir.PointerType):
align = self._get_align(ptr.type.pointee)
else:
align = 0
# volatile=None ⇒ 按登记名自动判定(见 _IsPtrVolatile)。判定先于下方任何
# ptr 重建(null_struct_ptr_cast 等),否则会丢登记名。
if volatile is None:
volatile = self._IsPtrVolatile(ptr)
if isinstance(ptr.type, ir.PointerType):
target_type: ir.Type = ptr.type.pointee
if isinstance(target_type, ir.IdentifiedStructType) and target_type.name in self.typedef_int_widths:
@@ -261,7 +337,7 @@ class MemoryOpsMixin:
val = self.builder.trunc(val, actual_type, name="trunc_typedef")
else:
val = self.builder.zext(val, actual_type, name="zext_typedef")
return self.builder.store(val, bitcast_ptr, align=align)
return self._EmitStore(val, bitcast_ptr, align=align, volatile=volatile)
if val.type != target_type:
if isinstance(val, ir.Constant) and val.constant is None:
if isinstance(target_type, ir.PointerType):
@@ -280,7 +356,7 @@ class MemoryOpsMixin:
else:
src_info: str = self._get_node_info()
raise TypeError(f"cannot store {val.type} to {ptr.type}: mismatching types{src_info}")
return self.builder.store(val, ptr, align=align)
return self._EmitStore(val, ptr, align=align, volatile=volatile)
def _get_var_ptr(self, name: str) -> ir.Value | None:
if name in self._reg_values:
+75 -23
View File
@@ -10,6 +10,14 @@ from lib.includes import t as _t
class StructGenMixin:
def _get_align(self, llvm_type: ir.Type) -> int:
"""类型自然对齐(字节)。
与 `_get_struct_size` 同一约定:**不允许静默近似**。对齐猜错会让字段字节偏移
与 LLVM 实际布局不一致(本函数是 `_get_struct_size` 的字段偏移累加与
`_compute_field_byte_offset` 的输入),故拿不到对齐时一律响亮失败:
- opaque/未解析的 IdentifiedStructType → 曾按 ptr_size 猜;
- 未识别类型 → 曾按 4 猜。
"""
if isinstance(llvm_type, ir.IntType):
return max(1, llvm_type.width // 8)
if isinstance(llvm_type, ir.FloatType):
@@ -20,15 +28,28 @@ class StructGenMixin:
return self.ptr_size
if isinstance(llvm_type, ir.ArrayType):
return self._get_align(llvm_type.element)
if isinstance(llvm_type, ir.VectorType):
# 与 LLVM 一致:向量对齐 = 元素对齐(同 _get_struct_size 的向量口径)
return self._get_align(llvm_type.element)
if isinstance(llvm_type, ir.LiteralStructType):
return max((self._get_align(f) for f in llvm_type.elements), default=1)
if isinstance(llvm_type, ir.IdentifiedStructType):
resolved = self._resolve_opaque_struct(llvm_type)
if resolved is not llvm_type:
return self._get_align(resolved)
if llvm_type.elements is None or len(llvm_type.elements) == 0:
return self.ptr_size # opaque struct, assume pointer alignment
raise ValueError(
f"[FATAL][__sizeof__] 结构体 '{getattr(llvm_type, 'name', llvm_type)}' "
f"布局未就绪(opaque / 未解析到定义),拒绝按指针对齐兜底 —— "
f"对齐猜错会让字段字节偏移与 LLVM 实际布局不一致(读写落到错误字段)。"
f"请先确保该结构体已注册/可从 stub 加载后再取对齐。"
)
return max((self._get_align(f) for f in llvm_type.elements), default=1)
if isinstance(llvm_type, ir.VoidType):
return 0
return 4
raise ValueError(
f"[FATAL][__sizeof__] 不支持的类型 {llvm_type}({type(llvm_type).__name__})"
f"无法计算对齐,拒绝以常数 4 兜底 —— 对齐猜错会让字段字节偏移与 LLVM 实际布局不一致。")
def _resolve_opaque_struct(self, llvm_type: ir.Type) -> ir.Type:
"""尝试从 Gen.structs 中查找已解析的同名 struct,解决跨模块 struct 类型对象不一致的问题"""
@@ -77,6 +98,17 @@ class StructGenMixin:
return llvm_type
def _get_struct_size(self, llvm_type: ir.Type) -> int:
"""类型字节尺寸(含字段对齐/填充)。
**不允许静默近似**:本函数的返回值会被 `X.__sizeof__()` 直接采用
(HandlesExprCall._HandleSizeofLlvm),而 memhub 的 alloc 又直接吃该值。
此前两处静默兜底会**少算尺寸**,进而按过小尺寸分配 → 越界写坏邻居块的
8 字节块头(块头 `(order<<1)|1` 被写成偶数 ⇒ reset_to 视为"已释放"跳过、
free 被 _is_valid_ptr 判无效而静默 no-op)⇒ 该块永久丢失 ⇒ 1 GiB 主池漏满:
- opaque/未解析的 IdentifiedStructType → 曾一律返回 ptr_size;
- 未识别类型(如 SIMD VectorType)→ 曾一律返回 4。
故尺寸拿不到时一律响亮失败(项目约定:类型信息缺失立即终止,不兜底)。
"""
if isinstance(llvm_type, ir.IntType):
return max(1, llvm_type.width // 8)
if isinstance(llvm_type, ir.FloatType):
@@ -87,10 +119,20 @@ class StructGenMixin:
return self.ptr_size
if isinstance(llvm_type, ir.ArrayType):
return self._get_struct_size(llvm_type.element) * llvm_type.count
if isinstance(llvm_type, ir.VectorType):
# 与 LLVM 一致:元素尺寸 × 元素数,再向上取到 2 的幂(<3 x i32> = 16 字节)
elem_size: int = self._get_struct_size(llvm_type.element)
lanes: int = llvm_type.count
padded: int = 1
while padded < lanes:
padded = padded << 1
return elem_size * padded
if isinstance(llvm_type, ir.LiteralStructType):
total: int = 0
max_align: int = 1
for f in llvm_type.fields:
# llvmlite 的 LiteralStructType 只有 `.elements`(**没有** `.fields`):
# 旧代码写 `.fields` 必然 AttributeError,尺寸/对齐链在此静默断掉。
for f in llvm_type.elements:
fa: int = self._get_align(f)
fs: int = self._get_struct_size(f)
max_align = max(max_align, fa)
@@ -102,22 +144,32 @@ class StructGenMixin:
return total
if isinstance(llvm_type, ir.IdentifiedStructType):
# 尝试解析 opaque struct 为已注册的版本
llvm_type = self._resolve_opaque_struct(llvm_type)
resolved = self._resolve_opaque_struct(llvm_type)
if resolved is not llvm_type:
return self._get_struct_size(resolved)
if llvm_type.elements is None or len(llvm_type.elements) == 0:
return self.ptr_size # opaque struct, assume pointer size
total: int = 0
max_align: int = 1
raise ValueError(
f"[FATAL][__sizeof__] 结构体 '{getattr(llvm_type, 'name', llvm_type)}' "
f"布局未就绪(opaque / 未解析到定义),拒绝按指针大小兜底 —— "
f"近似会少算尺寸,调用方(memhub alloc)按过小尺寸分配并越界写坏邻居块头。"
f"请先确保该结构体已注册/可从 stub 加载后再取尺寸。"
)
total2: int = 0
max_align2: int = 1
for f in llvm_type.elements:
fa: int = self._get_align(f)
fs: int = self._get_struct_size(f)
max_align = max(max_align, fa)
if fa > 1:
total = (total + fa - 1) // fa * fa
total += fs
if max_align > 1:
total = (total + max_align - 1) // max_align * max_align
return total
return 4
fa2: int = self._get_align(f)
fs2: int = self._get_struct_size(f)
max_align2 = max(max_align2, fa2)
if fa2 > 1:
total2 = (total2 + fa2 - 1) // fa2 * fa2
total2 += fs2
if max_align2 > 1:
total2 = (total2 + max_align2 - 1) // max_align2 * max_align2
return total2
raise ValueError(
f"[FATAL][__sizeof__] 不支持的类型 {llvm_type}({type(llvm_type).__name__})"
f"无法计算尺寸,拒绝以常数兜底 —— 近似会少算尺寸并越界写坏内存池块头。"
)
@staticmethod
def _strip_unnecessary_quotes(ir_str: str) -> str:
@@ -140,12 +192,12 @@ class StructGenMixin:
_DbgInfo = self._GetDebugInfo()
if _DbgInfo is not None:
_DbgInfo.Finalize()
# Ctrl+C 调用栈(--ctrlc-stack):同样是"落成 .ll 文本之前"的模块级收尾——
# 当本模块就是 includes/dbgstack.vp 时,注入 @llvm.global_ctors 构造器调用
# 其 Install()(见 BaseGen._RegisterDbgStackInstallViaCtor)。未启用/非该模块
# 时 ctrlc_stack_ctor=False,不做任何事(零影响)。
if self.ctrlc_stack_ctor:
self._RegisterDbgStackInstallViaCtor()
# Ctrl+C 调用栈(--traceback):同样是"落成 .ll 文本之前"的模块级收尾——
# 当本模块就是 includes/traceback.vp 时,注入 @llvm.global_ctors 构造器调用
# 其 Install()(见 BaseGen._RegisterTracebackInstallViaCtor)。未启用/非该模块
# 时 traceback_ctor=False,不做任何事(零影响)。
if self.traceback_ctor:
self._RegisterTracebackInstallViaCtor()
ir_text: str
try:
ir_text = str(self.module)
+21 -4
View File
@@ -56,6 +56,16 @@ class TypeConvertMixin:
if resolved is not None:
return resolved
# 枚举(CEnum)类型注解 `x: Tri`:宽度 = 该枚举的存储宽度(最宽成员类型),
# 与成员常量发射点同点(ABI:两侧必须同宽)。默认宽度 32 时与改前逐字一致。
# 走 IsTypedef 之前的分支不受影响(typedef 形式枚举先被上方解析)。
if type_info.IsEnum and type_info.Name and not type_info.IsRenum:
_enum_ty: ir.Type = self._enum_int_type(type_info.Name)
_enum_result: ir.Type = _enum_ty
for _ in range(type_info.PtrCount):
_enum_result = ir.PointerType(_enum_result)
return _enum_result
# REnum 类型:通过 Name 查找或创建结构体(BaseType 可能为 None)
# 这对 BinOp `LLVMType | t.CPtr` 合并后的 CTypeInfo 至关重要
if type_info.IsRenum and type_info.Name:
@@ -146,6 +156,9 @@ class TypeConvertMixin:
enum_name: str = type_info.Name if type_info else ''
if enum_name and enum_name in self.structs:
return self.structs[enum_name]
if isinstance(BaseType, _t.CEnum):
# CEnum 存储宽度 = 最宽成员类型(REnum 仍走上方结构体路径)
return self._enum_int_type(enum_name)
return ir.IntType(32)
if isinstance(BaseType, _t.CUnion):
@@ -510,14 +523,16 @@ class TypeConvertMixin:
return ir.PointerType(_cls_struct)
return _cls_struct
if _Entry and _Entry.IsEnum:
return ir.IntType(32) if not IsPtr else ir.PointerType(ir.IntType(32))
_enum_ty: ir.Type = self._enum_int_type(type_str, _Entry)
return ir.PointerType(_enum_ty) if IsPtr else _enum_ty
if _Entry and _Entry.IsExceptionClass:
return ir.IntType(32) if not IsPtr else ir.PointerType(ir.IntType(32))
if _Entry and _Entry.IsFunction:
return ir.PointerType(ir.IntType(8))
if _Entry and _Entry.BaseType:
if (isinstance(_Entry.BaseType, type) and issubclass(_Entry.BaseType, _t.CEnum)) or _Entry.BaseType is _t.CEnum:
return ir.IntType(32) if not IsPtr else ir.PointerType(ir.IntType(32))
_enum_ty2: ir.Type = self._enum_int_type(type_str, _Entry)
return ir.PointerType(_enum_ty2) if IsPtr else _enum_ty2
for ClassName in self.structs:
if type_str == ClassName:
if IsPtr:
@@ -564,14 +579,16 @@ class TypeConvertMixin:
return ir.PointerType(opaque_lp)
return opaque_lp
if _Entry and _Entry.IsEnum:
return ir.IntType(32) if not IsPtr else ir.PointerType(ir.IntType(32))
_enum_ty3: ir.Type = self._enum_int_type(LastPart, _Entry)
return ir.PointerType(_enum_ty3) if IsPtr else _enum_ty3
if _Entry and _Entry.IsExceptionClass:
return ir.IntType(32) if not IsPtr else ir.PointerType(ir.IntType(32))
if _Entry and _Entry.IsFunction:
return ir.PointerType(ir.IntType(8))
if _Entry and _Entry.BaseType:
if (isinstance(_Entry.BaseType, type) and issubclass(_Entry.BaseType, _t.CEnum)) or _Entry.BaseType is _t.CEnum:
return ir.IntType(32) if not IsPtr else ir.PointerType(ir.IntType(32))
_enum_ty4: ir.Type = self._enum_int_type(LastPart, _Entry)
return ir.PointerType(_enum_ty4) if IsPtr else _enum_ty4
if LastPart in self.structs:
if IsPtr:
return ir.PointerType(self.structs[LastPart])
+6
View File
@@ -4,6 +4,8 @@ from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple, TYPE_CHECKING
import vast as ast
from lib.core.SymbolUtils import _ENUM_DEFAULT_INT_WIDTH
if TYPE_CHECKING:
from lib.core.Handles.HandlesBase import CTypeInfo
@@ -18,6 +20,10 @@ class ClassSymbolData:
is_cpython_object: bool = False
is_packed: bool = False
enum_members: List[Tuple[str, int, int]] = field(default_factory=list) # (name, value, lineno)
# 枚举存储宽度(bit):按"最宽成员类型"推导(SymbolUtils.EnumClassIntWidth)。
# 仅 type_kind == 'enum' 有意义;非枚举 / 无可解析整数成员 → 默认 32。
# 该属性随符号表条目序列化(_shared_sym.json pickle 的 _sm._extra)⇒ 跨模块同宽。
enum_width: int = _ENUM_DEFAULT_INT_WIDTH
@dataclass
+12 -3
View File
@@ -10,7 +10,8 @@ from lib.core.SymbolData import (
FuncSymbolData, DefineSymbolData, AnonymousTypeData
)
from lib.core.Handles.HandlesBase import CTypeInfo
from lib.core.SymbolUtils import AnnotationContainsTType, CheckAnnotationHasCInline, IsTModule, AnnotationContainsName
from lib.core.SymbolUtils import AnnotationContainsTType, CheckAnnotationHasCInline, IsTModule, AnnotationContainsName
from lib.core.SymbolUtils import EnumClassIntWidth, _ENUM_DEFAULT_INT_WIDTH
from lib.includes import t
@@ -24,7 +25,7 @@ class ASTSymbolExtractor:
"""提取文件中的所有符号信息"""
with open(file_path, 'r', encoding='utf-8') as f:
code: str = f.read()
tree: ast.Module = ast.parse(code)
tree: ast.Module = ast.parse(code, file_path)
result: ModuleSymbols = ModuleSymbols(file_path=file_path)
@@ -192,9 +193,16 @@ class ASTSymbolExtractor:
type_kind = 'exception'
break
# 提取枚举成员
# 枚举成员提取 + 存储宽度推导("最宽成员类型",与定义端
# HandlesClassDef._RegisterEnumMembers / TPV ClassGenEnum 同一规则源)。
# 本条通路(LoadModuleSymbols → 消费 .vpi/.pyi)是**跨模块符号表**的来源
# (Phase2Translator._build_shared_symbol_data → _shared_sym.json pickle),
# 宽度在此推导后随条目序列化,跨模块读取端(HandlesImports 的 .vpi 直读
# 路径 + _enum_int_type)即得到同一宽度;旧产物/老 pickle 无该字段 → 退 32。
enum_members: list[tuple[str, int, int]] = []
enum_width: int = _ENUM_DEFAULT_INT_WIDTH
if type_kind == 'enum':
enum_width = EnumClassIntWidth(node.body, self._symtab)
next_enum_value: int = 0
for item in node.body:
if isinstance(item, ast.Assign):
@@ -257,6 +265,7 @@ class ASTSymbolExtractor:
is_cpython_object=is_cpython_object,
is_packed=is_packed,
enum_members=enum_members,
enum_width=enum_width,
)
# ------------------------------------------------------------------
+7 -3
View File
@@ -27,9 +27,13 @@ class SymbolInserter:
# 插入类符号
for cls in module_symbols.classes:
# 枚举存储宽度("最宽成员类型")随条目登记:本条通路是跨模块符号表的
# 来源(.vpi/.pyi → LoadModuleSymbols),宽度必须一路带到序列化产物。
_enum_width: int | None = cls.enum_width if cls.type_kind == 'enum' else None
self._symtab._InsertClassSymbol(
cls.name, cls.type_kind, cls.lineno, file_path,
cls.members, cls.is_cpython_object, cls.is_packed
cls.members, cls.is_cpython_object, cls.is_packed,
EnumWidth=_enum_width
)
loaded.append(cls.name)
if self._namespace:
@@ -38,10 +42,10 @@ class SymbolInserter:
# 插入枚举成员(短名 + 类名.成员名)—— CEnum 和 REnum 共用
if cls.type_kind in ('enum', 'renum'):
for member_name, member_value, member_lineno in cls.enum_members:
self._symtab._InsertEnumMemberSymbol(member_name, cls.name, member_lineno, file_path, value=member_value)
self._symtab._InsertEnumMemberSymbol(member_name, cls.name, member_lineno, file_path, value=member_value, EnumWidth=_enum_width)
loaded.append(member_name)
class_member_name: str = f"{cls.name}.{member_name}"
self._symtab._InsertEnumMemberSymbol(class_member_name, cls.name, member_lineno, file_path, value=member_value)
self._symtab._InsertEnumMemberSymbol(class_member_name, cls.name, member_lineno, file_path, value=member_value, EnumWidth=_enum_width)
loaded.append(class_member_name)
# REnum: 记录变体名列表到类符号(供 HandlesExprCall 跨模块变体构造识别)
if cls.type_kind == 'renum':
+7 -3
View File
@@ -28,9 +28,13 @@ class PackageReexporter:
# 插入类符号(前缀.类名)
for cls in module_symbols.classes:
full_name: str = f"{prefix}.{cls.name}"
# 枚举宽度同样带到"模块限定名"条目上(跨模块类型注解按
# <sha1>.Enum / module.Enum 形态查表,与短名条目同宽)。
_enum_width: int | None = cls.enum_width if cls.type_kind == 'enum' else None
self._symtab._InsertClassSymbol(
full_name, cls.type_kind, cls.lineno, file_path,
cls.members, cls.is_cpython_object, cls.is_packed
cls.members, cls.is_cpython_object, cls.is_packed,
EnumWidth=_enum_width
)
loaded.append(full_name)
if self._namespace:
@@ -40,11 +44,11 @@ class PackageReexporter:
if cls.type_kind in ('enum', 'renum'):
for member_name, member_value, member_lineno in cls.enum_members:
full_member_name: str = f"{prefix}.{member_name}"
self._symtab._InsertEnumMemberSymbol(full_member_name, cls.name, member_lineno, file_path, value=member_value)
self._symtab._InsertEnumMemberSymbol(full_member_name, cls.name, member_lineno, file_path, value=member_value, EnumWidth=_enum_width)
loaded.append(full_member_name)
# 同时注册 prefix.ClassName.member_name,以支持 module.Class.MEMBER 三层访问
qualified_member_name: str = f"{prefix}.{cls.name}.{member_name}"
self._symtab._InsertEnumMemberSymbol(qualified_member_name, cls.name, member_lineno, file_path, value=member_value)
self._symtab._InsertEnumMemberSymbol(qualified_member_name, cls.name, member_lineno, file_path, value=member_value, EnumWidth=_enum_width)
loaded.append(qualified_member_name)
# REnum: 记录变体名列表到前缀类符号
if cls.type_kind == 'renum':
+10 -2
View File
@@ -417,7 +417,7 @@ class SymbolTable:
# 符号插入方法(供 SymbolInserter / PackageReexporter 调用)
# ==================================================================
def _InsertClassSymbol(self, FullName: str, TypeKind: str, lineno: int, FilePath: str, members: dict[str, CTypeInfo], IsCpythonObject: bool, IsPacked: bool = False) -> None:
def _InsertClassSymbol(self, FullName: str, TypeKind: str, lineno: int, FilePath: str, members: dict[str, CTypeInfo], IsCpythonObject: bool, IsPacked: bool = False, EnumWidth: int | None = None) -> None:
info: CTypeInfo = CTypeInfo()
info.Lineno = lineno
info.file = FilePath
@@ -429,6 +429,10 @@ class SymbolTable:
info.IsUnion = True
elif TypeKind == 'enum':
info.IsEnum = True
# 枚举存储宽度("最宽成员类型"):登记到条目上 → 随符号表序列化
# (ToDict/pickle 的 _sm._extra)→ 跨模块读回同宽;未登记 → 默认 32。
if EnumWidth:
info.set('enum_width', int(EnumWidth))
elif TypeKind == 'renum':
info.IsStruct = True
info.IsRenum = True
@@ -444,7 +448,7 @@ class SymbolTable:
self._symbols[FullName] = info
def _InsertEnumMemberSymbol(self, FullName: str, EnumName: str, lineno: int, FilePath: str, value: int | None = None) -> None:
def _InsertEnumMemberSymbol(self, FullName: str, EnumName: str, lineno: int, FilePath: str, value: int | None = None, EnumWidth: int | None = None) -> None:
info: CTypeInfo = CTypeInfo()
info.IsEnumMember = True
info.EnumName = EnumName
@@ -452,6 +456,10 @@ class SymbolTable:
info.file = FilePath
if value is not None:
info.value = value
# 成员条目同样携带枚举宽度:读取端(_enum_int_type)优先用调用点手头的
# 成员条目取宽,跨模块下成员 key 可能是模块限定名,不依赖枚举条目名可达。
if EnumWidth:
info.set('enum_width', int(EnumWidth))
self._symbols[FullName] = info
def _InsertTypedefSymbol(self, FullName: str, OriginalType_kind: str | None, OriginalClass: CTypeInfo | str | None, lineno: int, FilePath: str, members: dict[str, CTypeInfo] | None = None) -> None:
+1543 -9
View File
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -198,7 +198,7 @@ class AnnotationLoaderMixin:
with open(ModuleName, 'r', encoding='utf-8') as f:
ann_content: str = f.read()
ann_content = ExpandSoftKeywords(ann_content)
ann_tree: ast.Module = ast.parse(ann_content)
ann_tree: ast.Module = ast.parse(ann_content, ModuleName)
for node in ann_tree.body:
if isinstance(node, ast.ClassDef):
ClassName: str = node.name
@@ -239,7 +239,7 @@ class AnnotationLoaderMixin:
with open(ModuleName, 'r', encoding='utf-8') as f:
ann_content: str = f.read()
ann_content = ExpandSoftKeywords(ann_content)
ann_tree: ast.Module = ast.parse(ann_content)
ann_tree: ast.Module = ast.parse(ann_content, ModuleName)
for node in ann_tree.body:
# 检测 xxx: t.CEnum 形式的枚举成员变量
if isinstance(node, ast.AnnAssign):
@@ -286,7 +286,7 @@ class AnnotationLoaderMixin:
with open(ModuleName, 'r', encoding='utf-8') as f:
ann_content: str = f.read()
ann_content = ExpandSoftKeywords(ann_content)
ann_tree: ast.Module = ast.parse(ann_content)
ann_tree: ast.Module = ast.parse(ann_content, ModuleName)
for node in ann_tree.body:
# 检测 xxx: t.CTypedef = xxx 形式的 typedef 语句
if isinstance(node, ast.AnnAssign):
+13
View File
@@ -32,6 +32,19 @@ class ConstEvalMixin:
_fallback: ir.Constant = _zv if _zv is not None else ir.Constant(ElemType, ir.Undefined)
if not isinstance(value_node, (ast.List, ast.Set)):
return None
# 显式空元素判据(与 TPV AssignGenMixin._init_global_array_from_list 的裸空指针
# 判据两侧一致):Python 侧的空 AST 元素就是 None(TPV 侧是裸 0)。列表元素计数
# 与实际填充不一致(存在 None 槽)时,按下标取元素会静默产出错误常量/回退值;
# 故在此响亮失败(不静默跳过、不静默补 fallback),并指名下标与位置。
for _gi, _elt in enumerate(value_node.elts):
if _elt is None:
raise TypeError(
f'[TransPyC] 数组初始化列表字面量第 {_gi} 个元素为空 AST 元素(None)。'
f'上层某处产生了空 AST 元素(元素计数与实际填充不一致);'
f'拒绝静默补零生成残缺初始化,已终止编译。'
f'位置 line {getattr(value_node, "lineno", 0)}'
f':{getattr(value_node, "col_offset", 0)}'
)
if isinstance(ElemType, ir.ArrayType):
constants: list[ir.Constant] = []
for elt in value_node.elts:
+4 -4
View File
@@ -125,7 +125,7 @@ class LlvmGeneratorMixin:
TypeInfo: CTypeInfo | None = None
if Node.type_comment:
try:
tc_ast: ast.Expression = ast.parse(Node.type_comment, mode='eval')
tc_ast: ast.Expression = ast.parse(Node.type_comment, '<LlvmGenerator:type_comment>', mode='eval')
TypeInfo = self.TypeMergeHandler.GetCTypeInfo(tc_ast.body)
except Exception as _e:
if _config_mode == "strict":
@@ -701,10 +701,10 @@ class LlvmGeneratorMixin:
# LlvmCodeGenerator;未启用时 dwarf_enabled=False → 不构造 DI、不挂 !dbg,
# 生成的 .ll 与改动前逐字节一致(零影响)
self.LlvmGen.dwarf_enabled = bool(getattr(self, 'DwarfEnabled', False))
# Ctrl+C 调用栈(--ctrlc-stack):透传"本模块需注入 Install() 启动期构造器"
# 标记(仅 includes/dbgstack.vp 的翻译上下文为 True);未启用时为 False
# Ctrl+C 调用栈(--traceback):透传"本模块需注入 Install() 启动期构造器"
# 标记(仅 includes/traceback.vp 的翻译上下文为 True);未启用时为 False
# → finalize() 不做任何事,生成的 .ll 与改动前逐字节一致(零影响)
self.LlvmGen.ctrlc_stack_ctor = bool(getattr(self, 'CtrlcStackCtor', False))
self.LlvmGen.traceback_ctor = bool(getattr(self, 'TracebackCtor', False))
# --- 修复:共享 vtable 状态跨模块 ---
# 根因:LlvmGen 每模块重新创建,class_vtable/class_methods 等是实例级属性,
+1 -1
View File
@@ -80,7 +80,7 @@ class PythonParserMixin:
self.CurrentFile = FilePath
# 解析Python代码为AST
tree: ast.Module = ast.parse(content)
tree: ast.Module = ast.parse(content, FilePath)
# 保存 AST 树供后续使用
self.tree = tree
+189 -41
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import os
import re
import threading
import vast as ast
import warnings
from typing import TYPE_CHECKING
@@ -96,13 +97,68 @@ class TypeAnnotationResolver:
# ============================================================
# builtins 最后匹配(对齐 TPV TypeGenResolve._lookup_builtins_typedef):
# 所有常规注解解析路径 miss 后,扫描 includes/builtins.vp 的
# "NAME: t.CTypedef = TYPE" 声明作为最终兜底(rawptr/size_t 等)。
# 所有常规注解解析路径 miss 后,扫描 includes/builtins.vp 的 typedef
# 声明作为最终兜底(rawptr/sizet/uint64 等)。
# builtins 始终纳入编译链但不在 App 文件 import 闭包,其 typedef 不随
# from_imports 注入,故需按源文件扫描兜底。
# 必须同时兼容两种等价写法:
# - 语法糖 `typedef 名字 = 类型`(builtins.vp 现用形态)
# - 注解形态 `名字: t.CTypedef = 类型`(stdint.vp 等库模块)
# 只认注解形态时语法糖声明全被跳过 → 兜底表为空 → 裸名退化为
# `%"{mod}.名字"*` 假结构体(sizet 曾被当成结构体,BinOp 报
# "Operands must be the same type, got (%"…sizet"*, i64)")。
# ============================================================
_builtins_scan_cache: dict | None = None
# builtins 解析链的**全链**重入深度上限与线程局部计数。
# 既有 `_resolve_builtins_type_str` 的 `depth`/`visited` 只在自身递归内传递;
# 而复合值(含 `|` / `[`)分支会经 `from_node` 重新进入
# `_from_node_name → _lookup_builtins_typedef`,把 depth/visited 归零 ⇒ 该方向
# 可绕过环检测无限递归(A = B | t.CPtr、B = A 一类声明)。此计数器覆盖
# `_lookup_builtins_typedef → _resolve_builtins_type_str → from_node → …` 全程,
# 超上限即判为成环并 fail-fast(不再沿链继续)。合法链通常 ≤ 4 层。
_BUILTINS_CHAIN_MAX_DEPTH: int = 32
_chain_state: threading.local = threading.local()
# 解析失败(成环/悬空)的 builtins typedef 名 → 错误报文缓存:
# 同一失败不再被反复重试(每次重试都会重新扫描/解析整条链)。
# builtins.vp 在一次编译进程内不变,故无需失效。
_builtins_failure_cache: dict[str, str] = {}
@classmethod
def _chain_depth_enter(cls) -> int:
depth: int = getattr(cls._chain_state, 'depth', 0)
cls._chain_state.depth = depth + 1
return depth
@classmethod
def _chain_depth_exit(cls, prev: int) -> None:
cls._chain_state.depth = prev
# `typedef 名字 = 类型` 语法糖(对应 SymbolUtils._ExpandTypedefs 的展开形态)
_TYPEDEF_SUGAR_RE: re.Pattern = re.compile(
r'^typedef\s+([A-Za-z_]\w*)\s*=(?!=)\s*(.+?)\s*$')
@staticmethod
def _parse_typedef_line(stripped: str) -> tuple[str, str] | None:
"""单行源码文本 → typedef 声明 (名字, 类型值);非 typedef 行返回 None
兼容 `typedef 名字 = 类型`(语法糖)与 `名字: t.CTypedef = 类型`(注解)。
"""
m: re.Match | None = TypeAnnotationResolver._TYPEDEF_SUGAR_RE.match(stripped)
if m is not None:
return m.group(1), m.group(2)
if ':' not in stripped or 't.CTypedef' not in stripped:
return None
name, _, rest = stripped.partition(':')
if 't.CTypedef' not in rest:
return None
value: str = rest.partition('=')[2].strip()
name = name.strip()
if not name or not value:
return None
return name, value
@staticmethod
def _builtins_include_dirs() -> list[str]:
"""includes 目录列表(builtins.vp 定位用)——只认项目配置 includes,
@@ -150,23 +206,23 @@ class TypeAnnotationResolver:
if m:
aliases[m.group(2)] = m.group(1)
continue
if ':' not in stripped:
continue
name: str
rest: str
name, _, rest = stripped.partition(':')
name = name.strip()
if 't.CTypedef' not in rest:
continue
value: str = rest.partition('=')[2].strip()
if value:
typedefs[name] = value
td: tuple[str, str] | None = TypeAnnotationResolver._parse_typedef_line(stripped)
if td is not None:
typedefs[td[0]] = td[1]
# 只缓存"扫到了东西"的结果:includes 目录尚未装配(PROJECT_INCLUDE_DIRS 为空)
# 或 builtins.vp 未定位时,缓存空表会让本进程内后续所有 builtins typedef 解析
# (sizet(x)/uint64(x) 这类 cast 调用)永久 miss,只在真正扫到内容后落缓存。
if not typedefs and not aliases:
return {'typedefs': typedefs, 'aliases': aliases}
cls._builtins_scan_cache = {'typedefs': typedefs, 'aliases': aliases}
return cls._builtins_scan_cache
@classmethod
def _scan_library_typedef(cls, mod: str, attr: str) -> str | None:
"""查库模块源中 "attr: t.CTypedef = TYPE",返回 TYPE(权威定义)或 None"""
"""查库模块源中 typedef 声明,返回类型值(权威定义)或 None
兼容 `typedef 名字 = 类型`(语法糖)与 `名字: t.CTypedef = 类型`(注解)。
"""
path: str | None = TypeAnnotationResolver._find_include_file(mod)
if path is None:
return None
@@ -180,29 +236,24 @@ class TypeAnnotationResolver:
stripped: str = line.strip()
if not stripped or stripped.startswith('#'):
continue
if ':' not in stripped:
continue
name: str
rest: str
name, _, rest = stripped.partition(':')
if name.strip() != attr:
continue
if 't.CTypedef' not in rest:
continue
value: str = rest.partition('=')[2].strip()
if value:
return value
td: tuple[str, str] | None = TypeAnnotationResolver._parse_typedef_line(stripped)
if td is not None and td[0] == attr:
return td[1]
return None
@classmethod
def _resolve_builtins_type_str(cls, type_str: str, aliases: dict[str, str],
SymbolTable, depth: int = 0) -> CTypeInfo | None:
SymbolTable, depth: int = 0,
visited: frozenset[str] | None = None) -> CTypeInfo | None:
"""解析 builtins typedef 值(支持 t.XXX / mod.attr / 裸名;mod 支持 import 别名)
Args:
type_str: typedef 值(如 t.CPtr / __stdint.size_t / CSizeT)
type_str: typedef 值(如 t.CPtr / __stdint.size_t / CSizeT / sizet)
aliases: builtins.vp 的 import 别名表(__stdint → stdint)
depth: 递归深度守卫(防 typedef 值自引用成环)
visited: 本次展开链上已展开的 typedef 名(环检测)。命中环/自引用返回
None(而不是退化成 %"{mod}.{名}"* 假结构体),由调用方
_lookup_builtins_typedef fail-fast 报出根因。
"""
if not type_str or depth > 8:
return None
@@ -225,8 +276,13 @@ class TypeAnnotationResolver:
mod = mod.strip()
attr = attr.strip()
real_mod: str = aliases.get(mod, mod)
# t/c 内建命名空间:走常规 Attribute 解析(含 CPtr/State 特殊语义)
# t/c 内建命名空间:**优先按内建类名直接解析**,不依赖调用方 SymbolTable
# 的装配进度(import 注册阶段 t 尚未入表 ⇒ 原先返回 None,被上层误归因为
# "自引用/循环引用";实测 builtins typedef 'sizet' → 't.CSizeT' 即卡在此处)。
if real_mod in ('t', 'c'):
_bi: CTypeInfo | None = TypeAnnotationResolver.from_type_name(attr)
if _bi is not None:
return _bi
node = TypeAnnotationResolver._parse_type_expr(type_str)
if node is not None:
return TypeAnnotationResolver.from_node(node, SymbolTable)
@@ -234,20 +290,58 @@ class TypeAnnotationResolver:
# 库模块权威 typedef(stdint.size_t → 读 stdint.vp 扫描)
lib_val: str | None = TypeAnnotationResolver._scan_library_typedef(real_mod, attr)
if lib_val is not None:
return TypeAnnotationResolver._resolve_builtins_type_str(lib_val, aliases, SymbolTable, depth + 1)
return TypeAnnotationResolver._resolve_builtins_type_str(
lib_val, aliases, SymbolTable, depth + 1, visited)
# 库源未命中:回退常规解析(模块可能已由 import 闭包注入 SymbolTable)
node = TypeAnnotationResolver._parse_type_expr(type_str)
if node is not None:
return TypeAnnotationResolver.from_node(node, SymbolTable)
return None
# 裸名:按 BuiltinTypeMap/t 类名解析
return TypeAnnotationResolver.from_type_name(type_str)
# 裸名:名字本身即类型(t/C 类名、BuiltinTypeMap 的 C 速记名、裸 LLVM 标量名)
# → 直接解析;否则它可能是**另一个 typedef 的引用**,沿 builtins typedef 表
# 继续展开到基础类型。
# 依据:stdint.vp 的 `size_t/SIZE_T: t.CTypedef = sizet` 与 builtins.vp 的
# `size_t/uzt: t.CTypedef = sizet` 都是"别名 → 权威名 → t 类型"的链式写法,
# 与 TPV resolve_typedef_or_map 的 CTypedef 表递归(TypeGenResolve.vp:723-734)
# 同口径。此前裸名一律走 from_type_name:链中名字命中不到基础类型时退化成
# %"{mod}.{名}"* 假结构体(sizet/size_t → "Type of #N arg mismatch:
# %…sizet"* != i32" 的根因)。
if TypeAnnotationResolver._is_direct_type_name(type_str):
return TypeAnnotationResolver.from_type_name(type_str)
chain: str | None = TypeAnnotationResolver._scan_builtins()['typedefs'].get(type_str)
if chain is None:
# 非 builtins typedef 名的裸名(str/bytes/None 等):保留既有兜底
return TypeAnnotationResolver.from_type_name(type_str)
seen: frozenset[str] = visited or frozenset()
if type_str in seen or chain == type_str:
# 自引用/环(如 `sizet: t.CTypedef = sizet`):无信息可展开,返回 None
return None
return TypeAnnotationResolver._resolve_builtins_type_str(
chain, aliases, SymbolTable, depth + 1, seen | {type_str})
@staticmethod
def _is_direct_type_name(name: str) -> bool:
"""名字本身即类型(非"指向另一个 typedef 的别名")
覆盖 t 的 CType 子类名(CInt/CChar/CSizeT/CInt64T 等)、BuiltinTypeMap 的
C 速记名(int/char/bool)与裸 LLVM 标量名(i64/double…,泛型推导产物)。
"""
if not name:
return False
if BuiltinTypeMap.Get(name) is not None:
return True
if name in CTypeInfo._get_llvm_primitive_map():
return True
cls: type | None = CTypeHelper.GetTModuleCType(name)
if cls is None:
cls = getattr(t, name, None)
return isinstance(cls, type) and issubclass(cls, t.CType) and cls is not t.CType
@staticmethod
def _parse_type_expr(text: str) -> ast.AST | None:
"""vast mode='eval' 解析单个表达式,返回表达式节点或 None"""
try:
expr_tree: ast.AST = ast.parse(text, mode='eval')
expr_tree: ast.AST = ast.parse(text, '<TypeAnnotationResolver:type_expr>', mode='eval')
body = getattr(expr_tree, 'body', None)
if body is not None and not isinstance(body, list):
return body
@@ -278,18 +372,52 @@ class TypeAnnotationResolver:
"""builtins 最后匹配:查 includes/builtins.vp 的 CTypedef 声明
命中(该名字确为 builtins typedef)返回解析出的 CTypeInfo,
miss 返回 None(交由调用方走原有兜底)。
miss(该名字不是 builtins typedef)返回 None(交由调用方走原有兜底)。
rawptr 为编译器内置语法:优先于一切外部声明(不可重定义)。
名字在表内却解析不出基础类型 = 声明本身无信息/成环(自引用)⇒ fail-fast,
不再静默退化为 %"{mod}.{名}"* 假结构体(下游仅报 "Type of #N arg
mismatch: %…* != …",无法归因到 typedef 声明)。
"""
if not TypeName:
return None
if TypeName == cls._BUILTIN_ROWPTR_NAME:
return cls._builtin_rawptr_info()
data: dict = cls._scan_builtins()
type_str: str | None = data['typedefs'].get(TypeName)
if type_str is None:
return None
return cls._resolve_builtins_type_str(type_str, data['aliases'], SymbolTable)
# 同一失败不再反复解析:命中失败缓存则直接按既有报文 fail-fast,
# 不再重扫 builtins.vp / 重走整条解析链。
_cached_err: str | None = cls._builtins_failure_cache.get(TypeName)
if _cached_err is not None:
raise ValueError(_cached_err)
# 全链重入深度守卫:覆盖 `_lookup_builtins_typedef → _resolve_builtins_type_str
# → from_node → _from_node_name → _lookup_builtins_typedef` 的所有回路
# (含 `|`/`[` 复合值分支导致的 depth/visited 归零绕过)。超上限即成环。
_prev_depth: int = cls._chain_depth_enter()
try:
if _prev_depth >= cls._BUILTINS_CHAIN_MAX_DEPTH:
_err: str = (
f"builtins typedef 解析链成环或过深(>{cls._BUILTINS_CHAIN_MAX_DEPTH} 层),"
f"解析 '{TypeName}' 时终止:includes/builtins.vp 中相关 typedef 声明"
f"(含 `|` / `[...]` 复合值)互相引用成环。请改为指向真实基础类型"
f"(如 t.CSizeT),或断开环上引用。")
cls._builtins_failure_cache[TypeName] = _err
raise ValueError(_err)
data: dict = cls._scan_builtins()
type_str: str | None = data['typedefs'].get(TypeName)
if type_str is None:
return None
info: CTypeInfo | None = cls._resolve_builtins_type_str(type_str, data['aliases'], SymbolTable)
if info is None:
# 不再一律归因"自引用/循环引用"(那正是把 t.CSizeT 解析失败误报成环、
# 把人引向错误方向的根因):把声明值与该值解析失败的形态如实报出。
_err = (
f"builtins typedef '{TypeName}' 无法解析到基础类型:includes/builtins.vp "
f"声明 '{TypeName}: t.CTypedef = {type_str}',其值 '{type_str}' 解析失败"
f"(既不是内建类型名,也不是链上可展开的 typedef)。"
f"请把该声明指向真实类型(如 t.CSizeT),或确认该类型名存在。")
cls._builtins_failure_cache[TypeName] = _err
raise ValueError(_err)
return info
finally:
cls._chain_depth_exit(_prev_depth)
@staticmethod
def _from_node_constant(Node: ast.AST, SymbolTable: dict | SymbolTable) -> CTypeInfo:
@@ -742,6 +870,17 @@ class TypeAnnotationResolver:
Result.ArrayDims.insert(0, str(count_val))
else:
Result.PtrCount += 1
else:
# 单参数 t.CArray[T](= t.CArray[T, None]):长度自动推导数组。
# 指针位置语义为"元素指针" Ptr(T),需在元素类型上再加一层指针。
# 与裸名 CArray[T](HandlesTypeMerge.CollectAll 的
# `ElementType.PtrCount + 1`)、stub 侧(LLVMTypeMapper
# IsListAnnotation 的 `elem_type + '*'`)、声明端
# (DeclarationGenerator `[0 x T]` → _decay_array_to_ptr = T*)
# 以及 TPV(TypeGenResolve 单参 → Ptr(elem_ty))同口径。
# 此前缺这一层:t.CArray[str] 得 i8*(即 str 本身),
# result[k] 退化成 i8 → 写字符串指针触发 ptr→int fail-fast。
Result.PtrCount += 1
if ElemInfo.IsPtr and not ElemInfo.ArrayDims:
Result.PtrCount = max(Result.PtrCount, 1)
return Result
@@ -789,12 +928,21 @@ class TypeAnnotationResolver:
params_list = slice_node.elts[0]
return_node = slice_node.elts[1]
ParamTypes = []
if isinstance(params_list, ast.List):
_ParamsIsList: bool = isinstance(params_list, ast.List)
if _ParamsIsList:
for elt in params_list.elts:
ParamTypeInfo = TypeAnnotationResolver.from_node(elt, SymbolTable)
if ParamTypeInfo:
ParamTypes.append(('', ParamTypeInfo))
if not ParamTypes:
# 空参数表 `t.Callable[[], R]` 语义 = 零形参:LLVM 端即 `R ()*`,
# 调用点按零实参间接 call。此前在此注入 void 占位形参 → 得
# `R (void)*`(void 作形参本身不是合法 LLVM 函数类型),调用点
# 实参个数(0) 与形参个数(1) 不符 → llvmlite CallInstr
# `arg = args[i]` IndexError(Repl.vp 模块级 t.Callable 槽
# `ReplFragFn()` 翻译崩溃根因);与本文件另三处 Callable 形态
# (:378 / :422 / :471)的 `FuncPtrParams = []` 口径统一。
# 非 List 形态(遗留写法 t.Callable[A, R])保留原 void 占位行为。
if not ParamTypes and not _ParamsIsList:
ParamTypes.append(('', TypeAnnotationResolver.from_type_name('void')))
Result.FuncPtrParams = ParamTypes
ReturnTypeInfo = TypeAnnotationResolver.from_node(return_node, SymbolTable)
+248 -19
View File
@@ -38,6 +38,32 @@ def _JoinConstParts(parts: list[object]) -> str | bytes:
return ''.join(str(p) for p in parts)
_HEX_ALPHABET: str = '0123456789abcdefABCDEF'
def _HexValOf(ch: str) -> int:
"""单个字符的十六进制位值;非十六进制字符返回 0(与 C 解析器 vast_c.HexVal 同口径)。"""
idx: int = _HEX_ALPHABET.find(ch)
if idx < 0:
return 0
return idx if idx < 16 else idx - 6
def _HexEscapeVal(body: str, start: int, count: int) -> int:
"""取转义序列后 count 位十六进制的值(越界位置与非十六进制字符均按 0 计)。
两侧解析器对非法 `\\x` / `\\u` / `\\U` 转义必须产出同一结果且都不许抛异常:
C 解析器走 HexVal(非 hex → 0),此处原来直接 `int(..., 16)`,遇 `\\xNN`
这类字面量会抛 ValueError(includes/ast/lexer.vp 的文档字符串即触发)。
"""
val: int = 0
n: int = len(body)
for k in range(count):
idx: int = start + k
val = val * 16 + _HexValOf(body[idx] if idx < n else '0')
return val
class TokenStream:
"""token 流管理基类:统一提供游标操作、位置标注工具。"""
@@ -54,6 +80,13 @@ class TokenStream:
# 类型注解解析模式:为 True 时 '*' / '**' 按 C 风格指针后缀处理
# (int* → int | t.CPtr、int** → 嵌套两层),不按乘法/幂运算消费。
self._InTypeAnnotation: bool = False
# 下标实参嵌套层数:'[' 实参内是表达式位置(t.CArray[T, A * B] 的尺寸),
# 此处的 '*' 在有右操作数时按乘法解析(见 _ParseTerm / _AtOperandAfterStarRun)。
self._SubscriptArgs: int = 0
# Tail token of the last consumed C-style pointer suffix ('*' / '**').
# _ParseParen compares it against _Prev() to recognize the paren-less cast
# `(T*)x`, which must not be mistaken for a plain group `(a | b)`.
self._PtrSuffixTail: Token | None = None
# ------------------------------------------------------------------
# 游标操作
@@ -397,8 +430,17 @@ class ExprParserMixin(TokenStream):
tok: Token = self._Peek()
op_type: type | None = None
if tok.Type == OP:
if tok.Value == '*' and self._AtPtrSuffix(left):
# C 风格指针后缀('*' 一层);展开为 base | t.CPtr 后继续。
left = self._ConsumePtrSuffix(left)
continue
if self._InTypeAnnotation and tok.Value == '*':
break # 注解模式:'*' 是 C 风格指针后缀标记,不按乘法处理
# 注解模式:'*' 后已无操作数(']' / ',' / ')' / ':' / '=' / 行尾 / ';')
# 时它是 C 风格指针后缀标记,交上方判据与 _ParseTypeAnnotation 统一消费;
# 但下标实参内的尺寸表达式(t.CArray[T, A * B])'*' 后仍有操作数,
# 必须按乘法继续——否则下标收尾报 `expected ']'`。
if not (self._SubscriptArgs > 0 and self._AtOperandAfterStarRun()):
break
op_type = {'*': N.Mult, '/': N.Div, '//': N.FloorDiv,
'%': N.Mod, '@': N.MatMult}.get(tok.Value)
if op_type is None:
@@ -429,6 +471,9 @@ class ExprParserMixin(TokenStream):
start: Token = self._Peek()
base: N.expr = self._ParseAwaitPrimary()
if self._AtOp('**'):
if self._AtPtrSuffix(base):
# C 风格指针后缀('**' 计 2 层)
return self._ConsumePtrSuffix(base)
if self._InTypeAnnotation:
return base # 注解模式:'**' 是 C 风格指针后缀标记(2 层),不按幂处理
self._Next()
@@ -456,7 +501,13 @@ class ExprParserMixin(TokenStream):
while True:
self._SkipNL()
tok: Token = self._Peek()
if tok.Type == OP and tok.Value == '.':
if tok.Type == OP and tok.Value in ('*', '**') and self._AtPtrSuffixCall(node):
# `T*(x)` / `T**(x)` 指针后缀强转:解析为 cast 调用(与
# `(T | t.CPtr)(x)` 同形)。在 trailer 循环里消费,故其后的
# `.attr` / `[i]` / `(…)` 仍由本循环继续挂接(如
# `ast.Attribute*(expr_root).value`)。
node = self._ConsumePtrSuffixCall(node, start)
elif tok.Type == OP and tok.Value == '.':
self._Next()
attr_tok: Token = self._ExpectName()
attr_node: N.Attribute = N.Attribute(value=node, attr=attr_tok.Value, ctx=N.Load())
@@ -543,13 +594,22 @@ class ExprParserMixin(TokenStream):
return node
def _ParseSliceList(self) -> N.AST:
"""解析下标列表:a[1] / a[1:2] / a[1:2:3] / a[1,2] / a[...] / a[::]"""
start: Token = self._Peek()
elts: list[N.AST] = [self._ParseSliceItem()]
while self._AtOp(','):
self._Next()
self._SkipNL()
elts.append(self._ParseSliceItem())
"""解析下标列表:a[1] / a[1:2] / a[1:2:3] / a[1,2] / a[...] / a[::]
下标实参内是**表达式**位置(如 `t.CArray[T, A * B]` 的尺寸),此处的 '*'
应按乘法解析;同时 `t.CArray[str*, 4]` 的指针后缀仍要成立——两者由
`_AtOperandAfterStarRun`('*' 后是否还有操作数)区分,故此处登记已进入实参。
"""
self._SubscriptArgs += 1
try:
start: Token = self._Peek()
elts: list[N.AST] = [self._ParseSliceItem()]
while self._AtOp(','):
self._Next()
self._SkipNL()
elts.append(self._ParseSliceItem())
finally:
self._SubscriptArgs -= 1
if len(elts) == 1:
return elts[0]
tup: N.Tuple = N.Tuple(elts=elts, ctx=N.Load())
@@ -770,16 +830,13 @@ class ExprParserMixin(TokenStream):
out.append(simple[esc])
i += 1
elif esc == 'x':
hex_digits: str = body[i + 1:i + 3]
out.append(chr(int(hex_digits, 16)))
out.append(chr(_HexEscapeVal(body, i + 1, 2)))
i += 3
elif esc == 'u':
hex_digits = body[i + 1:i + 5]
out.append(chr(int(hex_digits, 16)))
out.append(chr(_HexEscapeVal(body, i + 1, 4)))
i += 5
elif esc == 'U':
hex_digits = body[i + 1:i + 9]
out.append(chr(int(hex_digits, 16)))
out.append(chr(_HexEscapeVal(body, i + 1, 8)))
i += 9
elif esc.isdigit() and '0' <= esc <= '7':
oct_digits: str = body[i:i + 3]
@@ -823,13 +880,13 @@ class ExprParserMixin(TokenStream):
out.append(simple[esc])
i += 1
elif esc == 'x':
out.append(int(body[i + 1:i + 3], 16))
out.append(_HexEscapeVal(body, i + 1, 2))
i += 3
elif esc == 'u':
out.append(int(body[i + 1:i + 5], 16))
out.append(_HexEscapeVal(body, i + 1, 4))
i += 5
elif esc == 'U':
out.append(int(body[i + 1:i + 9], 16))
out.append(_HexEscapeVal(body, i + 1, 8))
i += 9
elif esc.isdigit() and '0' <= esc <= '7':
oct_digits: str = body[i:i + 3]
@@ -1105,6 +1162,10 @@ class ExprParserMixin(TokenStream):
self._FixLoc(walrus, start)
return walrus
first: N.expr = self.ParseExpr()
# Paren-less C cast `(T*)x`: the inner expression ends with a pointer suffix
# (_Prev() is that suffix's tail token) and the operand follows ')'. Same
# product as `(T*)(x)` / `T*(x)`: a Call whose func is `T | t.CPtr`.
is_ptr_cast: bool = self._PtrSuffixTail is not None and self._AtOp(')') and self._Prev() is self._PtrSuffixTail
self._SkipNL()
# 生成器表达式 (x for ...)
if self._AtName('for'):
@@ -1130,6 +1191,11 @@ class ExprParserMixin(TokenStream):
return tup
# 普通括号表达式 (x)
self._ExpectOp(')')
if is_ptr_cast and self._AtCastOperand():
operand: N.expr = self._ParseFactor()
cast_call: N.Call = N.Call(func=first, args=[operand], keywords=[])
self._FixLoc(cast_call, start)
return cast_call
return first
def _ParseList(self) -> N.expr:
@@ -1496,6 +1562,11 @@ class ExprParserMixin(TokenStream):
展开为 BinOp(BitOr) 链后,TPC/TPV 现有类型解析(TypeAnnotationResolver
的 PtrCount 相加、TPV TypeGenResolve 的递归 BinOp 分支)零改动即可
生成多级指针 LLVM 类型。
无法解析的后缀形态**响亮失败**(SyntaxError,含 行:列 + 源码片段 + 形态 +
正确写法示例),绝不静默丢弃:注解末尾若仍遗留未被 `_AtPtrSuffix` 接受的
'*'/'**'(如 `a: int * 2` / `a: int* | t.CPtr`),判据拒绝消费即终止编译
(历史事故:静默退化会产出错误的指针层数)。
"""
saved_flag: bool = self._InTypeAnnotation
self._InTypeAnnotation = True
@@ -1505,20 +1576,178 @@ class ExprParserMixin(TokenStream):
self._InTypeAnnotation = saved_flag
if expr is None:
return None
# 循环消费 '*' / '**' 后缀('**' 是词法单 token,计 2 层)
if self._AtPtrSuffix(expr):
return self._ConsumePtrSuffix(expr)
tok: Token = self._Peek()
if tok.Type == OP and tok.Value in ('*', '**'):
snippet: str = ''
try:
snippet = (self.Source.splitlines()[tok.Line - 1] or '').strip()
except Exception:
snippet = ''
raise SyntaxError(
f"[vast] 注解位置的指针后缀无法解析:第 {tok.Line} 行第 {tok.Col} 列的 "
f"{tok.Value!r} 前面不是可加后缀的类型(Name / Attribute / Subscript),"
f"或其后不是后缀的合法结束位置(注解结尾,或 ']' / ',' / ')' / ':' / '=')。"
f"源码片段 {snippet!r}。"
f"期望形态:`T*`(1 层)/ `T**`(2 层),T 可为裸名 / t.X / mod.Type / "
f"rawptr[T] / t.CPtr[T]。"
f"正确示例:`a: t.CPtr*` / `a: rawptr*` / `a: t.X**` / "
f"`a: rawptr[ast.AST*]` / `def f(p: int*) -> int**`。"
)
return expr
# 类型结束后缀:'_AtPtrSuffix' 的判据表
# - 全局(任意表达式上下文):'*'/'**' 串后紧跟这些 operator 时,乘法/幂本就缺右操作数
# (即原语法错误),故按指针后缀处理不会改变任何合法表达式的语义;
# - 注解模式额外允许语句结束位置(NEWLINE/NL/ENDMARKER/';')。
_PTR_SUFFIX_END_OPS: tuple[str, ...] = (']', ',', ')', ':', '=')
_PTR_SUFFIX_STMT_END_OPS: tuple[str, ...] = (';',)
# 指针后缀「强转调用」后缀:'*'/'**' 串后**紧贴**(无空白)'(' 时,写成 C 风格
# 强转调用 `T*(x)`。'(' **不**放进 `_PTR_SUFFIX_END_OPS`:`a * (b)` 是合法乘法
# ('(' 正是乘法的右操作数起点),无条件把 '(' 计入会误伤乘法;故另立一条
# 「紧贴」判据(见 `_AtPtrSuffixCall`),只认 `T*(` 这种**无空白**的强转写法。
_PTR_SUFFIX_CALL_OP: str = '('
def _AtPtrSuffix(self, base: N.expr) -> bool:
"""判定当前 '*'/'**' 串是否为 C 风格指针后缀(作用于 base)。
判据(两条同时成立):
1. base 是"类型原子"形态(Name / Attribute / Subscript)—— 即 ``ast.AST``、
``t.CArray[X, N]``、``rawptr[X]`` 之类;
2. 该 '*'/'**' 串(可跨括号内换行 NL)之后紧跟"无右操作数"位置:
``]`` / ``,`` / ``)`` / ``:`` / ``=``(全局),或注解模式下的
NEWLINE / NL / ENDMARKER / ``;``。
因此合法表达式 ``x[a * b]`` / ``f(x * y)`` / ``x[1 * 2]`` / ``a ** b``
('*' 之后是操作数)永不被判为指针后缀。
"""
if not isinstance(base, (N.Name, N.Attribute, N.Subscript)):
return False
j: int = 0
stars: int = 0
while True:
tok: Token = self._Peek(j)
if tok.Type == OP and tok.Value in ('*', '**'):
stars += 1 if tok.Value == '*' else 2
j += 1
elif tok.Type == NL:
j += 1
else:
break
if stars == 0:
return False
nxt: Token = self._Peek(j)
if nxt.Type == OP and nxt.Value in self._PTR_SUFFIX_END_OPS:
return True
if self._InTypeAnnotation:
if nxt.Type in (NEWLINE, NL, ENDMARKER):
return True
if nxt.Type == OP and nxt.Value in self._PTR_SUFFIX_STMT_END_OPS:
return True
return False
def _ConsumePtrSuffix(self, expr: N.expr) -> N.expr:
"""消费连续的 C 风格指针后缀('*' 一层 / '**' 两层),展开为 base | t.CPtr 链。"""
while True:
tok: Token = self._Peek()
if tok.Type == OP and tok.Value == '*':
self._Next()
self._PtrSuffixTail = tok
expr = self._MakePtrType(expr, tok)
elif tok.Type == OP and tok.Value == '**':
self._Next()
self._PtrSuffixTail = tok
expr = self._MakePtrType(expr, tok)
expr = self._MakePtrType(expr, tok)
else:
break
return expr
def _AtCastOperand(self) -> bool:
"""Whether the current token may start the operand of a paren-less cast `(T*)x`.
'(' is deliberately absent: `(T*)(x)` is already handled by the call trailer,
so that path's AST stays untouched. NL is not skipped either: a line break
after ')' starts a new statement, not a cast operand.
"""
tok: Token = self._Peek()
if tok.Type in (NAME, NUMBER, STRING):
return True
return tok.Type == OP and tok.Value in ('[', '{', '*', '&', '-', '+', '~')
def _AtOperandAfterStarRun(self) -> bool:
"""当前 '*'/'**' 串之后是否还有操作数起点(可跨括号内换行 NL)。
与 `_AtPtrSuffix` 互补:后者判「'*' 后是无右操作数位置」(']' / ',' / ')' /
':' / '=' / 行尾 / ';' ⇒ 指针后缀),本方法判其反面('*' 后是 NAME / NUMBER /
STRING / FSTRING 或 '(' / '[' / '{' / 一元前缀 ⇒ 乘法)。用于下标实参内区分
`t.CArray[T, A * B]`(乘法)与 `t.CArray[T*, 4]`(指针后缀)。
"""
j: int = 0
while True:
tok: Token = self._Peek(j)
if tok.Type == OP and tok.Value in ('*', '**'):
j += 1
elif tok.Type == NL:
j += 1
else:
break
nxt: Token = self._Peek(j)
if nxt.Type in (NAME, NUMBER, STRING, FSTRING):
return True
return nxt.Type == OP and nxt.Value in ('(', '[', '{', '*', '&', '-', '+', '~', '...')
def _AtPtrSuffixCall(self, base: N.expr) -> bool:
"""判定当前 '*'/'**' 串是否为 `T*(x)` 形式的指针后缀**强转调用**。
判据(三条同时成立):
1. base 是"类型原子"形态(Name / Attribute / Subscript)—— 与
`_AtPtrSuffix` 的判据 1 同源(`ast.Call` / `list[ast.AST*]` 之类);
2. base 与 '*'/'**' 串**紧贴**(源码字符相邻,无空白/换行);
3. 该 '*'/'**' 串(串内**逐个紧贴**)之后**紧贴**一个 '('。
即整段写作 **无空白**的 `T*(x)`(C 风格强转写法)。为什么 '(' 不在
`_PTR_SUFFIX_END_OPS` 里:`a * (b)` 是合法乘法('(' 可作乘法右操作数的
起点),把 '(' 无条件计入表会误伤乘法。故此处叠加"前驱是类型原子 + 两侧
紧贴"两条判据:`a * (b + c)` / `f(x) * (y)` / `n * (m - 1)` /
`(i + 1) * (j)`('*' 前后有空白,且前驱多为非类型原子)仍按乘法解析。
"""
if not isinstance(base, (N.Name, N.Attribute, N.Subscript)):
return False
first: Token = self._Peek(0)
prev: Token = self._Prev()
if prev.Type in (NEWLINE, NL) or prev.End != first.Start:
return False # base 与 '*'/'**' 之间有空白/换行:非 C 风格强转写法
j: int = 0
stars: int = 0
last_end: int = -1
while True:
tok: Token = self._Peek(j)
if tok.Type == OP and tok.Value in ('*', '**'):
if last_end != -1 and tok.Start != last_end:
return False # 星串内部出现空白:非紧邻,不判为强转
last_end = tok.End
stars += 1
j += 1
else:
break
if stars == 0:
return False
nxt: Token = self._Peek(j)
return nxt.Type == OP and nxt.Value == self._PTR_SUFFIX_CALL_OP \
and nxt.Start == last_end
def _ConsumePtrSuffixCall(self, base: N.expr, start: Token) -> N.expr:
"""把已认定为 `T*(...)` 的指针后缀强转解析为**调用**。
产物与既有强转形态 `(T | t.CPtr)(...)` 逐字同形:同为 `ast.Call`
(func = `T` 展开的 `base | t.CPtr` 链,args/keywords = 括号实参),
不新造第二套 cast 表示。
"""
func: N.expr = self._ConsumePtrSuffix(base)
return self._ParseCallTail(func, start)
def _MakePtrType(self, base: N.expr, tok: Token) -> N.expr:
"""构造一层指针包装:base | t.CPtr。"""
cptr: N.expr = N.Attribute(value=N.Name(id='t', ctx=N.Load()),
+156 -6
View File
@@ -22,9 +22,56 @@ from lib.vast.Tokenizer import (
)
# ======================================================================
# virtual / novirtual 修饰符(软关键字)
# ======================================================================
# 语法:`virtual class` / `novirtual class` / `virtual def` / `novirtual def`
# (修饰符写在 class/def **之前**,允许与修饰对象同行,也允许各占一行)。
#
# 语义:与既有装饰器**同一套表示**——virtual → `@t.CVTable`,
# novirtual → `@t.NoVTable`。本层只"合成装饰器节点",不新增第二套表示:
# - 类级:CVTable 强制有 vtable / NoVTable 强制无 vtable
# (TPC:HandlesClassDef._resolve_class_flags 读 ClassDef.decorator_list
# 的 CVTable/NoVTable;TPV:ClassGenBase._detect_vtable_status)
# - 方法级:CVTable 进虚表槽 / NoVTable 不进虚表槽
# (TPV:ClassGenBase._should_method_be_virtual;TPC:同判据见
# SymbolUtils.ClassVtableSlotMethods / 方法收集处)
#
# 行号守恒:合成节点的位置取"修饰符 token"的位置,ClassDef/FunctionDef 自身的
# lineno/col_offset 仍是源码里 class/def 的真实位置 ⇒ 与手写装饰器形态在
# 行号上也不产生任何偏移(vs 文本插入装饰器行会整篇 +1 行)。
#
# 软关键字边界(非保留字):
# - 只在**语句起始**且后随 class/def 时生效;作标识符的其它位置
# (`x.virtual` / `f(virtual)` / `d = {"virtual": 1}`)逐字不变;
# - 语句起始但后随非 class/def ⇒ **响亮失败**(绝不静默丢弃修饰符);
# - `virtual def` / `novirtual def` 出现在类体之外(模块级 / 函数体 / 分支体)
# ⇒ **响亮失败**。
_VIRTUAL_MARKERS: dict[str, str] = {'virtual': 'CVTable', 'novirtual': 'NoVTable'}
# def 的别名(与 Tokenizer.SOFT_KEYWORDS 的 def 系列同集合,**不含** extern——
# extern 由 _ParseExternDef 单独解析,不能走 _ParseFunctionDef)
_DEF_ALIASES: tuple[str, ...] = ('fn', 'fun', 'func', 'function')
_VIRTUAL_POSITION_HINT: str = (
'可写位置:类级 `virtual class C:` / `novirtual class C:`;'
'方法级 `virtual def m(self): ...` / `novirtual def m(self): ...`'
'(**只能写在类体内**:virtual class / class / novirtual class 的类体)'
)
_VIRTUAL_EXAMPLE: str = (
'virtual class Shape: / novirtual class Node: / '
'class A: virtual def run(self) -> int: ... / '
'novirtual class B: novirtual def helper(self) -> int: ...'
)
class Parser(ExprParserMixin):
"""Viper/Python 完整语法分析器。"""
# virtual / novirtual 修饰符:语句起始 + 紧跟 class/def 时才生效(软关键字)。
# _InClassBody:当前正在解析的套件的**直接拥有者**是否为 ClassDef
# (由 _ParseSuite 按 owner_is_class 维护),用于 fail-fast
# "virtual def / novirtual def 只能直接写在类体内"。
_InClassBody: bool = False
# ==================================================================
# 模块级入口
# ==================================================================
@@ -67,6 +114,14 @@ class Parser(ExprParserMixin):
# def 分派之前
if v == 'define' and self._Peek(1).Type == NAME:
return self._ParseDefineDef()
# virtual / novirtual 修饰符(软关键字):只在"语句起始 + 紧跟 class/def"
# 时生效,合成既有装饰器 @t.CVTable / @t.NoVTable(见 _ParseVirtualModified)。
# 放在 def/class 分派之前:修饰符必须先被消费,class/def 才能照常分派。
# 与软关键字 fn→def 的先后关系:`.vp` 源码在进入本解析器之前已过
# SymbolUtils.ExpandSoftKeywords(fn/fun/func/function → def),
# 故 `virtual fn m()` 在此已是 `virtual def m()`;本层只认 class/def。
if v in _VIRTUAL_MARKERS:
return self._ParseVirtualModified()
# 软关键字函数定义:fn/fun/func/function + 标识符
if v in SOFT_KEYWORDS and self._Peek(1).Type == NAME:
return self._ParseFunctionDef()
@@ -520,8 +575,83 @@ class Parser(ExprParserMixin):
return self._ParseFunctionDef(is_async=True, decorators=decorator_list)
if tok.Type == NAME and tok.Value == 'class':
return self._ParseClassDef(decorators=decorator_list)
if tok.Type == NAME and tok.Value in _VIRTUAL_MARKERS:
# `@deco` 与 `virtual class`/`virtual def` 组合:合成标记追加在既有装饰器之后
return self._ParseVirtualModified(decorators=decorator_list)
raise SyntaxError(f"decorator must be followed by def/class, got {tok.Value!r}")
# ==================================================================
# virtual / novirtual 修饰符 + class/def
# ==================================================================
def _SourceLine(self, lineno: int) -> str:
"""取 lineno 行的源码文本(去掉行终止符),供 fail-fast 报文附源码片段。"""
starts: list[int] = self.LineStarts
if lineno < 1 or lineno > len(starts):
return ''
begin: int = starts[lineno - 1]
end: int = starts[lineno] if lineno < len(starts) else len(self.Source)
return self.Source[begin:end].rstrip('\n').rstrip('\r')
def _VirtualSyntaxError(self, detail: str, tok: Token, extra: str = '') -> SyntaxError:
"""构造 virtual/novirtual 修饰符非法形态的 fail-fast 报文。
报文含 行:列 + 源码片段 + 该修饰符允许出现的位置 + 正确示例;行尾另附
`at line N`(TPC 既有诊断路径按该正则兜底提取行号,见
HandlesImports._ParseFailureSite),文件名由调用方补全。
"""
return SyntaxError(
f"[TPC] virtual/novirtual 修饰符非法({detail}):行 {tok.Line} 列 {tok.Col},"
f"源码片段 {self._SourceLine(tok.Line).strip()!r}。"
f"{_VIRTUAL_POSITION_HINT}。正确示例:{_VIRTUAL_EXAMPLE}。"
+ (f" {extra}" if extra else '')
+ " (修饰符不会被静默忽略:非法形态一律终止编译)"
+ f" at line {tok.Line}"
)
def _ParseVirtualModified(self, decorators: list[N.expr] | None = None) -> N.stmt:
"""`virtual class` / `novirtual class` / `virtual def` / `novirtual def`。
合成 `@t.CVTable` / `@t.NoVTable` 装饰器后交给 _ParseClassDef /
_ParseFunctionDef,产出与**手写同名装饰器**逐字段同形的 AST
(decorator_list 多一个 Attribute(Name('t', Load), 'CVTable'|'NoVTable', Load)),
故下游(_resolve_class_flags / _should_method_be_virtual 同族判据)零新增分支。
非法形态**响亮失败**(不静默丢修饰符):
- 后随非 class/def;
- `virtual def` / `novirtual def` 不在类体内(模块级 / 函数体 / 分支体)。
"""
mod: Token = self._Peek() # virtual / novirtual
nxt: Token = self._Peek(1)
marker: str = _VIRTUAL_MARKERS[mod.Value]
# 后随必须是 class / def;def 的别名 fn/fun/func/function 同样接受
# (与 TPV 的 _is_funcdef_kw 口径一致)。注意:ExpandSoftKeywords 的
# fn→def 展开只命中**行首**的 fn,`virtual fn m()` 不会被先展开,
# 故此处必须显式接受别名(否则两端对同一源码一端报错一端通过)。
if nxt.Type != NAME or (nxt.Value != 'class'
and nxt.Value != 'def'
and nxt.Value not in _DEF_ALIASES):
nxt_text: str = ('(行尾)' if nxt.Type in (NEWLINE, NL, ENDMARKER)
else repr(nxt.Value))
raise self._VirtualSyntaxError(
f"{mod.Value!r} 后随 {nxt_text}(非 class/def)", mod,
f"`{mod.Value}` 只在 class/def(def 的别名 fn/fun/func/function)"
f"之前生效;若此处本意是普通标识符名,"
f"请改名(该词在语句起始会被判为修饰符)。")
if nxt.Value == 'def' and not self._InClassBody:
raise self._VirtualSyntaxError(
f'{mod.Value} def 出现在类体之外', mod,
f'{mod.Value} def 描述的是「类体内方法的虚/非虚」,'
f'模块级 / 函数体 / 分支体内没有可归属的类。')
self._Next() # 消费修饰符
deco: N.expr = N.Attribute(value=N.Name(id='t', ctx=N.Load()),
attr=marker, ctx=N.Load())
self._FixLoc(deco, mod)
deco_list: list[N.expr] = list(decorators or [])
deco_list.append(deco)
if nxt.Value == 'class':
return self._ParseClassDef(decorators=deco_list)
return self._ParseFunctionDef(decorators=deco_list)
# ==================================================================
# 函数定义(含软关键字 fn/fun/func/function)
# ==================================================================
@@ -766,7 +896,7 @@ class Parser(ExprParserMixin):
break
self._ExpectOp(')')
self._ExpectOp(':')
body: list[N.stmt] = self._ParseSuite()
body: list[N.stmt] = self._ParseSuite(owner_is_class=True)
node: N.ClassDef = N.ClassDef(
name=name_tok.Value, bases=bases, keywords=keywords, body=body,
decorator_list=decorators or [], type_params=type_params,
@@ -777,11 +907,18 @@ class Parser(ExprParserMixin):
# ==================================================================
# 语句块(套件)
# ==================================================================
def _ParseSuite(self) -> list[N.stmt]:
def _ParseSuite(self, owner_is_class: bool = False) -> list[N.stmt]:
"""解析语句块:':' NEWLINE INDENT ... DEDENT 或单行简单语句。
块首允许出现空行/注释行(NL),跳过后再检测 INDENT。
owner_is_class:本套件的**直接拥有者**是否 ClassDef。套件解析期间置
`_InClassBody`,供 `_ParseVirtualModified` 判 `virtual def`/`novirtual def`
是否"直接写在类体内"(函数体 / 分支体 / 循环体都不算)。默认 False ⇒
除类体外的所有套件拥有者天然正确,无需逐个改动。
"""
prev_in_class: bool = self._InClassBody
self._InClassBody = owner_is_class
# 已消费 ':' 后的情况:检查是否换行
if self._At(NEWLINE):
self._Next()
@@ -804,8 +941,10 @@ class Parser(ExprParserMixin):
body.append(extra)
self._SkipNEWLINE()
self._Expect(DEDENT)
self._InClassBody = prev_in_class
return body
# 空套件(语法错误,宽松返回空)
self._InClassBody = prev_in_class
return []
# 单行套件:statement (; statement)* NEWLINE
body = [self.ParseStatement()]
@@ -815,6 +954,11 @@ class Parser(ExprParserMixin):
break
body.append(self.ParseStatement())
self._ExpectNEWLINE()
# 吞掉紧跟在单行套件之后的注释行/空行 NL:多行套件分支在 DEDENT 之前
# 已由循环内 _SkipNEWLINE 吞掉这些 NL,单行套件没有 DEDENT,若不在此吞掉,
# 调用方紧随其后的 elif/else/except/finally 续接子句会被 NL 挡住 → 分支链断裂。
self._SkipNL()
self._InClassBody = prev_in_class
return body
def _Expect(self, tok_type: str, value: str | None = None) -> Token:
@@ -1227,9 +1371,15 @@ def Parse(source: str) -> N.Module:
def _ComputeLineStarts(source: str) -> list[int]:
"""计算源码所有行起始字符索引(含首行 0)。"""
"""计算源码所有行起始字符索引(含首行 0)。
Uses C-level str.find instead of a per-character Python loop (large .vp
sources: millions of characters scanned once per parse).
"""
starts: list[int] = [0]
for i, ch in enumerate(source):
if ch == '\n':
starts.append(i + 1)
Find = source.find
i: int = Find('\n')
while i >= 0:
starts.append(i + 1)
i = Find('\n', i + 1)
return starts
+4 -2
View File
@@ -179,7 +179,7 @@ class Tokenizer:
if self.Pos >= self.Length:
break
continue
if ch in ' \t\f':
if ch in ' \t\f\v':
self.Pos += 1
continue
if ch == '\r':
@@ -258,7 +258,9 @@ class Tokenizer:
elif ch == '\t':
col = (col // 8 + 1) * 8
self.Pos += 1
elif ch == '\f':
elif ch == '\f' or ch == '\v':
# \f(换页)与 \v(垂直制表)不计入缩进列,直接跳过;\v 与 Viper
# 词法层 includes/ast/lexer.vp `_is_space` 一致(对齐语言定义)。
self.Pos += 1
else:
break
+90 -4
View File
@@ -19,6 +19,7 @@ ExpandSoftKeywords 预处理。
from __future__ import annotations
import os as _os
import re as _re
import sys as _sys
from lib.vast.Nodes import AST
@@ -56,27 +57,112 @@ def _ParsePure(source: str, mode: str) -> AST:
# C 加速解析器(vast_c 扩展):可用则优先,输出与 vast.parse 结构完全一致。
# VAST_USE_C=0 禁用 C 路径;=strict 时 C 路径异常直接抛出(fail-fast)。
#
# 回退策略(响亮回退):C 路径抛异常时回退纯 Python,并向 stderr 打印一行可诊断
# 信息(文件名 + 异常)。该回退**不会**改变产出——纯 Python 是本包语义基准,
# 故绝不静默产出与纯 Python 不同的 AST。连续相同的(文件, 异常)只打印一次。
_VAST_C_PARSE = None
try:
from lib.vast_c import parse as _VAST_C_PARSE
except Exception:
_VAST_C_PARSE = None
_CFallbackSeen: set = set()
def _ReportCFallback(filename: str, exc: BaseException) -> None:
"""C 解析失败回退时的可诊断信息(去重,写 stderr)。"""
detail: str = f'{type(exc).__name__}: {exc}'
key = (filename, detail)
if key in _CFallbackSeen:
return
_CFallbackSeen.add(key)
_sys.stderr.write(
f'[vast] C 解析器失败,回退纯 Python:{filename}: {detail}\n')
# vast 语法错误统一报文:解析器原生消息末段带 "at line N"(ExprParser/Parser 各站点同款),
# 取出行号后补齐源码行与列号,用户看到的是单行 [TPC] 报文而不是解析器内部调用链。
_SYNTAX_LINE_RE: _re.Pattern[str] = _re.compile(r'at line (\d+)')
# 原生消息里的 token 原文(如 NAME('foo') / OP('*=') / INDENT(' ')),用于定位列号
_SYNTAX_TOKEN_RE: _re.Pattern[str] = _re.compile(r"\((?:'([^']*)'|\"([^\"]*)\")\)")
def _SourceLine(source: str, lineno: int) -> str:
"""取 1-based 第 lineno 行源码(行号缺失/越界返回空串)。"""
if lineno <= 0:
return ''
lines: list[str] = source.splitlines()
if lineno > len(lines):
return ''
return lines[lineno - 1]
def _LocateToken(msg: str, line_text: str) -> int:
"""定位原生消息里 token 原文在该行的 1-based 列;找不到退回首个非空白列。"""
if not line_text:
return 0
pos: int = -1
matched = _SYNTAX_TOKEN_RE.search(msg)
if matched is not None:
tok_text: str = matched.group(1) if matched.group(1) is not None else matched.group(2)
if tok_text.strip():
pos = line_text.find(tok_text)
if pos < 0:
pos = len(line_text) - len(line_text.lstrip())
return pos + 1
def _HumanizeSyntaxError(exc: SyntaxError, source: str, filename: str) -> SyntaxError:
"""把解析器的 SyntaxError 收口成「行:列 + 源码片段 + 原始消息」的单行报文。
重新抛出的语法错误带 filename/lineno/offset/text 四要素,故上层即使用
traceback.format_exc 也只打印该文件对应行与 caret,不再漏出解析器内部调用链。
"""
msg: str = str(exc)
lineno: int = getattr(exc, 'lineno', 0) or 0
if lineno <= 0:
found = _SYNTAX_LINE_RE.search(msg)
if found is not None:
lineno = int(found.group(1))
line_text: str = getattr(exc, 'text', '') or _SourceLine(source, lineno)
col: int = getattr(exc, 'offset', 0) or 0
if col <= 0:
col = _LocateToken(msg, line_text)
new: SyntaxError = SyntaxError(
f"[TPC] 语法错误({msg}):行 {lineno} 列 {col},源码片段 {line_text.strip()!r}。")
new.filename = filename
new.lineno = lineno
new.offset = col
new.text = line_text
return new
def parse(source: str, filename: str = '<unknown>', mode: str = 'exec', *,
type_comments: bool = False, feature_version: tuple[int, int] | int | None = None) -> AST:
"""解析源码字符串为 AST(与 ast.parse 同签名、同结构)。
优先使用 C 加速解析器 vast_c(输出结构完全一致),异常时回退纯 Python vast;
环境变量 VAST_USE_C:'0' 禁用 C 路径,'strict' 时 C 路径异常直接抛出。
优先使用 C 加速解析器 vast_c(输出结构完全一致),异常时回退纯 Python vast
并打印可诊断信息;环境变量 VAST_USE_C:'0' 禁用 C 路径,'strict' 时 C 路径
异常直接抛出。两条路径的 SyntaxError 都收口成带 文件/行/列/源码片段 的统一
报文(不再向用户漏出解析器内部 traceback)。
"""
if _VAST_C_PARSE is not None and _os.environ.get('VAST_USE_C', '1') != '0':
try:
return _VAST_C_PARSE(source, mode)
except Exception:
except SyntaxError as _exc:
if _os.environ.get('VAST_USE_C') == 'strict':
raise _HumanizeSyntaxError(_exc, source, filename) from None
_ReportCFallback(filename, _exc)
except Exception as _exc:
if _os.environ.get('VAST_USE_C') == 'strict':
raise
return _ParsePure(source, mode)
_ReportCFallback(filename, _exc)
try:
return _ParsePure(source, mode)
except SyntaxError as _exc:
raise _HumanizeSyntaxError(_exc, source, filename) from None
__all__: list[str] = [
Binary file not shown.
+412 -19
View File
@@ -46,6 +46,22 @@ typedef struct {
int EndOff;
} Token;
/* Token 类型名(与 Python vast.Tokenizer 的常量同名,使两侧报文措辞一致)。 */
static const char *TokKindName(TokenKind K) {
switch (K) {
case TK_NAME: return "NAME";
case TK_NUMBER: return "NUMBER";
case TK_STRING: return "STRING";
case TK_FSTRING: return "FSTRING";
case TK_OP: return "OP";
case TK_NEWLINE: return "NEWLINE";
case TK_NL: return "NL";
case TK_INDENT: return "INDENT";
case TK_DEDENT: return "DEDENT";
default: return "ENDMARKER";
}
}
/* ================= 词法分析器(Lexer) ================= */
typedef struct {
@@ -81,7 +97,11 @@ typedef struct {
int Error; /* 语法错误标志(可回退) */
const char *ErrMsg;
int ErrLine;
Token *ErrTok; /* 出错处的 token(供报错带上类型与原文) */
int FStringExprEnd; /* f-string 表达式段结束位置(body 内偏移) */
int InTypeAnnotation; /* 类型注解模式:'*'/'**' 允许按 C 风格指针后缀判定 */
int InClassBody; /* 当前套件的直接拥有者是否为 ClassDef(virtual def 判据) */
Token *PtrSuffixTail; /* 最近消费的 C 指针后缀尾 token(识别 (T*)x 无括号强转) */
} Parser;
/* vast.nodes 模块与节点类缓存(进程级惰性初始化) */
@@ -481,7 +501,9 @@ static void HandleIndent(Lexer *L) {
} else if (Ch == '\t') {
Col = (Col / 8 + 1) * 8;
L->Pos++;
} else if (Ch == '\f') {
} else if (Ch == '\f' || Ch == '\v') {
/* \f(换页)与 \v(垂直制表)不计入缩进列;\v 对齐 Viper 词法层
* includes/ast/lexer.vp 的 _is_space(与 Python Tokenizer 同口径)。 */
L->Pos++;
} else {
break;
@@ -770,7 +792,7 @@ static void TokenizeMainLoop(Lexer *L) {
}
continue;
}
if (Ch == ' ' || Ch == '\t' || Ch == '\f') {
if (Ch == ' ' || Ch == '\t' || Ch == '\f' || Ch == '\v') {
L->Pos++;
continue;
}
@@ -927,6 +949,7 @@ static void SyntaxErrorAt(Parser *P, Token *T, const char *Msg) {
P->Error = 1;
P->ErrMsg = Msg;
P->ErrLine = T ? T->Line : 0;
P->ErrTok = T;
}
static Token *ExpectOp(Parser *P, const char *S) {
@@ -1466,6 +1489,7 @@ static PyObject *JoinConstParts(PyObject *Parts) {
/* ================= 表达式解析 ================= */
static PyObject *MakeTAttrNode(Parser *P, const char *AttrName);
static PyObject *ParseExpr(Parser *P);
static PyObject *ParseConditional(Parser *P);
static PyObject *ParseDisjunction(Parser *P);
@@ -1507,7 +1531,7 @@ static PyObject *ParseSubExpression(Parser *P, const char *Text, Py_ssize_t Text
int AbsBase);
static int SkipInnerLiteral(const char *Raw, int BodyStart, int Start, int BodyEnd);
/* f-string 内部字面量常量(仅 lineno/col_offset,对齐 vast) */
/* f-string 相邻字面量合并常量(仅 lineno/col_offset,对齐 vast) */
static PyObject *MakeFStrConstant(Parser *P, PyObject *Value, Token *OuterTok) {
PyObject *N = MakeConstant(P, Value);
if (N != NULL) {
@@ -1516,6 +1540,26 @@ static PyObject *MakeFStrConstant(Parser *P, PyObject *Value, Token *OuterTok) {
return N;
}
/* 合并常量:位置取被合并的前一个 Constant 节点(对齐 Python 的 parts[-1].lineno/col_offset)。 */
static PyObject *MakeFStrConstantLike(Parser *P, PyObject *Value, PyObject *SrcNode) {
PyObject *N = MakeConstant(P, Value);
if (N == NULL) {
return NULL;
}
PyObject *Line = PyObject_GetAttrString(SrcNode, "lineno");
PyObject *Col = PyObject_GetAttrString(SrcNode, "col_offset");
if (Line != NULL) {
PyObject_SetAttrString(N, "lineno", Line);
}
if (Col != NULL) {
PyObject_SetAttrString(N, "col_offset", Col);
}
Py_XDECREF(Line);
Py_XDECREF(Col);
PyErr_Clear();
return N;
}
static PyObject *ParseStringConcat(Parser *P) {
Token *Start = PeekTok(P, 0);
PyObject *Parts = NewList(); /* JoinedStr 的 values */
@@ -1607,7 +1651,7 @@ static PyObject *ParseStringConcat(Parser *P) {
Py_DECREF(ConstParts);
return NULL;
}
PyObject *MergedConst = MakeFStrConstant(P, Merged, Start);
PyObject *MergedConst = MakeFStrConstantLike(P, Merged, Last);
Py_DECREF(Merged);
if (MergedConst == NULL) {
Py_DECREF(JVals);
@@ -1675,7 +1719,7 @@ static PyObject *ParseStringConcat(Parser *P) {
Py_DECREF(ConstParts);
return NULL;
}
PyObject *MergedConst = MakeFStrConstant(P, Merged, Start);
PyObject *MergedConst = MakeFStrConstantLike(P, Merged, Last);
Py_DECREF(Merged);
if (MergedConst == NULL) {
Py_DECREF(Parts);
@@ -2510,6 +2554,170 @@ static PyObject *ParseArith(Parser *P) {
return ParseBinLoop(P, ParseTerm, "+", "-", "Add", "Sub");
}
/* ============ C 风格指针后缀(* / **,对齐 vast ExprParser) ============ */
/* 构造一层指针包装 base | t.CPtr(对齐 Python _MakePtrType)。 */
static PyObject *MakePtrType(Parser *P, PyObject *Base, Token *Tok) {
PyObject *CPtr = MakeTAttrNode(P, "CPtr");
if (CPtr == NULL) {
return NULL;
}
PyObject *Op = MakeSingleton(P, "BitOr");
if (Op == NULL) {
Py_DECREF(CPtr);
return NULL;
}
PyObject *Argv[3] = { Base, Op, CPtr };
PyObject *Node = NewNode(P, "BinOp", Argv, 3);
Py_DECREF(Op);
Py_DECREF(CPtr);
if (Node != NULL) {
FixLoc(P, Node, Tok);
}
return Node;
}
/* 判定当前位置的 '*'/'**' 串是否为 C 风格指针后缀(作用于"类型原子"节点)。
*
* 判据(两条同时成立,对齐 Python ExprParser._AtPtrSuffix):
* 1. 节点是 Name / Attribute / Subscript(ast.AST / t.CArray[X, N] / rawptr[X] …);
* 2. '*'/'**' 串(可跨括号内换行 NL)之后紧跟"无右操作数"位置:
* ']'/','/')'/':'/'='(任意上下文)—— 乘法/幂在这些位置本就缺右操作数
* (即原语法错误),故不会改变任何合法表达式(x[a * b] / f(x * y) /
* x[1 * 2] / a ** b)的语义;注解模式额外允许 NEWLINE/NL/ENDMARKER/';'。
*/
static int AtPtrSuffixEnd(Parser *P, PyObject *Base) {
static const char *EndOps[] = {"]", ",", ")", ":", "="};
if (!IsNameOrAttrOrSub(Base)) {
return 0;
}
int J = 0;
int Stars = 0;
for (;;) {
Token *T = PeekTok(P, J);
if (TokIsOp(T, "*")) {
Stars += 1;
J++;
} else if (TokIsOp(T, "**")) {
Stars += 2;
J++;
} else if (T->Kind == TK_NL) {
J++;
} else {
break;
}
}
if (Stars == 0) {
return 0;
}
Token *Nxt = PeekTok(P, J);
for (size_t I = 0; I < sizeof(EndOps) / sizeof(EndOps[0]); I++) {
if (TokIsOp(Nxt, EndOps[I])) {
return 1;
}
}
if (P->InTypeAnnotation) {
if (Nxt->Kind == TK_NEWLINE || Nxt->Kind == TK_NL
|| Nxt->Kind == TK_ENDMARKER || TokIsOp(Nxt, ";")) {
return 1;
}
}
return 0;
}
/* 消费连续的 '*'/'**' 指针后缀('**' 计 2 层),展开为 base | t.CPtr 链。 */
static PyObject *ConsumePtrSuffix(Parser *P, PyObject *Node) {
for (;;) {
Token *T = PeekTok(P, 0);
int Layers;
if (TokIsOp(T, "*")) {
Layers = 1;
} else if (TokIsOp(T, "**")) {
Layers = 2;
} else {
break;
}
NextTok(P);
P->PtrSuffixTail = T; /* 记录尾 token,供 ParseParen 识别 (T*)x */
for (int I = 0; I < Layers; I++) {
PyObject *Wrapped = MakePtrType(P, Node, T);
Py_DECREF(Node);
if (Wrapped == NULL) {
return NULL;
}
Node = Wrapped;
}
}
return Node;
}
static PyObject *ParseCallTail(Parser *P, PyObject *Func, Token *Start);
/* 判定当前位置的 '*'/'**' 串是否为 `T*(x)` 形式的指针后缀**强转调用**
* (对齐 Python ExprParser._AtPtrSuffixCall):
* 1. 节点是类型原子 Name/Attribute/Subscript;
* 2. 节点与 '*'/'**' 串**紧贴**(源码字节相邻,无空白/换行);
* 3. '*'/'**' 串(串内 token 逐个紧贴)之后**紧贴**一个 '('。
* 即整段写作**无空白**的 `T*(x)`(C 风格强转写法)。'(' 不入 EndOps 表
* (`a * (b)` 是合法乘法,'(' 可作乘法右操作数起点);故叠加"前驱是类型原子 +
* 两侧紧贴",`a * (b + c)` / `f(x) * (y)` / `n * (m - 1)` 等仍按乘法解析。
*/
static int AtPtrSuffixCall(Parser *P, PyObject *Base) {
if (!IsNameOrAttrOrSub(Base)) {
return 0;
}
Token *First = PeekTok(P, 0);
Token *Prev = PrevTok(P);
if (Prev == NULL || Prev->Kind == TK_NL || Prev->Kind == TK_NEWLINE
|| Prev->End != First->Start) {
return 0; /* base 与 '*'/'**' 之间有空白/换行 */
}
int J = 0;
int Stars = 0;
Token *RunPrev = NULL;
Token *Last = NULL;
for (;;) {
Token *T = PeekTok(P, J);
if (TokIsOp(T, "*") || TokIsOp(T, "**")) {
if (RunPrev != NULL && T->Start != RunPrev->End) {
return 0; /* 星串内部出现空白:非紧邻 */
}
Stars++;
RunPrev = T;
Last = T;
J++;
} else {
break;
}
}
if (Stars == 0 || Last == NULL) {
return 0;
}
Token *Nxt = PeekTok(P, J);
return TokIsOp(Nxt, "(") && Nxt->Start == Last->End;
}
/* 消费已认定的 `T*(...)` 强转:func = base 展开的指针类型,随后解析调用实参。
* 接管 Base 引用(与 ConsumePtrSuffix 一致),返回 Call 的新引用。 */
static PyObject *ConsumePtrSuffixCall(Parser *P, PyObject *Base, Token *Start) {
PyObject *Func = ConsumePtrSuffix(P, Base);
if (Func == NULL) {
return NULL;
}
PyObject *Call = ParseCallTail(P, Func, Start);
Py_DECREF(Func);
return Call;
}
/* 类型注解入口:置注解模式标志(对齐 Python ExprParser._ParseTypeAnnotation)。 */
static PyObject *ParseTypeAnnotation(Parser *P) {
int Saved = P->InTypeAnnotation;
P->InTypeAnnotation = 1;
PyObject *Node = ParseExpr(P);
P->InTypeAnnotation = Saved;
return Node;
}
/* term:factor (*|/|//|%|@ factor)* */
static PyObject *ParseTerm(Parser *P) {
Token *Start = PeekTok(P, 0);
@@ -2522,6 +2730,14 @@ static PyObject *ParseTerm(Parser *P) {
Token *T = PeekTok(P, 0);
const char *OpName = NULL;
if (T->Kind == TK_OP) {
if (TokIsOp(T, "*") && AtPtrSuffixEnd(P, Left)) {
/* C 风格指针后缀('*' 一层) */
Left = ConsumePtrSuffix(P, Left);
if (Left == NULL) {
return NULL;
}
continue;
}
if (TokIsOp(T, "*")) OpName = "Mult";
else if (TokIsOp(T, "/")) OpName = "Div";
else if (TokIsOp(T, "//")) OpName = "FloorDiv";
@@ -2557,17 +2773,23 @@ static PyObject *ParseTerm(Parser *P) {
return Left;
}
/* factor:+|-|~ factor | power */
/* factor:+|-|~|*|& factor | power */
static PyObject *ParseFactor(Parser *P) {
Token *T = PeekTok(P, 0);
if (T->Kind == TK_OP && (TokIsOp(T, "+") || TokIsOp(T, "-") || TokIsOp(T, "~"))) {
/* 一元前缀与 Python ExprParser._ParseFactor 逐字对齐:'+' USub/'-'/'~' 之外还含
* Viper 新规范的 '*'(Deref) 与 '&'(AddrOf)(`*p = v` / `&x`)。缺这两项时
* C 解析器对含取址/解引用的文件整体失败并回退纯 Python(C 缺口清单根因)。 */
if (T->Kind == TK_OP && (TokIsOp(T, "+") || TokIsOp(T, "-") || TokIsOp(T, "~")
|| TokIsOp(T, "*") || TokIsOp(T, "&"))) {
Token *Start = NextTok(P);
PyObject *Operand = ParseFactor(P);
if (Operand == NULL) {
return NULL;
}
const char *OpName = TokIsOp(Start, "+") ? "UAdd"
: (TokIsOp(Start, "-") ? "USub" : "Invert");
: (TokIsOp(Start, "-") ? "USub"
: (TokIsOp(Start, "~") ? "Invert"
: (TokIsOp(Start, "*") ? "Deref" : "AddrOf")));
PyObject *Op = MakeSingleton(P, OpName);
if (Op == NULL) {
Py_DECREF(Operand);
@@ -2595,6 +2817,10 @@ static PyObject *ParsePower(Parser *P) {
if (!AtOp(P, "**")) {
return Base;
}
if (AtPtrSuffixEnd(P, Base)) {
/* C 风格指针后缀('**' 计 2 层) */
return ConsumePtrSuffix(P, Base);
}
NextTok(P);
PyObject *Exp = ParseFactor(P);
if (Exp == NULL) {
@@ -2946,7 +3172,15 @@ static PyObject *ParsePrimary(Parser *P) {
for (;;) {
SkipNL(P);
Token *T = PeekTok(P, 0);
if (T->Kind == TK_OP && TokIsOp(T, ".")) {
if (T->Kind == TK_OP && (TokIsOp(T, "*") || TokIsOp(T, "**"))
&& AtPtrSuffixCall(P, Node)) {
/* `T*(x)` / `T**(x)` 指针后缀强转:解析为 cast 调用(与 (T | t.CPtr)(x)
* 同形)。在 trailer 循环里消费,故其后的 .attr / [i] / (…) 继续挂接。 */
Node = ConsumePtrSuffixCall(P, Node, Start);
if (Node == NULL) {
return NULL;
}
} else if (T->Kind == TK_OP && TokIsOp(T, ".")) {
NextTok(P);
Token *AttrTok = ExpectName(P, NULL);
if (AttrTok == NULL) {
@@ -3032,6 +3266,26 @@ static PyObject *ParseAtom(Parser *P) {
return NULL;
}
/* 无括号强转 `(T*)x` 的操作数起点判据(对齐 Python ExprParser._AtCastOperand):
* NAME/NUMBER/STRING,或 OP 中的 '['/'{'/'*'/'&'/'-'/'+'/'~'。'(' 不在内
* (`(T*)(x)` 已由调用 trailer 处理);NL 不跳过(换行后是新语句,不是操作数)。 */
static int AtCastOperand(Parser *P) {
Token *T = PeekTok(P, 0);
if (T->Kind == TK_NAME || T->Kind == TK_NUMBER || T->Kind == TK_STRING) {
return 1;
}
if (T->Kind != TK_OP) {
return 0;
}
static const char *Ops[] = {"[", "{", "*", "&", "-", "+", "~"};
for (size_t I = 0; I < sizeof(Ops) / sizeof(Ops[0]); I++) {
if (TokIsOp(T, Ops[I])) {
return 1;
}
}
return 0;
}
/* 括号:空元组 / yield / walrus / 生成器表达式 / 元组 / 普通表达式 */
static PyObject *ParseParen(Parser *P) {
Token *Start = NextTok(P); /* '(' */
@@ -3101,6 +3355,10 @@ static PyObject *ParseParen(Parser *P) {
if (First == NULL) {
return NULL;
}
/* 无括号强转 `(T*)x`:内层表达式以指针后缀结束(_Prev() 即该后缀尾 token),
* ')' 紧随其后 → 与 `(T*)(x)` / `T*(x)` 同形,产物为 Call(func = T | t.CPtr)。 */
int IsPtrCast = (P->PtrSuffixTail != NULL && AtOp(P, ")")
&& PrevTok(P) == P->PtrSuffixTail);
SkipNL(P);
if (AtName(P, "for")) {
PyObject *Generators = ParseComprehensionGenerators(P);
@@ -3170,6 +3428,33 @@ static PyObject *ParseParen(Parser *P) {
Py_DECREF(First);
return NULL;
}
if (IsPtrCast && AtCastOperand(P)) {
PyObject *Operand = ParseFactor(P);
if (Operand == NULL) {
Py_DECREF(First);
return NULL;
}
PyObject *Args = NewList();
PyObject *Kw = NewList();
if (Args == NULL || Kw == NULL) {
Py_XDECREF(Args);
Py_XDECREF(Kw);
Py_DECREF(Operand);
Py_DECREF(First);
return NULL;
}
PyList_Append(Args, Operand);
Py_DECREF(Operand);
PyObject *Argv[3] = { First, Args, Kw };
PyObject *Call = NewNode(P, "Call", Argv, 3);
Py_DECREF(Args);
Py_DECREF(Kw);
Py_DECREF(First);
if (Call != NULL) {
FixLoc(P, Call, Start);
}
return Call;
}
return First;
}
@@ -4012,7 +4297,7 @@ static PyObject *ParseArg(Parser *P, int AllowAnnotation) {
PyObject *Annotation = NULL;
if (AllowAnnotation && AtOp(P, ":")) {
NextTok(P);
Annotation = ParseExpr(P);
Annotation = ParseTypeAnnotation(P);
if (Annotation == NULL) {
return NULL;
}
@@ -4037,6 +4322,7 @@ static PyObject *ParseArg(Parser *P, int AllowAnnotation) {
static PyObject *ParseStatement(Parser *P);
static PyObject *ParseSimpleStmt(Parser *P);
static PyObject *ParseSuite(Parser *P);
static PyObject *ParseSuiteOwned(Parser *P, int OwnerIsClass);
static PyObject *ParseIf(Parser *P);
static PyObject *ParseWhile(Parser *P);
static PyObject *ParseFor(Parser *P, int IsAsync);
@@ -4056,6 +4342,7 @@ static PyObject *ParseGlobal(Parser *P, const char *NodeName);
static PyObject *ParseDelete(Parser *P);
static PyObject *ParseAssert(Parser *P);
static PyObject *ParseDecorated(Parser *P);
static PyObject *ParseVirtualModified(Parser *P, PyObject *Decorators);
static PyObject *ParseTypeParams(Parser *P);
static PyObject *ParseExceptHandler(Parser *P);
static PyObject *ParseTargetList(Parser *P);
@@ -4116,6 +4403,9 @@ static PyObject *ParseStatement(Parser *P) {
if (TokIsName(T, "define") && PeekTok(P, 1)->Kind == TK_NAME) {
return ParseFunctionDef(P, 0, NULL);
}
if (TokIsName(T, "virtual") || TokIsName(T, "novirtual")) {
return ParseVirtualModified(P, NULL);
}
if ((TokIsName(T, "fn") || TokIsName(T, "fun") || TokIsName(T, "func")
|| TokIsName(T, "function"))
&& PeekTok(P, 1)->Kind == TK_NAME) {
@@ -4361,7 +4651,7 @@ static PyObject *TryParseAnnAssign(Parser *P) {
if (T0->Kind == TK_NAME && T1->Kind == TK_OP && TokIsOp(T1, ":")) {
Token *NameTok = NextTok(P);
NextTok(P); /* ':' */
PyObject *Annotation = ParseExpr(P);
PyObject *Annotation = ParseTypeAnnotation(P);
if (Annotation == NULL) {
return NULL;
}
@@ -4406,7 +4696,7 @@ static PyObject *TryParseAnnAssign(Parser *P) {
}
if (AtOp(P, ":") && IsNameOrAttrOrSub(Expr)) {
NextTok(P);
PyObject *Annotation = ParseExpr(P);
PyObject *Annotation = ParseTypeAnnotation(P);
if (Annotation == NULL) {
Py_DECREF(Expr);
return NULL;
@@ -4848,11 +5138,15 @@ static PyObject *ParseDecorated(Parser *P) {
SkipNEWLINE(P);
}
Token *T = PeekTok(P, 0);
/* extern / export 必须走 ParseExternDef(与 Python _ParseDecorated 的
* _ParseExternDef/_ParseExportDef 分派一致)。此前误走 ParseFunctionDef:
* 它无条件把当前 token 当 `def` 吃掉、再 ExpectName 拿到 `def`,
* 于是 `@deco` + `export def f(...)` 在 `(` 处报 expected operator NAME('f')。 */
if (T->Kind == TK_NAME && TokIsName(T, "extern")) {
return ParseFunctionDef(P, 0, DecoratorList);
return ParseExternDef(P, 0, DecoratorList);
}
if (T->Kind == TK_NAME && TokIsName(T, "export")) {
return ParseFunctionDef(P, 0, DecoratorList);
return ParseExternDef(P, 1, DecoratorList);
}
if (T->Kind == TK_NAME && TokIsName(T, "define")) {
return ParseFunctionDef(P, 0, DecoratorList);
@@ -4867,11 +5161,67 @@ static PyObject *ParseDecorated(Parser *P) {
if (T->Kind == TK_NAME && TokIsName(T, "class")) {
return ParseClassDef(P, DecoratorList);
}
if (T->Kind == TK_NAME && (TokIsName(T, "virtual") || TokIsName(T, "novirtual"))) {
return ParseVirtualModified(P, DecoratorList);
}
SyntaxErrorAt(P, T, "decorator must be followed by def/class");
Py_DECREF(DecoratorList);
return NULL;
}
/* virtual / novirtual 修饰符(对齐 vast Parser._ParseVirtualModified):
* 语句起始的 `virtual`/`novirtual` 后随 class/def(def 别名 fn/fun/func/function 同样接受)
* 时,合成装饰器 t.CVTable / t.NoVTable 追加到既有装饰器之后,再交由
* ParseClassDef / ParseFunctionDef 解析。非法形态(后随非 class/def;virtual def
* 不在类体内)一律报错,绝不静默丢弃修饰符。 */
static PyObject *ParseVirtualModified(Parser *P, PyObject *Decorators) {
Token *Mod = PeekTok(P, 0);
Token *Nxt = PeekTok(P, 1);
const char *Marker = TokIsName(Mod, "virtual") ? "CVTable" : "NoVTable";
int IsClass = 0;
int IsDef = 0;
if (Nxt->Kind == TK_NAME) {
if (TokIsName(Nxt, "class")) {
IsClass = 1;
} else if (TokIsName(Nxt, "def") || TokIsName(Nxt, "fn")
|| TokIsName(Nxt, "fun") || TokIsName(Nxt, "func")
|| TokIsName(Nxt, "function")) {
IsDef = 1;
}
}
if (!IsClass && !IsDef) {
SyntaxErrorAt(P, Mod, "virtual/novirtual must precede class/def");
return NULL;
}
if (IsDef && !P->InClassBody) {
SyntaxErrorAt(P, Mod, "virtual def must appear inside a class body");
return NULL;
}
NextTok(P); /* 消费修饰符 */
PyObject *Deco = MakeTAttrNode(P, Marker);
if (Deco == NULL) {
return NULL;
}
FixLoc(P, Deco, Mod);
PyObject *DecoList = NewList();
if (DecoList == NULL) {
Py_DECREF(Deco);
return NULL;
}
if (Decorators != NULL) {
Py_ssize_t N = PyList_GET_SIZE(Decorators);
for (Py_ssize_t I = 0; I < N; I++) {
PyList_Append(DecoList, PyList_GET_ITEM(Decorators, I));
}
}
PyList_Append(DecoList, Deco);
Py_DECREF(Deco);
PyObject *Node = IsClass ? ParseClassDef(P, DecoList)
: ParseFunctionDef(P, 0, DecoList);
Py_DECREF(DecoList);
return Node;
}
/* def / fn / fun / func / function(含 async def) */
static PyObject *ParseFunctionDef(Parser *P, int IsAsync, PyObject *Decorators) {
Token *Start = NextTok(P);
@@ -4903,7 +5253,7 @@ static PyObject *ParseFunctionDef(Parser *P, int IsAsync, PyObject *Decorators)
PyObject *Returns = NULL;
if (AtOp(P, "->")) {
NextTok(P);
Returns = ParseExpr(P);
Returns = ParseTypeAnnotation(P);
if (Returns == NULL) {
Py_DECREF(Args);
Py_DECREF(TypeParams);
@@ -5054,7 +5404,7 @@ static PyObject *ParseExternDef(Parser *P, int IsExport, PyObject *Decorators) {
PyObject *Returns = NULL;
if (AtOp(P, "->")) {
NextTok(P);
Returns = ParseExpr(P);
Returns = ParseTypeAnnotation(P);
if (Returns == NULL) {
Py_DECREF(Args);
Py_DECREF(TypeParams);
@@ -5335,7 +5685,7 @@ static PyObject *ParseClassDef(Parser *P, PyObject *Decorators) {
Py_DECREF(TypeParams);
return NULL;
}
PyObject *Body = ParseSuite(P);
PyObject *Body = ParseSuiteOwned(P, 1);
if (Body == NULL) {
Py_DECREF(Bases);
Py_DECREF(Keywords);
@@ -5381,7 +5731,7 @@ static PyObject *ParseClassDef(Parser *P, PyObject *Decorators) {
}
/* 语句块(套件) */
static PyObject *ParseSuite(Parser *P) {
static PyObject *ParseSuiteImpl(Parser *P) {
if (PeekTok(P, 0)->Kind == TK_NEWLINE) {
NextTok(P);
while (PeekTok(P, 0)->Kind == TK_NL) {
@@ -5458,9 +5808,25 @@ static PyObject *ParseSuite(Parser *P) {
Py_DECREF(Body);
return NULL;
}
/* 吞掉单行套件后的空行/注释行 NL:与纯 Python _ParseSuite 的单行分支一致,
* 使外层语句的 end_lineno 落在最后一个被吞掉的 NL 上(而非本行 NEWLINE)。 */
SkipNL(P);
return Body;
}
/* 套件入口:OwnerIsClass 表示该套件的直接拥有者是否 ClassDef(供 virtual def 判据)。 */
static PyObject *ParseSuiteOwned(Parser *P, int OwnerIsClass) {
int Prev = P->InClassBody;
P->InClassBody = OwnerIsClass;
PyObject *Body = ParseSuiteImpl(P);
P->InClassBody = Prev;
return Body;
}
static PyObject *ParseSuite(Parser *P) {
return ParseSuiteOwned(P, 0);
}
/* if / elif / else */
static PyObject *ParseIf(Parser *P) {
Token *Start = NextTok(P); /* if */
@@ -6492,13 +6858,40 @@ static PyObject *VastC_Parse(PyObject *Self, PyObject *Args, PyObject *Kwargs) {
P.Error = 0;
P.ErrMsg = NULL;
P.ErrLine = 0;
P.ErrTok = NULL;
P.FStringExprEnd = 0;
P.InTypeAnnotation = 0;
P.InClassBody = 0;
P.PtrSuffixTail = NULL;
PyObject *Tree = ParseModule(&P);
PyMem_Free(Tokens);
PyMem_Free(LineStarts);
if (Tree == NULL) {
if (P.Error) {
PyErr_SetString(PyExc_SyntaxError, P.ErrMsg ? P.ErrMsg : "vast_c: syntax error");
/* 报错必须自足可定位:带上 类型名 + token 原文 + 行号,措辞与
* Python ExprParser 的 `unexpected token OP('*') at line N` 一致,
* 这样 `[vast] C 解析器失败,回退纯 Python:<文件>: …` 一行即可定位。 */
char TokBuf[64];
const char *TokName = "ENDMARKER";
TokBuf[0] = '\0';
if (P.ErrTok != NULL) {
TokName = TokKindName(P.ErrTok->Kind);
Py_ssize_t TL = (Py_ssize_t)(P.ErrTok->End - P.ErrTok->Start);
if (TL < 0) {
TL = 0;
}
if (TL > (Py_ssize_t)sizeof(TokBuf) - 1) {
TL = (Py_ssize_t)sizeof(TokBuf) - 1;
}
for (Py_ssize_t I = 0; I < TL; I++) {
char C = P.ErrTok->Start[I];
TokBuf[I] = (C == '\n' || C == '\r') ? ' ' : C;
}
TokBuf[TL] = '\0';
}
PyErr_Format(PyExc_SyntaxError, "%s %s('%s') at line %d",
P.ErrMsg ? P.ErrMsg : "vast_c: syntax error",
TokName, TokBuf, P.ErrLine);
}
return NULL;
}