From c83fc752c6c15bc6de10fe079954ffd62f2049d9 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 26 Sep 2026 19:27:22 +0800 Subject: [PATCH] =?UTF-8?q?fix(sizeof,parse):=20=E5=B0=BA=E5=AF=B8?= =?UTF-8?q?=E9=93=BE=E5=8E=BB=E9=9D=99=E9=BB=98=E5=85=9C=E5=BA=95=EF=BC=8C?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E6=8A=A5=E9=94=99=E5=8F=AF=E5=AE=9A=E4=BD=8D?= =?UTF-8?q?=EF=BC=8C=E8=A1=A5=20C=20=E8=A7=A3=E6=9E=90=E5=99=A8=E7=BC=BA?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 口径等历史改动一并入库 --- Tools/AuditViperParseEntries.py | 152 ++ Tools/ScanAstFieldMiss.py | 840 +++++++++++ lib/Projectrans/Config.py | 16 +- lib/Projectrans/DeclarationGenerator.py | 58 +- lib/Projectrans/Phase1Generator.py | 15 +- lib/Projectrans/Phase2Translator.py | 188 +-- lib/Projectrans/Utils.py | 5 +- lib/Shell/TransPyC.py | 10 +- lib/StubGen/Converter.py | 60 +- lib/core/Handles/HandlesAnnAssign.py | 97 +- lib/core/Handles/HandlesAssign.py | 326 ++++- lib/core/Handles/HandlesAsyncFunctions.py | 70 +- lib/core/Handles/HandlesAugAssign.py | 17 +- lib/core/Handles/HandlesBase.py | 142 +- lib/core/Handles/HandlesBody.py | 11 +- lib/core/Handles/HandlesClassDef.py | 202 ++- lib/core/Handles/HandlesExpr.py | 107 +- lib/core/Handles/HandlesExprAsm.py | 158 ++- lib/core/Handles/HandlesExprAttr.py | 92 +- lib/core/Handles/HandlesExprBuiltin.py | 19 +- lib/core/Handles/HandlesExprCall.py | 657 ++++++--- lib/core/Handles/HandlesExprLambda.py | 24 +- lib/core/Handles/HandlesExprOps.py | 11 + lib/core/Handles/HandlesExprUtils.py | 4 +- lib/core/Handles/HandlesFor.py | 14 +- lib/core/Handles/HandlesFunctions.py | 14 +- lib/core/Handles/HandlesImports.py | 389 +++++- lib/core/Handles/HandlesReturn.py | 14 +- lib/core/Handles/HandlesSpecialCall.py | 97 +- lib/core/Handles/HandlesWith.py | 7 +- lib/core/LLVMCG/BaseGen.py | 24 +- lib/core/LLVMCG/ExprGen.py | 60 +- lib/core/LLVMCG/IRMetadataFix.py | 168 ++- lib/core/LLVMCG/MemoryOps.py | 110 +- lib/core/LLVMCG/StructGen.py | 98 +- lib/core/LLVMCG/TypeConvert.py | 25 +- lib/core/SymbolData.py | 6 + lib/core/SymbolExtractor.py | 15 +- lib/core/SymbolInserter.py | 10 +- lib/core/SymbolReexporter.py | 10 +- lib/core/SymbolTable.py | 12 +- lib/core/SymbolUtils.py | 1552 ++++++++++++++++++++- lib/core/Translator/AnnotationLoader.py | 6 +- lib/core/Translator/ConstEval.py | 13 + lib/core/Translator/LlvmGenerator.py | 8 +- lib/core/Translator/PythonParser.py | 2 +- lib/core/TypeAnnotationResolver.py | 230 ++- lib/vast/ExprParser.py | 267 +++- lib/vast/Parser.py | 162 ++- lib/vast/Tokenizer.py | 6 +- lib/vast/__init__.py | 94 +- lib/vast_c/_parser.pyd | Bin 210838 -> 215440 bytes lib/vast_c/vast_c.c | 431 +++++- 53 files changed, 6238 insertions(+), 887 deletions(-) create mode 100644 Tools/AuditViperParseEntries.py create mode 100644 Tools/ScanAstFieldMiss.py diff --git a/Tools/AuditViperParseEntries.py b/Tools/AuditViperParseEntries.py new file mode 100644 index 0000000..89a1236 --- /dev/null +++ b/Tools/AuditViperParseEntries.py @@ -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 ('', 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.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:])) diff --git a/Tools/ScanAstFieldMiss.py b/Tools/ScanAstFieldMiss.py new file mode 100644 index 0000000..849d68b --- /dev/null +++ b/Tools/ScanAstFieldMiss.py @@ -0,0 +1,840 @@ +# -*- coding: utf-8 -*- +"""作用域感知静态扫描: `rawptr[<族>.]` 变量上访问 不存在的字段(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 = /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 /TransPyV/App + --includes /includes + --out /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 = /TransPyC/Tools +# TpcRoot = /TransPyC (TransPyC 仓库根) +# 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[<族>.](可被 `| 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[<族>.](...)` 形态的 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()) diff --git a/lib/Projectrans/Config.py b/lib/Projectrans/Config.py index 23225e5..d453349 100644 --- a/lib/Projectrans/Config.py +++ b/lib/Projectrans/Config.py @@ -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 导出为 ".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 导出为 ".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 各模块按真实次数降序写入 (默认为 hotspot_sites.txt);同时为每个函数定义加计时器,按自身耗时(self)降序写入同目录 hotspot_time.txt,行为 `<函数名> <进入次数>`(单位 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 导出 .stackmap。 + # Ctrl+C 调用栈(--traceback):链接后用 llvm-dwarfdump 导出 .stackmap。 # 不在此处做工具/平台校验——真正的 fail-fast 落在链接成功后的旁文件导出步骤 # (那里才有最终 exe 路径可判 PE)。此处仅提示已启用。 - if args.ctrlc_stack: - _vlog().info('Ctrl+C 调用栈模式已启用(链接后用 llvm-dwarfdump 导出 .stackmap)', 'ctrlc-stack') + if args.traceback: + _vlog().info('Ctrl+C 调用栈模式已启用(链接后用 llvm-dwarfdump 导出 .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=未启用(零影响) diff --git a/lib/Projectrans/DeclarationGenerator.py b/lib/Projectrans/DeclarationGenerator.py index 85a9470..154ee53 100644 --- a/lib/Projectrans/DeclarationGenerator.py +++ b/lib/Projectrans/DeclarationGenerator.py @@ -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 记为 , + # 既无法定位文件,也无法知道是哪份 .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, } @@ -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, '', 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] diff --git a/lib/Projectrans/Phase1Generator.py b/lib/Projectrans/Phase1Generator.py index f8fb5b0..c87fb31 100644 --- a/lib/Projectrans/Phase1Generator.py +++ b/lib/Projectrans/Phase1Generator.py @@ -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), '') except SyntaxError: return result for node in tree.body: diff --git a/lib/Projectrans/Phase2Translator.py b/lib/Projectrans/Phase2Translator.py index 8e7bd20..d37723f 100644 --- a/lib/Projectrans/Phase2Translator.py +++ b/lib/Projectrans/Phase2Translator.py @@ -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:地址→源行 旁文件(.stackmap)---------- +# ---------- --traceback:地址→源行 旁文件(.stackmap)---------- # -# 开启 --ctrlc-stack 时,链接成功后用 llvm-dwarfdump 解析最终 PE 的 +# 开启 --traceback 时,链接成功后用 llvm-dwarfdump 解析最终 PE 的 # .debug_line/.debug_info,把已有的"函数级/语句级 DWARF 行标记"导出成 # .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 行表导出为 ".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:链接成功后导出"地址→源行"旁文件 .stackmap。 + """--traceback:链接成功后导出"地址→源行"旁文件 .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:链接成功后导出"地址→源行"旁文件(.stackmap)。 + # --traceback:链接成功后导出"地址→源行"旁文件(.stackmap)。 # 刻意放在上面的链接 try/except 之外,使导出阶段的 fail-fast RuntimeError # 直接向上抛出终止编译,而不被"链接异常兜底"吞掉。 - if self.ctrlc_stack_enabled: + if self.traceback_enabled: self._WriteStackMap(exe_path) diff --git a/lib/Projectrans/Utils.py b/lib/Projectrans/Utils.py index ee81547..c91c3ba 100644 --- a/lib/Projectrans/Utils.py +++ b/lib/Projectrans/Utils.py @@ -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) diff --git a/lib/Shell/TransPyC.py b/lib/Shell/TransPyC.py index 074bd5c..d98a250 100644 --- a/lib/Shell/TransPyC.py +++ b/lib/Shell/TransPyC.py @@ -110,7 +110,7 @@ class TransPyC: f.write('') # 解析AST并提取符号 - tree: ast.Module = ast.parse(content) + tree: ast.Module = ast.parse(content, SymbolFile.FilePath or '') 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 '') # 从源代码直接预扫描 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 '') for _node in ast.iter_child_nodes(_prescan_tree): if isinstance(_node, ast.Import): for _alias in _node.names: diff --git a/lib/StubGen/Converter.py b/lib/StubGen/Converter.py index 64e2cbc..ef9e585 100644 --- a/lib/StubGen/Converter.py +++ b/lib/StubGen/Converter.py @@ -88,7 +88,8 @@ class PythonToStubConverter: HasImportT: bool = False HasImportC: bool = False try: - tree: ast.Module = ast.parse(PyContent) + # 传来源名:解析失败时 vast 的报错/回退诊断才带得上模块名(否则记为 ) + 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: diff --git a/lib/core/Handles/HandlesAnnAssign.py b/lib/core/Handles/HandlesAnnAssign.py index 0a05874..b6102eb 100644 --- a/lib/core/Handles/HandlesAnnAssign.py +++ b/lib/core/Handles/HandlesAnnAssign.py @@ -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' diff --git a/lib/core/Handles/HandlesAssign.py b/lib/core/Handles/HandlesAssign.py index f882d70..d3c536d 100644 --- a/lib/core/Handles/HandlesAssign.py +++ b/lib/core/Handles/HandlesAssign.py @@ -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. / t.attr.(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. 节点,命中返回该节点。 + + 注解形态是 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.("...") 的字符串字面量参数(缺参/非字面量返回 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 '' + 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): diff --git a/lib/core/Handles/HandlesAsyncFunctions.py b/lib/core/Handles/HandlesAsyncFunctions.py index 157868b..a72560b 100644 --- a/lib/core/Handles/HandlesAsyncFunctions.py +++ b/lib/core/Handles/HandlesAsyncFunctions.py @@ -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) diff --git a/lib/core/Handles/HandlesAugAssign.py b/lib/core/Handles/HandlesAugAssign.py index 588088e..b32e0c2 100644 --- a/lib/core/Handles/HandlesAugAssign.py +++ b/lib/core/Handles/HandlesAugAssign.py @@ -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") diff --git a/lib/core/Handles/HandlesBase.py b/lib/core/Handles/HandlesBase.py index 71ccc73..ce64180 100644 --- a/lib/core/Handles/HandlesBase.py +++ b/lib/core/Handles/HandlesBase.py @@ -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 '' - 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 != %".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) diff --git a/lib/core/Handles/HandlesBody.py b/lib/core/Handles/HandlesBody.py index 0232dbb..0b08050 100644 --- a/lib/core/Handles/HandlesBody.py +++ b/lib/core/Handles/HandlesBody.py @@ -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) \ No newline at end of file + 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) \ No newline at end of file diff --git a/lib/core/Handles/HandlesClassDef.py b/lib/core/Handles/HandlesClassDef.py index a1eea78..de9a044 100644 --- a/lib/core/Handles/HandlesClassDef.py +++ b/lib/core/Handles/HandlesClassDef.py @@ -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__' diff --git a/lib/core/Handles/HandlesExpr.py b/lib/core/Handles/HandlesExpr.py index ed3cdd9..2b631cb 100644 --- a/lib/core/Handles/HandlesExpr.py +++ b/lib/core/Handles/HandlesExpr.py @@ -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) \ No newline at end of file diff --git a/lib/core/Handles/HandlesExprAsm.py b/lib/core/Handles/HandlesExprAsm.py index 05c85db..109e909 100644 --- a/lib/core/Handles/HandlesExprAsm.py +++ b/lib/core/Handles/HandlesExprAsm.py @@ -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, "") diff --git a/lib/core/Handles/HandlesExprAttr.py b/lib/core/Handles/HandlesExprAttr.py index 98e3be0..7ba840b 100644 --- a/lib/core/Handles/HandlesExprAttr.py +++ b/lib/core/Handles/HandlesExprAttr.py @@ -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: diff --git a/lib/core/Handles/HandlesExprBuiltin.py b/lib/core/Handles/HandlesExprBuiltin.py index f8001de..a603bd9 100644 --- a/lib/core/Handles/HandlesExprBuiltin.py +++ b/lib/core/Handles/HandlesExprBuiltin.py @@ -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 diff --git a/lib/core/Handles/HandlesExprCall.py b/lib/core/Handles/HandlesExprCall.py index afa901b..2eabcfe 100644 --- a/lib/core/Handles/HandlesExprCall.py +++ b/lib/core/Handles/HandlesExprCall.py @@ -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_()(返回帧指针) 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_bits: int = self.Trans.LlvmGen._enum_int_type(enum_type_name).width + type_str = f"" 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 diff --git a/lib/core/Handles/HandlesExprLambda.py b/lib/core/Handles/HandlesExprLambda.py index b60cad3..4c30f7b 100644 --- a/lib/core/Handles/HandlesExprLambda.py +++ b/lib/core/Handles/HandlesExprLambda.py @@ -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") diff --git a/lib/core/Handles/HandlesExprOps.py b/lib/core/Handles/HandlesExprOps.py index 7f63659..c382ee8 100644 --- a/lib/core/Handles/HandlesExprOps.py +++ b/lib/core/Handles/HandlesExprOps.py @@ -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)): diff --git a/lib/core/Handles/HandlesExprUtils.py b/lib/core/Handles/HandlesExprUtils.py index 721a1f1..5aed459 100644 --- a/lib/core/Handles/HandlesExprUtils.py +++ b/lib/core/Handles/HandlesExprUtils.py @@ -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) diff --git a/lib/core/Handles/HandlesFor.py b/lib/core/Handles/HandlesFor.py index cf2c7ec..8dbcb99 100644 --- a/lib/core/Handles/HandlesFor.py +++ b/lib/core/Handles/HandlesFor.py @@ -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") diff --git a/lib/core/Handles/HandlesFunctions.py b/lib/core/Handles/HandlesFunctions.py index f7354f5..3f81a8a 100644 --- a/lib/core/Handles/HandlesFunctions.py +++ b/lib/core/Handles/HandlesFunctions.py @@ -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, '', 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, '', 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 diff --git a/lib/core/Handles/HandlesImports.py b/lib/core/Handles/HandlesImports.py index aa3dd2c..bb83498 100644 --- a/lib/core/Handles/HandlesImports.py +++ b/lib/core/Handles/HandlesImports.py @@ -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 与 '' 两种形态可能指同一个类,宁可本次漏加载(由最外层收尾) + 也不递归——`_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) diff --git a/lib/core/Handles/HandlesReturn.py b/lib/core/Handles/HandlesReturn.py index d043f8a..503650d 100644 --- a/lib/core/Handles/HandlesReturn.py +++ b/lib/core/Handles/HandlesReturn.py @@ -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, [ diff --git a/lib/core/Handles/HandlesSpecialCall.py b/lib/core/Handles/HandlesSpecialCall.py index 2c368dd..2baa11f 100644 --- a/lib/core/Handles/HandlesSpecialCall.py +++ b/lib/core/Handles/HandlesSpecialCall.py @@ -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: diff --git a/lib/core/Handles/HandlesWith.py b/lib/core/Handles/HandlesWith.py index 4b37d10..1915c4f 100644 --- a/lib/core/Handles/HandlesWith.py +++ b/lib/core/Handles/HandlesWith.py @@ -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." ) diff --git a/lib/core/LLVMCG/BaseGen.py b/lib/core/LLVMCG/BaseGen.py index 266ee14..62e55bd 100644 --- a/lib/core/LLVMCG/BaseGen.py +++ b/lib/core/LLVMCG/BaseGen.py @@ -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, [ diff --git a/lib/core/LLVMCG/ExprGen.py b/lib/core/LLVMCG/ExprGen.py index 4a7ca98..9e5a202 100644 --- a/lib/core/LLVMCG/ExprGen.py +++ b/lib/core/LLVMCG/ExprGen.py @@ -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: diff --git a/lib/core/LLVMCG/IRMetadataFix.py b/lib/core/LLVMCG/IRMetadataFix.py index 4276567..9f50f73 100644 --- a/lib/core/LLVMCG/IRMetadataFix.py +++ b/lib/core/LLVMCG/IRMetadataFix.py @@ -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) diff --git a/lib/core/LLVMCG/MemoryOps.py b/lib/core/LLVMCG/MemoryOps.py index 40cc4b6..e624405 100644 --- a/lib/core/LLVMCG/MemoryOps.py +++ b/lib/core/LLVMCG/MemoryOps.py @@ -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: diff --git a/lib/core/LLVMCG/StructGen.py b/lib/core/LLVMCG/StructGen.py index 100acd9..4c5ef7b 100644 --- a/lib/core/LLVMCG/StructGen.py +++ b/lib/core/LLVMCG/StructGen.py @@ -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) diff --git a/lib/core/LLVMCG/TypeConvert.py b/lib/core/LLVMCG/TypeConvert.py index b6de9d6..ecd510f 100644 --- a/lib/core/LLVMCG/TypeConvert.py +++ b/lib/core/LLVMCG/TypeConvert.py @@ -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]) diff --git a/lib/core/SymbolData.py b/lib/core/SymbolData.py index 62276f1..a74fe94 100644 --- a/lib/core/SymbolData.py +++ b/lib/core/SymbolData.py @@ -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 diff --git a/lib/core/SymbolExtractor.py b/lib/core/SymbolExtractor.py index 6e80042..333412e 100644 --- a/lib/core/SymbolExtractor.py +++ b/lib/core/SymbolExtractor.py @@ -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, ) # ------------------------------------------------------------------ diff --git a/lib/core/SymbolInserter.py b/lib/core/SymbolInserter.py index 01dc3a1..8763ca3 100644 --- a/lib/core/SymbolInserter.py +++ b/lib/core/SymbolInserter.py @@ -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': diff --git a/lib/core/SymbolReexporter.py b/lib/core/SymbolReexporter.py index ae86b80..0ff0607 100644 --- a/lib/core/SymbolReexporter.py +++ b/lib/core/SymbolReexporter.py @@ -28,9 +28,13 @@ class PackageReexporter: # 插入类符号(前缀.类名) for cls in module_symbols.classes: full_name: str = f"{prefix}.{cls.name}" + # 枚举宽度同样带到"模块限定名"条目上(跨模块类型注解按 + # .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': diff --git a/lib/core/SymbolTable.py b/lib/core/SymbolTable.py index 34da36a..7147715 100644 --- a/lib/core/SymbolTable.py +++ b/lib/core/SymbolTable.py @@ -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: diff --git a/lib/core/SymbolUtils.py b/lib/core/SymbolUtils.py index a58487f..24851e5 100644 --- a/lib/core/SymbolUtils.py +++ b/lib/core/SymbolUtils.py @@ -1,5 +1,7 @@ """共享工具函数:符号表处理中使用的 AST 分析工具""" from __future__ import annotations +import io +import tokenize import vast as ast import re from lib.includes import t @@ -13,19 +15,1358 @@ _SOFT_KEYWORD_RE: re.Pattern[str] = re.compile( r'^(\s*)(function|func|fun|fn)(\s+)(?=[A-Za-z_])', re.MULTILINE ) +# enum 语句(Viper 语法糖):`enum Name:` + 成员行,与 +# `class Name(t.CEnum):` **完全等价**(同一实现路径——展开后再解析, +# 下游 EnumTable/符号表注册只看 ClassDef + t.CEnum 基类,不存在第二套枚举实现)。 +# 头行 enum Name: → class Name(t.CEnum): +# 裸成员 Name → Name: t.State(默认 i32;必须落为 AnnAssign, +# 否则 Enum/REnum 成员收集按 Assign/AnnAssign 过滤会漏掉裸名语句) +# 带注解 Name: T → 原样保留(自定义具体类型) +# 带值 Name = 0 → 原样保留(Assign 形态,两侧枚举收集均支持) +# 多名行 A, B, C → A: t.State; B: t.State; C: t.State(同行分号分隔,见下; +# 可带尾随逗号 A, B,,与 TPV 的 AST 口径一致) +_ENUM_HEAD_RE: re.Pattern[str] = re.compile( + r'^(\s*)enum(\s+)([A-Za-z_]\w*)(\s*):([ \t]*(?:#.*)?)$' +) +_ENUM_MEMBER_BARE_RE: re.Pattern[str] = re.compile( + r'^(\s*)([A-Za-z_]\w*)([ \t]*(?:#.*)?)$' +) +# 逗号分组成员行(`Add, Sub` / `A, B, C` / 尾随逗号 `A, B,`):与"每个名字各占一行"等价。 +# 只匹配"纯标识符列表 + 可选尾随逗号 + 可选注释",故 `A, B = 1`(Assign)、 +# `A, B: int`(带注解)与 `f(a, b)`(调用)都不命中,原样保留(由注册处的 +# EnumBodyMemberNames 兜底判据 fail-fast,不再静默丢成员)。 +# 尾随逗号必须支持:TPV 侧走 AST(`class X(t.CEnum): A, B,` 与 `A, B` 在 AST 上 +# 完全同形,无法区分),TPC 若在此拒绝会造成"同源两种编译器两种结果"。group(3) +# 只收注释正文(不含前导空白/逗号),展开时另行补一个空格。 +_ENUM_MEMBER_LIST_RE: re.Pattern[str] = re.compile( + r'^(\s*)([A-Za-z_]\w*(?:[ \t]*,[ \t]*[A-Za-z_]\w*)+)[ \t]*,?[ \t]*((?:#.*)?)$' +) +# 裸名语句保留集:这些行是语句而非枚举成员,展开时不得追加 `: t.State` +_ENUM_MEMBER_SKIP: frozenset[str] = frozenset({ + 'pass', 'return', 'break', 'continue', 'global', 'nonlocal', 'raise', + 'yield', 'import', 'from', 'del', 'assert', 'if', 'while', 'for', 'with', + 'try', 'match', 'case', 'lambda', 'def', 'class', 'enum', + 'None', 'True', 'False', +}) + + +def _ExpandEnumBlocks(Code: str) -> str: + """把 `enum Name:` 块展开为等价的 `class Name(t.CEnum):` 块(行号不变)。 + + 只在"行首缩进 + enum + 空白 + 标识符 + 冒号(行尾)"的枚举头形态触发: + `enum = 5` / `import enum` / `x.enum` / 注解 `x: enum` 均不匹配。 + 成员行只对块内"更深缩进"的行做展开,块内空行与注释行原样保留; + 缩进回到枚举头同级(或更浅)即块结束。全程不增删换行,行号映射保持有效。 + + 成员行两种形态:单名(`Name`)追加 `: t.State`;逗号分组多名(`Add, Sub`) + 在同一行内展开为分号分隔的多个 `Name: t.State`(简单语句,`vast` 与 Python + 均按多个 AnnAssign 解析)。未展开的一行多名语句会被当作元组表达式,下游 + 枚举成员收集(仅认 Assign/AnnAssign)会静默漏掉这些成员。 + """ + lines: list[str] = Code.splitlines(keepends=True) + out: list[str] = [] + i: int = 0 + n: int = len(lines) + while i < n: + raw: str = lines[i] + # 行终止符单独拆出(兼容 LF / CRLF / CR):正文参与匹配,终止符原样回写, + # 保证"不增删换行、行号不变"。CRLF 源(本仓 .vp 大量为 CRLF,但 TPC 各 + # 调用点多为文本模式读入已被通用换行归一,双保险)若不做此拆分, + # 行尾 '\r' 会让 `$` 锚定的头行正则失配 → 静默不展开。 + k: int = len(raw) + while k > 0 and (raw[k - 1] == '\n' or raw[k - 1] == '\r'): + k -= 1 + body: str = raw[:k] + term: str = raw[k:] + head = _ENUM_HEAD_RE.match(body) + if head is None: + out.append(raw) + i += 1 + continue + indent: str = head.group(1) + out.append(f'{indent}class {head.group(3)}(t.CEnum):{head.group(5)}{term}') + i += 1 + while i < n: + raw2: str = lines[i] + k2: int = len(raw2) + while k2 > 0 and (raw2[k2 - 1] == '\n' or raw2[k2 - 1] == '\r'): + k2 -= 1 + body2: str = raw2[:k2] + term2: str = raw2[k2:] + stripped: str = body2.strip() + if stripped == '' or stripped.startswith('#'): + out.append(raw2) + i += 1 + continue + if len(body2) - len(body2.lstrip()) <= len(indent): + break # 缩进回到枚举头同级:块结束(该行留给外层循环) + member = _ENUM_MEMBER_BARE_RE.match(body2) + if member is not None and member.group(2) not in _ENUM_MEMBER_SKIP: + out.append(f'{member.group(1)}{member.group(2)}: t.State{member.group(3)}{term2}') + else: + # 逗号分组成员行:同一行展开为"分号分隔的多个 AnnAssign"。 + # `Name: t.State` 是简单语句,分号分隔在 Python 与 vast 中均合法 + # ⇒ 与单名成员行产出同一批 AnnAssign(枚举成员/宽度/编号全部一致), + # 且不增删换行,行号映射保持有效。group(3) 是注释正文(可为空), + # 需要时补一个空格再接回(尾随逗号已在正则里吃掉,不参与拼接)。 + member_list = _ENUM_MEMBER_LIST_RE.match(body2) + names: list[str] = ([nm.strip() for nm in member_list.group(2).split(',')] + if member_list is not None else []) + if names and all(nm not in _ENUM_MEMBER_SKIP for nm in names): + joined: str = '; '.join(f'{nm}: t.State' for nm in names) + tail: str = member_list.group(3) + comment: str = f' {tail}' if tail else '' + out.append(f'{member_list.group(1)}{joined}{comment}{term2}') + else: + out.append(raw2) + i += 1 + return ''.join(out) + + +# Viper soft keyword `union` (statement-level reserved word): `union X:` is exactly +# `class X(t.CUnion):` (same desugar target as TPV includes/ast/parser.vp:_parse_uniondef). +# Unlike enum, union members are ordinary annotated fields, so no member rewriting. +# `union` is not used as an identifier anywhere in .vp sources. +_UNION_HEAD_RE: re.Pattern[str] = re.compile( + r'^(\s*)union(\s+)([A-Za-z_]\w*)(\s*):([ \t]*(?:#.*)?)$' +) +_UNION_PREFIX_RE: re.Pattern[str] = re.compile(r'^(\s*)union\b') + + +def _ExpandUnionBlocks(Code: str) -> str: + """Expand `union Name:` into `class Name(t.CUnion):` (same-line, line numbers kept). + + Mirrors TPV _parse_uniondef: the body is ordinary AnnAssign fields, so only the + head line is rewritten. A line starting with `union` that is not the exact + `union Name:` form fails loudly (union is never an identifier in .vp sources). + """ + if 'union' not in Code: + return Code + out: list[str] = [] + for Lineno, raw in enumerate(Code.splitlines(keepends=True), 1): + k: int = len(raw) + while k > 0 and (raw[k - 1] == '\n' or raw[k - 1] == '\r'): + k -= 1 + body: str = raw[:k] + term: str = raw[k:] + head = _UNION_HEAD_RE.match(body) + if head is not None: + out.append(f'{head.group(1)}class {head.group(3)}(t.CUnion):{head.group(5)}{term}') + continue + if _UNION_PREFIX_RE.match(body) is not None: + raise SyntaxError( + f"union 语法错误(行 {Lineno},原文: {body.strip()!r}):应为 `union 名字:`" + f"(等价于 `class 名字(t.CUnion):`)。union 是语句级保留字,成员必须写成 " + f"`名字: 类型`;非法形态一律终止编译(非静默)。" + ) + out.append(raw) + return ''.join(out) + + +# --------------------------------------------------------------------------- +# define 常量声明(Viper 语法糖)——与 `NAME: t.CDefine = 值` **完全等价** +# --------------------------------------------------------------------------- +# 带类型 define NAME: T = <表达式> → NAME: T | t.CDefine = <表达式> +# 不带类型 define NAME = <表达式> → NAME: t.CDefine = <表达式> +# +# 语义:编译期常量声明,落到**既有的** t.CDefine 注册路径(TPC: +# LlvmGenerator._register_cdefine_constant / HandlesAnnAssign 的 IsDefine 分支; +# 跨模块:SymbolExtractor → SymbolTable → .vpi/.pyi → HandlesImports), +# 不新增第二套常量实现。可见性与 `t.CDefine` 常量完全一致: +# 模块级 / 类体内 / 函数内 三种位置均可(各自走既有注册点)。 +# +# 为什么带类型写成 `T | t.CDefine`(**类型在前**)而不是既有手写惯用法的 +# `t.CDefine | T`:HandlesTypeMerge._MergeCTypeInfoInto 的 BaseType 采用 +# "首个非 void 基类型胜出"(`if Target.BaseType is None: Target.BaseType = Source.BaseType`), +# 而 _CollectAllOperandTypes 按**书写顺序**左→右展开 ⇒ `t.CDefine | T` 的 +# BaseType 会停在 t.CDefine,显式类型 T 被**丢弃**(既有手写形态的既有事实, +# 见 Test/DefineTest/App/main.vp:30-32);把 T 写在前面才能让 BaseType = T, +# 从而让"值必须能放进 T"这条判据有据可依(同时也是显式类型的真实含义)。 +# +# 只在"行首缩进 + define + 标识符 + (`=` 或 `: … =`)"形态触发: +# - `define NAME(...)`(既有宏函数软关键字,返回注解自动追加 t.CDefine, +# 见 lib/vast/Parser.py:_ParseDefineDef)后跟 `(`,两个正则都不命中 ⇒ 原样保留; +# - 把 define 当标识符的写法(`define = 5` / `define(x)`)同样不命中; +# - 同行替换、不增删换行 ⇒ 行号映射保持有效(与 fn/enum 同约定)。 +_DEFINE_CONST_TYPED_RE: re.Pattern[str] = re.compile( + r'^(\s*)define[ \t]+([A-Za-z_]\w*)[ \t]*:[ \t]*([^=#]+?)[ \t]*=(?!=)(.*)$' +) +_DEFINE_CONST_PLAIN_RE: re.Pattern[str] = re.compile( + r'^(\s*)define[ \t]+([A-Za-z_]\w*)[ \t]*=(?!=)(.*)$' +) + + +def _ExpandDefineConsts(Code: str) -> str: + """把 `define NAME[: T] = 值` 展开为等价的 `NAME: [T |] t.CDefine = 值`(行号不变)。 + + 逐行处理:行终止符(LF/CRLF/CR)单独拆出后原样回写(与 _ExpandEnumBlocks + 同约定,避免行尾 '\\r' 让 `$` 锚定的正则失配而**静默不展开**)。 + 未命中的行(含 `define NAME(...)` 宏函数、注释、字符串内容以外的行) + 逐字节原样保留。缺 `=` 的 `define NAME: T`(无初始值)不在本展开范围, + 会原样落到解析器并**响亮失败**(非静默)。 + """ + if 'define' not in Code: + return Code + out: list[str] = [] + for raw in Code.splitlines(keepends=True): + k: int = len(raw) + while k > 0 and (raw[k - 1] == '\n' or raw[k - 1] == '\r'): + k -= 1 + body: str = raw[:k] + term: str = raw[k:] + typed = _DEFINE_CONST_TYPED_RE.match(body) + if typed is not None: + out.append(f'{typed.group(1)}{typed.group(2)}: ' + f'{typed.group(3).strip()} | t.CDefine ={typed.group(4)}{term}') + continue + plain = _DEFINE_CONST_PLAIN_RE.match(body) + if plain is not None: + out.append(f'{plain.group(1)}{plain.group(2)}: t.CDefine ={plain.group(3)}{term}') + continue + out.append(raw) + return ''.join(out) + + +# --------------------------------------------------------------------------- +# typedef 类型别名声明(Viper 语法糖)——与 `T1: t.CTypedef = T2` **完全等价** +# --------------------------------------------------------------------------- +# typedef T1 = T2 → T1: t.CTypedef = T2 +# +# 语义:与手写 `T1: t.CTypedef = T2` 落到**同一条** typedef 注册/查找路径,不新增 +# 第二套实现(TPC:符号表 SymbolTypeResolver._resolve_ctypedef_typedef → +# _resolve_typedef_from_value;翻译期 HandlesAnnAssign / DeclarationGenerator 的 +# typedef 表。TPV:AssignGenMixin.translate_annassign 的 CTypedef 分支 → +# TypeGenCType.register_ctypedef)。RHS 即"类型表达式"(书写在值位): +# 裸名 `int32` / `t.X` / 并集 `t.CChar | t.CPtr` / 泛型 `array[E, N]` / 链式 `A = B`。 +# +# `typedef` 是**语句级保留字**:行首出现即必须是 `typedef 名字 = 类型` 形态。 +# - 合法形态:同行替换为 `名字: t.CTypedef = 类型`(不增删换行,行号映射保持有效, +# 与 fn/enum/define 同约定); +# - 非法形态(`typedef = 1` / `typedef A` / `typedef A = ` 缺 RHS)⇒ **响亮失败** +# (SyntaxError,非静默);`typedef A = 1`(RHS 不是类型)通过语法层,落到既有 +# typedef 注册/解析路径处理(见 Debugger/define-constants.md 同族口径)。 +# - 全仓冲突普查:`typedef` 仅在注释/文档字符串中出现,**无任何**变量/参数/字段/ +# 函数/类/模块以其命名 ⇒ 作为语句级保留字不会误伤既有源码。 +_TYPEDEF_DECL_RE: re.Pattern[str] = re.compile( + r'^(\s*)typedef[ \t]+([A-Za-z_]\w*)[ \t]*=(?!=)(.*)$' +) +_TYPEDEF_HEAD_RE: re.Pattern[str] = re.compile(r'^(\s*)typedef\b') + + +def _ExpandTypedefs(Code: str) -> str: + """把 `typedef T1 = T2` 展开为等价的 `T1: t.CTypedef = T2`(行号不变)。 + + 逐行处理:行终止符(LF/CRLF/CR)单独拆出后原样回写(与 _ExpandEnumBlocks / + _ExpandDefineConsts 同约定,避免行尾 '\\r' 让 `$` 锚定的正则失配而**静默不展开**)。 + 行首 `typedef` 但不符合 `typedef 名字 = …` 形态 ⇒ 响亮失败(SyntaxError); + `typedef_x` / `typedefA` 等以 typedef 为前缀的标识符不命中(`\\b` 词边界)。 + """ + if 'typedef' not in Code: + return Code + out: list[str] = [] + for raw in Code.splitlines(keepends=True): + k: int = len(raw) + while k > 0 and (raw[k - 1] == '\n' or raw[k - 1] == '\r'): + k -= 1 + body: str = raw[:k] + term: str = raw[k:] + decl = _TYPEDEF_DECL_RE.match(body) + if decl is not None: + out.append(f'{decl.group(1)}{decl.group(2)}: t.CTypedef ={decl.group(3)}{term}') + continue + if _TYPEDEF_HEAD_RE.match(body) is not None: + raise SyntaxError( + f"typedef 语法错误(原文: {body.strip()!r}):应为 " + f"`typedef 名字 = 类型`(等价于 `名字: t.CTypedef = 类型`)。" + f"合法的类型写法:裸名 / t.X / a | t.CPtr / array[E, N] / 另一 typedef 名。" + ) + out.append(raw) + return ''.join(out) + + +# --------------------------------------------------------------------------- +# 类型描述符 const / volatile(Viper 语法糖:描述符写在**类型前**) +# --------------------------------------------------------------------------- +# const int → t.CConst | int +# volatile int → t.CVolatile | int +# const volatile int → t.CConst | t.CVolatile | int +# volatile const int → t.CVolatile | t.CConst | int(顺序任意,两个限定符都累积) +# const t.CInt32T → t.CConst | t.CInt32T +# const rawptr[Foo] → t.CConst | rawptr[Foo] (描述符只管注解最前面) +# a: const int → a: t.CConst | int (局部 / 类字段) +# def f(p: const int) → def f(p: t.CConst | int) (形参) +# def f() -> const int → ... -> t.CConst | int (返回) +# +# 语义:与**手写** `t.CConst | T` / `t.CVolatile | T` 逐点等价——**同一表示**, +# 不新造第二套(描述符不是新类型,只是 `|` 联合里的既有 flag 载体 t.CConst / +# t.CVolatile): +# - TPC:展开文本落到既有识别路径(HandlesTypeMerge.GetCTypeInfo 的 +# ModulePath=='t' 分支 → Info.DataConst/DataVolatile(HandlesTypeMerge.py:192-201); +# TypeAnnotationResolver 的类型限定符分支 → DataConst/DataVolatile +# (TypeAnnotationResolver.py:934-938)),再由 _MergeCTypeInfoInto 累积 +# (HandlesTypeMerge.py:1064-1071)、TypeAnnotationResolver.from_node 的 +# BitOr 合并分支累积(TypeAnnotationResolver.py:626-633/640-674); +# **BaseType 取非限定符一侧**(TypeAnnotationResolver.py:642-644),故 +# `t.CConst | int` 与 `int | t.CConst` 等价,本层统一产出**描述符在前** +# (与全仓既有真实写法一致:includes/stdio.vp:4 `fmt: t.CConst | str`、 +# includes/spinlock.vp:6 `locked: t.CVolatile | int`、 +# ViperApps/ViperOS/VKernel/Kernel/main.vp:301 `t.CConst | int | t.CPtr`)。 +# - TPV:includes/ast/parser.vp 的 `_parse_type_ann` 合成**同形** AST +# (BinOp(BitOr, Attribute(t,'CConst'), <类型>)),产出与手写形态逐字段同形。 +# +# 软关键字边界(唯一识别面 = "注解位置 + token 相邻",与 array 那次同口径): +# const/volatile 只在该位置生效——修饰符串的前一个 token 是 `:` 且再前一个是 +# Name(注解目标:`a:` / `self.x:` / 形参名),或前一个 token 是 `->`(返回注解)。 +# 非类型位置**原样保留**为普通标识符(不做词法保留字): +# - `t.attr.const()`(TransPyV/Test/App/attr_test.vp:142,全仓 .vp 代码内唯一出现) +# - `const = 5` / `x = const` / `d = {"k": const}` +# 注释与字符串内容**不参与**:本层用 `tokenize` 做词法切分(不是行正则), +# 故 openssl/*.vp 等 C 原型注释里的 `const char *`(全仓 275 处)逐字不变。 +# +# 非法形态**响亮失败**(SyntaxError,非静默,不丢描述符): +# `a: const`(缺类型)/ `a: const = 1` / `a: const 1`(后随非类型)/ 裸 `const` 语句。 +# 报文含 行:列 + 源码片段 + 期望形态 + 正确示例(文件名由调用方的 SyntaxError +# 上报路径补全,与 _ExpandTypedefs / TPV dump_parse_traceback 同约定)。 +_TYPE_DESCRIPTOR_MARKERS: dict[str, str] = {'const': 'CConst', 'volatile': 'CVolatile'} +_DESCRIPTOR_SKIP_TYPES: tuple[int, ...] = (tokenize.COMMENT, tokenize.NL) +_DESCRIPTOR_HINT: str = ( + '`名字: const 类型` / `名字: volatile 类型`(可组合:`名字: const volatile 类型`)' +) +_DESCRIPTOR_EXAMPLE: str = ( + 'a: const int / a: volatile int / a: const volatile t.CInt32T / ' + 'def f(p: const rawptr[Foo]) -> const int' +) + + +def _PrevSignificantTokenIndex(Tokens: list, Index: int) -> int: + """取 Index 之前第一个"有意义"的 token 下标(跳过注释与 NL;-1=无)。""" + j: int = Index - 1 + while j >= 0 and Tokens[j].type in _DESCRIPTOR_SKIP_TYPES: + j -= 1 + return j + + +def _TypeDescriptorSyntaxError(Detail: str, Lineno: int, Col: int, Snippet: str) -> SyntaxError: + """构造类型描述符非法形态的 fail-fast 报文(行:列 + 源码片段 + 期望形态 + 正确示例)。""" + return SyntaxError( + f"[TPC] 类型描述符非法({Detail}):行 {Lineno} 列 {Col},源码片段 {Snippet.strip()!r}。" + f"const/volatile 是只在**类型位置**生效的软关键字,必须写成 {_DESCRIPTOR_HINT};" + f"正确示例:{_DESCRIPTOR_EXAMPLE}。" + f"(描述符不会被静默忽略:缺类型 / 后随非类型 / 裸用一律终止编译)" + ) + + +# C-style leading array declarator `const [N]T`: textual desugar into +# `t.CArray[T, N]` (same shape as TPV includes/ast/parser.vp:_parse_array_prefix_declarator). +_TYPE_BRACKET_OPENERS: frozenset[str] = frozenset({'(', '[', '{'}) +_TYPE_BRACKET_CLOSERS: frozenset[str] = frozenset({')', ']', '}'}) +# Annotation type expression ends at these top-level operators. +_TYPE_EXPR_STOP_TOKENS: frozenset[str] = frozenset({'=', ';', ',', ':'}) + + +def _ScanAnnotationTypeEnd(Tokens: list, Start: int, Count: int) -> int: + """Return the index just past an annotation type expression starting at Start. + + Bracket-aware: separators inside ()/[]/{} do not end the type; at top level the + type ends at '=' / ';' / ',' / ':' or NEWLINE / ENDMARKER. + """ + k: int = Start + Depth: int = 0 + while k < Count: + Tok = Tokens[k] + if Tok.type == tokenize.COMMENT: + # 行尾注释不属于类型文本。若当普通注释跳过继续扫,ElemEnd - 1 会落到注释上, + # 使数组前缀脱糖把注释并进 `t.CArray[...]` 内部(`data: [512]uint8 # 注释` + # 会变成 `t.CArray[uint8 # 注释, 512]` ⇒ 方括号失衡、整段注解作废)。 + break + if Tok.type in _DESCRIPTOR_SKIP_TYPES: + k += 1 + continue + if Tok.type in (tokenize.NEWLINE, tokenize.ENDMARKER): + break + s: str = Tok.string + if s in _TYPE_BRACKET_OPENERS: + Depth += 1 + elif s in _TYPE_BRACKET_CLOSERS: + if Depth == 0: + break + Depth -= 1 + elif Depth == 0 and s in _TYPE_EXPR_STOP_TOKENS: + break + k += 1 + return k + + +def _DesugarArrayPrefix(Tokens: list, BracketIdx: int, Count: int, + Code: str) -> tuple[int, str] | None: + """Desugar a leading array prefix `[N]...T` into the text `t.CArray[T, N]`. + + `Tokens[BracketIdx]` is '['. Returns `(End, TypeText)` where End is the index + just past the element type. Supports `[N][M]T` (outer-to-inner) and `[]T` + (length omitted -> None). Cross-line / missing element -> None (caller keeps + the existing fail-fast). + """ + Lines0: list[str] = Code.splitlines() + Line: int = Tokens[BracketIdx].start[0] + Groups: list[str] = [] + k: int = BracketIdx + while k < Count and Tokens[k].type == tokenize.OP and Tokens[k].string == '[': + k += 1 + First: int = k + Depth: int = 1 + while k < Count and Depth > 0: + Tok = Tokens[k] + if Tok.type in _DESCRIPTOR_SKIP_TYPES: + k += 1 + continue + if Tok.string in _TYPE_BRACKET_OPENERS: + Depth += 1 + elif Tok.string in _TYPE_BRACKET_CLOSERS: + Depth -= 1 + if Depth == 0: + break + k += 1 + if k >= Count or Tokens[k].string != ']': + return None + Last: int = k - 1 + if Tokens[k].start[0] != Line or (Last >= First and Tokens[Last].end[0] != Line): + return None # cross-line array prefix: same-line only + if Last < First: + Groups.append('None') # `[]`: length omitted + else: + Groups.append(Lines0[Line - 1][Tokens[First].start[1]:Tokens[Last].end[1]]) + k += 1 + if not Groups: + return None + ElemStart: int = k + ElemEnd: int = _ScanAnnotationTypeEnd(Tokens, k, Count) + if ElemEnd <= ElemStart: + return None + if (Tokens[ElemStart].start[0] != Line + or Tokens[ElemEnd - 1].end[0] != Line): + return None + ElemText: str = Lines0[Line - 1][Tokens[ElemStart].start[1]:Tokens[ElemEnd - 1].end[1]] + Result: str = ElemText + for GroupText in reversed(Groups): # inner first (right-to-left) + Result = f't.CArray[{Result}, {GroupText}]' + return ElemEnd, Result + + +# Bare C-style leading array declarator `[N]T` / `[]T` at annotation positions (no +# descriptor), desugared to `t.CArray[T, N]` / `t.CArray[T, None]` — same shape as the +# const/volatile path and TPV _parse_array_prefix_declarator. +_ARRAY_PREFIX_HINT: str = ( + '`名字: [长度]元素类型`(长度可省略:`[]元素类型`);' + '形参 / 返回值写作 `def f(p: [N]T) -> [M]U`' +) +_ARRAY_PREFIX_EXAMPLE: str = ( + 'buf: [128]uint8 / HEX_VALS: [256]int / def transform(self, block: [64]uint8)' +) +_ARRAY_STMT_BOUNDARY: tuple[int, ...] = ( + tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT +) + + +def _ArrayPrefixSyntaxError(Detail: str, Lineno: int, Col: int, Snippet: str) -> SyntaxError: + """Build the fail-fast error for an array prefix that cannot be desugared.""" + return SyntaxError( + f"[TPC] 数组前缀注解非法({Detail}):行 {Lineno} 列 {Col},源码片段 {Snippet.strip()!r}。" + f"`[N]T` 只在**注解位置**生效(AnnAssign 的 ':' 之后 / 形参 / '->' 之后 / 类字段)," + f"期望形态:{_ARRAY_PREFIX_HINT};正确示例:{_ARRAY_PREFIX_EXAMPLE}。" + f"(注解位置的数组前缀无法脱糖时一律终止编译,绝不静默产出 List 注解)" + ) + + +def _InArrayAnnotationPosition(Tokens: list, Index: int, Stack: list) -> bool: + """True if the '[' at Index begins an annotation-position type. + + Stack holds open brackets before Index as (char, open_index). AnnAssign / class + field: ':' preceded by a (possibly dotted) target name at a statement boundary. + Parameter: ':' inside a def's parameter paren. Return annotation: '->' before '['. + Dict / slice / lambda colons are excluded so right-hand `[...]` literals stay put. + """ + PrevIdx: int = _PrevSignificantTokenIndex(Tokens, Index) + if PrevIdx < 0: + return False + Prev = Tokens[PrevIdx] + if Prev.string == '->': + return True + if Prev.string != ':': + return False + TargetIdx: int = _PrevSignificantTokenIndex(Tokens, PrevIdx) + if TargetIdx < 0 or Tokens[TargetIdx].type != tokenize.NAME: + return False + if Stack: + Char, OpenIdx = Stack[-1] + if Char != '(': + return False + FnIdx: int = _PrevSignificantTokenIndex(Tokens, OpenIdx) + if FnIdx < 0 or Tokens[FnIdx].type != tokenize.NAME: + return False + KwIdx: int = _PrevSignificantTokenIndex(Tokens, FnIdx) + if KwIdx < 0 or Tokens[KwIdx].string != 'def': + return False + for Tok in Tokens[OpenIdx + 1:Index]: # a default lambda is not an annotation + if Tok.type == tokenize.NAME and Tok.string == 'lambda': + return False + return True + j: int = TargetIdx + while True: # walk back over a dotted target chain + DotIdx: int = _PrevSignificantTokenIndex(Tokens, j) + if DotIdx < 0 or Tokens[DotIdx].string != '.': + break + HeadIdx: int = _PrevSignificantTokenIndex(Tokens, DotIdx) + if HeadIdx < 0 or Tokens[HeadIdx].type != tokenize.NAME: + break + j = HeadIdx + BoundIdx: int = _PrevSignificantTokenIndex(Tokens, j) + if BoundIdx < 0: + return True + Bound = Tokens[BoundIdx] + return Bound.type in _ARRAY_STMT_BOUNDARY or Bound.string == ';' + + +def _ApplySameLineEdits(Code: str, Edits: list[tuple[int, int, int, str, str]]) -> str: + """Apply (lineno, col_start, col_end, text, orig) edits right-to-left, keeping line endings. + + Reverse order so multiple edits on one line keep their column offsets valid; the line + terminator is split off and written back verbatim. A position-check failure (odd line + ending / encoding) leaves the text untouched so the later ast/vast.parse fails loudly + (never silently rewrites the wrong span). + """ + Lines: list[str] = Code.splitlines(keepends=True) + for Lineno, ColStart, ColEnd, Text, Orig in sorted( + Edits, key=lambda e: (e[0], e[1]), reverse=True): + raw: str = Lines[Lineno - 1] + k: int = len(raw) + while k > 0 and (raw[k - 1] == '\n' or raw[k - 1] == '\r'): + k -= 1 + Body: str = raw[:k] + Term: str = raw[k:] + if Body[ColStart:ColEnd] != Orig: + # Position check failed: leave untouched so the later ast/vast.parse fails loudly. + continue + Lines[Lineno - 1] = f'{Body[:ColStart]}{Text}{Body[ColEnd:]}{Term}' + return ''.join(Lines) + + +def _ExpandArrayPrefixAnnotations(Code: str) -> str: + """Desugar bare `[N]T` array-prefix annotations into `t.CArray[T, N]` (same line). + + Only fires when '[' sits at an annotation position (see _InArrayAnnotationPosition); + right-hand list literals are never touched. A '[' at an annotation position that + cannot be desugared (missing element type / cross-line / incomplete) fails loudly + (never silently becomes a List annotation). + """ + if '[' not in Code: + return Code + try: + Tokens: list = list(tokenize.generate_tokens(io.StringIO(Code).readline)) + except Exception: + return Code + n: int = len(Tokens) + Lines0: list[str] = Code.splitlines() + Edits: list[tuple[int, int, int, str, str]] = [] + Stack: list[tuple[str, int]] = [] + i: int = 0 + while i < n: + Tok = Tokens[i] + if Tok.type == tokenize.OP: + if Tok.string in _TYPE_BRACKET_OPENERS: + if Tok.string == '[' and _InArrayAnnotationPosition(Tokens, i, Stack): + Desugared = _DesugarArrayPrefix(Tokens, i, n, Code) + if Desugared is None: + raise _ArrayPrefixSyntaxError( + '无法脱糖(缺元素类型 / 跨行 / 不完整)', + Tok.start[0], Tok.start[1], Lines0[Tok.start[0] - 1]) + End, ArrayText = Desugared + Line0: str = Lines0[Tok.start[0] - 1] + Edits.append((Tok.start[0], Tok.start[1], Tokens[End - 1].end[1], + ArrayText, Line0[Tok.start[1]:Tokens[End - 1].end[1]])) + i = End + continue + Stack.append((Tok.string, i)) + elif Tok.string in _TYPE_BRACKET_CLOSERS: + if Stack: + Stack.pop() + i += 1 + if not Edits: + return Code + return _ApplySameLineEdits(Code, Edits) + + +def _ExpandTypeDescriptors(Code: str) -> str: + """把 `const T` / `volatile T`(含组合、任意顺序)展开为 `t.CConst | T` 形态(行号不变)。 + + - 词法级切分(`tokenize`):注释/字符串内容天然不参与,不会被误改写。 + - 只在"注解位置"(前一个 token 是 `:` 且再前一个是 Name,或前一个 token 是 `->`) + 识别描述符串;非类型位置原样保留为普通标识符(软关键字边界)。 + - 只做**同行**替换(不增删换行 ⇒ 行号映射保持有效,与 fn/enum/define/typedef 同约定)。 + - 词法失败(未闭合字符串等)时原样返回:该输入在后续 ast.parse 处必然响亮失败, + 本层不新增第二套错误通道(不静默丢描述符:词法都没过,描述符根本没被识别为描述符)。 + - `const [N]T` (C-style leading array prefix) desugars to + `t.CConst | t.CArray[T, N]`; see _DesugarArrayPrefix. + """ + if 'const' not in Code and 'volatile' not in Code: + return Code + try: + Tokens: list = list(tokenize.generate_tokens(io.StringIO(Code).readline)) + except Exception: + return Code + n: int = len(Tokens) + # (行号, 起列, 止列, 替换文本, 原文片段) + Edits: list[tuple[int, int, int, str, str]] = [] + i: int = 0 + while i < n: + Tok = Tokens[i] + if Tok.type == tokenize.NAME and Tok.string in _TYPE_DESCRIPTOR_MARKERS: + Run: list = [] + j: int = i + while (j < n and Tokens[j].type == tokenize.NAME + and Tokens[j].string in _TYPE_DESCRIPTOR_MARKERS): + Run.append(Tokens[j]) + j += 1 + PrevIdx: int = _PrevSignificantTokenIndex(Tokens, i) + Prev = Tokens[PrevIdx] if PrevIdx >= 0 else None + Next = Tokens[j] if j < n else None + InAnnotationPosition: bool = False + if Prev is not None: + if Prev.string == '->': + InAnnotationPosition = True + elif Prev.string == ':': + BeforePrevIdx: int = _PrevSignificantTokenIndex(Tokens, PrevIdx) + if BeforePrevIdx >= 0 and Tokens[BeforePrevIdx].type == tokenize.NAME: + InAnnotationPosition = True + if InAnnotationPosition: + if Run[0].start[0] != Run[-1].end[0]: + # 描述符串跨行:本层只支持同行(不增删换行的前提),响亮失败不静默丢 + First = Run[0] + raise _TypeDescriptorSyntaxError( + '描述符串跨行', First.start[0], First.start[1], + Code.splitlines()[First.start[0] - 1]) + if Next is not None and Next.type == tokenize.OP and Next.string == '[': + # `const [N]T` / `volatile [N]T` / `const volatile [N]T`: TPC vast has + # no `[N]T`; desugar the array prefix together with the descriptor into + # `t.CConst | t.CArray[T, N]` (same shape as TPV). + Desugared = _DesugarArrayPrefix(Tokens, j, n, Code) + if Desugared is not None: + End, ArrayText = Desugared + First = Run[0] + Line0: str = Code.splitlines()[First.start[0] - 1] + Text: str = ' | '.join( + f't.{_TYPE_DESCRIPTOR_MARKERS[m.string]}' for m in Run + ) + f' | {ArrayText}' + Edits.append((First.start[0], First.start[1], + Tokens[End - 1].end[1], Text, + Line0[First.start[1]:Tokens[End - 1].end[1]])) + i = End + continue + # Not a valid array prefix: fall through to the fail-fast below. + if Next is not None and Next.type == tokenize.NAME: + Text: str = ' | '.join( + f't.{_TYPE_DESCRIPTOR_MARKERS[m.string]}' for m in Run) + ' |' + Edits.append((Run[0].start[0], Run[0].start[1], Run[-1].end[1], + Text, Code.splitlines()[Run[0].start[0] - 1] + [Run[0].start[1]:Run[-1].end[1]])) + else: + First = Run[0] + NextText: str = '(行尾)' if Next is None else repr(Next.string) + Detail: str = ( + f'描述符后缺少类型,后随 {NextText}' + if (Next is None or Next.type in (tokenize.NEWLINE, tokenize.ENDMARKER) + or Next.string in ('=', ';')) + else f'描述符后随非类型 {NextText}' + ) + raise _TypeDescriptorSyntaxError( + Detail, First.start[0], First.start[1], + Code.splitlines()[First.start[0] - 1]) + else: + # 非注解位置:仅"整条语句恰好是一个描述符串"(裸用)才响亮失败, + # 其余(t.attr.const() / const = 5 / x = const …)按普通标识符原样保留。 + AtStmtStart: bool = Prev is None or Prev.type in ( + tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT) + AtStmtEnd: bool = Next is None or Next.type in ( + tokenize.NEWLINE, tokenize.ENDMARKER) or Next.string == ';' + if AtStmtStart and AtStmtEnd: + First = Run[0] + raise _TypeDescriptorSyntaxError( + '裸用描述符(整条语句只有描述符,缺类型)', + First.start[0], First.start[1], + Code.splitlines()[First.start[0] - 1]) + i = j + continue + i += 1 + if not Edits: + return Code + return _ApplySameLineEdits(Code, Edits) + + +# --------------------------------------------------------------------------- +# 枚举存储宽度("最宽成员类型")——两侧(TPV ClassGenEnum / TPC HandlesClassDef) +# 的唯一同口径规则源,对照表见 Debugger/enum-semantics.md。 +# --------------------------------------------------------------------------- +# 规则: +# 无注解 / `t.State` 标记注解 → "无整数成员"口径(枚举宽度回落默认 32) +# 整数类型注解 → 按该类型位宽参与"取最宽"(见 EnumClassIntWidth) +# `bool` / `t.CBool` → **8**(枚举侧口径):枚举存储必须是可按字节寻址的 +# 整数类型,`i1` 不能作为独立枚举宽度;本口径只作用于"枚举成员注解" +# 这一处,**不改** TPC 全局 `bool`→`i8`(LLVMTypeMapper.py:174)与 +# TPV 全局 `bool`→`i1`(TypeGenResolve.vp:2201-2203)的各自的全局语义。 +# TPC 侧 `bool` 经 BuiltinTypeMap → `t.CBool.Size == 8` 天然成立 +# (t.CBool 同 8),故本文件不另加硬编码分支。 +# 指针注解 → 在成员注册处明确报错(本文件与 ClassGenEnum 的 ptr 拦截) +# 其余(无法解析 / 非整数:float/void/struct…)→ 视为"无整数成员" +# (不引入新的硬失败;TPV 侧更严,对"有注解但解析失败"是 fatal,差异已记录) +_ENUM_DEFAULT_INT_WIDTH: int = 32 + +# State 标记名(与 TPV ClassGenEnum._is_state_annotation 同集合):该注解只是 +# `t.CEnum` 成员的存在性标记,不携带宽度语义。 +_ENUM_STATE_MARKER_NAMES: frozenset[str] = frozenset({'State'}) + + +def EnumIntWidth(Info: object | None, Default: int = _ENUM_DEFAULT_INT_WIDTH) -> int: + """取枚举条目(枚举类或枚举成员)已登记的整数位宽;未登记 → Default。 + + 宽度在枚举定义处按"最宽成员类型"推导(EnumClassIntWidth)并登记到条目上; + 跨模块场景由**两条读侧路径**各自补齐同一属性: + - `.vpi`/`.pyi` 声明(HandlesImports._EmitExternalClassDeclLlvm)→ 从成员 + 注解重新推导(`.vpi` 保留 `Name: T` 注解形态); + - 共享符号表(SymbolExtractor._extract_class → SymbolTable pickle, + temp/_shared_sym.json)→ 同样在提取期推导后随 `_sm._extra` 序列化。 + 两者都不带宽度信息时(旧产物 / 老 pickle 缺 `enum_width` 字段)→ Default(32), + 与"改前恒 i32"逐字一致 ⇒ 向后兼容、不崩。 + """ + if Info is None: + return Default + Getter = getattr(Info, 'get', None) + if Getter is None: + return Default + try: + Width = Getter('enum_width') + except Exception: + return Default + if isinstance(Width, int) and Width > 0: + return Width + return Default + + +def IsEnumStateMarker(Annotation: object | None) -> bool: + """注解节点是否为 `t.State` / `State`(枚举默认成员的存在性标记)。""" + if Annotation is None: + return False + if isinstance(Annotation, ast.Attribute): + return Annotation.attr in _ENUM_STATE_MARKER_NAMES + if isinstance(Annotation, ast.Name): + return Annotation.id in _ENUM_STATE_MARKER_NAMES + return False + + +def _EnumAnnotationSimpleName(Annotation: object | None) -> str | None: + """注解节点的"顶名":`t.CInt64T` → 'CInt64T'、`int64` → 'int64';其余 → None。""" + if isinstance(Annotation, ast.Attribute): + return Annotation.attr + if isinstance(Annotation, ast.Name): + return Annotation.id + return None + + +def _IntSizeOf(BaseType: object | None) -> int | None: + """CType 实例 → 整数位宽(bit);非整数(浮点/void/结构体/数组)→ None。""" + Size = getattr(BaseType, 'Size', None) + IsSigned = getattr(BaseType, 'IsSigned', None) + if not isinstance(Size, int) or Size <= 0 or IsSigned is None: + return None + return int(Size) + + +def _EnumTypeStrToClass(Text: str | None) -> type | None: + """builtins typedef 值(`t.CInt64T` / `CInt64T`)→ t 模块类型类;其余 → None。 + + 供"无 SymbolTable 的声明端"把 int8/int64/… 归一到 t 类型类取位宽。 + """ + if not Text: + return None + Name: str = Text.strip() + if Name.startswith('t.') or Name.startswith('c.'): + Name = Name[2:] + if not Name or not Name.isidentifier(): + return None + Cls = getattr(t, Name, None) + if isinstance(Cls, type) and issubclass(Cls, t.CType) and Cls is not t.CType: + return Cls + return None + + +def EnumMemberAnnotationIntWidth(Annotation: object | None, + SymbolTable: object | None = None) -> int | None: + """枚举成员注解 → 整数位宽(bit);None = "默认口径"(走 _ENUM_DEFAULT_INT_WIDTH)。 + + `t.State` 标记或无注解 → None;整数类型(`int`/`char`/`C*` 家族 / builtins + 与库 typedef)→ 其 `Size`;无法解析或非整数(float/void/struct…)→ None + (默认 32,与改前行为一致,不引入新的硬失败)。指针形态由调用方在解析前 + 拦截(HandlesClassDef._EnumMemberAnnotationIsPtr / TPV 的 + _enum_member_annotation_is_ptr),此处不重复判定。 + + 解析顺序(同一规则源,供"有 SymbolTable 的定义端"与"无 SymbolTable 的 + 声明端(DeclarationGenerator)"共用): + 1) t 模块类型类名(CInt64T/CUInt8T/CChar/CBool/CInt…)与其 C 速记名 + 2) includes/builtins.vp 的 CTypedef(int8/int16/…/uint64/size_t,仅扫描) + 3) 符号表 typedef(需注入 SymbolTable;覆盖库内自定义 typedef) + 4) AST 解析器兜底(联合/下标等复合形态,需注入 SymbolTable) + """ + if Annotation is None or IsEnumStateMarker(Annotation): + return None + Name = _EnumAnnotationSimpleName(Annotation) + if Name is not None: + Cls = getattr(t, Name, None) + if isinstance(Cls, type) and issubclass(Cls, t.CType) and Cls is not t.CType: + try: + Width = _IntSizeOf(Cls()) + except Exception: + Width = None + if Width is not None: + return Width + from lib.core.Handles.HandlesBase import BuiltinTypeMap + BuiltinEntry = BuiltinTypeMap.Get(Name) + if BuiltinEntry is not None and BuiltinEntry[1] == 0: + Width3 = _IntSizeOf(BuiltinEntry[0]()) + if Width3 is not None: + return Width3 + from lib.core.TypeAnnotationResolver import TypeAnnotationResolver + try: + BVal: str | None = TypeAnnotationResolver.builtins_typedef_value(Name) + except Exception: + BVal = None + if BVal: + BCls = _EnumTypeStrToClass(BVal) + if BCls is not None: + Width4 = _IntSizeOf(BCls()) + if Width4 is not None: + return Width4 + try: + BInfo = TypeAnnotationResolver._lookup_builtins_typedef(Name, SymbolTable) + except Exception: + BInfo = None + if BInfo is not None and getattr(BInfo, 'PtrCount', 0) == 0: + Width2 = _IntSizeOf(getattr(BInfo, 'BaseType', None)) + if Width2 is not None: + return Width2 + if SymbolTable is None: + return None + if SymbolTable is None: + return None + # 局部导入:SymbolUtils 被 HandlesBase 导入,模块级引入 CTypeInfo 会成环。 + from lib.core.Handles.HandlesBase import CTypeInfo + try: + Info = CTypeInfo.FromNode(Annotation, SymbolTable) + except Exception: + return None + if Info is None or getattr(Info, 'PtrCount', 0) > 0: + return None + return _IntSizeOf(getattr(Info, 'BaseType', None)) + + +def EnumClassIntWidth(Body: list | None, + SymbolTable: object | None = None, + Default: int = _ENUM_DEFAULT_INT_WIDTH) -> int: + """枚举存储宽度 = **最宽成员类型**(bit);无"可解析为整数的成员" → Default(32)。 + + 与 TPV ClassGenEnum._translate_enum_def 的 base_ty 同口径(`base_ty` 从 i32 + 起步,被任何 `get_llvm_type_bits > 0` 的成员替换 ⇒ 等价于"成员位宽最大值, + 无成员时 i32")。**不是** `max(默认 32, 成员位宽)`:该写法会把 `A: uint8` + 单成员的枚举钉在 i32,与 TPV 的 i8 分裂(本轮已统一为前者)。 + + 调用点(同一规则源,禁止各写一套): + - 定义端:HandlesClassDef._RegisterEnumMembers → _EnumClassIntWidth + - 跨模块读取端:HandlesImports._EmitExternalClassDeclLlvm(.vpi/.pyi 读侧重推导) + - 跨模块符号表构建:SymbolExtractor._extract_class(→ SymbolTable → pickle) + - 声明端(无 SymbolTable):DeclarationGenerator 的 `@__config_*` 全局宽度 + """ + Width: int = 0 + for item in Body or (): + if not isinstance(item, ast.AnnAssign): + continue + if not isinstance(item.target, ast.Name): + continue + MemberWidth: int | None = EnumMemberAnnotationIntWidth( + item.annotation, SymbolTable) + if MemberWidth is not None and MemberWidth > Width: + Width = MemberWidth + return Width if Width > 0 else Default + + +# --------------------------------------------------------------------------- +# 枚举成员"可见名 vs 登记名"兜底判据——唯一规则源 +# (TPC HandlesClassDef._RegisterEnumMembers / TPV +# ClassGenEnum._translate_enum_def 在**注册处**共用同一口径) +# +# 背景:`enum` 成员行一旦出现"注册器不认的写法",成员会被**静默丢弃** +# (ASTKind 71→3、OpKind 32→2;历史事故见 Debugger/enum-semantics.md §15、 +# rawptr-migration-and-fastfail.md §27)。这是同族问题**第三次**发生,故不再 +# 逐个白名单化"已知可展开形态",而在注册处比对 +# **源码里可见的成员名字数** 与 **实际登记成员数**:不一致即 fail-fast。 +# +# 取全口径(为什么能兜住未知形态):枚举成员必定是**有名字的**,且成员名必定 +# 落在成员语句的三类"名字位置"之一——Assign 的 targets、AnnAssign 的 target、 +# Expr 语句的表达式(裸名 / 逗号组 `A, B, C` → Tuple/List)。本函数把这三类位置 +# 里的标识符**全部**收进来(Tuple/List 逐项、Starred 解引用递归),因此任何 +# 注册器漏掉的成员写法(`A, B = 1`、`A, B: int`、单名尾随逗号 `A,`,以及未来 +# 出现的新形态)都会表现为"可见名数 > 登记数" ⇒ 一律 fail-fast,不再静默。 +# 不含成员名的语句(docstring / pass / 方法定义 / 嵌套类)计 0,不误报。 +# --------------------------------------------------------------------------- +def _EnumMemberNameNodes(Node: object) -> list[object]: + """成员名位置上的名字节点清单(Tuple/List 逐项、Starred 解引用)。""" + if Node is None: + return [] + if isinstance(Node, ast.Name): + return [Node] + if isinstance(Node, (ast.Tuple, ast.List)): + Out: list[object] = [] + for Elt in (getattr(Node, 'elts', None) or []): + Out.extend(_EnumMemberNameNodes(Elt)) + return Out + if isinstance(Node, ast.Starred): + return _EnumMemberNameNodes(getattr(Node, 'value', None)) + return [] + + +def EnumBodyMemberNames(Body: list | None) -> list[tuple[str, object]]: + """枚举类体内"源码可见的成员名"清单 `[(名字, 语句节点), …]`(按出现顺序)。 + + 与 TPV `ClassGenEnum._enum_body_visible_names` / `_enum_target_visible_names` + 同口径(同一判据的两侧实现)。调用点(注册处,登记完成后立即比对): + - TPC:HandlesClassDef._RegisterEnumMembers + - TPV:ClassGenEnum._translate_enum_def + """ + Names: list[tuple[str, object]] = [] + for Stmt in (Body or ()): + if isinstance(Stmt, ast.Assign): + for Tgt in (getattr(Stmt, 'targets', None) or []): + for Nd in _EnumMemberNameNodes(Tgt): + Names.append((Nd.id, Stmt)) + elif isinstance(Stmt, ast.AnnAssign): + for Nd in _EnumMemberNameNodes(getattr(Stmt, 'target', None)): + Names.append((Nd.id, Stmt)) + elif isinstance(Stmt, ast.Expr): + for Nd in _EnumMemberNameNodes(getattr(Stmt, 'value', None)): + Names.append((Nd.id, Stmt)) + return Names + + +def EnumMemberCountMismatchError(ClassName: str, Visible: list[tuple[str, object]], + RegisteredNames: list[str], + Loc: str = '', LineOffset: int = 0) -> str: + """"可见成员名数 ≠ 登记成员数"的 fail-fast 报文(含全部要求要素)。 + + 要素:枚举名 + 可数(可见/登记/漏掉)+ 未识别的成员写法(首个漏掉的成员名 + + 其语句形态与行号)+ 期望形态 + 修法 + 站点(文件:行:列 + 源码片段)。 + + `LineOffset` = `_inject_auto_imports` 注入行数(Phase2Translator 计算并挂到 translator): + 成员语句行号须按它还原成磁盘真实行号(0/缺省时不改,逐字不变)。 + """ + Rest: list[str] = list(RegisteredNames) + Missing: list[tuple[str, object]] = [] + for Nm, Nd in Visible: + if Nm in Rest: + Rest.remove(Nm) + else: + Missing.append((Nm, Nd)) + if Missing: + BadName, BadNode = Missing[0] + Shape: str = type(BadNode).__name__ + BadLine: int = int(getattr(BadNode, 'lineno', 0) or 0) + # 注入行偏移还原:AST 行号含 _inject_auto_imports 注入的 N 行 + if LineOffset > 0 and BadLine > LineOffset: + BadLine -= LineOffset + BadCol: int = int(getattr(BadNode, 'col_offset', -1)) + BadText: str = f'成员名 {BadName!r}(语句形态 {Shape},行 {BadLine},列 {BadCol})' + else: + BadText = '(可见名与登记名的差集为空:登记数多于可见数,属 AST 损坏)' + return ( + f"[fast-fail] 枚举 '{ClassName}' 成员登记数不一致:源码里可见的成员名 " + f"{len(Visible)} 个,实际只登记 {len(RegisteredNames)} 个" + f"(差额 {len(Visible) - len(RegisteredNames)} 个会被静默丢弃)。" + f"未识别的成员写法:{BadText}。" + f"期望形态:每行一个 `Name` / `Name: T` / `Name = 值`," + f"或多个简单标识符用逗号分隔(`A, B, C`,可带尾随逗号)。" + f"修法:把该成员改写为逐个成员一行,或改写为纯标识符逗号组(去掉 `= 值` / `: T`)。" + f"(本判据在注册处比对『可见名数 vs 登记数』,任何未识别形态都会命中,不再静默丢成员)" + + (f"。位置 {Loc}" if Loc else '') + ) + + +# ExpandSoftKeywords 是 Code 的纯函数(全程无全局可变状态,见其"五种展开"实现): +# 同一文件内容在一次编译里会被多处重复展开(Phase1 / Phase2 / 依赖扫描), +# 缓存结果避免重复正则替换与 tokenize(cProfile:_ExpandTypeDescriptors 里的 +# tokenize.generate_tokens 全程 ~590 万次 TokenInfo._make)。纯函数缓存不改输出;有界防膨胀。 +_EXPAND_CACHE: dict[str, str] = {} +_EXPAND_CACHE_LIMIT: int = 256 + def ExpandSoftKeywords(Code: str) -> str: - """将 Viper 软关键字 fn/fun/func/function 展开为 def(函数定义行)。 + """将 Viper 软关键字/语法糖展开为等价的 Python 形态(行号不变)。 - 仅在"行首缩进 + 关键字 + 空白 + 标识符"的函数定义形态匹配, - 变量名(fn = 5)、调用(fn(x))、注解(x: fn)与字符串内容不受影响。 - 展开为 def 与关键字等宽或更短,行号不变,AST 行号映射保持有效。 + 1) fn/fun/func/function → def(函数定义行):仅在"行首缩进 + 关键字 + 空白 + + 标识符"的函数定义形态匹配,变量名(fn = 5)、调用(fn(x))、注解(x: fn) + 与字符串内容不受影响。 + 2) `enum Name:` 块 → `class Name(t.CEnum):` 块(裸成员补 `: t.State`): + 与手写继承式枚举产出**同一 AST**,故语义(默认编号 0..N、自定义成员类型、 + 宽度取最宽成员)完全一致。 + 3) `define NAME[: T] = 值` → `NAME: [T |] t.CDefine = 值`:编译期常量声明, + 与手写 `NAME: t.CDefine = 值` 产出**同一 AST/同一注册路径**(见 + _ExpandDefineConsts)。`define NAME(...)`(宏函数)不受影响。 + 4) `typedef T1 = T2` → `T1: t.CTypedef = T2`:类型别名声明,与手写 + `T1: t.CTypedef = T2` 产出**同一 AST/同一注册路径**(见 _ExpandTypedefs)。 + 行首 `typedef` 的非声明形态**响亮失败**(该词不在任何既有源码中作标识符)。 + 5) 类型描述符 `const T` / `volatile T` → `t.CConst | T` / `t.CVolatile | T`: + 与手写 `t.CConst | T` 产出**同一 AST/同一解析路径**(见 _ExpandTypeDescriptors)。 + 只在注解位置生效的软关键字(`t.attr.const()` 等非类型位置原样保留); + 缺类型 / 后随非类型 / 裸用**响亮失败**(SyntaxError)。 + 6) `union NAME:` → `class NAME(t.CUnion):`:与手写 `class NAME(t.CUnion):` 产出 + **同一 AST/同一注册路径**(见 _ExpandUnionBlocks)。成员为普通注解字段。 + 行首 `union` 的非 `union 名字:` 形态**响亮失败**(该词不在任何源码中作标识符)。 + 7) 裸数组前缀 `[N]T` / `[]T` → `t.CArray[T, N]` / `t.CArray[T, None]`(见 + _ExpandArrayPrefixAnnotations):只在注解位置生效(AnnAssign 的 ':' 之后 / + 形参 / '->' 之后 / 类字段),右值列表字面量**绝不改动**;无法脱糖的注解位置 + 数组前缀**响亮失败**(SyntaxError,绝不静默产出 List 注解)。 + 七种展开都同行替换(长度可增,不增删换行),AST 行号映射保持有效。 """ if not Code: return Code - return _SOFT_KEYWORD_RE.sub(lambda m: f'{m.group(1)}def{m.group(3)}', Code) + Cached: str | None = _EXPAND_CACHE.get(Code) + if Cached is not None: + return Cached + Result: str = _ExpandSoftKeywordsImpl(Code) + if len(_EXPAND_CACHE) >= _EXPAND_CACHE_LIMIT: + _EXPAND_CACHE.clear() + _EXPAND_CACHE[Code] = Result + return Result +def _ExpandSoftKeywordsImpl(Code: str) -> str: + """ExpandSoftKeywords 的实现体(无缓存,供缓存未命中时调用)。""" + Code = _SOFT_KEYWORD_RE.sub(lambda m: f'{m.group(1)}def{m.group(3)}', Code) + if 'enum' in Code: + Code = _ExpandEnumBlocks(Code) + if 'union' in Code: + Code = _ExpandUnionBlocks(Code) + if 'define' in Code: + Code = _ExpandDefineConsts(Code) + if 'typedef' in Code: + Code = _ExpandTypedefs(Code) + # 描述符放在最后:先让语句级语法糖(enum/union/define/typedef)落到规范形态, + # 本层再看规范注解(如 `define X: const int = 1` → `X: const int | t.CDefine = 1` + # → `X: t.CConst | int | t.CDefine = 1`,两种顺序结果相同)。 + if 'const' in Code or 'volatile' in Code: + Code = _ExpandTypeDescriptors(Code) + # 裸 `[N]T` 数组前缀在描述符之后:`const [N]T` 已由 _ExpandTypeDescriptors 消化为 + # `t.CConst | t.CArray[T, N]`(此处不再有注解位置的裸 '['),避免两条路径重复改写。 + if '[' in Code: + Code = _ExpandArrayPrefixAnnotations(Code) + return Code + + +# --------------------------------------------------------------------------- +# t.CDefine 编译期整数常量的"可存储范围 + 宽度规则"判定——唯一规则源 +# (`define` 与手写 `X: t.CDefine = 值` 共用同一判据;对照表见 +# Debugger/define-constants.md) +# --------------------------------------------------------------------------- +# 规则: +# ① 本语言**没有 64 位以上的整数类型**(t.py 最大为 CInt64T/CUInt64T,不存在 +# CInt128T/CUInt128T)⇒ 常量的实际存储上限即 64 位,按"有符号/无符号两种 +# 解释"合并为 [-2^63, 2^64-1]。这与既有位模式惯用法一致(0xFFFFFFFF 落 i32 +# 的 -1、0xA5A5A5A5A5A5A5A5 落 i64 负数,见 includes/memhub.vp:123 与 +# LLVMCG/TypeConvert.py:181-186、HandlesExprAttr.py:163-173)。 +# ② 显式类型注解(`define NAME: T = 值` ⇒ `NAME: T | t.CDefine = 值`)另行要求 +# 值能落进 T 的**位宽** w:接受 [-(2^(w-1)), 2^w - 1](同样按"两种解释" +# 惯例,`define X: int = 0xFFFFFFFF` 与 `= -1` 均合法);超出位宽必须报错。 +# ③ 判据基于**精确值**(任意精度整数:字面量原值或 ConstEvaluator 的求值结果), +# 绝不做定宽截断/回绕后再比较。 +# ④ 非整数常量(浮点/字符串/bool)不进入本判据,走既有 register_cdefine_double +# /register_cdefine_string 路径。 +# +# 宽度规则("W_min + 使用处宽度",用户 2026-09-21 确认): +# W_min = 8/16/32/64 中**能容纳该值的最小位宽**(DefineMinWidth)。 +# 常量是立即数,故**每个使用点按上下文决定实际物化宽度**: +# 使用处宽度更宽 ⇒ 就地按该宽度物化(升宽,不损失信息); +# 使用处宽度更窄 ⇒ fail-fast(RequireDefineUseWidth,报文点明 W_min 与所需宽度); +# 取不到使用处宽度时(裸语句 / 变参实参 / 当前架构未下传类型的调用实参)⇒ +# 沿用既有 32/64 二值口径(语言默认整型宽度;见 §8 论证),**不**降到 W_min: +# 降到 W_min 会把 i8/i16 立即数喂进 i32 上下文与 C 默认实参提升, +# 属于"静默降级/ABI 破坏",与本轮口径冲突。 +DEFINE_STORE_MIN: int = -(1 << 63) +DEFINE_STORE_MAX: int = (1 << 64) - 1 +# W_min 候选位宽(升序):取"最小可容纳者" +DEFINE_WIDTH_LADDER: tuple[int, ...] = (8, 16, 32, 64) +# 报文用:可用整数类型清单(与 EnumMemberAnnotationIntWidth 的类型解析口径同源) +_DEFINE_INT_TYPE_HINT: str = ( + 't.CInt8T/t.CUInt8T、t.CInt16T/t.CUInt16T、t.CInt32T/t.CUInt32T、t.CInt64T/t.CUInt64T' + '(或 builtins 的 int8/uint8/int16/uint16/int32/uint32/int64/uint64、char、sizet)' +) + + +def DefineWidthLoHi(Width: int) -> tuple[int, int]: + """位宽 w → 可接受值闭区间(两解释口径):`[-(2^(w-1)), 2^w - 1]`。 + + 负值恰为有符号可表示范围,非负值放宽到无符号位模式范围(与既有 + `0xFFFFFFFF` → i32 `-1`、`0xA5A5A5A5A5A5A5A5` → i64 负数的惯用法一致)。 + """ + return (-(1 << (Width - 1)), (1 << Width) - 1) + + +def DefineMinWidth(Value: object) -> int | None: + """值 → W_min(8/16/32/64 中最小可容纳者);None = 超出 64 位(不可存储)。 + + 判定表(与 Debugger/define-constants.md 的"判定表"逐行对应,**闭区间,边界值 + 归属右侧档**:`2^16 = 65536` 属 32 位档): + [0, 2^8-1] → 8 [2^8, 2^16-1] → 16 + [2^16, 2^32-1] → 32 [2^32, 2^64-1] → 64 + [-2^7, -1] → 8 [-2^15, -2^7-1] → 16 + [-2^31, -2^15-1] → 32 [-2^63, -2^31-1] → 64 + > 2^64-1 / < -2^63 → None(需要扩容/更大的类型) + 符号性:负数按**有符号**解释(必须留出符号位);非负数按**无符号位模式** + 解释(同时兼容有符号解释下的可表示范围,取更宽的 2^w-1 上界)。 + bool / 非 int 不进入本判据(返回 None,由调用方按非整数路径处理)。 + + 本实现按位宽**计算**上下界(`1 << (w-1)` / `(1 << w) - 1`),不含十进制大字面量, + Python 侧因此天然精确。**TPV 侧的孪生实现不得照抄十进制阈值**:TPC 编译 + `[0x80000000, 0xFFFFFFFF]` 的无类型字面量会落到 i32 并在提升时 sext 成负数 + (HandlesExpr.py:263-265 + HandlesExprOps.py:401-413),曾把 `<= 4294967295` + 的 32 位档阈值退化成 `<= -1`(现场 `includes/subprocess.vp:141`, + 详见 Debugger/define-constants.md §14)。 + """ + if isinstance(Value, bool) or not isinstance(Value, int): + return None + for Width in DEFINE_WIDTH_LADDER: + Lo, Hi = DefineWidthLoHi(Width) + if Lo <= Value <= Hi: + return Width + return None + + +def IsCDefineMarker(BaseType: object | None) -> bool: + """BaseType 是否为 t.CDefine 标记本身("未声明具体类型"的自动推导形态)。 + + 兼容 HandlesTypeMerge 把**类型类**直接放进 BaseType 的形态 + (`Info.BaseType = TypeClass`,HandlesTypeMerge.py:228-234)。 + """ + if BaseType is None: + return False + if isinstance(BaseType, type): + return issubclass(BaseType, t.CDefine) + return isinstance(BaseType, t.CDefine) + + +def DefineIntWidthOf(BaseType: object | None) -> int | None: + """声明类型 → 整数位宽(bit);非整数/无法判定 → None。 + + 取宽口径与 EnumMemberAnnotationIntWidth 同源(复用 _IntSizeOf), + 并兼容"类形态"(未实例化的 CType 子类)与"实例形态"两种 BaseType。 + """ + if BaseType is None: + return None + Width: int | None = _IntSizeOf(BaseType) + if Width is not None: + return Width + if (isinstance(BaseType, type) and issubclass(BaseType, t.CType) + and BaseType is not t.CType): + try: + return _IntSizeOf(BaseType()) + except Exception: + return None + return None + + +def RequireDefineIntStorable(Value: object, BaseType: object | None, Name: str, + AnnotationText: str, Lineno: int, Col: int, + FilePath: str, CodeLine: str) -> None: + """t.CDefine 整数常量的可存储性校验(fail-fast;唯一判据,两侧同口径)。 + + 通过则原样返回(零副作用);不可存储则抛 TypeError,报文含五要素: + **文件:行[:列] / 源码片段 / 常量名 / 声明类型或自动推导上限 / 实际值 / + 可存储范围与可用类型清单 / 修法建议(点明"需要更大的类型/扩容")**。 + + 调用点(均为 CDefine 常量的**注册处**,即值与已解析注解同时可得之处): + - 模块级 / 类体内:`Translator/LlvmGenerator._register_cdefine_constant` + - 函数内:`Handles/HandlesAnnAssign._HandleAnnAssignLlvmInner` 的 IsDefine 分支 + **不触碰**非 CDefine 声明路径(调用点全部在 IsDefine 分支内)。 + """ + if isinstance(Value, bool) or not isinstance(Value, int): + return + Lo: int = DEFINE_STORE_MIN + Hi: int = DEFINE_STORE_MAX + Width: int | None = DefineIntWidthOf(BaseType) + WMin: int | None = DefineMinWidth(Value) + WMinText: str = f'{WMin} 位' if WMin is not None else '无(超出 64 位)' + if IsCDefineMarker(BaseType) or Width is None: + Deduced: int = 32 if 0 <= Value <= 0xFFFFFFFF else 64 + TypeDesc: str = f'未声明具体类型(自动推导宽度 {Deduced} 位)' + else: + TypeDesc = f'{AnnotationText or "?"}({Width} 位)' + if Width > 0: + Lo = max(Lo, -(1 << (Width - 1))) + Hi = min(Hi, (1 << Width) - 1) + if Lo <= Value <= Hi: + return + Loc: str = f'{FilePath or "unknown"}:{Lineno}' + if Col >= 0: + Loc += f':{Col}' + raise TypeError( + f'[TransPyC] define 常量数值不可存储: 常量 {Name!r} 的初始值 {Value} 不可存储。' + f'最小可容纳宽度 W_min: {WMinText}。' + f'声明类型: {TypeDesc};该声明可存储范围: {Lo}..{Hi}。' + f'本语言最宽的整数类型为 64 位({DEFINE_STORE_MIN}..{DEFINE_STORE_MAX},' + f'覆盖有符号/无符号两种解释),不存在更宽的整数类型 ⇒ 需要扩容。' + f'禁止静默截断/降级,已终止编译。可用整数类型: {_DEFINE_INT_TYPE_HINT}。' + f'修法: 把声明类型改为更宽的类型(如 t.CInt64T / uint64);若仍放不下,' + f'说明该数字超出本语言可表示的整数范围,需要更大的整数类型(扩容);' + f'或修正该字面量。位置 {Loc}' + + (f',源码: {CodeLine}' if CodeLine else '') + ) + + +def RequireDefineUseWidth(Value: object, ContextWidth: int | None, Name: str, + Lineno: int, Col: int, FilePath: str, CodeLine: str, + ContextDesc: str = '') -> int | None: + """**使用处宽度**校验(fail-fast;唯一判据,两侧同口径)。 + + 规则(`define` 常量被物化成立即数的使用点): + 上下文宽度已知且 `>= W_min` ⇒ 返回该宽度(调用方按此宽度就地升宽物化); + 上下文宽度已知且 `< W_min` ⇒ 抛 TypeError(含 W_min、使用处要求的宽度、 + 实际值、文件:行:列、源码片段、修法); + 上下文宽度无法判定(None) ⇒ 返回 None(调用方沿用既有 32/64 二值口径)。 + 非整数常量(float/str/bool)或值超出 64 位(W_min=None)不在此判据内:后者由 + RequireDefineIntStorable 在**注册处**拦住(更早、更准确的站点)。 + """ + if isinstance(Value, bool) or not isinstance(Value, int): + return None + WMin: int | None = DefineMinWidth(Value) + if WMin is None or ContextWidth is None: + return None + if ContextWidth >= WMin: + return ContextWidth + Loc: str = f'{FilePath or "unknown"}:{Lineno}' + if Col >= 0: + Loc += f':{Col}' + raise TypeError( + f'[TransPyC] define 常量使用处宽度不足: 常量 {Name!r} 的实际值 {Value} ' + f'需要至少 {WMin} 位(最小可容纳宽度 W_min),' + f'但使用处{("(" + ContextDesc + ")") if ContextDesc else ""}只提供 {ContextWidth} 位' + f'(可表示范围: {DefineWidthLoHi(ContextWidth)[0]}..{DefineWidthLoHi(ContextWidth)[1]})。' + f'禁止静默截断/降级,已终止编译。' + f'修法: 把使用处(变量声明 / 形参 / 运算对象 / 返回注解)的类型加宽到至少 {WMin} 位' + f'(如 t.CInt{WMin}T / CUInt{WMin}T 及更宽类型),或改用更小的常量值。' + f'位置 {Loc}' + + (f',源码: {CodeLine}' if CodeLine else '') + ) + + +def _DefineSiteInfo(Gen: object, Trans: object, Node: object) -> tuple[int, int, str, str]: + """站点四要素:行号(含 `_line_offset` 还原)/ 列号 / 源文件路径 / 源码片段。 + + 口径与 `HandlesExprCall._RequireArgValue`(rawptr-migration-and-fastfail.md §27.3) + 逐条一致,供 RequireDefineConstStorable / RequireDefineUseWidthAt 共用。 + """ + Lineno: int = int(getattr(Node, 'lineno', 0) or 0) + Offset: int = int(getattr(Trans, '_line_offset', 0) or 0) + if Offset > 0 and Lineno > Offset: + Lineno -= Offset + Col: int = int(getattr(Node, 'col_offset', -1)) + FilePath: str = getattr(Gen, '_current_source_file', '') or '' + CodeLine: str = '' + if Lineno > 0 and hasattr(Gen, '_get_source_line'): + try: + CodeLine = (Gen._get_source_line(Lineno) or '').strip() + except Exception: + CodeLine = '' + return Lineno, Col, FilePath, CodeLine + + +# 整数字面量的可存储范围(两种解释的并集):[-2^63, 2^64-1]。 +# 与 TPV 侧(includes/ast/lexer.vp 的 _lex_int_exceeds_u64 / _lex_int_neg_exceeds_i64、 +# includes/ast/parser.vp 的一元减联合判定)**同一区间、同一口径**。 +INT_LITERAL_MIN: int = -(1 << 63) +INT_LITERAL_MAX: int = (1 << 64) - 1 + + +def RequireIntLiteralStorable(Gen: object, Trans: object, Node: object, Value: int) -> None: + """整数字面量(含"一元减作用于整数字面量"后的**结果值**)的存储范围 fail-fast。 + + `-` 是一元运算符、不属于字面量:字面量本身无符号(0..2^64-1),负数侧由一元减 + 表达,取负后必须 >= -2^63。任何越界值(如 18446744073709551616、-18446744073709551615、 + -9223372036854775809)在此 **fail-fast**(不静默回绕/截断)。 + + 只在 `Value` 为 int(非 bool)时生效;报文含 `文件:行[:列] + 源码片段 + 字面量原文 + (含负号)+ 允许范围 [-2^63, 2^64-1] + 修法`(站点口径与 _DefineSiteInfo 一致)。 + """ + if isinstance(Value, bool) or not isinstance(Value, int): + return + if INT_LITERAL_MIN <= Value <= INT_LITERAL_MAX: + return + Lineno, Col, FilePath, CodeLine = _DefineSiteInfo(Gen, Trans, Node) + Loc: str = f'{FilePath}:{Lineno}:{Col}' if FilePath else f'line {Lineno}:{Col}' + raise TypeError( + f'[TransPyC] 整数字面量越界: 字面量 {str(Value)!r} 超出 64 位可表示范围' + f'(允许范围 -9223372036854775808..18446744073709551615 = [-2^63, 2^64-1];' + f'负数结果必须 >= -2^63)。本语言不存在更宽的整数类型;' + f'禁止静默回绕/截断,已终止编译。' + f'修法: 把该字面量改到上述范围内(如需更大数值请拆分为多个 64 位字表达)。' + f'位置 {Loc}' + + (f',源码: {CodeLine}' if CodeLine else '') + ) + + +def RequireDefineUseWidthAt(Gen: object, Trans: object, Node: object, + Value: object, ContextWidth: int | None, Name: str, + ContextDesc: str = '') -> int | None: + """使用处宽度校验的站点包装(取"文件:行[:列] + 源码片段"后转调 RequireDefineUseWidth)。""" + Lineno, Col, FilePath, CodeLine = _DefineSiteInfo(Gen, Trans, Node) + return RequireDefineUseWidth(Value, ContextWidth, Name, Lineno, Col, FilePath, + CodeLine, ContextDesc) + + +def RequireDefineConstStorable(Gen: object, Trans: object, Node: object, + BaseType: object | None, Annotation: object | None, + Value: object, Name: str) -> None: + """编译期常量注册处的站点包装:取"文件:行[:列] + 源码片段"后调 RequireDefineIntStorable。 + + 站点信息口径与 `HandlesExprCall._RequireArgValue`(rawptr-migration-and-fastfail.md §27) + 逐条一致:`Gen._current_source_file` / `Gen._get_source_line(行号)` / + `Trans._line_offset`(`_inject_auto_imports` 注入行导致的行号偏移还原)。 + """ + AnnText: str = '' + if Annotation is not None: + try: + AnnText = ast.unparse(Annotation) + except Exception: + AnnText = '' + Lineno, Col, FilePath, CodeLine = _DefineSiteInfo(Gen, Trans, Node) + RequireDefineIntStorable(Value, BaseType, Name, AnnText, Lineno, Col, FilePath, CodeLine) + + +def RequireNameDefined(Gen: object, Trans: object, Node: object, Name: str) -> None: + """未定义名称的 fail-fast(NameError)。 + + 调用点:`HandlesExpr._HandleNameLlvm` 的所有查找路径(局部/全局变量、函数、 + 枚举成员、CDefine、from-import 别名、t/c 别名)都已失败之后。原实现返回 + `ir.Constant(IntType(32), 0)`,会把"常量名写错/define 丢失/变量漏声明"这类 + 上游缺陷静默变成运行期的 0(实测:`8 * RENUM_CLASS_MAX` 因常量名少一个 `_ENUM_` + 被折成 `mul i32 8, 0` → `alloc(0)` → REnum 变体表注册静默跳过 → match 分派丢失 + 全部 case)。 + """ + Lineno, Col, FilePath, CodeLine = _DefineSiteInfo(Gen, Trans, Node) + Loc: str = f'{FilePath}:{Lineno}:{Col}' if FilePath else f'line {Lineno}:{Col}' + raise NameError( + f'[TransPyC] 未定义名称: {Name!r} —— 该处既不是局部/全局变量、函数、枚举成员,' + f'也不是 CDefine 常量 / from-import 别名 / t.c 导入名。' + f'禁止静默物化为 i32 0,已终止编译。' + f'修法: 核对该名拼写;补上声明(模块级 `name: 类型 = 值`、`define NAME = 值`、' + f'函数形参或局部注解);跨模块使用需显式 import / from … import。' + f'位置 {Loc}' + + (f',源码: {CodeLine}' if CodeLine else '') + ) + + def IsTModule(module_id: str, symtable: object | None = None) -> bool: """检查模块标识符是否指向 t 类型模块(支持别名如 import t as tt)""" if module_id in _T_MODULE_NAMES: @@ -37,6 +1378,93 @@ def IsTModule(module_id: str, symtable: object | None = None) -> bool: return False +# --------------------------------------------------------------------------- +# virtual / novirtual 修饰符(软关键字)的判据 —— TPC 侧**唯一规则源** +# --------------------------------------------------------------------------- +# 源码形态 等价既有写法 语义 +# virtual class C: @t.CVTable class C: 类级:强制有 vtable +# novirtual class C: @t.NoVTable class C: 类级:强制无 vtable +# virtual def m(...) @t.CVTable def m(...) 方法级:占 vtable 槽 +# novirtual def m(...) @t.NoVTable def m(...) 方法级:不占 vtable 槽 +# +# 解析层(lib/vast/Parser.py:_ParseVirtualModified)只合成同名装饰器,**不新造第二套 +# 表示**;语义全部落在既有点位,本节函数是那些点位共用的同一判据(避免多处各写一份 +# 而漂移),逐条对齐 TPV(Viper 自举编译器)的既有实现: +# - ClassHasFuncLevelCVTable ←→ TPV ClassGenBase._has_any_func_cvtable +# - MethodVirtualMarker ←→ TPV ClassGenBase._should_method_be_virtual 第 1/2 条 +# - IsVtableSlotMethod ←→ TPV ClassGenBase._should_method_be_virtual 全 5 条 +# +# 回归性:判据只在"方法带 CVTable/NoVTable 标记"时偏离原行为;全仓既有 .vp 无任何 +# 方法级标记(普查结论见报告),故对既有类/既有代码零影响。 +def _DecoratorListHasMarker(DecoratorList: object, MarkerName: str) -> bool: + """装饰器表中是否存在指定标记名(接受 @t.X / @X / @t.X() / @X() 四种既有写法)。""" + if not DecoratorList: + return False + for Dec in DecoratorList: + Target: object = Dec + if isinstance(Dec, ast.Call): + Target = Dec.func + if isinstance(Target, ast.Attribute): + if Target.attr == MarkerName: + return True + elif isinstance(Target, ast.Name): + if Target.id == MarkerName: + return True + return False + + +def MethodVirtualMarker(MethodNode: ast.AST) -> str | None: + """方法级 virtual/novirtual 标记 → 'CVTable' / 'NoVTable' / None。 + + NoVTable 优先(与 TPV _should_method_be_virtual 第 1 条同序):同一方法同时带 + 两种标记时按"排除"处理。 + """ + DecList: object = getattr(MethodNode, 'decorator_list', None) + if _DecoratorListHasMarker(DecList, 'NoVTable'): + return 'NoVTable' + if _DecoratorListHasMarker(DecList, 'CVTable'): + return 'CVTable' + return None + + +def ClassHasFuncLevelCVTable(ClassNode: ast.AST) -> bool: + """类体内是否有方法带方法级 @t.CVTable(对齐 TPV _has_any_func_cvtable)。 + + 跳过构造函数(__new__/__init__/__before_init__)——它们不进虚表。 + 用于"类的 vtable 是否由方法级标记触发"这一支(类级 NoVTable / 父类 NoVTable / + 无真实父类时,TPV _detect_vtable_status 返回 _has_any_func_cvtable)。 + """ + for Item in getattr(ClassNode, 'body', None) or []: + if not isinstance(Item, ast.FunctionDef): + continue + if Item.name in ('__new__', '__init__', '__before_init__'): + continue + if _DecoratorListHasMarker(getattr(Item, 'decorator_list', None), 'CVTable'): + return True + return False + + +def IsVtableSlotMethod(MethodNode: ast.AST, VtableByClassLevel: bool) -> bool: + """方法是否占 vtable 槽——与 TPV ClassGenBase._should_method_be_virtual 同判据: + + 1) 方法级 NoVTable ⇒ 不占(最高优先级) + 2) 方法级 CVTable ⇒ 占 + 3) vtable 来自类级 CVTable / 继承链(VtableByClassLevel,即**不看**方法级 + 标记算出来的 vtable 状态)⇒ 占 + 4) 其余(含"类的 vtable 仅由方法级标记触发":如普通类里只写了一个 + `virtual def`)⇒ 不占 + + VtableByClassLevel 必须由调用方按"忽略方法级标记"的口径计算(TPC: + HandlesClassDef._resolve_class_flags 返回的 VtableByClassLevel)。 + """ + Marker: str | None = MethodVirtualMarker(MethodNode) + if Marker == 'NoVTable': + return False + if Marker == 'CVTable': + return True + return bool(VtableByClassLevel) + + def IsTModulePath(module_path: str) -> bool: """检查模块路径是否指向 t 类型模块""" if module_path in _T_MODULE_NAMES: @@ -71,8 +1499,23 @@ def AnnotationContainsTType(node: ast.AST, TypeName: str) -> bool: return False +# 内建定长数组构造子的裸名(与 t.CArray 等价的写法): +# CArray —— from t import * 后的裸名形式 +# rawarray —— CArray 的内建复刻名(hashlib 等裸名化重构使用,免写 t. 前缀) +# array —— 本轮新增的内建语法保留字(软关键字,仅在类型/注解位置生效, +# 等价 t.CArray[E, N];非类型位置仍可作普通标识符——见 +# Debugger/array-builtin-spec.md 与 FixedArrayKeywordAnnotationError) +_FIXED_ARRAY_BARE_NAMES: tuple[str, ...] = ('CArray', 'rawarray', 'array') +_ARRAY_KEYWORD: str = 'array' + + +def IsFixedArrayBareName(Name: str) -> bool: + """裸名是否为内建定长数组构造子(CArray / rawarray / array)""" + return Name in _FIXED_ARRAY_BARE_NAMES + + def IsListAnnotation(annotation: ast.expr) -> bool: - """检查注解是否为栈上固定数组类型(t.CArray[...] 或 list[type, count])""" + """检查注解是否为栈上固定数组类型(t.CArray[...] / 内建 array[...] / list[type, count])""" if not isinstance(annotation, ast.Subscript): return False value = annotation.value @@ -82,10 +1525,10 @@ def IsListAnnotation(annotation: ast.expr) -> bool: and value.value.id in ('t', 'c') and value.attr == 'CArray'): return True - # 裸名 CArray[...](from t import *)/ 内建 rawarray[...]:与 t.CArray 等价 - # rawarray 是 CArray 的内建复刻名(hashlib 等裸名化重构使用,免写 t. 前缀) + # 裸名 CArray[...](from t import *)/ 内建 rawarray[...] / 内建 array[...]: + # 三者与 t.CArray 等价(array 为本轮新增的内建软关键字,见上方常量注释) if (isinstance(value, ast.Name) - and value.id in ('CArray', 'rawarray')): + and value.id in _FIXED_ARRAY_BARE_NAMES): return True # list[type, count] — 仅匹配双参数 Tuple 切片(固定数组), # 单参数 list[type] 不匹配(走泛型类 list[T] 路径) @@ -141,6 +1584,97 @@ def ParseListAnnotation(annotation: ast.expr) -> ListAnnotationParseResult | Non return ListAnnotationParseResult(slice_node, None, is_pointer=True) +# --------------------------------------------------------------------------- +# 内建定长数组构造子 array 的注解校验(唯一规则源) +# +# array 是**软关键字**:仅在"类型/注解位置"作为内建定长数组构造子生效, +# 语义与 t.CArray 逐点等价(元素类型在前、长度在后)。非类型位置仍是普通 +# 标识符(如 includes/json 的 def array(...)、numpy 的 def array(...), +# 因此**不能**做成词法保留字)。 +# +# 与内建 rawptr 的机制对应:都"不可重定义"(在类型位置优先于任何用户声明), +# 但 array 因与既有标识符重名,只在类型位置生效(软关键字),而 rawptr 在 +# 各解析入口直接短路(硬内置名)。 +# +# 新写法要求**恰好两个参数** `array[E, N]`:缺参 / 多参 / 长度非正整型常量 +# 表达式均为 fail-fast(不静默降级)。t.CArray[E] 的单参/长度自动推导写法 +# 仍然可用(t.CArray 不受影响)。 +# --------------------------------------------------------------------------- +def _IsConstExprShape(Node: ast.AST) -> bool: + """节点是否为"常量表达式形态"(字面量 / 名字 / 属性 / 常量算术/位运算/一元)。 + + 用于 array 长度参数的**语法级**拒绝:调用/列表/比较/布尔/推导式等明显不是 + 编译期整数常量,直接判为误用;Name/Attribute(可能是 CDefine 常量或 + 跨模块常量)与算子组合保留,交由既有常量求值路径判定(避免前向引用误报)。 + """ + if isinstance(Node, (ast.Constant, ast.Name, ast.Attribute)): + return True + if isinstance(Node, ast.BinOp): + return _IsConstExprShape(Node.left) and _IsConstExprShape(Node.right) + if isinstance(Node, ast.UnaryOp): + return _IsConstExprShape(Node.operand) + return False + + +def FixedArrayKeywordAnnotationError(Annotation: ast.AST, + SymbolTable: object = None) -> str | None: + """校验内建定长数组构造子 array 的注解写法。 + + 返回 None 表示"合法"或"不是 array 注解";否则返回中文错误消息(不含 + 位置与源码片段——那两项由调用方按 @p Annotation 的 lineno/col_offset + 与源码行补齐)。SymbolTable 非空时对常量表达式长度做一次求值校验; + 求值不到(前向引用/声明期依赖未就绪)则**延迟**给下游常量求值路径, + 避免误报。 + """ + if Annotation is None: + return None + if isinstance(Annotation, ast.Name): + if Annotation.id == _ARRAY_KEYWORD: + return ("裸用内建定长数组构造子 'array'(缺少下标与参数)" + "——'array' 只在类型/注解位置生效,必须写成 array[E, N]" + "(元素类型在前、长度在后)。" + "期望形态:array[E, N];正确示例:state: array[uint32, 4]") + return None + if not isinstance(Annotation, ast.Subscript): + return None + base: ast.AST = Annotation.value + if not (isinstance(base, ast.Name) and base.id == _ARRAY_KEYWORD): + return None + SliceNode: ast.AST = Annotation.slice + if not isinstance(SliceNode, ast.Tuple): + return ("内建定长数组构造子 'array' 缺少长度参数(当前仅 1 个参数)" + "——必须同时给出元素类型与长度。" + "期望形态:array[E, N];正确示例:state: array[uint32, 4]") + if len(SliceNode.elts) != 2: + return (f"内建定长数组构造子 'array' 参数个数错误(当前 {len(SliceNode.elts)} 个,期望 2 个)" + "——只接受 array[E, N](E=元素类型,N=长度)。" + "期望形态:array[E, N];正确示例:state: array[uint32, 4]") + CountNode: ast.AST = SliceNode.elts[1] + if isinstance(CountNode, ast.Constant): + Value = CountNode.value + if not (isinstance(Value, int) and not isinstance(Value, bool) and Value > 0): + return ("内建定长数组构造子 'array' 的长度必须是正整型常量(>0)" + "——None/0/负数/非整数都不允许(长度自动推导请改用 t.CArray[E])。" + "期望形态:array[E, N];正确示例:state: array[uint32, 4]") + return None + if not _IsConstExprShape(CountNode): + return ("内建定长数组构造子 'array' 的长度必须是整型常量表达式" + "(字面量 / CDefine 常量名 / 常量算术或位运算表达式)" + "——函数调用/列表/比较等运行期表达式不允许。" + "期望形态:array[E, N];正确示例:state: array[uint8, MAX_LEN]") + if SymbolTable is not None: + try: + from lib.core.ConstEvaluator import ConstEvaluator + Value = ConstEvaluator.eval_with_symtab(CountNode, SymbolTable) + except Exception: + Value = None + if Value is not None and not (isinstance(Value, int) + and not isinstance(Value, bool) and Value > 0): + return ("内建定长数组构造子 'array' 的长度必须求值为正整型常量(>0)。" + "期望形态:array[E, N];正确示例:state: array[uint32, 4]") + return None + + def CheckAnnotationHasCInline(annotation_node: ast.AST | None) -> bool: """检测注解节点中是否包含 CInline""" if annotation_node is None: diff --git a/lib/core/Translator/AnnotationLoader.py b/lib/core/Translator/AnnotationLoader.py index e1ffdfa..54d8c4f 100644 --- a/lib/core/Translator/AnnotationLoader.py +++ b/lib/core/Translator/AnnotationLoader.py @@ -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): diff --git a/lib/core/Translator/ConstEval.py b/lib/core/Translator/ConstEval.py index ae75f85..9c452ea 100644 --- a/lib/core/Translator/ConstEval.py +++ b/lib/core/Translator/ConstEval.py @@ -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: diff --git a/lib/core/Translator/LlvmGenerator.py b/lib/core/Translator/LlvmGenerator.py index 2595b36..f96dbad 100644 --- a/lib/core/Translator/LlvmGenerator.py +++ b/lib/core/Translator/LlvmGenerator.py @@ -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, '', 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 等是实例级属性, diff --git a/lib/core/Translator/PythonParser.py b/lib/core/Translator/PythonParser.py index 87be9f8..97616d2 100644 --- a/lib/core/Translator/PythonParser.py +++ b/lib/core/Translator/PythonParser.py @@ -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 diff --git a/lib/core/TypeAnnotationResolver.py b/lib/core/TypeAnnotationResolver.py index a239610..a6a8724 100644 --- a/lib/core/TypeAnnotationResolver.py +++ b/lib/core/TypeAnnotationResolver.py @@ -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, '', 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) diff --git a/lib/vast/ExprParser.py b/lib/vast/ExprParser.py index 00c8544..07c3935 100644 --- a/lib/vast/ExprParser.py +++ b/lib/vast/ExprParser.py @@ -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()), diff --git a/lib/vast/Parser.py b/lib/vast/Parser.py index 6f5f385..6336141 100644 --- a/lib/vast/Parser.py +++ b/lib/vast/Parser.py @@ -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 diff --git a/lib/vast/Tokenizer.py b/lib/vast/Tokenizer.py index ace5e9d..4f990be 100644 --- a/lib/vast/Tokenizer.py +++ b/lib/vast/Tokenizer.py @@ -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 diff --git a/lib/vast/__init__.py b/lib/vast/__init__.py index 95583a3..d3572c8 100644 --- a/lib/vast/__init__.py +++ b/lib/vast/__init__.py @@ -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 = '', 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] = [ diff --git a/lib/vast_c/_parser.pyd b/lib/vast_c/_parser.pyd index 9dd06f399642af2aef1160ec43fb0a4cdb3da2d7..306d403f28aedb813eb2fba900f220383e593cc1 100644 GIT binary patch delta 81788 zcma%k34Bb~7yrzY&62)YMuJ3wkRV8`nIy7lhWD0xsGzFNLFT6pi)^3tS6 zqiRr&=U<6dGtJ+dz-PRm*}A+@1OKQaT21WoMzsUvT7u?HVR@taYx1@TLDjA=MfcQY z0b8qW*?cup75=4aG=;5S9Wy#-v_=zF9|b#69*)m9*@@x^We@SUfHm}s(QaTp@OSr~DSqd)LDfIiYD~^nN0~XG zdBfWP(ivcTEQpQ~0LSDCo^=hy(tzk#`G+N}AfQ9gVpKu>RQA7s=Hs$?eMh0_xC%Im z189nJLLO?kf^Ywz)yQL=T8*PH_Z5>^REau>^nm&DWwQ znwP_!GvDO&-?fJg4;-ZXXb)hk0@!jJq4#9!{X-`^5f~x3o$QyuhC-)3EHEfScvE7{ zf(BOIwVUAV{Kal&4~i8+B(^;$RCtunii2W=)A{UvP*Y)ZJ`1fHEsU1-bgmlgr+szL zi@~2((SFIaHS>h4uQOZCj)LuVcA#cNaGl5K%u$%r1^}O6Kh^AAe>CMdffr<(q$4>N zvxLtFZe2c0uhlg6<40Oe*>_^39n%0Fr7I@e(Zr!6Ozx(I)X*?GlT z8s)Uf;TG+yX*RabuOUm5W8o8?fvExmgr0PhGk6%UUF6oaWBqE!2UUAWrTz`r8?~c) zHbzy8(-<9V@jy3Xd35#gy)_wIS zdr&*9VRuv}oMYIwyaZz}<}FLzx+yHCPG@mEswhp-gGD;**1g1L)M+U^VQfR4h}3I; zyWIxqJ`u*HJI}yN|KmBbn)5%(IlyxsqryofslawLcIb$a!Beb= zb-%Msbzj$x+tV!cjPOFM`&x}?C%H5w`)4x$`t4h_yhorOCj>5CLt^Fh&qFOpQ9XPl zJmlb)jj>2i+`1T+UH_f%?_7kBO=xGLc7-`ZEz)fh&0_sM7TI8gJ_ALH4pYl+?Ijq{ zXhAcTw?+;(3^NQjj4+G@%L%9qG!u7*vV9F2_4@NJS`R>L&`q)6)3ARZlp0X-7MVme`&i7en3#@c z`t~|B%vu?p$CH-mSyXS7(;iKtp)V3$?byxmR$6}+5|ON}$}ADFO;i0e8gXAy-qVqy z-5#jX*dANOWpnXGJXmCt<86<`4}>d*lG~wtn|e&1*}iQdV=jXo7k|aHia#37}liB z!MV+5j5cY@wt)2rP!0GNJ*MkRt#y?8?C*wQf~>H*k*$IyP}(3}HtT;7m+oPGBIBTN z=SRls4gsMS@Jw@XSYNlv_VfjDQD62&P38pMMoCYL2T-XIlP2wr(K`zEUO z*q5JyFf^JOqcv2jRKdg}H7V6{8qnnU%xG5j+cdJc&o441IQIr`fQc*7)Y@B*cP}sh z6C&WuiB9xYa{tZdG-_aNj2`lyR1@v>h+RRKRF1kM%1N-k$<-Z$AOnZLo1H!o6K zE%!}yEtI)9hP)UNmziixnt?JC-#|LsV~}nlzH`#ige=l;7U`s^d8yp$7q|NhQep;H zwQ-pB7-)m(5mhvp=@)q@IS_?EZe55B){zc#o_Zf;1kEfjFG+864pIu9^7baE6(Fog zT>1mmv=wU0S`hv7o>mp@Gcla)cJ;icT5&N%866eznV}kcr*Vk(TeiJ%%PLrB*|o-< zwf9*>lP214n4wAi$XyS>DD(Ibso>^_Ge1Kx&zYRyWA`~_Bb(DCre7e6(_J+pm7bUi z$r&I4T{J1dqMgJ&1qhx@2*z@es#4^HT#+J>lEqMf9QVd(i!z7(-Xtb!4)2XfYQg{} zS08sV3V|5#^jXohoFzp!7?6ufmMly3C;}<7n{$Blw?#T(B)O`Ep)ukZgZ|7sgX64d zKZFdUt9>nV)G6y+}@dnOqpVN%Tf%k2i^SK&*TG94+CtG__j1$Yco z8iUCIaj2_U@KubA!a8-*a1aF5C@rZvNrkv{B&0XZaZ+45h^=qhOq;=uH*GCw-0We~ zNNe>+0Pqz47^EwT4M3p>lT@mVBOG$2u&+qjMt#|ez}^^Ic>~k=A{Ap84w9j#q+o0> zs!2gkC1IeExeO`usaVw|Kq#o{E6MuD-JN)uubi>8WJ+gb>#t}H4W0@>u%UHi8w#L=5y$5u^@w+NhpjOpHQ}n%BhkbgV9kRSVT95mJ#|tZh(2M{9WA>~ z0tTDGt0GmCAqtcEX)eHX8iGvDO!NjkT#-BRRynm%`VEyLE1I&bg}GtKTW{sl2YgXj zVwK&X99agsPds&>WYeHI7mziJSG^+ttY(EGYoaIX9M2LR9}{7u2Po-@5_t?~PH)n! zi~gPlqlHzpv%PWQ;`#4r(cVF=l6CH@>{{HE{%*?i7*8rxSW3URN^9bDXmIrXU=F&0 zi42QP?wf@_lWt*6?1?|VW%K9RyvoO@DYwpI8(P)3CQ+-(xLJ%96O)^WKATY~aFe5E zWg^SZlQmk+N<-Fl*vM3?x0=-tS?4@i$!b58P&NQLHR*m{=kd# zk0CYX#8lC@4prpkC{U9*NV;`>ud?~An~7TBLbMCiX|gV^lw@K(SK*jCXvrx+w+z+L4%hCAL<0f$12!DLI zEreP}6+3FmL8wvyYOzW2Np&+JcNWP4+Z^(3vXGF=-Llx1@lpNSnOy!Cz*2_dZk*%I zQnz@6(-4*}^&$Z^NZk^lKLLH>5#P$s+5U=r>}4`u(W`~@ILLLeY&XP$wN9uv=%*Wm zA#SqTUm=7zQ-hp&Tfk3_!r5si+o41fubgDqsJ&>v1Lb9}NA;wqZe7%6uO%zm+aojK zEVox5Tw?De#6=uHDRc_0Dp3h1-8xFs`bE5ZiT#|=Iy8(rqU3twU&qy)Vd>7A31^vh zZzPM%uFF!}Ce^8RiCXzL*h3$DMRlBJOWTGw6R3a|-*ZrFAcsN>ONOY#TO8vZf5uAM zhFRaG8a5ePGfg8p3MeHhZr%S-QF$FHuNv^6{tO4%t=oojjyGDOqm<*+F4_OyMPi<4 zS{9T2>+=LQ-l#Y~1beI-$oQO;u|=Avk}=B7##ASyqB5rsA07yg+(k12o?k#A?LML$g|kMQoN1tqn16wkyKhp(NLdKe zd}ca8!5b($_!8hhhPrpwh*oD-!Y5>5-?y)+eUsg5-_Ux9MgV%jaZ&EG-XXHQLx6A4 zF(I!HWdV6-l^%RVsQn+$156btiD5-j;!q;r`K|)PFL@PJT)};2fZ@*%B}KH4DDKh? zMGguABWI%Iz);3IGz|SC+;m{c- z>Kh8{&#*T7F4~c7iauW3nyuGIXhYan`j*125_Vf}3O@BMjjF2tUaV`9v0g$6@r=so zO}R|MI0QlZvbU3>>jVL3M(2!hqx94wDQVJu`Sv|_J}FVFVZq4*M)bk*);-na>fCZV z`gH~rclRu$bK|A{iD0y} z41nS|$_^!m34fkuSCS2pUl3?n6T>a8;14+Fo){!q4!`Gi6EWhLJ|(VY#%c7_{FM8W z$upnJ#2?PySceCN@B*`RloM}0D@dtZJ%)Eglo`f$rGyFXv}{-JI@%xE^^_sjVN+oj zc&F1n-q{mVotr9kPfP&oK8A{nr@gRY7%55^2Nj3Wb^RXz;V|T=Y5*qgP|9;b1PuEj z=)@jvF#>TwtUT^_l80ii#Iyt=tmRS}fg~45ExJ%dq&SXT{$EV-fk;3B$Q}T41_7xI z;ne^bR}979pyeTzp*)h`k$Uw>eylo1g4?ZIM|JD7m@YlEr`eP)5!wW{q)Rj_?2;|) z8^B__Hm~m-K#BoNfmwez_uX{qghjdu(#rh@u!&vG!p>7{U)M$)1cUV!9t?puG7wK0S>T{)5Lx04o^ ze?GoJ)T0S?uufy~$&nW&pJ9^>4Vn)3)$IF!t+||PT3etTTol1S{OhT;qdeWkVu1{y zEYKxN?nQcv7DJW9wqFxb{PzUj{sI5+{}msAEAP4r4z<5VS-gF>dI#B-b-(wx1+(?H5J{b7SE64GiEKR43i><13-7Gh~$VzNEf=EUN!kQe3K(#Hkim9Xn z`5%7^l#@u`zt6L`@Cq<>IuA>C;e%ct3NW~L}!CM&I9z75Wp;IS8r;fNo+;qfD z+CYsnJE=y_io`Jwu3&30zcZn2Sm@i8N-q z#=^Td6lxt~Dcze1w~w+h-CGC;kFtX9Evze!!X;LyB+(ShtzQq{C#o7S+aykbt#A%> z+m;P$sIGqwT5%OA)+Fh3-6ryJ|KA+o73=DOBUC}>hPCUyKv^!`Q+$W&q$IYJ?wl-C zd)tb7W+-Ww?gB4@ zZ!*l^o9e!GuBS6yEXFMRN+m{T6YsfDrCDI^XWXPDsLHmgs)S6;sgz#gNyG9O$vj!=7H6wzXsfQ6IUD!_ zbcq^qfphvpO+W-aOWBqzV%RYxiI6^d*)Qx+T0P;`DK`Dr+U!nRm=JiF1@`C{bHvO$ zUP=AX_Pq#Wd)MTzNLk%^|_Yf-S&*XF?ixd;`=Rn~rHs7rc`^h{8G1p*Q z0uDNW4|z`yf`SVHjNzu<`eh&R%-;-RNNPy7cn`aX&kr*Ho~^8<7^3VKuWYOO*{7ai zCUOr(Zn4`f9V3-=9zzK-> zA?fXK=5|OdkvFXJ*T@4pjgkx&=gxhv-yE!-sxjoHM7IzfuR@GLZly6kIlE$MK>S16#5oc5I_yI|9E<((C4h9c+JKZ1o<$h%A5cNoF_X&=bh_?m z)*d(LZ!ZY29n(q^!@C-6U9_U(CVImj6sKNh&t|s|a?CI=%^m~uqSx|liUP0qL@HvV z&xcyygYc4fDeFZ5(mzCh!idFPI$;od7h;_w7nSKSF^UR+Nn2t|^v~5@(w|g02A&aWYa+We6(}(~g>q8_A~nUz$J2r< z@;B2=xs3ui)*=D79@gvwSd^Dg5$#geP^u3tuLXuYeQVL-#CL=Kgt&A*gwSQs+71hr ztP>fSq8U@PXipjRg<{5G%+Hpj&7Ej+_5$7d@ofj^3M)o-+{7Zx2{3y~1#N7?;Y1X6 zPhNmcQh;dRh+z`2f*c+1wcNPjrkehK!0UBCleP$B<*b6R?;hsc3h<3UHwa39+6buR ze#d09a+@r7B|m06fbCS7gvH8^QHY;9yowGC&Ou?mEzgX24i>g?fXsqj1z~Oy~`pWnC>VXz)VgH;I2dXMxz_#c49HEnZ`TLNMoh^|R zlQnST8hm>z7Q>sQ9PES^UL(NkI?j@%d>eLSUD1Aks6ZM^{D)aAEv=i2s1DSaav3c1 zqRY=p1cwCqYm~QXS3x{_7M&}9(z5CDU-puZ0W1=1f$rR|XOUi1nE=8d=D$k2D{iTh z6J`vhs2eABGYBcwc(F)l+(z9@${ay`gGjL)&u-nz$mCY0LR(a-sLMbWmpR1>upo69 z9eek;lz?Z9 zHGBT#oEh8_4du5E0TRMb2E@41w&2n}CiZSrR@``D;g>j_u( zvE1I#LRKML*L#GpCX@ZuyIw?1s_fEj#My-WwnB3x-(}8ToN%Ro$`Ue~R(<#>%``&% zl#R?-C?4HQ-nyB7pb=bmd)U209Z9y4f=$9H*HmJfK~zY8o?g1^}wR@gVGc1zSH3n~h5?E^^iuAfU4)sI7s)XQtGIT#T$K_DrVyyoHN^zR=XUG1U2J22UA+|S z`js1Clghv}AHcrtuQSx8(mh_5dI^0ir}>!^^n-;`A5+TlFsGiX=xTY?NN!cGLYnVl zO$RiOKiD5N&FDp5xgUR~;*ZbY{X%adTJTwX$b&j@f47lM8Qdav2hEtylAtufQRImj z{g_}^*=RQD5NC&Xcd(KHO@tvk*`EWpi&``i?Qc?1vVYumwtHZr@bV7!>%hj^7nnF` zRK(G~7$)p9g1IoKxDv%)+Rl~?im0&^HMl!bzH**T88UFpZ>X*G7gET7BVPXK)YR|bH7GNpWT8HtoZEH&why@H1 zGIJil43W>AWRr*H2_rvZ_lCA_&=AG67HsrxVz;O!nl5z<>oP379^sLD4W%>Og9+1( zl>XNz*ohb;)X zfNAl@Hz6eQfnsJGo*c5ISH;X}#q9L(hEXGsNkERr0Gvy!L6OiBnM%9BH$G%FMnu+Z zf=U)uZjF%Je*a@BBgRx2w$sgS^w!0A7gh*=KVa3jNRyWD5k{*smU=OgT^rGGsLv-W z68^C)`f7Wfval-DV~pH=;6d{FHTV!DCC7qq3BZaO;GH42wqoUnn=-0#$bRhPusaO_#iiTwyGPlEQT4{N_W|T5UKQh@)MBLg`a6?S zuRz7n(#wxFr1s#~1h=vaRn$GU)BhFJQr$*mH@kwn18g7CQ&o*Q`4nq3I=V|Bbr^}Z z*##W1N1Kpt!qaG&2l$-xz!DTz;?{kELRcZ>ouUowflbUdI=PySCIPw!oMb0PCkeea zF~65%`$kbmWq_1-TMMbOt6UQVQb8gYGPtzdx<}YB5c=ZD-iTFlF*dJv4f#q6XvI4? z^B$Z3a@W8mX^^jVN7#{;1u9LtE^#<(Fu;gLP3$E68z z`gJ&H^~wp{b~eJKOBdBeB|?iYk;D?=2m>D3^jCCjz=VsTjn@K~JZMMt;bZUM)&;L+ zhKZrVA8)ajCWZ?a-(s^ShBw&%7IxF*)e)uTSi+UJ@P~8W`nT8))L!%!JAvBc-(o*c zOcYuqv#3dRgrLt^$4Si+?j~18bqSU=0!5@%3bRdwuwC)hk$3H9u1SsJ-o@&q{LU9) zh))5fijhLyyrjzgjoHsiCk?F^iYi!PMf)%Uu)7yP1mpIe+%n|c8g$SZnS8xHwuVWQ zyM#C>^E4j=ihbr9c4=~JVdNSXI3-$Ww1%~svgU>S)u>X7IzuqqDt2~C%l;XZRRh3t z1Q?JiTMb5c>pD_FTfWKx&fZiQrFiHNX`gD8QhR~U{d*-+Lr~MOe=Yn- zV3U8^0(Y+9KVVl_scv#p>+kib!~ z9tq_vy7Tyz_$0n{p&JT0EBCTvIik$Zkjcm5^)^%1el+GIy+tXKOkU=sR4P(#U7kxD z2RY&2b16F;U+L7!=M_x_ZA#;bHmBvR?d-3avm=7wH1S4%Ov3ie=*-5ZdkSs3`?X=q zXVnqbY-O8gbqmgFL*~>?GSE$1*~3{~DRjNl>=waOQ6M_}(N>Py&8Ex_7mA#0$?WhK z2CO3Fmhx@L{x{gZ*^X)-wxZD<$o00ev^mW}UT#gg@Uo}dBOkN5bJ~V%MIAK}2TcCI;8MH|YJ=f;KqBw?~qgdbP%g_ae&Jn#{-&9yY@ zfy@eFVyfBoS}H|}(W)?e9J@bvn4pnZ?|JoV|CEp3t`pn2f+xg*x9jX+bLKUvxd!>F z)swRsyUYFgZ2P>>ki}H{AI?VDH_Xmw7v?p$er?J!W3SiVY?4kmUko!H{@Kqp+ilTa z)IXjdfbdT8Gm%dc!B-l`*_=q|1L+iiZvr^6F()hcug&T*;Zqbk8Y zJm`s_Co8?kTgGjO5VTY1xX|n3b@@+-tz?#$pTu(?o;v*kk4wp)w=`l3{Fvx_Rm1~x z&-0-MW2l5Gg*Xe zSq&~0*X$|>&;da7|Hj5vrHt&c~1# zh%kt=*d@_B(c0_&(#?orC9cpaa)8A07k0HSz!FL!AO5^gxYNiCikBCxCwkz-4@S&x zVoaxhe;bJ6dONy=_l7kDso-e{RN-o2FV8ng_Gk+Akb8eb?bSH6UpXuYROJk=I~YYx zo;b&2JxhHtOx`O&v0?w|(rr~saF7llF0QnY@Br`|zS(2@O%tKho=BzVm2!^kdmPo{R{D`cCLP`j@~6Qn4f z%s_B22=g$H#7-s1N|g+*;Mz?HurDi9<&O`ku`0M4-dM#2VkLBA8d3wrnMq^`lV4g) zXrM#derkbiLJrv|){z*`LyFsomb6leHhQxvxP12!)QApA#bh)F{X=o-6A&Ri>zeet z%83@2?kYfh(UlSo@iw^pB29179Rk0U`QoxW;P;f5#AVy)Ge)!rW1B86{fttvVnh2c z^rgAzpbYX@(rJw2JzR4zNNFZE!7;t@t^%mAVwfEECIAH3+zK#E&aE6B!0OUj?3rUi z@HX_DKu*B2B+o%^WOnrxiv!rAMTfMPSdYc6wV5n;aa&>AJoeGzCb~btEgl62M!$|e z<;k1ajm4SP?%Y)H@%S>>idY&ea8_otY_)kAeWqG0Pv$v~f#hkPoS{62(Ef@i$BY~g zwts_%crIbP^7%xzpC%9|2J=nIH|$WpS@`2GR!!sT$R52Pv(jE8eVeLmW!K(#F{taG*uEDk%~@M#VWYNi z;t@NJg76;+{}(!v*R$G7;@h2^g<9a}OsMv~WG$T_{E+#-10_nkM{ajYqnx0WB+;=H z%dJ5gZep)3S(bWfCgH7Esfh{@E4S_;G8CP+u}rBY9M%7&+U7MN)n=5%`TsG$Y82%Q z*{Au@p%|ZFl|N`4tT^+i=M+Bb8Qj{M$u_v^gy;T7P5x`7pXy_zW|5148BPsRnvAc? zYm}y0K6?$_jc2;0Q^FqP!VJI`95F_OHAf+$6-4`wf3S5+_h~1xe#>HTd1%hEh`J_R zR8UHIk5F@N-B>hKsLgHFh zcX=YB7&Dfe$D4fpLcTMBO@6}y9gb2y2Y6=HU_ST&kaANWvPqX$_{m=449sa0QY7L6nIY*%>QVL=CaLs!k2(jq1Xqbh%Gmz0GU$E>EJ0#i?c} zcVIVqyiWO19tcc4(6tt&h|+JNzT(GbuSgJHewA%q(V(f04{}`j?JiY;iP4^_Fa$Jm zS~jCp7W;KYZ1er!Q=aPJj)1~7fTUq&=w-67FV%)071#`y4=-n(S-9>;9@MCYNuA+q zXy?LdYzoV5)DTtWF|*Mb5tUr!mE79zO=Es58`YRyOPzeI9I*!L*bt%Z9JXX-KjH3G zc70`n@YhsUdsSMey;A`mon~@iKSZjGt~nrLh6=I?Av7l!r7rRm_|rO-EnC%47&(>g zSd}4+sJ&u!sF1si)mt5#eu>v`5v_SEVgQO zqZS_mOqlq@JYO#*S-y`+ImTjn0a7~E^c6N-%C4_&6*vOb<)oFY&YD(rPFi%G~@wczK&Qb=Lh*_0I0$&WT}w>Xb;qjJIgNZgI8h90oNe6-u`a(s8r& zt?XK&TI+w(szdf(x-%Uf^83oGjs=<~yb|$g zFpoGcx$6X;@(HNKcOLM*fS?8I$Td=miHhIm$T3Ve}$95o5jqU8MYj~ap zqzlADAu{EAB^+v8k>wfe+1fB`sz#cN!|54uUQH4~(#1FbbXqHNS%W5d_zFZyi&TLD z+X*A0QCY8RLW4%{uFf-@@4qw>j@<>c|u7XbhMnO$7+aUs?lzv{@eLA zTs;MZFNu~CsXw^3BIdhZ;QKQ#=JRO3ieqWGqEvK z_;M=fgDX?PZy8ZRSmwurx$T@7!SgUyIt3C$0z2%?ClS@%x<2Scc?2Ag>&qHXUBaD@ zvu-kU>Zqs>$gR#bTrHGl7vMPI4C?#b%%puX!Db!?D)6T+z(Bv=Ck8^C5#2xIe3X`O zNBJ2jQ@3(n&(tP#rY={02DqkHA;~>5AD|gO+n^gSC$ha!c<}6U^O@fXc_LqXgHVOT z8QgyDyds-@`A&nT9k?x^qVFF}74Z)q^7I0ZjnGAM;?Xh`V5;R-_a#!;FAEyz>$5+%Msg4a*A-9c>8p9=yFt{&v{Ac=W$h9<5~ z@_%3ruFK4aZ~4hq+QYg0w?J$6a$2H-K}r#!GSP7+kt0<~T~#^|D+;O7%v{t*cF6vjHq- z7>c+{N=`wNgZ3y3sn`DEft?u}gZlhL1}*RO=K{yv;631%Vqm@i$Q_ zgr@-0DoPQj@|ugY*yfGV)usTEzl#exOwVE$HYN(9jfwAt4_fU{D<&313?bioU0p8Y zpjolD-tenXcEVFe(RnDlSh3w!PuUW#e6Glo*uwV`YPdP&$SuAzpB;S9BwU=xf;P48 zT?I|~c+!k;3~hvT#K~tCB7_6yQ(*CrprJ6r*HtQvz8l5x$dG&9QW2`P3z>aWOj-bj zlE1{R7LE?3J@CyVy1+HD|KoYwIQx=jg;+e*a2SeioHzrAC7|HQPHw%B-QN_E6^Cq| z2O;Apc>@64(>z*W@QuLx3X=pFP7WSUQpxjr1r@05FxZfgBM|7GvadX4AE1mFc|M!= ze!W^d(Y)gDwmi1({dTo}#PAenpO`IP%mL<>E@Ic;j}~G_v49U6#lC@3u_jC@Ie8Jy z3nmDu)kZ3>1mMyu?O3jjWEmgycyY~0FhM^sUv@!S!b3pSpdwVSSSvxCt*roYx~I$w zaWK37LG!v>0I}!DeiAL;WWQxNSmcKTgwIE?+z(@F=}?1Ce!ajK+S&FGlLX@kcKgF_ zv4tn;Y=jg{4z@RQJ!E*K0Ul<64B$U7gM4Z}8?ZT1*f5+e-P}f)I-DKa94ZVN&Mt56 z5zPQ3jho7mlPGtg{WJ)^N|i+%g#0>;b=%Te*fxwU+|o5<5@O?&ZeBqhF^rXL2^G?Y zF=fjD_P=c-gu^efR@)m}p|g(A6*KgVmsj9h8YO~KAg8@OzTi<4y15+%>VbPYlnen0 z%*BXf81xdLaduuzA8%sS<{_>oEut?7!is_6-tcC+g5ol$D!AM~$Uedg8gN#y!tLSK zvrfVGI54*kf+81OLU=IljFf%rafsR;SHXQ^h@b7TpXf*pCU49MMAPzGpuF6K+X3D% z-IWj{+VW!UO$~aO_X*%f$fljWa{YI>W?9VPo2qW(;d>l-E-goc6a`Iv(%z5Rp|o@+Z;;=r|Q-;QNm-nsmeHaihp@ zjUsWEQL!7ZB8H|Q>}=s1m< zX>`DL+X|wZ;mWuBq3?A4pQ61EzL;$Di7vZ%hiPHx>(W!&XJXfb5j>r}&O6I(p}Y)I zQM?9}!IfUlI@f7uV{l)xI41`g9`a~+u)#v_K4@4=62&@K;@*^w|m@Irg;ph`!maRs-Z#k=4oZW(Oy(%Q(YML*ZhqUMJ(ez=H<6n@j4UlzH!P=*@mNatDtmhF-mQ(eY0{VEMwO+~GEpvx zKy;zj@C9qOH{J@<6mx*nHikMNU&c-O)#)CAfXO=*zF?K>5GboN4IpMxZ~xZtnF+MT z_=j5tFmn6{LK)qs)W3MVfM|@!7hu=x2WMS-1+<^c$KVk$-h2eM!arWyQvlJfTyQFY zjokODby5fdM9!LvkHQ3i>E@aJ;;%rOw1l%H0EOyBDzd)*=?}oJ!W6-ESo(SwU-`@* zVCKDCH9i1!Ast;&=Dl0y2bAL;1|147UCi;bH+mEhmQYyM0h*7E`mCw7#4MFq^w&jK z7gR8c`w(PnarM!f@&84NxHaTG8J2q?U4LF&Iu0Bw?#uDl8eP4AK>i?c@hfO#aYfvQ za)1Z^WJXpEXS()^!F_{UG%X3v%_?RA2WaEg{n8I4kd7%OSn)s@E9A_jJN^fOJ|9H{ zI(DQ!{qPIR|;N$b_#m?+p7Da4`=go zES>@*AP#9e@(o|2YfV{mbu-3MTT%a9sD%9$B(t;%ZKV!q75b9CkoGyI)Zjv*aul!t zT)1^dGY~_<@qY{l>KIP>j@T3$06Pm8HE>O+!tX9B`6^$ybx){Tf7%Ri@kKjyLM*Zo zO~Gm$CaqTd(tx$mc3x@n0okr?HABaE=(FOlZK3#O@)^$c1xtTLL z`FVt%)#5SiRVC;HlKSfBeNojW92RP3eUA zdxHVDDGYf}1JcB9htW$F>Y@`D$|`BDzUTrk8ZA1_yO>U0oKd?7RJ$1lhwV+zDSx}(*nF;s?U4yIlz#`IkWT|+%E)P zCu+$H;0|HQ7SG%#@_&Z5zrQWQSSbHEfEtEu7XRp zQKE7N%f)dm?nkO9EaFMXx0d6SEHGSHixiKNRI z|B*5m%ALrBgCZgYA?ag~D&6(+KW5i>t&&d%MzqEJCLJ6Z%jJ*GN=Vm;`EGukG%p&4 zvUg8<$J^j-YH*Fa{Sl+Nlu|Q+QLe;jdBowzY-DmK=Bi7SdZAqDM`))JhoY3$J`9GX zOq3R(FJ&qI069@q5K9&k1vHo@V~5&m7`1~cue|P^BsEKF`!6yY$To$YS`S_OOw=y- zP=JlqD3a>&a#OfQRhL-~d^u#J@?dDA)1;o6<_=PM9O2RLx9evY0oIt)ukxf1dYI-XSqzXrb89=I=4xJ`9jADs!=QE z_w{to4O>U|M>@|AKAHyTK!t>JGQK!bC2vE@>%=`TIB%%b2SPHMMiCjvW+x`M?-*Z{h%IQ zpzsKiOdr8!e8_FjXf!=tM&Tnx1B!TK1a-x~@G4HKBBF|6KfY1h?P|d>e8>-RVCU9N z0}A;?abj}B6_28{RUmVVxVj%D4`MCgCZb!{jnXlk9XP93pb-KHRpPuxjfpA)iHDs% zL$Hfj(A4A{jJU5!MYMzH@hh=Pn!0&-gsbIXNxPds1Q4jY8QO7K_<1TdQ@1^g1c?*Qr5i%CmK5Kftv74{bFn21eP%Xl>*fU6L)jPv;t3j;I;kSTMJ0#lsy(yj$pLgjj4&>!HO z5%{ADSBYXq1$hYoP%rQ4-oLG}l7;5hW%40`5t?3k{i@F|@cTY^ej&Fik3h6u zrzUhF5`ZrG>z+?!3lBO7h5ZXl5CGHNxX>I6H*&A>Q*k1dvv0X2_z{^nB!mit)1)uO zjbAQ1@{U0AJ`;1Avc47B@o5zT^|>OI2RR@ie+F`^d0=C@d<-ZSPz35q#`6)W`O3X#M?Fxd!%C zO=T8;bWr_~5N+R;l8SH66tE3P%i4ToEI+QGKsOWJ;l&bn82RLT&-AQw`~t?=+w6;D ztHsAZ!8(Cw7iSrYd@YNOJl>-A18ipCdVmJ$!!Ob5)^=xaA8#Zyyv+_BZzxRtgvQE|BSkcE_VRtU}`>B#jTOvi#(gN*e2<_$WInv%txiS|U$!CBk>g(VjQxkK^w0F&sP?Y$?$QD)@kNv?Ut%mIp+u zfZ4kFW}IyQjQEf4eL}V13+~_nour(J8#Cpdu|Dlv77o9>8v-)U{rD zk*YmYW`L~=UUV5Jt0!yPqbd~kp&YUPU;kK%6b5L+FpHE$mw{A8Hak>ljV^o}9d0Cr zldSjz`~^x8Ykz70M`(~TL=R)p2Q|9)4(2{FbX#((g_jk zf3iV?T&k=h8cKD={El0mAfN8HxE5pm|&Z_=R(Vhj^9X zv64gpp6O#alePb*k+8WZ8~n}iRx2DS`4^^u&ZTDe)Sg6!B-KJpE-BaoRk5bsChF4d!eC zr1b&%s7M@F@E`(!P=+4lr=vO0%3@UaoZ)Ro3uQE^iUQ6~Ybc$}({l1GcI0#`?P&Jk zbVDIMnbrKZp3pCe#eKWDrsW4B-(w&SOPfWW^AbDy?bP74-+OcAe}=HslAhu=JpjVk ziJVqr+3J$v!vH-v)8^Z6_T+IvZ z*zfA_*<}K^dn&*sYn~tn|4X%@Q(%1DAAa3Q=Rj|`-ROVwE4Qyy&2RGfc=c3^|g z8*A<;Y0`B-ZPm99k?mRo0}uR93>m%sK>B~{B`BH?%I zRTGrEx)9TkgD_RK2IRiQPq;Mbqim^10lRc}&4oqPg}^5_XEqRG2^VMZ1&4O@ z{sFGw(y08J&|~iIz&*fJxfW}AaY^mv7r1O7JJoYNus=I>F+6$9#pm+V`l|>_govfm z2swZZ1Wrapno%0|OMK*7tkI>qVVya3u%JW3&_UZM5~A^P7nX4;tmP^!j{lYg7ti%Y znUWc1U(8lqYWDI<6-W*sz1+axU67~r^FTz<;2MagT<8g)9ke&`sAoN?L64(yT*ANA z^95G_@=)PY0-JfcsrDgz=W?iaAN%xjYr`w3tn4kLnff<7SD6KTZ)_+G6^!&2jcn9o zgAR+K5KcA=%!MKrbtNp4%v9Jhuw_zVGT@aq8AU6RnU~ZVFN?6jSK@-V#gl@=)rn(r zHGvgesV^LC!?s>&Jg6Nt_AS>a0s?lRDkdOzFkjqNgiuP@ckaOQVGyZP>cf4gvXCm6T2FVrfI` z02IQ2eNxbDj`%*70i_8 z8iaVJ{^80BXe`W8?%0t&pUAE8$=Rho)KnoUdGhknS2i&nc-$es)wZ5OzG!ApPMm^|0q_0Q$*LC=c3nUzJ1VF~IUEepgQt>M8 zn3+|eVyjB-VqV=s)n}u+Z)=}@5&*h_Tbv>jd^i4_Y=@1&`}l(=05trOD(?$|qnv%= z5}FNbcSyMFB~0-&kC~}(Or(BgJ9Qy+ZfiB_OQp+T&m+EgJ_{ZcG}D8E+NTEfK)1G0 ze|5n{1K!X#N+ZI{zM)O4jb5c94};6}%=SbpE;^3y2!PLd&Gy(oS8v8+H)eW)(AnYj zmO#Jvts-n7;7fj7B{wvCp}uu%(P|eV;}y@FAy(tz9jJ7(svB(BL3Fb!0T-j`$?j3! zyM=hT(6g?ZopbnzrsjBx6KBl!dZhRO3|{HNH-rA_d_)X&*GfHL0Qi3mKyb=rvf@qb zO5G{j#t@;N&)t0bzj6)Zu&nRXQjFafU?PgKJB;b1%*DYUEhP{KWw(GUsOszgqpi?ou)Pr>$x(7}FV&203lqTz_g)nB z>zx`LWUZRh!&mNe>f={Y?sN1QOB9$ro$gPsBOS<->zRvRS(fD=m*h7zFiE@l{Be;&s5WN*CmppIllo zM@&P1Rb2K9HNaGUQ@+Qyyx9VtSo$403SmF~?J%covpD64Y;n?&it{DCA4S&9bF(mh z4Ke8e*u8>(g0vm^M4T{!Yb8>=4xXtvDad*P$@kDl_V_A%0DAl`E*nM+y(*-;cgX;Ns}$Zl=*?jAS^Lcz2u34;Hd zee0Ih@Q52#`|JOW7UJZbbHIfb6ROC?mY$r8t#%2*`END`1^})+vyB%6m}F%PG^>CC zSZ*8xl*cgu68r}S7M`e#0bteYiPps}AZP>-ix>)oB0>9PhX*qL+yeDm6@*&YwfEF1 zW7X> zpFA9nvYY{UGC2ex97Cw(k=gp{Wn)J1ZjIv&EbMF zgwDZpCwP7zS^#a6uujU&y)~^Kq>A<<*p(0uXEY&}Q6;+r-@P0L9Ly6mL;4MQH zNzMhdk^S|U*C>a>m@xHicLE&WXk6mic=#lYh@vp)A1|oR8IyLw3&QnJfd=b;KQ(Ae z(L#}t;|fD80mKwz_!@eqNnJ-=43VDn5U2WQ6<9#K$vNiajgK~)a-f^dQb zR*T0B`9Rlz%#aECr}*71F@v5}dj8!*4vgauV4tRNQH8$jG!o)6oH6m--<0aa(16ND z(fb~#R>HRQlAn0Y;OdW^)OuZA|1|v(sf5Kp4a)=ri1>AwiVT1`H-Yj2g+Vv@bT@Um z&7g%11DtM3TS@gdcpgE>jF_6mIRbo({(AwMvk-`kBIf?N?#L<-)~;A2@jli?oc~CJ z!s$*71XpeZhRRYmY&Gy5l)psl^%0QXEd5RIl>@Pe=A&PT;VSJJ#Mq0(5kO);cvWaB zfn-ILh#73-@VZuQU~aHMU%H?aCI(q*XEBxUjuQEB9UlBAcQL4wSqep_hmJ!49^rim;d3q?|1l^ChhZ zS38_7Okj%=&K91Zo-rLMS5xU!|T7@1(DMg~93Y zCQ8sY<|}Hwil-jP({R1O>`~r}VLlQ+)+BADGGqm8vQcGzRsl>#7`^D>#?ydcs0u-a zRdy&~;R`Hh^O3-E8XW@F#+VSUi`0h#jGvD<^+K~wx_~tqsYp4+<=d*!X^L_ z2mhE{orlBjfKZj;_6C0g*MYQ2Uo6`9pgJBe@E)Pb=RZUN8Hn^$?b&p4Kt7PyBNLI^ zM~K2R;;AR~8eMQzs;n!-#ZwYnv~r)NFU5EYHj1lnH?}tX@-Z>VN|@^WWDN9}k2dN7 zm?sVjK8-!pooa;xt>7p8f)b3tV2Ca^*2q)RN6vx*<|lfU)oTItvFa-HbO_b`2J!|! zFQI=zg8>S9#s&LB?|bbJ_7XI2H{ZSoXBFw7J-v5dPiAQ-c)4Imi{k)(YI9P}WWx`*PMC-`2m%_s#C*L?+h zkTxjtVklpAD@B)Yi$uo&2#C6;dd>ojK;N9CV*Vvay*fJ9l2b@>abBr>i6q$! zk(i>_X4pdYu{=5bpVT9p;b|qCVFcL>Uw+ByR8F1sNhAstYz8_7S8ayNUYmh06RNL3 zeVy3)-1Ee$&9F+f8GcuXkb9tK(la&DA+c5fkNo#sT!pAtuo)^nv_7WdQpfEDQZ6tM zj`$1&YqtGzMM%FJt-a?UJp%GPFdKQ z)He=#E30q%F~8?EHN}GBFftu`qDq>2I}l-!{!%WG`&s3w?@Z)j#_7U$?{xyi%;LkCDQvTQ);i*Nxt6GUcG5B@xaA3IU`a|^6A z&$9*J-$jp{5|E-2B)DZK5QIfkL`3)leRD^#DV~x)Q<7r*JPrxv8dU@f)p?%fhWWD} zUJ14KObfH_ET1U#sXIZmP?E=9gp%{;BPlUv`5 zwv9;eRtKmR;~syA#x|r#;;m6-r?{-Vb4_nJE}3Pt-9uDWR9zl*!Byb{bFzWVce z+?w(J5)Uk#MR+eq1IwMZlz#*XTUqKYzi1TWses*!54*BvZOvZi3W!ZYT$T{v1p3k@|0MGN&mEiF56I;v<5d5xioh4D1YTx$bD$q$~L&qe(1v0GP<8q>{uVBiwuhhCy{B&gfS&w*L8 zJREgslc9PS=;;b|R^+`hxy!0yI@Q#zo+%4EKz)fR>#zyV2EMC=Wqibh@%etznsXlf zHxxwm85^HqsPdKWKHxArdz9N2Ie-8eVPq~K?mYzdglayBlMZ`riswWdk4yx&!$i1m zOSKqY>qvtPfN;@J*v!K+@|g_WVt~uiWAt+A#X$o7{1QJoTwIX?bG#I|B@)_@^4Q~_ zk%s1Uf<68*5*)b)sn*+6L~fHwRt4Ww`+YQhMSULjJb)5JjAc~62U1)^ETq&-uaM86 z6b1P4yd#v{c=elVq0b1{ShFjHzHQb6{7oXimk)8dije*G<-AGiLvn2z^RzLpo=1nxW`vN*8DMJLBY_gZ>Zcv6ylEUpsPK9(9J`03(F0lx?4*U|0o_>giPEgHwNQ7{hD))e=knqRj6h@%P@sK-^F%x19pWoPuX^8eN zsNf88^?gclS&KkVwo zOZcy#8ZHBBQ*fyD4xrUP5|`G)r0|46h44a?)_MWX=qj!r0=?!qUxSu4P>Ui;5-rYj zgaF~H@f8b&QIj<U-Kaod6rHup-*SzIOA$QG!>URud?Ht1olu;w zV;H1Jse)GJ4;$e3S3uz%$SbGt_}bL3hr)Si^M9c*CvXm$mJ^sBCsGMKj2>s@Q4=Y# zxyc+%&lFk!OdqDj{7tB*Dj|vnUqu~I2u5^u=!3;b;V9VgHyH*l-Lmb}@`@aWAYpDw zHU@AA?1A^T5%+z2S;=<-k4HXnX#6vN&E>AGOJHsd{nPnZN$vL!FT<&>TlaO*nx?vmq8$e-5$0P0SwAy;72I0Ws2M*1_>7s_gRO&I;D`G2cO#&G~V>gnvz%KBDZ ztiK}H_<&?coz84h_FmjlupI4q{~|@tmn3?$;|@}=+i-1JeGd!2PW>@W_KMuUxd&rE zl4@`T$0A>z65`1`jm%1D+!}~|idSx6F?#U`lwfI41Bo|%KwTrGijX-Nk>bFl&Q58c9diC8MGS(!aDt|^%4NAmz4+MRtR6#2+I#e+(M+Ab` zdr>#U`NJqRhj*&pmzYA|`LatAGK!Z(SUaK`)jy|>A?ABuS>fY2`k4%1O)GOxrm60h>C^^E(xxn zppa;|FKL2`drEL|*9!^d4N**U%r~`N%M`8b-e@5!#TNO!&dl7) z*605`-{=4ReZM~*uQPMz%zoy~nKLuzP3Z@S5b&B%h-^v=@!vq9Z6Xe}xb2Qm3s9l! zdXQ=Ur)*fYR{?IZACK`l!Tu-V+mc~2-FGU%S&8?&qWdL_4)B538t_p7>F zzXNR%kC|eifs*`$WAyQoCqC~%+9@vpo*-%zLLo39{}DsdB)qRm1Crk53S!|v`Q0YSB<)=)QC7-6v{ zd#yZ&wd1cdmZh+Rni0_9qj3d4IKd6Adp7G{xtgXI& zJMZJhBFxVt(v>gi*u(6r+VU(n*2AZK8&wVKs(pP&vSOW}AM)nj2G(x8QOK^cvcp8h zkK6A9P)n{b}TnqCt_O*kwUH$H0bJ&%40( zV2A@G48&6w`R2o-LU--=wG{(k%t@!l=KGcup@Bm@6>tiGwMK^CggKdQwCElf`Jx2` z_NaBi1ASAFgJ`N_9v|V(IuG51G>(brKQUh(r%(#fj;pVsPiij`K_I@13t#c-{@4^p zLKG@O8-k=NjURJoy*f=0OhxIaQDQCQigyaZqro-2+Ac(QhGw!bv~rY8$+H39}b zunAa5PQGulpewt06Jy^P$p7+WQO0dzXccf+ARp|-x&~Yqqk7ri)H$lR+^G7O?b~>s z7hBqTq=-LFOHwplkdG&;JK~o71bbLp9_-CBbbgm}Z`QtR0EQ$FR^jL+4m}Vl36Qg_ zF^y!t@1uwYSXOFB0RO?8rRnE~@yQKYxSn;FuWQKO)=yUQ2YgttURcQ&_+X~2tmGSf zn4g|n$@loMR-K1bqCdV7L=6&;L`W^#HZq*=ps&aYqG?^pD|}dU-Mx}~`Lcd3Pv1d0 zlJZcI+!tz8q^mzq_GP_2G6^AaW5qws_^ZAw$kdm@_nZZFxx=sfvd+Q(K(_;1j)^dZ zO83I-%wLhi780%US~b4SyEI~to5n&|5fd8(>ah3qJUAPF|8_Pc((y^*!`D|(K?Gf1$;^Jn?J<6ABHWFs5y*0ky#-)Cg&&7*3TLn_!X z`+;UW$;A8y%uJ@4AdmeTf$pJ6+WR{kG4;TsUDNNvvozu`rk;?u*dz+GzZ}4Km{@PO z>;K&2S52&y9`PHAi@b4THrKRabIn#{H061X*@z~2w~!0Io|yBoJC$XA{a0Szn8mT@ zctjJ{RJZ@ihcsbr^p|e&Sxwjj`a`$)>rGg{@UN#(Eor`4Y+xpCo6xBVf8LXl_0qa? z?1^7_o2IO-&l4M|%ETOXf1cQs&1mlP*FC&#!T74Zv><|^zw*;fS!=!Hul)O_Y+T&A z3XJJFFd-3p9HDaD%)kQuCPi!#+zCtaZVF+ULB$Zal|nx_>1u5Sf2`jeWYr`6xSyE~Z8Y^Rnk!~SmHEA& z`8+ea>9Jq)g$N=bO&e8_TsCMD+qq7!vkP68XYRfW4cUxA9{w^qflG z$B)J6ul~eW`mr9ZEk9%2oIH0I)Dbk5C3V@en(9G(&{C$95Zi#C-2x-Uu zHEsa8M4lB-Qk#6E^Dz1+Gw|Ov#L{Kx8W)}MkqW1@Ga-KZqj(yh0ug2oDE7nHsGG+8 zZ$T`i3W+D}M}9x-xW0$X_{-{n_{J(k9bC;HYO=a6F1Nvq04o>({x!s_B@8|>oLJI)4akg!Hq?F`I~k!c!^$W1l=3X! zO4n>N!C{4!l=&hX!y;)?O3Ju)m*oC4J{^NMj$o*$=WFYcwRqnF{%LOt;-q?XMF#Xr z5!!>x_5s+B0+@RbEyJ_NsGptRwxviZ=AN>O>h~fRpYJ;blgQoz9W8*gF{`znjW4Ix zNLi+bNa6a11hFM*@b-M!U=R^hVfK${uYtK-W4jS=!)_txkYGPh2=Ih4tRXAl*|VA1 zIu}s7ynt`eNc+FyO2uXS4OD5k?RPSW6ied}8-i1Dw2VHU>!7tnz~za7cuhq@dCorc zMa_HXuJ6`}FEiky#}6wbz~~L_3Mx8G?f`QiM~>y^q5Fn5XO%HG6Z!nh*e@VD7u?jc z3;X#+X#JfHXuaIl^XvfTXT|PMv+um6Iy4b8p;m`}WuG5m0f|{zVXoz!1ElJ95`|TbWR2d|s>Ry0^n1ejX%8}mh zK4Z@Fl%b@seqD??{Y1@t4QL#?Bc}_3{W>H5rvgHA|BW2HQgk#!gj3iAmt5$;i+4vR z0jup*k`y`~t#%A2c!A&qLn%B8Rh?O|+TNjG1LG6pi0__0J_0!L#_h+As}~~HL9(La z^zYaPxR73j3AUPza-=|3wH657@Ap_w9V?MqJzM;O!fP;mQ~@xi(-fYj!XzeB zScuLA&i(%(8f@48e-}bx!N@1fEeJV#P~tCvMHISDVl{+VI!Io)CPU>QJ})$r0#x*H z%3y*syMYLmpR2v48(-^FJQHeYfRzF`!S<1z_@{HZ(J+aW0=V}t-xFzc$$|qONb2d( zYE5W7I}LtXE&Rsdi4X1_&J{N(h2qK@bqk*fhXxf?TldmtJUbJc=ZQ~@G{|fb17och zpb(Bh0fFUbtH&GB?wKuXJ%hpnlGf1a;%0$75uwK(PEwS5W1_;&mXLnkaKY+H%sqS! zyAiSF2;q@Ghi2Y?h`3t0%z5@k)XQ5b>g$1_iJ7SPknWgyH z9a&Gq_>qbS*Pa|zrR7hH9|*&d8y~#mXC;m6ds0W^k@+?#F5)Zf`;dtvQ_URQ z5~03vZ{;GO+_ylE$bU32(63Fw(H%wbI`NQ%~hcc!kG8>U>6gndZ`QKSColY|r@T;Z z`?$-+hffG%Q?2b6(OA*ig?C4`pQ$yuL#UIK6apaxdk9Pmq@=s0@ZkXW7eC>{`=$ZS zg%6sh%Q!5E$sV|9PP)aID~^`3t!|dgl=;S7`mh`p%|1wKz) zrjvNMqd5w`>;WkmOBWb(tVokRVEX)&4BW^CZuWr1v**k==Cr~!#q0r(q*$io{#9f0 zr>D#T&cFbASP^ZlDdR!S@C-yj@Nqik3AKkoWkfQ~*+7&n4j}W*HXzuWstAu()e$5_ z+W*9}BTC_DT^&(?$!ou^B4UG(KV=0vbKLp6>OKrrv?Qe*Oof=J-&WNS`6h$7otvnF zfB2j2SnHsma+23Eb-~ed%BYxHtFeVwwqs$|j`DT2j|~X6!OUIv zrzs&Ew-3f00%73*BPX8p*Eyp&4{CxzX1uC~$UQ9^iPV$oAMEi}g06C!qSY{19`($n`au|JFq#hWOCc;|!MDMJ#?g3SU;vF`g$w2{oSrg2K8n7fNFbPnVvqA~p{&!;+gKtMg#(dz zP-coiTnWUiRi0pB8SpvYM(rgYQ3&`5Nu2P`nF;ptR~1hQVoiBc2WIXyv^!Y-Djo11 zG<#|b`!J2+c#h~$OcLsE>Y62g^FAHfedb}9QRQ!lsH|0n>Bd~LM#s0dWvzN9vP-KY z!Jwtd9kND4FSyUhUNFfP!@_YlB*>0hJjXKInA?tb3T55KPKy%V;bqM2LI(`4RwhJ& za|ZwvlVzo58sT-AA&#i|OG9JKIf*uaMIu8>Z!|o8;Ujp8bGJr&o4=2PYP4!3`U925 zYp_R=hZ^OGS^Y$3fa;b|FGf<2WJl3t>t&+J7KO={+Y|}#@G{+)>vflR4u!h!a1W6T zkZwp8alZUCvPMT;uH?HzSsUv)tW(ab$L-|RFt1f;CN@omzpmyvXO za1zLa;^~G`sC_D!x-BEgiH8N*m_x@6B9`Xr)yp)sWiFVr#+>LT$jE8N;J)8iDqQEx zgLNbOU(g4MG{hdsl0^=bDjHAjggw_}BtYrJLZKt0Dh!7vs2W>T_NFiC{Rh{mE#XHJ zet4|duy1KW7$jSvZsABoMPXZeI@UHg$(XYf8K@I&C12qH6a9?$#(0!4CL~SrF;NRh zi|eyhE9iP)8VTY{=-Y8wsI~nX7>TGJU71IfVc!dKr0Wi80_|lzP#i=Ia0TbJs01>V zFv_!DEqrpO5RhJ2%=v>)?92kKmv2Cp9P$Uok#aSziGo*dP(YSp2%yX=vm3_2(HbGl zsHHF;2pWwjfjuttMm*j;(c9`l;%f}~=I@rH1lh$C67$<%M38ze(teHI8bK)nA z)IJo#n!%IjKJ1btAPf!;Na?>Ms?BWYUeO98vC=fu->6C+siVRAL5QT0USBD>_)4ro z`Kzj;esStZ!0&%s25u|W)4$p;-XTOwBV-?6jTQAX@GjNBGd}o_eXl?@=DtU~!gkqe zpNHEK&N(y{t1(@=iJy+;E8lnBQdDWQKQvBIHxUWi@5$6TztDUF07HNq;Opo?7Zgvc zPr+K%87Gs5A?gQDCFUpkYQkDo6u4G8@=wT1fb=&~nyT`%qJy1kn|q)mr3vVWmWyt@ z2{EmTpFTKEK*pjF?N@Y38$;K zv5Bn0r6Id;jRY~AHeeSY`8m?cYiD1B7dmHYuCTyNlG+_0I8KO#Lv_g?P*{pCc@EWx z_$khckH63%0*LGz$_-;L8+)7TRjaWX;EB}OEX34k>jlAjWLt&73suruDoLr*;z4>i zlb$0A62$vKRjC7~N>x@-21=ia(w%~lFR|Y`Ma809=hi4nO?0|H|wwS&mqH3vOIaMtNFW_h2Izr+jzZ^ zrv6<9%BjX@ebfy!u**NaWkk553(pGKKSC$-7d_t~x;XzC=;9}YV#w4+ey_Yt>hEDr zfnAtT!mBb^P_p9dvb33%2CSPKb0;7mx_3S0@c@Oedd#M!V}k7tcnNWfMVL;sSHNTt z3r@1_XIdtb>|a7RN2p@4OePM8{fm}AzAEa6Ri3f1RfnY~P!f<}x3N}{tS7XiRE#u} zMJI!h=kk_=(~2b+FSQ0I+1F!HwJjGp6P_qWthT#=|F1?>Cp4mE80YsHCQ{~xlDM7k zyG8IiVYcsAS-#}_L8MotN{F1nC|6LuZ#j2PHES&@o#n(A^NmO&;sdZ9k(q56)!qIz zwuVY_s8MCS2>6|-5X!3XjXM^a7W_`j0ajHEDi#fNU%co|Frb&gTZP>kX#zmx9glN5 znxljqo_k&rGn$u15!i9io_{W-gGQ51*p}G;0za;9*~YfJnnoCX{*CP3_)lbawBg3* z7dCHrlj4bS&ijc63}6A)ufN2){J>ANE;moK(ZqoLPuq)-OdT)bKOs9%rSZ)+{Zr7O z5D(DDp^o(s#H45;AIAZsth5$JHZUN_K{j21M*>(wYAxYJ8HwE5o%vc*`jL(pn@pPY zB;V2@%XR82YuCe#5z_1 z84BV2*hb+r-*6{Vr};)#UOkPuZ({)xTr%Q=n&Yw(^`O4sH+$gj6>fomg;3v;0W1uZ zK(+5Z=M?bJ#S_5Jz0Z+siCYhZ1X_nkLH`K>CR#Mp;f)n&^)C#PhHK{2Kos<$666uC zfw)<|`Rt=|jNbpHlUlw0#DbDHuZC=E?~J)@o99}>FY~oMS)2F=7gNd=B#Np@fl7qh zHwk_s?shvbZ8{gvoJVMtuU@moIZHGx)y;9q7$XAl%paxGC*Fvaw)|LJ`MpKdgxWvy z2@bF%seqMS17FRfPsV}kk|;J1x>%47@x5p$0%|fnN?h>*HC^uMcY+*{5mF-`dm7bg z7{0<`ht0;j_%bIE2I(fTd=q=?>RhrGeOzP7>IFVra~XxaNlVtU-}u#D(AM|>m#96| zaTQsJi*&ROl{&Clu$5?cjfXK>UhZ&K|S&AN6ze)15T45cNONi zQwfyc>p2Tn4AS#RGAGs!wJzlH9HgwCpaKPlE0kR|Kllz8fN;TgAa;AXo$w3>`%Kyu zw}#iTQ_PIo(`i5YS86QlJPc}%e8u88&S(pvU1~?l?Jy)nBDFqX2dI;ngRycplGOj% z7}6YB+Pm-yC7%6l5n;^b-&3=0Iqbjimm^r$d71mAl~>okI`cWFBw>z3TKhyKgaQK( zvsVkvh~wywV&9539mh1q`d0qEQ^)c=9t%N&#*UF#5NVQa_*WTqdL?GgP#zJLKT5A+F7P$%9EMG6!not~y#c7x*me?JN4R_4K2oWOK z8I10CRdtGVyoLRq5c-U3F4NWzRY|ZxX!-~NLFsNlbsVKAnlXQzffaN|r8D?Ff{u3t zDunyX6nc;1AWF8797&KeI1(Az0m_>|q09N9nx*x|tik{|qrsvaMi8pht(^L<%7e(K!5JRWmv!d%M>7ABP`EX~n)N;C4@~XcLNV3sxk^cm z)m^aAxE|C!k_$fb4JhBKC^<5Z+CyBB>^+9suYAu>_GN8*-tCSisrN=?GDwj*mNT)Q z(F>pPD2qfq$UwvahyKJ{^kY4(zuiE}>9{!|A3Y@G_iW&Z0E4QLgRwEC%J=<#0qN86 z1cC@2$2b_iji)3nhL%4s7^vX&HyN?}l`6jK9GY_r9If1 zT~d~t9-`9bpZU#R^H2N4Zce3J25dy;Ar$Udo%*|VN) z=M!FI)IcpsxvwC~apNjw>i)@Gr?`yz;$AzRfMGuhi{Fl5V)3*9ft&0-LDG$AAWUi_ zNsT#OjMFj7S?WV{ntXI_@)mjnXdXsuF3y_;*OucSkGw=*kV(PZV~BHrz>o!KBMfm4 zt`;6a1451k;WrVfoSQ%__cclCkK?sSDhWg+mJ_Uuu#yOtRb~FHHp)<-h!Woc+z~5Q zGYpjc?u*TUu%jCRX=&X;C_&&@BEgdsB0&o}US$tAVm4X|O<;aHnd-sP6k|-RfD+Jt z6{l?RLv5EK!DQI=QIrFw76xhd*YMX^+(~l_UiS(J&^{MznKXKJMBxH{a1iTYeeNtM z$yVj?Ko0g}xW-P{6d`YaFaf3{!E6u<=AR8_(ZjBTB%(ewuBWfikiy;T7>&7HLJlzUU$&1e zp$*=2?DnCV%BvACe(5ZjP72xmtIn% zU1E77K5uFlCgMfC@j)<`$Sig_9gsDQ#vTPqZDlDwdo7eCShPO_X@F5l!}DZOEenP& zWAeue2Gxwu8N#}lZ;EMj+1}MvwlT-Z_YPq_Ss2InAzJosdxXA_NHgtw_#E#c!OViZ zVS(jl6zn_%j`|djneb`NVR$6`F&XG1du0A?Ef*Y*i2^_qn<+I|Q&j#YR=E9GE|Wp;IN0a%! zHy@^3wQ+&=5nl;CJ-9^e7vw_>$Otz&Kj}haF7rr39@tAnz^azRp6XBZD5u44k@+M3 zae1kvk;U2Kuq31=#+;5z1i6Uv z)_1^u#g>}r)b_T4+$kF3oGgu0L#XyBYu^+SN`{+au_lFK`UlxpisCHI#zPWXDPvZXgg?GsUS-?gGz zWcmU|X`Ge#VAj(b5bB{!oNmWpSv4W7^1{DJ{#Uy{?FlY;( z+|BpBor21S!{Ut~87l@|Ad{X$-hlP>e+U>x+@=-X`y_I%d5z3G99hYuAIqb(nBa{I znkRY+vaav*lgKYrKa+8b>@;)@k_Ii^ph!cdoTwp)9l}RJ{uG_2p`!<6esQxb z-+kLebj<0vFr-JT;=*B}D8aTeI>r_k8G$aUjL$x~1yR+U7uK6myBxr3jA=vpy`7vy z)Q}E)tipJdn6JAfD|gF$5&Rk7`zXc&v`MxPoiQ`nIYOu3bdFG0!0c%d0@b9>jV9iP z0m_Ajxk}jekm;e&6BE?2UbF|jiURH+pF}HWzqON#E#$sRB5EKOQ<+e3&vQU@%ty-V zD;ibiw+WbzzEVphcAYN~r~oC;ETFG597NDLA;^A0n&T+tA@D`Mi-BNu(ECBUoC=;l zi!!)LNC;!K9#7#DA}ne=l)_B~KAo7xhQMg!7ih1VO_-LjHiz-SgNvR(g_4TQePC9O zAvsq~O``hyk*Az`R<-@X?16%!-rU-lVz&W8KS+q#Ba293OM}^U)<0*VygvB0YD22s!DcNJa zDZER?qX#=unC@Du9&AqGP!X=nRxY4v@wppKC(PDs zFCRkY@}t7Bs_TqvLQolr3l&Xs1jNW%3Dt}0>AD$(LCOXcCJL>NcywQZV5v`2QR14n zaScp`sy?Gfw#z9*%8j0s*CPnI9=D+3Z3x(!8d_Zd5qSfG_X1obz56;sO%b9w?J_XI zZ9{P)@qSF+R0uP;RhjQID&AH~YJ8Yf9iAWJNossjf@2opBOamX87(2-;u#)e8>+sO zl(P|>4Te@50EM8d2Vni)CAg4#;cK+&2~I~QM&Jj2>(Ub52}n@0 z$HJloH2&68;1G+kgfGStK2$B?ckP+zQ^2u=%RM*UWdt|cJ66_a7iue*e}CB?tiwaq zI!uMtH`bhv+24>nxKq+ zSii&}x>(H`Q7F&78&byGj__UyxUSO*&6L}!eBWQNxX86v9&{zsp_bhUlWoK-!XXLQ zU3yoGrMK?e@F@Z+tT#BRIwuM++Ws$zk8)Vr$VC`S(_pf~!ZM9j<~gs5g-No0oZ(x1 zAi0DRaUgf?IuolMKuu%#cgUBlc`i=W%x3nhOZbD0j zPjcn_fG8&!5}oBtqA8c}zHty1NI1iv(Y+zE(YC+L6 zoF;~t_(8HJ_;i8bL#qzf5%u}G2bgbv+%C9lZ}>L0{P8r^1S_w92K^NE4;^#Y-be5j z!&nGA$OjB#-K?=e1iveFu^RLmkb5Oz#yipZP12 z@Y)2sra>+(O^yY!aj}SoX>wqJN?)1)2b!h4F7^Y+yXtdD9a{mjZwQ8@?37B$T7sad zM5=eb@6wU9suuG`iL6CD(kY>2l~YX*jToIAMFaI7Xq_fD&OTtUz#jTJi7+LY5SIH0 z3gpBP;jg^1BT0(8-ZMq3+#(Lfn(UEvi^W!0iN z6j+Y8L=QputuptfFtJg!1azeELJ^)Iy0AF`ZVLb{(?JN*;#fcw*ElC63$EjLhyg@? zf^CJv?ki5AoihhYBV$fy%y)3`Z}wB-yL`!T){5=sn}_4kG(e74sH9@g7+<;)ZRzNw zS;s4o&1c-th_n!hrM1zDf>qa>1vG!L&d~l!mIW*Mtg7Q?Zy2k zxvo^8cSwy=vWHU2{o*E20)2ik7Q26}HOEI&V~1g_sT41Jp;NS9KYPy#@`aap;20X3 zeBW15x8)K13d5%cq0|&#gt>roI;b{7NVOT_Z=doy zwgs(0E@}?`gxb}(g~J<7X7^iD6R9UI+mq1*%zJr;fv%`|0(}w(A+flCA{hxTyHYdj zXm66KQ*5_|rqRWsk*^&i?M%0p(vd9&$UE9@YORMAMXq_4=*D>}woVWOFpdGkk%dr= zsU}w&T!kgRPr9?MgyfEplH!r>2?EjWDdHdCCyObZC&D7hEDEQ}a31eIg>@XT9U)2g zn2Vb!Kvir-h-(mXwnt>w0MK$gz5=5b+1+`G^C_(Dz*v=sG?j8cLIVBWmyk(ljZ8$= zFA?-9K-Duk{t~}4g$=d7KwJsE=CPaVAc@_ZVEN{yBV^QGXobW;2{>@S#6)u%5W7v( zl5cMIfW$SPQMl^>hl;S~pl09{G}boVHK_A|Vx_t9*G^h8q%yD#Rur7ik|q`uZ>DT9 zHE2Wwz1g|N4{j&?)bzz{c{P?xxO(+qQK{)4pWLgYff(QHH2k|0YYKFAkLWqyN#sJEc? zs-qBu1td^kU0LZHGQ`bskMZy5j>S#c~(*P5UTwE=AESBm+jGP`>-##HX4WFR<@t!&spOIZp55sA;ZGDbrkeqGz zxN$lzqQ5kZ*crwcLXdqfO(U!pb;h?Tkz_SY-1Vl5zv9yCDUmniO=xeuQSZ9^lCIp+ zdvLMlygyi0mCk=}+4mm5$9GI;A#L{|o$FBO4kn+d(F&R$RIN8obH{WRY#j+S@g`R) zddwrJ;b>L@nk=Re4i)MwiwAHjC+>60MvMJ3<%#Ll61m0U-94=5qC_b(qFhoaigK}_ zxknQtp#6v+=Ja5(7om;z67hXp@rC)Ek7>qKcaBiQUly}SJP~)=x2GGy_L!G>$qeS# zMGzdm=?l&=FVNv7m?l~Rl9^hFCgu=XyLQK$Q<+)sUC5iKves5xHC(6}8W6slm11MJLAF3CD>k+rB%sVMZns{XH z5ATHIs^{6K5MW&M>?8%`JbR0OnaTouUI0YRvW=T(vR1r*5=%({ z;TaAKA#r5pDc^Tc4CT2H#~-kX^ecUMIpiqsGZIUZBbvk9i0w6!QX(*pNPDZV#r7JK(R(pAwY`p_Je+Fo#m`MDIG3@}EYPj#7Hwlny1!n@`MB}0+42SFi^sDL`YW8jIG%-=Y#gN?L#g0cxCg@d zXXDvIlQ%)%6eqcW{=J!xn!x%oov)w3!t|w^`MVQf-{$@PYBcRTj_@BQAoJf|@7^vRh`{P9VwlNHy<6JS5>TwVgG@QYa6*+kfCiHZ6EU&=KO zFC~+#=kZ?geEf9W;0_!WlRuKhAOFUX$li-OYfLCfd zUp!FEO#&2Fs`gd3oT5^Q$*{P|t`>Z^FnxxRpggWfyB zvZ)-J%*-P(SO%fRlOj zpE% z zW7FbEvg~2t<>lxhS9rgLtb_NH)P8`^@8RrfP{uBl)(ilWd2&^0JxE|MnX4FdNaWr{fr`3LzO3CSY2(}~2W6N$Lz67j+C z+>%7H{e*h08$hy`?JnLedGK5z&tveE!sCF&4yw@+^3Rq2J>-8_`Ztpwm&JtjK=KcPpBnoC z6va@5OXf}cTW1SgE3N+h{e0k~%+Kx7*Y0gR{#Y~pq&$@`#5jv_=`{)B6xc4t<)Scl z--fb*Qf@YSHwJ~-uVZ4;OqpIIBAX)4s0d7BjDbwCk0M@?5xJzbKrRyj6AP{pMUkAA z=LR*J^BM&fx(&d?z|q@F`ckGmlc3v=*wcx$heEPYhP~ibhypmzlNSp~L+et4ou&Zo zyuwh1gRIJc$_t+q<$u{rmXEJ{QQ1^9&q`-)MwL_j`npE~&6p#02z}kB+bAFowO6k) zF-vk?DLR@G<+*PM9@;ja@+SIQjj*Ew|1}*ao6BX~6VABN6h{XDw1S|Hgp0Jz!{9)T zjC$33`$CFeaSC?~$!c~;GtroRvO`9Jmx>ONMv}!@?rOUgCtP%{@q|-8`;p40=N@7d zu2Lw429pHCQVM{=V2OK(LZh5xHk?9q%p!VcAcb^E?Te`_ zH7muJGB8V~1LpV%;<5edf*MOh&kls@TiF4Zx9ls7AQz740(oZk_8&BL^L;Ngg;Ujj zEUH#e^RKG?SplkQ|F&{pR_<2iKB-(X#Z*vG)qbvWCn@&<<@Q;F@(H1{0t6`6Te*Ke zAv5?vxnC;xta9I0?#s&Es@x})o37kx${nTLLCWp^1OytYK(GQdQLe7sA0C(SCzSh| zaDdj$<+;rtmQ|>6`4pMG+s&EKs)+-EWC%gj7}F^e8|(ubhtcZs-Q zmn9b!7EgHJ0#k&*L9z6$n+l5DjY#`2{0HmTBv-8dF)4m}uU*N6^C39ay5^D}Y)R%< z7qbE8EKiD&YO0&a4qXtJ%p;aC{|+0yAQp`Gi8&lj@O&R!esyn78DTpego`u&{EHuNf72b3#0k3vQ;Wn% zd{n6|<_@x6hNKYTfwBfD`1*jqzkP(5fb=23G2MBY*pjFT4S^E%SNeLFW*OCWUo}N6HtR-GMf3VM z%%YV6gPkL@%yUnf!p+M;IFVC$pXIEZK0SiZTh98kwS3QVwvYw#7Ax3Tc8V`t0by@* zH(s!U1&n^V8(OiScD?z&_miJKvQkhj*GOs_E5{cs%=goWh=cvLQnYLb0$3U?J+P#h zRuQPbVYlBJRa{Zn|W%sqfp{^Vf{WL0@CG&!K^JolrB!q9t1lu(hFAsRi4 zL-Kb;n(tRohQ0-q7oN6vrCT8Mc+GbvuUg4Ewpk>w4g*VNP~ELI_ozucZWRlf*hZ8A zgZ}96)9$_q5e0}#0<@i?LNN(5f6jCNVbb!+66i zwnpz1#$m*LmA>abZq5e3m~$T=n~kueGyj~zdpdLTV{Dz?qI2=i$Jj`wdvq$UT+Plh z>!?i0?wsx<75*P`t?mE4ASX#zwclou6?{s$k101@xzm(8O1XoS+g-WA!gc27mh30m1X==uiUE&g!@1IYgk1Uph8ox z@O2y6Bmb5f+PR1C^)WNe; zr_T$GO`X0_Oz(_N+K8CpaoUKn!w1EU(ngONl`vwcHbl7(CTSz$#wR9>079acFd{Z? z#276$PB?KRVu#0!8WuN-Z{Nc1*67C3WFjNMsm7A;FgU4k8XD;67lwY#kDlz!ex6^q7zmJ(WAZLS|b52~P@p#f8t}o{Y72X@wN+ zELyl(wZe7d>xQ-WYNwBHDy!klGEG&(8E$4Rs+}OI4#I>w;WKqASXw81sZO}8PI&0x z!F_^4N2M-`=n)n)D09x6FEG2Cq4WhQ_s05)9z^X*s3(g@zQ$f*y!3H4ma`q~D{FQu zP1{t)v;w%5tu^gA__a3B597BR{w%mt+hV5$e=^*k@H+#41>D3CO)G^z3GU7gnsx>L zgK+b$N5lODzsT#%s+Gd?FqEm$h)9O}D=Ajt*PsR8 z4&C|~_z%MUppT{{!(R$Fudk-fhra->Uo@^7z#jxRH%8Oe5*+TMP{TY2e-_;2A)0m& zesbTAM+4w5f&2O}OxsHo7={|-R{_74sA)a%tAsxq?sELJ8^{RmJNS9RUjn!PgPLZ7 zKN{})NtzZ4e<|GGMxtf#YopNmv1knZS#TSV*R;v-2f+=Pf=ZL$3eWNxs5K$Moij_* zcEFzoH+U|(9sY2*Iq91A5&Q*kJ+n3KTlh_I%bw7*zu_nM`?aXhHy9eY|Exn-!>_H! zwrV>X1b;Z(+qjcBf#AD<{}P6IJ|%$X!&fzJ1^lION4y1^fIkUtXKc6+!XFNI<4yDs z{DpA4I8c#q0f)O3Uv3M6KMU?^Bhz}qUjWygKA#T13GPK44<*B22Dg1{rdi+*Z;kP9 zg5$8Y2n4}x+J$L5$Pc$;IMWUj65OwFQ@9lVGPvy~qQV4+J2{1EhVPIu+@5op76^Yd z+;?E1sVDp;aEGR&67VO%bz1~n_)T!1&tlq=@9=f#LU`unpvH(ugF9(0({{t33^xP6 z68KB=nbrh%KTpCR1otWYK7zjx?lJr>!CwORbNsHrUk3M~^>E=&hC3a}4%^>Rz^k|9XJ=^W4460;jCHK<3E*A{nvjoo0~{9Edpy@{?Bnsl%a z`L_hHNL+S|r*}{4FDX#h%9`pM+M2CdW(nS++=I$3QSKGx{;u4=mFw*%(*-KmnRS>7 z_fc-Vaz`n5vT|oj*Q%u{zzXF)uH1FX-J;xW%H5~jL&`m>+%w8OuiPujy{X&^7d*J${nHHnaa&l?mFcbD))eLk1DrRxi^%1Te;r;vYgh+?Mbc=suQaK zllk>GSaa)I6?{Rt2bFt5xfhlDt#WTES8pNnZ=&1|%8gd;B<0RkZkBR4DR-N6t=gLk za8|kBDz{R(rT|$`m~vy4J59M3$sRu*bSo<*i2|DvFx(4z37$fD??_@bnu2}Q|8 zvy0M-mK0?bttl!f+EP?lw7aOJ=uA;*(WRoYB70Fqk+#iqn`xW>wxDg=_Mq*d+rzg< zZZFwUx}$7I#SU$!X=l*R@SV{+lXfQWOxu~YvtVc8&fR+|_UM&+NTeReHlJcmJmt<} zAavM%Jp2?hd6nkNy}&OU+BWCOr&zd`kit>n?F2b^iY0lct(Q2-?vyO(G&6aZZIYm; zurB!Vq|+?iJ&OaxkKfYG8^S$!Awb^vUYOX)+!Whh|qJRn&bJWKkMdV>=%OB=Cdqa&w97mau#2d)v>*J`%B!~ zg?s5sHn4%Liw`)gzl_r`bCW>}YJsZoq^mfcqXMq7H&n^`8zul^uCW#hU=V=RYpkOJFgs!JYb=VA zp1;OItg?{tO|WwKiI%x}+ugLT#>O%t2oVihs=yzpN~FY+?H4=J2HjQk$B0fsbO0L_ zz(#5f5)}L(;IpgYT}LSRi-5a7ummB#tPIDAaGxz>lbn^0KwURqcQwX?!W(JsBN=H#jJt5;#7tGK)wvsVU#f3g~>XP|WQ?yR^k#!O;H@n)}8!e_7hHPP4^)HfM_uuvN0=l@_sMIUkdA1tB$ z=V}apK|g9Or=qLUQ)khDX>1;&V0)bR2yz^bQnW}l>E?Z$B zrdvGdPqxk{M0T?G4ak4Z9!Xg;8V3vbKum`Ax=(kJI0tPk{@p`=kr|>lAU*fgMRSjP z>OqEg3G$t%zQAAt&*tO3^nTLEz4V@eEfpW%VMg92^e!8vL#SvNMz{wW;v=n9UV2!M z_Y|5hW*AVs+-9FtBhMxfzScer89elq0+NS6?5z**b%G26w8vZTX_!fo*S+;-hQ;Kg zLVUX;i&m6HDS=uyL>-0i;fAQ=HNa?AQXRj-Ja*QxLM0z=*trp@sv7Fy($~XB4{QH2 zV9r#pg3}>Y!xb77>s3_&tF&OP;Soxd161Fcs&zVf_`5!+w-aO#AWvV^`%#LF_C>uP zBcJG;G+*6)@GB}gb{-T2-RR?JeOKU`nD9stA^_h((X;hEHzWRMF2C6C#^rcKD z>MIfHXT|sx|Lv<6fMXHG6&Uq#hJyw0{b1C?=6(wwHIvdz1tX?(8A$fCf_DbI;omCY z8oo;|m7!O-iaQ9zLXp8Jk--!N-vYQ-HUd1$q_^xDjOtK%lvTMz_i`(AX4kNLjcC!> zEJX2hCcTXyXnxD2ceg%=c2FKOa5reaq$t*1^V+MxaRAf(Wu1-ma!Yn*?e%tzRNJak zol#(4q$-iAP=6hCi9~`v8qUc?9pF^#!+n^ zQ2{c1;;IdA6F{wf8uiZ)(8mt;QdyS)X*#YUxe%$In#W)jEt;340>^4*=%(?h0lJy@ zX{k3cBvHOeE%lK>HuywS8({rJLy#|vAUP;rYxF8%9dD^OjZnSg`?oK*#%300q;~YmNR;6OUT@o2-$Sn`?kV zA5i$Us=#*z>g$ORdbiS77)Ye$#jW&K(s!wq{tCs}TI)T@J=R*!G?0v29N9*Hs-dA| zD}bML)}NHV$@l3U8=ePD^lSm+jl=b(yy!kX(DzRTW#r*!@6+cS!pYYsOrIis&xRp! zv@`KsmAEWS4>Y6zy19!!#!&GLl1%8L2TR|oE_z5q&6(sum87JL9$`o$%s;wd5fHvv zUG)${Ho?|+)sqb2@bY^-^|uYQohUxiOMjlx z7(LWm&lMp4ZEt;A%W}2XmA{Z8K`qZ8ZP+op3(YP!+P@NN9%36=cr3R zH&DwRs3lFdNX>nR0>26{y?~V9h96V9tI>LQL!l@*2Fq&&`NqWPeXRzSXd@C~zPfLi z;1Ll5K6wln$U~ztUWDwX%J^P^sf;(P!QE6Y?*sgq1nZtbH2)*DhNEiKOJ)OcYtDi> zmN_Z&d80ufP|**A2k9{Z*eVo1K3QLn10oX8TO{iXFuEju<)!GIq#$-U1-(zgS#jJn zy&3p4d8bS#OcH>K&&|;N43fh;X6sy+(>G_HzDG{ii1~UScS$k}AJu0{`nde4zElq2 zxO9Do9Ibol`bt?hYlgmEr)^BgBK_e8a?cULW0pWdDq5tsRS+W|xma%#7Km;urJved z4}zcm@Xd;qjlkG?!cXyHJJD=0go{FIVB%suERdQ?-ATMv z#QHFUd}7U8yA){!_xot69@f*jj}M~?;SXC@?Gma$Gm&b7GbQmCi~k8OrhnKnJ=RJL zgD|p0e054A{j z`OzJEYmXA(CrdxywL|yQ3q>GUFOa~^J9UGerI35kh$;Ut_)TU$Vy8Ytk5;g~fQ1Wx zz)Kf)>bic=%#&~H!Nrv?>9LHJ@qu6J?bzJnls&qGnP#JC`S$=?P~gi;_hVwr=fn2u zEt^gPC={_+2Q_LRBrpE_UcH@rHbPmvXfIatFZb%+9%Q&Jxv@yg?-Ae`04gNFL~E-2 zBaV~QOrj0hr#FJ3tcZQcW9~jA`3R9(TN$~I09OIXk^m!a(5k=R0L3}QKD~{56}+Y7 zHJh?TEG~p#i}PSwatE1B>;1Y1iAI_)7>m|X08KdpmHrC`^kArf8XtE-qW~@CBljcM zH7;N*fCac?fzIJ2`*m;s(EtP?y1HwU2@*~yt=%nz&d*UEIHgp7HUP7F$~?jjpn*>V zkbJ)cBoSa2040$;`2c9*b@KNWktXiULOTYi)=wrmLP@`zh7Fv4*9Zep%+#VMDX_g73=MqA6b1glmC0>n0;@8z{Xe?E8g?kAnF{=~3)~3c!ZZc0 zaDm$aoMw^W;NJvz*kW7(vvlndgb>6XH6Go7m<&?9hWz`VC(W}0c`(DtcQz%wY47B z8rf(T$@>-q>k466>R}Zqte*TW!kTm#SkvlZZBba^y!T;XT_UX5dRT=DD~!KESmWLT zR#-i(-4ctJyrDPYvB2RM2+>pz@t{H+fL`Dy2yxWgK&-?by!LP$R)|LkF&a3$ln}qI zhghNz`=BrQQ9>N?4iL}QLp-Ap4-;ZP;P8(K@y&XOr3&$W^ay{O5Fa=K#BKEuFA2mU zJQC6T4B>35hf}69>rQQblMsiz3&gB?h<1f|kPv$Uho251@pS8D5PggEFZ z5GU0`tW=2m2r(Quyo3;=>mh0@WsaSxtp^Bkz%d|psE6pO5O)(|7;yLzLTpkG(WDSN zP=jA4#6BfJ)axPoujFY*^dUSH(fly|mx%xLAeBCtnzxI<_a8_4Z$Trq$s|-E77}6z zaQGoYy!amxTT}D45n}ffKs-?oF z5VsIwAaM9TLR?!9FSbo3VpcuGLWQ`5`s8myTuX>E>mlw|h_?xG8F0Aw zM?f4=5Aoni9(@KqSV=f*fWrgvpMF@SzeWAEh`=5nBYlT@=}Q!18g;|(gqTB!-t`dA zD8!!$F&#Ku{{)DCLwr$#KNI)zK?Y0or?cFG`2!SN20@`VX_SJYe4-cDj8W+xr*r{) zz&YJhmsoS$dY0)tTz6~h)@zA7d4gFI|LYvKl>2y_Qb->YQBB|$Ucf#wk54SsTYAg` zC}@=cnbwowBmPzrVl)0iDKzMXtDN!=+sdz(>SijgvQ(ezeiD+2JOAY~y*W4S(LLGU z{MhG^XIg)ud-9h*)i3GMeqwvecb(Tm*a`mOdA*%pvZ}b^ygrEOX+r!7Myl}_P`6zu zvP4znl&d0d09vM?AGx3>30G7vke6N1Jvw*>IHhQDGPw-GpbJ6``U(k>v3d9g?Li^o zpX+mBqvP4n(KSK*)vrT!?}joCeT+sq$B@}5`pU=M!~Uv#!}xc$2$n1zI(S#T@* z>kmjpGpQL_K$NN!8_&XDCg1_yF0N+f_pYF!$xZp(uk~Pl)s9YxEkKHRh&k zdKi10_rHcJze#!I$SU`o?y4KDeI+QVx)QW(7S@8?0wgI{5OMHKS4tz6=iRmNf&nR$ zc&6Ddyg`7bJuZo(EQg1d>2vvjA9ZgQ%Gn=!3pTsBxJ+-uaQD+$XHWj;bu?xxZ*v1f zk@WrU_?yK@>O>4^}c#EgBLLH8#w8f@#nSK@;ot^rvnu zco{?8@;Ht8mp#_c3sv@3C(oc?^i6f~Bq=SqW%~f}VFl*8*xl+v_e6zxS)3_hbLtYq;8Ubeh|S+o|I#S zdRy!uutX1DW=9|GD0Xvzw)7x>$#v%bpoi#LdA#5UAeH2~dLv#>@~53wFa|iU93zA{ zF&6))j`M+yvOeSZ_1?BzXc;#+NDEjBn5}LTOK{K-#EOm-g3w814b=6Zz1oAe-1^5A z2fJHzjBL;qCwn{RTt|lMG9=A5ZAVm2qgiJht0uBa5Gr$|Bj^rfqXNo4&-=djzW3y^ zOZv9`yx-sRd!Fa_=l$J#@BNZLO1DM6*A}gpv9il<27fTmOngL6_grK|CaiiEm~|gA zjZ~SB@ykDAnUbNKUxBNo_RT(bOmI5&A#+c{+)Q)&=UC>h`3D@2pZT}{)-N=2&&O^} z>vlR{r)sH-EUZsw!1s{Y-DjtO--Rjvwt^l;2a_?qsL<-O{xZkWms3o`V~+V_m*}*Q zn`bXE-A1tzwyZ2*vw*V8Oy^IF5%c&--nPcy;w|Z&j*U~7u@Nk`lX~)FY%DRae~gWt z*huWNn`kdy+pi~}eGdJhOOHTf`)#i&v+oKO{@SYW{%6w_R_2+>cl@)g3&`M#|M*LD z{FPRZrnhDX=W@&ugb{H!^MRQJsk8lVz zWeEe0Fe$>Y1bsz>u`FTG5w7?Q%^4B4aR}|o5*9ha7e!brQKv+>CreoB2$$VNb4rA* z975e$!ZJrVB*Gd=&qWdTW(mt};lU-wH1nkBws8o_eJ9;`6}B#?#=Q4aCcAM_9m!Hv zI;vXp6;U;kq0k?*R8@{DY~KADst-gZyQk_Jt8JBeMiyu%Br{t%god+(HI6VW!nG3c zmwI_4sCkFqSWwW# zhqso2oC5P(&--WPI8JP&TQh4OM`KQ7y!iJzCq`wcz3eB9bp{Sh_B?dnahny1==C9<$%6y9VW))A`D)We>pd5{vcgUci_;=~d?0 zk#2hxWhS_@Xy0D6OK#=5H1(vda8@08X3jtSGx9$GCasH{b?uTzD;linza-yJU+A}% z!t?#HV!3e6GM7&_0`ZYzds%2N%cmk6V!D^sw8?ykToydC6w;?kZG?oCCJO1_WnYMG zJC8xSoE~kHA47`b96Zy%-s* zFqw|uWXWlZNCZ`WvK2l{zCt zMSTG>q++}Zkr$J-(~x;_vRb1mD}e+QSq{ zR1?y;u1B)JR+IAaETl`B{s_{mBKrhVu2MXW?iqING&VMOwqv$THPx4c463~kfh2`F`qIHslXOON?nJfd?lnOu9CJMlvEDYK?YQ@7(}bz$CAuYb<;`*PAT^3qKzi-E;!gGVf`;Ab+dJA?>sw;JCe?=@ zL^GsZ+y>Ff-w6pS7k>uXt4bY)G&vcH`+e-jVvU$;x3W5J7`5zZhy+}M`@YvHHm3T= zD5OT2J}(-6>@g)*D5CIHDm&LWKasX0?awpSc$`!NrWjJqQou{>SB0p`?t}CzvI$a= zHf>psL6V9*4w0nWYX_@-6I7)P9TJgp=v7FMGBgSqP@40Qek;~=uky`AqpqAYASsVX z{qsR`FU*T|5hUS>(A))~_Ka!x0Z19dE7c51ukoE_8e_`mPUrmXdsHlEgv$NdAAq_{ zbA~Z@^V4E8 zNRe_#zDw4moF0RO)f{vKGO~+(7@*FOpHf7RQif(j22}cQgXmbRAf;|8Yc*O=k8-CD z%=;cfv)64RAqfZ<>3GIsa=(~X$v+GUsOn#Ul)06Op}#Mf}Py91K2Tx7J{hAKhn=WJ~9Af!SiKLY7h zhQ0^Uz2PU2l*-3ZDW&r98YE_u^wnk*B-MKFpRPd0Rk6>_rV}aCvmx^GN-c)Ol;$2t zziUVCLA36`MW+$D+Q@4Iq9GMogBVh2Y=>03&A~K-B?gt6X7-}#Q>9Eu(wzyUuIC_C zX*)98`g|urW6JAsF+@uOod%jbhc$z8aVDh6ZJ@f&R_on!P&srPnsHU?E=a<4(W-wf zNKZSpkUr&56f)tGN1O#?%&n}kEfSGXDqJG}?WUA0_3ZB1&3_9UB3nBjuG_+%oDWCZ zeS<%rZIl=@SGUU+p6lCW56|^&?BBUC+TONh@P!iNshrKSn_y>4gzW`4bT&3dyVwhE zdqni>SP@wNtlH4gmfmx4ZFGCYD!s6rE#7#Ra$RT3)<_%MgSrNINYqi7O{K=4&6V?v z+jH;f*wJD}=NZ5F`s$Wgvt88Q%1w4;vn*w9-)4btL_3=7Q}?E~ZnOBtXj@0;mS*;< hi#A3h4TEdu8zYkoGrv|pIQ@2GSFT3gi;O`2{{XW~1r7iJ delta 77503 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 - } 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; }