Compare commits

Author SHA1 Message Date
TermiNexus 8461a3da47 fix(viperc): typedef 强转补充 stdint/win32base 解析兜底 2026-10-19 19:16:46 +08:00
TermiNexus 08fe5a7b6b fix(viperc): 支持多级模块路径的 typedef 强转(win32.win32base.LPCWSTR) 2026-10-19 19:16:00 +08:00
TermiNexus 37c15c9aa0 修改 README.md 为纯英文 2026-10-19 18:25:15 +08:00
TermiNexus 850f61dc22 refactor(viperc): 清理标记下标推导中的内联类型转换 2026-10-18 13:40:22 +08:00
TermiNexus 82a735c367 refactor(viperc): 完全删除 T1 | T2 类型组合,语法糖改用标记下标 2026-10-18 13:39:38 +08:00
TermiNexus 42b094a575 refactor(viperc): 退役 t/c 模块标记,内建类型改由编译器直接识别 2026-10-18 12:56:57 +08:00
TermiNexus 3242560d02 支持 tls 关键字,全面准备清退 t, c 2026-10-17 23:40:06 +08:00
TermiNexus 6e17716a69 revert(vtable): 恢复按类实际规模动态分配
bf0bbcc 已经把固定上限去掉:收集前按「各父类 VTableMethodCount 之和 + 本类 FunctionDef 数 + 1」一次性分配三张表,越界检查保留为 InternalCompilerError。eff88dd 在并行开发中误把这段覆盖回固定 1024,这里恢复 bf0bbcc 的实现——无任意上限,且符合「能动态就动态」的既定规则。
2026-10-17 19:22:50 +08:00
TermiNexus eff88dd591 fix(vtable): VTBL_METHOD_MAX 32 -> 1024
memhub.MemBuddy 这类工具类的虚方法数远超 32,上一轮加的 fail-fast 直接把它拦下了。三张槽位表由 PermPool 分配(各 8*VTBL_METHOD_MAX),1024 时每类 24 KiB,可接受。
2026-10-17 19:21:44 +08:00
TermiNexus bf0bbcc89b fix(vtable): 虚方法表按类实际规模动态分配,去掉 32 上限
MemBuddy 继承 MemManager 后虚方法约 60+ 个,VTBL_METHOD_MAX=32 会触发 fail-fast(此前静默只收前 32 个,之后的调用点找不到槽位、退化成静态直调)。现在收集前先算容量上界:各父类已收集的 VTableMethodCount 之和 + 本类 FunctionDef 数量 + 1,三个 PermPool 数组按此一次性分配;两处越界检查保留为 InternalCompilerError(理论不可达)。删除 VTBL_METHOD_MAX。注:本次提交同时带上了该文件既有的 t.CPtr → rawptr 迁移与 _generate_default_init 删除等未提交改动。
2026-10-17 19:20:03 +08:00
TermiNexus d46fdf152a refactor(struct): AST vtable 拷贝改为按 uint64 槽显式读写
原写法 *dst_c = vt_ptr_c 把 i8* 赋给结构体类型的左值,TPC 会以「非基本类型禁止指针隐式转值类型」报错。新增 _copy_ast_vtable(src, dst):把两侧 AST 节点都视作一个 uint64 槽(src_vt[0] -> dst_vt[0]),语义不变(只复制首字段 vptr),Constant 与 List 两条深拷贝路径统一调用它。
2026-10-17 19:13:08 +08:00
TermiNexus 3e1e1fc1b3 fix(gen): 消除 ViperC 侧 P0 级静默截断
match:case 数超过 MAX_MATCH_CASES 时 fail-fast(原先 break 会静默少分支,need_case 检查也发现不了)。vtable:初始化串按真实方法表动态分配,_vtable_strcat 溢出改 fatal;VTBL_METHOD_MAX 超限不再 break(原先子类 override 静默失效)。符号名:coro_pool_/__tls_init_done_/__decor_rec_/闭包 fn_ptr 的定义与引用改为按实际长度分配,消除 96 vs 128、64 vs 128 的不对称。Stub:_PyiEmit 溢出改 VLogger.error(半截 .vpi 会被当完整文件解析);VpiReader 参数段/返回注解缓存槽放不下时 fail-fast,不再丢段导致形参注解错位。Includes:文件超过 1MiB 拒绝哈希(SHA1 冲突会让两个模块共用缓存);add_file_entry 失败不再被忽略。f-string:格式串按字面量/转换符实际长度分配,字面量与 format_spec 溢出 fail-fast。
2026-10-17 18:46:18 +08:00
TermiNexus 15952d3852 fix(asm): 内联汇编/LLVMIR 文本缓冲扩容并将所有超限改为 fail-fast
ASM_TEXT_BUF_SIZE 2048→8192(内核 _do_switch 模板已 2245 字节),c.LLVMIR 模板 512→4096。_asm_append_cstr 不再静默截断:装不下时报 TypeError 并给出已用/待写/容量字节数(此前截断会把模板切成半条 mov,最终以 llc invalid operand 的面目出现)。操作数超过 16、clobber 超过 32、符号改写缓冲溢出同样 fail-fast;约束串/参数串/extractvalue 行缓冲改为按实际操作数与类型文本长度动态分配。
2026-10-17 17:41:11 +08:00
TermiNexus 201c881b43 fix(assign): 拦截注解为类值类型但右值取地址的 typo
dirp: devfs_directory = &directories[d](注解漏写 *)此前会静默通过,把栈/表元素地址塞进值语义槽位,导致 vptr/父指针悬垂、对象被就地覆写。在 translate_annassign 中新增 fail-fast:注解解析为 struct(类值类型)而右值是取地址表达式(&x 或 c.Addr(x))时报 TypeError 并提示补 *。新增 _is_addr_of_expr 助手,只认取地址,不含 *p 解引用,避免误伤值拷贝。
2026-10-16 20:19:51 +08:00
TermiNexus 19feb40e8c fix(call): 实参个数与被调方不符时前端 fail-fast - 不再漏到 LLVM verifier(含 EH 参数扣除与可变参数放行) 2026-10-16 19:26:22 +08:00
TermiNexus 2f4031b3d6 chore(typegen): read_all 长度参数去掉多余 ULONG 转换 2026-10-16 16:40:41 +08:00
TermiNexus d8648176e9 feat(asm): op=/clobber= 支持裸枚举名(CLOBBER_MEMORY) - 等价 ASM_DESCR.XXX 2026-10-16 15:26:17 +08:00
TermiNexus e796314a24 fix(gi): 模块级空数组字面量 = [] 改走 zeroinitializer - 修大数组被逐元素展开(元素过多+池耗尽) 2026-10-16 13:02:38 +08:00
TermiNexus 7e9ed22dff revert(main): POOL_SIZE 回退 1GiB - 2GiB 触发 MemBuddy 内部索引溢出(_fl_push 在 __init__ 崩) 2026-10-16 12:13:02 +08:00
TermiNexus c57c931099 fix(cdefine): 支持浮点常量表达式(define X = 160.0 * 4) - 含浮点字面量时按浮点求值登记 double 2026-10-16 11:23:41 +08:00
TermiNexus 832b0a5706 chore(diag): [VAR-DIAG] 增加 alloca 地址打印 - 定位 pos/npos 共享存储(值语义被破坏) 2026-10-16 00:12:06 +08:00
TermiNexus 6196bdfe56 fix(gi): 模块级负浮点字面量按浮点初始化(不再送整型提取器) - 修 double = -100.0 报错 2026-10-16 00:03:21 +08:00
TermiNexus d06e665ebd fix(cdefine): stub 读取对负浮点字面量/浮点强转包裹剥壳后再注册 - 修 X: double = -100.0 走整型提取 fatal 2026-10-15 23:46:25 +08:00
TermiNexus 5460d78ff1 fix(gi): 模块级类全局按值本位存储(仅 __new__ 类用指针) - 修 CAM: V3 被包成 Ptr 致悬垂误报 2026-10-15 23:11:24 +08:00
TermiNexus 9d0a43d5e6 chore(diag): [GVAR-DIAG] 模块级全局类型决策(hasnew/hasvtable/isoop) - 定位 CAM 被包成 Ptr 2026-10-15 23:06:20 +08:00
TermiNexus 15a210bede chore(diag): 变量声明槽类型 [VAR-DIAG] - 定位 class 标注是否被解析成 ptr 2026-10-15 23:00:04 +08:00
TermiNexus 59c032f135 chore(diag): CTOR-DIAG 打印构造类型与目标类型的真实 LLVM 文本 - 定位 CAM 全局值本位 2026-10-15 22:53:23 +08:00
TermiNexus 23236c88ea fix(lifetime): 裸构造判值拷贝不再依赖类型打印比较 - 修 CAM = V3(...) 全局赋值误报 2026-10-15 22:26:17 +08:00
TermiNexus d1dd6de070 fix(lifetime): 目标为值类型即判 LIFE_VALUE(不再要求源非指针) - class 局部变量内部为指针槽,修 stars 误报 2026-10-15 21:59:39 +08:00
TermiNexus cbadc356f9 chore(diag): life 判定加全量诊断 [LIFE-DIAG]/[RT-DIAG] - 定位 return col 误判 2026-10-15 21:54:08 +08:00
TermiNexus 5dc483e256 fix(lifetime): 值类型变量按值拷贝判据改为双非指针(不再依赖类型打印比较),堆/静态登记优先 2026-10-15 21:47:30 +08:00
TermiNexus 6b86f846ca fix(lifetime): 值类型局部变量在同型聚合目标位置按值拷贝(LIFE_VALUE) - 修 return col 被误判 stack address escapes 2026-10-15 21:40:30 +08:00
TermiNexus 217f5f021d fix(viperc): 解引用赋值补位拷贝(*a = *b 按位重解释,不做数值转换)
现场(math.vp sin/cos):
    u: uint64 = 0; xv: double = x
    *&u = *&xv        # 位拷贝,应为 movq %xmm0, %rax
    ef: uint64 = (u >> 52) & 0x7FF
生成:404084 cvttsd2si %xmm0, %rax ← 把 double 数值截断成 int64
代价:u 成了数值截断结果 ⇒ ef/neg 全错;x 为负时(-1.5 → u = -1)ef 命中 0x7FF
⇒ 直接 return x - x = NaN ⇒ sin/cos 负参全废。

VPC 的 `*p = v` 目标路径无条件 coerce_to_type(rhs, pointee);TPC 侧早有该判据
(HandlesAssign.py:592 调 HandlesSpecialCall.EmitRawCopyLlvm,注释明确"位拷贝,绝不转换"),
VPC 漏了 ⇒ 本次按同口径补齐。

- RHS 为 UnaryOp(Deref)(`*p`)且两侧是**等宽标量**时:按**源自身类型** load(类型与槽的
  存储类型一致),再 bitcast 成目的类型存回 —— 不做任何数值转换;
- 不用"按目的类型重解释的 load"(`load i64, ptr %xv`):文本层 T2「store→load 转发」按**名字**
  替换,会把 `store double %x, ptr %xv` 的 %x 顶进这条 i64 行,报
  `use of value '%x' expects different type than prior uses: 'i64' vs 'f64'`;
- 源操作数限定非调用形状,避免与前面 rhs_val 的求值重复;源不是指针 ⇒ fail-fast;
- 两侧位宽不等或含聚合 ⇒ 不做位拷贝,交回 coerce 路径(等价改动前行为,聚合位拷贝未支持)。
2026-10-15 20:16:50 +08:00
TermiNexus 5ea0ce094e fix(viperc): 恢复一元负号字面量按目标类型直出 f32(位宽根因已修,路径不再有风险)
背景:负号字面量(float(-10.0) / x: float = -3.5)此前回退过——当时 MLIR 渲染报
「f32 类型配 16 位文本」,原因是 mlirlite.ConstFloat 用 snprintf 打印位型时,
高位置位的 32 位值会被按符号扩展后的 64 位打印。该根因已在 includes/mlirlite 侧修死
(提交 8dbad70:十六进制位数只由 MlirTypeText(ty) 定,与 MLIR 渲染器同源,构造上一致),
故此路径恢复为「正确路径」:f32 目标下直接物化 f32 常量,去掉 f64 常量 + fptrunc 的绕行。

- ExprGenOps.translate_unaryop 增加 target_ty 形参:目标为 32 位浮点时折叠直接出 f32 常量;
- ExprGenMixin 把 target_ty 透传给 translate_unaryop。
2026-10-15 19:43:15 +08:00
TermiNexus 3e52baa209 fix(viperc): 回退一元负号字面量的 f32 直出,保留正号字面量与 CDefine 的 f32 直出
- 现场二分定位:float(-10.0) / -float(10.0) 走「按目标类型直出 f32 常量」后,
  MLIR 渲染阶段报 float 位模式位宽不匹配(VKernel 整个编不过);同一份 gfx.vp
  只把负号字面量换成正号就渲染通过,故该路径回退为改动前行为(按操作数自身类型
  出常量,f64 常量 + 使用处 fptrunc)。
- 保留两条已验证有效的修复:
  1) 正号浮点字面量在 f32 目标下直接物化 f32 常量(translate_constant);
  2) CDefine 记宽度种类(1=f64 / 2=f32),define PI: float = ... 的使用处直出 f32
     常量(含 -PI —— 折叠按操作数类型出常量,操作数已是 f32,故仍是 f32)。
- define 常量补 ': int' 注解:无注解 define 按值的最小可存宽度物化(1/2 -> i8),
  直接当实参传给 list[int].append(x: int) 会落成 i8 != i32 被 mlir-opt 拦下
  (自举编译 TypeGenCDef 时必现)。
2026-10-15 19:25:49 +08:00
TermiNexus 508428bbc9 fix(viperc): 浮点常量按目标类型/声明宽度直接出 f32,不再先出 double
- 浮点字面量在 f32 目标(float(1.5) / x: float = 1.5 / f32 形参)下直接物化 f32 常量,
  负号折叠(-c)同口径;去掉旧的「f64 常量 + fptrunc」
- CDefine 浮点条目记录宽度种类(1=f64 / 2=f32):define PI: float = ... 的使用处
  直接出 f32 常量,与 f32 字面量混算不再被提升成 double(现场 33 处 cvtss2sd)
- 本模块注册 / pyi·vpi 跨模块重解析 / xmod 缓存三处都按注解裁决宽度,保持跨模块一致
2026-10-15 18:35:48 +08:00
TermiNexus 3a687db0bb feat(viperc): 静态常量算式补上除法/整除/取模折叠(与运行期 sdiv/srem 同口径)
此前纯常量折叠只覆盖 + - * | & ^ << >>,Div/FloorDiv/Mod 一律落到运行期 sdiv/udiv/srem/urem。现按同一维护口径补上:以精确值折叠,再由既有逻辑按结果值自动提升位宽(不无故抬宽,避免把 A/1 这类算式整体抬成 i64)。语义与运行期一致:Div/FloorDiv → sdiv(向零截断)、Mod → srem(余数符号随被除数)——宿主侧 // 与 % 正是这两个语义,无须额外修正;无符号位型常量按规范恒为非负,与运行期 udiv/urem 取值一致。除零不交给宿主(会打挂编译器):先关掉折叠再 fail-fast,诊断文本与 AssignGenMixin 的常量求值器保持一致。
2026-10-14 22:28:43 +08:00
TermiNexus 38a277a914 feat(viperc): 纯常量折叠补上 / // %,与 + - * 同口径(精确值 + 按值自动提升位宽)
口径与 AssignGenMixin 的常量求值器逐字对齐:①除零先关折叠再 fail-fast(同一条诊断文案,避免交给宿主 Python 打挂编译器);②结果按精确值自动提升位宽,不无故抬宽;③负数语义待定 ⇒ 本轮只折叠两侧都非负的情形(此时 sdiv 与 udiv 结果相同、无歧义),含负操作数的算式不折叠,照旧走运行期 sdiv/udiv/srem/urem。
2026-10-14 22:27:55 +08:00
TermiNexus dc4468593c fix(viperc): CDefine 常量按位宽处理时不再把可容正值翻转成负数,修常量算式折错
现场:Gargantua _start 的 string.memset(fb, 0, GW * GH * 4)(GW=160, GH=90)应折叠为 57600,实际生成 movl \(= -34560)作为 memset 长度 → 从 0x457ca0 写 4GB → 越界写撞到映射区末尾 0x467000,用户态 #PF err=0x7(cr2 与反汇编逐字对上)。根因:define GW = 160 的位宽按值推断为 8(无符号可容),而 _cdefine_bits_value/_normalize_cdefine_bits 无条件 '(value<<56)>>56' 重解释为有符号 → 160 变 -96(=-96*90*4=-34560)。现只在两种解释都装不下时才截断;位型不变(const_int 本就按位宽截断),仅不再翻转可容正值的语义。
2026-10-14 22:20:52 +08:00
TermiNexus a143c2de0c fix(stubgen): 装饰器循环变量改名 ddi,避免与默认值处的注解声明 di 冲突
同一个函数里 di 先被 for 循环绑定为 int(i32),后面又用注解声明为 sizet(i64) 且右值是运行期 i64 值:存储类型 i32 收不下,编译器按 fail-fast 报'带注解重声明冲突'(此前 TPC 是静默丢掉 store,生成的 viperc.exe 在 list[AST*].get 越界读)。装饰器循环改用 ddi,di 保持为首次声明。
2026-10-14 21:13:03 +08:00
TermiNexus 7da9d7cb0d fix(viperc): 位宽探测循环改为固定 64 次上界,修主编译器死循环
- 现象:Phase1 编译到 viperlib.vp 时卡死,栈顶停在 ExprGenOps.vp:1362(我新加的位宽探测 while)

- 原因:`while t != 0 { t = t >> 1 }`(t 为 int64)在生成代码里不收敛(疑似 i64 移位/比较被按窄宽度处理)→ 死循环;改为 `while i < 64 and t > 0` 的固定上界写法,两处(ExprGenOps 运行期常量折叠 / AssignGenMixin 静态求值)一起改

- 遗留待查:该 while 为何不收敛(i64 >> / != 0 的代码生成),属同类“最小宽度”问题面
2026-10-14 19:54:29 +08:00
TermiNexus d09b79995d fix(viperc): 运行期纯常量整型算式按精确值折叠并按值定宽,禁止最小宽度回绕
- 现场:`1024 * 1024 * 4` 在表达式侧按最小宽度(i16)运算,1024×1024 先回绕成 0 ⇒ 常量变 0(WINBUF_MAX_BYTES=0 ⇒ 窗口缓冲只剩几字节 ⇒ 内核 memcpy 240KB 越界读 → 三重故障)

- 在 translate_binop 取到操作数类型后加入纯常量折叠:+ - * | & ^ << >> 两侧都是编译期整型常量时按精确值求结果,并按“能精确容纳该值的最小合法宽度(下限取操作数较宽者)”定宽、向上取 8/16/32/64;只有装不下才加宽(与 61f23d9 的静态求值器同口径)

- 与 61f23d9 合起来:静态 define 求值与运行期常量算式都不再按最小宽度回绕
2026-10-14 19:39:11 +08:00
TermiNexus 61f23d99a1 fix(viperc): 静态常量求值对 + - * << 按结果值加宽,杜绝 define 算式静默回绕
- 现象:Gargantua 的 define V3_POOL_SIZE=8192 / V3_POOL_BYTES=V3_POOL_SIZE*24+512 在运行时变成 512(正好是表达式里的 +512),等价于 8192*24=196608 被按 16 位回绕成 0 → 池只剩 512B → 每像素 trace 立刻 OOM 刷屏

- 根因:extract_cdefine_int_value_bits 的 BinOp 位宽规则只“取较宽”,而 + - * << 会增长结果位宽 → define 物化时按 16 位截断

- 现在:操作数的声明宽度仍保持最小(不提升);基准取较宽者;若精确结果装不进基准宽度,则按结果值加宽(向上取 8/16/32/64)—— 静态计算不再回绕;基准未知(纯字面量算式)时保持“未知”,仍由物化端按值推断

- 顺带:未知 BinOp 算子恢复 fail-fast(重构后原落空分支若留着会静默返回 0)
2026-10-14 19:18:52 +08:00
TermiNexus 813b59e25f fix(viperc): 修正 (expr).method() 接收者误入 get_attribute_ptr 导致的 strcmp(_,0x1) 崩溃
- 表达式接收者分支(L1236 设计上可收 BinOp/Subscript/Call)在接收者求值非指针时会调 get_attribute_ptr;该 API 只接受 Attribute,内部直接强转成 ast.Attribute 并读 .attr,对别的节点会命中偏移 9 的字段:BinOp 的 op(OpKind.Sub=1)、Name 的 ctx(ASTCtx.Store=1)等,于是把整数 1 当字符串传给 strcmp → ASan access-violation on 0x1

- 现在仅当接收者确为 ast.Attribute 时才走“取字段地址”,其余节点保持已翻译的接收者值(与旧实现的回退结果一致)

- 现场:Test/OopInheritTest/App/main.vp 的 (a3 * 2.0 - a4).cross(a5),接收者 BinOp 的 op=Sub=1
2026-10-14 17:57:01 +08:00
TermiNexus ee3337f3c4 revert(viperc): 回退闭包变量取址的放行,仅保留“实参级报错定位”
- 回退 a060e1d 中 ExprGenOps 的闭包取址改动与 TypeGenErr 的临时打印移除:&_on_submit 仍按级数不符报 CastError(source level 2 -> target level 1),语义不变、不放行

- 保留 CallGenCall._call_method_on_ptr 的实参级 AST 节点透传:方法/构造实参转换失败时报错定位到具体实参表达式,并显示完整两帧(函数定义帧 + 错误点)
2026-10-14 17:40:53 +08:00
TermiNexus a060e1d76e fix(viperc): 闭包变量取址返回闭包指针,并让方法/构造实参转换报错定位到具体实参
- ExprGenOps 取址:嵌套 def 提升出的闭包变量在父函数里是 i8** 槽,&f 原来返回槽地址(二级),传给 callback: t.CPtr 会因级数不符报 CastError(source level 2 -> target level 1);现按闭包名表判定,返回其值 i8*(一级),与“函数名即地址(同 C)”一致

- CallGenCall._call_method_on_ptr:实参逐个转换原来统一传 node=None,失败时只能退回函数定义行(只剩一帧);现按 arg_nodes 带上每个实参自身的 AST 节点(slot 0 是 self),报错可定位到具体实参表达式并显示完整两帧

- TypeGenErr:移除定位用的 [TEMP-DIAG] 临时打印
2026-10-14 17:38:50 +08:00
TermiNexus 3c6041c97c fix(viperc/diag): 把“当前实参位置”的记录点下沉到 translate_call_arg(覆盖所有调用路径)
TEMP-DIAG 实测:node=0 curarg=0 trace=1 top=11 —— 节点是合成的(无行号),但“当前实参
位置”压根没被记录,说明该构造器调用没走 CallGenLib 的参数循环,兜底只能退回栈帧(函数定义行)。

改为在**所有调用实参的公共入口** CallGenBase.translate_call_arg 顶部记录(它持有 arg_node),
并移除 CallGenLib 里的重复挂点。这样任何调用路径(含构造器)都能把报错定位到具体实参表达式。
2026-10-14 17:13:38 +08:00
TermiNexus ca81c1185c diag(viperc): 放宽报错位置兜底判据 + 临时打印定位决策输入(定位后删除)
兜底判据改为:只要实参位置有效,且节点行号“不深于”栈最内层帧行号(或栈为空),就用实参
位置 —— 覆盖 FunctionDef 节点被传下来的情形(其行号恰好等于函数定义行、并非真实表达式)。
另加一行 [TEMP-DIAG](node/curarg/trace/top/shown),用于一次看清兜底为何未触发;
定位完成后立即删除该行。
2026-10-14 17:08:59 +08:00
TermiNexus 40ea5d6513 fix(viperc/diag): 节点行号等于函数定义行时也用实参位置兜底(定位到具体表达式)
上一版只在 lineno==0 时用 CurArg 兜底;实测该报错的节点自带行号且恰好等于当前函数定义行
(节点其实是 FunctionDef 本身,构造器内联路径把它传了下来),于是兜底没触发,错误位置与
栈帧都在定义行、被去重合并,看起来只剩一帧。

现把“节点位置不可信”判据放宽为:lineno==0,**或** lineno 等于最内层栈帧的行号(即函数
定义行)→ 两条都优先用 CurArg,报错精确指向调用处的实参表达式(如 `&_on_submit`)。
2026-10-14 17:02:16 +08:00
TermiNexus 4f45b391ea fix(viperc/diag): 保留函数定义帧,不覆盖栈帧位置(补齐“定义帧 + 出错语句帧”两帧)
上一版 B 把最内层栈帧的位置改写成“当前语句”,结果它与错误帧落在同一 file:line,
被去重合并 —— 输出只剩一帧,`_start` 那帧(函数定义)反而没了。

改为:栈帧只记录**被调函数的定义位置**(不随语句推进覆盖);“当前语句/具体表达式”
统一由错误帧给出(CurArg 兜底,见 TypeGenErr)。这样能得到完整两帧:
  File "./Apps/HelloWorld/main.vp", line 11   export def _start() -> int:   ← 定义帧
  File "./Apps/HelloWorld/main.vp", line 36   ... &_on_submit)             ← 出错语句帧
同时删除已无调用的 TypeGenBase.update_top_trace_position。
2026-10-14 16:51:51 +08:00
TermiNexus d6b87da063 fix(viperc/attr): 不再要求 import c 才认 @__attribute__(否则函数属性被静默丢弃)
ast/parser.vp 的 _DesugarMagicAttribute 会把用户写的 `@__attribute__(...)` 脱糖成
`@c.Attribute(...)`,此处看到的 `c.` 前缀是编译器自己产生的(用户并未写 `@c.Attribute`)。
原先这里要求 is_module_imported(...,"c"),用户没写 `import c` 时整条装饰器被静默丢弃,
stackrealign / always_inline 等函数属性全部失效。现去掉该守卫,与 extract_func_section
(本就没有此守卫)保持一致。
2026-10-14 16:42:16 +08:00
TermiNexus 0f2675e794 fix(viperc/codegen): Win64 变参提升按实参符号性选择 zext/sext,避免无符号值高位被填脏
变参调用把 <64 位整数提升到 i64 时原先一律 sext:uint8 0x8E 会被填成
0xFFFFFFFFFFFFFF8E,va_arg(i64) 读到脏高位(即 Debugger/VKERNELERR.md 记录的 sext 家族)。
现按实参符号性选择:无符号走 zext、有符号仍走 sext,与
CallGenVa._check_and_promote_variadic_call 同口径(复用 ExprGenOps.should_zext_widen)。
2026-10-14 16:41:58 +08:00
TermiNexus 0678cc8c48 feat(viperc/diag): 报错定位到具体表达式 + 栈帧跟踪当前语句(方案 A+B)
A:报错节点是合成节点(lineno==0,如构造器内联出的参数赋值)时,优先用“正在翻译的实参
   位置”兜底 → 报错精确指向调用处的表达式(如 `&_on_submit`),不再退回函数定义行。
   - TypeGenBase 新增 CurArgFile/CurArgLine/CurArgCol 与 set_cur_arg()
   - CallGenLib 翻译实参前调用 set_cur_arg(当前文件, arg_node 位置)
   - TypeGenErr 兜底顺序改为:节点自身行号 → CurArg → 栈最内层帧
   - 语句边界清空 CurArg,避免兜底串味到上一条语句的实参

B:栈帧跟踪“当前语句”位置(Python traceback 口径)
   - TypeGenBase.update_top_trace_position():更新最内层帧的 lineno/col
   - translate_stmt 每条语句推进时调用 → 报错时栈帧给出的是调用点所在语句,而非函数定义行
   配合 A 即可得到“调用点 + 具体表达式”两帧。
2026-10-14 16:40:35 +08:00
TermiNexus 70c271d2a4 fix(viperc): &func 对函数指针变量应取其值(Ptr(Func) 一级),不再多一层地址
现场:Apps/HelloWorld 里 vpui.Button(..., &_on_submit) 报
  CastError: pointer level differs (source level 2 -> target level 1);
  source: level 2, pointee !llvm.ptr -> target: level 1, pointee i8

根因:嵌套函数名在符号表里是 Ptr(Func) 变量。ExprGenOps 的取址分支有一条
“变量本身就是那个对象”的规则(结构体指针变量要 load 一次、而非取变量地址),
但该 match 只覆盖了 Ptr(Ptr(Struct)),未覆盖 Ptr(Ptr(Func)) —— 于是 `&f` 落到末尾的
`return v_ao`,给出的是变量地址 Ptr(Ptr(Func))(二级),而不是函数地址 Ptr(Func)(一级)。

修改:在该 match 中补上 Ptr(Ptr(Func)) 分支,load 一次后返回 Ptr(Func),
与结构体指针变量同口径。
2026-10-14 16:37:37 +08:00
TermiNexus 9d02a3ac0b improve(viperc/diag): 指针级数不符报错补印“级数 + pointee 类型”
LLVM 不透明指针下 source/target 两边的类型文本都是 !llvm.ptr,看不出差异,现场只剩
一个 “2 -> 1”。现改为同时打印:
  source: level N, pointee <T>  ->  target: level M, pointee <U>
这样一眼能定位是哪个表达式/哪个形参触发的级数不符。
2026-10-14 16:01:09 +08:00
TermiNexus 6b180c8f59 fix(viperc/life): 进程入口豁免补上 _start(ViperOS 应用入口),消除栈逃逸误报
现象:Apps/HelloWorld/main.vp 报 LifetimeError(`master` frame-local,传给构造器参数会
悬垂)。而 `master = &root` 中 root 是 _start 的帧内 Tk,_start 是进程入口
(@__attribute__(section(".text.startup")),结尾 mainloop/process.exit),本帧返回即进程
结束 —— 与 main 同口径,确无逃逸。

_CurFuncIsEntryMain 此前只认 "main",现把 `_start` 一并视为进程入口。
2026-10-14 15:53:28 +08:00
TermiNexus a23c1bda3e fix(viperc/diag): 调用栈帧与错误点同位置时不再重复打印
现象:`_start` 定义行在 Traceback 里被打印两遍 —— 栈内最内层帧与随后的“错误点帧”
落在同一 file:line(错误的 AST 节点 lineno 为 0 时,错误点位置会回退到栈最内层帧,
于是与栈帧完全重合)。

Diag.emit 增加两条去重规则(DIAG_DIR_UP / DIAG_DIR_DOWN 两个方向都生效):
① 连续两个位置完全相同的栈帧只印一次;
② 与紧随其后的“错误点帧”(emit_current 打印的那一帧)位置相同的栈帧不再重复印。
2026-10-14 15:53:05 +08:00
TermiNexus dc24970af2 fix(gen): 修 __before_init__ 字段默认值写入的参数次序(build_store 缺 builder/file 顺序颠倒),修编译期参数缺失报错 2026-10-13 21:46:20 +08:00
TermiNexus ea54d5685d feat(gen): virtual(HasVTable)类豁免「无 def => struct」规则,必须合成 __before_init__/__init__ 且必发 vtable;非泛型构造路径按符号存在性判定调用 2026-10-13 21:29:24 +08:00
TermiNexus 99a021ad67 fix(gen): 类合成默认构造——有方法但无显式 __init__ 的类合成空 __init__(补 undefined 符号);完全无 def 的类按 struct 处理,不合成 __init__/__before_init__ 2026-10-13 20:59:41 +08:00
TermiNexus 12da1cbfc1 fix(gen): define 复用前置声明时按源码形参名对齐签名参数名,修签名与函数体两套参数名导致 MLIR「region entry argument already in use」 2026-10-13 20:33:06 +08:00
TermiNexus bc1eeb6e38 feat(gen): REPL 会话变量的复合赋值下传槽类型作为 target_ty(复用 repl_slot_ty,缺失即 fail-fast),与二元/普通变量路径对齐 2026-10-13 20:04:34 +08:00
TermiNexus c1e5a11962 feat(gen): 复合赋值统一二元运算口径(常量无损适配另一侧、不做整数提升、RHS 按左值类型翻译),修 u16 -= 1 误报窄化 2026-10-13 20:01:16 +08:00
TermiNexus d2138678ef feat(gen): 同型聚合值 -> 同型聚合指针 允许隐式值拷贝(调用者帧溢出临时槽后取址),修裸类形参接收聚合值实参报错 2026-10-13 18:33:49 +08:00
TermiNexus 1c3cdf3493 fix(stub): 消费模块补齐按值返回聚合的具名结构体完整体(_inject_method_declare 补 _ensure_full_value_types、_inject_class_struct_declare 补登完整定义),修 MLIR opaque 报错 2026-10-13 17:48:57 +08:00
TermiNexus 377c0db5d3 fix(gen): life_of_expr 的构造分支按目标类型判定——目标为同型聚合值时判 LIFE_VALUE(值拷贝),不再误判帧逃逸 2026-10-13 17:48:35 +08:00
TermiNexus 6cb1f525d9 feat(gen): 常量操作数隐式适配另一侧位宽(仅另一侧非常量时),修 u16 变量 + 字面量被抬到 i32 导致误报窄化 2026-10-13 17:01:53 +08:00
TermiNexus a01e9280cc fix(stub,phase1): 因取消整数提升,模块属性 CDefine 的窄乘法显式加宽为 sizet(修缓冲区尺寸回绕成 0/负值) 2026-10-13 16:34:39 +08:00
TermiNexus 14d8c05a2a feat(gen): 二元运算取消 C 式整数提升(任何位宽不再默认提升到 i32),改为按 max(两侧)/目标位宽运算 2026-10-13 16:20:02 +08:00
TermiNexus 97c0a1c6fd fix(gen): 取消「宽整型 -> bool(i1)」的隐式真值归一化,改为 fail-fast(仅保留编译期常量豁免) 2026-10-13 16:07:43 +08:00
TermiNexus d7c5af9b80 fix(gen): callable[[]] 省略返回类型按 void 注册(作为值使用时报错,不再静默按 int) 2026-10-13 13:32:47 +08:00
TermiNexus cb2dc4cb3a feat(gen): 字节序 bswap 改为按目标端序判断(BigEndian/LittleEndian 都相对目标),原语改名 apply_byte_order_bswap 2026-10-13 13:29:03 +08:00
TermiNexus 444e87dd7d fix(gen): callable[[]](省略返回类型)按默认 int(i32) 注册函数指针签名,修间接调用签名缺失 fail-fast 2026-10-13 13:26:51 +08:00
TermiNexus d3997e0746 fix(gen): 函数指针变量别名(f2: rawptr = f)继承已注册的返回类型签名,修 rawptr 间接调用的签名缺失 fail-fast 2026-10-13 13:26:11 +08:00
TermiNexus 32599a08c1 fix(stub): .vpi 读取侧复刻字段标记判定(volatile/pointee-volatile/const/字节序),跨模块不再丢字段标记 2026-10-13 12:30:23 +08:00
TermiNexus 37c4b0da3b feat(gen): 字节序 bswap 代码生成——字段/局部变量/数组元素读写处成对 llvm.bswap(仅 BigEndian 且 16/32/64 位整数) 2026-10-13 12:30:05 +08:00
TermiNexus 5d15d01e21 feat(gen): 支持 t.BigEndian / t.LittleEndian 字节序描述符(注解识别 X|marker 并把字节序登记到 FieldEntry.ByteOrder,暂不生成 bswap) 2026-10-13 12:17:02 +08:00
TermiNexus 3ca2165fd9 feat(gen): 指向同型聚合的指针→同型聚合值允许隐式值拷贝(裸类形参 T* ABI 的直接赋值仍按值拷贝) 2026-10-13 10:30:15 +08:00
TermiNexus b859f8a136 fix(gen): CDefine 常量(含 mod.N 属性形态)拓宽方向单独判定——负值 sext、非负 zext,修 zlib 窗口尺寸被 sext 成负数与 Z_DEFAULT_COMPRESSION->255 2026-10-12 21:48:05 +08:00
TermiNexus 22475e0fda fix(gen): 逃逸检查把模块级(根 scope)变量判为 LIFE_STATIC,修模块级数组 return 误判帧逃逸(pyzlib_error_msg) 2026-10-12 21:23:23 +08:00
TermiNexus c0ff5c9d96 fix(gen): 复合赋值取消「右值有符号 vs 左值无符号」拦截,与二元 x = x op y 口径一致(修 sizet += int 误报) 2026-10-12 21:16:50 +08:00
TermiNexus c9562a6e04 feat(gen): 一元 +/-/~ 按符号性提升——u 类不提升(保持原位宽),i 类提升到 i32 防负数陷阱 2026-10-12 21:10:06 +08:00
TermiNexus d206abf3e1 chore: 全仓同步 w32→win32 模块改名与 range(int(...)) 显式收窄适配 2026-10-12 20:59:11 +08:00
TermiNexus ea2af739dd feat(gen): range 边界与循环变量统一提升到至少 i32(取消 u8/u16 循环变量档),u64 边界保持 i64 2026-10-12 20:57:12 +08:00
TermiNexus 00bf7ebf38 fix(gen): 非负 define 常量按最小位宽的无符号位模式参与宽化/比较(修 range(50000) 迭代 0 次) 2026-10-12 20:52:52 +08:00
TermiNexus e3743b185f fix(gen): zip/enumerate 的 range 源工作位宽随参数(仿 for-range),修 range(3) 被 i64 索引误判窄化 2026-10-12 20:24:50 +08:00
TermiNexus 54b239747e chore(gen): 清理「按值物化」残留注释(裸类形参即 T*,非值语义槽) 2026-10-12 20:03:10 +08:00
TermiNexus ae12ae8581 Revert "feat(gen): 裸类形参按值物化(值语义),调用方 ABI 不变"
This reverts commit 87914b0a51.
2026-10-12 19:56:02 +08:00
TermiNexus 001f8afc37 fix(gen): 常量适配限定为位运算,算术一律按 C 语义拓宽(修 i8 乘法回绕致 memhub 不变式误报) 2026-10-12 19:40:13 +08:00
TermiNexus e7b05c6a3d fix(gen): 三元表达式两支统一按常量无损适配,修 i8 表元素 vs 字符字面量被抬成 i32 的窄化误报 2026-10-12 19:27:58 +08:00
TermiNexus 3d638927d5 fix(gen): 移位的工作位宽不计入计数宽度,修 ushort 表达式被抬成 32 位后赋回 ushort 误报窄化 2026-10-12 19:22:36 +08:00
TermiNexus 4a66aa7fd2 feat(gen): 二元运算采用目标感知工作位宽(仿 C:算到目标位宽;移位只提升左操作数),隐式窄化仍禁止 2026-10-12 18:53:50 +08:00
TermiNexus 9d02cc4684 fix(gen): 移位按 C 语义把左操作数提升到 int,且移位数不参与常量适配(修 uchar<<8 误报) 2026-10-12 18:42:54 +08:00
TermiNexus 65c72fd55c fix(gen): 二元运算按常量无损适配另一侧位宽,修 crc(uint)^0xFFFFFFFF 被抬成 64 位 2026-10-12 18:32:55 +08:00
TermiNexus 8453698f66 chore(gen): ExprGenBase 残留清理(range(int()) 与 TPV->VPC 注释) 2026-10-12 18:25:56 +08:00
TermiNexus 74fafb3b56 fix(gen): 整型字面量宽度按值推导(新增 u32 档)+ 显式宽度严格校验 2026-10-12 18:25:38 +08:00
TermiNexus f044d94e90 fix(gen): -x/~x 对编译期常量折叠,修负字面量被隐式窄化误拦 2026-10-12 18:25:24 +08:00
TermiNexus 87914b0a51 feat(gen): 裸类形参按值物化(值语义),调用方 ABI 不变 2026-10-12 18:25:04 +08:00
TermiNexus d7a5f7e68b fix(gen): 值语义聚合槽豁免改为类型级判定,去掉易漏判的槽身份比较 2026-10-12 16:56:03 +08:00
TermiNexus 7c11c589f7 fix(gen): 值语义聚合槽允许隐式值拷贝,修 self.field = param 的 Foo* -> Foo 误报 2026-10-12 16:42:52 +08:00
TermiNexus 3384941cd1 chore: 改名同步 TransPyV -> ViperC(源码/配置内的纯替换) 2026-10-12 15:06:28 +08:00
TermiNexus d0c7b9c77c chore: 改名同步 TransPyV -> ViperC(.gitignore/文档/脚本) 2026-10-12 15:02:13 +08:00
TermiNexus 9b93586eec docs: README 随改名同步(ViperC/viperc.exe/__vpcbuild__)并纠正过时内容
- w32.* -> win32.*(中文前提条件/对比表、英文 Prerequisites/对比表 共 4 处)
- 规模数字按实测刷新:App 82 文件 63,783 行;Gen 54 文件 47,297 行;
  Phase 6/5,339;Stub 9/6,207;Projectrans 3/712
- 自测套件位置纠正:不在本仓库,位于工作区根 Test\(独立仓库,97 工程 129 个 .vp),
  由 Test\SequentialRegressionTest.py 驱动;删除已不存在的 Test/{App,NegativeTest,Sha1Test} 描述
- 工作区布局补 Test\ 与 exe\,修正 ViperC\ 对齐
2026-10-12 14:48:47 +08:00
TermiNexus 023a22af08 chore: 工程改名 TransPyV -> ViperC(输出 viperc.exe,构建目录 __vpcbuild__) 2026-10-12 14:28:52 +08:00
TermiNexus 4e46a25ab1 fix(gen): define 位宽/整数提升/移位/AugAssign 规则收敛,修 va_list 与 traceback 卡死
- define 位宽:cdefine_decl_bits 单一裁决(注解 → 右侧强转 → 值最小可存宽度),定义方与 VpiReader 两处跨模块注册共用;lookup_cdefine_bits 取最后一条非零记录;存储即截断;使用侧改按声明位宽;通用求值器加编译期除零/移位越界 fail-fast
- 消除对无符号强转加宽方向的依赖:define_min_width 改 (value>>32)==0,截断改左移+算术右移,修 TPC/TPV 判定不一致导致 0x80000000 误判 64 位
- 整数提升:算术/移位、比较、一元 +/-/~ 提升到 i32;位运算保持同宽不提升
- AugAssign:y 有效宽度须 <= x、禁 y 显式有符号而 x 无符号;移位计数静态范围检查
- 函数入口复位 va_list 状态与 willFree 失效槽表(FuncGenDef/FuncGenMixin/VarGenMixin 等)
- BuildPipeline:traceback 逐行扫描改 span 判定,去掉对全文 strlen 的 O(n^2)
2026-10-12 12:37:48 +08:00
TermiNexus be4db7b5d1 fix(narrow): StmtGenMixin exc_code 显式 int(cst_r.int_val)
ast.Constant.int_val 为 int64,exc_code 为 int(并传入 const_int32),改用显式 int(...)
2026-10-11 10:07:37 +08:00
TermiNexus 985983391b fix(narrow): ClassGenBase 方法元数据指针声明 int* 而非 t.CPtr
flags_ptr/flags_ptr2 被显式 (int*)(...) 赋值但声明为 t.CPtr,下标元素被判为 i8,与 i*4 的 32 位步长及位标志语义不符(且 >127 的值会读出符号错误);声明改为 int*
2026-10-11 10:04:19 +08:00
TermiNexus 3b9deb08d8 fix(narrow): ClassGenEnum RenumClassVariantCounts 元素读取改用 int(...)
n_first_v(1029) 与 vcount(1010/1055) 承接 sizet* 元素,原先 64->32 隐式窄化;改用显式 int(...) 保持原截断语义
2026-10-11 10:00:05 +08:00
TermiNexus 7468b007f7 fix(narrow): ClassGenEnum pos_max 加宽为 sizet
pos_max 承接 variant_field_sizes 的 sizet 元素,仅与 sizet 比较并流入 sizet();原 int 触发 64->32 隐式窄化
2026-10-11 09:54:18 +08:00
TermiNexus 3c0d3d13b5 fix(narrow): 清理注解式整数窄化(Converter/TypeGenCType/CallGenLeafKit/ClassGenEnum)
CDefine 查询返回 int64 处按下游用法定型:回传给 int 的用显式 int(...),流入 int64 目标的加宽为 int64;sizet 数组元素赋给 int 的用 int(...)
2026-10-11 09:51:08 +08:00
TermiNexus 966fc3b8f5 fix(narrow): BuildPipeline.ensure_dir 消除隐式窄化(int(path_len) / char(saved))
buf[i] 元素为 i8,saved 由 int 改为 char 并用 char(ch) 显式收窄;plen 由 sizet 显式 int(path_len) 收窄
2026-10-11 09:42:39 +08:00
TermiNexus 77f4a25055 fix(narrow): CallGenFd 的 fd_can 改用 int(len(...)) 消除 64->32 隐式窄化 2026-10-11 09:38:24 +08:00
TermiNexus 82ad5da7c7 fix(narrow): len() 结果赋给 int 的形参/默认值计数改用显式 int(...)
FuncGenDef 的 fd_param_count/fd_default_count/tfd_param_count/tfd_default_count 与 ClassGenVTable 的 md_param_count/md_default_count 原本直接接收 len()(sizet),触发隐式窄化;改 int(len(...)) 保持原有截断语义
2026-10-11 09:34:43 +08:00
TermiNexus d023e9932d fix(narrow): StructGenMixin 守卫候选与归纳区间常量改用 int64,消除 64->32 隐式窄化
kv/lo/hi/k 原本声明为 int 承接 int64(Scratch[...]),触发新禁止的隐式窄化;_AddGuardCand/_PackEscapeClear 的 kv 形参同步拓宽为 int64(打包范围检查需看到完整 64 位值),打包结果处显式 int() 收窄
2026-10-10 22:20:47 +08:00
TermiNexus 41772b4a0c refactor(narrow): 撤销 File.write 实参多余的 ULONG(...) 显式窄化
File.write 形参已改为 sizet,调用点不再需要显式 ULONG 包裹(覆盖 WriteCallSidecar/_phase2_write_cmd/write_ir_to_file/WriteStackMap/TranslateFileGetTrans)
2026-10-10 22:15:49 +08:00
TermiNexus 58951a29a8 fix(narrow): File.write 长度实参加显式 ULONG(...)(避免 64→32 隐式窄化)
fileio.File.write(buf, count: ULONG) 的 count 是 32 位,而实参是 sizet/int64(strlen、pos、ir_len、total 等);规则收紧后为隐式窄化,改为显式 ULONG(...)。本文件其它位置无隐式窄化。
2026-10-10 22:10:01 +08:00
TermiNexus 5bb6538703 feat(types): 禁止隐式窄化(整数 高→低);_tb_read_whole 容量改 sizet
规则收紧(2026-10-10):整数位宽 高→低 隐式窄化一律 fail-fast(CastError),仅两类豁免:① 目标 i1 的真值归一化;② 源是编译期常量且值落进目标位宽。_scalar_convert_reason 的 int↔int 同步改为'目标位宽≥源位宽才 PC_OK'。BuildPipeline._tb_read_whole 去掉 ULONG(sz)+ULONG(2) 的窄化,改 sizet(sz)+2。
2026-10-10 21:57:18 +08:00
TermiNexus 9db3748f68 feat(gen): main 内栈逃逸检查整体豁免(收窄为结构体指针不再采用)
按用户决定:入口 main 的帧生命期=进程生命期,main 内任何栈地址写进长命存储都不报,不再区分结构体/标量。移除上一版为'仅豁免结构体指针'新增的 _TypeIsPtrToStruct/_ExprIsStructPtr,fail_if_life_escapes 只保留 _CurFuncIsEntryMain 短路。嵌套函数不豁免。
2026-10-10 14:46:45 +08:00
TermiNexus 87e0bce5ee feat(gen): 入口 main 内豁免栈上结构体指针的逃逸检查
memhub.MemBuddy(...) 是裸构造(无 __new__),life_of_expr 判定为 LIFE_FRAME;main 里 argparse._mbuddy = mb 把帧内结构体指针写进模块级字段即触发 P3 拦截。入口 main 的帧生命期=进程生命期,其栈上结构体不会被本帧返回后使用,故在 fail_if_life_escapes 放行。仅豁免'结构体指针'(新增 _CurFuncIsEntryMain/_TypeIsPtrToStruct/_ExprIsStructPtr):标量指针(&local、栈数组退让、ret(frame) 返回 int*)仍受检,保持 NewLifetimeFieldEscapeTest / NewLifetimeAnnFieldEscapeTest 等负向工程不回归。
2026-10-10 14:45:07 +08:00
TermiNexus 034293df59 fix(gen): freed_restore/freed_join/freed_capture 补 global 声明,修复野写崩溃
上一提交(eea8111)在 freed_restore 里重新赋值全局指针 _g_freed_slots 却未声明 global,被解析成未初始化的局部变量:is None 判假 → 跳过分配 → 对垃圾地址(0x11)野写,ASan access-violation。为三个新函数统一声明 global _g_freed_slots, _g_freed_count(与 _FreedMark 同口径)。
2026-10-10 14:27:00 +08:00
TermiNexus eea8111e61 fix(gen): willFree 失效槽表补控制流合流,修复分支内 free 泄漏导致的误报
失效表原为平坦单调全局表,无分支合流语义:if 分支体内 free(x) 会把失效态泄漏到合流之后,于是 fileio.vp 的 if ...: free(buf); return None 之后 return buf 误报 LifetimeError(1784eaf 修好 free 的 wf 位后才暴露)。新增 freed_capture/freed_restore/freed_join(pool 快照 + 求交),并在 if(结合 then/else 是否到达合流)与 while/for 的既有 life_* 同点位接入:只有所有到达合流的路径都失效才算失效。
2026-10-10 14:10:53 +08:00
TermiNexus f2cbb658e1 fix(stubgen): vpi 落盘入口统一重建文件级名字窗口,修复 FileFuncNames 悬垂野读崩溃
Phase1 1b-pre / 1b 兜底 / BuildPipeline / Repl 为生成 vpi 各自重新 parse 出独立 AST,但不走 register_file_funcs,导致 is_file_func(range)(_PushInductionEntry)仍读到上一个文件的表 + 已被 reset 的池 → 悬垂指针,ASan access-violation 崩在 HashTable._slot_hash。将 reset_file_name_window + register_file_func_names 收敛到 _GeneratePyiFromAst 唯一入口,覆盖全部 vpi 落盘调用点;Phase2 的重复调用随之移除。
2026-10-10 14:00:41 +08:00
TermiNexus c1d3f62bf0 fix(gen): ret(borrows=...) 支持 0 起序号写法(原来只认形参名)
现场:@life(noescape(a, b), ret(borrows=1)) 报
LifetimeError: @life(ret(borrows=...)) accepts only a parameter name or `none`
—— _RetKindFromTag 的关键字分支只处理 Name(形参名)与 CONST_NONE,遇到整数常量
(形参位置序号)就落到 fail-fast。

修法:Name(形参名 / none)与 Constant(CONST_NONE = 非本帧)先分流,
其余一律交给 _WillFreeArgIndex —— 它本来就同时认"形参名"与"0 起序号"
(与 willFree(...) 同口径,见 includes/stdlib.vp 注释),非法实参在那边 fail-fast。
于是 ret(borrows=x) 与 ret(borrows=0) 等价可用。
2026-10-10 13:27:24 +08:00
TermiNexus 5ec75b2d38 fix(gen): 模块限定函数调用的 ret(borrows=x) 形参位映射错位(A2 跨模块静默失效)
现场:NewLifetimeRetXmodTest 漏检(应拒绝却通过)。诊断(printf 断点,已删)显示
_CallRetKind 完全正确:cls=(none) → 模块分支 → mod_p=retdep2 sha=1 → vpi lookup rk=8。
即契约取到了,错在**下一跳**:life_of_expr 的 borrows 分支用
_CallArgNodeForParam(cl, pi) 把 pi=0 当"接收者"——那是**方法**的语义(形参表含 self)。
`retdep2.IdentBorrow(l)` 是**模块限定函数**(callee 也是 Attribute,但不是方法),
形参 0 就是第一个实参,于是取到了 `retdep2` 这个模块名节点:
life_of_expr(Name retdep2) 查不到变量 → 返回 LIFE_STATIC(≠FRAME)→ 放行。

修法:
- _CallArgNodeForParam 增加 is_method 参数:为 1 时才把 pi=0 映射到接收者、其余偏移一格;
  为 0(模块限定/裸函数)时 pi 直接就是实参下标。
- 新增 _CallIsMethod(trans, cl):接收者是对象变量(_RecvClassName 命中)才算方法调用。
- life_of_expr 的 borrows 分支据此传参。

同模块用例(NewLifetimeRetEscapeTest)与正向用例(NewLifetimeRetOkTest)在此之前"碰巧"
通过/失效——前者走 Name callee 不涉偏移,后者的正例把结果用在本帧内、STATIC 也放行。
2026-10-10 13:00:51 +08:00
TermiNexus 1784eaf906 fix(gen): _WillFreeMergeTag 置位判据写反(is_wf != 0 → == 0),P2 willFree 全面漏检
string.strcmp 相等返回 0,所以 `is_wf != 0` 的含义是"这个标签**不是** willFree"。原来的
`if is_wf != 0: mask |= 1 << idx` 于是:真正标了 willFree 的形参位**永不置位**,而任何别的
标签(noescape/escape/stores/borrows)反而把 willFree 位置 1。

实测证据(本轮编译产物):
- EscapeXmodTest/.tpv_build/.../73b2b3ff01d73ce2.vpi(stdlib)第 9 行
  `def free(p: rawptr) -> t.State: pass` —— 连尾注都没有(wf_mask 为 0 ⇒ 不写 wf=);
- EscapeOkTest/.tpv_build/.../4ccdbfc0e006b3d5.vpi 第 44 行 StoreNS(只标了 noescape)
  却写着 `# wf=1 escn=1`(反向误标)。

后果:跨模块 wf= 通道形同虚设 → 调用点从不"标记实参失效" → P2 全面漏检
(NewLifetimeWillFreeTest / WillFreeKwargTest / WillFreeMethodTest / WillFreeAddrTest 静默通过),
同时 NewLifetimeWillFreeOkTest 是**空过**(根本没有标记发生)。反向误标还可能造成假报。

回归来源:fc797da(A1 把三元 guard 扩成五元时把这行一起改反)。本行判据改为 is_wf == 0,
与同文件 @willFree 兼容分支(strcmp(...) == 0 即相等)的口径一致。
2026-10-10 11:55:57 +08:00
TermiNexus 948e1855a6 fix(gen): 跨模块 vpi 查询只在加载成功时记"已加载"备忘(修漏检回归)
A2 引入的 lookup_ret_kind_via_vpi 会在 life_of_expr(判右值生命期,可能在极早的翻译阶段)
里触发对方模块 vpi 的按需加载,但它沿用 _vpi_escape_packed 的旧写法:**无论加载成功与否**
都把该模块记入 _vpi_escape_module_loaded 备忘。于是当那个时刻 vpi 还没生成/打不开时,
该模块被毒化成"已加载",之后所有共用这条备忘的跨模块查询(逃逸摘要 esc=、willFree wf=)
都跳过真正的加载 → 缓存恒空 → 判据静默失效。

现场:EscapeXmodTest(跨模块 esc)与 NewLifetimeWillFreeTest(跨模块 wf)双双漏检,
即"应拒绝却通过"。同模块用例不受影响,故更隐蔽。

修法:两处加载点都改为 _ensure_vpi_ret_annot_loaded 返回非 0(= 真加载/已缓存)才
_vpi_escape_mark_module;失败直接返回 0,留给后续查询重试。
- lookup_ret_kind_via_vpi(新代码,回归根源)
- _vpi_escape_packed(既有同款隐患,一并修)。
2026-10-10 11:25:43 +08:00
TermiNexus a3a627f513 fix(gen): lambda 返回类型完全跟随 body(整数也跟随,不再一律 i32)
承接上一提交:原来只修正指针/浮点两类,整数仍保留 i32 语义(i64 实参会被 trunc 成
i32,静默有损)。现按用户要求改为**完全跟随 body 的实际类型**:
- func.RetTy = body_val.Ty,然后原样 build_ret,不做任何强转;
- void body(如 lambda: <void 调用>)→ RetTy = Void + build_ret_void
  (原来会因 "a void value cannot take part in any operation" 直接报错)。
副作用(预期内):body 为 bool → i1;body 为 i64/u64/f64 → 同宽返回;指针 → 指针。
调用点从函数签名取返回类型(CallGenMixin 读 local_fn.RetTy),自动适配。
注意:这是 ABI 行为变更,重编后必须回归 ClosureTest / ClosureTest2 / ClosureCallTest。
2026-10-10 10:46:51 +08:00
TermiNexus ea57986312 fix(gen): lambda 提升函数按 body 类型修正返回类型(原先恒 i32)
translate_lambda 第 4 步创建提升函数时只能先写 i32(body 尚未翻译);第 11 步原来把
body 值一律 coerce_to_type 到 i32,于是:
- body 返回指针 → 撞 CastError(implicit pointer-to-integer conversion is forbidden);
- body 返回浮点 → fptosi 成 i32(静默有损)。
现按 body 的实际类型修正 func.RetTy(mlirlite 公开字段;本步之后才发 ret,期间无人读
RetTy;调用点读到的已是修正后的签名):
- 指针   → RetTy = body 类型,直接 ret(不再强转);
- 浮点   → 同上(不再 fptosi);
- 整数/其它 → 保持既有 i32 语义(与"无注解嵌套 def 保持 i32"同口径,避免改动既有行为)。
与嵌套 def 的返回类型缺口同类(见上一提交)。
2026-10-10 10:44:07 +08:00
TermiNexus 5d1e604aac fix(gen): 嵌套 def 的返回类型跟随 -> T 注解(原先写死 i32)
现场:def Inner(p: char*) -> char*: return p 报
CastError: implicit pointer-to-integer conversion is forbidden ...
—— 因为 translate_nested_function_def 把提升后的闭包函数写死成
define i32 @__closure_Inner(i8* %env, i8* %p)(grep 过整个文件,从未读 fd.returns),
return p 于是要把 char* 隐式转 i32。即"嵌套 def 只支持 int 返回",此前被同文件
更早的用例报错掩盖(EscapeMethodTest 的 (A)/(B) 因同命裁剪静音后才露出来)。

改动:
- 提升函数按 fd.returns 解析返回类型(resolve_annotation_type);解析不出 -> fail-fast。
  边界:fd.returns 为 None 或注解是 None / t.CVoid 时**保持 i32**(不改既有 ABI,
  避免打断现有无返回值/无注解的嵌套 def)。
- 隐式收尾的零值改为 ConstZero(ret_ty)(原写死 const_int32(0),签名一变 llc 会拒绝),
  取不到零值则 fail-fast。

已知未覆盖:lambda 的提升路径同样写死 i32(见 FuncGenMixin 中 lambda 分支),
本轮不动(lambda 无返回注解可跟随)。
2026-10-10 10:40:36 +08:00
TermiNexus 41674d125c fix(gen): 修 vpi 生成阶段 is_file_func 读到已回收池的悬垂表(ASan 野读崩溃)
现场:TransPyV.exe(ASan)编译 EscapeOkTest 崩在 Phase A-pre-vpi →
_GeneratePyiFromAst → _PyiFunction → compute_escape_summary → _ScanEscapes →
_PushInductionEntry → is_file_func → HashTable._slot_hash
(access-violation on unknown address 0xffffffffffffffff)。

根因(潜伏已久,非本阶段语义改动引起):
- FileClassNames / FileFuncNames / FileVarNames / FileModuleSha1 是按文件重建的表,
  表与键共用该文件的瞬时池;契约要求跨文件/跨阶段先清空再重建
  (原写在 reset_visible_structs 里那四行)。
- Phase A-pre-vpi 为生成 vpi 会重新 lex/parse 每个文件,但不走 register_file_*、
  也不清表 → _PushInductionEntry 的 is_file_func 只认 range 的判据,落在 Phase1
  最后一个文件的表上,而那个池已被 reset_to(mark1) 回收。非 ASan 下常表现为读到
  0/旧值 的静默错答,ASan 下 0xfd 填充 → 垃圾容量/垃圾 __slots__ → 野读崩溃。

修法:
- 抽出 reset_file_name_window() 作为文件级名字窗口的唯一开启入口(原四行清空搬进去,
  reset_visible_structs 改为调它),并在注释里写死契约。
- 新增轻量 register_file_func_names(pool, tree):只重建本文件顶层函数名(核对过 vpi
  路径只需这一张;FileClassNames/FileModuleSha1/FileVarNames 的读取点全在翻译期)。
- Phase A-pre-vpi 循环里,在 _GeneratePyiFromAst 之前调用
  reset_file_name_window() + register_file_func_names(mb, tree_vp2)。
- register_file_classes / register_file_funcs 改为无条件重建(原来 没有才建 的写法在
  表已被别的阶段建过时会静默复用悬垂表)。
2026-10-10 09:41:15 +08:00
TermiNexus 79d1c86982 fix(gen): return 本帧存储改为声明感知 fail-fast(A2 方案 A)
A2 原本整体移除定义端 return <本帧地址> 的 fail-fast,导致无 @life(ret(...))
声明的函数返回本帧存储时静默通过(调用点只拿到 LIFE_UNKNOWN,悬垂一路无声)。
现改为按被调者自己的 ret 声明判定:
- ret(frame) / ret(borrows=<该形参>) → 放行;
- ret(heap|static|borrows=none) → 报错(声明与返回值矛盾);
- 无声明 → 报错(提示补 @life(ret(frame)) 或 ret(borrows=<param>))。
落点:AssignGenMixin.check_return_frame_decl(判据复用 life_of_expr,target_ty 传
返回类型,与移除前的口径一致);ret_kind 由 StructGenMixin.get_ret_kind_by_func
反查 EscFuncTable → EscDirTable(顶层函数/方法/嵌套 def 一次覆盖,无需猜类名),
当前 FunctionDef 取 StructGenMixin.NllCurFunc。
新检查是旧检查的严格子集 ⇒ 自举无回归。
2026-10-10 09:16:26 +08:00
TermiNexus 6da8e2e095 feat(gen): 落地 @life(ret(...)) 返回值生命期契约(A2)
- GenApi:新增 compute_life_anno / _RetKindFromTag 与 RET_KIND_* 编码,
  提供 get_ret_kind / get_ret_kind_method / lookup_ret_kind_via_vpi;
  _WillFreeMergeTag 不再拦截 ret(改由 compute_life_anno 收集校验)。
- StructGenMixin:EscDirTable 由 3 槽扩到 4 槽(新增 ret_kind 列),
  compute_escape_summary 把契约落在 box[5] 并登记。
- Stub:.vpi 行尾新增 escr=<ret_kind>(Converter 写、VpiReader 读 _g_vpi_esd_r)。
- AssignGenMixin.life_of_expr(Call):@life(ret(...)) 声明优先于任何推断;
  frame/heap/static/borrows=none 直接映射,borrows=x 取对应实参生命期,
  方法调用按接收者静态类型解析类名。
- StmtGenCifMixin:同批移除定义端 return <本帧地址> 的 fail-fast,
  返回值去向改由调用点按被调者契约判定。
2026-10-09 22:58:33 +08:00
TermiNexus 4907e64714 feat(lifetime): _g_new_life 透传——new stack/slife 的构造也算 FRAME(参与构造器同命裁剪)
新增模块级 _g_new_life 与 _g_new_storage 同步维护(set/restore 支持嵌套 ctor),口径与 life_of_expr 的
New 分支一致:stack(含 stack slife)/slife → LIFE_FRAME;heap·memgr + 缺省/hlife → LIFE_HEAP;-1 = 未知。
_translate_struct_ctor 两份副本的 recv_life_ct 改为:裸 T() → FRAME;有 new → 取 _g_new_life。
于是 new stack S(&l) / new heap slife S(&l) 不再误报,new heap hlife S(&l) 仍报。
2026-10-09 22:43:43 +08:00
TermiNexus 953c829e9f feat(lifetime): 构造器路径填 recv_life——裸 T() 构造(入口块 alloca)算 FRAME
_translate_struct_ctor 两份副本都算 recv_life_ct:_g_new_storage == None(无 new 描述符提供存储)→
裸 T() 的存储是入口块 alloca → LIFE_FRAME(参与构造器同命裁剪);非空(存储来自 new …)→ -1 不裁剪。
两处 __init__ 调用点(:171 / :675)均传入。覆盖 CtorHolder(&l) / BoxCtor(&l) 这类裸构造误报。
2026-10-09 22:40:30 +08:00
TermiNexus 357a8bba05 feat(lifetime): 构造器路径同命裁剪的透传管道(recv_life,默认 -1 = 现状不变)
_call_method_on_ptr 新增 recv_life: int = -1(被构造对象的生命期,由调用方 CallGenCtor 算出);
构造器分支在指令最终裁决之后,若 recv_life ∈ {FRAME, VALUE} 则复用 _FrameArgsEscapeClearsMethod
(recv_node=None,接收者已由 recv_life 判定)清"写进形参对象字段"那类位。
下一轮只需在 3 个 __init__ 调用点(CallGenCtor:171/665、ExprGenConst:257)填 recv_life;未填 = 完全现状。
2026-10-09 22:36:09 +08:00
TermiNexus bf5ecd7668 fix(lifetime): 方法路径补上"同命裁剪"(此前缺失)—— h.set_q(l) 不该报
现场:h: S* = new heap slife S()(生命期本就是 FRAME:作用域出口释放)+ l 是栈数组;
h.set_q(l) 却报"escaping method parameter"。根因:同命裁剪只做在模块级函数路径,方法路径一直没接
(此前已明确告知"还差一半")。

新增 CallGenMixin._FrameArgsEscapeClearsMethod:位 0 = 接收者(self)、位 (i+1) = 用户第 i 个实参;
某位可清 ⇔ 该位对应实参是 FRAME/VALUE 且其余全部实参(含接收者)也在本帧。
CallGenCall 方法路径在 refine 之后、指令最终裁决之前套用;硬位仍用 get_param_escapes_hard_method 排除。
构造器路径暂未接(__init__ 的 self 是"正在构造的对象",此处拿不到接收者 AST 变量)。
2026-10-09 22:30:35 +08:00
TermiNexus 6000734704 feat(life): A1——@life 新增 stores(x)/borrows(x)(复用 escape/noescape 契约位);ret/owns 显式报未实现
§14 路线 A 第一步(零语义变化):
- stores(x) 归到"强制逃逸"位、borrows(x) 归到"强制不逃逸"位(同一对契约位,调用点已在最后套用、.vpi 已有 esce=/escn= 通道)
- _WillFreeMergeTag 白名单同步扩 stores/borrows;ret/owns 显式 fail-fast "not implemented yet (planned A2/A3)",不做静默忽略
- 矛盾契约报文改为 escape/stores 与 noescape/borrows 两两对立
2026-10-09 22:14:26 +08:00
TermiNexus 71730d920c chore(dbg): 删除 NLL 临时诊断(§13 已在 EscapeTest 上验证通过)
实测:var=h confined=1,g(l,h) 不再报错;报错前移到真正的泄漏点 `if 1==0 or a == 1: return l`(where=a return value)。
2026-10-09 21:59:48 +08:00
TermiNexus 0f504a9010 chore(dbg): 给 NLL 谓词插桩([NLL-ST] 逐条语句 + [NLL-UNSAFE]/[NLL-MENTION] 拒绝原因),定位后删除 2026-10-09 21:53:44 +08:00
TermiNexus 960ecdfd74 fix(nll): 补 BoolOp/Compare 的字段递归(if 1==0 or a == 1 曾把整条判据否掉)
_init_ast 里 node.children = None 是**默认值**(只有语句体这类显式赋值的才有子树),所以"认不出的表达式
一律保守"这条策略会被任何未列举的表达式种类触发。新用例的 if 条件 BoolOp(values) 与 Compare(left/comparators)
就是这种。补上后 h 应能降级,报错位置回到真正的泄漏点 return l(第 19 行)。
2026-10-09 21:34:17 +08:00
TermiNexus 5a10a2ea10 fix(nll): 谓词改为按字段显式递归(AST 表达式节点不填 children,导致 new stack [2]char 被误判"提及")
根因([NLL-DBG] var=l 已证实):_set_parent_list 只设父指针、不填 children,故 New/Attribute/Call 等表达式节点
的 children 基本都是 None。原实现把"children==None"当作"提及/拿不到子树",于是 k 里第一条
`l = new stack [2]char` 就把整条判据否掉(var=h confined=0)。

现在 _NllMentions / _NllExprUnsafe 均按字段显式递归:Name/Constant/Attribute/Subscript/Call/New/UnaryOp/
BinOp/IfExp/Tuple|List|Set;认不出的种类保守(提及=1 / 不安全=1)。_NllStmts 的"认不出的语句"
分支改为按子节点判(无子节点的 pass 之类放行)。
2026-10-09 21:23:51 +08:00
TermiNexus e82e2ac768 chore(dbg): 加 NLL 判定链临时诊断([NLL-DBG]),定位后删除 2026-10-09 21:05:31 +08:00
TermiNexus 337be883d3 fix(nll): Attribute 节点显式只递归 .value,避免"拿不到子树=算提及"把 stdio.printf(...) 误判成不安全
现场:EscapeTest 的 h = new heap S() 永不降级(g(l,h) 照旧报逃逸)。根因是 _NllMentions 对 Attribute
落进保守分支(children 不含 value 时返回"提及")→ _NllExprUnsafe 把 stdio.printf("%s", h.q) 判为不安全
→ 整个 NLL 判据返回 0。属性名是静态字符串,变量只可能在基对象里,显式递归 .value 即可。
2026-10-09 20:57:31 +08:00
TermiNexus d0ffb094c2 feat(lifetime): §13 NLL——默认生命期的 new heap 证明帧内封闭时降级为帧对象
StructGenMixin: 新增 NllFrameConfined 及遍历器(_NllStmts/_NllExprUnsafe/_NllMentions/_NllInSet/_NllAdd/_NllCalleeEscMask)+
has_param_escapes(区分"有记录=权威不逃逸"与"无记录=判不定");别名集只增不减故单遍即可,if/while/for 全走、
不进嵌套 def/class;认不出的语句"不提及别名才放行";取址/实参位逃逸/return/长命目标一律按外泄拒绝。

CallGenCtor._new_is_nll_demotable:只降级"堆且未显式指定生命期"的 new;AssignGenMixin.propagate_life
判据成立则 life=FRAME(于是调用点同命裁剪放过 g(l,h)),_slife_note_assign 接 target_name、判据成立按托管堆
对象登记 → 复用 slife 的作用域出口释放(手动 free/del 也会被既有检查挡住)。

三条函数体翻译入口记录 NllCurFunc(谓词要遍历当前函数体),与 traceback 推帧同一处。

注:谓词按 §13.2 保守优先,判不定一律维持 HEAP;本提交未编译验证。
2026-10-09 20:49:21 +08:00
TermiNexus 79299ec47d feat(lifetime): 声明式指令契约落 .vpi(esce=/escn=),跨模块调用点也能最终裁决
写侧 Converter:读 compute_escape_summary 落在 box[3]/box[4] 的契约掩码,随函数头行尾写 esce=<强制逃逸位> / escn=<强制不逃逸位>;
只有指令(既无 esc/ret 也无 wf)时也补一个 "# esc=0 ret=0" 注释段——读侧是"见 # 才解析"。

读侧 VpiReader:_cache_vpi_esc_clears_text 同一次行扫描里顺带收 esce=/escn= 进 _g_vpi_esd_*(与 escg 同键 sha1[.Cls].func);
新增 _lookup_vpi_escape_dir(.., noescape);CallGenBase.lookup_escape_dir_via_vpi 包装。

调用点:模块级函数 / 方法 / 构造器三条路径在本文件指令表为空(= 跨模块)时改查对方 .vpi,
仍放在所有推断/回退之后最后套用:esc_mask = (esc_mask | esce) & ~escn。
2026-10-09 20:40:30 +08:00
TermiNexus 2bd37e80e3 feat(lifetime): 方法/构造器调用点也接 @life(escape/noescape) 最终裁决
StructGenMixin: 新增 _DirForceOf / _DirForceMethod(查 "Class.method" 并沿 MRO 回退)与
get_param_escape_force_method / get_param_noescape_force_method;GenApi 加同名委托。

CallGenCall: 构造器分支(esc_mask_ci)与方法分支(esc_mask_mc)在跨模块回退、常量特化之后
最后套用一次 esc_mask = (esc_mask | escape_force) & ~noescape_force,与模块级函数那条口径一致。
2026-10-09 20:34:34 +08:00
TermiNexus fc7c5d5560 feat(lifetime): @life(escape/noescape) 升为调用点最终裁决(契约不可被推断推翻)
StructGenMixin: 新增 EscDirTable(3 槽/项:[名哈希, 强制逃逸位, 强制不逃逸位]),
_RegisterFuncEscapeSummary 把 compute_escape_summary 落在 box[3]/box[4] 的指令掩码单独落表
(与 EscTable 的"推断结果"分开);新增 _DirLookup / get_param_escape_force / get_param_noescape_force。
compute_escape_summary 的指令输出缓冲由 g+68 改为 box[3](同一内存,语义显式化)。

CallGenMixin: 在常量特化、同命裁剪、跨模块回退**全部做完之后**,最后套用一次指令:
esc_mask = (esc_mask | escape_force) & ~noescape_force。这样 escape(...) 不会被"死分支/同命"折掉,
noescape(...) 也不会被任何上游路径重新解释成"逃逸"。
2026-10-09 20:30:03 +08:00
TermiNexus 8881dea2e2 fix(diag): 泄漏点采集改为"确认报错后才跑",并保存/恢复扫描期状态
原先在调用点的位循环里就调 escape_leak_line:该函数会重扫被调者函数体并改 EscConstEnv/EscIndCount,
而循环里可能紧接着发现"该实参其实不是帧生命期"从而不报错 —— 等于白白跑一遍扫描、还留下脏状态。
现在改成把 (被调者, 形参位) 传给检查方,过了 LIFE_FRAME 判定才采集;escape_leak_line 内部
把 EscConstEnv/EscIndCount/EscLeakWant 先存后还。
2026-10-09 19:50:31 +08:00
TermiNexus 2353dca8ec feat(diag): 逃逸类 LifetimeError 追加 File lifetime leak 帧,标出泄漏点
StructGenMixin: 新增 escape_leak_line——错误路径重扫被调者函数体,取指定形参的**首个逃逸点**行号;
_NoteLeak 在三处置位来源采集(字段/全局写、return 形参、传递闭包调用),新增 EscLeakWant/EscLeakLine。

Diag: DiagEntry 增加 LeakFileName/LeakLine + set_leak + emit_leak_frame(标签 File lifetime leak),
打印顺序 = 调用链 → 错误点 → 泄漏点 → 类别: 消息。

TypeGenErr.fatal_error / GenApi.fail_if_stack_addr_escape 增加 leak_file/leak_line 透传;
CallGenMixin 调用点按失败实参位取行号。**仅同模块**:跨模块 EscFuncTable 无 AST → 返回 0,不打该帧。
2026-10-09 19:32:54 +08:00
TermiNexus c98730f717 refactor(diag): 精简 LifetimeError 报错词,去掉冗长从句
逃逸主报文由三段长从句压缩为「frame-local + 悬垂 + 修法」两句:
  stack address escapes: `&x` is frame-local; using it at {where} hands out a dangling pointer
  once this frame returns. Copy the value, or allocate on the heap (`new heap hlife ...`).

同步缩短 where 短语(return value / escaping callee|method parameter / constructor parameter stored
into a field / longer-lived field-element / module-level global)

其余冗长报文一并精简:willFree 失效、slife 手动 free/del、async slife、stack+hlife 矛盾、三处容量上限、
@life 标签实参缺失

测试只断言关键字 LifetimeError,不受文案影响(Test/*.py 已确认)。
2026-10-09 19:22:04 +08:00
TermiNexus 007943142a fix(diag): DiagEntry.File 改名 FileName,避开与 fileio.File 的非限定名相撞
现场:TPC 编译 Diag.vp 时 read_source_line 的 fileio.File(...) 报 llvmlite
"Type of #1 arg mismatch: i8** != %"1f18c0c34662cbfb.File"*"
(1f18c0c34662cbfb = fileio 的模块 sha1,即 self 形参被解析成 i8**,实参本是正确的 fileio.File*)。

Diag.vp 是唯一一个"类字段名 == 同模块 import 的类名"的模块(全仓仅此处有 File: 字段),
而其余 24 个调用 fileio.File(...) 的模块(含 BuildPipeline.vp 的 6 个类)都无此字段且编译正常。
TPC 的 Gen.structs / SymbolTable / _mangle_func_name 均按非限定名索引,故改名规避。
2026-10-09 19:12:10 +08:00
TermiNexus 6769cf0335 refactor(diag): 编译期 traceback 抽为 Diag 模块,单条诊断携带完整调用链
- 新增 Diag.DiagEntry 与 new_entry/begin/set_current/add_frame/emit/free_entry,方向由 DIAG_DIR_* 决定

- TypeGenErr.fatal_error 改走 DiagEntry:填调用链 + 当前帧,emit(UP) 后 free + finish

- 顶层函数体/嵌套函数体/方法体三处翻译入口各 push/pop 一帧(def 位置),报错可回溯所在函数
2026-10-09 18:45:06 +08:00
TermiNexus c791bd5e46 feat(new): new 支持标量/数组与前导 [N]T;类名解析不出时回落基本类型路径
- CallGenCtor._translate_new_primitive:认 Array 元素类型,返回值退化为首元素指针,零初始化按整体类型写
2026-10-09 18:45:01 +08:00
TermiNexus fc797da542 feat(lifetime): @life(escape/noescape) 多形参、硬逃逸位同命精化、归纳变量区间与跨模块常量特化
- GenApi:escape/noescape 标签支持多实参;新增 compute_escape_directives 与若干委托

- StructGenMixin:硬逃逸位(box[2])、常量与归纳变量区间折叠、_PackEscapeClear/compute_escape_clears

- Call 系:调用点同命裁剪(_FrameArgsEscapeClears)+ 跨模块 escg= 查询;写侧 Converter、读侧 VpiReader

- AssignGenMixin:裸 T()(无 new、无 __new__)判为帧生命期
2026-10-09 18:44:51 +08:00
TermiNexus 14a8dcb98b feat(gen): new 支持基本类型 + 跨过程常量特化重算逃逸摘要
- CallGenCtor._translate_new_primitive:
ew [stack|heap|memgr(x)] T(T 为标量)取存储 + 零初始化 → T*;stack 走入口块 alloca、heap 走 malloc、memgr 复用既有通道;标量带实参 fail-fast,不引入第二套标量构造语义

- StructGenMixin:新增 EscFuncTable / EscRefineScratch / EscConstEnv 与常量求值器 _EvalConstInt / _EvalConstCond;_ScanEscapes 的 if 分支按形参常量折叠只扫被走到的分支(死分支里的写不记为逃逸)

- 调用点(普通函数 + 方法,方法沿 MRO 回退)在逃逸检查前用常量实参重算摘要;跨模块拿不到函数体仍保守返回原掩码

- 折叠只会减少逃逸位,不引入新的误报
2026-10-09 14:55:28 +08:00
TermiNexus ab30b264fc feat(gen): P3 覆盖注解形态的属性/下标目标
- translate_annassign 原先对非 Name 目标直接 return 0,obj.f: T = v / rr[i]: T = v 整条语句被静默丢弃(既不报错也不写出)

- 新增 _translate_lvalue_annassign:解析注解(先特化泛型)→ get_attribute_ptr / get_subscript_ptr 求左值指针 → 走同一处 P3 长命左值检查 → RHS 以注解为靶型求值 → coerce_to_type 后写出;const / volatile / f-string 逃逸与普通 Assign 同口径

- 所有失败路径 fail-fast,不再有静默丢语句
2026-10-09 14:55:10 +08:00
TermiNexus d926987d44 chore(dbg): 清除 P4 转移分支的逐点探针
崩点根因已定位(slife 登记表跨文件悬垂),debugger 探针全部移除。
2026-10-09 13:04:40 +08:00
TermiNexus eb0c3dfd31 fix(gen): slife 登记表改为跨文件存活,并修正区间弹回
- _g_slife_owned 原先从 trans.Pool(每文件池)分配,Phase2 每文件 reset_to(mark2) 后表体即悬垂,第二个文件起每次登记都写坏活跃池数据(现场 access-violation on 0x0,崩点随分配漂移、ASan 无法给栈)

- _SlifeRestore 只还了 count、未还 top,top 随函数数量单调增长;上一文件/函数的陈旧条目留在扫描区间内,因每文件池地址复用被误判为新变量,_SlifeShadow 返回悬垂影子槽,_SlifeNoteStore 直接写坏内存

- 新增 _g_slife_save/_g_slife_depth:进入时保存 count/top 两个边界,退出时一起弹回

- _SlifeDropOwnedUnlessSame 补 Pointee==0 守卫(与 _SlifeEmitGuardedFree 同口径)
2026-10-09 13:04:26 +08:00
TermiNexus 1a3ef521b3 chore(dbg): P4 转移分支逐点探针(owned/begin/ensured/drop.begin/dropped)
崩点已后移到 CaseOwnershipTransfer 的 q = p(P4 别名转移),逐点定位该分支内具体崩溃位置。TEMP。
2026-10-09 12:43:59 +08:00
TermiNexus aeb396831a chore(dbg): 清除 CallGenMixin 残留的 call.enter 探针 2026-10-09 12:43:28 +08:00
TermiNexus 6758ecbf69 chore(dbg): 清除最后一处 TEMP 探针(AssignGenMixin slife.note) 2026-10-09 12:03:43 +08:00
TermiNexus 5f92abb7bc fix(gen): 补回 CallGenMixin 的 _FreedMark(修行尾时被 checkout 回滚)
TPC 报 MemberLookupFailFast: 属性 Freed 在 VarEntry 中找不到 —— 该文件的行尾修复用 git checkout 恢复过,把未提交的 _FreedMark 改动一并回滚了,残留 ent_wf.Freed = 1。
2026-10-09 12:03:23 +08:00
TermiNexus dd24dc670f fix(gen): P2 失效状态改存独立槽表,不再给 VarEntry 加字段
现象:任何工程编译都在函数形参注册刚结束处 access-violation(pc=0,逐字节可复现)。定位:逐层探针把崩点压到 FuncGenDef 的 pre_scan 段,而该段对 Check(body 仅 if+return)应为空操作,唯一变量是 VarEntry 这个热结构被加了 Freed 字段。修法:撤销 VarEntry.Freed,改用 VarGenMixin 的独立失效槽表 _g_freed_slots(键 = 变量槽 alloca,整次翻译内唯一,故全局累积、无需按函数 reset/restore);_FreedMark/_FreedCheck/_FreedClearSlot 三个 API 替换字段读写,P2 语义不变。同时清除全部 TEMP 探针。
2026-10-09 11:51:11 +08:00
TermiNexus 95b076581a chore(dbg): pre-scan 与 _SlifeReset 之间补 p1d 探针(TEMP)
p1c 之后仍崩,最后一段只剩 pre-scan 与 _SlifeReset;p1d 把两者分开。
2026-10-09 11:30:33 +08:00
TermiNexus 054dcf0a0b chore(dbg): 形参注册与 pre-scan 之间补 p1c 探针(TEMP)
add_var 三次全过(esz=56),崩点在其后。p1c 夹出 L700-763(状态保存)与 L765-775(pre-scan)两段。
2026-10-09 11:24:29 +08:00
TermiNexus 07ff906338 chore(dbg): add_var 逐行探针 a1..a8(TEMP)
崩点落在形参注册段,而这段里唯一的结构性改动是 VarEntry 加了字段。逐行埋点定位到具体哪一步(esz/cnt 也会打出来,可用于核对 sizeof 是否异常)。
2026-10-09 11:12:31 +08:00
TermiNexus a2e1c651fc fix(gen): 变量表 Freed 不再逐个字段写,统一依赖 init_vars 的整块清零
崩点已定位在普通函数形参注册段(auto_addr 之后、_SlifeReset 之前)。add_var 里对刚加过字段的 VarEntry 末端逐个写 entry.Freed 疑似越界/踩偏移;init_vars 本就 memset 整块,去掉冗余写入。
2026-10-09 11:01:08 +08:00
TermiNexus 9b97470c09 chore(dbg): 函数翻译内段探针 p1/p2/p3
夹住 auto_addr → 形参注册/pre-scan → slife reset → 语句翻译 四段;TEMP,定位后移除。
2026-10-09 10:44:56 +08:00
TermiNexus 20e3f3694f chore(dbg): 补语句级与调用级探针(stmt.kind / call.enter fn)
崩点已收窄到 Check 函数体内的翻译,这两个探针把范围压到具体语句与具体被调者。均为 TEMP,定位后移除。
2026-10-09 10:39:24 +08:00
TermiNexus 400cc6e17b chore(dbg): 再加两个探针(函数名 / slife 赋值 rhs kind)
TR.fn: <name> 定位到具体函数;slife.note rhs_kind=<k> 定位到 _slife_note_assign 的哪条 RHS 路径。均为 TEMP,定位后移除。
2026-10-09 10:19:29 +08:00
TermiNexus be2521fe46 chore(dbg): 临时阶段日志定位 Phase A 崩溃点(定位后移除)
四个探针:PhaseA translating <path> / vpi.gen <rel_path> / TR.enter sha1 / TR.prelude.done。用于把崩溃点夹在 解析→vpi生成→模块前言→函数体翻译 四段之间。
2026-10-09 10:08:43 +08:00
TermiNexus 8b63ae4ca2 fix(gen): 模块前言的声明池逐级判空,避免 PermPool 未就绪时空指针崩溃
现象:TransPyV.exe 编译任何工程都 AddressSanitizer access-violation on address 0x0(pc=0)。根因:_emit_module_prelude 跑在每个模块翻译的开头,此时 CompilerFactory.factory 或其 PermPool 可能尚未就绪,直接 factory.PermPool.alloc(...) 解引用空指针(PermPool 是 factory 的首字段,偏移 0,与 ASan 报的地址 0x0 吻合)。改为 factory / PermPool 逐级判空,不可用时回退模块池。
2026-10-09 09:45:36 +08:00
TermiNexus b71ed43f10 feat(gen): P5 slife 登记表容量提到 256,超限 fail-fast
分配与上限判定共用 _SLIFE_MAX = 256(原 128 且满时静默停止登记)。_SlifeEnsureOwned 超限改为 fatal_error(LifetimeError):静默返回 None 会让该对象永不登记、永不释放(纯泄漏),与编译器自动管理的承诺矛盾。
2026-10-09 08:46:24 +08:00
TermiNexus efb4a54428 fix(gen): P4 自别名重复赋值不得释放共持对象
q = p; q = p 这类自别名重复赋值在覆盖释放时会把两个槽共持的对象提前释放(q 与 p 同时悬垂)。新增 _SlifeDropOwnedUnlessSame:在 IR 里比较 target 与源槽的当前值,相同则跳过整个覆盖释放(含析构调度),不同才走常规保护式释放。P4 转移改用它。
2026-10-09 08:45:09 +08:00
TermiNexus 3209a7a633 feat(gen): P4 别名赋值改为所有权转移(影子标志从源槽移到目标槽)
q = p 时若源槽确实拥有 slife 对象,则把所有权影子标志转移:先按覆盖释放处理目标槽原有对象,再置 q=1 / p=0。于是 q = p; p = None 不再释放对象(q 不悬垂),出口只释放 q 恰好一次。自赋值 p = p 与源不拥有(普通/外来指针)保持原状。
2026-10-09 08:43:48 +08:00
TermiNexus 301a9d3328 feat(gen): P3 左值比右值长命时禁止写入本帧栈地址
新增 AssignGenMixin.life_of_lvalue:Name 目标取变量槽本身的生命期(局部 FRAME / 全局 STATIC),Attribute/Subscript 目标递归其基对象的 life_of_expr(h.p 的生命期 = h 指向的存储)。在 propagate_life 的非 Name 目标出口比较:仅当左值明确长命(LIFE_HEAP/LIFE_STATIC)且右值为 LIFE_FRAME 时报 LifetimeError(复用 fail_if_life_escapes)。h.p = &local 被拒;local_struct.f = &local2 与 p = &local 放行(不误报)。已知缺口:AnnAssign 的属性/下标目标不走该出口。
2026-10-08 22:47:28 +08:00
TermiNexus 2b5ff215c7 fix(gen): 跨模块裸名 CExport/State 函数不再被误清,关键字实参不解析时 fail-fast
现象:stdlib.free(p = p) 生成 llvm.call @free()(零实参),mlir-opt 报 incorrect number of operands (0) for callee。根因:跨模块检查要求函数名前 16 字符等于模块 sha1,而 CExport/t.State 型函数(stdlib.free 等)本来就以裸名定义与声明,于是 found_func 被清成 None → 关键字实参路径整体跳过 → 位置实参循环一个也不翻译。修两处:1) 该检查对 is_cexport_func / _is_state_func_from_vpi 命中的函数放行;2) 带关键字实参却解析不出签名时 fail-fast,不再静默丢弃实参。
2026-10-08 22:38:28 +08:00
TermiNexus 8b2008031d fix(gen): 模块前言 C 库声明名改落 PermPool,避免被回绕的模块池覆盖
现象:@malloc 的声明名被渲染成乱码,mlir-opt 报 llvm.call @malloc does not reference a symbol in the current scope。根因:create_declare 用 _str_dup(pool, name) 把名字拷进传入的池,而前言用的是模块池(大模块上会被复用/回绕),早期分配的声明名被后续分配覆盖;类型是 mlirlite 内部 interned 的,所以只有名字坏、参数类型正常。修法:声明与参数名改落 CompilerFactory.PermPool(never reset)。
2026-10-08 22:30:07 +08:00
TermiNexus 8fb0e38f4c fix(gen): 泛型类构造只在真有 __init__ 时才调 __before_init__/__init__
无方法的泛型类(如 NewTest 的 Box[T])没有 __init__,ClassGenVTable 也就不会生成 __before_init__,但 _translate_generic_ctor_call 无条件发射这两个调用,导致链接期 undefined symbol(1cf2df6387df0bbf.Box[int].__before_init__ / .__init__)。改为与非泛型 _translate_struct_ctor 同判据:有 __init__ 才走 __before_init__ + __init__,否则按字段顺序逐字段 store。
2026-10-08 22:16:23 +08:00
TermiNexus c02aa3a2eb fix(gen): @life/@willFree 不再被当作运行时装饰器
自定义装饰器检测(__decor_wrap_<name> 生成路径)此前把 P2 的编译期标注当成 User 装饰器:会生成 wrapper 调用并不存在的 @life/@willFree,并把被装饰的 extern def(如 stdlib.free)变成本模块 internal 声明,llc 报 invalid linkage for function declaration。把 life/willFree 加入该处标记白名单。
2026-10-08 21:43:47 +08:00
TermiNexus b60ce06335 feat(gen): willFree 覆盖关键字实参、方法调用与取地址三条路径
关键字/混合实参:CallGenMixin._MarkWillFreeKwargs 按形参序(位置实参占前 can 个形参,其余按关键字名匹配)标记。方法调用:CallGenCall._translate_method_call 的 kwargs 与位置两条循环,实参链含 self 故用户第 ai 个实参对应 bit (ai+1);新增 GenApi/StructGenMixin.get_param_willfree_method(查 Class.method 并沿 MRO 回退)。取地址:ExprGenOps 的 AddrOf 分支对已失效变量 fail-fast。
2026-10-08 21:39:53 +08:00
TermiNexus d921dcd9e7 fix(gen): willFree 形参表访问先转 Arguments*,修复 TPC 侧 MemberLookupFailFast
FunctionDef.args 的字段静态类型是 AST*,直接写 fn.args.args 会在 AST 类上查 args(class_members=7 不含 args)。改为先 ast.Arguments*(fn.args) 再取 .args 列表,与 VpiReader / FuncGenMixin / ClassGenVTable 等既有写法一致。
2026-10-08 21:34:14 +08:00
TermiNexus 97313e9e55 feat(gen): P2 生命期标注 @life(willFree(...)) 驱动释放后使用与二次释放检查
标注面:@life(willFree(<形参名|0起序号>)),兼容叠加式 @willFree(...),可混用累加;序号按形参表下标、self/cls 计入。GenApi.compute_willfree_mask 解析标注并对非法标注 fail-fast。本文件表 StructGenMixin.WfTable + 跨模块 .vpi 行尾 wf=<mask>(Converter 写 / VpiReader 读 / CallGenBase 查)。调用点在实参全部翻译之后把对应实参变量槽置 VarEntry.Freed;其后的读取路径报 LifetimeError。重新赋值清除失效态。
2026-10-08 21:28:49 +08:00
TermiNexus 9ff5a3a03e fix(gen): 泛型类构造复用 new 描述符存储,并支持泛型下标 callee
- _translate_generic_ctor_call 之前一律 _alloca_at_entry:new memgr(pool) hlife list[int](...) 会忽略描述符给的内存、把对象构造到栈上、返回栈地址(静默悬垂)。改为与 _translate_struct_ctor 同款:tmp = _g_new_storage,为空才回落 alloca。

- _translate_new_expr 支持泛型下标 callee(Box[int]):新增 _specialize_new_generic_class 先特化模板拿具体类名,再走同一条取存储路径。
2026-10-08 21:04:41 +08:00
TermiNexus b6a9b50f93 fix(gen): 异常与传播出口也释放 slife(P1)
插桩点选在 StmtGenMixin._eh_ret_default —— 异常路径上真正离开函数的唯一汇聚点(translate_raise 的 bare raise / StopIteration / 传播三条分支 + try 的 UnhandledBB 传播分支都走它),发 ret 前 _SlifeEmitFrees。不得改在 raise 语句本身:被本函数 except 捕获时对象仍在用(那一步只 br 到 DispatchBB);EH 返回值也不会是 slife 对象(逃逸检查已拒)。
2026-10-08 21:04:33 +08:00
TermiNexus 70390858d8 feat(gen): 协程体里的 new heap slife fail-fast(释放点无法确定)
AsyncGenMixin 的状态机会把协程 body 拆成多个 state 块并多次转译,编译器无法确定 slife 的释放点;此前是静默漏释放。新增锚点 trans._coro_body_func(Translator 字段 + init 置 None),只在 body 翻译期间由 translate_async_function 设置并保存/恢复旧值;_translate_new_expr 以**函数对象同一性**(_coro_body_func == _cur_func)判定并 fatal_error(LifetimeError)。嵌套普通函数会换掉 _cur_func 故不误报;new stack 与 hlife 不受影响。
2026-10-08 20:42:09 +08:00
TermiNexus 2178970ff7 feat(gen): slife 释放前调度析构 __del__,并禁止手动 del(§3.6 落地)
- 释放统一入口 _SlifeEmitGuardedFree 改为「先析构、再 free」;析构名定案 __del__(对齐 TPC LLVMCG/MemoryOps._emit_local_heap_frees 的释放路径;del obj 那条语句走的仍是 __delete__)。

- 查表沿 MRO / 跨模块 stub 回退(复用 ExprGenOps._resolve_dunder_by_class);类未定义 __del__ 时**不加任何分支**,常见路径零开销。

- 析构用条件分支而不是 select:未拥有的路径必须完全不进析构体(析构体里会解引用 self,传 null 进去会踩空);块标签走 trans._label_counter,避免同函数内重名。

- §2.4 判据5 扩展:对 slife 对象手动 del obj → LifetimeError(否则析构跑两次)。hlife 路径不受影响。

- 为把 trans 传进析构查找,_SlifeEmitFrees / _SlifeEmitGuardedFree / _SlifeDropOwned 加 trans 形参,9 处调用点同步。
2026-10-08 20:34:11 +08:00
TermiNexus 78b2052445 feat(gen): 接线 M3 owned slife 的 RAII 出口释放(函数级 scope guard)
- 所有权影子标志(i8,登记时入口块置 0)+ free(select(影子!=0, load 槽, null)) 保护式释放:赋值在分支/循环里而未执行 → free(NULL);槽里写入外来指针或别名 → 清影子,绝不 free 外来指针;循环反复 new → 赋值前覆盖释放上一轮,不累积。

- 三条函数体转译路径(FuncGenDef 主路径 / FuncGenMixin 嵌套函数提升为 __closure_* / ClassGenVTable 方法体)各自抬升与弹回登记区间;修两处漏 reset 导致内层出口 free 外层对象再被外层 free 一次 = double free(Windows STATUS_HEAP_CORRUPTION)。出口按槽去重做兜底。

- free 由模块前言统一声明(Translator.vp,紧挨 malloc);释放点只查表 + fail-fast,绝不吐指向未声明符号的 llvm.call。

- 赋值处记账:new heap slife → 登记 + 覆盖释放 + 影子置 1;其它 RHS(new stack / hlife / 别名 / 外来指针 / None)→ 已登记则交还所有权。手动 free slife 对象 → LifetimeError。
2026-10-08 20:03:54 +08:00
TermiNexus 934708b206 fix(type): 数组退让豁免提到级数检查之前,修 [N]str 形参被误判 CastError
形参侧 CArray 按 C 语义退让成元素指针 str*(级数 2),实参仍是 [N]str 的数组地址(级数 1)→ _ptr_cast_reason 先查级数就判 PC_DEPTH,_pointee_convert_reason 里那条既有数组退让豁免成了死代码。元素为 i8 时级数不变所以一直没暴露,元素本身是指针才炸(现场:includes/tomllib _parse_key_path 的 segs)。
2026-10-08 20:03:42 +08:00
TermiNexus 1dd53ebff5 feat(gen): M3 owned slife 登记机制(函数级 scope guard 的底座)
登记本函数内由 new heap/memgr slife 直接绑定的变量 alloca,提供 Reset/Register/Owned/MarkAliased/EmitFrees/ReleaseOld 六个 API,供出口插桩与覆盖释放使用。

为什么是函数级而非块级(已核实):Stmt 层(if/while/for/with)不调用 enter_scope/exit_scope,局部存储一律入口块 alloca(函数级寿命)——块级释放会让"块之后仍可达的变量"变悬垂。别名(q = p)在覆盖释放时保守跳过(宁可泄漏不悬垂),函数出口释放不受别名影响。

本提交仅机制,尚未接线(无行为变化);下一步接 4 处钩子:函数入口/收尾、translate_return 的 3 个 build_ret、赋值处的登记与覆盖释放、以及 §4.3 手动 free 判据。
2026-10-08 18:41:52 +08:00
TermiNexus 09b5bf5ac3 docs(gen): _translate_new_expr 注明缺省规则(stack 默认 slife)
缺省规则(§3.2 / D3):来源缺省 = heap;生命期缺省由来源决定——stack → slife(故 new stack T(...) 不写 slife 也等效),heap/memgr → hlife。生命期在 life_of_expr 按同一张表落地(LIFE_FRAME / LIFE_HEAP)。
2026-10-08 18:21:00 +08:00
TermiNexus 9e55e909a8 feat(gen): LifeTag 接到 new 描述符;vpi 文本缓存收敛为单一入口
life_of_expr 新增 ASTKind.New 分支(§2.3 矩阵):stack→LIFE_FRAME;heap/memgr 的 slife 同样按帧生命期(作用域末 RAII 释放,逃逸必悬垂);hlife→LIFE_HEAP。于是 new stack slife Foo() 的结果被赋给变量/返回时会被既有汇点判据 fail_if_life_escapes 拦住(该判据本来就是生命期驱动,无需再加语法特例)。

VpiReader 新增 _cache_vpi_annots_text 单一入口(ret/param/逃逸摘要一次喂齐),两个 vpi 加载点都改调它:从结构上消除"某个加载点漏喂某类缓存"这类静默退化(本次 EscapeXmodTest 漏检的根因类别)。
2026-10-08 18:19:15 +08:00
TermiNexus 8a98051759 fix(gen): new 的分配尺寸常量改用 const_int64,修 mlir-opt 校验失败
ConstInt 的第 4 个参数是常量在 IR 里的文本(不是注释/名字),此前传 "newsize" 会打印成 llvm.mlir.constant(newsize : i64),mlir-opt 报 expected attribute value。已改用项目通用的 mlirlite.const_int64(pool, size_bytes)。
2026-10-08 18:18:03 +08:00
TermiNexus c77d38e923 fix(gen): new heap/memgr 的尺寸改用 ClassGenEnum._get_type_size,修结构体报 cannot determine the size
原先调用 TypeGenResolve.get_type_size_bytes,它只解析 typedef/标量,结构体一律返回 0,于是 new heap Point(3,4) 在语义期 fail-fast。

改用语言级 T.__sizeof__() 的同一实现 ClassGenEnum._get_type_size(struct_ty),保证 new 分配的尺寸与源码手写 __sizeof__() 完全一致(含对齐/填充)。
2026-10-08 18:07:28 +08:00
TermiNexus 14920c0e1e fix(vpi): 逃逸摘要在所有 vpi 加载点都喂缓存,修 EscapeXmodTest 跨模块漏检
_ensure_vpi_ret_annot_loaded 开头有"本模块 ret 注解已缓存则直接 return 1"的短路:若某模块先经 _ParseVpiIntoModuleImpl(stub 注入)加载,而该处只喂了 ret/param 缓存、没喂逃逸缓存,后续跨模块逃逸查询就会永久查空。现场:EscapeXmodTest 漏检——box.vpi 里 `# esc=2 ret=0` 落盘正确,查询侧却命中不到。在该加载点补 _cache_vpi_escape_annots_text。
2026-10-08 17:58:07 +08:00
TermiNexus 64502471e1 feat(gen): M2.4 new 描述符语义落地(stack/heap/memgr 三来源)
CallGenCtor 新增 _translate_new_expr:只替换取存储一步——stack 用帧内 alloca、heap 用 malloc(sizeof)+bitcast、memgr(x) 用 x.alloc(sizeof)+bitcast;其余构造序列(__before_init__ → __init__ / 字段赋值)用合成 Call 复用原路径,因此 __init__ 的逃逸检查对 new 同样生效;结果恒为 T*。

文件级 _g_new_storage 承接描述符算出的存储,两个 ctor 入口的默认 alloca 改为有则复用;_translate_new_expr 前后保存/恢复,支持嵌套 new。

§2.4:stack+hlife 语义矛盾 fail-fast;memgr(x) 拿不到管理器类 fail-fast。ExprGenMixin 分派 ASTKind.New。
2026-10-08 17:45:39 +08:00
TermiNexus cd68bb62ac feat(gen): M2.3 v3 跨模块逃逸摘要 + 全局赋值判据双保险
【v3 跨模块摘要】Converter._PyiFunction 在每个 def 签名行尾写 `# esc=<mask> ret=<ret>`(仅非零时写);VpiReader 新增逃逸摘要缓存(键 {sha1}.{Class}.{method} / {sha1}.{func},值打包 mask|ret<<32),与返回注解缓存同一次加载解析,并加"模块已加载"备忘 + 键前缀过滤避免拖慢编译;StructGenMixin 抽出 compute_escape_summary 供登记与落盘共用;CallGenBase.lookup_escape_via_vpi 按本类 + MRO 祖先模块 sha1 查询,方法/构造器/模块级函数三处调用点在本地表未命中时回退查 vpi。

【修复】Converter._PyiEnsureEscScratch 补 global(漏写导致全局仍 NULL,box 算成 0+520 → ASan WRITE on 0x208);函数内写"本文件已声明的模块全局"且无 global/nonlocal 时必须 fail-fast——原判据只看 SymTab.Root.Vars,部分时序看不到模块全局而静默建局部影子;新增 FileVarNames(register_file_funcs 预扫收集顶层变量名)+ is_file_var() 兜底,普通赋值与注解式重声明两处都接上。
2026-10-08 17:28:06 +08:00
TermiNexus 9f98e683a9 feat(gen): M2.3 覆盖面扩展——类方法与嵌套函数的逃逸摘要
摘要键约定:模块级/嵌套函数用裸名(与调用点 func_name 一致),类方法用 "Class.method",查询沿 MRO 回退;刻意不回退裸名,避免方法名与同名模块函数(error/init 等)串味产生误报。

register_file_funcs 登记类方法与嵌套函数摘要,v2 传递闭包相应覆盖(_RescanEscapes 拆为 _RescanOne/_RescanNested/_RescanClassMethods)。

调用点新增:_translate_method_call 的方法路径检查(形参下标含 self,用户第 ai 个实参对应 bit ai+1)、_call_method_on_ptr 的 __init__ 构造器检查(self.f = p 的逃逸;__new__ 形参可能与 __init__ 共享并裁剪,不查)。

已知边界:摘要表按文件重置,跨模块查询待 v3;_PropagateCallEscapes 只处理裸名被调者,方法调用链的传递待补。
2026-10-08 16:55:34 +08:00
TermiNexus b03e55bb7e fix(gen): 类归属不采信星号导入候选,收口 fatal_error 返回类型与默认值补全
TypeGenResolve._class_true_def_sha1 / CallGenBase._lookup_reexport_from_vpi 新增 allow_star 参数:类归属只采信具名 re-export。此前 `from X import *` 候选被当成类的真实定义模块,而几乎每个文件都 star-import stdint,导致 TypeGenErr 前向声明被建成 "stdint.TypeGenErr";该名字在 StructTable 里只剩一个错误 sha1 的条目,fatal_error 的跨模块返回类型解析全部落空并 fail-fast。

TypeGenErr.fatal_error/fatal_type_error 显式 -> int(留空会解析出退化类型 i0,命中 CallGenCall 的 i0 守卫);_fail_method_ret_ty 增加失败上下文转储并并入同一条 VLogger.error(批处理下 error 即 sys.exit,其后代码全是死代码)。

CallGenMixin 模块级调用:默认值已知但重建不出时 fail-fast,不再静默产出操作数不足的 llvm.call(只会被 MLIR 校验拦下);AssignGenMixin 两处 VLogger.error 补上 category 实参。

M2.3 v1b 收尾:逃逸摘要表与扫描临时区改为只分配一次、传递闭包扫到不动点、u64 改 sizet;VpiReader 不再 free 跨阶段引用的 src_sha1(heap-use-after-free)。
2026-10-08 16:38:35 +08:00
TermiNexus 1fb0f4a4ea feat(gen): 逃逸摘要传递闭包(M2.3 v2)
- walker 认值位置上的调用(Return/Assign/AnnAssign/Expr 的 value 为 Call)。 - 传递规则:g(p,...) 中 g 会逃逸的实参位若绑到本函数形参 p,则 p 也逃逸。 - 注册完再重扫 2 遍(单调 OR-in)。已知:超过 2 层的链仍可能漏,真正收敛需改为扫描到不变为止。
2026-10-08 13:47:54 +08:00
TermiNexus 52fd89decf feat(gen): 调用点逃逸检查(M2.3 v1b 步骤4)
translate_call 里按被调者摘要核对实参:第 k 位为 1(该形参会逃逸/被返回)的形参位上,实参若交出本帧栈地址(&local / 局部数组退让)即报 LifetimeError。此处拿不到被调者形参类型,故传哑指针类型,仅用于让 life_of_expr 判定裸局部数组名处于指针位置=退让。
2026-10-08 13:41:00 +08:00
TermiNexus 4ce577d8fb feat(gen): 函数生命期摘要表 + 逃逸 walker(M2.3 v1b 步骤1-3)
- StructGenMixin:EscTable/EscCount(追加在类末尾,避免偏移漂移);register_file_funcs 每文件重置并逐函数算摘要。 - 逃逸 walker:return p / global 赋值 / obj.f 赋值,递归 if/while/for;不进嵌套 def/class;形参访问逐级显式重转(AST 退化)。 - 表内只存 u64 名哈希(djb2-64),不解引用字符串指针,消除槽内坏指针导致的 strcmp 崩溃。 - 表从 PermPool 分配并 memset。 - GenApi:get_param_escapes/get_ret_borrow 查询入口。调用点检查(步骤4)待接。
2026-10-08 13:29:28 +08:00
TermiNexus a6e1957768 feat(gen): 控制流汇合 Meet(M2.2)
- VarGenMixin:life_capture/life_restore/life_join(作用域链快照,池上 [count,(entry,life)…] 数组)。 - StmtGenCifMixin.translate_if:进分支前快照、then 末快照、else 前复位、出口按 min 合并。 - StmtGenMixin:while/for 统一在语句分派处 capture→run→join(一处覆盖全部迭代形态与解糖,含 0 次迭代路径)。
2026-10-08 12:35:59 +08:00
TermiNexus ec47401151 feat(gen): 生命期标签(LifeTag)与借用传播(M2.1)
- GenTypes:新增 define LIFE_VALUE/FRAME/HEAP/STATIC/UNKNOWN 与 VarEntry.Life。 - VarGenMixin:set_var_life/get_var_life/life_meet;add_var 初始化 Life=UNKNOWN。 - AssignGenMixin:life_of_expr(&local、数组退让、变量 Life 读取)、propagate_life(赋值传播)、fail_if_life_escapes(汇点检查,LifetimeError);在 AnnAssign 与单 Name Assign 处挂传播。 - GenApi.fail_if_stack_addr_escape 改为薄壳并委托 fail_if_life_escapes;原 (a)(b) 语法判据并入 life_of_expr。于是 s: str = l; return s 被拦截。
2026-10-08 12:22:10 +08:00
TermiNexus 7070bc1608 feat(gen): 栈地址逃逸检查(LifetimeError)、按值返回聚合、标量→聚合 fail-fast
- GenApi.fail_if_stack_addr_escape:禁止把本帧栈地址写出帧外(显式 &local,以及局部数组名退让到指针目标,如 return arr with '-> char*');裸聚合名 + 同型聚合目标是值拷贝,不在其列;err_type 记为 LifetimeError。 - StmtGenCifMixin.translate_return:按值返回聚合时,对被指类型与返回聚合类型同一的指针补 load(与多返回值 struct 的 ret 同形),修复 return 局部数组报 CastError。 - ExprGenBase.coerce_to_type:聚合目标不接受标量(旧行为落成 store i32 0, ptr,只写首元素字节并抬高对齐),改为编译期 fail-fast。
2026-10-08 12:10:13 +08:00
TermiNexus 2ab6342d3c fix(gen): arg 糖支持转发形态的 va_list
_translate_arg_sugar 原本在 trans._va_list_ptr 为 None 时(非变参的 vprintf 式包装函数,如 vsnprintf(buf,size,fmt,args))直接返回 0,转发下来的 va_list 因此读不到;现增加回退:按名 args 取形参 alloca 并 load 出槽内的 va_list 指针(自身变参形态仍用 trans._va_list_ptr,不 load)。点号形式 stdarg.arg(...) 经 ExprGenBase.get_func_name 取属性短名,已天然命中同一分派,未改动。
2026-10-07 20:15:40 +08:00
TermiNexus c20bd4734b feat(gen): 值引用自动取址接入虚分派/构造/属性/静态等全部方法调用路径
CallGenBase 新增 _vpi_auto_addr_mode(把原先埋在 _coerce_args_to_def_params 里的 vpi 注解解析抽成唯一实现,两处共用)与 _apply_auto_addr_to_call_args(同模块先查 AutoAddrModes,跨模块回退 vpi 注解)。接入原先完全不查表的路径:虚分派(原在 vpi 取址前 return)、_call_method_on_ptr 覆盖的构造 __new__/__init__(含泛型)与 dunder 分派、属性 getter/setter/deleter、静态/类方法、super()、FakeDuck;属性 setter 由 AssignGenMixin 传入 rhs AST 以支持 &T 取左值地址。
2026-10-07 19:59:52 +08:00
TermiNexus 5c1b882169 chore(gen): c.CIf 家族移除报错覆盖 CElif/CElse/CEndif,注释口径统一为 cif
ExprGenName / CallGenDispatch 的「已删除拼写」守卫原本只认 CIf/CIfdef/CIfndef,现补齐 CElif/CElse/CEndif 六个(与 TPC 侧一致),报文同步;StmtGenCifMixin 的头注释、docstring 与状态注释把语句统一称作 cif,内部标记仍写作 c.@CIf 族。
2026-10-07 19:10:05 +08:00
TermiNexus 2fb648d68a feat(lang): 新增 cif/celif/celse 条件编译与值引用注解 &T/&temp T,assert 改译 testcheck.check
cif 三件套:StmtGenCifMixin 负责常量求值(含 defined(模块宏) 的 Attribute 形式),链式错配与含运行时变量一律 fail-fast;删掉 c.CIf 家族。值引用:TypeGenResolve 让 CRef/CTempRef 与 t.CPtr 走同一解析路径(&void 折叠为 i8*,删除 t.CNeedPtr),CallGenBase 在调用点按形参模式自动取址(ref 只接受左值,右值 fail-fast;temp 物化临时栈),跨模块经 VpiReader/StubMergerBuild 传递模式。assert:StmtGenMixin 合成 testcheck.check(cond, None, msg),Translator 检测到 assert 时自动注入 testcheck 导入(带去重与防环),深递归守卫改用 stdio.printf + stdlib.exit(Translator 无 Gen 成员)。
2026-10-07 19:03:16 +08:00
TermiNexus ce50daa87a refactor(build): 构建产物目录去掉 _mlir 后缀(temp/output)
TempDir/OutputDir 回归 {BuildDir}/temp 与 {BuildDir}/output,与 TPC 及项目脚本预期一致;mlir_dir_suffix() 仅保留给 includes 二进制缓存(其布局为 sha1 子目录,与 TPC 的扁平 obj 缓存不同,合并会混淆)。
2026-10-07 19:03:02 +08:00
TermiNexus 3b4bd1c6dc fix(gen): 跨模块函数作值时不再伪造同名外部全局
`serial.puts` 这类模块属性指向的是函数(elf.vp 的 _resolve_symbol 返回它作
函数指针),属性兜底路径却为它造了一个 llvm.mlir.global external @<sha1>.puts,
与已有的 llvm.func @<sha1>.puts 同名,mlir-opt 报 redefinition of symbol named。
改为在伪造之前先精确匹配函数符号(目标模块 sha1 前缀过的函数名与 Function.Name
逐字符相等,避免误命中他模块同名函数),命中则返回函数符号引用本身
(@<mangled>,类型 Ptr(Func),与同模块 &f 分支同一形态),并让属性读路径跳过
对该值的 load(函数地址即值,load 会去读函数体)。
EOF
2026-10-07 15:59:54 +08:00
TermiNexus edca6c3760 chore(detsite): 类型文本 4096 缓冲补插桩
19 处 TypePrint(..., 4096, ...) 对应的 pool.alloc(4096) 全部改用 alloc_tagged
(AssignGenMixin/ClassGenVTable/FuncGenDef/ExprGenBase/ExprGenCdef/CallGenLeafKit/
TypeGenCType/TypeGenResolve/CallGenCBuiltin/ExprGenOps),tag 33..51 见 memhub.vp。
detection==0 时 alloc_tagged 直接转发,无布局与性能变化。
2026-10-07 15:16:52 +08:00
TermiNexus f3628a797a fix(global): 修复存储结构体字段零值文本的池缓冲溢出
_stg_field_zero_text 分配 160 字节,却用 snprintf(buf, 576, ...) 写出,长类型文本
(如 !llvm.array<8 x i8> zeroinitializer)可越界 415 字节,写穿相邻空闲块的元数据,
触发 [MEM] _fl_pop / [FREEFRAME] 空闲块被写穿(内核编译在 PhaseA 45/50 处中断)。
按实际类型文本长度分配(strlen+24),并把 snprintf 上限与分配对齐。

本文件同时含 3 处 DETSITE 插桩(elem_ty_str_n / ty_str / ty_buf_v)。
2026-10-07 15:16:47 +08:00
TermiNexus 12b15958c6 feat(global): 支持模块级符号地址初始化器
新增 init_kind=5(符号地址):分类阶段识别 UnaryOp(AddrOf, Name),不再把它
误送进整数常量提取器;发射阶段取模块作用域里该全局的引用文本(@mangled /
@mangled 带引号两种形式)作为初始化器,MLIR 层渲染为 llvm.mlir.addressof。
类型/操作数/引用目标不合法时按 fail-fast 报错。

复现:_v: int = 1 / _p: rawptr = &_v
      修复前报 CDefine value extraction failed(该分支原假设所有 UnaryOp 都是负数/取反字面量)
2026-10-07 14:54:44 +08:00
TermiNexus 5993affd9c fix(type): const 等限定联合不再被误判为 str*/bytes*
_is_ptr_element_annotation 原先只要注解联合里出现 str/bytes 就返回 1(按 i8** 处理,
8 字节元素步长),于是 const str(t.CConst | str,实际仍是 i8*)的下标取到的是指针。
现在要求联合必须带 CPtr 标记(真正的 str*/bytes*)才判定;模块级全局的同一处逻辑
(TranslatorGenMixin)同步收紧。新增 _annotation_has_cptr_marker 供两处复用。

复现:def F(p: str) -> int: / path: const str = p / ch: char = path[i]
      -> CastError 指针转整数(形参位置的 const str 一直正常,仅局部/全局错)
2026-10-07 14:35:25 +08:00
TermiNexus fb3d3787e6 refactor(build): linker.cmd 由绝对路径改为裸名,--asan 提示文案去硬编码
- project.vpj / repl.vpj 的 linker.cmd 改为 clang++.exe,交由 PATH 解析

- main.vp 的 --asan 运行库提示改用占位符路径,并说明 clang64 根目录由 PATH 自动探测
2026-10-07 11:25:38 +08:00
TermiNexus 36d23d2f42 feat(build): 实现 Qt 运行时依赖部署(runtime.qt6_path / qt6_dlls / qt6_plugins)
背景:project.vpj 里的 runtime 块此前只是声明(编译器不读取),Qr 程序运行时报 Could not find the Qt platform plugin "windows"。

实现:链接成功后(Phase2 的 PhaseC 成功点)调用 BuildPipeline.deploy_runtime_deps(exe_path)——把 qt6_dlls 各项从 <qt6_path>/bin 复制到 exe 同目录;把新增的可选字段 qt6_plugins 各项从 <qt6_path>/plugins 复制到 <exe 目录>/<相对路径>(父目录不存在则 mkdir)。Config.vp 新增 Qt6Path/Qt6Dlls/Qt6Plugins 读取(口径同 LinkerStaticLibs)。

fail-fast:声明了但源文件不存在 / 复制失败 / mkdir 失败 => VLogger.error 并中止构建;三字段全未声明则整段跳过。

验证:重编 SUCCESS(210.11s);ViperMNIST 产物含 7 个 Qt6*.dll + platforms/qwindows.dll,实跑 stderr 无插件报错;ViperAIChat / ViperQtHelloWorld 同样 OK 并完成部署。
2026-10-07 09:08:35 +08:00
TermiNexus 848dc0ab38 fix(compiler): 跨模块调用补全 int→ptr 转换与别名默认参数;类型文本缓冲 512→4096
1) CallGenMixin.vp(跨模块 extern 实参提升):指针目标形参改为无条件走 coerce_to_type——原守卫按整数位宽比较,而指针类型的 bits 恒为 0,整段提升被跳过,致 VirtualAlloc(0,...) 以 i32 传入指针形参、mlir-opt 报 operand type mismatch(ViperAIChat 失败)。

2) CallGenMixin.vp(默认参数限定键):改用 StructGen._resolve_module_sha1 解析模块 SHA——原只精确匹配模块名,import x as y 这类别名取不到真实 SHA,默认值表落空、跨模块调用实参数目不符(ViperRequestsTest 失败)。

3) 11 个文件的 TypePrint 类型文本缓冲 512→4096(分配与传入 size 成对修改):48 字段类(如 MNIST 的 Net)的 MLIR 结构体类型文本超过 512 被截断并 fail-fast。
2026-10-07 08:48:09 +08:00
TermiNexus 7a9bc5ae75 chore(messages): TransPyV 运行时中文字符串英文化
把 TransPyV/App 下 57 个文件的运行时中文字符串(编译器报错/告警/日志、CLI 帮助与横幅、REPL 提示、Phase1/Phase2 流程日志、Codegen 各 Mixin 的类型与调用报错、Stub/Vpi 诊断等)共 1091 行译为英文。

口径:只改字符串字面量内部文字,行数零变化(+1092/-1092);printf 格式符与转义序列多重集合校验不变(_strcheck 异常 0);译文不引入撇号以免干扰单引号字面量解析。验证:三重重编 SUCCESS(136.20s)+ 全量 77/77 顺序回归通过 + VET 编译 SUCCESS / --search-paths 7 条 exit 0;新产物 help/日志输出已为英文。
2026-10-07 00:03:58 +08:00
TermiNexus 0cf80632c1 feat(gen): typedef 源文本归一 C 式 const/T*(R16)
TPV 的 typedef 注册走裸源码扫描,需与 TPC 同形:_parse_typedef_line 把右值的前导 const/volatile 归一为 t.CConst | / t.CVolatile |、末尾 * 归一为 | t.CPtr;register_ctypedef_from_source 的缓冲 320->512 以容纳展开后的文本。
2026-10-05 20:36:40 +08:00
TermiNexus 374921623b fix(gen): 补全新语法 &/* 的取地址与解引用路径
(*p).attr 属性基址:_get_cderef_attr_base 增加 UnaryOp(Deref) 分支(读写都要求 GEP 指针本身,而非加载后的结构体值)。_is_addrlike_call 增加 AddrOf/Deref 分支:指针型右值不应走模块全局的『静态存储对象拷贝』分支。
2026-10-05 19:58:45 +08:00
TermiNexus df2de5ecfa refactor: 去 t.CPtr(&a) 冗余包装(R15)
TransPyV 4 文件 / 7 行:Phase/BuildPipeline.vp、core/Gen/AssignGenMixin.vp、core/Gen/TranslatorGenMixin.vp。验证:自举三重重编 SUCCESS + 全量 77 项顺序回归通过。
2026-10-05 17:55:52 +08:00
TermiNexus cd55a7e214 refactor: rawptr[T] -> T*(R14)+ 修 REnum 前向占位未就地填充
R14:rawptr[X] -> X*(56 文件 / 834 行改动);保留 6 处 rawptr[t.CPtr](存纯裸指针的规范例外)。 修复:class X(t.REnum) 的变体字段写成自引用 X* 时,字段解析在变体扫描期经 ensure_forward_class 建了一个 opaque 前向占位;末尾 register_struct 按 Ty 指针去重会再建第二个同名 entry,而 find_struct 取首个(占位,0 字段)→ X.__sizeof__() == 0,报「已注册但大小为 0(字段链表未填充)」。改为注册前若命中同模块前向占位则就地填充(与 ensure_forward_class 文档口径一致)。TPC 无此问题,属 TPV 侧缺口。 验证:自举三重重编 SUCCESS;全量 77/77 顺序回归通过(修复前卡在 LLvmLiteTest)。
2026-10-05 17:38:28 +08:00
TermiNexus aeb4e8e54c refactor(gen): R12 收尾同步——12 处计数 while 循环改 for range
与 includes 同步:<= 条件转 range(expr + 1),并放宽初始化行的紧贴要求(同块内最近一次写即为初始化即可)。其余保守守卫不变。

验证:bootstrap 三重重编 SUCCESS;全量 77/77 顺序回归通过。
2026-10-05 16:36:36 +08:00
TermiNexus fb53f88893 refactor(gen): R12/R13 同步——下标 while 循环改 for range、去 c.Set
与 includes 同步执行两批源改写:R12 把「i: int = 0 与 while i < N: … i += 1」形式的计数循环改写为「for i in range(N):」;R13 把 c.Set(*p, v) 迁移为 *p = v、c.Set(a, b) 迁移为 a = b(c.Set 是正在退化的路径)。

验证:bootstrap 三重重编 SUCCESS(无 vast_c 解析回退);全量 77/77 顺序回归通过(WebTest 首轮网络超时,单独重跑通过)。
2026-10-05 15:33:58 +08:00
TermiNexus a7b8118ce8 fix(call): c.Set 支持 Starred 解引用目标(*p 在实参位被解析为 Starred)
新规范把 c.Set(c.Deref(p), v) 改写成 c.Set(*p, v),而实参位的 '*' 由 ast/parser.vp 解析为 Starred(value=p, ctx=Load),translate_c_set 只认 c.Deref/Call 与 Name 形态 ⇒ 整条 store 被静默丢弃。

后果:zlib 侧 includes 全部 47 处 c.Set(*out_len/*out/*length/*cap/*buf/*best_dist/*p_done, ...) 失效 —— 压缩结果长度恒为 0(decompress 报 Z_DATA_ERROR -3),ZlibTest 运行期 0xC0000005。现按 UnaryOp/Deref 与 Starred 两种形态统一走解引用写入,并对非指针目标 fail-fast。
2026-10-05 14:35:50 +08:00
TermiNexus 87b24c2445 fix(type): resolve_typedef_or_map 补齐裸内建别名,修复 int(x, t.CPtr) 类型解析
裸名 str/bytes/int/char/bool/float/double/void 在 map_t_type 中只有 t.CXXX 拼写,导致 _tc_h4 的双参指针包装分支拿不到 bare_ty,落回外部函数返回类型兜底并 fail-fast(argparse.vp:93 c.Deref(int(uint64(ptr), t.CPtr)) 报 TypeError: int)。
2026-10-05 14:14:29 +08:00
TermiNexus 0f4aeeceff docs(tpv): R11 注释与 docstring 英文化并压缩到 2 行内
按 wiki/新规范:注释只讲作用、不掺杂历史原因、避免超长、尽可能英文(含三引号 __doc__)。全文 79 个 .vp:# 注释 16269 行(中文 13808)与 docstring(中文 1381 处)全部改英文;超 2 行的注释块(2093)与 docstring(397)全部压到 2 行。运行时可见文本未动(fatal_error/VLogger 的中文报错文案、stdio.printf 的横幅格式串)。校验:屏蔽注释与三引号后逐行比对各文件代码骨架,0 漂移。验证:自举重建 153.48s 通过(跨模块内联完成);TPV 顺序回归 77/77 全通过。
2026-10-05 13:38:22 +08:00
TermiNexus a2874a14e6 chore(tpv): 移除 --allow-multiple-definition 兜底并同步缺省链接参数
main.vp 的缺省 linker flags 与 ViperBuild 内置 _c_linker 默认值去掉 -Wl,--allow-multiple-definition,与 project.vpj 同口径;Phase2 三处引用该 flag 的注释改为描述链接器选 main / 链接前按路径去重的实际机制。重建验证:跨模块内联优化完成、链接成功 139.17s。
2026-10-05 09:22:58 +08:00
TermiNexus e79ea010b0 fix(tpv): 消除 PYI_BUF_SIZE 跨模块重复全局并去掉 --allow-multiple-definition 兜底
Phase1 与 BuildPipeline 各自把 PYI_BUF_SIZE 声明为模块级全局,模块级全局走裸名符号(类 C extern 全局语义),两模块各发一份强定义 @PYI_BUF_SIZE,llvm-link 报 symbol multiply defined 并回退到单独编译。改为编译期宏 define PYI_BUF_SIZE = 262144(与同文件 define SRC_BUF_SIZE 同口径,不产生符号),同步更新引用该符号的非 CDefine 示例注释。project.vpj 移除 -Wl,--allow-multiple-definition 兜底。验证:跨模块内联优化完成,最终链接无该 flag 成功(161.70s);TransPyV 自举产物顺序回归 77/77 全通过。
2026-10-05 09:16:12 +08:00
TermiNexus f2ef1bb240 refactor(tpv): 新语法 R10 括号内隐式续行并一行(1140 处 / 67 文件) 2026-10-05 07:14:14 +08:00
TermiNexus 4a685d478f refactor(tpv): 新语法 R10 单语句块并一行(7722 处 if/for/while 单语句体) 2026-10-05 03:10:55 +08:00
TermiNexus f923dc4d8d chore(tpv): 删除已废弃的 Test 目录(回归测试保留在仓库外 Test/) 2026-10-04 23:10:44 +08:00
TermiNexus 30a3da4cd4 refactor(tpv): 新语法 R10(反斜杠续行合并)+ 回退 R5(语义不等价)
- 反斜杠续行合并为单行(304 处),呼应"尽可能不换行"。
- 回退 c.DerefAs(p, v) → *p = v 两处(StructGenMixin):原写法是 bitcast 后
  store 指针位,新写法按 pointee 值类型赋值,类型不符(i8* → List 值),
  语义不等价,保留 c.DerefAs。

种子自举通过。
2026-10-04 22:56:27 +08:00
TermiNexus 185a2196ca refactor(tpv): 迁移新语法 R1/R2/R7/R8/R3/R4/R5(指针 T*、短别名、define、&/*、__asm__)
按 wiki/新规范:
- T | t.CPtr → T*(无 | 标准)
- t.CInt/t.CUInt64T/t.CChar/t.CDouble 等 → int/uint64/char/double 等短别名
- X: t.CDefine = Y → define X = Y
- c.Asm → __asm__,c.AsmInp/c.AsmOut → asminp/asmout,t.ASM_DESCR. → ASM_DESCR.
- c.Addr(x) → &x,c.Deref(p) → *p,c.DerefAs(p, v) → *p = v

种子自举通过(96s)。
2026-10-04 22:49:01 +08:00
TermiNexus 9717ce0107 feat(traceback): 运行期调用栈显示 callsite 行号(编译器旁文件 + 链接后 objdump 归一)
MLIR 后端下 mlirlite 只记录 DI 不输出、且 mlir-translate 不产 !dbg(实测),
导致我方 .vp 函数在最终 exe 无任何 .debug_line(<exe>.stackmap 只剩 mingw CRT
的 C 源记录)→ 用户代码帧只能显示 line 0。

改为 Route A:--traceback 时编译器记录每函数(F)与每次调用(C,含调用点行),
写 <sha1>.vpcall;链接后 WriteStackMap 用 llvm-objdump 反汇编,把 callq 的
返回地址 RVA 按 (调用方,被调方,出现序号) 匹配到调用点行,并在函数入口写定义行
兜底;合并进现有 .stackmap 格式。运行期 traceback.vp 不改。

验收:PtrOpProbe --traceback 生成 427 条我方记录(抽查 main 的 malloc/printf
等行号与源码逐一吻合);非 --traceback 构建零旁文件、产物与运行结果不变。
2026-10-04 22:38:02 +08:00
TermiNexus 4236127956 fix(tpv): f-string 隐式 snprintf 声明形参 buf/fmt 用 i8* 而非裸 i8
f-string 降级为 snprintf 时按"隐式提供"原则在使用点补 declare,但三段固定
参数里的 buf/fmt 误写成数组元素类型 i8(应为指针 i8*),与调用侧传入的
buf_ptr/fmt_ptr(i8*)不匹配 → MLIR 'llvm.call' operand type mismatch
'!llvm.ptr' != 'i8'(ZlibDbg 编译失败根因)。改为 Ptr(i8)。
2026-10-04 20:51:38 +08:00
TermiNexus 68cd5f2f90 fix(tpv): __new__ 截断条件放宽到 nn_pc>=1(源侧 __new__ 可无参数)
OopInheritTest 报:
  error: 'llvm.call' op incorrect number of operands (4) for callee (expecting: 1)
      %16 = llvm.call @"...Vec3.__new__(%0, %5, %10, %15) : (!llvm.ptr, f64, f64, f64) -> !llvm.ptr

源码 `def __new__() -> 'Vec3' | CPtr:` 无参数(TransPyV 只注入 self),
__init__(self, x, y, z) 才收 x/y/z。上一提交的截断条件写成 nn_pc > 1,
nn_pc == 1 时被跳过 ⇒ 仍多传 3 个。放宽为 nn_pc >= 1(裁到只传 self)。

验证(均已跑通):
- OopInheritTest:PhaseB 编译完成 → PhaseC 产出 OopInheritTest.exe → 完成
- SlotsBugTest:同上(无回归)
2026-10-04 20:15:37 +08:00
TermiNexus 58cfe2346e fix(tpv): __new__ 调用按被调方签名截掉多余尾参
mlir-opt 报:
  error: 'llvm.call' op incorrect number of operands (8) for callee (expecting: 7)
      %245 = llvm.call @a959d1abdb31a471.FunctionDef.__new__(%35, %239, %240,
             %241, %242, %243, %is_async, %244) : (...) -> !llvm.ptr

定义/声明侧(ast/stmts.vp vpi 核实):
  FunctionDef.__new__(self, pool, name, args, decorator_list, returns, is_async)
      = 7 个形参(非-self 6)
  FunctionDef.__init__(..., is_async, type_params = None)
      = 8 个形参(非-self 7,多出的 type_params 有默认值)

根因:构造器路径把 ctor 实参**全量**转发给 __new__,而 __new__ 的签名比
__init__ 短(无 type_params)⇒ 调用点传了 7 个非-self 实参,声明只收 6 个。
(临时探针实测:mdf_param_count=6 正确、封参逻辑未触发,extra_count=7 才是不匹配源。)

修复:_call_method_on_ptr 中对 method_name == "__new__" 且已找到 declare 时,按被调方
非-self 形参数截掉多余**尾参**(这些尾参 __new__ 本就不接收,语义上由 __init__
消费;__new__ 只负责分配存储)。探针已删除。

验证:python ./TransPyC/Projectrans.py --project ./TransPyV/project.vpj --clean
      --rebuild-includes --clear-cache 重编通过;
      ./TransPyV/output/TransPyV.exe --project ./Test/SlotsBugTest/project.json
      PhaseB 编译完成 → PhaseC 产出 SlotsBugTest.exe → TransPyV 完成。
2026-10-04 20:09:21 +08:00
TermiNexus 6a6cae2a44 fix(tpv): 模块限定调用优先用 vpi 定义侧形参类型,Win32 白名单降为兜底
mlir-opt 报:
  error: 'llvm.call' op operand type mismatch for operand 2: 'i64' != 'i32'
      %5 = llvm.call @VirtualAlloc(%c6, %2, %3, %4) : (!llvm.ptr, i64, i64, i64) -> !llvm.ptr

声明(真值):extern def VirtualAlloc(VOIDPTR, sizet, ULONG, ULONG) -> VOIDPTR
           ⇒ llvm.func @VirtualAlloc(!llvm.ptr, i64, i32, i32)

根因:调用点 found_func 为 None 时用 `_get_external_func_param_ty` 硬编码白名单,
它把 Win32 API 的**所有**非指针参数一律当 i64(注释依据"DWORD/ULONG 在 stubs 里
被映射为 i64"的旧口径),而现实中 ULONG → i32 ⇒ 字面量被 sext 到 i64,与声明
i32 不匹配。

修复:CallGenMixin 的参数 coerce 分支(found_func is None)改为**模块限定调用
先用定义侧 vpi 形参注解**(`_resolve_vpi_param_annot_seg`,模块级函数段下标 ==
形参下标)取真实类型;拿不到注解才回退白名单。CallGenLib 的该函数补注释,
标明它是**兜底专用**且 Win32 口径已过时。
2026-10-04 18:17:51 +08:00
TermiNexus b7e4861e6c fix(tpv): vpi 返回注解纯 marker(t.State 等)解析为 void,修 void 调用被当 i32
调用侧 `_resolve_vpi_ret_annot_str` 只把 `t.CVoid` 映射为 void,纯 `t.State`
(存储类装饰器标记)会解析失败返回 None → 调用侧回退外部函数白名单默认 i32。

现场(MLIRLiteTest):`includes/stdlib.vp` 的 `extern def exit(status: int)`
在 stdlib 的 vpi 中是 `def exit(status: int) -> t.State: pass`,于是生成
  %6 = llvm.call @exit(%c3) : (i32) -> i32
而模块声明是 `llvm.func @exit(%status: i32)`(void),mlir-opt 报
  'llvm.call' op calling function with void result must not produce values

修复:在 `_resolve_vpi_ret_annot_str` 增加"纯 marker 注解 → void"分支,与
declare 注入路径 `_inject_module_declare` 的 is_pure_marker_rt 判据同口径。
仅**纯** marker(`t.State` / `t.CExtern` / `t.CExport`,可带 t./c. 前缀)
才归 void;`HANDLE | t.State` 等含真实类型的注解仍由段循环返回真实类型。
2026-10-03 21:51:13 +08:00
TermiNexus 94b174c8a0 fix(tpv): 补齐 asm_arg_cap 声明与嵌套数组 element 预算漏改
上一条提交(3426139)中两处改动实际未落盘(编辑动作报告成功但文件内容未变),
导致 TransPyC 编译报 NameError: 未定义名称 'asm_arg_cap'。

- CallGenCBuiltin:补 asm_arg_cap 声明(按操作数个数给足内联汇编实参表容量),
  args_buf 改用该容量分配(此前 4 处用法已改为 asm_arg_cap,声明却缺席)
- TranslatorGenMixin:嵌套数组 element 预算 192 → 768(含子数组类型前缀)
2026-10-03 21:17:41 +08:00
TermiNexus 3426139d03 fix(mlir): TypePrint 输出缓冲按 MLIR 类型长度放大;初始化器文本按类型长度给足
MLIR 类型文本内联具名结构体完整字段体(可达数百字节),旧的短缓冲会截断类型
文本、产出畸形 MLIR(mlirlite 侧 TypePrint 现已对截断响亮报错,此处补齐容量)。

- 10 处 TypePrint 调用点及其缓冲统一放大到 512:
  AssignGenMixin / TranslatorGenMixin / CallGenCBuiltin(×2) / CallGenLeafKit /
  ClassGenVTable / ExprGenBase / ExprGenOps(×4) / FuncGenDef / TypeGenResolve(×2)
- TranslatorGenMixin:
  - _stg_field_zero_text 缓冲 160 → 576(元素 = 类型前缀 + 值)
  - _default_val_const_text 缓冲 48 → 576
  - 嵌套数组 element 预算 192 → 768(含子数组类型前缀)
  - storage 结构体 element 预算 160 → 576
- CallGenCBuiltin:内联汇编实参表按操作数个数分配(原固定 512,一个结构体
  操作数即溢出)、汇编行缓冲按实际长度分配
2026-10-03 21:08:30 +08:00
TermiNexus d0434bf114 fix(repl): 片段导出符号探针改用 MLIR 形态 llvm.func;布局恢复文案改指 {sha1}.meta
- Repl.vp ⑧ 探针由 "define i32 @名(i8* %__repl_ctx__" 改为
  "llvm.func @名(%__repl_ctx__: !llvm.ptr) -> i32"(单端后 dump 出的是 MLIR 文本)
- VpiReader.vp REPL 布局恢复的注释/报错/汇总文案 .stub.ll → {sha1}.meta
  (数据源早已是 meta:_repl_stub_collect_module 读 {sha1}.meta,
   _repl_meta_scan_layout 解析 STRUCT/VTABLE;本次只改文案与注释,无逻辑改动)
2026-10-03 19:58:37 +08:00
TermiNexus cbf76cd468 fix(callgen): text.ll 返回类型回退支持 MLIR 形态 llvm.func
- _CacheModuleRetTyTexts 同时认 define 行与 mlirlite 的 llvm.func 行(含缩进/void 省略 ->/变参)

- 缓存值以 '#' 前缀标记 MLIR 形态,命中时用新增 _ParseMlirRetTyStr 解析

- _ParseMlirRetTyStr 覆盖 none/iN/f16..f128/!llvm.ptr/!llvm.array/!llvm.struct(具名按登记表回查,未登记返回 None 不臆造)

- 背景:单端 MLIR 后 text.ll 无 define 行,回退恒空 ⇒ 指针返回值退回默认 i32 被 inttoptr 截断
2026-10-03 19:45:47 +08:00
TermiNexus 7b3e814524 refactor(backend): 单端 MLIR —— 发射器全树改名 mlirlite 并删除 --backend 开关
- App/Test 的 .vp 中 llvmlite 全部改名为 mlirlite(import、限定名、类型注解、注释)

- 删除后端开关:Config.set_backend/is_mlir_backend/alias_module_name、BackendTarget/BackendIsMlir、CLI --backend、options.backend/options.target 读取,以及 IncludesScanner/StubMergerIR 的模块别名调用

- BuildPipeline 的 mlir-opt -> mlir-translate 降级管线去掉开关门,改为恒执行

- backend_dir_suffix 改名 mlir_dir_suffix 并恒返回 _mlir:TempDir/OutputDir/includes 缓存继续落在 *_mlir,避免复用旧 LLVM 后端的 .ll/obj

- project.vpj 删除已失效的 options.target
2026-10-03 19:26:24 +08:00
TermiNexus 1dcd29db9c fix(phaseb): llc 并行失败按阶段定位,不再一律报 llc 编译失败
- LlcTask 追加 FailStage 字段(0=未失败/1=mlir-opt/2=mlir-translate/3=ASan opt/4=llc),worker 在各步失败时写入

- run_llc_parallel 按 FailStage 打印真正失败的阶段名与对应命令:MLIR 后端下 mlir-opt/mlir-translate 失败此前被误指为 llc(现场 .ll 含 LLVM IR 时 mlir-opt 报 unexpected character,日志却指向 llc)

- 字段追加在结尾,所有 stride/memset 均按 LlcTask.__sizeof__() 计算,既有偏移不变
2026-10-03 19:01:50 +08:00
TermiNexus cda4cca805 refactor(generic): 类型实参符号判定复用 spec_sign_of_annot
- 两处 Call 推断由 SpecSignFromTypeNameSpan 改为 TypeGenCType.spec_sign_of_annot,复用注解侧唯一真源(精确名白名单 + CTypedef 链逐层展开)

- 收益:别名(uint64/size_t/WCHAR->WORD->uint16 等)也能判出符号性,不再是裸白名单的 UNKNOWN
2026-10-03 18:20:42 +08:00
TermiNexus 2ac412c11a feat(generic): 泛型实参推断补齐全部内建标量位宽的精确票名
- 整型按 位宽×符号性 全覆盖:i8 -> t.CUInt8T/char、i32 -> t.CUInt32T/int,i16/i64 维持 t.CUInt16T/t.CInt16T、t.CUInt64T/t.CInt64T

- 浮点补 half -> t.CFloat16T、fp128 -> t.CFloat128T(原仅 float/double,其余返回 None)

- 票名与 TypeGenResolve.map_t_type 的可解析名一一对应;无符号一律不走裸名(避免静默变号),位宽无裸名别名且符号未知时返回 None(宁缺毋错)
2026-10-03 18:19:53 +08:00
TermiNexus f9ba391236 feat(generic): 无符号 i32 泛型实参改发精确票名 t.CUInt32T
- _infer_scalar_type_name_from_spec 对 i32 增加符号性分支:无符号(CUInt32T/CSizeT/CUIntPtrT 等)发 t.CUInt32T,有符号/未知保持裸名 int(位宽无误且与历史逐字一致)

- 修复 CUInt32T 实参经泛型推断被静默变号为 int 的问题
2026-10-03 18:18:21 +08:00
TermiNexus 21436b2690 feat(generic): 泛型实参推断为无裸名别名的整型位宽补精确票名
- _infer_scalar_type_name_from_spec 支持 i64/i16 -> t.CUInt64T/t.CInt64T、t.CUInt16T/t.CInt16T;符号性未知时不猜(返回 None)

- infer_scalar_type_arg_from_llvm_ty 增加 sign 参数:LLVM 类型层无法区分 iN/uN,符号性由被调名源码文本经 SpecSignFromTypeNameSpan 判定

- 泛型函数与泛型类构造两处 Call 推断同步传符号性,修复 GenericOopTest fxi2 第 3 个类型实参(CUInt64T)推断失败
2026-10-03 17:12:47 +08:00
TermiNexus ddb1e6cc2a fix(类型): get_type_size_bytes 补浮点位宽,修复浮点泛型实参的 __sizeof__ 尺寸解析
现象:Test/VectorGenericTest/App/vectortest.vp:32 vector.Vector[CFloat64T] 特化时 vector.vp:12 T.__sizeof__() 报 "NameError: 未定义名称 T(当前泛型上下文: 类型形参 1 个 / 类型实参 1 个)" —— 新增诊断证明上下文是好的(T 能换成 CFloat64T),故问题在 __sizeof__ 快径没接住。

根因:get_type_size_bytes 只用 get_llvm_type_bits 取位宽,而该函数只认整数(Float/Double/Half/FP128 一律返回 0)⇒ 浮点 typedef(CFloat64T 经 map_t_type 已正确解析为 Double)被判"尺寸未知"返回 0 ⇒ CallGenDispatch._tc_h5_sizeof_len_isinst 的尺寸表也不含 CFloat64T(只有 CInt/CDouble/CFloat 等)⇒ 落回"回退 2"把接收者 Name(T) 当实例翻译 → NameError。同文件第 15 行 Vector[CInt] 走整数路径正常,故只有浮点特化暴露(不对称现象由此解释)。

修复:整数位宽为 0 时再取 get_llvm_float_bits(浮点位宽),两者皆 0 才返回 0。
2026-10-03 16:50:42 +08:00
TermiNexus 3a4323e288 feat(generic): 特化上下文自检 fail-fast + NameError 附泛型上下文诊断
背景:VectorGenericTest 在第 32 行 vector.Vector[CFloat64T] 特化时仍报 "NameError: 未定义名称 T"(8af967d 的类模板 tp 名补缺只能覆盖 tp 名缺失那一种,这里 tp 名在场)。静态读完 6 个 _specialize_generic_class 调用点与 ClassGenDispatch 的 __sizeof__ 快径后,无法确定是"上下文没建"还是"建了装不上",故加两处诊断。

1) ClassGenGeneric._specialize_generic_class 在 set_generic_context 之后立刻自检:类型实参个数必须等于模板类型形参个数,且首形参必须能解析成具体类型名;否则 fatal_error 点出类名与特化键(含实参),替代下游 12 行之外那句脱节的未定义名称。

2) ExprGenName 未定义名称报文追加当前泛型上下文(类型形参 N 个 / 类型实参 M 个):形参 0 个=上下文没建,形参>0 且实参 0 个=实参表丢了,皆>0=上下文在但该名不在其中,一眼可分。
2026-10-03 16:38:01 +08:00
TermiNexus da18dd39a4 fix(classdef): 已注册类早退路径补字段类型完整定义
上一条(96dad30)只改了"全新定义"路径的字段扫描循环,但 includes 在 Phase A 预注册、Phase B 重新翻译时走的是 existing 早退分支(ClassGenDef 117-167,只 module_add_named_type 后即 return 0),字段循环根本不执行 ⇒ includes/condition.vp 的 Condition 仍内嵌 opaque 成员、自身 unsized,llc 仍报 base element of getelementptr must be sized。

物证:同一构建里 ThreadingTest 的 main.vp(fb785af4a2aa1a46.ll)因按需调 ensure_struct_def_in_module,已正确拷入 CRITICAL_SECTION/CONDITION_VARIABLE/SRWLOCK 的完整定义;而定义它们的 condition.vp 自身(de351f55b29d33a3.ll)没有 ⇒ 只有定义模块自身体内 GEP 时才暴露。

修复:早退分支在 module_add_named_type 后补调 _ensure_field_types_in_module(Struct/Array 值类型递归补完整定义、Ptr 补 opaque),与全新定义路径同口径。
2026-10-03 16:05:24 +08:00
TermiNexus 96dad309ac fix(classdef): 值类型字段补完整结构体定义,修复跨模块嵌套结构体 unsized 导致 llc GEP 报错
现场:Test/ThreadingTest 编译 includes/condition.vp 时 llc 报「base element of getelementptr must be sized」:getelementptr %Condition, %Condition* %1, i32 0, i32 1。

根因:ClassGenDef 定义类时对字段类型一律只调 module_ensure_opaque_for_type(仅 opaque 声明)。当字段是**值类型**跨模块结构体时,本类体内嵌的成员在 IR 中仍是 opaque ⇒ 成员 unsized ⇒ 本类 unsized ⇒ 本模块内对本类做带字段下标的 GEP 时 llc 报错。现场物证:condition.vp 的 .ll 里 CONDITION_VARIABLE/CRITICAL_SECTION = type opaque,而 Condition = type {CV, CS}。

修复:指针字段维持 opaque(指向物无需在本模块有体);非指针(值类型结构体/结构体数组)改调 ensure_struct_def_in_module 复制完整定义,并配合 _ensure_field_types_in_module 覆盖数组元素。
2026-10-03 15:58:48 +08:00
TermiNexus 28b7437e21 fix(generic): 泛型类构造 Call 实参推断同步收紧,抽出共用精确映射
CallGenCtor._infer_generic_ctor_type_arg 的 Call 分支此前与泛型函数路径同款名字子串猜测(含 Int→int、Float→double),把 CUInt64T/CInt16T 静默归成 int=i32、CFloat32T 猜成 double。改为:把被调表达式交给注解解析器,命中标量才据精确位宽映射;非标量/不可解析仍 fail-fast(保持原有"明确的类型意图却无法解析"报错)。

新增 ClassGenGeneric.infer_scalar_type_arg_from_llvm_ty 作为两处共用的唯一入口(内部走 _infer_scalar_type_name_from_spec),泛型函数路径一并改为调用它,消除重复的 arena/SpecFromLLVMType 样板,保证两处同口径。
2026-10-03 15:51:12 +08:00
TermiNexus 9a80f1df6c fix(generic): 泛型函数 Call 实参推断改为精确类型解析,移除名字子串猜测
原实现按被调名子串猜类型实参(含 Int→int、Float→double 等),把 CUInt64T/CInt16T 一律归成 int=i32,静默窄化/扩宽位宽(禁止静默默认原则);CFloat32T 也会被猜成 double。

改为:把被调表达式本身交给注解解析器 resolve_annotation_type,命中**标量**才据其精确位宽经 _infer_scalar_type_name_from_spec(唯一真源)映射;非标量(构造/普通函数调用)或不可解析一律返回 None,由调用方汇总后 fail-fast 提示显式标注。行为变化:CUInt64T(x)/CInt16T(x) 等无等位宽裸名的转换实参不再被静默猜成 int。
2026-10-03 15:49:58 +08:00
TermiNexus e196d6a28a fix(类型): 模块限定注解支持 from-import 重导出解析
现场:Test/ThreadingTest/App/main.vp:184 `lock: vthreading.Lock = vthreading.Lock()` 报「变量类型注解解析失败」。vthreading.Lock 是重导出(includes/vthreading.vp `from lock import Lock`),Lock 真定义在 lock.vp;普通模块的 from-import 重导出不像包 __init__ 那样登记别名 entry(VpiReader._inject_reexport_class_declare 仅对包 __init__ 触发),故按 vthreading sha1 查 Lock 恒落空。

resolve_annotation_type 的 Attribute 模块限定分支此前未做真定义模块校正:先按原 sha1 查(既有命中情形逐字不变),未命中时再经 _class_true_def_sha1(唯一真源,已在 _normalize_type_name 规则 4b 使用)校正后重查一次,无证据时原样返回不改变行为。
2026-10-03 15:44:49 +08:00
TermiNexus c035a82394 fix(类型): str/bytes 聚合指针检查豁免字符数组衰减,并统一判据真源
误报现场:includes/os/_win32.vp:207 `name: str = find_data.cFileName`。WIN32_FIND_DATAA.cFileName 是 [260 x i8],字段访问得到 Ptr(Array(i8,260)),其指针在 C 语义下天然衰减为 char*/str,属合法赋值,原判据把 Ptr(Array) 一律当真而误杀。

新增 ExprGenBase._str_rejects_ptr_kind(单一真源:1=结构体/类/联合指针、2=非字符数组指针、3=函数指针、0=可接受;[N x i8] 豁免)供 fail_if_str_annot_agg_ptr 与 CallGenLib 跨模块实参检查共用,消除两处重复的 kind 判定,避免同规则两处演化。
2026-10-03 15:40:01 +08:00
TermiNexus bf2f38acd2 fix(generic): 修正 SpecToLLVMText 返回值判据倒置,恢复内建标量实参的泛型类型实参推断
_infer_scalar_type_name_from_spec 把 SpecToLLVMText 的『0=成功』误判为失败(写成 == 0 就 return None),使该函数对任何内建标量都恒返回 None —— 泛型函数隐式推断中『实参来自内建标量形参/字段』的路径(Name/Attribute 分支)永远推不出,报『无法从实参推断泛型函数 ... 禁止静默默认 int』(现场:GenericOopTest fxi 体内 fxi2(a, b, ...),a: double)。判据改为 != 0(与 ClassGenVTable 同约定)。

同时收紧位宽映射:i16/i64/f16 在 Viper 没有等位宽裸名别名(裸 int=int32、裸 float=float32),原映射 i16/i64->int、half->float 会静默窄化/扩宽位宽(i64->int 即 i64->i32),违反『禁止静默默认/兜底』原则;改为只映射位宽精确对应的形态(i1->bool、i8->char、i32->int、f32->float、f64->double),其余返回 None 交给调用方 fail-fast 提示显式标注。
2026-10-03 15:28:02 +08:00
TermiNexus 09ea7283f9 fix(类型): str/bytes 不再作通用指针通配,补齐实参/赋值/返回三处隐式转换 fail-fast
规则:void*/t.CPtr/rawptr 允许隐式转为任意指针类型,str/bytes 不允许(即便两者在 IR 层同为 i8*)。此前 coerce_to_type 的规则 1(通用指针通配)会把指向结构体/类/数组/函数的指针静默 bitcast 到 str 目标(反向亦然)。

- ExprGenBase:新增 _annot_name_is_bare_str/_annot_is_bare_str(裸 str/bytes 判定,组合段如 str|t.CPtr 不受影响)、fail_if_str_annot_agg_ptr(值是指向聚合类型的指针则 fail-fast,消息 CastError)、fail_if_str_annot_ast_agg_ptr(AST 注解入口)。

- 返回路径 StmtGenCifMixin.translate_return:在类型转换分支之前用 trans.CurFuncRetAnnot 判定,避免被规则 1 静默 bitcast。

- 赋值路径 AssignGenMixin.translate_annassign:四个分支(global / nonlocal / mod_glob / 局部)在 coerce 前用 aa.annotation 判定。

- 同模块实参 CallGenMixin.translate_call:在参数循环前一次性确保本模块 .vpi 参数注解缓存就绪(同跨模块 CallGenLib 键式:模块级函数 cls=None、段下标==形参下标),循环内按 _vpi_param_annot_seg_is 判定裸 str/bytes。
2026-10-03 14:46:57 +08:00
TermiNexus 8af967db7f fix(generic): 泛型类模板类型参数名补缺 + 空上下文 fail-fast,修复特化方法体 T 变 NameError 2026-10-03 14:34:55 +08:00
TermiNexus 98ee53340f feat(call): 跨模块实参加 str/bytes 形参的 fail-fast(str 不作通用指针通配),并加 vpi 注解段等值判定 2026-10-03 14:23:12 +08:00
TermiNexus feb29fa7a3 fix(generic-func): 发射特化调用改传符号名(callee: str)而非 Function*,修复生成空 @ 的非法 IR 2026-10-03 14:18:14 +08:00
TermiNexus df1ffa4c2a refactor(subscript): 删除调用侧下标 zext 与 _zext_index_to_i32,统一交给 build_gep(单一真源) 2026-10-03 13:35:36 +08:00
TermiNexus bcbc7142be fix(subscript): 指针下标 GEP 补 zext,修复 i8 下标被符号扩展(uint8ptr[200] -> p[-56] 越界),zlib 漏计字节>=128 根因 2026-10-03 13:20:11 +08:00
TermiNexus ab0ee723b3 fix(classgen): 补 import llvmlite,修复 llvmlite.LLVMType/Int32 解析不一致导致的指针隐式转换 fast-fail 2026-10-03 13:01:42 +08:00
TermiNexus 2868798f58 fix(classgen): 补声明 GenericFunc* 五个类字段,修复 GenericFuncKeys 成员查找 fast-fail 2026-10-03 12:54:56 +08:00
TermiNexus 63acb2bf4d fix(fstring): 使用点按需补 @snprintf declare,f-string 不再依赖显式 import stdio 2026-10-03 12:46:30 +08:00
TermiNexus f2b65f57ec fix(match): case A | B 的 OR 模式注册全部备选(switch 仅取首个)
_handle_simple_match 的 MatchOr 分支此前只把 pats[0] 译成 case 值 → switch value: case 1 | 2 中 2 落 default(LogicFlowTest SwitchMultiValueTest(2) != 10)。改为按臂记录 MatchOr 节点,建 sw 链时逐备选 new_switch_case 挂同一 body(等价 C 的 case 1: case 2:);_handle_guarded_simple_match 同款:cond 改为各备选 subj==cv 的 OR 再与 guard 取与。非 MatchOr 路径逐字不变;非值备选改为 fail-fast(原为静默丢弃)。
2026-10-03 12:11:43 +08:00
TermiNexus 3b465121cd fix(call): kwargs 调用路径补齐省略形参的默认值
_translate_call_with_kwargs 此前对「位置/关键字均未提供的形参」直接跳过 → 调用实参数 < define 形参数,被调函数按 ABI 读未写寄存器/栈槽(remote.one(x = 1) 的 cap 读成 0,DefaultArgTest 断言失败)。改为:按 found_func mangled 名前缀取定义模块 SHA1,用全局默认表 find_func_defaults 取定义处默认值快照补齐(右对齐索引);取不到默认值时 fail-fast,绝不静默漏参/填零。跨模块默认值通道本就通(vpi 带默认值),无需改产物格式。
2026-10-03 12:08:32 +08:00
TermiNexus 692998820c feat(generic): 泛型函数类型实参推断与单态化
支持 def f[T](x: T) -> T 及 f(x)/f[T](x):模板注册(Phase1Scan/Phase2 顶层 FunctionDef,键 {sha1}.{name})、按形参注解对齐从实参推断类型实参(无来源即 fail-fast,不静默默认 int)、克隆模板并 set_generic_context 后翻译生成特化体(名字 {sha1}.f[int],规范化复用 _mangle_generic_class_name)、同签名去重、跨模块按模板签名补 declare。泛型函数模板不再发 vpi(模板签名含未绑定 T),也不再发射 int 兜底版本。已知限制(未静默):bound [T: bound] 解析期已被丢弃;t.CExport/t.CExtern/t.State 泛型函数、关键字/默认参数调用、mod.f[T](x) 显式下标等暂 fail-fast 或走既有报错。
2026-10-03 11:59:26 +08:00
TermiNexus 1fbebb2a62 fix(type): 普通模块 from-import 重导出类的归属模块校正
_class_true_def_sha1 在 cand 模块下查不到该类时原样返回 cand_sha1;而普通模块(非包 __init__)的 from X import Y 重导出不登记别名 entry(VpiReader._inject_reexport_class_declare 仅对包 __init__ 触发)。于是 vthreading.Lock(vthreading 仅 from lock import Lock)的前向声明被错误建在 vthreading 模块下 → 方法返回类型查询去读 vthreading 的 text.ll/vpi(无 Lock.acquire)→ fail-fast(ThreadingTest 根因)。补:先查全局 re-export 内存表 lookup_reexport,未命中再按需解析 .vpi 的 from-import 定位源模块,取其真定义 SHA1(源为包时按同一 IR 名前缀口径继续校正);无证据时行为不变。
2026-10-03 11:37:27 +08:00
TermiNexus 18f4c2326f fix(match): REnum match 变体字段绑定前确保跨模块结构体完整定义
_bind_variant_fields 对 renum_ty 做带字段下标的 GEP,LLVM 要求被指结构体 sized;而模块内对跨模块 REnum(如 llvmlite.__types.LLVMType)只有签名扫描产生的 type opaque,作 GEP base element 时 llc 报 base element of getelementptr must be sized(LLvmLiteTest 根因)。其它取字段路径在 GEP 前都会调 ensure_struct_def_in_module,唯独 REnum match 绑定漏了。补上该调用(同名原地替换 opaque,非追加;本地非跨模块类型自动跳过)。
2026-10-03 11:21:41 +08:00
TermiNexus 105456e56e feat(type): 泛型类注解必须带类型实参,裸名 fail-fast
resolve_annotation_type 入口集中守卫:注解(或其 A | t.CPtr 一侧)的基名(Name.id / Attribute.attr)是已注册泛型模板却没写 [...] 时 fatal_error,报文给出正确写法示例(vector.Vector[CInt]、GSList[G5Node])。判据复用 _find_generic_template(按项目注册),非泛型同名类(struct_test 的裸 Box、dict)不误报;Subscript 形态不受影响,仍走 _resolve_generic_subscript_ty 与 _fail_on_degenerated_generic_annotation。自举源码 includes/** 与 TransPyV/App/** 已确认无裸泛型注解。
2026-10-03 11:16:42 +08:00
TermiNexus 8da4550d79 fix(vpi): 全局变量注入补自注入守卫,消除同模块 external global 与定义重名
循环 import(os/__init__ 导入 os._win32/os._posix,而二者又导入包 os)使 imported_modules 含本模块包名 → LoadDepVpiIntoModule 命中本模块自身 sha1 → _inject_global_variable_declare 为本模块自己的模块级全局发射 @<sha1>.<name> = external global,随后定义端 handle_module_level_var 再发 = global … → llc redefinition of global(SysLibTest 根因)。补上 _inject_function_declare / _inject_method_declare 已有的同款自注入守卫(_declare_only==0 且 src_sha1==trans.ModuleSha1 时早退)。
2026-10-03 11:08:58 +08:00
TermiNexus 19d7a90c28 feat(match): 支持 match/switch 守卫(case X if cond)
通配计数只统计无守卫的 case _,允许多个带守卫的 case _ if ...;_handle_simple_match 预扫描 has_guard,有守卫时改走 _handle_guarded_simple_match(按书写顺序的 if-else 比较链,guard 归一为 i1 后与值比较取与,链尾落 default/end),无守卫时保持原 switch 分派路径逐字不变(零回归);_handle_renum_match 对带守卫臂 fail-fast,避免 switch(tag) 静默忽略 guard。
2026-10-03 10:58:47 +08:00
TermiNexus 67667b10db fix(class): __before_init__ 定义时复用同名前向声明,消除同模块 declare+define
本类方法体内构造自身(如 Vec2.__add__ 里 return Vec2(...))时,构造路径先经 _call_method_on_ptr → _ensure_method_declare 在本模块建好同名 __before_init__ 前向声明(IsDeclared=1);随后 _generate_before_init 又 create_function 追加一条同名 define,使同一模块 IR 同时出现 declare 与 define → llc 报 invalid redefinition of function(OopInheritTest 根因;④ 改用 full.ll 直喂 llc 后不再被旧 BuildCombinedIR 去重掩盖)。改为按名复用该前向声明(命名首参 %self、校准类型、清 IsDeclared 转 define),与 _generate_method/FuncGenDef 同构。
2026-10-03 10:26:57 +08:00
TermiNexus 4c114d8875 fix(call): 泛型模板的前向声明占位不再阻断构造期类型实参推断
注解 vector.Vector | CPtr(无实参)会经 TypeGenResolve 登记不透明前向声明占位 {sha}.Vector(IsForwardDecl=1)。模块限定构造 vector.Vector(...) 在 CallGenMixin 命中该占位后,gen_ctor_entry != None 使泛型推断分支(struct_ty is None and gen_ctor_entry is None)被整段跳过,静默按普通结构体/函数构造并产出裸泛型基类对象 → 后续方法调用触发 D25 接收者 fail-fast(VectorGenericTest 根因)。改为:命中项是前向声明占位且类名为已注册泛型模板时,不锁定 struct_ty/gen_ctor_entry,放行泛型推断按实参(_hint: T)特化;裸名分支与模块限定分支同口径。
2026-10-03 10:18:29 +08:00
TermiNexus 53ba4e48bd fix(expr): 修正 not 取反的 const_int 参数顺序(bits 在前)
上一提交 df9a5a9 误把 const_int 参数写成 (value, bits),实际签名为 const_int(pool, bits, val)。改为 const_int(pool, 1, 0)(i1 false),使 not x 真正降级为 (bool(x)==0),不再与 1 比较而恒等。对照本文件 663/680 行的 const_int(pool, 1, 0) 用法可确认参数顺序。
2026-10-03 09:58:35 +08:00
TermiNexus 9bd3ac3dfb refactor(sidecar): 删除 BuildCombinedIR 文本合并死代码(④-d)
在线模型方案(④-a~④-c)已由 LoadFullIR 取代文本合并,BuildCombinedIR 及其专属辅助(_Seen* 去重表、_RegisterStubLines、_LoadAndAppendStub、_LoadAndAppendTextDeclares、_IsTypeDefLine/_IsIrHeaderLine/HoistTypeDefsBeforeDeclares、_collect_stub_sha1s、_stub_scan_cache_*)已无调用方,一并删除(约 1348 行);同步改写 #35/TranslatorGenMixin 中引用旧函数的注释。
2026-10-03 09:13:17 +08:00
TermiNexus df9a5a9beb fix(expr): 修复 not 一元运算未取反(i1 条件与 0 比较)
not x 的降级此前写成 (bool(x) == 1),在 i1 上与恒等等价,导致 not r2 不取反(FuncTest Test 7 bool return 失败)。注释本意即 not x = (bool(x) == 0),改为与 i1 常量 0 比较。
2026-10-03 09:07:04 +08:00
TermiNexus cc8ea01476 feat(sidecar): 翻译点增写 {sha1}.full.ll,Phase B 改用在线 FULL 产物(④-b/④-c)
- 翻译点(Phase1Main / Phase2 PhaseA / Phase2 includes 重试)在 dump stub/text/meta 后
  追加 dump_ir(OUTPUT_FULL) → {sha1}.full.ll(FULL 缓冲 16MB = stub+text 2× 余量)
- Phase1 增量跳转新增要求 full.ll 存在(缺失即重译补写)
- 新增 StubMergerBuild.LoadFullIR(与 BuildCombinedIR 同契约:读文件到 out_buf)
- Phase B 三处调用点 BuildCombinedIR → LoadFullIR;COMBINED_IR_SIZE 4MB → 16MB
- 诊断报文/注释同步(读 full.ll 失败)

full.ll 为模型在线时产出的自足单模块 IR(类型定义在声明之前,天然满足 llc 顺序);
BuildCombinedIR 暂保留(④-d 再删)。未编译验证。
2026-10-03 08:43:03 +08:00
TermiNexus ec9e950185 fix(gen): 全局初始化器引用跨模块 vtable 时补 external 声明(④-a)
模块级对象全局的 storage 初始化器以符号名写入依赖类 vtable
(i8* @<dep_sha1>.<Cls>.vtable),此前不建 GlobalVariable ⇒ 单模块
OUTPUT_FULL 缺该符号声明,只能靠 BuildCombinedIR 合并依赖 stub 补入。

现于写初始化器前:跨模块(ModuleSha1 != 当前模块)且模块内无该全局时,
new_global_variable(..., Ptr(i8)) + module_add_global 补 external 声明
(对齐 CallGenLeafKit._make_vtable_ref_i8)。仅跨模块补,本模块类由
_generate_vtable 产出定义,避免同模块重复定义。

去 IR 化步骤④-a(生产者自足,FULL IR 自足化前置);未编译验证。
2026-10-03 08:39:13 +08:00
TermiNexus 324a22b6d1 refactor(call): CallGenFd 返回类型精确化改以 vpi 为权威
返回类型「精确化」原只读定义模块 text.ll;改为先查 vpi 返回注解缓存
(_lookup_method_ret_ty_via_vpi),未命中再回退 text.ll,与 ② 的
vpi→text.ll 口径一致。

去 IR 化遗留收尾;未编译验证。
2026-10-03 08:28:07 +08:00
TermiNexus 3b0ed04e70 chore(stubmerger): 删除 StubMergerIR 中 6 个无调用方的旧 IR 文本扫描助手
删除 _IsFuncDeclaredOrDefined / _CommentOutDeclareInBuf / _CommentOutGlobalInBuf /
_HasGlobalInBuf / _CommentOutOpaqueTypeInBuf / _HasFullTypeDefinition —— 全仓无调用方
(去重判定早已迁移到 StubMergerBuild 的 _seen_* 哈希表),保留在用中的
_is_submodule_of_imported。顺带更新本模块头注释(现为 meta sidecar 工具)。

去 IR 化收尾清理;未编译验证。
2026-10-03 08:26:02 +08:00
TermiNexus 611f49cfb6 refactor(repl): 会话函数签名改从翻译期模型取,去除片段 IR 文本解析
- 新增 _repl_ty_ptr_token(LLVMType* → tag*1000+bits)与 _repl_model_func_sig
  (按裸导出名查 tr.Module 的 Function,读 RetTy/Params;含 ctx 首形参与 EH 出参判定)
- _repl_precheck_session_funcs / _repl_register_session_funcs 增 mod 形参;
  两处调用点传 tr.Module
- 删除旧 IR 文本解析:_repl_ir_func_sig / _repl_ir_find_define_at /
  _repl_llvm_ty_token / _repl_span_str 及其 scratch ReplTySpan / ReplTySpan3 /
  ReplIrDefOff / _REPL_FN_TY_CAP

去 IR 化步骤⑤;返回码(1/2/3/4/5)与旧实现一致。未编译验证。
2026-10-03 08:23:45 +08:00
TermiNexus 35f2070d3b feat(sidecar): meta 增写 STRUCT/VTABLE,REPL 布局恢复改读 meta
- WriteModuleFuncMeta 更名 WriteModuleMeta:追加 STRUCT <name> FIELDS n PACKED 0|1
  (opaque → STRUCT <name> OPAQUE)与 VTABLE <class> SLOTS n;数据源改为
  Module.NamedTypeHead / GlobalHead,不再有任何 IR 文本反推
- VpiReader REPL 布局改读 {sha1}.meta:新增 _meta_atoi / _repl_meta_scan_layout
  按前缀精确匹配解析 STRUCT/VTABLE 行
- 删除旧 .stub.ll 文本解析辅助 _repl_stub_take_name / _repl_stub_body_count
  及其 scratch 缓冲 ReplStubNameBuf / _REPL_STUB_NAME_CAP

去 IR 化步骤③;未编译验证。
2026-10-03 08:18:15 +08:00
TermiNexus 78d3941f7d refactor(call): 跨模块返回类型解析改以 .vpi 为权威,text.ll 降为回退
将 8 处跨模块方法/函数返回类型解析的 text.ll → vpi 顺序对调为 vpi → text.ll:
- CallGenProp:属性方法调用
- CallGenMixin:跨模块裸名 CExport、from-import 函数
- CallGenCall:跨模块方法、类方法调用、self 方法调用(含 MRO 祖先链重试段整体搬移)
- CallGenDispatch:__len__ 跨模块、泛型 Mixin 方法

本模块 found_func / local_fn 取当前 IR 声明类型的路径保持不变。
去 IR 化步骤②;未编译验证。
2026-10-03 08:10:34 +08:00
TermiNexus 94d2bf5047 feat(sidecar): 落盘 {sha1}.meta 函数定义清单,声明文件判定改读结构化 sidecar
- 新增 WriteModuleFuncMeta:翻译期从 Module.FuncHead + Function.IsDeclared 产出 FUNC <mangled> DEF|DECL(与 dump_ir 的 stub/text 判据同源)
- 新增 _MetaHasDef / 重写 IsDeclOnlyModule:不再扫 text.ll 的 define 行,改读 meta
- Phase1/Phase2 落盘点紧随 text.ll 写出 meta;Phase1 增量跳转额外要求 meta 存在
- Phase2 内联 is_decl_mi 与 PhaseC 判定统一走 IsDeclOnlyModule

去 IR 化步骤①(sidecar 最小闭环),全后端统一,默认行为不变。
2026-10-03 08:06:39 +08:00
TermiNexus 0ebe2e0752 feat(backend): 新增 MLIR 后端开关/产物隔离/降级管线/模块别名(默认 llvm 逐字不变)
- Config: BackendTarget/BackendIsMlir/set_backend/is_mlir_backend/backend_dir_suffix/alias_module_name;读取 options.backend(回退 options.target)

- main.vp: 新增 --backend llvm|mlir(CLI 优先级最高,默认 llvm),加载工程后、resolve_paths 前生效

- 产物隔离: temp/output 追加 _mlir 后缀;includes 缓存追加后端后缀(.binary[_asan][_mlir])

- BuildPipeline: MLIR 降级 mlir-opt(校验) -> mlir-translate --mlir-to-llvmir,覆盖串行/并行/--phase2command,且置于 ASan opt 之前;LlcTask 增 MlirOptCmd/MlirTranslateCmd 字段

- 别名: ResolveModuleToPath 与 _FindSha1ByModName 经 Config.alias_module_name 把 llvmlite 重定向到 mlirlite(调用点零改动)
2026-10-03 07:53:04 +08:00
TermiNexus 4eec3f0908 feat(dunder): 补齐 dunder 协议(__bool__/__call__/__str__/__abs__/__del__/**/增量/反向)
- __bool__:ExprGenOps.coerce_cond_to_bool 统一 if/while/and/or/not/三元/协程条件的真值归一,优先调度 __bool__

- __call__:obj(args) 分发到 obj.__call__(CallGenMixin 裸名分支)

- __str__:str()/print/f-string 调度(CallGenDispatch/CallGenLeafKit/ExprGenFstr)

- __abs__:abs(obj) 调度(CallGenMixin)

- __del__/__delete__:del obj → __delete__;del obj.attr 无 property deleter 时回退对象 __del__

- ** / __pow__:新增 llvm.pow 内联(整数按常量指数回写),_binop_to_dunder 补 Pow

- 增量赋值:__iX__ 优先、缺失回退 __X__(新增 _aug_op_to_dunder)

- 反向运算符:新增 _rbinop_to_dunder,lhs 无 dunder 时调度 rhs.__rX__
2026-10-03 00:40:00 +08:00
TermiNexus a3bb62f2c3 feat(switch): switch 允许重复 case 标签(match 仍禁止)
- HandlesMatch / MatchGenMixin:值标签去重仅对 match 生效;switch 允许重复 case
  标签(按书写顺序,先出现者生效);通配臂多个(default / case _)仍 fail-fast
- wiki/07-control-flow.md 同步该语义
2026-10-03 00:18:55 +08:00
TermiNexus fabd92adc7 feat(switch): 新增 switch 语句并禁止 match 内 fallthrough
- MatchGenMixin:case 体按 node.is_switch 置 _in_switch_case;_has_no_break 只认 FallThrough 节点(去掉 c.NoBreak() 兼容路径)
- StmtGenDeclMixin:translate_fallthrough 只在 switch 的 case 体内合法,match/switch 之外 fail-fast
- Translator:_in_match_case 更名 _in_switch_case(语义 = 当前 case 体属于 switch)
- FuncGenDef / FuncGenMixin:函数体 / 嵌套函数 / lambda 入口隔离并恢复 _in_switch_case
- 因 match 改为硬关键字,重命名同名局部变量 match -> Matched(StubMergerStore / ImportsGenMixin / ExprGenAttr / CallGenBase)
2026-10-02 23:15:43 +08:00
TermiNexus 38860a6313 feat(match): 重名 case 标签 fail-fast
MatchGenMixin._translate_match_impl 新增前置检查(任何 IR 产出之前):新增
_case_label_same / _value_label_same 比较 switch 标签(常量 / 名字 / 属性链 /
MatchSingleton / MatchClass 变体名 / MatchOr 子模式),标签重复或出现多个
`case _` 时 fatal_error 终止(C switch 不允许重复 case 标签)。
2026-10-02 22:44:34 +08:00
TermiNexus d633a5786f fix(flow): 函数体入口清空 break/continue 目标与 match-case 标记
- FuncGenDef.translate_function_def:进函数体前保存并清空 _break_bb / _continue_bb /
  _in_match_case,退出恢复
- FuncGenMixin:嵌套函数体与 lambda 体两处同样处理——原实现下嵌套函数体内的
  break/continue 会继承外层函数的跳转目标(跨函数 br,非法 IR)
2026-10-02 22:40:04 +08:00
TermiNexus fb4175736f fix(flow): 循环/match 外的 break/continue 改为 fail-fast
- StmtGenDeclMixin:translate_break / translate_continue 增加 node 形参,目标块
  缺失(_break_bb / _continue_bb 为 None)时 fatal_error,不再静默不发跳转
- StmtGenMixin:Break / Continue 分派传入 node(供错误定位)
2026-10-02 22:35:26 +08:00
TermiNexus 43d25e58b9 feat(match): case 内 break 跳出 match,并落地 fallthrough
- Translator:新增 _in_match_case(FallThrough 合法性判定用);_break_bb 注释补充
  case 内 break 语义
- StmtGenDeclMixin:新增 translate_fallthrough(本身不产出 IR;非 case 体内 fail-fast)
- StmtGenMixin:FallThrough 语句分派
- MatchGenMixin:进出 case 体(含 default 臂)时暂存/覆盖 _break_bb = match end_bb,
  使 case 内 break 跳出 match 而非外层循环;_has_no_break 兼容 FallThrough 标记;
  REnum / simple 两条 switch 路径的 fallthrough 由"只留无终结符块"改为显式 br
  下一个 case body(原实现产出非法 IR)
2026-10-02 22:33:15 +08:00
TermiNexus 0b7f7a50c7 docs(types): 同步 list 相关注释,list[T, N] 不是定长数组
- TypeGenResolve._is_count_arg_container:说明把 list 纳入判据只是为了历史写法
  list[E, N] 的第 2 实参不被误当类名归一;list 本体是 _list.vp 的堆上 STL 容器
- AssignGenMixin:注释示例由 list[type] / list[type, N] 改为 CArray 形态
2026-10-02 22:16:30 +08:00
TermiNexus 8d01c01afd feat(viperbuild): 落地 --src 工程模式配置面,并补齐循环 else 与整型 None 比较
- 新增 lib/core/builtin/ViperBuild.vp:--src Pass 0 依赖闭包扫描 + 宏配置提取 +
  默认值填充 + 写回 Config;main.vp 路由改为工程模式并删除单文件分支
- Translator.vp:写入 triple / datalayout,翻译期擦除 viperbuild 宏语句;
  ImportsGenMixin.vp:对 viperbuild 豁免 SHA1 校验
- StmtGenCifMixin.vp:for / while 的 else 子句在 11 条循环生成路径全部接通
- ExprGenOps.vp:None 比较按 None 所在侧分派,整型 None 隐式为 == 0 / != 0
2026-10-02 22:07:01 +08:00
TermiNexus 15b416b527 fix(type): 统一注入期类归属与导入上下文记账,修占位表爆与属性标记误解析
- StubMergerStore 新增共享导入上下文记账(AppendImportedModule / AppendFromImport /
  ImportCtx / AllocImportCtx / NotePlainImport),ImportsGenMixin 与 VpiReader 共用同一份
  口径;顺带移除 add_from_import 的 256 缓冲静默截断,VpiReader 删除两份私有副本

- 注入窗口按「名字归属」判据解析(is_module_class + CurDefModuleSha1):
  _forward_declare_ptr_from_node 的 Name 分支、resolve_annotation_type 的 Name/Attribute
  分支均补齐,使同一 stub 内先声明/先引用的类产出**具名占位**而不是 Ptr(i8),并新增
  _is_tattr_marker 让 t.attr.* 属性标记不参与类型联合竞争

- VpiReader._inject_class_struct_declare:命中前向声明占位时不再早退,改为「占位 Ty
  原地写入」(对齐 ClassGenDef.fill_fwd),修 StructTable 爆表(table full, cannot
  register Translator)

- 清除全部一次性探针:[DBG-GEN](VpiReader / StructGenMixin / ClassGenDef)、
  [DBG-ATTR](ExprGenAttr)、[MODSHA1](CallGenMixin)

根因记录:vpi 注入期漏记 `import X as Y -> from_imports "Y:X"`,使 GenTypes.VarEntry.Spec
(TSpec.TypeSpec | t.CPtr)取不到模块 SHA1、退化为 i8* 并被永久冻结
(CallGenCtor.vp:897 .PtrLevel 属性访问失败)。
2026-10-02 17:29:38 +08:00
TermiNexus 2c33effee1 fix(type): 前向声明补 import-as 别名反查(LookupModNameByAlias)
_forward_declare_ptr_from_node 的 Attribute 分支此前仅走
ImportsGen.lookup_from_import 反查别名;对 `import X as Y`(from_imports 里存
"Y:X")补一道 StubMergerStore.LookupModNameByAlias 专用反查,避免别名解析不到
→ 拿不到模块 sha1 → 无法前向声明而退回 Ptr(i8)。现场候选:GenTypes.vp 的
`import lib.core.Gen.Type.TypeSpec as TSpec`,字段 `Spec: TSpec.TypeSpec*`
(ve_i.Spec.PtrLevel 属性访问失败)。
2026-10-02 11:48:31 +08:00
TermiNexus 4958fd2c14 debug(attr): [DBG-ATTR] 泛化到任意属性访问失败,打印基座/结构体/字段数(用完删除)
Generator.FuncGenBase 已由前向声明修复;新报错 ve_i.Spec.PtrLevel 属同类症状
(某字段指针的 pointee 不是可查字段的结构体)。去掉 attr=="FuncGenBase" 守卫,
任何属性访问失败都打印 obj_ptr.Ty / struct 名 / 条目 FieldCount / base_class。
仅打印,不改行为。
2026-10-02 02:50:01 +08:00
TermiNexus 87e2356eea feat(type): rawptr[T]/t.CPtr[T] 下标形也走前向声明,并覆盖同文件前向引用
承接 c0d3231(X | t.CPtr 已改前向声明):
- t.CPtr[T] 分支:inner 解析为 None 时先尝试前向声明具名占位并返回
  Ptr(占位.Ty),保住类身份;无法确定类名/模块时才回 None 交多遍收敛。
  rawptr[T] 经 builtins typedef(rawptr = t.CPtr)兜底归一到本分支,故一并覆盖。
- _forward_declare_ptr_from_node 的 Name 分支补"本文件顶层类"回退:用
  StructGen.register_file_classes 登记的"{名字 → 本文件模块 SHA1}"前向声明,
  覆盖同文件前向引用 / 自我引用(如 JsonValue | t.CPtr 自引用字段)。

无编译验证。
2026-10-02 02:48:26 +08:00
TermiNexus c0d3231e02 fix(type): 未注册类指针改前向声明(具名不透明占位)+ 定义侧原位填充
对齐 TPC 语义(未注册类 → 具名空 CStruct 占位 + 后续 set_body 填充)。
此前 TPV 对"指向尚未注册类的指针"回退匿名 Ptr(i8),丢类身份且被冻结后
无法修复:现场 CallGenBase.Gen: Generator.Generator* 与 Generator 构成
循环依赖(Gen→Generator,Generator.CallGenBase→CallGenBase),Gen 先于
Generator 注册 ⇒ 冻结成 i8* ⇒ self.Gen.FuncGenBase 属性访问失败
(1.log 取证:Generator 条目本身正确 fcount=47,但 lookup_field 收到
struct_ty=<non-struct>)。

- GenTypes.StructEntry 新增 IsForwardDecl 标记(占位条目)。
- StructGenMixin.ensure_forward_class:按"类名 + 定义模块 SHA1"登记具名
  不透明结构体 {sha}.Class 占位(名称与定义侧 ClassGenDef 同口径,持久池
  分配),幂等;定义侧据此按名定位原位填充。
- TypeGenResolve._forward_declare_ptr_from_node(Attribute/Name 两形,经
  from_imports 与模块名表求 sha1,再 _class_true_def_sha1 校正)+ BinOp
  (X|t.CPtr) 未注册回退:优先返回 Ptr(占位.Ty);仅在无法确定类名/模块时
  才退回 Ptr(i8) 由 AnnotClassName 恢复。
- ClassGenDef:命中前向声明占位时不早退,把新构建的字段体**原位写入同一
  Ty 对象**(*existing.Ty = Struct(...),指针身份不变),登记条目沿用占位
  条目;declare_only 1/0 均适用。所有已发出的 Ptr(占位.Ty) 引用在填充后
  自动指向完整定义。

TPC 侧核对:TPC 本就是"具名占位 + set_body"模型(TypeAnnotationResolver
.CreateFromTypeName / BaseGen._get_or_create_struct / StructGen op 占位),
并有 fail-fast 拒绝类引用降级为 void**,无需改动。
2026-10-02 02:40:05 +08:00
TermiNexus d1c0f6754c debug(gen): 补 [DBG-ATTR] 探针——FuncGenBase 处 obj_ptr 类型与注解类名兜底(用完删除)
已知(1.log 取证):Generator 结构体登记正确(tbl[337] fcount=47 含 FuncGenBase),
真正问题是 `self.Gen` 字段类型退化成 i8*(lookup_field 收到 struct_ty=<non-struct>)。
本探针在报错点补打印:
- obj_ptr.Ty(确认是否为 i8*)
- base_class(_resolve_attr_field_class 取到的 Gen 字段注解类名)
- 用 base_class 走 lookup_field_by_class 找 FuncGenBase 的字段下标

用于判定"注解类名未记录"还是"按类名查询也失败"。仅打印,不改行为。
2026-10-02 02:31:09 +08:00
TermiNexus 01e95b51cf debug(gen): 一次性取证插桩——Generator 结构体登记/查找全链路(用完删除)
覆盖点(全部以 [DBG-GEN] 为前缀,仅打印不改行为):
- StructGenMixin.register_struct:IN(name/sha1/ty/StructCount)、DEDUP(命中条目)
- StructGenMixin.add_field:逐字段打印写入 Generator 条目的字段名与下标
- StructGenMixin.find_struct_by_module:IN / HIT / MISS
- StructGenMixin.lookup_field(field=FuncGenBase):struct_ty 名、按 Ty 指针命中条目的
  全字段名、以及结构体表内所有含 "Generator" 的条目
- VpiReader._inject_class_struct_declare:IN / EXISTING / RENUM-SKIP / EMPTY /
  FIELD-FAIL / REGISTERED
- ClassGenDef.translate_class_def:EXISTING / REGISTER / DONE

目的:一次重建即可判定 Generator.FuncGenBase 查不到是"命中空/旧条目""Ty 错配"
还是"字段名悬空",并看清是哪条登记路径产出的条目。
2026-10-02 02:18:55 +08:00
TermiNexus 1ad7abf33d refactor(TransPyV): 统一自举写法(novirtual / define / rawptr→* / 去续行反斜杠)
- @t.NoVTable class X → novirtual class X
- t.CDefine = N → define N
- rawptr[X]、X | t.CPtr → X*
- t.CPtr → rawptr(裸指针标记)
- t.Callable[...] → callable[...]
- 去掉 if/elif 条件的行续反斜杠(改单行写法)
- w32.win32file.X → win32file.X(已 import as)
- core/__init__.vp 去掉未使用的 import t, c / from stdint import *

涉及:CompilerFactory、IncludesScanner、Repl、core/__init__、
Gen/Call/{CallGenBase,CallGenCBuiltin,CallGenCall,CallGenDispatch,CallGenLeafKit}、
Gen/Class/ClassGenGeneric。
2026-10-02 02:16:37 +08:00
TermiNexus 7e82ed6940 chore(debug): 移除 [DBG-GEN] 一次性插桩(改走静态分析)
按用户要求撤掉 lookup_field 里的临时打印,恢复原函数体。
2026-10-02 02:14:54 +08:00
TermiNexus 1d1efc6288 debug(gen): 一次性插桩打印 Generator.FuncGenBase 字段查找上下文(用完删除)
AsyncGenMixin.vp:58 `self.Gen.FuncGenBase` 报「属性访问失败」,需确认
lookup_field 收到的 struct_ty 是谁、结构体表里 Generator 条目的 FieldCount/sha1,
以判断是字段缺失、Ty 身份不一致还是按到了空/错误条目。仅打印,不改行为。
2026-10-02 02:10:58 +08:00
TermiNexus f865986675 fix(stub): App 预注册注入依赖 vpi 时跳过函数/方法 declare,避免打断多遍收敛
承接上一提交(App 预注册期开始注入依赖 vpi)。注入的依赖 vpi 里,函数/方法
declare 的形参/返回注解可能引用**本遍尚未注册**的类(跨文件注册顺序:被依赖模块
的类可能排在更后,需靠下一遍收敛),resolve 落空会在 declare 注入里 fail-fast,
直接打断 Phase A-pre 的多遍收敛。现场:App 预注册 AssignGenMixin.vp 注入
Translator.vpi 时,InjectTracebackInstallCtor 形参 rawptr[Translator] 解析失败
(Translator 结构体因字段引用尚未注册的 GenTypes 类而本遍未登记)。

改动:VpiReader._ParseVpiIntoModuleImpl 新增 pre_skip_fn_declares(仅当
trans._pre_allow_dep_inject != 0 即 App 预注册时置 1),在「第一遍 B 方法 declare」
与「模块级函数 declare」两处跳过注入。预注册的唯一目的是注册 struct/enum/union
(第一遍 A)与包重导出别名(ImportFrom 段),两者均保留;方法/函数 declare 写在
预注册的瞬时模块里、随即随 mb.reset_to 丢弃,无跨遍作用。全量翻译
(_declare_only==0)不跳过,仍 fail-fast。
2026-10-02 01:47:18 +08:00
TermiNexus 981af07d5c fix(phase): App 预注册期注入依赖 vpi,修 list[ast.ClassDef*] 票名前缀分裂
Phase A-pre(App 预注册)此前不做导入处理、不注入依赖 .vpi,导致包重导出
类的字段注解在预注册期只能保留「包前缀」(1abcd…),并被冻结进结构体布局;
阶段 A 构造函数侧却能借包别名 entry 校正为真定义前缀(e70bd…),两处票名
分裂 ⇒「非基本类型指针不可隐式转换」(现场 ClassGenGeneric.vp:92)。

改动(只放开 App 预注册,不动 includes 预注册那条防依赖环自锁的约束):
- Translator 新增 _pre_allow_dep_inject 开关,并在导入处理门拆成两道:
  ①导入处理 + 自动容器可见性 + LoadDepVpiIntoModule —— 全量翻译与 App 预注册共用;
  ②模块级变量预注册 + 函数前向声明 —— 仍只在全量翻译做。
- BuildPipeline.TranslateFileGetTrans 新增 pre_allow_dep_inject 参数并写入 Translator。
- Phase2 App 预注册调用点传 1;includes 预注册保持默认 0。
2026-10-02 01:03:31 +08:00
TermiNexus 57f8c655c8 chore(debug): 移除 [DBG-P7] 一次性插桩(根因已定位)
定位结论见 Debugger/debug-nested-class-struct.md 的 P7:不是别名登记失败,而是
Phase A-pre(预注册 struct/enum/union,declare_only)解析类字段注解时,被 import
模块的 vpi 一概尚未注入 ⇒ 包重导出别名 entry 不存在 ⇒ 字段侧票名保留包前缀并被
冻结进结构体布局,与阶段 A 构造函数侧的真定义前缀分裂。

删除 96c1102 / 3b8a2b2 引入的全部临时打印(VpiReader.vp / TypeGenResolve.vp /
CallGenCtor.vp 三处),代码回到插桩前状态(-108/+0)。
2026-10-02 00:59:44 +08:00
TermiNexus acd6c9a615 docs(expr): 去掉指向已删除的 wiki/14-oo-refactor.md 的注释引用
该文档(对象化重构计划)已完成并从 wiki 删除,注释只保留「对象化 P3」的来源标记,
避免留下悬空文档引用。
2026-10-02 00:47:17 +08:00
TermiNexus 3b8a2b225f debug(stub): 补全 [DBG-P7] 插桩覆盖所有票名产生路径(定位后删除)
在 96c1102 基础上把"可能产出该票名"的路径一次打全,保证一次重建即可定死根因:

- TypeGenResolve._class_true_def_sha1:MISS 时额外 dump 结构体表里所有名字含
  ClassDef 的条目(下标/名字/归属 sha1/Ty 的 IR 名前缀)——直接看出别名 entry
  到底有没有、挂在哪个 sha1 上。
- TypeGenResolve.extract_class_name_from_annotation:AnnotClassName 通道算出的
  泛型票名(含 ClassDef 才打)。
- TypeGenResolve._resolve_generic_subscript_ty:注解/字段侧算出的泛型特化名
  gen_mangled(含 ClassDef 才打)。
- CallGenCtor._extract_type_args_from_slice:构造函数侧实参串的 raw/norm 两态。
- VpiReader._inject_class_struct_declare:进入 + 各出口
  (already / renum_skip / field_unresolved / zero_fields / registered)。
- VpiReader._inject_reexport_class_declare:调用现场与四个出口
  (already / no_src_entry / alias_reg_fail / alias_ok)。
2026-10-02 00:34:38 +08:00
TermiNexus 96c110242e debug(stub): 一次性插桩定位 ast.ClassDef 票名前缀分裂(定位后删除)
9994d2e 改判据后现象不变,需要在一次重建里同时看到三处事实,避免反复猜:
- TypeGenResolve._class_true_def_sha1:cls 含 "ClassDef" 时打印 HIT/MISS 与候选
  sha1、命中条目的 IR 名前缀(用 strstr 以便同时捕获 "ClassDef*" 形态);
- VpiReader._inject_reexport_class_declare:src/local 为 ClassDef 时打印调用现场
  的「源 entry 是否已注册 / 别名是否已存在」(src_entry=0 ⇒ 别名必然建不起来);
- VpiReader 的 ImportFrom 分支:判定为包 __init__ 时各打印一行(模块名、包前缀、
  子模块名与解析出的 sha1),确认包判定与子模块解析是否成立。

全部带 [DBG-P7] 前缀,便于 grep 与事后清除。
2026-10-02 00:32:05 +08:00
TermiNexus 9994d2e983 fix(stub): 包 __init__ 判定改按模块名,修 ast.ClassDef 票名前缀分裂
存量缺口:VpiReader 判定"本 stub 是否包 __init__"依赖 sha1→rel_path 反查
(strstr(rel_path, "__init__.vp"))。该反查在部分运行形态下取不到条目 ⇒ 判定恒
false ⇒ 包重导出类的别名 entry 静默不登记 ⇒ `ast.ClassDef` 这类限定名在泛型实参
里保留包前缀(幽灵名 `list[1abcd5277fa442de.ClassDef*]` 全产物恒 opaque),与构造
函数侧的真定义模块前缀(`list[e70bd9b4487d921d.ClassDef*]`)分裂成两个类型,报
「指针隐式转指针已禁止」(现场 ClassGenGeneric.vp:92 的 CastError)。

改法(不再依赖 rel_path 字段,均为模块名表的既有事实):
- StubMergerStore 新增 LookupModNameBySha1:按 SHA1 反查模块名(只读指针)。
- VpiReader 的 ImportFrom 分支:取 level>0 的兄弟模块 S,用其模块名去掉末段得到包
  前缀 P;又因 Utils.PathToModuleName 把 pkg/__init__.vp 记成 "pkg",故
  「本 stub 的模块名 == P」即证明本 stub 就是包 P 的 __init__ ⇒ is_pkg_init_rt=1,
  同时直接用 P 拼出子模块全名(不再从 rel_path 反推目录名)。
- 模块名证据不足时仍回退原 rel_path 路线,行为不变。
2026-10-02 00:07:23 +08:00
TermiNexus b91dfef777 feat(stub): vpi 注入侧递归登记嵌套类为扁平名 {外类}_{内类}
VpiReader 此前只登记顶层 ClassDef,跨模块引用 `外类.内类` 拿不到类型
(TPC 侧 stub.ll 已递归声明,双端不对齐)。现按定义端
ClassGenDef._translate_nested_classdefs 的命名口径补上:

- _inject_class_struct_declare 增加 name_prefix 形参,非空时算出扁平名
  real_name;REPL 分支、已注册检查、命名结构体类型名、register_struct、
  add_visible_struct 一律改用 real_name。
- 新增 _inject_nested_class_declares:遍历类体内缩进 ClassDef 子节点,
  以 real_name 为前缀递归调用(多层嵌套自然展开为 A_B_C)。
- 嵌套登记后再次调用 mark_module_class 复原"正在定义类"窗口:否则内层
  类名会顶掉 CurDefClassName,使外层字段注解的规则 5.5 自引用证据判错、
  且 `外类.内类` 的守卫①失效。
2026-10-01 23:39:11 +08:00
TermiNexus c3f554f1f5 feat(struct): 嵌套 class/struct 发射为独立结构体 {外类}_{内类}
用户 2026-10-01 定:struct 内允许叠加 struct(与 class 内写 class 同口径)。
此前类体里的嵌套 ClassDef 被**静默丢弃**(字段循环只认 AnnAssign、
_translate_oop_methods 只认 FunctionDef),现按 TPC 侧 union/renum 的
`{ClassName}_{Nested}` 命名先例发射为独立结构体,与 TPC 提交 5eef845e 同源。

- ClassGenDef.vp:新增 _translate_nested_classdefs —— 在 enum/union/renum/泛型/
  异常分派之后、existing 检查之前(所有提前 return 之前,故 declare_only=1/0 都递归)
  遍历类体 ClassDef 子节点,把子节点 name 临时改写为扁平名后递归
  translate_class_def(本模块所有名字派生点都读 cd.name ⇒ 改名即全链路一致)。
  两个护栏:扁平名分配在 PermPool(跨 Phase 存活);递归后还原 CurDefModuleSha1/
  CurDefClassName(内层 mark_module_class 会覆盖,否则外层字段解析误判当前定义类)。
- TypeGenResolve.vp:新增 _resolve_nested_class_type 并在 Attribute 分支的
  "base 不是模块" 出口前接上 —— 限定名 `外类.内类` 按扁平名查结构体表即可命中
  (裸名 `外类_内类` 走既有 Name 分支天然命中)。判据两道:base 是已知类
  (find_struct 命中,或正处于该类的定义窗口内);扁平名条目存在且 Ty 非空。

引用两种写法都可用:`外类.内类`、`外类_内类`;多层嵌套递归为 A_B_C。
2026-10-01 23:30:15 +08:00
TermiNexus 3ccc37ca3f fix(type): 泛型实参归一先剥尾随星号,消除包前缀票名分裂
_norm_type_arg_name 规则 4 用 strchr('.') 切出 attr 时把尾随 '*' 一起带进了类名
('ast.ClassDef*' → attr='ClassDef*'),于是 _class_true_def_sha1 按名查不到
entry、真定义模块校正失效,留下包前缀:

  字段侧 ClassGenBase.vp:123 → list[1abcd5277fa442de.ClassDef*](无定义的幽灵名,
                              全部产物里只有 = type opaque,从无 = type {...})
  构造侧 ClassGenGeneric.vp:92 → list[e70bd9b4487d921d.ClassDef*]
  ⇒ ClassGenGeneric.vp:92 报「指针隐式转指针已禁止」。

TPC 侧名称域(SymbolUtils.ResolveTypeArgStr 的 BinOp 分支)本就是「先解析基类型 X,
再 AddPointerLayers 追加 '*'」→ {真定义sha}.X*,TPV 与之对齐。

新增规则 1.6:剥掉全部尾随 '*' → 递归归一基名 → 回贴同样多的 '*'。
i8** / int* / T* / 已带 SHA1 前缀的文本逐字不变。
2026-10-01 22:40:54 +08:00
TermiNexus 02b9429363 fix(type): void* 归并分支的 match 必须守 None,修 ASan 空指针崩溃
BinOp 的 CPtr 分支里,上一笔为 void* 归并新增的 match other_ty 未判空。
other_ty = resolve_annotation_type(另一边) 在 Phase 1a prescan 时**可以是 None**
(本分支下方既有代码就写着 if other_ty is None: ... 回退 Ptr(i8),正说明 None 是
期望值);对 None 做 REnum case 匹配会按其 tag 解引用空指针。

现场:TPV(ASan) 自举 Phase1 报 access-violation on address 0x0(READ),
栈 _prescan_funcdef_signatures → resolve_annotation_type;触发源是用户 WIP 里
新增的 X* 返回注解(parser 把 X* 展开成 BinOp X | t.CPtr,正好走本分支)。

改为 if other_ty is not None: 包住,None 继续走下方既有回退路径。
2026-10-01 22:07:41 +08:00
TermiNexus be14fa5f94 fix(ctor): REnum 变体构造识别取错限定名,导致构造物化豁免失效
_ctor_call_agg_ty 从被调方沿属性链一路走到链根裸名,三段式
llvmlite.LLVMType.Struct(...) 于是被判成限定名 'llvmlite'(非 REnum 类)⇒
返回 None ⇒ ClassGenDef.vp:128 的 *existing.Ty = LLVMType.Struct(...) 被
「指针 → 非基本类型值」禁令拦下。改为取「变体名左边那一段」:两段式取裸名
(EnumType.Variant)、三段式取属性名(llvmlite.LLVMType.Struct → LLVMType)。
2026-10-01 21:44:55 +08:00
TermiNexus 2f6db41b11 fix(void): 形参与字段注解禁止 void,改为编译期 fail-fast
void 只允许用于 void* 与 typedef 不透明结构体别名(用户 2026-10-01 定)。
此前 def f(p: void) / class C: x: void 不走 translate_annassign,会按 LLVM void
直接建形参/字段槽,生成非法 IR 拖到 llc 阶段才暴露。现补 6 处形参落点
(FuncGenDef 前向声明/外部声明/函数体/alloca 各一、ClassGenVTable 签名/alloca 各一)
与 1 处字段落点(ClassGenDef),统一编译期 TypeError。
void* 已在上游归并为 i8*,不受影响。
2026-10-01 21:43:11 +08:00
TermiNexus 59865b2a3a feat(TPV): void 值操作 fail-fast(声明 void 变量 / 把 void 值当值用一律报 TypeError)
承上一笔(裸名 void = t.CVoid 别名,仅 `void*` 与 `typedef 不透明结构体 = void` 可用):

- ExprGenBase.coerce_to_type 顶部:void 值被 coerce 到**非 void** 目标(赋值 / 传参 /
  返回 / 二元运算对齐)直接 fatal_error(TypeError),不再按 LLVM void 硬拼出非法 IR
  ("load void" / "bitcast ... to void");void → void 属无值传递,放行。
- AssignGenMixin.translate_annassign:注解解析结果为 void 时禁止声明该变量
  (否则会按 LLVM void 建槽)。
2026-10-01 21:39:48 +08:00
TermiNexus e0bcf4bcef feat(TPV): 裸名 void 作为 t.CVoid 的别名(void* = i8*)
用户 2026-10-01 需求:`void` 即 t.CVoid 的别名,只允许两种用法——`a: void*` 与
`typedef 不透明结构体 = void`(后者 `X*` 亦为 i8*)。

- TypeGenResolve 内置名表:新增 "void"(与 "CVoid" 同义 → llvmlite.Void)
- TypeGenResolve._is_cvoid_marker:裸名 "void" 与 "CVoid" 同判
- TypeGenResolve BinOp 联合合并:裸名 "void" 直接置 other_is_cvoid(无需 import t);
  解析结果本身是 Void 时亦按 void* = i8* 归并,覆盖 `typedef X = void` 的 `X*`
  (否则会拼出非法的 Ptr(void))
- VpiReader:注解文本里的裸 "void" 段与 "CVoid" 同判(has_cvoid → i8*);
  has_cptr 分支对解析结果为 Void 的 first_ty 同样归并为 i8*(跨模块侧同口径)

TPC 侧无需改动:HandlesBase 的内置名表已有 'void': (t.CVoid, 0),
`void*` 经既有 CVoid+CPtr 合并即 i8*。
2026-10-01 21:37:40 +08:00
TermiNexus 21d5cd1cd1 fix(ExprGenBase): REnum 变体构造纳入「构造物化」豁免;错误报文只报问题本身
1) _ctor_call_agg_ty 增加 REnum 变体构造识别:`EnumType.Variant(...)` 的变体名不是结构体名
   (find_struct 必 miss)⇒ 取属性链最右裸名,若判为基础枚举则返回该枚举的聚合类型。
   否则 `*槽 = <boxed 变体>` 会被「指针 → 非基本类型值」禁令拦下——
   现场 ClassGenDef.vp:128 `*existing.Ty = llvmlite.LLVMType.Struct(...)`。
2) 按用户要求简化 Error 输出:CastError 只报问题(原因 + 源/目标类型),去掉
   ";出错表达式: <形态>(行/列)源码: ..." 后缀(文件与行号已由 TypeGenErr 的 Traceback
   帧给出,属重复展示),并删除随之失去调用点的 _cast_fail_where / _source_snippet。
2026-10-01 21:17:49 +08:00
TermiNexus a714ec9d37 fix(VpiReader): 返回/形参注解切分只在顶层 | 处断段(括号感知)
_resolve_vpi_ret_annot_str 原按裸 `|` 切段,`list[ast.AST | t.CPtr] | t.CPtr` 会被切成
`list[ast.AST` / `t.CPtr]` / `t.CPtr`;首段再经 strrchr('.') 落到 `AST` ⇒ 定义侧类型解析成
`AST*`(元素指针),而正确答案是 list 专化结构体的指针。现场:跨模块调用 coerce 时报
"源类型 list[AST*]* → 目标类型 AST*"(CallGenLib._coerce_args_to_def_params 的 cargs 形参)。

现切分按 `[`/`]` 深度,只在顶层 `|` 处断段(与 _cache_vpi_param_annots_text 的顶层逗号
切分同口径)。改后该段解析不出 ⇒ 落 i8* 通配,与 `list[str] | t.CPtr` 等同形注解的既有
行为一致;配合 CallGen 侧的 bitcast 对齐(见上一笔)即可安全使用。
2026-10-01 20:45:21 +08:00
TermiNexus 8e3811c490 fix(CallGen): 跨模块调用的 declare 形参与实参统一以定义侧为准
问题一(declare 形参被首个调用点冻结):CallGenBase._ensure_method_declare 新建跨模块方法
declare 时形参类型取"首个调用点的实参类型",而这条 declare 被同模块所有调用点共用 ⇒ 后续
调用传入另一种具体指针类型时,被这条被冻住的形参类型 coerce,报"非基本类型指针间隐式转换"。
现场:VarGenMixin.repl_ensure_entry 的 node: ast.AST*,先被 translate_annassign 以 ast.AnnAssign*
建 declare,后 translate_assign 传 ast.AST* 即报"源类型 …AST* → 目标类型 …AnnAssign*"。
⇒ 指针形参与整数同口径,一律采用定义侧(.vpi 注解段)类型;解析不出指针时保留实参类型。

问题二(实参未对齐 declare):declare 改用定义侧类型后实参也必须对齐,否则首个调用点
(由它建立 declare、不走 coerce_to_type)原样传具体指针会与 declare 类型不一致(llc 报
argument type mismatch)。⇒ CallGenLib._coerce_arg_value_to_ty 的同层指针分支改为"类型内容
不一致即 bitcast 到定义侧类型"(原判据只覆盖一侧为 i8*、或模板注解来源两种情形)。
2026-10-01 20:29:15 +08:00
TermiNexus 93e983f74e fix(TPV): 删除残留的"| t.CPtr struct 引用语义吸收"(T* 被降一层的真正根因)
现场(0a81e67 之后仍复现):Test/SlotsBugTest 编译 includes/_list.vp:54
  TypeError: 下标赋值的左值元素是对象 'd1ed020479454541.AST'(48 字节),右值却是指向
  'd1ed020479454541.AST' 的指针
  elem_ptr[0] = item

关键事实:`T*` **不是** `rawptr[T]` 下标形——parser 把尾随 '*' 解析成
BinOp(`T | t.CPtr`)(includes/ast/parser.vp:1528-1556 `_make_ptr_type`、:2700-2707)。
所以它走 resolve_annotation_type 的 **BinOp 联合分支**,0a81e67 只修了
rawptr[T] / t.CPtr[T] 两条**下标**分支,覆盖不到这里。

链路(逐段在源码上核对过):
1. 泛型实参 `rawptr[AST]` 经 _norm_type_arg_name 归一为文本 `{sha}.AST*`;
2. `T` 走 Name 分支的泛型替换 → _parse_type_string 的 `T*` 反解析(:995-1018,
   按尾随 '*' 层数包 rawptr[...])⇒ other_ty = Ptr(AST),本身正确;
3. BinOp 分支尾部的"struct 引用语义吸收":other_ty 已是 Ptr(已注册 struct)
   ⇒ 直接 `return other_ty`,**吞掉 CPtr 这一层** ⇒ `T*` 变成 AST*(应 AST**)。

改动:
- TypeGenResolve.vp BinOp 分支:删除该吸收,恒 `return Ptr(other_ty)`
  (与 rawptr[T] / t.CPtr[T] 下标形、_parse_type_string 的 `T*` 反解析三处同口径)。
- VpiReader.vp:1614-1621 注入侧的同源吸收一并删除(维持"定义侧 / 注入侧 / 调用侧
  三处指针层数口径同步")。
- 两处口径注释同步改为"`| t.CPtr` 恒追加恰好 1 层,无例外"。

安全性(该吸收已是死补偿):它是"裸类名自动升指针"时代的产物——当时裸类名解析成
Ptr(struct),所以要吸收;自 2026-10-01 起裸类名恒为原型 struct,`X | t.CPtr` 由
Ptr(struct) 天然得到 X*。json/__init__.vp:90 的引号注解 `'JsonValue' | t.CPtr`
现在 other_ty 就是 struct ⇒ 吸收本不触发,删除后行为不变(JsonTest 已实测翻译通过并
进入 llc 阶段)。`list[int]`(i32*)、`list[ast.AST]`(struct*)、裸类名 `X | t.CPtr`
(struct*)均不受影响。

待验证:重编译 TransPyV(自举两遍)→ SlotsBugTest / JsonTest / WebTest。
2026-10-01 17:46:53 +08:00
TermiNexus 0a81e67e89 fix(TPV/TypeGenResolve): rawptr[T]/t.CPtr[T] 恒追加 1 层,删除"裸类引用吸收"旧补偿
现场:Test/SlotsBugTest 编译 includes/_list.vp:54 报
  TypeError: 下标赋值的左值元素是对象 'd1ed020479454541.AST'(48 字节),右值却是指向
  'd1ed020479454541.AST' 的指针
  源码: elem_ptr[0] = item

用户判定正确:`elem_ptr: T*` 与 `item: T` 同为 T,赋值本身合法;错在编译器把 `T*`
少算了一层——T 实例化为 rawptr[AST](= AST*)时,`T*` 应为 AST**。

根因:rawptr[T] / t.CPtr[T] 分支里有"切片是裸类引用(解析结果 Ptr(已注册 struct))
就吸收这 1 层"的旧补偿(_ptr_subscript_absorbs_class)。那是"裸类名自动升指针"时代的
产物——当时裸类名解析成 Ptr(struct),于是 rawptr[X] 会多包一层,需要吸收;
自 875a9be 起裸类名恒为**原型(struct 值)**,Ptr(struct) 天然就是 X*,补偿已无对象;
而**泛型形参实例化为指针类型**时(T = rawptr[AST]),切片的解析结果同样是
Ptr(已注册 struct) ⇒ 被误吸收:`elem_ptr: T*` 变成 AST*(应为 AST**)⇒ 左值元素被判成
48 字节的 AST 对象、右值是指针 ⇒ fail-fast。

改动:
- 删除 _ptr_subscript_absorbs_class,以及 rawptr[T] / t.CPtr[T] 两处调用,改为**恒追加
  1 层**:`rawptr[T]` ≡ `t.CPtr[T]` ≡ `T*` ≡ `T | t.CPtr` 四种写法同型(用户口径),
  不再有"裸类引用吸收"例外。
- 同步修正四处引用该函数的口径注释(:407 / :433 / :567 / :1000)。
- **保留** BinOp `| t.CPtr` 分支内的同名吸收:它只对**非裸名**侧可达(Constant/Attribute;
  裸名侧在更早的"Name 必须已注册 struct"守卫处就改走其它分支),故
  includes/json/__init__.vp 的引号注解 `'JsonValue' | t.CPtr` 行为不变。
  仓库内也不存在 `rawptr['X']` / `CPtr['X']` 切片写法(仅注释提及),删除吸收无副作用。

已知遗留(本次未改,待你定夺):`T | t.CPtr` 这一写法在 T 实例化为指针类型或类类型时
仍会退化——Name 侧解析出的 Ptr(struct) 被丢弃,随后按名字 'T' 查 struct 失败 ⇒
返回 Ptr(i8)(位置:本文件 BinOp 分支的 keep_other_ty 判定,约 :3166-3194)。
当前仓库没有使用该写法的现场,故未一并改动。

待验证:重编译 TransPyV(自举两遍)→ SlotsBugTest / JsonTest / NoVTableCrossMixinTest。
2026-10-01 16:51:31 +08:00
TermiNexus f748cae562 fix(TPV/CallGenCall): 判据化重加兄弟 Mixin 方法解析(修 NoVTableCrossMixinTest 未定义符号)
现场:Test/NoVTableCrossMixinTest 链接期报
  ld.lld: error: undefined symbol: 1e19037e6cfdce36.BaseB.helper
    >>> referenced by ...(1e19037e6cfdce36.BaseB.caller)
  ld.lld: error: undefined symbol: 1e19037e6cfdce36.MixinC2.mid2
    >>> referenced by ...(1e19037e6cfdce36.MixinC2.outer2)

这是 455c760 删除"兄弟 Mixin 兜底"的必然结果:NoVTable 多继承下,Mixin 内的
self.<兄弟方法>()(Combined(BaseA, BaseB) 中 BaseB.caller 调 BaseA.helper)本类
MRO 查不到、跨模块 stub 也没有 ⇒ 调用名退化成 {sha1}.BaseB.helper(该符号不存在)。

改动(不是恢复原兜底,而是**判据化解析**,不做"同名即绑"的猜测):
- CallGenCall._translate_method_call:在自身 MRO 查找失败后新增兄弟 Mixin 解析,
  两条**正向判据**缺一不可:
    ① 接收类必须是某个已注册类的**直接父类**(它确实被组合使用,是真 Mixin)——
       挡掉 includes/w32/fileio.vp 的 FileW.__init__ → self.seek() 误绑 File.seek
       (FileW 无人继承,不是任何人的 Mixin);
    ② 候选类与接收类必须同处某个已注册**组合类的 MRO**(真·兄弟)。
  命中后 found_func / mc_anc 都指向定义类 ⇒ 调用名用定义类 SHA1 前缀(符号存在),
  this 由既有的 mc_anc this-adjust 分支按组合布局前移(该分支未改动)。
  只取**本模块内已有函数**的候选(跨模块定义类需经其 text.ll 取签名,本轮不启用);
  接收类为跨模块类时不启用(沿用原 sib_recv_local 守卫,避免误绑跨模块同名方法)。
- 新增两个判据辅助方法:_recv_is_mixin_base / _mixin_sibling_same_composite。

待验证:重编译 TransPyV(自举两遍)→ NoVTableCrossMixinTest / WebTest / FileIoTest。
2026-10-01 15:00:55 +08:00
TermiNexus dc4e06d88b fix(TPV): 本文件自己声明的 extern 裸名优先于依赖 vpi 注入的同名声明
现场:Test/WebTest 编译 includes/socket.vp:236 报
  CastError: 指针隐式转指针已禁止:源 %"56d0ee99ecf0a0b5.SocketAddr"* →
  目标 %"99c88d4cbd933ac6.WSASocketAddr"*
  源码: result = connect(self.fd, c.Addr(addr), 16)

机制(**链接期无冲突**——两边都不是 export,@connect 是同一个 C 符号,最终只有
ws2_32 里一个定义;问题在"编译 socket.vp 时的同一个模块内"):
1. extern/state/export 声明的符号**不带 SHA1 前缀**(FuncGenDef.vp:172-173
   `mangled_name = fd.name`)⇒ socket.vp 与 w32.winsock2 都产出裸名 @connect
   (前者形参 SocketAddr|t.CPtr,后者 WSASocketAddr|t.CPtr)。
2. Translator 的 prologue 先注入依赖 w32.winsock2 的 vpi
   (StubMergerBuild.LoadDepVpiIntoModule → VpiReader.ParseVpiIntoModule →
   _inject_function_declare:create_declare + module_add_function),当前模块里
   先出现了 winsock2 版的 @connect。
3. 随后 forward_declare_functions 处理 socket.vp 自己的 `extern def connect` 时,
   find_func_in_module 命中已存在 ⇒ `continue`(FuncGenDef.vp:178-182)
   ⇒ **本文件自己的声明被静默丢弃**,调用点改按 winsock2 的形参 coerce
   (报错里的目标类型 WSASocketAddr* 正是 winsock2 版的指纹)。
   (同文件 send/recv/socket/listen 亦被顶掉,只是形参是 i8*/u64,不报错——
   即 socket.vp 的 POSIX 分支本来就在调用 winsock2 的声明。)

改动(注入期跳过"本文件已声明的裸名",让本文件自己的声明优先):
- StructGenMixin:新增 FileFuncNames 表 + register_file_funcs(pool, tree) /
  is_file_func(name),与既有的 FileClassNames / register_file_classes /
  is_file_class 同规矩(键与表同池、每文件随 reset_visible_structs 重建)。
- GenApi:新增 register_file_funcs / is_file_func 委托包装。
- Translator._translate_module_level:在 register_file_classes 之后调用
  register_file_funcs(三种模式共用、早于依赖 vpi 注入)。
- VpiReader._inject_function_declare:算出 mangled name 后,若 `use_bare_name != 0`
  且 `is_file_func(fd.name) != 0` 直接返回(不注入)。判据只用**裸名**:带 SHA1
  前缀的依赖函数名与本文件同名声明不会互相顶替,避免误跳过跨模块调用。

待验证:重编译 TransPyV(自举两遍)→ WebTest / JsonTest / FileIoTest。
2026-10-01 14:46:47 +08:00
TermiNexus d979cc8c1f fix(TPV/ExprGenName): 结构体值局部变量在使用处要求同型结构体值时 load 出值
现场:Test/WebTest 编译 includes/socket.vp:121 报
  CastError: 非基本类型的值/指针隐式转换已禁止(构造物化除外):
  不能把指针类型隐式转为非基本类型值(Foo* → Foo)
  源类型 %"56d0ee99ecf0a0b5.SocketAddr"* → 目标值类型 %"56d0ee99ecf0a0b5.SocketAddr"

现场代码:
  class SocketAddr:  family: u16 / port: u16 / addr: u32 / zero: u64   # 纯结构体,无 __new__、无方法
  def _MakeAddr(host: str, port: INT) -> SocketAddr:
      addr: SocketAddr                     # 结构体值局部变量
      string.memset(c.Addr(addr), 0, 16)
      ...
      return addr                          # ← 第 121 行报错

根因:ExprGenName.translate_name_value 对"结构体值局部变量"的右值**恒返回 alloca 地址**
(原实现,注释解释为"对象即指针"语义,避免 ptrtoint %Struct 非法指令);而 _MakeAddr
的返回类型经 resolve_annotation_type 解析为**结构体值**。translate_return 会把函数返回
类型作为 target_ty 下传给 translate_value(StmtGenCifMixin.vp:557-558),于是 `addr`
求值出 %SocketAddr* → coerce_to_type 的"指针 → 非基本类型值"分支
(ExprGenBase.vp:954-970)只对"构造物化"豁免 → 报错。
标量局部变量无此问题(同函数内非 Struct 分支会 build_load 出值),属结构体值局部变量
独有的表示差异。

改动:ExprGenName.translate_name_value 的 Struct 分支增加 target_ty 判据——使用处
**明确要求同一非基本类型(非指针)**时(_same_agg_ty 判据:与"构造物化豁免"同一类型
匹配真源 AssignGen._stg_ty_equal),像标量一样 build_load 出值;否则维持既有"返回地址"。
- target_ty 为指针时不命中(裸类形参 lowering 为 Ptr(struct),见 FuncGenDef)⇒
  Test/StructTest 的 point_sum(p) 这类实参传址行为不变。
- 字段访问基座(ExprGenAttr.vp:743-744,直接 lookup_var 取地址)、c.Addr 取址
  (CallGenCBuiltin.vp:293)、方法接收者均不经本函数取地址,不受影响。
- 同时修正该处已失真的注释(原文举例 `x: Cls | t.CPtr` 实际解析为 Ptr(struct),
  并非 Struct 值局部变量)。

待验证:重编译 TransPyV(自举两遍)→ WebTest / StructTest / ClassArrayTest / JsonTest。
2026-10-01 14:26:18 +08:00
TermiNexus 25c1c9875b fix(TPV/VpiReader): 阻断 vpi 自注入(循环 import)+ __init__ 声明端与定义端同取 void
现场:Test/JsonTest 编译 includes/json/__parser.vp 后 llc 报
  06cb4f5b8fd08841.ll:259:7: error: value doesn't match function result type 'i32'
    ret void
(该函数是 define i32 @"06cb4f5b8fd08841.ParseState.__init__",函数体末尾却是 ret void)

根因两层,都在 vpi 注入侧:
1) 自注入:__parser.vp 与 json/__init__.vp 循环 import(__parser 顶部
   `from . import (JsonValue, ...)`,__init__.vp 底部 `from .__parser import parse`)。
   __parser 编译时,其**自身** vpi(06cb4f5b8fd08841.vpi 里有
   `class ParseState: def __init__(self, src: str, pool: rawptr[...]): pass`)经依赖链
   (deps: t c stdint memhub string json → json 的依赖含 json.__parser)被反向注入
   ⇒ 模块 IR 里先出现 `declare i32 @"06cb....ParseState.__init__"`(IsDeclared=1)。
   该方法随后走 _translate_method 的"复用前向声明"路径,函数体按**定义端**语义发射
   (__init__ → 末尾 ret void),而函数对象自身的 RetTy 仍是注入时的 i32。
2) 类型口径分裂:VpiReader._inject_method_declare 对"方法无返回注解"一律退 i32 默认,
   而定义端 ClassGenVTable._translate_method 对 __init__ 恒 Void ⇒ declare i32 /
   define void。此前只在跨模块出现(不同 .obj,链接器不校验类型,静默 ABI 不一致);
   自注入把两者塞进同一个模块,才被 llc 拦下。

改动:
- VpiReader._inject_method_declare / _inject_function_declare:新增自注入守卫——
  仅全量翻译阶段(trans._declare_only == 0)且 src_sha1 == trans.ModuleSha1 时直接返回。
  本模块自己的 define 由 ClassGenVTable._translate_oop_methods / FuncGenDef 负责发射,
  不需要也不允许经依赖 vpi 反向注入(注入侧类型可能与定义端不一致)。
- VpiReader._inject_method_declare:__init__ 无返回注解 → Void(与定义端一致),
  消除 declare/define 返回类型分裂。__init__ 的返回值在三处调用点
  (CallGenCtor.vp:227 / :589 / :1275)均未被使用,改 void 不影响调用方。

待验证:重编译 TransPyV(自举两遍)→ JsonTest / FileIoTest。
2026-10-01 14:11:45 +08:00
TermiNexus 2bfcb6ef6e fix(TPV): 修 455c760 遗漏——args_node 必须声明在 decl_reuse 分支之外
现场:Test/FakeDuckTest 编译 includes/viperio.vp 第 12 行
  self.data = data   →   NameError: 未定义名称 'data'
(Buf.__init__ 的第一个形参名在方法体里找不到。)

原因:455c760 把整段"参数创建块"置于 if decl_reuse == 0: 之下时,把
args_node: ast.Arguments* = fd.args 一并圈了进去;但函数体段的
"为其他参数创建 alloca 并 store"(ClassGenVTable.vp:904 起)仍要遍历
args_node。复用前向声明(decl_reuse==1,现已是常规路径)时 args_node
未定义 → 形参不进符号表 → 方法体引用形参名即报 NameError。

修法:把 args_node 的声明提到 decl_reuse 分支之前。该块内其余变量
(md_defaults/md_default_count/md_param_count/md_args_node/md_ags/
md_alist/md_dlist/max_funcs/first_pname/first_pname_buf/fd_ags/
fd_first_arg/ags/alist/an/start_idx/ai/arg/param_ty/resolved/pname/
i1_ty_si/i1_ptr_ty_si)已逐一核对,全部只在块内使用,无需外提。

待验证:重编译 TransPyV(自举两遍)→ FileIoTest / FakeDuckTest。
2026-10-01 13:57:04 +08:00
TermiNexus 455c7608b1 fix(TPV): 方法先声明后定义(消除方法查找的源顺序依赖),删除兄弟 Mixin 兜底
现场:Test/FileIoTest 编译 includes/w32/fileio.vp 报
  CastError: 源 %"b0721da228be3d55.FileW"* → 目标 %"b0721da228be3d55.File"*
  (指针隐式转指针已禁止:被指类型是非基本类型)

根因(有旧 IR 佐证,非本轮改动引起):FileW.__init__(源 304 行)里的
self.seek(0, SEEK_END) 被解析成 File.seek,而不是 FileW.seek:
  1) _translate_method 只在方法体翻译时才 create_function,于是方法查找
     (find_func_in_module)依赖方法在类体内的书写顺序:FileW.seek 在源 403 行,
     晚于 __init__ → 查不到 → None;
  2) MRO 无父类 → None;
  3) 兄弟 Mixin 兜底随即遍历全部已注册 struct,取"本模块存在同名方法"的
     第一个候选类 → 命中 File → 绑成 File.seek,self 再被 this-adjust
     bitcast 成 File*。
旧 IR(TransPyV\.tpv_build\temp\b0\b0721da228be3d55.text.ll,mtime 9/27,
早于本轮全部改动)里就是:
  %97 = bitcast %"...FileW"* %96 to %"...File"*
  %98 = call i32 @"...File.seek"(%"...File"* %97, i32 0, i32 2)
即靠一层隐式跨类 bitcast 静默通过;指针规则收紧后以 CastError 报出。
(File.__init__ 的 self.seek 同样查不到,只是兜底恰好扫到 File.seek 而"碰巧正确"。)

改动:
1. ClassGenVTable._translate_oop_methods:翻译方法体之前,先对本类**全部方法**
   跑一遍 sig_only=1 建前向声明(declare + 参数 + 函数表登记)——与
   FuncGenDef.forward_declare_functions 对模块级函数做的事完全同构,
   使方法查找不再依赖类体内的书写顺序。
2. ClassGenVTable._translate_method 新增 sig_only 形参:
   · 签名部分改用 create_declare(IsDeclared=1),已存在同名前向声明则复用;
   · 发射函数体前把 IsDeclared 清零转 define(对齐 FuncGenDef.vp:544-545),
     否则打印端按 IsDeclared 输出 declare、函数体整体丢失;
   · 复用声明时跳过重复的 add_param 与 add_func_to_table;
   · 命中同名函数且 IsDeclared==0(函数体已发射)时直接返回,杜绝重复发射
     (llvmlite.create_function 不做同名去重,旧写法会造 duplicate symbol)。
3. CallGenCall._translate_method_call:按"不兜底、fail-fast"决定,整块删除
   兄弟 Mixin 兜底(遍历 struct 表猜候选类)及其死分支
   (mixin_hit / mixin_ret_ty / mixin_anc_name / mixin_anc_sha1 的声明与消费点)。
   方法未命中即为真未定义,交下方 text.ll / vpi / 本模块豁免路径
   (CallGenCall.vp:962 起的 is_local_mod 分支,从已声明的本模块函数直接取
   返回类型)处理,不再猜候选类。
4. 同步修正 ClassGenVTable 顶部 _translate_method 的注释块与函数文档串。

TPC 侧无需同步:TPC 的声明阶段把类成员全部登记进 SymbolTable,本就不存在
"方法查找依赖源顺序",也没有这段兄弟 Mixin 扫描(HandlesExprCall.py:3877-3914
的兜底带"调用类自身未声明该方法"守卫,与本次删除的 TPV 版不同)。

待验证(不自行执行编译命令):重编译 TransPyV(自举需编两遍),再跑
FileIoTest / NoVTableCrossMixinTest / DictTest / 泛型相关测试。
2026-10-01 13:45:10 +08:00
TermiNexus 875a9beb08 refactor(TypeGenResolve): 裸类名恒为原型(struct 本身),删除 5 处 HasNew 自动升指针
口径(用户 2026-10-01 定):**裸类名注解 = 原型(struct 本身,类型/布局语义)**,
堆对象引用必须显式写 `T*` / `T | t.CPtr` / `rawptr[T]`。旧行为"有 __new__ 的类
(堆对象)裸名 ⇒ Ptr(struct)"是为兼容 `d: dict = dict(bd)` 这类"裸名 + 构造"写法
而设,代价是同一类型两种写法分裂:

  includes/json/__init__.vp:79  `def __getitem__(...) -> 'JsonValue' | t.CPtr`
  左边(引号字符串注解)先被解析成 Ptr(struct),联合分支再包一层 ⇒ JsonValue**
  而 `cur: JsonValue*` 是 JsonValue* ⇒ :90 `return cur` 报
  「指针层数不同(源 1 级 → 目标 2 级)」(660427f 已在联合分支做过吸收,
  本次从源头统一:裸名不再自动升指针,吸收那条规则对裸名侧自然不再触发)。

改动(resolve_annotation_type 的裸名/限定名分支,共 5 处,全部去掉 HasNew 分支):
1. `{16hex}.Class` 已归一前缀名分支(原 :2768-2770)
2. 本模块自有类分支(原 :2806-2814,删掉那段"有 __new__ ⇒ Ptr"的注释与分支)
3. from-import 归属分支(原 :2832-2834)
4. 可见结构体裸名分支(原 :2842-2844)
5. `mod.Class` 限定名分支(原 :3040-3044)

影响面盘点(静态扫描 474 个 .vp):本文件内"裸堆类名注解"16 处、跨文件候选 63 处
(含同名跨模块误报),典型:
  includes/hashtable.vp:45   ht: HashTable = HashTable(mb)
  includes/_dict.vp:29       d: dict = dict(mb)
  Test/BuiltinDecoratorTest/App/main.vp:33/36/68/98/137/180/184/194/199/234/241/248
  TransPyV/Test/App/new_test.vp:81/93/105
  Test/{ClassArrayTest,StructTest,ClosureTest2,StringIterTest}/App/*.vp 若干
这些站点需按新口径显式补 `*`(或改用 `T | t.CPtr`);真实集合以编译器 fail-fast
报错为准(同名跨模块无法静态判定归属)。

TPC 侧核查结论:**无需改动,TPC 本就同口径**。证据:
1. 全仓 `HasNew|has_new` 仅 8 处,全在**构造路径**——HandlesAnnAssign.py:401
   (`list[T] = [...]` 字面量构造)、HandlesExprCall.py:5105/5161/5170/5198/5235/5263
   (`ClassName(...)` 构造,`_HandleClassNewLlvm`);**注解解析链上一处都没有**。
2. 规范注解解析 `TypeAnnotationResolver._from_node_name`(裸 Name)、
   `_from_node_attribute`(mod.Class)、`_from_node_constant`('X' 引号注解)
   全程不查 __new__,裸类名一律 PtrCount=0(原型)。
3. 局部变量路径 HandlesAnnAssign.py:678-683 显式把裸类名 PtrCount 由 1 改回 0
   ("纯结构体名注解应为值类型"),仅保留 vtable 类为指针;模块级全局
   HandlesAssign.py:1840-1856 亦按 struct 值类型建 GlobalVariable。
4. 符号表类条目 PtrCount 默认 0(SymbolTable._InsertClassSymbol:489-518 从不设
   PtrCount;TypeSpec.py:139 默认 0)。

因此 `d: dict = dict(mb)`(dict 有 __new__、非 vtable 类)在 TPC 侧本来就是
struct 值语义,与本次 TPV 改动后的口径一致。json/__init__.vp 的
`'JsonValue' | t.CPtr`(定义端 0+1 层、声明端裸类引用吸收 1 层)两侧都已是
`%"…JsonValue"*`(1 层),本次 TPV 改动后亦对齐。

待验证(不自行执行编译命令):先重编译 TransPyV,再跑
  JsonTest / DictTest / StructTest / BuiltinDecoratorTest / ClassArrayTest
预期先出现一批"注解需补 `*`"的报错——那正是本次口径变更要求的显式化。
2026-10-01 12:38:24 +08:00
TermiNexus 660427f759 fix(TypeGenResolve): X | t.CPtr 的 struct 引用语义吸收这层指针,修 json __getitem__ 报 JsonValue** vs JsonValue* 层数不同
现象(TransPyV 编译 includes/json/__init__.vp):
  line 90  return cur
  CastError: 指针隐式转指针已禁止:指针层数不同(源 1 级 → 目标 2 级)
    源 %"e966d554e91364bc.JsonValue"* → 目标 %"e966d554e91364bc.JsonValue"**

根因链(逐环取证):
1. includes/json/__init__.vp:79
     def __getitem__(self, key: str) -> 'JsonValue' | t.CPtr:
   注意左边是**引号字符串注解**(Constant),不是裸 Name。
2. JsonValue 是带 __new__ 的**堆类**(该文件 :34),按引用语义,裸类名注解在
   resolve_annotation_type 的 Name/Attribute 分支直接给 Ptr(struct)
   (:2806-2813 注释「有 __new__ 的类(堆对象)注解 → Ptr(struct)(引用语义,对齐 TPC)」,
    Attribute/sha1 前缀/from-import 各分支同口径)。
3. 于是 BinOp(`| t.CPtr`) 分支里 other_ty 已经是 Ptr(JsonValue);而 :3176 那段
   「Name 且未注册 struct 才丢弃」的守卫只认 Name —— Constant 绕过了它,
   带着 Ptr(struct) 走到 :3264 `return llvmlite.Ptr(pool, other_ty)` 再包一层
   ⇒ JsonValue**。
4. 裸名写法(不带引号)不会暴露:它会在 :3222-3239 被判 td_res_is_ptr=1 →
   other_ty 置 None → 走 :3254 回退取 struct 再包一层,恰好抵消。
⇒ 同一类型两种写法产出两个层数(`JsonValue*` vs `JsonValue**`),与用户
   2026-09-30 定的统一口径冲突:T* ≡ rawptr[T] ≡ t.CPtr[T] ≡ T | t.CPtr。

改动(TypeGenResolve.resolve_annotation_type 的 BinOp(|t.CPtr) 分支):
- 在 :3264 包装前落实本文件 :3219-3221 注释里早已写明、但此前**未实现**的那条
  规则:「| t.CPtr 恒为追加 1 层,唯一例外是 struct 引用语义时吸收这 1 层
  (判据 = Ptr 的 pointee 是已注册 struct,find_struct_by_type 命中即吸收)」。
- 即:other_ty 已是 Ptr(已注册 struct) 时直接返回 other_ty,不再加一层。
- 副作用(同步修正同族写法):引号字符串写法、以及 Attribute 分支在
  find_struct_by_module 命中 HasNew 时返回 Ptr(struct) 的那条路径
  (:3040-3044),都从「多一层」收敛为 T*。
- 不影响的既有例外:VOIDPTR/CVoid|CPtr 等「指针别名」侧解析出的是 Ptr(i8)
  (i8 非 struct,命中不到 find_struct_by_type)⇒ `VOIDPTR | t.CPtr` 仍是
  i8**(追加一层),与 VpiReader 侧口径一致。

仅 TPV 侧(TPC 侧走 SymbolUtils.AddPointerLayers 的另一套口径,本次不动)。

待验证(不自行执行编译命令):先重编译 TransPyV,再跑
  TransPyV.exe --project ./Test/JsonTest/project.json --clean --clear-cache --rebuild-includes --traceback
预期 :90 不再报层数不同;若还有同族报错请把栈贴回。
2026-10-01 00:51:51 +08:00
TermiNexus 3329c9beae fix(TPV/CallGenBase): text.ll 返回类型的命名指针不再坍缩成通用 i8*,修 FakeDuck parts[0] 报整数隐式转指针
现象(TransPyV 编译 Test/FakeDuckTest):
  main.vp:115  p0: str = parts[0]
  CastError: 整数隐式转指针已禁止…出错表达式: 下标表达式 Subscript
    (parts = s1.split(','),s1: str,split 由 includes/_fakeduck.vp 的 _str 提供)

根因链(逐环取证,均落在产物文件上):
1. _fakeduck.vp:273  `def split(self, delimiter: str) -> list[str]*`。
2. 定义侧产物正确:temp/88/884eaabef9bd161c.text.ll:1775
     define %"b0798adca340002b.list[str]"* @"884eaabef9bd161c._str.split"(...)
   (884eaabef9bd161c = includes/_fakeduck.vp,b0798adca340002b = includes/_list.vp)
3. 调用侧 declare 走了 vpi 注解:temp/88/884eaabef9bd161c.vpi:27
     def split(self, delimiter: str) -> list[str] | t.CPtr: pass
   该联合在 stub 侧解析不出 `list[str]` 这个泛型特化名 ⇒ 坍缩成 i8*(既有设计已知的
   「跨模块 declare 的 list[T] 返回类型被简化」),故 _translate_fakeduck_call 里那条
   「从定义模块 text.ll 读精确返回类型」的兜底(CallGenFd.vp:134-142)必须生效。
4. 但该兜底读到的 `%"b0798adca340002b.list[str]"*` 又被本函数坍缩回 i8*:
   CallGenBase._ParseLLVMRetTyStr 的「含 * 者一律返回通用 Ptr(Int8)」分支
   (原注释:"包括 i8*, i32*, %"name"*, { i32, i8 }* 等")。
5. 于是 parts 的 alloca 退化为 i8* ⇒ parts[0] 走字节下标取到整数 ⇒ 赋给 str 报
   「整数隐式转指针已禁止」(报文指不到真因,正是本问题最难查的地方)。

改动(CallGenBase._ParseLLVMRetTyStr):
- 新增「命名类型 `%"name"*` / `%name*`」分支:剥引号/空格与尾随 `*`(支持多级),
  先 GenApi.get_struct_type(name)、未命中再 GenApi.get_enum_base_type(name),命中即
  按星号层数包 Ptr 返回真实结构体/枚举指针;查不到登记项时才保持原来的通用 Ptr(Int8)
  行为(名字可能来自尚未登记的模块,不引入新的静默降级面)。
- 同步更新该函数顶部说明(原写「含 * 者返回通用 Ptr(Int8)」,现区分字面量结构体 /
  命名类型 / 其余)。
- 收益面不限 FakeDuck:所有经 text.ll 取返回类型的方法调用(CallGenCall / CallGenDispatch
  / CallGenProp / CallGenFd 等)都从「指针一律 i8*」变为「命名类指针精确」,
  调用方 alloca 与下标/字段语义不再退化。

待验证(不自行执行编译命令):用 TransPyV.exe 重跑
  --project ./Test/FakeDuckTest/project.json --clean --clear-cache --rebuild-includes --traceback
预期 115 行不再报错;若仍在 parts2.get(0)(131 行)等处冒新错,请把栈贴回来。
2026-09-30 23:53:18 +08:00
TermiNexus f2f3e5b7bf fix(TypeGenResolve): 裸名内建类型统一为 t.X/c.X,消 CPtr 与 t.CPtr 的分裂与误报 ERROR
内建伪模块 t/c 没有模块 SHA1(规则 5 的模块名→sha1 表查不到),于是 from t import CPtr 之后的裸名 CPtr 被原样保留,而规则 3 保留的是 t.CPtr ⇒ 同一类型两种票名(list[CPtr] 与 list[t.CPtr] 分裂),并额外刷 ERROR [类型实参归属] 裸类名 CPtr 保留裸名(现场 Test/NewAstTest/App/main.vp,log 里 from_imports 明写 CPtr:t)。新增规则 5.2:from_imports 命中模块 t/c 且 map_t_type 认该名 ⇒ 返回 t.X/c.X(规则 3 已定的规范形)。只对"本名就是 t/c 已知内建类型名"生效,别名写法不猜真实符号名、维持原样。
2026-09-30 22:59:01 +08:00
TermiNexus 79f044bc47 fix(TypeGenResolve): _class_true_def_sha1 只取 hex 段,修双点前缀 d1ed…461..AST*
GenApi.sha1_prefix_len 返回含末尾 "." 的 17(其文档写明),前一版按 17 拷贝 hex 段后再交 _concat_sha1_class 拼 ".",得到双点前缀 %"….d1ed020479454541..AST*" ⇒ parser.vp:3355 return scratch.children 处 coerce 报 CastError。改为按 17 判形、比对与拷贝都用 16(hex 段本身)。
2026-09-30 22:45:26 +08:00
TermiNexus 7b0c05b70b fix(TypeGenResolve): 类名前缀校正为真定义模块,消 ast.AST(包) 与裸 AST 的前缀分裂
规则 4b 对 ast.AST(模块限定属名)走"模块名→SHA1"表,对包名回退到 ast/__init__.vp 的 sha1 1abcd5277fa442de(re-export 模块),而裸名 AST(stmts.vp 字段注解)经规则 5 后缀匹配得到真定义模块 d1ed020479454541(ast/base.vp)⇒ list[1abcd….AST*] 与 list[d1ed….AST*] 两个特化体并存,main.vp:63 classify_list(ifn.orelse,…) 报"指针隐式转指针已禁止"。新增 _class_true_def_sha1:在 cand 名下精确找登记项(find_struct_by_module,不按名回落,避免同名类跨模块误改),取其结构体 IR 名 %"<sha>.{类名}" 的前缀作真定义模块(不能取 entry.ModuleSha1——包 re-export 的别名 entry 记的是包 sha1、Ty 复用源 entry);无证据或前缀相同则原样返回 cand(已一致情形逐字不变)。接线:规则 4a/4b/5 各一行。
2026-09-30 22:16:15 +08:00
TermiNexus 0f6e102e02 fix(TypeGenResolve): rawptr[T] 下标形漏折叠致两个 list 特化体(修 _list.vp:83 return self 的 CastError)
前一版把 rawptr 从 _is_ptr_marker_text 移除(避免裸 rawptr 参与联合时名说 T*、类型说 T),但下标容器分支复用了同一判据 ⇒ rawptr[T] 完全没折叠,只有 T|t.CPtr 折叠:现场同时存在 list[<sha>.AST*] 与 list[rawptr[<sha>.AST]] 两个结构体,_list.vp:83 return self 的 self(前者)被 coerce 到返回注解(后者)时报 CastError。新增专用判据 _is_ptr_subscript_container_text(rawptr/t.rawptr/c.rawptr/CPtr 一族),下标分支改用它;联合分支仍用 _is_ptr_marker_text 保持与类型域 _is_cptr_marker 同口径。
2026-09-30 21:39:21 +08:00
TermiNexus b289e93650 fix(泛型实参 T* 形): 补齐 T* 消费侧(VpiReader 段解析 / AnnotClassName 提取 / 构造推断)
唯一规范形 T*(rawptr[T] ≡ t.CPtr[T] ≡ T|t.CPtr,见 83baf5a/2b2b70e)落地后,
所有"读**已归一实参文本**或**结构体名**并做文本解析/嗅探"的点都必须接受 T* 形,
否则会**静默**走错分支(不是报错,更难发现):

- VpiReader._resolve_single_vpi_type_str:尾随 * 段按层重写回 rawptr[...] 文本形
  再走原分支(与既有 rawptr[ 分支逐字等价)。必须补的原因:_subst_template_arg
  用特化类名 [...] 内的**已归一实参**替换模板注解里的 T,喂进来的就是 {sha}.AST*;
  旧代码只认 rawptr[ / t.CPtr[ / 裸 t.CPtr ⇒ 落到 strrchr('.') 取末段 "AST*"、
  查 struct miss ⇒ 返回 None/i8*,跨模块参数/返回宽度被悄悄收窄。
- ExprGenCls / AssignGenMixin 的 _extract_annot_class_name(各 2 处):段尾剔除集
  补 '*',剥后再 find_struct(旧形态 {sha}.AST|t.CPtr 按 '|' 分段能命中,新形态
  不剥则 miss ⇒ 返回 None ⇒ 链式 .get(0).kind() 分派退化)。
- CallGenCtor._infer_generic_ctor_type_arg:新增 _ptr_layer_type_arg —— 先经
  _norm_type_arg_name 归一(保证类名带定义模块 SHA1,否则裸 "AST*" 无法再归一、
  反而新分裂),再按 VarEntry/FieldEntry 的 Spec.PtrLevel 补 '*',使构造侧推出的
  类型实参与注解侧同票名(否则 Box(x) 这类隐式特化分裂成两个结构体)。
- CallGenDispatch.__sizeof__:'*' 判据由 strrchr 改为**尾随**判定 —— 原写法会把
  list[{sha}.AST*](元素为指针的容器)误判成指针并返回 8。

现场:Test/AstTest/App/main.vp:21 的 list[ast.AST | t.CPtr] | t.CPtr 特化链
(→ includes/_list.vp:36 T.__sizeof__())。
2026-09-30 21:16:48 +08:00
TermiNexus 2b2b70e636 fix(CallGenDispatch): __sizeof__ 认归一后的指针规范形 T*(尾随 * 一律按 8)
泛型实参归一为 T* 后,T.__sizeof__() 的实参串如 {sha}.AST* 既不命中 | 也不命中 rawptr[ 前缀,落回把接收者 Name(T) 当实例翻译 → NameError: 未定义名称 T(现场 Test/AstTest/App/main.vp:21 特化 list[ast.AST|t.CPtr] → includes/_list.vp:36)。在既有两条指针判据旁补尾随 * 判据(同一处、同一顺序,均在 dot-strip 之前)。
2026-09-30 20:58:02 +08:00
TermiNexus 83baf5a4b6 fix(TypeGenResolve/ExprGenName): 指针类型实参统一为 T*,消泛型特化名分裂(假报错 CastError)
用户定口径:t.CPtr[T] / rawptr[T] / T|t.CPtr ≡ T*(与 TPC 侧
SymbolUtils.ResolveTypeArgStr+AddPointerLayers 逐字同形,两侧必须同票名)。

- _norm_type_arg_name:联合一侧为裸 CPtr 标记 ⇒ 折成 <另一侧>*;单实参的
  指针标记容器 rawptr[T]/t.CPtr[T] ⇒ 折成 <实参>*(新增 _is_ptr_marker_text /
  _ptr_star_text;刻意只认 CPtr 一族,与类型域 _is_cptr_marker 同判据,
  避免名字说 T*、类型说 T 的不一致)
- _parse_type_string:能反解析尾随 '*'(按层包回 rawptr[...],供 ClassGenGeneric
  克隆注解时替换实参文本)
- ExprGenName._list_elem_obj_ty_from_struct_name:元素段剥尾随 '*' 后再查结构体
  (否则对象元素还原失败、静默退回 Ptr(i8) 丢元素类名)

现场:Test/AstTest/App/main.vp:63 classify_list(ifn.orelse, ...) 源
list[rawptr[<sha>.AST]] vs 目标 list[<sha>.AST|t.CPtr] ⇒ 严格指针规则按
「不同非基本类型」拒绝(假报错);改后同票名 ⇒ 同一结构体。

注意:特化名进结构体名与方法符号名 ⇒ 必须全量重编 includes
(--clean --rebuild-includes --clear-cache)。
2026-09-30 20:19:42 +08:00
TermiNexus f3c708613f refactor(TransPyV): 统一自举写法(字段默认值/novirtual/define/enum/指针注解)
- CallGenBase:字段声明处直接给默认值,InitCallGen 不再逐字段赋值
- CallGenFd/CallGenDispatch:@t.NoVTable 改 novirtual,清理过时注释
- VLogger:LogLevel 改 enum、t.CDefine 改 define 关键字、Logger|t.CPtr 改 Logger*、longjmp 用 rawptr
- Generator:_g_gen 由 Generator|t.CPtr 改为 Generator*
- CallGenProp:合并续行
2026-09-30 19:09:22 +08:00
TermiNexus 849001ae9e fix(AssignGen): 禁止注解声明类型被右值继承上转窄化
TPV 侧「注解退化修复」只挡了裸泛型基类降级,漏挡「注解写明基类、右值是派生类」的上转窄化:node: rawptr[AST] = Name(...) 会把 alloca 重建成 Ptr(Name),随后 node = <返回 rawptr[AST]> 变成被禁止的父→子下转(includes/ast/parser.vp:1919 报 指针隐式转指针已禁止:%AST* → %Name*)。补上与 TPC HandlesAnnAssign._rb_is_upcast 同判据的守卫(复用 ExprGenBase._is_inheritance_upcast)。
2026-09-30 19:08:58 +08:00
TermiNexus a92b38e826 fix(Translator): 文件级类名预登记覆盖三种 _declare_only 模式(修 Phase 1a 的类型实参裸名分裂)
类别:a(编译器缺陷)

现象:编译 includes/ast/base.vp 报
  ERROR [TypeGen]: [类型实参归属] 裸类名 'AST' 本次保留裸名(可能与带前缀形式分裂):
  entry_present=0 entry.Ty=None cur_def_mod=d1ed020479454541 cur_def_cls=ASTFlag
  is_module_class=0 find_struct_by_module=0 from_imports=*:stdint TokOp:ast.tokens
  file=./includes/ast/base.vp

根因链(逐条对应报文):
  报文来自 TypeGenResolve._norm_type_arg_name 规则 5.5:五类证据全落空即返回裸名。
  - ④ entry_present=0 / ① find_struct_by_module=0:'AST' 尚未 register_struct
    (AST 在 base.vp:313;本次归一化发生在更早的模块级函数签名处)。
  - ② is_module_class=0:'AST' 的 mark_module_class 尚未执行(类定义入口按 AST 顺序走)。
  - ③ cur_def_cls=ASTFlag:CurDefClassName 停在更早的 enum ASTFlag(类结束后不复位),
    与裸名 'AST' 逐字不同 ⇒ ③ 不适用(该值同时证明本次不是"泛型特化平移窗口")。
  - ⑤ is_file_class + get_file_module_sha1 本应兜住"本文件自己声明的类名",但
    register_file_classes 的调用被 `if self._declare_only == 0:` 整块门住;而本次是
    Phase 1a(_declare_only == 1)——TranslatorGenMixin 在 1a 会调用
    _prescan_funcdef_signatures **预解析模块级函数签名**(含注解归一化)⇒ 证据⑤ 恒落空。
  ⇒ 该实参特化名被冻结成**裸名**版本,与 1b 全量翻译期解析出的带 SHA1 形式分裂成两个
    特化结构体名,后续以"非基本类型指针不可隐式转换"报出(同类旧现场 base.vp:359)。

修复:把 GenApi.register_file_classes(pool, self.ModuleSha1, tree) 从 `_declare_only == 0`
  块内提到 _translate_module_level 开头,三种模式(1a=1 / 1a-pre=2 / 全量=0)共用。
  该登记只依赖 tree + 本文件模块 SHA1,与导入、类定义窗口、类定义顺序都无关;
  reset_visible_structs 在每个模式的 translate 之前调用(BuildPipeline:226 /
  Phase1Main:643),FileClassNames 与键同池创建,无悬空风险。未放宽任何 fail-fast。
2026-09-30 17:43:15 +08:00
TermiNexus 1cce6c4a47 fix(TypeGenResolve): 「保留裸名」误报的第二入口——逐实参归一时跳过定长数组的元素个数位 + CDefine 名不再算类名
类别:a(编译器缺陷)。`ef4421d` 只补了 `_norm_type_arg_name` 规则 1.5 的"跳过第 2 实参",
但那条规则**拿不到容器上下文**的入口不止一个,实测 still 报:

  ├ ERROR [TypeGen] [类型实参归属] 裸类名 'TRACEBACK_FRAME_BUF' 本次保留裸名 …
    entry_present=0 entry.Ty=None cur_def_mod=ba7cdbd0dec2aef8 cur_def_cls=TracebackState
    … file=./includes/traceback.vp

两处修(互为补充,判据都指向同一条事实:定长数组的第 2 实参是**常量**,不是类名):

1) `extract_class_name_from_annotation` 的 **Tuple 分支**(:2156/:2167,本函数自己按
   `,` 把实参拆开逐个归一,规则 1.5 在这里根本不生效):
   `TracebackState.FrameAddr: t.CArray[uint64, TRACEBACK_FRAME_BUF]`
   ⇒ 先按 `_is_count_arg_container(base_name)` 判容器是 t.CArray/list,是则第 2 实参
   (`ti_sn == 1`)**原样保留、不调用归一**(长度计算与拼接两个循环同步改)。

2) `_is_non_class_type_name` 增 **⑤ CDefine 名**:`NAME: t.CDefine = 128` 是编译期常量,
   名域就是它自身,永远不是类名 ⇒ 命中 `TypeGenCDef.lookup_cdefine_constant` +
   `is_cdefine_found()`(-1 也是合法值,必须看标志)即豁免"保留裸名"取证。
   这条判据落在**所有入口共用**的函数上,彻底覆盖其余"逐实参归一"的点
   (如 CallGenCtor:768 的泛型实参提取)。

静态核验:同形语句片(`self.Gen.TypeGenCDef.lookup_cdefine_constant(...)` 丢弃返回值、
`elt_name: str = raw` 后条件重赋值、`range`/`len` 循环)过 `lib.vast` 解析。
生效需重新自举 TransPyV.exe。
2026-09-30 16:05:29 +08:00
TermiNexus 2a798dd643 fix(类型转换): int→i1 目标改为真值归一化 != 0,不再 trunc 取最低位
类别:a(编译器缺陷)。`b: bool = f()`(f 运行时返回偶数非零,如 2)此前经
coerce_to_type 的窄化分支走 trunc ⇒ 最低位 0 ⇒ false,而同一值在 `if 2:` /
`bool(2)`(icmp ne 0)为真 —— 两处判据相反(静默语义分裂)。

- ExprGenBase.coerce_to_type:整数窄化分支前先判目标位宽 == 1 ⇒ `icmp ne x, 0`
  (C 6.3.1.2 的 _Bool 转换语义)。归一化不丢位,故对 i1 目标不再适用下方
  「窄化截断溢出」常量诊断(先返回)——`b: bool = 2` 由编译期 OverflowError
  变为正确的 true。
- CallGenCtor._translate_t_type_cast:显式 `t.CBool(x)` 与隐式落存储同一判据。

判据与 bool(x)(CallGenDispatch.vp:255-281)、条件归一化(AsyncGenMixin.vp:1611
_normalize_cond)、and/or 操作数三处统一。App/ 与 includes/ 全量检索未见
`: bool = …(& | >> | // | |)` 位提取写法,既有代码无依赖 trunc 取最低位的点。

生效需重新自举 TransPyV.exe(并清缓存)。
2026-09-30 15:14:32 +08:00
TermiNexus ef4421db20 fix(TypeGenResolve): 定长数组容器的「元素个数」实参不参与类名归属归一(消「保留裸名」误报)
【b 机制漏豁免】`_norm_type_arg_name` 规则 1.5 把 `Name[...]` 的**每个**顶层实参都递归
归一(目的是把类名统一成 `{定义模块SHA1}.类`)。但定长数组容器 `t.CArray[T, N]` /
`list[T, N]` 的**第 2 实参是元素个数**(编译期整数:CDefine 名 / 字面量 / 常量表达式,
依据是 TPC 侧 t.CArray 的定义原文"第一个参数为元素类型,第二个参数为数组大小(整数或
常量名)",lib/includes/t.py),对它做类名归属归一既无意义,又会落进规则 5.5 的
"保留裸名"取证 ⇒ 误报"类名归属缺失"。

现场:编译 includes/traceback.vp 时 TracebackState 的字段注解
`t.CArray[uint64, TRACEBACK_FRAME_BUF]`(traceback.vp:532),第 2 实参
'TRACEBACK_FRAME_BUF' 是 traceback.vp:482 的 `TRACEBACK_FRAME_BUF: t.CDefine = 128`
⇒ 每次编译刷
"[类型实参归属] 裸类名 'TRACEBACK_FRAME_BUF' 本次保留裸名…"ERROR。

修法:新增 `_is_count_arg_container(container)`(按**容器短名**判定,兼容 `tt.CArray`
这类别名;只认 CArray / list,泛型类 GSList[T]/Box[T] 的实参全是类型名不受影响),
规则 1.5 的实参拆分循环按序号跳过第 2 实参 —— 原样保留、不递归。
归一结果逐字不变:常量名与字面量既非类名也非 CTypedef,归一恒返回原串;TPC 侧
`ResolveTypeArgStr` 对同位置同样原样保留(Name→NormalizeGenericArgName / Constant→
unparse),两侧仍一致。改变的只是不再触发该取证日志。

配套:上一提交 d63787f 的 `_is_non_class_type_name` 覆盖同注解的**第 1 实参**
(`uint64` = builtins.vp 的 typedef)——两条判据互补:类型位放行非类名,个数位不归一。

未验证:需重新自举 TransPyV.exe 才生效。vast 解析通过。
2026-09-30 14:05:03 +08:00
TermiNexus d63787f8fc fix(TypeGenResolve): 「保留裸名」取证对 CTypedef/内置标量/泛型形参不再报 ERROR
【b 机制漏豁免】取证日志(df1244d 加入)的前提是"这是个**类名**":只有类名存在
"{定义模块SHA1}.类" 这第二个 mangling 形式,留裸名才可能与它分裂。CTypedef /
语言内置标量 / 泛型形参的名域就是它自身,保留裸名即规范形式(TPC 侧
SymbolUtils.NormalizeGenericArgName 同口径),对它报 ERROR 是纯噪声。

现场:编译 includes/traceback.vp 时,TracebackState.FrameAddr 的注解
`t.CArray[uint64, TRACEBACK_FRAME_BUF]`(traceback.vp:532)经规则 1.5 递归规范化
实参 'uint64' ⇒ 规则 5.5 取不到 SHA1 ⇒ 每次编译刷一条
"[类型实参归属] 裸类名 'uint64' 本次保留裸名…"。'uint64' 是 includes/builtins.vp 的
`typedef uint64 = t.CUInt64T`,不进类归属是预期行为,不是缺陷。

修法:新增 `_is_non_class_type_name(pool, name)`(正向白名单,不猜):
  ① CTypedef 动态表命中(Phase1 1b register_ctypedef_from_source 文本扫描注册);
  ② builtins 兜底 `_lookup_builtins_typedef`(与 resolve_typedef_or_map 同一口径:
     builtins 始终在编译链内,但其 typedef 不随 from_imports 注入 ⇒ 表可能 miss;
     命中即幂等注册进表);
  ③ 语言内置标量/字符串(int/char/bool/float/double/str/bytes/void,与
     _resolve_annot_type_name 的跳过清单同口径);
  ④ 泛型形参名(单字母大写 T/U/V…,特化前出现在实参位属正常)。
门控只对「结构体表里根本没有该名」(_bare_entry is None)豁免:若它确是已注册的类
(.vpi 半条目)只是缺定义模块 SHA1,那是真正的归属缺口,照旧报出,不被同名 typedef 掩盖。

未验证:本改动需重新自举 TransPyV.exe 才生效(TPC 侧无对应逻辑)。vast 解析通过。
2026-09-30 13:51:22 +08:00
TermiNexus 0e5ebd07ba fix(TypeGenCType): 撤销在子类类体重复声明父类字段(TypeGenBase 已声明),修指针隐式转整数误报
上一版(6a287b9)为修 Repl.vp:802 在 TypeGenCType 类体补了 5 个字段,但这 5 个
(CTypedefNames / CTypedefTypeStrs / CTypedefNameIdx / CTypedefNameIdxCap /
CTypedefNameIdxBuilt)**本就由父类 TypeGenBase 在类体声明**(TypeGenBase.vp:73-84),
属重复声明(c 源码违规)。

后果(有实证):这 5 个名字进入子类"自有字段"后,HandlesImports._FlattenExt 的
继承前缀前置过滤器(HandlesImports.py:2516-2521,为防重复前置而按名字过滤)
把**同名继承项整条丢弃** ⇒ class_members['TypeGenCType'] 变成 [36 继承项]+[5 自有项]。
被丢掉的 5 项(继承下标 22/23/25/26/27)都排在 LastBuiltinsTypedefResult
(继承下标 32)之前,使其表内下标降为 27;NoVTable 类 base=0 ⇒ 命中 body 槽 27
= CTypedefNameIdxBuilt(i32) ⇒ TypeGenResolve.vp 的
`self.Gen.TypeGenCType.LastBuiltinsTypedefResult = "t.CPtr"` 触发
"指针隐式转整数已禁止:不能把指针类型 i8* 赋给整数类型 i32"。

该重复声明还会让父类方法(TypeGenBase.vp InitTypeMixinFields 等)与子类方法
访问同一字段落到不同槽(split-brain)。

撤销后:子类自有字段=0,继承前缀 41 项按原顺序保留,LastBuiltinsTypedefResult
回到表内下标 32 = body 槽 32(i8*,与 TypeGenBase 声明的 str 一致)。

说明:Repl.vp:802 的真实根因(引用模块里继承前缀是否可达)尚未定论,本次只撤销
误修、不掩盖;该类属性链失败现已由 TPC 侧新增记账(a18ca4da)逐条给出失败原因链。
2026-09-30 10:46:43 +08:00
TermiNexus 6a287b955e fix(TypeGenCType): 跨模块可见字段改为在类体声明(修 Repl.vp:802 属性链求值为空)
类别:c 源码违规(跨模块读得到的字段必须在类体声明)

现象:TPC 编译 TransPyV/App/lib/core/Repl.vp:802
  ct_names: list[str]* = GenApi._g_gen.TypeGenCType.CTypedefNames
报「带注解赋值表达式 …(Attribute 形态)编译期求值为空(表达式未解析出目标、无值可存),
赋值将被跳过、变量保持未初始化」。

判据(.vpi 与定义侧不同源,实证):
· TypeGenCType 类体**一个字段都没声明**:CTypedefNames / CTypedefTypeStrs / CTypedefNameIdx
  / CTypedefNameIdxCap / CTypedefNameIdxBuilt 全部只以方法体里的裸 `self.x = ...` 出现
  (:184 `self.CTypedefNames = tn`、:185、:66-73、:118-121)。
· 声明存根(.vpi)只携带**类体 AnnAssign 字段**与 `__init__` 里**带注解**的 self.x;方法体
  里的裸赋值只被**定义模块自己**的成员收集(HandlesClassDef 的 self.x 扫描)收录
  ⇒ 同一字段"定义模块成员表里有、引用模块没有"(实证:temp/05d515186a7c3668.vpi 内不含
  CTypedefNames 条目)⇒ 引用模块的属性链在第三级断掉,整条带注解赋值被判"无值可存"而终止。

修法:把这 5 个字段在**类体**显式声明(类型取自各自赋值处的真实类型):
  CTypedefNames / CTypedefTypeStrs : list[str] | t.CPtr   (:177-178 的 tn / ts 即 list[str]*)
  CTypedefNameIdx : sizet*                                (:64 / :71 的 idx)
  CTypedefNameIdxCap / CTypedefNameIdxBuilt : int
声明后定义侧与 .vpi 两侧字段集一致(类体 AnnAssign 先收集,方法体 self.x 扫描按名去重不再
重复登记)⇒ 布局同源。注意:该类的字段顺序/类型与旧构建不同(字段从"仅扫描收集"变为
"类体声明"),需要清缓存重编译。

附带(另一仓 commit):TPC 侧 `_handle_attr_nested` 的 7 处静默 return 已补 `_NoteAttrNone`
记账(含内层失败原因链),下次同类报错会直接给出"读取侧原因=…"。
2026-09-30 09:59:54 +08:00
TermiNexus c1a22411ca fix(TypeGenResolve): 规则 5.5 增「本文件顶层类名」证据,模块级早于类定义的窗口不再留裸名
类别:a 编译器缺陷(TPV 侧;补齐"第二个裸类名窗口")

现象(与上一轮 e1bb42f 同一现场、不同窗口):编译 includes/ast/base.vp 时 :359
`_append_child(self.children, node, self.pool)` 报"指针隐式转指针已禁止(被指类型是
非基本类型…)",源 list[rawptr[AST]]* 与目标 list[rawptr<定义模块SHA1>.AST]* 差在实参的
SHA1 前缀。

判据(第二个窗口 = 模块级语句早于类定义):
· Translator.translate 的 prologue 先跑模块级 AnnAssign/Assign(Translator.vp:592-599)
  与 forward_declare_functions(:601),**之后**才 translate_children(:602) 逐个翻译类;
  而"正在定义类"窗口(CurDefModuleSha1/CurDefClassName)只在类定义入口
  (ClassGenDef.vp:50)或 .vpi 注入(VpiReader.vp:2659)才建立 ⇒ 前者解析注解时窗口为
  None ⇒ `_norm_type_arg_name` 规则 5.5 的 ①②③④ 全落空 ⇒ 注解里的裸类名(base.vp:148
  `_append_child(..., lst: list[rawptr[AST]])` 的形参即此窗口)以**裸名**形式被首次注册,
  并被后续复用(首写者胜:StructGenMixin 按 Ty 去重、ClassGenDef existing 早退不重算)
  ⇒ 与同一特化在类定义期解析出的带 SHA1 形式分裂。

修法(正向事实,不猜;与窗口/类定义顺序无关):
1. `StructGenMixin` 增文件级登记:字段 `FileClassNames`(本文件顶层 ClassDef 类名表,
   表与键同池、每文件随 `reset_visible_structs` 重建)+ `FileModuleSha1`(本文件模块
   SHA1),以及 `register_file_classes` / `is_file_class` / `get_file_module_sha1`。
   键从 AST 的 name **复制到 pool** 后再登记(AST name 零拷贝指向 src_buf,跨阶段可能
   失效,直接当表的键会 strcmp 到悬空内存)。
2. `Translator.translate` prologue:在模块级变量/前向声明**之前**调用
   `GenApi.register_file_classes(pool, self.ModuleSha1, tree)`(GenApi 增同名委托)。
3. `TypeGenResolve._norm_type_arg_name` 规则 5.5 增**第五类证据**:①②③④ 全落空时,
   若 `is_file_class(name)`(该裸名确由本文件声明)且本文件模块 SHA1 已知 ⇒ 归属本文件。

验证:TPV 侧改动需重新自举 TransPyV.exe 后生效;本轮以代码级证据链
(Translator.vp:592-602 / ClassGenDef.vp:50 / TypeGenResolve.vp 规则 5.5 闸门)自证。
TransPyV 仓另有他人未提交文件,本次只提交本改动的 4 个文件。
2026-09-30 00:23:43 +08:00
TermiNexus e1bb42fade fix(ClassGenGeneric): 泛型特化不再平移"类定义窗口",裸类名实参不再冻结成裸名
类别:a 编译器缺陷(TPV 侧)

现象(反复出现 4 轮):编译 includes/ast/base.vp 时 :359 `_append_child(self.children, node,
self.pool)` 报
「指针隐式转指针已禁止(被指类型是非基本类型…):源类型 %"…list[rawptr[AST]]"* →
目标类型 %"…list[rawptr<定义模块SHA1>.AST]]"*」——同一泛型特化被写成两种拼名(实参一侧
裸类名 `AST`、另一侧带定义模块 SHA1 前缀)。

判据(结构缺陷,代码级可证):
· `_norm_type_arg_name` 规则 5.5 判定"裸类名归属"依赖两个**全局窗口变量**
  `StructGen.CurDefModuleSha1` / `CurDefClassName`(证据 ② `is_module_class`、
  ③ `CurDefClassName` 逐字相同),入口闸门是 `_def_mod is not None`。
· `ClassGenGeneric._specialize_generic_class` 在 :715-720 把 `trans.ModuleSha1` 临时切到
  **泛型模板定义模块**,再于 :729 调 `ClassGenDef.translate_class_def`;后者在
  `ClassGenDef.vp:50` 会 `mark_module_class(pool, trans.ModuleSha1, class_name)`
  ⇒ **窗口被覆盖成"模板模块 + 特化类名"**。原实现返回时只恢复 `trans.ModuleSha1` 与
  `_imported_modules`(:733-735),**没有恢复这两个窗口变量**。
· 后果:调用方文件在此之后解析任何裸类名时,证据 ②③ 都在对着"模板模块 / 特化类"判 ⇒
  全落空 ⇒ 该实参以**裸名**形式被首次注册并被后续复用(首写者胜:`StructGenMixin.vp:455`
  按 Ty 指针去重、`ClassGenDef.vp:100-150` existing 早退不重算)⇒ 与同一特化在别处解析出的
  带 SHA1 形式分裂成两个名字 ⇒ 被"非基本类型指针不可隐式转换"拦下。

修法:
1. `ClassGenGeneric.vp`:窗口与 `ModuleSha1` **同进同出** —— 在切 ModuleSha1 之前保存
   `CurDefModuleSha1`/`CurDefClassName`,:744 之后一并恢复(嵌套特化不再污染调用方窗口)。
2. `TypeGenResolve.vp` 规则 5.5 的取证日志扩展:除原有"命中 struct 条目但 entry.ModuleSha1
   为空"外,**新增触发条件"窗口存在却属于另一个类"**(`_def_mod` 有值且
   `CurDefClassName` 与裸名不同)——正是"窗口被特化平移/污染"或"前向引用未登记类"的签名;
   报文补出 `entry_present` 与 `find_struct_by_module` 两项事实,一轮即可定位来源是否缺失。

验证:TPV 侧改动需重新自举 TransPyV.exe 后生效;本次以静态证据链(上述行号)与取证日志
自证。注:TransPyV 仓另有 7 个他人未提交文件,本次只提交本文件与 TypeGenResolve.vp。
2026-09-30 00:20:30 +08:00
TermiNexus 31f341cf6f fix(transpyv): vpi 注入期登记「本模块声明的类」,裸类名实参不再冻结成裸名
类别 a(编译器缺陷):_norm_type_arg_name 规则 5.5 的入口闸门是
`_def_sha1 is None and _def_mod is not None`(_def_mod = CurDefModuleSha1),而
VpiReader._inject_class_struct_declare 是在**注入期**解析字段注解的——此刻既没有
"类定义窗口"(CurDefModuleSha1=None ⇒ 闸门整条关闭、四条证据一条都不查),被注入的类
也尚未 register_struct ⇒ 注解里的裸类名实参取不到定义模块 SHA1 ⇒ 特化名被冻结成
**裸名版本**,与该类在自身翻译期(类已注册)解析出的带前缀形式分裂成两个特化名,
最终以"非基本类型指针不可隐式转换"报出。现场:includes/ast/base.vp:359
  _append_child(self.children, node, self.pool)
  源 %"…list[rawptr[AST]]"* vs 目标 %"…list[rawptr<定义模块SHA1>.AST]"*
这也解释了前两轮修复为何无效:③(自引用判据)依赖 CurDefClassName、④(entry.Ty 取
前缀)依赖 struct 条目,而注入期两者都不可用,且此时连日志条件(entry 命中)都不成立。

- GenApi:新增 mark_module_class 委托(与 add_visible_struct / find_struct_by_module 同款)。
- VpiReader._inject_class_struct_declare:在解析字段注解之前调用
  GenApi.mark_module_class(pool, src_sha1, cls_name) —— 一次调用同时完成
  ① 登记"{src_sha1}.{cls_name}"(名字归属,与 struct 注册状态无关);
  ② 把 CurDefModuleSha1/CurDefClassName 指向本次注入的模块与类(打开规则 5.5 的闸门)。
2026-09-29 22:29:48 +08:00
TermiNexus df1244d6f4 fix(transpyv): 裸类名归属补「符号名真源」(entry.Ty 的 {SHA1}.{类} 前缀)+ 取证日志
类别 a(编译器缺陷):_norm_type_arg_name 规则 5.5 判定裸类名归属只认三条证据——
struct 注册表的 ModuleSha1、ModuleClasses 登记表、以及"正在定义类"。Phase A-pre 没有
"类定义窗口"(CurDefModuleSha1=None),而 .vpi 注入的类条目常常 ModuleSha1 为空 ⇒
三条全落空 ⇒ 实参保留裸名,与后续(类已注册时)解析出的带前缀形式分裂成两个特化名
⇒ 被"非基本类型指针不可隐式转换"拦下。现场:includes/ast/base.vp:359
  _append_child(self.children, node, self.pool)
  源 %"…list[rawptr[AST]]"* → 目标 %"…list[rawptr<定义模块SHA1>.AST]"*

- 规则 5.5 新增证据 ④(表/窗口都不可用时的**符号名真源**):结构体自身的 IR 名本就是
  "{定义模块SHA1}.{类名}"(与本仓全部 mangling 同源:ClassGenDef/ClassGenVTable/
  VpiReader 的 Stub 构造)⇒ 打印 entry.Ty 并取回其 sha1 前缀(复用
  _is_sha1_prefixed 这一唯一判据,不另写判定)。
- 同一处补取证日志:命中 struct 条目却一条 SHA1 证据都取不到时(可疑子集:
  entry.ModuleSha1 为空),把 entry.Ty / cur_def_mod / cur_def_cls /
  is_module_class / from_imports / file 一次打出(只加日志,返回值语义不变)。
2026-09-29 21:28:18 +08:00
TermiNexus bbd0cdbf62 fix(transpyv): 自引用类名归属加「表无关」判据,字段与形参不再分裂成两个特化名
类别 a(编译器缺陷):_norm_type_arg_name 规则 5.5 判定"正在定义的类"的裸名归属
时只认两条证据——struct 注册表(find_struct_by_module)与 ModuleClasses 登记表
(is_module_class)。自引用字段(includes/ast/base.vp `AST.children:
list[rawptr[AST]]`)在类定义体内解析时本类尚未 register_struct,而 ModuleClasses
的键是 pool 字符串,登记/查询受"表未就绪、键随 pool 回收"影响 ⇒ 两条都可能落空
⇒ 实参留在裸名 `list[rawptr[AST]]`,与同一注解在形参处(类已注册)解析出的
`list[rawptr<定义模块SHA1>.AST]` 分裂成两个特化名 ⇒ 被"非基本类型指针不可隐式
转换"拦下(现场:includes/ast/base.vp:359
`_append_child(self.children, node, self.pool)`,源 %"list[rawptr[AST]]"* →
目标 %"list[rawptr<sha1>.AST]"*)。

- StructGenMixin:新增 CurDefClassName —— 与 CurDefModuleSha1 同源同生命周期,
  由 mark_module_class 在类定义入口(ClassGenDef 第 50 行,早于字段注解解析)
  一并记录;InitStructGen / reset_visible_structs 同步清空。
- TypeGenResolve._norm_type_arg_name 规则 5.5:新增证据 ③(表无关)——查询名与
  正在定义的类名逐字相同即判为本类,用 CurDefModuleSha1 加前缀。类名直接来自类
  定义入口、不经任何表 ⇒ 自引用窗口内必然可判;证据 ② 失败时不再退回裸名。
2026-09-29 20:16:05 +08:00
TermiNexus 1b2324edab fix(transpyv): 修正 mark_module_class 的表生命周期(ASan 悬空键崩溃)
类别:a(编译器缺陷 —— 上一提交自引入的跨文件悬空 HashTable)

现场:ASan access-violation(READ @0x1),rcx=0x1
  string.strcmp (includes/string.vp:86)
  → HashTable._find_slot (_resize) → __setitem__ → set_int
  → StructGenMixin.mark_module_class → ClassGenDef.translate_class_def
  → TranslatorGenMixin.translate_children → Translator.translate → RunPhase1 → main

根因:`ModuleClasses` 原先在 `mark_module_class` 里**懒创建**,并长期挂在 Gen 上;
表体与键都取自当次文件的 pool,而 pool 在文件/遍切换时会被 reset ⇒ 下一个文件里
`_resize` 重新散列旧键时 strcmp 读到已回收内存(0x1)⇒ 崩溃。

改动(与既有 `VisibleStructs` 同口径:表与键同池、同文件生命周期):
1. `ModuleClasses` 改由 `reset_visible_structs`(每文件翻译前调用)**无条件新建**
   (不再区分 strict 分支),与键同池 ⇒ 无悬空。
2. `CurDefModuleSha1` 指向 `trans.ModuleSha1`(同一 pool 的串),同样在该函数里
   清空,避免下一次 strcat/strcmp 读已回收内存。
3. `mark_module_class` 不再懒建表:池或表缺失时只记 `CurDefModuleSha1`、跳过登记
   (宁可退化为改前行为,也不冒悬空风险)。

语义不变:规则 5.5 的证据链仍是 ① 结构体表按名(跨文件/跨遍)→ ② 正在定义类所属
模块 + find_struct_by_module → ③ 本文件已声明类归属表(自引用字段窗口 —— 登记与
查询在同一文件同一遍内,正是该表的作用域)。
2026-09-29 19:14:10 +08:00
TermiNexus 2b8cc2a69b fix(transpyv): 特化名规范化下沉到唯一生成器,裸实参路径不再造第二个名字
类别:a(编译器缺陷 —— 特化名生成点不止一处,实参在"裸名/带前缀"两态并存)

背景(命名源审计,两个独立检索子代理收敛到同一清单):`_norm_type_arg_name`
是唯一规范化器,但"拼 Class[a0,a1]"的构造点有 20+ 处,其中 6 处绕过它。
本轮把规范化**下沉到唯一生成器**,让所有调用方自动同源 —— 该函数对已规范化的串
是幂等的(规则 2/3/6 原样返回,嵌套 rawptr[X] 递归同形)。

改动:
1. ClassGenGeneric._mangle_generic_class_name 增 from_imports 参数:逐实参经
   TypeGenResolve._norm_type_arg_name 归一后再拼串(长度计算与拼接同用归一后列表)。
   唯一调用点 _specialize_generic_class 传 trans._from_imports。
2. ClassGenBase._get_base_name 增 from_imports 参数:Subscript 分支的类型实参
   (原为裸 Name.id)同样归一 —— 该函数产出的父类特化名要拿去 find_struct_by_module
   查、并写入 entry.ParentName,而注册名由生成器产出,不归一必然对不上
   (父类 GSListNode[Value] vs 注册的 GSListNode[{sha1}.Value])。
   三处调用点(_get_parent_class / _collect_parents / _trigger_base_specialization)
   传 trans._from_imports;递归调用透传。
3. _trigger_base_specialization 本身无需改:它的裸实参进入 _specialize_generic_class,
   已由第 1 条统一归一。

收敛效果(连带):_specialize_generic_class 的 spec_name 与
TypeGenResolve._resolve_generic_subscript_ty 自拼的 gen_mangled 现已同形(后者本就
归一),源码注释里"gen_mangled 与特化名可能不一致 ⇒ 用 spec_name_chk 重查"那条
隐性双名自然不再触发。

遗留(未动,见审计清单):ExprGenConst.translate_list_literal /
CallGenCtor._infer_generic_ctor_type_arg 传的内建名不受影响(规则 6 原样保留),
但其"类名"实参现由生成器统一归一;CallGenCall/CallGenDispatch 从已注册名回拆
"[...]" 时用 strchr 按首个逗号切分(深度不敏感)另计。

影响面:ABI 可见(结构体名 / 方法符号后缀段);需 TPV 自举重建 + 回归验证,
重点观察 VpiReader 的泛型键匹配(其键以 T 占位符,初判不受影响)。
2026-09-29 18:53:58 +08:00
TermiNexus 868d48ae22 fix(transpyv): 自引用字段的特化名不再留在裸名(新增"本模块声明类"归属表)
类别:a(编译器缺陷 —— 字段类型在类注册前被冻结成裸名版特化名)

现场(上一轮未修掉的那一处):includes/ast/base.vp:357
    if self.children is None: self.children = list[rawptr[AST]](self.pool, 8)
CastError:源 %"7e2b6dda6e423bfb.list[rawptr[d1ed020479454541.AST]]"*
       → 目标 %"7e2b6dda6e423bfb.list[rawptr[AST]]"*

根因:字段 children: list[rawptr[AST]] | t.CPtr 写在**类 AST 自己体内**(自引用),
其类型在 translate_class_def 的字段循环里解析,而本类的 register_struct 在该函数
**末尾**才执行 ⇒ 解析时 struct 表里没有 AST ⇒ 上一轮 5.5 的两条查法
(find_struct 按名 / find_struct_by_module)全部落空 ⇒ 实参仍留裸名 rawptr[AST],
字段类型被冻结成 list[rawptr[AST]](第一遍 A-pre 即发生);而 :357 的构造调用发生在
类注册之后 ⇒ 带 SHA1 ⇒ 两侧分裂。(同一分裂在 stmts.vp:60 表现为形参侧/实参侧,
本处表现为字段侧/构造侧。)

改动:
1. StructGenMixin 新增"本模块声明类"归属表 ModuleClasses(键 "{模块SHA1}.{类名}")
   与 CurDefModuleSha1(正在定义类所属模块 SHA1,登记时同步记录):
   - mark_module_class(pool, sha1, name) / is_module_class(pool, sha1, name)
   - 键含 SHA1 ⇒ 与翻译顺序/模块切换无关、无需每文件重置,也不会把别的模块的类
     错认成本模块。
2. ClassGenDef 类定义入口(add_visible_struct 之后、枚举/联合分派之前)登记
   mark_module_class(pool, trans.ModuleSha1, class_name),覆盖 class/enum/union/
   renum 全部类种类。
3. TypeGenResolve 规则 5.5 补齐证据链:① 按名取 StructEntry.ModuleSha1;
   ② struct 表暂无条目时用 CurDefModuleSha1 走 find_struct_by_module;
   ③ 仍无条目(正是自引用窗口)时查归属表 ⇒ 加 SHA1 前缀。
   CurDefModuleSha1 取代 CurrentModuleSha1 作为"当前定义模块"真源——后者在
   A-pre/StubMerger 路径未设置或留着上一文件旧值(只有 Translator.ModuleSha1 权威)。

影响面:ABI 可见(特化名);需 TPV 自举重建 + 回归验证。
2026-09-29 18:47:35 +08:00
TermiNexus dddaba3254 fix(transpyv): 5.5 步补「类定义中」回退,字段侧特化名不再留在裸名
类别:a(编译器缺陷 —— 同一特化在字段侧/构造侧仍分裂,方向相反)

现场:includes/ast/base.vp:357
    if self.children is None: self.children = list[rawptr[AST]](self.pool, 8)
CastError:非基本类型指针不可隐式转换
  源(构造调用结果) %"7e2b6dda6e423bfb.list[rawptr[d1ed020479454541.AST]]"*
  目标(字段声明类型) %"7e2b6dda6e423bfb.list[rawptr[AST]]"*

上一处(stmts.vp:60)是"形参侧裸名 vs 实参侧带前缀",本处反向:**字段侧**
仍是裸名。字段 children: list[rawptr[AST]] | t.CPtr 写在**类 AST 自己体内**
(自引用),解析该字段类型时类正在定义中:find_struct('AST') 按名查不到(登记项
尚未命名)⇒ 5.5 首条查法落空 ⇒ 仍返回裸名;而 :357 的构造调用发生在类已注册之后
⇒ 带前缀,两侧再度分裂。

改动:5.5 步在 find_struct 未命中 / ModuleSha1 为空时,回退用与
resolve_annotation_type 的 Name 分支**同一处证据**认定本模块类:
find_struct_by_module(name, self.Gen.TypeGenBase.CurrentModuleSha1) 命中即以当前
模块 SHA1 加前缀。对同一模块的类,"按名取定义模块 SHA1"与"当前模块身份"给出同一
SHA1,故不引入新分裂。文档串规则表同步补该回退。
2026-09-29 18:38:52 +08:00
TermiNexus 65d7f7cc31 fix(transpyv): 泛型实参里的「本模块裸类名」也加定义模块 SHA1,特化名不再分裂
类别:a(编译器缺陷 —— 同一类型在本模块与被引用模块得到两个特化名)

现场:includes/ast/stmts.vp:60
    _set_parent_list(targets, self)
CastError:非基本类型指针不可隐式转换
  源   %"7e2b6dda6e423bfb.list[rawptr[d1ed020479454541.AST]]"*
  目标 %"7e2b6dda6e423bfb.list[rawptr[AST]]"*
外层特化前缀相同,只有类型实参里的类名一个带 SHA1、一个裸名 ⇒ 同一个
list[rawptr[AST]] 变成两个结构体 ⇒ 被"非基本类型指针不能隐式转换"拦下。

根因:_norm_type_arg_name 的第 5 步只查 from_imports(可见性表);**本模块自己
声明的类**不在表内 ⇒ 落到第 6 步原样返回裸名,而被引用模块解析同一类型时第 5 步
命中(from-import 归属模块)带 SHA1 ⇒ 两侧对同一类型产出两个名字。

改动(_norm_type_arg_name 新增 5.5 步):
- 简单裸名(不含 '[' / '.')先经结构体表 find_struct(name) 取该类的
  **定义模块 SHA1**(StructEntry.ModuleSha1)加前缀 —— 与"当前模块"无关,
  故本模块内与跨模块得到同一票名;仅当登记项存在且 ModuleSha1 非 None 时生效。
- 文档串规则表同步补 5.5;第 6 步措辞改为"内置类型名等"。

与第 5 步同口径(都只处理简单裸名):泛型特化名 list[...] 与模块限定名
mod.Class 各有自己的分支,不在 5.5 重复加工。

注:TPC 侧同名规则源(SymbolUtils.ResolveTypeArgStr)是否需要同步,待该现场
回归通过后再核。
2026-09-29 18:31:52 +08:00
TermiNexus 8e7d7feb57 fix(transpyv): rawptr[T] / t.CPtr[T] 对裸类引用吸收一层指针(不再多包一层)
类别:a(编译器缺陷 —— TPV 注解解析与「联合写法 / TPC 两侧」口径分裂)

现场:includes/ast/tokens.vp:217
    ptr: rawptr[Token] = pool.alloc(Token.__sizeof__())
CastError(TPV ExprGenBase.vp:746-753):指针层数不同(源 1 级 → 目标 2 级);
源类型 i8* → 目标类型 %"…Token"**。

根因链:
1. 本文件 Name 分支(:2269-2277):有 __new__ 的堆类裸名注解解析为 Ptr(struct)
   (引用语义,注释明写「对齐 TPC」)⇒ Token → %"…Token"*。
2. rawptr[T] 分支(原 :2865-2871)与 t.CPtr[T] 分支(原 :2928-2939)只做
   Ptr(resolve(T)),不含「裸类引用吸收一层」⇒ %"…Token"**(2 级)。
3. 同一语义的其余实现都有该规则,唯此两处缺失:
   - 联合写法 X | t.CPtr 的 BinOp 分支(:2682-2684 的判据注释 + :2703-2720 回退)
     得 1 级;
   - TPC 侧 LLVMTypeMapper._rawptr_subscript_type_str(内层映射成 %…* 即吸收)
     与 HandlesTypeMerge._MergeAllComponentTypes(IsStruct 时吸收一层)。
   规则真源见 TPC SymbolUtils.IsPtrSubscriptNode 文档:「内层 T 为类引用时该层被
   结构体自身吸收('<sha1>.X*'),其余(标量 / 已是指针的 typedef / 泛型容器特化 /
   嵌套指针下标)逐层外包」。

改动:
1. 新增判据 _ptr_subscript_absorbs_class(slice, resolved):切片**非**用户写明的
   指针包装(联合 BinOp / 嵌套 rawptr[...]、t.CPtr[...]、裸名 CPtr[...])且解析
   结果已是 Ptr(已注册 struct)(find_struct_by_type 命中,与 BinOp 分支同一判据)
   ⇒ 返回 1(吸收该层)。纯判据,不发射类型。
2. rawptr[T] 与 t.CPtr[T] 两个分支接入该判据。

逐条核对保持不变的形态:rawptr[int]→i32*、rawptr[str]/rawptr[bytes]→i8**、
t.CPtr[t.CPtr]→i8**、rawptr[rawptr[X]]→X**、t.CPtr[int]→i32*、
rawptr[Point(无 __new__ 的栈类)]→Point*。

影响面:ABI 可见(字段 / 形参 / 局部变量类型),需 TPV 自举重建 + 整轮回归验证。
2026-09-29 18:07:37 +08:00
TermiNexus c2d7480f3d fix(transpyv): 槽视图统一 cast 到声明类型(47 处,含 codegen 自身)
同 includes 的同类修正:把一级指针 i8* 隐式塞进二级注解(i8** / str*)。
TransPyV 侧 coerce(ExprGenBase._ptr_cast_reason)没有 TPC 那样的「值侧是显式
指针 cast ⇒ 按 bitcast 重解释」豁免,故一旦用新构建的 TransPyV 编到这些文件
必然 fail-fast(回归测试首爆点在 includes/llvmlite 侧,本仓同形态站点随后必然
同样触发)。

涉及的两种形态:
  · `slot: str | t.CPtr = (t.CPtr)(addr)`   —— cast 目标是一级 t.CPtr
  · `nm_slot: str* = (t.CPtr)(addr)`        —— 注解 str* 即 i8**
统一改为 cast 到声明类型:`(str | t.CPtr)(addr)` / `str*(addr)`(后者与
includes/sys.vp `argv_arr: str* = str*(...)` 同一写法)。

本仓 47 处 / 6 文件:
  App/lib/core/Gen/VarGenMixin.vp 3
  App/lib/core/Gen/TranslatorGenMixin.vp 8
  App/lib/core/Gen/Func/FuncGenMixin.vp 13
  App/lib/core/Gen/AsyncGenMixin.vp 15
  App/lib/core/Gen/DebugInfoGen.vp 6
  App/lib/Stub/StubGen/Converter.vp 2
Converter.vp:107 的 `_g_pyi_cdef_names: str | t.CPtr = None` 保持原样
(None 即空指针,任何指针类型均可,不属本类问题)。

只提交本 6 个文件;本仓其余未提交改动(CallGenBase/CallGenDispatch/CallGenFd/
CallGenProp/Generator/Translator/VLogger)与本迁移无关,保持不动。
2026-09-29 15:10:33 +08:00
TermiNexus 07f2f2e9fc fix(transpyv): CallGenCBuiltin 的 clobbers 改为单级 t.CPtr,并同步该文件语法迁移
【类别 c 源码违规】clobbers 声明的指针层数与 alloc/memset 不一致。

现象:
  翻译 App/lib/core/Gen/Call/CallGenCBuiltin.vp 报
  「指针隐式转换已禁止(层数不同):不能用指针类型 i8* 初始化 clobbers: i8**」
  出错表达式 'pool.alloc(8 * ASM_MAX_CLOBBERS)'(Call 形态,CallGenCBuiltin.vp:955:33)。

判据(三条,缺一不可):
  1. memhub.MemBuddy.alloc(size: sizet) -> t.CPtr(includes/memhub.vp)即 i8* 单级;
  2. str | t.CPtr 是 i8**(CChar + PtrCount 2),即字符串指针数组视图;
  3. MemoryOps._ClassifyPtrImplicitConvert 判定顺序为**先比层数**,层数不同直接禁,
     通用指针通配(void*)不跨层。
  故 i8* 结果不能隐式初始化 i8** 变量 —— 与是否同名 str 无关。

取证补充:同函数下一行 string.memset(clobbers, 0, ...) 首参同样是 t.CPtr
  (includes/string.vp: memset(ptr: t.CPtr, value: int, num: sizet)),
  即便只给 clobbers 加 cast,memset 一样会挂 —— 必须把声明本身改回单级。

修法:clobbers 声明改回单级裸缓冲 t.CPtr,每槽按 8 字节字符串指针的写入沿用
  下方既有的显式 cast 槽视图写法
  (cl_ptr: str | t.CPtr = (str | t.CPtr)(cl_addr)),
  与 includes/_dict.vp 的「缓冲单级 t.CPtr、槽视图 str | t.CPtr 显式 cast」范式一致。
  此改**不引入新 cast、不绕过检查**,只是把声明修正到与真实分配/清零层数相符。

同类扫描:全 App + includes 内 str | t.CPtr = *.alloc(...) 形态仅此一处;
  includes/sys.vp 两处早已是显式 str*(...) 转换。

同时并入本文件他人已完成的语法迁移(同文件合并提交):
  - 常量声明 t.CDefine: T = 值 → define NAME = 值 语法糖(_CEXPORT_MAX /
    ASM_MAX_OPERANDS / ASM_MAX_CLOBBERS / ASM_TEXT_BUF_SIZE / ASM_CONST_BUF_SIZE /
    ASM_ARGS_BUF_SIZE / ASM_LINE_BUF_SIZE / LLVMIR_MAX_OPS / LLVMIR_TEMPLATE_BUF);
  - @t.NoVTable + class → novirtual class;
  - 删除常量块内已失效的注释与多余空行。

1 file changed, 17 insertions(+), 18 deletions(-)
2026-09-29 14:47:42 +08:00
TermiNexus 1f93550b6d revert(transpyv): 还原 *existing.Ty = <boxed 变体构造>(c.DerefAs 是绕过自家检查)
a9e4aaf 把 `*existing.Ty = llvmlite.LLVMType.Struct(...)` 改写成
c.DerefAs(existing.Ty, ...)。但 DerefAs ≡ *p = a(_HandleCDerefAsLlvm 的
「整对象拷贝」分支就是 load + store),二者语义完全等价 —— 换写法只是绕开了
*槽 = <boxed 变体> 触发的严格规则③,并未解决根因。

根因在编译器侧:「构造物化」豁免漏了 REnum 变体构造这一形态。已在 TPC 侧修好
(fix(tpc): 「构造物化」豁免覆盖 REnum 变体构造),本处源码随之还原为直白的
`*槽 = <boxed 变体>` 写法,不再用内建绕开严格检查。
2026-09-28 23:21:36 +08:00
TermiNexus a9e4aafae1 fix(transpyv): ClassGenDef 用 c.DerefAs 物化 REnum 变体(替代 *ptr = <boxed 构造>)
c 源码违规:`*existing.Ty = llvmlite.LLVMType.Struct(...)` 把 **boxed 变体指针**
赋给了值槽(指针→值,严格规则禁止)。REnum 变体构造返回 boxed 指针,仓库既定
写法是用 c.DerefAs(ptr, <boxed 变体>) 做整对象物化(includes/llvmlite/__types.vp:195
的同款 `c.DerefAs(ptr, LLVMType.Struct(fields, fcount, name))`),
_HandleCDerefAsLlvm 的"整对象拷贝"分支正是为此(HandlesExprCall.py:4718-4740)。

用 DerefAs 而非改成 `existing.Ty = <新指针>`:本段的目的就是修 Phase 1a 遗留的
**悬空 Name**(注释 L97-101),Phase A 已把该指针登记进 named-type 表;换指针会让
旧条目继续指向悬空 Name,in-place 覆盖才保留指针同一性、真正修掉悬空。

现场报错:非基本类型的值/指针隐式转换已禁止(构造物化除外):
  不能把指针类型 %"50741ad4b4b0934d.LLVMType"* 隐式转为值类型
  %"50741ad4b4b0934d.LLVMType"
2026-09-28 23:15:46 +08:00
TermiNexus fddadf9d56 fix(transpyv): 修正 _PyiClass 的 type_params 局部变量注解(list[ast.AST*]* → list[str]*)
c 源码违规(注解写错,被严格指针规则抓出):ClassDef.type_params 的真身是
rawptr[list[str]](**名字串**列表,见 includes/ast/stmts.vp:475-483 与
parser.vp:3619-3663 的 `list[str](ps.pool, 8)`);`list[rawptr[AST]]` 是
decorator_list 的类型。Converter._PyiClass 把局部变量注解从 decorator_list
抄错成 list[ast.AST*]* ⇒ `.get()` 被解析到 list[ast.AST*].get,而实参是
list[str]* ⇒ fail-fast:

  调用 650c1c3366d09e39.list[10f3f43ddd447eec.AST*].get 第 0 个实参
  %"650c1c3366d09e39.list[str]"* 不能隐式转为形参
  %"650c1c3366d09e39.list[10f3f43ddd447eec.AST*]"*

已核对全仓其余 8 处 type_params 用点(VpiReader.vp:2253/2274/4345、
Phase2/__init__.vp:317、Phase1Scan.vp:153/318、ClassGenDef.vp:69、
CallGenMixin.vp:415)均写作 list[str]*,本次与之对齐。
2026-09-28 22:54:50 +08:00
TermiNexus aa52cf157d revert(transpyv): 回滚跨模块链表 Next 改访问器(改由编译器沿继承链解析字段名)
回滚 13080f6 的 110 处访问器调用,恢复裸字段访问 `x.Next`。

理由:该批访问器是把访问移到定义模块内执行,**绕开**了"继承字段名解析",
属于规避而非修复;缺陷本体在 TPC 侧(导入侧继承展平把泛型基类的继承字段
记成 __inherited_N 占位名,名字丢失)。TPC 已按语义修正:
  · e6451a07 继承展平沿继承链取真实字段名(泛型特化回退模板名),
    展平表直接落 `[("Next", <槽位类型>), ...]`,属性访问是普通表命中;
  · 3ca46c01 顺带修掉 _get_or_create_struct 里"同名枚举成员遮蔽类定义"的 i32 出口。
因此源码侧恢复为唯一写法(裸字段访问),由编译器负责解析。

注:本 commit 只回滚访问器;13080f6 里夹带的 CallGenProp.vp 续行合并(非访问器改动)
已在工作区保留(未提交)。
2026-09-28 21:48:53 +08:00
TermiNexus 13080f646f fix(transpyv): 跨模块链表 Next 字段改用访问器(消除 __inherited_N 占位名 fail-fast)
c 源码违规:llvmlite 的 Value/ParamNode/Function/Param/GlobalVariable/PhiIncoming/
SwitchCase 的 Next 继承自泛型基类 GSListNode[T],而其泛型特化体不在任何 .vpi 中
⇒ 导入侧 class_members 只有 __inherited_N 占位名;严格属性解析(回退已移除)下
裸写 `x.Next` fail-fast。现场:CallGenCall.vp `cur_fb = cur_fb.Next` 报
"[fast-fail] 属性 Next 在类 ParamNode 中找不到(class_members=[__inherited_0, Ty])"。

按仓库既有约定(_Type.md:链表遍历用 paramnode_get_next/paramnode_set_next;
VarGenMixin/TypeSpec/StructGenMixin/VpiReader 等已如此书写)统一改为访问器:
  value_get_next/value_set_next、paramnode_get_next/paramnode_set_next、
  function_get_next、param_get_next、globalvariable_get_next、
  phiincoming_set_next、switchcase_set_next
覆盖 24 个文件共 110 处(含 CallGenProp.vp:122 的嵌套读 cur_arg_pc.Next)。

访问器在 includes 侧为定义模块内直通实现(None 守卫 + x.Next),语义等价、零行为
变化。已全量扫描 core\Gen 全 47 文件与 Stub/Phase/core/main:App 侧已无裸继承字段
访问(剩余 .Next 命中全部在注释里)。
2026-09-28 21:20:58 +08:00
TermiNexus 75ea6fb9b2 fix(transpyv): 消除隐式向下转型(ast.AST* 实参 → 派生 AST 形参)
c 源码违规(严格指针规则:基类指针传派生类形参属向下转型,须显式转换):

1) TranslatorGenMixin._prescan_funcdef_signatures:形参由 ast.FunctionDef* 改为
   ast.AST*,函数体内显式 `fd: ast.FunctionDef* = ast.FunctionDef*(node)`。
   理由:模块级调度器只持有 ast.AST* 子节点,同一处调用的
   translate_function_def / translate_class_def / _register_cexport_from_funcdef
   形参一律是 ast.AST*,本函数是唯一例外(报错现场:第 2 个实参 %"..AST"* 不能
   隐式转为形参 %"..FunctionDef"*)。改为与兄弟入口同口径,调用点无需改动。

2) CallGenDispatch len() 内置分支:_is_list_builtin_receiver 第 5 形参是
   ast.Call*(仅用于 D25 fail-fast 定位节点),实参 len_arg_node 声明为基类
   ast.AST* ⇒ 显式窄化 `len_arg_cl: ast.Call* = ast.Call*(len_arg_node)`。

附:对 App\lib 全量静态扫描(core\Gen 全 47 文件 + Stub/Phase/core/main)确认
此类「基类指针实参 → 派生形参」的隐式向下转型仅此两处,其余调用点均为同类型
或向上转型(规则允许)。

同文件另含仓库内既有书写整理(续行合并),无行为变化。
2026-09-28 20:20:56 +08:00
TermiNexus af5fba81c5 fix(transpyv): 严格指针规则下的源码级窄化与跨层指针写法修正
c 源码违规(按新严格指针规则的显式书写):

1) AssignGenMixin.vp 属性赋值:target 是基类 ast.AST*,而
   CallGenProp._try_property_setter 形参是 ast.Attribute*。基类指针传派生类形参属
   向下转型,严格指针规则下禁止 ⇒ 在 ASTKind 判据确定为 Attribute 后显式窄化
   `at_tg: ast.Attribute* = ast.Attribute*(target)`,并复用到 fd_an(不再重复窄化)。

2) TypeGenResolve.vp `return (str | t.CPtr)(v)`:`(str | t.CPtr)` 语义是 i8**(char**),
   而该函数声明返回 i8*,声明/返回层数不符 ⇒ 改为 `t.CPtr(v)`(i8* 显式转换)。

同文件另含仓库内既有书写统一(ast.OpKind.USub 全限定名、rawptr(...) 取代
(str | t.CPtr)(...)、空行/续行缩进整理),无行为变化。
2026-09-28 19:40:42 +08:00
TermiNexus 825fe6d326 fix(TransPyV): 指针 cast 的跨层 idiom 修正:(str | t.CPtr)(x) → rawptr(x)
c 类(源码违规):(str | t.CPtr)(x) 按既定语义 = i8**(char**,指针的指针),

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

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

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

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

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

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

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

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

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

另:Gen/Type/TypeGenResolve.vp:1221 手工改为 return t.CPtr(v)(该文件含你的未提交改动,未一并提交)。
2026-09-28 18:43:59 +08:00
TermiNexus d579d70113 fix(TransPyV): 修正形参/字段链节点的注解为 llvmlite.ParamNode*
c 类(源码违规):这些变量由 new_param_node 构造、或取自 get_struct_fields /

param_list_append / function_get_param_head,实际是 llvmlite.ParamNode 链节点,

注解却写成 llvmlite.Function*。旧的无条件指针 bitcast 掩盖了该错误,严格指针规则下暴露。

涉及:VarGenMixin / CallGenVt / VpiReader / ClassGenEnum / ClassGenVTable /

ExprGenName / FuncGenMixin / FuncGenDef / TypeGenResolve / TypeSpec /

StmtGenCifMixin / AssignGenMixin(纯注解替换,零语义变化)。15 行。
2026-09-28 17:54:13 +08:00
TermiNexus 70448f4590 fix(CallGenLeafKit): 闭包/函数指针调用的形参链表误注解 Function* → ParamNode*(Vipeye 600/814/818)
param_node/pn_tail/pn_new(600 的 Func 实参、593 的 pn_tail.Next)与 fp_first/fp_prev/pn2(814 paramnode_set_next、818 Func 实参)均为 ParamNode 链节点,原先误注解 llvmlite.Function*(不相关类)→ 严格指针规则下四处实参/字段赋值被判 struct↔struct 隐式转换禁止。改注解即根治。
2026-09-28 17:50:05 +08:00
TermiNexus 29acf97dbc fix(CallGenCBuiltin): 多输出 asm 返回 struct 的字段链表误注解 Function* → ParamNode*(Vipeye 1268)
out_pn_head/out_pn_last/pn5 与 new_param_node 的返回、Struct(fields: rawptr[ParamNode]) 形参、ParamNode.Next 语义一致,原先误注解 Function*(不相关类)⇒ out_pn_last.Next = pn5 / Struct(pool, out_pn_head, ...) 被判 struct↔struct 隐式转换禁止。改注解即根治。
2026-09-28 17:18:41 +08:00
TermiNexus eb1065f387 fix(CallGen): Function 的 Param 形参链与 llvmlite.Func 的 ParamNode 构造链不可互传(Vipeye 329/401/1065)
① 新增 CallGenBase._FuncTyFromFunction(pool, f_ref):Function.Params 是 Param 链(__function.vp),LLVMType.Func 的类型构造链是 ParamNode 链(__types.vp,new_param_node/paramnode_set_next),两者是不相关 class ⇒ 严格指针规则下 Func(pool, ret, p_head, cnt) 的实参传递直接报错。改为逐个 new_param_node 重建 ParamNode 链(语义等价,只取各参数类型)。CallGenCBuiltin 329/401 + ExprGenOps 878(第 3 处同型缺陷)三处统一走该 helper。

② CallGenCBuiltin 1065 asm-out 全局变量回退:原把 find_global_in_module 的 GlobalVariable* 直接赋给 llvmlite.Value* 变量(不相关类;且 GlobalVariable 字段序为 Name→Ty,Value 为 Ty→Name,旧无条件 bitcast 会让 .Ty 读到 Name,后续 alloca.Ty.Pointee 全是错位数据)⇒ 改走 c.Addr(模块全局) 分支同款写法 make_global_ref(pool, Name, Ty) 构造带引号的 Ptr(Ty) Value 引用,同时修掉该错位读取。
2026-09-28 17:16:57 +08:00
TermiNexus 23428b63a5 fix(CallGenCall): 虚表/函数类型参数链表节点误注解 Function* → ParamNode*(Vipeye 143/146/179/184)
includes/llvmlite/__types.vp: new_param_node -> rawptr[ParamNode]、paramnode_set_next/Struct/Func 的 params 形参均为 rawptr[ParamNode];本处 vt_first/vt_prev/pn/fp_first/fp_prev/fpn 误注解为 llvmlite.Function*(不相关类)⇒ paramnode_set_next / Struct / Func 三处实参被判 struct↔struct 隐式转换禁止。

修法:6 处注解改 llvmlite.ParamNode*(改类型即根治,不用强转)。
2026-09-28 17:04:57 +08:00
TermiNexus 4807012bd8 fix(CallGenBase): struct 字面量字段链表节点误注解 Function* → ParamNode*(Vipeye 922/927)
事实:includes/llvmlite/__types.vp 里 new_param_node -> rawptr[ParamNode]、paramnode_set_next(node: rawptr[ParamNode], nxt: rawptr[ParamNode])、Struct(fields: rawptr[ParamNode], ...),而本处 th_st/tt_st/en_st 被注解成 llvmlite.Function*(Function 与 ParamNode 是不相关类)⇒ 严格指针规则下这两处实参传递被判「struct↔struct 隐式转换已禁止」。

修法:把注解改成真实类型 llvmlite.ParamNode*(不需要、也不应该 t.CPtr 强转——变量本来就是 ParamNode,强转只是把类型错误重新掩盖)。
2026-09-28 17:02:35 +08:00
TermiNexus 9c427aa548 fix(CallGenCall): 拆开 vtable_base 类型推断的变量复用(LLVMType* 与 StructEntry* 不可同一变量)
严格指针规则下 struct↔struct 隐式转换已禁止(仅允许派生→基类上转),原写法把 get_struct_type_from_value 的返回(LLVMType*)赋给 vc_entry: StructEntry* 再转手交给 find_struct_by_type,靠的是旧的无条件 bitcast。

改为各用独立变量(vb_ty / sp_ty),语义与改前逐字一致(仅去掉两次隐式 bitcast)。
2026-09-28 16:54:25 +08:00
TermiNexus 3bed3a70a6 docs(len): len() 两条 fail-fast 报文措辞与 TPC 逐字对齐(行为零改动)
补上「只支持 定长数组/数组指针、i8*(str/bytes)、带 __len__ 的结构体」+「非 i8 裸指针没有长度语义」的正确做法提示,与 TPC HandlesExprBuiltin._LEN_SUPPORTED_HINT 一致,便于用户对照两侧报文。
2026-09-28 15:53:09 +08:00
TermiNexus be1acdb602 feat(cast): 放行数组退让([N]T* → T* 单向),与 TPC 同口径
新增 _is_array_decay(复用 is_array_type + LLVMType.Array 解构 + AssignGen._stg_ty_equal),在 _pointee_convert_reason 的非基本分支里紧接 _is_inheritance_upcast 之后调用

与 TPC 真值表一致:数组→元素指针 ALLOW、反向 DENY、多维只退一层;其余规则(通用指针/层数/完全相同/None/构造物化/标量无损/继承上转)未动
2026-09-28 15:38:42 +08:00
TermiNexus e2c4c7645e fix(cast): 补继承上转豁免(Derived*→Base* 放行),与 TPC 同口径
新增 _class_name_of_ptr_pointee(复用 StructGen.get_class_name_by_type)与 _is_inheritance_upcast(复用 StructGen.get_mro_chain:目标类名命中源类 MRO 链自身之后的祖先即放行,含多级)

在 _pointee_convert_reason 落入“非基本类型”分支前插入上转豁免;其余规则(通用指针通配/层数不同即禁/完全相同放行/None 任意/构造物化豁免/标量无损)未动;Base*→Derived* 与无关结构体仍禁

注:本提交顺带带入 App/main.vp 的既有版本号改动(0.0.5-dev.479,非本次会话产出)
2026-09-28 15:29:42 +08:00
TermiNexus f03aea15c7 fix(cast): 规则③两侧对齐——聚合值↔指针也上闸,仅豁免构造物化(p: Point = Point())
TPV 的 coerce_to_type 两条“聚合值↔指针”路径不再无条件放行:新增 _ctor_call_agg_ty(AST 层识别构造调用,经 _value_node_of 下沉 + find_struct 取聚合类型)与 _same_agg_ty(委托 _stg_ty_equal);非构造物化一律 fatal_error,报文前缀与 TPC 逐字一致

无损口径以本文件 _scalar_convert_reason 为真源(int↔int 全向、float↔float 全向、int→float 需目标浮点位宽≥源整数位宽、float→int 有损),未动

已知未覆盖:泛型构造(Box(42))静态拿不到特化 struct 名 ⇒ 不豁免;部分 coerce 调用点不传 node ⇒ 可能误报(需实机确认)
2026-09-28 15:17:37 +08:00
TermiNexus ee50ce61fe feat(cast): 指针隐式转换按“同级+被指类型无损”判定(通用指针 t.CPtr/i8* 通配),并修诊断错位
_ptr_cast_reason 统一判据:层数不同→禁;完全相同类型→放行;任一侧通用指针(i8*/t.CPtr)→放行;否则按被指类型:非基本类型→禁,基本标量按既有无损规则(int↔int、float↔float、int→float 需目标浮点位宽≥源整数位宽、float→int 有损)

4 处禁令与新增的“指针→指针”处统一走 _value_node_of/_cast_fail_where:外层 Assign(kind=3) 下沉到真正被转换的值节点;列号按有符号打印(-1/未知打 -,不再出现 4294967295);附源码片段

未动“聚合值↔指针”那条路径(构造返回指针 load 回结构体,如 p: Point = Point()):LLVM 层无法与真实 Foo*→Foo 区分,禁止会直接编不过;规则③在该轴上的收紧待 AST 层区分构造调用后另做
2026-09-28 15:10:30 +08:00
TermiNexus 59a9fc5b1f fix(cast): None 允许隐式转换为任意类型(修 p.fd_used[fd] = None 被“指针隐式转整数已禁止”拦下)
coerce_to_type 顶部新增单一短路判据 IsNoneValue(val, node):AST 为 CONST_NONE,或已物化的 null 常量(实机报错里 node 是外层 Assign kind=3、col=-1,拿不到 None 字面量节点,故以值判定为主)

指针目标保持既有 bitcast 形态(与 T* x = None 初始化一致);非指针目标按目标类型物化零值:整数 0 / 浮点 0.0 / 聚合与其余用 ConstZero

覆盖范围:App/** 下真正 fatal 的指针/整数禁令只有 coerce_to_type 内 4 处,均在短路之后;CallGenDispatch/ExprGenAttr/CallGenLib 等命中都是注释或不报错的原样返回,无遗漏

未动指针层级/被指类型的转换规则(待确认后另行实现)
2026-09-28 14:59:31 +08:00
TermiNexus dfae2f090c refactor(return): 移除 @c.CReturn 处理,改为遇到即 fatal_error(只保留 -> tuple[...])
FuncGenBase:整体删除 _resolve_creturn_ret_ty,新增 RejectCReturnDecorator(兼容 @c.CReturn(...) 与裸 @c.CReturn 两形态;命中即 TypeGenErr.fatal_error,报文指向 -> tuple[ty1, ty2, ...])

FuncGenDef:前向声明与定义两处调用点改为调用 RejectCReturnDecorator,删除随之失效的注释;周围类型解析主干(-> 注解、State/CExport、无注解 fail-fast)逐字未动

Converter:删除 _PyiCreturnArgs 与其“无 -> 注解则合成 -> tuple[...]”的调用点,改为 _PyiRejectCReturnDecorator(vpi 中仍带 @c.CReturn 即报错,提示用 --clean 重新生成该 .vpi)

注:TPV.vp.txt 是生成的单文件打包产物,需重新生成(本次未手改)
2026-09-28 13:57:05 +08:00
TermiNexus d06c4d2c0e refactor(main): 旧写法 a: static int 迁移为前置形式 static a: int
static 修饰符改为语句起始前置(TPC 与 includes 同步改动)后,本文件是 TPV 自身源码,必须一并迁移,否则新编译器编不过自己
2026-09-28 13:49:30 +08:00
TermiNexus f2d7b748ea fix(func): 末尾收尾的可达性判据只认 _ctrl_merge_reachable(删掉“穷尽通配臂即不可达”的错误推断)
症状:includes/llvmlite/__types.vp::_TypePrintDepth 末句 match ty:(各臂只 snprintf 后 br 到 merge)被生成 match.end: unreachable;llc 不给可达 unreachable 生成任何指令 ⇒ 执行掉进布局紧随其后的同一 switch 跳转表,用上一条 snprintf 的返回值 3 当 tag 取 table[3]=Array 臂 ⇒ 把 Int 的 Bits(32) 当指针递归解引用 ⇒ AV at 0x20(裸 0xC0000005;ASan 下同一崩溃被转为 0x80000003 = -2147483645)

根因:last_is_ctrl and (_ctrl_merge_reachable == 0 or MatchHasWildcardArm(...)) 的“追加92”通配臂子句——穷尽性只保证匹配总能命中某臂,不保证各臂都终止且不 br 到 merge;而 _ctrl_merge_reachable 恰是逐臂真正 br end_bb 时才置 1 的精确信号(MatchGenMixin:717 写回)

修法:两处收尾判据(FuncGenDef 模块级函数 / ClassGenVTable 方法)都只在 _ctrl_merge_reachable == 0 时 build_unreachable;merge 可达时走既有分支正常收尾(void→ret void;无注解→ConstZero;有非 void 注解→fail-fast,不发明返回值)

删除随之失效的 MatchHasWildcardArm 与其私有 helper _IsWildcardPattern(全仓已无引用)
2026-09-28 12:41:04 +08:00
TermiNexus 5e100e2195 fix(match): REnum 变体字段绑定不再静默跳过(__types.vp bits NameError)
根因:_handle_renum_match step6 用 renum_name 查 struct 且不校验 Ty;拿不到 entry/Ty 时 step7 的 _bind_variant_fields 被整段静默跳过 -> case 绑定名在 body 里未定义,最终只报 NameError(bits),真因藏到下游

1) 名字回退:变体表与 struct 注册都按裸类名,而 renum_name 来自注解 SourceName(Attribute 可带模块限定名)-> 精确查找失配;与 tag 查表同一裸名推导(tag_cls_bare)

2) 新增 StructGenMixin.find_struct_entry_with_ty:跳过 D30 重注册时就地中和的旧 entry(Name[0]=0/Ty=None/FieldCount=0),避免命中它拿到 Ty=None

3) 禁止静默:仍有 REnum 判据为真却无法绑定时 fail-fast,打印 class/查表名/entry/FieldCount/ty_present(对齐 D30/D32 的响亮失败口径)
2026-09-27 23:58:15 +08:00
TermiNexus a79e723e8d fix(dwarf): DIFile 改用绝对路径,栈转储显示完整路径并回读源码行
根因:CurrentFile 是项目相对路径(./Test/LLvmLiteTest/App/main.vp),LLVM 把该目录写成 CU 的 DW_AT_comp_dir,主文件条目用 dir_index: 0 指向 comp_dir;stackmap 写入端只解析 --debug-line(无 comp_dir)⇒ 行记录退化成裸名 main.vp,运行期既显示不全也无法按路径重读源码行

改法:DIFile filename 保留整条绝对路径(相对路径先经 w32file.GetCurrentDirectoryA 前置 CWD,与 Repl.vp 同款低层调用,不经 os._mbuddy);directory 另存目录前缀。无论 LLVM 保留整条 name 还是拆成 dir+basename,写端都得到绝对路径,故无需改写入端
2026-09-27 19:18:16 +08:00
TermiNexus bb864dff6f fix(traceback): stackmap 的文件名全部退化为 ?? —— _tb_parse_dec 不跳前导空格
dwarfdump 的方括号索引右对齐(file_names[  1] / include_directories[ 12]),_tb_bracket_idx 把 '  1]' 交给 _tb_parse_dec,首字符非数字使循环体一次不进 -> 全部 pidx=0;行记录 fidx=1..N 永不匹配 -> 每条记录落到 '??'(实证 9146/9146 条共用同一个 '??' 串,而函数名/行号均正确)

_tb_parse_dec 增跳前导空格与制表符(修复文件名与目录索引两处)

dir_index: 偏移 st+9 指向冒号本身,恒解析为 0 -> 相对文件名拼不出绝对路径;改为 st+10
2026-09-27 19:01:23 +08:00
TermiNexus 31898ab4a5 fix(stubmerger): define→declare 降级时剥掉行尾函数级元数据附加(!dbg !N)
llc 对 declare 不接受元数据附加,报 expected = here(实证 LLvmLiteTest 949685246c8e241c.ll:898:104 的 declare ... !dbg !71)

根因:降级只删尾部 ' {',源 define 头行的 ' !dbg !71' 被原样抄下;declare 无函数体,!dbg 无意义(FunctionPrint 对 declare 从不打印 !dbg)

判据:参数表之后、括号深度 0、不在双引号内的 ' !';连同前导空白一起截断
2026-09-27 18:30:39 +08:00
TermiNexus 66e697e8df feat(codegen): 模块级 extern 全局生成外部声明(@裸名 = external global T)
注解含 t.CExtern/t.State → is_global_extern:裸名(无 SHA1 头)、Linkage=external、不写初始值、不建 storage

与 export 全局(本模块定义 + 裸名)区分;配合 includes f854f7e 的 export/extern 变量语法
2026-09-27 17:56:51 +08:00
TermiNexus d82f24e17a fix(diag): AST 解析失败日志行内联 行/列 + 原因
四处解析失败上报点(Phase2 的 [Phase A-pre-vpi]、Phase1 的 [Phase 1b-pre] 与单文件、main.vp 单文件)此前只打 AST 解析失败,看不到定位与原因;现内联 第 N 行第 M 列: <原因>,与 dump_parse_traceback 的完整 traceback 同源(原因由 includes/ast/parser.vp 的 _record_* 与 parse_tokens 出口兜底补记提供)。另:App/main.vp 同时带上你工作区既有的 define 迁移/版本号改动。
2026-09-27 17:32:34 +08:00
TermiNexus e65f04f16b fix(codegen): 字段表↔槽位一致性护栏豁免联合体
union 的字段表有 N 条(每成员一条)但 LLVM 布局只有 1 个槽位(成员全偏移 0、按最大成员定宽),二者本不相等;字段访问走 bitcast 到字段类型后的 GEP,不依赖槽位下标。误报实证:EdgeCaseTest 的 class Data(t.CUnion)(i_val/f_val 两成员 → 1 槽)被护栏拦下。
2026-09-27 17:26:04 +08:00
TermiNexus 0279965074 fix(codegen): 指针强转基座 T*(x) 的字段读写补按需过滤类名通道
(ast.Call*(vinner)).IsVolatile 报属性写入失败:T*(x) 被 parser 的 _consume_ptr_suffix_call 解析成 (T | t.CPtr)(x) 强转调用,结果类型是联合简化后的 i8*,lookup_field(i8,...) 必失败,而按需过滤的类名提取只认 Name/Attribute。新增 _base_class_from_cast_base:从强转注解(func 是 BitOr 联合)取裸类名,读路径、写路径、属性链递归父节点三处接入。取裸名而非模块限定名——lookup_field_by_class 的模块限定分支对「包名可解析、类注册在子模块」的形态会 fail-fast。
2026-09-27 16:02:04 +08:00
TermiNexus beccb36b66 feat(codegen): 单目 & 支持对构造调用/函数调用结果取址
&ViperKernel() 此前被左值检查拒绝(只在 Name/Attribute/Subscript 放行)。构造调用 _translate_struct_ctor 返回的就是入口 alloca 的栈存储指针,取址即原样返回该指针;其它返回值落到入口临时槽 alloca+store。复用 translate_unaryop 已求值结果,避免构造被求值两遍(__before_init__/__init__ 跑两次)。
2026-09-27 15:15:59 +08:00
TermiNexus 4255ab9d32 fix(fstr): f-string 改回每站点栈缓冲 + 禁止逃逸,逃逸点改 stdlib 拷贝
ExprGenFstr._fstr_finalize:结果缓冲改回每站点栈 alloca(线程安全、零运行时依赖;每站点各建一个,站点之间不再共享),docstring 与文件头写明语义:f-string 结果是当前函数内的临时值。

GenApi 新增唯一判据 fail_if_fstring_escape 与唯一拷贝出口 escape_fstring_copy(stdlib.malloc + string.strcpy);逃逸位置接护栏:translate_return(函数返回值)、translate_assign(字段/元素/全局变量赋值)、handle_module_level_var(模块级 AnnAssign 初始化)。

编译器自身 7 处逃逸点(ExprGenBase._cast_fail_where 6 处、AssignGenMixin.const_assign_msg 1 处)改为 escape_fstring_copy 显式拷贝——正是造成 ASan stack-buffer-underflow in vsnprintf 的那条路径。

残留(已在 GenApi 头注释诚实记录):间接逃逸(把 f-string 传进会保存它的函数、或放进容器后带走容器)静态判不出;类字段默认值路径未接护栏。全仓自查:TransPyV/App、includes、ViperApps 均无 容器/字段/全局 存 f-string 的写法,也无残留 return f-string。
2026-09-27 14:45:28 +08:00
TermiNexus 72a3c483e4 fix(fstr): f-string 结果缓冲改为模块级全局,修 return f-string 的悬空栈指针
现象:--asan 下 ASan stack-buffer-underflow(READ size 1,地址落在栈左红区)in vsnprintf,栈为 ExprGenBase.coerce_to_type:409 的 f-string 报文;非 asan 构建表现为随机乱码或 0xC0000005。

根因:_fstr_finalize 用 _alloca_at_entry 在当前函数开一个 [FSTR_BUF_SIZE x i8] 栈缓冲并返回其指针;而 f-string 结果在全仓被大量 return 带出函数(ExprGenBase._cast_fail_where、AssignGenMixin.const_assign_msg、CallGenLib 报文构造等),返回后再作 %s 喂给 snprintf 即读已弹栈的帧;且同一函数内多个 f-string 共用同一个 entry alloca 会互相覆盖。

修法:结果缓冲改为每个 f-string 站点一个模块级可写全局 internal unnamed_addr global [N x i8] zeroinitializer,经 bitcast 取 i8*(与 ExprGenConst.create_global_string 同一构造路径,符号链接期解析)。被否决的中间方案记录在注释里:编译期池缓冲 + inttoptr 常量地址会把编译进程的宿主地址烘进 .ll,对产出程序无效,绝不可用。

已知取舍(注释中写明,与 C 的 static char buf[N] 惯用法同):同一站点反复求值且同时保留多个结果时后者覆盖前者;多线程避免共享同一站点结果。彻底消除需运行时字符串分配器(当前无)。
2026-09-27 14:33:54 +08:00
TermiNexus ca5473304f fix(gen): 结构体字段表与 LLVM 槽位一致性 fail-fast 护栏
新增 StructGenMixin.verify_field_slot_consistency:字段表条数必须等于结构体槽位数,且每条目 Ty 与 Index 槽位同类(指针性/整数位宽/浮点位宽);不一致即 fatal_error 并指名 类/字段/下标/两侧类型。由 lookup_field 第一遍调用。

背景(真实崩溃):模块 8a96a2885bae09ca(ExprGenOps)把 ast.Name.id 编译成 GEP 下标 9(该槽是 ctx,i32),同一次构建里 320b55907b889d59(AssignGenMixin)用下标 8(i8*),两者共用同一 %struct 布局。后果:nm.id 读成 ctx(ASTCtx.Store=1)- inttoptr i32 1 - strlen(0x1) - ASan access-violation at 0x1。
2026-09-27 14:00:18 +08:00
TermiNexus 12bd7b7a16 fix(gen): 收敛 static 跨模块拦截为独立方法,消除类型推断污染
新增五个独立方法承载 static 检查与登记:_CheckStaticCrossModule(mod.name 取值)、_CheckStaticCallCrossModule(mod.fn 调用)、_CheckStaticFromImport(from-import)、_RegisterStaticModuleVar(模块级变量登记)、_RegisterStaticFunc(函数登记)。调用点只保留一行调用——内联新增分支会改变超大函数的类型推断,曾导致 translate_attribute 后续 match obj_ptr.Ty 报指针隐式转整数已禁止。

删除 _resolve_module_attribute_global 内与入口检查重复的 static 拦截(其两个调用点均在 _CheckStaticCrossModule 之后)。
2026-09-27 13:12:22 +08:00
TermiNexus dd56580855 feat(gen,static): static 符号禁止跨模块使用(定义侧登记 + 引用侧三处拦截)
语义 = C 的 internal linkage:static 符号(函数/变量)只在定义模块内可见,任何跨模块引用一律 fail-fast(NameError)。

- 登记表:CallGenBase 新增 StaticSha1Arr/StaticNameArr/StaticCount(PermPool 定长槽,与 CexportSha1Arr 同款)+ RegisterStaticSymbol/IsStaticSymbol(按 (符号名, 模块 SHA1) 精确配对,避免跨模块同名误判)。

- 定义侧登记:FuncGenDef 识别注解形态(-> t.CVoid | t.CStatic)与装饰器形态(@t.CStatic;裸 static def 已由解析层归一为装饰器)→ 登记函数名;TranslatorGenMixin.handle_module_level_var 识别注解形态(static x: int 已归一为 t.CStatic | int)→ 登记变量名。

- 引用侧拦截(三处,覆盖全部跨模块形态):ExprGenAttr.translate_attribute 入口统一拦截 mod.name(含 import 别名还原);CallGenMixin.translate_call 入口拦截 mod.fn(...) 调用(按 AST 形态一次覆盖 found_func/vpi/裸名兜底各分支);ImportsGenMixin.translate_import_from_names 拦截 from mod import name。

- 性能:三处检查均以 StaticCount > 0 快速短路——项目中无 static 符号时零开销(不触碰逐条线性扫描与 sha1 解析)。

- 同模块自引用(sha1 相同)与非模块基座(self.method / obj.field / Class.static_method)一律放行。
2026-09-27 12:58:51 +08:00
TermiNexus 424dbd7379 chore(gen): 加 [MODSHA1] 临时诊断定位 string.split 模块名解析失败(定位后删除)
在 translate_call 的模块限定分支加两条探针:

- [MODSHA1]:mod_ret_sha1 为 None 时打印 mod_path_ret / func_name / exact / suffix / ctrl_stdint(对照模块,必在 includes)——可区分 store 整体为空 与 store 有内容但缺该模块。

- [MODSHA1-D]:裸名兜底分支里,若 cl.func 仍是 Attribute(模块限定却解析不出模块的异常组合)则打印 mod_path_ret 与 func_name——覆盖 mod_path_ret 为 None 的情形。

同时把 mod_path_ret 的声明从 if 块内移到 if 外(与 mod_ret_sha1 一致),避免下游分支读未声明局部取到栈垃圾。

证据链:报错来自本函数 861 行(全仓唯一 fatal_if_unknown=1 的调用点),条件恒为 _resolve_module_sha1 对 string 返回 None;但 _sha1_map.txt 有 includes/string.vp、其 vpi 有 def split -> int、Phase1Main:390 是唯一写 map 处 ⇒ 静态无法解释,需运行时诊断。
2026-09-27 12:52:03 +08:00
TermiNexus 87f5b1026c chore(gen): 删除 CallGenDispatch 的 [MDISP] 临时探针及其专用 import stdio
该探针(并排取证 __with_ctx.__enter__ 与 pool.alloc 的 H6 判据命中差异)为定位 MpoolOopTest 的「跨模块调用 pool.alloc 找不到模块 SHA1」临时加入,现缺陷已定位,探针与其唯一使用者 import stdio 一并移除;非 local 对象分派逻辑不变。
2026-09-27 12:32:52 +08:00
TermiNexus 9455db0eec feat(gen,type): callable[] / callable[[]] / callable[[A,B],R] 与 t.Callable 逐点等价
- 判据单一真源:_SubscriptAttrIsCallable 扩为 _IsCallableSubscriptValue,同时认 Attribute(attr=Callable) 与裸名 Name(callable | Callable)。

- Subscript 分支在 Name 路径(泛型特化 / CArray / builtins typedef 兜底)之前拦截裸名 callable/Callable → i8*;否则 callable[] 会先撞 slice=None 的 fail-fast、callable[[]] 会被当泛型类名查空。

- TypeGenErr.try_extract_callable_ret 改用同一判据:callable[[A, B], R] 现在也会注册返回类型 R(可间接调用),无签名形态([]、[[]])仍返回 None。
2026-09-27 12:30:47 +08:00
TermiNexus a76cf55a2d feat(gen,type): 注解位置的小写裸名 callable 等价 t.Callable(i8*)
支持 func: callable = 函数名 的写法:Name 分支兜底 map_t_type 命中 callable → Ptr(Int8),与 t.Callable / t.Callable[] / t.Callable[[]] 同一 LLVM 表示(无签名函数指针,只取地址不作间接调用)。

同时明确大写裸名 Callable 同时服务 from t import Callable 与 Attribute 分支的 t.Callable。
2026-09-27 12:28:58 +08:00
TermiNexus c387926977 feat(gen,type): 支持无签名函数指针 t.Callable / t.Callable[] / t.Callable[[]]
三种写法统一解析为 i8*(与带完整签名的 t.Callable[[A,B], Ret] 同表示;函数值按 TPV 惯例 bitcast 为 i8* 存储):

- map_t_type 新增 Callable → Ptr(Int8):裸 t.Callable 此前落空导致 AnnAssign 注解类型解析失败。

- resolve_annotation_type 的 Subscript 分支:slice 为 None 时先放行 Callable(t.Callable[] 走空下标解析),其它类型仍 fail-fast 缺少 slice。

- 新增 _SubscriptAttrIsCallable 判据。

无签名形态不注册返回类型(try_extract_callable_ret 对非 Tuple slice 返回 None)⇒ 只适用于取地址/内联汇编操作数,不作编译器间接调用。
2026-09-27 12:25:52 +08:00
TermiNexus 7c53a8744d fix(gen,match): 穷尽 match 不再误报流程掉出末尾;default 臂直译不再经 def_arr 反查
现象:函数末句为 match 且含 case _: 穷尽臂、各臂均 return 时,仍报 TypeError 函数流程掉出函数末尾(ioctl 4 具体臂 + case _)。

- FuncGenDef / ClassGenVTable:收尾判据由「_ctrl_merge_reachable == 0」扩为「或末句 match 含无 guard 的 case _ 通配臂」。穷尽 match 不存在全臂不命中的路径,与 If/While 全分支 return 同语义,应生成 unreachable 而非 fail-fast。

- MatchGenMixin 新增 MatchHasWildcardArm / _IsWildcardPattern:识别 MatchAs(pattern=None)(含 as/or 组合),带 guard 的 case _ if c 不算穷尽。

- MatchGenMixin:_handle_simple_match / _handle_renum_match / _handle_sequence_match 三条路径改为 pass1 显式记录 default_case_node,default 块直译该节点;原实现经 def_arr[di] 反查 cases_list[di],collected 下标与原始下标在「有臂被跳过」时错位 → default_bb 空转 → merge 误判可达(同时 case _ 臂不生成 IR)。
2026-09-27 12:17:14 +08:00
TermiNexus cf815b17de feat(gen): volatile 构造写入落 LLVM volatile(逐字段 store + 构造三方法屏障)
值位置 volatile 前缀(a: int = volatile f() / a: S = volatile S(...))此前只在调用点无落点。LLVM 的 call 指令没有 volatile 限定符,故语义只能落在内存存取上:

- AssignGenMixin:赋值目标的结果槽按 volatile 存取(本次 store 立即带 volatile,并登记 VarEntry.IsVolatile 使后续 load/store 亦带);对已存在的 global/nonlocal/mod_glob 条目在分支前统一登记。

- CallGenCtor:_translate_struct_ctor / _translate_struct_ctor_kw / _translate_renum_construct / _apply_struct_defaults 增加 is_volatile 参数,清零、tag、默认值、逐字段 store 全部带 volatile;_translate_generic_ctor_call 直接从 cl.IsVolatile 取。

- CallGenCall._call_method_on_ptr 增加 is_volatile 参数:__new__/__before_init__/__init__ 的写入发生在被调函数内部,调用点无法逐条附加 volatile,故在调用后对该 self 指针追加一次 i8 volatile load 作为内存屏障,使构造写入不被优化器消除或跨越重排。

- CallGenMixin / CallGenDispatch:结构体、关键字、泛型、REnum 构造调用点统一透传 cl.IsVolatile。
2026-09-27 12:01:22 +08:00
TermiNexus 7d28c9649e feat(gen,err): CastError 定位到出错表达式;vpi 生成 fail-fast;函数属性接受直接形态
- ExprGenBase:新增 _cast_fail_where,指针/整数隐式转换失败时打印出错表达式与列号(多实参调用可定位到具体实参,如 Name 'toggle_ptr')

- Converter:import / from-import 别名列表为空、Alias 指针为空、name 为空分别 fail-fast,不再写出 import ,  这类残缺 vpi 行(该残缺会让包重导出注册失效,报出与根因无关的 NameError)

- FuncGenBase.extract_func_attrs:接受直接 @t.attr.<name> 装饰器形态(与类侧 _has_decorator 同口径),使 inline def 在无 import c 的文件里也生效

- 附:CallGenDispatch 内保留一处临时 [MDISP] 探针(MpoolOopTest pool.alloc 定位用)
2026-09-27 11:41:11 +08:00
TermiNexus f084da7c01 feat(gen): 重复声明改为无条件失败(不再比较类型),global 后再注解同样报错
- 判据改为:模块级已有同名变量 + 函数内再次用注解声明同名变量 ⇒ TypeError 重复声明,不再比较注解类型是否与全局一致(此前仅在类型不一致时报错)

- 有 global/nonlocal 声明后再带注解声明同名变量也报错(注解与既有全局声明重复)

- 检查点前移到注解解析之前(紧跟 REPL 分支),保证优先于 coerce_to_type 报错

- 合法写法只剩两种:global + 无注解赋值修改全局;或换不重名的局部变量
2026-09-27 08:55:08 +08:00
TermiNexus 78fc7313fb feat(gen): 同名全局 + 注解冲突优先报重复定义
- translate_annassign: 函数内(无 global/nonlocal、非模块级)再次注解同名变量时,若注解类型与模块级变量类型内容不一致 ⇒ TypeError 重复定义 fail-fast,不再延后到 RHS 的 coerce_to_type 报无关的「指针隐式转整数」(PointerTest/App/main.vp:46 _g_ptr: int = None,全局已是 t.CUInt32T | t.CPtr)

- 注解与全局一致时仍按 Python 语义遮蔽为独立局部变量(既有行为不变)

- 该检查走 _stg_ty_equal 内容比较(TypePrint),非地址比较,避免 Int32()/Ptr() 新实例造成误判
2026-09-27 08:50:18 +08:00
TermiNexus f0309e4904 fix(gen,renum): 指针操作符目标写入落盘 + REnum tag 三侧口径统一,修 kind() CastError
- AssignGenMixin: 补 UnaryOp(Deref) 赋值目标分支(*p = v / *&x = *&y),非 Deref 的 &x/-x/~x fail-fast;*p += v 真正实现;Tuple/List 目标合并

- CallGenDispatch: kind() 改四级 REnum 判据(变体表 / struct __tag / pointee 带 __tag / X.field 声明类型反推);修正 build_load 第一参数误传指针类型 rk_tag_p(生成 load i32*, i32* 使返回值 Ty 为 i32*,导致 CallGenLib.vp:355 vk_a: int = val.Ty.kind() 报指针隐式转整数的 CastError),rk_load 分支同步改 rk_ty.Pointee

- CallGenCtor / MatchGenMixin: __tag 读/写/比统一取字段声明类型 FieldEntry.Ty

- Test/FieldReadTest: 字段读取探针
2026-09-27 00:19:44 +08:00
TermiNexus bcf66edebe fix(vpi): 防环访问表表满不再静默丢标记,改为 ×2 扩容 + fail-fast
表满时曾静默 return(丢标记)⇒ 防环失效 ⇒ 递归闭包反复重解析同一批 .vpi,每个 vpi 的 Lexer/Token/AST 都从池分配且本段不回收 ⇒ 单次 translate 吃数百 MB ⇒ 1 GiB 主池 OOM

改为可扩容(ResetVpiTypedefVisited 同步复位容量),分配/扩容失败立即报错终止
2026-09-26 21:25:23 +08:00
TermiNexus 373a20c3e5 perf(prof,gen): 编译热点优化 + 内存池 OOM 诊断接线 + REnum 指针载荷修正
CallGenBase: 模块返回类型文本表改单趟登记+查找,find_func_in_module 合并为单趟链表遍历(PROF 热点)

BuildPipeline: ensure_dir 增加快速路径(已存在目录直接返回)

AssignGenMixin/Translator: alloc 失败打印 Pool/PermPool 统计与阶段上下文(SetAllocCtx)

CallGenCtor: REnum 指针载荷补显式 build_ptrtoint 再 coerce/store;新增 DebugInfoGen.vp

其余为历史累积改动一并入库
2026-09-26 19:27:23 +08:00
TermiNexus 676c19c362 feat(asm,err,typedef): 支持 __asm__/__llvmir__ 裸名与 typedef 语法糖;解析报错统一 Python 风格 traceback
CallGenCBuiltin/CallGenDispatch: __asm__/__llvmir__ 裸名调用,asminp/asmout/irinp/irout 与裸 ASM_DESCR。

AssignGenMixin: 支持 define NAME: T = V 形式的 CDefine 浮点值恢复;负浮点(-1e308)剥一层 UnaryOp(USub)。

Phase1Main/Phase2: AST 解析失败改调 GenApi.dump_parse_traceback(Traceback + File/行 + 源码行 + caret)。

TypeGenResolve/TypeGenCType: typedef 文本扫描统一支持 typedef 语法糖(builtins/库/单文件三处共用同一解析)。
2026-09-25 13:11:16 +08:00
TermiNexus a7af34bd7e 修正了部分代码的表达方式,最后可用版本,mlirlite 已基本完善 2026-09-23 17:41:49 +08:00
TermiNexus 951b89be36 修正了部分代码的表达方式,最后可用版本,正在为 mlirlite 做准备 2026-09-23 16:09:32 +08:00
TermiNexus a5ceb3d4eb 增加 JIT REPL 2026-09-21 16:17:19 +08:00
TermiNexus 9910e05760 修复了一些错误,更改了一点语法 2026-09-18 20:00:40 +08:00
TermiNexus 9e2ccbfab7 将部分 | t.CPtr 修正为 rawptr[T] 2026-09-18 17:56:24 +08:00
TermiNexus eb01a45024 修复了一些问题 2026-09-17 14:51:18 +08:00
TermiNexus cb0836de8a 清理了 t.CVoid 2026-09-16 16:06:48 +08:00
TermiNexus 9a191daa8d 错拼 rowptr 改为 rawptr 2026-09-16 11:42:28 +08:00
TermiNexus 7cba4cbc95 chore: 清理超长历史注释,并一并提交并行会话的代码改动
注释清理(65 文件 / 约 -2578 行 / 约 516 处):删除/压缩叙述式历史注释——多行根因叙事、工作代号段(D25/D30/N11/B5/S4/阶段E 等)、改前改后与先例叙述、逐条推导块,压成一行讲清作用信息;契约类注释(用途、参数与返回、关键约束与不变量、IR 结构说明)与被注释掉的代码一律保留

存疑 标记 6 处一字未动:StubMergerStore.vp、VpiReader.vp、VLogger.vp、AssignGenMixin.vp、CallGenLib.vp、CallGenLeafKit.vp

本次一并带入并行会话的代码改动(同批文件,按文件粒度无法拆分):fileio.File -> rowptr[fileio.File] 等

已知副作用/残留:① CallGenDispatch.vp 有 2 行非注释行的行尾被 LF->CRLF 规范化(纯字节变化,无文本改动);② core/Gen 区约 24 处压缩因并行会话整文件覆写被写回原样,尚未生效(StmtGenCifMixin/ClassGenVTable/FuncGenDef/ClassGenEnum/StructGenMixin/TypeSpec 等)

清单与复跑判据见 Debugger/comment_cleanup_summary.md(该目录不受版本控制)
2026-09-16 11:27:05 +08:00
TermiNexus b28f20cce7 chore: 清理调试打印与失效 import
CallGenCall.vp: 前次清理 DBG 打印后 import stdio 已无使用者,删除(该文件全文无 stdio. 调用,亦无经本模块的外部访问)

Test/App/nested_match_test.vp: 删除 _init_mbuddy 与 test_single_match 中的 8 处 [DBG] 进度打印;该测试的判定只看 --- Test N --- / OK / FAIL 行,test_main.vp 亦只取返回码,无断言引用这些文本

未改动任何逻辑(保留 FAIL: malloc for arena failed 等错误分支的真实输出)
2026-09-16 10:32:40 +08:00
TermiNexus 429de97083 fix(call): 泛型特化类跨模块 declare 按模板 vpi 注解对齐形参口径
特化名({sha}.list[str].__init__)在 vpi 参数注解缓存中无精确键——键段取自 class 行文本,只存模板 list[T]——定义侧形参注解取不到,调用点 declare 停在调用点实参口径(elem_size 字面量 i32 vs 定义侧 t.CSizeT=i64;池实参 MemBuddy*/i8* vs 定义侧 MemManager*),并经 .stub.ll 传播到未调用该函数的消费方模块

新增 VpiReader._resolve_vpi_param_annot_seg_template:按 {sha1}.{base}[T].{method} 定位模板条目、用特化实参替换段内类型形参 token 后再解析,未命中返回 None 不兜底;_coerce_args_to_def_params 在精确键未命中且类名含 [ 时改走模板退查并置 tpl_src,使 _coerce_arg_value_to_ty 对同为具体结构体指针但基类型不同补 bitcast;_call_method_on_ptr 的跨模块分支补建 declare 前的 coerce(先例 a83d183 的三处调用点)

同时把 {sha}.{cls}.{method} 的切分由首个 . 改为末个 .(CallGenLib/CallGenBase 两处):method 不含 .,首个 . 会把含模块限定名的类型实参截断(list[3571eabe5a83d798.AST|t.CPtr] 截成无 ] 的类名 ⇒ 模板退查必 miss)

另修 VpiReader 一处 double-free:_subst_template_arg 早退分支返回入参本身,替换前释放原缓冲需先判同一性(三条路径各释放一次)

效果: 危险级签名不一致 TransPyV 8->4->0、Test/AstTest 6->2->0,ZlibTest/WebTest/FakeDuckTest/ListTest/GenericTest2/VectorGenericTest 保持 0;4 个目标符号 define/declare 共 148 处全部统一为 MemManager*+i64(0 处旧口径);自举达全树逐字节不动点(exe sha256 相同、temp/** 635 文件零差异);AstTest 及既有测试全 PASS 且无 ASan 报告
2026-09-16 09:31:57 +08:00
TermiNexus a83d183abb fix(gen/call): 跨模块调用 SSA 整型实参按定义侧形参扩宽
declare 由调用点实参链建立,而 _coerce_arg_value_to_ty 对非常量(SSA)整型此前原样返回,导致 declare/call 停在调用点窄宽度(string.strncpy 第三参 i32),与定义侧(vpi 注解 n: t.CSizeT => i64)不一致,并经 .stub.ll 传播到未调用该函数的消费方模块

现按源符号性 zext/sext 扩宽(真源 ExprGenBase._zext_or_sext_val,先例 CallGenCtor._translate_t_type_cast),实参 AST/trans 下传;self_off 模块级 0、方法 1

效果: WebTest 危险级签名不一致 1 => 0
2026-09-15 23:58:40 +08:00
TermiNexus 31b8dccffd fix(stub): declare 侧识别 t.CPtr[...] 段,与定义侧指针层级口径对齐
t.CUInt8T | t.CPtr[t.CPtr] 在定义侧(TypeGenResolve._is_cptr_subscript)按指针层级合并得 i8**,但声明侧段判据只认整段等于 t.CPtr/CPtr,下标段落入普通段解析后取首段 => i8,造成跨模块 define/declare 签名不一致(实参 1 字节 vs 写入 8 字节)

新增 t.CPtr[/c.CPtr[ 段的识别与递归解析(与同文件 rowptr[...] 分支同构),数外层 Ptr 层数 N 后按 Ptr^N 合并;裸 t.CPtr 段路径未动,未放宽 fail-fast

注: TPC 侧 LLVMTypeMapper._resolve_bitor 与 SymbolUtils.IsCPtrNode 同源缺陷未改,两套编译器需同时修以免产物分叉
2026-09-15 23:58:39 +08:00
TermiNexus de86a1ed70 fix(gen/for): 负数字面量 stop 的 range 循环退回有符号谓词,修复 _find_provider 死循环
stop 边界为负数字面量(如 range(top - 1, -1, -1))时,3.6 的边界推导把无符号 start(UINT64 字段)判为无符号循环,负步长分支随即由 sgt 覆盖为 ugt;而比较在 stop 类型(i32)上进行,-1 在无符号域是 UINT32_MAX,条件恒假 => 循环体成为死代码。_withcontent._find_provider 因此恒返回 NULL,with 上下文的 __mbuddy__ 注入永不生效(FakeDuckTest Test 9 FAIL,随后 _str.upper 解引用 NULL vtable 触发 ASan READ)

新增 3.7: stop 为负数字面量时撤销无符号谓词(判定与既有 step_neg_const 同口径),退回 slt/sgt;正步长非负 stop 的既有 ult 优化与 range(stop) 单参形态不受影响
2026-09-15 22:45:28 +08:00
TermiNexus 296f6a739e fix(gen/renum): REnum 补齐 Assign 形式无载荷变体的变体表注册
根因: _translate_renum_def / _register_renum_variants 只认嵌套 ClassDef 变体,Empty = 1 / None_ = 0 这类无载荷 Assign 变体既不计入 variant_count 也不写变体表 => match 查不到 tag(-1) => 本会话硬校验 fail-fast,此前则静默丢 case(Test\EdgeCaseTest 旧产物 ac0d686cb2cc783b.text.ll:117-119 仅 1 个 case,臂体 match.case.11.1 结构性不可达)

修复: 按 TPC 口径(HandlesClassDef._EmitREnumLlvm / SymbolExtractor)两遍处理——先取 Assign 显式值上界,ClassDef 变体从 max(显式值)+1 起编号(避免与显式值撞号),Assign 变体 tag = 显式整数值;表槽号与 tag 值解耦。纯 ClassDef REnum(含 LLVMType)生成结果不变。硬校验保留不动
2026-09-15 22:45:28 +08:00
TermiNexus 617d4ea911 fix(gen/assign): 模块级与 nonlocal 分支补写赋值注解上下文通道
模块级 AnnAssign 的 mod_glob 分支与 global 语句分支、以及 nonlocal 分支此前均未写入 trans.CurAssignAnnot/CurAssignNode,导致模块级/闭包内 s: Stack[int] = Stack() 在泛型裸名构造处 fail-fast、x: int = c.Deref(p) 退化为 i8 单字节读取

按主分支既有先例(AssignGenMixin set 在 RHS 翻译前 / clear 在返回前)在三处分支补齐同款 set/clear,并覆盖全部分支的提前 return 与末尾出口,避免通道残留污染后续 c.Deref 读取宽度与泛型构造类型实参推断

影响面: 全仓 模块级/global/nonlocal 的 X: T = c.Deref(...) 与 X: Generic[...] = Ctor(...) 均 0 处,属纯增量
2026-09-15 21:52:42 +08:00
TermiNexus 5a59432733 fix(gen): 泛型裸名构造补齐注解上下文与关键字实参两条推断来源
- 赋值目标/返回注解上下文作为最后一级类型实参来源(s: Stack[int] = Stack()、-> Stack[int] 里 return Stack()),注解解析复用 _extract_type_args_from_slice,以 CurAssignNode/CurRetNode identity 限定直接值避免嵌套误用

- 仅关键字实参构造按 __init__ 形参名定位槽位(Box(v=42)、Pair(b=string, a=1) 乱序),泛型构造码生成不再静默丢弃 cl.keywords(此前会生成少一个实参的错误调用);形参名查无/槽位空洞 fail-fast

- 抽取共用 __init__ 形参遍历(_get_ctor_init_params),位置对齐语义保持不变
2026-09-15 21:52:41 +08:00
TermiNexus 6394ea30e2 fix(call): 泛型类裸名构造按 __init__ 签名对齐类型实参并禁止静默默认 int
- 删除按序号消费「过滤纯 int 的 hint 列表」的压缩逻辑(整型实参被剔除后 hint 下标与形参序号错位,Pair(1,字符串) 被误判为 Pair<str,str>),改为取 __init__ 中注解为该类型形参的形参位次所对应的位置实参推断(Vector[T].__init__(self,pool,capacity,_hint:T) => T 对齐第 3 个实参)

- _infer_generic_ctor_type_arg 对 Name/Attribute 实参改取真实类型(变量注解类名/字段注解类名),取不到即 fail-fast,不再回退 int
2026-09-15 21:33:54 +08:00
TermiNexus b27633c86e fix(Gen/Type): long/unsigned long 按目标平台取宽,Windows 对齐 TPC LLP64(ULONG/LONG => i32)
- map_t_type 对 CLong/CUnsignedLong 原为硬编码 Int64(64 位平台假设),导致 Windows 目标下 ULONG/LONG 出成 i64:83 个 Win32/posix 声明的 long 形参宽度错误,且 CRITICAL_SECTION 等结构体布局为 48B(正确 40B,真实 Win64 ABI)

- 新增 _long_llvm_ty:target triple 含 windows/win32/mingw/msvc => Int32,其余(LP64/裸机 ELF)保持 Int64;规则对齐 TPC lib/includes/t.py configure_platform;triple 嗅探写法对齐 StmtGenCifMixin.vp:95-109

- 定义侧与 stub/declare 侧共用同一 map_t_type,单点改动两侧同变;未改 includes/stdint.vp(ULONG=t.CUnsignedLong 的意图正确,错在宽度)
2026-09-15 20:56:52 +08:00
TermiNexus 813f0157e3 fix(Gen/Expr): 类名不再被当模块级全局变量制造引用,消除 ASan 构建 undefined symbol
ExprGenAttr._resolve_module_attribute_global 在未命中已注入全局时会新建 @<sha>.ClassName = external global i8* 并 load;该 load 无使用者,非 ASan 构建被 llc 删除,ASan 插桩后其全局地址参与 shadow 检查无法删除 => ld.lld: undefined symbol 481fd55266ff1187.LLVMType (.refptr)。若属性名是已注册类/结构体/REnum(StructGen.find_struct 命中)则直接返回 None,交给类/REnum 路径。
2026-09-15 20:47:15 +08:00
TermiNexus 592400a1ea fix(asan): 并行 llc 任务在 ASan 模式下改用插桩后的 .asan.ll 作输入
Phase B 的 init_llc_task 先前用未插桩的 ir_path 拼 LlcCmd,opt 产出的 .asan.ll 只写进 OptCmd,worker 先跑 opt 再跑 llc 却读回原 .ll,导致 App 侧 76/76 个 obj 无 __asan 符号(ASan 内存排查半盲)。现将 llc 输入统一为 compile_input,与串行路径 compile_ll_to_obj 及 TransPyC 参考实现同口径;非 ASan 分支命令逐字不变。
2026-09-15 20:47:14 +08:00
TermiNexus 8312e653e8 fix(CodeGen): 裸结构体形参按 C ABI 按值、修正 |t.CPtr 指针层级,对齐 TPC 并消除签名发散
- FuncGenDef/VpiReader._AggregateParamTy: C ABI 外部声明(t.CExtern/t.State)的裸结构体形参按值 %Struct(可取完整定义时),真 opaque 降级 i64;TPV 内部函数/方法维持 Ptr(struct)。三处(定义侧/函数注入侧/方法注入侧)同口径

- TypeGenResolve/VpiReader: BinOp(A|t.CPtr) 的指针别名守卫改为 pointee 为已注册 struct 才吸收,否则追加一层 => VOIDPTR|t.CPtr 由 i8* 修正为 i8**(对齐 TPC)

- 效果: SetConsoleScreenBufferSize 等 6 个符号由 i64/%COORD* 双形态收敛为按值,同符号多形态数 193->186
2026-09-15 20:47:14 +08:00
TermiNexus 31df298341 fix(gen): 修 REnum match case 静默丢弃与 CDefine 负 64 位常量截断,补 void 注解放行、移植类型定义前移
- MatchGenMixin: 2a 路径查变体 tag 改用裸类名(注解书写形态带模块前缀,与变体表裸名注册口径不一致致 tag=-1、case 被静默丢弃、match 全走 default),并在 build_switch 前硬校验 应提交 case 数==sw_count,不再静默丢

- ExprGenCdef: _const_int_for_cdefine 补 value < -2147483648 下限判据,修正 (0xA5A5A5A5<<32)|0xA5A5A5A5 等负 64 位模式被物化为 i32(写入 inttoptr 零扩展 vs 比较 sext 不一致,致 MemBuddy canary 假阳性 rc=6)

- FuncGenDef/ClassGenVTable: 掉尾检查新增注解级 void 判据(t.CVoid/裸名 CVoid/None),不再因 REnum match 退化误判 void 函数缺 return

- StubMergerBuild: 移植 TPC 的 类型定义前移 遍,满足 llc 对 by-value 具名类型需先定义的要求(invalid type for function argument)

- main.vp/Phase1Main.vp/project.vpj: 新增 --detection-deep 深诊断开关与 canary 哨兵、ASan 静态库声明
2026-09-15 19:11:54 +08:00
TermiNexus 60100fa6af fix(type): 联合类型 A|t.CPtr 判据由白名单改为解析成功即保留,stub 侧同步
定义侧 TypeGenResolve.resolve_annotation_type 的 BinOp(A | t.CPtr) 分支:裸名
pointee 的保留判据由“硬编码名字白名单 + 必须是已注册 struct”改为“以解析是否
成功为准”——命中统一口径 resolve_typedef_or_map(CTypedef 动态表 → builtins
→ stdint/w32.win32base 库源 → map_t_type)且结果非指针即保留该已解析类型。
于是 sizet | t.CPtr ⇒ i64*(原静默退化为 i8*)。

修复前的故障:TPV2 Phase1 的 includes/ast/tokens.vp _kw_index_put 按 i8* 处理
哈希槽,slot[0] 只做 1 字节读写,指针被截断成低字节 0xd0,随后被当 KwEntry*
解引用 → ASan 读地址 0xd0 崩溃(TPC 侧同 sha 为 alloca i64*/load i64/store i64)。

stub/declare 侧 VpiReader._resolve_vpi_ret_annot_str 同步同一判据:新增
first_td 标记,对裸名(无 '.')、非白名单、非指针的解析结果复用同一个
GenApi.resolve_typedef_or_map 判定,命中即保留 pointee。声明侧与定义侧现在
同型(sizet/uint64/INT/u32 等别名两侧都得到 i64*/i32*),同型即收敛,
不再需要把 declare 侧坍缩为 i8* 去迁就定义侧。

既有路径行为不变:泛型参数名、内建标量白名单(int/char/str/bytes/bool/float/
double)、解析失败与裸枚举名的 i8* 兜底;指针别名(BYTEPTR/HANDLE/VOIDPTR)
因解析结果为 Ptr(...) 不置位 first_td,仍坍缩 i8*,与定义侧 td_res_is_ptr
守卫对称。
2026-09-15 13:52:21 +08:00
TermiNexus c1859a111c fix(tpv): 撤销 llc 输出重定向,回归继承父控制台并保留 GetLastError 与退出码诊断
病因:_run_cmd_wait 置 STARTF_USESTDHANDLES 后所有 llc 均无法启动(CreateProcessA 返回 0,GetLastError=87 ERROR_INVALID_PARAMETER),整条构建被阻断。已逐项核对生成的 IR 并排除:STARTUPINFOA 布局(偏移 80/88/96、size 104 正确)、si.cb=104、GEP 字段索引 11/15/16/17 正确、CreateFileA 的 access/share/disposition/flags 正确、dwFlags=256 正确、CreateProcessA 十个参数正确;静态证据无法再推进,故撤回重定向。

移除:_run_cmd_wait 的 log_path 形参、CreateFileA 打开 .llc.log、STARTF_USESTDHANDLES 与三个 hStd 句柄赋值、父侧 CloseHandle 守卫;连带删除失效链路 _log_captured_output、CMD_OUTPUT_MAX、LlcTask.LogPath 及其 init/free 与成功路径 DeleteFileA。进程创建回到继承父控制台(dwFlags 保持 memset 的 0),llc 输出直接外显。

保留诊断(用户要求):CreateProcessA 失败后立即取 GetLastError 并以 VLogger.warning 打印(worker 线程内不能用 error,它会 sys.exit);串行 compile_ll_to_obj 与并行 run_llc_parallel 失败时均打印 模块sha1、退出码、完整命令;LlcTask.ExitCode 保留。
2026-09-14 23:02:59 +08:00
TermiNexus 05d5bfbf37 fix(tpv): 修 CreateProcessA 重定向未启动外部命令,并把捕获输出移到 error 之前
主因:_run_cmd_wait 置 STARTF_USESTDHANDLES 时只设了 hStdOutput/hStdError,hStdInput 仍是 memset 后的 NULL(0);Windows 要求三个句柄都有效且可继承,故 CreateProcessA 直接返回 0(表现为 exit=4294967295 即 -1,且 .llc.log 恒空、一次也没有 llc 输出)。日志文件改以 GENERIC_WRITE|GENERIC_READ 打开并令 hStdInput 同指该文件(llc 不读 stdin),三态句柄均不泄漏;无重定向时不置 USESTDHANDLES、退回继承控制台的行为不变。

另在 CreateProcessA 失败处立即取 w32.win32base.GetLastError 并以 VLogger.warning 打印错误码+命令+重定向文件路径(warning 而非 error:error 会 sys.exit,而此处运行在 llc worker 线程内)。

同时修正一处使功能完全失效的顺序问题:_log_captured_output 原先排在 VLogger.error 之后,而 error 内部 sys.exit(1),故捕获到的 llc stderr|stdout 永远不会打印(这正是只看到一行 llc 编译失败、.llc.log 留空的原因)。串行 compile_ll_to_obj 与并行 run_llc_parallel 两处均改为先打印捕获输出、再报错。
2026-09-14 22:14:45 +08:00
TermiNexus 82f16127c0 fix(tpv): llc 日志文件句柄初始化改用 NULL,避免整数常量赋给指针类型
_run_cmd_wait 里 log_fh 原以 w32.win32base.INVALID_HANDLE_VALUE 初始化,而该常量是 CDefine = -1(整数),赋给 HANDLE 指针类型触发"整数隐式转指针已禁止"的 fail-fast(BuildPipeline.vp 原 595 行)。

改为以 None(NULL) 初始化;仓库既有惯例是只把 INVALID_HANDLE_VALUE 用于比较(全仓 20 余处同写法),故其余比较判断不变。因 NULL 与 INVALID 都不应参与关闭,父侧 CloseHandle 增加 None 守卫,避免对 NULL 调 CloseHandle。
2026-09-14 21:19:20 +08:00
TermiNexus 785741026f fix(tpv): llc 失败日志附带 stderr|stdout、退出码与完整命令
此前 llc 失败只打 模块sha1 与命令行:llc 的 stdout/stderr 因继承控制台而外显,程序拿不到文本,导致无法定位 verifier 报错。

_run_cmd_wait 新增可选 log_path:非空时用可继承安全属性把子进程 stdout|stderr 重定向到临时文件(不用管道:写文件不会因缓冲满阻塞、天然无死锁;且 worker 线程禁用池分配,故不复用 includes/subprocess.vp),CreateProcessA 成功后立即关闭文件句柄;log_path 为空时行为与原先逐字节一致。

LlcTask 末尾追加 LogPath/ExitCode(构造点均按 __sizeof__ 与整体 memset 处理,既有字段偏移不变);worker 记录退出码;run_llc_parallel 与串行 compile_ll_to_obj 在失败时打印 模块sha1、退出码、完整命令 与捕获输出(上限 8KB,带截断标记;输出为空/打开失败/无文件 三态如实打印,不伪造),成功时删除捕获文件、零额外输出。

包装层(compile_module_to_obj 摘要、run_pipeline 的 ErrorMsg、Phase2 PhaseB/PhaseB+ 聚合终止语)本身拿不到输出,保持不动,详情由深层打印。
2026-09-14 20:21:57 +08:00
TermiNexus 51daf28297 fix(expr): 三元表达式 else 分支按 then 的指针类型下传目标类型
VpiReader.vp:1963 的 rel_path_rt if rel_path_rt is not None else 单字符问号 触发 CastError(探针 kind=35 IfExp、val.Ty=i32、target_ty=i8*)。根因并非跨模块函数返回类型未解析:then 分支 rel_path_rt 已是 i8*(解析成功,赋值处与已有 fail-fast 均未触发,三条独立证据);真正的整数来自 else 分支——单引号单字符字面量在词法层被判定为 CONST_CHAR,而 translate_ifexp 翻译 else 时未下传目标类型上下文,遂物化为 i32(63),随后在类型对齐阶段被按 then 的指针类型 coerce,撞上整数隐式转指针的 fail-fast。

改为:仅当 then 分支是指针时,把其类型作为 else 的 target_ty 下传;单字符字面量随即走 ExprGenConst 的指针目标分支物化为 1 字符字符串(对齐 TPC 的引号推断),两分支同为 i8*,不再进入 coerce。非指针 then 不下传,既有常量物化与位宽行为逐字节不变(如 base64.vp 中 then 为 char 的两处不受影响)。

另经核查:模块级函数返回类型并无静默默认 i32 的问题——「依赖已就绪却解析不到」已是 fail-fast(CallGenMixin 跨模块函数路径与方法侧 _fail_method_ret_ty 同构),保留 i32 的仅是多遍收敛中间态的占位,故本次不动。
2026-09-14 19:26:46 +08:00
TermiNexus 7fabf886fc fix(vpi): 返回注解索引重建的键长常量拼写修正为 VPI_KEY_LEN
_vpi_rebuild_ret_idx 里把键长写成了不存在的 VPI_RET_KEY_LEN(参数侧另有 VPI_PARAM_KEY_LEN=224,本文件仅定义 VPI_KEY_LEN=224 与 VPI_PARAM_KEY_LEN)。此前该未定义标识符被旧规则按类型零值兜底编成 0,使重放偏移 ri*0 恒为 0(每条键都从第 0 条键位置读取),返回注解索引重建结果错误;因探测用 live 键比较,表现为未命中回退线性扫描——结果仍正确但索引形同无效。

这解释了此前实测中 _lookup_vpi_ret_annot 加索引后仍有约 913K 次 strcmp(返回侧索引一直未生效),而参数侧索引生效并从榜单消失。改为本文件既有且返回侧一致使用的 VPI_KEY_LEN(malloc/memset/查找/写入各处均用该常量)。
2026-09-14 16:54:36 +08:00
TermiNexus a46391bf42 fix(expr): coerce_to_type 拓宽方向判定改传 builder.Pool,修正未定义实参 pool
coerce_to_type 作用域内并无 pool(其参数是 builder),L425 却把 pool 作为第 1 个实参传给 _zext_or_sext_val。此前该实参翻译失败会被静默按类型零值兜底,使传给 ExprGenOps.should_zext_widen 的池为空、变量注解符号性判定失效,从而在无符号拓宽时错走 sext(即该处注释所述的无符号扩展缺陷根因)。

改为传 builder.Pool(与同函数内 llvmlite.const_int64(builder.Pool, 0) 等一致,是共同值所处的池)。该未定义实参由本会话新增的「实参翻译失败即 fail-fast」规则暴露。
2026-09-14 15:48:09 +08:00
TermiNexus b918321f77 perf(callgen): text.ll 存在性改属性查询 + 返回类型按模块一次性缓存
第 1 项:_dep_text_ll_exists 原以 fileio.File(MODE.R) 只读打开探测存在性(33,244 次调用,占总 create_file_utf8 46.6%,每次约 57us 的开句柄成本)。改用 GetFileAttributesA 并显式排除目录(对齐 IncludesScanner._PathExistsA 先例),无输出缓冲、零句柄。

第 2 项:_LookupMethodRetTyFromTextLL(29,974 次、约 380,505 tick/次、self 11.41e9 即 8.9%)原每次重开加重读加逐字节重扫整份 text.ll(实测约 2.47 tick/字节,均值 154KB)。新增 PermPool 缓存表(mangled 名 到 返回类型文本):某模块 text.ll 首次成功读入时登记其全部 define 行,同模块后续查询 O(1);命中后用调用方 pool 经 _ParseLLVMRetTyStr 重建类型,与原路径等价。

关键安全点:只存返回类型文本、绝不存会随每文件 reset_to 失效的 LLVMType*;同名只记首次(保持首个匹配优先);仅成功读到文件后才回写,未找到或文件暂不存在绝不缓存(Phase A 顺序生成依赖,负缓存会出错);缓存或分配失败一律回退原扫描。原扫描逻辑一字未改,保留为未命中路径。
2026-09-14 15:26:10 +08:00
TermiNexus ce612c8e57 perf(lookup): 恢复名字索引可信未命中即确无的短路(Ty 索引保持停用)
4ed7f39 因 Ty 索引不等价,把名字索引的未命中短路一并回退了,导致 find_struct(8.25M)/lookup_ctypedef(5.64M) 等又回到全表扫描。

经逐处自证不变量后仅恢复名字键索引:名字或键只在注册时写入一次(中和仅置空名,空名查询显式回退扫描);索引在所有写入路径同步维护,判据 Built==Count 失败即失效回退;探测用 live 键且同键条目沿链按注册顺序排列(无空槽夹层)⇒ 可信未命中即原线性扫描亦无所获,短路逐字节等价。

恢复点:StructGenMixin.find_struct、TypeGenCType.lookup_ctypedef、CallGenLeafKit.find_func_in_table/find_func_entry_global、TypeGenCDef.lookup_cdefine_{constant,double,string}、VpiReader._lookup_vpi_param_annots/_lookup_vpi_ret_annot(后者保留泛型特化归一化兜底)。

find_struct_by_type 的 Ty 索引判定保持停用(事故根因);首字符预筛、Function.NameLen/FUNC_SIZE、includes 均未改动。
2026-09-14 12:59:27 +08:00
TermiNexus 4ed7f3909a fix(lookup): 索引未命中一律回退原线性扫描,停用不可信的 Ty 索引判定
降低「索引可信且未命中即判定确无」语义带来的风险面(该语义是本轮构建 fail-fast 的最高嫌疑):索引只用于加速命中,未命中一律回退原线性扫描,使各查找函数在所有输入上的返回值与优化前逐字节一致。

StructGenMixin.find_struct:去掉「索引可信且查询名非空则跳过 O(n) 扫描」分支,未命中恢复原精确匹配全表扫描。

StructGenMixin.find_struct_by_type:整体停用 Ty 索引判定 —— 条目 Ty 会被深拷贝改写,槽按注册时 Ty 落位,改写后与 live 键脱钩,命中不保证是原线性首命中(不等价),故恢复原线性精确扫描;get_class_name_by_type(REnum.kind 识别路径)、register_struct 去重、lookup_field 首遍随之回到优化前语义。

CallGenLeafKit.find_func_in_table/find_func_entry_global、TypeGenCType.lookup_ctypedef、TypeGenCDef.lookup_cdefine_{constant,double,string}、VpiReader._lookup_vpi_param_annots/_lookup_vpi_ret_annot:可信未命中不再直接返回,一律落入原线性扫描。

未触碰:Function.NameLen/FUNC_SIZE、各处首字符预筛、includes/string.vp。
2026-09-14 12:41:27 +08:00
TermiNexus e7f47735ed perf(callgen): find_func_in_module 以 function_get_name_len 替代 strlen 热点
第二趟后缀匹配对每个函数节点调用 strlen(cur_name)(实测 2373 万次、占全程序 strlen 73.7%)改为读取 Function.NameLen;两处替换点:CallGenBase 约 563 与 597。

并在外部函数返回类型白名单登记 function_get_name_len 返回 i64(CSizeT),避免 stub 未注入时按默认 i32 导致签名不一致。匹配语义与返回顺序不变。
2026-09-14 08:26:18 +08:00
TermiNexus cb430bf00a perf(gen): 结构体/CTypedef 查名加索引,热点线性查名补首字符预筛
StructGenMixin.find_struct:索引可信且查询名非空时,未命中即判定确无同名条目,跳过 O(n) 精确扫描(此前索引只短路命中、未命中仍全表扫描,实测 527 万次逐条 strcmp 即源于此)。

TypeGenCType:新增 CTypedef 名索引(开放寻址、槽存 index+1、Built==len 才可信、不可信或未命中回退线性扫描、clear_ctypedefs 同步复位),消除 lookup_ctypedef 约 445 万次 strcmp。

补首字符预筛:StubMergerIR._FindSha1ByModName、CallGenBase._ensure_method_declare、VarGenMixin.find_var、CallGenLeafKit.find_func_entry_in_table(_owned)(首字符不等即判不等,匹配结果与顺序不变)。
2026-09-14 04:15:36 +08:00
TermiNexus e8db102b9e perf(gen): 结构体按模块查找与 ctypedef 跨度查找加首字节预筛
StructGenMixin.find_struct_by_module 的名与 ModuleSha1 两次 strcmp、TypeGenCType.lookup_ctypedef_span 的逐项名比较,均加首字符/首字节必要条件短路(首字节不等即判不等),并省去原逐项 strlen。匹配结果与返回顺序逐字节不变。
2026-09-14 00:09:06 +08:00
TermiNexus 16472a61d9 perf(lookup): 热点线性查找表加开放寻址名索引,O(n) 降为期望 O(1)
VpiReader 参数/返回注解键表(查表 + 写去重共用)、CallGenLeafKit 全局函数表、TypeGenCDef CDefine 表:由线性扫描改为开放寻址索引,对应热点约 117M + 17.8M + 10.5M 次 strcmp。

索引槽只存下标+1(不存键/节点指针),键表 realloc/移动后槽下标仍有效,无悬空指针;可信判据 Built==count,每次注册同步维护,不可信或未命中一律回退原线性扫描(正确性保证)。每次注册占一槽以保持首注册优先(探测链首个键相等者即原首个命中)。CallGenLeafKit 快速路径仅限 owner_sha1 为 None 的全局表查询,保持 owner 优先语义不变。
2026-09-14 00:05:12 +08:00
TermiNexus a9819e4123 fix(gen): 方法分派与 list 内建判定改用接收者实际特化名,根治 list[int].get 截断
_translate_method_call 原以注解来源 class_name 构造被调名;同一函数作用域内同名变量被声明/赋值两次时该名退化(get_class_name_by_type 的类型后缀匹配会把 list[sha.AST] 误判为元素类 AST),使 list[ast.ASTptr].get 被误绑为 list[int].get(返回 i32)后 inttoptr 截断指针,读垃圾 AST 节点崩溃(MatchGenMixin._handle_renum_match / _handle_simple_match)。

现统一以接收者实际 LLVM 结构体类型名(IR 真值)为准:基名为泛型模板时,含类型实参则以实际特化名覆盖;退化为裸泛型基类则 fail-fast(D25 口径,禁止静默默认 list[int])。

抽出 CallGenProp._strip_module_sha1_prefix / _resolve_generic_receiver_name / _is_list_builtin_receiver 共用实现;CallGenDispatch 8 处 list 内建判据同步改为基于接收者实际类型(真实 list 命中与 __elem_size__/hit_box 行为不变,实际类型不可得时保留注解名回退)。lookup_name 缓冲改为按精确长度分配。
2026-09-13 21:11:33 +08:00
TermiNexus 76df3f0225 perf(gen): 结构体/类型查找去线性化并守卫全量遍历残量
StructGenMixin: 加「名 | Ty 指针值 -> 首注册下标」索引,find_struct/find_struct_by_module/find_struct_by_type 走 O(1) 快速路径;每次注册占槽保序,中和/改名后键不等自然跳过,未命中或索引不可信回退原扫描。并补 _struct_name_hash/_struct_ty_hash(32 位 FNV,常量均小于 2^63 以避开大十进制字面量解析风险)。

TypeSpec.InternSpec: 加「文本键 -> 驻留下标」开放寻址索引,去重由 O(n) 降为期望 O(1);命中返回原同一实例,注册顺序/容量 fail-fast 语义不变。

register_struct/lookup_field/get_class_name_by_type 的按 Ty 指针首匹配改走 find_struct_by_type;CallGenLeafKit/CallGenCall 的按序号全量遍历补守卫注释(避免后续误改为按名查找)。
2026-09-13 18:56:16 +08:00
TermiNexus 0dd7511a9b fix(tpv): 修常量折成0致alloc(0)与多处fail-fast,并落地热点优化
TypeGenCDef: CDefine 表名深拷贝(vpi 注入名为零拷贝指针且 vpi_buf 提前释放,悬空名致常量查出错误值 0)

VpiReader: 模块级 SHA1_LEN(16) 改名 SHA1_HEX_LEN,消除与 Phase1.SHA1_LEN(17) 的跨模块同名冲突

CallGenCall: 实参翻译失败/漏传必需实参改 fail-fast,禁用零值兜底(堵住 SHA1_LEN 未解析变 alloc 0 的通路)

MatchGenMixin: 序列 match 的 case _ 分支补容量守卫(原先会越界写穿 pool 块、破坏空闲链)

Phase1Main/Phase1Scan: 预扫描缓冲分配失败改 fail-fast;加临时取证打点

CallGenBase: find_func_in_module 4 趟链表扫描合并为 2 趟

ImportsGenMixin: import 查找加哈希索引(键为 NUL 结尾副本,未命中回退原扫描)

TypeGenBase/TypeGenResolve: 库 typedef 按模块一次性门禁与缓存

VpiReader: vpi 注解解析补 rowptr[T] 分支
2026-09-13 17:50:03 +08:00
TermiNexus af2c66e7d7 chore(tpv): 永久池扩容至 1 GiB
PERM_POOL_SIZE 由 512MB (536871176) 调整为 1 GiB (1073741824),为 REnum 注册表挂到 PermPool 后留足余量。
2026-09-12 23:14:52 +08:00
TermiNexus eef84657a1 fix(tpv): 修正 REnum 表按名查找与 match 分派,恢复 str() 指针强转
str() 对整数(sizet/i64)走 snprintf 十进制格式化(Python 语义),不是 i8* 强转;用它读 sizet|t.CPtr 联合表元素再 strcmp 永不相等 => _is_renum_class_name 恒 0、变体名查不到(__types.vp:407 bits / :159 Void 根因)。IR 实证 output/a8891afb66d3eda4.text.ll L5551-5558。

- ClassGenEnum: RenumClassNames/RenumVariantNames 读侧统一改 (str)(t.CVoid(x, t.CPtr));新增 _is_renum_by_entry 与 RenumTableDump;REnum 注册静默早退改 fail-fast

- MatchGenMixin/CallGenMixin: match 分派 2a/2b 与变体构造入口并上 by_entry 判据

- TypeGenResolve: 泛型下标 Phase B 解析不出特化时 fail-fast

- CallGenProp: 泛型特化名末段按括号外点切分

- CompilerFactory/ClassGenBase: REnum 注册表改挂编译期单例

- ClassGenDef: CEnum/REnum 分派歧义 fail-fast
2026-09-12 23:14:23 +08:00
TermiNexus e0e024ee93 fix(tpv): 注解裸名按 from-import 归属查找(D26 闭环)
stub 注入注解时 trans.ModuleSha1 是正在翻译的文件,而注解里的类归属 stub 源模块;仅靠当前模块 sha1 加全局可见性在 App 严格模式下查不到(AsyncGenMixin 未 import ast.Expression),导致 ast/__init__.vpi 的 parse_expression -> rowptr[Expression] 返回类型解析失败 fail-fast。TypeGenResolve 的 Name 分支在 own 之后、可见性之前插入 from_imports 归属查找(LookupModNameByAlias 到 LookupSha1ByModName 到 find_struct_by_module)。TPV 自举 Phase1 从 28/53 推进到 40/53
2026-09-12 19:37:55 +08:00
TermiNexus ea2abba574 refactor(tpv): 删除 _deps_pending 置位面与 Phase A pending 多遍收敛机制
69 个工程成功编译且 _phaseA_pending.log 恒 0 字节,证实 14 个置位点全部零命中。删除 14 个 _MarkDepsPending 调用点(原「依赖未就绪」分支保留默认类型或原报错路径);删除 _MarkDepsPending 与 _AppendPhaseAPending 及 _phaseA_pending.log 读写;删除 Phase A 出口「pending 则不写盘、回滚 MethodsEmitted、continue」整段;删除 _deps_pending 与 _pf_dbg_pend_flagged 字段及全部读取分支;清理遗留探针 [PF-DBG] 与 [METHDBG]。16 files changed, +37 / -558
2026-09-12 19:24:52 +08:00
TermiNexus 17f109e204 fix(tpv): 修复 vpi 重导出包前缀与 D25 变量名悬垂,补齐 fatal_error 刷盘
VpiReader 包 __init__ 重导出补包前缀,类注册与方法声明两遍化,按需注入依赖 vpi(先登记后注入防环,失败撤销登记);VarGenMixin 的 add_var 深拷贝变量名,符号表条目不再依赖 AST tree 生命周期;CallGenCall 的 list 内建与泛型基类判定类名来源回退到接收者实际 struct 名;TypeGenErr 的 fatal_error 退出前 fflush;StubMergerBuild 每模块重置 vpi 注入去重表
2026-09-12 19:00:19 +08:00
TermiNexus 626f79cbd1 fix(tpv): vpi 相对导入补回包前缀(D26)
- vpi 只存 module=base(前导点记在 level),from-import 映射写字面 base 时,解析端按精确 LookupSha1ByModName 查不到 ast.base ⇒ 裸类名(AST 等)解析失败 ⇒ rowptr[AST] 等注解 fail-fast(TPV→TPV 自举 includes/ast/parser.vp parse 根因)

- 按 stub 自身相对路径还原包名(ast/parser.vp → ast),level==1 拼成 ast.base 再登记;_stub_add_imported 与 _stub_add_from 两处同步
2026-09-12 15:59:37 +08:00
TermiNexus 3c45895878 fix(tpv): alloca 退化守卫收窄到「裸泛型模板基类」形态(避免误杀同名重声明换类型)
- parser.vp:964 'node: rowptr[AST] = Slice(...)' 命中上一版守卫(旧 alloca 是 Tuple*)——但 Tuple* vs AST* 是同名重声明换具体类,两者都是 8 字节指针槽,属合法用法,不该 fatal

- 收窄判据:仅当旧 alloca 的 pointee 是**裸泛型模板基类**(剥 sha1 前缀后不含 '[' 且 _find_generic_template 命中)时才 fail-fast —— 这正是 D25 的退化形态(丢类型实参 → 方法调用按错误元素类型特化 → 截断)
2026-09-12 15:24:23 +08:00
TermiNexus 590a6a2bbe fix(tpv): 复用的 alloca 与注解类型不一致时 fail-fast(D25 alloca 退化根治)
- get_or_create_sym 只用「名字+作用域」复用(ty 不参与判定),可能命中类型不同的旧 alloca(同名重声明 / 退化 alloca,如裸 list* 对上 list[ast.ASTptr]*)→ alloca 类型与使用点注解类型分裂 → 方法调用按错误元素类型特化 → inttoptr 截断

- 只比较「双方都是指针且 pointee 都是结构体」这一最典型退化形态:结构体名不同即 fatal_error,报出变量名与两个类型,直接暴露退化点(不兜底)
2026-09-12 15:13:41 +08:00
TermiNexus b206e7d9c1 fix(tpv): 泛型类型实参禁止静默默认 int(D25 fail-fast 两处)
- CallGenMixin: 裸名泛型构造推断不出类型实参时不再默认 int,改 fatal(与 _infer_generic_ctor_type_arg 注释「绝不默认 int」对齐)

- CallGenCall._translate_method_call: 泛型模板基类(剥 sha1 前缀后不含 '[')上的非 list 内建方法调用改 fatal —— 此前静默按默认 list[int] 特化,元素按 i32 处理 → 方法返回 i32 → inttoptr 截断指针 → 读垃圾 AST 节点(D21/D25 崩溃族)

- list 内建方法仍走内联路径(运行时 __elem_size__),不受影响
2026-09-12 14:52:23 +08:00
TermiNexus ced50bd78b fix(tpv): 注解 mangled 名算键统一——补回丢失的 from_imports(D25 根因第 1 步)
- extract_class_name_from_annotation 的 Subscript 分支递归取 base 时漏传 from_imports(同函数其余调用点都传了),导致同一注解在「派发名 / 查表键 / 注册键」三处算出不同 mangled 名

- 症状:MatchGenMixin._handle_renum_match 两遍对同一裸 list* alloca 分别按 list[ast.ASTptr] 与 list[ast.ClassDefptr] 解释 → .get 误分派 → 节点指针被截断 → StmtGenMixin._translate_stmt_impl ASan 崩溃(D21 复发,见 Debugger/_Type.md D25)
2026-09-12 14:32:57 +08:00
TermiNexus 967095df5b refactor(tpv): _deps_pending 置位收敛到唯一入口 _MarkDepsPending(S4 第一步)
- CallGenBase 新增 _MarkDepsPending(trans,kind,cls,method,dep,extra):一处实现「置标志 + 统一格式 [DEPDBG] + 持久化 _phaseA_pending.log」三连

- 14 处置位点(D7-1 清单:attr_read/attr_write/method_ret/mro_ret/mro_anc/special_ret/prop_ret/dispatch_ret/dispatch_sp/func_ret/func_mangled/global_stub/class_parent/class_field_ty)全部改经该入口

- 行为不变;DEPDBG 行格式统一为 pending(<kind>): mod=.. cls=.. method=.. dep=.. extra=..,供下一步按 kind 统计命中数、删除死点
2026-09-12 13:53:20 +08:00
TermiNexus 7e68e76ec4 refactor(tpv): SHA1 前缀判定收敛到 GenApi 唯一真源(C10/D3 收尾)
- CallGenCall: list 泛型前缀剥离(719) 与类型名提 sha1(907/1190) 改 GenApi.sha1_prefix_len,不再盲取前 16 字节当 sha1

- CallGenCBuiltin._asm_name_is_mangled / CallGenLib._is_sha1_mangled_call: 改真源(去大写 A-F、去扫描任意位置 '.')

- CallGenFd: 类型名提 sha1 加前缀格式校验(裸名不再误取前 16 字节)

- CallGenMixin: CExport declare 前缀判定改真源

- StructGenMixin / TypeGenResolve: 类型名、模块段判定改真源;StructGenMixin 补漏掉的 GenApi import(L596/L943 引用原会 fail-fast)
2026-09-12 12:59:01 +08:00
TermiNexus 471bedbcf4 fix(tpv): 去掉条目指针数组往返,改上界/量长直写(修自举 ASan strcat -1 崩溃) 2026-09-12 12:43:08 +08:00
TermiNexus 82eea2719f refactor(tpv): rowptr[T] 迁移 + D 表定长缓冲改精确分配 + SHA1 前缀判定收敛(D1/D3/D4/D5/D6/D7/C10) 2026-09-12 11:09:35 +08:00
TermiNexus 7471cc64f9 docs(tpv): 精简 CompilerFactory 装配设计注释块 2026-09-12 10:52:00 +08:00
TermiNexus 679758c05b refactor(tpv): 删无符号判定的旧字符串兜底,真源唯一(S4 去字符串化收尾)
删 _type_name_unsigned_legacy / _annot_str_unsigned_scan / _annot_str_unsigned_legacy / _last_dot_tail / _is_simple_ident;_type_name_unsigned 与 _annot_str_unsigned 变为真源唯一(spec_sign_of_annot)。

依据:真源已含顶层 | 分段、去括号/空白、IDENT[INNER] 取元素、去模块前缀、精确名白名单、CTypedef 链展开,旧路径能力是它的真子集(S2 已把旧路径修到与真源一致),差异面就是假阳性区 —— strstr 子串扫描正是 A4/A5 根因(BYTEPTR 含 BYTE、任何含 uint32 的 mangled 名都被判无符号)。现语义:真源 UNKNOWN=不表态 => 返回 0;若日后发现漏判,补白名单/补 CTypedef,不恢复子串扫描。
2026-09-12 10:26:51 +08:00
TermiNexus cc17a8f8db refactor(tpv): 去掉函数内懒导入,改用顶层 import
Viper 的 import 是编译期的,懒导入在编译型语义下毫无作用,模块环不需要也不应该用它规避。

TypeSpec 顶层 import GenApi(GenApi→GenTypes→TypeSpec 成环但编译期无碍);CallGenLib 的既有懒导入(VpiReader)一并改顶层(VpiReader 不反向依赖 Call 层,无环)。
2026-09-12 10:23:08 +08:00
TermiNexus 7fbf69ae63 fix(tpv): InternSpec 表满改为 fatal(不再返回 None 静默丢类型信息)
表容量超限原实现只是 return None,但注释声称 fail-fast:None 会沿调用链传到 entry.Spec,条目静默失去类型信息(_entry_spec_unsigned 恒返回 0 ⇒ zext/sext 方向取错),属静默数据损坏。

改为 GenApi.fatal_error(InternalCompilerError);TypeSpec 与 GenApi 互为依赖(GenApi→GenTypes→TypeSpec),故函数内懒 import 破环(同 CallGenLib.vp:220 既有模式)。仅当 fatal_error 无 gen 通道时退化为不驻留返回完整实例,守住不返回 None 的底线。SPEC_INTERN_MAX 仍为 8192(当前未触发)。
2026-09-12 10:21:27 +08:00
TermiNexus 1039969f69 refactor(tpv): CPtr 标记语义定为注解基名精确比较(第 5 处收敛)
CallGenMixin 闭包 vs 函数指针分派:原 strstr(注解名, CPtr) 子串匹配改为 SpecTypeNameBaseIs(fp_annot, CPtr)。

语义:CPtr 标记 = 注解基名精确等于 CPtr(裸 t.CPtr / from t import CPtr)。不能改用 Spec 判定——t.Callable[[A], R] 解析结果同样是 Ptr(i8)(TypeGenResolve.vp:2525-2531),与 t.CPtr 完全同型,类型层无区分度,故标记只能由注解名承载,不新增 Spec 字段。副作用:含 CPtr 子串的类名(如 CPtrHolder)不再被误判为闭包指针。已知局限(与改造前一致):Foo | t.CPtr 的 SourceName 只留 Foo,该形态判为非标记。
2026-09-12 10:12:01 +08:00
TermiNexus ae63f4f6ab refactor(tpv): 容器判断改基名精确比较(SpecTypeNameBaseIs)
TypeSpec 新增 SpecTypeNameBaseIs(name, base):基名 = 截断第一个 '[' 起的泛型实参后,取最后一个 '.' 之后(去模块名 / LLVM 注册名的模块 sha1 前缀),再精确比较;零分配,复用 _spec_name_eq。

4 处容器判断改写:StmtGenCifMixin:1133/1142/1498(读注解名)、CallGenDispatch:1685(读 LLVM 注册名)。原 strstr 子串匹配会误命中 MyDictHelper / playlist;副作用是 GSList 等用户类不再被误判为内置 list,改由 OOP __iter__/__next__ 路径处理。第 5 处 CallGenMixin:973 的 CPtr 属标记判断(SourceName 会丢掉联合里的标记),需先定标记语义,本次不动。
2026-09-12 10:10:14 +08:00
TermiNexus 19fca19ca7 refactor(tpv): get_llvm_type_bits 双副本收敛到 TSpec.SpecTypeBits
TypeSpec 新增 SpecTypeBits 作为「LLVMType → 整数位宽」块的唯一实现(对齐 SpecTypePtrLevel 的收拢先例);ExprGenBase/TypeGenErr 两份同码副本改为委托,TypeGenErr 补 TypeSpec 导入(无环)。

保留方法名与 self. 调用点:@t.NoVTable 静态绑定下 Mixin 内部大量 self.get_llvm_type_bits 依赖其存在,只换实现体不动签名。副本分叉会导致同一类型两处算出不同位宽的隐蔽不一致。
2026-09-11 23:14:51 +08:00
TermiNexus 41c5908c5e refactor(tpv): Y 类兜底改 fail-fast(vpi 声明返回/形参 + vpi 类型文本)
新增 GenApi.fatal_error:VpiReader/Converter 等模块级函数无 self.Gen,统一经此报错通道;GenApi._g_gen 为空时回退 Generator._g_gen(Phase 1 vpi 生成早于 BuildPipeline 注入 GenApi._g_gen)。

VpiReader._inject_function_declare/_inject_method_declare 共 4 处:有注解却解析不出类型 → fatal;无注解的 i32 返回/i8* 形参合法默认保留。Converter._PyiTypeStr 未知节点类型不再静默写 int。
2026-09-11 22:46:39 +08:00
TermiNexus db8220801c refactor(tpv): Z 类兜底改 fail-fast(返回类型/模块级变量/枚举成员)
- ClassGenVTable 方法返回:有注解但解析失败即 fatal;State/CExport/CExtern 标记归一为 Void
- TranslatorGenMixin 模块级变量:有注解但解析失败即 fatal(保留无注解合法默认)
- ClassGenEnum._resolve_enum_member_type 4 处:Attribute 缺失名/Name 缺失 id/两处解析失败均改 fatal
  (无注解与 t.State 标记的 i32 默认保留)
2026-09-11 22:17:45 +08:00
TermiNexus cc3359faea refactor(tpv): 形参类型注解解析失败改 fail-fast(不再静默用 i32)
- FuncGenDef 前向声明形参 / 定义签名形参:resolved is None 即 fatal
- FuncGenBase create_function 形参:同上(暴露调用方未传 trans 上下文)
- ClassGenVTable _translate_method 形参:同上(签名/调用点/实现体三方一致)
- TypeGenCDef 整数 CDefine 值缓冲分配失败改 fatal(对齐同文件浮点/字符串分支)
- FuncGenMixin._type_byte_size 类型缺失改 fatal(case _ 保留 8,注释说明无布局查询能力)
2026-09-11 22:14:10 +08:00
TermiNexus 22c71388d4 docs(tpv): 更新 TypeSpec 注释(IsPtrElement 已删,仅容器种类待后续) 2026-09-11 22:09:32 +08:00
TermiNexus a4bc5dc0ce refactor(tpv): 删除 IsPtrElement 字段,指针元素语义承载于 Spec.ElemBits
- TypeSpec 抽 _spec_clone(消除 SpecWithSign/Source/ElemBits 三处重复逐字段拷贝)
- 新增 SpecWithElemBits:覆盖元素槽位宽(独立副本不驻留,ElemBits 不在驻留键内)
- set_var_ptr_element 改写 Spec.ElemBits=64;Spec 不可承载(非指针/缺失)即 fail-fast
- 新增 var_is_ptr_element(PtrLevel>=1 且 Elem.Bits==8 且 ElemBits==64,排除真实 i64* 与纯 str)
- ExprGenName 4 处下标读写切读该入口;GenTypes 删 VarEntry.IsPtrElement
2026-09-11 22:09:18 +08:00
TermiNexus e55ec5fde0 docs(tpv): 更新 TypeSpec 设计注释至 S4 现状(TypeStr/AnnotClassName 已删) 2026-09-11 22:06:20 +08:00
TermiNexus 9e461d21cb refactor(tpv): 删除 AnnotClassName 字段,注解类名统一承载于 Spec.SourceName
- GenTypes 删 VarEntry.AnnotClassName / FieldEntry.AnnotClassName
- set_var_annot_class_name / add_field 改为写入 Spec(Spec 为 None 时建 TS_UNKNOWN 承载,
  不表态具体类型,等价原 Spec 缺失);删 1b 的 AnnotClassName 专属深拷贝
- var_annot_class_name / get_field_annot_class_name 去旧字段回退分支
- 剩余 7 处直读(StmtGenCifMixin list/dict 5 处、CallGenMixin CPtr 2 处)改走封装
2026-09-11 22:06:00 +08:00
TermiNexus 95133d8483 refactor(tpv): 变量/字段注解类名读取统一走 Spec.SourceName 封装
- VarGenMixin 新增 var_annot_class_name(SourceName 优先,回退 AnnotClassName)
- 变量侧类名恢复与诊断读取切读:ExprGenAttr(5) / ExprGenOps(6) / ExprGenName(1)
  / CallGenDispatch(3) / CallGenCall / CallGenProp / CallGenVt / ExprGenCls / AssignGenMixin / MatchGenMixin
- ExprGenAttr 字段侧直读改走 get_field_annot_class_name
2026-09-11 22:02:28 +08:00
TermiNexus 8aad248c25 fix(tpv): SpecWithSign 不复制 SourceName,避免驻留表来源名污染
InternSpec 键不含 SourceName,若带来源名的副本入表,后续同键查找会把该来源名
污染给无关条目(键相同必须语义相同 ⇒ 表内实例 SourceName 恒为 None)
2026-09-11 21:54:29 +08:00
TermiNexus 029a423f55 feat(tpv): Spec 增 SourceName 承接注解来源名(类名块基础设施)
- TypeSpec 新增 SourceName 字段:注解书写形态名(裸名/模块限定名/t.XXX attr/mangled),
  与 BaseName(LLVM 注册名)命名域不同;不参与驻留键,避免条目暴增与线性查找变慢
- 新增 SpecWithSource(独立副本、不驻留、幂等)/ SpecSourceName 读取入口 / _spec_set_source_name
- set_var_annot_class_name 与 add_field 注解侧填 SourceName
- get_field_annot_class_name 切读 SourceName 优先(3 个调用点自动生效)
2026-09-11 21:54:10 +08:00
TermiNexus 7028399366 refactor(tpv): 无符号判定切读 Spec(条目级 TS_UINT 优先)
- 新增 _entry_spec_unsigned:Spec.Kind==TS_UINT 为注解明确无符号的强信号
- _node_type_unsigned 的 Name/Subscript 分支、Attribute 字段分支切读 Spec 优先
- 只切无符号方向(TS_INT 含未表态,不可反推 SIGNED,仍走字符串真源)
2026-09-11 21:51:11 +08:00
TermiNexus 7fedf8daad feat(tpv): Spec 注解侧填符号性(SpecWithSign + 变量/字段接入)
- TypeSpec 新增 SpecWithSign:整数 TS_INT↔TS_UINT 切换 + 驻留(SpecToText 键区分符号)
- set_var_annot_class_name 接入:注解类名提取符号性,补 Spec 无符号性
- add_field 接入:字段注解类名提取符号性,补 Spec 无符号性
2026-09-11 21:48:23 +08:00
TermiNexus 0a108e6289 refactor(tpv): 删除 FuncEntry.TypeStr 旧路径,函数类型文本改由 Spec 派生
- 删 FuncEntry.TypeStr 字段、_build_func_type_str 生产者、_check_func_spec_text 闸门
- update_global_func_typestr 改名 update_global_func_spec,仅填 Spec(去 pool 参数)
- ClassGenVTable vtable 合并 func_valid 分支,统一 SpecToLLVMText 派生
- 同步 FuncGenDef/ClassGenVTable/GenApi/VpiReader 调用点
2026-09-11 21:33:58 +08:00
TermiNexus a173ba0296 refactor(tpv): ClassGenVTable vtable 类型文本切到 SpecToLLVMText
- 删除 _format_func_type_str(512 缓冲 + >100 参截断的 TypePrint 重复实现)
- _generate_vtable 当前类方法分支改为 SpecToLLVMText 派生,回退全局表 TypeStr
- Spec/TypeStr 皆失败时 fail-fast
2026-09-11 21:21:01 +08:00
TermiNexus 0e973d5f23 feat(tpv): SpecToLLVMText 格式化器与函数签名一致性闸门,并修驻留键静默截断
- SpecToLLVMText/_spec_llvm_text/_spec_llvm_append 逐分支对齐 llvmlite.TypePrint
- CallGenLeafKit.update_global_func_typestr 增 _check_func_spec_text 闸门,不一致即 fatal
- _spec_text_append 溢出返回哨兵;_spec_to_text 各结构分支检查哨兵
- SpecBaseText 前缀先算长度;InternSpec 键装不下返回完整实例不写截断键
2026-09-11 21:13:28 +08:00
TermiNexus ff7caa57e8 feat(tpv): S3 条目双写接线,三个唯一创建点填 TypeSpec
- GenTypes: VarEntry / FieldEntry / FuncEntry 各加 Spec 字段(FuncEntry 合并为单个整签名 Spec,替代设计 D4 的 RetSpec+ParamSpecs,理由见注释)
- VarGenMixin: add_var 新增 _spec_of_var_alloca,用 alloca.Ty 的 pointee 构造 Spec(is_ptr_type 判定)
- StructGenMixin: add_field 统一在两条写入分支之后填 Spec(PermPool 驻留故无需 DeepCopyType)
- CallGenLeafKit: _register_global_func / add_func_to_table 初始化为 None;update_global_func_typestr 新增 _build_func_spec 填整签名 Spec(用 function_get_param_head 两遍遍历,变参取 func.IsVarArg,避免 SpecFromLLVMType 的 Func 分支丢变参)
- 注: 当前尚无读取方;驻留表溢出会导致该条目 Spec=None(已记录,S4 改 fatal)
2026-09-11 20:43:17 +08:00
TermiNexus f0deff931d refactor(tpv): S3 无符号判定切读真源,指针层级收拢到 TypeSpec
- ExprGenOps: _type_name_unsigned / _annot_str_unsigned 改为真源优先(UNSIGNED->1 / SIGNED->0),仅真源不表态 UNKNOWN 时按需回退旧子串路径(热路径避免双份开销)
- ExprGenOps: 删除 S2 的双写一致性闸门 _spec_sign_check(切读后读即取真源,比对恒等、无意义)
- TypeSpec: 新增 SpecTypePtrLevel -- 指针层级推导的唯一实现,原 get_ptr_level 记录的 match codegen 缺陷规避写法随实现移入
- ExprGenBase: get_ptr_level 删去同码副本,改为委托 TSpec.SpecTypePtrLevel(行为等价)
- TypeGenCType: spec_sign_of_annot 注释更新为无符号判定的读入口
- 注: 值级切读(读 Spec.PtrLevel)仍需条目双写(VarEntry.Spec / FieldEntry.Spec 等)接线,S2 路线 C 未做,留待下一步
2026-09-11 20:40:26 +08:00
TermiNexus b7100e9dc1 feat(tpv): S2 落无符号判定双写闸门,并修 i8 拓宽丢符号与 WCHARPTR 旧判
- TypeSpec: 新增 SpecFromLLVMType(LLVM 类型 -> TypeSpec 结构转换器,当前无调用点);SpecSignFromTypeName 拆出零分配切片版 Span
- TypeGenCType: 新增 lookup_ctypedef_span 与独立符号性真源 spec_sign_of_annot(精确名白名单 + CTypedef 链逐层展开)
- ExprGenOps: 旧判定改名 *_legacy,外层加双写闸门 _spec_sign_check(新真源明确表态且不一致即 fatal;S2 不切读)
- ExprGenOps: 修旧符号性两个洞 -- 子串表补 WORD、裸别名沿 CTypedef 链展开(闸门实测暴露 WCHARPTR 旧判 signed、新真源 unsigned)
- ExprGenOps: should_zext_widen 的 i8 分支补「源注解明确有符号则 sext」例外,修 b8470c1 引入的 TypeCastTest 回归(t.CInt32T(v) 丢符号);新增 _node_type_signed
- TypeGenBase: 补 import lib.core.Gen.Type.TypeSpec(此前遗漏)并接入驻留表 ensure_spec_arena(PermPool 惰性创建)
- 修两条链接错误: TypeSpec.vp 残留 stdlib.atoi 改 string.atoi;模块别名与模块内类名同名会被误解析为该类静态方法(隐式补 self 实参),四处导入别名统一改 TSpec
2026-09-11 20:32:55 +08:00
TermiNexus b1090abe70 feat(tpv): 新增 TypeSpec 统一类型描述符(S1,未接线)
类型信息目前散落在 AnnotClassName/IsPtrElement/TypeStr 等字符串与布尔里,
导致「无符号/指针层级/数组维度/函数签名」到处重复解析且结论不一致
(Debugger/_Type.md 二次盘点 A/C 类)。

新增 App/lib/core/Gen/Type/TypeSpec.vp 作为统一描述符:
- TypeSpec:Kind/Bits/IsSigned/PtrLevel/Elem/ArrayCount/BaseName/Ret/Params/
  ParamCount/IsVarArg/Storage,以及元素语义三元组 ElemBits/IsElemPtr/
  DerefBase(替代单一 IsPtrElement 布尔——同一 Ptr(i8) 下存在两种相反语义)
- TypeSpecArena 驻留表(对象持有,非全局表)+ InternSpec 去重(键=规范文本)
- SpecToText/TextToSpec 规范文本编解码(驻留键/日志/vpi 通道)
- 构造器 NewTypeSpec/NewTypeStruct/NewTypePtr/NewTypeArray/NewTypeFunc
- 访问器 SpecIsPtr/SpecPointedTo/SpecIsUnsigned/SpecBitWidth
- SpecSelfCheck 自检(不参与编译流程)
- 实例与数组一律 PermPool 分配(跨 mark 持久,跨模块可直接传指针)

GenTypes 仅加 import 使该模块参与编译(否则新文件不会被编译、无法验证);
未改动任何既有调用点,产物 IR 应与上一版一致。
2026-09-11 20:02:52 +08:00
TermiNexus b8470c1cae fix(tpv): 拓宽方向统一为单一真源,并修 TypeStr 变参与 should_zext_widen 缺参
三项独立缺陷(均来自类型系统盘点 _Type.md D7):

1) should_zext_widen 签名缺 trans 形参,函数体内的 trans 退化为隐式全局(恒
   None),而 translate_binop 两处调用早已在传第 5 实参(被静默丢弃)⇒ 注释所
   述「源无符号 → zext」补救从未生效:Name/Subscript 形态的无符号源(uint16/
   uint32 变量、数组元素)在比较/二元路径恒被 sext 扩展(ide.vp/timer.vp 根因)。
   修:should_zext_widen 补 trans 形参并明确为拓宽方向的单一真源;
   translate_compare 两处补传 trans;ExprGenBase._zext_or_sext_val 改为委托
   should_zext_widen(wide_val=None 使「宽侧常量」启发式自然失效)。

2) _translate_t_type_cast 原以「目标类型名符号性」决策整数拓宽,且 target_name
   未知时默认 zext,与 coerce_to_type / should_zext_widen 的默认 sext 极性相反。
   修:统一由源的符号性决定(与 coerce 同源),并删除已无使用点的 target_name
   形参(1 处签名 + 7 处调用点)。

3) TypeStr 生成(FuncEntry.TypeStr 的唯一生产者)与其现算现用副本
   (ClassGenVTable._format_func_type_str)都未打印变参后缀 ...,而 FuncGenDef
   对 *args 会设 IsVarArg=1,且继承方法强制走 TypeStr 回退 ⇒ vtable 中的函数
   指针类型与真实签名不符。修:按 llvmlite IR 打印规则补齐(有参 ", ...",
   无参 "...")。
2026-09-11 19:57:18 +08:00
TermiNexus c71642dca0 清理一部分异 2026-09-11 18:55:33 +08:00
TermiNexus cfb92e5da5 初步清理 t. 类型异味 2026-09-11 18:36:32 +08:00
TermiNexus 4ce55e3ea3 fix(tpv): 泛型注解预特化下沉到 AnnAssign Handle 路径,修 .get() 误分派崩溃
translate_annassign 解析注解前未先注册泛型特化,`list[ast.AST | t.CPtr]
| t.CPtr` 会退化为裸 list*,随后 body_list.get(i) 被分派到默认
list[int].get(返回 i32)→ inttoptr 截断指针 → 读垃圾 AST 节点,
StmtGenMixin._translate_stmt_impl 崩溃(寄存器指纹与既有潜伏崩溃一致,
IR 实证见 Debugger/ViperOS3.md 第十九节)。

抽出 _prespecialize_generic_annotation 供 pre_scan_allocas 与
translate_annassign 共用(该补救原先只存在于预扫描路径),使两处解析一致。

同批(P1-A):赋值/返回路径把目标类型作为 target_ty 传入 translate_value,
使 `x: uint64 = 12345` 与 `u64(12345)` 物化出同一条 i64 常量、省去 coerce
的 zext。站点:translate_annassign(global/nonlocal/模块级/局部四处)、
translate_assign(单 Name 目标)、translate_return。target_ty 仅被
Constant 节点消费,不影响调用/二元/属性路径。
2026-09-11 18:10:19 +08:00
TermiNexus d3690f9143 fix(tpv): 依赖注入期默认参数按定义模块 SHA1 登记,消除跨模块同名函数串味
N17:VpiReader 注入函数/方法 declare 时显式传入 src_sha1(被依赖模块自身
sha1),add_func_to_table 透传至 _register_global_defaults;调用点默认值查找
改用 find_func_entry_in_table_owned 按 "{owner_sha1}." 前缀限定。修复
usb.uhci.init 误命中 mm.init 的 5 形参/2 默认值而凭空补 2 个 i32 0 的 ABI
错位(IR 实证:call @...init(i16 %4, i32 0, i32 0) → (i16 %4))。

同轮并入的其他编译器侧改动:
- N11 Converter.vp:无 -> 但带 @c.CReturn 时合成 tuple 返回注解,与 define 对齐
- N12 VpiReader.vp:union 指针(T | t.CPtr)stub 解析与定义侧收敛到 i8*
- N14 ExprGenFstr.vp:f-string 32 位整型不再写 l 修饰符
- N2 撤回 ExprGenOps.vp:删除在 _node_type_unsigned 热点查/填 vpi 缓存的
  _call_ret_annot_unsigned(有副作用,触发 fat32_diskio.vp match 路径崩溃)
2026-09-11 17:39:21 +08:00
TermiNexus e66d9e3adb fix(tpv): 修 N 系列 codegen 缺陷(常量位宽/无符号判定/实参扩宽/结构体取址)
- N1 ExprGenConst/CallGenDispatch: CONST_INT 按 target_ty 位宽物化, 修 uint64(0xFFFFFFFFFFFFFFFF) 被降 i32 再 zext

- N2 ExprGenOps: 新增 _call_ret_annot_unsigned 查被调方 vpi 返回注解, 修 uint32 返回值 >> 生成 ashr

- N3 TypeGenResolve: 联合注解提取跳过存储类限定宏, 修模块级全局 uint32 比较生成 sge/slt

- N5 CallGenLib: 常量实参按形参扩宽 + _coerce_args_to_def_params 支持 3 段方法调用

- N17 CallGenLeafKit/CallGenMixin: 默认值表按定义模块 sha1 限定 + 收紧交叉校验守卫

- N18 ExprGenBase/CallGenLib: rvalue 结构体实参落栈取址
2026-09-11 15:50:09 +08:00
TermiNexus 751fbccb29 feat(tpv): vtable 对象全局 storage 也按字段默认值折叠
补 f5ee178 未覆盖的 vtable 分支:抽出 _build_stg_struct_body_text,_build_stg_struct_init_text 改为其包装;vtable 分支以 start_idx=1 复用默认值折叠,字段默认值全零时回退原零值循环(元素个数语义不变)。
2026-09-11 15:29:43 +08:00
TermiNexus f5ee178407 feat(tpv): 模块级对象全局按字段默认值生成 storage 初始化器
- 新增 _default_val_const_text:字段默认值 AST -> 常量文本(仅整型/字符/负号字面量可折叠,其余零值兜底)

- 新增 _build_stg_struct_init_text:按 StructEntry 字段默认值生成完整 struct 常量初始化器(LLVM 要求列全字段);无非零默认值返回 None

- storage 分支新增 elif:无 vtable 对象全局走默认值初始化器(vtable 路径不变)

- 效果:fat32.default_fs 的 .stg 由 zeroinitializer 变为含 sector_size=512/root_cluster=2/free_clusters=0xFFFFFFFF/backup_sector=6/winsect=-1 的完整常量;drive_lock 等零值等价场景保持 zeroinitializer
2026-09-11 14:52:29 +08:00
TermiNexus e20a6f3d11 fix(tpv): 修 VKernel 暴露的 codegen 缺陷(浮点 CDefine/__sizeof__ packed/CONST_STR/alloca/无符号/ABI)
- 浮点 t.CDefine 初值序列化与源恢复(gfx PI 编成 0.0)

- __sizeof__ 按 packed 计算(cpu _tss memset 越界写 4B)

- 数组初始化器支持 CONST_STR 单字符;模块级别名不再产出 global i32 0 假全局

- 临时缓冲/构造临时量 alloca 提升至入口块(B6 未覆盖路径)

- 无符号判定补 CSizeT/小写别名/结构体字段 + udiv/urem + range 位宽与符号 + zext 扩展

- 跨模块 declare 形参按 vpi 定义侧收敛(修 _vpi_method_param_annot 拒绝 cls_name=None 导致 coerce 恒失效)
2026-09-11 14:26:54 +08:00
TermiNexus bd46b47b22 docs(readme): 按事实重写 README, 反映自举已完成与真实源码结构
- 自举状态: 重写中 -> 已完成自主编译, TransPyC 明确为自举种子
- 目录结构: 全文 .py 名称纠正为真实 .vp 布局 (Phase/ Stub/ core/Gen/ 树, Gen 52 文件)
- 工程配置: project.json -> project.vpj; 产物路径 output/ -> .tpv_build/output/
- CLI 表补齐 --asan/--phase2command/--nowarn-register/--no-llvmpeephole/--detection/--show-args/--build-dir/--version
- 内存模型: 补常驻池 512 MiB 与 CompilerFactory 单例, 说明自举后 _mbuddy 需逐模块注入
- 规模数据改为实测值 (App 76 文件 64656 行 / Gen 52 文件 / Test 39 用例)
- 修复失效链接 wiki/01-overview.md
2026-09-10 22:33:23 +08:00
TermiNexus a5260f4441 fix(tpv): ASan 运行库改由 project.vpj 声明; read_all 容量语义修正与超限源文件 fail-fast
- --asan: 移除 BuildPipeline 中硬编码的 clang64 libclang_rt 注入, 改由工程在 linker.static_libs / dynamic_libs / flags 中自行声明; 新增 Config.asan_runtime_declared(), main.vp 在未声明时 fail-fast 并列出所需运行库, 不再给含混的 undefined reference
- read_all: 容量参数含终止符(最多读 cap-1 字节), 故删除 12 处恒不可达的 else 截断兜底; 并在读取前按 f.size() fail-fast 拒绝超限源文件(main.vp / BuildPipeline / Phase1Main / Phase1Scan / Phase2), 避免静默截断后报出莫名其妙的语法错误
- project.vpj(根 / Test / NegativeTest / Sha1Test): linker.cmd 由裸 clang++ 改为绝对路径并补 -fuse-ld=lld
2026-09-10 22:33:18 +08:00
TermiNexus 76f03b5987 fix(tpv): 修模块级浮点全局零初始化、跨模块 CDefine 重导出、默认参数快照、typedef 诊断悬垂指针
- TranslatorGenMixin/AssignGenMixin: 模块级全局补 CONST_FLOAT 初始化分支。
  此前无此分支 → init_kind 保持 0 → t.CDouble 全局一律退化成 zeroinitializer
  (torch 的 LCG_A/LCG_M/_rng_state 全变 0 → _next_rand() 得 0.0/0.0 = NaN
  → Kaiming 初始化写入全 NaN 权重 → 推理 softmax 分母 NaN → 十个概率全为 0)。
  浮点值不走 AST 的 float_val(会被 pool 操作覆写),改为按变量名从当前源文件
  重读恢复;_recover_cdefine_float 泛化为 _recover_float_literal。
- ExprGenCdef: 跨模块 CDefine 重导出链支持绝对导入(from pkg.sub import *),
  此前只认相对 from .X → vqt6 包(__init__.vp 用 from vqt6._types import *)
  里的 CDefine 全查不到,报 "跨模块 CDefine 未找到: 'vqt6.QT_ALIGN_CENTER'";
  相对导入基准改用"当前模块所属包"而非模块名本身,修正子模块 from .X 拼接
  (对齐 CallGenBase._lookup_reexport_from_vpi 的包名口径)。
- CallGenLeafKit: 默认参数常量快照支持一元字面量(USub/UAdd/Invert 且操作数为
  整型 Constant)。此前只认 Constant/Name → ex3: t.CInt = -1 这类默认值快照为空,
  跨模块重建时报 "跨模块默认参数无法重建(快照缺失)"。其余形态仍保持 kinds=0,
  重建时 fail-fast,不静默兜底。
- TypeGenCType/TypeGenResolve/TypeGenBase: builtins typedef 诊断串改为自有缓冲
  副本(stdlib.malloc,clear_ctypedefs 释放)。调用方常传临时段缓冲,直接存指针
  会悬垂,_build_ann_fail_msg 的 snprintf 读已释放内存(ASan heap-use-after-free)。
2026-09-10 14:22:02 +08:00
TermiNexus adfd202218 feat(tpv): 支持 --no-llvmpeephole 关闭 llvmlite 本地窥孔(T2/T3)便于 IR 对比; 移除 project.json(改用 project.VPj) 2026-09-09 21:10:20 +08:00
TermiNexus 1d76280d0e perf(tpv): find_func_in_table 两遍扫描合并为单遍 2026-09-09 10:38:34 +08:00
TermiNexus bb9bb76653 fix(tpv-asm): c.Asm clobber 独立上限 ASM_MAX_CLOBBERS=32,修复 >16 项 clobber 截断致寄存器冲突崩溃 2026-09-09 10:38:34 +08:00
TermiNexus 7611d10072 fix(stubgen): CONST_STR 引号改用字符串字面量,修复 char 隐式转指针 2026-09-09 08:57:29 +08:00
TermiNexus 3e4f3b88b0 fix(tpv-class): lookup_field_by_class 识别 16hex 类键前缀为 sha1,修复 GSList[Param] 模块解析误报 2026-09-09 08:57:29 +08:00
TermiNexus 82558bd86b fix(tpv-expr): list 内置内联按元素原型类 load,支持链式 get(0).method() 分派 2026-09-08 21:30:56 +08:00
TermiNexus 709c3570fb fix(tpv-class): REnum 自引用字段注册后回填别名类型,修复 match 绑定变量退化为整数 2026-09-08 21:30:56 +08:00
TermiNexus ab814f55e6 fix(tpv-call): 裸名调用同模块函数优先(owner_sha1),修复跨模块同名 stub 抢占致 _advance 误绑 2026-09-08 20:47:54 +08:00
TermiNexus a260b9316d style(stubgen): Converter.vp 去除 .vpi 类/函数定义后多余空行 2026-09-07 22:35:21 +08:00
TermiNexus 1146719de1 fix(generic): 泛型显式实例化 Box[t.CInt](v) 实参提取 Attribute 直读字段
TypeGenResolve._get_type_name_from_annotation 的 Attribute 分支改用 at.attr/at.value 直读:ast.attribute_get_attr 等跨模块访问器在 Phase A 阶段 text.ll 未生成时为 stub 无体返回 None → tn_raw 为空 → Box[t.CInt](v) 泛型类型实参提取失败 → 构造调用 func 名 '?' → RHS None(GenericTest4 main.vp:22 根因)。与 Name 分支 L53-55 同源;同时移除 CallGenCtor 残留 [EXTRACT-DBG] 诊断日志。
2026-09-07 22:11:05 +08:00
TermiNexus 4496738a24 fix(asm,irbuf): c.Asm 输出类型收集时缓存 + 大模块 IR 缓冲 1MB→8MB
1) c.Asm 输出操作数类型改为收集时直接从 alloca 缓存(单输出 scalar、多输出按序数组 out_ptys),不再依赖从 pool AsmOperand 槽二次读回 Value.Ty.Pointee。修 atom/__atomic_test_and_set 与 vrandom rdrand*_step:此前槽读回不稳定 → ret 静默 Void → 发射带名 void asm(llc 'instructions returning void cannot have a name')或报输出操作数类型缺失。2) 超大模块 IR 截断:ast/parser.vp 的 text.ll >1MB,Phase1(PHASE1_IR_SIZE)/Phase2(PHASE_A_IR_SIZE、PBP_IR_SIZE) 写缓冲与 StubMergerBuild 读缓冲(STUB_READ_BUF_SIZE) 均 1MB 单次截断 → 合并 IR 尾部丢字(opt 'end of file in global variable name');统一提到 8MB。
2026-09-07 22:06:26 +08:00
TermiNexus bcedb15b23 fix(generic,rowptr): SlotsBugTest 编译修复——T.__sizeof__ 特化与 rowptr[sha1.Class] 注解解析
1) CallGenDispatch __sizeof__:指针包装判断(X|t.CPtr 联合与 rowptr[X])移到 dot-strip 之前。类型实参规范化名如 rowptr[7565e081d88517ac.AST] 内嵌 '.',strrchr('.') 会把括号内 .AST 剥成 'AST]',rowptr[ 前缀判断失配 → 落回把接收者 Name(T) 当实例翻译 → NameError 'T'(list[rowptr[AST]] 特化 list.__init__ 根因)。2) TypeGenResolve:a) 注解 Name 分支支持 '{16hex}.Class'(_norm_type_arg_name 产物,模块段即 SHA1)经 find_struct_by_module 直解;b) _parse_type_string 下标内层以数字开头(sha1 前缀类名)且单实参无嵌套/无逗号时手工拆 base+slice 递归解析——完整 lexer 会把 7565e081d88517ac 当数值误扫 → slice 变 Constant 占位 → 特化方法 append 参数 'item' 类型解析失败。3) CallGenMixin:泛型类模板缺失分支补内建下标 cast:(rowptr[list[int]])(x)/(t.CPtr[T])(x)/(t.CArray[i8,N])(x) 按 resolve_annotation_type 出类型后单参 _translate_t_type_cast,替代误报 '泛型类 rowptr 未找到模板'(ast/base.vp L392 根因)。
2026-09-07 19:36:57 +08:00
TermiNexus 41ed7176e9 fix(codegen): __sizeof__ 缺 rowptr[X] 指针尺寸分支,泛型特化 T.__sizeof__() 报未定义名称 'T'
list[rowptr[AST]] 特化翻译 list.__init__ 的 T.__sizeof__() 时,T 解析为新一代指针包装 rowptr[AST],尺寸表全部 miss 后落回把接收者 Name(T) 当实例翻译 → NameError: 未定义名称 'T'(SlotsBugTest ast 包 rowptr[list[rowptr[AST]]] 泛型特化根因)。在旧语法联合 X|t.CPtr → 8 分支旁补 rowptr[ 前缀 → 8(指针大小),与联合类型口径对齐。
2026-09-07 15:31:18 +08:00
TermiNexus ed373a3ef2 基线重置,修复了一些 bug 2026-09-07 14:46:48 +08:00
TermiNexus 6b368b46a7 feat(tpv): App 与 TPVAppBK 新代版本对齐(rowptr 配套实现全套)
把 TransPyV/App 覆盖为 TPVAppBK 内容(39 文件):6 参注册式 _BuildReachableSha1Set 全套(StubMergerBuild/Store/Collect/__init__/IncludesScanner/Phase1Scan 等)、CallGen 与 TypeGenResolve 等新代实现。配套根 includes 为 rowptr 工作区(另有 includes_rowptr_bk 备份)。
2026-09-05 14:13:03 +08:00
TermiNexus c35246f426 fix(phase1): 旧 5 参 def 前补全量 includes 预注册(1a-pre),修复'无法构建 SHA1 映射'
178ea40 重构把全量 includes 扫描/注册(PopulateSha1MapStore + 逐文件 import 依赖 PopulateIncludesDeps)从 Phase1Main 移除,依赖注册式 def 在 BFS 中自行登记。改用旧 5 参只读 def(调用点同步改回 5 参)后 store 无预注册 → _BuildIncludesSha1Map 返回 0 → '无法构建 SHA1 映射' 终止。

按 ab0f37e 1a-pre 模式补回:scan_includes 全量扫描 → PopulateSha1MapStore 登记 store → 逐文件 declare_only=2 解析 import 依赖(含 _inject_auto_container_visibility)→ PopulateIncludesDeps(文件级 mark/reset 防 OOM)。_BuildReachableResult 增加 reachable_set/count 过滤,result 只保留可达 includes 子集(1a/1b 只翻译可达文件)。
2026-09-05 12:56:10 +08:00
TermiNexus 50d53d3ce6 fix(link): 补 CallGenLib._promote_clib_call_args 定义;BuildPipeline PYI_BUF_SIZE 改 t.CDefine 宏
CallGenMixin.translate_call 调用 self.Gen.CallGenLib._promote_clib_call_args(B3 裸名 C 库函数第三参 i64 提升),但 CallGenLib 缺该方法 → ld.lld undefined symbol。按 TPVAppBK 旧语法移植完整方法。

PYI_BUF_SIZE 是跨模块共享裸全局(@PYI_BUF_SIZE),Phase1/__init__ 与 BuildPipeline 两模块各定义强符号 → duplicate symbol。BuildPipeline 侧改用 t.CDefine 宏(同 SRC_BUF_SIZE),不再产生全局。
2026-09-05 12:48:03 +08:00
TermiNexus 8dda9f7c2d feat(linker): TPV 支持 project.vpj linker.dynamic_libs 动态链接 .dll/.so(Config 解析+绝对路径解析+链接命令合并 static/dynamic,Viper str+= 拼接改用 snprintf) 2026-09-01 22:01:20 +08:00
TermiNexus 75db09e18a fix(call): 跨模块返回类型解析兜底修复——star import 候选验证失败时先验证原模块(stdlib.malloc extern def 不再误归 stdint);包模块 vpi 未命中时经 reexport 定位定义模块查返回注解(fltk.Fl_Input_value 返回 str 不再退化 i32) 2026-09-01 21:23:12 +08:00
TermiNexus 131f3a9a4d fix(phase1): 移除 [CYCBREAK] 临时诊断 printf 2026-09-01 21:07:24 +08:00
TermiNexus 178ea401b7 fix(phase1): 含 class 定义的 includes 模块强制翻译,修复增量编译跨模块类构造 find_struct_by_module 落空(fltk.Fl_Window 返回类型 fail-fast) 2026-09-01 21:06:31 +08:00
TermiNexus c7c37e1007 fix(call): star import 再导出改为多候选验证定义模块
- _lookup_reexport_from_vpi:from X import * 累积全部候选(空格分隔),不遇首个星行即返回

- CallGenMixin:按候选逐个验证 is_cexport_func/_is_state_func_from_vpi 确认定义模块(Fl_init_all→_cfl)

- 修复 fltk/__init__ 依次 from _types/_enums/_cfl import * 时定位到首个模块(_types)的误判
2026-09-01 20:39:19 +08:00
TermiNexus 60deee1d17 fix(call): _lookup_reexport_from_vpi 支持 star import 再导出定位
- from X import * 的 re-export(如 fltk/__init__ 再导出 _cfl 全部 extern FFI)此前无法定位定义模块

- fltk.Fl_init_all 被误加 SHA1 前缀走 mangled 精确解析 → extern def 无 define → fail-fast

- 命中后由 is_cexport_func/_is_state_func_from_vpi 二次校验裸名
2026-09-01 20:26:14 +08:00
TermiNexus 29667672e4 feat(linker): TPV 支持 project.vpj 静态链接 .a/.obj
- Config.vp 新增 LinkerStaticLibs(读取 linker.static_libs 数组并基于 project_dir 解析绝对路径)

- BuildPipeline link_obj_to_exe/link_objs_to_exe/run_pipeline 支持 static_libs(位于 .obj 之后、-l 之前)

- main.vp/Phase2.RunMultiFileProject 全链传递 linker_static_libs
2026-09-01 20:15:26 +08:00
TermiNexus 9b46e1da78 fix(compiler): for x in ptr/数组 实现解引用为空终止(指针/浮点元素支持 + 数组长度上限+NULL终止) 2026-08-30 08:37:39 +08:00
TermiNexus d14415a4e6 fix(compiler): asm 约束映射补全 8 位寄存器 clobber(AL/AH/BL/BH/CL/CH/DL/DH) 2026-08-30 08:12:44 +08:00
TermiNexus a0d6dced00 fix(compiler): va_arg 截断回目标宽度(%d 高位垃圾)+ 比较按无符号类型选 ULT/ULT/UGT/UGE 2026-08-30 07:54:24 +08:00
TermiNexus 4db68ff5c4 fix(compiler): 修复模块级全局初始化数据丢失(-1 与字符数组全 0) 2026-08-30 07:27:40 +08:00
TermiNexus 23e7f5b1fb fix(compiler): storage vtable 初始化改完整 struct 初始化器(LLVM 拒绝部分初始化)
llc 报 initializer has wrong # elements。__vtable__ 字段(索引 0)= i8* @vtable,其余用户字段按类型生成零值(Ptr→null/Int→0/Float→0.0/Array→zeroinitializer),列全 FieldCount 字段。
2026-08-29 18:46:18 +08:00
TermiNexus fa4c69f95d fix(compiler): storage 对象类全局初始化 vtable 首字段(@t.CVTable)
storage 方案 @g.stg 全零初始化 → @t.CVTable 模块级全局对象 vtable=0 → 用户程序入口虚调用 call [vtable+0x10] 跳 0x0 #PF(HelloWorld 启动即崩)。@g.stg 初始值改为 { i8* @Class.vtable }(首字段 vtable 地址,其余零),无 vtable 的 @t.Object 保持 zeroinitializer。
2026-08-29 18:39:28 +08:00
TermiNexus c2b9a26c62 fix(tpv): vpi 注入完整类型定义 + list elem_size + 路径修复,清理临时诊断
- VpiReader: 值 struct 全局/数组元素/函数返回类型注册完整定义,修复 llc use of undefined type;vpi 注入类后补 add_field 字段表(LARGE_INTEGER.QuadPart 恒 0 根因)
- TypeGenResolve: list[str](pool, elem_size) 语义修复(参数个数误当 elem_size,Mat4 崩溃根因);t/c 内置命名空间 Subscript 特判
- Config: _join_path 防双份路径
- GenApi: 新增 add_field 代理
- 移除 DBG-VK/PHASEADBG/GAP-DIAG/DBG-EMP 临时诊断
2026-08-29 17:27:29 +08:00
TermiNexus 1fe7878074 fix(compiler): 对象类模块级全局改静态 BSS storage,修复页 0 混叠与栈悬垂
对象类全局(裸类名注解)此前编译为 NULL 指针,实例建在栈 alloca 后悬垂,对象字段落在物理页 0(IVT+BDA 混叠),导致 VKernel devfs/fat32/sched 全部对象失效。新增 @g.stg 静态 storage(BSS),@g 编译期指向它;赋值/构造点(translate_assign/translate_annassign 全局分支)检测 storage 后绑定引用并复制对象,消除 NULL 与悬垂。
2026-08-29 17:27:21 +08:00
TermiNexus 2b2c242d14 fix(tpv): extern declare 返回类型含真实类型时不再误归 Void + 白名单兜底
vpi 注入 extern declare 时 has_state→Void 无条件触发:注解 HANDLE | t.State(FindFirstFileA/GetStdHandle)被归为 void → declare 返回 void,运行期 FindFirstFileA 句柄丢失 → 文件存在性判断失效(文件实际存在但报不存在)。修复:仅纯标记注解归 Void;含真实类型但解析 miss 时回退外部函数返回类型白名单(FindFirstFileA/GetStdHandle→i8*),GenApi 新增 infer_external_func_ret_ty 委托。
2026-08-29 12:03:12 +08:00
TermiNexus efc8f7948f fix(tpv): void 函数 return <expr> 丢弃值 + ret void(llc ret i32 vs define void)
GenApi.add_global_name 声明 -> None 但 body 为 return g.TranslatorGen.add_global_name(...)(委托返回 i32)→ translate_return 对带值 return 直接 build_ret(val) 生成 ret i32,与 define void 冲突 → llc 'value doesn't match function result type void'。修复:函数返回类型为 void 时 return <expr> 仅求值丢弃 + ret void(Python 语义)。
2026-08-29 11:12:23 +08:00
TermiNexus b7eeb504ee fix(tpv): 空类 FieldCount=0 中和重建 + 移除 [ATTR]/[DET] 调试打印
1) WIN32_FIND_DATAA 等空类(FieldCount=0)字段访问:模块限定路径补 FC==0 容忍(返回 None 交 pending,对齐裸名路径);ClassGenDef 空类不再 pending 延迟(多遍死锁),改为中和旧 entry 走完整注册重建。2) 用户要求移除 [ATTR](FuncGenBase/FuncGenDef)与 [DET](CallGenCtor)无条件调试打印。3) 拓宽 [DBG-REG]/[DBG-ADDF] 过滤器到 WIN32_FIND_DATAA 定位空类注册来源。
2026-08-29 10:44:43 +08:00
TermiNexus a99db2a29b fix(tpv): REnum 变体引用作值返回 tag 常量 + kind() 注解兜底
1) 表达式中的 REnumClass.Variant 无括号引用(llvmlite.LLVMType.Struct)此前未处理 → 与 kind() 返回的 i32 tag 比较时条件翻译失败(TranslatorGenMixin L517 根因)。仿枚举成员访问,translate_attribute 增加变体引用 → tag 常量。2) kind() 特殊处理在运行时类型退化(联合 i8*)时无法识别 REnum 类,补变量注解类名兜底。
2026-08-29 10:28:39 +08:00
TermiNexus 58d61badcb fix(tpv): 跨模块调用返回类型解析支持 import 别名(GenApi.translate_printf_call)
模块限定调用 GenApi.translate_printf_call 的 mod_ret_sha1 用 LookupSha1ByModName(别名) 查不到 → None → 误走裸名外部函数路径 fatal_if_unknown=1 fail-fast 误杀。改经 _resolve_module_sha1(LookupModNameByAlias 别名还原 → 精确/后缀)解析,与 L620-629 模块归属校验的既有模式一致。
2026-08-29 10:14:32 +08:00
TermiNexus 8d0241e0c3 fix(tpv): lookup_field_by_class 模块限定路径补 REnum 变体字段别名回退
llvmlite.LLVMType.Pointee 等模块限定 REnum 字段访问:类命中(FieldCount=4)但变体字段名(_p1/_p2 槽位别名)不是普通字段,此前变体别名回退只在裸名路径,模块限定路径 fatal 先触发永远走不到(CallGenDispatch L1005 根因)。在模块限定路径字段未找到时补 _lookup_renum_field_alias 回退,与裸名路径一致。同时移除已定位完成的 REEXPORT-DBG 临时诊断。
2026-08-29 10:03:10 +08:00
TermiNexus 9af7b2ead4 fix(tpv): from . import Y 跳过包目录名 SHA1 校验(无 __init__.vp 目录包)
from . import CallGenBase(CallGenCall.vp L12)解析后 resolved 是包目录名 lib.core.Gen.Call——无 __init__.vp 的目录包也允许这样导入,目录本身不是模块文件、无 SHA1,对它做 _check_import_sha1 恒失败误报。真实目标子模块 pkg.Y 由下方 LookupSha1ByModName/本地兄弟文件分支逐个校验,此处跳过包级校验即可。
2026-08-29 09:48:42 +08:00
TermiNexus d3bdfd5205 fix(tpv): 修复包重导出两个前置判定 bug(level 判定 + __init__ 后缀偏移)
1) 相对导入判定改 sifr.level>0:parser 把前导点存 level,module 字段不含点,原 sifr_mod[0]=='.' 恒假,走绝对分支查不到 ast.exprs,sub_sha1=None 重导出被跳过。2) __init__ 判定改 strstr:原 strcmp(rel_path+len-9) 对 ast/__init__.vp 指向 init__.vp 恒不匹配,is_pkg=0。两 bug 叠加致包重导出从未触发,ast.Attribute 未注册(AssignGenMixin L46 fail-fast 根因)。
2026-08-29 09:32:12 +08:00
TermiNexus 25363120f8 debug(tpv): 临时诊断定位 ast.Attribute 包重导出失败根因(REEXPORT-DBG) 2026-08-29 09:15:33 +08:00
TermiNexus 99ddff2d87 fix(tpv): t.CArray/t.CPtr 下标元素解析失败改为返回 None 交多遍收敛
阶段2 删 map_t_type stdint 硬编码后,UINT 等 typedef 仅经 CTypedef 动态表解析;Phase 1a(declare_only=1)无 vpi 注入,依赖 typedef 的类(zhuff_code)被跳过,导致 t.CArray[zhuff_code, N] 元素解析被误 fatal 中断编译(zhuff.vp L19 根因)。与裸名 CArray 路径及 ClassGenDef 字段 pending 机制保持一致,真实类型错误由 Phase A 收敛检查暴露。
2026-08-29 09:01:42 +08:00
TermiNexus 0a84ca8db3 debug(tpv): 临时诊断定位 SECURITY_ATTRIBUTES 空类注册来源
- register_struct/add_field 打印 [DBG-EMP](SECURITY_ATTRIBUTES 过滤)
- ClassGenDef 空类报错改为 pending + 打印(定位期间不中断编译)
- 定位后移除
2026-08-29 08:42:26 +08:00
TermiNexus 7b81e3c7cf fix(tpv): vpi 注入不再注册空类(FieldCount=0)——避免阻塞源模块真实定义
- vpi 存根不含类字段(只序列化 class 名 + def 签名),_inject_class_struct_declare
  的 field_count 恒为 0 → 注册空类
- 空类在源模块编译时被 find_struct_by_module 命中 → "结构体已注册但字段数为0"
  InternalCompilerError(win32base.SECURITY_ATTRIBUTES 根因)
- field_count==0 时不注册:依赖已编译命中真实类(L1604-1608),未编译走
  _deps_pending 多遍收敛
2026-08-29 08:20:26 +08:00
TermiNexus 6540e27844 fix(tpv): translate_attribute 读路径字段未找到时标记 _deps_pending
- fileio.vp li.QuadPart 报错:LARGE_INTEGER(win32base)未编译时只有 vpi
  空 stub,读路径字段查找失败未标记 pending → 直接 fatal 中断多遍收敛
- 读路径对齐写路径:字段未找到 → 标记 trans._deps_pending=1 下遍重译
2026-08-29 08:14:25 +08:00
TermiNexus 20ba8e0297 fix(tpv): 字段访问依赖未就绪时标记 _deps_pending 而非 fail-fast
- subprocess.vp sa.nLength 报错根因:SECURITY_ATTRIBUTES 定义侧(win32base)
  未编译时只有 vpi stub 空类(FieldCount=0),lookup_field_by_class 裸名
  回退命中空 stub → 字段未找到 → fatal_error 中断多遍收敛
- lookup_field_by_class:期望 sha1 未匹配且命中 FieldCount==0 空 stub →
  返回 None(不 fatal),交给上层 pending
- get_attribute_ptr 写路径:字段未找到 → 标记 trans._deps_pending=1 下遍
  重译(对齐读路径/AssignGenMixin L1793),真实错误由 Phase A 收敛检查暴露
2026-08-29 07:46:15 +08:00
TermiNexus 0b0cf5ba66 refactor(tpv): _parse_type_string 用 Expression 注解访问 body(源头修正后简化)
- ast.parse_expression 返回注解已改为 Expression | t.CPtr(includes/ast),
  expr_node 直接以 Expression 注解声明,去掉强转与 kind 检查
2026-08-29 07:26:12 +08:00
TermiNexus f75a4d795a fix(tpv): _parse_type_string 的 expr_node.body 修正——Expression 包装强转访问
- parse_expression 返回 Expression 节点,body 是 Expression 类字段
  (stmts.vp),ast.AST 基类无 body 字段(仅 children)
- 原注解 ast.AST 直接访问 expr_node.body 类型错误;补 kind 检查 +
  (ast.Expression) 强转后访问
2026-08-29 07:23:13 +08:00
TermiNexus 9e72393a11 fix(tpv): ExprGenName 三处 LLVMType.Int8() 模式修正为 Int(bits)+bits==8
- LLVMType 是 REnum,整数变体统一为 Int(Bits 字段),无 Int8 变体;
  Int8 仅是模块级工厂函数(返回 Int(8)),LLVMType.Int8 类成员不存在
- case llvmlite.LLVMType.Int8(): 永不匹配 → T|t.CPtr 退化 i8* 的
  8 字节步长 GEP 特判静默失效(走 i8 1 字节步长,指针截断)
- 三处(L858/1049/1087)改为 case Int(bits) + bits==8
2026-08-29 07:19:36 +08:00
TermiNexus ec4fb0bcf8 refactor(tpv): ClassGenVTable 的 self._mangle_name 统一为 self.Gen.FuncGenBase 显式调用
- 继承链 ClassGenVTable→ClassGenBase(无父类)不含 FuncGenBase,
  5 处 self._mangle_name 沿 MRO 解析不到定义(依赖编译器兜底)
- 统一为 self.Gen.FuncGenBase._mangle_name(对齐项目其他 Mixin 写法)
2026-08-29 07:16:43 +08:00
TermiNexus 98efd9b1d2 fix(tpv): __sizeof__ 模块限定类按模块归属匹配,防跨模块同名类误匹配
- w32.win32base.SECURITY_ATTRIBUTES.__sizeof__() 仅取 attr 丢失模块前缀,
  get_struct_type 裸名全局匹配误命中跨模块同名类(FieldCount=0)
- Attribute 形式保留完整模块路径(w32.win32base.SECURITY_ATTRIBUTES),
  结构体查找先按模块归属 find_struct_by_module 精确匹配,miss 再裸名
2026-08-29 07:06:11 +08:00
TermiNexus 8a69719acf fix(tpv): 裸名 typedef 强转改 resolve_typedef_or_map 统一口径(BYTEPTR 等)
- 阶段2 删除 map_t_type 的 stdint 别名后,BYTEPTR(x) 裸名 typedef 强转
  判定 map_t_type 返回 None → 误走外部函数路径 fail-fast
  (string.vp L104 TypeError 根因)
- 裸名 typedef call(BYTEPTR/HANDLE/size_t 等)改经 resolve_typedef_or_map:
  CTypedef 动态表优先 + t/c 核心 map_t_type 兜底,所有 CTypedef/t 库类型
  call 恢复强转语义
2026-08-29 06:22:30 +08:00
TermiNexus fa90586a83 refactor(tpv): 类型系统收敛——正常库分析优先,t/c 库内部特判兜底
- 阶段1a:Name 分支 from-import 别名反查(from t import CInt as XXX 可解析,
  支撑 YYY: t.CTypedef = XXX 复杂别名链)
- 阶段1b:Attribute 路径 CTypedef 动态表优先(stdint.INT 走库定义)
- 阶段2:map_t_type 删除与 stdint.vp 重复的 ~70 个硬编码别名(保留 t/c 核心
  原语 + float/double + HANDLE/LPCSTR/LPCWSTR),新增 resolve_typedef_or_map/
  get_type_size_bytes 统一口径 + GenApi 委托,改造 VpiReader/ClassGenEnum/
  TypeGenErr 旁路调用点
- 阶段3:__sizeof__/REnum 尺寸/无符号名单三张派生表加 CTypedef 动态兜底
2026-08-29 06:15:00 +08:00
TermiNexus 2610f361fd fix(tpv): 泛型特化名分裂——嵌套泛型实参递归 SHA1 规范化(_Type.md 问题 7)
- _norm_type_arg_name 顶层 '|' 分割改括号感知(list[A|B] 不再误分)
- 新增嵌套泛型实参递归:list[list[AST]] → list[list[{sha1}.AST]],
  内部按顶层 ',' 分割逐个 SHA1 规范化
- 所有 module 表示统一为 Hash Namespace({SHA1}.Class),消除跨模块
  特化名分裂(undefined symbol)
2026-08-29 05:51:49 +08:00
TermiNexus 4415f1f38b fix(tpv): 窄化截断加常量溢出诊断(_Type.md 问题 8)
- coerce_to_type 宽→窄 trunc 前检测编译期常量正溢出目标位宽无符号
  范围 → fatal_error(OverflowError),消除 buf[idx] = 0x1FF 类静默截断
- 负常量按模 2^bits 语义放行(-1 → 0xFF 合法);非常量运行时窄化保留
  trunc(值域可能安全,无法静态判定)
2026-08-29 05:48:15 +08:00
TermiNexus 4748915472 fix(tpv): 外部函数返回类型不可收敛时 fail-fast,豁免 from-import 与多层模块限定调用
- _infer_external_func_ret_ty 白名单未命中改为 fatal_error(Python 风格
  traceback),移除默认 i32 的 WARN 输出
- 裸名调用 fail-fast 前查 from_imports 可解析性:from-import 函数走
  mangled 精确解析收敛(SSAValue 场景),纯外部 C 函数才 fail-fast
- mod_ret_sha1 解析支持多层模块路径(w32.win32file.CreatePipe 场景),
  避免嵌套模块限定调用被误判为裸名外部函数
- 移除 FuncGenBase/StructGenMixin 遗留调试输出
2026-08-29 05:46:25 +08:00
TermiNexus 5551db3432 fix(tpv): try_extract_callable_ret 调用多传第 6 个参数(签名仅 5 参数)
- ab0f37e 引入:误加尾参 0(疑为 depth 参数),签名实为 (pool, annot, imported_modules, from_imports, trans)

- 删除多余参数,恢复 5 参数调用
2026-08-28 15:35:28 +08:00
TermiNexus 85e7011b6c refactor(tpv): INTPTR/UINTPTR 去硬编码,依赖 stdint.vp CTypedef 声明
- map_t_type 删除 INTPTR/UINTPTR = Ptr(i32) 硬编码(与 stdint.vp L10/L14 声明重复)

- INTPTR = t.CInt|t.CPtr、UINTPTR = UINT|t.CPtr 由 CTypedef 动态查表解析(L1377 先于 map_t_type 兜底)

- stdint.vp 成为单一事实源;语义不变(INTPTR 是 int* 历史命名,非 C 标准 intptr_t 整数)
2026-08-28 15:33:45 +08:00
TermiNexus 8445af93d2 fix(tpv): 泛型污染检测改 token 边界匹配(根治 strstr 误杀合法特化)
- _list.vp L57 elem_ptr: T | t.CPtr 在 list[AST|t.CPtr] 特化时被误报污染:strstr('e9dfb296a8674aee.AST|t.CPtr', 'T') 把 AST 内部字符 T 当参数名

- 新增 _str_contains_ident:独立标识符检测(两侧非字母/数字/_),实参内部字符不算污染

- T→T 完全自映射仍直接报错(明确污染);子串场景按边界判定
2026-08-28 15:06:30 +08:00
TermiNexus 27ba74c720 fix(tpv): REnum 别名表去指针化(class 槽位改 perm_pool 字符串 + strcmp 内容比较)
- _register_renum_field_alias: class_name strcpy 到 perm_pool,去重检查改 strcmp

- _lookup_renum_field_alias/_lookup_renum_field_alias_ty/_lookup_renum_member_ty: 指针值比较改 strcmp 内容比较

- 删除 _update_renum_alias_class_ptr 及调用点(去指针化后无需指针统一)

- 根治 'LLVMType 字段 Pointee 未找到' 反复报错(注册 AST 指针 vs 查找 entry.Name 池拷贝指针恒不等)
2026-08-28 15:00:50 +08:00
TermiNexus a8eb7fe5fe fix(tpv): 类型静默退化改编译期报错 + 统一整数拓宽 zext/sext
- FuncGenDef: 有返回注解但解析失败 → fatal_error(不再静默回退 i32;无注解函数保留 i32 默认)

- TypeGenResolve: 泛型类型实参污染(T→T 自映射 / 实参含类型参数名)→ fatal_error(不再 return None 静默退化)

- ExprGenBase: coerce_to_type 窄→宽接入 _zext_or_sext_val(i1/i8 默认 zext、无符号类型名 zext、否则 sext),修正 t.CUInt32T 等无符号值被符号扩展的隐蔽数据损坏
2026-08-28 14:57:46 +08:00
TermiNexus 24ae2dc966 refactor(tpv): 删除 Handles 目录,核心逻辑合并进 Gen
- 新建 Gen/Translator.vp:Translator 类 + Mixin + 常量 + _mbuddy 自 HandlesTranslator 迁入(删 7 个委托壳与 _g_gen)

- GenApi 下沉 7 个 global/nonlocal 管理委托(add/is_global_name、add/is_nonlocal_name、get_nonlocal_type/index、clear_scope_names)+ update_global_func_typestr

- CallGenLeafKit 并入 ExprCallHandle 类,NewExprCallHandle 改本地引用

- 删除 Handles/HandlesExpr/ExprCallTable/HandlesBase(60+ 委托零消费者/纯委托壳)与 Handles 目录整体

- 全 App 56 文件 import 改指 lib.core.Gen.*,清理死 import,委托调用改 GenApi,main/BuildPipeline _g_gen 注入删除
2026-08-28 14:29:25 +08:00
TermiNexus d99b6268de refactor(tpv): Gen 目录按前缀分组迁移子目录 + REnum.kind() 编译期转 __tag
- Gen/*.vp 按前缀分组迁入 Call/Class/Expr/Func/Stmt/Type 子目录,import 同步更新(BuildPipeline/Generator/StructGenMixin 等)

- CallGenDispatch: REnum.kind() 前置拦截 → bitcast i32* + load(读 __tag),杜绝方法查找兜底误绑 AST.kind(this 偏移语义错误)

- 留根:Assign/Async/Base/Imports/Match/Struct/Translator/Var 单文件前缀 + GenApi/GenTypes/Generator/__init__
2026-08-28 14:16:03 +08:00
TermiNexus b3a4be21d3 feat(tpv): VLogger 迁移 f-string + 模块限定构造 SHA1 解析 + 包重导出别名注册
- VLogger 全面改用栈 f-string 传参,移除 fmtbuffer 静态缓冲(Phase1Scan/Config/Converter/StubMergerStore/StubMergerTopo/ClassGenDef/StmtGenDeclMixin/CallGenCtor 等)

- CallGenMixin: Module.Class(...) 按模块名解析 SHA1 精确命中类(Scene3D 跨模块同名根因);import X as Y 别名三级还原(精确→后缀→LookupModNameByAlias)

- CallGenCtor: lookup_field_by_class 传 kw 节点与 from_imports;REnum 错误 f-string 化;新增 [DET] ctor 入口 can 核对

- StructGenMixin/GenApi/VpiReader: 包重导出类以包 sha1 注册别名 entry(ast.Attribute 归属错位)

- main: 版本号更新、VLogger API(get_logger→init_logger/logger)、新增 --show-args/--build-dir

- TypeGenCType: 移除 CDEF-MISS 调试日志;.gitignore 忽略 output_dbg/
2026-08-28 13:29:26 +08:00
TermiNexus 19171ee165 feat(tpv): vpi 预生成失败输出失败文件路径与原因(含 AST 解析错误行列) 2026-08-28 08:05:46 +08:00
TermiNexus 6daec5fa58 feat(tpv): translate_unaryop 支持单目 * / &(Deref/AddrOf) 2026-08-28 07:35:03 +08:00
TermiNexus d971bae7cd 移除了 VLogger fmt buffer 缓冲区,使用栈 f-string 2026-08-28 06:01:17 +08:00
TermiNexus 54462320d2 简化了一些表达,最后仍使用 fb 缓冲区的版本(新版本使用 栈f-string) 2026-08-28 04:50:09 +08:00
TermiNexus c542442987 fix(structgen): 裸名回退字段未找到时返回 None 而非 fatal_error,让 REnum 变体别名回退生效
- ce62546 新增的裸名回退分支在类命中但字段未找到时 fatal_error,
  截断了 ExprGenAttr L853-864 的 REnum 变体别名回退(LLVMType.Pointee
  → _p2 槽位是变体别名非普通字段)→ AssignGenMixin.vp:1002
  "类 llvmlite.LLVMType 裸名回退但字段 Pointee 未找到"
- 改为字段未找到 return None,对齐主 SHA1 匹配分支语义,让调用方
  有机会走 REnum 别名/继承链回退
2026-08-27 16:16:03 +08:00
TermiNexus ce62546e90 fix(structgen): lookup_field_by_class 模块归属未命中回退裸名同名类(唯一时),修复 ast.Attribute 归属错位误报
- 场景:ast.Attribute(ast/exprs.vp 定义,ModuleSha1=c0def010dcebadb1)经
  lookup_field_by_class 模块归属解析,模块段 "ast" 走包名回退 → ast/__init__.vp
  的 sha1 (35245c385b0ddd7b)→ find_struct_by_module 不匹配 → 误报
  "类 ast.Attribute 未找到"(AssignGenMixin.is_cdefine_annotation 的 at.attr 触发)
- 修复:模块可解析但类未注册时,统计裸名同名类数量,唯一时回退查找字段
  (Attribute 短名无跨模块同名,安全);多同名仍 fail-fast(防 sched.Thread 顶掉)
2026-08-27 16:10:48 +08:00
TermiNexus 907f7892f9 fix(callgen): 跨模块返回类型解析的 mod_ret_sha1 显式初始化 None,修复裸名调用读栈残留崩溃
- CallGenMixin.translate_call 中 mod_ret_sha1 原声明在 Attribute 分支内、
  elif mod_ret_sha1 is not None 在外层读取,裸名调用(cl.func 是 Name,
  如 LLVMModuleCore.CreateGlobalInt 内裸名 create_global_int)时该 alloca
  从未 store,读到栈残留垃圾(0x1)→ _LookupMethodRetTyFromTextLL 的
  strlen(0x1) 零页访问(llvmlite/__init__.vp 翻译时 0xC0000005)
- 前移声明并显式初始化为 None,裸名调用不再进入跨模块返回类型解析
2026-08-27 15:52:09 +08:00
TermiNexus c54e87bd6d fix(imports): from . import Y 的 Y 是包内符号(非子模块)时跳过 SHA1 校验
- translate_import_from_module 对 from . import Y 曾无条件把每个 name 拼成
  resolved.Y 当子模块校验 SHA1 → json/__parser.vp 的 from . import JsonValue
  等符号(json/__init__.vp 定义)被误报"模块 json.JsonValue 未注册 SHA1"
- 仅在完整名已注册 SHA1(真子模块)或本地文件时才校验+登记;符号映射
  由 translate_import_from_names 的 from-import / re-export 处理
- 顺带:Reexport 懒初始化改用 CompilerFactory.factory.PermPool(配合 TPC
  跨模块全局写端修复)
2026-08-27 14:27:24 +08:00
TermiNexus a603736f64 修正:直接操作操作跨模块变量(VLogger),避免间接 global 函数 2026-08-27 13:14:39 +08:00
176 changed files with 62931 additions and 58125 deletions

No files matched your search

+2 -1
View File
@@ -10,6 +10,7 @@ __pycache__/
# 构建产物(防御性)
**/output/
**/temp/
**/output_dbg/
**/*.deps.json
**/*.doc.json
**/*.pyi
@@ -27,4 +28,4 @@ __pycache__/
# 编译
**/output/
**/temp/
**/.tpv_build/
**/__vpcbuild__/
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+255 -353
View File
@@ -1,115 +1,64 @@
import t, c
from stdint import *
import string
import stdlib
import memhub
import viperlib
import sys
import w32.fileio as fileio
import w32.win32file
import w32.win32base
import win32.fileio as fileio
import ast
import llvmlite
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesTranslator as HandlesTranslator
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.IncludesScanner as IncludesScanner
import lib.Stub.StubMerger as StubMerger
import lib.core.Gen.Generator as GenGenerator
import lib.core.CompilerFactory as CompilerFactory
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Projectrans.Config as Config
import lib.Stub.StubGen.Converter as StubConverter
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.Phase.Phase1.Phase1Scan as Phase1Scan
import lib.Phase.Phase1.Phase1Topo as Phase1Topo
import lib.Phase.Phase1 as Phase1
import lib.core.Gen.GenApi as GenApi
# ============================================================
# Phase1Scan - Phase1 拆分(阶段 E):Phase1Scan
#
# 直接移动模式:函数迁至此模块,原 Phase1.vp 仅保留常量/_mbuddy;
# 调用方经本模块前缀访问。全局符号经 Phase1._xxx 跨模块访问。
# ============================================================
# Phase1Scan - scan class inheritance, generic-class markers and container usage.
# Functions live in this module; globals are accessed cross-module via Phase1._xxx.
# 拓扑排序使用扁平字节数组存储类信息和文件依赖,
# 避免结构体中的 str 指针字段在 mb.alloc 零填充时为 NULL 导致崩溃。
# 类名/父类名存储在 class_names_buf/parent_names_buf(每个 Phase1.CLASS_NAME_LEN 字节),
# 定义 SHA1 存储在 def_sha1s_buf(每个 Phase1.SHA1_LEN 字节)。
# Topological sort stores class info/deps in flat byte arrays (names in
# class_names_buf/parent_names_buf, SHA1s in def_sha1s_buf) to avoid NULL str fields.
# ============================================================
# _ScanClassInheritance - 预扫描 includes 文件,收集类继承关系
#
# 遍历所有可达 includes 文件,解析 AST 收集 ClassDef 的类名和父类名。
# 结果存入扁平字节数组,返回收集到的类数量。
#
# Args:
# mb: 内存池
# result: IncludesScanner 扫描结果
# reachable_set: 可达 SHA1 集合
# reachable_count: 可达 SHA1 数量
# use_reachable: 是否使用可达集合过滤
# sha1_set: SHA1 集合(use_reachable=0 时使用)
# set_count: SHA1 集合数量
# class_names_buf: 类名扁平数组(每个 Phase1.CLASS_NAME_LEN 字节,连续内存)
# parent_names_buf: 父类名扁平数组(每个 Phase1.CLASS_NAME_LEN 字节,无父类填 '\0')
# def_sha1s_buf: 定义 SHA1 扁平数组(每个 Phase1.SHA1_LEN 字节)
#
# Returns:
# 收集到的类数量,-1 失败
# ============================================================
def _ScanClassInheritance(mb: memhub.MemBuddy | t.CPtr,
result: IncludesScanner.ScanResult | t.CPtr,
reachable_set: str, reachable_count: int,
use_reachable: int,
sha1_set: str, set_count: int,
class_names_buf: bytes,
parent_names_buf: bytes,
def_sha1s_buf: bytes,
generic_file_indices: t.CPtr,
generic_file_count_box: t.CPtr) -> int:
"""预扫描 includes 文件,收集类继承关系"""
if result is None or class_names_buf is None or parent_names_buf is None or def_sha1s_buf is None:
return -1
# _ScanClassInheritance - pre-scan includes files, parse ASTs and collect ClassDef
# names/parent names into flat byte arrays; returns the class count (-1 on failure).
def _ScanClassInheritance(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, reachable_set: str, reachable_count: int, use_reachable: int, sha1_set: str, set_count: int, class_names_buf: bytes, parent_names_buf: bytes, def_sha1s_buf: bytes, generic_file_indices: rawptr, generic_file_count_box: rawptr) -> int:
"""Pre-scan includes files and collect class inheritance info."""
if result is None: return -1
# The three flat buffers are mb.alloc'ed by the caller; a NULL buffer would make
# strcpy write the zero page, so fail-fast abort instead of returning -1 silently.
if class_names_buf is None or parent_names_buf is None or def_sha1s_buf is None: VLogger.error("class-inheritance pre-scan flat buffer allocation failed (class_names_buf/parent_names_buf/def_sha1s_buf is NULL)", "Phase1")
entry_size: t.CSizeT = IncludesScanner.FileEntry.__sizeof__()
entry_size: sizet = IncludesScanner.FileEntry.__sizeof__()
class_count: int = 0
for i in range(result.Count):
# 文件级 mark:收集完类继承信息后立即回收本文件 AST(可达 includes
# 全量 parse 的 AST 若驻留到 mark1 → 1GB 主池耗尽 OOM 根因;
# class_names_buf/parent_names_buf/def_sha1s_buf 在调用前分配,
# 位于本 mark 之前,reset 不影响;generic_file_indices 同理)
scan_mark: t.CSizeT = mb.mark()
entry_addr: t.CUInt64T = t.CUInt64T(result.Entries) + i * entry_size
entry: IncludesScanner.FileEntry | t.CPtr = (IncludesScanner.FileEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr))
if entry is None or entry.Sha1 is None:
continue
for i in range(int(result.Count)):
# Per-file mark: reclaim this file's AST right after collecting inheritance
# info (keeping all ASTs alive until mark1 would OOM the 1GB main pool).
scan_mark: sizet = mb.mark()
entry_addr: uint64 = uint64(result.Entries) + i * entry_size
entry: IncludesScanner.FileEntry* = IncludesScanner.FileEntry*(entry_addr)
if entry is None or entry.Sha1 is None: continue
sha1_e: str = entry.Sha1
file_has_generic: int = 0
# 按需翻译过滤
# on-demand translation filter
in_set: int = 0
if use_reachable != 0:
in_set = StubMergerCollect._is_in_sha1_set(sha1_e, reachable_set, reachable_count)
else:
in_set = StubMergerCollect._is_in_sha1_set(sha1_e, sha1_set, set_count)
if in_set == 0:
continue
if use_reachable != 0: in_set = StubMergerCollect._is_in_sha1_set(sha1_e, reachable_set, reachable_count)
else: in_set = StubMergerCollect._is_in_sha1_set(sha1_e, sha1_set, set_count)
if in_set == 0: continue
# 读取文件
# read the file
file_path: str = entry.Path
f: fileio.File | t.CPtr = fileio.File(file_path, fileio.MODE.R)
if f.closed:
f: fileio.File* = fileio.File(file_path, fileio.MODE.R)
if f.closed: continue
# read_all's capacity includes the terminator (reads at most cap-1 bytes); longer files are truncated
if f.size() >= Phase1.SRC_BUF_SIZE:
VLogger.error(f"source file exceeds the {Phase1.SRC_BUF_SIZE}-byte limit, refusing to read: {file_path}", "Phase1")
f.close()
continue
src_buf: bytes = stdlib.malloc(Phase1.SRC_BUF_SIZE)
@@ -122,177 +71,156 @@ def _ScanClassInheritance(mb: memhub.MemBuddy | t.CPtr,
if bytes_read <= 0:
stdlib.free(src_buf)
continue
if bytes_read < Phase1.SRC_BUF_SIZE:
src_buf[bytes_read] = 0
else:
src_buf[Phase1.SRC_BUF_SIZE - 1] = 0
if bytes_read < Phase1.SRC_BUF_SIZE: src_buf[bytes_read] = 0
# 解析 AST
lx: ast.Lexer | t.CPtr = ast.new_lexer(mb)
# parse the AST
lx: ast.Lexer* = ast.new_lexer(mb)
if lx is None:
stdlib.free(src_buf)
continue
ast._lexer_init(lx, src_buf, mb)
tokens: ast.Token | t.CPtr = ast.tokenize(lx)
tree: ast.AST | t.CPtr = ast.parse_tokens(mb, tokens)
tokens: ast.Token* = ast.tokenize(lx)
tree: ast.AST* = ast.parse_tokens(mb, tokens)
if tree is None:
stdlib.free(src_buf)
continue
# 遍历 AST 顶层节点,收集 ClassDef
ch: list[ast.AST | t.CPtr] | t.CPtr = tree.children
# walk top-level AST nodes, collect ClassDef
ch: list[ast.AST*]* = tree.children
if ch is not None:
cn: t.CSizeT = len(ch)
for ci in range(cn):
child: ast.AST | t.CPtr = ch.get(ci)
if child is None:
continue
cn: sizet = len(ch)
for ci in range(int(cn)):
child: ast.AST* = ch.get(ci)
if child is None: continue
kd: int = child.kind()
if kd != ast.ASTKind.ClassDef:
continue
if kd != ast.ASTKind.ClassDef: continue
cd: ast.ClassDef | t.CPtr = (ast.ClassDef | t.CPtr)(child)
if cd is None or cd.name is None:
continue
cd: ast.ClassDef* = ast.ClassDef*(child)
if cd is None or cd.name is None: continue
# 检测泛型类(有 type_params),记录文件索引以供 Phase 1a 预注册
# detect generic classes (type_params); record the file index for Phase 1a
if file_has_generic == 0:
tp_scan: list[str] | t.CPtr = cd.type_params
tp_scan: list[str]* = cd.type_params
if tp_scan is not None:
file_has_generic = 1
if generic_file_count_box is not None:
gfc_ptr_scan: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(generic_file_count_box, t.CPtr))
gfc_ptr_scan: int* = (int*)(generic_file_count_box)
if gfc_ptr_scan is not None:
gfc_scan: int = gfc_ptr_scan[0]
if gfc_scan < Phase1.MAX_FILE_DEPS and generic_file_indices is not None:
gi_addr_scan: t.CUInt64T = t.CUInt64T(generic_file_indices) + gfc_scan * 4
gi_ptr_scan: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(gi_addr_scan, t.CPtr))
if gi_ptr_scan is not None:
gi_ptr_scan[0] = i
gi_addr_scan: uint64 = uint64(generic_file_indices) + gfc_scan * 4
gi_ptr_scan: int* = (int*)(gi_addr_scan)
if gi_ptr_scan is not None: gi_ptr_scan[0] = i
gfc_ptr_scan[0] = gfc_scan + 1
if class_count >= Phase1.MAX_CLASSES:
break
if class_count >= Phase1.MAX_CLASSES: break
# 填充扁平数组:类名(偏移 = class_count * Phase1.CLASS_NAME_LEN)
# fill flat array: class name (offset = class_count * Phase1.CLASS_NAME_LEN)
name_dst: str = class_names_buf + class_count * Phase1.CLASS_NAME_LEN
nm_len: t.CSizeT = string.strlen(cd.name)
nm_len: sizet = string.strlen(cd.name)
if nm_len >= Phase1.CLASS_NAME_LEN:
string.strncpy(name_dst, cd.name, Phase1.CLASS_NAME_LEN - 1)
name_dst[Phase1.CLASS_NAME_LEN - 1] = '\0'
else:
string.strcpy(name_dst, cd.name)
else: string.strcpy(name_dst, cd.name)
# 填充扁平数组:父类名(无父类填 '\0')
# fill flat array: parent name (no parent -> '\0')
parent_dst: str = parent_names_buf + class_count * Phase1.CLASS_NAME_LEN
parent_dst[0] = '\0'
parent_nm: str = GenApi._get_parent_class(cd, None)
# parent_nm 是 AST 节点内部指针,不需要释放
# parent_nm is an internal AST pointer; no need to free
if parent_nm is not None:
pnm_len: t.CSizeT = string.strlen(parent_nm)
pnm_len: sizet = string.strlen(parent_nm)
if pnm_len >= Phase1.CLASS_NAME_LEN:
string.strncpy(parent_dst, parent_nm, Phase1.CLASS_NAME_LEN - 1)
parent_dst[Phase1.CLASS_NAME_LEN - 1] = '\0'
else:
string.strcpy(parent_dst, parent_nm)
else: string.strcpy(parent_dst, parent_nm)
# 填充扁平数组:定义 SHA1(偏移 = class_count * Phase1.SHA1_LEN)
# fill flat array: defining SHA1 (offset = class_count * Phase1.SHA1_LEN)
sha1_dst: str = def_sha1s_buf + class_count * Phase1.SHA1_LEN
string.strcpy(sha1_dst, sha1_e)
class_count += 1
stdlib.free(src_buf)
# 回收本文件 AST/lexer/token(类名/父类/SHA1 已拷入扁平数组)
# reclaim this file's AST/lexer/token (names/SHA1 already copied into flat arrays)
mb.reset_to(scan_mark)
return class_count
# ============================================================
# _BufHasGenericClassMarker - 文本扫描检测文件是否含泛型类标记
#
# 泛型类(PEP 695)语法: class Name[T]:(类名后紧跟 '[')。
# 逐字节扫描找词边界 'class' 关键字,其后标识符后非空白是 '[' → 命中。
# 用于 _PreRegisterAllGenericTemplates 预注册前筛选,避免 parse 全部
# 98 个 includes 导致 AST 驻留 mark1 前把 pool 推到 OOM
# (NewAstTest [52/68] pool 耗尽根因:1a-pre 已 parse 全部驻留,
# 预注册再全量 parse 等于翻倍)。
# 误报(字符串/注释里的 "class X[")无害(多 parse 一个文件);
# 漏报有害(泛型模板未预注册 → 悬空复发),模式保守匹配行内单行形式
# (TPV 源码泛型类均为单行 `class X[T]:`,grep 确认)。
# ============================================================
def _BufHasGenericClassMarker(buf: bytes, blen: t.CSizeT) -> int:
"""文本扫描:buf 中是否有 'class 标识符[' 泛型类标记,返回 1/0"""
if buf is None or blen <= 6:
return 0
pos: t.CSizeT = 0
# _BufHasGenericClassMarker - text scan for a generic-class marker (PEP 695 "class
# Name["). Used to pre-filter files before registering generic templates.
def _BufHasGenericClassMarker(buf: bytes, blen: sizet) -> int:
"""Return 1 if buf contains a 'class identifier[' generic-class marker, else 0."""
if buf is None or blen <= 6: return 0
pos: sizet = 0
while pos + 5 < blen:
if buf[pos] == 'c' and buf[pos + 1] == 'l' and buf[pos + 2] == 'a' \
and buf[pos + 3] == 's' and buf[pos + 4] == 's':
# class 前是词边界(行首或非标识符字符)
if buf[pos] == 'c' and buf[pos + 1] == 'l' and buf[pos + 2] == 'a' and buf[pos + 3] == 's' and buf[pos + 4] == 's':
# 'class' preceded by a word boundary (line start or non-identifier)
prev_ok: int = 1
if pos > 0:
pc: t.CChar = buf[pos - 1]
if(pc >= 'a' and pc <= 'z') or(pc >= 'A' and pc <= 'Z') \
or(pc >= '0' and pc <= '9') or pc == '_':
prev_ok = 0
pc: char = buf[pos - 1]
if(pc >= 'a' and pc <= 'z') or(pc >= 'A' and pc <= 'Z') or(pc >= '0' and pc <= '9') or pc == '_': prev_ok = 0
if prev_ok != 0:
p: t.CSizeT = pos + 5
# 跳过空白
while p < blen and(buf[p] == ' ' or buf[p] == '\t'):
p += 1
# 读类名标识符
if p < blen and((buf[p] >= 'a' and buf[p] <= 'z')
or(buf[p] >= 'A' and buf[p] <= 'Z') or buf[p] == '_'):
while p < blen and((buf[p] >= 'a' and buf[p] <= 'z')
or(buf[p] >= 'A' and buf[p] <= 'Z')
or(buf[p] >= '0' and buf[p] <= '9')
or buf[p] == '_'):
p: sizet = pos + 5
# skip whitespace
while p < blen and(buf[p] == ' ' or buf[p] == '\t'): p += 1
# read the class-name identifier
if p < blen and((buf[p] >= 'a' and buf[p] <= 'z') or(buf[p] >= 'A' and buf[p] <= 'Z') or buf[p] == '_'):
while p < blen and((buf[p] >= 'a' and buf[p] <= 'z') or(buf[p] >= 'A' and buf[p] <= 'Z') or(buf[p] >= '0' and buf[p] <= '9') or buf[p] == '_'):
p += 1
# 类名后非空白是 '[' → 泛型类
while p < blen and(buf[p] == ' ' or buf[p] == '\t'):
p += 1
if p < blen and buf[p] == '[':
return 1
pos += 1
else:
# non-space after the name is '[' -> generic class
while p < blen and(buf[p] == ' ' or buf[p] == '\t'): p += 1
if p < blen and buf[p] == '[': return 1
pos += 1
else: pos += 1
return 0
# ============================================================
# _PreRegisterAllGenericTemplates - 预注册 includes 泛型类模板(mark1 前)
#
# 背景:泛型模板表(GenericClassNames/GenericClassNodes/GenericClassSha1s/
# GenericClassFiles + 模板节点)是懒分配的跨文件持久表,首次注册必须落在
# mark1 之前才能跨 Phase1 1b(reset_to(mark1))与 Phase2(reset_to(mark2),
# mark2 > mark1)存活。现有 1a-pre-generic 只注册"可达"泛型文件;FuncTest
# 这类可达集合无泛型的项目,模板要等 Phase2 Phase A-pre-inc(mark2 之后)
# 翻译全部 includes 时才注册 → reset 回收表与节点 → _find_generic_template
# 遍历悬空内存 strcmp 崩溃。
# 本函数先文本扫描(_BufHasGenericClassMarker,不 parse、不驻留)筛选出
# 含泛型类的文件(全量 parse 会把 98 个 includes 的 AST 驻留 mark1 前 →
# NewAstTest [52/68] pool 耗尽 OOM),仅对命中的文件用**永久池(perm_pool)**
# 解析 → 模板节点与表直接进 perm_pool,跨 mark 永久存活、不占主 pool。
# 重复注册由 _register_generic_template 内部查重跳过。
# ============================================================
def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy | t.CPtr,
result: IncludesScanner.ScanResult | t.CPtr) -> int:
"""预注册全部 includes 文件的泛型类模板,返回处理到的泛型类数量"""
if result is None or mb is None:
return 0
perm: memhub.MemBuddy | t.CPtr = CompilerFactory.get_perm_pool()
if perm is None:
return 0
p1e_size: t.CSizeT = IncludesScanner.FileEntry.__sizeof__()
# _BufHasGenericFuncMarker - text scan for a generic-function marker (PEP 695
# "def name["); cheap pre-filter before parsing for generic function templates.
def _BufHasGenericFuncMarker(buf: bytes, blen: sizet) -> int:
"""Return 1 if buf contains a 'def identifier[' generic-function marker, else 0."""
if buf is None or blen <= 4: return 0
pos: sizet = 0
while pos + 3 < blen:
if buf[pos] == 'd' and buf[pos + 1] == 'e' and buf[pos + 2] == 'f':
# 'def' preceded by a word boundary (line start or non-identifier)
prev_ok: int = 1
if pos > 0:
pc: char = buf[pos - 1]
if(pc >= 'a' and pc <= 'z') or(pc >= 'A' and pc <= 'Z') or(pc >= '0' and pc <= '9') or pc == '_': prev_ok = 0
if prev_ok != 0:
p: sizet = pos + 3
# skip whitespace
while p < blen and(buf[p] == ' ' or buf[p] == '\t'): p += 1
# read the function-name identifier
if p < blen and((buf[p] >= 'a' and buf[p] <= 'z') or(buf[p] >= 'A' and buf[p] <= 'Z') or buf[p] == '_'):
while p < blen and((buf[p] >= 'a' and buf[p] <= 'z') or(buf[p] >= 'A' and buf[p] <= 'Z') or(buf[p] >= '0' and buf[p] <= '9') or buf[p] == '_'):
p += 1
# non-space after the name is '[' -> generic function
while p < blen and(buf[p] == ' ' or buf[p] == '\t'): p += 1
if p < blen and buf[p] == '[': return 1
pos += 1
else: pos += 1
return 0
# _PreRegisterAllGenericTemplates - pre-register includes generic templates before
# mark1 so the lazy tables survive later resets; parses only marker-matched files.
def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*) -> int:
"""Pre-register generic templates from all includes files and return the count."""
if result is None or mb is None: return 0
perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if perm is None: return 0
p1e_size: sizet = IncludesScanner.FileEntry.__sizeof__()
handled_count: int = 0
for i in range(result.Count):
entry_addr_pg: t.CUInt64T = t.CUInt64T(result.Entries) + i * p1e_size
entry_pg: IncludesScanner.FileEntry | t.CPtr = (
IncludesScanner.FileEntry | t.CPtr)(t.CVoid(entry_addr_pg, t.CPtr))
if entry_pg is None or entry_pg.Sha1 is None:
continue
# 读取文件
for i in range(int(result.Count)):
entry_addr_pg: uint64 = uint64(result.Entries) + i * p1e_size
entry_pg: IncludesScanner.FileEntry* = (IncludesScanner.FileEntry*)(entry_addr_pg)
if entry_pg is None or entry_pg.Sha1 is None: continue
# read the file
file_path_pg: str = entry_pg.Path
f_pg: fileio.File | t.CPtr = fileio.File(file_path_pg, fileio.MODE.R)
if f_pg.closed:
f_pg: fileio.File* = fileio.File(file_path_pg, fileio.MODE.R)
if f_pg.closed: continue
# read_all's capacity includes the terminator (reads at most cap-1 bytes); longer files are truncated
if f_pg.size() >= Phase1.SRC_BUF_SIZE:
VLogger.error(f"source file exceeds the {Phase1.SRC_BUF_SIZE}-byte limit, refusing to read: {file_path_pg}", "Phase1")
f_pg.close()
continue
src_buf_pg: bytes = stdlib.malloc(Phase1.SRC_BUF_SIZE)
if src_buf_pg is None:
@@ -303,210 +231,184 @@ def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy | t.CPtr,
if bytes_read_pg <= 0:
stdlib.free(src_buf_pg)
continue
if bytes_read_pg < Phase1.SRC_BUF_SIZE:
src_buf_pg[bytes_read_pg] = 0
else:
src_buf_pg[Phase1.SRC_BUF_SIZE - 1] = 0
# 文本扫描筛选:非泛型文件跳过(不 parse、不驻留,防 OOM)
if _BufHasGenericClassMarker(src_buf_pg, bytes_read_pg) == 0:
if bytes_read_pg < Phase1.SRC_BUF_SIZE: src_buf_pg[bytes_read_pg] = 0
# Text-scan filter: skip non-generic files (no parse, no residency, avoids
# OOM). A class or function marker hit selects the file for parsing.
if _BufHasGenericClassMarker(src_buf_pg, bytes_read_pg) == 0 and _BufHasGenericFuncMarker(src_buf_pg, bytes_read_pg) == 0:
stdlib.free(src_buf_pg)
continue
# 泛型文件:永久池解析 → 注册模板(节点/表进 perm_pool,跨 reset 存活)
lx_pg: ast.Lexer | t.CPtr = ast.new_lexer(perm)
# generic file: parse in perm_pool and register templates (survives reset)
lx_pg: ast.Lexer* = ast.new_lexer(perm)
if lx_pg is None:
stdlib.free(src_buf_pg)
continue
ast._lexer_init(lx_pg, src_buf_pg, perm)
tokens_pg: ast.Token | t.CPtr = ast.tokenize(lx_pg)
tree_pg: ast.AST | t.CPtr = ast.parse_tokens(perm, tokens_pg)
tokens_pg: ast.Token* = ast.tokenize(lx_pg)
tree_pg: ast.AST* = ast.parse_tokens(perm, tokens_pg)
if tree_pg is None:
stdlib.free(src_buf_pg)
continue
# 遍历顶层 ClassDef,注册泛型模板(type_params 非 None)
ch_pg: list[ast.AST | t.CPtr] | t.CPtr = tree_pg.children
# walk top-level ClassDef/FunctionDef, register generic templates (type_params != None)
ch_pg: list[ast.AST*]* = tree_pg.children
if ch_pg is not None:
cn_pg: t.CSizeT = len(ch_pg)
for ci_pg in range(cn_pg):
child_pg: ast.AST | t.CPtr = ch_pg.get(ci_pg)
if child_pg is None or child_pg.kind() != ast.ASTKind.ClassDef:
continue
cd_pg: ast.ClassDef | t.CPtr = (ast.ClassDef | t.CPtr)(child_pg)
if cd_pg is None or cd_pg.name is None:
continue
tp_pg: list[str] | t.CPtr = cd_pg.type_params
if tp_pg is None:
continue
GenApi.set_current_file_type(file_path_pg)
GenApi._register_generic_template(perm, cd_pg, entry_pg.Sha1)
handled_count += 1
cn_pg: sizet = len(ch_pg)
for ci_pg in range(int(cn_pg)):
child_pg: ast.AST* = ch_pg.get(ci_pg)
if child_pg is None: continue
ck_pg: int = child_pg.kind()
if ck_pg == ast.ASTKind.ClassDef:
cd_pg: ast.ClassDef* = ast.ClassDef*(child_pg)
if cd_pg is None or cd_pg.name is None: continue
tp_pg: list[str]* = cd_pg.type_params
if tp_pg is None: continue
GenApi.set_current_file_type(file_path_pg)
GenApi._register_generic_template(perm, cd_pg, entry_pg.Sha1)
handled_count += 1
elif ck_pg == ast.ASTKind.FunctionDef:
fd_pg: ast.FunctionDef* = ast.FunctionDef*(child_pg)
if fd_pg is None or fd_pg.name is None: continue
tf_pg: list[str]* = fd_pg.type_params
if tf_pg is None: continue
GenApi.set_current_file_type(file_path_pg)
GenApi._register_generic_func_template(perm, fd_pg, entry_pg.Sha1)
handled_count += 1
stdlib.free(src_buf_pg)
if handled_count > 0:
fb_gpre2: str = VLogger.fmt_buf()
if fb_gpre2 is not None:
viperlib.snprintf(fb_gpre2, 1024, "mark1 前永久池预注册 %d 个 includes 泛型类模板", handled_count)
VLogger.info(fb_gpre2, "Phase1")
if handled_count > 0: VLogger.info(f"pre-registered {handled_count} includes generic templates (classes and functions) in the permanent pool before mark1", "Phase1")
else: VLogger.info(f"pre-registered 0 generic templates in the permanent pool before mark1 (scanned {result.Count} files; the marker may not have matched or the full scan failed)", "Phase1")
return handled_count
# ============================================================
# _FindClassDefSha1 - 在扁平数组中查找类名对应的定义文件 SHA1
#
# Args:
# class_names_buf: 类名扁平数组
# def_sha1s_buf: 定义 SHA1 扁平数组
# class_count: 类数量
# class_name: 要查找的类名
#
# Returns:
# SHA1 字符串指针(指向 def_sha1s_buf 内部偏移),None 未找到
# ============================================================
def _FindClassDefSha1(class_names_buf: bytes, def_sha1s_buf: bytes,
class_count: int, class_name: str) -> str:
"""查找类名对应的定义文件 SHA1"""
if class_names_buf is None or def_sha1s_buf is None or class_name is None:
return None
for i in range(class_count):
# _FindClassDefSha1 - look up a class name's defining file SHA1 in the flat arrays;
# returns a pointer into def_sha1s_buf, or None if not found.
def _FindClassDefSha1(class_names_buf: bytes, def_sha1s_buf: bytes, class_count: int, class_name: str) -> str:
"""Find a class name's defining file SHA1 in the flat arrays."""
if class_names_buf is None or def_sha1s_buf is None or class_name is None: return None
for i in range(int(class_count)):
name_ptr: str = class_names_buf + i * Phase1.CLASS_NAME_LEN
if string.strcmp(name_ptr, class_name) == 0:
return def_sha1s_buf + i * Phase1.SHA1_LEN
if string.strcmp(name_ptr, class_name) == 0: return def_sha1s_buf + i * Phase1.SHA1_LEN
return None
# ============================================================
# _ScanTreeForContainers - 递归扫描 AST 检测 list/dict 容器使用
#
# TPV 的 AST 中,语句块挂在 children,而表达式/注解树全部以结构体
# 字段挂载(value/slice/func/args/annotation/targets/returns/bases/
# left/right 等)。只递归 children 会漏掉 list[...] | t.CPtr(BinOp
# 包裹)、list[...](...)(Call.func)、字段注解等一切非语句块位置。
# 因此每个节点先递归其表达式承载字段,再递归 children 语句块。
# 命中即置 need_list/need_dict;误报无害(最多多编译两个空壳模块)。
# ============================================================
def _ScanTreeForContainers(node: ast.AST | t.CPtr,
need_list_box: t.CPtr,
need_dict_box: t.CPtr,
depth: int = 0):
"""递归扫描 AST(字段 + 语句块),检测 list/dict 容器使用"""
# 递归深度守卫:AST 损坏成自引用环(字段/children 回指祖先)时无限
# 递归直至栈耗尽(每个 includes 文件 parse 后必执行)。fail-fast 终止。
if depth > 128:
fb_stc: str = VLogger.fmt_buf()
if fb_stc is not None:
viperlib.snprintf(fb_stc, 256, "容器扫描递归过深(AST 环或嵌套超过 128 层)")
VLogger.error(fb_stc, "Phase1")
sys.exit(1)
if node is None:
return
# _ScanTreeForContainers - recursively scan the AST for list/dict container usage:
# recurse expression/annotation struct fields first, then statement-block children.
def _AnnIsContainer(ann: ast.AST*) -> int:
"""Return 1 if the annotation is a list/dict container, else 0."""
while ann is not None:
ak_c: int = ann.kind()
if ak_c == ast.ASTKind.Name:
nm_c: ast.Name* = ast.Name*(ann)
if nm_c.id is not None:
if string.strcmp(nm_c.id, "list") == 0: return 1
if string.strcmp(nm_c.id, "dict") == 0: return 1
return 0
elif ak_c == ast.ASTKind.Subscript:
sub_c: ast.Subscript* = ast.Subscript*(ann)
ann = sub_c.value
else: return 0
return 0
def _ScanTreeForContainers(node: ast.AST*, need_list_box: rawptr, need_dict_box: rawptr, depth: int = 0):
"""Recursively scan the AST for list/dict container usage."""
# Recursion depth guard: a self-referential AST cycle would recurse until the
# stack is exhausted; fail-fast abort.
if depth > 128: VLogger.error("container scan recursion too deep (AST cycle or nesting over 128 levels)", "Phase1")
if node is None: return
k_s: int = node.kind()
if k_s == ast.ASTKind.Name:
nm_s: ast.Name | t.CPtr = (ast.Name | t.CPtr)(node)
nm_s: ast.Name* = ast.Name*(node)
if nm_s.id is not None:
if string.strcmp(nm_s.id, "list") == 0:
need_list_box[0] = 1
elif string.strcmp(nm_s.id, "dict") == 0:
need_dict_box[0] = 1
if string.strcmp(nm_s.id, "list") == 0: need_list_box[0] = 1
elif string.strcmp(nm_s.id, "dict") == 0: need_dict_box[0] = 1
elif k_s == ast.ASTKind.Subscript:
sub_s: ast.Subscript | t.CPtr = (ast.Subscript | t.CPtr)(node)
sub_s: ast.Subscript* = ast.Subscript*(node)
_ScanTreeForContainers(sub_s.value, need_list_box, need_dict_box, depth + 1)
_ScanTreeForContainers(sub_s.slice, need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.BinOp:
bop_s: ast.BinOp | t.CPtr = (ast.BinOp | t.CPtr)(node)
bop_s: ast.BinOp* = ast.BinOp*(node)
_ScanTreeForContainers(bop_s.left, need_list_box, need_dict_box, depth + 1)
_ScanTreeForContainers(bop_s.right, need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.Call:
cl_s2: ast.Call | t.CPtr = (ast.Call | t.CPtr)(node)
cl_s2: ast.Call* = ast.Call*(node)
_ScanTreeForContainers(cl_s2.func, need_list_box, need_dict_box, depth + 1)
cl_args_s: list[ast.AST | t.CPtr] | t.CPtr = cl_s2.args
cl_args_s: list[ast.AST*]* = cl_s2.args
if cl_args_s is not None:
na_s: t.CSizeT = len(cl_args_s)
i_a2: t.CSizeT
for i_a2 in range(na_s):
_ScanTreeForContainers(cl_args_s.get(i_a2), need_list_box, need_dict_box, depth + 1)
cl_kws_s: list[ast.AST | t.CPtr] | t.CPtr = cl_s2.keywords
na_s: sizet = len(cl_args_s)
i_a2: sizet
for i_a2 in range(int(na_s)): _ScanTreeForContainers(cl_args_s.get(i_a2), need_list_box, need_dict_box, depth + 1)
cl_kws_s: list[ast.AST*]* = cl_s2.keywords
if cl_kws_s is not None:
nk_s: t.CSizeT = len(cl_kws_s)
i_k2: t.CSizeT
for i_k2 in range(nk_s):
kw_s: ast.Keyword | t.CPtr = (ast.Keyword | t.CPtr)(cl_kws_s.get(i_k2))
if kw_s is not None:
_ScanTreeForContainers(kw_s.value, need_list_box, need_dict_box, depth + 1)
nk_s: sizet = len(cl_kws_s)
i_k2: sizet
for i_k2 in range(int(nk_s)):
kw_s: ast.Keyword* = ast.Keyword*(cl_kws_s.get(i_k2))
if kw_s is not None: _ScanTreeForContainers(kw_s.value, need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.Assign:
asn_s: ast.Assign | t.CPtr = (ast.Assign | t.CPtr)(node)
asn_s: ast.Assign* = ast.Assign*(node)
_ScanTreeForContainers(asn_s.value, need_list_box, need_dict_box, depth + 1)
tgts_s: list[ast.AST | t.CPtr] | t.CPtr = asn_s.targets
tgts_s: list[ast.AST*]* = asn_s.targets
if tgts_s is not None:
nt_s: t.CSizeT = len(tgts_s)
i_t2: t.CSizeT
for i_t2 in range(nt_s):
_ScanTreeForContainers(tgts_s.get(i_t2), need_list_box, need_dict_box, depth + 1)
nt_s: sizet = len(tgts_s)
i_t2: sizet
for i_t2 in range(int(nt_s)): _ScanTreeForContainers(tgts_s.get(i_t2), need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.AnnAssign:
aa_s2: ast.AnnAssign | t.CPtr = (ast.AnnAssign | t.CPtr)(node)
aa_s2: ast.AnnAssign* = ast.AnnAssign*(node)
_ScanTreeForContainers(aa_s2.annotation, need_list_box, need_dict_box, depth + 1)
_ScanTreeForContainers(aa_s2.value, need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.FunctionDef:
fn_s: ast.FunctionDef | t.CPtr = (ast.FunctionDef | t.CPtr)(node)
fn_s: ast.FunctionDef* = ast.FunctionDef*(node)
_ScanTreeForContainers(fn_s.returns, need_list_box, need_dict_box, depth + 1)
fn_args_s: ast.Arguments | t.CPtr = fn_s.args
fn_args_s: ast.Arguments* = ast.Arguments*(fn_s.args)
if fn_args_s is not None:
fa_s: list[ast.AST | t.CPtr] | t.CPtr = fn_args_s.args
fa_s: list[ast.AST*]* = fn_args_s.args
if fa_s is not None:
nf_s: t.CSizeT = len(fa_s)
i_f2: t.CSizeT
for i_f2 in range(nf_s):
arg_s: ast.Arg | t.CPtr = (ast.Arg | t.CPtr)(fa_s.get(i_f2))
if arg_s is not None:
_ScanTreeForContainers(arg_s.annotation, need_list_box, need_dict_box, depth + 1)
nf_s: sizet = len(fa_s)
i_f2: sizet
for i_f2 in range(int(nf_s)):
arg_s: ast.Arg* = ast.Arg*(fa_s.get(i_f2))
if arg_s is not None: _ScanTreeForContainers(arg_s.annotation, need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.ClassDef:
cd_s: ast.ClassDef | t.CPtr = (ast.ClassDef | t.CPtr)(node)
bases_s: list[ast.AST | t.CPtr] | t.CPtr = cd_s.bases
cd_s: ast.ClassDef* = ast.ClassDef*(node)
bases_s: list[ast.AST*]* = cd_s.bases
if bases_s is not None:
nb_s: t.CSizeT = len(bases_s)
i_b2: t.CSizeT
for i_b2 in range(nb_s):
_ScanTreeForContainers(bases_s.get(i_b2), need_list_box, need_dict_box, depth + 1)
nb_s: sizet = len(bases_s)
i_b2: sizet
for i_b2 in range(int(nb_s)): _ScanTreeForContainers(bases_s.get(i_b2), need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.Attribute:
at_s: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(node)
at_s: ast.Attribute* = ast.Attribute*(node)
_ScanTreeForContainers(at_s.value, need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.Tuple:
tu_s: ast.Tuple | t.CPtr = (ast.Tuple | t.CPtr)(node)
elts_s: list[ast.AST | t.CPtr] | t.CPtr = tu_s.elts
tu_s: ast.Tuple* = ast.Tuple*(node)
elts_s: list[ast.AST*]* = tu_s.elts
if elts_s is not None:
ne_s: t.CSizeT = len(elts_s)
i_e2: t.CSizeT
for i_e2 in range(ne_s):
_ScanTreeForContainers(elts_s.get(i_e2), need_list_box, need_dict_box, depth + 1)
ne_s: sizet = len(elts_s)
i_e2: sizet
for i_e2 in range(int(ne_s)): _ScanTreeForContainers(elts_s.get(i_e2), need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.UnaryOp:
uop_s: ast.UnaryOp | t.CPtr = (ast.UnaryOp | t.CPtr)(node)
uop_s: ast.UnaryOp* = ast.UnaryOp*(node)
_ScanTreeForContainers(uop_s.operand, need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.Expr:
ex_s: ast.Expr | t.CPtr = (ast.Expr | t.CPtr)(node)
ex_s: ast.Expr* = ast.Expr*(node)
_ScanTreeForContainers(ex_s.value, need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.Return:
rt_s: ast.Return | t.CPtr = (ast.Return | t.CPtr)(node)
rt_s: ast.Return* = ast.Return*(node)
_ScanTreeForContainers(rt_s.value, need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.Compare:
cp_s: ast.Compare | t.CPtr = (ast.Compare | t.CPtr)(node)
cp_s: ast.Compare* = ast.Compare*(node)
_ScanTreeForContainers(cp_s.left, need_list_box, need_dict_box, depth + 1)
cps_s: list[ast.AST | t.CPtr] | t.CPtr = cp_s.comparators
cps_s: list[ast.AST*]* = cp_s.comparators
if cps_s is not None:
nc_s: t.CSizeT = len(cps_s)
i_c2: t.CSizeT
for i_c2 in range(nc_s):
_ScanTreeForContainers(cps_s.get(i_c2), need_list_box, need_dict_box, depth + 1)
nc_s: sizet = len(cps_s)
i_c2: sizet
for i_c2 in range(int(nc_s)): _ScanTreeForContainers(cps_s.get(i_c2), need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.BoolOp:
bo_s: ast.BoolOp | t.CPtr = (ast.BoolOp | t.CPtr)(node)
bvs_s: list[ast.AST | t.CPtr] | t.CPtr = bo_s.values
bo_s: ast.BoolOp* = ast.BoolOp*(node)
bvs_s: list[ast.AST*]* = bo_s.values
if bvs_s is not None:
nv_s: t.CSizeT = len(bvs_s)
i_v2: t.CSizeT
for i_v2 in range(nv_s):
_ScanTreeForContainers(bvs_s.get(i_v2), need_list_box, need_dict_box, depth + 1)
elif k_s == ast.ASTKind.List:
# [] 字面量
need_list_box[0] = 1
elif k_s == ast.ASTKind.Dict:
# {} 字面量
need_dict_box[0] = 1
# 递归语句块子节点
nv_s: sizet = len(bvs_s)
i_v2: sizet
for i_v2 in range(int(nv_s)): _ScanTreeForContainers(bvs_s.get(i_v2), need_list_box, need_dict_box, depth + 1)
# `[...]`/`{}` literals do not trigger _list/_dict imports (compile-time constant lists, array init);
# recurse into statement-block children.
if node.children is not None:
ch_s: list[ast.AST | t.CPtr] | t.CPtr = node.children
n_s: t.CSizeT = len(ch_s)
ci_s: t.CSizeT
for ci_s in range(n_s):
_ScanTreeForContainers(ch_s.get(ci_s), need_list_box, need_dict_box, depth + 1)
ch_s: list[ast.AST*]* = node.children
n_s: sizet = len(ch_s)
ci_s: sizet
for ci_s in range(int(n_s)): _ScanTreeForContainers(ch_s.get(ci_s), need_list_box, need_dict_box, depth + 1)
+154 -266
View File
@@ -1,181 +1,109 @@
import t, c
from stdint import *
import string
import stdlib
import memhub
import viperlib
import w32.fileio as fileio
import w32.win32file
import w32.win32base
import ast
import llvmlite
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesTranslator as HandlesTranslator
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.IncludesScanner as IncludesScanner
import lib.Stub.StubMerger as StubMerger
import lib.core.Gen.Generator as GenGenerator
import lib.core.CompilerFactory as CompilerFactory
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Projectrans.Config as Config
import lib.Stub.StubGen.Converter as StubConverter
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.Phase.Phase1.Phase1Scan as Phase1Scan
import lib.Phase.Phase1.Phase1Topo as Phase1Topo
import lib.Phase.Phase1 as Phase1
# ============================================================
# Phase1Topo - Phase1 拆分(阶段 E):Phase1Topo
#
# 直接移动模式:函数迁至此模块,原 Phase1.vp 仅保留常量/_mbuddy;
# 调用方经本模块前缀访问。全局符号经 Phase1._xxx 跨模块访问。
# ============================================================
# Phase1Topo - topologically sort source files by class inheritance and import dependencies.
# Functions live in this module (accessed via its prefix); globals go through Phase1._xxx.
# ============================================================
# _TopoSortFiles - 根据类继承关系对文件进行拓扑排序
#
# 算法(Kahn):
# 1. 从扁平类信息数组构建"文件 SHA1 → 依赖文件 SHA1 集合"映射
# 2. 计算每个文件的入度(依赖数量)
# 3. 入度为 0 的文件先处理,处理后减少依赖它的文件入度
# 4. 重复直到所有文件处理完成
#
# 注意:依赖方向是"父类所在文件 → 子类所在文件",
# 即父类文件必须先处理。Kahn 用 DepCount 作为剩余依赖计数,
# 依赖为 0 的先处理,初始化 InDegree = DepCount。
#
# Args:
# mb: 内存池
# result: IncludesScanner 扫描结果
# reachable_set: 可达 SHA1 集合
# reachable_count: 可达 SHA1 数量
# use_reachable: 是否使用可达集合过滤
# sha1_set: SHA1 集合
# set_count: SHA1 集合数量
# class_names_buf: 类名扁平数组
# parent_names_buf: 父类名扁平数组
# def_sha1s_buf: 定义 SHA1 扁平数组
# class_count: 类数量
# out_order: 输出拓扑顺序的文件索引数组(调用者分配,int 数组)
# out_count: 输出文件数量
#
# Returns:
# 0 成功,-1 失败
# ============================================================
def _TopoSortFiles(mb: memhub.MemBuddy | t.CPtr,
result: IncludesScanner.ScanResult | t.CPtr,
reachable_set: str, reachable_count: int,
use_reachable: int,
sha1_set: str, set_count: int,
class_names_buf: bytes, parent_names_buf: bytes,
def_sha1s_buf: bytes, class_count: int,
out_order: t.CPtr, out_count: t.CPtr,
temp_dir: str, td_len_ts: t.CSizeT) -> int:
"""根据类继承关系对文件进行拓扑排序"""
if result is None or out_order is None or out_count is None:
return -1
# _TopoSortFiles - topologically sort files by class inheritance (Kahn).
# Builds a "file SHA1 -> dependency SHA1 set" map, computes in-degrees (parent-file first), then outputs the order; returns 0 or -1.
def _TopoSortFiles(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, reachable_set: str, reachable_count: int, use_reachable: int, sha1_set: str, set_count: int, class_names_buf: bytes, parent_names_buf: bytes, def_sha1s_buf: bytes, class_count: int, out_order: rawptr, out_count: rawptr, temp_dir: str, td_len_ts: sizet) -> int:
"""Topologically sort files by class inheritance and import dependencies."""
if result is None or out_order is None or out_count is None: return -1
entry_size: t.CSizeT = IncludesScanner.FileEntry.__sizeof__()
entry_size: sizet = IncludesScanner.FileEntry.__sizeof__()
# 1. 分配扁平数组:文件 SHA1、依赖 SHA1 列表、依赖数量、入度、处理状态
# 用扁平数组代替 FileDepEntry 结构体,避免 str 指针字段 NULL 崩溃
# 1. Allocate flat arrays: file SHA1, dependency SHA1 list, dep counts, in-degrees, processed flags.
# Flat arrays replace a FileDepEntry struct to avoid NULL str-pointer fields crashing.
file_sha1s_buf: bytes = mb.alloc(Phase1.MAX_FILE_DEPS * Phase1.SHA1_LEN)
file_deps_buf: bytes = mb.alloc(Phase1.MAX_FILE_DEPS * Phase1.MAX_DEP_EDGES * Phase1.SHA1_LEN)
file_dep_counts: t.CPtr = mb.alloc(Phase1.MAX_FILE_DEPS * 4)
file_indegrees: t.CPtr = mb.alloc(Phase1.MAX_FILE_DEPS * 4)
file_processed: t.CPtr = mb.alloc(Phase1.MAX_FILE_DEPS * 4)
# 取消整数提升后,模块属性 CDefine(Phase1.MAX_*) 按最小位宽物化为 i16/i8;三段乘积 256*16*17=69632 会在 i16 回绕,
# 首乘数显式 sizet() 加宽到 64 位。上一行 256*17=4352 未超 i16,故无需改。
file_deps_buf: bytes = mb.alloc(sizet(Phase1.MAX_FILE_DEPS) * Phase1.MAX_DEP_EDGES * Phase1.SHA1_LEN)
file_dep_counts: rawptr = mb.alloc(Phase1.MAX_FILE_DEPS * 4)
file_indegrees: rawptr = mb.alloc(Phase1.MAX_FILE_DEPS * 4)
file_processed: rawptr = mb.alloc(Phase1.MAX_FILE_DEPS * 4)
if file_sha1s_buf is None or file_deps_buf is None or file_dep_counts is None or file_indegrees is None or file_processed is None:
if file_sha1s_buf is not None:
mb.free(file_sha1s_buf)
if file_deps_buf is not None:
mb.free(file_deps_buf)
if file_dep_counts is not None:
mb.free(file_dep_counts)
if file_indegrees is not None:
mb.free(file_indegrees)
if file_processed is not None:
mb.free(file_processed)
if file_sha1s_buf is not None: mb.free(file_sha1s_buf)
if file_deps_buf is not None: mb.free(file_deps_buf)
if file_dep_counts is not None: mb.free(file_dep_counts)
if file_indegrees is not None: mb.free(file_indegrees)
if file_processed is not None: mb.free(file_processed)
return -1
string.memset(file_sha1s_buf, 0, Phase1.MAX_FILE_DEPS * Phase1.SHA1_LEN)
string.memset(file_deps_buf, 0, Phase1.MAX_FILE_DEPS * Phase1.MAX_DEP_EDGES * Phase1.SHA1_LEN)
string.memset(file_deps_buf, 0, sizet(Phase1.MAX_FILE_DEPS) * Phase1.MAX_DEP_EDGES * Phase1.SHA1_LEN)
string.memset(file_dep_counts, 0, Phase1.MAX_FILE_DEPS * 4)
string.memset(file_indegrees, 0, Phase1.MAX_FILE_DEPS * 4)
string.memset(file_processed, 0, Phase1.MAX_FILE_DEPS * 4)
file_count: int = 0
# sha1→file_index 映射(简单线性查找,文件数不多)
for i in range(result.Count):
entry_addr: t.CUInt64T = t.CUInt64T(result.Entries) + i * entry_size
entry: IncludesScanner.FileEntry | t.CPtr = (IncludesScanner.FileEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr))
if entry is None or entry.Sha1 is None:
continue
# sha1 -> file_index map (simple linear search; file count is small)
for i in range(int(result.Count)):
entry_addr: uint64 = uint64(result.Entries) + i * entry_size
entry: IncludesScanner.FileEntry* = IncludesScanner.FileEntry*(entry_addr)
if entry is None or entry.Sha1 is None: continue
sha1_e: str = entry.Sha1
in_set: int = 0
if use_reachable != 0:
in_set = StubMergerCollect._is_in_sha1_set(sha1_e, reachable_set, reachable_count)
else:
in_set = StubMergerCollect._is_in_sha1_set(sha1_e, sha1_set, set_count)
if in_set == 0:
continue
if use_reachable != 0: in_set = StubMergerCollect._is_in_sha1_set(sha1_e, reachable_set, reachable_count)
else: in_set = StubMergerCollect._is_in_sha1_set(sha1_e, sha1_set, set_count)
if in_set == 0: continue
if file_count >= Phase1.MAX_FILE_DEPS:
break
if file_count >= Phase1.MAX_FILE_DEPS: break
# 初始化扁平数组:文件 SHA1(偏移 = file_count * Phase1.SHA1_LEN)
# DepCount/InDegree/Processed 已由 memset 清零
# Initialize the flat array: file SHA1 (offset = file_count * Phase1.SHA1_LEN).
# DepCount/InDegree/Processed are already zeroed by memset.
sha1_dst: str = file_sha1s_buf + file_count * Phase1.SHA1_LEN
string.strcpy(sha1_dst, sha1_e)
file_count += 1
# 2. 构建依赖边:对每个类,找到父类定义所在文件,建立 子文件→父文件 依赖
for ci in range(class_count):
# 读取父类名(偏移 = ci * Phase1.CLASS_NAME_LEN)
# 2. Build dependency edges: for each class, find the parent's defining file and add a child->parent edge
for ci in range(int(class_count)):
# Read parent class name (offset = ci * Phase1.CLASS_NAME_LEN)
parent_ptr: str = parent_names_buf + ci * Phase1.CLASS_NAME_LEN
if parent_ptr[0] == '\0':
continue
if parent_ptr[0] == '\0': continue
# 读取子类定义 SHA1(偏移 = ci * Phase1.SHA1_LEN)
# Read child class definition SHA1 (offset = ci * Phase1.SHA1_LEN)
child_def_sha1: str = def_sha1s_buf + ci * Phase1.SHA1_LEN
# 查找父类定义所在文件的 SHA1
# Find the SHA1 of the file defining the parent class
parent_sha1: str = Phase1Scan._FindClassDefSha1(class_names_buf, def_sha1s_buf, class_count, parent_ptr)
if parent_sha1 is None:
continue # 父类不在 includes 中(如 Object、CEnum 等标记基类)
continue # parent not in includes (e.g. Object, CEnum marker base classes)
# 父类文件 SHA1 不能等于子类文件 SHA1(同文件内继承不需要依赖)
if string.strcmp(parent_sha1, child_def_sha1) == 0:
continue
# The parent file SHA1 must differ from the child's (same-file inheritance needs no dependency)
if string.strcmp(parent_sha1, child_def_sha1) == 0: continue
# 找到子类文件对应的索引,添加父文件 SHA1 到依赖列表
for fi in range(file_count):
# Find the child file's index and add the parent file SHA1 to its dependency list
for fi in range(int(file_count)):
fsha1: str = file_sha1s_buf + fi * Phase1.SHA1_LEN
if string.strcmp(fsha1, child_def_sha1) != 0:
continue
if string.strcmp(fsha1, child_def_sha1) != 0: continue
# 读取当前依赖数量
cnt_addr: t.CUInt64T = t.CUInt64T(file_dep_counts) + fi * 4
cnt_ptr: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(cnt_addr, t.CPtr))
if cnt_ptr is None:
break
# Read the current dependency count
cnt_addr: uint64 = uint64(file_dep_counts) + fi * 4
cnt_ptr: int* = (int*)(cnt_addr)
if cnt_ptr is None: break
cur_cnt: int = cnt_ptr[0]
# 检查依赖是否已添加(去重)
# Check whether the dependency is already present (dedupe)
already: int = 0
for di in range(cur_cnt):
for di in range(int(cur_cnt)):
dep_slot: str = file_deps_buf + fi * Phase1.MAX_DEP_EDGES * Phase1.SHA1_LEN + di * Phase1.SHA1_LEN
if string.strcmp(dep_slot, parent_sha1) == 0:
already = 1
break
if already != 0:
break
if already != 0: break
if cur_cnt < Phase1.MAX_DEP_EDGES:
dep_dst: str = file_deps_buf + fi * Phase1.MAX_DEP_EDGES * Phase1.SHA1_LEN + cur_cnt * Phase1.SHA1_LEN
@@ -183,65 +111,44 @@ def _TopoSortFiles(mb: memhub.MemBuddy | t.CPtr,
cnt_ptr[0] = cur_cnt + 1
break
# 2.5 补充 import 依赖边:被 import 的模块先完整翻译。
#
# 仅按类继承建边不够:跨模块全局变量 stub(lexer.vp from-import
# tokens.vp 的 _op_head)、跨模块 vtable TypeStr(astaux 继承 base 的
# AST)、泛型模板(base.vp 用 list[...])都要求依赖模块先翻译,而它们
# 之间可能无类继承关系。deps(_includes_deps_*)由 Phase 1a-pre 填充
# (含容器按需注入的 _list/_dict)。
#
# 用 _BuildIncludesSha1Map 复制局部 sha1→模块名 数组(与
# _BuildReachableSha1Set 的 BFS 一致),避免直接使用全局 store 返回值
# 跨函数传参(bytes|t.CPtr 联合转 str 参数)的地址异常。
# 2.5 Add import dependency edges: imported modules must be fully translated first.
# Class inheritance alone is insufficient (cross-module globals/vtable TypeStr/generics need deps); deps come from Phase 1a-pre, mapped via _BuildIncludesSha1Map.
sha1_arr_ts: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 17)
mod_arr_ts: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 64)
map_count_ts: int = 0
if sha1_arr_ts is not None and mod_arr_ts is not None:
map_count_ts = StubMergerIR._BuildIncludesSha1Map(
temp_dir, td_len_ts, sha1_arr_ts, mod_arr_ts)
for fi_imp in range(file_count):
if map_count_ts <= 0:
break
map_count_ts = StubMergerIR._BuildIncludesSha1Map(temp_dir, td_len_ts, sha1_arr_ts, mod_arr_ts)
for fi_imp in range(int(file_count)):
if map_count_ts <= 0: break
fsha1_imp: str = file_sha1s_buf + fi_imp * Phase1.SHA1_LEN
deps_imp: str = StubMergerStore.LookupIncludesDeps(fsha1_imp)
if deps_imp is None:
continue
dl_imp: t.CSizeT = string.strlen(deps_imp)
dpos_imp: t.CSizeT = 0
if deps_imp is None: continue
dl_imp: sizet = string.strlen(deps_imp)
dpos_imp: sizet = 0
while dpos_imp < dl_imp:
dstart_imp: t.CSizeT = dpos_imp
while dpos_imp < dl_imp and deps_imp[dpos_imp] != ' ':
dpos_imp += 1
dlen_imp: t.CSizeT = dpos_imp - dstart_imp
if dpos_imp < dl_imp:
dpos_imp += 1
if dlen_imp == 0:
continue
dstart_imp: sizet = dpos_imp
while dpos_imp < dl_imp and deps_imp[dpos_imp] != ' ': dpos_imp += 1
dlen_imp: sizet = dpos_imp - dstart_imp
if dpos_imp < dl_imp: dpos_imp += 1
if dlen_imp == 0: continue
dname_imp: bytes = stdlib.malloc(dlen_imp + 1)
if dname_imp is None:
continue
if dname_imp is None: continue
string.strncpy(dname_imp, deps_imp + dstart_imp, dlen_imp)
dname_imp[dlen_imp] = '\0'
dsha1_imp: str = StubMergerIR._FindSha1ByModName(
sha1_arr_ts, mod_arr_ts, map_count_ts, dname_imp)
dsha1_imp: str = StubMergerIR._FindSha1ByModName(sha1_arr_ts, mod_arr_ts, map_count_ts, dname_imp)
stdlib.free(dname_imp)
if dsha1_imp is None:
continue
# 找依赖文件在列表中的索引(跳过自身)
for fj_imp in range(file_count):
if string.strcmp(file_sha1s_buf + fj_imp * Phase1.SHA1_LEN, dsha1_imp) != 0:
continue
if fj_imp == fi_imp:
break
# 去重 + 加边:fi_imp 依赖 fj_imp(InDegree[fi_imp]++)
cnt_addr_imp: t.CUInt64T = t.CUInt64T(file_dep_counts) + fi_imp * 4
cnt_ptr_imp: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(cnt_addr_imp, t.CPtr))
if cnt_ptr_imp is None:
break
if dsha1_imp is None: continue
# Find the dependency file's index in the list (skip self)
for fj_imp in range(int(file_count)):
if string.strcmp(file_sha1s_buf + fj_imp * Phase1.SHA1_LEN, dsha1_imp) != 0: continue
if fj_imp == fi_imp: break
# Dedupe and add the edge: fi_imp depends on fj_imp (InDegree[fi_imp]++)
cnt_addr_imp: uint64 = uint64(file_dep_counts) + fi_imp * 4
cnt_ptr_imp: int* = (int*)(cnt_addr_imp)
if cnt_ptr_imp is None: break
cur_cnt_imp: int = cnt_ptr_imp[0]
already_imp: int = 0
for di_imp in range(cur_cnt_imp):
for di_imp in range(int(cur_cnt_imp)):
dep_slot_imp: str = file_deps_buf + fi_imp * Phase1.MAX_DEP_EDGES * Phase1.SHA1_LEN + di_imp * Phase1.SHA1_LEN
if string.strcmp(dep_slot_imp, dsha1_imp) == 0:
already_imp = 1
@@ -252,120 +159,104 @@ def _TopoSortFiles(mb: memhub.MemBuddy | t.CPtr,
cnt_ptr_imp[0] = cur_cnt_imp + 1
break
# 释放 import 边用的局部映射数组
if sha1_arr_ts is not None:
stdlib.free(sha1_arr_ts)
if mod_arr_ts is not None:
stdlib.free(mod_arr_ts)
# Free the local mapping arrays used for import edges
if sha1_arr_ts is not None: stdlib.free(sha1_arr_ts)
if mod_arr_ts is not None: stdlib.free(mod_arr_ts)
# 3. Kahn 算法:InDegree = DepCount(依赖多少个父文件)
# 依赖为 0 的先处理,处理后减少依赖它的文件入度
for fi in range(file_count):
cnt_addr3: t.CUInt64T = t.CUInt64T(file_dep_counts) + fi * 4
cnt_ptr3: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(cnt_addr3, t.CPtr))
ind_addr3: t.CUInt64T = t.CUInt64T(file_indegrees) + fi * 4
ind_ptr3: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(ind_addr3, t.CPtr))
if cnt_ptr3 is not None and ind_ptr3 is not None:
ind_ptr3[0] = cnt_ptr3[0]
# 3. Kahn: InDegree = DepCount (number of parent files depended on).
# Process zero-dependency files first, then decrement the in-degree of dependents.
for fi in range(int(file_count)):
cnt_addr3: uint64 = uint64(file_dep_counts) + fi * 4
cnt_ptr3: int* = (int*)(cnt_addr3)
ind_addr3: uint64 = uint64(file_indegrees) + fi * 4
ind_ptr3: int* = (int*)(ind_addr3)
if cnt_ptr3 is not None and ind_ptr3 is not None: ind_ptr3[0] = cnt_ptr3[0]
# 4. 拓扑排序输出
# 4. Topological sort output
out_idx: int = 0
changed: int = 1
while changed != 0:
changed = 0
for fi in range(file_count):
# 检查处理状态
proc_addr4: t.CUInt64T = t.CUInt64T(file_processed) + fi * 4
proc_ptr4: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(proc_addr4, t.CPtr))
if proc_ptr4 is None or proc_ptr4[0] != 0:
continue
# 检查入度
ind_addr4: t.CUInt64T = t.CUInt64T(file_indegrees) + fi * 4
ind_ptr4: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(ind_addr4, t.CPtr))
if ind_ptr4 is None or ind_ptr4[0] > 0:
continue
for fi in range(int(file_count)):
# Check processed flag
proc_addr4: uint64 = uint64(file_processed) + fi * 4
proc_ptr4: int* = (int*)(proc_addr4)
if proc_ptr4 is None or proc_ptr4[0] != 0: continue
# Check in-degree
ind_addr4: uint64 = uint64(file_indegrees) + fi * 4
ind_ptr4: int* = (int*)(ind_addr4)
if ind_ptr4 is None or ind_ptr4[0] > 0: continue
# 入度为 0,可以处理
# 找到对应的 result.Entries 索引
# In-degree 0: can be processed
# Find the matching result.Entries index
target_sha1: str = file_sha1s_buf + fi * Phase1.SHA1_LEN
for ri in range(result.Count):
ea_r: t.CUInt64T = t.CUInt64T(result.Entries) + ri * entry_size
ent_r: IncludesScanner.FileEntry | t.CPtr = (IncludesScanner.FileEntry | t.CPtr)(t.CVoid(ea_r, t.CPtr))
for ri in range(int(result.Count)):
ea_r: uint64 = uint64(result.Entries) + ri * entry_size
ent_r: IncludesScanner.FileEntry* = IncludesScanner.FileEntry*(ea_r)
if ent_r is not None and ent_r.Sha1 is not None:
if string.strcmp(ent_r.Sha1, target_sha1) == 0:
# 写入输出索引数组
out_idx_addr: t.CUInt64T = t.CUInt64T(out_order) + out_idx * 4
out_int_ptr: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(out_idx_addr, t.CPtr))
if out_int_ptr is not None:
out_int_ptr[0] = ri
# Write into the output index array
out_idx_addr: uint64 = uint64(out_order) + out_idx * 4
out_int_ptr: int* = (int*)(out_idx_addr)
if out_int_ptr is not None: out_int_ptr[0] = ri
out_idx += 1
break
# 标记已处理
# Mark as processed
proc_ptr4[0] = 1
changed = 1
# 减少依赖该文件的其他文件的入度
for fj in range(file_count):
proc_addr5: t.CUInt64T = t.CUInt64T(file_processed) + fj * 4
proc_ptr5: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(proc_addr5, t.CPtr))
if proc_ptr5 is None or proc_ptr5[0] != 0:
continue
# 读取 fj 的依赖数量
cnt_addr5: t.CUInt64T = t.CUInt64T(file_dep_counts) + fj * 4
cnt_ptr5: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(cnt_addr5, t.CPtr))
if cnt_ptr5 is None:
continue
# Decrement the in-degree of files that depend on this one
for fj in range(int(file_count)):
proc_addr5: uint64 = uint64(file_processed) + fj * 4
proc_ptr5: int* = (int*)(proc_addr5)
if proc_ptr5 is None or proc_ptr5[0] != 0: continue
# Read fj's dependency count
cnt_addr5: uint64 = uint64(file_dep_counts) + fj * 4
cnt_ptr5: int* = (int*)(cnt_addr5)
if cnt_ptr5 is None: continue
fj_dep_cnt: int = cnt_ptr5[0]
for di in range(fj_dep_cnt):
for di in range(int(fj_dep_cnt)):
dep_slot5: str = file_deps_buf + fj * Phase1.MAX_DEP_EDGES * Phase1.SHA1_LEN + di * Phase1.SHA1_LEN
if string.strcmp(dep_slot5, target_sha1) == 0:
ind_addr5: t.CUInt64T = t.CUInt64T(file_indegrees) + fj * 4
ind_ptr5: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(ind_addr5, t.CPtr))
if ind_ptr5 is not None:
ind_ptr5[0] = ind_ptr5[0] - 1
ind_addr5: uint64 = uint64(file_indegrees) + fj * 4
ind_ptr5: int* = (int*)(ind_addr5)
if ind_ptr5 is not None: ind_ptr5[0] = ind_ptr5[0] - 1
break
# 处理循环依赖:环内优先输出"被依赖者"(被其余剩余文件依赖数量多者先)。
#
# 背景:容器按需注入(string 用 list 泛型 → 依赖 _list)与真实 import
# (_list import string)形成伪环,Kahn 无法完全归零。若按扫描顺序追加,
# 依赖者(astaux)会先于被依赖者(base)翻译 → 跨模块 vtable TypeStr
# 未注册 → 误杀。被依赖者先翻译,其 stub/TypeStr/模板先就绪,依赖者
# 翻译时可引用(函数/全局声明由 1a-pre 的 vpi 兜底)。
# Handle cycles: within a cycle emit the more-depended-upon files first.
# On-demand container injection (e.g. string -> _list) plus real imports form pseudo-cycles Kahn cannot fully drain; scan order would translate dependents first -> unregistered cross-module vtable TypeStr.
if out_idx < file_count:
rem_idx: t.CPtr = stdlib.malloc(4 * file_count)
rem_depcnt: t.CPtr = stdlib.malloc(4 * file_count)
rem_idx: int* = (int*)(stdlib.malloc(4 * file_count))
rem_depcnt: int* = (int*)(stdlib.malloc(4 * file_count))
rem_count: int = 0
if rem_idx is not None and rem_depcnt is not None:
for fi in range(file_count):
proc_addr7: t.CUInt64T = t.CUInt64T(file_processed) + fi * 4
proc_ptr7: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(proc_addr7, t.CPtr))
for fi in range(int(file_count)):
proc_addr7: uint64 = uint64(file_processed) + fi * 4
proc_ptr7: int* = (int*)(proc_addr7)
if proc_ptr7 is not None and proc_ptr7[0] == 0:
rem_idx[rem_count] = fi
rem_depcnt[rem_count] = 0
rem_count += 1
# 计算每个剩余文件的被依赖数(被其余剩余文件依赖)
for i_a in range(rem_count):
# Compute each remaining file's dependents count (among the remaining files)
for i_a in range(int(rem_count)):
fi_a: int = rem_idx[i_a]
target_sha1_a: str = file_sha1s_buf + fi_a * Phase1.SHA1_LEN
for i_b in range(rem_count):
for i_b in range(int(rem_count)):
fi_b: int = rem_idx[i_b]
if fi_b == fi_a:
continue
cnt_addr_b: t.CUInt64T = t.CUInt64T(file_dep_counts) + fi_b * 4
cnt_ptr_b: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(cnt_addr_b, t.CPtr))
if cnt_ptr_b is None:
continue
if fi_b == fi_a: continue
cnt_addr_b: uint64 = uint64(file_dep_counts) + fi_b * 4
cnt_ptr_b: int* = (int*)(cnt_addr_b)
if cnt_ptr_b is None: continue
cnt_b: int = cnt_ptr_b[0]
for di_b in range(cnt_b):
for di_b in range(int(cnt_b)):
dep_slot_b: str = file_deps_buf + fi_b * Phase1.MAX_DEP_EDGES * Phase1.SHA1_LEN + di_b * Phase1.SHA1_LEN
if string.strcmp(dep_slot_b, target_sha1_a) == 0:
rem_depcnt[i_a] = rem_depcnt[i_a] + 1
break
# 冒泡降序(被依赖数多者在前)
for i_a in range(rem_count):
for j_a in range(rem_count - 1):
# Bubble sort descending (more dependents first)
for i_a in range(int(rem_count)):
for j_a in range(int(rem_count - 1)):
if rem_depcnt[j_a] < rem_depcnt[j_a + 1]:
tmp_i: int = rem_idx[j_a]
rem_idx[j_a] = rem_idx[j_a + 1]
@@ -373,35 +264,32 @@ def _TopoSortFiles(mb: memhub.MemBuddy | t.CPtr,
tmp_d: int = rem_depcnt[j_a]
rem_depcnt[j_a] = rem_depcnt[j_a + 1]
rem_depcnt[j_a + 1] = tmp_d
# 按序输出剩余文件
for i_a in range(rem_count):
# Emit the remaining files in order
for i_a in range(int(rem_count)):
fi_a2: int = rem_idx[i_a]
proc_addr8: t.CUInt64T = t.CUInt64T(file_processed) + fi_a2 * 4
proc_ptr8: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(proc_addr8, t.CPtr))
if proc_ptr8 is None or proc_ptr8[0] != 0:
continue
proc_addr8: uint64 = uint64(file_processed) + fi_a2 * 4
proc_ptr8: int* = (int*)(proc_addr8)
if proc_ptr8 is None or proc_ptr8[0] != 0: continue
target_sha1_2: str = file_sha1s_buf + fi_a2 * Phase1.SHA1_LEN
for ri in range(result.Count):
ea_r2: t.CUInt64T = t.CUInt64T(result.Entries) + ri * entry_size
ent_r2: IncludesScanner.FileEntry | t.CPtr = (IncludesScanner.FileEntry | t.CPtr)(t.CVoid(ea_r2, t.CPtr))
for ri in range(int(result.Count)):
ea_r2: uint64 = uint64(result.Entries) + ri * entry_size
ent_r2: IncludesScanner.FileEntry* = IncludesScanner.FileEntry*(ea_r2)
if ent_r2 is not None and ent_r2.Sha1 is not None:
if string.strcmp(ent_r2.Sha1, target_sha1_2) == 0:
out_idx_addr2: t.CUInt64T = t.CUInt64T(out_order) + out_idx * 4
out_int_ptr2: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(out_idx_addr2, t.CPtr))
if out_int_ptr2 is not None:
out_int_ptr2[0] = ri
out_idx_addr2: uint64 = uint64(out_order) + out_idx * 4
out_int_ptr2: int* = (int*)(out_idx_addr2)
if out_int_ptr2 is not None: out_int_ptr2[0] = ri
out_idx += 1
break
proc_ptr8[0] = 1
stdlib.free(rem_idx)
stdlib.free(rem_depcnt)
# 写入输出数量
out_count_ptr: t.CPtr = (t.CInt | t.CPtr)(t.CVoid(out_count, t.CPtr))
if out_count_ptr is not None:
out_count_ptr[0] = out_idx
# Write the output count
out_count_ptr: int* = (int*)(out_count)
if out_count_ptr is not None: out_count_ptr[0] = out_idx
# 释放扁平数组
# Free the flat arrays
mb.free(file_sha1s_buf)
mb.free(file_deps_buf)
mb.free(file_dep_counts)
+13 -35
View File
@@ -1,41 +1,19 @@
import t, c
from stdint import *
import string
import stdlib
import memhub
import viperlib
import w32.fileio as fileio
import w32.win32file
import w32.win32base
import ast
import llvmlite
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesTranslator as HandlesTranslator
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.IncludesScanner as IncludesScanner
import lib.Stub.StubMerger as StubMerger
import lib.core.Gen.Generator as GenGenerator
import lib.core.CompilerFactory as CompilerFactory
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Projectrans.Config as Config
import lib.Stub.StubGen.Converter as StubConverter
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
# 全局 mbuddy 指针
_mbuddy: memhub.MemBuddy | t.CPtr
# Global mbuddy pointer
_mbuddy: memhub.MemBuddy*
# 源代码缓冲区大小(1MB)
SRC_BUF_SIZE: t.CDefine = 1048576
# Source buffer size (1MB)
define SRC_BUF_SIZE = 1048576
# vpi 缓冲区大小(256KB)
PYI_BUF_SIZE: t.CSizeT = 262144
# vpi buffer size (256KB).
# Compile-time constant via define: a module-level global would emit a strong symbol per module, causing llvm-link errors.
define PYI_BUF_SIZE = 262144
# 拓扑排序相关常量
MAX_CLASSES: t.CDefine = 512
MAX_FILE_DEPS: t.CDefine = 256
MAX_DEP_EDGES: t.CDefine = 16
SHA1_LEN: t.CDefine = 17
CLASS_NAME_LEN: t.CDefine = 64
# Topological sort constants
define MAX_CLASSES = 512
define MAX_FILE_DEPS = 256
define MAX_DEP_EDGES = 16
define SHA1_LEN = 17
define CLASS_NAME_LEN = 64
File diff suppressed because it is too large. Load diff
+346 -372
View File
@@ -1,166 +1,169 @@
import t, c
from stdint import *
import memhub
import string
import stdio
import stdlib
import viperlib
import lib.core.VLogger as VLogger
from json.__parser import parse as json_parse
from json import JsonValue, JSON_OBJECT, JSON_INT
import w32.fileio as fileio
import win32.fileio as fileio
# ============================================================
# Config - 工程配置
# ============================================================
# Config - project configuration
# 全局 mbuddy 指针
_mbuddy: memhub.MemManager | t.CPtr
# Global mbuddy pointer
_mbuddy: memhub.MemManager*
# 配置值(从 project.vpj 加载)
# 默认 None/0:未加载 project.vpj 时字段为 null,调用方 is not None
# 检查才能正确兜底(否则垃圾指针通过检查 → 用非法路径 → 崩溃,
# 尤其非工作区执行无配置时)。
# Config values (loaded from project.vpj); default None/0: callers must guard with `is not None` (else garbage pointer crash)
SourceDir: str = None
BuildDir: str = None
TempDir: str = None # = {BuildDir}/temp(自动计算)
OutputDir: str = None # = {BuildDir}/output(自动计算)
TempDir: str = None # = {BuildDir}/temp (auto-computed)
OutputDir: str = None # = {BuildDir}/output (auto-computed)
ProjectName: str = None
ProjectVersion: str = None
CompilerCmd: str = None
CompilerFlags: str = None # 编译器参数(空格分隔,从 compiler.flags 数组拼接)
CompilerFlags: str = None # compiler flags (space-separated, joined from compiler.flags array)
LinkerCmd: str = None
LinkerFlags: str = None # 链接器参数(空格分隔,从 linker.flags 数组拼接)
LinkerFlags: str = None # linker flags (space-separated, joined from linker.flags array)
LinkerOutput: str = None
LinkerStaticLibs: str = None # explicit static libs (space-separated abs paths, from linker.static_libs array)
LinkerDynamicLibs: str = None # explicit dynamic libs (space-separated abs paths, from linker.dynamic_libs array, .dll/.so)
TargetTriple: str = None
TargetDataLayout: str = None
ProjectDir: str = None # project.vpj 所在目录(resolve_paths 设置)
Sha1SliceLevel: int = 0 # SHA1 切片层数(目录分散等级)
ProjectDir: str = None # directory containing project.vpj (set by resolve_paths)
Sha1SliceLevel: int = 0 # SHA1 slice level (directory dispersion depth)
StrictMode: int = 0
IncludesDir: str = None # includes 目录路径(从 includes 数组首元素,兼容单目录场景)
IncludesDirs: str = None # 全部 includes 目录(空格分隔,多 includes 目录支持)
IncludesDir: str = None # includes dir path (first element of includes array, single-dir compat)
IncludesDirs: str = None # all includes dirs (space-separated, multi-dir support)
Qt6Path: str = None # runtime.qt6_path: Qt6 install root (optional)
Qt6Dlls: str = None # runtime.qt6_dlls joined (space-separated DLL names; optional)
Qt6Plugins: str = None # runtime.qt6_plugins joined (space-separated paths relative to <qt6_path>/plugins; optional)
# includes 预编译缓存目录后缀(asan 模式用 .binary_asan 隔离,避免
# asan 插桩 obj 与非 asan obj 混链:asan obj 引用 __asan_init 等符号,
# 非 asan 链接不提供 asan 库 → undefined reference)
# includes prebuilt cache dir suffix (asan mode uses .binary_asan to isolate instrumented obj)
_inc_bin_suffix: str = ".binary"
# Emission backend: single-track MLIR (since 2026-10-03).
# The tree now uses includes/mlirlite; BuildPipeline always runs mlir-opt -> mlir-translate to lower back to LLVM IR.
def Load_project_config(path: str) -> int:
"""加载工程配置文件(project.vpj)
Args:
path: 配置文件路径
Returns:
0 表示成功,非 0 表示失败
"""
"""Load the project config file (project.vpj); 0=success, non-zero=failure."""
global SourceDir, BuildDir, TempDir, OutputDir, ProjectName, ProjectVersion
global CompilerCmd, CompilerFlags, LinkerCmd, LinkerFlags, LinkerOutput
global CompilerCmd, CompilerFlags, LinkerCmd, LinkerFlags, LinkerOutput, LinkerStaticLibs, LinkerDynamicLibs
global TargetTriple, TargetDataLayout, Sha1SliceLevel, StrictMode
global IncludesDir, IncludesDirs
global Qt6Path, Qt6Dlls, Qt6Plugins
if path is None: return 1
if _mbuddy is None: return 1
# 打开文件
f: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.R)
# Open file
f: fileio.File* = fileio.File(path, fileio.MODE.R)
if f is None: return 1
if f.closed: return 1
# 分配读取缓冲区
CFG_BUF_SIZE: t.CSizeT = 8192
# Allocate read buffer
CFG_BUF_SIZE: sizet = 8192
buf: bytes = _mbuddy.alloc(CFG_BUF_SIZE)
if buf is None:
f.close()
return 1
bytes_read: t.CInt64T = f.read_all(buf, CFG_BUF_SIZE)
bytes_read: int64 = f.read_all(buf, CFG_BUF_SIZE)
f.close()
if bytes_read <= 0: return 1
# 解析 JSON
root: JsonValue | t.CPtr = json_parse(_mbuddy, buf)
# Parse JSON
root: JsonValue* = json_parse(_mbuddy, buf)
if root is None: return 1
if not root.is_object(): return 1
# 读取顶层字段(使用显式 __getitem__ 调用,兼容旧编译器二进制)
sd_val: JsonValue | t.CPtr = root["source_dir"]
# Read top-level fields (explicit __getitem__ calls for old compiler binaries)
sd_val: JsonValue* = root["source_dir"]
SourceDir = sd_val.as_string() if sd_val is not None else None
# build_dir: 统一构建目录(默认 ./.tpv_build),temp 和 output 作为其子目录
bd_val: JsonValue | t.CPtr = root["build_dir"]
if bd_val is not None and bd_val.is_string():
BuildDir = bd_val.as_string()
else:
BuildDir = "./.tpv_build"
name_val: JsonValue | t.CPtr = root["name"]
# build_dir: unified build dir (default ./__vpcbuild__); temp and output are subdirs
bd_val: JsonValue* = root["build_dir"]
if bd_val is not None and bd_val.is_string(): BuildDir = bd_val.as_string()
else: BuildDir = "./__vpcbuild__"
name_val: JsonValue* = root["name"]
ProjectName = name_val.as_string() if name_val is not None else None
ver_val: JsonValue | t.CPtr = root["version"]
ver_val: JsonValue* = root["version"]
ProjectVersion = ver_val.as_string() if ver_val is not None else None
# 读取 compiler 子对象
compiler: JsonValue | t.CPtr = root["compiler"]
# Read compiler sub-object
compiler: JsonValue* = root["compiler"]
if compiler is not None and compiler.is_object():
cc_cmd_val: JsonValue | t.CPtr = compiler["cmd"]
cc_cmd_val: JsonValue* = compiler["cmd"]
CompilerCmd = cc_cmd_val.as_string() if cc_cmd_val is not None else None
# 读取 compiler.flags 数组,拼接成空格分隔的字符串
cc_flags_val: JsonValue | t.CPtr = compiler["flags"]
# Read compiler.flags array, join into a space-separated string
cc_flags_val: JsonValue* = compiler["flags"]
CompilerFlags = _join_flags_array(cc_flags_val)
# 读取 linker 子对象
linker: JsonValue | t.CPtr = root["linker"]
# Read linker sub-object
linker: JsonValue* = root["linker"]
if linker is not None and linker.is_object():
ld_cmd_val: JsonValue | t.CPtr = linker["cmd"]
ld_cmd_val: JsonValue* = linker["cmd"]
LinkerCmd = ld_cmd_val.as_string() if ld_cmd_val is not None else None
ld_out_val: JsonValue | t.CPtr = linker["output"]
ld_out_val: JsonValue* = linker["output"]
LinkerOutput = ld_out_val.as_string() if ld_out_val is not None else None
# 读取 linker.flags 数组,拼接成空格分隔的字符串
ld_flags_val: JsonValue | t.CPtr = linker["flags"]
# Read linker.flags array, join into a space-separated string
ld_flags_val: JsonValue* = linker["flags"]
LinkerFlags = _join_flags_array(ld_flags_val)
# mingw-w64:同时链接 -lmsvcrt 与 -lucrt 时,GNU ld 会从两个 import 库各拉出一个都定义
# __imp_<name> 的成员(现场:__imp_ldexp 同时出现在 libmsvcrt 与
# libapi-ms-win-crt-math),直接报 "multiple definition"。lld 按需解析 import 库不会冲突,
# 仓库默认链接命令也一直是 lld;项目配置漏写 -fuse-ld 时在这里补齐,避免逐个测试改配置。
if LinkerCmd is not None and LinkerFlags is not None and string.strstr(LinkerCmd, "clang") is not None:
if string.strstr(LinkerFlags, "-fuse-ld") is None and string.strstr(LinkerFlags, "-lucrt") is not None:
lf_cap: sizet = string.strlen(LinkerFlags) + 32
lf_new: str = _mbuddy.alloc(lf_cap)
if lf_new is not None:
viperlib.snprintf(lf_new, lf_cap, "-fuse-ld=lld %s", LinkerFlags)
LinkerFlags = lf_new
# Read linker.static_libs array: explicit static libs (.a/.obj); joined raw here,
# then resolved to abs paths against project_dir in resolve_paths.
sl_val: JsonValue* = linker["static_libs"]
LinkerStaticLibs = _join_flags_array(sl_val)
# Read linker.dynamic_libs array: explicit dynamic libs (.dll/.so); joined raw here,
# then resolved to abs paths against project_dir in resolve_paths.
dl_val: JsonValue* = linker["dynamic_libs"]
LinkerDynamicLibs = _join_flags_array(dl_val)
# 读取 target 子对象
target: JsonValue | t.CPtr = root["target"]
# Read target sub-object
target: JsonValue* = root["target"]
if target is not None and target.is_object():
tgt_triple_val: JsonValue | t.CPtr = target["triple"]
tgt_triple_val: JsonValue* = target["triple"]
TargetTriple = tgt_triple_val.as_string() if tgt_triple_val is not None else None
tgt_dl_val: JsonValue | t.CPtr = target["datalayout"]
tgt_dl_val: JsonValue* = target["datalayout"]
TargetDataLayout = tgt_dl_val.as_string() if tgt_dl_val is not None else None
# 读取 options 子对象
# Sha1SliceLevel: SHA1 切片层数(默认 1,每层取 SHA1 前 2 字符作为子目录)
# level=0 → 不切片,文件直接放 base_dir
# level=1 → b7/{sha1}.ext
# level=2 → b7/90/{sha1}.ext
options: JsonValue | t.CPtr = root["options"]
# Read options sub-object
# Sha1SliceLevel: SHA1 slice level (default 1; each level uses 2 SHA1 chars: 0=none, 1=b7/, 2=b7/90/)
options: JsonValue* = root["options"]
Sha1SliceLevel = 1
StrictMode = 1
if options is not None and options.is_object():
sl: JsonValue | t.CPtr = options["sha1_slice_level"]
if sl is not None and sl.is_int():
Sha1SliceLevel = sl.as_int()
sm: JsonValue | t.CPtr = options["strict_mode"]
if sm is not None and sm.is_bool():
StrictMode = 1 if sm.as_bool() else 0
sl: JsonValue* = options["sha1_slice_level"]
if sl is not None and sl.is_int(): Sha1SliceLevel = int(sl.as_int())
sm: JsonValue* = options["strict_mode"]
if sm is not None and sm.is_bool(): StrictMode = 1 if sm.as_bool() else 0
# 读取 includes 数组(支持多目录:首元素存 IncludesDir,全部拼接存 IncludesDirs)
includes_val: JsonValue | t.CPtr = root["includes"]
# Read includes array (multi-dir: first element -> IncludesDir, all joined -> IncludesDirs)
includes_val: JsonValue* = root["includes"]
if includes_val is not None and includes_val.is_array():
arr_len: t.CSizeT = len(includes_val)
arr_len: sizet = len(includes_val)
inc_buf: bytes = stdlib.malloc(2048)
if inc_buf is not None:
inc_buf[0] = '\0'
inc_pos: t.CSizeT = 0
for inc_i in range(arr_len):
inc_item: JsonValue | t.CPtr = includes_val.get_item(inc_i)
if inc_item is None or not inc_item.is_string():
continue
inc_pos: sizet = 0
for inc_i in range(int(arr_len)):
inc_item: JsonValue* = includes_val.get_item(inc_i)
if inc_item is None or not inc_item.is_string(): continue
inc_str: str = inc_item.as_string()
inc_slen: t.CSizeT = string.strlen(inc_str)
if inc_i == 0:
IncludesDir = inc_str
# 拼接进 IncludesDirs(空格分隔)
inc_slen: sizet = string.strlen(inc_str)
if inc_i == 0: IncludesDir = inc_str
# Append into IncludesDirs (space-separated)
if inc_pos + inc_slen + 1 < 2048:
if inc_pos > 0:
inc_buf[inc_pos] = ' '
@@ -169,56 +172,53 @@ def Load_project_config(path: str) -> int:
inc_pos += inc_slen
inc_buf[inc_pos] = '\0'
IncludesDirs = inc_buf
if IncludesDir is None:
IncludesDir = inc_buf
if IncludesDir is None: IncludesDir = inc_buf
# Read runtime sub-object (optional): declared runtime deps deployed next to the exe.
# qt6_dlls / qt6_plugins join via _join_flags_array (space-separated, same口径 as linker.static_libs).
runtime: JsonValue* = root["runtime"]
if runtime is not None and runtime.is_object():
qp_val: JsonValue* = runtime["qt6_path"]
Qt6Path = qp_val.as_string() if qp_val is not None else None
qd_val: JsonValue* = runtime["qt6_dlls"]
Qt6Dlls = _join_flags_array(qd_val)
qpl_val: JsonValue* = runtime["qt6_plugins"]
Qt6Plugins = _join_flags_array(qpl_val)
return 0
def _join_flags_array(flags_val: JsonValue | t.CPtr) -> str:
"""将 JSON 字符串数组拼接成空格分隔的字符串
用于 linker.flags 和 compiler.flags 数组。
用 while + 索引遍历,避免 list 迭代器 bug。
Args:
flags_val: JsonValue,应为字符串数组
Returns:
空格分隔的字符串,或 None 如果 flags_val 为 None 或非数组或空数组
"""
def _join_flags_array(flags_val: JsonValue*) -> str:
"""Join a JSON string array into a space-separated string; None if empty/invalid."""
if flags_val is None: return None
if not flags_val.is_array(): return None
arr_len: t.CSizeT = len(flags_val)
if arr_len == 0:
return None
arr_len: sizet = len(flags_val)
if arr_len == 0: return None
# 第一遍:计算总长度(所有 flag 长度 + 空格分隔符)
total_len: t.CSizeT = 0
for i in range(arr_len):
item: JsonValue | t.CPtr = flags_val.get_item(i)
# First pass: compute total length (all flags + space separators)
total_len: sizet = 0
for i in range(int(arr_len)):
item: JsonValue* = flags_val.get_item(i)
if item is not None and item.is_string():
s: str = item.as_string()
if s is not None:
total_len += string.strlen(s) + 1 # +1 for space
if total_len == 0:
return None
if total_len == 0: return None
# 分配缓冲区(total_len 已含每个 flag 后的空格,最后一个是 '\0')
# Allocate buffer (total_len includes a trailing space per flag; last one becomes '\0')
buf: str = _mbuddy.alloc(total_len + 1)
if buf is None:
return None
if buf is None: return None
# 第二遍:拼接(flag 之间用空格分隔)
pos: t.CSizeT = 0
for i in range(arr_len):
# Second pass: join (flags separated by spaces)
pos: sizet = 0
for i in range(int(arr_len)):
item = flags_val.get_item(i)
if item is not None and item.is_string():
s = item.as_string()
if s is not None:
slen: t.CSizeT = string.strlen(s)
slen: sizet = string.strlen(s)
if pos > 0:
buf[pos] = ' '
pos += 1
@@ -229,138 +229,118 @@ def _join_flags_array(flags_val: JsonValue | t.CPtr) -> str:
def _join_path(rel: str, project_dir: str) -> str:
"""将相对路径与 project_dir 拼接为绝对路径
"""Join a relative path with project_dir, expanding env vars and normalizing it."""
if rel is None: return None
# Env var expansion: only when '%' present (fileio.ExpandEnvironmentPath returns None on
# failure/no '%'); on failure keep the original string unchanged.
ep: str = fileio.ExpandEnvironmentPath(rel)
if ep is not None: rel = ep
# Absolute paths (starting with / or \) stay unchanged
if rel[0] == '/' or rel[0] == '\\': return rel
# Skip joining when rel already has the project_dir prefix (avoids double paths); boundary at separator or end
if project_dir is not None:
pdl: sizet = string.strlen(project_dir)
if pdl > 0 and string.strncmp(rel, project_dir, pdl) == 0:
if rel[pdl] == '/' or rel[pdl] == '\\' or rel[pdl] == '\0': return rel
- ./App + Test → Test/App
- ./temp + Test → Test/temp
- ../includes + Test → Test/../includes(OS 解析为 includes)
- 绝对路径保持不变
- None 返回 None
rel_len: sizet = string.strlen(rel)
dir_len: sizet = string.strlen(project_dir)
拼接结果经 _normalize_path 规范化:./Test/../includes → ./includes,
消除 .. 段,保证路径显示干净且相对寻址正确。
"""
if rel is None:
return None
# 绝对路径(以 / 或 \ 开头)保持不变
if rel[0] == '/' or rel[0] == '\\':
return rel
rel_len: t.CSizeT = string.strlen(rel)
dir_len: t.CSizeT = string.strlen(project_dir)
# 处理 ../ 或 ..\\ 前缀:拼接 project_dir/../...
# Handle ../ or ..\ prefix: join project_dir/../...
if rel_len >= 3 and rel[0] == '.' and rel[1] == '.':
if rel[2] == '/' or rel[2] == '\\':
if dir_len == 0:
return rel
total_len: t.CSizeT = dir_len + 1 + rel_len + 1
if dir_len == 0: return rel
total_len: sizet = dir_len + 1 + rel_len + 1
buf: str = _mbuddy.alloc(total_len)
if buf is None:
return rel
if buf is None: return rel
viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel)
return _normalize_path(buf)
# 处理 ./ 或 .\\ 前缀:拼接 project_dir + rel[2:]
rel_off: t.CSizeT = 0
# Handle ./ or .\ prefix: join project_dir + rel[2:]
rel_off: sizet = 0
if rel_len >= 2 and rel[0] == '.':
if rel[1] == '/' or rel[1] == '\\':
rel_off = 2
if rel[1] == '/' or rel[1] == '\\': rel_off = 2
else:
return rel # 不是 ./ 开头,保持不变
return rel # not starting with ./: keep unchanged
else:
return rel # 不是 . 开头,保持不变
return rel # not starting with .: keep unchanged
if dir_len == 0:
# project_dir 为空(当前目录),去掉 ./ 前缀即可
# project_dir empty (current dir): just strip the ./ prefix
return rel + rel_off
# 拼接: project_dir + "/" + rel[rel_off:]
# Join: project_dir + "/" + rel[rel_off:]
total_len = dir_len + 1 + (rel_len - rel_off) + 1
buf = _mbuddy.alloc(total_len)
if buf is None:
return rel # 分配失败,返回原路径
return rel # allocation failed: return original path
viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel + rel_off)
return _normalize_path(buf)
def _normalize_path(path: str) -> str:
"""规范化路径:解析 . 和 .. 段,合并多余分隔符
例: ./test/BuddyTest/../../includes → ./includes
./test/BuddyTest/App → ./test/BuddyTest/App
绝对路径(/ 或 \\ 开头,含盘符 C:)保持不变。
返回 _mbuddy 分配的字符串;失败时返回原路径。
"""
if path is None:
return None
plen: t.CSizeT = string.strlen(path)
if plen == 0:
return path
# 绝对路径(/ 或 \ 开头,或盘符如 C:)不规范化
if path[0] == '/' or path[0] == '\\':
return path
if plen >= 2 and path[1] == ':':
return path
# 输出缓冲(最坏情况 = 输入长度 + 可能的前缀 ./)
"""Normalize a path by resolving . and .. segments; absolute paths pass through."""
if path is None: return None
plen: sizet = string.strlen(path)
if plen == 0: return path
# Absolute paths (/ or \ start, or drive letter like C:) are not normalized
if path[0] == '/' or path[0] == '\\': return path
if plen >= 2 and path[1] == ':': return path
# Output buffer (worst case = input length + possible ./ prefix)
out: str = _mbuddy.alloc(plen + 3)
if out is None:
return path
seg_start: t.CSizeT = 0
out_len: t.CSizeT = 0
if out is None: return path
seg_start: sizet = 0
out_len: sizet = 0
has_seg: int = 0
i: t.CSizeT = 0
while i <= plen:
ch: t.CChar = '\0'
if i < plen:
ch = path[i]
for i in range(int(plen + 1)):
ch: char = '\0'
if i < plen: ch = path[i]
if ch == '/' or ch == '\\' or ch == '\0':
seg_len: t.CSizeT = i - seg_start
seg_len: sizet = i - seg_start
if seg_len > 0:
# 单段 ".":跳过
if seg_len == 1 and path[seg_start] == '.':
pass
# 单段 "..":弹出上一段(无段可弹时保留,如 ../includes)
# Single "." segment: skip
if seg_len == 1 and path[seg_start] == '.': pass
# Single ".." segment: pop previous; leading ".." with nothing to pop must be kept (like Python normpath)
elif seg_len == 2 and path[seg_start] == '.' and path[seg_start + 1] == '.':
if out_len > 0:
# 回退到上一个分隔符
k: t.CSizeT = out_len
while k > 0 and out[k - 1] != '/' and out[k - 1] != '\\':
k -= 1
if k > 0:
out_len = k - 1
else:
out_len = 0
# 仍有剩余段时保持分隔符逻辑
if out_len > 0:
has_seg = 1
else:
has_seg = 0
# Whether the last segment is already a leading ".." (standalone: at start or preceded by a separator)
last_is_dotdot: int = 0
if out_len >= 2 and out[out_len - 1] == '.' and out[out_len - 2] == '.':
if out_len == 2: last_is_dotdot = 1
elif out[out_len - 3] == '/' or out[out_len - 3] == '\\': last_is_dotdot = 1
if out_len > 0 and last_is_dotdot == 0:
# Back up to the previous separator
k: sizet = out_len
while k > 0 and out[k - 1] != '/' and out[k - 1] != '\\': k -= 1
if k > 0: out_len = k - 1
else: out_len = 0
# Keep separator logic when segments remain
if out_len > 0: has_seg = 1
else: has_seg = 0
else:
# 无段可弹:保留 ..
out[0] = '.'
out[1] = '.'
out_len = 2
# No normal segment to pop: append ".." as a normal segment (keep leading ..)
if has_seg > 0:
out[out_len] = '/'
out_len += 1
out[out_len] = '.'
out[out_len + 1] = '.'
out_len += 2
has_seg = 1
else:
# 普通段:追加分隔符 + 段
# Normal segment: append separator + segment
if has_seg > 0:
out[out_len] = '/'
out_len += 1
jj: t.CSizeT = 0
while jj < seg_len:
for jj in range(int(seg_len)):
out[out_len] = path[seg_start + jj]
out_len += 1
jj += 1
has_seg = 1
seg_start = i + 1
i += 1
if has_seg == 0:
# 全部段被消掉(如 ./..):返回当前目录
# All segments consumed (e.g. ./..): return current dir
return "./"
# 相对路径补回 ./ 前缀(与原路径风格一致;以 .. 开头的除外)
# Re-add ./ prefix for relative paths (matches original style; except those starting with ..)
if out[0] != '.' and out[0] != '/':
# 右移 2 位留出 "./"(从后往前拷贝,避免覆盖源字符)
mv: t.CSizeT = out_len
# Shift right by 2 to make room for "./" (copy back-to-front to avoid overwriting)
mv: sizet = out_len
while mv > 0:
mv -= 1
out[mv + 2] = out[mv]
@@ -372,32 +352,20 @@ def _normalize_path(path: str) -> str:
def resolve_paths(project_dir: str) -> int:
"""解析并规范化工程路径(src/build/includes)
将 project.vpj 中的相对路径(以 ./ 开头)转换为基于 project_dir 的路径。
BuildDir 解析后,TempDir = {BuildDir}/temp,OutputDir = {BuildDir}/output。
Args:
project_dir: 工程根目录(project.vpj 所在目录)
Returns:
0 表示成功,非 0 表示失败
"""
"""Resolve and normalize project paths (src/build/includes); 0=success."""
global SourceDir, BuildDir, TempDir, OutputDir, IncludesDir, IncludesDirs
global ProjectDir
if project_dir is None:
return 1
if _mbuddy is None:
return 1
global ProjectDir, LinkerStaticLibs, LinkerDynamicLibs
if project_dir is None: return 1
if _mbuddy is None: return 1
ProjectDir = project_dir
SourceDir = _join_path(SourceDir, project_dir)
BuildDir = _join_path(BuildDir, project_dir)
# TempDir = {BuildDir}/temp
# TempDir = {BuildDir}/temp; OutputDir = {BuildDir}/output
if BuildDir is not None:
bd_len: t.CSizeT = string.strlen(BuildDir)
bd_len: sizet = string.strlen(BuildDir)
temp_buf: str = _mbuddy.alloc(bd_len + 8)
if temp_buf is not None:
viperlib.snprintf(temp_buf, bd_len + 8, "%s/temp", BuildDir)
@@ -408,24 +376,21 @@ def resolve_paths(project_dir: str) -> int:
viperlib.snprintf(output_buf, bd_len + 8, "%s/output", BuildDir)
OutputDir = output_buf
# includes 路径可能是 ../includes 形式,需要基于 project_dir 解析
# IncludesDir(首元素)与 IncludesDirs(全部,空格分隔)逐项规范化
# includes paths may be of the form ../includes, resolved against project_dir.
# Normalize IncludesDir (first element) and IncludesDirs (all, space-separated) item by item.
if IncludesDirs is not None:
id_total: t.CSizeT = string.strlen(IncludesDirs)
id_total: sizet = string.strlen(IncludesDirs)
id_out: str = _mbuddy.alloc(id_total * 3 + 64)
if id_out is not None:
id_out[0] = '\0'
id_pos: t.CSizeT = 0
id_i: t.CSizeT = 0
id_pos: sizet = 0
id_i: sizet = 0
while id_i < id_total:
while id_i < id_total and IncludesDirs[id_i] == ' ':
id_i += 1
if id_i >= id_total:
break
id_start: t.CSizeT = id_i
while id_i < id_total and IncludesDirs[id_i] != ' ':
id_i += 1
seg_len: t.CSizeT = id_i - id_start
while id_i < id_total and IncludesDirs[id_i] == ' ': id_i += 1
if id_i >= id_total: break
id_start: sizet = id_i
while id_i < id_total and IncludesDirs[id_i] != ' ': id_i += 1
seg_len: sizet = id_i - id_start
if seg_len > 0:
seg_buf: str = _mbuddy.alloc(seg_len + 1)
if seg_buf is not None:
@@ -433,7 +398,7 @@ def resolve_paths(project_dir: str) -> int:
seg_buf[seg_len] = '\0'
joined: str = _join_path(seg_buf, project_dir)
if joined is not None:
jl: t.CSizeT = string.strlen(joined)
jl: sizet = string.strlen(joined)
if id_pos > 0:
id_out[id_pos] = ' '
id_pos += 1
@@ -441,74 +406,108 @@ def resolve_paths(project_dir: str) -> int:
id_pos += jl
id_out[id_pos] = '\0'
IncludesDirs = id_out
if IncludesDir is not None:
IncludesDir = _join_path(IncludesDir, project_dir)
# linker.flags 中 `-T <脚本>` 的链接脚本路径同样基于 project_dir 解析,
# 否则 subprocess 按进程 cwd(TransPyC/)找 linker.ld → ld.lld 报
# "cannot find linker script linker.ld"
if IncludesDir is not None: IncludesDir = _join_path(IncludesDir, project_dir)
# linker.static_libs explicit paths resolved segment-by-segment to abs paths against project_dir
if LinkerStaticLibs is not None:
sl_total: sizet = string.strlen(LinkerStaticLibs)
sl_out: str = _mbuddy.alloc(sl_total * 3 + 64)
if sl_out is not None:
sl_out[0] = '\0'
sl_pos: sizet = 0
sl_i: sizet = 0
while sl_i < sl_total:
while sl_i < sl_total and LinkerStaticLibs[sl_i] == ' ': sl_i += 1
if sl_i >= sl_total: break
sl_start: sizet = sl_i
while sl_i < sl_total and LinkerStaticLibs[sl_i] != ' ': sl_i += 1
seg_len: sizet = sl_i - sl_start
if seg_len > 0:
seg_buf: str = _mbuddy.alloc(seg_len + 1)
if seg_buf is not None:
string.strncpy(seg_buf, LinkerStaticLibs + sl_start, seg_len)
seg_buf[seg_len] = '\0'
joined: str = _join_path(seg_buf, project_dir)
if joined is not None:
jl: sizet = string.strlen(joined)
if sl_pos > 0:
sl_out[sl_pos] = ' '
sl_pos += 1
string.strcpy(sl_out + sl_pos, joined)
sl_pos += jl
sl_out[sl_pos] = '\0'
LinkerStaticLibs = sl_out
# linker.dynamic_libs explicit files (.dll/.so) likewise resolved to abs paths against project_dir.
if LinkerDynamicLibs is not None:
dl_total: sizet = string.strlen(LinkerDynamicLibs)
dl_out: str = _mbuddy.alloc(dl_total * 3 + 64)
if dl_out is not None:
dl_out[0] = '\0'
dl_pos: sizet = 0
dl_i: sizet = 0
while dl_i < dl_total:
while dl_i < dl_total and LinkerDynamicLibs[dl_i] == ' ': dl_i += 1
if dl_i >= dl_total: break
dl_start: sizet = dl_i
while dl_i < dl_total and LinkerDynamicLibs[dl_i] != ' ': dl_i += 1
seg_len: sizet = dl_i - dl_start
if seg_len > 0:
seg_buf: str = _mbuddy.alloc(seg_len + 1)
if seg_buf is not None:
string.strncpy(seg_buf, LinkerDynamicLibs + dl_start, seg_len)
seg_buf[seg_len] = '\0'
joined: str = _join_path(seg_buf, project_dir)
if joined is not None:
jl: sizet = string.strlen(joined)
if dl_pos > 0:
dl_out[dl_pos] = ' '
dl_pos += 1
string.strcpy(dl_out + dl_pos, joined)
dl_pos += jl
dl_out[dl_pos] = '\0'
LinkerDynamicLibs = dl_out
# -T <script> paths in linker.flags also resolved against project_dir, else ld.lld misses them
_resolve_linker_t_paths(project_dir)
return 0
def _join_path_abs(rel: str, project_dir: str) -> str:
"""将相对路径基于 project_dir 拼接为绝对路径(用于 -T 链接脚本等)
已为绝对路径(/ \\ 开头)或盘符形式(x:)时原样返回。
"""
if rel is None:
return None
rel_len: t.CSizeT = string.strlen(rel)
if rel_len == 0:
return rel
if rel[0] == '/' or rel[0] == '\\':
return rel
if rel_len >= 2 and rel[1] == ':':
return rel
dir_len: t.CSizeT = string.strlen(project_dir)
if dir_len == 0:
return rel
total_len: t.CSizeT = dir_len + 1 + rel_len + 1
"""Join a relative path under project_dir into an absolute path; absolutes unchanged."""
if rel is None: return None
rel_len: sizet = string.strlen(rel)
if rel_len == 0: return rel
if rel[0] == '/' or rel[0] == '\\': return rel
if rel_len >= 2 and rel[1] == ':': return rel
dir_len: sizet = string.strlen(project_dir)
if dir_len == 0: return rel
total_len: sizet = dir_len + 1 + rel_len + 1
buf: str = _mbuddy.alloc(total_len)
if buf is None:
return rel
if buf is None: return rel
viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel)
return buf
def _resolve_linker_t_paths(project_dir: str) -> None:
"""将 LinkerFlags 中 `-T <相对路径>` 的链接脚本路径解析为绝对路径
ld.lld 的 -T 参数若是相对路径,将按 subprocess 进程 cwd 解析而找不到
脚本。逐 token 扫描 LinkerFlags,遇 -T 其后 token 若非绝对路径则基于
project_dir 拼接(与 resolve_paths 对其他相对路径的处理保持一致)。
"""
"""Resolve relative `-T <script>` paths in LinkerFlags against project_dir."""
global LinkerFlags
if LinkerFlags is None or project_dir is None:
return
fl_total: t.CSizeT = string.strlen(LinkerFlags)
if fl_total == 0:
return
if LinkerFlags is None or project_dir is None: return
fl_total: sizet = string.strlen(LinkerFlags)
if fl_total == 0: return
out: str = _mbuddy.alloc(fl_total * 2 + 512)
if out is None:
return
if out is None: return
out[0] = '\0'
out_pos: t.CSizeT = 0
i: t.CSizeT = 0
prev_is_t: int = 0 # 前一 token 是否为 -T
out_pos: sizet = 0
i: sizet = 0
prev_is_t: int = 0 # whether the previous token is -T
while i < fl_total:
# 跳过前导空格
while i < fl_total and LinkerFlags[i] == ' ':
i += 1
if i >= fl_total:
break
start: t.CSizeT = i
while i < fl_total and LinkerFlags[i] != ' ':
i += 1
tok_len: t.CSizeT = i - start
# Skip leading spaces
while i < fl_total and LinkerFlags[i] == ' ': i += 1
if i >= fl_total: break
start: sizet = i
while i < fl_total and LinkerFlags[i] != ' ': i += 1
tok_len: sizet = i - start
is_t_flag: int = 0
if tok_len == 2 and LinkerFlags[start] == '-' and LinkerFlags[start + 1] == 'T':
is_t_flag = 1
# 输出分隔符(前一 token 之后)
if tok_len == 2 and LinkerFlags[start] == '-' and LinkerFlags[start + 1] == 'T': is_t_flag = 1
# Output separator (after the previous token)
if out_pos > 0:
out[out_pos] = ' '
out_pos += 1
@@ -516,14 +515,14 @@ def _resolve_linker_t_paths(project_dir: str) -> None:
string.strcpy(out + out_pos, "-T")
out_pos += 2
elif prev_is_t != 0:
# -T 后的脚本路径:非绝对路径则基于 project_dir 拼接
# Script path after -T: join against project_dir if not absolute
tok_buf: str = _mbuddy.alloc(tok_len + 1)
if tok_buf is not None:
string.memcpy(tok_buf, LinkerFlags + start, tok_len)
tok_buf[tok_len] = '\0'
abs_path: str = _join_path_abs(tok_buf, project_dir)
if abs_path is not None:
p_len: t.CSizeT = string.strlen(abs_path)
p_len: sizet = string.strlen(abs_path)
string.strcpy(out + out_pos, abs_path)
out_pos += p_len
else:
@@ -541,96 +540,71 @@ def _resolve_linker_t_paths(project_dir: str) -> None:
def get_includes_binary_dir() -> str:
"""获取 includes.binary 目录路径(includes 目录 + 缓存后缀)
includes 目录如 ../includes,默认返回 ../includes.binary;
asan 模式(set_inc_bin_suffix(".binary_asan"))返回 ../includes.binary_asan,
隔离 asan 插桩 obj,避免与非 asan obj 混链。
"""
if IncludesDir is None:
return None
inc_len: t.CSizeT = string.strlen(IncludesDir)
suf_len: t.CSizeT = string.strlen(_inc_bin_suffix)
buf: str = _mbuddy.alloc(inc_len + suf_len + 1)
if buf is None:
return None
viperlib.snprintf(buf, inc_len + suf_len + 1, "%s%s", IncludesDir, _inc_bin_suffix)
"""Return the includes binary cache dir (includes dir + cache and MLIR suffixes)."""
if IncludesDir is None: return None
inc_len: sizet = string.strlen(IncludesDir)
suf_len: sizet = string.strlen(_inc_bin_suffix)
be_suf: str = mlir_dir_suffix()
be_suf_len: sizet = string.strlen(be_suf)
buf: str = _mbuddy.alloc(inc_len + suf_len + be_suf_len + 1)
if buf is None: return None
viperlib.snprintf(buf, inc_len + suf_len + be_suf_len + 1, "%s%s%s", IncludesDir, _inc_bin_suffix, be_suf)
return buf
# Directory isolation suffix for the includes binary cache only.
# TempDir/OutputDir are plain {BuildDir}/temp and {BuildDir}/output (same layout as TPC and as
# the project scripts expect). The includes cache keeps "_mlir": it holds MLIR-era artifacts
# (sha1 subdirectories) while TPC's includes cache at {IncludesDir}.binary/ holds flat
# "<sha1>.<triple>.pic.obj" files, so sharing one directory would mix the two layouts.
def mlir_dir_suffix() -> str:
"""Directory isolation suffix for the includes cache of the MLIR backend (always "_mlir")."""
return "_mlir"
def asan_runtime_declared() -> int:
"""Whether project.vpj declares an ASan runtime; 1=declared."""
if LinkerStaticLibs is not None:
if string.strstr(LinkerStaticLibs, "asan") is not None: return 1
if LinkerDynamicLibs is not None:
if string.strstr(LinkerDynamicLibs, "asan") is not None: return 1
if LinkerFlags is not None:
if string.strstr(LinkerFlags, "asan") is not None: return 1
return 0
def print_config():
"""打印当前配置"""
buf: str = VLogger.fmt_buf()
if buf is None:
return
if ProjectName is not None:
viperlib.snprintf(buf, 1024, "project: %s v%s", ProjectName,
ProjectVersion if ProjectVersion is not None else "?")
VLogger.info(buf, "config")
if SourceDir is not None:
viperlib.snprintf(buf, 1024, "source_dir: %s", SourceDir)
VLogger.info(buf, "config")
if BuildDir is not None:
viperlib.snprintf(buf, 1024, "build_dir: %s", BuildDir)
VLogger.info(buf, "config")
if TempDir is not None:
viperlib.snprintf(buf, 1024, "temp_dir: %s", TempDir)
VLogger.info(buf, "config")
if OutputDir is not None:
viperlib.snprintf(buf, 1024, "output_dir: %s", OutputDir)
VLogger.info(buf, "config")
if CompilerCmd is not None:
viperlib.snprintf(buf, 1024, "compiler: %s", CompilerCmd)
VLogger.info(buf, "config")
if LinkerCmd is not None:
viperlib.snprintf(buf, 1024, "linker: %s -> %s", LinkerCmd,
LinkerOutput if LinkerOutput is not None else "?")
VLogger.info(buf, "config")
if IncludesDir is not None:
viperlib.snprintf(buf, 1024, "includes: %s", IncludesDir)
VLogger.info(buf, "config")
viperlib.snprintf(buf, 1024, "sha1_slice_level: %d", Sha1SliceLevel)
VLogger.info(buf, "config")
"""Print the current configuration."""
if ProjectName is not None: VLogger.info(f"project: {ProjectName} v{ProjectVersion if ProjectVersion is not None else "?"}", "config")
if SourceDir is not None: VLogger.info(f"source_dir: {SourceDir}", "config")
if BuildDir is not None: VLogger.info(f"build_dir: {BuildDir}", "config")
if TempDir is not None: VLogger.info(f"temp_dir: {TempDir}", "config")
if OutputDir is not None: VLogger.info(f"output_dir: {OutputDir}", "config")
if CompilerCmd is not None: VLogger.info(f"compiler: {CompilerCmd}", "config")
if LinkerCmd is not None: VLogger.info(f"linker: {LinkerCmd} -> {LinkerOutput if LinkerOutput is not None else "?"}", "config")
if IncludesDir is not None: VLogger.info(f"includes: {IncludesDir}", "config")
VLogger.info(f"sha1_slice_level: {Sha1SliceLevel}", "config")
# ============================================================
# slice_subdir - 根据 SHA1 和切片层数返回子目录路径
#
# 每层取 SHA1 的 2 个字符作为子目录名:
# level=0 → None(不切片)
# level=1 → "b7"
# level=2 → "b7/90"
# level=3 → "b7/90/23"
#
# Args:
# sha1: SHA1 字符串(至少 2*level 字符)
# level: 切片层数
#
# Returns:
# 子目录路径字符串(mbuddy 分配,无需释放),level<=0 时返回 None
# ============================================================
# slice_subdir - use 2 SHA1 chars per level as subdir names (level=0 none, 1->"b7", 2->"b7/90").
# Args: sha1 (>= 2*level chars) / level. Returns mbuddy-allocated subdir path, or None if level<=0.
def slice_subdir(sha1: str, level: int) -> str:
"""根据 SHA1 和切片层数返回子目录路径"""
if level <= 0 or sha1 is None:
return None
if _mbuddy is None:
return None
# 每层 2 字符 + 分隔符,最后无分隔符 + null
# buf 大小: level * 2 + (level - 1) + 1 = level * 3
buf_len: t.CSizeT = t.CSizeT(level * 3)
"""Return the SHA1 slice subdir path for the given level."""
if level <= 0 or sha1 is None: return None
if _mbuddy is None: return None
# 2 chars per level + separators; no trailing separator, then null
# buf size: level * 2 + (level - 1) + 1 = level * 3
buf_len: sizet = sizet(level * 3)
buf: str = _mbuddy.alloc(buf_len)
if buf is None:
return None
if buf is None: return None
pos: int = 0
i: int = 0
while i < level:
for i in range(int(level)):
if i > 0:
buf[pos] = '/'
pos += 1
idx: int = i * 2
idx: int = int(i * 2)
buf[pos] = sha1[idx]
buf[pos + 1] = sha1[idx + 1]
pos += 2
i += 1
buf[pos] = '\0'
return buf
+92 -170
View File
@@ -1,59 +1,38 @@
import t, c
from stdint import *
import memhub
import string
import stdlib
import viperlib
import hashlib
import w32.win32file
import w32.win32base
import w32.fileio as fileio
import win32.win32file
import win32.win32base
import win32.fileio as fileio
# ============================================================
# Utils - 工程工具函数
#
# 提供 SHA1 计算和目录清理等通用工具函数。
# ============================================================
# Utils - project utility functions (SHA1 hashing, directory cleanup)
# 全局 mbuddy 指针
_mbuddy: memhub.MemBuddy | t.CPtr
# Global mbuddy pointer
_mbuddy: memhub.MemBuddy*
# ============================================================
# nibble_to_hex_char - 半字节转十六进制字符
# ============================================================
def nibble_to_hex_char(n: int, uppercase: int) -> t.CChar:
"""0-15 → '0'-'9'/'a'-'f'(uppercase=1 时用 'A'-'F')"""
if n < 10: return '0' + n
if uppercase != 0: return 'A' + (n - 10)
return 'a' + (n - 10)
# nibble_to_hex_char - convert a nibble to a hex character
def nibble_to_hex_char(n: int, uppercase: int) -> char:
"""Convert a 0-15 nibble to a hex character (uppercase=1 uses 'A'-'F')."""
if n < 10: return char('0' + n)
if uppercase != 0: return char('A' + (n - 10))
return char('a' + (n - 10))
# ============================================================
# bytes_to_hex - 字节序列转十六进制字符串
# ============================================================
def bytes_to_hex(pool: memhub.MemBuddy | t.CPtr,
digest: bytes,
nbytes: t.CSizeT,
uppercase: int,
use_malloc: int) -> str:
"""将 digest 前 nbytes 字节转为 2*nbytes 个十六进制字符(含 null 终止)
use_malloc=1 时用 stdlib.malloc(调用方负责 free),否则 pool.alloc。
合并自 Utils.compute_sha1 / IncludesScanner.compute_file_sha1 的重复 hex 转换。
"""
if digest is None or nbytes <= 0:
return None
hex_len: t.CSizeT = nbytes * 2 + 1
# bytes_to_hex - convert a byte sequence to a hex string
def bytes_to_hex(pool: memhub.MemBuddy*, digest: bytes, nbytes: sizet, uppercase: int, use_malloc: int) -> str:
"""Convert the first nbytes of digest to a null-terminated hex string."""
if digest is None or nbytes <= 0: return None
hex_len: sizet = nbytes * 2 + 1
hex_buf: str = None
if use_malloc != 0:
hex_buf = stdlib.malloc(hex_len)
else:
hex_buf = pool.alloc(hex_len)
if hex_buf is None:
return None
for i in range(nbytes):
if use_malloc != 0: hex_buf = stdlib.malloc(hex_len)
else: hex_buf = pool.alloc(hex_len)
if hex_buf is None: return None
for i in range(int(nbytes)):
hi: int = (digest[i] >> 4) & 0xF
lo: int = digest[i] & 0xF
hex_buf[i * 2] = nibble_to_hex_char(hi, uppercase)
@@ -62,170 +41,118 @@ def bytes_to_hex(pool: memhub.MemBuddy | t.CPtr,
return hex_buf
# ============================================================
# compute_sha1 - 计算字符串的 SHA1,返回前 16 个十六进制字符
#
# 使用 includes/hashlib 库的 sha1 类计算摘要,转为十六进制字符串。
# 参考 Projectrans.py 的 compute_sha1(hashlib.sha1().hexdigest()[:16])。
# ============================================================
def compute_sha1(pool: memhub.MemBuddy | t.CPtr, content: str) -> str:
"""计算字符串的 SHA1,返回前 16 个十六进制字符(用于文件命名)"""
if content is None or pool is None:
return None
# compute_sha1 - compute a string SHA1, return the first 16 hex chars
def compute_sha1(pool: memhub.MemBuddy*, content: str) -> str:
"""Compute the SHA1 of a string, returning its first 16 hex chars."""
if content is None or pool is None: return None
# 构造 sha1 对象(__init__ 会初始化 state/count/buf)
# ctx 在栈上分配(alloca),函数内使用完即丢弃,无需堆分配
ctx: hashlib.sha1 | t.CPtr = hashlib.sha1()
# ctx is stack-allocated (alloca); no heap allocation needed
ctx: hashlib.sha1* = hashlib.sha1()
if ctx is None: return None
# 计算摘要
# Compute the digest
ctx.update(content)
digest: bytes = pool.alloc(hashlib.SHA1_DIGEST_LEN)
if digest is None: return None
ctx.final(digest)
# 转为十六进制字符串(取前 8 字节 = 16 个十六进制字符)
# Convert to a hex string (first 8 bytes = 16 hex chars)
return bytes_to_hex(pool, digest, 8, 0, 0)
# ============================================================
# is_vp_at - 检查路径段是否以 .vp 后缀结尾
# ============================================================
def is_vp_at(p: str, start: t.CSizeT, plen: t.CSizeT) -> int:
"""检查 p[start, start+plen) 是否以 .vp 后缀结尾(1=是,0=否)。
不再识别 .py(移除 vp -> py 后缀识别回退:Viper 源文件仅 .vp)。
"""
if p is None or plen <= 3:
return 0
base: t.CSizeT = start + plen - 3
if p[base] == '.' and p[base + 1] == 'v' and p[base + 2] == 'p':
return 1
# is_vp_at - check whether a path segment ends with .vp
def is_vp_at(p: str, start: sizet, plen: sizet) -> int:
"""Return 1 if p[start, start+plen) ends with the .vp suffix."""
if p is None or plen <= 3: return 0
base: sizet = start + plen - 3
if p[base] == '.' and p[base + 1] == 'v' and p[base + 2] == 'p': return 1
return 0
# ============================================================
# JoinPath - 拼接两个路径段为 a/b
# ============================================================
# JoinPath - join two path segments as a/b
def JoinPath(a: str, b: str) -> str:
"""拼接路径段为 "a/b"(stdlib.malloc 分配,调用方负责 free),失败返回 None"""
if a is None or b is None:
return None
a_len: t.CSizeT = string.strlen(a)
b_len: t.CSizeT = string.strlen(b)
total_len: t.CSizeT = a_len + b_len + 2
"""Join two path segments as "a/b"; caller frees the malloc'd result."""
if a is None or b is None: return None
a_len: sizet = string.strlen(a)
b_len: sizet = string.strlen(b)
total_len: sizet = a_len + b_len + 2
buf: str = stdlib.malloc(total_len)
if buf is None: return None
viperlib.snprintf(buf, total_len, "%s/%s", a, b)
return buf
# ============================================================
# read_file_to_buf - 读取整个文件到缓冲区(null 终止)
# ============================================================
def read_file_to_buf(path: str, buf_size: t.CSizeT) -> bytes:
"""读取文件到 stdlib.malloc 缓冲区并保证 null 终止
返回缓冲区(调用方负责 free);打开失败/空文件/分配失败返回 None。
合并自 Phase2/StubMerger/IncludesScanner 的重复"打开+读取+终止"序列。
"""
# read_file_to_buf - read a whole file into a null-terminated buffer
def read_file_to_buf(path: str, buf_size: sizet) -> bytes:
"""Read a whole file into a null-terminated malloc buffer, or None."""
if path is None or buf_size <= 0: return None
sf: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.R)
sf: fileio.File* = fileio.File(path, fileio.MODE.R)
if sf.closed: return None
buf: bytes = stdlib.malloc(buf_size)
if buf is None:
sf.close()
return None
br: t.CInt64T = sf.read_all(buf, buf_size)
br: int64 = sf.read_all(buf, buf_size)
sf.close()
if br <= 0:
stdlib.free(buf)
return None
if br < buf_size:
buf[br] = 0
else:
buf[buf_size - 1] = 0
if br < buf_size: buf[br] = 0
return buf
# ============================================================
# PathToModuleName - 文件路径转模块名
# ============================================================
# PathToModuleName - convert a file path to a module name
def PathToModuleName(path: str, strip_includes_prefix: int) -> str:
"""将文件路径转换为模块名(stdlib.malloc 分配,调用方负责 free)
strip_includes_prefix=1 时跳过 includes/ 前缀(如 includes/ast/__init__.vp → ast)。
特殊处理 __init__.vp:pkg/__init__.vp → pkg(而非 pkg.__init__)。
合并自 StubMerger._PathToModuleName / IncludesScanner 内联模块名计算。
"""
if path is None:
return None
plen: t.CSizeT = string.strlen(path)
# 跳过 includes/ 或 includes\ 前缀(Windows 反斜杠兼容)
prefix_len: t.CSizeT = 9
path_start: t.CSizeT = 0
"""Convert a file path to a dotted module name; caller frees the result."""
if path is None: return None
plen: sizet = string.strlen(path)
# Skip the includes/ or includes\ prefix
prefix_len: sizet = 9
path_start: sizet = 0
if strip_includes_prefix != 0 and plen > prefix_len and string.strncmp(path, "includes", 8) == 0:
psep_ch: t.CChar = path[8]
psep_ch: char = path[8]
if psep_ch == '/' or psep_ch == '\\':
path_start = prefix_len
plen = plen - prefix_len
# 去掉 .vp 后缀(Viper 源文件仅 .vp,不再识别 .py)
if is_vp_at(path, path_start, plen) != 0:
plen -= 3
if plen == 0:
return None
# 检查是否以 /__init__ 或 \__init__ 结尾(包的 __init__.vp)
# 同时支持 / 和 \ 两种分隔符(TPC 写入的路径可能混合使用)
# Strip the .vp suffix
if is_vp_at(path, path_start, plen) != 0: plen -= 3
if plen == 0: return None
# Check for a trailing /__init__ or \__init__ (package __init__.vp)
if plen > 9:
tail_off: t.CSizeT = path_start + plen - 9
sep_char: t.CChar = path[tail_off]
if (sep_char == '/' or sep_char == '\\') \
and path[tail_off + 1] == '_' and path[tail_off + 2] == '_' \
and path[tail_off + 3] == 'i' and path[tail_off + 4] == 'n' and path[tail_off + 5] == 'i' \
and path[tail_off + 6] == 't' and path[tail_off + 7] == '_' and path[tail_off + 8] == '_':
# 去掉 分隔符+__init__ 后缀,plen 就是包名部分长度
tail_off: sizet = path_start + plen - 9
sep_char: char = path[tail_off]
if (sep_char == '/' or sep_char == '\\') and path[tail_off + 1] == '_' and path[tail_off + 2] == '_' and path[tail_off + 3] == 'i' and path[tail_off + 4] == 'n' and path[tail_off + 5] == 'i' and path[tail_off + 6] == 't' and path[tail_off + 7] == '_' and path[tail_off + 8] == '_':
# Drop the separator + __init__ suffix
plen = tail_off - path_start
if plen == 0:
return None
# 复制并替换 / 为 .
if plen == 0: return None
# Copy and replace / with .
result: str = stdlib.malloc(plen + 1)
if result is None:
return None
for ri in range(plen):
ch: t.CChar = path[path_start + ri]
if ch == '/' or ch == '\\':
result[ri] = '.'
else:
result[ri] = ch
if result is None: return None
for ri in range(int(plen)):
ch: char = path[path_start + ri]
if ch == '/' or ch == '\\': result[ri] = '.'
else: result[ri] = ch
result[plen] = '\0'
return result
# ============================================================
# CleanDir - 递归删除目录中所有文件和子目录(保留 dir_path 本身)
#
# 用于 --clean 选项:清理 temp_dir 和 output_dir 中的旧文件。
# SHA1 分片目录结构(如 temp/1c/、temp/7a/)需要递归清理,
# 否则旧的截断文件(如 256KB text.ll)会被 Phase1 跳过逻辑复用。
# ============================================================
# CleanDir - recursively delete all files/subdirs under dir_path (for --clean)
def CleanDir(dir_path: str) -> int:
"""递归删除目录中所有文件和子目录,保留 dir_path 本身,返回删除的文件数,-1 表示错误"""
if dir_path is None:
return -1
dir_len: t.CSizeT = string.strlen(dir_path)
"""Recursively delete files/subdirs under dir_path, returning the count."""
if dir_path is None: return -1
dir_len: sizet = string.strlen(dir_path)
pattern: bytes = stdlib.malloc(dir_len + 8)
if pattern is None:
return -1
if pattern is None: return -1
viperlib.snprintf(pattern, dir_len + 8, "%s/*", dir_path)
find_data: w32.win32file.WIN32_FIND_DATAA | t.CPtr = stdlib.malloc(w32.win32file.WIN32_FIND_DATAA.__sizeof__())
find_data: win32.win32file.WIN32_FIND_DATAA* = stdlib.malloc(win32.win32file.WIN32_FIND_DATAA.__sizeof__())
if find_data is None:
stdlib.free(pattern)
return -1
string.memset(find_data, 0, w32.win32file.WIN32_FIND_DATAA.__sizeof__())
string.memset(find_data, 0, win32.win32file.WIN32_FIND_DATAA.__sizeof__())
handle: w32.win32base.HANDLE = w32.win32file.FindFirstFileA(pattern, find_data)
if handle == w32.win32base.INVALID_HANDLE_VALUE:
handle: win32.win32base.HANDLE = win32.win32file.FindFirstFileA(pattern, find_data)
if handle == win32.win32base.INVALID_HANDLE_VALUE:
stdlib.free(pattern)
stdlib.free(find_data)
return 0
@@ -234,34 +161,29 @@ def CleanDir(dir_path: str) -> int:
while True:
fname: str = find_data.cFileName
if fname is not None:
# 跳过 . 和 ..
# Skip . and ..
if fname[0] == '.':
if fname[1] == '\0':
fname = None
elif fname[1] == '.' and fname[2] == '\0':
fname = None
if fname[1] == '\0': fname = None
elif fname[1] == '.' and fname[2] == '\0': fname = None
if fname is not None:
full_path: bytes = JoinPath(dir_path, fname)
if full_path is not None:
# 检查是否为目录(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
is_dir: t.CUInt32T = find_data.dwFileAttributes & w32.win32file.FILE_ATTRIBUTE_DIRECTORY
# Check whether this is a directory
is_dir: uint32 = find_data.dwFileAttributes & win32.win32file.FILE_ATTRIBUTE_DIRECTORY
if is_dir != 0:
# 递归清理子目录中的文件
# Recurse into the subdirectory
sub_deleted: int = CleanDir(full_path)
if sub_deleted > 0:
deleted += sub_deleted
# 删除空子目录(RemoveDirectoryA 要求目录为空)
w32.win32file.RemoveDirectoryA(full_path)
if sub_deleted > 0: deleted += sub_deleted
# Remove the now-empty subdirectory
win32.win32file.RemoveDirectoryA(full_path)
else:
# 删除文件
if w32.win32file.DeleteFileA(full_path) != 0:
deleted += 1
# Delete the file
if win32.win32file.DeleteFileA(full_path) != 0: deleted += 1
stdlib.free(full_path)
if w32.win32file.FindNextFileA(handle, find_data) == 0:
break
if win32.win32file.FindNextFileA(handle, find_data) == 0: break
w32.win32file.FindClose(handle)
win32.win32file.FindClose(handle)
stdlib.free(pattern)
stdlib.free(find_data)
return deleted
+1 -7
View File
@@ -1,12 +1,6 @@
import t, c
from stdint import *
from .Config import Load_project_config, resolve_paths
from .Utils import compute_sha1, CleanDir
__all__ = [
'Load_project_config',
'resolve_paths',
'compute_sha1',
'CleanDir',
]
__all__ = ['Load_project_config', 'resolve_paths', 'compute_sha1', 'CleanDir',]
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+1 -3
View File
@@ -1,3 +1 @@
# ============================================================
# StubGen 包入口
# ============================================================
# StubGen
File diff suppressed because it is too large. Load diff
+58 -218
View File
@@ -1,18 +1,17 @@
import t, c
from stdint import *
import memhub
import string
import stdlib
import llvmlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import mlirlite
import win32.win32file as win32file
import win32.win32base as win32base
import win32.fileio as fileio
import viperlib
import sys
import ast
import lib.core.VLogger as VLogger
import lib.core.IncludesScanner as IncludesScanner
import lib.core.Handles.HandlesTranslator as HandlesTranslator
import lib.core.Gen.Translator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.Projectrans.Utils as Utils
@@ -25,258 +24,99 @@ import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger as StubMerger
# ============================================================
# StubMergerCollect - StubMerger 拆分(阶段 E):StubMergerCollect
#
# 直接移动模式:函数实现迁至此模块,原 StubMerger.vp 仅保留
# 常量/全局变量;调用方经本模块前缀访问。全局变量跨模块经
# StubMerger._xxx 访问(main.vp 先例)。
# ============================================================
# StubMergerCollect - slice-path construction and stub-file SHA1 collection.
# Functions live in this module; cross-module globals are accessed via StubMerger._xxx.
# ============================================================
# _sliced_path - 构建切片路径(stdlib.malloc 分配,调用者负责 free)
#
# 根据 Config.Sha1SliceLevel 将文件分散到 SHA1 前缀子目录:
# level=0 → {temp_dir}/{sha1}.{ext}
# level=1 → {temp_dir}/b7/{sha1}.{ext}
# level=2 → {temp_dir}/b7/90/{sha1}.{ext}
#
# 自动调用 BuildPipeline.ensure_dir 创建子目录(level>0 时)。
#
# Args:
# temp_dir: 基础目录
# td_len: temp_dir 长度(避免重复 strlen)
# sha1: SHA1 字符串
# ext: 文件扩展名(如 "stub.ll")
#
# Returns:
# 完整路径(stdlib.malloc 分配,需 stdlib.free),None 失败
# ============================================================
def _sliced_path(temp_dir: str, td_len: t.CSizeT, sha1: str, ext: str) -> str:
"""构建切片路径(stdlib.malloc 分配,调用者负责 free)
# _sliced_path - build a slice path (stdlib.malloc; caller frees; creates subdir when level>0).
# Shards by Config.Sha1SliceLevel into SHA1-prefix subdirs; returns the full path or None on failure.
def _sliced_path(temp_dir: str, td_len: sizet, sha1: str, ext: str) -> str:
"""Build a SHA1-sliced path (stdlib.malloc; caller frees; creates the subdir)."""
if temp_dir is None or sha1 is None or ext is None: return None
sha1_len: sizet = string.strlen(sha1)
ext_len: sizet = string.strlen(ext)
level>0 时自动调用 ensure_dir 创建子目录。
"""
if temp_dir is None or sha1 is None or ext is None:
return None
sha1_len: t.CSizeT = string.strlen(sha1)
ext_len: t.CSizeT = string.strlen(ext)
# 子目录前缀:复用 Config.slice_subdir(SHA1 切片唯一权威实现,mbuddy 分配无需 free)
# Subdir prefix: reuse Config.slice_subdir (the authoritative SHA1 slice impl; mbuddy-allocated, no free)
subdir_buf: str = Config.slice_subdir(sha1, Config.Sha1SliceLevel)
sub_len: t.CSizeT = 0
sub_len: sizet = 0
if subdir_buf is not None:
sub_len = string.strlen(subdir_buf)
# 确保子目录存在: {temp_dir}/{subdir}
# 不创建目录会导致 fileio.File(MODE.W) 静默失败(file.closed==True),
# 进而 BuildCombinedIR 读不到文件返回 0("BuildCombinedIR 失败")。
dir_path_len: t.CSizeT = td_len + sub_len + 2
# Ensure subdir {temp_dir}/{subdir} exists.
# Otherwise fileio.File(MODE.W) fails silently (file.closed==True) and the reader returns 0.
dir_path_len: sizet = td_len + sub_len + 2
dir_path: bytes = stdlib.malloc(dir_path_len)
if dir_path is not None:
viperlib.snprintf(dir_path, dir_path_len, "%s/%s", temp_dir, subdir_buf)
BuildPipeline.ensure_dir(dir_path)
stdlib.free(dir_path)
# 构建完整路径
# Build the full path
path: str = None
path_len: t.CSizeT = 0
path_len: sizet = 0
if sub_len > 0:
path_len = td_len + sub_len + sha1_len + ext_len + 4
path = stdlib.malloc(path_len)
if path is not None:
viperlib.snprintf(path, path_len, "%s/%s/%s.%s", temp_dir, subdir_buf, sha1, ext)
if path is not None: viperlib.snprintf(path, path_len, "%s/%s/%s.%s", temp_dir, subdir_buf, sha1, ext)
else:
path_len = td_len + sha1_len + ext_len + 3
path = stdlib.malloc(path_len)
if path is not None:
viperlib.snprintf(path, path_len, "%s/%s.%s", temp_dir, sha1, ext)
if path is not None: viperlib.snprintf(path, path_len, "%s/%s.%s", temp_dir, sha1, ext)
return path
# ============================================================
# _collect_stub_sha1s - 递归扫描子目录,收集 .stub.ll 文件的 SHA1
#
# 根据 level 递归扫描子目录:
# level=0 → 扫描 {base_dir}/*.stub.ll,从文件名提取 SHA1
# level>0 → 扫描 {base_dir}/* 的子目录,对每个子目录递归调用
#
# Args:
# base_dir: 当前扫描目录
# bd_len: base_dir 长度
# level: 剩余递归层数
# out_arr: 输出数组(每个 SHA1 17 字节 = 16字符 + null)
# out_count: 当前已收集数量
# max_count: 最大数量
#
# Returns:
# 收集后的总数量
# ============================================================
def _collect_stub_sha1s(base_dir: str, bd_len: t.CSizeT, level: int,
out_arr: str, out_count: int, max_count: int) -> int:
"""递归扫描子目录,收集 .stub.ll 文件的 SHA1"""
if base_dir is None or out_arr is None:
return out_count
if out_count >= max_count:
return out_count
find_data: win32file.WIN32_FIND_DATAA | t.CPtr = stdlib.malloc(win32file.WIN32_FIND_DATAA.__sizeof__())
if find_data is None:
return out_count
string.memset(find_data, 0, win32file.WIN32_FIND_DATAA.__sizeof__())
if level <= 0:
# 扫描 {base_dir}/*.stub.ll
pattern: bytes = stdlib.malloc(bd_len + 16)
if pattern is None:
stdlib.free(find_data)
return out_count
viperlib.snprintf(pattern, bd_len + 16, "%s/*.stub.ll", base_dir)
handle: win32base.HANDLE = win32file.FindFirstFileA(pattern, find_data)
stdlib.free(pattern)
if handle != win32base.INVALID_HANDLE_VALUE:
while True:
if out_count >= max_count:
break
fname: str = find_data.cFileName
if fname is not None:
if fname[0] != '\0':
# 从文件名提取 SHA1(前 16 字符)
string.strncpy(out_arr + out_count * 17, fname, 16)
out_arr[out_count * 17 + 16] = '\0'
out_count += 1
if win32file.FindNextFileA(handle, find_data) == 0:
break
win32file.FindClose(handle)
else:
# 扫描 {base_dir}/* 的子目录
pattern = stdlib.malloc(bd_len + 4)
if pattern is None:
stdlib.free(find_data)
return out_count
viperlib.snprintf(pattern, bd_len + 4, "%s/*", base_dir)
handle = win32file.FindFirstFileA(pattern, find_data)
stdlib.free(pattern)
if handle != win32base.INVALID_HANDLE_VALUE:
while True:
# 检查是否是目录(FILE_ATTRIBUTE_DIRECTORY = 0x10 = 16)
attrs: t.CUInt32T = find_data.dwFileAttributes
if (attrs & 16) != 0:
fname = find_data.cFileName
if fname is not None:
# 跳过 "." 和 ".."
is_dot: int = 0
if fname[0] == '.':
if fname[1] == '\0':
is_dot = 1
elif fname[1] == '.' and fname[2] == '\0':
is_dot = 1
if is_dot == 0:
# 构建子目录路径并递归扫描
sub_dir: str = Utils.JoinPath(base_dir, fname)
if sub_dir is not None:
sub_dir_len: t.CSizeT = string.strlen(sub_dir)
out_count = _collect_stub_sha1s(sub_dir, sub_dir_len, level - 1, out_arr, out_count, max_count)
stdlib.free(sub_dir)
if win32file.FindNextFileA(handle, find_data) == 0:
break
win32file.FindClose(handle)
stdlib.free(find_data)
return out_count
# ============================================================
# _load_includes_sha1_set - 从全局内存存储器收集 includes 的 SHA1
#
# 数据来源是 PopulateSha1MapStore 填充的全局存储器(不读取 _sha1_map.txt)。
# 用于过滤 stub 文件,避免加载 TransPyV 自身源文件(lib/*)的 stub 导致链接失败。
# ============================================================
def _load_includes_sha1_set(pool: memhub.MemBuddy | t.CPtr,
temp_dir: str,
sha1_set: str) -> int:
"""从全局内存存储器将 includes 的 SHA1 写入 sha1_set
pool/temp_dir 参数保留以兼容现有调用者,但不再使用。
数据来源是 PopulateSha1MapStore 填充的全局存储器。
sha1_set 大小为 MAX_INCLUDES_SHA1 * 17(每个 SHA1 16字符+null)
返回找到的 includes SHA1 数量,-1 表示错误"""
if sha1_set is None:
return -1
if StubMerger._sha1_store_arr is None:
return -1
# _load_includes_sha1_set - collect includes SHA1 from the global in-memory store.
# Source is PopulateSha1MapStore (not _sha1_map.txt); filters stubs to avoid linking ViperC's own lib/* stubs.
def _load_includes_sha1_set(pool: memhub.MemBuddy*, temp_dir: str, sha1_set: str) -> int:
"""Copy includes SHA1s from the global store into sha1_set; returns the count, -1 on error."""
if sha1_set is None: return -1
if StubMerger._sha1_store_arr is None: return -1
count: int = 0
for i in range(StubMerger._sha1_store_count):
if count >= StubMerger.MAX_INCLUDES_SHA1:
break
idx: t.CSizeT = t.CSizeT(i) * 17
string.strcpy(sha1_set + t.CSizeT(count) * 17, StubMerger._sha1_store_arr + idx)
for i in range(int(StubMerger._sha1_store_count)):
if count >= StubMerger.MAX_INCLUDES_SHA1: break
idx: sizet = sizet(i) * 17
string.strcpy(sha1_set + sizet(count) * 17, StubMerger._sha1_store_arr + idx)
count += 1
return count
# ============================================================
# _is_in_sha1_set - 检查 SHA1 是否在集合中
# ============================================================
# _is_in_sha1_set - check whether a SHA1 is in the set.
def _is_in_sha1_set(sha1: str, sha1_set: str, set_count: int) -> int:
"""检查 sha1 是否在集合中"""
if sha1 is None or sha1_set is None:
return 0
for i in range(set_count):
if string.strcmp(sha1_set + i * 17, sha1) == 0:
return 1
"""Whether sha1 is present in the set."""
if sha1 is None or sha1_set is None: return 0
for i in range(int(set_count)):
if string.strcmp(sha1_set + i * 17, sha1) == 0: return 1
return 0
# ============================================================
# WriteIncludesSha1Map - 将 includes 扫描结果写入 _sha1_map.txt(人类可读输出)
#
# _sha1_map.txt 仅作为人类可读的调试输出,程序内部不读取此文件。
# 机器分析使用 PopulateSha1MapStore 填充的全局内存存储器。
#
# Args:
# mb: 内存池
# temp_dir: 临时目录(_sha1_map.txt 写入位置)
# scan_result: IncludesScanner 扫描结果
#
# Returns:
# 0 成功,非 0 失败
# ============================================================
def WriteIncludesSha1Map(mb: memhub.MemBuddy | t.CPtr, temp_dir: str,
scan_result: IncludesScanner.ScanResult | t.CPtr,
filter_set: str,
filter_count: int) -> int:
"""将 includes 扫描结果写入 _sha1_map.txt(人类可读输出,程序内部使用内存存储器)
# WriteIncludesSha1Map - write includes scan results to _sha1_map.txt (human-readable debug output only).
# Internally the in-memory store filled by PopulateSha1MapStore is used; returns 0 on success, non-zero on failure.
def WriteIncludesSha1Map(mb: memhub.MemBuddy*, temp_dir: str, scan_result: IncludesScanner.ScanResult*, filter_set: str, filter_count: int) -> int:
"""Write the includes scan result to _sha1_map.txt (human-readable debug output)."""
if temp_dir is None or scan_result is None: return 1
若 filter_set 不为 None 且 filter_count > 0,只写入 filter_set 中的条目。
"""
if temp_dir is None or scan_result is None:
return 1
# 构造路径 temp_dir/_sha1_map.txt
dir_len: t.CSizeT = string.strlen(temp_dir)
# Build the path temp_dir/_sha1_map.txt
dir_len: sizet = string.strlen(temp_dir)
map_path: bytes = stdlib.malloc(dir_len + 32)
if map_path is None:
return 1
if map_path is None: return 1
viperlib.snprintf(map_path, dir_len + 32, "%s/_sha1_map.txt", temp_dir)
# 打开文件写入(CREATE_ALWAYS)
f: fileio.File | t.CPtr = fileio.File(map_path, fileio.MODE.W)
if f.closed:
return 1
# Open the file for writing (CREATE_ALWAYS)
f: fileio.File* = fileio.File(map_path, fileio.MODE.W)
if f.closed: return 1
# 写入每个 include 条目: {sha1}:includes/{rel_path}\n
entry_size_w: t.CSizeT = IncludesScanner.FileEntry.__sizeof__()
# Write each include entry: {sha1}:includes/{rel_path}\n
entry_size_w: sizet = IncludesScanner.FileEntry.__sizeof__()
line_buf: bytes = stdlib.malloc(512)
if line_buf is None:
f.close()
return 1
written_count: int = 0
for i in range(scan_result.Count):
ea_w: t.CUInt64T = t.CUInt64T(scan_result.Entries) + i * entry_size_w
ent_w: IncludesScanner.FileEntry | t.CPtr = (IncludesScanner.FileEntry | t.CPtr)(t.CVoid(ea_w, t.CPtr))
if ent_w is None or ent_w.Sha1 is None or ent_w.RelPath is None:
continue
# 若提供过滤集合,只写入集合中的条目
for i in range(int(scan_result.Count)):
ea_w: uint64 = uint64(scan_result.Entries) + i * entry_size_w
ent_w: IncludesScanner.FileEntry* = (IncludesScanner.FileEntry*)(ea_w)
if ent_w is None or ent_w.Sha1 is None or ent_w.RelPath is None: continue
# When a filter set is provided, write only its entries
if filter_set is not None and filter_count > 0:
if _is_in_sha1_set(ent_w.Sha1, filter_set, filter_count) == 0:
continue
if _is_in_sha1_set(ent_w.Sha1, filter_set, filter_count) == 0: continue
viperlib.snprintf(line_buf, 512, "%s:includes/%s\n", ent_w.Sha1, ent_w.RelPath)
line_len_w: t.CSizeT = string.strlen(line_buf)
line_len_w: sizet = string.strlen(line_buf)
f.write(line_buf, line_len_w)
written_count += 1
+187 -362
View File
@@ -1,19 +1,18 @@
import lib.core.Gen.GenApi as GenApi
import t, c
from stdint import *
import memhub
import string
import stdlib
import llvmlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import mlirlite
import win32.win32file as win32file
import win32.win32base as win32base
import win32.fileio as fileio
import viperlib
import sys
import ast
import lib.core.VLogger as VLogger
import lib.core.IncludesScanner as IncludesScanner
import lib.core.Handles.HandlesTranslator as HandlesTranslator
import lib.core.Gen.Translator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.Projectrans.Utils as Utils
@@ -26,385 +25,211 @@ import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger as StubMerger
# ============================================================
# StubMergerIR - StubMerger 拆分(阶段 E):StubMergerIR
# ============================================================
# StubMergerIR - module structured sidecar ({sha1}.meta) write/read, submodule checks and module-name/SHA1 mapping helpers
# ============================================================
# IsDeclOnlyModule - 判断模块是否为纯声明文件
#
# 读取 {temp_dir}/{sha1}.text.ll,扫描是否含 "define " 行:
# 返回 1 = 纯声明(无函数体,如 stdio.vp 全 pass),无需 .obj
# 返回 0 = 实现文件(含 define 函数体)
# 返回 -1 = text.ll 不存在/无法判断
#
# 与 Phase B+(Phase2.vp 编译缺失 includes 时)的内联判断逻辑一致,
# 供 Phase C 链接时区分"声明文件无需 .obj"与"实现文件缺失"。
# ============================================================
def IsDeclOnlyModule(temp_dir: str, td_len: t.CSizeT, sha1: str) -> int:
"""判断模块是否为纯声明文件(text.ll 无 define 行)"""
if temp_dir is None or sha1 is None:
return -1
tpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "text.ll")
if tpath is None:
return -1
tf_d: fileio.File = fileio.File(tpath, fileio.MODE.R)
stdlib.free(tpath)
if tf_d.closed:
return -1
tbuf_d: bytes = stdlib.malloc(StubMerger.STUB_READ_BUF_SIZE)
tbr_d: t.CInt64T = 0
if tbuf_d is not None:
tbr_d = tf_d.read_all(tbuf_d, StubMerger.STUB_READ_BUF_SIZE)
tf_d.close()
if tbuf_d is None:
return -1
if tbr_d <= 0:
# 空 text.ll:无函数体,纯声明
stdlib.free(tbuf_d)
# WriteModuleMeta - write {sha1}.meta (structured sidecar: funcs + struct layout + vtables).
# Lines: FUNC <mangled> DEF|DECL, STRUCT <sha1>.<name> FIELDS <n> PACKED <0|1>|OPAQUE, VTABLE <sha1>.<name> SLOTS <n>.
def WriteModuleMeta(mod: mlirlite.LLVMModule*, temp_dir: str, td_len: sizet, sha1: str) -> int:
"""Write the module's func defines, struct layouts and vtables to {sha1}.meta."""
if mod is None or temp_dir is None or sha1 is None:
VLogger.error("meta write: null argument", "Meta")
return 1
if tbr_d < StubMerger.STUB_READ_BUF_SIZE:
tbuf_d[tbr_d] = '\0'
else:
tbuf_d[StubMerger.STUB_READ_BUF_SIZE - 1] = '\0'
# 逐行扫描:找 "define " 行即为实现文件
tpos_d: t.CSizeT = 0
while tpos_d < tbr_d:
tls_d: t.CSizeT = tpos_d
while tpos_d < tbr_d:
if tbuf_d[tpos_d] == '\n':
mpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "meta")
if mpath is None:
VLogger.error("meta write: path construction failed", "Meta")
return 1
mf: fileio.File = fileio.File(mpath, fileio.MODE.W)
if mf.closed:
VLogger.error(f"meta write: cannot open {mpath}", "Meta")
return 1
cur: mlirlite.Function* = mod.FuncHead
steps: sizet = 0
while cur is not None:
steps += 1
if steps > 100000:
VLogger.error("meta write: function list has a cycle or exceeded the limit", "Meta")
break
nm: str = mlirlite.function_get_name(cur)
if nm is not None:
nlen: sizet = string.strlen(nm)
line: bytes = stdlib.malloc(nlen + 16)
if line is None:
VLogger.error("meta write: line buffer allocation failed", "Meta")
break
tpos_d += 1
tll_d: t.CSizeT = tpos_d - tls_d
if tpos_d < tbr_d:
tpos_d += 1
if tll_d >= 7 and string.strncmp(tbuf_d + tls_d, "define ", 7) == 0:
stdlib.free(tbuf_d)
return 0
stdlib.free(tbuf_d)
mark: str = "DECL"
if mlirlite.function_is_declared(cur) == 0: mark = "DEF"
viperlib.snprintf(line, nlen + 16, "FUNC %s %s\n", nm, mark)
mf.write(line, string.strlen(line))
stdlib.free(line)
cur = mlirlite.function_get_next(cur)
# 2) Struct layout: STRUCT <name> FIELDS <n> PACKED <0|1> (opaque -> STRUCT <name> OPAQUE)
nt: mlirlite.NamedTypeNode* = mod.NamedTypeHead
nt_steps: sizet = 0
while nt is not None:
nt_steps += 1
if nt_steps > 100000:
VLogger.error("meta write: named-type list has a cycle or exceeded the limit", "Meta")
break
tn: str = mlirlite.get_struct_name(nt.Ty)
if tn is not None and tn[0] != '\0':
tl: sizet = string.strlen(tn)
lb: bytes = stdlib.malloc(tl + 48)
if lb is None:
VLogger.error("meta write: line buffer allocation failed", "Meta")
break
fc: int = mlirlite.get_struct_fcount(nt.Ty)
if fc < 0: viperlib.snprintf(lb, tl + 48, "STRUCT %s OPAQUE\n", tn)
else: viperlib.snprintf(lb, tl + 48, "STRUCT %s FIELDS %d PACKED %d\n", tn, fc, nt.IsPacked)
mf.write(lb, string.strlen(lb))
stdlib.free(lb)
nt = nt.Next
# 3) Vtable slots: VTABLE <class> SLOTS <n> (global <class>.vtable; Ty anonymous struct fcount = slots)
gv: mlirlite.GlobalVariable* = mod.GlobalHead
gv_steps: sizet = 0
while gv is not None:
gv_steps += 1
if gv_steps > 100000:
VLogger.error("meta write: global-variable list has a cycle or exceeded the limit", "Meta")
break
gn: str = gv.Name
if gn is not None:
gnl: sizet = string.strlen(gn)
if gnl > 7 and string.strcmp(gn + gnl - 7, ".vtable") == 0:
base_len: sizet = gnl - 7
base: bytes = stdlib.malloc(base_len + 1)
if base is not None:
string.strncpy(base, gn, base_len)
base[base_len] = '\0'
ar: int = mlirlite.get_struct_fcount(gv.Ty)
if ar < 0: ar = 0
lb2: bytes = stdlib.malloc(base_len + 40)
if lb2 is None: VLogger.error("meta write: line buffer allocation failed", "Meta")
else:
viperlib.snprintf(lb2, base_len + 40, "VTABLE %s SLOTS %d\n", base, ar)
mf.write(lb2, string.strlen(lb2))
stdlib.free(lb2)
stdlib.free(base)
gv = gv.Next
mf.close()
stdlib.free(mpath)
return 0
# _MetaHasDef - read {sha1}.meta to check for a function definition ("FUNC ... DEF" line).
# Returns: 1=has DEF (impl file); 0=readable but no DEF (decl-only); -1=missing/unreadable.
def _MetaHasDef(temp_dir: str, td_len: sizet, sha1: str) -> int:
"""Read {sha1}.meta: 1 if it has a FUNC ... DEF line, 0 if none, -1 if unreadable."""
if temp_dir is None or sha1 is None: return -1
mpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "meta")
if mpath is None: return -1
mf: fileio.File = fileio.File(mpath, fileio.MODE.R)
stdlib.free(mpath)
if mf.closed: return -1
mbuf: bytes = stdlib.malloc(StubMerger.STUB_READ_BUF_SIZE)
mbr: int64 = 0
if mbuf is not None: mbr = mf.read_all(mbuf, StubMerger.STUB_READ_BUF_SIZE)
mf.close()
if mbuf is None: return -1
if mbr <= 0:
# Empty meta: no DEF, decl-only
stdlib.free(mbuf)
return 0
if mbr < StubMerger.STUB_READ_BUF_SIZE: mbuf[mbr] = '\0'
# Scan line by line: a trailing " DEF" marks an impl file
mpos: sizet = 0
while mpos < mbr:
mls: sizet = mpos
while mpos < mbr:
if mbuf[mpos] == '\n': break
mpos += 1
mll: sizet = mpos - mls
if mpos < mbr: mpos += 1
if mll > 0 and mbuf[mls + mll - 1] == '\r': mll -= 1
if mll >= 10 and string.strncmp(mbuf + mls, "FUNC ", 5) == 0:
if mbuf[mls + mll - 4] == ' ' and string.strncmp(mbuf + mls + mll - 3, "DEF", 3) == 0:
stdlib.free(mbuf)
return 1
stdlib.free(mbuf)
return 0
# IsDeclOnlyModule - whether a module is decl-only (reads the structured definition list in {sha1}.meta).
# 1=decl-only (no FUNC ... DEF, e.g. all-pass modules, no .obj needed); 0=impl; -1=unknown/missing.
def IsDeclOnlyModule(temp_dir: str, td_len: sizet, sha1: str) -> int:
"""Whether the module is decl-only (its meta has no FUNC ... DEF line)."""
h: int = _MetaHasDef(temp_dir, td_len, sha1)
if h < 0: return -1
if h == 1: return 0
return 1
# ============================================================
# _IsFuncDeclaredOrDefined - 检查 out_buf 中是否已有函数的 declare/define
#
# 逐字节strncmp搜索 func_sig(如 @"sha1.func"(/ @printf( ),每次
# 找到匹配后检查行首是否以 "declare " 或 "define " 开头,跳过 call 语句。
# 避免 strstr 子串匹配误匹配 call ... @"func"(...) 中的 @"func"( 子串。
#
# Args:
# out_buf: 已有的 IR 文本缓冲区
# func_sig: 函数签名片段,如 @"sha1.func"(/ @printf(
#
# Returns:
# 1=已存在 declare/define, 0=不存在
# ============================================================
def _IsFuncDeclaredOrDefined(out_buf: bytes, func_sig: str) -> int:
"""检查 out_buf 中是否已有函数的 declare 或 define 行"""
if out_buf is None or func_sig is None:
return 0
sig_len: t.CSizeT = string.strlen(func_sig)
out_len: t.CSizeT = string.strlen(out_buf)
if sig_len == 0 or out_len == 0 or sig_len > out_len:
return 0
pos: t.CSizeT = 0
for pos in range(0, out_len - sig_len + 1):
if string.strncmp(out_buf + pos, func_sig, sig_len) == 0:
ls: t.CSizeT = pos
while ls > 0 and out_buf[ls - 1] != '\n':
ls -= 1
if string.strncmp(out_buf + ls, "declare ", 8) == 0:
return 1
if string.strncmp(out_buf + ls, "define ", 7) == 0:
return 1
return 0
# ============================================================
# _CommentOutDeclareInBuf - 将 out_buf 中匹配 func_sig 的 declare 行注释掉
#
# 用于解决 declare/define 冲突:当本地 text.ll 包含 define 而依赖
# text.ll 已生成了同函数的 declare 时,将 declare 行首字符改为 ';'
# 使其成为注释,然后追加 define 行。
# ============================================================
def _CommentOutDeclareInBuf(out_buf: bytes, func_sig: str) -> int:
"""在 out_buf 中搜索 declare 行(包含 func_sig),将其注释掉"""
if out_buf is None or func_sig is None:
return 0
sig_len: t.CSizeT = string.strlen(func_sig)
out_len: t.CSizeT = string.strlen(out_buf)
if sig_len == 0 or out_len == 0 or sig_len > out_len:
return 0
pos: t.CSizeT = 0
while pos + sig_len <= out_len:
if string.strncmp(out_buf + pos, func_sig, sig_len) == 0:
ls: t.CSizeT = pos
while ls > 0 and out_buf[ls - 1] != '\n':
ls -= 1
if string.strncmp(out_buf + ls, "declare ", 8) == 0:
out_buf[ls] = ';'
return 1
pos += 1
return 0
# ============================================================
# _CommentOutGlobalInBuf - 将 out_buf 中匹配全局变量名的行注释掉
#
# 解决 stub/text 全局变量重复定义问题:stub 中可能是 external 声明
# 或完整定义,以 text 中的定义为准,将 stub 中的同名行注释掉。
# ============================================================
def _CommentOutGlobalInBuf(out_buf: bytes, global_name: str) -> int:
"""在 out_buf 中搜索全局变量定义行(以 global_name 开头),将行首改为 ';'
注释掉**所有**匹配行(而非仅第一个):跨模块同名裸全局(如 main.vp 与
argparse.vp 都声明 StubMerger._mbuddy)时,本地 stub external + 依赖 stub external +
依赖 text 定义会同时存在。只注释第一个会导致依赖 text 的定义残留 →
合成 IR 重复定义 → llc "redefinition of global '@StubMerger._mbuddy'"(ArgparseTest
回归根因)。"以 text 中定义为准"的语义:后追加者注释掉全部旧行,最终
只保留最后一个定义。
"""
if out_buf is None or global_name is None:
return 0
name_len: t.CSizeT = string.strlen(global_name)
out_len: t.CSizeT = string.strlen(out_buf)
if name_len == 0 or out_len == 0 or name_len > out_len:
return 0
pos: t.CSizeT = 0
commented: int = 0
while pos + name_len <= out_len:
if string.strncmp(out_buf + pos, global_name, name_len) == 0:
# 确认是行首(前一个是换行或 pos==0)
if pos == 0 or out_buf[pos - 1] == '\n':
out_buf[pos] = ';'
commented = 1
pos += 1
return commented
def _HasGlobalInBuf(out_buf: bytes, global_name: str) -> int:
"""检测 out_buf 中是否已有同名全局变量行(行首匹配,未注释)
用于跳过重复追加依赖全局强定义:stub 注入已提供 external 声明,
依赖 text.ll 的强定义只需在定义模块存在一份,复制进每个依赖方
会导致 ld.lld duplicate symbol(如 _op_head、MemBuddy.vtable)。
"""
if out_buf is None or global_name is None:
return 0
name_len: t.CSizeT = string.strlen(global_name)
out_len: t.CSizeT = string.strlen(out_buf)
if name_len == 0 or out_len == 0 or name_len > out_len:
return 0
pos: t.CSizeT = 0
while pos + name_len <= out_len:
if string.strncmp(out_buf + pos, global_name, name_len) == 0:
# 确认是行首(前一个是换行或 pos==0)
if pos == 0 or out_buf[pos - 1] == '\n':
return 1
pos += 1
return 0
# ============================================================
# _CommentOutOpaqueTypeInBuf - 将 out_buf 中匹配类型名的 opaque 声明行注释掉
#
# 解决跨模块类型定义冲突:本地 stub 可能包含自动生成的 opaque 声明
#(如 %"sha1.Widget" = type opaque),而依赖 stub 包含完整定义
#(如 %"sha1.Widget" = type { i8*, i32 })。去重时需要将 opaque 声明
# 注释掉,让完整定义被追加,否则 LLC 报错 "Cannot allocate unsized type"。
# ============================================================
def _CommentOutOpaqueTypeInBuf(out_buf: bytes, type_name_prefix: str) -> int:
"""在 out_buf 中搜索 type_name_prefix 对应的 opaque 声明行,将行首改为 ';'
type_name_prefix 格式: '%"sha1.ClassName"'(不含 '=' 和后续内容)
返回 1=已注释, 0=未找到
"""
if out_buf is None or type_name_prefix is None:
return 0
prefix_len: t.CSizeT = string.strlen(type_name_prefix)
out_len: t.CSizeT = string.strlen(out_buf)
if prefix_len == 0 or out_len == 0 or prefix_len > out_len:
return 0
pos: t.CSizeT = 0
while pos + prefix_len <= out_len:
if string.strncmp(out_buf + pos, type_name_prefix, prefix_len) == 0:
# 确认是行首(前一个是换行或 pos==0)
if pos == 0 or out_buf[pos - 1] == '\n':
# 检查后面是否是 " = type opaque"
p: t.CSizeT = pos + prefix_len
# 跳过空格
while p < out_len and out_buf[p] == ' ':
p += 1
if p < out_len and out_buf[p] == '=':
p += 1
while p < out_len and out_buf[p] == ' ':
p += 1
if p + 11 <= out_len:
if string.strncmp(out_buf + p, "type opaque", 11) == 0:
# 找到 opaque 声明,将行首改为 ';'
out_buf[pos] = ';'
return 1
pos += 1
return 0
# ============================================================
# _is_submodule_of_imported - 检查模块名是否是已导入包的子模块
#
# deps.txt 只记录直接导入的模块名(如 "ast"),但包的 __init__.vp
# 内部 from .lexer import Lexer 会让 IR 引用 ast.lexer 子模块类型。
# 当模块名包含 '.' 时,检查其顶层包名是否在 deps_buf 中。
#
# 例如:
# deps_buf = "ast stdio string"
# mod_name = "ast.lexer" → 顶层包 "ast" 在 deps_buf 中 → 返回 1
# mod_name = "ast.token" → 顶层包 "ast" 在 deps_buf 中 → 返回 1
# mod_name = "foo.bar" → 顶层包 "foo" 不在 deps_buf 中 → 返回 0
# ============================================================
# _is_submodule_of_imported - check whether a module name is a submodule of an imported package.
# deps_buf holds only direct imports; a name with '.' is matched by its top-level package (e.g. "ast.lexer" -> 1).
def _is_submodule_of_imported(deps_buf: str, mod_name: str) -> int:
"""检查 mod_name 是否是 deps_buf 中已导入包的子模块"""
if deps_buf is None or mod_name is None:
return 0
# 查找第一个 '.',提取顶层包名
"""Whether mod_name is a submodule of a package imported in deps_buf."""
if deps_buf is None or mod_name is None: return 0
# Find the first '.' and extract the top-level package name
dot_pos: str = string.strchr(mod_name, 46) # '.' = 46
if dot_pos is None:
return 0
pkg_len: t.CSizeT = t.CSizeT(t.CUInt64T(dot_pos) - t.CUInt64T(mod_name))
if pkg_len == 0 or pkg_len >= 256:
return 0
# 构造顶层包名(截断到 '.' 之前)
# 直接在 mod_name 上临时截断检查,避免分配
saved_ch: t.CChar = mod_name[pkg_len]
if dot_pos is None: return 0
pkg_len: sizet = sizet(uint64(dot_pos) - uint64(mod_name))
if pkg_len == 0 or pkg_len >= 256: return 0
# Build the top-level package name (truncated before '.') by temporary in-place
# truncation to avoid an allocation
saved_ch: char = mod_name[pkg_len]
mod_name[pkg_len] = '\0'
result: int = GenApi.is_module_imported(deps_buf, mod_name)
mod_name[pkg_len] = saved_ch
return result
# ============================================================
# _HasFullTypeDefinition - 检查 out_buf 中是否已有完整类型定义(非 opaque)
#
# type_name_prefix 格式: '%"sha1.ClassName"'(不含 '=')
# 完整定义: %"name" = type { ... } 或 %"name" = type < { ... } >
# 返回 1=已有完整定义, 0=无完整定义
# ============================================================
def _HasFullTypeDefinition(out_buf: bytes, type_name_prefix: str) -> int:
"""检查 out_buf 中是否已有 type_name_prefix 的完整定义"""
if out_buf is None or type_name_prefix is None:
return 0
prefix_len: t.CSizeT = string.strlen(type_name_prefix)
out_len: t.CSizeT = string.strlen(out_buf)
if prefix_len == 0 or out_len == 0 or prefix_len > out_len:
return 0
pos: t.CSizeT = 0
while pos + prefix_len <= out_len:
if string.strncmp(out_buf + pos, type_name_prefix, prefix_len) == 0:
# 确认是行首(前一个是换行或 pos==0),且未被注释(行首不是 ';')
is_line_start: int = 0
if pos == 0:
is_line_start = 1
elif out_buf[pos - 1] == '\n':
is_line_start = 1
if is_line_start == 1:
# 检查后面是否是 " = type {" 或 " = type <"
p: t.CSizeT = pos + prefix_len
while p < out_len and out_buf[p] == ' ':
p += 1
if p < out_len and out_buf[p] == '=':
p += 1
while p < out_len and out_buf[p] == ' ':
p += 1
if p + 5 < out_len:
if string.strncmp(out_buf + p, "type ", 5) == 0:
p += 5
if p < out_len:
if out_buf[p] == '{' or out_buf[p] == '<':
return 1
pos += 1
return 0
# ============================================================
# _BuildIncludesSha1Map - 从全局内存存储器构建 sha1 → module_name 映射
#
# 数据来源是 PopulateSha1MapStore 填充的全局存储器(不读取 _sha1_map.txt)。
# 返回两个数组(sha1_list 和 mod_list),长度存储在 count 中。
# 所有内存由 stdlib.malloc 分配,调用者负责释放。
# ============================================================
def _BuildIncludesSha1Map(temp_dir: str, td_len: t.CSizeT,
sha1_list: str,
mod_list: str) -> int:
"""从全局内存存储器复制 sha1→module_name 映射到调用者数组
temp_dir/td_len 参数保留以兼容现有调用者,但不再使用。
数据来源是 PopulateSha1MapStore 填充的全局存储器。
"""
if sha1_list is None or mod_list is None:
return 0
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None:
return 0
# _BuildIncludesSha1Map - build the sha1 -> module_name map from the global in-memory store.
# Source is the store filled by PopulateSha1MapStore (no _sha1_map.txt read); returns the entry count.
def _BuildIncludesSha1Map(temp_dir: str, td_len: sizet, sha1_list: str, mod_list: str) -> int:
"""Copy the sha1 to module_name map from the global store into caller arrays."""
if sha1_list is None or mod_list is None: return 0
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None: return 0
count: int = 0
for i in range(StubMerger._sha1_store_count):
if count >= StubMerger.MAX_INCLUDES:
break
idx: t.CSizeT = t.CSizeT(i) * 17
idx2: t.CSizeT = t.CSizeT(i) * 64
string.strcpy(sha1_list + t.CSizeT(count) * 17, StubMerger._sha1_store_arr + idx)
string.strcpy(mod_list + t.CSizeT(count) * 64, StubMerger._sha1_store_mod + idx2)
for i in range(int(StubMerger._sha1_store_count)):
if count >= StubMerger.MAX_INCLUDES: break
idx: sizet = sizet(i) * 17
idx2: sizet = sizet(i) * 64
string.strcpy(sha1_list + sizet(count) * 17, StubMerger._sha1_store_arr + idx)
string.strcpy(mod_list + sizet(count) * 64, StubMerger._sha1_store_mod + idx2)
count += 1
return count
# ============================================================
# _FindSha1ByModName - 从模块名查找 SHA1(精确匹配)
#
# 在 sha1_arr/mod_arr 映射中查找模块名对应的 SHA1。
# 用于依赖图分析:模块名 → SHA1。
#
# Args:
# sha1_arr: SHA1 数组(每个 17 字节)
# mod_arr: 模块名数组(每个 64 字节)
# count: 映射条目数
# mod_name: 要查找的模块名
#
# Returns:
# SHA1 字符串指针(指向 sha1_arr 内部,无需释放),未找到返回 None
# ============================================================
def _FindSha1ByModName(sha1_arr: str, mod_arr: str,
count: int, mod_name: str) -> str:
"""从模块名查找 SHA1(精确匹配 + 包名回退)
精确匹配失败时,尝试 "{mod_name}.__init__" 回退,
支持 import ast(包)→ ast.__init__ 的 SHA1 查找。
"""
if sha1_arr is None or mod_arr is None or mod_name is None:
return None
if count <= 0:
return None
# 第一遍:精确匹配
for i in range(count):
idx: t.CSizeT = t.CSizeT(i) * 64
if string.strcmp(mod_arr + idx, mod_name) == 0:
sidx: t.CSizeT = t.CSizeT(i) * 17
# _FindSha1ByModName - look up a SHA1 by module name (exact match) for dependency graph analysis.
# sha1_arr holds 17-byte entries, mod_arr 64-byte names; returns a pointer into sha1_arr, or None.
def _FindSha1ByModName(sha1_arr: str, mod_arr: str, count: int, mod_name: str) -> str:
"""Look up a SHA1 by module name, falling back to the .__init__ package form."""
if sha1_arr is None or mod_arr is None or mod_name is None: return None
if count <= 0: return None
# Pass 1: exact match
for i in range(int(count)):
idx: sizet = sizet(i) * 64
# Fast path: strcmp equality implies equal first char, so skip strcmp when it differs
# (hot spot: this function strcmp-scans the whole table per lookup).
if mod_arr[idx] == mod_name[0] and string.strcmp(mod_arr + idx, mod_name) == 0:
sidx: sizet = sizet(i) * 17
return sha1_arr + sidx
# 第二遍:包名回退("ast" → "ast.__init__")
nm_len: t.CSizeT = string.strlen(mod_name)
# Pass 2: package fallback ("ast" -> "ast.__init__")
nm_len: sizet = string.strlen(mod_name)
init_buf: bytes = stdlib.malloc(nm_len + 12)
if init_buf is not None:
viperlib.snprintf(init_buf, nm_len + 12, "%s.__init__", mod_name)
for i in range(count):
idx2: t.CSizeT = t.CSizeT(i) * 64
if string.strcmp(mod_arr + idx2, init_buf) == 0:
sidx2: t.CSizeT = t.CSizeT(i) * 17
for i in range(int(count)):
idx2: sizet = sizet(i) * 64
if mod_arr[idx2] == init_buf[0] and string.strcmp(mod_arr + idx2, init_buf) == 0:
sidx2: sizet = sizet(i) * 17
stdlib.free(init_buf)
return sha1_arr + sidx2
stdlib.free(init_buf)
return None
# ============================================================
# _AddSha1ToSet - 将 SHA1 加入集合(去重)
#
# 检查 SHA1 是否已在集合中,若不在则加入。返回新的 count。
#
# Args:
# set_buf: SHA1 集合缓冲区(每个 17 字节)
# count: 当前集合中的 SHA1 数量
# sha1: 要加入的 SHA1
#
# Returns:
# 新的 count(如果已存在则不变)
# ============================================================
# _AddSha1ToSet - add a SHA1 to a set (deduped; count unchanged if already present).
# set_buf holds 17-byte entries; returns the new count.
def _AddSha1ToSet(set_buf: str, count: int, sha1: str) -> int:
"""将 SHA1 加入集合,返回新 count"""
if set_buf is None or sha1 is None:
return count
for i in range(count):
idx: t.CSizeT = t.CSizeT(i) * 17
if string.strcmp(set_buf + idx, sha1) == 0:
return count
"""Add a SHA1 to the set (deduped) and return the new count."""
if set_buf is None or sha1 is None: return count
for i in range(int(count)):
idx: sizet = sizet(i) * 17
if string.strcmp(set_buf + idx, sha1) == 0: return count
if count < StubMerger.MAX_INCLUDES_SHA1:
idx: t.CSizeT = t.CSizeT(count) * 17
idx: sizet = sizet(count) * 17
string.strcpy(set_buf + idx, sha1)
return count + 1
return count
+410 -359
View File
@@ -1,18 +1,17 @@
import t, c
from stdint import *
import memhub
import string
import stdlib
import llvmlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import mlirlite
import win32.win32file as win32file
import win32.win32base as win32base
import win32.fileio as fileio
import viperlib
import sys
import ast
import lib.core.VLogger as VLogger
import lib.core.IncludesScanner as IncludesScanner
import lib.core.Handles.HandlesTranslator as HandlesTranslator
import lib.core.Gen.Translator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.Projectrans.Utils as Utils
@@ -26,63 +25,137 @@ import lib.Stub.StubMerger as StubMerger
# ============================================================
# StubMergerStore - StubMerger 拆分(阶段 E):StubMergerStore
# ============================================================
# StubMergerStore - global SHA1 map store (sha1 -> module name / rel path) read/write
# Import-context accounting, single source of truth: AST import statements map to
# imported_modules / from_imports; both translate-time and vpi-injection use these helpers.
class ImportCtx:
"""Import context container: two space-separated lists (ImportedModules / FromImports)"""
ImportedModules: str
FromImports: str
# ============================================================
# PopulateSha1MapStore - 从扫描结果填充全局 SHA1 映射存储器
#
# 遍历 ScanResult.Entries,为每个条目构建 sha1→module_name 映射。
# 模块名通过 _PathToModuleName(includes/rel_path) 计算。
# 内存用 stdlib.malloc 分配(全局存储器,不随 mbuddy 释放)。
#
# Args:
# scan_result: IncludesScanner 扫描结果
#
# Returns:
# 填充的条目数,-1 表示失败
# ============================================================
def PopulateSha1MapStore(scan_result: IncludesScanner.ScanResult | t.CPtr) -> int:
"""从扫描结果填充全局 SHA1 映射存储器
# AppendImportedModule - append module name to space-separated list (pure bookkeeping)
def AppendImportedModule(pool: memhub.MemBuddy*, cur: str, name: str) -> str:
"""Append a module name to a space-separated list (pool-allocated); returns the new list"""
if name is None: return cur
if cur is None:
nlen: sizet = string.strlen(name)
buf: str = pool.alloc(nlen + 1)
if buf is not None: string.strcpy(buf, name)
return buf
old_len: sizet = string.strlen(cur)
name_len: sizet = string.strlen(name)
buf2: str = pool.alloc(old_len + 1 + name_len + 1)
if buf2 is not None:
string.strcpy(buf2, cur)
buf2[old_len] = ' '
string.strcpy(buf2 + old_len + 1, name)
return buf2
遍历 ScanResult.Entries,为每个条目构建 sha1→module_name 映射。
模块名通过 _PathToModuleName(includes/rel_path) 计算。
内存用 stdlib.malloc 分配(全局存储器,不随 mbuddy 释放)。
"""
if scan_result is None:
return -1
# 释放旧存储器
if StubMerger._sha1_store_arr is not None:
stdlib.free(StubMerger._sha1_store_arr)
if StubMerger._sha1_store_mod is not None:
stdlib.free(StubMerger._sha1_store_mod)
if StubMerger._sha1_store_rel is not None:
stdlib.free(StubMerger._sha1_store_rel)
if StubMerger._sha1_store_incdir is not None:
stdlib.free(StubMerger._sha1_store_incdir)
# AppendFromImport - append a from-import mapping ("local:mod[:orig]") to a space-separated list.
# Exact-size allocation (no fixed 256 buffer) so overlong entries are never silently truncated.
def AppendFromImport(pool: memhub.MemBuddy*, cur: str, local: str, mod: str, orig: str) -> str:
"""Append a from-import mapping (pool-allocated); returns the new list"""
if local is None or mod is None: return cur
llen: sizet = string.strlen(local)
mlen: sizet = string.strlen(mod)
use_orig: int = 0
olen: sizet = 0
if orig is not None and string.strcmp(orig, local) != 0:
use_orig = 1
olen = string.strlen(orig)
# local ':' mod [':' orig] '\0'
need: sizet = llen + 1 + mlen + 1
if use_orig != 0: need += 1 + olen
entry: str = pool.alloc(need)
if entry is None: return cur
string.strcpy(entry, local)
entry[llen] = ':'
string.strcpy(entry + llen + 1, mod)
if use_orig != 0:
entry[llen + 1 + mlen] = ':'
string.strcpy(entry + llen + 1 + mlen + 1, orig)
if cur is None: return entry
old_len: sizet = string.strlen(cur)
entry_len: sizet = string.strlen(entry)
buf: str = pool.alloc(old_len + 1 + entry_len + 1)
if buf is not None:
string.strcpy(buf, cur)
buf[old_len] = ' '
string.strcpy(buf + old_len + 1, entry)
return buf
# ============================================================
# AllocImportCtx - build the accounting container from two existing context strings
def AllocImportCtx(pool: memhub.MemBuddy*, imported_modules: str, from_imports: str) -> ImportCtx*:
"""Build an import context container (pool-allocated); either list may be None"""
ctx: ImportCtx* = pool.alloc(ImportCtx.__sizeof__())
if ctx is None: return None
ctx.ImportedModules = imported_modules
ctx.FromImports = from_imports
return ctx
# NotePlainImport - record `import X` / `import X as Y` in place on ctx.
# Aliased: append alias Y + from-import "Y:X" (sole source for reverse module lookup).
def NotePlainImport(pool: memhub.MemBuddy*, ctx: ImportCtx*, name: str, asname: str) -> int:
"""Record a single import in place on ctx; returns 0"""
if ctx is None or name is None: return 1
ctx.ImportedModules = AppendImportedModule(pool, ctx.ImportedModules, name)
if asname is not None:
ctx.ImportedModules = AppendImportedModule(pool, ctx.ImportedModules, asname)
ctx.FromImports = AppendFromImport(pool, ctx.FromImports, asname, name, None)
else:
dot_ptr: str = string.strstr(name, ".")
if dot_ptr is not None:
pkg_len: sizet = sizet(uint64(dot_ptr) - uint64(name))
if pkg_len > 0 and pkg_len < 256:
pkg_buf: str = pool.alloc(pkg_len + 1)
if pkg_buf is not None:
string.strncpy(pkg_buf, name, pkg_len)
pkg_buf[pkg_len] = '\0'
ctx.ImportedModules = AppendImportedModule(pool, ctx.ImportedModules, pkg_buf)
return 0
# PopulateSha1MapStore - build sha1 -> module_name map from ScanResult.Entries into the global store.
# Memory via stdlib.malloc (global, not freed with mbuddy). Returns entry count, -1 on failure.
def PopulateSha1MapStore(scan_result: IncludesScanner.ScanResult*) -> int:
"""Populate the global SHA1 store from a scan result; returns the entry count or -1"""
if scan_result is None: return -1
# free the old store
if StubMerger._sha1_store_arr is not None: stdlib.free(StubMerger._sha1_store_arr)
if StubMerger._sha1_store_mod is not None: stdlib.free(StubMerger._sha1_store_mod)
if StubMerger._sha1_store_rel is not None: stdlib.free(StubMerger._sha1_store_rel)
if StubMerger._sha1_store_incdir is not None: stdlib.free(StubMerger._sha1_store_incdir)
StubMerger._sha1_store_arr = stdlib.malloc(StubMerger.MAX_INCLUDES * 17)
StubMerger._sha1_store_mod = stdlib.malloc(StubMerger.MAX_INCLUDES * 64)
StubMerger._sha1_store_rel = stdlib.malloc(StubMerger.MAX_INCLUDES * StubMerger.MAX_REL_PATH_LEN)
StubMerger._sha1_store_incdir = stdlib.malloc(StubMerger.MAX_INCLUDES * StubMerger.MAX_INC_DIR_LEN)
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None or StubMerger._sha1_store_incdir is None:
return -1
# 取消 C 整数提升后:MAX_INCLUDES/MAX_REL_PATH_LEN/MAX_INC_DIR_LEN 这些模块属性 CDefine 按最小位宽物化为 i16,
# 两侧都 i16 的乘积(1024*256 / 1024*512)会静默回绕成 0;显式 sizet() 把乘数加宽到 64 位,结果与旧「提升到 int」一致。
StubMerger._sha1_store_rel = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_REL_PATH_LEN)
StubMerger._sha1_store_incdir = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_INC_DIR_LEN)
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None or StubMerger._sha1_store_incdir is None: return -1
string.memset(StubMerger._sha1_store_arr, 0, StubMerger.MAX_INCLUDES * 17)
string.memset(StubMerger._sha1_store_mod, 0, StubMerger.MAX_INCLUDES * 64)
string.memset(StubMerger._sha1_store_rel, 0, StubMerger.MAX_INCLUDES * StubMerger.MAX_REL_PATH_LEN)
string.memset(StubMerger._sha1_store_incdir, 0, StubMerger.MAX_INCLUDES * StubMerger.MAX_INC_DIR_LEN)
string.memset(StubMerger._sha1_store_rel, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_REL_PATH_LEN)
string.memset(StubMerger._sha1_store_incdir, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_INC_DIR_LEN)
StubMerger._sha1_store_count = 0
p_bad: int = 0
entry_size_p: t.CSizeT = IncludesScanner.FileEntry.__sizeof__()
for i in range(scan_result.Count):
if StubMerger._sha1_store_count >= StubMerger.MAX_INCLUDES:
VLogger.error("includes SHA1 存储超上限,终止", "StubMerger")
ea: t.CUInt64T = t.CUInt64T(scan_result.Entries) + i * entry_size_p
ent: IncludesScanner.FileEntry | t.CPtr = (IncludesScanner.FileEntry | t.CPtr)(t.CVoid(ea, t.CPtr))
entry_size_p: sizet = IncludesScanner.FileEntry.__sizeof__()
for i in range(int(scan_result.Count)):
if StubMerger._sha1_store_count >= StubMerger.MAX_INCLUDES: VLogger.error("includes SHA1 store exceeded the limit; aborting", "StubMerger")
ea: uint64 = uint64(scan_result.Entries) + i * entry_size_p
ent: IncludesScanner.FileEntry* = IncludesScanner.FileEntry*(ea)
if ent is None or ent.Sha1 is None or ent.RelPath is None:
p_bad += 1
continue
# 构建完整路径 includes/{RelPath}(_PathToModuleName 需要)
rp_len: t.CSizeT = string.strlen(ent.RelPath)
# build full path includes/{RelPath} (needed by _PathToModuleName)
rp_len: sizet = string.strlen(ent.RelPath)
full_path_buf: bytes = stdlib.malloc(rp_len + 10)
if full_path_buf is None:
p_bad += 1
@@ -91,235 +164,270 @@ def PopulateSha1MapStore(scan_result: IncludesScanner.ScanResult | t.CPtr) -> in
mod_name: str = Utils.PathToModuleName(full_path_buf, 1)
stdlib.free(full_path_buf)
if mod_name is not None:
idx: t.CSizeT = t.CSizeT(StubMerger._sha1_store_count) * 17
idx: sizet = sizet(StubMerger._sha1_store_count) * 17
string.strcpy(StubMerger._sha1_store_arr + idx, ent.Sha1)
idx2: t.CSizeT = t.CSizeT(StubMerger._sha1_store_count) * 64
mn_len: t.CSizeT = string.strlen(mod_name)
if mn_len < 64:
string.strcpy(StubMerger._sha1_store_mod + idx2, mod_name)
idx2: sizet = sizet(StubMerger._sha1_store_count) * 64
mn_len: sizet = string.strlen(mod_name)
if mn_len < 64: string.strcpy(StubMerger._sha1_store_mod + idx2, mod_name)
else:
string.strncpy(StubMerger._sha1_store_mod + idx2, mod_name, 63)
StubMerger._sha1_store_mod[idx2 + 63] = '\0'
stdlib.free(mod_name)
# 保存 rel_path(供 Phase2 3.5 编译缺失 includes 使用)
idx3: t.CSizeT = t.CSizeT(StubMerger._sha1_store_count) * StubMerger.MAX_REL_PATH_LEN
if rp_len < StubMerger.MAX_REL_PATH_LEN:
string.strcpy(StubMerger._sha1_store_rel + idx3, ent.RelPath)
# save rel_path (used by Phase2 3.5 to compile missing includes)
idx3: sizet = sizet(StubMerger._sha1_store_count) * StubMerger.MAX_REL_PATH_LEN
if rp_len < StubMerger.MAX_REL_PATH_LEN: string.strcpy(StubMerger._sha1_store_rel + idx3, ent.RelPath)
else:
string.strncpy(StubMerger._sha1_store_rel + idx3, ent.RelPath, StubMerger.MAX_REL_PATH_LEN - 1)
StubMerger._sha1_store_rel[idx3 + StubMerger.MAX_REL_PATH_LEN - 1] = '\0'
# 保存所属 includes 根目录(Phase2 预注册/按需编译拼路径用)
idx_inc: t.CSizeT = t.CSizeT(StubMerger._sha1_store_count) * StubMerger.MAX_INC_DIR_LEN
# save owning includes root dir (Phase2 pre-register / on-demand path building)
idx_inc: sizet = sizet(StubMerger._sha1_store_count) * StubMerger.MAX_INC_DIR_LEN
if ent.IncDir is not None:
icd_len: t.CSizeT = string.strlen(ent.IncDir)
if icd_len < StubMerger.MAX_INC_DIR_LEN:
string.strcpy(StubMerger._sha1_store_incdir + idx_inc, ent.IncDir)
icd_len: sizet = string.strlen(ent.IncDir)
if icd_len < StubMerger.MAX_INC_DIR_LEN: string.strcpy(StubMerger._sha1_store_incdir + idx_inc, ent.IncDir)
else:
string.strncpy(StubMerger._sha1_store_incdir + idx_inc, ent.IncDir, StubMerger.MAX_INC_DIR_LEN - 1)
StubMerger._sha1_store_incdir[idx_inc + StubMerger.MAX_INC_DIR_LEN - 1] = '\0'
StubMerger._sha1_store_count += 1
if p_bad > 0:
fb_pbad: str = VLogger.fmt_buf()
if fb_pbad is not None:
viperlib.snprintf(fb_pbad, 1024, "includes SHA1 存储有 %d 个条目失败", p_bad)
VLogger.error(fb_pbad, "StubMerger")
if p_bad > 0: VLogger.error(f"includes SHA1 store has {p_bad} failed entries", "StubMerger")
return StubMerger._sha1_store_count
# ============================================================
# AppendToSha1MapStore - 追加条目到全局 SHA1 映射存储器
#
# Phase1 调用 PopulateSha1MapStore 填充 includes 文件,
# Phase2 扫描 App 源文件后调用此函数追加 App 模块条目,
# 确保编译 App 自身时跨模块 CDefine 查找能命中 App 模块
# (如 StubMerger.StubMerger.MAX_INCLUDES 在编译 Phase2 时通过
# _build_source_path_from_sha1 查找 StubMerger 的源路径)。
#
# 若存储器未初始化(PopulateSha1MapStore 未调用),自动初始化。
# 若 SHA1 已存在(去重),跳过。
#
# Args:
# sha1: SHA1 字符串(16 字符 + null)
# rel_path: 相对路径(如 "lib/core/StubMerger.vp",
# 不含 includes/ 前缀,与 _sha1_map.txt 格式一致)
#
# Returns:
# 0 成功(含去重跳过),1 失败
# ============================================================
# AppendToSha1MapStore - append an App module entry to the global SHA1 store (Phase2 use);
# auto-initializes if empty, skips on duplicate SHA1. Returns 0 ok, 1 failed.
def AppendToSha1MapStore(sha1: str, rel_path: str) -> int:
"""追加条目到全局 SHA1 映射存储器(Phase2 用)"""
if sha1 is None or rel_path is None:
return 1
# 存储器未初始化:自动初始化(Phase2 可能先于 Phase1 调用)
"""Append an entry to the global SHA1 store (Phase2)"""
if sha1 is None or rel_path is None: return 1
# init store if empty (Phase2 may run before Phase1)
if StubMerger._sha1_store_arr is None:
StubMerger._sha1_store_arr = stdlib.malloc(StubMerger.MAX_INCLUDES * 17)
StubMerger._sha1_store_mod = stdlib.malloc(StubMerger.MAX_INCLUDES * 64)
StubMerger._sha1_store_rel = stdlib.malloc(StubMerger.MAX_INCLUDES * StubMerger.MAX_REL_PATH_LEN)
StubMerger._sha1_store_incdir = stdlib.malloc(StubMerger.MAX_INCLUDES * StubMerger.MAX_INC_DIR_LEN)
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None or StubMerger._sha1_store_incdir is None:
return 1
# sizet(): 取消整数提升后 i16(MAX_INCLUDES)*i16(长度) 会回绕,显式加宽到 64 位
StubMerger._sha1_store_rel = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_REL_PATH_LEN)
StubMerger._sha1_store_incdir = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_INC_DIR_LEN)
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None or StubMerger._sha1_store_incdir is None: return 1
string.memset(StubMerger._sha1_store_arr, 0, StubMerger.MAX_INCLUDES * 17)
string.memset(StubMerger._sha1_store_mod, 0, StubMerger.MAX_INCLUDES * 64)
string.memset(StubMerger._sha1_store_rel, 0, StubMerger.MAX_INCLUDES * StubMerger.MAX_REL_PATH_LEN)
string.memset(StubMerger._sha1_store_incdir, 0, StubMerger.MAX_INCLUDES * StubMerger.MAX_INC_DIR_LEN)
string.memset(StubMerger._sha1_store_rel, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_REL_PATH_LEN)
string.memset(StubMerger._sha1_store_incdir, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_INC_DIR_LEN)
StubMerger._sha1_store_count = 0
if StubMerger._sha1_store_count >= StubMerger.MAX_INCLUDES:
return 1
# 模块名:rel_path "lib/core/StubMerger.vp" → "lib.core.StubMerger"
if StubMerger._sha1_store_count >= StubMerger.MAX_INCLUDES: return 1
# module name: rel_path "lib/core/StubMerger.vp" -> "lib.core.StubMerger"
full_path_buf: bytes = stdlib.malloc(string.strlen(rel_path) + 10)
if full_path_buf is None:
return 1
if full_path_buf is None: return 1
viperlib.snprintf(full_path_buf, string.strlen(rel_path) + 10, "includes/%s", rel_path)
mod_name: str = Utils.PathToModuleName(full_path_buf, 1)
stdlib.free(full_path_buf)
if mod_name is None:
return 1
# 去重:SHA1 与模块名都相同才跳过。同一 SHA1 可能被 includes 扫描先以
# 全路径模块名注册(如 App 源文件在 includes 目录下 → "Apps.Scene3D.gfx"),
# App 追加的短模块名("gfx")必须保留——否则 import gfx 的精确查找
# LookupSha1ByModName("gfx") 失败 → 后缀匹配劫持到 VKernel 同名 gfx
# (VKernel/Kernel/drivers/video/vesafb/gfx.vp,类 Scene3D 归属错乱根因)。
for i in range(StubMerger._sha1_store_count):
sidx_d: t.CSizeT = t.CSizeT(i) * 17
if mod_name is None: return 1
# Dedup only when both SHA1 and module name match: the same SHA1 may be registered first
# under a full-path name; the App short name must stay or exact lookup breaks.
for i in range(int(StubMerger._sha1_store_count)):
sidx_d: sizet = sizet(i) * 17
if string.strcmp(StubMerger._sha1_store_arr + sidx_d, sha1) == 0:
midx_d: t.CSizeT = t.CSizeT(i) * 64
midx_d: sizet = sizet(i) * 64
if string.strcmp(StubMerger._sha1_store_mod + midx_d, mod_name) == 0:
stdlib.free(mod_name)
return 0
# 写入 SHA1
idx_a: t.CSizeT = t.CSizeT(StubMerger._sha1_store_count) * 17
# write SHA1
idx_a: sizet = sizet(StubMerger._sha1_store_count) * 17
string.strcpy(StubMerger._sha1_store_arr + idx_a, sha1)
# 写入模块名
idx_m: t.CSizeT = t.CSizeT(StubMerger._sha1_store_count) * 64
mn_len: t.CSizeT = string.strlen(mod_name)
if mn_len < 64:
string.strcpy(StubMerger._sha1_store_mod + idx_m, mod_name)
# write module name
idx_m: sizet = sizet(StubMerger._sha1_store_count) * 64
mn_len: sizet = string.strlen(mod_name)
if mn_len < 64: string.strcpy(StubMerger._sha1_store_mod + idx_m, mod_name)
else:
string.strncpy(StubMerger._sha1_store_mod + idx_m, mod_name, 63)
StubMerger._sha1_store_mod[idx_m + 63] = '\0'
stdlib.free(mod_name)
# 写入相对路径
idx_r: t.CSizeT = t.CSizeT(StubMerger._sha1_store_count) * StubMerger.MAX_REL_PATH_LEN
rp_len: t.CSizeT = string.strlen(rel_path)
if rp_len < StubMerger.MAX_REL_PATH_LEN:
string.strcpy(StubMerger._sha1_store_rel + idx_r, rel_path)
# write rel path
idx_r: sizet = sizet(StubMerger._sha1_store_count) * StubMerger.MAX_REL_PATH_LEN
rp_len: sizet = string.strlen(rel_path)
if rp_len < StubMerger.MAX_REL_PATH_LEN: string.strcpy(StubMerger._sha1_store_rel + idx_r, rel_path)
else:
string.strncpy(StubMerger._sha1_store_rel + idx_r, rel_path, StubMerger.MAX_REL_PATH_LEN - 1)
StubMerger._sha1_store_rel[idx_r + StubMerger.MAX_REL_PATH_LEN - 1] = '\0'
# App 源文件不属于任何 includes 目录:IncDir 留空(Phase2 回退 includes_dir 参数)
idx_inc_a: t.CSizeT = t.CSizeT(StubMerger._sha1_store_count) * StubMerger.MAX_INC_DIR_LEN
# App sources belong to no includes dir: leave IncDir empty (Phase2 falls back to includes_dir)
idx_inc_a: sizet = sizet(StubMerger._sha1_store_count) * StubMerger.MAX_INC_DIR_LEN
StubMerger._sha1_store_incdir[idx_inc_a] = '\0'
StubMerger._sha1_store_count += 1
return 0
# AddIncludeToSha1MapStore - register one includes entry into the global SHA1 store (on-demand).
# Unreferenced includes stay out; App sources still use AppendToSha1MapStore (empty inc_dir).
def AddIncludeToSha1MapStore(sha1: str, rel_path: str, inc_dir: str, mod_name: str) -> int:
"""Register one includes entry in the global SHA1 store (on-demand scan)"""
if sha1 is None or rel_path is None or mod_name is None: return 1
# init store if empty
if StubMerger._sha1_store_arr is None:
StubMerger._sha1_store_arr = stdlib.malloc(StubMerger.MAX_INCLUDES * 17)
StubMerger._sha1_store_mod = stdlib.malloc(StubMerger.MAX_INCLUDES * 64)
# sizet(): 取消整数提升后 i16(MAX_INCLUDES)*i16(长度) 会回绕,显式加宽到 64 位
StubMerger._sha1_store_rel = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_REL_PATH_LEN)
StubMerger._sha1_store_incdir = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_INC_DIR_LEN)
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None or StubMerger._sha1_store_incdir is None: return 1
string.memset(StubMerger._sha1_store_arr, 0, StubMerger.MAX_INCLUDES * 17)
string.memset(StubMerger._sha1_store_mod, 0, StubMerger.MAX_INCLUDES * 64)
string.memset(StubMerger._sha1_store_rel, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_REL_PATH_LEN)
string.memset(StubMerger._sha1_store_incdir, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_INC_DIR_LEN)
StubMerger._sha1_store_count = 0
if StubMerger._sha1_store_count >= StubMerger.MAX_INCLUDES: return 1
# dedup when both SHA1 and module name match (aligned with AppendToSha1MapStore)
for i in range(int(StubMerger._sha1_store_count)):
sidx_d: sizet = sizet(i) * 17
if string.strcmp(StubMerger._sha1_store_arr + sidx_d, sha1) == 0:
midx_d: sizet = sizet(i) * 64
if string.strcmp(StubMerger._sha1_store_mod + midx_d, mod_name) == 0: return 0
# write SHA1
idx_a: sizet = sizet(StubMerger._sha1_store_count) * 17
string.strcpy(StubMerger._sha1_store_arr + idx_a, sha1)
# write module name
idx_m: sizet = sizet(StubMerger._sha1_store_count) * 64
mn_len: sizet = string.strlen(mod_name)
if mn_len < 64: string.strcpy(StubMerger._sha1_store_mod + idx_m, mod_name)
else:
string.strncpy(StubMerger._sha1_store_mod + idx_m, mod_name, 63)
StubMerger._sha1_store_mod[idx_m + 63] = '\0'
# write rel path
idx_r: sizet = sizet(StubMerger._sha1_store_count) * StubMerger.MAX_REL_PATH_LEN
rp_len: sizet = string.strlen(rel_path)
if rp_len < StubMerger.MAX_REL_PATH_LEN: string.strcpy(StubMerger._sha1_store_rel + idx_r, rel_path)
else:
string.strncpy(StubMerger._sha1_store_rel + idx_r, rel_path, StubMerger.MAX_REL_PATH_LEN - 1)
StubMerger._sha1_store_rel[idx_r + StubMerger.MAX_REL_PATH_LEN - 1] = '\0'
# write owning includes root (non-empty = includes entry; App entries empty)
idx_inc: sizet = sizet(StubMerger._sha1_store_count) * StubMerger.MAX_INC_DIR_LEN
if inc_dir is not None:
icd_len: sizet = string.strlen(inc_dir)
if icd_len < StubMerger.MAX_INC_DIR_LEN: string.strcpy(StubMerger._sha1_store_incdir + idx_inc, inc_dir)
else:
string.strncpy(StubMerger._sha1_store_incdir + idx_inc, inc_dir, StubMerger.MAX_INC_DIR_LEN - 1)
StubMerger._sha1_store_incdir[idx_inc + StubMerger.MAX_INC_DIR_LEN - 1] = '\0'
else: StubMerger._sha1_store_incdir[idx_inc] = '\0'
StubMerger._sha1_store_count += 1
return 0
# ============================================================
# GetSha1StoreCount - 获取全局存储器条目数
# ============================================================
# GetSha1StoreCount - return the global store entry count
def GetSha1StoreCount() -> int:
"""返回全局 SHA1 存储器的条目数"""
"""Return the global SHA1 store entry count"""
return StubMerger._sha1_store_count
# ============================================================
# LookupSha1ByModName - 从模块名查找 SHA1(直接查全局存储器)
#
# 精确匹配失败时,尝试 "{mod_name}.__init__" 回退,
# 支持 import ast(包)→ ast.__init__ 的 SHA1 查找。
#
# Returns:
# SHA1 字符串指针(指向全局存储器内部,无需释放),None 未找到
# ============================================================
# LookupSha1ByModName - find SHA1 by module name; falls back to "{mod_name}.__init__".
# Returns a pointer into the store (do not free), None if not found.
def LookupSha1ByModName(mod_name: str) -> str:
"""从模块名查找 SHA1(精确匹配 + 包名回退),委托通用实现 _FindSha1ByModName"""
"""Look up a SHA1 by module name (exact then package fallback); delegates to _FindSha1ByModName"""
return StubMergerIR._FindSha1ByModName(StubMerger._sha1_store_arr, StubMerger._sha1_store_mod, StubMerger._sha1_store_count, mod_name)
# Generic-class owner table (generic class name -> defining module SHA1), REPL-session only;
# needed because REPL skips generic templates, so generic subscript resolution would otherwise miss.
define GENERIC_OWNER_MAX = 32
_generic_owner_names: bytes = None
_generic_owner_sha1s: bytes = None
_generic_owner_count: int = 0
def ResetGenericOwners() -> None:
"""Clear the generic-class owner table (called once at REPL session start)"""
global _generic_owner_names, _generic_owner_sha1s, _generic_owner_count
if _generic_owner_names is not None:
stdlib.free(_generic_owner_names)
_generic_owner_names = None
if _generic_owner_sha1s is not None:
stdlib.free(_generic_owner_sha1s)
_generic_owner_sha1s = None
_generic_owner_count = 0
def RegisterGenericOwner(cls_name: str, mod_sha1: str) -> int:
"""Register generic-class-name to defining-module SHA1; 1 = registered, 0 = failed"""
global _generic_owner_names, _generic_owner_sha1s, _generic_owner_count
if cls_name is None or mod_sha1 is None: return 0
if cls_name[0] == '\0' or mod_sha1[0] == '\0': return 0
if _generic_owner_names is None:
_generic_owner_names = stdlib.malloc(GENERIC_OWNER_MAX * 64)
_generic_owner_sha1s = stdlib.malloc(GENERIC_OWNER_MAX * 17)
if _generic_owner_names is None or _generic_owner_sha1s is None: return 0
string.memset(_generic_owner_names, 0, GENERIC_OWNER_MAX * 64)
string.memset(_generic_owner_sha1s, 0, GENERIC_OWNER_MAX * 17)
_generic_owner_count = 0
i_go: int
for i_go in range(int(_generic_owner_count)):
if string.strcmp(_generic_owner_names + sizet(i_go) * 64, cls_name) == 0:
if string.strcmp(_generic_owner_sha1s + sizet(i_go) * 17, mod_sha1) == 0: return 1
return 0
if _generic_owner_count >= GENERIC_OWNER_MAX: return 0
string.strncpy(_generic_owner_names + sizet(_generic_owner_count) * 64, cls_name, 63)
string.strncpy(_generic_owner_sha1s + sizet(_generic_owner_count) * 17, mod_sha1, 16)
_generic_owner_count += 1
return 1
def LookupGenericOwnerSha1(cls_name: str) -> str:
"""Look up the defining-module SHA1 by generic class name (internal pointer, no free)"""
if cls_name is None or _generic_owner_names is None: return None
i_lg: int
for i_lg in range(int(_generic_owner_count)):
if string.strcmp(_generic_owner_names + sizet(i_lg) * 64, cls_name) == 0: return _generic_owner_sha1s + sizet(i_lg) * 17
return None
# ============================================================
# LookupSha1ByModNameSuffix - 后缀匹配查找模块 SHA1
#
# 用于 from .X import Y 的相对导入:impf.module 是 ".X"(带前导点),
# 去掉前导点后为 "X",SHA1 映射表中键是 "pkg.X",需要后缀匹配。
# 包含三种匹配方式:精确、后缀、反向后缀。
#
# Returns:
# SHA1 字符串指针(指向全局存储器内部,无需释放),None 未找到
# ============================================================
# LookupSha1ByModNameSuffix - find SHA1 by suffix; tries exact, suffix, reverse-suffix. None if absent.
def LookupSha1ByModNameSuffix(mod_suffix: str) -> str:
"""后缀匹配查找模块 SHA1"""
if mod_suffix is None:
return None
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None:
return None
if StubMerger._sha1_store_count <= 0:
return None
suffix_len_sf: t.CSizeT = string.strlen(mod_suffix)
if suffix_len_sf == 0:
return None
"""Look up a module SHA1 by suffix match"""
if mod_suffix is None: return None
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None: return None
if StubMerger._sha1_store_count <= 0: return None
suffix_len_sf: sizet = string.strlen(mod_suffix)
if suffix_len_sf == 0: return None
i_sf: int
for i_sf in range(StubMerger._sha1_store_count):
idx_sf: t.CSizeT = t.CSizeT(i_sf) * 64
for i_sf in range(int(StubMerger._sha1_store_count)):
idx_sf: sizet = sizet(i_sf) * 64
mod_name_sf: str = StubMerger._sha1_store_mod + idx_sf
mod_len_sf: t.CSizeT = string.strlen(mod_name_sf)
if mod_len_sf == 0:
continue
# 精确匹配
mod_len_sf: sizet = string.strlen(mod_name_sf)
if mod_len_sf == 0: continue
# exact match
if mod_len_sf == suffix_len_sf:
if string.strcmp(mod_name_sf, mod_suffix) == 0:
sidx_sf: t.CSizeT = t.CSizeT(i_sf) * 17
sidx_sf: sizet = sizet(i_sf) * 17
return StubMerger._sha1_store_arr + sidx_sf
# 后缀匹配:mod_name 以 "." + mod_suffix 结尾
# suffix match: mod_name ends with "." + mod_suffix
if mod_len_sf > suffix_len_sf:
if mod_name_sf[mod_len_sf - suffix_len_sf - 1] == '.':
cmp_start_sf: str = mod_name_sf + mod_len_sf - suffix_len_sf
if string.strcmp(cmp_start_sf, mod_suffix) == 0:
sidx2_sf: t.CSizeT = t.CSizeT(i_sf) * 17
sidx2_sf: sizet = sizet(i_sf) * 17
return StubMerger._sha1_store_arr + sidx2_sf
# 反向后缀匹配:mod_suffix 以 "." + mod_name 结尾
# reverse suffix match: mod_suffix ends with "." + mod_name
if suffix_len_sf > mod_len_sf:
if mod_suffix[suffix_len_sf - mod_len_sf - 1] == '.':
cmp_start_rev_sf: str = mod_suffix + suffix_len_sf - mod_len_sf
if string.strcmp(cmp_start_rev_sf, mod_name_sf) == 0:
sidx_rev_sf: t.CSizeT = t.CSizeT(i_sf) * 17
sidx_rev_sf: sizet = sizet(i_sf) * 17
return StubMerger._sha1_store_arr + sidx_rev_sf
return None
# ============================================================
# LookupSha1ByAlias - 从 from_imports 别名映射解析模块 SHA1
#
# from_imports 条目格式: "local:module" 或 "local:module:orig"(空格分隔),
# 由 ImportsGenMixin.add_from_import 写入(import X as Y → "Y:X")。
# 命中 local==alias 时,对 module 执行 LookupSha1ByModName 返回 SHA1。
#
# Returns:
# SHA1 字符串指针(指向全局存储器内部),None 未找到
# ============================================================
# LookupSha1ByAlias - resolve SHA1 from from_imports alias "local:module[:orig]" entries.
def LookupSha1ByAlias(from_imports: str, alias: str) -> str:
"""从 from_imports 别名映射解析模块 SHA1(改名别名 import X as Y 场景)"""
if from_imports is None or alias is None:
return None
alias_len: t.CSizeT = string.strlen(alias)
if alias_len == 0:
return None
total_len: t.CSizeT = string.strlen(from_imports)
ci: t.CSizeT = 0
"""Resolve a module SHA1 from a from-import alias mapping (import X as Y)"""
if from_imports is None or alias is None: return None
alias_len: sizet = string.strlen(alias)
if alias_len == 0: return None
total_len: sizet = string.strlen(from_imports)
ci: sizet = 0
while ci < total_len:
# 跳过前导空格
while ci < total_len and from_imports[ci] == ' ':
ci += 1
if ci >= total_len:
break
# 条目起点 ci,找 ':'(local_name 结束)
colon: t.CSizeT = ci
while colon < total_len and from_imports[colon] != ':' and from_imports[colon] != ' ':
colon += 1
local_len: t.CSizeT = colon - ci
# skip leading spaces
while ci < total_len and from_imports[ci] == ' ': ci += 1
if ci >= total_len: break
# entry starts at ci; find ':' (end of local_name)
colon: sizet = ci
while colon < total_len and from_imports[colon] != ':' and from_imports[colon] != ' ': colon += 1
local_len: sizet = colon - ci
if colon < total_len and from_imports[colon] == ':' and local_len == alias_len:
# 比较 local_name == alias
match: int = 1
li: t.CSizeT = 0
while li < alias_len:
# compare local_name == alias
Matched: int = 1
for li in range(int(alias_len)):
if from_imports[ci + li] != alias[li]:
match = 0
Matched = 0
break
li += 1
if match == 1:
# 提取 module(colon+1 到下一个 ':' 或空格)
m_start: t.CSizeT = colon + 1
m_end: t.CSizeT = m_start
while m_end < total_len and from_imports[m_end] != ':' and from_imports[m_end] != ' ':
m_end += 1
if Matched == 1:
# extract module (from colon+1 to next ':' or space)
m_start: sizet = colon + 1
m_end: sizet = m_start
while m_end < total_len and from_imports[m_end] != ':' and from_imports[m_end] != ' ': m_end += 1
if m_end > m_start:
mbuf: str = stdlib.malloc(m_end - m_start + 1)
if mbuf is not None:
@@ -329,55 +437,38 @@ def LookupSha1ByAlias(from_imports: str, alias: str) -> str:
stdlib.free(mbuf)
return res
return None
# 跳到下一条目
while ci < total_len and from_imports[ci] != ' ':
ci += 1
# advance to next entry
while ci < total_len and from_imports[ci] != ' ': ci += 1
return None
# ============================================================
# LookupModNameByAlias - 从 from_imports 别名映射解析模块真名
#
# import X as Y 时 ImportsGenMixin.add_from_import 写入 "Y:X"(Y=别名,
# X=模块真名)。本函数返回别名 Y 对应的模块真名 X(供限定名解析
# "Y.Class" → 按 X 模块归属匹配,如 "types.fileinfo" → "fat32_types")。
#
# Returns:
# 模块名字符串(stdlib 分配,调用方负责 free),None 未找到
# ============================================================
# LookupModNameByAlias - return real module name X for alias Y (from_imports "Y:X"; caller frees).
def LookupModNameByAlias(from_imports: str, alias: str) -> str:
"""从 from_imports 别名映射解析模块真名(import X as Y → Y:X)"""
if from_imports is None or alias is None:
return None
alias_len: t.CSizeT = string.strlen(alias)
if alias_len == 0:
return None
total_len: t.CSizeT = string.strlen(from_imports)
ci: t.CSizeT = 0
"""Resolve the real module name for an alias from from_imports (import X as Y -> Y:X)"""
if from_imports is None or alias is None: return None
alias_len: sizet = string.strlen(alias)
if alias_len == 0: return None
total_len: sizet = string.strlen(from_imports)
ci: sizet = 0
while ci < total_len:
# 跳过前导空格
while ci < total_len and from_imports[ci] == ' ':
ci += 1
if ci >= total_len:
break
# 条目起点 ci,找 ':'(local_name 结束)
colon: t.CSizeT = ci
while colon < total_len and from_imports[colon] != ':' and from_imports[colon] != ' ':
colon += 1
local_len: t.CSizeT = colon - ci
# skip leading spaces
while ci < total_len and from_imports[ci] == ' ': ci += 1
if ci >= total_len: break
# entry starts at ci; find ':' (end of local_name)
colon: sizet = ci
while colon < total_len and from_imports[colon] != ':' and from_imports[colon] != ' ': colon += 1
local_len: sizet = colon - ci
if colon < total_len and from_imports[colon] == ':' and local_len == alias_len:
# 比较 local_name == alias
match: int = 1
li: t.CSizeT = 0
while li < alias_len:
# compare local_name == alias
Matched: int = 1
for li in range(int(alias_len)):
if from_imports[ci + li] != alias[li]:
match = 0
Matched = 0
break
li += 1
if match == 1:
# 提取 module(colon+1 到下一个 ':' 或空格)
m_start: t.CSizeT = colon + 1
m_end: t.CSizeT = m_start
while m_end < total_len and from_imports[m_end] != ':' and from_imports[m_end] != ' ':
m_end += 1
if Matched == 1:
# extract module (from colon+1 to next ':' or space)
m_start: sizet = colon + 1
m_end: sizet = m_start
while m_end < total_len and from_imports[m_end] != ':' and from_imports[m_end] != ' ': m_end += 1
if m_end > m_start:
mbuf: str = stdlib.malloc(m_end - m_start + 1)
if mbuf is not None:
@@ -385,141 +476,101 @@ def LookupModNameByAlias(from_imports: str, alias: str) -> str:
mbuf[m_end - m_start] = '\0'
return mbuf
return None
# 跳到下一条目
while ci < total_len and from_imports[ci] != ' ':
ci += 1
# advance to next entry
while ci < total_len and from_imports[ci] != ' ': ci += 1
return None
# ============================================================
# PopulateIncludesDeps - 将 includes 文件的依赖存入内存
#
# Phase B+ 前用 declare_only 翻译 includes 文件,提取 _imported_modules
# 后调用此函数存入内存。_BuildReachableSha1Set 从内存读取,不读 deps.txt。
#
# Args:
# sha1: includes 文件的 SHA1(16字符)
# deps_str: 依赖模块名字符串(空格分隔,如 "t c stdint string")
#
# Returns:
# 0 成功,非 0 失败
# ============================================================
# PopulateIncludesDeps - store includes deps in memory (deps_str space-separated). 0 ok, non-0 fail.
def PopulateIncludesDeps(sha1: str, deps_str: str) -> int:
"""将 includes 文件的依赖存入内存"""
if sha1 is None:
return 1
# 延迟初始化
"""Store includes-file dependencies in memory"""
if sha1 is None: return 1
# lazy init
if StubMerger._includes_deps_sha1 is None:
StubMerger._includes_deps_sha1 = stdlib.malloc(StubMerger.MAX_INCLUDES * 17)
StubMerger._includes_deps_str = stdlib.malloc(StubMerger.MAX_INCLUDES * StubMerger.MAX_DEPS_STR_LEN)
if StubMerger._includes_deps_sha1 is None or StubMerger._includes_deps_str is None:
return 1
# sizet(): 取消整数提升后 i16(MAX_INCLUDES)*i16(MAX_DEPS_STR_LEN) 会回绕成 0,显式加宽到 64 位
StubMerger._includes_deps_str = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_DEPS_STR_LEN)
if StubMerger._includes_deps_sha1 is None or StubMerger._includes_deps_str is None: return 1
string.memset(StubMerger._includes_deps_sha1, 0, StubMerger.MAX_INCLUDES * 17)
string.memset(StubMerger._includes_deps_str, 0, StubMerger.MAX_INCLUDES * StubMerger.MAX_DEPS_STR_LEN)
string.memset(StubMerger._includes_deps_str, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_DEPS_STR_LEN)
StubMerger._includes_deps_count = 0
if StubMerger._includes_deps_count >= StubMerger.MAX_INCLUDES:
return 1
# 去重:若 SHA1 已存在则跳过
for i in range(StubMerger._includes_deps_count):
sidx: t.CSizeT = t.CSizeT(i) * 17
if string.strcmp(StubMerger._includes_deps_sha1 + sidx, sha1) == 0:
return 0
# 写入 SHA1
idx: t.CSizeT = t.CSizeT(StubMerger._includes_deps_count) * 17
if StubMerger._includes_deps_count >= StubMerger.MAX_INCLUDES: return 1
# dedup: skip if SHA1 already present
for i in range(int(StubMerger._includes_deps_count)):
sidx: sizet = sizet(i) * 17
if string.strcmp(StubMerger._includes_deps_sha1 + sidx, sha1) == 0: return 0
# write SHA1
idx: sizet = sizet(StubMerger._includes_deps_count) * 17
string.strcpy(StubMerger._includes_deps_sha1 + idx, sha1)
# 写入依赖字符串
idx2: t.CSizeT = t.CSizeT(StubMerger._includes_deps_count) * StubMerger.MAX_DEPS_STR_LEN
# write deps string
idx2: sizet = sizet(StubMerger._includes_deps_count) * StubMerger.MAX_DEPS_STR_LEN
if deps_str is not None:
dl: t.CSizeT = string.strlen(deps_str)
if dl < StubMerger.MAX_DEPS_STR_LEN:
string.strcpy(StubMerger._includes_deps_str + idx2, deps_str)
else:
string.strncpy(StubMerger._includes_deps_str + idx2, deps_str, StubMerger.MAX_DEPS_STR_LEN - 1)
else:
StubMerger._includes_deps_str[idx2] = '\0'
dl: sizet = string.strlen(deps_str)
if dl < StubMerger.MAX_DEPS_STR_LEN: string.strcpy(StubMerger._includes_deps_str + idx2, deps_str)
else: string.strncpy(StubMerger._includes_deps_str + idx2, deps_str, StubMerger.MAX_DEPS_STR_LEN - 1)
else: StubMerger._includes_deps_str[idx2] = '\0'
StubMerger._includes_deps_count = StubMerger._includes_deps_count + 1
return 0
# ============================================================
# LookupIncludesDeps - 从内存查找 includes 文件的依赖
#
# Args:
# sha1: includes 文件的 SHA1(16字符)
#
# Returns:
# 依赖模块名字符串指针(空格分隔),None 表示未找到
# ============================================================
# LookupIncludesDeps - look up includes deps by SHA1 in memory; returns space-separated deps or None.
def LookupIncludesDeps(sha1: str) -> str:
"""从内存查找 includes 文件的依赖"""
if sha1 is None or StubMerger._includes_deps_sha1 is None:
return None
if StubMerger._includes_deps_count <= 0:
return None
for i in range(StubMerger._includes_deps_count):
sidx: t.CSizeT = t.CSizeT(i) * 17
"""Look up includes-file dependencies in memory"""
if sha1 is None or StubMerger._includes_deps_sha1 is None: return None
if StubMerger._includes_deps_count <= 0: return None
for i in range(int(StubMerger._includes_deps_count)):
sidx: sizet = sizet(i) * 17
if string.strcmp(StubMerger._includes_deps_sha1 + sidx, sha1) == 0:
idx2: t.CSizeT = t.CSizeT(i) * StubMerger.MAX_DEPS_STR_LEN
idx2: sizet = sizet(i) * StubMerger.MAX_DEPS_STR_LEN
return StubMerger._includes_deps_str + idx2
return None
def GetSha1StoreArrPtr() -> bytes:
"""返回 SHA1 数组指针(每个条目 17 字节)"""
"""Return the SHA1 array pointer (17 bytes per entry)"""
return StubMerger._sha1_store_arr
def GetSha1StoreModArrPtr() -> bytes:
"""返回模块名数组指针(每个条目 64 字节)"""
"""Return the module-name array pointer (64 bytes per entry)"""
return StubMerger._sha1_store_mod
def GetSha1StoreRelArrPtr() -> bytes:
"""返回相对路径数组指针(每个条目 StubMerger.MAX_REL_PATH_LEN 字节)"""
"""Return the rel-path array pointer (StubMerger.MAX_REL_PATH_LEN bytes per entry)"""
return StubMerger._sha1_store_rel
def GetSha1StoreIncdirArrPtr() -> bytes:
"""返回所属 includes 根目录数组指针(每个条目 StubMerger.MAX_INC_DIR_LEN 字节,空串表示不属于任何 includes 目录)"""
"""Return the owning includes-root array pointer (empty string = not in any includes dir)"""
return StubMerger._sha1_store_incdir
# ============================================================
# GetSha1StoreArr - 获取全局存储器数组指针(直接遍历用)
#
# 返回 sha1_arr、mod_arr、rel_arr 三个数组的指针和条目数。
# 调用者直接遍历数组:sha1_arr[i*17]、mod_arr[i*64]、rel_arr[i*StubMerger.MAX_REL_PATH_LEN]
# 内存归全局存储器所有,调用者不要 free
# ============================================================
def GetSha1StoreArr(out_sha1_arr: t.CPtr, out_mod_arr: t.CPtr, out_rel_arr: t.CPtr) -> int:
"""获取全局存储器数组指针
out_sha1_arr/out_mod_arr/out_rel_arr 是调用者提供的 t.CPtr 指针,
函数将存储器数组指针写入这些位置。
返回条目数,-1 失败
"""
if out_sha1_arr is None or out_mod_arr is None or out_rel_arr is None:
return -1
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None:
return -1
sp: t.CPtr = (t.CPtr | t.CPtr)(t.CVoid(out_sha1_arr, t.CPtr)) # 存疑
if sp is not None:
sp[0] = StubMerger._sha1_store_arr
mp: t.CPtr = (t.CPtr | t.CPtr)(t.CVoid(out_mod_arr, t.CPtr))
if mp is not None:
mp[0] = StubMerger._sha1_store_mod
rp: t.CPtr = (t.CPtr | t.CPtr)(t.CVoid(out_rel_arr, t.CPtr))
if rp is not None:
rp[0] = StubMerger._sha1_store_rel
# GetSha1StoreArr - return the store's sha1/mod/rel array pointers and entry count; do not free.
def GetSha1StoreArr(out_sha1_arr: rawptr[t.CPtr], out_mod_arr: rawptr[t.CPtr], out_rel_arr: rawptr[t.CPtr]) -> int:
"""Get the global store array pointers and entry count; returns -1 on failure"""
if out_sha1_arr is None or out_mod_arr is None or out_rel_arr is None: return -1
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None: return -1
sp: rawptr[t.CPtr] = out_sha1_arr
if sp is not None: sp[0] = StubMerger._sha1_store_arr
mp: rawptr[t.CPtr] = out_mod_arr
if mp is not None: mp[0] = StubMerger._sha1_store_mod
rp: rawptr[t.CPtr] = out_rel_arr
if rp is not None: rp[0] = StubMerger._sha1_store_rel
return StubMerger._sha1_store_count
# ============================================================
# LookupSha1RelPath - 通过 SHA1 查找相对路径
#
# 遍历全局存储器,返回 sha1 对应的相对路径指针(只读,不要 free)。
# 用于错误提示:将 sha1 转换为人类可读的文件路径。
#
# Args:
# sha1: SHA1 字符串(16 字符)
#
# Returns:
# 相对路径字符串指针(只读),None=未找到
# ============================================================
# LookupSha1RelPath - return the read-only rel_path pointer for a SHA1 (no free), None if missing.
def LookupSha1RelPath(sha1: str) -> str:
"""通过 SHA1 查找相对路径,返回只读指针,None=未找到"""
if sha1 is None:
return None
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_rel is None:
return None
for i in range(StubMerger._sha1_store_count):
sidx: t.CSizeT = t.CSizeT(i) * 17
"""Return the read-only rel-path pointer for a SHA1, or None if missing"""
if sha1 is None: return None
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_rel is None: return None
for i in range(int(StubMerger._sha1_store_count)):
sidx: sizet = sizet(i) * 17
if string.strcmp(StubMerger._sha1_store_arr + sidx, sha1) == 0:
ridx: t.CSizeT = t.CSizeT(i) * StubMerger.MAX_REL_PATH_LEN
ridx: sizet = sizet(i) * StubMerger.MAX_REL_PATH_LEN
return StubMerger._sha1_store_rel + ridx
return None
# ============================================================
# LookupModNameBySha1 - reverse lookup module name by SHA1 (read-only ptr, no free); first hit wins.
def LookupModNameBySha1(sha1: str) -> str:
"""Return the read-only module-name pointer for a SHA1, or None if missing"""
if sha1 is None or sha1[0] == '\0': return None
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None: return None
for i in range(int(StubMerger._sha1_store_count)):
sidx_mb: sizet = sizet(i) * 17
if string.strcmp(StubMerger._sha1_store_arr + sidx_mb, sha1) == 0:
midx_mb: sizet = sizet(i) * 64
return StubMerger._sha1_store_mod + midx_mb
return None
+62 -128
View File
@@ -1,182 +1,116 @@
import t, c
from stdint import *
import memhub
import string
import stdlib
import llvmlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import viperlib
import sys
import ast
import lib.core.VLogger as VLogger
import lib.core.IncludesScanner as IncludesScanner
import lib.core.Handles.HandlesTranslator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger as StubMerger
# ============================================================
# StubMergerTopo - StubMerger 拆分(阶段 E):StubMergerTopo
#
# 直接移动模式:函数实现迁至此模块,原 StubMerger.vp 仅保留
# 常量/全局变量;调用方经本模块前缀访问。全局变量跨模块经
# StubMerger._xxx 访问(main.vp 先例)。
# ============================================================
# StubMergerTopo - reverse-topological sort of reachable includes (dependencies first).
# Functions live in this module; cross-module globals are accessed via StubMerger._xxx.
# ============================================================
# BuildTopoSortedIndices - 对可达 includes 逆拓扑排序(被依赖者先)
#
# 背景:跨模块 vtable 生成要求被依赖模块先完整翻译(如 astaux.vp 继承
# base.vp 的 AST,vtable 槽位需 AST.accept 的 TypeStr 已注册到全局表)。
# Phase B+ 原先按 store 扫描顺序编译,依赖顺序不保证 → fail-fast 暴露。
#
# 算法:Kahn 逆拓扑。边 m→d 表示 m 依赖 d(d 在 m 的 deps 中)。
# indegree[m] = m 在可达集合内的直接依赖数;
# 入度为 0(不依赖任何可达模块)者先输出,输出后对依赖它的模块
# 减入度,入度归 0 者入队。结果即"被依赖者先编译"。
# reachable_count <= 0 时对全部 store 排序(与既有全量编译行为一致)。
#
# Args:
# store_count / store_sha1_arr / store_mod_arr: store 元数据
# reachable_set / reachable_count: 可达集合(_BuildReachableSha1Set 输出)
# sorted_idx: 输出数组(store_count+1 个 8 字节槽),前 N 个为拓扑序 store 索引
#
# Returns:
# 拓扑序条数;有环时剩余条目按 store 顺序补齐(避免漏编译)
# ============================================================
def BuildTopoSortedIndices(store_count: int, store_sha1_arr: bytes,
store_mod_arr: bytes, reachable_set: bytes,
reachable_count: int,
sorted_idx: t.CSizeT | t.CPtr) -> int:
"""对可达 includes 逆拓扑排序(被依赖者先),store 索引写入 sorted_idx"""
if store_count <= 0 or store_sha1_arr is None or store_mod_arr is None or sorted_idx is None:
return 0
indegree: t.CSizeT | t.CPtr = (t.CSizeT | t.CPtr)(stdlib.malloc(8 * (store_count + 1)))
queue: t.CSizeT | t.CPtr = (t.CSizeT | t.CPtr)(stdlib.malloc(8 * (store_count + 1)))
rel_idx: t.CSizeT | t.CPtr = (t.CSizeT | t.CPtr)(stdlib.malloc(8 * (store_count + 1)))
# BuildTopoSortedIndices - reverse-topological sort of reachable includes (dependencies first).
# Uses Kahn's algorithm; reachable_count <= 0 sorts all stores; cycles are appended in store order.
def BuildTopoSortedIndices(store_count: int, store_sha1_arr: bytes, store_mod_arr: bytes, reachable_set: bytes, reachable_count: int, sorted_idx: sizet*) -> int:
"""Reverse-topologically sort reachable includes (dependencies first); store indices go to sorted_idx."""
if store_count <= 0 or store_sha1_arr is None or store_mod_arr is None or sorted_idx is None: return 0
indegree: sizet* = sizet*(stdlib.malloc(8 * (store_count + 1)))
queue: sizet* = sizet*(stdlib.malloc(8 * (store_count + 1)))
rel_idx: sizet* = sizet*(stdlib.malloc(8 * (store_count + 1)))
if indegree is None or queue is None or rel_idx is None:
if indegree is not None:
stdlib.free(indegree)
if queue is not None:
stdlib.free(queue)
if rel_idx is not None:
stdlib.free(rel_idx)
if indegree is not None: stdlib.free(indegree)
if queue is not None: stdlib.free(queue)
if rel_idx is not None: stdlib.free(rel_idx)
return 0
# 清零 indegree/queue/rel_idx(malloc 返回未初始化内存,Kahn 依赖入度初值 0)
# Zero indegree/queue/rel_idx (malloc memory is uninitialized)
zi: int = 0
for zi in range(store_count + 1):
for zi in range(int(store_count + 1)):
indegree[zi] = 0
queue[zi] = 0
rel_idx[zi] = 0
# 收集可达 store 索引(reachable_count<=0 时全量,对齐既有全量编译行为)
# Collect reachable store indices (all stores when reachable_count<=0)
rel_count: int = 0
i_t: int
if reachable_set is None or reachable_count <= 0:
for i_t in range(store_count):
for i_t in range(int(store_count)):
rel_idx[rel_count] = i_t
rel_count += 1
else:
for i_t in range(store_count):
if StubMergerCollect._is_in_sha1_set(store_sha1_arr + t.CSizeT(i_t) * 17, reachable_set, reachable_count) != 0:
for i_t in range(int(store_count)):
if StubMergerCollect._is_in_sha1_set(store_sha1_arr + sizet(i_t) * 17, reachable_set, reachable_count) != 0:
rel_idx[rel_count] = i_t
rel_count += 1
# 诊断:可达 store 索引数(rel_count=0 → topo_count=0 → PhaseB+ 循环不执行)
fb_rel: str = VLogger.fmt_buf()
if fb_rel is not None:
viperlib.snprintf(fb_rel, 1024, "诊断 topo rel_count=%d", rel_count)
VLogger.debug(fb_rel, "StubMergerTopo")
# 计算入度:m 在可达集合内的直接依赖数
i_r: int
for i_r in range(rel_count):
si: int = rel_idx[i_r]
deps_si: str = StubMergerStore.LookupIncludesDeps(store_sha1_arr + t.CSizeT(si) * 17)
if deps_si is None:
continue
dl_si: t.CSizeT = string.strlen(deps_si)
dpos: t.CSizeT = 0
# Compute indegree: number of direct deps of m within the reachable set
for i_r in range(int(rel_count)):
si: int = int(rel_idx[i_r])
deps_si: str = StubMergerStore.LookupIncludesDeps(store_sha1_arr + sizet(si) * 17)
if deps_si is None: continue
dl_si: sizet = string.strlen(deps_si)
dpos: sizet = 0
while dpos < dl_si:
dstart: t.CSizeT = dpos
while dpos < dl_si and deps_si[dpos] != ' ':
dpos += 1
dlen: t.CSizeT = dpos - dstart
if dpos < dl_si:
dpos += 1
if dlen == 0:
continue
dstart: sizet = dpos
while dpos < dl_si and deps_si[dpos] != ' ': dpos += 1
dlen: sizet = dpos - dstart
if dpos < dl_si: dpos += 1
if dlen == 0: continue
dname_buf: bytes = stdlib.malloc(dlen + 1)
if dname_buf is None:
continue
if dname_buf is None: continue
string.strncpy(dname_buf, deps_si + dstart, dlen)
dname_buf[dlen] = '\0'
dsha1: str = StubMergerIR._FindSha1ByModName(store_sha1_arr, store_mod_arr, store_count, dname_buf)
stdlib.free(dname_buf)
if dsha1 is not None and StubMergerCollect._is_in_sha1_set(dsha1, reachable_set, reachable_count) != 0:
# 中间 int 变量累加:绕过联合类型(CSizeT|CPtr)+ 整数的指针算术编译 bug
indg: int = indegree[si]
# Use an intermediate int to work around a union-pointer arithmetic codegen bug
indg: int = int(indegree[si])
indg = indg + 1
indegree[si] = indg
# Kahn:入度为 0(不依赖任何可达模块)者先出 → 最先编译
# Kahn: nodes with indegree 0 (no reachable deps) are output first
q_tail: int = 0
for i_r in range(rel_count):
si2: int = rel_idx[i_r]
for i_r in range(int(rel_count)):
si2: int = int(rel_idx[i_r])
if indegree[si2] == 0:
queue[q_tail] = si2
q_tail += 1
out_count: int = 0
# Kahn 主循环必须用 while(不能用 for range):循环体内会动态入队
# (queue[q_tail]=si3; q_tail+=1),for range 上界只求值一次,
# 动态入队的模块永远不会被处理且兜底(indegree>0)也捞不回 → 拓扑漏项
# The Kahn main loop must use while, not for range: the body enqueues dynamically,
# and a for range bound is evaluated once, so newly queued modules would be skipped.
q_head: int = 0
while q_head < q_tail:
m: int = queue[q_head]
m: int = int(queue[q_head])
sorted_idx[out_count] = m
out_count += 1
# 对依赖 m 的可达模块减入度
mod_m: str = store_mod_arr + t.CSizeT(m) * 64
mod_m_len: t.CSizeT = string.strlen(mod_m)
for i_r in range(rel_count):
si3: int = rel_idx[i_r]
if indegree[si3] == 0:
continue
deps_si3: str = StubMergerStore.LookupIncludesDeps(store_sha1_arr + t.CSizeT(si3) * 17)
if deps_si3 is None:
continue
dl_si3: t.CSizeT = string.strlen(deps_si3)
dpos3: t.CSizeT = 0
# Decrement indegree of reachable modules that depend on m
mod_m: str = store_mod_arr + sizet(m) * 64
mod_m_len: sizet = string.strlen(mod_m)
for i_r in range(int(rel_count)):
si3: int = int(rel_idx[i_r])
if indegree[si3] == 0: continue
deps_si3: str = StubMergerStore.LookupIncludesDeps(store_sha1_arr + sizet(si3) * 17)
if deps_si3 is None: continue
dl_si3: sizet = string.strlen(deps_si3)
dpos3: sizet = 0
found_dep: int = 0
while dpos3 < dl_si3 and found_dep == 0:
dstart3: t.CSizeT = dpos3
while dpos3 < dl_si3 and deps_si3[dpos3] != ' ':
dpos3 += 1
dlen3: t.CSizeT = dpos3 - dstart3
if dpos3 < dl_si3:
dpos3 += 1
if dlen3 == 0:
continue
if dlen3 == mod_m_len and string.strncmp(deps_si3 + dstart3, mod_m, mod_m_len) == 0:
found_dep = 1
dstart3: sizet = dpos3
while dpos3 < dl_si3 and deps_si3[dpos3] != ' ': dpos3 += 1
dlen3: sizet = dpos3 - dstart3
if dpos3 < dl_si3: dpos3 += 1
if dlen3 == 0: continue
if dlen3 == mod_m_len and string.strncmp(deps_si3 + dstart3, mod_m, mod_m_len) == 0: found_dep = 1
if found_dep == 1:
# 中间 int 变量递减:绕过联合类型指针算术编译 bug
indg3: int = indegree[si3]
# Use an intermediate int decrement to work around a union-pointer arithmetic codegen bug
indg3: int = int(indegree[si3])
indg3 = indg3 - 1
indegree[si3] = indg3
if indegree[si3] == 0:
queue[q_tail] = si3
q_tail += 1
q_head += 1
# 有环时剩余条目按 store 顺序补齐(避免漏编译)
for i_r in range(rel_count):
si4: int = rel_idx[i_r]
# On cycles, append remaining entries in store order to avoid skipping compilation
for i_r in range(int(rel_count)):
si4: int = int(rel_idx[i_r])
if indegree[si4] > 0:
sorted_idx[out_count] = si4
out_count += 1
+32 -68
View File
@@ -1,78 +1,42 @@
import t, c
from stdint import *
import memhub
import string
import stdlib
import llvmlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import viperlib
import sys
import ast
import lib.core.VLogger as VLogger
import lib.core.IncludesScanner as IncludesScanner
import lib.core.Handles.HandlesTranslator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config
# ============================================================
# StubMerger - stub 声明合并模块
#
# 从 temp 目录加载所有 .stub.ll 文件,将声明内容合并到主 IR。
# 这是 Phase2 的核心功能:翻译主文件时,将所有依赖的 stub
# 声明合并到主 IR 前面,使链接器能解析跨模块符号引用。
#
# 合并策略:
# 1. 扫描 temp 目录,收集所有 {sha1}.stub.ll 文件
# 2. 读取每个 stub 文件内容
# 3. 过滤去重(避免重复声明)
# 4. 将声明插入到主 IR 的 target triple 之后
# ============================================================
# StubMerger - merges dependency stub declarations into the main IR (Phase2 core):
# collect {sha1}.stub.ll from temp, dedup, then insert after the target triple so cross-module symbols link.
# 全局 mbuddy 指针
_mbuddy: memhub.MemBuddy | t.CPtr
# Global mbuddy pointer
_mbuddy: memhub.MemBuddy*
# 最大 includes SHA1 数(用于过滤)
MAX_INCLUDES_SHA1: t.CDefine = 1024
# stub 读取缓冲区大小
STUB_READ_BUF_SIZE: t.CDefine = 1048576
# 最大 includes 条目数
MAX_INCLUDES: t.CDefine = 1024
# 可达性分析 worklist/processed 缓冲区大小(8192 不够:递归扫描 source_dir
# 后 worklist 含全部 App 文件 imports 的拼接 + BFS 依赖展开,追加82 溢出)
REACH_WORKLIST_BUF: t.CDefine = 65536
# Max includes SHA1 entries (for filtering)
define MAX_INCLUDES_SHA1 = 1024
# Stub/text read buffer size. 1MB is too small: text.ll files exceed 1MB, so a single
# truncated read drops trailing data (opt: "end of file in global variable name").
define STUB_READ_BUF_SIZE = 8388608
# Max includes entries
define MAX_INCLUDES = 1024
# Reachability worklist/processed buffer size. 8192 is too small: after scanning
# source_dir it holds all App imports plus BFS expansion, overflowing on append 82.
define REACH_WORKLIST_BUF = 65536
# 全局 SHA1 映射存储器(内存中,不依赖 _sha1_map.txt 文件)
# Phase1 扫描完成后填充,供所有函数使用
_sha1_store_arr: bytes # SHA1 数组(每个 17 字节,MAX_INCLUDES 个)
_sha1_store_mod: bytes # 模块名数组(每个 64 字节,MAX_INCLUDES 个)
_sha1_store_rel: bytes # 相对路径数组(每个 256 字节,MAX_INCLUDES 个)
_sha1_store_incdir: bytes # 所属 includes 根目录数组(每个 512 字节,MAX_INCLUDES 个,多 includes 支持)
_sha1_store_count: int # 条目数
# Global SHA1 map store (in memory, no dependency on _sha1_map.txt).
# Filled after the Phase1 scan and shared by all functions.
_sha1_store_arr: bytes # SHA1 array (17 bytes each, MAX_INCLUDES entries)
_sha1_store_mod: bytes # Module-name array (64 bytes each, MAX_INCLUDES entries)
_sha1_store_rel: bytes # Relative-path array (256 bytes each, MAX_INCLUDES entries)
_sha1_store_incdir: bytes # Owning includes-root array (512 bytes each, MAX_INCLUDES entries, multi-includes)
_sha1_store_count: int # Entry count
# includes 依赖内存存储(不依赖 deps.txt 过程文件)
# Phase B+ 前用 declare_only 翻译所有 includes 文件,提取 _imported_modules 存入此处
# _BuildReachableSha1Set 从此处读取依赖,不读 deps.txt
_includes_deps_sha1: bytes # SHA1 数组(每个 17 字节,MAX_INCLUDES 个)
_includes_deps_str: bytes # 依赖字符串数组(每个 512 字节,MAX_INCLUDES 个)
_includes_deps_count: int # 条目数
# In-memory includes dependency store (no dependency on the deps.txt file).
# Before Phase B+ all includes are translated in declare_only mode; _BuildReachableSha1Set reads deps from here.
_includes_deps_sha1: bytes # SHA1 array (17 bytes each, MAX_INCLUDES entries)
_includes_deps_str: bytes # Dependency-string array (512 bytes each, MAX_INCLUDES entries)
_includes_deps_count: int # Entry count
# BuildCombinedIR 全目录扫描缓存:Phase B 对每个源文件调用一次 BuildCombinedIR,
# 每次 _collect_stub_sha1s 递归扫描 temp_dir 全部切片子目录(O(N×S) 目录枚举,
# 90 文件 × 全目录是 Phase B 慢的根因之一)。按 temp_dir 缓存扫描结果,
# 首次扫描后复用,temp_dir 变化时自动重建。
_stub_scan_cache_dir: str # 缓存对应的 temp_dir(None=未初始化)
_stub_scan_cache_arr: str # 扫描结果数组(每 17 字节一个 SHA1,MAX_INCLUDES_SHA1 个)
_stub_scan_cache_count: int # 收集数量
# Max length of a single dependency string
define MAX_DEPS_STR_LEN = 512
# 单条依赖字符串最大长度
MAX_DEPS_STR_LEN: t.CDefine = 512
# rel_path 最大长度
MAX_REL_PATH_LEN: t.CDefine = 256
# includes 根目录最大长度(绝对路径,多 includes 支持)
MAX_INC_DIR_LEN: t.CDefine = 512
# Max rel_path length
define MAX_REL_PATH_LEN = 256
# Max includes-root path length (absolute path, multi-includes)
define MAX_INC_DIR_LEN = 512
+4 -9
View File
@@ -1,13 +1,8 @@
import t, c
from stdint import *
import memhub
import lib.core.VLogger as VLogger
# ============================================================
# lib 包入口 - 声明全局 _mbuddy 内存池指针
# ============================================================
# lib package entry - declares the global _mbuddy memory-pool pointer.
# 全局 mbuddy 内存池指针(由 App/main.vp 初始化后注入)
# 注意: 使用 MemBuddy 类型而非 MemManager,避免虚函数分派不工作时调用父类 alloc 返回 None
_mbuddy: memhub.MemBuddy | t.CPtr
# Global mbuddy memory-pool pointer (injected by App/main.vp after init).
# Uses MemBuddy, not MemManager, to avoid returning None when virtual dispatch is unavailable.
_mbuddy: memhub.MemBuddy*
+23 -60
View File
@@ -1,68 +1,31 @@
import t, c
import memhub
# ============================================================
# CompilerFactory - 编译器结构体工厂(对齐 TPC Translator 装配模式)
#
# TPC 的 Translator 在 __init__ 集中创建编译器各子对象(Handler/
# SymbolTable/Exportable 等),是编译器的"装配器/工厂";TPV 的 main
# 目前手动创建 mb/gen 并逐模块注入(_mbuddy/set_global_gen),装配
# 逻辑散落。本工厂集中持有编译器级共享内存对象:
#
# - Pool :主内存池(MemBuddy,瞬时数据,受 mark/reset_to 回收)
# - PermPool :永久内存池(MemBuddy,跨 mark 持久数据专用,从不 reset)
#
# 各模块经模块级委托 CompilerFactory.get_perm_pool() 获取永久池,
# 避免把编译器专属单例塞进通用内存库 memhub(memhub 保持通用)。
# includes 层(llvmlite/__types.vp 的 DeepCopyType)不依赖本工厂:
# 深拷贝的分配源由调用方以 pool 参数传入(传 PermPool 即持久)。
# 类名与模块名同名(均为 CompilerFactory):合法场景,TPC 已支持
# 模块别名优先解析——`X.func()` 中 X 是 import 别名(IsModuleAlias)
# 时按模块函数处理,`X.X(args)` 的类构造按 FuncAttr==类名 走 struct
# 路径,二者互不冲突(勿再改类名规避,见用户明确要求)。
# ============================================================
@t.NoVTable
class CompilerFactory:
"""编译器结构体工厂(持有主池/永久池等共享对象)"""
# CompilerFactory - compiler struct factory (aligned with TPC Translator assembly).
novirtual class CompilerFactory:
"""Compiler struct factory holding shared objects such as the main and permanent pools."""
Pool: memhub.MemBuddy* # Main memory pool (transient data; reclaimed by mark/reset)
PermPool: memhub.MemBuddy* # Permanent memory pool (persistent data; never reset)
Pool: memhub.MemBuddy | t.CPtr # 主内存池(瞬时数据,mark/reset 回收)
PermPool: memhub.MemBuddy | t.CPtr # 永久内存池(持久数据,从不 reset)
# ---- REnum variant registry (compile-time singleton; same lifetime as perm_pool struct entries)----
RenumClassNames: sizet*
RenumClassVariantCounts: sizet*
RenumVariantNames: sizet*
RenumVariantTags: sizet*
RenumClassCount: int
def __init__(self, pool: memhub.MemBuddy | t.CPtr,
perm_pool: memhub.MemBuddy | t.CPtr):
if self is None:
return
# ---- REnum variant field alias table (same lifetime; _bind_variant_fields resolves real field types)----
RenumAliasClass: sizet*
RenumAliasField: sizet*
RenumAliasIndex: sizet*
RenumAliasTy: sizet*
RenumAliasVariant: sizet*
RenumAliasCount: int
def __init__(self, pool: memhub.MemBuddy*, perm_pool: memhub.MemBuddy*):
if self is None: return
self.Pool = pool
self.PermPool = perm_pool
# ============================================================
# 全局工厂单例
# ============================================================
_g_factory: CompilerFactory | t.CPtr = None
def set_factory(factory: CompilerFactory | t.CPtr) -> None:
"""注册全局工厂单例(main 创建后注册)"""
global _g_factory
_g_factory = factory
def get_factory() -> CompilerFactory | t.CPtr:
"""获取全局工厂单例(未注册返回 None)"""
return _g_factory
def get_pool() -> memhub.MemBuddy | t.CPtr:
"""获取主内存池(瞬时数据分配源)"""
if _g_factory is not None:
return _g_factory.Pool
return None
def get_perm_pool() -> memhub.MemBuddy | t.CPtr:
"""获取永久内存池(跨 mark 持久数据分配源)"""
if _g_factory is not None:
return _g_factory.PermPool
return None
# Global factory singleton
factory: CompilerFactory* = None
+334
View File
@@ -0,0 +1,334 @@
from stdint import *
import string
import stdio
import stdlib
import win32.fileio as fileio
import lib.core.VLogger as VLogger
# ============================================================
# Diag - 编译期诊断(traceback 系统):源码行读取 + 统一打印 + 终止策略
# ============================================================
# 抽出来的原因:格式化 / 源码行读取 / 终止策略原先都散在 TypeGenErr 里(连同它的
# TraceCount 调用栈遍历),导致"多错误栈"(错误收集 + 每单元去重 + 上限)无处安放。
# 现在 TypeGenErr / GenApi 的 `fatal_error` / `dump_parse_traceback` 全部**委托**到这里;
# 保留在 TypeGenErr 里的只有两件"它才知道"的东西:
# ① 调用栈帧的遍历(`TypeGenBase._get_trace_frame`);
# ② 当前文件名(`TypeGenResolve.get_current_file`)。
#
# **行为与抽离前逐字一致**:报文顺序、缩进、caret 规则、fflush、repl_abort 全不变。
# 诊断模型:**单条 + 立即终止**(不收集多条错误)—— 但单条自带**整条调用链**(DiagEntry),
# 由调用方(TypeGenErr)填好 entry 后 emit,方向由 DIAG_DIR_* 决定。所有打印都拆成独立的
# emit_*,便于在不改调用点的前提下调整格式。
# ============================================================
# ---- traceback 打印方向 ----
define DIAG_DIR_UP = 0 # 外→内:最外层触发点在上,错误点最后(Python "most recent call last")
define DIAG_DIR_DOWN = 1 # 内→外:错误点最先,越往外越下
define DIAG_MAX_FRAMES = 32 # 调用链上限(与 TypeGenMixin.TRACE_MAX 对齐)
# ============================================================
# DiagEntry - 单条编译期诊断(= 一次 traceback 要打印的全部内容)
# ============================================================
# **不是**错误收集容器:任何时刻只处理"出错的那一条"(打印完即终止),
# 但这一条自带**整条调用链** —— 编译器逐层下潜进各源码上下文
# (import 触发依赖加载 / 泛型特化 / 函数体翻译)时的触发点,
# 用于"从错误发生点向外展示触发过程"。
# 所有字符串都用 stdlib 深拷贝:编译期池会回绕,不能借用池内指针。
struct DiagEntry:
# 字段名不要与"本模块 import 的类名"重名:TPC 的类型/符号表按**非限定名**索引
# (Gen.structs / SymbolTable / _mangle_func_name),曾把本字段的 `File` 与
# `win32.fileio.File` 撞在一起,导致同模块 `fileio.File(...)` 的构造调用把
# `File.__before_init__` 的 self 形参解析成 `i8**`(llvmlite: "Type of #1 arg
# mismatch: i8** != %"...fileio.File"*")。故此处用 FileName 而非 File。
FileName: str # 当前帧(错误点)文件
Line: int # 当前帧行号
ColStart: int # caret 起始列(1-based;0 = 不画)
ColEnd: int # caret 结束列
Category: str # LifetimeError / SyntaxError / TypeError ...
Msg: str # 消息
Hint: str # 可选提示(可 None)
FrameCount: int # 调用链帧数
FrameFiles: sizet* # 调用链每帧文件名(深拷贝)
FrameLines: sizet* # 调用链每帧行号(与 FrameFiles 同容量)
# 泄漏点(可选):被调者那边**造成逃逸的写入点**。只有逃逸类 LifetimeError 会有值;
# 打印成独立一帧 `File lifetime leak "<file>", line N`,与调用栈帧(`File "..."`)区分。
LeakFileName: str
LeakLine: int
Cap: int # 内部:FrameFiles/FrameLines 容量(不参与打印)
# dup_str - 深拷贝到 stdlib 堆(诊断需跨池回绕点存活)
def dup_str(s: str) -> str:
if s is None: return None
n: sizet = string.strlen(s)
b: str = str(stdlib.malloc(n + 1))
if b is None: return None
string.strncpy(b, s, n)
b[n] = '\0'
return b
# new_entry - 分配一条诊断(调用方持有;打印完用 free_entry 释放)
def new_entry(max_frames: int) -> DiagEntry*:
ent: DiagEntry* = (DiagEntry*)(stdlib.malloc(DiagEntry.__sizeof__()))
if ent is None: return None
if max_frames <= 0: max_frames = 1
ent.FileName = None
ent.Line = 0
ent.ColStart = 0
ent.ColEnd = 0
ent.Category = None
ent.Msg = None
ent.Hint = None
ent.FrameCount = 0
ent.LeakFileName = None
ent.LeakLine = 0
ent.Cap = max_frames
ent.FrameFiles = (sizet*)(stdlib.malloc(sizet(max_frames) * 8))
ent.FrameLines = (sizet*)(stdlib.malloc(sizet(max_frames) * 8))
return ent
# begin - 初始化一条诊断(随后用 add_frame/set_current 填调用链与当前位置)
def begin(ent: DiagEntry*, category: str, msg: str, hint: str = None) -> int:
if ent is None: return 0
ent.Category = dup_str(category)
ent.Msg = dup_str(msg)
ent.Hint = dup_str(hint)
ent.FileName = None
ent.Line = 0
ent.ColStart = 0
ent.ColEnd = 0
ent.FrameCount = 0
ent.LeakFileName = None
ent.LeakLine = 0
return 0
# set_current - 设置当前帧(错误点)位置
def set_current(ent: DiagEntry*, file_name: str, lineno: int, col_start: int, col_end: int) -> int:
if ent is None: return 0
ent.FileName = dup_str(file_name)
ent.Line = lineno
ent.ColStart = col_start
ent.ColEnd = col_end
return 0
# set_leak - 设置"泄漏点"帧(File lifetime leak);仅逃逸类 LifetimeError 使用
def set_leak(ent: DiagEntry*, file_name: str, lineno: int) -> int:
if ent is None or lineno <= 0: return 0
ent.LeakFileName = dup_str(file_name)
ent.LeakLine = lineno
return 0
# add_frame - 追加调用链一帧(按"自外向内"顺序追加)
def add_frame(ent: DiagEntry*, file_name: str, lineno: int) -> int:
if ent is None or ent.FrameFiles is None or ent.FrameLines is None: return 0
if ent.FrameCount >= ent.Cap: return 0
ent.FrameFiles[ent.FrameCount] = sizet(dup_str(file_name))
ent.FrameLines[ent.FrameCount] = sizet(lineno)
ent.FrameCount += 1
return 0
# emit - 打印一条诊断:头 → 调用链(按 direction) → 当前帧(caret) → 类别: 消息
# 去重:① 连续两个位置完全相同的栈帧只印一次;② 与“错误点帧”位置相同的栈帧不再重复印
#(此前常见 `_start` 定义行被打印两遍:栈内最内层帧与错误点帧落在同一行)。
def emit(ent: DiagEntry*, direction: int) -> int:
if ent is None: return 0
emit_header()
last_file: str = ""
last_line: int = -1
if ent.FrameFiles is not None and ent.FrameLines is not None:
if direction == DIAG_DIR_DOWN:
i_d: int = ent.FrameCount - 1
while i_d >= 0:
ff: str = str(ent.FrameFiles[i_d])
ll: int = int(ent.FrameLines[i_d])
dup: int = 1 if last_file != "" and string.strcmp(ff, last_file) == 0 and ll == last_line else 0
if dup == 0:
emit_frame(ff, ll)
last_file = ff
last_line = ll
i_d -= 1
else:
i_u: int = 0
while i_u < ent.FrameCount:
ff2: str = str(ent.FrameFiles[i_u])
ll2: int = int(ent.FrameLines[i_u])
dup2: int = 1 if last_file != "" and string.strcmp(ff2, last_file) == 0 and ll2 == last_line else 0
cur_same: int = 1 if ent.FileName is not None and string.strcmp(ff2, ent.FileName) == 0 and ll2 == ent.Line else 0
if dup2 == 0 and cur_same == 0:
emit_frame(ff2, ll2)
last_file = ff2
last_line = ll2
i_u += 1
emit_current(ent.FileName, ent.Line, ent.ColStart, ent.ColEnd)
# 泄漏点帧紧跟在"错误点"之后(往下再一行),再印类别: 消息
if ent.LeakLine > 0: emit_leak_frame(ent.LeakFileName, ent.LeakLine)
emit_category(ent.Category, ent.Msg)
return 0
# free_entry - 释放一条诊断(含深拷贝的字符串)
def free_entry(ent: DiagEntry*) -> int:
if ent is None: return 0
if ent.FileName is not None: stdlib.free(ent.FileName)
if ent.LeakFileName is not None: stdlib.free(ent.LeakFileName)
if ent.Category is not None: stdlib.free(ent.Category)
if ent.Msg is not None: stdlib.free(ent.Msg)
if ent.Hint is not None: stdlib.free(ent.Hint)
if ent.FrameFiles is not None:
i_f: int = 0
while i_f < ent.FrameCount:
if ent.FrameFiles[i_f] != 0: stdlib.free(str(ent.FrameFiles[i_f]))
i_f += 1
stdlib.free(ent.FrameFiles)
if ent.FrameLines is not None: stdlib.free(ent.FrameLines)
stdlib.free(ent)
return 0
# read_source_line - 读源文件第 lineno 行,供 traceback 展示
def read_source_line(file_path: str, lineno: int) -> str:
"""读第 lineno 行;返回 stdlib 分配的缓冲(调用方 free),读不到返回 None"""
if file_path is None or lineno <= 0: return None
f: fileio.File* = fileio.File(file_path, fileio.MODE.R)
if f.closed: return None
# Allocate by actual file size: a fixed 256KB buffer truncated large files so late line
# numbers were unreadable. The 64MB cap guards against reading abnormally huge files.
fsize: LONGLONG = f.size()
if fsize <= 0 or fsize > 67108864:
f.close()
return None
src_size: sizet = sizet(fsize)
buf: bytes = stdlib.malloc(src_size + 1)
if buf is None:
f.close()
return None
bytes_read: LONG = f.read_all(buf, ULONG(src_size + 1))
f.close()
if bytes_read <= 0:
stdlib.free(buf)
return None
# Scan line by line for the target lineno
cur_line: int = 1
line_start: sizet = 0
for i in range(int(bytes_read)):
if buf[i] == '\n':
if cur_line == lineno:
line_len: sizet = i - line_start
if line_len > 0 and buf[line_start + line_len - 1] == '\r': line_len -= 1
line_buf: bytes = stdlib.malloc(line_len + 1)
if line_buf is not None:
string.strncpy(line_buf, buf + line_start, line_len)
line_buf[line_len] = '\0'
stdlib.free(buf)
return line_buf
stdlib.free(buf)
return None
cur_line += 1
line_start = i + 1
stdlib.free(buf)
return None
# emit_header - traceback 头(解析失败与语义错误共用同一行文案)
def emit_header() -> int:
stdio.printf("Traceback (most recent call last, compile-time error):\n")
return 0
# emit_frame - 调用栈里的一帧(只印位置与源码行,不印 caret)
def emit_frame(file_name: str, lineno: int) -> int:
if file_name is None: return 0
if lineno > 0:
stdio.printf(' File "%s", line %d\n', file_name, lineno)
src: str = read_source_line(file_name, lineno)
if src is not None:
stdio.printf(" %s\n", src)
stdlib.free(src)
else: stdio.printf(' File "%s"\n', file_name)
return 0
# emit_leak_frame - "泄漏点"帧:标签用 `File lifetime leak`,与调用栈帧(`File "..."`)区分
def emit_leak_frame(file_name: str, lineno: int) -> int:
if file_name is None: return 0
if lineno > 0:
stdio.printf(' File lifetime leak "%s", line %d\n', file_name, lineno)
src: str = read_source_line(file_name, lineno)
if src is not None:
stdio.printf(" %s\n", src)
stdlib.free(src)
else: stdio.printf(' File lifetime leak "%s"\n', file_name)
return 0
# emit_current - 当前帧(带 caret 列范围;行读不出时印警告)
def emit_current(file_name: str, lineno: int, col_start: int, col_end: int) -> int:
# Sanity defense: read the line first to verify lineno belongs to the current file.
# If unreadable the file/line is untrustworthy (e.g. reused AST); print a warning instead.
src_line: str = None
line_ok: int = 1
if lineno > 0 and string.strcmp(file_name, "<unknown>") != 0:
src_line = read_source_line(file_name, lineno)
if src_line is None: line_ok = 0
if line_ok == 0:
stdio.printf(' File "%s", line %d (!! cannot read that source line: file does not exist or line number out of range; the error may come from a reused AST tree, rely on the error message)\n', file_name, lineno)
else:
stdio.printf(' File "%s", line %d\n', file_name, lineno)
if src_line is not None:
stdio.printf(" %s\n", src_line)
# Column-range marker: use ^ under the source line to mark the error range
if col_start > 0 and col_end >= col_start:
stdio.printf(" ")
j: int
for j in range(int(col_end)):
if j >= col_start - 1 and j < col_end: stdio.printf("^")
else: stdio.printf(" ")
stdio.printf("\n")
stdlib.free(src_line)
return 0
# emit_category - `类别: 消息`,随后立即 fflush
def emit_category(err_type: str, msg: str) -> int:
if err_type is None or err_type[0] == 0: err_type = "SyntaxError"
stdio.printf("%s: %s\n", err_type, msg)
# Force flush before exiting: stdout redirected to a file is fully buffered, so terminating
# directly drops the whole traceback (observed: 2.log kept only one VLogger line).
stdio.fflush(None)
return 0
# emit_parse - 解析失败的单帧 traceback(Python 风格,单个 ^ 指向 token 起始列)
def emit_parse(file_path: str, err_line: int, err_col: int, err_msg: str) -> int:
emit_header()
if file_path is None or file_path[0] == 0: file_path = "<unknown>"
if err_line > 0:
stdio.printf(' File "%s", line %d\n', file_path, err_line)
src: str = read_source_line(file_path, err_line)
if src is not None:
stdio.printf(" %s\n", src)
# Column marker: put ^ under the error token's start column (parser col_offset is
# 0-based, so 4-space indent + err_col spaces lands on the token's first char)
if err_col > 0:
stdio.printf(" ")
for _ in range(int(err_col)): stdio.printf(" ")
stdio.printf("^\n")
stdlib.free(src)
else: stdio.printf(' File "%s"\n', file_path)
if err_msg is None or err_msg[0] == 0: err_msg = "syntax error"
stdio.printf("SyntaxError: %s\n", err_msg)
return 0
# finish - 终止策略:REPL 里 longjmp 回循环(报文已印、进程存活),批处理里 sys.exit(1)
def finish() -> None:
VLogger.repl_abort()
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+19 -34
View File
@@ -1,50 +1,35 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import t, c
from stdint import *
import memhub
import llvmlite
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import mlirlite
# ============================================================
# BaseGenMixin - 生成器基座(状态容器)
#
# 对象化重构 P1(方案见 wiki/14-oo-refactor.md):
# 将 Translator/Handles 的共享状态集中于此。后续 Mixin
# (TypeGen/StructGen/ExprGen/CallGen/FuncGen/ClassGen/StmtGen/
# ImportsGen)单继承本类,逐步把 Handles* 模块的函数迁移为
# Generator 的方法,消除 (pool, builder, mod, node, ...) 六参连传。
#
# 注意:@t.NoVTable(静态绑定,骨架无多态需求;若未来需要
# 运行时多态再改 @t.CVTable,字段布局由编译器统一管理)。
# ============================================================
@t.NoVTable
class BaseGenMixin:
"""生成器状态容器(基座)"""
# BaseGenMixin - generator base (state container).
novirtual class BaseGenMixin:
"""Generator state container (base)."""
# 内存/IR 状态
Gen: Generator.Generator | t.CPtr
Pool: memhub.MemBuddy | t.CPtr # 内存池
Module: llvmlite.LLVMModule | t.CPtr # 当前 LLVM 模块
Builder: llvmlite.IRBuilder | t.CPtr # 当前 IRBuilder
# 符号表/函数表
SymTab: GenTypes.SymbolTable | t.CPtr
Funcs: GenTypes.FuncEntry | t.CPtr
# Memory / IR state
Gen: Generator.Generator*
Pool: memhub.MemBuddy* # Memory pool
Module: mlirlite.LLVMModule* # Current LLVM module
Builder: mlirlite.IRBuilder* # Current IRBuilder
# Symbol / function tables
SymTab: GenTypes.SymbolTable*
Funcs: GenTypes.FuncEntry*
FuncCount: int
# 导入/模块信息
# Import / module info
ImportedModules: str
FromImports: str
ModuleSha1: str
# 当前翻译上下文
CurFunc: llvmlite.Function | t.CPtr
CurBuilder: llvmlite.IRBuilder | t.CPtr
# Current translation context
CurFunc: mlirlite.Function*
CurBuilder: mlirlite.IRBuilder*
CurClassName: str
def InitBaseGen(self, pool: memhub.MemBuddy | t.CPtr) -> int:
"""初始化生成器状态(默认值;子类构造时调用)"""
if self is None:
return 1
def InitBaseGen(self, pool: memhub.MemBuddy*) -> int:
"""Initialize generator state to defaults; called from subclass constructors."""
if self is None: return 1
self.Pool = pool
self.Module = None
self.Builder = None
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+246
View File
@@ -0,0 +1,246 @@
import lib.core.Gen.GenTypes as GenTypes
from stdint import *
import ast
import mlirlite
import memhub
import string
import viperlib
import lib.core.Gen.Translator as HT
import lib.core.Gen.GenApi as GenApi
from . import CallGenBase
# CallGenFd - split from CallGenMixin (stage B): FakeDuck / requires field injection / provider stack
novirtual class CallGenFd(CallGenBase.CallGenBase):
"""FakeDuck / requires field injection / provider stack"""
def _translate_fakeduck_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cls_name: str, method_name: str, cl: ast.Call*, trans: HT.Translator*) -> mlirlite.Value*:
"""FakeDuck: resolve a str variable's method call to extension class _Type/_str, return the call result or None"""
if pool is None or builder is None or cl is None or trans is None: return None
if method_name is None: return None
# Only trigger for str (AnnotClassName == "str", object is char*);
# also try when cls_name is None (receiver resolution failed).
if cls_name is not None and string.strcmp(cls_name, "str") != 0: return None
if cl.func is None or cl.func.kind() != ast.ASTKind.Attribute: return None
fd_at: ast.Attribute* = ast.Attribute*(cl.func)
if fd_at is None or fd_at.value is None or fd_at.attr is None: return None
if string.strcmp(fd_at.attr, method_name) != 0: return None
# Candidate extension classes: _Type first (a.type_id()), then _str (a.upper() etc.)
fd_func: mlirlite.Function* = None
fd_cls: str = None
fd_key: str = pool.alloc(160)
if fd_key is not None:
for fd_ci in range(2):
fd_cn: str = "_Type"
if fd_ci == 1: fd_cn = "_str"
viperlib.snprintf(fd_key, 160, "%s.%s", fd_cn, method_name)
fd_func = self.find_func_in_module(mod, fd_key)
if fd_func is not None:
fd_cls = fd_cn
break
if fd_func is None or fd_cls is None: return None
# The extension class struct must be registered (GEP/temp shadow needs a type)
fd_sty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(fd_cls)
if fd_sty is None: return None
# Extension class field count: with fields (_str) self uses a shadow struct pointer;
# without fields (_Type) self bitcasts the object value to the extension pointer (matches TPC L2856-2876).
fd_entry_fd: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(fd_sty)
fd_has_fields: int = 0
if fd_entry_fd is not None and fd_entry_fd.FieldCount > 0: fd_has_fields = 1
# self arg: prefer an existing shadow {var}__meta__ (created by fd2, only when the extension class has fields)
fd_self: mlirlite.Value* = None
if fd_at.value.kind() == ast.ASTKind.Name:
fd_onm: ast.Name* = ast.Name*(fd_at.value)
if fd_onm.id is not None:
# Pool-persistent buffer (an f-string stack buffer would expire after return)
fd_mn: str = pool.alloc(256)
if fd_mn is not None: viperlib.snprintf(fd_mn, 256, "%s__meta__", fd_onm.id)
fd_meta: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, fd_mn)
if fd_meta is not None and fd_has_fields != 0: fd_self = fd_meta
if fd_self is None:
if fd_has_fields != 0:
# Fielded extension class with no shadow (string literal etc.): create and init a temp
# shadow at entry; first ensure full _str definition (cross-module structs default to opaque and alloca would report unsized).
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, fd_sty)
fd_self = self.Gen.VarGen._alloca_at_entry(builder, fd_sty)
if fd_self is None: return None
fd_obj_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, fd_at.value, None, 0, trans)
if fd_obj_val is not None:
fd_dfe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_sty, "__data__")
if fd_dfe is not None and fd_dfe.Ty is not None:
fd_dp: mlirlite.Value* = mlirlite.build_gep_struct(builder, fd_sty, fd_dfe.Ty, fd_self, fd_dfe.Index)
if fd_dp is not None: mlirlite.build_store(builder, fd_obj_val, fd_dp)
fd_bfe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_sty, "__mbuddy__")
if fd_bfe is not None and fd_bfe.Ty is not None:
fd_bp: mlirlite.Value* = mlirlite.build_gep_struct(builder, fd_sty, fd_bfe.Ty, fd_self, fd_bfe.Index)
fd_null: mlirlite.Value* = mlirlite.ConstNull(pool, fd_bfe.Ty, "null")
if fd_bp is not None and fd_null is not None: mlirlite.build_store(builder, fd_null, fd_bp)
# FakeDuck: auto-inject requires fields from the with context (e.g. __mbuddy__)
self._inject_requires_fields(trans, pool, builder, mod, fd_self, fd_cls)
else:
# Fieldless extension class (_Type): bitcast the object value to the extension pointer
fd_obj_v: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, fd_at.value, None, 0, trans)
if fd_obj_v is None: return None
fd_self = mlirlite.build_bitcast(builder, fd_obj_v, mlirlite.Ptr(pool, fd_sty))
if fd_self is None: return None
# Build the argument list: self -> remaining args
mlirlite.value_set_next(fd_self, None)
fd_head: mlirlite.Value* = fd_self
fd_tail: mlirlite.Value* = fd_self
fd_args: list[ast.AST*]* = cl.args
fd_can: int = 0
if fd_args is not None: fd_can = int(len(fd_args))
for fd_ai in range(int(fd_can)):
fd_arg: ast.AST* = fd_args.get(fd_ai)
if fd_arg is not None:
fd_av: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, fd_arg, None, 0, trans)
# char constant arg -> single-char string (implicit char->str, e.g. s1.split(',') where
# ',' is i32 44); passing it raw to a str param makes strlen/strncmp deref 44 and crash.
if fd_arg.kind() == ast.ASTKind.Constant:
fd_ac: ast.Constant* = ast.Constant*(fd_arg)
if fd_ac.const_kind == ast.CONST_CHAR:
ch_buf: str = pool.alloc(2)
if ch_buf is not None:
ch_buf[0] = char(fd_ac.int_val)
ch_buf[1] = '\0'
fd_av = self.Gen.ExprGenConst.create_global_string(builder, pool, mod, ch_buf, trans)
if fd_av is not None:
mlirlite.value_set_next(fd_av, None)
mlirlite.value_set_next(fd_tail, fd_av)
fd_tail = fd_av
# Return type and call name (from the function definition hit by find_func_in_module)
fd_ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(fd_func)
if fd_ret_ty is None: fd_ret_ty = mlirlite.Int32(pool)
fd_fname: str = mlirlite.function_get_name(fd_func)
if fd_fname is None: return None
# Refine return type: cross-module declare may simplify a list[T] specialization return type to Ptr(i8), degrading the caller's alloca type -> parts[0] takes the char-index path instead of list inline __getitem__ (printf %s crash). Authority is the defining module's vpi (fallback text.ll); extract the module sha1 only when the name is "{16hex}.{func}" (via GenApi.sha1_prefix_len)
if GenApi.sha1_prefix_len(fd_fname) != 0:
fd_sha1: str = pool.alloc(17)
if fd_sha1 is not None:
string.strncpy(fd_sha1, fd_fname, 16)
fd_sha1[16] = '\0'
fd_exact: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, fd_sha1, fd_cls, method_name)
if fd_exact is None: fd_exact = self._LookupMethodRetTyFromTextLL(pool, fd_sha1, fd_cls, method_name)
if fd_exact is not None: fd_ret_ty = fd_exact
fd_total: int = 1 + fd_can
# Value-reference (&T / &temp T) params of the resolved extension method: auto-address the
# user args (the shadow self pointer occupies slot 0).
fd_head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, fd_fname, fd_args, fd_head, fd_total, 1)
return mlirlite.build_call(builder, fd_fname, fd_head, fd_total, fd_ret_ty, 0)
# _inject_requires_fields - FakeDuck: inject requires fields from the with context.
# For each class __requires__ field (registered by fd1), call _find_provider(field) at runtime; if non-NULL bitcast and store it (conditional inject); skip when _find_provider is unregistered.
# ============================================================
def _inject_requires_fields(self, trans: HT.Translator*, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, meta_ptr: mlirlite.Value*, class_name: str) -> int:
"""Inject requires fields from the with context, return the injected field count"""
if trans is None or pool is None or builder is None or mod is None: return 0
if meta_ptr is None or class_name is None: return 0
# _withcontent._find_provider (cross-module): inject only when visible in this module
# (matches TPC L4048-4050); otherwise non-with projects would emit undefined {sha1}._find_provider calls.
fd_fn_probe: mlirlite.Function* = self.find_func_in_module(mod, "_find_provider")
if fd_fn_probe is None: return 0
fd_fname: str = mlirlite.function_get_name(fd_fn_probe)
if fd_fname is None: return 0
cnt: int = self.Gen.ClassGenBase.class_meta_count(GenTypes.CLASS_META_REQUIRES, class_name)
if cnt <= 0: return 0
sty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(class_name)
if sty is None: return 0
i8_ty_j: mlirlite.LLVMType* = mlirlite.Int8(pool)
if i8_ty_j is None: return 0
i8_ptr_ty_j: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_j)
if i8_ptr_ty_j is None: return 0
cur_func: mlirlite.Function* = trans._cur_func
if cur_func is None: return 0
injected: int = 0
for ji in range(int(cnt)):
fname: str = self.Gen.ClassGenBase.class_meta_field_at(GenTypes.CLASS_META_REQUIRES, class_name, ji)
if fname is None: continue
fe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(sty, fname)
if fe is None or fe.Ty is None: continue
# Arg: field name string (i8*)
fd_fstr: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(builder, pool, mod, fname, trans)
if fd_fstr is None: continue
mlirlite.value_set_next(fd_fstr, None)
self._ensure_method_declare(pool, mod, fd_fname, i8_ptr_ty_j, fd_fstr, 1)
fd_ptr: mlirlite.Value* = mlirlite.build_call(builder, fd_fname, fd_fstr, 1, i8_ptr_ty_j, 0)
if fd_ptr is None: continue
# Conditional inject: store only when ptr != NULL (preserve user-set values)
fd_null: mlirlite.Value* = mlirlite.ConstNull(pool, i8_ptr_ty_j, "null")
if fd_null is None: continue
fd_cmp: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, fd_ptr, fd_null)
if fd_cmp is None: continue
# Unique block names (create_block does not auto-number; duplicate labels in textual IR
# conflict -> llc "Terminator found in the middle of a basic block").
vtc2: int = trans._label_counter
trans._label_counter = vtc2 + 1
fd_bb_buf2: str = pool.alloc(64)
if fd_bb_buf2 is None: continue
viperlib.snprintf(fd_bb_buf2, 64, "fd.skip.%d", vtc2)
fd_skip_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, cur_func, fd_bb_buf2)
viperlib.snprintf(fd_bb_buf2, 64, "fd.inject.%d", vtc2)
fd_inject_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, cur_func, fd_bb_buf2)
if fd_skip_bb is None or fd_inject_bb is None: continue
mlirlite.build_cond_br(builder, fd_cmp, fd_inject_bb, fd_skip_bb)
# inject block: bitcast + GEP + store, then jump back to skip
mlirlite.position_at_end(builder, fd_inject_bb)
fd_cast: mlirlite.Value* = mlirlite.build_bitcast(builder, fd_ptr, fe.Ty)
if fd_cast is not None:
fd_fp: mlirlite.Value* = mlirlite.build_gep_struct(builder, sty, fe.Ty, meta_ptr, fe.Index)
if fd_fp is not None: mlirlite.build_store(builder, fd_cast, fd_fp)
mlirlite.build_br(builder, fd_skip_bb)
# skip block: restore CurBlock and continue codegen
mlirlite.position_at_end(builder, fd_skip_bb)
injected += 1
return injected
# _emit_provider_push / _emit_provider_pop - FakeDuck: with-context provider push/pop (__provides__ metadata).
# Matches TPC HandlesWith.py L138-174: push _push_provider(field, ctx_i8) on entry and equal _pop_provider() on exit.
# ============================================================
def _emit_provider_push(self, trans: HT.Translator*, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cls_name: str, ctx_val: mlirlite.Value*) -> int:
"""Emit a _push_provider(field, ctx_i8) call per provided field, return the field count"""
if trans is None or pool is None or builder is None or mod is None: return 0
if cls_name is None or ctx_val is None: return 0
fd_pc: int = self.Gen.ClassGenBase.class_meta_count(GenTypes.CLASS_META_PROVIDES, cls_name)
if fd_pc <= 0: return 0
# _withcontent._push_provider (cross-module, same visibility guard as _find_provider:
# skip when this module does not import _withcontent, to avoid undefined symbol).
fd_pfn_probe: mlirlite.Function* = self.find_func_in_module(mod, "_push_provider")
if fd_pfn_probe is None: return 0
fd_pfn: str = mlirlite.function_get_name(fd_pfn_probe)
if fd_pfn is None: return 0
i8_ty_p: mlirlite.LLVMType* = mlirlite.Int8(pool)
if i8_ty_p is None: return 0
i8_ptr_ty_p: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_p)
if i8_ptr_ty_p is None: return 0
ret_i32_p: mlirlite.LLVMType* = mlirlite.Int32(pool)
if ret_i32_p is None: return 0
# Bitcast the ctx value to i8* (provider stack stores void*)
fd_ctx_i8: mlirlite.Value* = mlirlite.build_bitcast(builder, ctx_val, i8_ptr_ty_p)
if fd_ctx_i8 is None: return 0
for pi in range(int(fd_pc)):
fname: str = self.Gen.ClassGenBase.class_meta_field_at(GenTypes.CLASS_META_PROVIDES, cls_name, pi)
if fname is None: continue
fd_fstr: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(builder, pool, mod, fname, trans)
if fd_fstr is None: continue
mlirlite.value_set_next(fd_fstr, fd_ctx_i8)
mlirlite.value_set_next(fd_ctx_i8, None)
self._ensure_method_declare(pool, mod, fd_pfn, ret_i32_p, fd_fstr, 2)
mlirlite.build_call(builder, fd_pfn, fd_fstr, 2, ret_i32_p, 0)
return fd_pc
def _emit_provider_pop(self, trans: HT.Translator*, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cls_name: str) -> int:
"""Emit a _pop_provider() call per provided field, return the field count"""
if trans is None or pool is None or builder is None or mod is None: return 0
if cls_name is None: return 0
fd_pc: int = self.Gen.ClassGenBase.class_meta_count(GenTypes.CLASS_META_PROVIDES, cls_name)
if fd_pc <= 0: return 0
# _withcontent._pop_provider (cross-module, same visibility guard as _find_provider:
# skip when this module does not import _withcontent, to avoid undefined symbol).
fd_pon_probe: mlirlite.Function* = self.find_func_in_module(mod, "_pop_provider")
if fd_pon_probe is None: return 0
fd_pon: str = mlirlite.function_get_name(fd_pon_probe)
if fd_pon is None: return 0
ret_i32_q: mlirlite.LLVMType* = mlirlite.Int32(pool)
if ret_i32_q is None: return 0
self._ensure_method_declare(pool, mod, fd_pon, ret_i32_q, None, 0)
for _pi in range(int(fd_pc)): mlirlite.build_call(builder, fd_pon, None, 0, ret_i32_q, 0)
return fd_pc
File diff suppressed because it is too large. Load diff
+594
View File
@@ -0,0 +1,594 @@
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdlib
import lib.core.Gen.Translator as HT
import lib.core.Gen.GenTypes as GenTypes
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.core.Gen.GenApi as GenApi
from . import CallGenBase
# ============================================================
# CallGenLib - CallGenMixin split (stage C): C-lib declares / return-type inference
novirtual class CallGenLib(CallGenBase.CallGenBase):
"""C-lib declarations / return-type inference / kwargs call helpers (extends Base)"""
def _ensure_c_lib_declare(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, name: str) -> mlirlite.Function*:
"""Create a declare for a known C-lib function"""
if pool is None or mod is None or name is None: return None
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
ret_ty: mlirlite.LLVMType* = None
params: int = 0 # parameter count
if string.strcmp(name, "strlen") == 0 or string.strcmp(name, "strlength") == 0:
ret_ty = i64_ty
params = 1
elif string.strcmp(name, "memset") == 0:
ret_ty = i8_ptr_ty
params = 3
elif string.strcmp(name, "memcpy") == 0:
ret_ty = i8_ptr_ty
params = 3
elif string.strcmp(name, "memmove") == 0:
ret_ty = i8_ptr_ty
params = 3
elif string.strcmp(name, "memcmp") == 0:
ret_ty = i32_ty
params = 3
elif string.strcmp(name, "strcmp") == 0:
ret_ty = i32_ty
params = 2
elif string.strcmp(name, "strncmp") == 0:
ret_ty = i32_ty
params = 3
elif string.strcmp(name, "strcpy") == 0:
ret_ty = i8_ptr_ty
params = 2
elif string.strcmp(name, "strncpy") == 0:
ret_ty = i8_ptr_ty
params = 3
elif string.strcmp(name, "strcat") == 0:
ret_ty = i8_ptr_ty
params = 2
elif string.strcmp(name, "puts") == 0:
ret_ty = i32_ty
params = 1
elif string.strcmp(name, "atoi") == 0:
ret_ty = i32_ty
params = 1
elif string.strcmp(name, "atol") == 0:
ret_ty = i64_ty
params = 1
elif string.strcmp(name, "abs") == 0:
ret_ty = i32_ty
params = 1
elif string.strcmp(name, "labs") == 0:
ret_ty = i64_ty
params = 1
else: return None
# create the declare
fn: mlirlite.Function* = mlirlite.create_declare(pool, mod, name, ret_ty)
if fn is None: return None
# add parameters
if params == 1:
if string.strcmp(name, "abs") == 0: mlirlite.add_param(pool, fn, i32_ty, "n")
elif string.strcmp(name, "labs") == 0: mlirlite.add_param(pool, fn, i64_ty, "n")
else: mlirlite.add_param(pool, fn, i8_ptr_ty, "s")
elif params == 2:
mlirlite.add_param(pool, fn, i8_ptr_ty, "dst")
mlirlite.add_param(pool, fn, i8_ptr_ty, "src")
elif params == 3:
if string.strcmp(name, "memset") == 0:
mlirlite.add_param(pool, fn, i8_ptr_ty, "dst")
mlirlite.add_param(pool, fn, i32_ty, "c")
mlirlite.add_param(pool, fn, i64_ty, "n")
else:
mlirlite.add_param(pool, fn, i8_ptr_ty, "dst")
mlirlite.add_param(pool, fn, i8_ptr_ty, "src")
mlirlite.add_param(pool, fn, i64_ty, "n")
return fn
def _promote_clib_call_args(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, call_name: str, head: mlirlite.Value*, count: int) -> mlirlite.Value*:
"""Promote bare-name C-lib call arguments to their standard signature widths"""
if pool is None or builder is None or call_name is None or head is None: return head
# bare names only (no '.' -> no sha1 prefix); prefixed ones use vpi definition-side parsing
if string.strchr(call_name, 46) is not None: return head
# whitelist: index of the size_t (i64) length param (matches _ensure_c_lib_declare)
size_idx: int = -1
if string.strcmp(call_name, "memset") == 0: size_idx = 2
elif string.strcmp(call_name, "memcpy") == 0: size_idx = 2
elif string.strcmp(call_name, "memmove") == 0: size_idx = 2
elif string.strcmp(call_name, "memcmp") == 0: size_idx = 2
elif string.strcmp(call_name, "strncmp") == 0: size_idx = 2
elif string.strcmp(call_name, "strncpy") == 0: size_idx = 2
elif string.strcmp(call_name, "strncat") == 0: size_idx = 2
elif string.strcmp(call_name, "vsnprintf") == 0: size_idx = 1
if size_idx < 0 or count <= size_idx: return head
# locate the length param (index size_idx)
cur: mlirlite.Value* = head
idx: int = 0
while cur is not None and idx < size_idx:
cur = cur.Next
idx += 1
if cur is None or cur.Ty is None: return head
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
promoted: mlirlite.Value* = None
match cur.Ty:
case mlirlite.LLVMType.Int(bits):
if bits >= 64: return head
if cur.IsConst != 0:
# constant: use an i64 constant directly (bit pattern kept, unsigned length)
promoted = mlirlite.const_int(pool, 64, cur.IntVal)
else:
# SSA value: zext to i64 (size_t is an unsigned length)
promoted = mlirlite.build_zext(builder, cur, i64_ty)
case _:
return head
if promoted is None: return head
# rebuild the arg list: replace index size_idx with the promoted value
new_head: mlirlite.Value* = None
new_tail: mlirlite.Value* = None
cur2: mlirlite.Value* = head
i2: int = 0
while cur2 is not None:
node_v: mlirlite.Value* = cur2
if i2 == size_idx: node_v = promoted
if new_head is None: new_head = node_v
else: new_tail.Next = node_v
new_tail = node_v
cur2 = cur2.Next
i2 += 1
return new_head
# _coerce_args_to_def_params: coerce cross-module call args to vpi definition-side param
# types so call/declare/define widths match; bare C-lib names use the C-lib path instead.
def _coerce_args_to_def_params(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, call_name: str, head: mlirlite.Value*, count: int, cargs: list[ast.AST*]* = None, self_off: int = 0, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Coerce cross-module call args to vpi definition-side param types; returns the rebuilt chain"""
if pool is None or builder is None or call_name is None or head is None: return head
if self._is_sha1_mangled_call(call_name) == 0: return head
# split {sha1}.{rest}: if rest has '.', it is {cls}.{method}, else {func}
rest: str = call_name + 17
cls_nm: str = None
meth_nm: str = rest
# take the last '.': the method has none, and the first '.' would truncate qualified type args
dot_r: str = string.strrchr(rest, 46)
if dot_r is not None:
cls_len: sizet = uint64(dot_r) - uint64(rest)
if cls_len <= 0: return head
cls_buf: bytes = stdlib.malloc(cls_len + 1)
if cls_buf is None: return head
string.strncpy(cls_buf, rest, cls_len)
cls_buf[cls_len] = '\0'
cls_nm = cls_buf
meth_nm = dot_r + 1
if meth_nm is None or meth_nm[0] == '\0':
if cls_nm is not None: stdlib.free(cls_nm)
return head
sha1_buf: bytes = stdlib.malloc(17)
if sha1_buf is None:
if cls_nm is not None: stdlib.free(cls_nm)
return head
string.strncpy(sha1_buf, call_name, 16)
sha1_buf[16] = '\0'
# ensure-load + direct cache lookup (not _vpi_method_param_annot, which misses module funcs)
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: VpiReader._ensure_vpi_param_annot_loaded(sha1_buf, self.Gen.CallGenBase.TempDir)
prm_annots: str = VpiReader._lookup_vpi_param_annots(sha1_buf, cls_nm, meth_nm)
# generic-specialized class: annotations live on the template class, so fall back to template lookup
tpl_src: int = 0
if prm_annots is None:
if cls_nm is not None and string.strchr(cls_nm, 91) is not None: tpl_src = 1
else:
stdlib.free(sha1_buf)
if cls_nm is not None: stdlib.free(cls_nm)
return head
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
new_head: mlirlite.Value* = None
new_tail: mlirlite.Value* = None
cur: mlirlite.Value* = head
idx: int = 0
while cur is not None and idx < count:
node_v: mlirlite.Value* = cur
tgt_ty: mlirlite.LLVMType* = None
if tpl_src != 0: tgt_ty = VpiReader._resolve_vpi_param_annot_seg_template(pool, sha1_buf, cls_nm, meth_nm, idx, i32_ty, i8_ptr_ty)
else: tgt_ty = VpiReader._resolve_vpi_param_annot_seg(pool, sha1_buf, cls_nm, meth_nm, idx, i32_ty, i8_ptr_ty)
if tgt_ty is not None and cur.Ty is not None:
# Argument AST (for source-symbolic checks): AST index = chain index idx - self offset
arg_idx: int = idx - self_off
arg_node: ast.AST* = None
if cargs is not None and arg_idx >= 0 and arg_idx < len(cargs): arg_node = cargs.get(arg_idx)
# Value-reference params (&T / &temp T): auto-address the argument (cross-module callee).
# The mode comes from the definition-side vpi annotation segment (shared resolver); if the
# arg is not a pointer it is addressed (ref: lvalue) or copied into a temp slot (temp).
vpi_cur_sha: str = None
if trans is not None: vpi_cur_sha = trans.ModuleSha1
mode_cm: int = self.Gen.CallGenBase._vpi_auto_addr_mode(call_name, vpi_cur_sha, idx)
if mode_cm != 0 and trans is not None and trans.Module is not None:
node_v = self.Gen.CallGenBase._apply_auto_addr_arg(builder, pool, trans.Module, trans, mode_cm, arg_node, node_v)
# "str / bytes are not generic pointer wildcards": void* / t.CPtr / rawptr may
# convert to any pointer, but str/bytes may not, even though both are i8* at LLVM level.
if self.Gen.ExprGenBase._str_rejects_ptr_kind(cur) != 0:
if VpiReader._vpi_param_annot_seg_is(sha1_buf, cls_nm, meth_nm, idx, "str") != 0 or VpiReader._vpi_param_annot_seg_is(sha1_buf, cls_nm, meth_nm, idx, "bytes") != 0:
self.Gen.ExprGenBase.fail_if_str_annot_agg_ptr(cur, 1, arg_node)
node_v = self._coerce_arg_value_to_ty(pool, builder, node_v, tgt_ty, arg_node, trans, tpl_src)
if new_head is None: new_head = node_v
else: new_tail.Next = node_v
new_tail = node_v
cur = cur.Next
idx += 1
stdlib.free(sha1_buf)
if cls_nm is not None: stdlib.free(cls_nm)
return new_head
# _coerce_arg_value_to_ty: convert one arg Value to the target param type -- same-width ints
# pass through, narrow/wide ints trunc/extend, mismatched pointers bitcast; else unchanged.
def _coerce_arg_value_to_ty(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, val: mlirlite.Value*, tgt_ty: mlirlite.LLVMType*, node: ast.AST* = None, trans: HT.Translator* = None, tpl_src: int = 0) -> mlirlite.Value*:
"""Convert one argument Value to the target param type; leaves it unchanged when unsafe"""
if pool is None or builder is None or val is None or tgt_ty is None or val.Ty is None: return val
match val.Ty:
case mlirlite.LLVMType.Int(bits_a):
match tgt_ty:
case mlirlite.LLVMType.Int(bits_b):
if bits_b == bits_a: return val
if bits_b < bits_a:
if val.IsConst != 0:
nv_c: mlirlite.Value* = mlirlite.const_int(pool, bits_b, val.IntVal)
if nv_c is not None: return nv_c
else:
nv_t: mlirlite.Value* = mlirlite.build_trunc(builder, val, tgt_ty)
if nv_t is not None: return nv_t
# widening (arg narrower than param): rebuild constants by decimal value
if val.IsConst != 0:
nv_w: mlirlite.Value* = mlirlite.const_int(pool, bits_b, val.IntVal)
if nv_w is not None: return nv_w
return val
# SSA widening: direction from source signedness (i1/i8/unsigned -> zext, else sext)
if bits_b > bits_a:
if self.Gen.ExprGenBase._zext_or_sext_val(pool, val, node, trans) != 0:
nv_zw: mlirlite.Value* = mlirlite.build_zext(builder, val, tgt_ty)
if nv_zw is not None: return nv_zw
else:
nv_sw: mlirlite.Value* = mlirlite.build_sext(builder, val, tgt_ty)
if nv_sw is not None: return nv_sw
return val
case _:
pass
case mlirlite.LLVMType.Ptr(_):
match tgt_ty:
case mlirlite.LLVMType.Ptr(_):
# same-level pointers: bitcast when the arg type differs from the definition
# side (aligned so the first call site's declare matches; llc otherwise errors).
if self.Gen.AssignGen._stg_ty_equal(pool, val.Ty, tgt_ty) == 0:
nv_p: mlirlite.Value* = mlirlite.build_bitcast(builder, val, tgt_ty)
if nv_p is not None: return nv_p
case _:
pass
case _:
# aggregate -> pointer: spill rvalue struct/array and take its address (params lower to Ptr)
match tgt_ty:
case mlirlite.LLVMType.Ptr(_):
vk_a: int = val.Ty.kind()
if vk_a == mlirlite.LLVMType.Struct or vk_a == mlirlite.LLVMType.Array:
tmp_a: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, val.Ty)
if tmp_a is not None:
mlirlite.build_store(builder, val, tmp_a)
return tmp_a
case _:
pass
return val
def _infer_external_func_ret_ty(self, pool: memhub.MemBuddy*, func_name: str, node: ast.AST*, fatal_if_unknown: int) -> mlirlite.LLVMType*:
"""Infer the return type of an external includes function; a whitelist miss may fail fast"""
if pool is None or func_name is None: return mlirlite.Int32(pool)
# extract the bare function name by stripping the SHA1 prefix
# (16 hex digits + '.'), e.g. "240a9a4157959a9f.parse" -> "parse".
bare_name: str = func_name
dot_pos: str = string.strrchr(func_name, 46) # 46 = ord('.')
if dot_pos is not None:
# the part after '.' is the bare function name
bare_name = dot_pos + 1
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
# functions returning i8* (truncating the pointer would be wrong)
if string.strcmp(bare_name, "strchr") == 0 or string.strcmp(func_name, "strchr") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "strrchr") == 0 or string.strcmp(func_name, "strrchr") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "strstr") == 0 or string.strcmp(func_name, "strstr") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "strcpy") == 0 or string.strcmp(func_name, "strcpy") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "strncpy") == 0 or string.strcmp(func_name, "strncpy") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "memset") == 0 or string.strcmp(func_name, "memset") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "memset32") == 0 or string.strcmp(func_name, "memset32") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "memcpy") == 0 or string.strcmp(func_name, "memcpy") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "memmove") == 0 or string.strcmp(func_name, "memmove") == 0: return i8_ptr_ty
# JSON parse functions return JsonValue* (pointer); include the json_parse alias
# (from json.__parser import parse as json_parse).
if string.strcmp(bare_name, "parse") == 0 or string.strcmp(func_name, "parse") == 0 or string.strcmp(func_name, "json_parse") == 0: return i8_ptr_ty
# MemBuddy methods (may use the external-function path when type info is lost).
# malloc: bare-name CExport cross-module call (uhci/desktop mm.malloc -> @malloc), else ret is inferred i32 and the 64-bit pointer truncates.
if string.strcmp(bare_name, "malloc") == 0 or string.strcmp(func_name, "malloc") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
if string.strcmp(func_name, "alloc") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
if string.strcmp(func_name, "calloc") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
if string.strcmp(func_name, "realloc") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
if string.strcmp(func_name, "alloc_buf") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# VLogger module funcs (stubs not injected cross-module, pointers truncated to i32)
if string.strcmp(func_name, "get_logger") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
if string.strcmp(func_name, "fmt_buf") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# Config module funcs (stubs not injected cross-module, i8* truncated to i32)
if string.strcmp(func_name, "get_includes_binary_dir") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# HandlesType module func (returns ast.AST* union; inferred as i32 cross-module)
if string.strcmp(bare_name, "_parse_type_string") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# Functions returning i64
if string.strcmp(func_name, "strlen") == 0: return mlirlite.Int64(pool)
if string.strcmp(func_name, "atoll") == 0: return mlirlite.Int64(pool)
# mlirlite name-length accessors return sizet (i64); the default i32 clashes with the declare
if string.strcmp(bare_name, "function_get_name_len") == 0 or string.strcmp(func_name, "function_get_name_len") == 0: return mlirlite.Int64(pool)
# Functions returning double
if string.strcmp(func_name, "atof") == 0: return mlirlite.Double(pool)
# Functions returning i8
if string.strcmp(func_name, "samestr") == 0: return mlirlite.Int8(pool)
# Win32 APIs returning a pointer (VOIDPTR/HANDLE); no stubs are injected, so infer
# i8* to keep the pointer from being truncated to i32 and crashing on dereference.
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
if string.strcmp(func_name, "VirtualAlloc") == 0: return i8_ptr_ty
if string.strcmp(func_name, "HeapAlloc") == 0: return i8_ptr_ty
if string.strcmp(func_name, "HeapReAlloc") == 0: return i8_ptr_ty
if string.strcmp(func_name, "HeapCreate") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GetProcessHeap") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GlobalAlloc") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GlobalFree") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GlobalLock") == 0: return i8_ptr_ty
if string.strcmp(func_name, "LocalAlloc") == 0: return i8_ptr_ty
if string.strcmp(func_name, "LocalFree") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GetProcAddress") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GetModuleHandleA") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GetModuleHandleW") == 0: return i8_ptr_ty
# GetCommandLineA/W return LPSTR (i8*); without it a 64-bit pointer is truncated to i32
if string.strcmp(func_name, "GetCommandLineA") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GetCommandLineW") == 0: return i8_ptr_ty
# gethostbyname returns hostent* (i8*)
if string.strcmp(func_name, "gethostbyname") == 0: return i8_ptr_ty
if string.strcmp(func_name, "LoadLibraryA") == 0: return i8_ptr_ty
if string.strcmp(func_name, "LoadLibraryW") == 0: return i8_ptr_ty
# Win32 handle-returning functions (HANDLE = void* = i8*)
if string.strcmp(func_name, "GetStdHandle") == 0: return i8_ptr_ty
if string.strcmp(func_name, "CreateFileA") == 0: return i8_ptr_ty
if string.strcmp(func_name, "CreateFileW") == 0: return i8_ptr_ty
if string.strcmp(func_name, "FindFirstFileA") == 0: return i8_ptr_ty
if string.strcmp(func_name, "FindFirstFileW") == 0: return i8_ptr_ty
# Win32 APIs returning i64 (SIZE_T)
if string.strcmp(func_name, "VirtualQuery") == 0: return mlirlite.Int64(pool)
if string.strcmp(func_name, "HeapSize") == 0: return mlirlite.Int64(pool)
if string.strcmp(func_name, "HeapCompact") == 0: return mlirlite.Int64(pool)
if string.strcmp(func_name, "GlobalSize") == 0: return mlirlite.Int64(pool)
# mlirlite build_* return-type inference: most return Value*, terminators return void
if string.strncmp(func_name, "build_", 6) == 0:
# build_* returning void (terminators and void-returning ops)
if string.strcmp(func_name, "build_ret") == 0 or string.strcmp(func_name, "build_ret_void") == 0 or string.strcmp(func_name, "build_store") == 0 or string.strcmp(func_name, "build_br") == 0 or string.strcmp(func_name, "build_cond_br") == 0 or string.strcmp(func_name, "build_unreachable") == 0 or string.strcmp(func_name, "build_switch") == 0:
return mlirlite.Void(pool)
# other build_* return Value* (pointer)
return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# mlirlite type factories / const constructors / accessors return pointers (LLVMType* / Value*)
if string.strcmp(func_name, "Int1") == 0 or string.strcmp(func_name, "Int8") == 0 or string.strcmp(func_name, "Int16") == 0 or string.strcmp(func_name, "Int32") == 0 or string.strcmp(func_name, "Int64") == 0 or string.strcmp(func_name, "Ptr") == 0 or string.strcmp(func_name, "Func") == 0 or string.strcmp(func_name, "Array") == 0 or string.strcmp(func_name, "Struct") == 0 or string.strcmp(func_name, "Void") == 0 or string.strcmp(func_name, "Half") == 0 or string.strcmp(func_name, "Float") == 0 or string.strcmp(func_name, "Double") == 0 or string.strcmp(func_name, "FP128") == 0 or string.strcmp(func_name, "Label") == 0 or string.strcmp(func_name, "new_param_node") == 0 or string.strcmp(func_name, "new_phi_incoming") == 0 or string.strcmp(func_name, "const_int32") == 0 or string.strcmp(func_name, "const_int64") == 0 or string.strcmp(func_name, "ConstFloat") == 0 or string.strcmp(func_name, "ConstZero") == 0 or string.strcmp(func_name, "ConstNull") == 0 or string.strcmp(func_name, "ConstInt") == 0 or string.strcmp(func_name, "function_get_name") == 0 or string.strcmp(func_name, "param_get_ty") == 0 or string.strcmp(func_name, "value_get_ty") == 0 or string.strcmp(func_name, "value_get_next") == 0 or string.strcmp(func_name, "function_get_ret_ty") == 0 or string.strcmp(func_name, "function_get_params") == 0 or string.strcmp(func_name, "function_get_param_head") == 0 or string.strcmp(func_name, "param_get_next") == 0:
return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# Default i32 placeholder: fatal_if_unknown=1 fails fast; =0 (Phase A mid-pass) stays silent.
if fatal_if_unknown != 0: self.Gen.TypeGenErr.fatal_error(node, f"external function return type is unregistered and cannot be inferred conclusively (defaulting to i32 would truncate pointers/i64; provide a stub declaration or add it to the whitelist): {func_name}", "TypeError")
return mlirlite.Int32(pool)
def _get_external_func_param_ty(self, pool: memhub.MemBuddy*, func_name: str, param_idx: int) -> mlirlite.LLVMType*:
"""Return the expected type of an external function's param_idx-th parameter, or None (fallback only)"""
if pool is None or func_name is None: return None
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
# Win32 API list: every non-pointer parameter is i64 in declare
# (ULONG/DWORD/SIZE_T are all mapped to i64 during LLVM stub translation)
win32_apis: list[str]* = ["VirtualAlloc", "VirtualFree", "VirtualProtect", "VirtualQuery", "VirtualLock", "VirtualUnlock", "HeapCreate", "HeapDestroy", "HeapAlloc", "HeapReAlloc", "HeapFree", "HeapSize", "HeapValidate", "HeapCompact", "GlobalAlloc", "GlobalFree", "GlobalLock", "GlobalUnlock", "GlobalSize", "LocalAlloc", "LocalFree", "GetLastError", "SetLastError", "Sleep", "SleepEx", "GetTickCount", "GetProcessHeap"]
# list iteration is unsupported; use range + get instead
api_count: sizet = len(win32_apis)
for ai in range(int(api_count)):
api_name: str = win32_apis.get(ai)
if string.strcmp(api_name, func_name) == 0: return i64_ty
# Allocators: size params are declared i64 (size_t); without promotion on bare-name fallback call/define widths mismatch; calloc/realloc size is idx 1
if string.strcmp(func_name, "malloc") == 0 or string.strcmp(func_name, "alloc") == 0 or string.strcmp(func_name, "alloc_buf") == 0:
if param_idx == 0: return i64_ty
if string.strcmp(func_name, "calloc") == 0:
if param_idx == 0 or param_idx == 1: return i64_ty
if string.strcmp(func_name, "realloc") == 0:
if param_idx == 1: return i64_ty
return None
def _is_sha1_mangled_call(self, call_name: str) -> int:
"""Return whether call_name is a cross-module Viper function ("{16 hex}.func")"""
if call_name is None: return 0
if GenApi.sha1_prefix_len(call_name) != 0: return 1
return 0
def _emit_llvm_memcpy_intrinsic(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, dst_val: mlirlite.Value*, src_val: mlirlite.Value*, num_val: mlirlite.Value*) -> mlirlite.Value*:
"""Map a memcpy call to the @llvm.memcpy intrinsic"""
if pool is None or builder is None or mod is None: return None
if dst_val is None or src_val is None or num_val is None: return None
# Declare void_ty at the top of the function to avoid later shadowing
void_ty: mlirlite.LLVMType* = mlirlite.Void(pool)
# Declare @llvm.memcpy (if not already declared)
memcpy_func: mlirlite.Function* = self.find_func_in_module(mod, "llvm.memcpy")
if memcpy_func is None:
memcpy_func = mlirlite.create_declare(pool, mod, "llvm.memcpy", void_ty)
if memcpy_func is not None:
i8_ptr_ty_d: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
i64_ty_d: mlirlite.LLVMType* = mlirlite.Int64(pool)
i1_ty_d: mlirlite.LLVMType* = mlirlite.Int1(pool)
mlirlite.add_param(pool, memcpy_func, i8_ptr_ty_d, "dst")
mlirlite.add_param(pool, memcpy_func, i8_ptr_ty_d, "src")
mlirlite.add_param(pool, memcpy_func, i64_ty_d, "len")
mlirlite.add_param(pool, memcpy_func, i1_ty_d, "isvolatile")
# Coercions: dst, src -> i8*; num -> i64
# Use the value_get_ty accessor for Ty to avoid local Value field-index offset
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
# dst → i8*
dst_coerced: mlirlite.Value* = dst_val
dst_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(dst_val)
if self.Gen.ExprGenBase.is_ptr_type(dst_ty) != 0: dst_coerced = mlirlite.build_bitcast(builder, dst_val, i8_ptr_ty)
else:
dst_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(dst_ty)
if dst_bits != 0:
if dst_bits < 64: dst_coerced = mlirlite.build_zext(builder, dst_val, i64_ty)
dst_coerced = mlirlite.build_inttoptr(builder, dst_coerced, i8_ptr_ty)
# src → i8*
src_coerced: mlirlite.Value* = src_val
src_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(src_val)
if self.Gen.ExprGenBase.is_ptr_type(src_ty) != 0: src_coerced = mlirlite.build_bitcast(builder, src_val, i8_ptr_ty)
else:
src_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(src_ty)
if src_bits != 0:
if src_bits < 64: src_coerced = mlirlite.build_zext(builder, src_val, i64_ty)
src_coerced = mlirlite.build_inttoptr(builder, src_coerced, i8_ptr_ty)
# num → i64
num_coerced: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, num_val, i64_ty)
if dst_coerced is None or src_coerced is None or num_coerced is None: return None
# Append the i1 false arg (isvolatile) - llvm.memcpy needs 4 parameters
false_val: mlirlite.Value* = mlirlite.ConstInt(pool, mlirlite.Int1(pool), 0, "false")
# Build the arg list via include accessors to avoid local Value field-index offset
# (f26cd4c78b4d554c.Value lacks GSListNode.Next; direct .Next access corrupts .Ty)
mlirlite.value_set_next(dst_coerced, src_coerced)
mlirlite.value_set_next(src_coerced, num_coerced)
if false_val is not None:
mlirlite.value_set_next(num_coerced, false_val)
mlirlite.value_set_next(false_val, None)
else: mlirlite.value_set_next(num_coerced, None)
# Emit the call via build_call (it accesses include Value fields with correct indices)
# _ll_name_needs_quote already exempts the llvm. prefix from quoting
result: mlirlite.Value* = mlirlite.build_call(builder, "llvm.memcpy", dst_coerced, 4, void_ty, 0)
# Return dst (matches C memcpy semantics: returns the dest pointer)
return dst_coerced
# _translate_call_with_kwargs: keyword-arg call; builds args in found_func.Params order (positional first, else match by param name)
# ============================================================
def _translate_call_with_kwargs(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, func_name: str, found_func: mlirlite.Function*, cargs: list[ast.AST*]*, can: sizet, keywords: list[ast.AST*]*, kw_count: sizet, trans: HT.Translator*) -> mlirlite.Value*:
"""Translate a call with keyword arguments, building args in parameter order"""
head: mlirlite.Value* = None
tail: mlirlite.Value* = None
actual_count: sizet = 0
# memcpy-only: collect independent refs of the first three Values (avoid shared alloca Next pollution)
mc_dst1: mlirlite.Value* = None
mc_src1: mlirlite.Value* = None
mc_num1: mlirlite.Value* = None
# Default-arg lookup for omitted params (same source as CallGenMixin non-kwargs path):
# module SHA1 is the "{sha1}.func" prefix or current module; the name+SHA1-keyed snapshot survives resets.
kw_callee_sha: str = None
kw_found_name: str = mlirlite.function_get_name(found_func)
if kw_found_name is not None:
if GenApi.sha1_prefix_len(kw_found_name) != 0:
kw_sha_buf: str = pool.alloc(17)
if kw_sha_buf is not None:
string.strncpy(kw_sha_buf, kw_found_name, 16)
kw_sha_buf[16] = '\0'
kw_callee_sha = kw_sha_buf
if kw_callee_sha is None and self.Gen.TypeGenBase is not None: kw_callee_sha = self.Gen.TypeGenBase.CurrentModuleSha1
kw_gd: GenTypes.FuncDefaultsEntry* = None
if kw_callee_sha is not None: kw_gd = self.Gen.CallGenLeafKit.find_func_defaults(func_name, kw_callee_sha)
kw_default_count: int = 0
kw_default_off: int = 0
if kw_gd is not None:
kw_default_count = kw_gd.DefaultCount
kw_default_off = kw_gd.ParamCount - kw_gd.DefaultCount
if kw_default_off < 0: kw_default_off = 0
# Walk the function parameter list (accessor gets Head)
param_idx: sizet = 0
cur_param: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
while cur_param is not None:
arg_val: mlirlite.Value* = None
param_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(cur_param)
if param_idx < can:
# Prefer the positional argument
arg: ast.AST* = cargs.get(param_idx)
if arg is not None:
# Pass the param type (char literal -> str param materializes a string; see translate_constant)
arg_val = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans, param_ty)
else:
# Find the matching keyword argument
param_name: str = mlirlite.param_get_name(cur_param)
if param_name is not None:
for ki in range(int(kw_count)):
kw: ast.Keyword* = ast.Keyword*(keywords.get(ki))
if kw is None or kw.arg is None or kw.value is None: continue
if self.Gen.CallGenCall._kwarg_name_matches(kw.arg, param_name) == 1:
arg_val = self.Gen.ExprGen.translate_value(builder, pool, mod, kw.value, None, 0, trans, param_ty)
break
# Param supplied by neither position nor keyword: fill with the definition-site default
# (index = param_idx - param_count + default_count); else fail-fast, never silently drop/zero.
if arg_val is None:
if kw_default_count > 0 and int(param_idx) >= kw_default_off:
kw_di: int = int(param_idx) - kw_default_off
if kw_di >= 0 and kw_di < kw_default_count:
arg_val = self.Gen.CallGenLeafKit._build_const_from_snapshot(pool, mod, kw_gd, kw_di, trans, builder)
if arg_val is None:
kw_node: ast.AST* = None
if keywords is not None and kw_count > 0: kw_node = keywords.get(0)
self.Gen.TypeGenErr.fatal_error(kw_node, f"a parameter of call '{func_name}' is passed neither positionally nor by keyword, " f"and the definition has no default value to fill in (refusing to silently drop/zero a parameter)", "TypeError")
if arg_val is not None:
# Coerce by parameter type (via accessor)
if param_ty is not None: arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, param_ty)
arg_val.Next = None
if head is None: head = arg_val
else: tail.Next = arg_val
tail = arg_val
# memcpy-only: save independent refs of the first three args
if actual_count == 0: mc_dst1 = arg_val
elif actual_count == 1: mc_src1 = arg_val
elif actual_count == 2: mc_num1 = arg_val
actual_count += 1
param_idx += 1
cur_param = mlirlite.param_get_next(cur_param)
# declare and define both use found_func.Name: normal funcs carry the SHA1 prefix, t.CExtern/t.State/t.CExport use bare names (bare names desync call/define symbols)
call_name: str = func_name
found_name: str = mlirlite.function_get_name(found_func)
if found_name is not None: call_name = found_name
# Return type (via accessor)
call_ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(found_func)
if call_ret_ty is None:
# found_func has a declare but return type is unreadable (accessor anomaly, rare guard):
# even if the whitelist misses, do not fail-fast; keep the default i32.
call_ret_ty = self._infer_external_func_ret_ty(pool, func_name, None, 0)
# memcpy -> @llvm.memcpy intrinsic (avoid a missing cross-module @memcpy declare under monomorphization)
if string.strcmp(func_name, "memcpy") == 0 and actual_count >= 3: return self._emit_llvm_memcpy_intrinsic(pool, builder, mod, mc_dst1, mc_src1, mc_num1)
return mlirlite.build_call(builder, call_name, head, actual_count, call_ret_ty, 0)
File diff suppressed because it is too large. Load diff
+315
View File
@@ -0,0 +1,315 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.GenApi as GenApi
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import viperlib
import lib.core.Gen.Translator as HT
from . import CallGenBase
# CallGenProp - split from CallGenMixin (stage B): property access (getter/setter/deleter / static class calls / list builtins)
# ============================================================
novirtual class CallGenProp(CallGenBase.CallGenBase):
"""Property access (getter/setter/deleter / static class calls / list builtins)"""
# Property methods (@property/@x.setter/@x.deleter) and static/class call helpers.
# Translate obj.prop / obj.prop=v / del obj.prop / Class.method() into the matching {sha1}.ClassName.prop call.
# _translate_property_call - translate a property/static method call.
# Resolve the call name like _translate_method_call; call_head/call_count is the prebuilt arg list.
def _translate_property_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, call_head: mlirlite.Value*, call_count: int, trans: HT.Translator*, arg_nodes: list[ast.AST*]* = None, start_idx: int = 0) -> mlirlite.Value*:
"""Translate a property/static method call, return the call result Value"""
if pool is None or builder is None or class_name is None or method_name is None: return None
# Build the lookup name: ClassName.method_name
lookup_name: str = pool.alloc(128)
if lookup_name is None: return None
viperlib.snprintf(lookup_name, 128, "%s.%s", class_name, method_name)
# Look up the function (exact/suffix match)
found_func: mlirlite.Function* = self.find_func_in_module(mod, lookup_name)
# Determine the call name
call_name: str = lookup_name
if found_func is not None:
found_name: str = mlirlite.function_get_name(found_func)
if found_name is not None: call_name = found_name
else:
# Not found: build the call name from the class's owning module SHA1
cls_sha1_pc: str = self.Gen.StructGen.get_struct_sha1(class_name)
if cls_sha1_pc is not None:
mlen_pc: sizet = string.strlen(cls_sha1_pc) + string.strlen(lookup_name) + 2
mangled_pc: str = pool.alloc(mlen_pc)
if mangled_pc is not None:
viperlib.snprintf(mangled_pc, mlen_pc, "%s.%s", cls_sha1_pc, lookup_name)
call_name = mangled_pc
# Determine the return type
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
call_ret_ty: mlirlite.LLVMType* = i32_ty
if found_func is not None:
frt: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(found_func)
if frt is not None: call_ret_ty = frt
else:
sp_ty1: mlirlite.LLVMType* = self._special_method_ret_ty(pool, class_name, method_name)
if sp_ty1 is not None: call_ret_ty = sp_ty1
else:
cls_sha1_rt: str = self.Gen.StructGen.get_struct_sha1(class_name)
# Authoritative source: vpi stub return-annotation cache (in place, no hardcoded whitelist)
vpi_ret_ty: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, cls_sha1_rt, class_name, method_name)
if vpi_ret_ty is not None: call_ret_ty = vpi_ret_ty
else:
# Fallback: parse the module text.ll define line (legacy form not covered by vpi)
looked_up_ty: mlirlite.LLVMType* = self._LookupMethodRetTyFromTextLL(pool, cls_sha1_rt, class_name, method_name)
if looked_up_ty is not None: call_ret_ty = looked_up_ty
else:
# Phase A deps not ready (cls_sha1_rt None / the defining module's text.ll missing):
# keep the i32 default (build_call(Void) returning None would break expression translation).
if cls_sha1_rt is None or self._dep_text_ll_exists(pool, cls_sha1_rt) == 0: call_ret_ty = mlirlite.Int32(pool)
else: call_ret_ty = self._fail_method_ret_ty(method_name, None, class_name, None, cls_sha1_rt)
# Value-reference (&T / &temp T) params: auto-address the user args before param-type
# coercion (self/obj or the classmethod cls occupies index 0, skipped via start_idx).
call_head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, arg_nodes, call_head, call_count, start_idx)
# Coerce args to the declared param types (same as _translate_method_call); target func: found_func,
# else the mangled module declare. Otherwise call(i32) vs declare(i64) mismatches and llc errors.
tgt_func_pc: mlirlite.Function* = found_func
if tgt_func_pc is None: tgt_func_pc = self.find_func_in_module(mod, call_name)
if tgt_func_pc is not None:
prev_pc: mlirlite.Value* = None
cur_arg_pc: mlirlite.Value* = call_head
cur_param_pc: mlirlite.Param* = mlirlite.function_get_param_head(tgt_func_pc)
while cur_arg_pc is not None and cur_param_pc is not None:
param_ty_pc: mlirlite.LLVMType* = mlirlite.param_get_ty(cur_param_pc)
if param_ty_pc is not None and cur_arg_pc.Ty is not None:
coerced_pc: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, cur_arg_pc, param_ty_pc)
if coerced_pc is not None and coerced_pc != cur_arg_pc:
# Keep the list intact: the new Value inherits the node's Next; predecessor points to it
mlirlite.value_set_next(coerced_pc, cur_arg_pc.Next)
if prev_pc is None: call_head = coerced_pc
else: mlirlite.value_set_next(prev_pc, coerced_pc)
cur_arg_pc = coerced_pc
prev_pc = cur_arg_pc
cur_arg_pc = cur_arg_pc.Next
cur_param_pc = mlirlite.param_get_next(cur_param_pc)
# Ensure the declare exists, then call
self._ensure_method_declare(pool, mod, call_name, call_ret_ty, call_head, call_count)
return mlirlite.build_call(builder, call_name, call_head, call_count, call_ret_ty, 0)
# _resolve_obj_class_name - resolve an object expression's class short name.
# Prefer the variable annotation (AnnotClassName); fall back to the LLVM type (Ptr(Struct)).
def _resolve_obj_class_name(self, pool: memhub.MemBuddy*, trans: HT.Translator*, obj_node: ast.AST*) -> str:
"""Resolve an object expression's class short name, None on failure"""
if pool is None or trans is None or obj_node is None: return None
if obj_node.kind() == ast.ASTKind.Name:
obj_nm: ast.Name* = ast.Name*(obj_node)
if obj_nm.id is not None:
var_entry: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, obj_nm.id)
annot_val: str = self.Gen.VarGen.var_annot_class_name(var_entry)
if annot_val is not None:
# [D32] Split the qualified name only on '.' outside brackets: the old strrchr over the
# whole string picked the '.' inside "list[sha1.AST*]", yielding a bogus short name.
n_av: sizet = string.strlen(annot_val)
last_dot_av: sizet = n_av
depth_av: int = 0
for ai_av in range(int(n_av)):
ch_av: int = annot_val[ai_av]
if ch_av == '[': depth_av += 1
elif ch_av == ']': depth_av -= 1
elif ch_av == '.' and depth_av == 0: last_dot_av = ai_av
if last_dot_av < n_av: return annot_val + last_dot_av + 1
return annot_val
return None
# _try_property_getter_attr - try to translate obj.attr into a property getter call.
# If the class has an @property/@x.getter for attr, emit call Class.attr(obj); else return None.
def _try_property_getter_attr(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, at_node: ast.Attribute*, obj_ptr: mlirlite.Value*, trans: HT.Translator*, fallback_class_name: str = None) -> mlirlite.Value*:
"""Try a property getter access, return the call result Value; None if not a getter."""
if at_node is None or at_node.attr is None or obj_ptr is None or trans is None: return None
class_name: str = self._resolve_obj_class_name(pool, trans, at_node.value)
if class_name is None: class_name = fallback_class_name
if class_name is None: return None
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, at_node.attr)
if meta & GenTypes.META_PROP_GETTER == 0: return None
# Single-arg call: obj -> getter (no user args to auto-address; obj is slot 0)
mlirlite.value_set_next(obj_ptr, None)
return self._translate_property_call(pool, builder, mod, class_name, at_node.attr, obj_ptr, 1, trans, None, 1)
# _try_property_setter - try to translate obj.attr = v into a property setter call.
# If the class has an @x.setter for attr, emit call Class.attr$set(obj, v) and return 1; else 0.
def _try_property_setter(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, target_attr: ast.Attribute*, rhs_val: mlirlite.Value*, trans: HT.Translator*, rhs_node: ast.AST* = None) -> int:
"""Try a property setter assignment, return 1=handled (call emitted)"""
if pool is None or builder is None or mod is None or trans is None: return 0
if target_attr is None or target_attr.attr is None or rhs_val is None: return 0
class_name: str = self._resolve_obj_class_name(pool, trans, target_attr.value)
if class_name is None: return 0
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, target_attr.attr)
if meta & GenTypes.META_PROP_SETTER == 0: return 0
# Translate the object value (self argument)
obj_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, target_attr.value, None, 0, trans)
if obj_val is None: return 0
# Build the argument list: obj -> rhs
mlirlite.value_set_next(obj_val, None)
mlirlite.value_set_next(rhs_val, None)
mlirlite.value_set_next(obj_val, rhs_val)
# The setter function name carries the $set suffix
setter_name_buf: str = pool.alloc(128)
if setter_name_buf is None: return 0
viperlib.snprintf(setter_name_buf, 128, "%s$set", target_attr.attr)
# Value-reference (&T / &temp T) setter value param: expose the rhs AST as user arg 0
# (obj is slot 0) so the helper can take the lvalue address / build a temp copy.
set_arg_nodes: list[ast.AST*]* = None
if rhs_node is not None:
set_arg_nodes = list[ast.AST*](pool, 1)
if set_arg_nodes is not None: set_arg_nodes.append(rhs_node)
self._translate_property_call(pool, builder, mod, class_name, setter_name_buf, obj_val, 2, trans, set_arg_nodes, 1)
return 1
# _try_property_deleter - try to translate del obj.attr into a property deleter call.
# If the class has an @x.deleter for attr, emit call Class.attr$del(obj) and return 1; else 0.
def _try_property_deleter(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, target_attr: ast.Attribute*, trans: HT.Translator*) -> int:
"""Try a property deleter, return 1=handled (call emitted)"""
if pool is None or builder is None or mod is None or trans is None: return 0
if target_attr is None or target_attr.attr is None: return 0
class_name: str = self._resolve_obj_class_name(pool, trans, target_attr.value)
if class_name is None: return 0
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, target_attr.attr)
if meta & GenTypes.META_PROP_DELETER == 0: return 0
# Translate the object value (self argument)
obj_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, target_attr.value, None, 0, trans)
if obj_val is None: return 0
mlirlite.value_set_next(obj_val, None)
# The deleter function name carries the $del suffix
del_name_buf: str = pool.alloc(128)
if del_name_buf is None: return 0
viperlib.snprintf(del_name_buf, 128, "%s$del", target_attr.attr)
self._translate_property_call(pool, builder, mod, class_name, del_name_buf, obj_val, 1, trans, None, 1)
return 1
# _translate_static_class_call - translate ClassName.method(args) for static/class methods.
# @staticmethod: call Class.method(args...). @classmethod: prepend a stack-allocated class pointer.
def _translate_static_class_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, cl: ast.Call*, trans: HT.Translator*) -> mlirlite.Value*:
"""Translate a ClassName static/class method call, return the call result Value; None otherwise"""
if pool is None or builder is None or mod is None or trans is None: return None
if class_name is None or method_name is None or cl is None: return None
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, method_name)
is_static: int = 0
is_class: int = 0
if meta & GenTypes.META_STATIC != 0: is_static = 1
if meta & GenTypes.META_CLASS != 0: is_class = 1
if is_static == 0 and is_class == 0: return None
# Build the argument list
head: mlirlite.Value* = None
total_count: int = 0
# @classmethod: prepend a stack-allocated class pointer as the cls argument
if is_class != 0:
struct_ty_cc: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(class_name)
if struct_ty_cc is None: return None
cls_ptr: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, struct_ty_cc)
if cls_ptr is None: return None
mlirlite.value_set_next(cls_ptr, None)
head = cls_ptr
total_count = 1
# Translate the call arguments
cargs: list[ast.AST*]* = cl.args
can: sizet = 0
if cargs is not None: can = len(cargs)
tail: mlirlite.Value* = head
for ai in range(int(can)):
arg_node: ast.AST* = cargs.get(ai)
if arg_node is None: continue
arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, None, 0, trans)
if arg_val is None: continue
mlirlite.value_set_next(arg_val, None)
if head is None:
head = arg_val
tail = arg_val
else:
mlirlite.value_set_next(tail, arg_val)
tail = arg_val
total_count += 1
# Zero-arg @staticmethod (e.g. PyListObj.new()): head=None is a valid no-arg call, so still route
# through _translate_property_call; returning None would fall back to bare-name lookup and fail.
# Value-reference (&T / &temp T) user args: static methods have no self slot (start_idx=0),
# classmethods carry the cls pointer at slot 0 (start_idx=1).
sc_start_idx: int = 0
if is_class != 0: sc_start_idx = 1
return self._translate_property_call(pool, builder, mod, class_name, method_name, head, total_count, trans, cargs, sc_start_idx)
# _is_list_builtin_method - test whether (class_name, method_name) is a list generic builtin.
# list[T] methods are inlined by translate_call's list branch, not registered as functions.
def _is_list_builtin_method(self, class_name: str, method_name: str) -> int:
"""Test whether (class_name, method_name) is a list generic builtin method"""
if class_name is None or method_name is None: return 0
# Class name must start with "list" (list / list[AST*] specializations)
if string.strncmp(class_name, "list", 4) != 0: return 0
# Method name must be one of the list builtin methods
if string.strcmp(method_name, "append") == 0: return 1
if string.strcmp(method_name, "get") == 0: return 1
if string.strcmp(method_name, "set") == 0: return 1
if string.strcmp(method_name, "pop") == 0: return 1
if string.strcmp(method_name, "clear") == 0: return 1
if string.strcmp(method_name, "__iter__") == 0: return 1
if string.strcmp(method_name, "__len__") == 0: return 1
if string.strcmp(method_name, "__getitem__") == 0: return 1
if string.strcmp(method_name, "__setitem__") == 0: return 1
return 0
# _strip_module_sha1_prefix - strip the cross-module specialization sha1 prefix.
# Receiver struct names often carry a "{16 hex}." prefix; use GenApi.sha1_prefix_len (C10).
def _strip_module_sha1_prefix(self, name: str) -> str:
"""Strip the "{16hex}." prefix (return the input unchanged if absent; returns a substring of the input)"""
if name is None: return None
pfx: int = GenApi.sha1_prefix_len(name)
return name + pfx if pfx != 0 else name
# _resolve_generic_receiver_name - resolve the receiver's actual generic specialization name (IR truth).
# Strip the sha1 prefix, take the base before '['; bare generic base => fail-fast (D25), else None.
def _resolve_generic_receiver_name(self, pool: memhub.MemBuddy*, struct_ty: mlirlite.LLVMType*, method_name: str, cl: ast.Call*) -> str:
"""Return the actual generic specialization name (sha1 stripped) or None; a bare generic base fails fast"""
if struct_ty is None: return None
ty_nm: str = mlirlite.get_struct_name(struct_ty)
if ty_nm is None or ty_nm[0] == '\0': return None
short_nm: str = self._strip_module_sha1_prefix(ty_nm)
lb: str = string.strchr(short_nm, '[')
bare_len: sizet = 0
if lb is not None: bare_len = sizet(uint64(lb) - uint64(short_nm))
else: bare_len = string.strlen(short_nm)
if bare_len == 0: return None
base_nm: str = pool.alloc(bare_len + 1)
if base_nm is None: return None
string.strncpy(base_nm, short_nm, bare_len)
base_nm[bare_len] = '\0'
# Base is not a registered generic template (plain class receiver) => not generic; return None
if self.Gen.ClassGenGeneric._find_generic_template(base_nm) is None: return None
if lb is not None: return short_nm
# Bare generic base (no type args): element type unknown -> fail-fast (D25)
self.Gen.TypeGenErr.fatal_error(cl, f"the actual struct type of the receiver of method '{method_name}' is a bare generic base " f"'{short_nm}*' (type arguments lost), so the element type is unknown; " f"silently using the default element type is forbidden (treating elements as i32 would truncate pointers); " f"make the variable annotation/type carry type arguments (e.g. list[ast.AST*])", "TypeError")
return None
# _is_list_builtin_receiver - test list builtin methods from the receiver's actual type.
# Prefer the actual LLVM struct name (sha1 stripped); fall back to the annotation/registered name.
def _is_list_builtin_receiver(self, pool: memhub.MemBuddy*, recv_val: mlirlite.Value*, fallback_name: str, method_name: str, cl: ast.Call*) -> int:
"""Test list builtin methods from the receiver's actual type (fall back to the annotation name if unavailable)"""
sty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(recv_val)
if sty is not None:
ty_nm: str = mlirlite.get_struct_name(sty)
if ty_nm is not None and ty_nm[0] != '\0':
# Generic template receiver: bare base fail-fast; with type args use the actual specialization
act_nm: str = self._resolve_generic_receiver_name(pool, sty, method_name, cl)
if act_nm is not None: return self._is_list_builtin_method(act_nm, method_name)
# Non-generic template receiver: judge by the actual short name (sha1 stripped)
return self._is_list_builtin_method(self._strip_module_sha1_prefix(ty_nm), method_name)
# Actual type unavailable -> fall back to the annotation/registered name
return self._is_list_builtin_method(fallback_name, method_name)
+257
View File
@@ -0,0 +1,257 @@
from stdint import *
import ast
import mlirlite
import memhub
import string
import lib.core.Gen.Translator as HT
from . import CallGenBase
# CallGenVa - CallGenMixin split (stage B): va_arg variadics (start/arg/end / variadic call promotion)
# ============================================================
novirtual class CallGenVa(CallGenBase.CallGenBase):
"""va_arg variadics (start/arg/end and variadic call promotion)."""
# _func_has_eh_params - whether callee signature has try-except EH out params (main excluded)
# ============================================================
def _va_arg_get_alloca_ptr(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, arg_node: ast.AST*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
"""Return the alloca pointer of a va_list variable without loading it."""
if arg_node is None: return None
# Name node: look up the alloca pointer from the symbol table (no load)
if arg_node.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(arg_node)
if nm.id is not None:
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is not None: return alloca
# Fallback: translate_value (may return a loaded value; only valid when already a pointer)
return self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, funcs_ptr, func_count, trans)
def _resolve_va_arg_type(self, pool: memhub.MemBuddy*, type_node: ast.AST*, trans: HT.Translator*) -> mlirlite.LLVMType*:
"""Resolve the va_arg/arg type argument to an LLVMType, defaulting to i32."""
if type_node is None: return mlirlite.Int32(pool)
# Resolve the type expression via HandlesType.resolve_annotation_type
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, type_node, trans._imported_modules, trans._from_imports, trans, 0)
if resolved is not None: return resolved
# Type node present but unresolvable: fail-fast, never fall back to i32 (would read the vararg slot at the wrong width)
self.Gen.TypeGenErr.fatal_error(type_node, "va_arg type resolution failed", "TypeError")
return mlirlite.Int32(pool)
def _translate_va_arg_sugar(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, cargs: list[ast.AST*]*, can: sizet, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
"""Emit a va_arg instruction reading the next variadic argument from the va_list."""
if can == 0 or cargs is None: return mlirlite.const_int32(pool, 0)
# First arg: va_list (evaluate to an i8* address; aligned with TPC _HandleVaArgLlvm). In own *args
# translate_value returns the buffer address; in forwarding (sprintf -> vsnprintf) it loads the param.
ap_node: ast.AST* = cargs.get(0)
ap_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, ap_node, None, 0, trans)
if ap_val is None: return mlirlite.const_int32(pool, 0)
ap_ptr: mlirlite.Value* = ap_val
# Second arg: target type (optional, defaults to i32)
arg_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
if can >= 2:
type_node: ast.AST* = cargs.get(1)
arg_ty = self._resolve_va_arg_type(pool, type_node, trans)
# Win64 vararg slots are 8 bytes: read sub-64-bit ints as i64 (TPC emit_va_arg L72-73)
# then trunc to target width; otherwise high bits are garbage (%d/%i wrong, A6 root cause).
read_ty: mlirlite.LLVMType* = arg_ty
abits_va: int = self.Gen.ExprGenBase.get_llvm_type_bits(arg_ty)
if abits_va != 0 and abits_va < 64:
read_ty = mlirlite.Int64(pool)
va_raw: mlirlite.Value* = mlirlite.build_va_arg(builder, ap_ptr, read_ty)
if va_raw is None: return mlirlite.const_int32(pool, 0)
# 8-byte slot -> target width (low bits); caller int64(...) re-applies sext/zext
return mlirlite.build_trunc(builder, va_raw, arg_ty)
# Emit the va_arg instruction
return mlirlite.build_va_arg(builder, ap_ptr, read_ty)
def _translate_arg_sugar(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, cargs: list[ast.AST*]*, can: sizet, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
"""Emit va_arg reading from the enclosing variadic's own va_list or a forwarded va_list."""
# arg() has two forms:
# (1) Own-variadic form: inside `def f(a, *args)`, trans._va_list_ptr is the i8* address of
# the function's implicit va_list buffer; read the next vararg straight from it.
# (2) Forwarding form: in a NON-variadic wrapper such as vsnprintf(buf, size, fmt, args),
# the caller's va_list pointer is passed by value through the 4th param named "args"
# (vprintf style, see includes/viperlib.vp); there is no implicit va_list here, so
# trans._va_list_ptr is None and the pointer must be recovered from that param.
va_ptr: mlirlite.Value* = trans._va_list_ptr
if va_ptr is None:
# The "args" param's alloca is an i8** slot holding the forwarded i8* va_list value, so it
# MUST be loaded once: va_arg needs the pointer value itself (i8*), not the slot address.
args_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, "args")
if args_alloca is None: return mlirlite.const_int32(pool, 0)
if args_alloca.Ty is None: return mlirlite.const_int32(pool, 0)
va_ptr = mlirlite.build_load(builder, args_alloca.Ty.Pointee, args_alloca)
if va_ptr is None: return mlirlite.const_int32(pool, 0)
# First arg: target type (optional, defaults to i32)
arg_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
if can >= 1 and cargs is not None:
type_node: ast.AST* = cargs.get(0)
arg_ty = self._resolve_va_arg_type(pool, type_node, trans)
# Win64 vararg slots are 8 bytes: read sub-64-bit ints as i64 (same as _translate_va_arg_sugar),
# then trunc to target width (%d/%i high-bit garbage fix, see _translate_va_arg_sugar).
read_ty: mlirlite.LLVMType* = arg_ty
abits_arg: int = self.Gen.ExprGenBase.get_llvm_type_bits(arg_ty)
if abits_arg != 0 and abits_arg < 64:
read_ty = mlirlite.Int64(pool)
arg_raw: mlirlite.Value* = mlirlite.build_va_arg(builder, va_ptr, read_ty)
if arg_raw is None: return mlirlite.const_int32(pool, 0)
return mlirlite.build_trunc(builder, arg_raw, arg_ty)
# Emit va_arg, using va_ptr as the va_list address (own buffer or forwarded pointer)
return mlirlite.build_va_arg(builder, va_ptr, read_ty)
def _translate_va_start_sugar(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cargs: list[ast.AST*]*, can: sizet, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
"""Emit llvm.va_start to initialize the va_list (the second C-ABI argument is unused)."""
if can == 0 or cargs is None: return mlirlite.const_int32(pool, 0)
# First arg: va_list pointer (get its alloca, no load)
ap_node: ast.AST* = cargs.get(0)
ap_ptr: mlirlite.Value* = self._va_arg_get_alloca_ptr(pool, builder, mod, ap_node, funcs_ptr, func_count, trans)
if ap_ptr is None: return mlirlite.const_int32(pool, 0)
# bitcast to i8* (@llvm.va_start takes i8*)
i8_ty_vs: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty_vs: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_vs)
ap_i8: mlirlite.Value* = mlirlite.build_bitcast(builder, ap_ptr, i8_ptr_ty_vs)
if ap_i8 is None: return mlirlite.const_int32(pool, 0)
void_ty_vs: mlirlite.LLVMType* = mlirlite.Void(pool)
mlirlite.build_call(builder, "llvm.va_start", ap_i8, 1, void_ty_vs, 0)
return mlirlite.const_int32(pool, 0)
def _translate_va_end_sugar(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cargs: list[ast.AST*]*, can: sizet, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
"""Emit llvm.va_end to clean up the va_list (a no-op on most platforms)."""
if can == 0 or cargs is None: return mlirlite.const_int32(pool, 0)
# First arg: va_list pointer (get its alloca, no load)
ap_node: ast.AST* = cargs.get(0)
ap_ptr: mlirlite.Value* = self._va_arg_get_alloca_ptr(pool, builder, mod, ap_node, funcs_ptr, func_count, trans)
if ap_ptr is None: return mlirlite.const_int32(pool, 0)
# bitcast to i8* (@llvm.va_end takes i8*)
i8_ty_ve: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty_ve: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_ve)
ap_i8: mlirlite.Value* = mlirlite.build_bitcast(builder, ap_ptr, i8_ptr_ty_ve)
if ap_i8 is None: return mlirlite.const_int32(pool, 0)
void_ty_ve: mlirlite.LLVMType* = mlirlite.Void(pool)
mlirlite.build_call(builder, "llvm.va_end", ap_i8, 1, void_ty_ve, 0)
return mlirlite.const_int32(pool, 0)
def _get_func_fixed_param_count(self, func: mlirlite.Function*) -> int:
"""Return the count of fixed params by walking the Params list."""
if func is None: return 0
count: int = 0
param: mlirlite.Param* = mlirlite.function_get_param_head(func)
while param is not None:
count += 1
param = mlirlite.param_get_next(param)
return count
def _get_known_vararg_cfunc_fixed_count(self, name: str) -> int:
"""Return the fixed-param count for known variadic C functions (-1 if unknown)."""
if name is None: return -1
if string.strcmp(name, "snprintf") == 0: return 3
if string.strcmp(name, "sprintf") == 0: return 2
if string.strcmp(name, "vsprintf") == 0: return 2
return -1
def _get_known_vararg_cfunc_param_ty(self, pool: memhub.MemBuddy*, name: str, idx: int) -> mlirlite.LLVMType*:
"""Return the type of the idx-th fixed param of a known variadic C function."""
if pool is None or name is None or idx < 0: return None
i8_ptr_kv: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
i64_kv: mlirlite.LLVMType* = mlirlite.Int64(pool)
if string.strcmp(name, "snprintf") == 0:
if idx == 0: return i8_ptr_kv
if idx == 1: return i64_kv
if idx == 2: return i8_ptr_kv
return None
if string.strcmp(name, "sprintf") == 0 or string.strcmp(name, "vsprintf") == 0:
if idx == 0: return i8_ptr_kv
if idx == 1: return i8_ptr_kv
return None
return None
def _check_and_promote_variadic_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, call_name: str, head: mlirlite.Value*, can: sizet, arg_nodes: list[ast.AST*]* = None, self_offset: int = 0, trans: HT.Translator* = None) -> int:
"""Promote variadic args of a callee to the C ABI; returns 1 if variadic else 0."""
if mod is None or call_name is None or head is None: return 0
# Look up the function object
func: mlirlite.Function* = self.find_func_in_module(mod, call_name)
fixed_count: int = -1
if func is not None and func.IsVarArg == 1: fixed_count = self._get_func_fixed_param_count(func)
else:
# Fall back to the known variadic C-lib signature table (snprintf etc.) when func is None (stub not yet injected)
# or IsVarArg=0 (ad hoc declare); otherwise i32 varargs aren't promoted and the Win64 8-byte slot keeps only 4 bytes.
fixed_count = self._get_known_vararg_cfunc_fixed_count(call_name)
if fixed_count < 0: return 0
# No varargs (arg count <= fixed count) but still report is_variadic=1
if can <= fixed_count: return 1
# Walk to the fixed_count-th node (the last fixed param)
cur: mlirlite.Value* = head
prev: mlirlite.Value* = None
idx: int = 0
while cur is not None and idx < fixed_count:
prev = cur
cur = cur.Next
idx += 1
# cur now points at the first vararg; apply C ABI promotion to all varargs
i32_ty_promote: mlirlite.LLVMType* = mlirlite.Int32(pool)
double_ty_promote: mlirlite.LLVMType* = mlirlite.Double(pool)
v_idx: int = fixed_count
while cur is not None:
next_node: mlirlite.Value* = cur.Next
promoted: mlirlite.Value* = cur
# C ABI integer promotion: i8/i16 → i32
arg_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(cur.Ty)
if arg_bits != 0 and arg_bits < 32:
# Unsigned ints (uint8/CUInt16T etc.) -> zext; signed/unknown -> sext.
# Fix: printf("%02x", buf[i]) with CUInt8T must zext to 0xF3, not sext to 0xFFFFFFF3.
is_u_va: int = 0
if arg_nodes is not None and trans is not None:
ast_idx_va: int = v_idx - self_offset
if ast_idx_va >= 0 and ast_idx_va < len(arg_nodes):
an_va: ast.AST* = arg_nodes.get(ast_idx_va)
if an_va is not None:
if self.Gen.ExprGenOps._node_type_unsigned(pool, an_va, trans) != 0: is_u_va = 1
if is_u_va != 0: promoted = mlirlite.build_zext(builder, cur, i32_ty_promote)
else:
# If is_u_va is unknown (missing arg_nodes/trans or no annotation), default i8 to zext
# (aligned with should_zext_widen); sext would print uint8 bytes as ffffffXX garbage.
if self.Gen.ExprGenBase.get_llvm_type_bits(cur.Ty) == 8: promoted = mlirlite.build_zext(builder, cur, i32_ty_promote)
else: promoted = self.Gen.ExprGenBase.coerce_to_type(builder, cur, i32_ty_promote, None)
if promoted is not None:
promoted.Next = next_node
if prev is not None: prev.Next = promoted
# C ABI float promotion: float → double
arg_fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(promoted.Ty)
if arg_fbits != 0 and arg_fbits < 64:
promoted = self.Gen.ExprGenBase.coerce_to_type(builder, promoted, double_ty_promote, None)
if promoted is not None:
promoted.Next = next_node
if prev is not None: prev.Next = promoted
# Win64 vararg slots are 8 bytes: i8/i16/i32 -> i64. The va_arg read side always reads i64
# (_translate_va_arg_sugar), so callers must fill all 8 bytes or stale stack data remains.
i64_ty_promote: mlirlite.LLVMType* = mlirlite.Int64(pool)
pb_va: int = self.Gen.ExprGenBase.get_llvm_type_bits(promoted.Ty)
if pb_va != 0 and pb_va < 64:
promoted_va2: mlirlite.Value* = None
is_u_va2: int = 0
if arg_nodes is not None and trans is not None:
ast_idx_va2: int = v_idx - self_offset
if ast_idx_va2 >= 0 and ast_idx_va2 < len(arg_nodes):
an_va2: ast.AST* = arg_nodes.get(ast_idx_va2)
if an_va2 is not None:
if self.Gen.ExprGenOps._node_type_unsigned(pool, an_va2, trans) != 0: is_u_va2 = 1
if is_u_va2 != 0: promoted_va2 = mlirlite.build_zext(builder, promoted, i64_ty_promote)
else: promoted_va2 = mlirlite.build_sext(builder, promoted, i64_ty_promote)
if promoted_va2 is not None:
promoted_va2.Next = next_node
if prev is not None: prev.Next = promoted_va2
promoted = promoted_va2
prev = promoted
cur = next_node
v_idx += 1
return 1
+143
View File
@@ -0,0 +1,143 @@
from stdint import *
import ast
import mlirlite
import memhub
import string
import viperlib
import lib.core.Gen.Translator as HT
import lib.core.Gen.GenTypes as GenTypes
from . import CallGenBase
# CallGenVt - CallGenMixin split (phase B): vtable slot writing
# ============================================================
novirtual class CallGenVt(CallGenBase.CallGenBase):
"""vtable slot writing."""
# _write_vtable_slot / _write_vtable_attr: write a module-level function pointer into an object vtable slot via copy-on-write (vtable slots are not data fields, so get_attribute_ptr cannot find them).
# ============================================================
def _write_vtable_slot(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, obj_ptr: mlirlite.Value*, class_name: str, method_index: int, func_val: mlirlite.Value*, trans: HT.Translator*) -> int:
"""Emit a copy-on-write vtable slot write."""
if pool is None or builder is None or mod is None or trans is None: return 0
if obj_ptr is None or class_name is None or func_val is None: return 0
if method_index < 0 or trans._cur_func is None: return 0
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
if i8_ty is None: return 0
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
if i8_ptr_ty is None: return 0
i8_ptr_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty)
if i8_ptr_ptr_ty is None: return 0
method_count: int = self.Gen.StructGen.get_vtable_method_count(class_name)
if method_count <= 0: return 0
# Build the vtable type { i8*, i8*, ... }; its field list uses mlirlite.ParamNode, not Function*.
vt_first: mlirlite.ParamNode* = None
vt_prev: mlirlite.ParamNode* = None
for vi in range(int(method_count)):
pn: mlirlite.ParamNode* = mlirlite.new_param_node(pool, i8_ptr_ty)
if pn is None: continue
if vt_first is None: vt_first = pn
if vt_prev is not None: mlirlite.paramnode_set_next(vt_prev, pn)
vt_prev = pn
vtable_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, vt_first, method_count, None)
if vtable_ty is None: return 0
# Object vtable slot (object header -> i8**; the vtable pointer is always the first field).
vtable_slot: mlirlite.Value* = mlirlite.build_bitcast(builder, obj_ptr, i8_ptr_ptr_ty)
if vtable_slot is None: return 0
vtable_ptr: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, vtable_slot)
if vtable_ptr is None: return 0
# Address of the class default vtable (for comparison).
class_vt: mlirlite.Value* = self.Gen.CallGenLeafKit._make_vtable_ref_i8(pool, builder, mod, class_name, trans)
if class_vt is None: return 0
cmp_vt: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, vtable_ptr, class_vt)
if cmp_vt is None: return 0
# Unique block names: create_block does not auto-number, so repeated vtable writes in one function would collide.
vtc3: int = trans._label_counter
trans._label_counter = vtc3 + 1
vt_bb_buf3: str = pool.alloc(64)
if vt_bb_buf3 is None: return 0
viperlib.snprintf(vt_bb_buf3, 64, "vt.copy.%d", vtc3)
vt_copy_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, trans._cur_func, vt_bb_buf3)
viperlib.snprintf(vt_bb_buf3, 64, "vt.skip.%d", vtc3)
vt_skip_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, trans._cur_func, vt_bb_buf3)
if vt_copy_bb is None or vt_skip_bb is None: return 0
mlirlite.build_cond_br(builder, cmp_vt, vt_copy_bb, vt_skip_bb)
# copy block: create internal copy global (zeroinit) + memcpy + repoint object at it.
mlirlite.position_at_end(builder, vt_copy_bb)
vtc: int = trans._label_counter
trans._label_counter = vtc + 1
vcopy_buf: str = pool.alloc(256)
if vcopy_buf is not None:
viperlib.snprintf(vcopy_buf, 256, "%s.vtable.copy.%d", class_name, vtc)
vcopy_gv: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, vcopy_buf, vtable_ty)
if vcopy_gv is not None:
mlirlite.module_add_global(mod, vcopy_gv)
vcopy_gv.Linkage = None
vcopy_gv.Initializer = "zeroinitializer"
# Reference names need the @ prefix: SSAValue emits the name verbatim (unlike new_global_variable, it adds no @).
vcopy_ref_nm: str = pool.alloc(272)
if vcopy_ref_nm is None: vcopy_ref_nm = vcopy_buf
else: viperlib.snprintf(vcopy_ref_nm, 272, "@%s", vcopy_buf)
vcopy_ref: mlirlite.Value* = mlirlite.SSAValue(pool, mlirlite.Ptr(pool, vtable_ty), vcopy_ref_nm)
if vcopy_ref is not None:
vcopy_i8: mlirlite.Value* = mlirlite.build_bitcast(builder, vcopy_ref, i8_ptr_ty)
if vcopy_i8 is not None:
# ConstInt's name must be numeric text (e.g. "56"); an identifier would emit verbatim without % and break llc.
vtsz_buf: str = pool.alloc(32)
if vtsz_buf is not None:
viperlib.snprintf(vtsz_buf, 32, "%lld", method_count * 8)
vt_size: mlirlite.Value* = mlirlite.ConstInt(pool, mlirlite.Int64(pool), method_count * 8, vtsz_buf)
if vt_size is not None: self.Gen.CallGenLib._emit_llvm_memcpy_intrinsic(pool, builder, mod, vcopy_i8, class_vt, vt_size)
mlirlite.build_store(builder, vcopy_i8, vtable_slot)
mlirlite.build_br(builder, vt_skip_bb)
# skip block: reload vtable -> GEP the slot -> store the function pointer (i8*).
mlirlite.position_at_end(builder, vt_skip_bb)
vt_ptr2: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, vtable_slot)
if vt_ptr2 is None: return 0
vt_typed: mlirlite.Value* = mlirlite.build_bitcast(builder, vt_ptr2, mlirlite.Ptr(pool, vtable_ty))
if vt_typed is None: return 0
vt_slotp: mlirlite.Value* = mlirlite.build_gep_struct(builder, vtable_ty, i8_ptr_ty, vt_typed, method_index)
if vt_slotp is None: return 0
fptr_i8: mlirlite.Value* = mlirlite.build_bitcast(builder, func_val, i8_ptr_ty)
if fptr_i8 is not None: mlirlite.build_store(builder, fptr_i8, vt_slotp)
return 1
def _write_vtable_attr(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, rhs_val: mlirlite.Value*, trans: HT.Translator*) -> int:
"""Handle obj.method = func writes that target a vtable slot."""
if node is None or builder is None or pool is None or mod is None: return 0
if rhs_val is None or trans is None: return 0
at: ast.Attribute* = ast.Attribute*(node)
if at is None or at.value is None or at.attr is None: return 0
# Resolve the class name (AnnotClassName; strip the module prefix).
class_nm_v: str = None
if at.value.kind() == ast.ASTKind.Name:
nm_v: ast.Name* = ast.Name*(at.value)
if nm_v.id is not None:
ve_v: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_v.id)
if ve_v is not None: class_nm_v = self.Gen.VarGen.var_annot_class_name(ve_v)
elif at.value.kind() == ast.ASTKind.Attribute:
# Cross-mixin: _resolve_attr_field_class lives in ExprGenAttr, called via self.Gen.
class_nm_v = self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, at.value)
if class_nm_v is None: return 0
fd_dot_v: str = string.strrchr(class_nm_v, '.')
if fd_dot_v is not None: class_nm_v = fd_dot_v + 1
if self.Gen.StructGen.get_struct_type(class_nm_v) is None: return 0
vm_idx_v: int = self.Gen.StructGen.get_vtable_method_index(class_nm_v, at.attr)
if vm_idx_v < 0: return 0
# Resolve obj_ptr (same as the first half of get_attribute_ptr: Name looks up the alloca directly).
obj_ptr_v: mlirlite.Value* = None
if at.value.kind() == ast.ASTKind.Name: obj_ptr_v = self.Gen.VarGen.lookup_var(trans.SymTab, (ast.Name*(at.value)).id)
else: obj_ptr_v = self.Gen.ExprGen.translate_value(builder, pool, mod, at.value, None, 0, trans)
if obj_ptr_v is None or obj_ptr_v.Ty is None: return 0
# X* variable (Ptr(Ptr)): load-dereference to get the object pointer.
match obj_ptr_v.Ty:
case mlirlite.LLVMType.Ptr(inner_v):
match inner_v:
case mlirlite.LLVMType.Ptr(_):
obj_ptr_v = mlirlite.build_load(builder, inner_v, obj_ptr_v)
case _:
pass
case _:
pass
if obj_ptr_v is None: return 0
return self._write_vtable_slot(pool, builder, mod, obj_ptr_v, class_nm_v, vm_idx_v, rhs_val, trans)
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-404
View File
@@ -1,404 +0,0 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import sys
import viperlib
import w32.fileio as fileio
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesBase as HandlesBase
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.Stub.StubMerger as StubMerger
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.CallGenBase as CallGenBase
# ============================================================
# CallGenFd - CallGenMixin 拆分(阶段 B):FakeDuck / requires 字段注入 / provider 栈
#
# 继承 CallGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesExprCall 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class CallGenFd(CallGenBase.CallGenBase):
"""FakeDuck / requires 字段注入 / provider 栈"""
# ============================================================
# _translate_fakeduck_call - FakeDuck: str 变量方法调用 → 扩展类 _Type/_str
#
# 对齐 TPC HandlesExprCall.py L2841-2880:
# ClassName 常规解析失败且对象是 char*(i8*)时,遍历扩展类查找
# {CN}.{method} 函数。对象是 Name 变量且有影子({var}__meta__,fd2
# 创建)时 self=影子;否则(字符串字面量/匿名 str)在入口创建临时
# 影子,写 __data__=obj 值、__mbuddy__=NULL 后作 self,避免
# bitcast i8* → _str* 读取垃圾指针导致 ACCESS_VIOLATION。
# ============================================================
# ============================================================
def _translate_fakeduck_call(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
cls_name: str,
method_name: str,
cl: ast.Call | t.CPtr,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""FakeDuck: str 变量方法调用解析到扩展类 _Type/_str,返回 call 结果或 None"""
if pool is None or builder is None or cl is None or trans is None:
return None
if method_name is None:
return None
# 仅对 str 类型触发(AnnotClassName == "str",对象是 char*);
# cls_name 为 None(表达式接收者解析失败)时也尝试
if cls_name is not None and string.strcmp(cls_name, "str") != 0:
return None
if cl.func is None or cl.func.kind() != ast.ASTKind.Attribute:
return None
fd_at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(cl.func)
if fd_at is None or fd_at.value is None or fd_at.attr is None:
return None
if string.strcmp(fd_at.attr, method_name) != 0:
return None
# 候选扩展类:先 _Type(a.type_id())后 _str(a.upper() 等)
fd_func: llvmlite.Function | t.CPtr = None
fd_cls: str = None
fd_key: str = pool.alloc(160)
if fd_key is not None:
for fd_ci in range(2):
fd_cn: str = "_Type"
if fd_ci == 1:
fd_cn = "_str"
viperlib.snprintf(fd_key, 160, "%s.%s", fd_cn, method_name)
fd_func = self.find_func_in_module(mod, fd_key)
if fd_func is not None:
fd_cls = fd_cn
break
if fd_func is None or fd_cls is None:
return None
# 扩展类结构体必须已注册(GEP/临时影子需要类型)
fd_sty: llvmlite.LLVMType | t.CPtr = self.Gen.StructGen.get_struct_type(fd_cls)
if fd_sty is None:
return None
# 扩展类字段数:有字段(_str)时 self 用影子结构体指针;
# 无字段(_Type)时 self 用对象值 bitcast 到扩展类指针(对齐 TPC L2856-2876)
fd_entry_fd: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct_by_type(fd_sty)
fd_has_fields: int = 0
if fd_entry_fd is not None and fd_entry_fd.FieldCount > 0:
fd_has_fields = 1
# self 参数:优先使用已有影子 {var}__meta__(fd2 创建,仅当扩展类有字段时)
fd_self: llvmlite.Value | t.CPtr = None
if fd_at.value.kind() == ast.ASTKind.Name:
fd_onm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(fd_at.value)
if fd_onm.id is not None:
# pool 持久缓冲(f-string 栈缓冲在函数返回后失效)
fd_mn: str = pool.alloc(256)
if fd_mn is not None:
viperlib.snprintf(fd_mn, 256, "%s__meta__", fd_onm.id)
fd_meta: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_current(
trans.SymTab, fd_mn)
if fd_meta is not None and fd_has_fields != 0:
fd_self = fd_meta
if fd_self is None:
if fd_has_fields != 0:
# 有字段扩展类且无影子(字符串字面量等):入口创建临时影子并初始化
# 先确保 _str 完整定义(跨模块结构体默认 opaque,alloca 报 unsized)
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, fd_sty)
fd_self = self.Gen.VarGen._alloca_at_entry(builder, fd_sty)
if fd_self is None:
return None
fd_obj_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, fd_at.value, None, 0, trans)
if fd_obj_val is not None:
fd_dfe: GenTypes.FieldEntry | t.CPtr = \
self.Gen.StructGen.lookup_field(fd_sty, "__data__")
if fd_dfe is not None and fd_dfe.Ty is not None:
fd_dp: llvmlite.Value | t.CPtr = llvmlite.build_gep_struct(
builder, fd_sty, fd_dfe.Ty, fd_self, fd_dfe.Index)
if fd_dp is not None:
llvmlite.build_store(builder, fd_obj_val, fd_dp)
fd_bfe: GenTypes.FieldEntry | t.CPtr = \
self.Gen.StructGen.lookup_field(fd_sty, "__mbuddy__")
if fd_bfe is not None and fd_bfe.Ty is not None:
fd_bp: llvmlite.Value | t.CPtr = llvmlite.build_gep_struct(
builder, fd_sty, fd_bfe.Ty, fd_self, fd_bfe.Index)
fd_null: llvmlite.Value | t.CPtr = llvmlite.ConstNull(
pool, fd_bfe.Ty, "null")
if fd_bp is not None and fd_null is not None:
llvmlite.build_store(builder, fd_null, fd_bp)
# FakeDuck: with 上下文自动注入 requires 字段(如 __mbuddy__)
self._inject_requires_fields(
trans, pool, builder, mod, fd_self, fd_cls)
else:
# 无字段扩展类(_Type):对象值 bitcast 到扩展类指针
fd_obj_v: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, fd_at.value, None, 0, trans)
if fd_obj_v is None:
return None
fd_self = llvmlite.build_bitcast(
builder, fd_obj_v, llvmlite.Ptr(pool, fd_sty))
if fd_self is None:
return None
# 构建参数链表: self → 其余参数
llvmlite.value_set_next(fd_self, None)
fd_head: llvmlite.Value | t.CPtr = fd_self
fd_tail: llvmlite.Value | t.CPtr = fd_self
fd_args: list[ast.AST | t.CPtr] | t.CPtr = cl.args
fd_can: int = 0
if fd_args is not None:
fd_can = len(fd_args)
for fd_ai in range(fd_can):
fd_arg: ast.AST | t.CPtr = fd_args.get(fd_ai)
if fd_arg is not None:
fd_av: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, fd_arg, None, 0, trans)
# char 常量参数 → 单字符字符串(char 隐式转 str,
# 如 s1.split(','):',' 是 i32 值 44,传给 str 参数须先字符串化,
# 否则运行时 strlen/strncmp 解引用 44 崩溃)
if fd_arg.kind() == ast.ASTKind.Constant:
fd_ac: ast.Constant | t.CPtr = (ast.Constant | t.CPtr)(fd_arg)
if fd_ac.const_kind == ast.CONST_CHAR:
ch_buf: str = pool.alloc(2)
if ch_buf is not None:
ch_buf[0] = t.CChar(fd_ac.int_val)
ch_buf[1] = '\0'
fd_av = self.Gen.ExprGenConst.create_global_string(
builder, pool, mod, ch_buf, trans)
if fd_av is not None:
llvmlite.value_set_next(fd_av, None)
llvmlite.value_set_next(fd_tail, fd_av)
fd_tail = fd_av
# 返回类型与调用名(来自 find_func_in_module 命中的函数定义)
fd_ret_ty: llvmlite.LLVMType | t.CPtr = llvmlite.function_get_ret_ty(fd_func)
if fd_ret_ty is None:
fd_ret_ty = llvmlite.Int32(pool)
fd_fname: str = llvmlite.function_get_name(fd_func)
if fd_fname is None:
return None
# 返回类型精确化:跨模块 declare 的 list[T] 特化返回类型可能被简化
# 为 Ptr(i8),调用方变量 alloca 类型随之退化 → parts[0] 走字符下标
# 路径而非 list 内联 __getitem__(printf %s 解引用字符值崩溃)。
# 从定义模块 text.ll 读取精确返回类型(对齐普通方法调用 _translate_method_call)。
fd_sha1: str = pool.alloc(17)
if fd_sha1 is not None:
string.strncpy(fd_sha1, fd_fname, 16)
fd_sha1[16] = '\0'
fd_exact: llvmlite.LLVMType | t.CPtr = self._LookupMethodRetTyFromTextLL(
pool, fd_sha1, fd_cls, method_name)
if fd_exact is not None:
fd_ret_ty = fd_exact
fd_total: int = 1 + fd_can
return llvmlite.build_call(builder, fd_fname, fd_head, fd_total, fd_ret_ty, 0)
# ============================================================
# _inject_requires_fields - FakeDuck: with 上下文注入 requires 字段
#
# 对齐 TPC HandlesExprCall.py L4021-4079:
# 对类 __requires__ 元数据(fd1 注册)中的每个字段,运行时调用
# _find_provider(field) 查找 with 上下文提供的指针;非 NULL 时
# bitcast 到字段类型写入 meta_ptr 对应槽位(条件注入,避免覆盖
# 用户已手动设置的值)。_find_provider 未注册(未 import
# _withcontent)时跳过注入,不影响非 with 场景。
# ============================================================
# ============================================================
def _inject_requires_fields(self, trans: HT.Translator | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
meta_ptr: llvmlite.Value | t.CPtr,
class_name: str) -> int:
"""在 with 上下文注入 requires 字段,返回注入字段数"""
if trans is None or pool is None or builder is None or mod is None:
return 0
if meta_ptr is None or class_name is None:
return 0
# _withcontent._find_provider(跨模块):仅当本模块可见(_withcontent
# 被 import,vpi stub declare 已注入)时才注入。对齐 TPC L4048-4050
# (Gen.functions.get('_find_provider') 不存在则跳过)——否则非 with
# 项目(如 HashTableTest 未 import _withcontent)也会为每个 _str(...)
# 构造发射 {sha1}._find_provider 调用 → ld.lld undefined symbol。
fd_fn_probe: llvmlite.Function | t.CPtr = self.find_func_in_module(
mod, "_find_provider")
if fd_fn_probe is None:
return 0
fd_fname: str = llvmlite.function_get_name(fd_fn_probe)
if fd_fname is None:
return 0
cnt: int = self.Gen.ClassGenBase.class_meta_count(
GenTypes.CLASS_META_REQUIRES, class_name)
if cnt <= 0:
return 0
sty: llvmlite.LLVMType | t.CPtr = self.Gen.StructGen.get_struct_type(class_name)
if sty is None:
return 0
i8_ty_j: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
if i8_ty_j is None:
return 0
i8_ptr_ty_j: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty_j)
if i8_ptr_ty_j is None:
return 0
cur_func: llvmlite.Function | t.CPtr = trans._cur_func
if cur_func is None:
return 0
injected: int = 0
for ji in range(cnt):
fname: str = self.Gen.ClassGenBase.class_meta_field_at(
GenTypes.CLASS_META_REQUIRES, class_name, ji)
if fname is None:
continue
fe: GenTypes.FieldEntry | t.CPtr = self.Gen.StructGen.lookup_field(
sty, fname)
if fe is None or fe.Ty is None:
continue
# 参数:字段名字符串(i8*)
fd_fstr: llvmlite.Value | t.CPtr = self.Gen.ExprGenConst.create_global_string(
builder, pool, mod, fname, trans)
if fd_fstr is None:
continue
llvmlite.value_set_next(fd_fstr, None)
self._ensure_method_declare(
pool, mod, fd_fname, i8_ptr_ty_j, fd_fstr, 1)
fd_ptr: llvmlite.Value | t.CPtr = llvmlite.build_call(
builder, fd_fname, fd_fstr, 1, i8_ptr_ty_j, 0)
if fd_ptr is None:
continue
# 条件注入:ptr != NULL 才写入(保留用户手动设置的值)
fd_null: llvmlite.Value | t.CPtr = llvmlite.ConstNull(
pool, i8_ptr_ty_j, "null")
if fd_null is None:
continue
fd_cmp: llvmlite.Value | t.CPtr = llvmlite.build_icmp(
builder, llvmlite.ICMP_NE, fd_ptr, fd_null)
if fd_cmp is None:
continue
# 块名唯一化(create_block 不自动编号,同名 label 在文本 IR 中
# 冲突 → llc "Terminator found in the middle of a basic block")
vtc2: int = trans._label_counter
trans._label_counter = vtc2 + 1
fd_bb_buf2: str = pool.alloc(64)
if fd_bb_buf2 is None:
continue
viperlib.snprintf(fd_bb_buf2, 64, "fd.skip.%d", vtc2)
fd_skip_bb: llvmlite.BasicBlock | t.CPtr = llvmlite.create_block(
pool, cur_func, fd_bb_buf2)
viperlib.snprintf(fd_bb_buf2, 64, "fd.inject.%d", vtc2)
fd_inject_bb: llvmlite.BasicBlock | t.CPtr = llvmlite.create_block(
pool, cur_func, fd_bb_buf2)
if fd_skip_bb is None or fd_inject_bb is None:
continue
llvmlite.build_cond_br(builder, fd_cmp, fd_inject_bb, fd_skip_bb)
# inject 块:bitcast + GEP + store,跳回 skip
llvmlite.position_at_end(builder, fd_inject_bb)
fd_cast: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, fd_ptr, fe.Ty)
if fd_cast is not None:
fd_fp: llvmlite.Value | t.CPtr = llvmlite.build_gep_struct(
builder, sty, fe.Ty, meta_ptr, fe.Index)
if fd_fp is not None:
llvmlite.build_store(builder, fd_cast, fd_fp)
llvmlite.build_br(builder, fd_skip_bb)
# skip 块:恢复 CurBlock,继续后续代码生成
llvmlite.position_at_end(builder, fd_skip_bb)
injected += 1
return injected
# ============================================================
# _emit_provider_push / _emit_provider_pop - FakeDuck: with 上下文
# provider push/pop(__provides__ 元数据,fd1 注册)
#
# 对齐 TPC HandlesWith.py L138-174:
# with 进入时对每个提供字段生成 _push_provider(field_str, ctx_i8);
# 退出时生成等量 _pop_provider()。运行时 _withcontent 模块负责
# 维护 TLS provider 栈,供 _inject_requires_fields 查找。
# ============================================================
# ============================================================
def _emit_provider_push(self, trans: HT.Translator | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
cls_name: str,
ctx_val: llvmlite.Value | t.CPtr) -> int:
"""生成 _push_provider(field, ctx_i8) 调用(每提供字段一次),返回字段数"""
if trans is None or pool is None or builder is None or mod is None:
return 0
if cls_name is None or ctx_val is None:
return 0
fd_pc: int = self.Gen.ClassGenBase.class_meta_count(
GenTypes.CLASS_META_PROVIDES, cls_name)
if fd_pc <= 0:
return 0
# _withcontent._push_provider(跨模块,同 _find_provider 的可见性守卫:
# 本模块未 import _withcontent 时跳过,避免 undefined symbol)
fd_pfn_probe: llvmlite.Function | t.CPtr = self.find_func_in_module(
mod, "_push_provider")
if fd_pfn_probe is None:
return 0
fd_pfn: str = llvmlite.function_get_name(fd_pfn_probe)
if fd_pfn is None:
return 0
i8_ty_p: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
if i8_ty_p is None:
return 0
i8_ptr_ty_p: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty_p)
if i8_ptr_ty_p is None:
return 0
ret_i32_p: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
if ret_i32_p is None:
return 0
# ctx 值 bitcast 到 i8*(provider 栈存 void*)
fd_ctx_i8: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, ctx_val, i8_ptr_ty_p)
if fd_ctx_i8 is None:
return 0
for pi in range(fd_pc):
fname: str = self.Gen.ClassGenBase.class_meta_field_at(
GenTypes.CLASS_META_PROVIDES, cls_name, pi)
if fname is None:
continue
fd_fstr: llvmlite.Value | t.CPtr = self.Gen.ExprGenConst.create_global_string(
builder, pool, mod, fname, trans)
if fd_fstr is None:
continue
llvmlite.value_set_next(fd_fstr, fd_ctx_i8)
llvmlite.value_set_next(fd_ctx_i8, None)
self._ensure_method_declare(
pool, mod, fd_pfn, ret_i32_p, fd_fstr, 2)
llvmlite.build_call(builder, fd_pfn, fd_fstr, 2, ret_i32_p, 0)
return fd_pc
def _emit_provider_pop(self, trans: HT.Translator | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
cls_name: str) -> int:
"""生成 _pop_provider() 调用(每提供字段一次),返回字段数"""
if trans is None or pool is None or builder is None or mod is None:
return 0
if cls_name is None:
return 0
fd_pc: int = self.Gen.ClassGenBase.class_meta_count(
GenTypes.CLASS_META_PROVIDES, cls_name)
if fd_pc <= 0:
return 0
# _withcontent._pop_provider(跨模块,同 _find_provider 的可见性守卫:
# 本模块未 import _withcontent 时跳过,避免 undefined symbol)
fd_pon_probe: llvmlite.Function | t.CPtr = self.find_func_in_module(
mod, "_pop_provider")
if fd_pon_probe is None:
return 0
fd_pon: str = llvmlite.function_get_name(fd_pon_probe)
if fd_pon is None:
return 0
ret_i32_q: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
if ret_i32_q is None:
return 0
self._ensure_method_declare(pool, mod, fd_pon, ret_i32_q, None, 0)
for _pi in range(fd_pc):
llvmlite.build_call(builder, fd_pon, None, 0, ret_i32_q, 0)
return fd_pc
File diff suppressed because it is too large. Load diff
-584
View File
@@ -1,584 +0,0 @@
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import sys
import viperlib
import w32.fileio as fileio
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesBase as HandlesBase
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.Stub.StubMerger as StubMerger
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.CallGenBase as CallGenBase
# ============================================================
# CallGenLib - CallGenMixin 拆分(阶段 C):C 库声明 / 返回类型推断 /
# 变长参数 / memcpy / kwargs 调用辅助方法
#
# 继承 CallGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesExprCall 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class CallGenLib(CallGenBase.CallGenBase):
"""C 库声明 / 返回类型推断 / kwargs 调用辅助(继承 Base)"""
def _ensure_c_lib_declare(self, pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
name: str) -> llvmlite.Function | t.CPtr:
"""为已知的 C 库函数创建 declare 声明"""
if pool is None or mod is None or name is None:
return None
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
i64_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty)
ret_ty: llvmlite.LLVMType | t.CPtr = None
params: int = 0 # 参数数量
if string.strcmp(name, "strlen") == 0 or string.strcmp(name, "strlength") == 0:
ret_ty = i64_ty
params = 1
elif string.strcmp(name, "memset") == 0:
ret_ty = i8_ptr_ty
params = 3
elif string.strcmp(name, "memcpy") == 0:
ret_ty = i8_ptr_ty
params = 3
elif string.strcmp(name, "memmove") == 0:
ret_ty = i8_ptr_ty
params = 3
elif string.strcmp(name, "memcmp") == 0:
ret_ty = i32_ty
params = 3
elif string.strcmp(name, "strcmp") == 0:
ret_ty = i32_ty
params = 2
elif string.strcmp(name, "strncmp") == 0:
ret_ty = i32_ty
params = 3
elif string.strcmp(name, "strcpy") == 0:
ret_ty = i8_ptr_ty
params = 2
elif string.strcmp(name, "strncpy") == 0:
ret_ty = i8_ptr_ty
params = 3
elif string.strcmp(name, "strcat") == 0:
ret_ty = i8_ptr_ty
params = 2
elif string.strcmp(name, "puts") == 0:
ret_ty = i32_ty
params = 1
elif string.strcmp(name, "atoi") == 0:
ret_ty = i32_ty
params = 1
elif string.strcmp(name, "atol") == 0:
ret_ty = i64_ty
params = 1
elif string.strcmp(name, "abs") == 0:
ret_ty = i32_ty
params = 1
elif string.strcmp(name, "labs") == 0:
ret_ty = i64_ty
params = 1
else:
return None
# 创建 declare
fn: llvmlite.Function | t.CPtr = llvmlite.create_declare(pool, mod, name, ret_ty)
if fn is None:
return None
# 添加参数
if params == 1:
if string.strcmp(name, "abs") == 0:
llvmlite.add_param(pool, fn, i32_ty, "n")
elif string.strcmp(name, "labs") == 0:
llvmlite.add_param(pool, fn, i64_ty, "n")
else:
llvmlite.add_param(pool, fn, i8_ptr_ty, "s")
elif params == 2:
llvmlite.add_param(pool, fn, i8_ptr_ty, "dst")
llvmlite.add_param(pool, fn, i8_ptr_ty, "src")
elif params == 3:
if string.strcmp(name, "memset") == 0:
llvmlite.add_param(pool, fn, i8_ptr_ty, "dst")
llvmlite.add_param(pool, fn, i32_ty, "c")
llvmlite.add_param(pool, fn, i64_ty, "n")
else:
llvmlite.add_param(pool, fn, i8_ptr_ty, "dst")
llvmlite.add_param(pool, fn, i8_ptr_ty, "src")
llvmlite.add_param(pool, fn, i64_ty, "n")
return fn
def _infer_external_func_ret_ty(self, pool: memhub.MemBuddy | t.CPtr,
func_name: str) -> llvmlite.LLVMType | t.CPtr:
"""根据函数名推断外部 includes 函数的返回类型
支持三种函数名形式:
1. 裸名: "parse", "strlen"
2. 别名: "json_parse"(from X import parse as json_parse)
3. 带 SHA1 前缀: "240a9a4157959a9f.parse"(跨模块调用 mangled name)
"""
if pool is None or func_name is None:
return llvmlite.Int32(pool)
# 提取裸函数名:去掉 SHA1 前缀(如 "240a9a4157959a9f.parse" → "parse")
# SHA1 前缀是 16 位十六进制 + '.',检查是否有 '.' 分隔
bare_name: str = func_name
dot_pos: str = string.strrchr(func_name, 46) # 46 = ord('.')
if dot_pos is not None:
# '.' 后的部分是裸函数名
bare_name = dot_pos + 1
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
# 返回 i8* 的函数(指针返回值,截断会导致错误)
if string.strcmp(bare_name, "strchr") == 0 or string.strcmp(func_name, "strchr") == 0:
return i8_ptr_ty
if string.strcmp(bare_name, "strrchr") == 0 or string.strcmp(func_name, "strrchr") == 0:
return i8_ptr_ty
if string.strcmp(bare_name, "strstr") == 0 or string.strcmp(func_name, "strstr") == 0:
return i8_ptr_ty
if string.strcmp(bare_name, "strcpy") == 0 or string.strcmp(func_name, "strcpy") == 0:
return i8_ptr_ty
if string.strcmp(bare_name, "strncpy") == 0 or string.strcmp(func_name, "strncpy") == 0:
return i8_ptr_ty
if string.strcmp(bare_name, "memset") == 0 or string.strcmp(func_name, "memset") == 0:
return i8_ptr_ty
if string.strcmp(bare_name, "memset32") == 0 or string.strcmp(func_name, "memset32") == 0:
return i8_ptr_ty
if string.strcmp(bare_name, "memcpy") == 0 or string.strcmp(func_name, "memcpy") == 0:
return i8_ptr_ty
if string.strcmp(bare_name, "memmove") == 0 or string.strcmp(func_name, "memmove") == 0:
return i8_ptr_ty
# JSON 解析函数返回 JsonValue*(指针)
# from json.__parser import parse as json_parse → 别名 json_parse 也需覆盖
if string.strcmp(bare_name, "parse") == 0 or string.strcmp(func_name, "parse") == 0 or string.strcmp(func_name, "json_parse") == 0:
return i8_ptr_ty
# MemBuddy 方法(当类型信息丢失时可能走外部函数路径)
# 存疑
# malloc:裸名 CExport 跨模块调用(uhci/desktop 等 mm.malloc → @malloc)
# 在模块归属解析失败(mod_ret_sha1=None)或 vpi/text.ll 未命中时回退到
# 此白名单。缺失导致返回类型被推断为 i32 → call i32 @malloc 截断 64 位
# 指针 → mm.malloc 实际返回非空但调用方判 None(uhci frame list /
# desktop bg_row_buf 根因)。calloc/realloc/alloc 均已登记,malloc 遗漏。
if string.strcmp(bare_name, "malloc") == 0 or string.strcmp(func_name, "malloc") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
if string.strcmp(func_name, "alloc") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
if string.strcmp(func_name, "calloc") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
if string.strcmp(func_name, "realloc") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
if string.strcmp(func_name, "alloc_buf") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# VLogger 模块函数(跨模块调用时 stub 未注入,返回指针被截断为 i32 导致崩溃)
if string.strcmp(func_name, "get_logger") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
if string.strcmp(func_name, "fmt_buf") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# Config 模块函数(跨模块调用时 stub 未注入,返回 i8* 被 inttoptr i32 截断导致路径损坏)
if string.strcmp(func_name, "get_includes_binary_dir") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# HandlesType 模块函数(返回 ast.AST|t.CPtr 联合类型,跨模块推断为 i32 导致指针截断)
if string.strcmp(bare_name, "_parse_type_string") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# 返回 i64 的函数
if string.strcmp(func_name, "strlen") == 0:
return llvmlite.Int64(pool)
if string.strcmp(func_name, "atoll") == 0:
return llvmlite.Int64(pool)
# 返回 double 的函数
if string.strcmp(func_name, "atof") == 0:
return llvmlite.Double(pool)
# 返回 i8 的函数
if string.strcmp(func_name, "samestr") == 0:
return llvmlite.Int8(pool)
# Win32 API 返回指针(VOIDPTR/HANDLE)的函数
# stubs 翻译时未注入,需推断返回 i8* 避免指针被截断为 i32 导致解引用崩溃
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
if string.strcmp(func_name, "VirtualAlloc") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "HeapAlloc") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "HeapReAlloc") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "HeapCreate") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "GetProcessHeap") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "GlobalAlloc") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "GlobalFree") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "GlobalLock") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "LocalAlloc") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "LocalFree") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "GetProcAddress") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "GetModuleHandleA") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "GetModuleHandleW") == 0:
return i8_ptr_ty
# GetCommandLineA/W 返回 LPSTR(i8*):缺则 64 位指针被截成 i32 →
# inttoptr 后 strlen 读到无效地址(SysLibTest argv 段 ASAN 崩溃,
# 非 ASAN 环境随机表现:IDE 0xC0000005 / 部分环境侥幸可读)。
if string.strcmp(func_name, "GetCommandLineA") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "GetCommandLineW") == 0:
return i8_ptr_ty
# gethostbyname 返回 hostent*(i8*)
if string.strcmp(func_name, "gethostbyname") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "LoadLibraryA") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "LoadLibraryW") == 0:
return i8_ptr_ty
# Win32 Handle 返回函数(HANDLE = void* = i8*)
if string.strcmp(func_name, "GetStdHandle") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "CreateFileA") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "CreateFileW") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "FindFirstFileA") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "FindFirstFileW") == 0:
return i8_ptr_ty
# Win32 API 返回 i64 (SIZE_T) 的函数
if string.strcmp(func_name, "VirtualQuery") == 0:
return llvmlite.Int64(pool)
if string.strcmp(func_name, "HeapSize") == 0:
return llvmlite.Int64(pool)
if string.strcmp(func_name, "HeapCompact") == 0:
return llvmlite.Int64(pool)
if string.strcmp(func_name, "GlobalSize") == 0:
return llvmlite.Int64(pool)
# llvmlite build_* 函数返回类型推断
# stubs 翻译时未注入,需推断返回类型避免指针被截断为 i32
# 大多数 build_* 函数返回 Value*(指针),少数终止指令返回 void
if string.strncmp(func_name, "build_", 6) == 0:
# 返回 void 的 build_* 函数(终止指令和无返回值指令)
if string.strcmp(func_name, "build_ret") == 0 or string.strcmp(func_name, "build_ret_void") == 0 or \
string.strcmp(func_name, "build_store") == 0 or string.strcmp(func_name, "build_br") == 0 or \
string.strcmp(func_name, "build_cond_br") == 0 or string.strcmp(func_name, "build_unreachable") == 0 or \
string.strcmp(func_name, "build_switch") == 0:
return llvmlite.Void(pool)
# 其他 build_* 函数返回 Value*(指针)
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# llvmlite 类型工厂函数返回 LLVMType*(指针)
# Int1/Int8/Int16/Int32/Int64/Ptr/Func/Array/Struct/Void/Half/Float/Double/FP128/Label
# new_param_node/new_phi_incoming 返回 ParamNode*/PhiIncoming*(指针)
# const_int32/const_int64/ConstFloat/ConstZero/ConstNull/ConstInt 返回 Value*(指针)
# function_get_name/param_get_ty 等访问器返回指针
if string.strcmp(func_name, "Int1") == 0 or string.strcmp(func_name, "Int8") == 0 or string.strcmp(func_name, "Int16") == 0 or \
string.strcmp(func_name, "Int32") == 0 or string.strcmp(func_name, "Int64") == 0 or string.strcmp(func_name, "Ptr") == 0 or \
string.strcmp(func_name, "Func") == 0 or string.strcmp(func_name, "Array") == 0 or string.strcmp(func_name, "Struct") == 0 or \
string.strcmp(func_name, "Void") == 0 or string.strcmp(func_name, "Half") == 0 or string.strcmp(func_name, "Float") == 0 or \
string.strcmp(func_name, "Double") == 0 or string.strcmp(func_name, "FP128") == 0 or string.strcmp(func_name, "Label") == 0 or \
string.strcmp(func_name, "new_param_node") == 0 or string.strcmp(func_name, "new_phi_incoming") == 0 or \
string.strcmp(func_name, "const_int32") == 0 or string.strcmp(func_name, "const_int64") == 0 or \
string.strcmp(func_name, "ConstFloat") == 0 or string.strcmp(func_name, "ConstZero") == 0 or \
string.strcmp(func_name, "ConstNull") == 0 or string.strcmp(func_name, "ConstInt") == 0 or \
string.strcmp(func_name, "function_get_name") == 0 or string.strcmp(func_name, "param_get_ty") == 0 or \
string.strcmp(func_name, "value_get_ty") == 0 or string.strcmp(func_name, "value_get_next") == 0 or \
string.strcmp(func_name, "function_get_ret_ty") == 0 or string.strcmp(func_name, "function_get_params") == 0 or \
string.strcmp(func_name, "function_get_param_head") == 0 or string.strcmp(func_name, "param_get_next") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# 默认 i32(整数):与本模块函数定义的默认返回类型一致(HandlesFunctions.py line 324)
# 已知返回指针的函数在上方显式列出(返回 i8*),已知返回 i64 的函数也显式列出
# 避免使用 i64 导致 stub 声明(declare i64)与实际定义(define i32)类型不匹配
# x86-64 ABI 中 i32 返回值在 rax 低 32 位,与 define i32 完全一致
# WARNING: 未登记的外部函数返回类型默认推断为 i32,若实际返回指针/i64 会被截断
# --nowarn-register 可抑制此 WARN(自举编译中大量出现,刷屏影响日志收敛)
if VLogger.nowarn_register() == 0:
VLogger.warning(
f"外部函数返回类型未登记,默认推断为 i32(可能截断指针/i64): {func_name}",
"HandlesExprCall")
return llvmlite.Int32(pool)
def _get_external_func_param_ty(self, pool: memhub.MemBuddy | t.CPtr,
func_name: str,
param_idx: int) -> llvmlite.LLVMType | t.CPtr:
"""获取外部函数第 param_idx 个参数的期望类型,None 表示用字面量原类型
对 Win32 API 函数的所有非指针参数返回 i64,匹配 declare 中
ULONG/SIZE_T 被统一翻译为 i64 的情况。
"""
if pool is None or func_name is None:
return None
i64_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
# Win32 API 函数列表:所有非指针参数在 declare 中都是 i64
# (因为 ULONG/DWORD/SIZE_T 在 LLVM stubs 翻译时都被映射为 i64)
win32_apis: list[str] | t.CPtr = [
"VirtualAlloc", "VirtualFree", "VirtualProtect", "VirtualQuery",
"VirtualLock", "VirtualUnlock",
"HeapCreate", "HeapDestroy", "HeapAlloc", "HeapReAlloc",
"HeapFree", "HeapSize", "HeapValidate", "HeapCompact",
"GlobalAlloc", "GlobalFree", "GlobalLock", "GlobalUnlock", "GlobalSize",
"LocalAlloc", "LocalFree",
"GetLastError", "SetLastError",
"Sleep", "SleepEx",
"GetTickCount", "GetProcessHeap"
]
# list 迭代不支持,用 range + get 替代
api_count: t.CSizeT = len(win32_apis)
for ai in range(api_count):
api_name: str = win32_apis.get(ai)
if string.strcmp(api_name, func_name) == 0:
return i64_ty
# 内存分配函数:size 参数在声明中为 i64(size_t),裸名回退时
# 不提升则 call(i32 size) 与 define(i64 size) 不匹配(desktop start
# 第 4 次 mm.malloc(w*4) 返回值被破坏收到 0x0 根因;HelloWorld
# vpsdk 的 malloc 同理)。calloc/realloc 的 size 参数在 idx 1。
if string.strcmp(func_name, "malloc") == 0 \
or string.strcmp(func_name, "alloc") == 0 \
or string.strcmp(func_name, "alloc_buf") == 0:
if param_idx == 0:
return i64_ty
if string.strcmp(func_name, "calloc") == 0:
if param_idx == 0 or param_idx == 1:
return i64_ty
if string.strcmp(func_name, "realloc") == 0:
if param_idx == 1:
return i64_ty
return None
def _is_sha1_mangled_call(self, call_name: str) -> int:
"""判断 call_name 是否为跨模块 Viper 函数("{16位hex}.func" 形式)
C 库函数(strlen 等裸名)和 Win32 API(VirtualAlloc 等裸名)返回 0,
不参与精确返回类型解析(走 _infer_external_func_ret_ty 白名单)。
"""
if call_name is None:
return 0
first_dot: str = string.strchr(call_name, '.')
if first_dot is None:
return 0
pre_len: t.CSizeT = t.CSizeT(t.CUInt64T(first_dot) - t.CUInt64T(call_name))
if pre_len != 16:
return 0
for hx in range(16):
ch: int = call_name[hx]
if ch < 48 or ch > 57:
if ch < 97 or ch > 102:
if ch < 65 or ch > 70:
return 0
return 1
def _emit_llvm_memcpy_intrinsic(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
dst_val: llvmlite.Value | t.CPtr,
src_val: llvmlite.Value | t.CPtr,
num_val: llvmlite.Value | t.CPtr) -> llvmlite.Value | t.CPtr:
"""将 memcpy 调用映射到 @llvm.memcpy 内联函数
直接接收三个 Value 参数,避免通过 Next 链表遍历(共享 alloca 对象的
Next 字段可能被其他操作污染,导致链表断裂)。
"""
if pool is None or builder is None or mod is None:
return None
if dst_val is None or src_val is None or num_val is None:
return None
# 在函数顶部声明 void_ty,避免后续遮蔽
void_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Void(pool)
# 声明 @llvm.memcpy(如未声明)
memcpy_func: llvmlite.Function | t.CPtr = self.find_func_in_module(mod, "llvm.memcpy")
if memcpy_func is None:
memcpy_func = llvmlite.create_declare(pool, mod, "llvm.memcpy", void_ty)
if memcpy_func is not None:
i8_ptr_ty_d: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
i64_ty_d: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
i1_ty_d: llvmlite.LLVMType | t.CPtr = llvmlite.Int1(pool)
llvmlite.add_param(pool, memcpy_func, i8_ptr_ty_d, "dst")
llvmlite.add_param(pool, memcpy_func, i8_ptr_ty_d, "src")
llvmlite.add_param(pool, memcpy_func, i64_ty_d, "len")
llvmlite.add_param(pool, memcpy_func, i1_ty_d, "isvolatile")
# 类型转换: dst, src → i8*; num → i64
# 注意: 使用 value_get_ty 访问器获取 Ty,避免本地 Value 类型字段索引偏移
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
i64_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
# dst → i8*
dst_coerced: llvmlite.Value | t.CPtr = dst_val
dst_ty: llvmlite.LLVMType | t.CPtr = llvmlite.value_get_ty(dst_val)
if self.Gen.ExprGenBase.is_ptr_type(dst_ty) != 0:
dst_coerced = llvmlite.build_bitcast(builder, dst_val, i8_ptr_ty)
else:
dst_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(dst_ty)
if dst_bits != 0:
if dst_bits < 64:
dst_coerced = llvmlite.build_zext(builder, dst_val, i64_ty)
dst_coerced = llvmlite.build_inttoptr(builder, dst_coerced, i8_ptr_ty)
# src → i8*
src_coerced: llvmlite.Value | t.CPtr = src_val
src_ty: llvmlite.LLVMType | t.CPtr = llvmlite.value_get_ty(src_val)
if self.Gen.ExprGenBase.is_ptr_type(src_ty) != 0:
src_coerced = llvmlite.build_bitcast(builder, src_val, i8_ptr_ty)
else:
src_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(src_ty)
if src_bits != 0:
if src_bits < 64:
src_coerced = llvmlite.build_zext(builder, src_val, i64_ty)
src_coerced = llvmlite.build_inttoptr(builder, src_coerced, i8_ptr_ty)
# num → i64
num_coerced: llvmlite.Value | t.CPtr = self.Gen.ExprGenBase.coerce_to_type(builder, num_val, i64_ty)
if dst_coerced is None or src_coerced is None or num_coerced is None:
return None
# 添加 i1 false 参数(isvolatile)— llvm.memcpy 需要 4 个参数
false_val: llvmlite.Value | t.CPtr = llvmlite.ConstInt(pool, llvmlite.Int1(pool), 0, "false")
# 使用 includes 中的访问器函数构建参数链表,避免本地 Value 类型字段索引偏移
# (f26cd4c78b4d554c.Value 缺少 GSListNode 的 Next 字段,直接访问 .Next 会修改 .Ty)
llvmlite.value_set_next(dst_coerced, src_coerced)
llvmlite.value_set_next(src_coerced, num_coerced)
if false_val is not None:
llvmlite.value_set_next(num_coerced, false_val)
llvmlite.value_set_next(false_val, None)
else:
llvmlite.value_set_next(num_coerced, None)
# 使用 build_call 发射 call 指令(内部用 includes Value 类型访问字段,索引正确)
# _ll_name_needs_quote 已对 llvm. 前缀豁免引号
result: llvmlite.Value | t.CPtr = llvmlite.build_call(builder, "llvm.memcpy", dst_coerced, 4, void_ty, 0)
# 返回 dst(匹配 C memcpy 语义: 返回 dest 指针)
return dst_coerced
# ============================================================
# _translate_call_with_kwargs — 带关键字参数的函数调用
#
# 支持两种语法:
# add(b=4, a=3) — 全关键字乱序
# greet("Hello", name="World") — 位置 + 关键字混合
#
# 策略: 遍历 found_func.Params 链表,对每个参数:
# 1. 若位置参数仍有剩余(param_idx < can),取位置参数
# 2. 否则按参数名匹配关键字参数
# 3. 按参数顺序构建调用参数链表
# ============================================================
# ============================================================
def _translate_call_with_kwargs(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
func_name: str,
found_func: llvmlite.Function | t.CPtr,
cargs: list[ast.AST | t.CPtr] | t.CPtr,
can: t.CSizeT,
keywords: list[ast.AST | t.CPtr] | t.CPtr,
kw_count: t.CSizeT,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""带关键字参数的函数调用,按参数顺序构建调用链表
使用 llvmlite 访问器函数绕过 stub 类型限制:
found_func.Params/RetTy/Name 和 cur_param.Name/Ty/Next 在 stub
类型中字段不足,直接访问会被 TransPyC 静默跳过。
"""
head: llvmlite.Value | t.CPtr = None
tail: llvmlite.Value | t.CPtr = None
actual_count: t.CSizeT = 0
# memcpy 专用: 收集前三个 Value 的独立引用(避免共享 alloca 对象的 Next 被污染)
mc_dst1: llvmlite.Value | t.CPtr = None
mc_src1: llvmlite.Value | t.CPtr = None
mc_num1: llvmlite.Value | t.CPtr = None
# 遍历函数参数链表(使用访问器获取 Head)
param_idx: t.CSizeT = 0
cur_param: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(found_func)
while cur_param is not None:
arg_val: llvmlite.Value | t.CPtr = None
param_ty: llvmlite.LLVMType | t.CPtr = llvmlite.param_get_ty(cur_param)
if param_idx < can:
# 优先使用位置参数
arg: ast.AST | t.CPtr = cargs.get(param_idx)
if arg is not None:
# 传形参类型(char 字面量 → str 形参需物化字符串,见 translate_constant)
arg_val = self.Gen.ExprGen.translate_value(
builder, pool, mod, arg, None, 0, trans, param_ty)
else:
# 查找匹配的关键字参数
param_name: str = llvmlite.param_get_name(cur_param)
if param_name is not None:
for ki in range(kw_count):
kw: ast.Keyword | t.CPtr = (ast.Keyword | t.CPtr)(keywords.get(ki))
if kw is None or kw.arg is None or kw.value is None:
continue
if self.Gen.CallGenCall._kwarg_name_matches(kw.arg, param_name) == 1:
arg_val = self.Gen.ExprGen.translate_value(
builder, pool, mod, kw.value, None, 0, trans, param_ty)
break
if arg_val is not None:
# 类型转换(按参数类型,使用访问器)
if param_ty is not None:
arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, param_ty)
arg_val.Next = None
if head is None:
head = arg_val
else:
tail.Next = arg_val
tail = arg_val
# memcpy 专用: 保存前三个参数的独立引用
if actual_count == 0:
mc_dst1 = arg_val
elif actual_count == 1:
mc_src1 = arg_val
elif actual_count == 2:
mc_num1 = arg_val
actual_count += 1
param_idx += 1
cur_param = llvmlite.param_get_next(cur_param)
# 使用模块中的实际函数名(可能带 SHA1 前缀,使用访问器)
# declare 和 define 统一用 found_func.Name:
# - 普通函数的 declare/define 都带 SHA1 前缀(forward_declare_functions 和 stub 注入都用 mangled_name)
# - t.CExtern/t.State/t.CExport 函数的 declare/define 都用裸名
# 旧逻辑(IsDeclared==1 时用 func_name 裸名)是错误的:stub 注入的 declare Name 已带 SHA1 前缀,
# 用裸名会导致 call @_PathToModuleName 而 define 是 @"sha1._PathToModuleName",链接报 undefined reference
call_name: str = func_name
found_name: str = llvmlite.function_get_name(found_func)
if found_name is not None:
call_name = found_name
# 返回类型(使用访问器)
call_ret_ty: llvmlite.LLVMType | t.CPtr = llvmlite.function_get_ret_ty(found_func)
if call_ret_ty is None:
call_ret_ty = self._infer_external_func_ret_ty(pool, func_name)
# memcpy → @llvm.memcpy 内联函数(避免 monomorphization 跨模块 @memcpy 声明缺失)
if string.strcmp(func_name, "memcpy") == 0 and actual_count >= 3:
return self._emit_llvm_memcpy_intrinsic(pool, builder, mod, mc_dst1, mc_src1, mc_num1)
return llvmlite.build_call(builder, call_name, head, actual_count, call_ret_ty, 0)
File diff suppressed because it is too large. Load diff
-418
View File
@@ -1,418 +0,0 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import sys
import viperlib
import w32.fileio as fileio
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesBase as HandlesBase
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.Stub.StubMerger as StubMerger
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.CallGenBase as CallGenBase
# ============================================================
# CallGenProp - CallGenMixin 拆分(阶段 B):属性访问(getter/setter/deleter / 静态类调用 / list 内置方法)
#
# 继承 CallGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesExprCall 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class CallGenProp(CallGenBase.CallGenBase):
"""属性访问(getter/setter/deleter / 静态类调用 / list 内置方法)"""
# ============================================================
# _ensure_method_declare - 为跨模块方法调用创建 declare 声明
#
# 当 found_func 为 None(stub 未注入或跨模块调用)时,
# 在当前模块中创建函数声明,避免 llc 报 undefined value。
# ============================================================
# ============================================================
# 属性方法(@property/@x.setter/@x.deleter)与静态/类方法调用辅助
#
# 元数据表由 HandlesClassDef 在类翻译/预注册阶段填充,
# 这里负责把 obj.prop / obj.prop=v / del obj.prop / Class.method()
# 翻译为对应的 {sha1}.ClassName.prop 系列函数调用。
# ============================================================
# ============================================================
# _translate_property_call — 翻译属性方法调用
#
# 与 _translate_method_call 的调用名/返回类型解析逻辑一致:
# 1. 优先用模块中已有的函数(精确/后缀匹配,来自 stub 注入)
# 2. 否则用类所属模块的 SHA1 构造 "{sha1}.ClassName.method" 调用名
# 3. 返回类型:函数实类型 → text.ll 查找 → 按方法名推断(默认 i32)
#
# call_head/call_count: 已构建好的参数链表(如 getter 只有 obj 一个参数)
# ============================================================
def _translate_property_call(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
class_name: str,
method_name: str,
call_head: llvmlite.Value | t.CPtr,
call_count: int,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""翻译属性/静态方法调用,返回 call 结果 Value"""
if pool is None or builder is None or class_name is None or method_name is None:
return None
# 构造查找名: ClassName.method_name
lookup_name: str = pool.alloc(128)
if lookup_name is None:
return None
viperlib.snprintf(lookup_name, 128, "%s.%s", class_name, method_name)
# 查找函数(精确/后缀匹配)
found_func: llvmlite.Function | t.CPtr = self.find_func_in_module(mod, lookup_name)
# 确定调用名
call_name: str = lookup_name
if found_func is not None:
found_name: str = llvmlite.function_get_name(found_func)
if found_name is not None:
call_name = found_name
else:
# 未找到:用类所属模块的 SHA1 构造调用名
cls_sha1_pc: str = self.Gen.StructGen.get_struct_sha1(class_name)
if cls_sha1_pc is not None:
mlen_pc: t.CSizeT = string.strlen(cls_sha1_pc) + string.strlen(lookup_name) + 2
mangled_pc: str = pool.alloc(mlen_pc)
if mangled_pc is not None:
viperlib.snprintf(mangled_pc, mlen_pc, "%s.%s", cls_sha1_pc, lookup_name)
call_name = mangled_pc
# 确定返回类型
i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
call_ret_ty: llvmlite.LLVMType | t.CPtr = i32_ty
if found_func is not None:
frt: llvmlite.LLVMType | t.CPtr = llvmlite.function_get_ret_ty(found_func)
if frt is not None:
call_ret_ty = frt
else:
sp_ty1: llvmlite.LLVMType | t.CPtr = self._special_method_ret_ty(pool, class_name, method_name)
if sp_ty1 is not None:
call_ret_ty = sp_ty1
else:
cls_sha1_rt: str = self.Gen.StructGen.get_struct_sha1(class_name)
looked_up_ty: llvmlite.LLVMType | t.CPtr = self._LookupMethodRetTyFromTextLL(
pool, cls_sha1_rt, class_name, method_name)
if looked_up_ty is not None:
call_ret_ty = looked_up_ty
else:
# 精确查找:vpi stub 返回注解缓存(方法定义处,不硬编码白名单)
vpi_ret_ty: llvmlite.LLVMType | t.CPtr = self._lookup_method_ret_ty_via_vpi(
pool, cls_sha1_rt, class_name, method_name)
if vpi_ret_ty is not None:
call_ret_ty = vpi_ret_ty
else:
# Phase A 依赖未就绪(cls_sha1_rt None / 定义模块 text.ll 未生成):
# 标记 pending 下遍重译(对齐 _translate_method_call L4783-4790),
# 绝不 fail-fast 中断多遍收敛;真实错误由收敛检查兜底。
if cls_sha1_rt is None \
or self._dep_text_ll_exists(pool, cls_sha1_rt) == 0:
if trans is not None:
trans._deps_pending = 1
# [DEPDBG] 临时诊断:属性方法依赖未就绪即 pending 的根因(用后移除)
dbg_ms8: str = trans.ModuleSha1 if (trans is not None and trans.ModuleSha1 is not None) else "?"
stdio.printf("[DEPDBG] pending(prop_ret): mod=%s cls=%s method=%s sha1=%s\n",
dbg_ms8, class_name, method_name,
cls_sha1_rt if cls_sha1_rt is not None else "none")
# 依赖未就绪:保持默认 i32(build_call(Void) 返回 None
# 会破坏表达式翻译;pending 文件本遍不写盘,下遍重译)
call_ret_ty = llvmlite.Int32(pool)
else:
call_ret_ty = self._fail_method_ret_ty(method_name)
# 参数类型对齐:按目标函数声明的参数类型转换实参(与 _translate_method_call
# 一致)。例如 from_long(10) 的 i32 字面量 → i64 形参(v: t.CLong),
# 不转换则 call(i32) 与 declare(i64) 不匹配,llc 报参数类型错误。
# 目标函数优先用已找到的 found_func,否则按 mangled 调用名查模块 declare。
tgt_func_pc: llvmlite.Function | t.CPtr = found_func
if tgt_func_pc is None:
tgt_func_pc = self.find_func_in_module(mod, call_name)
if tgt_func_pc is not None:
prev_pc: llvmlite.Value | t.CPtr = None
cur_arg_pc: llvmlite.Value | t.CPtr = call_head
cur_param_pc: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(tgt_func_pc)
while cur_arg_pc is not None and cur_param_pc is not None:
param_ty_pc: llvmlite.LLVMType | t.CPtr = llvmlite.param_get_ty(cur_param_pc)
if param_ty_pc is not None and cur_arg_pc.Ty is not None:
coerced_pc: llvmlite.Value | t.CPtr = self.Gen.ExprGenBase.coerce_to_type(
builder, cur_arg_pc, param_ty_pc)
if coerced_pc is not None and coerced_pc != cur_arg_pc:
# 保持链表完整性:新 Value 继承原节点的 Next,前驱指向新节点
llvmlite.value_set_next(coerced_pc, cur_arg_pc.Next)
if prev_pc is None:
call_head = coerced_pc
else:
llvmlite.value_set_next(prev_pc, coerced_pc)
cur_arg_pc = coerced_pc
prev_pc = cur_arg_pc
cur_arg_pc = cur_arg_pc.Next
cur_param_pc = llvmlite.param_get_next(cur_param_pc)
# 确保 declare 存在,然后调用
self._ensure_method_declare(pool, mod, call_name, call_ret_ty, call_head, call_count)
return llvmlite.build_call(builder, call_name, call_head, call_count, call_ret_ty, 0)
# ============================================================
# _resolve_obj_class_name — 解析对象表达式的类名(短名)
#
# 优先从变量注解(AnnotClassName)解析(如 "memhub.MemBuddy" → "MemBuddy"),
# 失败时从 LLVM 类型解析(Ptr(Struct) → 类名)。
# 供属性访问/赋值/删除识别 property 时使用。
# ============================================================
# ============================================================
def _resolve_obj_class_name(self, pool: memhub.MemBuddy | t.CPtr,
trans: HT.Translator | t.CPtr,
obj_node: ast.AST | t.CPtr) -> str:
"""解析对象表达式的类名(短名),失败返回 None"""
if pool is None or trans is None or obj_node is None:
return None
if obj_node.kind() == ast.ASTKind.Name:
obj_nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(obj_node)
if obj_nm.id is not None:
var_entry: GenTypes.VarEntry | t.CPtr = self.Gen.VarGen.lookup_var_entry(
trans.SymTab, obj_nm.id)
if var_entry is not None and var_entry.AnnotClassName is not None:
annot_val: str = var_entry.AnnotClassName
dot_ptr: str = string.strrchr(annot_val, '.')
if dot_ptr is not None:
return dot_ptr + 1
return annot_val
return None
# ============================================================
# _try_property_getter_attr — 尝试将 obj.attr 翻译为 property getter 调用
#
# 检查 obj 的类是否有 @property/@x.getter 的 attr 方法;
# 若有则生成 call Class.attr(obj) 并返回调用结果 Value;否则返回 None。
# ============================================================
# ============================================================
def _try_property_getter_attr(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
at_node: ast.Attribute | t.CPtr,
obj_ptr: llvmlite.Value | t.CPtr,
trans: HT.Translator | t.CPtr,
fallback_class_name: str = None) -> llvmlite.Value | t.CPtr:
"""尝试 property getter 属性访问,返回调用结果 Value;非 getter 返回 None
fallback_class_name: 对象表达式非 Name 时从 LLVM 类型解析出的类名兜底。
"""
if at_node is None or at_node.attr is None or obj_ptr is None or trans is None:
return None
class_name: str = self._resolve_obj_class_name(pool, trans, at_node.value)
if class_name is None:
class_name = fallback_class_name
if class_name is None:
return None
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, at_node.attr)
if meta & GenTypes.META_PROP_GETTER == 0:
return None
# 单参数调用: obj → getter
llvmlite.value_set_next(obj_ptr, None)
return self._translate_property_call(
pool, builder, mod, class_name, at_node.attr, obj_ptr, 1, trans)
# ============================================================
# _try_property_setter — 尝试将 obj.attr = v 翻译为 property setter 调用
#
# 检查 obj 的类是否有 @x.setter 的 attr 方法;
# 若有则生成 call Class.attr$set(obj, v),返回 1;否则返回 0。
# ============================================================
# ============================================================
def _try_property_setter(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
target_attr: ast.Attribute | t.CPtr,
rhs_val: llvmlite.Value | t.CPtr,
trans: HT.Translator | t.CPtr) -> int:
"""尝试 property setter 赋值,返回 1=已处理(生成调用)"""
if pool is None or builder is None or mod is None or trans is None:
return 0
if target_attr is None or target_attr.attr is None or rhs_val is None:
return 0
class_name: str = self._resolve_obj_class_name(pool, trans, target_attr.value)
if class_name is None:
return 0
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, target_attr.attr)
if meta & GenTypes.META_PROP_SETTER == 0:
return 0
# 翻译对象值(self 参数)
obj_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, target_attr.value, None, 0, trans)
if obj_val is None:
return 0
# 构建参数链表: obj → rhs
llvmlite.value_set_next(obj_val, None)
llvmlite.value_set_next(rhs_val, None)
llvmlite.value_set_next(obj_val, rhs_val)
# setter 函数名带 $set 后缀
setter_name_buf: str = pool.alloc(128)
if setter_name_buf is None:
return 0
viperlib.snprintf(setter_name_buf, 128, "%s$set", target_attr.attr)
self._translate_property_call(pool, builder, mod, class_name,
setter_name_buf, obj_val, 2, trans)
return 1
# ============================================================
# _try_property_deleter — 尝试将 del obj.attr 翻译为 property deleter 调用
#
# 检查 obj 的类是否有 @x.deleter 的 attr 方法;
# 若有则生成 call Class.attr$del(obj),返回 1;否则返回 0。
# ============================================================
# ============================================================
def _try_property_deleter(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
target_attr: ast.Attribute | t.CPtr,
trans: HT.Translator | t.CPtr) -> int:
"""尝试 property deleter 删除,返回 1=已处理(生成调用)"""
if pool is None or builder is None or mod is None or trans is None:
return 0
if target_attr is None or target_attr.attr is None:
return 0
class_name: str = self._resolve_obj_class_name(pool, trans, target_attr.value)
if class_name is None:
return 0
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, target_attr.attr)
if meta & GenTypes.META_PROP_DELETER == 0:
return 0
# 翻译对象值(self 参数)
obj_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, target_attr.value, None, 0, trans)
if obj_val is None:
return 0
llvmlite.value_set_next(obj_val, None)
# deleter 函数名带 $del 后缀
del_name_buf: str = pool.alloc(128)
if del_name_buf is None:
return 0
viperlib.snprintf(del_name_buf, 128, "%s$del", target_attr.attr)
self._translate_property_call(pool, builder, mod, class_name,
del_name_buf, obj_val, 1, trans)
return 1
# ============================================================
# _translate_static_class_call — 翻译 ClassName.method(args) 静态/类方法调用
#
# @staticmethod: 直接 call Class.method(args...),无 self
# @classmethod: 在参数前插入栈上分配的类实例指针(与 TPC 一致)
# ============================================================
# ============================================================
def _translate_static_class_call(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
class_name: str,
method_name: str,
cl: ast.Call | t.CPtr,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""翻译类名静态/类方法调用,返回 call 结果 Value;非静态/类方法返回 None"""
if pool is None or builder is None or mod is None or trans is None:
return None
if class_name is None or method_name is None or cl is None:
return None
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, method_name)
is_static: int = 0
is_class: int = 0
if meta & GenTypes.META_STATIC != 0:
is_static = 1
if meta & GenTypes.META_CLASS != 0:
is_class = 1
if is_static == 0 and is_class == 0:
return None
# 构建参数链表
head: llvmlite.Value | t.CPtr = None
total_count: int = 0
# @classmethod: 插入栈上分配的类实例指针作为 cls 参数
if is_class != 0:
struct_ty_cc: llvmlite.LLVMType | t.CPtr = self.Gen.StructGen.get_struct_type(class_name)
if struct_ty_cc is None:
return None
cls_ptr: llvmlite.Value | t.CPtr = llvmlite.build_alloca(builder, struct_ty_cc)
if cls_ptr is None:
return None
llvmlite.value_set_next(cls_ptr, None)
head = cls_ptr
total_count = 1
# 翻译调用参数
cargs: list[ast.AST | t.CPtr] | t.CPtr = cl.args
can: t.CSizeT = 0
if cargs is not None:
can = len(cargs)
tail: llvmlite.Value | t.CPtr = head
for ai in range(can):
arg_node: ast.AST | t.CPtr = cargs.get(ai)
if arg_node is None:
continue
arg_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, arg_node, None, 0, trans)
if arg_val is None:
continue
llvmlite.value_set_next(arg_val, None)
if head is None:
head = arg_val
tail = arg_val
else:
llvmlite.value_set_next(tail, arg_val)
tail = arg_val
total_count += 1
# 零参 @staticmethod(如 PyListObj.new()):head 保持 None 是合法的
# 无参调用,照常走 _translate_property_call(SHA1 前缀名 + 精确返回类型)。
# 若此处 return None,调用方回退通用路径把方法名当裸函数解析,生成
# call i32 @new() → 链接 undefined reference(CPythonTest 根因)。
# @classmethod 恒有 cls_ptr(head 非 None),不受影响。
return self._translate_property_call(pool, builder, mod, class_name,
method_name, head, total_count, trans)
# ============================================================
# _is_list_builtin_method - 判断是否是 list 泛型内置容器方法
#
# list[T] 是内置泛型容器(_list.vp),其方法(append/get/set/pop/clear/
# __iter__/__len__/__getitem__/__setitem__)由 translate_call 的 list
# 内置分支内联 GEP 处理,不注册为方法函数。若 obj.method() 检测把
# list 特化 struct(如 list[AST | CPtr])误当普通结构体走
# _translate_method_call,会查不到 append 返回类型 → fail-fast
# (NewAstTest body.append 根因)。此判断用于跳过 method_call 路径。
#
# 返回: 1=list 内置方法(应落到 list 内置分支),0=普通方法
# ============================================================
# ============================================================
def _is_list_builtin_method(self, class_name: str,
method_name: str) -> int:
"""判断 (class_name, method_name) 是否是 list 泛型内置方法"""
if class_name is None or method_name is None:
return 0
# 类名必须以 "list" 开头(list / list[AST | CPtr] 特化名)
if string.strncmp(class_name, "list", 4) != 0:
return 0
# 方法名必须是 list 内置方法集合
if string.strcmp(method_name, "append") == 0: return 1
if string.strcmp(method_name, "get") == 0: return 1
if string.strcmp(method_name, "set") == 0: return 1
if string.strcmp(method_name, "pop") == 0: return 1
if string.strcmp(method_name, "clear") == 0: return 1
if string.strcmp(method_name, "__iter__") == 0: return 1
if string.strcmp(method_name, "__len__") == 0: return 1
if string.strcmp(method_name, "__getitem__") == 0: return 1
if string.strcmp(method_name, "__setitem__") == 0: return 1
return 0
-403
View File
@@ -1,403 +0,0 @@
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import sys
import viperlib
import w32.fileio as fileio
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesBase as HandlesBase
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.Stub.StubMerger as StubMerger
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.CallGenBase as CallGenBase
# ============================================================
# CallGenVa - CallGenMixin 拆分(阶段 B):va_arg 变长参数(start/arg/end / 变长调用提升)
#
# 继承 CallGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesExprCall 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class CallGenVa(CallGenBase.CallGenBase):
"""va_arg 变长参数(start/arg/end / 变长调用提升)"""
# ============================================================
# _func_has_eh_params - 被调函数签名是否含 try-except EH 输出参数
#
# EH 参数(%__eh_msg_out__/__eh_code_out__)由 translate_function_def/
# _translate_method 在含 raise 的函数签名末尾注入(main 除外)。
# ============================================================
def _va_arg_get_alloca_ptr(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
arg_node: ast.AST | t.CPtr,
funcs_ptr: t.CPtr,
func_count: int,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""获取 va_list 变量的 alloca 指针(不 load)
va_arg/va_start/va_end 需要的是 va_list 对象的地址(alloca 指针),
而非 va_list 的值。若 arg_node 是 Name,查符号表获取 alloca;
否则用 translate_value 翻译(调用者负责确保传入的是指针)。
"""
if arg_node is None:
return None
# Name 节点:直接查符号表获取 alloca 指针(不 load)
if arg_node.kind() == ast.ASTKind.Name:
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(arg_node)
if nm.id is not None:
alloca: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is not None:
return alloca
# 回退:translate_value(可能返回 load 后的值,仅适用于已是指针的情况)
return self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, funcs_ptr, func_count, trans)
def _resolve_va_arg_type(self, pool: memhub.MemBuddy | t.CPtr,
type_node: ast.AST | t.CPtr,
trans: HT.Translator | t.CPtr) -> llvmlite.LLVMType | t.CPtr:
"""解析 va_arg/arg 的类型参数为 LLVMType
支持的类型表达式:
t.CInt / t.CInt32T → i32
t.CInt64T → i64
t.CDouble → double
t.CPtr / str → i8*
str / bytes → i8*
无类型参数时默认 i32(vsnprintf 中 va_arg(ap) 常用于读取 i32)
"""
if type_node is None:
return llvmlite.Int32(pool)
# 使用 HandlesType.resolve_annotation_type 解析类型表达式
resolved: llvmlite.LLVMType | t.CPtr = self.Gen.TypeGenResolve.resolve_annotation_type(
pool, type_node, trans._imported_modules, trans._from_imports, trans, 0)
if resolved is not None:
return resolved
# 类型节点存在但解析失败:fail-fast,绝不回退 i32(会按错宽度读变参槽位)
self.Gen.TypeGenErr.fatal_error(type_node, "va_arg 类型解析失败", "TypeError")
return llvmlite.Int32(pool)
def _translate_va_arg_sugar(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
cl: ast.Call | t.CPtr,
cargs: list[ast.AST | t.CPtr] | t.CPtr,
can: t.CSizeT,
funcs_ptr: t.CPtr,
func_count: int,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""va_arg(ap, type) / va_arg(ap) → %N = va_arg <ap_ty> <ap>, <type>
生成 LLVM va_arg 指令,从 va_list 读取下一个变长参数。
"""
if can == 0 or cargs is None:
return llvmlite.const_int32(pool, 0)
# 第一个参数:va_list(求值为 i8* 地址值,对齐 TPC _HandleVaArgLlvm)
# 当前函数 *args:translate_value 经 vararg 名映射直接返回 va_list 缓冲区地址;
# 转发场景(sprintf → vsnprintf 的 ap 参数):translate_value load 参数得到地址值。
ap_node: ast.AST | t.CPtr = cargs.get(0)
ap_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, ap_node, None, 0, trans)
if ap_val is None:
return llvmlite.const_int32(pool, 0)
ap_ptr: llvmlite.Value | t.CPtr = ap_val
# 第二个参数:目标类型(可选,默认 i32)
arg_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
if can >= 2:
type_node: ast.AST | t.CPtr = cargs.get(1)
arg_ty = self._resolve_va_arg_type(pool, type_node, trans)
# Win64 变参槽位为 8 字节:<64 位整数按 i64 读取(对齐 TPC emit_va_arg L72-73)
read_ty: llvmlite.LLVMType | t.CPtr = arg_ty
abits_va: int = self.Gen.ExprGenBase.get_llvm_type_bits(arg_ty)
if abits_va != 0 and abits_va < 64:
read_ty = llvmlite.Int64(pool)
# 生成 va_arg 指令
return llvmlite.build_va_arg(builder, ap_ptr, read_ty)
def _translate_arg_sugar(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
cl: ast.Call | t.CPtr,
cargs: list[ast.AST | t.CPtr] | t.CPtr,
can: t.CSizeT,
funcs_ptr: t.CPtr,
func_count: int,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""arg(type) → %N = va_arg <va_list_ty> <trans._va_list_ptr>, <type>
使用当前函数的隐式 va_list(trans._va_list_ptr),
简化 *args 函数体内的 va_arg 调用。
"""
if trans._va_list_ptr is None:
return llvmlite.const_int32(pool, 0)
# 第一个参数:目标类型(可选,默认 i32)
arg_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
if can >= 1 and cargs is not None:
type_node: ast.AST | t.CPtr = cargs.get(0)
arg_ty = self._resolve_va_arg_type(pool, type_node, trans)
# Win64 变参槽位为 8 字节:<64 位整数按 i64 读取(与 _translate_va_arg_sugar 一致)
read_ty: llvmlite.LLVMType | t.CPtr = arg_ty
abits_arg: int = self.Gen.ExprGenBase.get_llvm_type_bits(arg_ty)
if abits_arg != 0 and abits_arg < 64:
read_ty = llvmlite.Int64(pool)
# 生成 va_arg 指令(使用 trans._va_list_ptr 作为 va_list 地址)
return llvmlite.build_va_arg(builder, trans._va_list_ptr, read_ty)
def _translate_va_start_sugar(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
cargs: list[ast.AST | t.CPtr] | t.CPtr,
can: t.CSizeT,
funcs_ptr: t.CPtr,
func_count: int,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""va_start(ap, last_arg) → call void @llvm.va_start(i8* %ap)
初始化 va_list。第二个参数 last_arg 在 LLVM intrinsic 中不使用(C 宏兼容)。
"""
if can == 0 or cargs is None:
return llvmlite.const_int32(pool, 0)
# 第一个参数:va_list 指针(获取 alloca,不 load)
ap_node: ast.AST | t.CPtr = cargs.get(0)
ap_ptr: llvmlite.Value | t.CPtr = self._va_arg_get_alloca_ptr(
pool, builder, mod, ap_node, funcs_ptr, func_count, trans)
if ap_ptr is None:
return llvmlite.const_int32(pool, 0)
# bitcast 到 i8*(@llvm.va_start 接受 i8*)
i8_ty_vs: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_ty_vs: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty_vs)
ap_i8: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(builder, ap_ptr, i8_ptr_ty_vs)
if ap_i8 is None:
return llvmlite.const_int32(pool, 0)
void_ty_vs: llvmlite.LLVMType | t.CPtr = llvmlite.Void(pool)
llvmlite.build_call(builder, "llvm.va_start", ap_i8, 1, void_ty_vs, 0)
return llvmlite.const_int32(pool, 0)
def _translate_va_end_sugar(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
cargs: list[ast.AST | t.CPtr] | t.CPtr,
can: t.CSizeT,
funcs_ptr: t.CPtr,
func_count: int,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""va_end(ap) → call void @llvm.va_end(i8* %ap)
清理 va_list。在大多数平台上是 no-op,但调用它是良好的 C ABI 习惯。
"""
if can == 0 or cargs is None:
return llvmlite.const_int32(pool, 0)
# 第一个参数:va_list 指针(获取 alloca,不 load)
ap_node: ast.AST | t.CPtr = cargs.get(0)
ap_ptr: llvmlite.Value | t.CPtr = self._va_arg_get_alloca_ptr(
pool, builder, mod, ap_node, funcs_ptr, func_count, trans)
if ap_ptr is None:
return llvmlite.const_int32(pool, 0)
# bitcast 到 i8*(@llvm.va_end 接受 i8*)
i8_ty_ve: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_ty_ve: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty_ve)
ap_i8: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(builder, ap_ptr, i8_ptr_ty_ve)
if ap_i8 is None:
return llvmlite.const_int32(pool, 0)
void_ty_ve: llvmlite.LLVMType | t.CPtr = llvmlite.Void(pool)
llvmlite.build_call(builder, "llvm.va_end", ap_i8, 1, void_ty_ve, 0)
return llvmlite.const_int32(pool, 0)
def _get_func_fixed_param_count(self, func: llvmlite.Function | t.CPtr) -> int:
"""获取函数的固定参数数量(遍历 Params 链表)"""
if func is None:
return 0
count: int = 0
param: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(func)
while param is not None:
count += 1
param = llvmlite.param_get_next(param)
return count
def _get_known_vararg_cfunc_fixed_count(self, name: str) -> int:
"""已知变参 C 库函数固定参数个数(-1=未知)
跨模块 stub 未注入时 find_func_in_module 返回 None(CallGenMixin
注释:stubs 在翻译后才注入,翻译时跨模块函数 found_func 恒 None),
无法从函数对象获取 IsVarArg/固定参数个数。这些 t.CExport 变参函数
(viperlib 定义)签名固定,按表回退(对齐 TPC _C_LIB_FUNCS 机制)。
"""
if name is None:
return -1
if string.strcmp(name, "snprintf") == 0:
return 3
if string.strcmp(name, "sprintf") == 0:
return 2
if string.strcmp(name, "vsprintf") == 0:
return 2
return -1
def _get_known_vararg_cfunc_param_ty(self, pool: memhub.MemBuddy | t.CPtr,
name: str, idx: int) -> llvmlite.LLVMType | t.CPtr:
"""已知变参 C 库函数第 idx 个固定参数类型(变参位置/未知返回 None)
固定参数类型是 C ABI 硬约束(snprintf 的 size 为 i64),stub 未注入
时按表 coerce,避免 call(i32 1024) 与 declare(i64 size) 不匹配。
"""
if pool is None or name is None or idx < 0:
return None
i8_ptr_kv: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
i64_kv: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
if string.strcmp(name, "snprintf") == 0:
if idx == 0:
return i8_ptr_kv
if idx == 1:
return i64_kv
if idx == 2:
return i8_ptr_kv
return None
if string.strcmp(name, "sprintf") == 0 or string.strcmp(name, "vsprintf") == 0:
if idx == 0:
return i8_ptr_kv
if idx == 1:
return i8_ptr_kv
return None
return None
def _check_and_promote_variadic_call(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
call_name: str,
head: llvmlite.Value | t.CPtr,
can: t.CSizeT,
arg_nodes: list[ast.AST | t.CPtr] | t.CPtr = None,
self_offset: int = 0,
trans: HT.Translator | t.CPtr = None) -> int:
"""检查被调用函数是否为变长函数,若是则对变长参数进行 C ABI 提升
返回: is_variadic (1=变长调用, 0=普通调用)
副作用: 修改 head 链表中超过固定参数数量的节点(C ABI 提升)
arg_nodes: 实参 AST 链表(不含 self),用于 i8/i16 提升的无符号性判断
self_offset: Value 链中 self 占用的偏移(方法调用=1,普通函数=0)
trans: 供 _node_type_unsigned 查询变量注解
"""
if mod is None or call_name is None or head is None:
return 0
# 查找函数对象
func: llvmlite.Function | t.CPtr = self.find_func_in_module(mod, call_name)
fixed_count: int = -1
if func is not None and func.IsVarArg == 1:
fixed_count = self._get_func_fixed_param_count(func)
else:
# 两种情况回退已知变参 C 库函数签名表(snprintf 等):
# 1. func 为 None——跨模块 stub 未注入(CallGenMixin 注释:stubs
# 在翻译后才注入,翻译时跨模块函数 found_func 恒 None);
# 2. func 存在但 IsVarArg=0——该 declare 由 _ensure_method_declare
# 按实参类型临时创建(无 ... 标记),不能代表真实签名。
# 否则 i32 变参不提升,Win64 8 字节槽只写 4 字节 →
# va_arg(i64) 高 4 字节读残留栈垃圾(clean 日志 0x7ff700000052 根因)。
fixed_count = self._get_known_vararg_cfunc_fixed_count(call_name)
if fixed_count < 0:
return 0
# 无变长参数(实参数量 <= 固定参数数量),仍需 is_variadic=1
if can <= fixed_count:
return 1
# 遍历到第 fixed_count 个节点(固定参数的最后一个)
cur: llvmlite.Value | t.CPtr = head
prev: llvmlite.Value | t.CPtr = None
idx: int = 0
while cur is not None and idx < fixed_count:
prev = cur
cur = cur.Next
idx += 1
# cur 指向第 fixed_count+1 个节点(第一个变长参数)
# 对变长参数进行 C ABI 提升
i32_ty_promote: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
double_ty_promote: llvmlite.LLVMType | t.CPtr = llvmlite.Double(pool)
v_idx: int = fixed_count
while cur is not None:
next_node: llvmlite.Value | t.CPtr = cur.Next
promoted: llvmlite.Value | t.CPtr = cur
# C ABI integer promotion: i8/i16 → i32
arg_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(cur.Ty)
if arg_bits != 0 and arg_bits < 32:
# 无符号整型(t.CUInt8T/CUInt16T 等)→ zext;有符号/未知 → sext。
# 修复: printf("%02x", buf[i]) 中 buf[i] 是 CUInt8T,sext 得
# 0xFFFFFFF3 打印 fffffff3(哈希摘要输出垃圾),应 zext 得 0xF3。
is_u_va: int = 0
if arg_nodes is not None and trans is not None:
ast_idx_va: int = v_idx - self_offset
if ast_idx_va >= 0 and ast_idx_va < len(arg_nodes):
an_va: ast.AST | t.CPtr = arg_nodes.get(ast_idx_va)
if an_va is not None:
if self.Gen.ExprGenOps._node_type_unsigned(pool, an_va, trans) != 0:
is_u_va = 1
if is_u_va != 0:
promoted = llvmlite.build_zext(builder, cur, i32_ty_promote)
else:
# is_u_va 判断失败(arg_nodes/trans 缺失或变量注解查不到)时,
# i8 按系统编程惯例默认 zext(对齐 should_zext_widen 的
# "i8 → zext(系统编程中 i8 几乎总是无符号字节)"策略),
# 否则 printf("%02x", buf[i]) 中 uint8 字节被 sext 成
# 0xFFFFFFF3,哈希摘要输出 ffffffXX 垃圾。
if self.Gen.ExprGenBase.get_llvm_type_bits(cur.Ty) == 8:
promoted = llvmlite.build_zext(builder, cur, i32_ty_promote)
else:
promoted = self.Gen.ExprGenBase.coerce_to_type(builder, cur, i32_ty_promote, None)
if promoted is not None:
promoted.Next = next_node
if prev is not None:
prev.Next = promoted
# C ABI float promotion: float → double
arg_fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(promoted.Ty)
if arg_fbits != 0 and arg_fbits < 64:
promoted = self.Gen.ExprGenBase.coerce_to_type(builder, promoted, double_ty_promote, None)
if promoted is not None:
promoted.Next = next_node
if prev is not None:
prev.Next = promoted
# Win64 变参槽位 8 字节:i8/i16/i32 → i64
# TPV va_arg 读取端统一按 i64 读(_translate_va_arg_sugar),
# 调用方必须写满 8 字节槽,否则高 4 字节残留旧栈数据,%d 读到
# 0x7ff700000052 类垃圾(clean 日志根因;对齐 CallGenMixin
# L798-811 的 found_func.IsVarArg 提升)。
i64_ty_promote: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
pb_va: int = self.Gen.ExprGenBase.get_llvm_type_bits(promoted.Ty)
if pb_va != 0 and pb_va < 64:
promoted_va2: llvmlite.Value | t.CPtr = None
is_u_va2: int = 0
if arg_nodes is not None and trans is not None:
ast_idx_va2: int = v_idx - self_offset
if ast_idx_va2 >= 0 and ast_idx_va2 < len(arg_nodes):
an_va2: ast.AST | t.CPtr = arg_nodes.get(ast_idx_va2)
if an_va2 is not None:
if self.Gen.ExprGenOps._node_type_unsigned(pool, an_va2, trans) != 0:
is_u_va2 = 1
if is_u_va2 != 0:
promoted_va2 = llvmlite.build_zext(builder, promoted, i64_ty_promote)
else:
promoted_va2 = llvmlite.build_sext(builder, promoted, i64_ty_promote)
if promoted_va2 is not None:
promoted_va2.Next = next_node
if prev is not None:
prev.Next = promoted_va2
promoted = promoted_va2
prev = promoted
cur = next_node
v_idx += 1
return 1
-246
View File
@@ -1,246 +0,0 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import lib.core.Gen.ExprGenAttr as ExprGenAttr
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import sys
import viperlib
import w32.fileio as fileio
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesBase as HandlesBase
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.Stub.StubMerger as StubMerger
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.CallGenBase as CallGenBase
# ============================================================
# CallGenVt - CallGenMixin 拆分(阶段 B):vtable 槽位写入
#
# 继承 CallGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesExprCall 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class CallGenVt(CallGenBase.CallGenBase):
"""vtable 槽位写入"""
# ============================================================
# _write_vtable_slot / _write_vtable_attr — vtable 方法槽位直接写入
#
# serial0_file.open = serial0_open(devfs 驱动注册模式):把模块级函数
# 指针写入对象 vtable 的方法槽位。vtable 方法不是数据字段(FieldEntry),
# get_attribute_ptr 查不到 → 此前 fatal_error "属性写入失败"。
# 对齐 TPC HandlesAssign.py L905-949 的写时复制:
# 对象 vtable 仍指向类默认 vtable 时,先复制一份(internal 全局 +
# llvm.memcpy)并让对象指向副本,再 GEP method_index 槽位 store
# 函数指针(i8*)。多次写入互不覆盖(第二次起 vtable 已非默认,
# 跳过复制直接写槽位)。
# ============================================================
# ============================================================
def _write_vtable_slot(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
obj_ptr: llvmlite.Value | t.CPtr,
class_name: str,
method_index: int,
func_val: llvmlite.Value | t.CPtr,
trans: HT.Translator | t.CPtr) -> int:
"""生成 vtable 槽位写入(写时复制),成功返回 1"""
if pool is None or builder is None or mod is None or trans is None:
return 0
if obj_ptr is None or class_name is None or func_val is None:
return 0
if method_index < 0 or trans._cur_func is None:
return 0
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
if i8_ty is None:
return 0
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty)
if i8_ptr_ty is None:
return 0
i8_ptr_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ptr_ty)
if i8_ptr_ptr_ty is None:
return 0
method_count: int = self.Gen.StructGen.get_vtable_method_count(class_name)
if method_count <= 0:
return 0
# 构造 vtable 类型 { i8*, i8*, ... }
vt_first: llvmlite.ParamNode | t.CPtr = None
vt_prev: llvmlite.ParamNode | t.CPtr = None
for vi in range(method_count):
pn: llvmlite.ParamNode | t.CPtr = llvmlite.new_param_node(pool, i8_ptr_ty)
if pn is None:
continue
if vt_first is None:
vt_first = pn
if vt_prev is not None:
llvmlite.paramnode_set_next(vt_prev, pn)
vt_prev = pn
vtable_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Struct(pool, vt_first, method_count, None)
if vtable_ty is None:
return 0
# 对象 vtable 槽位(对象头 → i8**,vtable 指针总是第一个字段)
vtable_slot: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, obj_ptr, i8_ptr_ptr_ty)
if vtable_slot is None:
return 0
vtable_ptr: llvmlite.Value | t.CPtr = llvmlite.build_load(
builder, i8_ptr_ty, vtable_slot)
if vtable_ptr is None:
return 0
# 类默认 vtable 地址(比较用)
class_vt: llvmlite.Value | t.CPtr = self.Gen.CallGenLeafKit._make_vtable_ref_i8(
pool, builder, mod, class_name, trans)
if class_vt is None:
return 0
cmp_vt: llvmlite.Value | t.CPtr = llvmlite.build_icmp(
builder, llvmlite.ICMP_EQ, vtable_ptr, class_vt)
if cmp_vt is None:
return 0
# 块名唯一化(create_block 不自动编号;同函数多次 vtable 写入(如
# devfs open/close/read/write 连续赋值)同名 label 冲突)
vtc3: int = trans._label_counter
trans._label_counter = vtc3 + 1
vt_bb_buf3: str = pool.alloc(64)
if vt_bb_buf3 is None:
return 0
viperlib.snprintf(vt_bb_buf3, 64, "vt.copy.%d", vtc3)
vt_copy_bb: llvmlite.BasicBlock | t.CPtr = llvmlite.create_block(
pool, trans._cur_func, vt_bb_buf3)
viperlib.snprintf(vt_bb_buf3, 64, "vt.skip.%d", vtc3)
vt_skip_bb: llvmlite.BasicBlock | t.CPtr = llvmlite.create_block(
pool, trans._cur_func, vt_bb_buf3)
if vt_copy_bb is None or vt_skip_bb is None:
return 0
llvmlite.build_cond_br(builder, cmp_vt, vt_copy_bb, vt_skip_bb)
# copy 块:创建 internal 副本全局(zeroinit)+ memcpy + 对象指向副本
llvmlite.position_at_end(builder, vt_copy_bb)
vtc: int = trans._label_counter
trans._label_counter = vtc + 1
vcopy_buf: str = pool.alloc(256)
if vcopy_buf is not None:
viperlib.snprintf(vcopy_buf, 256, "%s.vtable.copy.%d", class_name, vtc)
vcopy_gv: llvmlite.GlobalVariable | t.CPtr = llvmlite.new_global_variable(
pool, vcopy_buf, vtable_ty)
if vcopy_gv is not None:
llvmlite.module_add_global(mod, vcopy_gv)
vcopy_gv.Linkage = None
vcopy_gv.Initializer = "zeroinitializer"
# 引用名必须带 @ 前缀:SSAValue 名原样输出,裸名
# devfs_file.vtable.copy.71 无 @ → llc "expected value token"
# (devfs vtable.copy 根因;全局定义 @name 是 new_global_variable
# 输出时加的,引用侧不自动加)
vcopy_ref_nm: str = pool.alloc(272)
if vcopy_ref_nm is None:
vcopy_ref_nm = vcopy_buf
else:
viperlib.snprintf(vcopy_ref_nm, 272, "@%s", vcopy_buf)
vcopy_ref: llvmlite.Value | t.CPtr = llvmlite.SSAValue(
pool, llvmlite.Ptr(pool, vtable_ty), vcopy_ref_nm)
if vcopy_ref is not None:
vcopy_i8: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, vcopy_ref, i8_ptr_ty)
if vcopy_i8 is not None:
# ConstInt 的 name 必须是数字文本(如 "56"),不能是
# 标识符 "vtsz"——参数经 Name 原样输出,裸名 vtsz 无 %
# → llc "expected value token"(devfs memcpy 根因)
vtsz_buf: str = pool.alloc(32)
if vtsz_buf is not None:
viperlib.snprintf(vtsz_buf, 32, "%lld", method_count * 8)
vt_size: llvmlite.Value | t.CPtr = llvmlite.ConstInt(
pool, llvmlite.Int64(pool), method_count * 8, vtsz_buf)
if vt_size is not None:
self.Gen.CallGenLib._emit_llvm_memcpy_intrinsic(
pool, builder, mod, vcopy_i8, class_vt, vt_size)
llvmlite.build_store(builder, vcopy_i8, vtable_slot)
llvmlite.build_br(builder, vt_skip_bb)
# skip 块:reload vtable → GEP 槽位 → store 函数指针(i8*)
llvmlite.position_at_end(builder, vt_skip_bb)
vt_ptr2: llvmlite.Value | t.CPtr = llvmlite.build_load(
builder, i8_ptr_ty, vtable_slot)
if vt_ptr2 is None:
return 0
vt_typed: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, vt_ptr2, llvmlite.Ptr(pool, vtable_ty))
if vt_typed is None:
return 0
vt_slotp: llvmlite.Value | t.CPtr = llvmlite.build_gep_struct(
builder, vtable_ty, i8_ptr_ty, vt_typed, method_index)
if vt_slotp is None:
return 0
fptr_i8: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, func_val, i8_ptr_ty)
if fptr_i8 is not None:
llvmlite.build_store(builder, fptr_i8, vt_slotp)
return 1
def _write_vtable_attr(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
node: ast.AST | t.CPtr,
rhs_val: llvmlite.Value | t.CPtr,
trans: HT.Translator | t.CPtr) -> int:
"""属性写入 obj.method = func:检测 vtable 方法并生成槽位写入(成功返回 1)"""
if node is None or builder is None or pool is None or mod is None:
return 0
if rhs_val is None or trans is None:
return 0
at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(node)
if at is None or at.value is None or at.attr is None:
return 0
# 解析类名(AnnotClassName;去模块前缀)
class_nm_v: str = None
if at.value.kind() == ast.ASTKind.Name:
nm_v: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
if nm_v.id is not None:
ve_v: GenTypes.VarEntry | t.CPtr = self.Gen.VarGen.lookup_var_entry(
trans.SymTab, nm_v.id)
if ve_v is not None:
class_nm_v = ve_v.AnnotClassName
elif at.value.kind() == ast.ASTKind.Attribute:
# 跨 Mixin:_resolve_attr_field_class 定义在 ExprGenAttr,经 self.Gen 调用
class_nm_v = self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, at.value)
if class_nm_v is None:
return 0
fd_dot_v: str = string.strrchr(class_nm_v, '.')
if fd_dot_v is not None:
class_nm_v = fd_dot_v + 1
if self.Gen.StructGen.get_struct_type(class_nm_v) is None:
return 0
vm_idx_v: int = self.Gen.StructGen.get_vtable_method_index(class_nm_v, at.attr)
if vm_idx_v < 0:
return 0
# 解析 obj_ptr(对齐 get_attribute_ptr 前半段:Name 直接查 alloca)
obj_ptr_v: llvmlite.Value | t.CPtr = None
if at.value.kind() == ast.ASTKind.Name:
obj_ptr_v = self.Gen.VarGen.lookup_var(
trans.SymTab, ((ast.Name | t.CPtr)(at.value)).id)
else:
obj_ptr_v = self.Gen.ExprGen.translate_value(
builder, pool, mod, at.value, None, 0, trans)
if obj_ptr_v is None or obj_ptr_v.Ty is None:
return 0
# X|t.CPtr 变量(Ptr(Ptr))load 解引用取对象指针
match obj_ptr_v.Ty:
case llvmlite.LLVMType.Ptr(inner_v):
match inner_v:
case llvmlite.LLVMType.Ptr(_):
obj_ptr_v = llvmlite.build_load(builder, inner_v, obj_ptr_v)
case _:
pass
case _:
pass
if obj_ptr_v is None:
return 0
return self._write_vtable_slot(
pool, builder, mod, obj_ptr_v, class_nm_v, vm_idx_v, rhs_val, trans)
+943
View File
@@ -0,0 +1,943 @@
from stdint import *
import ast
import memhub
import string
import stdlib
import viperlib
import lib.core.Gen.Translator as HT
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
# ============================================================
# Local constants: metadata/decorator/exception table sizes and method metadata flag bits.
define META_TLS = 32
# Class compile-time metadata table capacity (__provides__/__requires__/__require_must__).
define CLASS_META_MAX = 256
# Custom-decorator wrapper table capacity (number of @decor functions).
define DECOR_FUNC_MAX = 64
# Decorator table slot length for function/wrapper names (incl. NUL).
define DECOR_NAME_LEN = 128
# Exception class registry capacity (user classes subclassing Exception).
define EXCEPTION_MAX = 64
# Exception table slot length for class/parent names (incl. NUL).
define EXCEPTION_NAME_LEN = 128
# Builtin exception codes (mirrors TPC EXCEPTION_CODE_MAP); user classes start at 100.
# KeyError=6 IOError=7 OSError=8 AssertionError=9 Exception=99.
define BUILTIN_EH_VALUE_ERROR = 1
define BUILTIN_EH_RUNTIME_ERROR = 3
define BUILTIN_EH_EXCEPTION = 99
define EH_USER_CODE_START = 100
# Method metadata table sizes (formerly HandlesClassDef.META_MAX/META_KEY_LEN).
define META_MAX = 512
define META_KEY_LEN = 128
# ============================================================
# ClassGenBase - fields plus metadata/exception/inheritance/decorator helpers.
novirtual class ClassGenBase:
"""Fields plus method-metadata, exception, inheritance and decorator helpers."""
# Method metadata table (formerly _g_method_meta_keys/_flags/_count).
Gen: Generator.Generator*
MethodMetaKeys: bytes
MethodMetaFlags: bytes
MethodMetaCount: int
# REnum variant table (formerly _renum_class_names/_variant_counts/_variant_names/_variant_tags/_renum_class_count).
RenumClassNames: sizet*
RenumClassVariantCounts: sizet*
RenumVariantNames: sizet*
RenumVariantTags: sizet*
RenumClassCount: int
# REnum field alias table (formerly _renum_alias_class/_alias_field/_alias_index/_alias_count).
RenumAliasClass: sizet*
RenumAliasField: sizet*
RenumAliasIndex: sizet*
# REnum variant member LLVM types (mirrors TPC class_members): deep-copied into perm_pool.
# match binds slots by the real member type, not the raw i64 slot width.
RenumAliasTy: sizet*
# Alias variant name (perm_pool copy): the same payload offset has a different type
# per variant, so match looks up the member type by (class, variant, offset).
RenumAliasVariant: sizet*
RenumAliasCount: int
# --- Generic specialization cache (formerly _spec_keys/_spec_names/_spec_count). ---
SpecKeys: sizet*
SpecNames: sizet*
SpecCount: int
# --- Class compile-time metadata table (__provides__/__requires__/__require_must__).
# perm_pool-allocated: kind(4B) + cls(64B) + field(64B) per slot, e.g. FakeDuck __requires__.
ClassMetaKinds: int*
ClassMetaClss: bytes
ClassMetaFields: bytes
ClassMetaCount: int
# --- Custom decorator wrapper table (@log/@skip/@repeat -> __decor_wrap_{name}).
# translate_call rewrites the original name to the wrapper via lookup_decor_wrapper.
DecoratorFuncCount: int
DecoratorFuncNames: bytes
DecoratorWrapNames: bytes
# First constant decorator arg (the 3 in @repeat(3)); 0 when none (8B i64 per slot).
DecoratorArgs: int64*
# --- Exception class registry for try/except (mirrors TPC HandlesClassDef).
# perm_pool-allocated slots: class name(128B) + parent(128B) + code(4B); user codes from 100.
ExceptionCount: int
ExceptionNames: bytes
ExceptionParents: bytes
ExceptionCodes: int*
# Next exception code to assign (user classes, starting at 100).
NextExceptionCode: int
# Temp counter for collect_descendant_codes (invalid after it returns).
# Must be declared at class top; mid-method field annotations break Mixin layout.
_exception_codes_tmp_count: int
# --- Generic specialization recursion depth (guards against cyclic specialization).
# Incremented on entry, decremented on exit; exceeding the limit aborts.
GenericSpecDepth: int
# --- Generic template tables (formerly HandlesClassDef._generic_class_names/
# _generic_class_nodes/_generic_class_sha1s). ---
GenericClassNames: list[str]*
GenericClassNodes: list[ast.ClassDef*]*
GenericClassSha1s: list[str]*
# Source file path per template (same order as above): specialization temporarily
# switches CurrentFile to it so template line numbers in errors stay correct.
GenericClassFiles: list[str]*
# Template type-param names (same order as GenericClassNames, deep-copied to stdlib):
# Phase 1 type_params may expire across phases, so they are flattened here.
GenericClassTpParams: sizet*
# --- Generic function template tables, keyed "{sha1}.{fname}" to template
# node/SHA1/file/type-params; must stay at class top for Mixin layout. ---
GenericFuncKeys: list[str]*
GenericFuncNodes: list[ast.FunctionDef*]*
GenericFuncSha1s: list[str]*
GenericFuncFiles: list[str]*
GenericFuncTpParams: sizet*
# --- vtable method collection tables (formerly _g_vt_full_names/
# _g_vt_method_names/_g_vt_parent_sha1s/_g_vt_method_count). ---
VtFullNames: sizet*
VtMethodNames: sizet*
VtParentSha1s: sizet*
VtMethodCount: int
# ============================================================
# Methods below migrated in P5-2 from HandlesClassDef.vp; intra-module calls use self.
def _is_cenum_base(self, base_node: ast.AST*, trans: HT.Translator* = None) -> int:
"""Return 1 if the base node is t.CEnum, else 0."""
if base_node is None: return 0
k: int = base_node.kind()
# Name('CEnum') form (from t import CEnum).
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(base_node)
if nm.id is None: return 0
if string.strcmp(nm.id, "CEnum") == 0: return 1
# Alias form: from t import CEnum as en; class Color(en).
# Resolve the original name (en -> CEnum) before comparing.
if trans is not None and trans._from_imports is not None:
alias_orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, nm.id)
if alias_orig is not None:
og_len_ce: sizet = 0
while alias_orig[og_len_ce] != ' ' and alias_orig[og_len_ce] != '\0': og_len_ce += 1
if og_len_ce == 5 and string.strncmp(alias_orig, "CEnum", 5) == 0: return 1
return 0
# Attribute form was retired together with the t module prefix.
return 0
def _is_enum_class(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> int:
"""Return 1 if the ClassDef is a CEnum (subclasses t.CEnum or uses @t.CEnum)."""
if cd is None: return 0
# Decorator form: @t.CEnum class Color: (equivalent to subclassing t.CEnum).
if cd.decorator_list is not None:
if self._has_decorator(cd.decorator_list, "CEnum") == 1: return 1
if cd.bases is None: return 0
bases: list[ast.AST*]* = cd.bases
bn: sizet = len(bases)
if bn == 0: return 0
for bi in range(int(bn)):
base_node: ast.AST* = bases.get(bi)
if base_node is None: continue
if self._is_cenum_base(base_node, trans) == 1: return 1
return 0
def _is_cunion_base(self, base_node: ast.AST*, trans: HT.Translator* = None) -> int:
"""Return 1 if the base node is t.CUnion, else 0."""
if base_node is None: return 0
k: int = base_node.kind()
# Name('CUnion') form (from t import CUnion).
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(base_node)
if nm.id is None: return 0
if string.strcmp(nm.id, "CUnion") == 0: return 1
# Alias form: from t import CUnion as un; class Data(un).
if trans is not None and trans._from_imports is not None:
alias_orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, nm.id)
if alias_orig is not None:
og_len_cu: sizet = 0
while alias_orig[og_len_cu] != ' ' and alias_orig[og_len_cu] != '\0': og_len_cu += 1
if og_len_cu == 6 and string.strncmp(alias_orig, "CUnion", 6) == 0: return 1
return 0
# Attribute form was retired together with the t module prefix.
return 0
def _is_union_class(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> int:
"""Return 1 if the ClassDef is a CUnion (subclasses t.CUnion or uses @t.CUnion)."""
if cd is None: return 0
# Decorator form: @t.CUnion class Data:
if cd.decorator_list is not None:
if self._has_decorator(cd.decorator_list, "CUnion") == 1: return 1
if cd.bases is None: return 0
bases: list[ast.AST*]* = cd.bases
bn: sizet = len(bases)
if bn == 0: return 0
for bi in range(int(bn)):
base_node: ast.AST* = bases.get(bi)
if base_node is None: continue
if self._is_cunion_base(base_node, trans) == 1: return 1
return 0
def _is_crenum_base(self, base_node: ast.AST*, trans: HT.Translator* = None) -> int:
"""Return 1 if the base node is t.REnum, else 0."""
if base_node is None: return 0
k: int = base_node.kind()
# Name('REnum') form (from t import REnum).
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(base_node)
if nm.id is None: return 0
if string.strcmp(nm.id, "REnum") == 0: return 1
# Alias form: from t import REnum as ren; class Opt(ren).
if trans is not None and trans._from_imports is not None:
alias_orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, nm.id)
if alias_orig is not None:
og_len_cr: sizet = 0
while alias_orig[og_len_cr] != ' ' and alias_orig[og_len_cr] != '\0': og_len_cr += 1
if og_len_cr == 5 and string.strncmp(alias_orig, "REnum", 5) == 0: return 1
return 0
# Attribute form was retired together with the t module prefix.
return 0
def _is_renum_class(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> int:
"""Return 1 if the ClassDef is a REnum (subclasses t.REnum or uses @t.REnum)."""
if cd is None: return 0
# Decorator form: @t.REnum class X:
if cd.decorator_list is not None:
if self._has_decorator(cd.decorator_list, "REnum") == 1: return 1
if cd.bases is None: return 0
bases: list[ast.AST*]* = cd.bases
bn: sizet = len(bases)
if bn == 0: return 0
for bi in range(int(bn)):
base_node: ast.AST* = bases.get(bi)
if base_node is None: continue
if self._is_crenum_base(base_node, trans) == 1: return 1
return 0
def _has_decorator(self, decorator_list: list[ast.AST*]*, deco_name: str) -> int:
"""Return 1 if decorator_list contains the given bare builtin decorator name."""
if decorator_list is None: return 0
dn: sizet = len(decorator_list)
if dn == 0: return 0
for di in range(int(dn)):
deco: ast.AST* = decorator_list.get(di)
if deco is None: continue
k: int = deco.kind()
# @NoVTable / @CVTable / @TLS / @CStatic form (bare builtin Name).
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(deco)
if nm.id is not None and string.strcmp(nm.id, deco_name) == 0: return 1
return 0
def _tattr_name(self, node: ast.AST*, attr_name: str) -> int:
"""Extract the outermost attribute name from t.attr.<name> / t.attr.<name>(args) and compare it."""
if node is None: return 0
if node.kind() == ast.ASTKind.Call:
cl2: ast.Call* = ast.Call*(node)
if cl2.func is None: return 0
return self._tattr_name(cl2.func, attr_name)
if node.kind() == ast.ASTKind.Attribute:
at2: ast.Attribute* = ast.Attribute*(node)
if at2.attr is None: return 0
return 1 if string.strcmp(at2.attr, attr_name) == 0 else 0
return 0
def _class_has_tattr(self, decorator_list: list[ast.AST*]*, attr_name: str) -> int:
"""Return 1 if decorator_list contains t.attr.<attr_name> (@c.Attribute(t.attr.xxx) or @t.attr.xxx)."""
if decorator_list is None: return 0
dn: sizet = len(decorator_list)
if dn == 0: return 0
for di in range(int(dn)):
deco: ast.AST* = decorator_list.get(di)
if deco is None: continue
k: int = deco.kind()
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(deco)
# @Attribute(attr.xxx) form: func is the bare builtin Name("Attribute").
if cl.func is None or cl.func.kind() != ast.ASTKind.Name: continue
at0: ast.Name* = ast.Name*(cl.func)
if at0.id is None or string.strcmp(at0.id, "Attribute") != 0: continue
if cl.args is None: continue
an: sizet = len(cl.args)
for ai in range(int(an)):
ag: ast.AST* = cl.args.get(ai)
if ag is not None and self._tattr_name(ag, attr_name) != 0: return 1
else:
# Direct @t.attr.xxx form.
if self._tattr_name(deco, attr_name) != 0: return 1
return 0
def _tattr_aligned_int(self, node: ast.AST*) -> int:
"""Return N for t.attr.aligned(N); 0 when not aligned or the argument is missing."""
if node is None: return 0
if node.kind() == ast.ASTKind.Call:
cl2: ast.Call* = ast.Call*(node)
if cl2.func is None or cl2.func.kind() != ast.ASTKind.Attribute: return 0
at2: ast.Attribute* = ast.Attribute*(cl2.func)
if at2.attr is None or string.strcmp(at2.attr, "aligned") != 0: return 0
if cl2.args is None or len(cl2.args) == 0: return 0
a0: ast.AST* = cl2.args.get(0)
if a0 is None or a0.kind() != ast.ASTKind.Constant: return 0
cn: ast.Constant* = ast.Constant*(a0)
if cn.const_kind != ast.CONST_INT: return 0
return int(cn.int_val)
return 0
def _class_aligned_value(self, decorator_list: list[ast.AST*]*) -> int:
"""Return the alignment bytes from @Attribute(attr.aligned(N)); 0 when absent."""
if decorator_list is None: return 0
dn: sizet = len(decorator_list)
if dn == 0: return 0
for di in range(int(dn)):
deco: ast.AST* = decorator_list.get(di)
if deco is None: continue
if deco.kind() == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(deco)
if cl.func is None or cl.func.kind() != ast.ASTKind.Name: continue
at0: ast.Name* = ast.Name*(cl.func)
if at0.id is None or string.strcmp(at0.id, "Attribute") != 0: continue
if cl.args is None: continue
an: sizet = len(cl.args)
for ai in range(int(an)):
ag: ast.AST* = cl.args.get(ai)
if ag is not None:
av: int = self._tattr_aligned_int(ag)
if av > 0: return av
else:
av2: int = self._tattr_aligned_int(deco)
if av2 > 0: return av2
return 0
def _extract_func_meta(self, decorator_list: list[ast.AST*]*) -> int:
"""Build the method-metadata bitmask from decorators; 0=no decorator metadata."""
meta: int = 0
if decorator_list is None: return meta
dn: sizet = len(decorator_list)
if dn == 0: return meta
for di in range(int(dn)):
deco: ast.AST* = decorator_list.get(di)
if deco is None: continue
k: int = deco.kind()
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(deco)
if nm.id is not None:
if string.strcmp(nm.id, "staticmethod") == 0: meta |= GenTypes.META_STATIC
elif string.strcmp(nm.id, "property") == 0: meta |= GenTypes.META_PROP_GETTER
elif string.strcmp(nm.id, "classmethod") == 0: meta |= GenTypes.META_CLASS
elif string.strcmp(nm.id, "TLS") == 0: meta |= META_TLS
elif k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(deco)
if at.attr is not None and at.value is not None:
if at.value.kind() == ast.ASTKind.Name:
vn: ast.Name* = ast.Name*(at.value)
is_prop_name: int = 0
if vn.id is not None and string.strcmp(vn.id, "property") == 0: is_prop_name = 1
# Recognize @property.setter and @x.setter/getter/deleter.
if is_prop_name != 0 or string.strcmp(at.attr, "setter") == 0 or string.strcmp(at.attr, "getter") == 0 or string.strcmp(at.attr, "deleter") == 0:
if string.strcmp(at.attr, "setter") == 0: meta |= GenTypes.META_PROP_SETTER
elif string.strcmp(at.attr, "getter") == 0: meta |= GenTypes.META_PROP_GETTER
elif string.strcmp(at.attr, "deleter") == 0: meta |= GenTypes.META_PROP_DELETER
return meta
def register_class_meta(self, pool: memhub.MemBuddy*, kind: int, cls: str, field: str) -> int:
"""Register a class compile-time metadata field (__provides__/__requires__/__require_must__), idempotent."""
if pool is None or cls is None or field is None: return -1
if self.ClassMetaKinds is None:
self.ClassMetaKinds = CompilerFactory.factory.PermPool.alloc(CLASS_META_MAX * 4)
self.ClassMetaClss = CompilerFactory.factory.PermPool.alloc(CLASS_META_MAX * 64)
self.ClassMetaFields = CompilerFactory.factory.PermPool.alloc(CLASS_META_MAX * 64)
if self.ClassMetaKinds is None or self.ClassMetaClss is None or self.ClassMetaFields is None: return -1
string.memset(self.ClassMetaKinds, 0, CLASS_META_MAX * 4)
string.memset(self.ClassMetaClss, 0, CLASS_META_MAX * 64)
string.memset(self.ClassMetaFields, 0, CLASS_META_MAX * 64)
self.ClassMetaCount = 0
if self.ClassMetaCount >= CLASS_META_MAX: return -1
# Idempotent: skip duplicate (kind, cls, field).
for i in range(int(self.ClassMetaCount)):
if self.ClassMetaKinds[i] == kind:
if string.strcmp(self.ClassMetaClss + i * 64, cls) == 0:
if string.strcmp(self.ClassMetaFields + i * 64, field) == 0: return 0
idx_cm: sizet = sizet(self.ClassMetaCount) * 64
self.ClassMetaKinds[self.ClassMetaCount] = kind
string.strncpy(self.ClassMetaClss + idx_cm, cls, 63)
self.ClassMetaClss[idx_cm + 63] = '\0'
string.strncpy(self.ClassMetaFields + idx_cm, field, 63)
self.ClassMetaFields[idx_cm + 63] = '\0'
self.ClassMetaCount += 1
return 0
def class_meta_count(self, kind: int, cls: str) -> int:
"""Return the field count of the given metadata kind for a class."""
if self.ClassMetaKinds is None or cls is None: return 0
n_cm: int = 0
for i in range(int(self.ClassMetaCount)):
if self.ClassMetaKinds[i] == kind:
if string.strcmp(self.ClassMetaClss + i * 64, cls) == 0: n_cm += 1
return n_cm
def class_meta_field_at(self, kind: int, cls: str, idx: int) -> str:
"""Return the idx-th field name of the given metadata kind for a class."""
if self.ClassMetaKinds is None or cls is None: return None
n_cm2: int = 0
for i in range(int(self.ClassMetaCount)):
if self.ClassMetaKinds[i] == kind:
if string.strcmp(self.ClassMetaClss + i * 64, cls) == 0:
if n_cm2 == idx: return self.ClassMetaFields + i * 64
n_cm2 += 1
return None
def register_decorator(self, pool: memhub.MemBuddy*, func_name: str, wrapper_name: str, arg0: int64) -> int:
"""Register a custom decorator function (@log/@skip/@repeat(3) -> __decor_wrap_{name}); arg0 is its first constant arg (0 when none)."""
if pool is None or func_name is None or wrapper_name is None: return -1
if self.DecoratorFuncNames is None:
self.DecoratorFuncNames = CompilerFactory.factory.PermPool.alloc(DECOR_FUNC_MAX * DECOR_NAME_LEN)
self.DecoratorWrapNames = CompilerFactory.factory.PermPool.alloc(DECOR_FUNC_MAX * DECOR_NAME_LEN)
self.DecoratorArgs = CompilerFactory.factory.PermPool.alloc(DECOR_FUNC_MAX * 8)
if self.DecoratorFuncNames is None or self.DecoratorWrapNames is None or self.DecoratorArgs is None: return -1
string.memset(self.DecoratorFuncNames, 0, DECOR_FUNC_MAX * DECOR_NAME_LEN)
string.memset(self.DecoratorWrapNames, 0, DECOR_FUNC_MAX * DECOR_NAME_LEN)
string.memset(self.DecoratorArgs, 0, DECOR_FUNC_MAX * 8)
self.DecoratorFuncCount = 0
if self.DecoratorFuncCount >= DECOR_FUNC_MAX: return -1
# Idempotent: skip an already-registered function name (multi-pass scan).
for i in range(int(self.DecoratorFuncCount)):
if string.strcmp(self.DecoratorFuncNames + i * DECOR_NAME_LEN, func_name) == 0: return 0
idx_dc: sizet = sizet(self.DecoratorFuncCount) * DECOR_NAME_LEN
string.strncpy(self.DecoratorFuncNames + idx_dc, func_name, DECOR_NAME_LEN - 1)
self.DecoratorFuncNames[idx_dc + DECOR_NAME_LEN - 1] = '\0'
string.strncpy(self.DecoratorWrapNames + idx_dc, wrapper_name, DECOR_NAME_LEN - 1)
self.DecoratorWrapNames[idx_dc + DECOR_NAME_LEN - 1] = '\0'
self.DecoratorArgs[self.DecoratorFuncCount] = arg0
self.DecoratorFuncCount += 1
return 0
def lookup_decor_wrapper(self, func_name: str) -> str:
"""Look up the wrapper name by original function name; None when not registered."""
if self.DecoratorFuncNames is None or func_name is None: return None
for i in range(int(self.DecoratorFuncCount)):
if string.strcmp(self.DecoratorFuncNames + i * DECOR_NAME_LEN, func_name) == 0: return self.DecoratorWrapNames + i * DECOR_NAME_LEN
return None
def lookup_decor_arg0(self, func_name: str) -> int64:
"""Look up a decorator's first constant arg by original function name (0 when none)."""
if self.DecoratorFuncNames is None or func_name is None: return 0
for i in range(int(self.DecoratorFuncCount)):
if string.strcmp(self.DecoratorFuncNames + i * DECOR_NAME_LEN, func_name) == 0: return self.DecoratorArgs[i]
return 0
# ============================================================
# Exception class registry for try/except (mirrors TPC HandlesClassDef).
def _ensure_exception_tables(self) -> int:
"""Lazily initialize the exception tables (perm_pool); 0=success."""
if self.ExceptionNames is not None: return 0
pp_e: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if pp_e is None: return -1
self.ExceptionNames = pp_e.alloc(EXCEPTION_MAX * EXCEPTION_NAME_LEN)
self.ExceptionParents = pp_e.alloc(EXCEPTION_MAX * EXCEPTION_NAME_LEN)
self.ExceptionCodes = pp_e.alloc(EXCEPTION_MAX * 4)
if self.ExceptionNames is None or self.ExceptionParents is None or self.ExceptionCodes is None: return -1
string.memset(self.ExceptionNames, 0, EXCEPTION_MAX * EXCEPTION_NAME_LEN)
string.memset(self.ExceptionParents, 0, EXCEPTION_MAX * EXCEPTION_NAME_LEN)
string.memset(self.ExceptionCodes, 0, EXCEPTION_MAX * 4)
self.ExceptionCount = 0
self.NextExceptionCode = EH_USER_CODE_START
return 0
def register_exception(self, class_name: str, parent_name: str) -> int:
"""Register a user exception class and assign its code (idempotent by name); returns the code or -1."""
if class_name is None: return -1
if self._ensure_exception_tables() != 0: return -1
# Idempotent: return the existing code if already registered (multi-pass).
for i in range(int(self.ExceptionCount)):
if string.strcmp(self.ExceptionNames + i * EXCEPTION_NAME_LEN, class_name) == 0: return self.ExceptionCodes[i]
if self.ExceptionCount >= EXCEPTION_MAX: return -1
idx_e: sizet = sizet(self.ExceptionCount) * EXCEPTION_NAME_LEN
string.strncpy(self.ExceptionNames + idx_e, class_name, EXCEPTION_NAME_LEN - 1)
self.ExceptionNames[idx_e + EXCEPTION_NAME_LEN - 1] = '\0'
if parent_name is not None:
string.strncpy(self.ExceptionParents + idx_e, parent_name, EXCEPTION_NAME_LEN - 1)
self.ExceptionParents[idx_e + EXCEPTION_NAME_LEN - 1] = '\0'
code_e: int = self.NextExceptionCode
self.NextExceptionCode += 1
self.ExceptionCodes[self.ExceptionCount] = code_e
self.ExceptionCount += 1
return code_e
def _builtin_exception_code(self, name: str) -> int:
"""Return the builtin exception code (per TPC EXCEPTION_CODE_MAP); 0 when not builtin."""
if name is None: return 0
if string.strcmp(name, "ValueError") == 0: return 1
if string.strcmp(name, "TypeError") == 0: return 2
if string.strcmp(name, "RuntimeError") == 0: return 3
if string.strcmp(name, "ZeroDivisionError") == 0: return 4
if string.strcmp(name, "IndexError") == 0: return 5
if string.strcmp(name, "KeyError") == 0: return 6
if string.strcmp(name, "IOError") == 0: return 7
if string.strcmp(name, "OSError") == 0: return 8
if string.strcmp(name, "AssertionError") == 0: return 9
if string.strcmp(name, "Exception") == 0: return 99
return 0
def lookup_exception_code(self, name: str) -> int:
"""Look up an exception code: builtin table first, then user table; 0 when neither matches."""
if name is None: return 0
bi: int = self._builtin_exception_code(name)
if bi != 0: return bi
if self.ExceptionNames is None: return 0
for i in range(int(self.ExceptionCount)):
if string.strcmp(self.ExceptionNames + i * EXCEPTION_NAME_LEN, name) == 0: return self.ExceptionCodes[i]
return 0
def lookup_exception_parent(self, name: str) -> str:
"""Look up a user exception's parent name; None when it is not a user exception."""
if name is None or self.ExceptionNames is None: return None
for i in range(int(self.ExceptionCount)):
if string.strcmp(self.ExceptionNames + i * EXCEPTION_NAME_LEN, name) == 0:
p_e: str = self.ExceptionParents + i * EXCEPTION_NAME_LEN
if p_e is not None and p_e[0] != '\0': return p_e
return None
return None
def is_exception_name(self, name: str) -> int:
"""Return 1 if name is a builtin or user exception class name."""
if name is None: return 0
if self._builtin_exception_code(name) != 0: return 1
if self.ExceptionNames is None: return 0
for i in range(int(self.ExceptionCount)):
if string.strcmp(self.ExceptionNames + i * EXCEPTION_NAME_LEN, name) == 0: return 1
return 0
def collect_descendant_codes(self, name: str, out_codes: int*, max_codes: int) -> int:
"""Collect the codes of name and all its descendant exceptions into out_codes; returns the count."""
if name is None or out_codes is None: return 0
self._exception_codes_tmp_count = 0
self._collect_desc_codes_rec(name, out_codes, max_codes, 0)
return self._exception_codes_tmp_count
def _collect_desc_codes_rec(self, name: str, out_codes: int*, max_codes: int, depth: int) -> None:
"""Recursively collect name's code plus all descendant codes."""
if depth > 32: return
code_e: int = self.lookup_exception_code(name)
if code_e != 0 and self._exception_codes_tmp_count < max_codes:
# Deduplicate (guards against inheritance cycles).
dup: int = 0
for i in range(int(self._exception_codes_tmp_count)):
if out_codes[i] == code_e:
dup = 1
break
if dup == 0:
out_codes[self._exception_codes_tmp_count] = code_e
self._exception_codes_tmp_count += 1
# Recurse over user exceptions whose parent is name.
if self.ExceptionNames is None: return
for i in range(int(self.ExceptionCount)):
cname_e: str = self.ExceptionNames + i * EXCEPTION_NAME_LEN
pname_e: str = self.ExceptionParents + i * EXCEPTION_NAME_LEN
if pname_e is not None and pname_e[0] != '\0':
if string.strcmp(pname_e, name) == 0: self._collect_desc_codes_rec(cname_e, out_codes, max_codes, depth + 1)
def _is_exception_class_def(self, cd: ast.ClassDef*) -> int:
"""Return 1 if the class def is an exception class (base is Exception or a registered exception)."""
if cd is None or cd.bases is None: return 0
bases_e: list[ast.AST*]* = cd.bases
bn_e: sizet = len(bases_e)
for bi_e in range(int(bn_e)):
base_e: ast.AST* = bases_e.get(bi_e)
if base_e is None: continue
base_name_e: str = None
if base_e.kind() == ast.ASTKind.Name: base_name_e = (ast.Name*(base_e)).id
elif base_e.kind() == ast.ASTKind.Attribute:
base_at_e: ast.Attribute* = ast.Attribute*(base_e)
if base_at_e.attr is not None: base_name_e = base_at_e.attr
if base_name_e is None: continue
if string.strcmp(base_name_e, "Exception") == 0: return 1
if self.is_exception_name(base_name_e) != 0: return 1
return 0
def _get_exception_parent_name(self, cd: ast.ClassDef*) -> str:
"""Return the real parent name of an exception class (first registered non-Exception base); None otherwise."""
if cd is None or cd.bases is None: return None
bases_e2: list[ast.AST*]* = cd.bases
bn_e2: sizet = len(bases_e2)
for bi_e2 in range(int(bn_e2)):
base_e2: ast.AST* = bases_e2.get(bi_e2)
if base_e2 is None: continue
base_name_e2: str = None
if base_e2.kind() == ast.ASTKind.Name: base_name_e2 = (ast.Name*(base_e2)).id
elif base_e2.kind() == ast.ASTKind.Attribute:
base_at_e2: ast.Attribute* = ast.Attribute*(base_e2)
if base_at_e2.attr is not None: base_name_e2 = base_at_e2.attr
if base_name_e2 is None: continue
if string.strcmp(base_name_e2, "Exception") != 0 and self.is_exception_name(base_name_e2) != 0: return base_name_e2
return None
def _register_exception_class_def(self, trans: HT.Translator*, cd: ast.ClassDef*) -> int:
"""Register an exception class (assign code + record parent) without emitting a struct; returns 0."""
if cd is None or cd.name is None: return 0
parent_e: str = self._get_exception_parent_name(cd)
self.register_exception(cd.name, parent_e)
return 0
def _register_class_meta_list(self, pool: memhub.MemBuddy*, kind: int, cls: str, value_node: ast.AST*) -> None:
"""Parse a class-body __provides__/__requires__ list[str] literal and register its entries."""
if value_node is None or cls is None: return
if value_node.kind() != ast.ASTKind.List: return
lst_rcm: ast.List* = ast.List*(value_node)
if lst_rcm.elts is None: return
elts_rcm: list[ast.AST*]* = lst_rcm.elts
en_rcm: sizet = len(elts_rcm)
for ei_rcm in range(int(en_rcm)):
el_rcm: ast.AST* = elts_rcm.get(ei_rcm)
if el_rcm is None or el_rcm.kind() != ast.ASTKind.Constant: continue
ec_rcm: ast.Constant* = ast.Constant*(el_rcm)
if ec_rcm.str_val is not None: self.register_class_meta(pool, kind, cls, ec_rcm.str_val)
def register_method_meta(self, class_name: str, method_name: str, meta: int) -> None:
"""Register method metadata (idempotent)."""
if class_name is None or method_name is None or meta == 0: return
# Lazy initialization.
if self.MethodMetaKeys is None:
self.MethodMetaKeys = CompilerFactory.factory.PermPool.alloc(META_MAX * META_KEY_LEN)
self.MethodMetaFlags = CompilerFactory.factory.PermPool.alloc(META_MAX * 4)
if self.MethodMetaKeys is None or self.MethodMetaFlags is None: return
string.memset(self.MethodMetaKeys, 0, META_MAX * META_KEY_LEN)
string.memset(self.MethodMetaFlags, 0, META_MAX * 4)
# Build key "ClassName.method".
cl_len: sizet = string.strlen(class_name)
mn_len: sizet = string.strlen(method_name)
key_buf: bytes = stdlib.malloc(cl_len + mn_len + 2)
if key_buf is None: return
viperlib.snprintf(key_buf, cl_len + mn_len + 2, "%s.%s", class_name, method_name)
# Update duplicate entry.
i: int
for i in range(int(self.MethodMetaCount)):
if string.strcmp(self.MethodMetaKeys + i * META_KEY_LEN, key_buf) == 0:
flags_ptr: int* = (int*)(uint64(self.MethodMetaFlags) + i * 4)
# OR-combine flags: a getter then setter/deleter registers repeatedly,
# so whole-value replacement would drop GETTER and crash downstream mlirlite.
flags_ptr[0] = flags_ptr[0] | meta
stdlib.free(key_buf)
return
if self.MethodMetaCount < META_MAX:
# Slot is META_KEY_LEN=128 bytes; clamp strncpy to avoid overflowing the next slot.
string.strncpy(self.MethodMetaKeys + self.MethodMetaCount * META_KEY_LEN, key_buf, META_KEY_LEN - 1)
flags_ptr2: int* = (int*)(uint64(self.MethodMetaFlags) + self.MethodMetaCount * 4)
flags_ptr2[0] = meta
self.MethodMetaCount += 1
stdlib.free(key_buf)
def lookup_method_meta(self, class_name: str, method_name: str) -> int:
"""Look up the method-metadata bitmask; 0=not registered or not a decorator method."""
if class_name is None or method_name is None: return 0
if self.MethodMetaKeys is None or self.MethodMetaCount <= 0: return 0
cl_len: sizet = string.strlen(class_name)
mn_len: sizet = string.strlen(method_name)
key_buf: bytes = stdlib.malloc(cl_len + mn_len + 2)
if key_buf is None: return 0
viperlib.snprintf(key_buf, cl_len + mn_len + 2, "%s.%s", class_name, method_name)
found: int = 0
i: int
for i in range(int(self.MethodMetaCount)):
if string.strcmp(self.MethodMetaKeys + i * META_KEY_LEN, key_buf) == 0:
flags_ptr: int* = (int*)(uint64(self.MethodMetaFlags) + i * 4)
found = flags_ptr[0]
break
stdlib.free(key_buf)
return found
def _is_marker_base(self, base_name: str) -> int:
"""Return 1 if the base name is a marker base class (Object/CVTable/Exception/CEnum/Enum/CStruct/Struct/CUnion/REnum)."""
if base_name is None: return 0
if string.strcmp(base_name, "Object") == 0: return 1
if string.strcmp(base_name, "CVTable") == 0: return 1
if string.strcmp(base_name, "Exception") == 0: return 1
if string.strcmp(base_name, "CEnum") == 0: return 1
if string.strcmp(base_name, "Enum") == 0: return 1
if string.strcmp(base_name, "CStruct") == 0: return 1
if string.strcmp(base_name, "Struct") == 0: return 1
if string.strcmp(base_name, "CUnion") == 0: return 1
if string.strcmp(base_name, "REnum") == 0: return 1
return 0
def _get_base_name(self, base_node: ast.AST*, pool: memhub.MemBuddy* = None, from_imports: str = None) -> str:
"""Extract the base class name from a base AST node; Subscript nodes also build the specialized name; None on failure."""
if base_node is None: return None
k: int = base_node.kind()
# Name(id) form.
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(base_node)
return nm.id
# Attribute(attr) form.
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(base_node)
return at.attr
# Subscript(value, slice) form, e.g. GSListNode[Value].
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(base_node)
if sub is None or sub.value is None or sub.slice is None: return None
# Extract the base class name (e.g. "GSListNode").
base_nm: str = self._get_base_name(sub.value, pool, from_imports)
if base_nm is None: return None
# Extract the type arg name (e.g. "Value") and normalize it like the generator.
slice_k: int = sub.slice.kind()
arg_nm: str = None
if slice_k == ast.ASTKind.Name:
sl_nm: ast.Name* = ast.Name*(sub.slice)
arg_nm = sl_nm.id
# Fall back to the base name when the type arg cannot be extracted.
if arg_nm is None: return base_nm
# Without a pool, fall back to the base name (cannot build the specialized name).
if pool is None: return base_nm
arg_norm: str = self.Gen.TypeGenResolve._norm_type_arg_name(pool, arg_nm, from_imports)
if arg_norm is not None: arg_nm = arg_norm
# Build the specialized name "GSListNode[Value]".
base_len: sizet = string.strlen(base_nm)
arg_len: sizet = string.strlen(arg_nm)
total_len: sizet = base_len + arg_len + 3 # "[" + arg + "]" + NUL
mangled: str = pool.alloc(total_len)
if mangled is None:
return base_nm # fallback
mangled[0] = '\0'
string.strcat(mangled, base_nm)
string.strcat(mangled, "[")
string.strcat(mangled, arg_nm)
string.strcat(mangled, "]")
return mangled
return None
def _get_parent_class(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> str:
"""Return the ClassDef's real parent name; None when there is no real parent."""
if cd is None or cd.bases is None: return None
pool: memhub.MemBuddy* = None
if trans is not None: pool = trans.Pool
bases: list[ast.AST*]* = cd.bases
bn: sizet = len(bases)
if bn == 0: return None
for bi in range(int(bn)):
base_node: ast.AST* = bases.get(bi)
if base_node is None: continue
_fi_pc: str = None
if trans is not None: _fi_pc = trans._from_imports
bname: str = self._get_base_name(base_node, pool, _fi_pc)
if bname is not None:
# Resolve base aliases (from t import CStruct as cs -> cs is CStruct).
# Otherwise find_struct("cs") fails and the class is deferred forever.
bname = self._resolve_base_alias(bname, trans)
if self._is_marker_base(bname) == 0: return bname
return None
def _resolve_base_alias(self, base_name: str, trans: HT.Translator* = None) -> str:
"""Resolve a base alias (from t import CStruct as cs -> base cs is actually CStruct)."""
if base_name is None or trans is None or trans._from_imports is None: return base_name
orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, base_name)
if orig is not None:
# lookup_from_import_original returns a non-NUL-terminated interior pointer;
# copy and cut at the space so strcmp gets a clean original name.
og_len: sizet = 0
while orig[og_len] != ' ' and orig[og_len] != '\0': og_len += 1
if og_len > 0:
og_buf: str = CompilerFactory.factory.PermPool.alloc(og_len + 1)
if og_buf is not None:
string.strncpy(og_buf, orig, og_len)
og_buf[og_len] = '\0'
return og_buf
return base_name
def _collect_parents(self, pool: memhub.MemBuddy*, cd: ast.ClassDef*, parents_buf: sizet*, max_parents: int, trans: HT.Translator* = None) -> int:
"""Collect all non-marker parent names into parents_buf and return the count."""
if pool is None or cd is None or cd.bases is None or parents_buf is None: return 0
bases: list[ast.AST*]* = cd.bases
bn: sizet = len(bases)
count: int = 0
for bi in range(int(bn)):
if count >= max_parents: break
base_node: ast.AST* = bases.get(bi)
if base_node is None: continue
_fi_cp: str = None
if trans is not None: _fi_cp = trans._from_imports
bname: str = self._get_base_name(base_node, pool, _fi_cp)
if bname is not None:
# Resolve base alias (same as _get_parent_class, see _resolve_base_alias).
bname = self._resolve_base_alias(bname, trans)
if self._is_marker_base(bname) == 0:
parents_buf[count] = sizet(bname)
count += 1
return count
def _field_name_exists(self, names_buf: sizet*, count: int, name: str) -> int:
"""Return 1 if the field name already exists in names_buf (first-declared parent wins; duplicates skipped)."""
if names_buf is None or name is None: return 0
for fi in range(int(count)):
addr: sizet = names_buf[fi]
if addr != 0:
existing: str = rawptr(addr)
if existing is not None and string.strcmp(existing, name) == 0: return 1
return 0
def _class_body_has_field(self, cd: ast.ClassDef*) -> int:
"""Return 1 if the class body declares fields (AnnAssign); 0=methods-only/no-field class."""
if cd is None or cd.children is None: return 0
cn: sizet = len(cd.children)
for ci in range(int(cn)):
stmt: ast.AST* = cd.children.get(ci)
if stmt is None: continue
if stmt.kind() == ast.ASTKind.AnnAssign: return 1
return 0
def _trigger_base_specialization(self, trans: HT.Translator*, cd: ast.ClassDef*):
"""Trigger generic specialization for Subscript bases (e.g. GSListNode[Value])."""
if trans is None or cd is None or cd.bases is None: return
pool: memhub.MemBuddy* = trans.Pool
bases: list[ast.AST*]* = cd.bases
bn: sizet = len(bases)
if bn == 0: return
for bi in range(int(bn)):
base_node: ast.AST* = bases.get(bi)
if base_node is None: continue
bk: int = base_node.kind()
if bk != ast.ASTKind.Subscript: continue
sub: ast.Subscript* = ast.Subscript*(base_node)
if sub is None or sub.value is None or sub.slice is None: continue
# Extract the generic class name (e.g. "GSListNode").
gen_class_name: str = self._get_base_name(sub.value, pool)
if gen_class_name is None: continue
# Skip marker base classes.
if self._is_marker_base(gen_class_name) == 1: continue
# Extract the type arg name (e.g. "Value").
slice_k: int = sub.slice.kind()
arg_nm: str = None
if slice_k == ast.ASTKind.Name:
sl_nm: ast.Name* = ast.Name*(sub.slice)
arg_nm = sl_nm.id
if arg_nm is None: continue
# Build type_args (elem_size=8: str is a pointer; 1 would overlap elements).
type_args: list[str]* = list[str](pool, 8)
if type_args is None: continue
type_args.append(arg_nm)
# Trigger specialization (cached, so repeated calls are safe).
self.Gen.ClassGenGeneric._specialize_generic_class(trans, gen_class_name, type_args)
def _has_any_func_cvtable(self, cd: ast.ClassDef*) -> int:
"""Return 1 if any non-constructor function in the class is marked @t.CVTable."""
if cd is None or cd.children is None: return 0
children: list[ast.AST*]* = cd.children
cn: sizet = len(children)
for ci in range(int(cn)):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
if stmt.kind() != ast.ASTKind.FunctionDef: continue
fd: ast.FunctionDef* = ast.FunctionDef*(stmt)
if fd is None or fd.name is None: continue
# Skip constructors.
if string.strcmp(fd.name, "__init__") == 0: continue
if string.strcmp(fd.name, "__before_init__") == 0: continue
if string.strcmp(fd.name, "__new__") == 0: continue
if fd.decorator_list is not None:
if self._has_decorator(fd.decorator_list, "CVTable") == 1: return 1
return 0
def _should_method_be_virtual(self, cd: ast.ClassDef*, fd: ast.FunctionDef*, trans: HT.Translator* = None) -> int:
"""Decide whether a method belongs in the vtable; 1=include, 0=exclude."""
if fd is None: return 0
# 1. Function-level @t.NoVTable has top priority (exclude).
if fd.decorator_list is not None:
if self._has_decorator(fd.decorator_list, "NoVTable") == 1: return 0
# 2. Function-level @t.CVTable (explicitly include).
if self._has_decorator(fd.decorator_list, "CVTable") == 1: return 1
# 3. Class-level @t.CVTable.
if cd is not None and cd.decorator_list is not None:
if self._has_decorator(cd.decorator_list, "CVTable") == 1: return 1
# 4. Check inheritance.
parent_name: str = self._get_parent_class(cd, trans)
if parent_name is not None:
# SHA1-aware parent lookup to avoid cross-module same-name collisions.
is_nvt: int = 0
if trans is not None:
p_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(parent_name, trans.ModuleSha1)
if p_entry is None:
# Fall back to a global lookup for cross-module parents; consistent with
# field flattening, otherwise a NoVTable parent is misread as having a vptr.
p_entry = self.Gen.StructGen.find_struct(parent_name)
if p_entry is not None: is_nvt = p_entry.IsNoVTable
else: is_nvt = self.Gen.StructGen.is_novtable_by_name(parent_name)
if is_nvt != 1:
# vtable follows the parent: parent virtual -> method is virtual, else static.
# Matches _detect_vtable_status to keep parent/child layouts aligned.
if p_entry is None:
return 1 # unknown parent: conservatively make it virtual
if p_entry.HasVTable == 1: return 1
return 0
# 5. Default: exclude.
return 0
def _detect_vtable_status(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> int:
"""Detect whether the class should enable a vtable."""
if cd is None: return 0
# Check class decorators.
if cd.decorator_list is not None:
# @t.CVTable has top priority: enable directly.
if self._has_decorator(cd.decorator_list, "CVTable") == 1: return 1
# @t.NoVTable disables at class level, but still check function-level @t.CVTable.
if self._has_decorator(cd.decorator_list, "NoVTable") == 1: return self._has_any_func_cvtable(cd)
# Check for a real parent (inheritance enables CVTable automatically).
parent_name: str = self._get_parent_class(cd, trans)
if parent_name is not None:
# SHA1-aware parent lookup to avoid cross-module same-name collisions.
is_nvt: int = 0
if trans is not None:
p_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(parent_name, trans.ModuleSha1)
if p_entry is None:
# Fall back to a global lookup for cross-module parents.
p_entry = self.Gen.StructGen.find_struct(parent_name)
if p_entry is not None: is_nvt = p_entry.IsNoVTable
else: is_nvt = self.Gen.StructGen.is_novtable_by_name(parent_name)
if is_nvt == 1:
# Parent is NoVTable; check function-level @t.CVTable.
return self._has_any_func_cvtable(cd)
# vtable follows the parent: parent virtual -> child virtual, else static.
# A vptr mismatch would shift child fields and corrupt runtime access.
if p_entry is None: return 1 # unknown/missing parent: conservatively enable vtable
if p_entry.HasVTable == 1: return 1
return 0
# Default (no decorator, no parent): check function-level @t.CVTable.
return self._has_any_func_cvtable(cd)
def InitClassGen(self) -> int:
"""Initialize the ClassGen family fields (called by Generator during construction)."""
if self is None: return 1
self.MethodMetaKeys = None
self.MethodMetaFlags = None
self.MethodMetaCount = 0
# REnum variant registry lives on the compile-time factory (perm_pool), so it is
# restored here, not cleared: clearing loses variants registered by earlier work.
self.RenumClassNames = CompilerFactory.factory.RenumClassNames
self.RenumClassVariantCounts = CompilerFactory.factory.RenumClassVariantCounts
self.RenumVariantNames = CompilerFactory.factory.RenumVariantNames
self.RenumVariantTags = CompilerFactory.factory.RenumVariantTags
self.RenumClassCount = CompilerFactory.factory.RenumClassCount
self.RenumAliasClass = CompilerFactory.factory.RenumAliasClass
self.RenumAliasField = CompilerFactory.factory.RenumAliasField
self.RenumAliasIndex = CompilerFactory.factory.RenumAliasIndex
self.RenumAliasCount = CompilerFactory.factory.RenumAliasCount
self.SpecKeys = None
self.SpecNames = None
self.SpecCount = 0
self.GenericClassNames = None
self.GenericClassNodes = None
self.GenericClassSha1s = None
self.GenericClassFiles = None
self.GenericClassTpParams = None
self.GenericFuncKeys = None
self.GenericFuncNodes = None
self.GenericFuncSha1s = None
self.GenericFuncFiles = None
self.GenericFuncTpParams = None
self.VtFullNames = None
self.VtMethodNames = None
self.VtParentSha1s = None
self.VtMethodCount = 0
self.RenumAliasTy = CompilerFactory.factory.RenumAliasTy
self.RenumAliasVariant = CompilerFactory.factory.RenumAliasVariant
self.ClassMetaKinds = None
self.ClassMetaClss = None
self.ClassMetaFields = None
self.ClassMetaCount = 0
self.DecoratorFuncCount = 0
self.DecoratorFuncNames = None
self.DecoratorWrapNames = None
self.DecoratorArgs = None
self.ExceptionCount = 0
self.ExceptionNames = None
self.ExceptionParents = None
self.ExceptionCodes = None
self.NextExceptionCode = 0
self._exception_codes_tmp_count = 0
self.GenericSpecDepth = 0
return 0
+587
View File
@@ -0,0 +1,587 @@
import lib.core.Gen.GenTypes as GenTypes
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import viperlib
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.core.CompilerFactory as CompilerFactory
from . import ClassGenBase
# ClassGenDef - ClassGenMixin split (stage D): translate_class_def entry point
novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
"""Entry point for translate_class_def."""
# translate_class_def - translate a class definition (cluster D)
def translate_class_def(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a class definition; returns 0."""
if node is None or trans is None: return 0
cd: ast.ClassDef* = ast.ClassDef*(node)
if cd is None or cd.name is None: return 0
pool: memhub.MemBuddy* = trans.Pool
class_name: str = cd.name
if trans.ModuleSha1 is None:
# fail-fast: ModuleSha1 missing while translating a class (namespace isolation depends on it)
VLogger.error("ModuleSha1 missing while translating a class", "CLASS")
self.Gen.TypeGenErr.fatal_error(cd, "ModuleSha1 missing while translating a class", "TypeError")
# Namespace isolation: mark as visible in the current file. Must precede
# the existing check, or Phase B's early return would never mark it.
self.Gen.StructGen.add_visible_struct(pool, class_name)
# Record that this module declares the class (key "{SHA1}.{name}", independent
# of struct registration) so bare self-references resolve to the right module.
self.Gen.StructGen.mark_module_class(pool, trans.ModuleSha1, class_name)
# Enum class: handled by HandlesEnum. Fail-fast on dispatch ambiguity: REnum
# takes priority over CEnum/CUnion, whose semantics are mutually exclusive.
if self.Gen.ClassGenBase._is_enum_class(cd, trans) == 1:
if self.Gen.ClassGenBase._is_renum_class(cd, trans) == 1:
self.Gen.TypeGenErr.fatal_error(cd, f"class '{class_name}' is classified as both CEnum and REnum (dispatch ambiguity), " f"the REnum variant table cannot be registered", "TypeError")
return self.Gen.ClassGenEnum._translate_enum_def(trans, cd)
# Union class: handled by the union path
if self.Gen.ClassGenBase._is_union_class(cd, trans) == 1: return self.Gen.ClassGenEnum._translate_union_def(trans, cd)
# REnum class: handled by REnum ({i32 tag, max_payload} layout)
if self.Gen.ClassGenBase._is_renum_class(cd, trans) == 1: return self.Gen.ClassGenEnum._translate_renum_def(trans, cd)
# Generic class: stored as a template, no IR emitted until instantiation.
# Bind cd.type_params to a local to drive type inference.
tp_val: list[str]* = cd.type_params
if self.Gen.ClassGenGeneric._is_generic_class(tp_val) == 1:
self.Gen.ClassGenGeneric._register_generic_template(pool, cd, trans.ModuleSha1)
return 0
# Exception class (try-except): register encoding and inheritance, no struct
# (aligned with TPC HandlesClassDef.py L509-511).
if self.Gen.ClassGenBase._is_exception_class_def(cd) == 1: return self.Gen.ClassGenBase._register_exception_class_def(trans, cd)
# Nested class/struct: emit as a separate struct `{Outer}_{Inner}`, before all
# early returns so both declare_only paths recurse and REnum/union don't repeat.
self._translate_nested_classdefs(trans, cd)
# Check if already registered (SHA1 distinguishes same-named classes across modules)
existing: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(class_name, trans.ModuleSha1)
# Incomplete empty-class registration: existing FieldCount==0 but the body has
# fields, so neutralize the stale entry and rebuild via the full path below.
needs_rereg: int = 0
# Forward-declaration placeholder hit: do not early-return, fall into the
# in-place fill branch below so Ptr(placeholder.Ty) refs stay valid.
fill_fwd: int = 0
if existing is not None:
if existing.IsForwardDecl != 0: fill_fwd = 1
elif existing.FieldCount == 0 and trans._declare_only == 0:
if self.Gen.ClassGenBase._class_body_has_field(cd) != 0:
if existing.Name is not None: existing.Name[0] = '\0'
existing.Ty = None
existing.FieldCount = 0
needs_rereg = 1
if existing is not None and needs_rereg == 0 and fill_fwd == 0:
# Struct already registered (Phase A-pre or Phase B retranslation), but methods still need translating here
if existing.Ty is not None:
# Phase 1b (declare_only=0): existing.Ty's Name points into Phase 1a pool
# memory, so deep-copy the Name to stdlib to keep it alive cross-file.
if trans._declare_only == 0:
# Deep-copy existing.Ty to stdlib: the 1a pool Ty dangles across the
# 1b reset, hanging when the dangling field chain is walked.
ty_deep: mlirlite.LLVMType* = mlirlite.DeepCopyType(CompilerFactory.factory.PermPool, existing.Ty)
if ty_deep is not None: existing.Ty = ty_deep
old_name: str = mlirlite.get_struct_name(existing.Ty)
if old_name is not None:
on_len: sizet = string.strlen(old_name)
on_copy: str = CompilerFactory.factory.PermPool.alloc(on_len + 1)
if on_copy is not None:
string.strcpy(on_copy, old_name)
# In-place overwrite keeps existing.Ty's pointer identity (Phase A
# registered it); *= a boxed REnum variant is a load+store materialization.
*existing.Ty = mlirlite.LLVMType.Struct(mlirlite.get_struct_fields(existing.Ty), mlirlite.get_struct_fcount(existing.Ty), on_copy)
# Phase B: register the existing named struct into the current module
mlirlite.module_add_named_type(trans.Module, pool, existing.Ty)
# B5: the existing path also needs packed/aligned set, since Phase B's
# re-add_named_type resets IsPacked=0 and prints `{...}` (idt_entry root cause).
if self.Gen.ClassGenBase._class_has_tattr(cd.decorator_list, "packed") != 0:
mlirlite.module_set_named_type_packed(trans.Module, pool, existing.Ty, 1)
# StructTable may be rebuilt per phase, so IsPacked must be re-set or
# cross-module stub injection reads 0 and emits a non-packed stub.
existing.IsPacked = 1
# Complete field types like the fresh-definition path: value-type fields
# need full definitions here or this class becomes unsized for GEP.
self.Gen.StructGen._ensure_field_types_in_module(pool, trans.Module, existing.Ty)
align_ex: int = self.Gen.ClassGenBase._class_aligned_value(cd.decorator_list)
if align_ex > 0 and existing.Align == 0: existing.Align = align_ex
# Phase A-pre (declare_only=1): class registered, skip method bodies.
# Phase B (declare_only=0): translate method bodies in full.
if trans._declare_only == 1: self.Gen.ClassGenVTable._translate_oop_methods(trans, cd, existing.Ty, class_name, 1)
else: self.Gen.ClassGenVTable._translate_oop_methods(trans, cd, existing.Ty, class_name)
# SHA1 is already set during register_struct, no need to re-set
return 0
# Trigger generic specialization of Subscripts in base (e.g. GSListNode[Value])
# before _detect_vtable_status and field inheritance, or the lookup returns None.
self.Gen.ClassGenBase._trigger_base_specialization(trans, cd)
# VTable detection: decide whether a vtable is enabled
has_vtable: int = self.Gen.ClassGenBase._detect_vtable_status(cd, trans)
parent_name: str = self.Gen.ClassGenBase._get_parent_class(cd, trans)
# Multiple inheritance: collect real parent names (skip marker bases) for flatten/vtable/MRO
parents_buf: sizet* = pool.alloc(8 * GenTypes.PARENT_MAX)
parents_count: int = 0
if parents_buf is not None: parents_count = self.Gen.ClassGenBase._collect_parents(pool, cd, parents_buf, GenTypes.PARENT_MAX, trans)
is_novtable_deco: int = 0
if cd.decorator_list is not None:
if self.Gen.ClassGenBase._has_decorator(cd.decorator_list, "NoVTable") == 1: is_novtable_deco = 1
# PhaseA pre-registration deferral: when the parent is unregistered, defer the
# current class to a later pass. Phase B (declare_only==0) never defers.
if trans._declare_only == 1 and parent_name is not None:
parent_check: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(parent_name, trans.ModuleSha1)
if parent_check is None: parent_check = self.Gen.StructGen.find_struct(parent_name)
if parent_check is None: return 0
# 1. Walk the class body and collect field info
# Use CSizeT arrays to store pointer values (8 bytes on 64-bit systems)
field_names_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
field_types_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
field_defaults_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
field_annot_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
# Field volatile flag buffer (1 = annotation union contains t.CVolatile), parallel
# to field_annot_buf with values 0/1.
field_volatile_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
# Field pointee-volatile flag buffer (1 = rawptr[t.CVolatile | T], i.e. volatile T*),
# parallel to field_volatile_buf with values 0/1.
field_pointee_volatile_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
# Field const-object flag buffer (1 = annotation has t.CConst and the type is not
# a pointer), parallel to field_volatile_buf with values 0/1.
field_const_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
# Field byte-order buffer (0 = native, 1 = t.BigEndian, 2 = t.LittleEndian), parallel
# to field_volatile_buf; from the `X | t.BigEndian` annotation union.
field_byteorder_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
if field_names_buf is None or field_types_buf is None or field_defaults_buf is None: return 0
if field_annot_buf is None or field_volatile_buf is None: return 0
if field_pointee_volatile_buf is None or field_const_buf is None: return 0
if field_byteorder_buf is None: return 0
string.memset(field_names_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_types_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_defaults_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_annot_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_volatile_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_pointee_volatile_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_const_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_byteorder_buf, 0, 8 * GenTypes.FIELD_MAX)
field_count: int = 0
# 1.5 Inherit parent fields (field flattening): parent fields precede child
# fields, layout [vtable_ptr?, parent..., child...]; dedup by name (first wins).
if parents_count > 0 and parents_buf is not None:
for pi in range(int(parents_count)):
pname_addr: sizet = parents_buf[pi]
if pname_addr == 0: continue
pname: str = rawptr(pname_addr)
if pname is None: continue
# SHA1-aware parent lookup to avoid picking a same-named class in another module
parent_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(pname, trans.ModuleSha1)
if parent_entry is None:
# The parent may live in another module (different SHA1), and
# find_struct_by_module does not fall back, so query find_struct manually.
parent_entry = self.Gen.StructGen.find_struct(pname)
if parent_entry is None:
# Parent not yet registered: do not register a partial entry (perm_pool
# entries survive passes and the existing early-return never repairs it).
return 0
pfc: int = parent_entry.FieldCount
for pfi in range(int(pfc)):
pfe: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(parent_entry, pfi)
if pfe is None: continue
pfn: str = self.Gen.StructGen.get_field_name_ptr(pfe)
if pfn is None: continue
# Skip the parent's vtable pointer field
if string.strcmp(pfn, "__vtable__") == 0: continue
# Multi-inheritance dedup: skip existing field names (earlier parent wins)
if self.Gen.ClassGenBase._field_name_exists(field_names_buf, field_count, pfn) != 0: continue
if field_count < GenTypes.FIELD_MAX:
pft: mlirlite.LLVMType* = self.Gen.StructGen.get_field_type_ptr(pfe)
pfd: ast.AST* = self.Gen.StructGen.get_field_default_ptr(pfe)
pfa: str = self.Gen.StructGen.get_field_annot_class_name(pfe)
field_names_buf[field_count] = sizet(pfn)
field_types_buf[field_count] = sizet(pft)
if pfd is not None: field_defaults_buf[field_count] = sizet(pfd)
else: field_defaults_buf[field_count] = 0
if pfa is not None:
stored_val: sizet = sizet(pfa)
field_annot_buf[field_count] = stored_val
else: field_annot_buf[field_count] = 0
# Inherit the volatile flag (semantics unchanged after flattening)
field_volatile_buf[field_count] = sizet(pfe.IsVolatile)
# Inherit the pointee-volatile flag (unchanged after flattening)
field_pointee_volatile_buf[field_count] = sizet(pfe.PointeeVolatile)
# Inherit the const-object flag (unchanged after flattening)
field_const_buf[field_count] = sizet(pfe.IsConst)
# Inherit the byte-order flag (unchanged after flattening)
field_byteorder_buf[field_count] = sizet(pfe.ByteOrder)
field_count += 1
children: list[ast.AST*]* = cd.children
if children is None: return 0
cn: sizet = len(children)
for ci in range(int(cn)):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk: int = stmt.kind()
if sk != ast.ASTKind.AnnAssign: continue
# AnnAssign(target=Name(id), annotation=type, value=...)
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
if aa is None or aa.target is None: continue
# Get the field name
if aa.target.kind() != ast.ASTKind.Name: continue
tgt: ast.Name* = ast.Name*(aa.target)
if tgt.id is None: continue
# Class-body CDefine constant (any position): register into the global CDefine
# table so bare names inside methods resolve; catches post-method AnnAssigns.
if self.Gen.AssignGen.is_cdefine_annotation(aa.annotation) != 0:
if aa.value is not None: self.Gen.AssignGen._register_cdefine_from_value(pool, tgt.id, aa.value, aa.annotation)
# CDefine field type is fixed i32, collected directly to skip generic annotation
# resolution (which does not know "CDefine") and avoid a Phase B NameError.
if field_count < GenTypes.FIELD_MAX:
cdef_ty_l: mlirlite.LLVMType* = mlirlite.Int32(pool)
if cdef_ty_l is not None:
field_names_buf[field_count] = sizet(tgt.id)
field_types_buf[field_count] = sizet(cdef_ty_l)
if aa.value is not None: field_defaults_buf[field_count] = sizet(aa.value)
else: field_defaults_buf[field_count] = 0
field_count += 1
continue
# Skip compile-time metadata fields (__provides__/__requires__/__require_must__):
# they only feed static with-context reachability checks, no runtime code.
if string.strcmp(tgt.id, "__provides__") == 0:
self.Gen.ClassGenBase._register_class_meta_list(pool, GenTypes.CLASS_META_PROVIDES, class_name, aa.value)
continue
if string.strcmp(tgt.id, "__requires__") == 0:
self.Gen.ClassGenBase._register_class_meta_list(pool, GenTypes.CLASS_META_REQUIRES, class_name, aa.value)
continue
if string.strcmp(tgt.id, "__require_must__") == 0:
self.Gen.ClassGenBase._register_class_meta_list(pool, GenTypes.CLASS_META_REQUIRE_MUST, class_name, aa.value)
continue
# Resolve the field type
# Value-reference markers are parameter-only: a struct field `x: &T` fails fast.
self.Gen.TypeGenResolve.reject_ref_marker_position(aa.annotation, trans._imported_modules, "a struct field")
field_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, aa.annotation, trans._imported_modules, trans._from_imports, trans, 0)
# void is not a valid field type (only for `void*` and opaque typedefs); letting
# it through builds an illegal layout, so fail-fast at compile time here.
if field_ty is not None and self.Gen.StmtGenDecl._is_void_type(field_ty) != 0: self.Gen.TypeGenErr.fatal_error(aa, f"field '{tgt.id}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
# Single-arg t.CArray[elem] with a list literal -> static array [N x elem] (like
# local AnnAssign); otherwise it stays Ptr(elem) and for-in iterates pointers wrongly.
if field_ty is not None: field_ty = self.Gen.AssignGen._infer_static_array_ty_from_literal(pool, aa.annotation, aa.value, field_ty)
if field_ty is None:
# BUG 5 fix: remove the silent i32 fallback. Phase A-pre defers to the next
# pass; Phase B fast-fails. Generic Subscripts fall back to Ptr(i8) instead.
is_generic_sub: int = 0
if aa.annotation is not None and aa.annotation.kind() == ast.ASTKind.Subscript:
# t.CArray[elem, N] is a fixed-size value array, not a generic heap object;
# falling back to Ptr(i8) would register a degraded layout Phase B reuses.
sub_g: ast.Subscript* = ast.Subscript*(aa.annotation)
is_carray_sub_g: int = 0
if sub_g is not None and sub_g.value is not None:
if sub_g.value.kind() == ast.ASTKind.Attribute:
svg_at: ast.Attribute* = ast.Attribute*(sub_g.value)
if svg_at.attr is not None and string.strcmp(svg_at.attr, "CArray") == 0: is_carray_sub_g = 1
elif sub_g.value.kind() == ast.ASTKind.Name:
svg_nm: ast.Name* = ast.Name*(sub_g.value)
if svg_nm.id is not None and (string.strcmp(svg_nm.id, "CArray") == 0 or string.strcmp(svg_nm.id, "rawarray") == 0 or string.strcmp(svg_nm.id, "array") == 0):
is_carray_sub_g = 1
if is_carray_sub_g == 0: is_generic_sub = 1
if is_generic_sub != 0:
# Generic not specialized: fall back to Ptr(i8); AnnotClassName restores it later
field_ty = mlirlite.Ptr(pool, mlirlite.Int8(pool))
else:
# Phase A-pre (declare_only=1): a dependency may not be registered yet, so
# silently return 0 and let the multi-pass convergence retry.
if trans._declare_only == 1: return 0
# Phase A (declare_only=0, multi-pass): a dependency module may translate
# after this one, so do not register the struct with incomplete fields.
return 0
# Old behavior fast-failed with NameError (removed: it misreported while
# dependencies were pending; the convergence check handles real errors).
# Store into the array (pointer cast to CSizeT)
if field_count < GenTypes.FIELD_MAX:
diag_name_val: sizet = sizet(tgt.id)
field_names_buf[field_count] = diag_name_val
field_types_buf[field_count] = sizet(field_ty)
# Store the default-value AST pointer (None = no default)
if aa.value is not None: field_defaults_buf[field_count] = sizet(aa.value)
else: field_defaults_buf[field_count] = 0
# Extract the class name from the raw annotation (used to find the struct when a
# union degrades to Ptr(i8)); pass pool so generic Subscripts return a mangled name.
if aa.annotation is not None:
annot_cn: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(aa.annotation, trans._imported_modules, pool, trans._from_imports)
if annot_cn is not None:
diag_cn_val: sizet = sizet(annot_cn)
field_annot_buf[field_count] = diag_cn_val
else: field_annot_buf[field_count] = 0
# t.CVolatile storage-class flag: loads/stores through this field are
# volatile (e.g. Spinlock.locked: t.CVolatile | t.CInt).
if self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "CVolatile", 0) != 0: field_volatile_buf[field_count] = 1
else: field_volatile_buf[field_count] = 0
# rawptr[t.CVolatile | T] (volatile pointee, i.e. C volatile T*): derefs
# through this field (self.ptr[i]/self.ptr.field) are volatile.
if self.Gen.TypeGenErr.has_pointee_volatile_marker(aa.annotation) != 0: field_pointee_volatile_buf[field_count] = 1
else: field_pointee_volatile_buf[field_count] = 0
# const object field (annotation has t.CConst, non-pointer type): assigning
# through the object fail-fasts, matching TPC's Gen.field_const_flags.
if self.Gen.TypeGenErr.is_const_object_marker(aa.annotation, field_ty) != 0: field_const_buf[field_count] = 1
else: field_const_buf[field_count] = 0
# Byte-order marker (t.BigEndian / t.LittleEndian) -> 1 / 2; annotation `|`
# means "and", so `X | t.BigEndian` is a BigEndian-encoded X field.
if self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "BigEndian", 0) != 0: field_byteorder_buf[field_count] = 1
elif self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "LittleEndian", 0) != 0: field_byteorder_buf[field_count] = 2
else: field_byteorder_buf[field_count] = 0
else:
field_annot_buf[field_count] = 0
field_volatile_buf[field_count] = 0
field_pointee_volatile_buf[field_count] = 0
field_const_buf[field_count] = 0
field_byteorder_buf[field_count] = 0
field_count += 1
if field_count == 0:
# During PhaseA pre-registration the parent may be unregistered (single alphabetic
# pass), so field_count==0; return silently and retry in a later pass.
if trans._declare_only == 1 and parent_name is not None: return 0
# Fieldless class (interface/marker): do not skip it, as it may hold only methods
# (e.g. ShapeM/Vec2Extra); an empty struct is legal in LLVM.
# 2. Create the LLVM StructType
# Build the ParamNode chain
first_node: mlirlite.ParamNode* = None
prev_node: mlirlite.ParamNode* = None
total_field_count: int = field_count
# With a vtable, add an i8* vtable pointer as the first field
if has_vtable == 1:
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
vtable_node: mlirlite.ParamNode* = pool.alloc(mlirlite.ParamNode.__sizeof__())
if vtable_node is None:
VLogger.error(f"failed to alloc vtable node for {class_name}", "CLASS")
return 0
string.memset(vtable_node, 0, mlirlite.ParamNode.__sizeof__())
vtable_node.Ty = i8_ptr_ty
first_node = vtable_node
prev_node = vtable_node
total_field_count = field_count + 1
for fi in range(int(field_count)):
fty_addr: sizet = field_types_buf[fi]
if fty_addr == 0: continue
fty: mlirlite.LLVMType* = (mlirlite.LLVMType*)(fty_addr)
# Create the ParamNode
pnode: mlirlite.ParamNode* = pool.alloc(mlirlite.ParamNode.__sizeof__())
if pnode is None: continue
string.memset(pnode, 0, mlirlite.ParamNode.__sizeof__())
pnode.Ty = fty
if first_node is None: first_node = pnode
if prev_node is not None: prev_node.Next = pnode
prev_node = pnode
# Build the named struct type name "sha1.ClassName" or "ClassName". In Phase 1b the
# Name must survive per-file reset_to(mark1), so deep-copy to stdlib; 1a uses pool.
type_name: str = class_name
if trans.ModuleSha1 is not None:
name_buf: str = pool.alloc(64)
if name_buf is not None:
viperlib.snprintf(name_buf, 64, "%s.%s", trans.ModuleSha1, class_name)
type_name = name_buf
if trans._declare_only == 0 and type_name is not None:
tn_len: sizet = string.strlen(type_name)
tn_copy: str = CompilerFactory.factory.PermPool.alloc(tn_len + 1)
if tn_copy is not None:
string.strcpy(tn_copy, type_name)
type_name = tn_copy
# Create the StructType (named struct)
struct_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, first_node, total_field_count, type_name)
if struct_ty is None:
VLogger.error(f"failed to create StructType for {class_name}", "CLASS")
return 0
# Deep-copy the whole struct_ty tree to stdlib and replace the pointer so entry.Ty
# and callers share one surviving copy; required too under StdlibAlloc=1.
if trans._declare_only == 0 or self.Gen.StructGen.get_stdlib_alloc() != 0:
st_copy: mlirlite.LLVMType* = mlirlite.DeepCopyType(CompilerFactory.factory.PermPool, struct_ty)
if st_copy is not None:
struct_ty = st_copy
type_name = mlirlite.get_struct_name(struct_ty)
# Forward-declaration hit: in-place fill keeps existing.Ty's identity, writing the
# field body into the same object before module_add_named_type to avoid re-registration.
if fill_fwd != 0 and existing is not None and existing.Ty is not None:
_fwd_name: str = mlirlite.get_struct_name(existing.Ty)
_fwd_fields: mlirlite.ParamNode* = mlirlite.get_struct_fields(struct_ty)
_fwd_fc: int = mlirlite.get_struct_fcount(struct_ty)
*existing.Ty = mlirlite.LLVMType.Struct(_fwd_fields, _fwd_fc, _fwd_name)
struct_ty = existing.Ty
type_name = _fwd_name
existing.IsForwardDecl = 0
existing.FieldCount = 0
# Register the named struct into the module (emit the %"name" = type { ... } line)
mlirlite.module_add_named_type(trans.Module, pool, struct_ty)
# B5: @c.Attribute(t.attr.packed) emits <{...}> (LLVM packed struct, no padding) as
# needed by binary-format structs like idt/gdt/elf/fat32.
is_packed_cd: int = self.Gen.ClassGenBase._class_has_tattr(cd.decorator_list, "packed")
if is_packed_cd != 0: mlirlite.module_set_named_type_packed(trans.Module, pool, struct_ty, 1)
# Scan field types and declare cross-module struct refs in the current module, e.g.
# list[int].__pool__ referencing memhub.MemManager needs a %"sha1.MemManager" opaque.
for fi in range(int(field_count)):
fty_addr_op: sizet = field_types_buf[fi]
if fty_addr_op == 0: continue
fty_op: mlirlite.LLVMType* = (mlirlite.LLVMType*)(fty_addr_op)
if self.Gen.ExprGenBase.is_ptr_type(fty_op) != 0:
# Pointer field: the pointee only needs an opaque declaration (GEP does not index into it)
mlirlite.module_ensure_opaque_for_type(trans.Module, pool, fty_op)
else:
# Value-type field (struct body / struct array): copy the full definition, or
# this class becomes unsized and llc rejects field-indexed GEPs.
self.Gen.StructGen.ensure_struct_def_in_module(pool, trans.Module, fty_op)
self.Gen.StructGen._ensure_field_types_in_module(pool, trans.Module, fty_op)
# 3. Register into HandlesStruct
entry: GenTypes.StructEntry* = None
if fill_fwd != 0 and existing is not None:
# Forward-declaration placeholder: the field body is already in existing.Ty, so
# reuse existing (a new entry/Ty would orphan issued Ptr(placeholder.Ty) refs).
entry = existing
else: entry = self.Gen.StructGen.register_struct(pool, class_name, struct_ty, trans.ModuleSha1)
if entry is None:
VLogger.error(f"failed to register {class_name}", "CLASS")
return 0
# B5: record the packed flag into StructEntry for __sizeof__/layout queries
if is_packed_cd != 0: entry.IsPacked = 1
# B5: record the aligned value (@c.Attribute(t.attr.aligned(N)), e.g. fpu_context 16)
# for global/local alloca alignment.
align_cd: int = self.Gen.ClassGenBase._class_aligned_value(cd.decorator_list)
if align_cd > 0: entry.Align = align_cd
# Mark vtable status (use entry directly to avoid same-named cross-module lookup errors)
if has_vtable == 1: entry.HasVTable = 1
if is_novtable_deco == 1: entry.IsNoVTable = 1
if parent_name is not None:
# In 1b the parent name is a pool string that dangles after reset, and later files
# strcmp it via find_struct, so copy it to stdlib for cross-file survival.
if self.Gen.StructGen.get_stdlib_alloc() != 0:
pn_len: sizet = string.strlen(parent_name)
pn_buf: str = CompilerFactory.factory.PermPool.alloc(pn_len + 1)
if pn_buf is not None:
string.strcpy(pn_buf, parent_name)
entry.ParentName = pn_buf
else: entry.ParentName = parent_name
# Multiple inheritance: set the parent array for MRO/field/vtable queries (stdlib-deep-copied)
if parents_count > 0 and parents_buf is not None: self.Gen.StructGen.set_parent_names(pool, class_name, parents_buf, parents_count)
# Add field info. With a vtable, add the __vtable__ placeholder first (index 0),
# so user fields start at index 1.
if has_vtable == 1:
i8_ptr_ty2: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
self.Gen.StructGen.add_field(pool, entry, "__vtable__", i8_ptr_ty2, None, None)
for fi in range(int(field_count)):
fname_addr: sizet = field_names_buf[fi]
if fname_addr == 0: continue
fname: str = rawptr(fname_addr)
fty_addr2: sizet = field_types_buf[fi]
if fty_addr2 == 0: continue
fty2: mlirlite.LLVMType* = (mlirlite.LLVMType*)(fty_addr2)
# Restore the default-value AST pointer
fdef_addr: sizet = field_defaults_buf[fi]
fdef: ast.AST* = None
if fdef_addr != 0: fdef = (ast.AST*)(fdef_addr)
# Restore the annotation class-name pointer
fannot_addr: sizet = field_annot_buf[fi]
fannot: str = None
if fannot_addr != 0: fannot = str(fannot_addr)
if fname is not None and fty2 is not None:
fidx_add: int = self.Gen.StructGen.add_field(pool, entry, fname, fty2, fdef, fannot)
# t.CVolatile field flag (field_volatile_buf[fi] corresponds 1:1 with add_field order)
if fidx_add >= 0 and field_volatile_buf[fi] != 0:
fe_vol: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add)
if fe_vol is not None: fe_vol.IsVolatile = 1
# rawptr[t.CVolatile | T] field flag (field_pointee_volatile_buf[fi] corresponds
# 1:1 with add_field): derefs through it load/store with volatile.
if fidx_add >= 0 and field_pointee_volatile_buf[fi] != 0:
fe_pv: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add)
if fe_pv is not None: fe_pv.PointeeVolatile = 1
# byte-order field flag (field_byteorder_buf[fi] corresponds 1:1 with add_field order)
if fidx_add >= 0 and field_byteorder_buf[fi] != 0:
fe_bo: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add)
if fe_bo is not None: fe_bo.ByteOrder = int(field_byteorder_buf[fi])
# const object field flag (field_const_buf[fi] corresponds 1:1 with add_field order)
if fidx_add >= 0 and field_const_buf[fi] != 0:
fe_c: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add)
if fe_c is not None: fe_c.IsConst = 1
# Phase 1a declaration mode: only register the struct and OOP flags, no method bodies
if trans._declare_only == 1:
self.Gen.ClassGenVTable._translate_oop_methods(trans, cd, struct_ty, class_name, 1)
return 0
# 4. OOP methods: scan FunctionDefs, mark OOP, translate methods, emit __before_init__
self.Gen.ClassGenVTable._translate_oop_methods(trans, cd, struct_ty, class_name)
return 0
# _translate_nested_classdefs - recursively translate nested classes/structs. Rewrite
# child names to flat `{Outer}_{Inner}` (PermPool) then recurse, restoring the def window.
def _translate_nested_classdefs(self, trans: HT.Translator*, cd: ast.ClassDef*) -> None:
"""Recursively translate nested ClassDefs under the flat name {Outer}_{Inner}."""
if trans is None or cd is None or cd.name is None: return
children: list[ast.AST*]* = cd.children
if children is None: return
cn: sizet = len(children)
ci: sizet = 0
while ci < cn:
child_ast: ast.AST* = children.get(ci)
ci += 1
if child_ast is None: continue
if child_ast.kind() != ast.ASTKind.ClassDef: continue
child: ast.ClassDef* = ast.ClassDef*(child_ast)
if child is None or child.name is None: continue
# Flat name `{Outer}_{Inner}` (prefer PermPool so it survives phases; fall back to
# the pass pool when missing, matching the old path).
flat_pool: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if flat_pool is None: flat_pool = trans.Pool
if flat_pool is None: continue
outer_len: sizet = string.strlen(cd.name)
inner_len: sizet = string.strlen(child.name)
flat_len: sizet = outer_len + 1 + inner_len
flat_buf: str = flat_pool.alloc(flat_len + 1)
if flat_buf is None: continue
viperlib.snprintf(flat_buf, flat_len + 1, "%s_%s", cd.name, child.name)
# Rewrite the child name then recurse (everything registers/emits with the flat name)
old_name: str = child.name
child.name = flat_buf
saved_def_sha1: str = self.Gen.StructGen.CurDefModuleSha1
saved_def_cls: str = self.Gen.StructGen.CurDefClassName
self.translate_class_def(trans, child_ast)
self.Gen.StructGen.CurDefModuleSha1 = saved_def_sha1
self.Gen.StructGen.CurDefClassName = saved_def_cls
child.name = old_name
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+6
View File
@@ -0,0 +1,6 @@
from stdint import *
from . import ClassGenBase
# ClassGenMixin - class definition / inheritance / generics / vtable / enum / union / renum.
novirtual class ClassGenMixin(ClassGenBase.ClassGenBase):
"""Class-definition mixin (top level after the stage-D split; methods live in sub-mixins)."""
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-579
View File
@@ -1,579 +0,0 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import viperlib
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesTranslator as HT
import lib.Stub.StubMerger as StubMerger
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.ClassGenBase as ClassGenBase
# ============================================================
# ClassGenDef - ClassGenMixin 拆分(阶段 D):translate_class_def 主入口
#
# 继承 ClassGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesClassDef 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class ClassGenDef(ClassGenBase.ClassGenBase):
"""translate_class_def 主入口"""
# ============================================================
# translate_class_def — 翻译 class 定义(簇 D)
# ============================================================
def translate_class_def(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""翻译 class 定义,返回 0"""
if node is None or trans is None:
return 0
cd: ast.ClassDef | t.CPtr = (ast.ClassDef | t.CPtr)(node)
if cd is None or cd.name is None:
return 0
pool: memhub.MemBuddy | t.CPtr = trans.Pool
class_name: str = cd.name
if trans.ModuleSha1 is None:
# fail-fast:翻译类时模块 SHA1 缺失(命名空间隔离依赖它)——
VLogger.error("翻译类时 ModuleSha1 缺失", "CLASS")
self.Gen.TypeGenErr.fatal_error(cd, "翻译类时 ModuleSha1 缺失", "TypeError")
# 命名空间隔离:标记为当前文件可见(必须在 existing 检查之前,
# 因为 Phase B 时结构体已注册会提前 return,否则永远无法标记本地类)
self.Gen.StructGen.add_visible_struct(pool, class_name)
# 枚举类:交给 HandlesEnum 处理
if self.Gen.ClassGenBase._is_enum_class(cd, trans) == 1:
return self.Gen.ClassGenEnum._translate_enum_def(trans, cd)
# 联合体类:交给联合体处理
if self.Gen.ClassGenBase._is_union_class(cd, trans) == 1:
return self.Gen.ClassGenEnum._translate_union_def(trans, cd)
# REnum 类:交给 REnum 处理({i32 tag, max_payload} 布局)
if self.Gen.ClassGenBase._is_renum_class(cd, trans) == 1:
return self.Gen.ClassGenEnum._translate_renum_def(trans, cd)
# 泛型类:存储为模板,不发射 IR(等实例化时特化)
# 用局部变量接收 cd.type_params,触发类型推断
tp_val: list[str] | t.CPtr = cd.type_params
if self.Gen.ClassGenGeneric._is_generic_class(tp_val) == 1:
self.Gen.ClassGenGeneric._register_generic_template(pool, cd, trans.ModuleSha1)
return 0
# 异常类(try-except):注册编码+继承树,不生成 struct
# (对齐 TPC HandlesClassDef.py L509-511:_is_exception_class →
# _RegisterExceptionClass → return,异常类不编译为结构体)
if self.Gen.ClassGenBase._is_exception_class_def(cd) == 1:
return self.Gen.ClassGenBase._register_exception_class_def(trans, cd)
# 检查是否已注册(用 SHA1 区分跨模块同名类)
existing: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct_by_module(
class_name, trans.ModuleSha1)
if existing is not None:
# BUG 5 方案1修改点2:existing 时字段完整性检查
# 结构体已注册,但需验证字段信息是否完整(防止 i32 回退残留或注册不完整)
# 注意:无字段类(接口/标记类,类体无 AnnAssign)合法(空 struct + 方法),
# 仅当"类体声明了字段但 FieldCount==0"才算注册不完整(Phase A-pre 漏注册)。
if existing.FieldCount == 0 and trans._declare_only == 0:
if self.Gen.ClassGenBase._class_body_has_field(cd) != 0:
# Phase B 时字段数为 0 说明注册不完整(Phase A-pre 可能漏注册)
self.Gen.TypeGenErr.fatal_error(
node,
f"结构体已注册但字段数为0,类: {class_name},可能注册不完整",
"InternalCompilerError")
# 结构体已注册(Phase A-pre 多遍扫描或 Phase B 重新翻译),但仍需在当前模块中翻译方法
if existing.Ty is not None:
# Phase 1b(declare_only=0):existing.Ty 是 Phase 1a 注册的 struct_ty,
# 其 Name(如 "sha1.ClassName")指向 Phase 1a 的 pool 内存。虽然 mark1
# 之前分配不被 reset 回收,但 struct_ty 会被后续文件翻译引用(vtable/
# self 参数类型打印),部分实现下 pool 块被复用导致 Name 悬空崩溃。
# 保险起见:深拷贝 Name 到 stdlib,确保跨文件永久存活。
if trans._declare_only == 0:
# 深拷贝整个 existing.Ty 到 stdlib:1a pool Ty 跨 1b reset 悬空,
# _ensure_field_types_in_module 遍历悬空字段 ParamNode 链表
# 读悬空挂起(G3 LinkedNode.prepend 挂起根因)。DeepCopyType
# 递归深拷贝字段链表/子类型/Name 到 stdlib(深度上限+链守卫)。
ty_deep: llvmlite.LLVMType | t.CPtr = llvmlite.DeepCopyType(
CompilerFactory.get_perm_pool(), existing.Ty)
if ty_deep is not None:
existing.Ty = ty_deep
old_name: str = llvmlite.get_struct_name(existing.Ty)
if old_name is not None:
on_len: t.CSizeT = string.strlen(old_name)
on_copy: str = CompilerFactory.get_perm_pool().alloc(on_len + 1)
if on_copy is not None:
string.strcpy(on_copy, old_name)
c.DerefAs(existing.Ty, llvmlite.LLVMType.Struct(
llvmlite.get_struct_fields(existing.Ty),
llvmlite.get_struct_fcount(existing.Ty),
on_copy))
# Phase B: 将已存在的命名结构体注册到当前模块(输出类型定义行)
llvmlite.module_add_named_type(trans.Module, pool, existing.Ty)
# Phase A-pre(declare_only=1)多遍扫描时,类已注册但不应翻译方法体
# Phase B(declare_only=0)时全量翻译方法体
if trans._declare_only == 1:
self.Gen.ClassGenVTable._translate_oop_methods(trans, cd, existing.Ty, class_name, 1)
else:
self.Gen.ClassGenVTable._translate_oop_methods(trans, cd, existing.Ty, class_name)
# SHA1 已在 register_struct 时设置,无需再补
return 0
# ============================================================
# 触发 base 中 Subscript 的泛型特化(如 GSListNode[Value])
# 必须在 _detect_vtable_status 和父类字段继承之前调用,
# 否则 find_struct_by_module("GSListNode[Value]") 返回 None
# ============================================================
self.Gen.ClassGenBase._trigger_base_specialization(trans, cd)
# ============================================================
# VTable 检测:判断是否启用虚表
# ============================================================
has_vtable: int = self.Gen.ClassGenBase._detect_vtable_status(cd, trans)
parent_name: str = self.Gen.ClassGenBase._get_parent_class(cd, trans)
# 多继承:收集所有真实父类名(跳过 marker base),供字段展平/vtable/MRO 使用
parents_buf: t.CSizeT | t.CPtr = pool.alloc(8 * GenTypes.PARENT_MAX)
parents_count: int = 0
if parents_buf is not None:
parents_count = self.Gen.ClassGenBase._collect_parents(pool, cd, parents_buf, GenTypes.PARENT_MAX, trans)
is_novtable_deco: int = 0
if cd.decorator_list is not None:
if self.Gen.ClassGenBase._has_decorator(cd.decorator_list, "NoVTable") == 1:
is_novtable_deco = 1
# ============================================================
# PhaseA 预注册延迟检查:父类未注册时延迟注册当前类
#
# 根因:AssignHandle(HandlesBase.Mixin) 有自己的字段 _CurrentClass,
# 当 Mixin 未注册时(字母序:HandlesAssign.py 在 HandlesBase.py 之前),
# field_count=1(不是0),field_count==0 静默返回不触发,
# AssignHandle 被注册为只有 _CurrentClass 字段(缺 Trans)。
# 后续遍 existing is not None,不补字段 → attribute ptr is None: self.Trans
#
# 修复:declare_only==1 且父类未注册时,延迟注册到下一遍。
# 多遍 PhaseA-pre 会在后续遍中重试(父类已注册后完整注册)。
# Phase B(declare_only==0)不延迟,避免死锁。
# ============================================================
if trans._declare_only == 1 and parent_name is not None:
parent_check: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct_by_module(
parent_name, trans.ModuleSha1)
if parent_check is None:
parent_check = self.Gen.StructGen.find_struct(parent_name)
if parent_check is None:
return 0
# ============================================================
# 1. 遍历 class body,收集字段信息
# ============================================================
# 用 CSizeT 数组存储指针值(64 位系统上 8 字节)
field_names_buf: t.CSizeT | t.CPtr = pool.alloc(8 * GenTypes.FIELD_MAX)
field_types_buf: t.CSizeT | t.CPtr = pool.alloc(8 * GenTypes.FIELD_MAX)
field_defaults_buf: t.CSizeT | t.CPtr = pool.alloc(8 * GenTypes.FIELD_MAX)
field_annot_buf: t.CSizeT | t.CPtr = pool.alloc(8 * GenTypes.FIELD_MAX)
if field_names_buf is None or field_types_buf is None or field_defaults_buf is None:
return 0
if field_annot_buf is None:
return 0
string.memset(field_names_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_types_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_defaults_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_annot_buf, 0, 8 * GenTypes.FIELD_MAX)
field_count: int = 0
# ============================================================
# 1.5 继承父类字段(字段展平)
# ============================================================
#
# 父类字段排在子类字段之前,结构体布局:
# [vtable_ptr?, parent_field1, ..., child_field1, ...]
# 跳过父类的 __vtable__ 字段(子类会有自己的 vtable 指针)
# ============================================================
# 多继承字段展平:遍历所有父类(声明顺序),字段名去重(先声明优先)
if parents_count > 0 and parents_buf is not None:
for pi in range(parents_count):
pname_addr: t.CSizeT = parents_buf[pi]
if pname_addr == 0:
continue
pname: str = (str | t.CPtr)(t.CVoid(pname_addr, t.CPtr))
if pname is None:
continue
# 用 SHA1 感知查找父类,规避跨模块同名找错
parent_entry: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct_by_module(
pname, trans.ModuleSha1)
if parent_entry is None:
# 父类可能定义在另一个模块(不同 SHA1,如 HandlesBase.Mixin),
# find_struct_by_module 不回退全局查找,此处手动回退到 find_struct,
# 否则子类无法继承父类字段 → field_count==0 → __sizeof__ 失败
parent_entry = self.Gen.StructGen.find_struct(pname)
if parent_entry is None:
# 父类尚未注册(依赖模块未翻译:Phase A 按文件序,子类模块可能
# 先于父类模块,如 Generator.vp G < TypeGenMixin.vp T)。此时
# **不能**继续注册——StructTable 在 StdlibAlloc=1 时用 perm_pool
# 分配(init_struct_table L136-140),残缺条目跨 pass 存活,
# 后续 pass 的 existing 早退(L2995-3006)永远无法修复 → 子类
# __init__ 写父类字段(self.TraceFrames)每遍永久失败。
# 整类 defer(不注册)+ 标记 _deps_pending 下遍重译;模块级
# 变量/函数参数解析失败静默降级 i32(handle_module_level_var
# L422-426 / FuncGenMixin L482-483),不致命,本遍文件不写盘。
if trans is not None:
trans._deps_pending = 1
# [PHASEADBG] 临时诊断:父类未注册即 pending 的根因(用后移除)
stdio.printf("[PHASEADBG] 父类未注册: class=%s parent=%s\n", class_name, pname)
return 0
pfc: int = parent_entry.FieldCount
for pfi in range(pfc):
pfe: GenTypes.FieldEntry | t.CPtr = self.Gen.StructGen._get_field_entry(
parent_entry, pfi)
if pfe is None:
continue
pfn: str = self.Gen.StructGen.get_field_name_ptr(pfe)
if pfn is None:
continue
# 跳过父类的 vtable 指针字段
if string.strcmp(pfn, "__vtable__") == 0:
continue
# 多继承去重:字段名已存在跳过(先声明父类优先)
if self.Gen.ClassGenBase._field_name_exists(field_names_buf, field_count, pfn) != 0:
continue
if field_count < GenTypes.FIELD_MAX:
pft: llvmlite.LLVMType | t.CPtr = self.Gen.StructGen.get_field_type_ptr(pfe)
pfd: ast.AST | t.CPtr = self.Gen.StructGen.get_field_default_ptr(pfe)
pfa: str = self.Gen.StructGen.get_field_annot_class_name(pfe)
field_names_buf[field_count] = t.CSizeT(pfn)
field_types_buf[field_count] = t.CSizeT(pft)
if pfd is not None:
field_defaults_buf[field_count] = t.CSizeT(pfd)
else:
field_defaults_buf[field_count] = 0
if pfa is not None:
stored_val: t.CSizeT = t.CSizeT(pfa)
field_annot_buf[field_count] = stored_val
else:
field_annot_buf[field_count] = 0
field_count += 1
children: list[ast.AST | t.CPtr] | t.CPtr = cd.children
if children is None:
return 0
cn: t.CSizeT = len(children)
for ci in range(cn):
stmt: ast.AST | t.CPtr = children.get(ci)
if stmt is None:
continue
sk: int = stmt.kind()
if sk != ast.ASTKind.AnnAssign: continue
# AnnAssign(target=Name(id), annotation=type, value=...)
aa: ast.AnnAssign | t.CPtr = (ast.AnnAssign | t.CPtr)(stmt)
if aa is None or aa.target is None:
continue
# 获取字段名
if aa.target.kind() != ast.ASTKind.Name:
continue
tgt: ast.Name | t.CPtr = (ast.Name | t.CPtr)(aa.target)
if tgt.id is None:
continue
# 类体 CDefine 常量(任意位置,2026-08-10):注册到全局 CDefine 表。
# 方法内裸名(如 ISINSTANCE_CLASS_MAX)经 translate_name_value 查
# CDefine 表解析为常量值;类体开头的常量(MRO_MAX 等)由早期预注册
# 路径注册,但方法之后的 AnnAssign 不经过那些路径 → 此处统一兜底注册。
if self.Gen.AssignGen.is_cdefine_annotation(aa.annotation) != 0:
if aa.value is not None:
self.Gen.AssignGen._register_cdefine_from_value(pool, tgt.id, aa.value)
# CDefine 字段类型固定 i32,直接收集后跳过通用注解解析:
# resolve_annotation_type 不识别 "CDefine"(map_t_type/CTypedef
# 表均无此名),类体尾部常量(ISINSTANCE_CLASS_MAX 是首个触发
# 该路径的实例)Phase B 会 NameError: 't.CDefine' is not defined。
# 与类头常量(MRO_MAX 等)的 i32 字段语义保持一致。
if field_count < GenTypes.FIELD_MAX:
cdef_ty_l: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
if cdef_ty_l is not None:
field_names_buf[field_count] = t.CSizeT(tgt.id)
field_types_buf[field_count] = t.CSizeT(cdef_ty_l)
if aa.value is not None:
field_defaults_buf[field_count] = t.CSizeT(aa.value)
else:
field_defaults_buf[field_count] = 0
field_count += 1
continue
# 跳过编译期元数据字段(__provides__/__requires__/__require_must__)
# 这些字段只用于 with 上下文的静态可达性检查,不生成运行时代码
# (FakeDuck:_str.__requires__=['__mbuddy__'])。此处解析 list[str]
# 字面量注册到持久元数据表,供 requires 注入 / with provider 查询。
if string.strcmp(tgt.id, "__provides__") == 0:
self.Gen.ClassGenBase._register_class_meta_list(pool, GenTypes.CLASS_META_PROVIDES, class_name, aa.value)
continue
if string.strcmp(tgt.id, "__requires__") == 0:
self.Gen.ClassGenBase._register_class_meta_list(pool, GenTypes.CLASS_META_REQUIRES, class_name, aa.value)
continue
if string.strcmp(tgt.id, "__require_must__") == 0:
self.Gen.ClassGenBase._register_class_meta_list(pool, GenTypes.CLASS_META_REQUIRE_MUST, class_name, aa.value)
continue
# 解析字段类型
field_ty: llvmlite.LLVMType | t.CPtr = self.Gen.TypeGenResolve.resolve_annotation_type(
pool, aa.annotation, trans._imported_modules, trans._from_imports, trans, 0)
# 单参 t.CArray[elem] + 列表字面量 → 静态数组 [N x elem]
# (与局部 AnnAssign 路径一致,对齐 TPC ParseListAnnotation 语义):
# 否则字段保持 Ptr(elem)(如 t.CArray[str] → i8**),
# for x in self.field 会误走指针迭代(元素非整数 → null 终止符
# 检查失败报错)。推断为静态数组后走数组迭代(按长度 N)。
if field_ty is not None:
field_ty = self.Gen.AssignGen._infer_static_array_ty_from_literal(
pool, aa.annotation, aa.value, field_ty)
if field_ty is None:
# BUG 5 方案1修改点1:消除 i32 静默回退
# Phase A-pre(declare_only=1):依赖的类可能尚未注册,延迟到下一遍
# Phase B(declare_only=0):真正的类型解析错误,fast-fail 终止该类翻译
# 不再用 i32 回退(会导致结构体布局错误 + GEP 索引错乱)
# 例外:泛型 Subscript(如 list[AST|t.CPtr])未特化时回退为 Ptr(i8),
# 因为泛型特化可能延迟到构造调用时。AnnotClassName 会携带 mangled name
# 供 translate_attribute 做后续 bitcast。
is_generic_sub: int = 0
if aa.annotation is not None and aa.annotation.kind() == ast.ASTKind.Subscript:
is_generic_sub = 1
if is_generic_sub != 0:
# 泛型未特化:回退为 Ptr(i8),依赖 AnnotClassName 做后续类型恢复
field_ty = llvmlite.Ptr(pool, llvmlite.Int8(pool))
else:
# Phase A-pre(declare_only=1):依赖的类可能尚未注册,
# 静默 return 0 让多遍收敛机制生效(下一遍依赖类已注册即可成功)
if trans._declare_only == 1:
return 0
# Phase A(declare_only=0,Phase2.vp L573 多遍):依赖模块按
# 文件序可能晚于本模块翻译(如 TypeGenMixin.vp G < HandlesType.vp
# H,TraceFrames 注解的 HandlesType.TraceFrame 未注册)→ 标记
# _deps_pending 下遍重译,不在本遍以残缺字段注册结构体。
# 真正的类型错误由 Phase A 收敛检查暴露("Phase A 无法完成
# 翻译: <file>,请检查依赖模块是否已编译/注册"),不静默掩盖。
if trans is not None:
trans._deps_pending = 1
# [PHASEADBG] 临时诊断:字段注解类型未解析即 pending 的根因(用后移除)
dbg_ak: t.CInt = -1
if aa.annotation is not None:
dbg_ak = aa.annotation.kind()
stdio.printf("[PHASEADBG] 字段类型未解析: class=%s field=%s annot_kind=%d\n",
class_name, tgt.id, dbg_ak)
return 0
# 旧行为:fast-fail NameError(已移除——Phase A 依赖未就绪时
# 误报真实错误,中断多遍收敛;真实错误交给收敛检查兜底)
# 存储到数组(指针转 CSizeT)
if field_count < GenTypes.FIELD_MAX:
diag_name_val: t.CSizeT = t.CSizeT(tgt.id)
field_names_buf[field_count] = diag_name_val
field_types_buf[field_count] = t.CSizeT(field_ty)
# 存储默认值 AST 指针(None=无默认值)
if aa.value is not None:
field_defaults_buf[field_count] = t.CSizeT(aa.value)
else:
field_defaults_buf[field_count] = 0
# 提取原始类型注解的类名(联合类型简化为 Ptr(i8) 时回退查找结构体)
# BUG 1 方案3:传 pool 使泛型 Subscript 返回 mangled name(如 "list[ast.AST|t.CPtr]")
if aa.annotation is not None:
annot_cn: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(
aa.annotation, trans._imported_modules, pool, trans._from_imports)
if annot_cn is not None:
diag_cn_val: t.CSizeT = t.CSizeT(annot_cn)
field_annot_buf[field_count] = diag_cn_val
else:
field_annot_buf[field_count] = 0
else:
field_annot_buf[field_count] = 0
field_count += 1
if field_count == 0:
# PhaseA 预注册时,父类可能尚未注册(字母序单遍扫描),
# 导致子类无法继承父类字段 → field_count==0。
# 静默返回,Phase2.py 的多遍 PhaseA-pre 会在后续遍中重试。
if trans._declare_only == 1 and parent_name is not None:
return 0
# 无字段类(接口/标记类):不能整体跳过——类可能只有方法
# (如多继承中的 ShapeM/Vec2Extra),需注册类并翻译方法。
# 空 struct 在 LLVM 中合法({});@t.CVTable 类由下方补 vtable 指针字段。
# ============================================================
# 2. 创建 LLVM StructType
# ============================================================
# 构建 ParamNode 链表
first_node: llvmlite.ParamNode | t.CPtr = None
prev_node: llvmlite.ParamNode | t.CPtr = None
total_field_count: int = field_count
# 如果有虚表,首字段添加 i8* vtable 指针
if has_vtable == 1:
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
vtable_node: llvmlite.ParamNode | t.CPtr = pool.alloc(llvmlite.ParamNode.__sizeof__())
if vtable_node is None:
fb_vn: str = VLogger.fmt_buf()
if fb_vn is not None:
viperlib.snprintf(fb_vn, 1024, "failed to alloc vtable node for %s", class_name)
VLogger.error(fb_vn, "CLASS")
return 0
string.memset(vtable_node, 0, llvmlite.ParamNode.__sizeof__())
vtable_node.Ty = i8_ptr_ty
first_node = vtable_node
prev_node = vtable_node
total_field_count = field_count + 1
for fi in range(field_count):
fty_addr: t.CSizeT = field_types_buf[fi]
if fty_addr == 0:
continue
fty: llvmlite.LLVMType | t.CPtr = (llvmlite.LLVMType | t.CPtr)(t.CVoid(fty_addr, t.CPtr))
# 创建 ParamNode
pnode: llvmlite.ParamNode | t.CPtr = pool.alloc(llvmlite.ParamNode.__sizeof__())
if pnode is None:
continue
string.memset(pnode, 0, llvmlite.ParamNode.__sizeof__())
pnode.Ty = fty
if first_node is None:
first_node = pnode
if prev_node is not None:
prev_node.Next = pnode
prev_node = pnode
# 构造命名结构体类型名: "sha1.ClassName" 或 "ClassName"
# 注意: Phase 1b(declare_only=0)时 struct_ty 会注册到全局表并被后续
# 文件翻译引用(vtable/self 参数类型打印),其 Name 必须跨 1b 每文件
# reset_to(mark1) 存活 → 用 stdlib 深拷贝;Phase 1a 仍用 pool(无需跨文件)。
type_name: str = class_name
if trans.ModuleSha1 is not None:
name_buf: str = pool.alloc(64)
if name_buf is not None:
viperlib.snprintf(name_buf, 64, "%s.%s", trans.ModuleSha1, class_name)
type_name = name_buf
if trans._declare_only == 0 and type_name is not None:
tn_len: t.CSizeT = string.strlen(type_name)
tn_copy: str = CompilerFactory.get_perm_pool().alloc(tn_len + 1)
if tn_copy is not None:
string.strcpy(tn_copy, type_name)
type_name = tn_copy
# 创建 StructType(命名结构体)
struct_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Struct(pool, first_node, total_field_count, type_name)
if struct_ty is None:
fb_st: str = VLogger.fmt_buf()
if fb_st is not None:
viperlib.snprintf(fb_st, 1024, "failed to create StructType for %s", class_name)
VLogger.error(fb_st, "CLASS")
return 0
# Phase 1b(declare_only=0)新注册的 struct_ty 会被注册到全局表并被后续文件
# 翻译引用(vtable/self 参数类型打印/find_struct_by_type 指针匹配)。LLVMType
# 对象和 Fields 链是 pool 分配的,本文件 reset_to(mark1) 后悬空。深拷贝整棵树
# 到 stdlib,并替换 struct_ty 指针,保证 entry.Ty 与调用方持有的 struct_ty
# 指向同一个 stdlib 拷贝(指针匹配不失效)且跨文件永久存活。
# StdlibAlloc=1(Phase2 mark2 后)也必须深拷贝:Phase A-pre-inc(declare_only=1)
# 预注册 includes 时若仍用 pool struct_ty,reset_to(mark2) 后 entry.Ty 悬空 →
# 后续文件字段类型引用(resolve → Ptr(entry.Ty))→ opaque 扫描遍历悬空 fields
# 崩溃(LoopTest _scan_type_for_cross_module_refs 0xFFFF 根因)。
if trans._declare_only == 0 or self.Gen.StructGen.get_stdlib_alloc() != 0:
st_copy: llvmlite.LLVMType | t.CPtr = llvmlite.DeepCopyType(
CompilerFactory.get_perm_pool(), struct_ty)
if st_copy is not None:
struct_ty = st_copy
type_name = llvmlite.get_struct_name(struct_ty)
# 注册命名结构体到模块(输出 %"name" = type { ... } 定义行)
llvmlite.module_add_named_type(trans.Module, pool, struct_ty)
# 扫描字段类型,为跨模块结构体引用添加 opaque 声明
# 例如 list[int].__pool__ 引用 memhub.MemManager,需要在当前模块添加 %"sha1.MemManager" = type opaque
for fi in range(field_count):
fty_addr_op: t.CSizeT = field_types_buf[fi]
if fty_addr_op == 0:
continue
fty_op: llvmlite.LLVMType | t.CPtr = (llvmlite.LLVMType | t.CPtr)(t.CVoid(fty_addr_op, t.CPtr))
llvmlite.module_ensure_opaque_for_type(trans.Module, pool, fty_op)
# ============================================================
# 3. 注册到 HandlesStruct
# ============================================================
entry: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.register_struct(
pool, class_name, struct_ty, trans.ModuleSha1)
if entry is None:
fb_re: str = VLogger.fmt_buf()
if fb_re is not None:
viperlib.snprintf(fb_re, 1024, "failed to register %s", class_name)
VLogger.error(fb_re, "CLASS")
return 0
# 标记 VTable 状态(直接用 entry,规避跨模块同名 find_struct 找错)
if has_vtable == 1:
entry.HasVTable = 1
if is_novtable_deco == 1:
entry.IsNoVTable = 1
if parent_name is not None:
# 1b 阶段父类名是 pool 字符串(本文件 AST/1a 预存),reset 后悬空;
# _translate_oop_methods/_detect_vtable_status 会在后续文件
# find_struct(ParentName) strcmp 解引用 → 用 stdlib 拷贝保证跨文件存活
if self.Gen.StructGen.get_stdlib_alloc() != 0:
pn_len: t.CSizeT = string.strlen(parent_name)
pn_buf: str = CompilerFactory.get_perm_pool().alloc(pn_len + 1)
if pn_buf is not None:
string.strcpy(pn_buf, parent_name)
entry.ParentName = pn_buf
else:
entry.ParentName = parent_name
# 多继承:设置父类数组(供 MRO/字段/vtable 查询,stdlib 深拷贝跨文件存活)
if parents_count > 0 and parents_buf is not None:
self.Gen.StructGen.set_parent_names(pool, class_name, parents_buf, parents_count)
# 添加字段信息
# 如果有虚表,先添加 __vtable__ 字段占位(索引 0),用户字段从索引 1 开始
if has_vtable == 1:
i8_ptr_ty2: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
self.Gen.StructGen.add_field(pool, entry, "__vtable__", i8_ptr_ty2, None, None)
for fi in range(field_count):
fname_addr: t.CSizeT = field_names_buf[fi]
if fname_addr == 0:
continue
fname: str = (str | t.CPtr)(t.CVoid(fname_addr, t.CPtr))
fty_addr2: t.CSizeT = field_types_buf[fi]
if fty_addr2 == 0:
continue
fty2: llvmlite.LLVMType | t.CPtr = (llvmlite.LLVMType | t.CPtr)(t.CVoid(fty_addr2, t.CPtr))
# 恢复默认值 AST 指针
fdef_addr: t.CSizeT = field_defaults_buf[fi]
fdef: ast.AST | t.CPtr = None
if fdef_addr != 0:
fdef = (ast.AST | t.CPtr)(t.CVoid(fdef_addr, t.CPtr))
# 恢复注解类名指针
fannot_addr: t.CSizeT = field_annot_buf[fi]
fannot: str = None
if fannot_addr != 0:
fannot = (str)(t.CVoid(fannot_addr, t.CPtr))
if fname is not None and fty2 is not None:
self.Gen.StructGen.add_field(pool, entry, fname, fty2, fdef, fannot)
# Phase 1a 声明模式:只注册 struct + 设置 OOP 标志,不翻译方法体
if trans._declare_only == 1:
self.Gen.ClassGenVTable._translate_oop_methods(trans, cd, struct_ty, class_name, 1)
return 0
# ============================================================
# 4. OOP 方法处理:扫描 FunctionDef,标记 OOP,翻译方法,生成 __before_init__
# ============================================================
self.Gen.ClassGenVTable._translate_oop_methods(trans, cd, struct_ty, class_name)
return 0
File diff suppressed because it is too large. Load diff
-710
View File
@@ -1,710 +0,0 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import viperlib
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesTranslator as HT
import lib.Stub.StubMerger as StubMerger
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.ClassGenBase as ClassGenBase
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
# 泛型特化递归深度上限(泛型循环套用防护)
# 正常嵌套特化深度 ≤ 3(如 list[list[str]]);64 远超合理值,命中即循环。
# (原 HandlesClassDef/ClassGenMixin 顶层常量,阶段 D 拆分时遗漏,补回)
GENERIC_SPEC_MAX_DEPTH: t.CDefine = 64
# 泛型模板表容量上限(与 GenericClassNames 等 pool 表可容纳的模板数对齐;
# 原 ClassGenMixin 顶层常量,阶段 D 拆分时遗漏,补回)
GENERIC_TEMPLATE_MAX: t.CDefine = 512
# 泛型特化缓存表容量(原 HandlesClassDef.SPEC_CACHE_MAX,阶段 D 拆分遗漏)
SPEC_CACHE_MAX: t.CDefine = 512
# ============================================================
# ClassGenGeneric - ClassGenMixin 拆分(阶段 D):泛型模板注册/特化/克隆
#
# 继承 ClassGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesClassDef 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class ClassGenGeneric(ClassGenBase.ClassGenBase):
"""泛型模板注册/特化/克隆"""
# ============================================================
# 簇 C:泛型模板注册/特化(P5-2 第三阶段迁移,函数体来自
# HandlesClassDef.vp,逻辑零改动)
#
# 说明:
# - _generic_class_* 全局表已迁入 self.GenericClassNames/
# GenericClassNodes/GenericClassSha1s(P5-2 收尾)
# - _spec_* 全局缓存迁移为 self.SpecKeys/SpecNames/SpecCount
# ============================================================
def _is_generic_class(self, tp: list[str] | t.CPtr) -> int:
"""检查 ClassDef 是否有类型参数(泛型类),返回 1=是 / 0=否
接收 type_params 而非 cd,避免 cd.type_params 属性访问
因跨模块类型推断失败返回 None。
"""
if tp is None:
return 0
return 1
def _register_generic_template(self, pool: memhub.MemBuddy | t.CPtr,
cd: ast.ClassDef | t.CPtr,
module_sha1: str) -> None:
"""注册泛型类模板(类名 + ClassDef 节点 + 来源模块 SHA1)"""
if self.GenericClassNames is None:
self.GenericClassNames = list[str](pool, 8)
self.GenericClassNodes = list[ast.ClassDef | t.CPtr](pool, 8)
self.GenericClassSha1s = list[str](pool, 8)
self.GenericClassFiles = list[str](pool, 8)
if self.GenericClassTpParams is None:
self.GenericClassTpParams = CompilerFactory.get_perm_pool().alloc(GENERIC_TEMPLATE_MAX * 8)
if self.GenericClassTpParams is None:
return
string.memset(self.GenericClassTpParams, 0, GENERIC_TEMPLATE_MAX * 8)
# 用局部变量接收 list,触发类型推断(避免字段 __len__ 失败)
names_local: list[str] | t.CPtr = self.GenericClassNames
cn: t.CSizeT = len(names_local)
i: t.CSizeT
for i in range(cn):
nm: str = names_local.get(i)
if nm is not None and string.strcmp(nm, cd.name) == 0:
return
# type_params stdlib 深拷贝:模板节点的 type_params 是 pool 分配的
tp_names_buf: str = self._copy_type_params_stdlib(cd.type_params)
self.GenericClassNames.append(cd.name)
self.GenericClassNodes.append(cd)
self.GenericClassSha1s.append(module_sha1)
# 同时保存模板来源文件路径(错误定位用,见字段注释)
self.GenericClassFiles.append(self.Gen.TypeGenResolve.get_current_file())
if tp_names_buf is not None and cn < GENERIC_TEMPLATE_MAX:
self.GenericClassTpParams[cn] = t.CSizeT(tp_names_buf)
def _copy_type_params_stdlib(self, tp: list[str] | t.CPtr) -> str:
"""将模板 type_params 深拷贝为空格分隔、NUL 结尾的 stdlib 缓冲区
TPC Python 参考实现:type_params = [tp.name for tp in Node.type_params]
(Python 字符串不可变天然跨阶段存活);Viper 的 type_params 是 pool
分配 list,跨 Phase 内容可能被复用污染,必须注册时拷贝到 stdlib。
返回缓冲区指针;tp 为 None(非泛型)或分配失败返回 None。
"""
if tp is None:
return None
tp_n: t.CSizeT = len(tp)
if tp_n == 0:
return None
# 计算总长:各名字长度 + (n-1) 个空格 + NUL
total: t.CSizeT = 1
ti: t.CSizeT
for ti in range(tp_n):
tp_name: str = tp.get(ti)
if tp_name is not None:
total += string.strlen(tp_name) + 1
buf: str = CompilerFactory.get_perm_pool().alloc(total)
if buf is None:
return None
buf[0] = '\0'
for ti in range(tp_n):
tp_name = tp.get(ti)
if tp_name is None:
continue
if ti > 0 and buf[0] != '\0':
string.strcat(buf, " ")
string.strcat(buf, tp_name)
return buf
def _find_generic_template_tp_params(self, pool: memhub.MemBuddy | t.CPtr,
class_name: str) -> list[str] | t.CPtr:
"""查找泛型类模板的类型参数名列表,未找到返回 None
从注册时深拷贝的 stdlib 缓冲区重建当前 pool 的 list[str]
(特化期间 pool 有效,方法与体翻译共用)。与 GenericClassNames 同序。
"""
if self.GenericClassNames is None or class_name is None \
or self.GenericClassTpParams is None:
return None
names_local: list[str] | t.CPtr = self.GenericClassNames
cn: t.CSizeT = len(names_local)
i: t.CSizeT
for i in range(cn):
nm: str = names_local.get(i)
if nm is not None and string.strcmp(nm, class_name) == 0:
if i >= GENERIC_TEMPLATE_MAX:
return None
buf_addr: t.CSizeT = self.GenericClassTpParams[i]
if buf_addr == 0:
return None
buf_tp: str = (str | t.CPtr)(t.CVoid(buf_addr, t.CPtr))
# 空格分隔 → list[str]
# 注意:list[str](pool, 8) 第二个参数是 elem_size(指针 8 字节),
# 不是容量!传 4 会导致 get(i) 偏移算错、append 相互覆盖
# (Pair[T1,T2] 的 seg1 写 data+4 覆盖 seg0 高 4 字节 → strcmp
# 收到 0x15B78D5015B78CB0 混合垃圾崩溃,GenericTest 回归根因)
tp_list: list[str] | t.CPtr = list[str](pool, 8)
if tp_list is None:
return None
seg_start: t.CSizeT = 0
pos: t.CSizeT = 0
blen: t.CSizeT = string.strlen(buf_tp)
while pos <= blen:
if buf_tp[pos] == ' ' or buf_tp[pos] == '\0':
seg_len: t.CSizeT = pos - seg_start
if seg_len > 0:
seg: str = pool.alloc(seg_len + 1)
if seg is not None:
string.strncpy(seg, buf_tp + seg_start, seg_len)
seg[seg_len] = '\0'
tp_list.append(seg)
seg_start = pos + 1
pos += 1
return tp_list
return None
def _find_generic_template(self, class_name: str) -> ast.ClassDef | t.CPtr:
"""查找泛型类模板,返回 ClassDef 节点或 None"""
if self.GenericClassNames is None or class_name is None:
return None
# 用局部变量接收 list,触发类型推断(避免字段 __len__ 失败)
names_local: list[str] | t.CPtr = self.GenericClassNames
nodes_local: list[ast.ClassDef | t.CPtr] | t.CPtr = self.GenericClassNodes
cn: t.CSizeT = len(names_local)
i: t.CSizeT
for i in range(cn):
nm: str = names_local.get(i)
if nm is not None and string.strcmp(nm, class_name) == 0:
node: ast.ClassDef | t.CPtr = nodes_local.get(i)
return node
return None
def _find_generic_template_sha1(self, class_name: str) -> str:
"""查找泛型类模板的来源模块 SHA1,未找到返回 None"""
if self.GenericClassNames is None or class_name is None:
return None
# 用局部变量接收 list,触发类型推断
names_local: list[str] | t.CPtr = self.GenericClassNames
sha1s_local: list[str] | t.CPtr = self.GenericClassSha1s
cn: t.CSizeT = len(names_local)
i: t.CSizeT
for i in range(cn):
nm: str = names_local.get(i)
if nm is not None and string.strcmp(nm, class_name) == 0:
sha1_val: str = sha1s_local.get(i)
return sha1_val
return None
def _find_generic_template_file(self, class_name: str) -> str:
"""查找泛型类模板的来源文件路径,未找到返回 None
与 _find_generic_template_sha1 同表同序;供特化翻译模板方法体时
临时切换 CurrentFile(错误定位:模板 node.lineno 需配模板文件路径)。
"""
if self.GenericClassNames is None or class_name is None:
return None
names_local: list[str] | t.CPtr = self.GenericClassNames
files_local: list[str] | t.CPtr = self.GenericClassFiles
cn: t.CSizeT = len(names_local)
i: t.CSizeT
for i in range(cn):
nm: str = names_local.get(i)
if nm is not None and string.strcmp(nm, class_name) == 0:
f_val: str = files_local.get(i)
return f_val
return None
def _mangle_generic_class_name(self, pool: memhub.MemBuddy | t.CPtr,
class_name: str,
type_args: list[str] | t.CPtr) -> str:
"""生成特化类名: list + [int] -> list[int]
分配新缓冲区拼接,避免原地 strcat 导致缓冲区溢出
"""
if class_name is None or type_args is None or pool is None:
return class_name
n: t.CSizeT = len(type_args)
if n == 0:
return class_name
# 计算总长度: class_name + "[" + arg0 + "," + arg1 + ... + "]" + NUL
total_len: t.CSizeT = string.strlen(class_name) + 2 # "[" 和 "]"
i: t.CSizeT
for i in range(n):
if i > 0:
total_len += 1 # ","
ta: str = type_args.get(i)
if ta is not None:
total_len += string.strlen(ta)
total_len += 1 # NUL
# 分配新缓冲区
mangled: str = pool.alloc(total_len)
if mangled is None:
return class_name
mangled[0] = '\0'
string.strcat(mangled, class_name)
string.strcat(mangled, "[")
for i in range(n):
if i > 0:
string.strcat(mangled, ",")
ta: str = type_args.get(i)
if ta is not None:
string.strcat(mangled, ta)
string.strcat(mangled, "]")
return mangled
def _clone_annotation(self, pool: memhub.MemBuddy | t.CPtr,
node: ast.AST | t.CPtr,
tp_names: list[str] | t.CPtr,
type_args: list[str] | t.CPtr) -> ast.AST | t.CPtr:
"""拷贝类型注解表达式,替换类型参数 T 为具体类型名
tp_names: 类型参数名列表 ['T']
type_args: 类型实参名列表 ['int']
"""
if node is None:
return None
k: int = node.kind()
# Name: 可能是类型参数 T
if k == ast.ASTKind.Name:
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(node)
if nm.id is not None:
n: t.CSizeT = len(tp_names) if tp_names is not None else 0
i: t.CSizeT
for i in range(n):
tpn: str = tp_names.get(i)
if tpn is not None and string.strcmp(nm.id, tpn) == 0:
ta: str = type_args.get(i)
# 类型实参可能是复合类型(如 "AST|t.CPtr"),用 _parse_type_string 解析
parsed_node: ast.AST | t.CPtr = self.Gen.TypeGenResolve._parse_type_string(pool, ta, nm.ctx)
return parsed_node
return ast.Name(pool, nm.id, nm.ctx)
return None
# Attribute: t.CPtr 等
if k == ast.ASTKind.Attribute:
at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(node)
new_value: ast.AST | t.CPtr = self._clone_annotation(pool, at.value, tp_names, type_args)
return ast.Attribute(pool, new_value, at.attr, at.ctx)
# BinOp: T | t.CPtr 等
if k == ast.ASTKind.BinOp:
bop: ast.BinOp | t.CPtr = (ast.BinOp | t.CPtr)(node)
new_left: ast.AST | t.CPtr = self._clone_annotation(pool, bop.left, tp_names, type_args)
new_right: ast.AST | t.CPtr = self._clone_annotation(pool, bop.right, tp_names, type_args)
return ast.BinOp(pool, new_left, bop.op, new_right)
# Subscript: list[int] 等
if k == ast.ASTKind.Subscript:
sub: ast.Subscript | t.CPtr = (ast.Subscript | t.CPtr)(node)
new_value2: ast.AST | t.CPtr = self._clone_annotation(pool, sub.value, tp_names, type_args)
new_slice: ast.AST | t.CPtr = self._clone_annotation(pool, sub.slice, tp_names, type_args)
return ast.Subscript(pool, new_value2, new_slice, sub.ctx)
# Tuple: (T, int) 等
if k == ast.ASTKind.Tuple:
tup: ast.Tuple | t.CPtr = (ast.Tuple | t.CPtr)(node)
new_elts: list[ast.AST | t.CPtr] | t.CPtr = list[ast.AST | t.CPtr](pool, 8)
if tup.elts is not None:
tup_elts: list[ast.AST | t.CPtr] | t.CPtr = tup.elts
en: t.CSizeT = len(tup_elts)
ei: t.CSizeT
for ei in range(en):
el: ast.AST | t.CPtr = tup_elts.get(ei)
new_el: ast.AST | t.CPtr = self._clone_annotation(pool, el, tp_names, type_args)
if new_el is not None:
new_elts.append(new_el)
return ast.Tuple(pool, new_elts, tup.ctx)
# Constant: 默认值等
if k == ast.ASTKind.Constant:
cnst: ast.Constant | t.CPtr = (ast.Constant | t.CPtr)(node)
return ast.Constant(pool, cnst.const_kind, cnst.int_val, cnst.float_val,
cnst.str_val, cnst.lineno, cnst.col_offset)
# 其他: 直接引用(不拷贝)
return node
def _clone_arguments(self, pool: memhub.MemBuddy | t.CPtr,
args: ast.AST | t.CPtr,
tp_names: list[str] | t.CPtr,
type_args: list[str] | t.CPtr) -> ast.AST | t.CPtr:
"""拷贝 Arguments 节点,替换 arg annotation 中的 T"""
if args is None:
return None
if args.kind() != ast.ASTKind.Arguments:
return args
old_args: ast.Arguments | t.CPtr = (ast.Arguments | t.CPtr)(args)
# 拷贝 args 列表,替换每个 Arg 的 annotation
new_arg_list: list[ast.AST | t.CPtr] | t.CPtr = list[ast.AST | t.CPtr](pool, 8)
if old_args.args is not None:
old_arg_list: list[ast.AST | t.CPtr] | t.CPtr = old_args.args
an: t.CSizeT = len(old_arg_list)
ai: t.CSizeT
for ai in range(an):
arg_node: ast.AST | t.CPtr = old_arg_list.get(ai)
if arg_node is None:
continue
if arg_node.kind() == ast.ASTKind.Arg:
ag: ast.Arg | t.CPtr = (ast.Arg | t.CPtr)(arg_node)
new_anno: ast.AST | t.CPtr = self._clone_annotation(pool, ag.annotation, tp_names, type_args)
new_arg: ast.Arg | t.CPtr = ast.Arg(pool, ag.arg, new_anno)
new_arg_list.append(new_arg)
else:
new_arg_list.append(arg_node)
return ast.Arguments(pool, new_arg_list, old_args.vararg, old_args.kwarg,
old_args.defaults, old_args.kw_defaults)
def _clone_classdef_for_spec(self, pool: memhub.MemBuddy | t.CPtr,
cd: ast.ClassDef | t.CPtr,
spec_name: str,
tp_names: list[str] | t.CPtr,
type_args: list[str] | t.CPtr) -> ast.ClassDef | t.CPtr:
"""拷贝 ClassDef 节点用于泛型特化
- 替换类名为 spec_name
- 清除 type_params(特化后不是泛型类)
- 拷贝 children 中的 AnnAssign 和 FunctionDef,替换类型注解中的 T
- 方法体直接引用原始节点(通过 type_map 上下文处理 T)
"""
# 创建新 ClassDef(type_params=None 表示非泛型)
new_cd: ast.ClassDef | t.CPtr = ast.ClassDef(pool, spec_name, cd.bases, cd.keywords,
cd.decorator_list, None)
# 拷贝 children
if cd.children is not None:
cd_children: list[ast.AST | t.CPtr] | t.CPtr = cd.children
cn: t.CSizeT = len(cd_children)
ci: t.CSizeT
for ci in range(cn):
child: ast.AST | t.CPtr = cd_children.get(ci)
if child is None:
continue
ck: int = child.kind()
if ck == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign | t.CPtr = (ast.AnnAssign | t.CPtr)(child)
new_target: ast.AST | t.CPtr = aa.target
new_anno2: ast.AST | t.CPtr = self._clone_annotation(pool, aa.annotation, tp_names, type_args)
new_value3: ast.AST | t.CPtr = aa.value
new_aa: ast.AnnAssign | t.CPtr = ast.AnnAssign(pool, new_target, new_anno2, new_value3, aa.simple)
new_cd.append(new_aa)
elif ck == ast.ASTKind.FunctionDef:
fd: ast.FunctionDef | t.CPtr = (ast.FunctionDef | t.CPtr)(child)
new_args2: ast.AST | t.CPtr = self._clone_arguments(pool, fd.args, tp_names, type_args)
new_returns2: ast.AST | t.CPtr = self._clone_annotation(pool, fd.returns, tp_names, type_args)
new_fd: ast.FunctionDef | t.CPtr = ast.FunctionDef(pool, fd.name, new_args2,
fd.decorator_list, new_returns2, 0)
# 方法体直接引用原始节点
if fd.children is not None:
fd_children: list[ast.AST | t.CPtr] | t.CPtr = fd.children
bcn: t.CSizeT = len(fd_children)
bci: t.CSizeT
for bci in range(bcn):
body_stmt: ast.AST | t.CPtr = fd_children.get(bci)
if body_stmt is not None:
new_fd.append(body_stmt)
new_cd.append(new_fd)
else:
new_cd.append(child)
return new_cd
def _find_cached_spec(self, spec_key: str) -> str:
"""查找已缓存的特化名,未找到返回 None"""
if self.SpecKeys is None or spec_key is None:
return None
i: int
for i in range(self.SpecCount):
key_addr: t.CSizeT = self.SpecKeys[i]
if key_addr != 0:
k: str = (str | t.CPtr)(t.CVoid(key_addr, t.CPtr))
if k is not None and string.strcmp(k, spec_key) == 0:
name_addr: t.CSizeT = self.SpecNames[i]
if name_addr != 0:
return (str | t.CPtr)(t.CVoid(name_addr, t.CPtr))
return None
def _cache_spec(self, pool: memhub.MemBuddy | t.CPtr, spec_key: str, spec_name: str):
"""缓存特化结果(stdlib 深拷贝存储,跨文件安全)"""
if spec_key is None or spec_name is None:
return
if self.SpecKeys is None:
self.SpecKeys = CompilerFactory.get_perm_pool().alloc(SPEC_CACHE_MAX * 8)
self.SpecNames = CompilerFactory.get_perm_pool().alloc(SPEC_CACHE_MAX * 8)
if self.SpecKeys is None or self.SpecNames is None:
return
string.memset(self.SpecKeys, 0, SPEC_CACHE_MAX * 8)
string.memset(self.SpecNames, 0, SPEC_CACHE_MAX * 8)
self.SpecCount = 0
if self.SpecCount >= SPEC_CACHE_MAX:
return
# 深拷贝字符串到 stdlib 区
key_len: t.CSizeT = string.strlen(spec_key)
name_len: t.CSizeT = string.strlen(spec_name)
key_copy: str = CompilerFactory.get_perm_pool().alloc(key_len + 1)
name_copy: str = CompilerFactory.get_perm_pool().alloc(name_len + 1)
if key_copy is None or name_copy is None:
return
string.strcpy(key_copy, spec_key)
string.strcpy(name_copy, spec_name)
self.SpecKeys[self.SpecCount] = t.CSizeT(key_copy)
self.SpecNames[self.SpecCount] = t.CSizeT(name_copy)
self.SpecCount += 1
def _merge_template_imports(self, trans: HT.Translator | t.CPtr,
template_sha1: str) -> None:
"""将模板模块的 imports 并入 trans._imported_modules(空格分隔)"""
if trans is None or template_sha1 is None:
return
tdeps: str = StubMergerStore.LookupIncludesDeps(template_sha1)
if tdeps is None or tdeps[0] == '\0':
return
cur: str = trans._imported_modules
if cur is None or cur[0] == '\0':
trans._imported_modules = tdeps
return
cur_len: t.CSizeT = string.strlen(cur)
td_len: t.CSizeT = string.strlen(tdeps)
merged: str = trans.Pool.alloc(cur_len + td_len + 2)
if merged is None:
return
string.strcpy(merged, cur)
merged[cur_len] = ' '
string.strcpy(merged + cur_len + 1, tdeps)
trans._imported_modules = merged
def _record_spec_trigger(self, pool: memhub.MemBuddy | t.CPtr,
spec_name: str, trigger_sha1: str) -> int:
"""记录泛型特化类的触发文件 sha1(StructEntry.SpecTriggerSha1)
特化类 ModuleSha1=模板 sha1(方法名带模板前缀),Phase A _deps_pending
作废重译时 reset_module_methods_emitted 按当前文件 sha1 匹配不到 →
特化方法体 MethodsEmitted 残留 1 → 下遍不补译 → 链接 undefined symbol。
记录触发文件 sha1 后按此精确重置(见 StructGenMixin.reset_module_methods_emitted)。
"""
if spec_name is None or trigger_sha1 is None:
return 0
entry_rt: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct(spec_name)
if entry_rt is None:
return 0
if string.strcmp(entry_rt.Name, spec_name) != 0:
return 0
ts_len_r: t.CSizeT = string.strlen(trigger_sha1)
ts_buf_r: str = CompilerFactory.get_perm_pool().alloc(ts_len_r + 1)
if ts_buf_r is None:
return 0
string.strcpy(ts_buf_r, trigger_sha1)
entry_rt.SpecTriggerSha1 = ts_buf_r
return 1
def _specialize_generic_class(self, trans: HT.Translator | t.CPtr,
class_name: str,
type_args: list[str] | t.CPtr) -> str:
"""特化泛型类,返回特化类名
1. 查找泛型模板
2. 生成特化名
3. 检查缓存
4. 拷贝 ClassDef 节点(替换类型注解中的 T)
5. 设置 type_map 上下文(用于方法体中的 T 替换)
6. 调用 translate_class_def 翻译特化类
7. 返回特化名
"""
if trans is None or class_name is None or type_args is None:
return None
# 泛型循环套用防护:递归深度 +1,超过上限立即报错终止(防 F-bounded
# 等循环特化把编译变死循环)。所有返回路径都必须 -1(见各 return 前)。
self.GenericSpecDepth += 1
# 特化触发文件 sha1:进入函数时 trans.ModuleSha1 是触发方文件,
# 翻译特化类时会被切换为模板 sha1(见下方),作废重译时按此重置
# 特化类 MethodsEmitted(见 _record_spec_trigger / reset_module_methods_emitted)
spec_trigger_sha1: str = trans.ModuleSha1
if self.GenericSpecDepth > GENERIC_SPEC_MAX_DEPTH:
err_spec: str = trans.Pool.alloc(256)
if err_spec is not None:
viperlib.snprintf(err_spec, 256,
"泛型特化递归过深(疑似泛型循环套用),类: %s 深度=%d",
class_name, self.GenericSpecDepth)
self.Gen.TypeGenErr.fatal_error(None, err_spec, "InternalCompilerError")
self.Gen.TypeGenErr.fatal_error(None, "泛型特化递归过深(疑似泛型循环套用)", "InternalCompilerError")
# 防御:泛型上下文内收集的 type_args 若残留未替换的类型参数名(如 "T"),
# 统一替换为具体类型名,避免 ndarray[T] 二次递归特化崩溃。
# 调用方传入的 type_args 均为临时新建列表,就地替换无共享副作用。
ta_cnt_g: t.CSizeT = len(type_args)
ta_i_g: t.CSizeT
for ta_i_g in range(ta_cnt_g):
ta_raw_g: str = type_args.get(ta_i_g)
if ta_raw_g is not None:
ta_rsv_g: str = self.Gen.TypeGenBase.resolve_generic_type_name(ta_raw_g)
if ta_rsv_g is not None and string.strcmp(ta_raw_g, ta_rsv_g) != 0:
type_args.set(ta_i_g, ta_rsv_g)
pool: memhub.MemBuddy | t.CPtr = trans.Pool
# 1. 查找泛型模板
template_cd: ast.ClassDef | t.CPtr = self._find_generic_template(class_name)
if template_cd is None:
self.GenericSpecDepth -= 1
return None
# 2. 生成特化名
spec_name: str = self._mangle_generic_class_name(pool, class_name, type_args)
if spec_name is None:
self.GenericSpecDepth -= 1
return None
# 治本修复:Phase B 补译泛型特化类方法体(统一入口,在 cache/existing 检查之前)
# Phase 1a(declare_only==1)特化时只注册结构体不译方法体(MethodsEmitted==0),
# Phase B(declare_only==0)重复进入时若方法体未译,补译方法体生成 define,
# 避免 GSList[Line].append 等符号只有 declare 无 define → 链接失败。
# 首次 Phase B 特化(Phase 1a 未特化)find_struct 返回 None,不补译,走下方正常全量翻译。
if trans._declare_only == 0:
pre_existing: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct(spec_name)
if pre_existing is not None and pre_existing.MethodsEmitted == 0:
# 防重入:先标记 MethodsEmitted=1,避免补译过程中方法体/返回类型注解
# 再次触发 _specialize_generic_class(如 __iter__ 返回 list[T]|t.CPtr)
# 导致无限递归补译 → OOM。补译完成后已设置,无需恢复。
# 注意:此处提前置位后调用 _translate_oop_methods 必须传 reemit=1
# (补译模式),否则其 MethodsEmitted==1 防重复检查会拦截补译,
# 方法体永不发射 → 特化方法只有 call 无 define → 链接 undefined symbol。
pre_existing.MethodsEmitted = 1
# 从 stdlib 深拷贝表取 type_params(模板节点 pool list 跨 Phase 可能失效)
tp_names_pre: list[str] | t.CPtr = self._find_generic_template_tp_params(
pool, class_name)
if tp_names_pre is None:
tp_names_pre = template_cd.type_params
spec_cd_pre: ast.ClassDef | t.CPtr = self._clone_classdef_for_spec(
pool, template_cd, spec_name, tp_names_pre, type_args)
if spec_cd_pre is not None:
trans.GenericTypeParamNames = tp_names_pre
trans.GenericTypeArgs = type_args
self.Gen.TypeGenBase.set_generic_context(tp_names_pre, type_args)
saved_ms_pre: str = trans.ModuleSha1
saved_imp_pre: str = trans._imported_modules
template_sha1_pre: str = self._find_generic_template_sha1(class_name)
if template_sha1_pre is not None:
trans.ModuleSha1 = template_sha1_pre
self._merge_template_imports(trans, template_sha1_pre)
# 错误定位上下文:补译的是模板方法体(spec_cd_pre 克隆自模板,
# node.lineno 是模板文件行号)。切换 CurrentFile 到模板文件并压入
# trace 帧,使 fatal_error 正确显示模板文件位置(翻译完恢复)。
saved_cur_pre: str = self.Gen.TypeGenResolve.get_current_file()
tpl_file_pre: str = self._find_generic_template_file(class_name)
if tpl_file_pre is not None:
self.Gen.TypeGenResolve.set_current_file(tpl_file_pre)
self.Gen.TypeGenBase.push_trace(tpl_file_pre, template_cd.lineno)
self.Gen.ClassGenVTable._translate_oop_methods(trans, spec_cd_pre, pre_existing.Ty, spec_name, 0, 1)
if tpl_file_pre is not None:
self.Gen.TypeGenBase.pop_trace()
self.Gen.TypeGenResolve.set_current_file(saved_cur_pre)
trans.ModuleSha1 = saved_ms_pre
trans._imported_modules = saved_imp_pre
trans.GenericTypeParamNames = None
trans.GenericTypeArgs = None
self.Gen.TypeGenBase.clear_generic_context()
# 3. 检查缓存(spec_key 包含所有类型实参,避免多参数泛型缓存冲突)
# 分配新缓冲区避免原地 strcat 溢出
sk_n: t.CSizeT = len(type_args)
sk_total: t.CSizeT = string.strlen(class_name) + 2 # "<" 和 ">"
sk_i: t.CSizeT
for sk_i in range(sk_n):
if sk_i > 0:
sk_total += 1 # ","
sk_ta: str = type_args.get(sk_i)
if sk_ta is not None:
sk_total += string.strlen(sk_ta)
sk_total += 1 # NUL
spec_key: str = pool.alloc(sk_total)
if spec_key is None:
self.GenericSpecDepth -= 1
return None
spec_key[0] = '\0'
string.strcat(spec_key, class_name)
string.strcat(spec_key, "<")
for sk_i in range(sk_n):
if sk_i > 0:
string.strcat(spec_key, ",")
sk_ta: str = type_args.get(sk_i)
if sk_ta is not None:
string.strcat(spec_key, sk_ta)
string.strcat(spec_key, ">")
cached: str = self._find_cached_spec(spec_key)
if cached is not None:
self._record_spec_trigger(pool, spec_name, spec_trigger_sha1)
self.GenericSpecDepth -= 1
return cached
# 4. 检查是否已注册(Phase B 重复特化)
existing: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct(spec_name)
if existing is not None:
self._cache_spec(pool, spec_key, spec_name)
self._record_spec_trigger(pool, spec_name, spec_trigger_sha1)
self.GenericSpecDepth -= 1
return spec_name
# 预注册缓存(防止递归)
self._cache_spec(pool, spec_key, spec_name)
# 5. 拷贝 ClassDef 节点
# 从 stdlib 深拷贝表取 type_params(模板节点 pool list 跨 Phase 可能失效)
tp_names: list[str] | t.CPtr = self._find_generic_template_tp_params(
pool, class_name)
if tp_names is None:
tp_names = template_cd.type_params
spec_cd: ast.ClassDef | t.CPtr = self._clone_classdef_for_spec(
pool, template_cd, spec_name, tp_names, type_args)
if spec_cd is None:
self.GenericSpecDepth -= 1
return None
# 6. 设置 type_map 上下文(用于方法体中的 T 替换)
trans.GenericTypeParamNames = tp_names
trans.GenericTypeArgs = type_args
self.Gen.TypeGenBase.set_generic_context(tp_names, type_args)
# 7. 翻译特化类
# 切换 trans.ModuleSha1 为泛型模板定义模块的 SHA1,
# 使结构体类型名与特化方法函数符号名都用定义模块的 SHA1 前缀
# (跨模块一致,单态化特化名 = {模板SHA1}.Class[param].method)。
# 同时并入模板模块的 imports(见 _merge_template_imports 注释),
# 避免模板方法体内的跨模块调用被误报"模块未导入"。
saved_module_sha1: str = trans.ModuleSha1
saved_imported: str = trans._imported_modules
template_sha1: str = self._find_generic_template_sha1(class_name)
if template_sha1 is not None:
trans.ModuleSha1 = template_sha1
self._merge_template_imports(trans, template_sha1)
# 错误定位上下文:spec_cd 克隆自模板,方法体的 node.lineno 是模板文件
# 行号,而 CurrentFile 仍是调用方文件 → 错误显示错位。切换 CurrentFile
# 到模板文件并压入 trace 帧(翻译完恢复),使 fatal_error 正确溯源。
saved_cur_full: str = self.Gen.TypeGenResolve.get_current_file()
tpl_file_full: str = self._find_generic_template_file(class_name)
if tpl_file_full is not None:
self.Gen.TypeGenResolve.set_current_file(tpl_file_full)
self.Gen.TypeGenBase.push_trace(tpl_file_full, template_cd.lineno)
self.Gen.ClassGenDef.translate_class_def(trans, spec_cd)
if tpl_file_full is not None:
self.Gen.TypeGenBase.pop_trace()
self.Gen.TypeGenResolve.set_current_file(saved_cur_full)
# 恢复原始 ModuleSha1 与 imports
trans.ModuleSha1 = saved_module_sha1
trans._imported_modules = saved_imported
# 8. 清除 type_map 上下文
trans.GenericTypeParamNames = None
trans.GenericTypeArgs = None
self.Gen.TypeGenBase.clear_generic_context()
# 特化结果字段名完整性检查(fail-fast:字段名缺失 = 字段表损坏,
# 静默继续会让后续 GEP 按错索引访问)
if string.strcmp(class_name, "GSListNode") == 0:
result_entry: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct(spec_name)
if result_entry is not None:
ri: int
for ri in range(result_entry.FieldCount):
rfe: GenTypes.FieldEntry | t.CPtr = self.Gen.StructGen._get_field_entry(
result_entry, ri)
if rfe is not None:
rfn: str = self.Gen.StructGen.get_field_name_ptr(rfe)
if rfn is None:
VLogger.error("SPEC 字段名缺失(字段表损坏)", "SPEC")
self.Gen.TypeGenErr.fatal_error(None, "SPEC 字段名缺失(字段表损坏)", "TypeError")
self._record_spec_trigger(pool, spec_name, spec_trigger_sha1)
self.GenericSpecDepth -= 1
return spec_name
-25
View File
@@ -1,25 +0,0 @@
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import viperlib
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesTranslator as HT
import lib.Stub.StubMerger as StubMerger
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.ClassGenBase as ClassGenBase
# ============================================================
# ClassGenMixin - 类定义/继承/泛型/vtable/enum/union/renum
#
# 500~1500 行/文件架构重构阶段 D:全部方法已拆至
# ClassGenBase/Enum/Generic/VTable/Def,本文件保留类声明。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class ClassGenMixin(ClassGenBase.ClassGenBase):
"""类定义处理 Mixin(阶段 D 拆分后顶层,方法全部在子 Mixin)"""
File diff suppressed because it is too large. Load diff
+378
View File
@@ -0,0 +1,378 @@
from stdint import *
import mlirlite
import memhub
import string
import stdlib
import viperlib
import win32.win32file as win32file
import lib.core.VLogger as VLogger
# DebugInfoGen - emits function-level DWARF metadata (mlirlite text IR), mirroring TPC LLVMCG/DebugInfo.py node-for-node.
# Active only under --traceback (SetEnabled); hooks come from the Gen layer. When disabled every entry point returns before touching DI APIs, keeping IR byte-identical.
# ---- module-scope state (reset per module by BeginModule) ----
_g_enabled: int = 0
_g_finalized: int = 0
_g_mod: mlirlite.LLVMModule* = None
_g_file_path: str = None
_g_abs_file_path: str = None
_g_file_id: int = -1
_g_null_id: int = -1
_g_subtype_id: int = -1
_g_cu_id: int = -1
_g_cur_sp: int = -1
_g_cur_entry_ref: str = None
_g_cur_builder: mlirlite.IRBuilder* = None
_g_depth: int = 0
# Push/Pop save stack: enclosing (sp id + 1, entry DILocation ref, builder) per level; +1 lets 0 mean "no enclosing function".
# The saved builder makes SetStatement reject a builder not pushed by the current DISubprogram (e.g. an async state machine in a bracketed function); such instrs must carry no !dbg or LLVM's verifier fails.
define DBG_STACK_MAX = 256
_g_stk_sp: sizet* = None
_g_stk_ref: str = None
_g_stk_bld: str = None
# DILocation cache: direct-mapped, up to 8-way linear probe, key (sp, line, col).
define DBG_LOC_SLOTS = 8192
define DBG_LOC_PROBE = 8
define DBG_LOC_KEY_STRIDE = 3
_g_loc_key: sizet* = None
_g_loc_ref: str = None
_g_loc_used: bytes = None
# SetEnabled / IsEnabled - mirror the --traceback switch (flag -> _g_enabled, forwarded to mlirlite's debug call-site recorder).
def SetEnabled(flag: bool) -> int:
"""Enable/disable DI emission (mirrors BuildPipeline._traceback_mode)."""
global _g_enabled
if flag: _g_enabled = 1
else: _g_enabled = 0
# Forward the same switch to mlirlite's debug call-site recorder (under --traceback we emit a <sha1>.vpcall side file for WriteStackMap+objdump).
mlirlite.DebugSetEnabled(flag)
return 0
def IsEnabled() -> int:
return _g_enabled
# _EnsureStorage - lazily allocate the long-lived stack / cache buffers.
def _EnsureStorage() -> int:
global _g_stk_sp, _g_stk_ref, _g_stk_bld, _g_loc_key, _g_loc_ref, _g_loc_used
if _g_stk_sp is not None: return 0
sbuf1: bytes = stdlib.malloc(DBG_STACK_MAX * 8)
sbuf2: bytes = stdlib.malloc(DBG_STACK_MAX * 8)
sbuf3: bytes = stdlib.malloc(DBG_STACK_MAX * 8)
kbuf: bytes = stdlib.malloc(DBG_LOC_SLOTS * DBG_LOC_KEY_STRIDE * 8)
rbuf: bytes = stdlib.malloc(DBG_LOC_SLOTS * 8)
ubuf: bytes = stdlib.malloc(DBG_LOC_SLOTS)
if sbuf1 is None or sbuf2 is None or sbuf3 is None or kbuf is None or rbuf is None or ubuf is None:
VLogger.error("--traceback: failed to allocate DWARF metadata buffer, aborting compilation", "DWARF")
return 1
string.memset(sbuf1, 0, DBG_STACK_MAX * 8)
string.memset(sbuf2, 0, DBG_STACK_MAX * 8)
string.memset(sbuf3, 0, DBG_STACK_MAX * 8)
string.memset(kbuf, 0, DBG_LOC_SLOTS * DBG_LOC_KEY_STRIDE * 8)
string.memset(rbuf, 0, DBG_LOC_SLOTS * 8)
string.memset(ubuf, 0, DBG_LOC_SLOTS)
_g_stk_sp = (sizet*)(sbuf1)
_g_stk_ref = sbuf2
_g_stk_bld = sbuf3
_g_loc_key = (sizet*)(kbuf)
_g_loc_ref = rbuf
_g_loc_used = ubuf
return 0
def _RefOf(pool: memhub.MemBuddy*, node_id: int) -> str:
"""Allocate a persistent "!N" reference string for a DI node id."""
buf: str = pool.alloc(24)
if buf is None: return None
viperlib.snprintf(buf, 24, "!%d", node_id)
return buf
# BeginModule - start of one module translation.
def BeginModule(mod: mlirlite.LLVMModule*, file_path: str) -> int:
"""Reset per-module DI state. Call once at the start of each module."""
global _g_mod, _g_file_path, _g_abs_file_path, _g_finalized, _g_file_id, _g_null_id
global _g_subtype_id, _g_cu_id, _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth
if _g_enabled == 0: return 0
if _EnsureStorage() != 0: return 1
_g_mod = mod
_g_file_path = file_path
_g_abs_file_path = None
# Module-boundary fallback: clear any un-Pop'd debug function context (mid-translation failure) so the previous module's caller is not attributed here.
mlirlite.DebugResetFunc()
_g_finalized = 0
_g_file_id = -1
_g_null_id = -1
_g_subtype_id = -1
_g_cu_id = -1
_g_cur_sp = -1
_g_cur_entry_ref = None
_g_cur_builder = None
_g_depth = 0
if _g_loc_used is not None: string.memset(_g_loc_used, 0, DBG_LOC_SLOTS)
return 0
# _EnsureModuleNodes - lazily create DIFile / !{ null } / DISubroutineType / DICompileUnit at the first define (once per module).
def _EnsureModuleNodes(pool: memhub.MemBuddy*) -> int:
global _g_file_id, _g_null_id, _g_subtype_id, _g_cu_id, _g_abs_file_path
if _g_cu_id >= 0: return 0
mod: mlirlite.LLVMModule* = _g_mod
if mod is None: return 1
# DIFile: absolute path (forward slashes), filename keeps the whole path.
# Must be absolute: relative paths make LLVM's DWARF line records degrade to a bare "main.vp" that runtime cannot reload; absolutizing works whether LLVM keeps the full name or splits dir+basename.
src: str = _g_file_path
if src is None: src = ""
slen: sizet = string.strlen(src)
cap_n: sizet = slen + 4112
nbuf: str = pool.alloc(cap_n)
if nbuf is None:
VLogger.error("--traceback: failed to allocate DIFile path buffer, aborting compilation", "DWARF")
return 1
if slen > 0: string.strncpy(nbuf, src, slen)
nbuf[slen] = '\0'
if slen > 0 and nbuf[0] != '/' and not (slen >= 2 and nbuf[1] == ':'):
# Relative path -> prefix CWD via a win32file low-level call (as in Repl.vp), not os._mbuddy which the compiler process may not have initialized.
CwdBuf: bytes = pool.alloc(4112)
if CwdBuf is not None:
CwdBuf[0] = '\0'
nc: ULONG = win32file.GetCurrentDirectoryA(ULONG(4111), CwdBuf)
if nc > 0:
# Defensive: when the buffer is too small GetCurrentDirectoryA writes undefined content; force a NUL.
CwdBuf[4110] = '\0'
viperlib.snprintf(nbuf, cap_n, "%s/%s", CwdBuf, src)
# Normalize all backslashes (the CWD segment may contain '\' too).
flen: sizet = string.strlen(nbuf)
ni: sizet = 0
for ni in range(int(flen)):
if nbuf[ni] == '\\': nbuf[ni] = '/'
# Save the absolute path for mlirlite's debug call-site recorder (.vpcall file paths must match DIFile: absolute + forward slash).
_g_abs_file_path = nbuf
# filename = whole path; directory = its prefix (both NUL-terminated, non-overlapping).
fname: str = nbuf
directory: str = ""
slash: str = string.strrchr(nbuf, '/')
if slash is not None:
dlen: sizet = string.strlen(nbuf) - string.strlen(slash + 1) - 1
dirbuf: str = pool.alloc(dlen + 1)
if dirbuf is not None:
string.strncpy(dirbuf, nbuf, dlen)
dirbuf[dlen] = '\0'
directory = dirbuf
dsize: sizet = string.strlen(fname) + string.strlen(directory) + 96
dbuf: str = pool.alloc(dsize)
if dbuf is None:
VLogger.error("--traceback: failed to allocate DIFile node buffer, aborting compilation", "DWARF")
return 1
viperlib.snprintf(dbuf, dsize, "!DIFile(filename: \"%s\", directory: \"%s\")", fname, directory)
fid: int = mlirlite.module_add_di_node(mod, pool, dbuf)
if fid < 0:
VLogger.error("--traceback: failed to allocate DIFile metadata node, aborting compilation", "DWARF")
return 1
_g_file_id = fid
# types: !{ null } placeholder list, then DISubroutineType referencing it
nid: int = mlirlite.module_add_di_node(mod, pool, "!{ null }")
if nid < 0:
VLogger.error("--traceback: failed to allocate DISubroutineType type table, aborting compilation", "DWARF")
return 1
_g_null_id = nid
tbuf: str = pool.alloc(64)
if tbuf is None:
VLogger.error("--traceback: failed to allocate DISubroutineType buffer, aborting compilation", "DWARF")
return 1
viperlib.snprintf(tbuf, 64, "!DISubroutineType(types: !%d)", _g_null_id)
tid: int = mlirlite.module_add_di_node(mod, pool, tbuf)
if tid < 0:
VLogger.error("--traceback: failed to allocate DISubroutineType metadata node, aborting compilation", "DWARF")
return 1
_g_subtype_id = tid
cbuf: str = pool.alloc(256)
if cbuf is None:
VLogger.error("--traceback: failed to allocate DICompileUnit buffer, aborting compilation", "DWARF")
return 1
viperlib.snprintf(cbuf, 256, "distinct !DICompileUnit(language: DW_LANG_C99, file: !%d, producer: \"TPC\", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)", _g_file_id)
cid: int = mlirlite.module_add_di_node(mod, pool, cbuf)
if cid < 0:
VLogger.error("--traceback: failed to allocate DICompileUnit metadata node, aborting compilation", "DWARF")
return 1
_g_cu_id = cid
return 0
# _LocRefAt - read a cached DILocation ref string pointer.
def _LocRefAt(slot: int) -> str:
slotp: str* = (str*)(uint64(_g_loc_ref) + sizet(slot) * 8)
r: str = slotp[0]
return r
# _EnsureLocation - DILocation ref for (sp, line, col), created + cached once.
def _EnsureLocation(pool: memhub.MemBuddy*, sp: int, line: int, col: int) -> str:
if line <= 0: return _g_cur_entry_ref
c: int = col
if c <= 0: c = 1
if _g_loc_used is None or _g_mod is None: return None
h: uint64 = (uint64(sp) * 1000003 + uint64(line) * 31 + uint64(c)) & uint64(DBG_LOC_SLOTS - 1)
pi: int = 0
while pi < DBG_LOC_PROBE:
slot: int = int((h + uint64(pi)) & uint64(DBG_LOC_SLOTS - 1))
if _g_loc_used[slot] == 0: break
kbase: int = slot * DBG_LOC_KEY_STRIDE
if int(_g_loc_key[kbase]) == sp and int(_g_loc_key[kbase + 1]) == line and int(_g_loc_key[kbase + 2]) == c: return _LocRefAt(slot)
pi += 1
lbuf: str = pool.alloc(96)
if lbuf is None: return None
viperlib.snprintf(lbuf, 96, "!DILocation(line: %d, column: %d, scope: !%d)", line, c, sp)
lid: int = mlirlite.module_add_di_node(_g_mod, pool, lbuf)
if lid < 0:
VLogger.error("--traceback: failed to allocate DILocation metadata node, aborting compilation", "DWARF")
return None
lref: str = _RefOf(pool, lid)
if lref is None:
VLogger.error("--traceback: failed to allocate DILocation reference buffer, aborting compilation", "DWARF")
return None
if pi < DBG_LOC_PROBE:
slot2: int = int((h + uint64(pi)) & uint64(DBG_LOC_SLOTS - 1))
kbase2: int = slot2 * DBG_LOC_KEY_STRIDE
_g_loc_key[kbase2] = sizet(sp)
_g_loc_key[kbase2 + 1] = sizet(line)
_g_loc_key[kbase2 + 2] = sizet(c)
rslot: str* = (str*)(uint64(_g_loc_ref) + sizet(slot2) * 8)
rslot[0] = lref
_g_loc_used[slot2] = 1
return lref
# PushFunction - create the DISubprogram for one define.
def PushFunction(pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, func: mlirlite.Function*, line: int, col: int) -> int:
"""Attach a DISubprogram to one define and set the builder's entry DILocation."""
global _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth
if _g_enabled == 0: return 0
if pool is None or builder is None or func is None or _g_mod is None: return 0
if _g_depth >= DBG_STACK_MAX:
VLogger.error("--traceback: DWARF function nesting depth exceeds the limit, aborting compilation", "DWARF")
return 0
if _EnsureModuleNodes(pool) != 0: return 0
name: str = mlirlite.function_get_name(func)
if name is None:
VLogger.error("--traceback: function name is empty, cannot emit DISubprogram, aborting compilation", "DWARF")
return 0
dline: int = line
if dline <= 0: dline = 1
dcol: int = col
if dcol <= 0: dcol = 1
ssize: sizet = string.strlen(name) + 320
spbuf: str = pool.alloc(ssize)
if spbuf is None:
VLogger.error("--traceback: failed to allocate DISubprogram buffer, aborting compilation", "DWARF")
return 0
viperlib.snprintf(spbuf, ssize, "distinct !DISubprogram(name: \"%s\", scope: !%d, file: !%d, type: !%d, line: %d, scopeLine: %d, unit: !%d, spFlags: DISPFlagDefinition)", name, _g_file_id, _g_file_id, _g_subtype_id, dline, dline, _g_cu_id)
sp_id: int = mlirlite.module_add_di_node(_g_mod, pool, spbuf)
if sp_id < 0:
VLogger.error("--traceback: failed to allocate DISubprogram metadata node, aborting compilation", "DWARF")
return 0
sp_ref: str = _RefOf(pool, sp_id)
if sp_ref is None:
VLogger.error("--traceback: failed to allocate DISubprogram reference buffer, aborting compilation", "DWARF")
return 0
mlirlite.function_set_dbg(func, sp_ref)
# save the enclosing function's DI context (LIFO), then switch to this one
_g_stk_sp[_g_depth] = sizet(_g_cur_sp + 1)
rslot: str* = (str*)(uint64(_g_stk_ref) + sizet(_g_depth) * 8)
rslot[0] = _g_cur_entry_ref
bslot: str* = (str*)(uint64(_g_stk_bld) + sizet(_g_depth) * 8)
bslot[0] = _g_cur_builder
_g_depth += 1
_g_cur_sp = sp_id
_g_cur_builder = builder
entry_ref: str = _EnsureLocation(pool, sp_id, dline, dcol)
if entry_ref is None:
VLogger.error("--traceback: failed to generate entry DILocation, aborting compilation", "DWARF")
return 0
_g_cur_entry_ref = entry_ref
mlirlite.builder_set_debug_location(builder, entry_ref)
# Enter this define: tell mlirlite's call-site recorder the current symbol/file/def-line and write an F record (for the .vpcall file + WriteStackMap entry fallback line).
mlirlite.DebugNoteFunc(name, _g_abs_file_path, dline)
return 1
# PopFunction - restore the enclosing function's DI context.
def PopFunction(builder: mlirlite.IRBuilder*) -> int:
global _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth
if _g_enabled == 0: return 0
if _g_depth <= 0: return 0
_g_depth -= 1
_g_cur_sp = int(_g_stk_sp[_g_depth]) - 1
rslot: str* = (str*)(uint64(_g_stk_ref) + sizet(_g_depth) * 8)
_g_cur_entry_ref = rslot[0]
bslot: str* = (str*)(uint64(_g_stk_bld) + sizet(_g_depth) * 8)
_g_cur_builder = (mlirlite.IRBuilder*)(bslot[0])
if builder is not None: mlirlite.builder_clear_debug_location(builder)
# Leave this define: restore mlirlite's outer function context (for nested defines).
mlirlite.DebugClearFunc()
return 0
# SetStatement - point the current builder at a statement DILocation.
def SetStatement(pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, line: int, col: int) -> int:
"""Update the builder's DILocation to the current statement (line precision)."""
if _g_enabled == 0 or pool is None or builder is None: return 0
# Record the current statement line: C records from build_call in this statement use it as the call-site line.
mlirlite.DebugNoteLine(line)
if _g_cur_sp < 0: return 0
ref: str = _EnsureLocation(pool, _g_cur_sp, line, col)
if ref is None: return 0
mlirlite.builder_set_debug_location(builder, ref)
return 1
# _ModuleHasDefine - does the module emit any define (IsDeclared == 0)?
def _ModuleHasDefine(mod: mlirlite.LLVMModule*) -> int:
if mod is None: return 0
cur: mlirlite.Function* = mod.FuncHead
steps: int = 0
while cur is not None:
steps += 1
if steps > 100000: break
if mlirlite.function_is_declared(cur) == 0: return 1
cur = mlirlite.function_get_next(cur)
return 0
# Finalize - register !llvm.dbg.cu / !llvm.module.flags before dump_ir.
def Finalize(pool: memhub.MemBuddy*) -> int:
"""Module-level DI tail; idempotent per module, no-op when no SP was emitted."""
global _g_finalized
if _g_enabled == 0: return 0
if _g_finalized != 0: return 0
mod: mlirlite.LLVMModule* = _g_mod
if mod is None or pool is None: return 0
_g_finalized = 1
# Self-check (mirrors TPC Phase2Translator.py:3444): a module that emits a
# define but carries no DI at all loses line info for its frames.
if mod.DIMetaCount == 0 and _ModuleHasDefine(mod) != 0: VLogger.warning("--traceback: this module emits a define but its IR carries no DWARF (!llvm.dbg.cu); its frames will show only addresses in the Ctrl+C stack", "DWARF")
if _g_cu_id < 0: return 0
cu_ref: str = _RefOf(pool, _g_cu_id)
if cu_ref is None:
VLogger.error("--traceback: failed to allocate DICompileUnit reference buffer, aborting compilation", "DWARF")
return 1
mlirlite.module_add_named_metadata(mod, pool, "llvm.dbg.cu", cu_ref)
fbuf: str = pool.alloc(64)
if fbuf is None:
VLogger.error("--traceback: failed to allocate module-level debug flags buffer, aborting compilation", "DWARF")
return 1
viperlib.snprintf(fbuf, 64, "!{i32 2, !\"Debug Info Version\", i32 3}")
flags_id: int = mlirlite.module_add_di_node(mod, pool, fbuf)
if flags_id < 0:
VLogger.error("--traceback: failed to allocate llvm.module.flags metadata node, aborting compilation", "DWARF")
return 1
fl_ref: str = _RefOf(pool, flags_id)
if fl_ref is None:
VLogger.error("--traceback: failed to allocate llvm.module.flags reference buffer, aborting compilation", "DWARF")
return 1
mlirlite.module_add_named_metadata(mod, pool, "llvm.module.flags", fl_ref)
return 0
File diff suppressed because it is too large. Load diff
+774
View File
@@ -0,0 +1,774 @@
import lib.core.Gen.Generator as Generator
from stdint import *
import ast
import mlirlite
import memhub
import string
import viperlib
import lib.core.Gen.Translator as HT
import lib.Projectrans.Utils as Utils
import lib.core.Gen.Type.TypeSpec as TSpec
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.GenTypes as GenTypes
# Pointer-to-pointer cast reason codes (coerce_to_type ptr -> ptr branch).
# PC_OK allowed; PC_DEPTH depth mismatch; PC_NONBASIC non-basic pointee; PC_LOSSY lossy.
define PC_OK = 0
define PC_DEPTH = 1
define PC_NONBASIC = 2
define PC_LOSSY = 3
# ExprGenBase - expression basics split out of ExprGenMixin (stage D):
# type checks / pointer level / coerce.
novirtual class ExprGenBase:
"""Expression basics: type checks / pointer levels / coercion."""
# --- docstring registry fields (__doc__ magic attribute support) ---
Gen: Generator.Generator*
DocKeys: bytes
DocValues: bytes
DocCount: int
# --- OOM diag: cross-module CDefine lookup counter (suspected CONST_INT loop) ---
DiagCDefineCount: int
# --- OOM diag: translate_name_value call counter (source of 14593 lookups) ---
DiagNameValueCount: int
# --- OOM diag: translate_value call counter (locates CONST_INT call loop) ---
DiagTranslateValueCount: int
# P3 migrated methods (bodies from HandlesExpr.vp module functions;
# self. for local calls, module prefix for external ones).
# Extract the function name from an AST node.
def get_func_name(self, func_node: ast.AST*) -> str:
"""Extract the function name from an AST node."""
if func_node is None: return None
k: int = func_node.kind()
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(func_node)
return nm.id
elif k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(func_node)
return at.attr
return None
# Escape an LLVM IR string literal.
def escape_llvm_string(self, pool: memhub.MemBuddy*, src: str) -> str:
"""Convert a string into an LLVM IR string literal."""
if src is None: return None
slen: sizet = string.strlen(src)
alloc_size: sizet = 4 * slen + 8
buf: str = pool.alloc(alloc_size)
if buf is None: return None
pos: sizet = 0
buf[pos] = 'c'
pos += 1
buf[pos] = '"'
pos += 1
for i in range(int(slen)):
ch: char = src[i]
if ch == '\0': break
if ch == '\"' or ch == '\\' or ch < 0x20:
buf[pos] = '\\'
pos += 1
hi: int = (ch >> 4) & 0xF
lo: int = ch & 0xF
buf[pos] = Utils.nibble_to_hex_char(hi, 1)
pos += 1
buf[pos] = Utils.nibble_to_hex_char(lo, 1)
pos += 1
else:
buf[pos] = ch
pos += 1
buf[pos] = '\\'
pos += 1
buf[pos] = '0'
pos += 1
buf[pos] = '0'
pos += 1
buf[pos] = '"'
pos += 1
buf[pos] = '\0'
return buf
# Get the bit width of an LLVM integer type.
# Delegates to TSpec.SpecTypeBits (single source; avoids diverging duplicates).
def get_llvm_type_bits(self, ty: mlirlite.LLVMType*) -> int:
"""Bit width of an LLVM integer type; 0 for non-integers (delegates to SpecTypeBits)."""
return TSpec.SpecTypeBits(ty)
# Get the bit width of an LLVM floating-point type.
def get_llvm_float_bits(self, ty: mlirlite.LLVMType*) -> int:
"""Bit width of an LLVM float type; 0 for non-floats."""
if ty is None: return 0
match ty:
case mlirlite.LLVMType.Float(bits):
return bits
case _:
return 0
# is_ptr_type - check whether a type is Ptr (1=yes, 0=no);
# a widely used type-check helper (50+ call sites).
def is_ptr_type(self, ty: mlirlite.LLVMType*) -> int:
"""Check whether ty is a Ptr type; returns 1=yes / 0=no."""
if ty is None: return 0
match ty:
case mlirlite.LLVMType.Ptr(pointee):
return 1
case _:
return 0
# get_ptr_level - compute pointer level (0=non-ptr, 1/2/3+=ptr depth);
# used by comparison checks to allow matching levels and reject mixed ones.
def get_ptr_level(self, ty: mlirlite.LLVMType*) -> int:
"""Compute pointer level (0=non-ptr, 1=ptr, 2=ptr-to-ptr); delegates to TSpec.SpecTypePtrLevel."""
return TSpec.SpecTypePtrLevel(ty)
# _const_fits_int_type - 编译期整型常量的值能否**无损**落进目标整型位宽。
# 有符号或无符号任一解释放得下即算「放得下」(位型不变);用于二元运算按常量适配另一侧位宽。
def _const_fits_int_type(self, val: mlirlite.Value*, target_ty: mlirlite.LLVMType*) -> int:
"""1 = the integer constant's value fits the target integer width losslessly; else 0."""
if val is None or target_ty is None or val.IsConst != 1: return 0
bits: int = self.get_llvm_type_bits(target_ty)
if bits <= 0 or bits > 63: return 0
v: int64 = val.IntVal
lo: int64 = -(int64(1) << (bits - 1))
hi: int64 = (int64(1) << (bits - 1)) - 1
if v >= lo and v <= hi: return 1
if v >= 0:
if bits == 63: return 1
if v <= (int64(1) << bits) - 1: return 1
return 0
# _int_type_of_bits - 合成指定位宽的 LLVM 整型(1/8/16/32/64 快路径,其余按 LLVMType.Int 合成)。
def _int_type_of_bits(self, pool: memhub.MemBuddy*, bits: int) -> mlirlite.LLVMType*:
"""LLVM integer type of the given bit width."""
if bits == 1: return mlirlite.Int1(pool)
if bits == 8: return mlirlite.Int8(pool)
if bits == 16: return mlirlite.Int16(pool)
if bits == 32: return mlirlite.Int32(pool)
if bits == 64: return mlirlite.Int64(pool)
ty: mlirlite.LLVMType* = pool.alloc(mlirlite.LLVMType.__sizeof__())
if ty is None: return None
string.memset(ty, 0, mlirlite.LLVMType.__sizeof__())
*ty = *mlirlite.LLVMType.Int(bits)
return ty
# apply_byte_order_bswap - byte-order bswap primitive (TPC parity: BaseGen._apply_bswap_if_big,
# extended to judge BOTH markers against the target's native order).
# Swapping is needed only when the declared encoding differs from the target's native order,
# so: BigEndian field on a little-endian target -> swap; LittleEndian field on a big-endian
# target -> swap; declared order == target order -> no swap. byte_order==0 (unspecified)
# means native and is always zero-cost: no IR is emitted at all. 16/32/64-bit integers only,
# matching TPC (i8/floats/pointers/aggregates are never swapped).
# Call name: bare "llvm.bswap" (no width suffix). build_call emits `call i32 @llvm.bswap(i32 %x)`;
# __function.vp:2607 detects a bare `@llvm.*` (non-`llvm.mlir.`) callee and renders it as
# `llvm.call_intrinsic "llvm.bswap"(...)`, and mlir-translate mangles the name from the operand
# type to `llvm.bswap.i32` (README.md:367-378, the same path used by CallGenVa.vp:123 `llvm.va_start`
# -> `llvm.va_start.p0`). Passing `llvm.bswap.i32` explicitly is unnecessary and would break the
# uniform mangle path, so the un-suffixed name is used.
def apply_byte_order_bswap(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, val: mlirlite.Value*, byte_order: int) -> mlirlite.Value*:
"""Return llvm.bswap(val) when val's declared byte_order differs from the target order; else val."""
if byte_order == 0: return val
# 1=BigEndian, 2=LittleEndian; compare against the target's native order.
want_big: int = 0
if byte_order == 1: want_big = 1
if want_big == self.Gen.TypeGenResolve.target_is_big_endian(): return val
if val is None or val.Ty is None: return val
bits: int = self.get_llvm_type_bits(val.Ty)
if bits != 16 and bits != 32 and bits != 64: return val
# Same-type in-and-out (iN -> iN); ret_ty=val.Ty keeps the call well-typed before mangling.
swapped: mlirlite.Value* = mlirlite.build_call(builder, "llvm.bswap", val, 1, val.Ty, 0)
if swapped is None: return val
return swapped
# _is_zero_int_literal - is the AST node the integer literal 0?
# Used for ptr != 0 / ptr == 0, where 0 means NULL and is allowed.
def _is_zero_int_literal(self, node: ast.AST*) -> int:
"""Is the node the integer literal 0; returns 1=yes / 0=no."""
if node is None: return 0
if node.kind() != ast.ASTKind.Constant: return 0
cn: ast.Constant* = ast.Constant*(node)
if cn is None or cn.const_kind != ast.CONST_INT: return 0
if cn.int_val == 0: return 1
return 0
# is_array_type - check whether a type is Array (1=yes, 0=no).
def is_array_type(self, ty: mlirlite.LLVMType*) -> int:
"""Check whether ty is an Array type; returns 1=yes / 0=no."""
if ty is None: return 0
match ty:
case mlirlite.LLVMType.Array(_, _):
return 1
case _:
return 0
# _get_custom_struct_cls_nm - class name if ty (or Ptr(ty)) is a registered struct.
# Used by subscript to decide __getitem__/__setitem__ forwarding vs raw GEP.
def _get_custom_struct_cls_nm(self, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*) -> str:
"""Class name if ty or its pointee is a registered struct; else None."""
if ty is None: return None
# Check whether ty itself is a registered struct.
cls_nm_direct: str = self.Gen.StructGen.get_class_name_by_type(pool, ty)
if cls_nm_direct is not None: return cls_nm_direct
# If ty is Ptr, check the pointee.
if self.is_ptr_type(ty) != 0:
pointee_cs: mlirlite.LLVMType* = ty.Pointee
if pointee_cs is not None: return self.Gen.StructGen.get_class_name_by_type(pool, pointee_cs)
return None
# _is_list_subscript_base - is the subscript base a list[T] variable?
# Wrong routing would treat list[T] as a struct array GEP and crash.
def _is_list_subscript_base(self, pool: memhub.MemBuddy*, trans: HT.Translator*, sub_node: ast.AST*) -> int:
"""Is the subscript base a list[T] variable; 1=yes / 0=no."""
if sub_node is None or trans is None: return 0
sub: ast.Subscript* = ast.Subscript*(sub_node)
if sub is None or sub.value is None or sub.value.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(sub.value)
if nm.id is None: return 0
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is None or alloca.Ty is None: return 0
if self.is_ptr_type(alloca.Ty) == 0: return 0
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
if pointee is None: return 0
match pointee:
case mlirlite.LLVMType.Ptr(inner_ty):
lsn_ls: str = None
match inner_ty:
case mlirlite.LLVMType.Struct(_, _, lsn3):
lsn_ls = lsn3
case _:
pass
if lsn_ls is not None and string.strstr(lsn_ls, "list[") is not None: return 1
case _:
pass
return 0
# _sub_base_class_name - resolve the struct class name of a subscript base.
def _sub_base_class_name(self, pool: memhub.MemBuddy*, trans: HT.Translator*, sub_node: ast.AST*) -> str:
"""Resolve the struct class name of a subscript base; None if absent."""
if sub_node is None or trans is None: return None
sub: ast.Subscript* = ast.Subscript*(sub_node)
if sub is None or sub.value is None or sub.value.kind() != ast.ASTKind.Name: return None
nm: ast.Name* = ast.Name*(sub.value)
if nm.id is None: return None
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is None or alloca.Ty is None: return None
if self.is_ptr_type(alloca.Ty) == 0: return None
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
if pointee is None: return None
if self.is_ptr_type(pointee) != 0:
inner: mlirlite.LLVMType* = pointee.Pointee
if inner is not None: return self.Gen.StructGen.get_class_name_by_type(pool, inner)
return self.Gen.StructGen.get_class_name_by_type(pool, pointee)
# _sub_base_uses_getitem - should the subscript base use __getitem__ semantics?
# list[T] / classes defining __getitem__ yes; plain struct-ptr arrays use GEP.
def _sub_base_uses_getitem(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, sub_node: ast.AST*) -> int:
"""Returns 1 if the subscript base uses __getitem__ semantics, else 0 (GEP address)."""
if self._is_list_subscript_base(pool, trans, sub_node) != 0: return 1
cls_ugi: str = self._sub_base_class_name(pool, trans, sub_node)
if cls_ugi is None: return 0
lookup_ugi: str = pool.alloc(128)
if lookup_ugi is None: return 0
viperlib.snprintf(lookup_ugi, 128, "%s.__getitem__", cls_ugi)
if self.Gen.CallGenBase.find_func_in_module(mod, lookup_ugi) is not None: return 1
return 0
# _deref_if_ptr_ptr - if obj_ptr is Ptr(Ptr(...)), load one level to deref.
# Supports attribute access on X* variables while leaving value vars alone.
def _deref_if_ptr_ptr(self, builder: mlirlite.IRBuilder*, obj_ptr: mlirlite.Value*) -> mlirlite.Value*:
"""If obj_ptr is Ptr(Ptr(...)), load once to obtain the inner pointer."""
if obj_ptr is None or obj_ptr.Ty is None: return obj_ptr
if self.is_ptr_type(obj_ptr.Ty) == 0: return obj_ptr
pointee: mlirlite.LLVMType* = obj_ptr.Ty.Pointee
if pointee is None: return obj_ptr
if self.is_ptr_type(pointee) == 0: return obj_ptr
# obj_ptr is Ptr(Ptr(...)); load to dereference.
loaded: mlirlite.Value* = mlirlite.build_load(builder, pointee, obj_ptr)
if loaded is None: return obj_ptr
return loaded
# _value_node_of - drill an outer statement node down to its value sub-expression.
# Fixes error locations pointing at the Assign node instead of the real value.
def _value_node_of(self, node: ast.AST*) -> ast.AST*:
"""Drill a statement node down to its value sub-expression; returns node if none."""
if node is None: return node
k: int = node.kind()
v: ast.AST* = None
if k == ast.ASTKind.Assign: v = (ast.Assign*(node)).value
elif k == ast.ASTKind.AnnAssign: v = (ast.AnnAssign*(node)).value
elif k == ast.ASTKind.AugAssign: v = (ast.AugAssign*(node)).value
elif k == ast.ASTKind.Expr: v = (ast.Expr*(node)).value
elif k == ast.ASTKind.Return: v = (ast.Return*(node)).value
if v is None: return node
return v
# IsNoneValue - is val/node the None (empty) constant? 1=yes / 0=no.
# None carries no type info, so it may be implicitly converted to any target.
def IsNoneValue(self, val: mlirlite.Value*, node: ast.AST* = None) -> int:
"""Is val/node the None constant; returns 1=yes / 0=no."""
# Criterion 1: the AST node is a None literal.
if node is not None and node.kind() == ast.ASTKind.Constant:
cn_nv: ast.Constant* = ast.Constant*(node)
if cn_nv is not None and cn_nv.const_kind == ast.CONST_NONE: return 1
# Criterion 2: an already-built null constant (ptr type + compile-time zero).
if val is not None and val.IsConst == 1 and val.IntVal == 0 and self.is_ptr_type(val.Ty) != 0: return 1
return 0
# _NoneValueAsTarget - materialize None as the target type's zero/empty value.
# Used only when coerce_to_type has detected the source is None.
def _NoneValueAsTarget(self, builder: mlirlite.IRBuilder*, target_ty: mlirlite.LLVMType*) -> mlirlite.Value*:
"""Materialize None as the target type's zero/empty value."""
if target_ty is None: return None
pool: memhub.MemBuddy* = builder.Pool
t_bits: int = self.get_llvm_type_bits(target_ty)
if t_bits != 0: return mlirlite.ConstInt(pool, target_ty, 0, "0")
t_fbits: int = self.get_llvm_float_bits(target_ty)
if t_fbits != 0: return mlirlite.ConstFloat(pool, target_ty, 0.0)
return mlirlite.ConstZero(pool, target_ty)
# _is_universal_ptr_ty - is ty a universal pointer (t.CPtr / i8* = void*)?
# Rule 1: universal pointers are wildcards, freely interconvertible with any T*.
def _is_universal_ptr_ty(self, ty: mlirlite.LLVMType*) -> int:
"""Is ty a universal pointer (i8* / t.CPtr); 1=yes / 0=no."""
if ty is None or self.is_ptr_type(ty) == 0: return 0
if self.get_ptr_level(ty) != 1: return 0
pointee: mlirlite.LLVMType* = ty.Pointee
if pointee is None: return 1
if self.get_llvm_type_bits(pointee) == 8: return 1
return 0
# "str / bytes are not universal pointers" - rule and unified fail-fast.
# They share i8* in IR but are not wildcards, so the annotation source decides.
def _annot_name_is_bare_str(self, txt: str) -> int:
"""Is the annotation text a bare str / bytes; 1=yes / 0=no."""
if txt is None or txt[0] == 0: return 0
if string.strcmp(txt, "str") == 0 or string.strcmp(txt, "bytes") == 0: return 1
return 0
def _annot_is_bare_str(self, annot: ast.AST*) -> int:
"""Is the annotation AST a bare str / bytes Name; 1=yes / 0=no."""
if annot is None or annot.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(annot)
if nm is None or nm.id is None: return 0
return self._annot_name_is_bare_str(nm.id)
def _str_rejects_ptr_kind(self, val: mlirlite.Value*) -> int:
"""Is val an aggregate pointer that str/bytes must reject (1/2/3=yes, 0=no); char arrays exempt."""
if val is None or val.Ty is None: return 0
if self.is_ptr_type(val.Ty) == 0: return 0
pointee: mlirlite.LLVMType* = val.Ty.Pointee
if pointee is None: return 0
pk: int = pointee.kind()
if pk == mlirlite.LLVMType.Struct: return 1
if pk == mlirlite.LLVMType.Func: return 3
if pk == mlirlite.LLVMType.Array:
match pointee:
case mlirlite.LLVMType.Array(elem, _):
if self.get_llvm_type_bits(elem) == 8: return 0
case _:
pass
return 2
return 0
def fail_if_str_annot_agg_ptr(self, val: mlirlite.Value*, annot_is_str: int, node: ast.AST* = None) -> int:
"""Fail fast (return 1) when a bare str/bytes annotation holds an aggregate pointer."""
if annot_is_str == 0 or val is None or val.Ty is None: return 0
if self._str_rejects_ptr_kind(val) == 0: return 0
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), "str/bytes does not accept a pointer to a non-basic type (struct/class/non-char array/function): both are" "i8* at the IR level, but with different semantics (str/bytes is not a general pointer wildcard). If a pointer must be passed, " "use a t.CPtr/rawptr parameter instead, or cast explicitly (e.g. t.CPtr(p))", "CastError")
return 1
def fail_if_str_annot_ast_agg_ptr(self, val: mlirlite.Value*, annot: ast.AST*, node: ast.AST* = None) -> int:
"""AST-annotation entry: fail fast when a bare str/bytes annotation holds an aggregate pointer."""
return self.fail_if_str_annot_agg_ptr(val, self._annot_is_bare_str(annot), node)
# _is_basic_scalar_ty - is ty a basic scalar (int/float)? 1=yes / 0=no.
# Non-basic types (struct/array/func) may not take part in implicit ptr casts.
def _is_basic_scalar_ty(self, ty: mlirlite.LLVMType*) -> int:
"""Is ty a basic scalar (int/float); 1=yes / 0=no."""
if ty is None: return 0
if self.get_llvm_type_bits(ty) != 0: return 1
if self.get_llvm_float_bits(ty) != 0: return 1
return 0
# _scalar_convert_reason - is the basic scalar conversion T -> U lossless?
# Returns PC_OK (lossless/allowed) or PC_LOSSY (lossy/forbidden).
def _scalar_convert_reason(self, src_pt: mlirlite.LLVMType*, dst_pt: mlirlite.LLVMType*) -> int:
"""Implicit basic-scalar conversion code src_pt -> dst_pt (PC_OK / PC_LOSSY)."""
s_int: int = self.get_llvm_type_bits(src_pt)
d_int: int = self.get_llvm_type_bits(dst_pt)
s_flt: int = self.get_llvm_float_bits(src_pt)
d_flt: int = self.get_llvm_float_bits(dst_pt)
# int <-> int: widening is lossless; narrowing (high->low) is lossy -> forbidden.
if s_int != 0 and d_int != 0:
if d_int >= s_int: return PC_OK
return PC_LOSSY
# float <-> float: allowed.
if s_flt != 0 and d_flt != 0: return PC_OK
# int -> float: lossless only when float width >= int width.
if s_int != 0 and d_flt != 0:
if d_flt >= s_int: return PC_OK
return PC_LOSSY
# float -> int: lossy, forbidden.
if s_flt != 0 and d_int != 0: return PC_LOSSY
return PC_OK
# _class_name_of_ptr_pointee - map a pointer's pointee type to a registered class name.
# Reuses StructGen.get_class_name_by_type; returns None for non-struct/unregistered.
def _class_name_of_ptr_pointee(self, pool: memhub.MemBuddy*, pt: mlirlite.LLVMType*) -> str:
"""Map a pointer's pointee type to a registered class name; None on failure."""
if pt is None: return None
return self.Gen.StructGen.get_class_name_by_type(pool, pt)
# _is_inheritance_upcast - Derived* -> Base* (incl. multi-level) exemption of rule 2.
# Allowed when the target class appears after the source in its MRO chain.
def _is_inheritance_upcast(self, pool: memhub.MemBuddy*, src_pt: mlirlite.LLVMType*, dst_pt: mlirlite.LLVMType*) -> int:
"""Derived* -> Base* upcast (incl. multi-level); 1=allowed / 0=no."""
src_name: str = self._class_name_of_ptr_pointee(pool, src_pt)
if src_name is None: return 0
dst_name: str = self._class_name_of_ptr_pointee(pool, dst_pt)
if dst_name is None: return 0
if string.strcmp(src_name, dst_name) == 0: return 0
chain: sizet* = self.Gen.StructGen.get_mro_chain(pool, src_name)
if chain is None: return 0
# chain[0] = self; ancestors follow, terminated by 0. Only look after self.
i: int = 1
while chain[i] != 0:
anc: str = rawptr(chain[i])
if anc is not None and string.strcmp(anc, dst_name) == 0: return 1
i += 1
return 0
# _is_array_decay - [N]T* -> T* one-way array decay exemption of rule 2.
# Matches when the source pointee is a fixed array and the target is its element.
def _is_array_decay(self, pool: memhub.MemBuddy*, src_pt: mlirlite.LLVMType*, dst_pt: mlirlite.LLVMType*) -> int:
"""Source pointee is a fixed array and target is its element type; 1=yes / 0=no."""
if src_pt is None or dst_pt is None: return 0
if self.is_array_type(src_pt) == 0: return 0
elem: mlirlite.LLVMType* = None
match src_pt:
case mlirlite.LLVMType.Array(et, _):
elem = et
case _:
return 0
if elem is None: return 0
return self.Gen.AssignGen._stg_ty_equal(pool, elem, dst_pt)
# _pointee_convert_reason - reason code for pointee T -> U (the T->U of T* -> U*).
# Applies rule 2 with the inheritance-upcast and array-decay exemptions.
def _pointee_convert_reason(self, pool: memhub.MemBuddy*, src_pt: mlirlite.LLVMType*, dst_pt: mlirlite.LLVMType*) -> int:
"""Conversion code for pointee src_pt -> dst_pt (PC_OK/PC_NONBASIC/PC_LOSSY/PC_DEPTH)."""
if src_pt is None or dst_pt is None: return PC_OK
s_is_ptr: int = self.is_ptr_type(src_pt)
d_is_ptr: int = self.is_ptr_type(dst_pt)
if s_is_ptr != 0 or d_is_ptr != 0:
# One side ptr, other not (e.g. Foo vs Foo*): non-basic swap, forbidden.
if s_is_ptr == 0 or d_is_ptr == 0: return PC_NONBASIC
# Both sides are pointers: recurse.
return self._ptr_cast_reason(pool, src_pt, dst_pt)
# One side is non-basic (struct/class/union/array/func): generally forbidden.
if self._is_basic_scalar_ty(src_pt) == 0 or self._is_basic_scalar_ty(dst_pt) == 0:
# Exemption 1: Derived* -> Base* inheritance upcast (incl. multi-level).
if self._is_inheritance_upcast(pool, src_pt, dst_pt) != 0: return PC_OK
# Exemption 2: one-way array decay [N]T* -> T* (source pointee is a
# fixed array, target pointee is its element type).
if self._is_array_decay(pool, src_pt, dst_pt) != 0: return PC_OK
return PC_NONBASIC
return self._scalar_convert_reason(src_pt, dst_pt)
# _ptr_cast_reason - reason code for implicit ptr -> ptr casts (single source of truth).
# Rules 1-4: wildcard ptr, lossless pointee, non-basic forbidden, identity allowed.
def _ptr_cast_reason(self, pool: memhub.MemBuddy*, src_ty: mlirlite.LLVMType*, dst_ty: mlirlite.LLVMType*) -> int:
"""Reason code for implicit ptr -> ptr casts (PC_OK means allowed)."""
if src_ty is None or dst_ty is None: return PC_OK
slev: int = self.get_ptr_level(src_ty)
dlev: int = self.get_ptr_level(dst_ty)
if slev == 0 or dlev == 0: return PC_OK
# 豁免 0(必须放在级数检查**之前**):固定数组退让 `[N]T* -> T*`(C 语义:数组名退让成
# 首元素指针)。元素是 i8 时级数不变(1→1,恰好由"全类型指针通配"放行),但元素本身
# 是指针时级数会 +1(`[N]str` -> `str*` 即 `Ptr([N]i8*) -> Ptr(i8*)`:1→2),
# 级数检查会先判成 PC_DEPTH,使下面 _pointee_convert_reason 里那条既有豁免永远够不到
# (死代码)。现场:includes/tomllib `_parse_key_path(s, segs: [N]str, cap)` —— 形参按
# C 语义退让成 `str*`(FuncGenDef 的 Array 形参退让),实参仍是 `[N]str` 的数组地址。
if self._is_array_decay(pool, src_ty.Pointee, dst_ty.Pointee) != 0: return PC_OK
# Different depth: forbidden regardless of pointee.
if slev != dlev: return PC_DEPTH
# Rule 4: identical types (same depth and pointee) are not a conversion.
if self.Gen.AssignGen._stg_ty_equal(pool, src_ty, dst_ty) != 0: return PC_OK
# Rule 1: universal pointers are wildcards (i8* <-> any T*).
if self._is_universal_ptr_ty(src_ty) != 0 or self._is_universal_ptr_ty(dst_ty) != 0: return PC_OK
# Rules 2/3: compare pointee level by level.
return self._pointee_convert_reason(pool, src_ty.Pointee, dst_ty.Pointee)
# _type_text - readable LLVM type text via TypePrint ("?" on failure).
def _type_text(self, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*) -> str:
"""Readable LLVM type text via TypePrint; "?" on failure."""
if ty is None: return "?"
buf: str = pool.alloc_tagged(4096, memhub.DETSITE_EXPRGENBASE_415)
if buf is None: return "?"
mlirlite.TypePrint(buf, 4096, ty, pool)
if buf[0] == 0: return "?"
return buf
# _ptr_cast_reason_text - human-readable text for a forbidden ptr -> ptr cast.
# Names the level and why, plus source/target types; result is heap-copied.
# 注意:LLVM 不透明指针(!llvm.ptr)打印出来两边一模一样,看不出问题所在;故额外
# 打印**级数 + pointee 类型**,否则现场只能看到一个"2 -> 1"而无法定位是哪个表达式。
def _ptr_cast_reason_text(self, code: int, pool: memhub.MemBuddy*, src_ty: mlirlite.LLVMType*, dst_ty: mlirlite.LLVMType*) -> str:
"""Human-readable reason for a forbidden ptr -> ptr cast (includes source/target types)."""
why: str = "unknown reason"
slev: int = self.get_ptr_level(src_ty)
dlev: int = self.get_ptr_level(dst_ty)
if code == PC_DEPTH: why = f"pointer level differs (source level {slev} -> target level {dlev})"
elif code == PC_NONBASIC: why = "the pointee type is a non-basic type (struct/class/union/array etc.): implicit conversion is never allowed between a non-basic type and its pointer, or between pointers of different non-basic types"
elif code == PC_LOSSY: why = "conversion between the pointee types is lossy (int -> float with insufficient width / float -> int)"
s_txt: str = self._type_text(pool, src_ty)
d_txt: str = self._type_text(pool, dst_ty)
s_pt: str = "?"
d_pt: str = "?"
if src_ty is not None and slev > 0: s_pt = self._type_text(pool, src_ty.Pointee)
if dst_ty is not None and dlev > 0: d_pt = self._type_text(pool, dst_ty.Pointee)
return GenApi.escape_fstring_copy(f"implicit pointer-to-pointer conversion is forbidden: {why}; source type {s_txt} -> target type {d_txt}; source: level {slev}, pointee {s_pt} -> target: level {dlev}, pointee {d_pt}")
# _ctor_call_agg_ty - aggregate type if node is a class/struct constructor call.
# The only exception to rule 3 ("construction materialization"); else None.
def _ctor_call_agg_ty(self, node: ast.AST*) -> mlirlite.LLVMType*:
"""Aggregate type if node (or its value child) is a constructor call; else None."""
n: ast.AST* = self._value_node_of(node)
if n is None or n.kind() != ast.ASTKind.Call: return None
cl: ast.Call* = ast.Call*(n)
if cl.func is None: return None
callee: str = None
if cl.func.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(cl.func)
callee = nm.id
elif cl.func.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(cl.func)
callee = at.attr
if callee is None: return None
ent: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(callee)
if ent is not None and ent.Ty is not None: return ent.Ty
# REnum variant ctor `EnumType.Variant(...)`: the callee is not a struct
# name, but materializes the base enum, so return that aggregate type.
if cl.func.kind() == ast.ASTKind.Attribute:
# Take the segment left of the variant name as the enum class name
# (the qualifier, not the chain root which caused earlier misses).
qual: ast.AST* = (ast.Attribute*(cl.func)).value
base_nm: str = None
if qual is not None:
if qual.kind() == ast.ASTKind.Attribute: base_nm = (ast.Attribute*(qual)).attr
elif qual.kind() == ast.ASTKind.Name: base_nm = (ast.Name*(qual)).id
if base_nm is not None and self.Gen.ClassGenEnum._is_renum_class_name(base_nm) != 0:
rn_ent: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(base_nm)
if rn_ent is not None and rn_ent.Ty is not None: return rn_ent.Ty
return None
# _same_agg_ty - are two aggregate types the same (type match for ctor exemption)?
def _same_agg_ty(self, pool: memhub.MemBuddy*, a: mlirlite.LLVMType*, b: mlirlite.LLVMType*) -> int:
"""Are a and b the same aggregate type; 1=yes / 0=no."""
if a is None or b is None: return 0
return self.Gen.AssignGen._stg_ty_equal(pool, a, b)
# _agg_value_slot_ty - 值语义槽:源表达式的变量槽**自身**持有聚合物(`x: Foo`,Spec.PtrLevel == 0)。
# 真·指针值(`p: Foo*`,以及**裸类形参** `node: T` —— 其 ABI 即 T*,见 ClassGenVTable "objects are
# pointers")的 Spec.PtrLevel >= 1,返回 None,仍必须显式解引用(c.deref(p) / *p)。
# 返回该聚合类型;不适用时 None。
def _agg_value_slot_ty(self, node: ast.AST*, val: mlirlite.Value*, trans: HT.Translator*) -> mlirlite.LLVMType*:
"""Aggregate type when the source is a variable slot holding the aggregate by value; else None."""
n: ast.AST* = self._value_node_of(node)
if n is None or n.kind() != ast.ASTKind.Name: return None
nm: str = (ast.Name*(n)).id
if nm is None or trans is None: return None
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm)
if ve is None or ve.Alloca is None or ve.Spec is None: return None
# 值语义槽:按名解析到该变量的值类型;此处仅做类型级判定(不依赖槽身份,避免别名/重复登记漏判)。
# PtrLevel>=1(含裸类形参 `node: T` 的 ABI 指针):真指针,必须显式解引用。
if ve.Spec.PtrLevel != 0: return None
if self.is_ptr_type(ve.Alloca.Ty) == 0: return None
return ve.Alloca.Ty.Pointee
# Type coercion: int sext/trunc, float fpext/fptrunc, int<->float si2fp/fp2si.
def coerce_to_type(self, builder: mlirlite.IRBuilder*, val: mlirlite.Value*, target_ty: mlirlite.LLVMType*, node: ast.AST* = None, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Coerce val to target_ty: int sext/trunc, float fpext/fptrunc, int/float conversions."""
if val is None or target_ty is None: return val
# [FIX 2026-09-28] None may convert to any type: it carries no type info,
# so all "real value" implicit-cast bans short-circuit for it (IsNoneValue).
if self.IsNoneValue(val, node) != 0:
if self.is_ptr_type(target_ty) != 0: return mlirlite.build_bitcast(builder, val, target_ty)
return self._NoneValueAsTarget(builder, target_ty)
# A void-typed value may not be used as a value: coerce to a non-void target
# fails fast instead of emitting illegal IR (load void / bitcast void).
if self.Gen.StmtGenDecl._is_void_type(val.Ty) != 0:
if self.Gen.StmtGenDecl._is_void_type(target_ty) == 0:
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), "a void value cannot take part in any operation (void is only allowed for void* and typedef aliases)", "TypeError")
return val
# 聚合目标(结构体/数组)不接受标量(整数/浮点)初始化或赋值:标量→聚合在
# VPC 无转换语义;旧行为把标量原样交给 build_store,而 build_store 不做类型
# 校验,落成 `store i32 0, ptr %x`——只写聚合首元素字节并抬高对齐,其余部分
# 保持未初始化(历史现场:`l: [10]char = 0` → 打印 "Hello"+乱码)。
# 聚合零初始化必须写 `= None`(上方 IsNoneValue → _NoneValueAsTarget 分支)。
tg_kind_agg: int = target_ty.kind()
if (tg_kind_agg == mlirlite.LLVMType.Struct or tg_kind_agg == mlirlite.LLVMType.Array) and self.is_ptr_type(val.Ty) == 0 and (self.get_llvm_type_bits(val.Ty) != 0 or self.get_llvm_float_bits(val.Ty) != 0):
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), "a scalar (integer/float) cannot initialize or assign to an aggregate (struct/array): only the first element's bytes would be written (and the alignment raised), leaving the rest of the aggregate uninitialized; for zero-initialization write `= None`, for contents use a list literal `[...]` or assign element by element", "TypeError")
return val
# func -> ptr: bitcast (function Values have Func type, handled before
# the integer/float conversions).
match val.Ty:
case mlirlite.LLVMType.Func(_, _, _):
if self.is_ptr_type(target_ty) != 0: return mlirlite.build_bitcast(builder, val, target_ty)
return val
case _:
pass
# Integer conversion.
val_bits: int = self.get_llvm_type_bits(val.Ty)
target_bits: int = self.get_llvm_type_bits(target_ty)
# int -> ptr: implicit conversion forbidden (FIX 2026-08-30); only the C
# null pointer constant 0 is exempt. Non-constant ints need an explicit cast.
if val_bits != 0 and self.is_ptr_type(target_ty) != 0:
# [FIX 2026-08-31] C null pointer constant exemption: compile-time 0
# assigned to a pointer becomes a null pointer (widen to i64, then inttoptr).
if val.IsConst == 1 and val.IntVal == 0: return mlirlite.build_inttoptr(builder, mlirlite.const_int64(builder.Pool, 0), target_ty)
m_i2p: str = "implicit integer-to-pointer conversion is forbidden: an explicit cast to pointer (e.g. t.CPtr(x)) is required to assign an integer to a pointer type"
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), m_i2p, "CastError")
return val
# Pointer -> integer: rejected (symmetric with the int -> ptr rule above).
# An explicit cast is required; the old code silently did ptrtoint.
if self.is_ptr_type(val.Ty) != 0 and target_bits != 0 and self.is_ptr_type(target_ty) == 0:
m_p2i: str = "implicit pointer-to-integer conversion is forbidden: an explicit cast (e.g. uint64(p) / int64(p) / int(p)) is required to assign a pointer to an integer type"
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), m_p2i, "CastError")
return val
if val_bits != 0 and target_bits != 0:
if val_bits == target_bits: return val
if val_bits < target_bits:
# Unified zext/sext: i1/i8 default zext, unsigned AST nodes zext,
# otherwise sext. trans is required so unsigned annotations resolve.
if self._zext_or_sext_val(builder.Pool, val, node, trans) != 0: return mlirlite.build_zext(builder, val, target_ty)
return mlirlite.build_sext(builder, val, target_ty)
# 隐式窄化**禁止**(规则 2026-10-10 收紧,取代"整数↔整数 所有方向都允许"):
# 位宽高→低会丢高位,必须显式 cast 或加宽目标类型。豁免只剩一类:
# 源是**编译期常量且值能落进目标位宽**(如字面量 5 → i32、True/False → i1),无信息损失。
# (注意 `True`/`False` 常量按 i32 1/0 物化,见 ExprGenConst.translate_constant,
# 正是靠此豁免才能赋给 i1 目标。)
if val.IsConst == 1:
iv: int64 = val.IntVal
lo_c: int64 = -(int64(1) << (target_bits - 1))
hi_c: int64 = (int64(1) << target_bits) - 1
if iv < lo_c or iv > hi_c:
self.Gen.TypeGenErr.fatal_error(node, f"narrowing truncation overflow: constant {iv} is outside the {target_bits}-bit representable range [{lo_c}, {hi_c}]; use an explicit cast or widen the target type", "OverflowError")
return mlirlite.build_trunc(builder, val, target_ty)
# 规则 2026-10-13:目标 i1(bool / t.CBool)**不再**对宽整型做隐式真值归一化
# (旧的 `x != 0 -> 1/0`)。真值归一化会掩盖真实的位宽不匹配(如 `b: bool = some_i32`),
# 属静默转换,必须 fail-fast;需要显式 `bool(x)` / `t.CBool(x)`。
# 合法写法不受影响:比较(!=/==/…)、`and` / `or`、`not` 本身即产出 i1
# (translate_compare / translate_boolop / translate_unaryop),i1 源走上面的
# `val_bits == target_bits` 直接放行;True/False 常量走上面的常量豁免。
if target_bits == 1:
self.Gen.TypeGenErr.fatal_error(node, f"implicit narrowing is forbidden: a {val_bits}-bit value cannot be implicitly assigned to bool (i1) (it would be silently reduced to its truthiness); use an explicit cast (e.g. bool(x) / t.CBool(x))", "CastError")
return val
self.Gen.TypeGenErr.fatal_error(node, f"implicit narrowing is forbidden: a {val_bits}-bit value cannot be implicitly assigned to a {target_bits}-bit type (the high bits would be lost); use an explicit cast (e.g. int32(x) / t.CInt32T(x)) or widen the target type", "CastError")
return val
# Float conversion.
val_fbits: int = self.get_llvm_float_bits(val.Ty)
target_fbits: int = self.get_llvm_float_bits(target_ty)
# float <-> ptr: forbidden (fast-fail to avoid the bogus build_load path).
if (val_fbits != 0 and self.is_ptr_type(target_ty) != 0) or (self.is_ptr_type(val.Ty) != 0 and target_fbits != 0):
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), "unsupported type conversion: float <-> ptr", "CastError")
return val
if val_fbits != 0 and target_fbits != 0:
if val_fbits == target_fbits: return val
if val_fbits < target_fbits: return mlirlite.build_fpext(builder, val, target_ty)
return mlirlite.build_fptrunc(builder, val, target_ty)
# int -> float
if val_bits != 0 and target_fbits != 0: return mlirlite.build_si2fp(builder, val, target_ty)
# float -> int
if val_fbits != 0 and target_bits != 0: return mlirlite.build_fp2si(builder, val, target_ty)
# Aggregate value -> pointer: spill a temp to the entry block and pass its
# address (VPC never passes aggregates by value).
if self.is_ptr_type(val.Ty) == 0 and self.is_ptr_type(target_ty) != 0:
vk_agg: int = val.Ty.kind()
if vk_agg == mlirlite.LLVMType.Struct or vk_agg == mlirlite.LLVMType.Array:
# Rule 3: implicit value -> ptr for non-basic types (Foo -> Foo*) is
# forbidden, except two exemptions:
# (a) "construction materialization" (see _ctor_call_agg_ty);
# (b) 同型聚合值 → 同型聚合指针:值拷贝语义(等价于 C 按值传结构体)。
# 实参聚合值被拷进**本帧(调用者帧)**的临时槽,再把该临时槽地址按 ABI
# 交给被调者:被调者得到一份独立拷贝、就地修改不影响调用者,且该地址在
# 整个调用期间有效(生命周期覆盖调用),故合法用例不会悬垂。
# 唯有被调者**保存**此地址(如容器 append 挂链)才会悬垂 —— 这类写法必须由
# 源码显式声明为指针形参(`node: T*`)并传入既有对象的地址(此时实参本就是
# 指针,不会走到此处隐式拷贝)。显式 T* / &v / c.addr(...) 的正常用法不受影响。
same_v2p: int = 0
match target_ty:
case mlirlite.LLVMType.Ptr(pointee_v2p):
if self._same_agg_ty(builder.Pool, pointee_v2p, val.Ty) != 0: same_v2p = 1
case _:
pass
if same_v2p == 0:
ctor_v2p: mlirlite.LLVMType* = self._ctor_call_agg_ty(node)
if self._same_agg_ty(builder.Pool, ctor_v2p, val.Ty) == 0:
m_v2p: str = "implicit value/pointer conversion for non-basic types is forbidden (except construction materialization): a non-basic value cannot be implicitly converted to a pointer (Foo -> Foo*); to take its address use c.addr(...) / &v explicitly, or cast explicitly"
v2p_txt: str = GenApi.escape_fstring_copy(f"; source value type {self._type_text(builder.Pool, val.Ty)} -> target type {self._type_text(builder.Pool, target_ty)}")
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), f"{m_v2p}{v2p_txt}", "CastError")
return val
tmp_agg: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, val.Ty)
if tmp_agg is not None:
mlirlite.build_store(builder, val, tmp_agg)
match target_ty:
case mlirlite.LLVMType.Ptr(pointee_agg):
if self.Gen.AssignGen._stg_ty_equal(builder.Pool, pointee_agg, val.Ty) != 0: return tmp_agg
case _:
pass
bc_agg: mlirlite.Value* = mlirlite.build_bitcast(builder, tmp_agg, target_ty)
if bc_agg is not None: return bc_agg
return tmp_agg
# Pointer -> non-pointer value(聚合目标)。两类隐式 load 被保留:
# ① **指向同型聚合的指针 → 同型聚合值**:值拷贝语义。裸类形参(`elem: T`)的 ABI 就是
# `T*`(对象即指针),把它赋给同型值字段/变量时按值拷贝;`x: T` 这类值语义槽指针同理。
# 拷贝后目标独立、不会悬垂 —— 与「传参用指针」的 ABI 选择互不冲突。
# ② 构造物化(见 _ctor_call_agg_ty)。
# 其余(不同型聚合 / 多级指针)仍 fail-fast,须显式 *p / c.deref(p) 或显式 cast。
if self.is_ptr_type(val.Ty) != 0 and self.is_ptr_type(target_ty) == 0:
target_kind: int = target_ty.kind()
if target_kind == mlirlite.LLVMType.Struct or target_kind == mlirlite.LLVMType.Array:
# ① 同型指针 → 值拷贝(覆盖裸类形参的 `T*` ABI)
if val.Ty.Pointee is not None and self._same_agg_ty(builder.Pool, val.Ty.Pointee, target_ty) != 0:
return mlirlite.build_load(builder, target_ty, val)
ctor_p2v: mlirlite.LLVMType* = self._ctor_call_agg_ty(node)
# ② 值语义槽(`x: Foo`,槽自身持有聚合物):读槽 = 值拷贝
slot_p2v: mlirlite.LLVMType* = self._agg_value_slot_ty(node, val, trans)
if self._same_agg_ty(builder.Pool, slot_p2v, target_ty) != 0:
return mlirlite.build_load(builder, target_ty, val)
if self._same_agg_ty(builder.Pool, ctor_p2v, target_ty) == 0:
m_p2v: str = "implicit value/pointer conversion for non-basic types is forbidden (except construction materialization): a pointer type cannot be implicitly converted to a non-basic value (Foo* -> Foo); to dereference use c.deref(p) / *p explicitly, or cast explicitly"
p2v_txt: str = GenApi.escape_fstring_copy(f"; source type {self._type_text(builder.Pool, val.Ty)} -> target value type {self._type_text(builder.Pool, target_ty)}")
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), f"{m_p2v}{p2v_txt}", "CastError")
return val
return mlirlite.build_load(builder, target_ty, val)
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), f"implicit pointer-to-non-pointer-value conversion is forbidden: an explicit dereference (*p / *p) or explicit cast (uint64(p)) is required", "CastError")
return val
# ptr -> ptr: implicit conversion must satisfy the new rules (see
# _ptr_cast_reason); violators fail fast with a CastError.
if self.is_ptr_type(val.Ty) != 0 and self.is_ptr_type(target_ty) != 0:
pc: int = self._ptr_cast_reason(builder.Pool, val.Ty, target_ty)
if pc != PC_OK:
reason: str = self._ptr_cast_reason_text(pc, builder.Pool, val.Ty, target_ty)
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), reason, "CastError")
return val
return mlirlite.build_bitcast(builder, val, target_ty)
return val
def _zext_or_sext_val(self, pool: memhub.MemBuddy*, val: mlirlite.Value*, node: ast.AST* = None, trans: HT.Translator* = None) -> int:
"""Unified integer widening direction: 1=zext (unsigned), 0=sext; delegates to ExprGenOps.should_zext_widen."""
if val is None or val.Ty is None: return 0
return self.Gen.ExprGenOps.should_zext_widen(pool, val, None, node, trans)
# translate_ptr_arith: ptr +/- int via getelementptr (index scaled by pointee
# size); array pointees ([N x ty]*) decay to element pointers first.
def translate_ptr_arith(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, ptr_val: mlirlite.Value*, int_val: mlirlite.Value*, op: int) -> mlirlite.Value*:
"""Pointer arithmetic: ptr +/- int via GEP with the index scaled by element size."""
if ptr_val is None or int_val is None or ptr_val.Ty is None: return None
# Extract the pointee type (non-pointer returns None).
elem_ty: mlirlite.LLVMType* = None
match ptr_val.Ty:
case mlirlite.LLVMType.Ptr(pointee):
elem_ty = pointee
case _:
return None
if elem_ty is None: return None
# Normalize the index to i64.
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
idx: mlirlite.Value* = self.coerce_to_type(builder, int_val, i64_ty, None)
if idx is None: return None
# ptr - int -> negative index (LLVM GEP indices are additive).
if op == ast.OpKind.Sub:
neg_idx: mlirlite.Value* = mlirlite.build_sub(builder, mlirlite.const_int64(pool, 0), idx)
if neg_idx is None: return None
idx = neg_idx
# Array pointee: decay to an element pointer first (GEP 0,0).
if self.is_array_type(elem_ty) != 0:
inner_ty: mlirlite.LLVMType* = None
match elem_ty:
case mlirlite.LLVMType.Array(inner, _):
inner_ty = inner
case _:
return None
if inner_ty is None: return None
decayed: mlirlite.Value* = mlirlite.build_gep_array(builder, elem_ty, inner_ty, ptr_val, mlirlite.const_int64(pool, 0))
if decayed is None: return None
return mlirlite.build_gep(builder, inner_ty, decayed, idx)
return mlirlite.build_gep(builder, elem_ty, ptr_val, idx)
def InitExprGen(self) -> int:
"""Initialize ExprGen family fields (called by Generator at construction)."""
if self is None: return 1
self.DocKeys = None
self.DocValues = None
self.DocCount = 0
self.DiagCDefineCount = 0
self.DiagNameValueCount = 0
self.DiagTranslateValueCount = 0
return 0
+919
View File
@@ -0,0 +1,919 @@
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import stdlib
import viperlib
import win32.fileio as fileio
import lib.Stub.StubMerger as StubMerger
import lib.core.VLogger as VLogger
import lib.Projectrans.Config as Config
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
# ============================================================
# ExprGenCdef - ExprGenMixin split (stage D): cross-module CDefine lookup
novirtual class ExprGenCdef(ExprGenBase.ExprGenBase):
"""Cross-module CDefine lookup"""
# _build_source_path_from_sha1: build a source .vp/.py path from SHA1 via the StubMerger
# global store (.vpi missing -> source .py); "lib/..." -> SourceDir, else IncludesDir.
def _build_source_path_from_sha1(self, sha1: str) -> str:
"""Build a source .py path from a SHA1 via the global store"""
if sha1 is None: return None
if Config.IncludesDir is None: return None
# get the rel-path array from the StubMerger global store
rel_arr_ptr: bytes = StubMergerStore.GetSha1StoreRelArrPtr()
count: int = StubMergerStore.GetSha1StoreCount()
if rel_arr_ptr is None or count <= 0: return None
sha1_arr_ptr: bytes = StubMergerStore.GetSha1StoreArrPtr()
if sha1_arr_ptr is None: return None
# iterate to find the matching SHA1
idx_s: int = -1
for i in range(int(count)):
sidx: sizet = sizet(i) * 17
cur_sha1: str = sha1_arr_ptr + sidx
if cur_sha1[0] == '\0': continue
if string.strcmp(cur_sha1, sha1) == 0:
idx_s = i
break
if idx_s < 0: return None
# get the rel path (MAX_REL_PATH_LEN=256, avoids cross-module CDefine issues)
MAX_RPL: sizet = 256
ridx: sizet = sizet(idx_s) * MAX_RPL
rel_path: str = rel_arr_ptr + ridx
if rel_path[0] == '\0': return None
# choose base dir: 1) stored includes root IncDir (absolute, supports multiple dirs);
# 2) prefix heuristic: "lib/" -> SourceDir, "includes/" -> IncludesDir, else SourceDir then IncludesDir.
base_dir: str = None
rel_used: str = rel_path
inc_arr_ptr: bytes = StubMergerStore.GetSha1StoreIncdirArrPtr()
if inc_arr_ptr is not None:
iidx: sizet = sizet(idx_s) * StubMerger.MAX_INC_DIR_LEN
inc_dir: str = inc_arr_ptr + iidx
if inc_dir[0] != '\0': base_dir = inc_dir
if base_dir is None and string.strlen(rel_path) >= 4:
if (rel_path[0] == 'l' and rel_path[1] == 'i' and rel_path[2] == 'b' and rel_path[3] == '/'):
base_dir = Config.SourceDir if Config.SourceDir is not None else Config.IncludesDir
if base_dir is None and string.strlen(rel_path) >= 9:
if (rel_path[0] == 'i' and rel_path[1] == 'n' and rel_path[2] == 'c' and rel_path[3] == 'l' and rel_path[4] == 'u' and rel_path[5] == 'd' and rel_path[6] == 'e' and rel_path[7] == 's' and rel_path[8] == '/'):
rel_used = rel_path + 9
base_dir = Config.IncludesDir
if base_dir is None: base_dir = Config.SourceDir if Config.SourceDir is not None else Config.IncludesDir
# build full path: {base_dir}/{rel_used}
base_len: sizet = string.strlen(base_dir)
rel_len: sizet = string.strlen(rel_used)
path_len: sizet = base_len + rel_len + 2
buf: str = stdlib.malloc(path_len)
if buf is None: return None
viperlib.snprintf(buf, path_len, "%s/%s", base_dir, rel_used)
# existence check: if missing under SourceDir and IncludesDir differs, retry IncludesDir
pf_chk: fileio.File* = fileio.File(buf, fileio.MODE.R)
if pf_chk.closed:
pf_chk.close()
if Config.IncludesDir is not None and base_dir != Config.IncludesDir:
stdlib.free(buf)
base_len2: sizet = string.strlen(Config.IncludesDir)
path_len2: sizet = base_len2 + rel_len + 2
buf = stdlib.malloc(path_len2)
if buf is None: return None
viperlib.snprintf(buf, path_len2, "%s/%s", Config.IncludesDir, rel_used)
pf_chk2: fileio.File* = fileio.File(buf, fileio.MODE.R)
if pf_chk2.closed:
pf_chk2.close()
stdlib.free(buf)
return None
pf_chk2.close()
else:
stdlib.free(buf)
return None
else: pf_chk.close()
return buf
# _pyi_annot_float_kind - 从 .vpi/.pyi 行的注解段(`NAME:` 到 `=` 之间)判定浮点宽度种类:
# 独立单词 float → 1(f32);double / 未声明 / 非浮点 → 0。词边界判定避免命中 `double`/`CFloat32`。
def _pyi_annot_float_kind(self, buf: bytes, start: sizet, end: sizet) -> int:
"""Return 1 when the annotation text [start,end) declares a 32-bit `float`, else 0."""
i: sizet = start
while i + 5 <= end:
if (buf[i] == 'f' and buf[i + 1] == 'l' and buf[i + 2] == 'o' and buf[i + 3] == 'a' and buf[i + 4] == 't'):
ok_before: int = 1
ok_after: int = 1
if i > start:
pb: char = buf[i - 1]
if (pb >= 'A' and pb <= 'Z') or (pb >= 'a' and pb <= 'z') or (pb >= '0' and pb <= '9') or pb == '_': ok_before = 0
if i + 5 < end:
pa: char = buf[i + 5]
if (pa >= 'A' and pa <= 'Z') or (pa >= 'a' and pa <= 'z') or (pa >= '0' and pa <= '9') or pa == '_': ok_after = 0
if ok_before != 0 and ok_after != 0: return 1
i += 1
return 0
# _lookup_cross_module_cdefine - cross-module CDefine lookup: resolve the source module
# from from_imports, parse its pyi "NAME: t.CDefine = value", then cache in the current module.
def _lookup_cross_module_cdefine(self, pool: memhub.MemBuddy*, name: str, from_imports: str, imported_modules: str = None, depth: int = 0, node: ast.AST* = None) -> int64:
"""Look up a CDefine constant across modules, returning its 64-bit value or -1"""
# recursion-depth guard: re-export chains + imported_modules walk can loop and OOM;
# cap at 32 to fail fast on cyclic imports (error carries node line + module chain).
if depth > 32:
err_buf: str = pool.alloc(256)
if err_buf is not None:
viperlib.snprintf(err_buf, 256, "cross-module CDefine lookup recursion too deep (suspected module import/re-export cycle): %s (depth=%d fi=%s)", name, depth, from_imports if from_imports is not None else "(nil)")
self.Gen.TypeGenErr.fatal_error(node, err_buf, "InternalCompilerError")
self.Gen.TypeGenErr.fatal_error(node, "cross-module CDefine lookup recursion too deep (suspected module import/re-export cycle)", "InternalCompilerError")
# clear the found flag at entry so every early return reports "not found",
# avoiding a stale success flag left over from a previous recursive call.
self.Gen.TypeGenCDef.set_cdefine_found(0)
if name is None: return -1
if from_imports is None: from_imports = ""
# 1. resolve the module name for `name` from from_imports; try exact match first
# (allow_star_fallback=0) so a star import cannot mislead CDefine constants.
mod_name_raw: str = self.Gen.ImportsGen.lookup_from_import(from_imports, name, 0)
if mod_name_raw is None:
# exact match failed: try the star import fallback
mod_name_raw = self.Gen.ImportsGen.lookup_from_import(from_imports, name, 1)
# truncate module name at NUL: lookup_from_import returns a pointer into from_imports
# (not NUL-terminated); match _lookup_sha1_from_modname_raw (space/colon/NUL delimited).
base_mod: str = None
if mod_name_raw is not None:
mbl: sizet = 0
while mod_name_raw[mbl] != ' ' and mod_name_raw[mbl] != '\0' and mod_name_raw[mbl] != ':': mbl += 1
mb_buf: str = pool.alloc(mbl + 1)
if mb_buf is not None:
mi2: sizet
for mi2 in range(int(mbl)): mb_buf[mi2] = mod_name_raw[mi2]
mb_buf[mbl] = '\0'
base_mod = mb_buf
while base_mod is not None and base_mod[0] == '.': base_mod = base_mod + 1
# cross-module CDefine cache (perm_pool, reused across files): key = "name@srcModule"
# keeps modules isolated so same-name constants from different modules do not cross.
xkey_buf: str = None
if base_mod is not None:
xk_len: sizet = string.strlen(name) + string.strlen(base_mod) + 2
xkey_buf = pool.alloc(xk_len)
if xkey_buf is not None:
viperlib.snprintf(xkey_buf, xk_len, "%s@%s", name, base_mod)
xval: str = self.Gen.TypeGenCDef.lookup_xmod_cdefine(xkey_buf)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
if self.Gen.TypeGenCDef.is_cdefine_str() != 0: self.Gen.TypeGenCDef.register_cdefine_string(pool, name, xval)
elif self.Gen.TypeGenCDef.is_cdefine_double() != 0:
xd_v: double = string.atof(xval)
# 缓存里带宽度种类(f32 声明 → 1),否则使用方会把 f32 常量当 double 物化
xd_f32: int = self.Gen.TypeGenCDef.is_cdefine_float32()
self.Gen.TypeGenCDef.register_cdefine_double(pool, name, xd_v, xd_f32)
else:
xv_i: int64 = string.atoll(xval)
self.Gen.TypeGenCDef.register_cdefine_constant(pool, name, xv_i)
return string.atoll(xval)
# 2. from_imports failed: fall back to the imported_modules walk (step 9.6);
# a star fallback may name a wrong module, so keep scanning imported_modules.
if mod_name_raw is None:
# from_imports matched nothing (exact and star both failed):
# go straight to the imported_modules walk (step 9.6).
if imported_modules is None: return -1
# skip reading the from_imports module; go directly to the step 9.6 walk.
# result_found=0 skips steps 3-9 and jumps to 9.6.
self.Gen.TypeGenCDef.set_cdefine_found(0)
# iterate imported_modules directly
im_len: sizet = string.strlen(imported_modules)
im_ci: sizet = 0
while im_ci < im_len:
while im_ci < im_len and imported_modules[im_ci] == ' ': im_ci += 1
if im_ci >= im_len: break
im_start: sizet = im_ci
while im_ci < im_len and imported_modules[im_ci] != ' ': im_ci += 1
im_end: sizet = im_ci
im_nlen: sizet = im_end - im_start
if im_nlen > 0 and im_nlen < 256:
im_buf: str = pool.alloc(im_nlen + 1)
if im_buf is not None:
for mk in range(int(im_nlen)):
im_buf[mk] = imported_modules[im_start + mk]
im_buf[im_nlen] = '\0'
sub_fi: str = pool.alloc(im_nlen + 3)
if sub_fi is not None:
sub_fi[0] = '*'
sub_fi[1] = ':'
for mk2 in range(int(im_nlen)):
sub_fi[2 + mk2] = im_buf[mk2]
sub_fi[2 + im_nlen] = '\0'
sub_val: int64 = self._lookup_cross_module_cdefine(pool, name, sub_fi, None, depth + 1, node)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0: return sub_val
return -1
# truncate module name, strip leading dots, find SHA1 via exact+suffix (shared logic)
sha1: str = self.Gen.CallGenBase._lookup_sha1_from_modname_raw(pool, mod_name_raw)
if sha1 is None: return -1
# 5. get temp_dir
temp_dir: str = self.Gen.TypeGenBase.get_temp_dir()
if temp_dir is None: return -1
# 6. build the vpi file path
td_len: sizet = string.strlen(temp_dir)
pyi_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "vpi")
if pyi_path is None: return -1
# 7. read vpi; if missing or too short (empty stub), fall back to the source .vp/.py
PYI_READ_BUF_SIZE: sizet = 65536
pyi_buf: bytes = stdlib.malloc(PYI_READ_BUF_SIZE)
if pyi_buf is None:
stdlib.free(pyi_path)
return -1
pf: fileio.File* = fileio.File(pyi_path, fileio.MODE.R)
need_py_fallback: int = 0
bytes_read: LONG = 0
if pf.closed:
# .vpi missing: fall back to the source .vp/.py
need_py_fallback = 1
else:
bytes_read = pf.read_all(pyi_buf, PYI_READ_BUF_SIZE)
pf.close()
# vpi too short (< 10 bytes): treat as empty stub, fall back to .py
if bytes_read < 10: need_py_fallback = 1
else:
if bytes_read < PYI_READ_BUF_SIZE: pyi_buf[bytes_read] = 0
# vpi stubs may declare a CDefine name without a value; the real value lives
# in the source .vp/.py, so trust vpi only when the target name's line has a value.
cd_sig: int = 0
name_has_val: int = 0
cs_name_len: sizet = string.strlen(name)
cs_total: sizet = string.strlen(pyi_buf)
cs_i: sizet = 0
while cs_i + 9 < cs_total:
if (pyi_buf[cs_i] == 't' and pyi_buf[cs_i + 1] == '.' and pyi_buf[cs_i + 2] == 'C' and pyi_buf[cs_i + 3] == 'D' and pyi_buf[cs_i + 4] == 'e' and pyi_buf[cs_i + 5] == 'f' and pyi_buf[cs_i + 6] == 'i' and pyi_buf[cs_i + 7] == 'n' and pyi_buf[cs_i + 8] == 'e'):
# check whether the rest of the line has '=' (only valued decls match in vpi)
cd_eq: int = 0
cs_j: sizet = cs_i + 9
while cs_j < cs_total and pyi_buf[cs_j] != '\n':
if pyi_buf[cs_j] == '=':
cd_eq = 1
break
cs_j += 1
if cd_eq != 0:
cd_sig = 1
# locate line start; check it begins with "name:" (target has a value)
cs_line_start: sizet = cs_i
while cs_line_start > 0 and pyi_buf[cs_line_start - 1] != '\n': cs_line_start -= 1
if cs_i - cs_line_start >= cs_name_len + 1:
nm_ok: int = 1
for ki2 in range(int(cs_name_len)):
if pyi_buf[cs_line_start + ki2] != name[ki2]:
nm_ok = 0
break
if nm_ok == 1 and pyi_buf[cs_line_start + cs_name_len] == ':': name_has_val = 1
cs_i += 1
if cd_sig == 0 or name_has_val == 0: need_py_fallback = 1
stdlib.free(pyi_path)
if need_py_fallback != 0:
# look up the rel path from the SHA1 store; build {base_dir}/{rel_path}
src_path: str = self._build_source_path_from_sha1(sha1)
if src_path is None:
# SHA1 absent from the store = compiler bug (Phase1/Phase2 not populated):
# report and abort instead of silently returning -1 (avoids bad @UNDEFINED refs).
err_buf: str = pool.alloc_tagged(4096, memhub.DETSITE_EXPRGENCDEF_263)
if err_buf is not None:
viperlib.snprintf(err_buf, 512, "cross-module CDefine lookup failed: SHA1 %s is not in the store (base_mod=%s name=%s). " "Phase1/Phase2 did not correctly populate PopulateSha1MapStore/AppendToSha1MapStore", sha1, base_mod, name)
VLogger.error(err_buf, "XMOD-CD")
stdlib.free(pyi_buf)
return -1
pf = fileio.File(src_path, fileio.MODE.R)
stdlib.free(src_path)
if pf.closed:
stdlib.free(pyi_buf)
return -1
bytes_read = pf.read_all(pyi_buf, PYI_READ_BUF_SIZE)
pf.close()
if bytes_read <= 0:
stdlib.free(pyi_buf)
return -1
if bytes_read < PYI_READ_BUF_SIZE: pyi_buf[bytes_read] = 0
# 8. search pyi_buf for the "name: t.CDefine = value" line
# format: NAME: t.CDefine = value
name_len: sizet = string.strlen(name)
total_len: sizet = string.strlen(pyi_buf)
pos: sizet = 0
result_val: int64 = 0
result_found: int = 0
res_is_double: int = 0
# 浮点宽度种类(1 = 注解声明为 32 位 float):跨模块缓存与本地注册都要带上,
# 否则使用方把 f32 常量当 double 物化(`define PI: float = ...` 现场)。
res_is_f32: int = 0
res_is_str: int = 0
while pos < total_len:
# find line start
line_start: sizet = pos
# find line end
while pos < total_len and pyi_buf[pos] != '\n': pos += 1
line_len: sizet = pos - line_start
if pos < total_len:
pos += 1 # skip '\n'
# check whether the line starts with "name:"
if line_len < name_len + 1: continue
match_ok: int = 1
for ki in range(int(name_len)):
if pyi_buf[line_start + ki] != name[ki]:
match_ok = 0
break
if match_ok == 0: continue
if pyi_buf[line_start + name_len] != ':': continue
# check the line contains the CDefine marker
has_cdefine: int = 0
ci: sizet = line_start + name_len + 1
while ci + 7 <= line_start + line_len:
if (pyi_buf[ci] == 'C' and pyi_buf[ci + 1] == 'D' and pyi_buf[ci + 2] == 'e' and pyi_buf[ci + 3] == 'f' and pyi_buf[ci + 4] == 'i' and pyi_buf[ci + 5] == 'n' and pyi_buf[ci + 6] == 'e'):
has_cdefine = 1
break
ci += 1
if has_cdefine == 0: continue
# find the value after "="
eq_pos: sizet = line_start + name_len + 1
while eq_pos < line_start + line_len and pyi_buf[eq_pos] != '=': eq_pos += 1
if eq_pos >= line_start + line_len: continue
# 注解段 = [名字冒号后, '='),用于判定声明的浮点宽度(如 `PI: float | t.CDefine`)
annot_start: sizet = line_start + name_len + 1
annot_end: sizet = eq_pos
eq_pos += 1 # skip '='
# skip spaces
while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1
# check for type-ctor forms like t.CUnsignedLong(...) / t.CLong(...) / int(...);
# if so, skip the "t.CXxx(" prefix, parse inside the parens, ignore trailing ')'.
is_type_ctor: int = 0
if eq_pos + 2 < line_start + line_len:
if pyi_buf[eq_pos] == 't' and pyi_buf[eq_pos + 1] == '.':
# find the '(' position
paren_pos: sizet = eq_pos + 2
while paren_pos < line_start + line_len and pyi_buf[paren_pos] != '(': paren_pos += 1
if paren_pos < line_start + line_len and pyi_buf[paren_pos] == '(':
is_type_ctor = 1
eq_pos = paren_pos + 1 # skip '('
# skip possible leading spaces inside the parens
while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1
# paren-wrapped flag: (1 << 23) / (512) / (A | B)
is_paren_expr: int = 0
if eq_pos < line_start + line_len and pyi_buf[eq_pos] == '(':
is_paren_expr = 1
eq_pos += 1
while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1
# === parse left operand token (number / hex / sign / name) ===
# sign flag
is_neg: int = 0
if eq_pos < line_start + line_len and pyi_buf[eq_pos] == '-':
is_neg = 1
eq_pos += 1
val_str_start: sizet = eq_pos
val_str_len: sizet = 0
is_hex: int = 0
is_float_val: int = 0
is_name_val: int = 0
is_str_val: int = 0
# string value (e.g. ZLIB_VERSION = "1.3.2"): read inside the quotes; without this,
# cross-module read-back skipped it and reported "CDefine not found".
if eq_pos < line_start + line_len and pyi_buf[eq_pos] == '"':
is_str_val = 1
eq_pos += 1 # skip opening quote
val_str_start = eq_pos
while eq_pos < line_start + line_len and pyi_buf[eq_pos] != '"':
val_str_len += 1
eq_pos += 1
if eq_pos < line_start + line_len and pyi_buf[eq_pos] == '"':
eq_pos += 1 # skip closing quote
if eq_pos + 1 < line_start + line_len:
if pyi_buf[eq_pos] == '0' and (pyi_buf[eq_pos + 1] == 'x' or pyi_buf[eq_pos + 1] == 'X'):
is_hex = 1
val_str_len = 2
eq_pos += 2
elif (pyi_buf[eq_pos] >= 'A' and pyi_buf[eq_pos] <= 'Z') or (pyi_buf[eq_pos] >= 'a' and pyi_buf[eq_pos] <= 'z') or pyi_buf[eq_pos] == '_':
is_name_val = 1
while eq_pos < line_start + line_len:
c_tk: char = pyi_buf[eq_pos]
if (c_tk >= 'A' and c_tk <= 'Z') or (c_tk >= 'a' and c_tk <= 'z') or (c_tk >= '0' and c_tk <= '9') or c_tk == '_':
val_str_len += 1
eq_pos += 1
else: break
# continue reading digit/hex chars (string values are read whole, skipped)
while eq_pos < line_start + line_len and is_name_val == 0 and is_str_val == 0:
ch: char = pyi_buf[eq_pos]
if is_hex == 1:
if ((ch >= '0' and ch <= '9') or (ch >= 'a' and ch <= 'f') or (ch >= 'A' and ch <= 'F')):
val_str_len += 1
eq_pos += 1
else: break
else:
if ch >= '0' and ch <= '9':
val_str_len += 1
eq_pos += 1
elif ch == '.' and is_float_val == 0:
# float value: '3.14159' ('.' allowed only once)
is_float_val = 1
val_str_len += 1
eq_pos += 1
else: break
if val_str_len == 0 and is_str_val == 0: continue
# copy the left token into a buffer
val_buf: str = pool.alloc(val_str_len + 1)
if val_buf is None: continue
for vi in range(int(val_str_len)): val_buf[vi] = pyi_buf[val_str_start + vi]
val_buf[val_str_len] = '\0'
# evaluate the left token
lval: int64 = 0
l_is_double: int = 0
ldbl: double = 0.0
if is_str_val != 0:
# string CDefine (e.g. ZLIB_VERSION = "1.3.2"): register the string value
# (including ""), mark result_found + res_is_str so the tail skips integers.
self.Gen.TypeGenCDef.register_cdefine_string(pool, name, val_buf)
result_val = 0
result_found = 1
res_is_str = 1
elif is_name_val != 0:
# True/False literal -> 1/0 (TPC treats bool as integer in CDefine context)
if string.strcmp(val_buf, "True") == 0: lval = 1
elif string.strcmp(val_buf, "False") == 0: lval = 0
else:
# name reference: look up the current CDefine table (e.g. FOREGROUND_COMBO)
lval = self.Gen.TypeGenCDef.lookup_cdefine_constant(val_buf)
if self.Gen.TypeGenCDef.is_cdefine_found() == 0:
# name not registered: cannot evaluate, skip the line
continue
elif is_hex == 1:
# parse hex manually (skip the 0x prefix) with 64-bit accumulation so
# 0xFFFFFFFF=4294967295 keeps full precision (constant side truncates to i32 -1).
hex_result: int64 = 0
hex_buf: str = val_buf + 2 # skip "0x"
for hi in range(int(val_str_len - 2)):
hc: char = hex_buf[hi]
hd: int = 0
if hc >= '0' and hc <= '9': hd = hc - '0'
elif hc >= 'a' and hc <= 'f': hd = hc - 'a' + 10
elif hc >= 'A' and hc <= 'F': hd = hc - 'A' + 10
else: break
hex_result = hex_result * 16 + hd
lval = hex_result
elif is_float_val != 0:
l_is_double = 1
ldbl = string.atof(val_buf)
lval = string.atoll(val_buf)
else: lval = string.atoll(val_buf)
if is_neg != 0:
lval = -lval
if l_is_double != 0: ldbl = -ldbl
# skip spaces; check for a binary operator
while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1
# parse operator (<< >> | & ^ + - * /)
cd_op: int = 0
if eq_pos + 1 < line_start + line_len:
if pyi_buf[eq_pos] == '<' and pyi_buf[eq_pos + 1] == '<':
cd_op = 1 # LShift
eq_pos += 2
elif pyi_buf[eq_pos] == '>' and pyi_buf[eq_pos + 1] == '>':
cd_op = 2 # RShift
eq_pos += 2
if cd_op == 0 and eq_pos < line_start + line_len:
if pyi_buf[eq_pos] == '|':
cd_op = 3 # BitOr
eq_pos += 1
elif pyi_buf[eq_pos] == '&':
cd_op = 4 # BitAnd
eq_pos += 1
elif pyi_buf[eq_pos] == '^':
cd_op = 5 # BitXor
eq_pos += 1
elif pyi_buf[eq_pos] == '+':
cd_op = 6 # Add
eq_pos += 1
elif pyi_buf[eq_pos] == '-':
cd_op = 7 # Sub
eq_pos += 1
elif pyi_buf[eq_pos] == '*':
cd_op = 8 # Mult
eq_pos += 1
elif pyi_buf[eq_pos] == '/':
cd_op = 9 # Div
eq_pos += 1
if cd_op != 0:
# === parse right operand token ===
while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1
r_neg: int = 0
if eq_pos < line_start + line_len and pyi_buf[eq_pos] == '-':
r_neg = 1
eq_pos += 1
r_start: sizet = eq_pos
r_len: sizet = 0
r_hex: int = 0
r_name: int = 0
if eq_pos + 1 < line_start + line_len:
if pyi_buf[eq_pos] == '0' and (pyi_buf[eq_pos + 1] == 'x' or pyi_buf[eq_pos + 1] == 'X'):
r_hex = 1
r_len = 2
eq_pos += 2
elif (pyi_buf[eq_pos] >= 'A' and pyi_buf[eq_pos] <= 'Z') or (pyi_buf[eq_pos] >= 'a' and pyi_buf[eq_pos] <= 'z') or pyi_buf[eq_pos] == '_':
r_name = 1
while eq_pos < line_start + line_len:
c_r: char = pyi_buf[eq_pos]
if (c_r >= 'A' and c_r <= 'Z') or (c_r >= 'a' and c_r <= 'z') or (c_r >= '0' and c_r <= '9') or c_r == '_':
r_len += 1
eq_pos += 1
else: break
while eq_pos < line_start + line_len and r_name == 0:
c_r2: char = pyi_buf[eq_pos]
if r_hex == 1:
if ((c_r2 >= '0' and c_r2 <= '9') or (c_r2 >= 'a' and c_r2 <= 'f') or (c_r2 >= 'A' and c_r2 <= 'F')):
r_len += 1
eq_pos += 1
else: break
else:
if c_r2 >= '0' and c_r2 <= '9':
r_len += 1
eq_pos += 1
else: break
if r_len == 0:
# no right operand after operator: cannot evaluate, skip the line
continue
r_buf: str = pool.alloc(r_len + 1)
if r_buf is None: continue
for r_i in range(int(r_len)): r_buf[r_i] = pyi_buf[r_start + r_i]
r_buf[r_len] = '\0'
rval: int64 = 0
if r_name != 0:
# True/False literal -> 1/0
if string.strcmp(r_buf, "True") == 0: rval = 1
elif string.strcmp(r_buf, "False") == 0: rval = 0
else:
rval = self.Gen.TypeGenCDef.lookup_cdefine_constant(r_buf)
if self.Gen.TypeGenCDef.is_cdefine_found() == 0:
# name not registered: cannot evaluate, skip the line
continue
elif r_hex != 0:
rhex_v: int64 = 0
rh_buf: str = r_buf + 2
for rh_i in range(int(r_len - 2)):
rc: char = rh_buf[rh_i]
rd: int = 0
if rc >= '0' and rc <= '9': rd = rc - '0'
elif rc >= 'a' and rc <= 'f': rd = rc - 'a' + 10
elif rc >= 'A' and rc <= 'F': rd = rc - 'A' + 10
else: break
rhex_v = rhex_v * 16 + rd
rval = rhex_v
else: rval = string.atoll(r_buf)
if r_neg != 0: rval = -rval
# compute the binary expression result (aligned with TPC ConstEvaluator)
if cd_op == 1: result_val = lval << rval
elif cd_op == 2: result_val = lval >> rval
elif cd_op == 3: result_val = lval | rval
elif cd_op == 4: result_val = lval & rval
elif cd_op == 5: result_val = lval ^ rval
elif cd_op == 6: result_val = lval + rval
elif cd_op == 7: result_val = lval - rval
elif cd_op == 8: result_val = lval * rval
else:
if rval == 0: continue
result_val = lval // rval
result_found = 1
break
else:
# single value (no operator): parenthesized values like (512) / (-11) also land here
if is_paren_expr != 0:
while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1
if eq_pos < line_start + line_len and pyi_buf[eq_pos] == ')': eq_pos += 1
if is_float_val != 0:
# float CDefine (e.g. U_M_PI = 3.14159...): cache as double for double
# constant generation; int part serves the int path (is_cdefine_double flag).
# 注解声明为 float 时带上 f32 种类,使用方才能直接出 f32 常量。
res_is_f32 = self._pyi_annot_float_kind(pyi_buf, annot_start, annot_end)
self.Gen.TypeGenCDef.register_cdefine_double(pool, name, ldbl, res_is_f32)
result_val = lval
result_found = 1
res_is_double = 1
else:
result_val = lval
result_found = 1
break
# 9.5 if not found in this module, recurse into submodules for `from .X import NAME` /
# `from .X import *` (e.g. ast.__init__ re-exports CONST_INT from .base).
if result_found == 0:
# base_mod was computed at function entry (mod_name_raw with leading dots removed);
# the submodule recursion uses it to build the full "base_mod.X" name.
name_len_rc: sizet = string.strlen(name)
rc_pos: sizet = 0
while rc_pos < total_len and result_found == 0:
# find line start
rc_line_start: sizet = rc_pos
# find line end
while rc_pos < total_len and pyi_buf[rc_pos] != '\n': rc_pos += 1
rc_line_len: sizet = rc_pos - rc_line_start
if rc_pos < total_len: rc_pos += 1
# check whether the line starts with "from " (module import)
if rc_line_len < 12: continue
if pyi_buf[rc_line_start] != 'f' or pyi_buf[rc_line_start+1] != 'r' or pyi_buf[rc_line_start+2] != 'o' or pyi_buf[rc_line_start+3] != 'm' or pyi_buf[rc_line_start+4] != ' ': continue
# extract the module token (skip leading dots); support both relative
# `from .base import X` (join base_mod.base) and absolute `from vqt6._types import *`.
rc_is_rel: int = 0
rc_sub_start: sizet = rc_line_start + 5
while rc_sub_start < rc_line_start + rc_line_len and pyi_buf[rc_sub_start] == '.':
rc_is_rel = 1
rc_sub_start += 1
rc_sub_end: sizet = rc_sub_start
while rc_sub_end < rc_line_start + rc_line_len and pyi_buf[rc_sub_end] != ' ' and pyi_buf[rc_sub_end] != '\t': rc_sub_end += 1
rc_sub_len: sizet = rc_sub_end - rc_sub_start
if rc_sub_len == 0: continue
# check whether the line contains "import"
rc_has_import: int = 0
rc_ipos: sizet = rc_sub_end
while rc_ipos + 6 <= rc_line_start + rc_line_len:
if pyi_buf[rc_ipos] == 'i' and pyi_buf[rc_ipos+1] == 'm' and pyi_buf[rc_ipos+2] == 'p' and pyi_buf[rc_ipos+3] == 'o' and pyi_buf[rc_ipos+4] == 'r' and pyi_buf[rc_ipos+5] == 't':
rc_has_import = 1
break
rc_ipos += 1
if rc_has_import == 0: continue
# check NAME is in the import list, or the import is *; handle multi-line
# imports: scan from "import" up to ")" when a "(" is present.
rc_imp_start: sizet = rc_ipos + 6
rc_star: int = 0
rc_name_match: int = 0
# determine the scan range: single line or multi-line (inside parens)
rc_scan_end: sizet = rc_line_start + rc_line_len
rc_has_paren: int = 0
rc_pp: sizet = rc_imp_start
while rc_pp < rc_line_start + rc_line_len:
if pyi_buf[rc_pp] == '(':
rc_has_paren = 1
break
if pyi_buf[rc_pp] == '*': rc_star = 1
rc_pp += 1
if rc_has_paren != 0:
# multi-line import: scan from after "(" to ")"
rc_scan_end = rc_pp + 1
rc_paren_closed: int = 0
rc_sp: sizet = rc_pp + 1
while rc_sp < total_len and rc_paren_closed == 0:
if pyi_buf[rc_sp] == ')':
rc_paren_closed = 1
rc_scan_end = rc_sp
break
rc_sp += 1
if rc_paren_closed == 0: rc_scan_end = total_len
# scan from rc_imp_start to rc_scan_end for NAME and *
rc_cpos: sizet = rc_imp_start
while rc_cpos < rc_scan_end:
rc_ch: char = pyi_buf[rc_cpos]
if rc_ch == '*': rc_star = 1
# check NAME matches as a whole word
if rc_cpos + name_len_rc <= rc_scan_end:
rc_match: int = 1
rc_ki: sizet
for rc_ki in range(int(name_len_rc)):
if pyi_buf[rc_cpos + rc_ki] != name[rc_ki]:
rc_match = 0
break
if rc_match == 1:
# check the preceding char is a non-identifier char
rc_before_ok: int = 0
if rc_cpos == rc_imp_start: rc_before_ok = 1
else:
rc_before_ch: char = pyi_buf[rc_cpos - 1]
if rc_before_ch == ' ' or rc_before_ch == '\t' or rc_before_ch == '(' or rc_before_ch == ',' or rc_before_ch == '\n' or rc_before_ch == '\r': rc_before_ok = 1
# check the following char is a non-identifier char
rc_after_ok: int = 0
rc_after_pos: sizet = rc_cpos + name_len_rc
if rc_after_pos >= rc_scan_end: rc_after_ok = 1
else:
rc_after_ch: char = pyi_buf[rc_after_pos]
if rc_after_ch == ' ' or rc_after_ch == '\t' or rc_after_ch == ')' or rc_after_ch == ',' or rc_after_ch == '\r' or rc_after_ch == '\n': rc_after_ok = 1
if rc_before_ok == 1 and rc_after_ok == 1: rc_name_match = 1
rc_cpos += 1
if rc_star == 0 and rc_name_match == 0: continue
# copy the submodule name into a new buffer (pyi_buf + rc_sub_start is not NUL-terminated)
rc_sub_buf: str = pool.alloc(rc_sub_len + 1)
if rc_sub_buf is None: continue
rc_si: sizet
for rc_si in range(int(rc_sub_len)): rc_sub_buf[rc_si] = pyi_buf[rc_sub_start + rc_si]
rc_sub_buf[rc_sub_len] = '\0'
# build the full module name: relative import -> package + "." + token (the base
# must be the package, not the module name); absolute import -> token as-is.
rc_full_mod: str = None
if rc_is_rel != 0:
rc_base_mod: str = base_mod
rc_pkg: str = self.Gen.CallGenBase._module_package_from_sha1(pool, sha1)
if rc_pkg is not None: rc_base_mod = rc_pkg
rc_full_len: sizet = string.strlen(rc_base_mod) + 1 + rc_sub_len + 1
rc_full_buf: str = pool.alloc(rc_full_len)
if rc_full_buf is None: continue
viperlib.snprintf(rc_full_buf, rc_full_len, "%s.%s", rc_base_mod, rc_sub_buf)
rc_full_mod = rc_full_buf
else:
if string.strcmp(rc_sub_buf, base_mod) == 0: continue
rc_full_mod = rc_sub_buf
# loop guard: if base_mod already ends with "."+rc_sub_buf (ast.base + .base),
# the recursion is re-appending the same segment -> skip (infinite-growth root cause).
rc_blen: sizet = string.strlen(base_mod)
if rc_blen > rc_sub_len + 1:
rc_tail: str = base_mod + (rc_blen - rc_sub_len)
if rc_tail[0] == '.' and string.strncmp(rc_tail + 1, rc_sub_buf, rc_sub_len) == 0: continue
# build a temporary from_imports "name:full_mod"
rc_fi_len: sizet = name_len_rc + 1 + string.strlen(rc_full_mod) + 1
rc_fi: str = pool.alloc(rc_fi_len)
if rc_fi is None: continue
viperlib.snprintf(rc_fi, rc_fi_len, "%s:%s", name, rc_full_mod)
# recursive call to search the submodule
rc_sub_val: int64 = self._lookup_cross_module_cdefine(pool, name, rc_fi, None, depth + 1, node)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
result_val = rc_sub_val
result_found = 1
stdlib.free(pyi_buf)
# 9.6 star-fallback module had no match: fall back to walking imported_modules,
# e.g. `from stdint import *` names stdint but SHARE_READ lives in `import win32.fileio`.
if result_found == 0 and imported_modules is not None:
im2_len: sizet = string.strlen(imported_modules)
im2_ci: sizet = 0
while im2_ci < im2_len and result_found == 0:
while im2_ci < im2_len and imported_modules[im2_ci] == ' ': im2_ci += 1
if im2_ci >= im2_len: break
im2_start: sizet = im2_ci
while im2_ci < im2_len and imported_modules[im2_ci] != ' ': im2_ci += 1
im2_end: sizet = im2_ci
im2_nlen: sizet = im2_end - im2_start
if im2_nlen > 0 and im2_nlen < 256:
im2_buf: str = pool.alloc(im2_nlen + 1)
if im2_buf is not None:
for mk3 in range(int(im2_nlen)):
im2_buf[mk3] = imported_modules[im2_start + mk3]
im2_buf[im2_nlen] = '\0'
sub_fi2: str = pool.alloc(im2_nlen + 3)
if sub_fi2 is not None:
sub_fi2[0] = '*'
sub_fi2[1] = ':'
for mk4 in range(int(im2_nlen)):
sub_fi2[2 + mk4] = im2_buf[mk4]
sub_fi2[2 + im2_nlen] = '\0'
sub_val2: int64 = self._lookup_cross_module_cdefine(pool, name, sub_fi2, None, depth + 1, node)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
result_val = sub_val2
result_found = 1
# 10. cache into the current module's CDefine table and the cross-module perm_pool;
# float CDefines are already cached as double, so only integers are written here.
if result_found != 0:
if res_is_str != 0:
# string CDefine: the value was already registered in the match loop (val_buf is
# the hit line's content, valid across the function); only write the cross cache.
if base_mod is not None:
ck_len_s: sizet = string.strlen(name) + string.strlen(base_mod) + 2
ckey_s: str = pool.alloc(ck_len_s)
if ckey_s is not None:
viperlib.snprintf(ckey_s, ck_len_s, "%s@%s", name, base_mod)
self.Gen.TypeGenCDef.register_xmod_cdefine(ckey_s, val_buf, 0, 1)
elif res_is_double == 0:
self.Gen.TypeGenCDef.register_cdefine_constant(pool, name, result_val)
# cross-module cache (perm_pool, reused across files), integers: key matches the
# entry query "name@srcModule" (truncated base_mod, leading dots removed).
if base_mod is not None:
ck_len: sizet = string.strlen(name) + string.strlen(base_mod) + 2
ckey_buf: str = pool.alloc(ck_len)
if ckey_buf is not None:
viperlib.snprintf(ckey_buf, ck_len, "%s@%s", name, base_mod)
cbuf_i: str = pool.alloc(32)
if cbuf_i is not None:
viperlib.snprintf(cbuf_i, 32, "%lld", result_val)
self.Gen.TypeGenCDef.register_xmod_cdefine(ckey_buf, cbuf_i, 0, 0)
else:
# float: fetch %.17g text from the local table and write the cross cache (lossless)
if base_mod is not None:
ck_len2: sizet = string.strlen(name) + string.strlen(base_mod) + 2
ckey2: str = pool.alloc(ck_len2)
if ckey2 is not None:
viperlib.snprintf(ckey2, ck_len2, "%s@%s", name, base_mod)
dval_cc: double = self.Gen.TypeGenCDef.lookup_cdefine_double(name)
if self.Gen.TypeGenCDef.is_cdefine_double() != 0:
cbuf_d: str = pool.alloc(64)
if cbuf_d is not None:
stdio.snprintf(cbuf_d, 64, "%.17g", dval_cc)
# 浮点种类随缓存一起带上(1=f64 / 2=f32),供后续使用方直接物化宽度
self.Gen.TypeGenCDef.register_xmod_cdefine(ckey2, cbuf_d, self.Gen.TypeGenCDef.xmod_float_kind(), 0)
self.Gen.TypeGenCDef.set_cdefine_found(1)
else: self.Gen.TypeGenCDef.set_cdefine_found(0)
return result_val
# _is_cdefine_name_pattern - CDefine names follow ALL_CAPS (A-Z, 0-9, _ with >=1 uppercase);
# lowercase keywords/variables won't match, avoiding fail-fast false positives. 1=match, 0=no.
def _is_cdefine_name_pattern(self, name: str) -> int:
"""Return 1 if name follows the CDefine convention (uppercase letters, digits, underscore)"""
if name is None: return 0
has_upper: int = 0
i: sizet = 0
while name[i] != '\0':
c: char = name[i]
if c >= 'a' and c <= 'z': return 0
if c >= 'A' and c <= 'Z': has_upper = 1
i += 1
return has_upper
# ============================================================
# Translate a variable reference (Name node) -> load
def _const_int_for_cdefine(self, pool: memhub.MemBuddy*, name: str, value: int64) -> mlirlite.Value*:
"""Materialize a CDefine integer constant as an LLVM constant (TPC TypeConvert aligned)
位宽优先用 define 的**已知位宽**(右侧显式强转或声明注解,见 register_cdefine_constant 的 bits);
没有位宽记录时按**推断位宽**物化(值的最小可存宽度;0x80000000 → 32)。
这样 `define X: WORD = WORD(...)` 的 X 保持 u16、`X | Y` 里两个同宽 u16 不会被提升成
i32;无注解的 `define A = 0x80000000` 也不会退化成 i64 把 `A | B` 放大到 64 位,
而小值 define 说成 8/16 也在算术提升下不会溢出。
"""
dbits: int = 0
if name is not None: dbits = self.Gen.TypeGenCDef.lookup_cdefine_bits(name)
if dbits == 8 or dbits == 16 or dbits == 32 or dbits == 64:
return mlirlite.const_int(pool, dbits, self._cdefine_bits_value(value, dbits))
# 无位宽记录(例如只从 .vpi 跨模块引入、未登记 bits)→ 按**推断位宽**物化:
# 值的最小可存宽度(2^31..2^32-1 取 32 位无符号位型,不退化成 i64;
# 小值取 8/16,参与算术时由整数提升先升到 i32,不会溢出)。
wmin: int = self.Gen.AssignGen.define_min_width(value)
return mlirlite.const_int(pool, wmin, self._cdefine_bits_value(value, wmin))
# ============================================================
# cdefine_use_width_value - materialize CDefine constants at the use-site width (TPC-aligned)
def _cdefine_bits_value(self, value: int64, bits: int) -> int64:
"""把值按位宽处理:**只有在当前位宽的两种解释下都装不下时才截断**,否则原样保留。
「左侧只会看到截断了的数据」:超宽的值先按 bits 取低位(如 WORD(0x80000000) → 0)。
但不该把"无符号可容、有符号装不下"的**正值**重解释成负数:旧实现无条件
`(value << 56) >> 56`,8 位下的 160 被读成 -96,正值静默变负 —— 现场
Gargantua `_start` 的 `string.memset(fb, 0, GW * GH * 4)`(GW=160, GH=90)
被折成 -34560,作为长度写穿映射区 → #PF。位型不变(const_int 本就按位宽截断),
只是不再翻转可容正值的语义。实现仍避开大掩码字面量(见下)。
"""
if bits == 8:
if value >= -128 and value <= 127: return value
if value > 127 and (value >> 8) == 0: return value
return (value << 56) >> 56
if bits == 16:
if value >= -32768 and value <= 32767: return value
if value > 32767 and (value >> 16) == 0: return value
return (value << 48) >> 48
if bits == 32:
if value >= -2147483647 - 1 and value <= 2147483647: return value
if value > 2147483647 and (value >> 32) == 0: return value
return (value << 32) >> 32
return value
def cdefine_use_width_value(self, pool: memhub.MemBuddy*, node: ast.AST*, name: str, value: int64, target_ty: mlirlite.LLVMType*, use_ctx: str = None) -> mlirlite.Value*:
"""Materialize a CDefine integer constant at a known use-site width, or None on fallback"""
bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(target_ty)
if bits != 8 and bits != 16 and bits != 32 and bits != 64: return None
wmin: int = self.Gen.AssignGen.define_min_width(value)
if bits < wmin:
# "representable range at the use site" uses the same rule as the check = TPC
# DefineWidthLoHi = [-(2^(bits-1)), 2^bits - 1]; literals built from safe 2147483647.
m31: int64 = 2147483647
lo: int64 = -m31 - 1
hi: int64 = m31 * 2 + 1
if bits == 8:
lo = -128
hi = 255
elif bits == 16:
lo = -32768
hi = 65535
ctx: str = ""
if use_ctx is not None:
# this language's str is i8* with no `str + str`: `+` degrades to pointer add.
# Build strings with f-string interpolation / string.strcat / viperlib.snprintf.
ctx = f"({use_ctx})"
self.Gen.TypeGenErr.fatal_error(node, f"[VPC] define constant use site width insufficient: constant '{name}' actual value {value} " f"needs at least {wmin} bits (minimum representable width W_min), " f"but the use site{ctx} provides only {bits} bits " f"(the acceptable range for this bit width under both signed/unsigned interpretation: {lo}..{hi}). " f"silent truncation/downgrade is forbidden, compilation aborted. " f"fix: widen the type of the use site{ctx} to at least {wmin} bits " f"(e.g. int{wmin}T / CUInt{wmin}T or wider types), or use a smaller constant value.", "TypeError")
return None
return mlirlite.const_int(pool, bits, self._cdefine_bits_value(value, bits))
+383
View File
@@ -0,0 +1,383 @@
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdlib
import viperlib
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.Translator as HT
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
# docstring registry capacity constants (kept in sync with ExprGenMixin)
define DOC_MAX = 512
define DOC_KEY_SZ = 192
define DOC_VAL_SZ = 4096
# ExprGenCls - split of ExprGenMixin (stage D): class-name inference / docstring registry
novirtual class ExprGenCls(ExprGenBase.ExprGenBase):
"""Expression class inference and docstring registry."""
# docstring extract/register/lookup (__doc__ support): pre-registration scans
# ASTs into a global "{module_sha1}.{symbol}" table; lookups inline the string.
def _extract_docstring_from_body(self, body: list[ast.AST*]*) -> str:
"""Extract a docstring from a body when body[0] is an Expr(Constant str)."""
if body is None or len(body) == 0: return None
first: ast.AST* = body.get(0)
if first is None or first.kind() != ast.ASTKind.Expr: return None
ex: ast.Expr* = ast.Expr*(first)
if ex is None or ex.value is None or ex.value.kind() != ast.ASTKind.Constant: return None
cn: ast.Constant* = ast.Constant*(ex.value)
if cn is None or cn.const_kind != ast.CONST_STR: return None
return cn.str_val
def register_docstring(self, module_sha1: str, symbol: str, doc: str) -> None:
"""Register a docstring under key "{module_sha1}.{symbol}", deduplicated."""
if module_sha1 is None or symbol is None or doc is None: return
if string.strlen(doc) == 0: return
if self.DocKeys is None:
self.DocKeys = CompilerFactory.factory.PermPool.alloc(DOC_MAX * DOC_KEY_SZ)
self.DocValues = CompilerFactory.factory.PermPool.alloc(DOC_MAX * DOC_VAL_SZ)
if self.DocKeys is None or self.DocValues is None: return
string.memset(self.DocKeys, 0, DOC_MAX * DOC_KEY_SZ)
string.memset(self.DocValues, 0, DOC_MAX * DOC_VAL_SZ)
if self.DocCount >= DOC_MAX: return
ml: sizet = string.strlen(module_sha1)
sl: sizet = string.strlen(symbol)
key_buf: bytes = stdlib.malloc(ml + sl + 2)
if key_buf is None: return
viperlib.snprintf(key_buf, ml + sl + 2, "%s.%s", module_sha1, symbol)
for i in range(int(self.DocCount)):
if string.strcmp(self.DocKeys + i * DOC_KEY_SZ, key_buf) == 0:
stdlib.free(key_buf)
return
string.strcpy(self.DocKeys + self.DocCount * DOC_KEY_SZ, key_buf)
string.strcpy(self.DocValues + self.DocCount * DOC_VAL_SZ, doc)
self.DocCount += 1
stdlib.free(key_buf)
def lookup_docstring(self, module_sha1: str, symbol: str) -> str:
"""Look up a docstring by "{module_sha1}.{symbol}", or None on miss."""
if self.DocKeys is None or module_sha1 is None or symbol is None: return None
ml: sizet = string.strlen(module_sha1)
sl: sizet = string.strlen(symbol)
key_buf: bytes = stdlib.malloc(ml + sl + 2)
if key_buf is None: return None
viperlib.snprintf(key_buf, ml + sl + 2, "%s.%s", module_sha1, symbol)
for i in range(int(self.DocCount)):
if string.strcmp(self.DocKeys + i * DOC_KEY_SZ, key_buf) == 0:
stdlib.free(key_buf)
return self.DocValues + i * DOC_VAL_SZ
stdlib.free(key_buf)
return None
def extract_module_docstrings(self, tree: ast.AST*, module_sha1: str) -> None:
"""Scan a module AST and register docstrings of its functions, classes and methods."""
if tree is None or module_sha1 is None: return
ch: list[ast.AST*]* = tree.children
if ch is None: return
cn: sizet = len(ch)
for ci in range(int(cn)):
child: ast.AST* = ch.get(ci)
if child is None: continue
kd: int = child.kind()
if kd == ast.ASTKind.FunctionDef:
fd: ast.FunctionDef* = ast.FunctionDef*(child)
if fd is not None and fd.name is not None:
fd_body: list[ast.AST*]* = fd.children
fdoc: str = self._extract_docstring_from_body(fd_body)
if fdoc is not None: self.register_docstring(module_sha1, fd.name, fdoc)
elif kd == ast.ASTKind.ClassDef:
cd: ast.ClassDef* = ast.ClassDef*(child)
if cd is None or cd.name is None: continue
cd_body0: list[ast.AST*]* = cd.children
cdoc: str = self._extract_docstring_from_body(cd_body0)
if cdoc is not None: self.register_docstring(module_sha1, cd.name, cdoc)
# class method docstring (key="{ClassName}.{method}")
cd_body: list[ast.AST*]* = cd.children
if cd_body is not None:
cbn: sizet = len(cd_body)
for mi in range(int(cbn)):
m: ast.AST* = cd_body.get(mi)
if m is None or m.kind() != ast.ASTKind.FunctionDef: continue
mfd: ast.FunctionDef* = ast.FunctionDef*(m)
if mfd is None or mfd.name is None: continue
mdoc: str = self._extract_docstring_from_body(mfd.children)
if mdoc is not None:
cl_nl: sizet = string.strlen(cd.name)
mt_nl: sizet = string.strlen(mfd.name)
sym_buf: bytes = stdlib.malloc(cl_nl + mt_nl + 2)
if sym_buf is not None:
viperlib.snprintf(sym_buf, cl_nl + mt_nl + 2, "%s.%s", cd.name, mfd.name)
self.register_docstring(module_sha1, sym_buf, mdoc)
stdlib.free(sym_buf)
def _resolve_doc_symbol_name(self, trans: HT.Translator*, name_id: str, out_mod: str, out_sym: str) -> int:
"""Resolve a Name symbol's docstring owner into out_mod/out_sym; return 1 on success."""
if trans is None or name_id is None or out_mod is None or out_sym is None: return 0
if trans.ModuleSha1 is None: return 0
# 1. local variable (function/global)
if self.Gen.VarGen.lookup_var(trans.SymTab, name_id) is not None:
string.strcpy(out_mod, trans.ModuleSha1)
string.strcpy(out_sym, name_id)
return 1
# 2. struct class (entry.ModuleSha1 distinguishes cross-module pre-registration)
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(name_id)
if se is not None:
if se.ModuleSha1 is not None: string.strcpy(out_mod, se.ModuleSha1)
else: string.strcpy(out_mod, trans.ModuleSha1)
string.strcpy(out_sym, name_id)
return 1
# 3. module name (rare); use StubMerger module-level functions to avoid a
# cross-Mixin self call that would emit a nonexistent symbol.
mod_sha1: str = StubMergerStore.LookupSha1ByModName(name_id)
if mod_sha1 is None: mod_sha1 = StubMergerStore.LookupSha1ByModNameSuffix(name_id)
if mod_sha1 is not None:
string.strcpy(out_mod, mod_sha1)
string.strcpy(out_sym, name_id)
return 1
# 4. fall back to the current module
string.strcpy(out_mod, trans.ModuleSha1)
string.strcpy(out_sym, name_id)
return 1
def _resolve_doc_symbol_attr(self, trans: HT.Translator*, at: ast.Attribute*, out_mod: str, out_sym: str) -> int:
"""Resolve a nested Attribute chain's docstring owner into out_mod/out_sym."""
if trans is None or at is None or out_mod is None or out_sym is None: return 0
if trans.ModuleSha1 is None: return 0
# collect the Attribute chain (outermost attr first)
DOC_CHAIN_MAX: int = 8
items: bytes = stdlib.malloc(DOC_CHAIN_MAX * 128)
if items is None: return 0
n_items: int = 0
cur: ast.AST* = (ast.AST*)(at)
while cur is not None and cur.kind() == ast.ASTKind.Attribute and n_items < DOC_CHAIN_MAX:
cat: ast.Attribute* = ast.Attribute*(cur)
if cat.attr is None: break
string.strcpy(items + n_items * 128, cat.attr)
n_items += 1
cur = cat.value
# leftmost must be a Name
head_id: str = None
if cur is not None and cur.kind() == ast.ASTKind.Name:
hnm: ast.Name* = ast.Name*(cur)
head_id = hnm.id
if head_id is None:
stdlib.free(items)
return 0
# head decision: module name or variable/class
is_mod: int = 0
mod_sha1: str = None
if self.Gen.VarGen.lookup_var(trans.SymTab, head_id) is None:
mod_sha1 = StubMergerStore.LookupSha1ByModName(head_id)
if mod_sha1 is None: mod_sha1 = StubMergerStore.LookupSha1ByModNameSuffix(head_id)
if mod_sha1 is not None: is_mod = 1
if is_mod != 0:
# module-qualified: symbol = items reversed (excluding head_id)
if n_items == 0:
string.strcpy(out_mod, mod_sha1)
string.strcpy(out_sym, head_id)
stdlib.free(items)
return 1
total: sizet = 0
for j in range(int(n_items)): total += string.strlen(items + j * 128) + 1
sym_buf: bytes = stdlib.malloc(total + 1)
if sym_buf is None:
stdlib.free(items)
return 0
sym_buf[0] = '\0'
for j in range(int(n_items)):
if j > 0: string.strcat(sym_buf, ".")
string.strcat(sym_buf, items + (n_items - 1 - j) * 128)
string.strcpy(out_mod, mod_sha1)
string.strcpy(out_sym, sym_buf)
stdlib.free(sym_buf)
stdlib.free(items)
return 1
# variable/class: symbol = head_id + '.' + items reversed
total2: sizet = string.strlen(head_id)
for j in range(int(n_items)): total2 += string.strlen(items + j * 128) + 1
sym2: bytes = stdlib.malloc(total2 + 1)
if sym2 is None:
stdlib.free(items)
return 0
sym2[0] = '\0'
string.strcat(sym2, head_id)
for j in range(int(n_items)):
string.strcat(sym2, ".")
string.strcat(sym2, items + (n_items - 1 - j) * 128)
se2: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(head_id)
if se2 is not None and se2.ModuleSha1 is not None: string.strcpy(out_mod, se2.ModuleSha1)
else: string.strcpy(out_mod, trans.ModuleSha1)
string.strcpy(out_sym, sym2)
stdlib.free(sym2)
stdlib.free(items)
return 1
# P3 migrated methods (from HandlesExprOps.vp: operator overload + binary ops)
# infer_expr_class - infer the struct class name from an expression (shared by
# unannotated assignment and method dispatch); returns None if undecidable.
def infer_expr_class(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator*) -> str:
"""Infer the struct class name from an expression, or None if undecidable."""
if node is None or trans is None: return None
k: int = node.kind()
# Call: class instantiation / method call
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
if cl is None or cl.func is None: return None
if cl.func.kind() == ast.ASTKind.Name:
cand: str = (ast.Name*(cl.func)).id
if cand is None: return None
if self.Gen.StructGen.find_struct(cand) is not None: return cand
return None
if cl.func.kind() == ast.ASTKind.Attribute:
# Method call a.cross(b): resolve the return class from the vpi return
# annotation cache (may differ from the receiver class).
ca: ast.Attribute* = ast.Attribute*(cl.func)
if ca is None or ca.attr is None or ca.value is None: return None
# module-level call (Module.func()): receiver is an imported module name
if ca.value.kind() == ast.ASTKind.Name:
mod_nm2: str = (ast.Name*(ca.value)).id
if mod_nm2 is not None and trans._from_imports is not None:
mod_sha1_2: str = self.Gen.CallGenBase._lookup_mod_sha1(pool, trans._from_imports, mod_nm2)
if mod_sha1_2 is not None:
temp_dir2: str = None
if self.Gen.CallGenBase.TempDir is not None: temp_dir2 = self.Gen.CallGenBase.TempDir
if temp_dir2 is not None: VpiReader._ensure_vpi_ret_annot_loaded(mod_sha1_2, temp_dir2)
annot_ret2: str = VpiReader._lookup_vpi_ret_annot(mod_sha1_2, None, ca.attr)
if annot_ret2 is not None:
ret_cls2: str = self._extract_annot_class_name(pool, annot_ret2)
if ret_cls2 is not None: return ret_cls2
obj_cls: str = self.infer_expr_class(pool, mod, ca.value, trans)
if obj_cls is None: return None
if self._cls_has_dunder(pool, mod, obj_cls, ca.attr) == 0: return None
cls_sha1: str = self.Gen.StructGen.get_struct_sha1(obj_cls)
if cls_sha1 is None and trans.ModuleSha1 is not None: cls_sha1 = trans.ModuleSha1
ret_cls: str = None
if cls_sha1 is not None:
temp_dir: str = None
# cross sub-object access via self.Gen for CallGenBase.TempDir
if self.Gen.CallGenBase.TempDir is not None: temp_dir = self.Gen.CallGenBase.TempDir
if temp_dir is not None: VpiReader._ensure_vpi_ret_annot_loaded(cls_sha1, temp_dir)
annot_ret: str = VpiReader._lookup_vpi_ret_annot(cls_sha1, obj_cls, ca.attr)
if annot_ret is not None: ret_cls = self._extract_annot_class_name(pool, annot_ret)
return ret_cls
return None
# Name: source variable annotation
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm is None or nm.id is None: return None
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if ve is None: return None
acn_n: str = self.Gen.VarGen.var_annot_class_name(ve)
if acn_n is None: return None
# Strip the module prefix (json.JsonValue -> JsonValue): the lookups use
# bare names; generic names containing '[' are not truncated.
acn_dot_n: str = string.strrchr(acn_n, 46)
if acn_dot_n is not None and string.strchr(acn_n, 91) is None: acn_n = acn_dot_n + 1
return acn_n
# BinOp/UnaryOp: operator overload returns the same class object
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop is None or bop.left is None: return None
dunder: str = self.Gen.ExprGenOps._binop_to_dunder(bop.op)
if dunder is None: return None
lhs_cls: str = self.infer_expr_class(pool, mod, bop.left, trans)
if lhs_cls is None or self._cls_has_dunder(pool, mod, lhs_cls, dunder) == 0: return None
return lhs_cls
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(node)
if uop is None or uop.operand is None: return None
dunder: str = self.Gen.ExprGenOps._unaryop_to_dunder(uop.op)
if dunder is None: return None
opr_cls: str = self.infer_expr_class(pool, mod, uop.operand, trans)
if opr_cls is None or self._cls_has_dunder(pool, mod, opr_cls, dunder) == 0: return None
return opr_cls
# Attribute: field annotation class name (recursive chain, e.g. p = r.origin)
if k == ast.ASTKind.Attribute: return self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, node)
return None
# _cls_has_dunder - whether a class (or its MRO ancestors) has the dunder/method;
# defined in this module or declared in a cross-module stub SHA1.
def _cls_has_dunder(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, class_name: str, dunder: str) -> int:
"""Return 1 if the class or its MRO ancestors define the dunder method."""
if class_name is None or dunder is None or mod is None: return 0
lookup_name: str = pool.alloc(128)
if lookup_name is None: return 0
viperlib.snprintf(lookup_name, 128, "%s.%s", class_name, dunder)
if self.Gen.CallGenBase.find_func_in_module(mod, lookup_name) is not None: return 1
# walk MRO ancestors
mro_ptr: sizet* = self.Gen.StructGen.get_mro_chain(pool, class_name)
mi: int = 1
while mro_ptr is not None and mro_ptr[mi] != 0:
anc: str = rawptr(mro_ptr[mi])
if anc is not None:
anc_lookup: str = pool.alloc(128)
if anc_lookup is not None:
viperlib.snprintf(anc_lookup, 128, "%s.%s", anc, dunder)
if self.Gen.CallGenBase.find_func_in_module(mod, anc_lookup) is not None: return 1
mi += 1
# cross-module: a class with a SHA1 (method declared in a stub) counts as existing
if self.Gen.StructGen.get_struct_sha1(class_name) is not None: return 1
return 0
# _extract_annot_class_name - extract a registered struct class name from a vpi
# return annotation, splitting on '|' and taking the first hit.
def _extract_annot_class_name(self, pool: memhub.MemBuddy*, annot: str) -> str:
"""Extract a registered struct class name from return annotation text, or None."""
if pool is None or annot is None: return None
alen: sizet = string.strlen(annot)
if alen == 0: return None
seg: str = pool.alloc(256)
if seg is None: return None
start: sizet = 0
i: sizet
for i in range(int(alen)):
if annot[i] == '|':
s: sizet = start
e: sizet = i
while s < e and (annot[s] == ' ' or annot[s] == '\'' or annot[s] == '"'): s += 1
# Also strip a trailing '*' (e.g. `{sha}.AST*`), matching the tail
# stripping in ExprGenName._list_elem_obj_ty_from_struct_name.
while e > s and (annot[e - 1] == ' ' or annot[e - 1] == '\'' or annot[e - 1] == '"' or annot[e - 1] == '*'): e -= 1
seglen: sizet = e - s
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
# Unwrap the CPtr[T] pointer marker: the class name is inside.
while seglen > 6 and string.strncmp(seg, "CPtr[", 5) == 0 and seg[seglen - 1] == ']':
seg = seg + 5
seglen -= 6
seg[seglen] = '\0'
if self.Gen.StructGen.find_struct(seg) is not None: return seg
# On a find_struct miss, retry with the last '.'-prefix (module
# qualifier) stripped: memhub.MemBuddy -> MemBuddy.
seg_dot2: str = string.strrchr(seg, 46)
if seg_dot2 is not None and seg_dot2 != seg:
seg_bare2: str = seg_dot2 + 1
if self.Gen.StructGen.find_struct(seg_bare2) is not None: return seg_bare2
start = i + 1
# last segment (no '|' suffix)
s: sizet = start
e: sizet = alen
while s < e and (annot[s] == ' ' or annot[s] == '\'' or annot[s] == '"'): s += 1
# Also strip a trailing '*' (e.g. `{sha}.AST*`), matching the tail
# stripping in ExprGenName._list_elem_obj_ty_from_struct_name.
while e > s and (annot[e - 1] == ' ' or annot[e - 1] == '\'' or annot[e - 1] == '"' or annot[e - 1] == '*'): e -= 1
seglen: sizet = e - s
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
# Unwrap the CPtr[T] pointer marker: the class name is inside.
while seglen > 6 and string.strncmp(seg, "CPtr[", 5) == 0 and seg[seglen - 1] == ']':
seg = seg + 5
seglen -= 6
seg[seglen] = '\0'
if self.Gen.StructGen.find_struct(seg) is not None: return seg
# On a find_struct miss, retry with the last '.'-prefix (module
# qualifier) stripped: memhub.MemBuddy -> MemBuddy.
seg_dot3: str = string.strrchr(seg, 46)
if seg_dot3 is not None and seg_dot3 != seg:
seg_bare3: str = seg_dot3 + 1
if self.Gen.StructGen.find_struct(seg_bare3) is not None: return seg_bare3
return None
+315
View File
@@ -0,0 +1,315 @@
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import stdlib
import viperlib
import lib.core.Gen.Translator as HT
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
# ExprGenConst - split from ExprGenMixin (stage D): constants / global strings / list literals
novirtual class ExprGenConst(ExprGenBase.ExprGenBase):
"""Translation of constants, global strings and list literals."""
# Create global string constant
def create_global_string(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, str_val: str, trans: HT.Translator*) -> mlirlite.Value*:
"""Create a global string constant and return its i8* bitcast."""
escaped: str = self.escape_llvm_string(pool, str_val)
if escaped is None: return None
slen: sizet = string.strlen(str_val)
count: int = int(slen + 1)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
arr_ty: mlirlite.LLVMType* = mlirlite.Array(pool, i8_ty, count)
# Use the module-level counter (trans._label_counter) to avoid cross-function naming conflicts
str_idx: int = trans._label_counter
trans._label_counter += 1
# Prefix string names with the SHA1 (same rule as function export) to avoid cross-module collisions
gv_name: str = pool.alloc(48)
if gv_name is None: return None
if trans.ModuleSha1 is not None: viperlib.snprintf(gv_name, 48, ".str.%s.%d", trans.ModuleSha1, str_idx)
else: viperlib.snprintf(gv_name, 48, ".str.%d", str_idx)
gv: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, gv_name, arr_ty)
if gv is None: return None
mlirlite.module_add_global(mod, gv)
# Drop private linkage, same as funcs: declare in stub, define in text
mlirlite.global_set_unnamed_addr(gv, 1)
mlirlite.global_set_constant(gv, 1)
# LLVM 22+ forbids initializers on external-linkage globals, so set internal
gv.Linkage = "internal"
gv.Initializer = escaped
arr_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, arr_ty)
gv_ref_name: str = pool.alloc(64)
if gv_ref_name is None: return None
viperlib.snprintf(gv_ref_name, 64, "@%s", gv.Name)
gv_ref: mlirlite.Value* = mlirlite.SSAValue(pool, arr_ptr_ty, gv_ref_name)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
bc: mlirlite.Value* = mlirlite.build_bitcast(builder, gv_ref, i8_ptr_ty)
return bc
# _explicit_width_int_const - 显式宽度字面量(`123_int32` / `0xFFFFFFFF_uint32`):
# 按声明宽度出常量;值必须落在该宽度的可表示范围(有符号 [-2^(w-1), 2^(w-1)-1];无符号 [0, 2^w-1]),
# 越界编译期 fail-fast(不允许隐式截断)。w==64 时 int64 全域都合法。
def _explicit_width_int_const(self, pool: memhub.MemBuddy*, node: ast.AST*, w: int, is_unsigned: int, iv: int64) -> mlirlite.Value*:
"""Explicit-width integer literal: strict range check, then a constant of exactly w bits."""
ok: int = 1
if is_unsigned != 0:
if w < 64 and iv < 0: ok = 0
elif w <= 62 and iv > (int64(1) << w) - 1: ok = 0
else:
if w < 64:
lo: int64 = -(int64(1) << (w - 1))
hi: int64 = (int64(1) << (w - 1)) - 1
if iv < lo or iv > hi: ok = 0
if ok == 0:
wname: str = "int"
if is_unsigned != 0: wname = "uint"
self.Gen.TypeGenErr.fatal_error(node, f"integer literal {iv} does not fit the explicit width {wname}{w} (it would truncate); use a wider suffix or an explicit cast", "OverflowError")
return mlirlite.const_int(pool, w, iv)
# Translate constant expressions
def translate_constant(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None, target_ty: mlirlite.LLVMType* = None) -> mlirlite.Value*:
"""Translate a constant node (int/str/bool)."""
cn: ast.Constant* = ast.Constant*(node)
if cn is None: return None
ck: int = cn.const_kind
if cn.const_kind == ast.CONST_INT:
# Bit width: (1) if the target context is >32-bit int, use i64 directly - required
# since atoll wraps 0xFFFF... to -1 as signed int64; (2) else i64 if out of i32 range, otherwise i32.
iv: int64 = cn.int_val
# 显式宽度后缀(_int<N>/_uint<N>/_i<N>/_u<N>):按声明宽度出常量并严格校验不截断
if cn.int_width > 0: return self._explicit_width_int_const(pool, node, cn.int_width, cn.int_unsigned, iv)
if target_ty is not None and self.is_ptr_type(target_ty) == 0 and self.get_llvm_type_bits(target_ty) > 32: return mlirlite.const_int64(pool, iv)
# 位宽按值推导:i32 放不下、但 u32 放得下的字面量归为 32 位(u32)常量,而不是一律落 64 位
# (否则 `crc ^ 0xFFFFFFFF` 的异或会变成 64 位,赋值回 32 位 uint 时报“隐式窄化禁止”)。
# 文本用有符号等价(LLVM/MLIR 惯例,避免越界字面量);IntVal 保留无符号真值,
# 使窄化检查按真实数值判定,不静默截断。
if iv > 2147483647 and iv <= 4294967295:
ty32: mlirlite.LLVMType* = mlirlite.Int32(pool)
nm32: str = pool.alloc(32)
if nm32 is None: return mlirlite.const_int32(pool, iv)
viperlib.snprintf(nm32, 32, "%lld", iv - 4294967296)
return mlirlite.ConstInt(pool, ty32, iv, nm32)
if iv > 2147483647 or iv < -2147483648: return mlirlite.const_int64(pool, iv)
return mlirlite.const_int32(pool, iv)
elif cn.const_kind == ast.CONST_FLOAT:
# 目标类型是 32 位浮点(float(1.5) / x: float = 1.5 / f32 形参)→ 直接出 f32 常量。
# 旧实现无视 target_ty 一律先出 double 常量、再由 coerce_to_type fptrunc,
# 现场:float(2.0) / float(1.0) 生成 f64 常量 + fptrunc,与双精度常量混算时又多出 fpext。
# 其余情况(无目标类型 / double / 非浮点)保持 double 常量,由 coerce_to_type 负责转换。
if target_ty is not None and self.get_llvm_float_bits(target_ty) == 32:
return mlirlite.ConstFloat(pool, target_ty, cn.float_val)
double_ty: mlirlite.LLVMType* = mlirlite.Double(pool)
return mlirlite.ConstFloat(pool, double_ty, cn.float_val)
elif cn.const_kind == ast.CONST_CHAR:
# Single-char literal (CONST_CHAR): default as char value (i32, coerce truncates to i8); if the target type is a pointer (str/bytes/t.CPtr) materialize a 1-char string, else inttoptr treats the char code as a pointer -> strlen crash. In 1a stage (declare_only=1) found_func=None so a misaligned target_ty is treated as garbage and skipped
if target_ty is not None and (uint64(target_ty) & 7) == 0 and self.is_ptr_type(target_ty) != 0:
cbuf: bytes = stdlib.malloc(2)
if cbuf is None: return mlirlite.const_int32(pool, 0)
cbuf[0] = char(cn.int_val)
cbuf[1] = '\0'
r_cs: mlirlite.Value* = self.create_global_string(builder, pool, mod, cbuf, trans)
stdlib.free(cbuf)
return r_cs
cv: int64 = cn.int_val
return mlirlite.const_int32(pool, cv)
elif cn.const_kind == ast.CONST_STR:
sv: str = cn.str_val
if sv is None: return None
r: mlirlite.Value* = self.create_global_string(builder, pool, mod, sv, trans)
return r
elif cn.const_kind == ast.CONST_BOOL:
if cn.int_val != 0: return mlirlite.const_int32(pool, 1)
return mlirlite.const_int32(pool, 0)
elif cn.const_kind == ast.CONST_NONE:
# None -> i8* null (null pointer constant) for `p is None` / `p is not None` comparisons
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
if i8_ty is None: return None
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
if i8_ptr_ty is None: return None
rv_none: mlirlite.Value* = mlirlite.ConstNull(pool, i8_ptr_ty, "null")
return rv_none
return None
# _infer_elem_type_from_value - infer the list element type name from an LLVM Value.
# Used for element type inference of list literals [a, b, c].
def _infer_elem_type_from_value(self, pool: memhub.MemBuddy*, value: mlirlite.Value*) -> str:
"""Infer the list element type name from an LLVM Value; None if unmappable."""
if value is None or value.Ty is None: return None
ty: mlirlite.LLVMType* = value.Ty
bits: int = self.get_llvm_type_bits(ty)
if bits != 0:
if bits == 32: return "int"
if bits == 64: return "CSizeT"
if bits == 8: return "CInt8T"
if bits == 16: return "CInt16T"
return None
if self.is_ptr_type(ty) != 0: return "str"
fbits: int = self.get_llvm_float_bits(ty)
if fbits != 0:
if fbits == 64: return "CDouble"
if fbits == 32: return "CFloat"
return None
# _find_pool_var - find the pool variable in the context.
# Tries "pool", "_mbuddy", "mbuddy", "mb" in order.
def _find_pool_var(self, trans: HT.Translator*) -> mlirlite.Value*:
"""Find the pool variable in context (pool/_mbuddy/mbuddy/mb), else fall back."""
if trans is None: return None
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, "pool")
if alloca is not None: return alloca
alloca = self.Gen.VarGen.lookup_var(trans.SymTab, "_mbuddy")
if alloca is not None: return alloca
alloca = self.Gen.VarGen.lookup_var(trans.SymTab, "mbuddy")
if alloca is not None: return alloca
alloca = self.Gen.VarGen.lookup_var(trans.SymTab, "mb")
if alloca is not None: return alloca
# Fallback: create a module-mangled @_mbuddy definition (linkonce_odr + zeroinitializer),
# matching the SHA1-prefixed global naming; a real crash exposes an uninitialized pool (fail-fast).
pool: memhub.MemBuddy* = trans.Pool
if pool is None or trans.Module is None or trans.SymTab is None: return None
# First check whether it is already registered in the module scope (avoid duplicate globals)
existing: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, "_mbuddy")
if existing is not None: return existing
# Create the i8* type (t.CPtr = i8*)
i8_ty_fb: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty_fb: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_fb)
# Module-mangled name (@<module-sha1>._mbuddy)
mb_name_fb: str = self.Gen.FuncGenBase._mangle_name(trans, "_mbuddy")
# Create a linkonce_odr definition (linker-mergeable, self-contained for linking)
gv_ext: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, mb_name_fb, i8_ptr_ty_fb)
if gv_ext is not None:
gv_ext.Linkage = "linkonce_odr"
gv_ext.Initializer = "zeroinitializer"
mlirlite.module_add_global(trans.Module, gv_ext)
# Create the Value reference (@<module-sha1>._mbuddy, type i8**)
i8_ptr_ptr_ty_fb: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty_fb)
ref_name_fb: str = pool.alloc(96)
if ref_name_fb is not None:
# Mangled names start with a digit (SHA1 prefix), so the reference must be quoted (see make_global_ref)
if self.Gen.ClassGenVTable._vtable_name_needs_quote(mb_name_fb) != 0: viperlib.snprintf(ref_name_fb, 96, "@\"%s\"", mb_name_fb)
else: viperlib.snprintf(ref_name_fb, 96, "@%s", mb_name_fb)
gv_ref_fb: mlirlite.Value* = mlirlite.SSAValue(pool, i8_ptr_ptr_ty_fb, ref_name_fb)
# Register in the module scope to avoid duplicate creation
self.Gen.VarGen.define_module_var(trans.SymTab, "_mbuddy", gv_ref_fb)
return gv_ref_fb
# translate_list_literal - lower [a, b, c] to list[T](pool) construction + append calls; elem_type_name None means infer from elements; returns Ptr(list[T] struct) or None
def translate_list_literal(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, elem_type_name: str, trans: HT.Translator*) -> mlirlite.Value*:
"""Lower a list literal to list[T](pool) construction plus append calls."""
if node is None or trans is None: return None
list_node: ast.List* = ast.List*(node)
if list_node is None: return None
elts: list[ast.AST*]* = list_node.elts
elts_count: sizet = 0
if elts is not None: elts_count = len(elts)
# Infer element type
inferred_type_name: str = elem_type_name
first_elem_val: mlirlite.Value* = None
if inferred_type_name is None:
if elts_count > 0:
first_elem_node: ast.AST* = elts.get(0)
first_elem_val = self.Gen.ExprGen.translate_value(builder, pool, mod, first_elem_node, None, 0, trans)
if first_elem_val is None or first_elem_val.Ty is None:
# fail-fast: first-element translation failed/Value type missing -> abort now (surface root cause)
self.Gen.TypeGenErr.fatal_error(first_elem_node, "list literal element type inference failed (Value type missing)", "TypeError")
inferred_type_name = self._infer_elem_type_from_value(pool, first_elem_val)
if inferred_type_name is None:
# fail-fast: first-element Value type cannot map to a list element type -> abort now
self.Gen.TypeGenErr.fatal_error(first_elem_node, "list literal element type inference failed (Value type missing)", "TypeError")
else:
# Empty list has no element to infer, keep default str (list[str])
inferred_type_name = "str"
# Find the pool variable
pool_alloca: mlirlite.Value* = self._find_pool_var(trans)
if pool_alloca is None:
self.Gen.TypeGenErr.fatal_error(node, "list literal requires pool variable in context")
return None
# Load the pool variable value (i8*)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
pool_val: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, pool_alloca)
if pool_val is None: return None
# Specialize the list[T] class (elem_size=8: str is a pointer; 4 would overlap elements)
type_args: list[str]* = list[str](pool, 8)
type_args.append(inferred_type_name)
spec_name: str = self.Gen.ClassGenGeneric._specialize_generic_class(trans, "list", type_args)
if spec_name is None:
self.Gen.TypeGenErr.fatal_error(node, "list literal generic specialization failed")
return None
# Find the struct entry
struct_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(spec_name)
if struct_entry is None:
self.Gen.TypeGenErr.fatal_error(node, "list literal specialized class not registered")
return None
struct_ty: mlirlite.LLVMType* = struct_entry.Ty
if struct_ty is None: return None
# alloca temporary list storage; ensure the cross-module struct definition is
# fully available in the current module (so alloca can size it).
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, struct_ty)
tmp: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, struct_ty)
if tmp is None: return None
# Storage pointer: alloca by default, or the pointer returned by __new__ if present.
# __new__ is optional and only called when the generic template defines it (consistent with ctor lowering).
storage_ptr: mlirlite.Value* = tmp
has_new_flag: int = 0
if struct_entry is not None: has_new_flag = struct_entry.HasNew
if has_new_flag != 0:
new_args: sizet* = pool.alloc(8 * 32)
if new_args is None: return None
string.memset(new_args, 0, 8 * 32)
new_args[0] = sizet(pool_val)
new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__new__", tmp, new_args, 1, trans)
if new_ptr is not None: storage_ptr = new_ptr
# Call __before_init__(storage_ptr)
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__before_init__", storage_ptr, None, 0, trans)
# Call __init__(storage_ptr, pool_val, elem_size=0)
init_args: sizet* = pool.alloc(8 * 32)
if init_args is None: return None
string.memset(init_args, 0, 8 * 32)
init_args[0] = sizet(pool_val)
elem_size_val: mlirlite.Value* = mlirlite.const_int64(pool, 0)
init_args[1] = sizet(elem_size_val)
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__init__", storage_ptr, init_args, 2, trans)
# Call append(storage_ptr, elem_val) for each element
if elts_count > 0:
# The first element may already be translated
if first_elem_val is not None:
append_args: sizet* = pool.alloc(8 * 32)
if append_args is not None:
string.memset(append_args, 0, 8 * 32)
append_args[0] = sizet(first_elem_val)
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "append", storage_ptr, append_args, 1, trans)
# Translate the remaining elements
for i in range(1, int(elts_count)):
elem_node_i: ast.AST* = elts.get(i)
elem_val_i: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, elem_node_i, None, 0, trans)
if elem_val_i is not None:
ap_args: sizet* = pool.alloc(8 * 32)
if ap_args is not None:
string.memset(ap_args, 0, 8 * 32)
ap_args[0] = sizet(elem_val_i)
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "append", storage_ptr, ap_args, 1, trans)
return storage_ptr
+407
View File
@@ -0,0 +1,407 @@
from stdint import *
import ast
import mlirlite
import memhub
import string
import lib.core.Gen.Translator as HT
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
# f-string translation constants (local copy of ExprGenMixin's to avoid a cross-module
# CDefine lookup bug: ExprGenFstr compiles before ExprGenMixin, so the table is unregistered).
define FSTR_BUF_SIZE = 1024
define FSTR_SPEC_MAX = 64
define FSTR_FMT_MAX = 64
# ExprGenFstr - ExprGenMixin split (stage D): f-string/formatted-value translation
novirtual class ExprGenFstr(ExprGenBase.ExprGenBase):
"""f-string and formatted-value translation."""
# translate_joined_str - f-string translation (aligned with TPC _HandleJoinedStrLlvm):
# join literals and format specs, snprintf into a per-site i8* stack buffer (no escape).
def translate_joined_str(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Lower an f-string to snprintf into a per-site i8* stack buffer."""
if node is None or builder is None or trans is None: return None
js: ast.JoinedStr* = ast.JoinedStr*(node)
if js is None or js.values is None: return None
parts: list[ast.AST*]* = js.values
pn: sizet = len(parts)
# Size the format buffer from the actual parts: each literal may double ('%' -> '%%') and
# each conversion contributes a short spec. A fixed 1024 silently dropped trailing
# literals/converters while their arguments were still appended -> wrong runtime output.
fmt_cap: sizet = 16
for pi0 in range(int(pn)):
part0: ast.AST* = parts.get(pi0)
if part0 is None: continue
if part0.kind() == ast.ASTKind.Constant:
pc0: ast.Constant* = ast.Constant*(part0)
if pc0.const_kind == ast.CONST_STR and pc0.str_val is not None:
fmt_cap = fmt_cap + string.strlen(pc0.str_val) * 2
elif part0.kind() == ast.ASTKind.FormattedValue:
fmt_cap = fmt_cap + FSTR_SPEC_MAX + 16
# Compile-time format string buffer (pool-allocated)
fmt_buf: str = pool.alloc(fmt_cap)
if fmt_buf is None: return None
fmt_buf[0] = '\0'
# Runtime argument list (linked by Value.Next, matching the printf call)
arg_head: mlirlite.Value* = None
arg_tail: mlirlite.Value* = None
arg_count: int = 0
for pi in range(int(pn)):
part: ast.AST* = parts.get(pi)
if part is None: continue
if part.kind() == ast.ASTKind.Constant:
pcn: ast.Constant* = ast.Constant*(part)
if pcn.const_kind == ast.CONST_STR and pcn.str_val is not None:
self._fstr_append_literal(fmt_buf, fmt_cap, pcn.str_val)
continue
if part.kind() != ast.ASTKind.FormattedValue: continue
fv: ast.FormattedValue* = ast.FormattedValue*(part)
if fv.value is None: continue
val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, fv.value, None, 0, trans)
if val is None:
# fail-fast: a failed formatted-value translation must abort compilation
self.Gen.TypeGenErr.fatal_error(fv.value, "f-string formatted value translation failed (expression returned None)", "TypeError")
continue
# Object defines __str__ and no format is given -> use its returned string (like TPC)
if fv.format_spec is None:
str_ov_fs: mlirlite.Value* = self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, val, "__str__", trans)
if str_ov_fs is not None: val = str_ov_fs
# Determine the C format specifier (format_spec first, else infer from the value type)
c_fmt: str = None
if fv.format_spec is not None: c_fmt = self._fstr_parse_format_spec(pool, fv.format_spec, val, fv.value)
if c_fmt is None: c_fmt = self._fstr_infer_c_fmt(pool, val, fv.value, trans)
if c_fmt is None:
# fail-fast: cannot infer the f-string format (missing value type); abort
self.Gen.TypeGenErr.fatal_error(fv.value, "f-string format cannot be inferred (value type missing)", "TypeError")
# Append the format specifier
self._fstr_append_raw(fmt_buf, fmt_cap, c_fmt)
# Convert the argument type (i8/i16->i32, bool->i32, f32->f64)
cast_val: mlirlite.Value* = self._fstr_cast_value(builder, pool, val, c_fmt, fv.value, trans)
if cast_val is None: cast_val = val
cast_val.Next = None
if arg_head is None: arg_head = cast_val
else: arg_tail.Next = cast_val
arg_tail = cast_val
arg_count += 1
return self._fstr_finalize(builder, pool, mod, fmt_buf, arg_head, arg_tail, arg_count, trans)
def translate_formatted_value(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Translate a bare FormattedValue without any literal parts."""
if node is None or builder is None or trans is None: return None
fv: ast.FormattedValue* = ast.FormattedValue*(node)
if fv is None or fv.value is None: return None
# A bare FormattedValue contributes exactly one conversion spec (bounded by FSTR_SPEC_MAX
# plus the C prefix/width); size the buffer from that instead of the fixed 1024.
fmt_cap: sizet = FSTR_SPEC_MAX + 64
fmt_buf: str = pool.alloc(fmt_cap)
if fmt_buf is None: return None
fmt_buf[0] = '\0'
arg_head: mlirlite.Value* = None
arg_tail: mlirlite.Value* = None
arg_count: int = 0
val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, fv.value, None, 0, trans)
if val is None:
# fail-fast: a failed bare FormattedValue translation must abort compilation
self.Gen.TypeGenErr.fatal_error(fv.value, "f-string formatted value translation failed (expression returned None)", "TypeError")
else:
# Object defines __str__ and no format is given -> use its returned string (like TPC)
if fv.format_spec is None:
str_ov_sf: mlirlite.Value* = self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, val, "__str__", trans)
if str_ov_sf is not None: val = str_ov_sf
c_fmt: str = None
if fv.format_spec is not None: c_fmt = self._fstr_parse_format_spec(pool, fv.format_spec, val, fv.value)
if c_fmt is None: c_fmt = self._fstr_infer_c_fmt(pool, val, fv.value, trans)
if c_fmt is None:
# fail-fast: cannot infer the f-string format (missing value type); abort
self.Gen.TypeGenErr.fatal_error(fv.value, "f-string format cannot be inferred (value type missing)", "TypeError")
self._fstr_append_raw(fmt_buf, fmt_cap, c_fmt)
cast_val: mlirlite.Value* = self._fstr_cast_value(builder, pool, val, c_fmt, fv.value, trans)
if cast_val is None: cast_val = val
cast_val.Next = None
arg_head = cast_val
arg_tail = cast_val
arg_count = 1
return self._fstr_finalize(builder, pool, mod, fmt_buf, arg_head, arg_tail, arg_count, trans)
def _fstr_finalize(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, fmt_buf: str, arg_head: mlirlite.Value*, arg_tail: mlirlite.Value*, arg_count: int, trans: HT.Translator*) -> mlirlite.Value*:
"""Emit the final snprintf over the global format and per-site buffer."""
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
if fmt_buf is None or fmt_buf[0] == '\0':
# Empty f-string (f"") -> empty string constant
return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, "", trans)
# Format string -> global string constant (i8*)
fmt_ptr: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(builder, pool, mod, fmt_buf, trans)
if fmt_ptr is None: return None
# Result buffer: a per-site entry alloca (thread-safe, no runtime dependency), so the
# f-string result is a function-local temporary that must not escape; copy to escape.
buf_ty: mlirlite.LLVMType* = mlirlite.Array(pool, i8_ty, FSTR_BUF_SIZE)
buf_alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, buf_ty)
if buf_alloca is None: return None
zero_i32: mlirlite.Value* = mlirlite.const_int32(pool, 0)
buf_ptr: mlirlite.Value* = mlirlite.build_gep_array(builder, buf_ty, i8_ty, buf_alloca, zero_i32)
if buf_ptr is None: return None
# f-string lowers to snprintf, declared in includes/stdio.vp. If the source imports no
# module declaring it, add the declare here (implicit-provide); i8*, i64, i8* + varargs.
if self.Gen.CallGenBase.find_func_in_module(mod, "snprintf") is None:
snprintf_decl: mlirlite.Function* = mlirlite.create_declare(pool, mod, "snprintf", i32_ty)
if snprintf_decl is not None:
snprintf_decl.IsVarArg = 1
# buf/fmt are pointer params (i8*), not the element type i8: a bare i8 declare
# would cause an MLIR 'llvm.call' operand type mismatch ('!llvm.ptr' != 'i8').
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
mlirlite.add_param(pool, snprintf_decl, i8_ptr_ty, "buf")
mlirlite.add_param(pool, snprintf_decl, mlirlite.Int64(pool), "size")
mlirlite.add_param(pool, snprintf_decl, i8_ptr_ty, "fmt")
# snprintf(buf, FSTR_BUF_SIZE, fmt, args...)
size_val: mlirlite.Value* = mlirlite.const_int64(pool, FSTR_BUF_SIZE)
buf_ptr.Next = size_val
size_val.Next = fmt_ptr
fmt_ptr.Next = arg_head
if arg_tail is not None: arg_tail.Next = None
mlirlite.build_call(builder, "snprintf", buf_ptr, 3 + arg_count, i32_ty, 1)
return buf_ptr
def _fstr_append_literal(self, fmt_buf: str, cap: sizet, lit: str) -> None:
"""Append a literal to the format string, escaping percent signs; fail-fast on overflow."""
if fmt_buf is None or lit is None: return
cur_len: sizet = string.strlen(fmt_buf)
src_len: sizet = string.strlen(lit)
# Worst case: every character expands to "%%".
if cur_len + src_len * 2 + 1 > cap:
self.Gen.TypeGenErr.fatal_error(None, f"f-string format text exceeds its {cap}-byte buffer ({cur_len} used + up to {src_len * 2} escaped + 1 NUL); shorten the literal", "OverflowError")
si: sizet = 0
while si < src_len:
ch: char = lit[si]
if ch == '%':
fmt_buf[cur_len] = '%'
fmt_buf[cur_len + 1] = '%'
cur_len += 2
else:
fmt_buf[cur_len] = ch
cur_len += 1
si += 1
fmt_buf[cur_len] = '\0'
def _fstr_append_raw(self, fmt_buf: str, cap: sizet, s: str) -> None:
"""Append a raw string without percent escaping; fail-fast on overflow."""
if fmt_buf is None or s is None: return
cur_len: sizet = string.strlen(fmt_buf)
src_len: sizet = string.strlen(s)
if cur_len + src_len + 1 > cap:
self.Gen.TypeGenErr.fatal_error(None, f"f-string format text exceeds its {cap}-byte buffer ({cur_len} used + {src_len} to append + 1 NUL); shorten the format spec", "OverflowError")
i: sizet = 0
while i < src_len:
fmt_buf[cur_len + i] = s[i]
i += 1
fmt_buf[cur_len + src_len] = '\0'
def _fstr_parse_format_spec(self, pool: memhub.MemBuddy*, spec_node: ast.AST*, val: mlirlite.Value*, arg_node: ast.AST*) -> str:
"""Convert a Python format_spec into a C printf format string."""
if spec_node is None or pool is None or val is None: return None
# 1. Extract the spec string (JoinedStr with constants only)
spec: str = pool.alloc(FSTR_SPEC_MAX)
if spec is None: return None
spec[0] = '\0'
sk: int = spec_node.kind()
if sk == ast.ASTKind.JoinedStr:
jss: ast.JoinedStr* = ast.JoinedStr*(spec_node)
if jss.values is None: return None
vn: sizet = len(jss.values)
for vi in range(int(vn)):
vpart: ast.AST* = jss.values.get(vi)
if vpart is None: continue
if vpart.kind() != ast.ASTKind.Constant: return None
vcn: ast.Constant* = ast.Constant*(vpart)
if vcn.const_kind != ast.CONST_STR or vcn.str_val is None: return None
# fail-fast: a truncated spec can still look valid (e.g. ">10.5f" cut to ">10."),
# which silently changes the output format.
if string.strlen(spec) + string.strlen(vcn.str_val) >= FSTR_SPEC_MAX - 1:
self.Gen.TypeGenErr.fatal_error(arg_node, f"f-string format spec exceeds {FSTR_SPEC_MAX - 1} characters; shorten it", "OverflowError")
string.strcat(spec, vcn.str_val)
elif sk == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(spec_node)
if cn.const_kind != ast.CONST_STR or cn.str_val is None: return None
if string.strlen(cn.str_val) >= FSTR_SPEC_MAX - 1:
self.Gen.TypeGenErr.fatal_error(arg_node, f"f-string format spec exceeds {FSTR_SPEC_MAX - 1} characters; shorten it", "OverflowError")
string.strncpy(spec, cn.str_val, FSTR_SPEC_MAX - 1)
else: return None
slen: sizet = string.strlen(spec)
if slen == 0: return None
# 2. Parse segment by segment: [fill]align / sign / # / 0 / width / , / .precision / type
pos: sizet = 0
align_char: char = 0
if pos < slen:
ch0: char = spec[pos]
if ch0 == '<' or ch0 == '>' or ch0 == '^' or ch0 == '=':
align_char = ch0
pos += 1
elif pos + 1 < slen:
ch1: char = spec[pos + 1]
if ch1 == '<' or ch1 == '>' or ch1 == '^' or ch1 == '=':
align_char = ch1
pos += 2
c_flags: str = pool.alloc(FSTR_FMT_MAX)
c_width: str = pool.alloc(32)
c_prec: str = pool.alloc(32)
if c_flags is None or c_width is None or c_prec is None: return None
c_flags[0] = '\0'
c_width[0] = '\0'
c_prec[0] = '\0'
# sign
if pos < slen and (spec[pos] == '+' or spec[pos] == '-' or spec[pos] == ' '):
if spec[pos] == '+': string.strcat(c_flags, "+")
elif spec[pos] == ' ': string.strcat(c_flags, " ")
pos += 1
# alt '#'
if pos < slen and spec[pos] == '#':
string.strcat(c_flags, "#")
pos += 1
# zero '0'
if pos < slen and spec[pos] == '0':
string.strcat(c_flags, "0")
pos += 1
# Left align '<'/'^' -> '-' (C semantics give '-' priority over '0')
if align_char == '<' or align_char == '^': string.strcat(c_flags, "-")
# width digits
while pos < slen and spec[pos] >= '0' and spec[pos] <= '9':
wl: sizet = string.strlen(c_width)
if wl < 30:
c_width[wl] = spec[pos]
c_width[wl + 1] = '\0'
pos += 1
# comma ',' -> C printf has no thousands separator, ignore
if pos < slen and spec[pos] == ',': pos += 1
# .precision
if pos < slen and spec[pos] == '.':
string.strcat(c_prec, ".")
pos += 1
while pos < slen and spec[pos] >= '0' and spec[pos] <= '9':
pl: sizet = string.strlen(c_prec)
if pl < 30:
c_prec[pl] = spec[pos]
c_prec[pl + 1] = '\0'
pos += 1
if string.strlen(c_prec) == 1:
return None # a lone '.' is invalid
# type char
fmt_type: char = 0
if pos < slen:
fmt_type = spec[pos]
pos += 1
if pos != slen: return None # extra characters cannot be parsed
# 3. Length modifier (by value type bit width)
len_mod: str = pool.alloc(4)
if len_mod is None: return None
len_mod[0] = '\0'
ibits: int = self.Gen.ExprGenBase.get_llvm_type_bits(val.Ty)
# Do not write a length modifier for 32-bit (N14): `{x:08X}` passes an i32, but `l`
# would make %08lX (long) mismatch on LP64 and misread the varargs.
if ibits == 64: string.strcpy(len_mod, "ll")
elif ibits == 16: string.strcpy(len_mod, "h")
elif ibits == 8: string.strcpy(len_mod, "hh")
# 4. Assemble the C format string
out: str = pool.alloc(FSTR_FMT_MAX)
if out is None: return None
out[0] = '\0'
string.strcat(out, "%")
string.strcat(out, c_flags)
string.strcat(out, c_width)
string.strcat(out, c_prec)
if fmt_type == 0:
# No type: infer from the value type
fbits2: int = self.Gen.ExprGenBase.get_llvm_float_bits(val.Ty)
if ibits != 0:
if ibits == 8: string.strcat(out, "c")
elif ibits == 1: string.strcat(out, "d")
else:
string.strcat(out, len_mod)
string.strcat(out, "d")
elif fbits2 != 0: string.strcat(out, "f")
elif self.Gen.ExprGenBase.is_ptr_type(val.Ty) != 0: string.strcat(out, "s")
else: string.strcat(out, "d")
elif fmt_type == 'c': string.strcat(out, "c")
elif fmt_type == 'f' or fmt_type == 'F': string.strcat(out, "f")
elif fmt_type == 'e' or fmt_type == 'E' or fmt_type == 'g' or fmt_type == 'G':
tmp_ch: str = pool.alloc(2)
if tmp_ch is None: return None
tmp_ch[0] = fmt_type
tmp_ch[1] = '\0'
string.strcat(out, tmp_ch)
elif fmt_type == 's': string.strcat(out, "s")
elif fmt_type == '%': string.strcat(out, "%")
elif fmt_type == 'b':
return None # binary is unsupported
elif fmt_type == 'd' or fmt_type == 'i':
string.strcat(out, len_mod)
string.strcat(out, "d")
elif fmt_type == 'x' or fmt_type == 'X' or fmt_type == 'o' or fmt_type == 'u':
string.strcat(out, len_mod)
tmp_ch2: str = pool.alloc(2)
if tmp_ch2 is None: return None
tmp_ch2[0] = fmt_type
tmp_ch2[1] = '\0'
string.strcat(out, tmp_ch2)
else: return None
return out
def _fstr_infer_c_fmt(self, pool: memhub.MemBuddy*, val: mlirlite.Value*, arg_node: ast.AST*, trans: HT.Translator*) -> str:
"""Infer the C printf format from the value type when no format_spec is given."""
if val is None or pool is None: return None
out: str = pool.alloc(16)
if out is None: return None
out[0] = '\0'
ibits: int = self.Gen.ExprGenBase.get_llvm_type_bits(val.Ty)
fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(val.Ty)
if ibits == 1: return "%d" # bool
if ibits == 8: return "%c" # character
if ibits != 0:
if self.Gen.ExprGenOps._node_type_unsigned(pool, arg_node, trans) != 0:
if ibits == 64: string.strcpy(out, "%llu")
else: string.strcpy(out, "%u")
else:
if ibits == 64: string.strcpy(out, "%lld")
else: string.strcpy(out, "%d")
return out
if fbits != 0: return "%f"
if self.Gen.ExprGenBase.is_ptr_type(val.Ty) != 0: return "%s"
# fail-fast: missing value type (not int/float/pointer); cannot infer the C format
self.Gen.TypeGenErr.fatal_error(arg_node, "f-string format cannot be inferred (value type missing)", "TypeError")
return None
def _fstr_cast_value(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, val: mlirlite.Value*, c_fmt: str, arg_node: ast.AST*, trans: HT.Translator*) -> mlirlite.Value*:
"""Cast the value to the type required by the C format specifier."""
if val is None or c_fmt is None: return None
# Find the format type char (last letter, ignoring l/h length modifiers)
fmt_char: char = 0
flen: sizet = string.strlen(c_fmt)
fi: sizet = flen
while fi > 0:
fi -= 1
ch: char = c_fmt[fi]
if ch == '%': break
if (ch >= 'a' and ch <= 'z') or (ch >= 'A' and ch <= 'Z'):
if ch != 'l' and ch != 'h':
fmt_char = ch
break
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
if fmt_char == 's': return val
if fmt_char == 'c':
ibits: int = self.Gen.ExprGenBase.get_llvm_type_bits(val.Ty)
if ibits != 0 and ibits < 32: return mlirlite.build_zext(builder, val, i32_ty)
return val
if fmt_char == 'd' or fmt_char == 'i' or fmt_char == 'x' or fmt_char == 'X' or fmt_char == 'o' or fmt_char == 'u':
ibits = self.Gen.ExprGenBase.get_llvm_type_bits(val.Ty)
if ibits == 1: return mlirlite.build_zext(builder, val, i32_ty)
if ibits == 8 or ibits == 16:
if self.Gen.ExprGenOps._node_type_unsigned(pool, arg_node, trans) != 0: return mlirlite.build_zext(builder, val, i32_ty)
return mlirlite.build_sext(builder, val, i32_ty)
return val
if fmt_char == 'f' or fmt_char == 'e' or fmt_char == 'E' or fmt_char == 'g' or fmt_char == 'G':
fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(val.Ty)
if fbits == 32: return mlirlite.build_fpext(builder, val, mlirlite.Double(pool))
return val
return val
+81
View File
@@ -0,0 +1,81 @@
from stdint import *
import ast
import memhub
import mlirlite
import lib.core.Gen.Translator as HT
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
# docstring registry capacities (__doc__ magic attribute support).
# DOC_MAX: max entries; DOC_KEY_SZ: key "{module_sha1}.{symbol}" width; DOC_VAL_SZ: content width.
define DOC_MAX = 512
define DOC_KEY_SZ = 192
define DOC_VAL_SZ = 4096
# f-string translation constants (aligned with TPC HandlesExprFormat).
# FSTR_BUF_SIZE: runtime stack buffer; FSTR_SPEC_MAX: format_spec parse width; FSTR_FMT_MAX: printf format width.
define FSTR_BUF_SIZE = 1024
define FSTR_SPEC_MAX = 64
define FSTR_FMT_MAX = 64
# ExprGenMixin - expression handling mixin (objectified P3).
# ExprGenMixin - expressions / constants / subscripts / attributes / operators.
novirtual class ExprGenMixin(ExprGenBase.ExprGenBase):
"""Expression-handling mixin (pure methods, no state fields)."""
# Translate a value expression (RHS dispatch) - module-level version.
def translate_value(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, funcs_ptr: rawptr = None, func_count: int = 0, trans: HT.Translator* = None, target_ty: mlirlite.LLVMType* = None, use_ctx: str = None) -> mlirlite.Value*:
"""Translate a value expression to an LLVM Value (depth-guard wrapper; see _translate_value_impl).
target_ty = assignment/parameter type context; use_ctx = real call site for CDefine width checks."""
if trans is not None:
if trans.TranslateDepth >= HT.TRANSLATE_MAX_DEPTH: self.Gen.TypeGenErr.fatal_error(node, "expression translation nested too deep (suspected AST self-reference cycle or translation bug)", "RecursionError")
trans.TranslateDepth += 1
rv: mlirlite.Value* = self._translate_value_impl(builder, pool, mod, node, funcs_ptr, func_count, trans, target_ty, use_ctx)
if trans is not None: trans.TranslateDepth -= 1
return rv
def _translate_value_impl(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, funcs_ptr: rawptr = None, func_count: int = 0, trans: HT.Translator* = None, target_ty: mlirlite.LLVMType* = None, use_ctx: str = None) -> mlirlite.Value*:
"""Translate a value expression to an LLVM Value."""
if node is None: return None
k: int = node.kind()
if k == ast.ASTKind.Constant:
rv: mlirlite.Value* = self.Gen.ExprGenConst.translate_constant(builder, pool, mod, node, trans, target_ty)
return rv
elif k == ast.ASTKind.Name: return self.Gen.ExprGenName.translate_name_value(builder, pool, node, trans, target_ty, use_ctx)
elif k == ast.ASTKind.BinOp: return self.Gen.ExprGenOps.translate_binop(pool, builder, mod, node, trans, target_ty)
elif k == ast.ASTKind.Call: return self.Gen.CallGen.translate_call(pool, builder, mod, node, funcs_ptr, func_count, trans)
# M2.4 `new stack|heap|memgr(x) [slife|hlife] T(args)`:只换"取存储",其余复用构造序列
elif k == ast.ASTKind.New: return self.Gen.CallGenCtor._translate_new_expr(pool, builder, mod, ast.New*(node), trans)
elif k == ast.ASTKind.Compare: return self.Gen.ExprGenOps.translate_compare(builder, pool, mod, node, trans)
elif k == ast.ASTKind.UnaryOp: return self.Gen.ExprGenOps.translate_unaryop(builder, pool, mod, node, trans, target_ty)
elif k == ast.ASTKind.BoolOp: return self.Gen.ExprGenOps.translate_boolop(builder, pool, mod, node, funcs_ptr, func_count, trans)
elif k == ast.ASTKind.Subscript: return self.Gen.ExprGenName.translate_subscript(builder, pool, mod, node, trans)
elif k == ast.ASTKind.Attribute: return self.Gen.ExprGenAttr.translate_attribute(builder, pool, mod, node, trans)
elif k == ast.ASTKind.List: return self.Gen.ExprGenConst.translate_list_literal(builder, pool, mod, node, None, trans)
elif k == ast.ASTKind.IfExp: return self.Gen.ExprGenOps.translate_ifexp(builder, pool, mod, node, trans)
elif k == ast.ASTKind.Lambda: return self.Gen.FuncGen.translate_lambda(builder, pool, mod, node, trans)
elif k == ast.ASTKind.JoinedStr: return self.Gen.ExprGenFstr.translate_joined_str(builder, pool, mod, node, trans)
elif k == ast.ASTKind.FormattedValue: return self.Gen.ExprGenFstr.translate_formatted_value(builder, pool, mod, node, trans)
elif k == ast.ASTKind.Await:
# Coroutine await (VPC-M1): inline-translate when the await target is an async call.
# Returns the child coroutine's i32 ret; top-level await is handled by AsyncGen.
aw: ast.Await* = ast.Await*(node)
if aw is not None and aw.value is not None and aw.value.kind() == ast.ASTKind.Call:
aw_cl: ast.Call* = ast.Call*(aw.value)
if aw_cl.func is not None and aw_cl.func.kind() == ast.ASTKind.Name:
aw_nm: ast.Name* = ast.Name*(aw_cl.func)
if aw_nm.id is not None and trans is not None:
if self.Gen.AsyncGen.is_async_func(trans, aw_nm.id) != 0: return self.Gen.AsyncGen._emit_await(builder, pool, mod, trans, aw, aw_nm.id)
return None
elif k == ast.ASTKind.Starred:
# *x in a value context: the parser tags '*' as Starred, but Viper has no unpacking.
# Treat it as C unary deref (*ptr -> load pointee) by building a Deref UnaryOp.
st: ast.Starred* = ast.Starred*(node)
if st is None or st.value is None: return None
deref_node: ast.AST* = ast.UnaryOp(pool, ast.OpKind.Deref, st.value)
if deref_node is None: return None
return self.Gen.ExprGenOps.translate_unaryop(builder, pool, mod, deref_node, trans)
return None
+954
View File
@@ -0,0 +1,954 @@
import lib.core.Gen.GenTypes as GenTypes
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import viperlib
import lib.core.Gen.Translator as HT
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
import lib.core.Gen.GenApi as GenApi
# ExprGenName - split out of ExprGenMixin (stage D): Name/Subscript and subscript access
novirtual class ExprGenName(ExprGenBase.ExprGenBase):
"""Name/Subscript subscript access helpers."""
def _cut_from_import_mod(self, pool: memhub.MemBuddy*, raw: str) -> str:
"""Copy the module name in raw into its own buffer, cut at space/colon/NUL; None when invalid."""
if raw is None: return None
mbl_c: sizet = 0
while raw[mbl_c] != ' ' and raw[mbl_c] != '\0' and raw[mbl_c] != ':': mbl_c += 1
if mbl_c == 0: return None
buf_c: str = pool.alloc(mbl_c + 1)
if buf_c is None: return None
string.strncpy(buf_c, raw, mbl_c)
buf_c[mbl_c] = '\0'
return buf_c
def translate_name_value(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, node: ast.AST*, trans: HT.Translator* = None, target_ty: mlirlite.LLVMType* = None, use_ctx: str = None) -> mlirlite.Value*:
"""Translate a variable reference (Name node) to a load; target_ty/use_ctx guide width."""
# mod must come from trans.Module: the signature has no mod param, and a bare `mod`
# would resolve to None, silently dropping string CDefine globals (llc undefined value).
mod: mlirlite.LLVMModule* = trans.Module
nm: ast.Name* = ast.Name*(node)
if nm is None: return None
nm_id: str = nm.id
if nm_id is None: return None
# P2 willFree:读取已被"消费"的变量(其所有权被 @life(willFree(...)) 标注的调用夺取)→ fail-fast。
# 只在**读**路径检查;对同名变量重新赋值会清除失效态(见 AssignGenMixin)。
if trans is not None:
if self.Gen.VarGen._FreedCheck(self.Gen.VarGen.lookup_var(trans.SymTab, nm_id)) != 0:
self.Gen.TypeGenErr.fatal_error(node, f"cannot use '{nm_id}': already released by a @life(willFree(...)) call", "LifetimeError")
# CDefine compile-time constant: return the constant (float -> double, int -> i32).
# Existing variables win over CDefine table entries injected from vpi stubs (lookup_var guard).
cdef_val_d: double = self.Gen.TypeGenCDef.lookup_cdefine_double(nm_id)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
if trans is None or self.Gen.VarGen.lookup_var(trans.SymTab, nm_id) is None:
if self.Gen.TypeGenCDef.is_cdefine_str() != 0:
# String CDefine (e.g. ZLIB_VERSION: t.CDefine = "1.3.2"): return a global
# string (i8*) for `return ZLIB_VERSION`.
cdef_sv: str = self.Gen.TypeGenCDef.lookup_cdefine_string(nm_id)
if cdef_sv is not None: return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, cdef_sv, trans)
self.Gen.TypeGenErr.fatal_error(node, f"CDefine string value missing: {nm_id}", "InternalCompilerError")
# 浮点常量:声明为 32 位 float(`define PI: float = ...`)时直接出 f32 常量,
# 否则出 f64 —— 出 f64 会把后续 f32 字面量整体提升成 double(fpext/cvtss2sd)。
if self.Gen.TypeGenCDef.is_cdefine_double() != 0:
if self.Gen.TypeGenCDef.is_cdefine_float32() != 0: return mlirlite.ConstFloat(pool, mlirlite.Float(pool), cdef_val_d)
return mlirlite.ConstFloat(pool, mlirlite.Double(pool), cdef_val_d)
# Integer CDefine: re-look-up the full 64-bit value (a double round-trip loses
# precision and would generate a wrong constant).
cdef_val_c: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(nm_id)
# Use-site width (passed via target_ty; use_ctx carries the site kind for messages):
# wider -> widen in place; narrower -> fail-fast; unknown -> the existing 32/64 default.
uw_val: mlirlite.Value* = self.Gen.ExprGenCdef.cdefine_use_width_value(pool, node, nm_id, cdef_val_c, target_ty, use_ctx)
if uw_val is not None: return uw_val
return self.Gen.ExprGenCdef._const_int_for_cdefine(pool, nm_id, cdef_val_c)
# Not found locally: try cross-module .vpi lookup, only for ALL_CAPS names so ordinary
# variable names never trigger the costly cross-module source read.
if self.Gen.ExprGenCdef._is_cdefine_name_pattern(nm_id) != 0:
if trans is not None:
# ALL_CAPS may also be a plain variable (e.g. CAM/FWD/RIGHT/UP: V3* module globals);
# an existing variable in scope takes priority, skipping the cross-module CDefine lookup.
if self.Gen.VarGen.lookup_var(trans.SymTab, nm_id) is None:
cdef_val: int64 = self.Gen.ExprGenCdef._lookup_cross_module_cdefine(pool, nm_id, trans._from_imports, trans._imported_modules, 0, node)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
if self.Gen.TypeGenCDef.is_cdefine_str() != 0:
cdef_sv2: str = self.Gen.TypeGenCDef.lookup_cdefine_string(nm_id)
if cdef_sv2 is not None: return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, cdef_sv2, trans)
self.Gen.TypeGenErr.fatal_error(node, f"CDefine string value missing: {nm_id}", "InternalCompilerError")
# Cross-module float CDefine is cached as double; return a float constant
# rather than a truncated integer.
dchk_nm: double = self.Gen.TypeGenCDef.lookup_cdefine_double(nm_id)
# 同本模块路径:f32 声明的常量直接出 f32 常量(跨模块缓存已带宽度种类)。
if self.Gen.TypeGenCDef.is_cdefine_double() != 0:
if self.Gen.TypeGenCDef.is_cdefine_float32() != 0: return mlirlite.ConstFloat(pool, mlirlite.Float(pool), dchk_nm)
return mlirlite.ConstFloat(pool, mlirlite.Double(pool), dchk_nm)
# Same use-site width rule (cross-module CDefine)
uw_x: mlirlite.Value* = self.Gen.ExprGenCdef.cdefine_use_width_value(pool, node, nm_id, cdef_val, target_ty, use_ctx)
if uw_x is not None: return uw_x
return self.Gen.ExprGenCdef._const_int_for_cdefine(pool, nm_id, cdef_val)
# CDefine lookup failed: from-import module-level globals (e.g. `from Phase2 import
# PA_DEPS_MAX_STR`, an i64 global) are located by suffix after confirming the source module.
if trans is not None and trans._from_imports is not None:
fi_mod_raw: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, nm_id, 0)
if fi_mod_raw is not None:
# Look up by the source module's SHA1 prefix to avoid same-name suffix collisions;
# fi_mod_raw is not NUL-terminated, so truncate before the SHA1 lookup.
fi_mod_cut: str = self._cut_from_import_mod(pool, fi_mod_raw)
if fi_mod_cut is None: fi_mod_cut = fi_mod_raw
fi_gv_q: str = self.Gen.FuncGenBase._mangle_name_with_sha1(StubMergerStore.LookupSha1ByModName(fi_mod_cut), nm_id)
fi_gv: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(trans.Module, fi_gv_q)
if fi_gv is not None and fi_gv.Ty is not None:
fi_ref: mlirlite.Value* = self.Gen.ExprGenAttr.make_global_ref(pool, fi_gv.Name, fi_gv.Ty)
if fi_ref is not None and fi_ref.Ty is not None:
fi_pty: mlirlite.LLVMType* = fi_ref.Ty.Pointee
if fi_pty is not None:
match fi_pty:
case mlirlite.LLVMType.Array(_, _):
# Array globals decay to the base address per C semantics
return fi_ref
case _:
pass
return mlirlite.build_load(builder, fi_pty, fi_ref)
# global variable: look up in the module scope (global declaration has highest priority)
if trans is not None:
if GenApi.is_global_name(trans, nm_id) != 0:
mod_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm_id)
if mod_alloca is not None:
load_ty: mlirlite.LLVMType* = None
if mod_alloca.Ty is not None: load_ty = mod_alloca.Ty.Pointee
if load_ty is None:
# fail-fast: a global with a missing storage type aborts compilation
self.Gen.TypeGenErr.fatal_error(node, f"variable '{nm_id}' storage type missing", "TypeError")
# Global arrays decay to the base address per C semantics; otherwise a loaded
# array value passed as i8* loses writes (test_pyzlib "error message is empty").
match load_ty:
case mlirlite.LLVMType.Array(_, _):
return mod_alloca
case _:
pass
# t.CVolatile global: load carries volatile (prevents optimizing away repeated reads)
vol_g: int = 0
if trans is not None:
ve_g: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_id)
vol_g = self.Gen.VarGen.var_is_volatile(ve_g)
return mlirlite.build_load(builder, load_ty, mod_alloca, vol_g)
# nonlocal variable: accessed through the closure env
if GenApi.is_nonlocal_name(trans, nm_id) != 0: return self.Gen.StmtGenDecl.load_nonlocal_var(trans, nm_id)
# *args va_list: the name resolves directly to an i8* pointing at the 24-byte va_list
# buffer (no load of the buffer contents; pass the address by value when forwarding).
if trans is not None and trans._vararg_name is not None:
if nm_id is not None and string.strcmp(nm_id, trans._vararg_name) == 0:
if trans._va_list_ptr is not None: return trans._va_list_ptr
# Local variable lookup must precede the module-alias check, or a local with the same
# name as a module (e.g. local `c` after `import t, c`) would be misread and not found.
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm_id)
if alloca is not None:
load_ty: mlirlite.LLVMType* = None
if alloca.Ty is not None: load_ty = alloca.Ty.Pointee
if load_ty is None:
# fail-fast: a local with a missing storage type aborts compilation
self.Gen.TypeGenErr.fatal_error(node, f"variable '{nm_id}' storage type missing", "TypeError")
# Struct-value variable (alloca of Struct): rvalues return its address by default;
# a value is loaded only when the use site demands a same-type struct value.
match load_ty:
case mlirlite.LLVMType.Struct(_, _, _):
# Struct-value locals return the alloca address by default; when the use site
# explicitly needs a same-type non-pointer struct value, load it (_same_agg_ty).
if target_ty is not None:
if self.Gen.ExprGenBase._same_agg_ty(pool, load_ty, target_ty) != 0:
vol_s: int = 0
if trans is not None:
ve_s: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_id)
vol_s = self.Gen.VarGen.var_is_volatile(ve_s)
return mlirlite.build_load(builder, load_ty, alloca, vol_s)
return alloca
case mlirlite.LLVMType.Array(_, _):
# Array variables decay to a pointer (base address) per C semantics; loading the
# whole array value into "%s" reads wrong data (ClassArrayTest crash root cause).
return alloca
# t.CVolatile local: load carries volatile
vol_l: int = 0
bo_l: int = 0
if trans is not None:
ve_l: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_id)
vol_l = self.Gen.VarGen.var_is_volatile(ve_l)
bo_l = self.Gen.VarGen.var_byte_order(ve_l)
local_val_l: mlirlite.Value* = mlirlite.build_load(builder, load_ty, alloca, vol_l)
# BigEndian local: the slot is byte-swapped, so un-swap the load (pairs the store in AssignGenMixin).
return self.apply_byte_order_bswap(pool, builder, local_val_l, bo_l)
# REPL session variables: a name missing from the local table is read back from its ctx slot,
# checked after locals and before module aliases; non-REPL behavior is unchanged.
if trans is not None and trans.ReplCtx is not None:
sess_val: mlirlite.Value* = self.Gen.VarGen.repl_try_read(trans, nm_id, node)
if sess_val is not None: return sess_val
# Module alias check (only after SymTab misses): if nm_id is an imported module name that is
# not a declared local, return None so the caller can handle attribute access.
if trans is not None and trans._imported_modules is not None:
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, nm_id) != 0: return None
# Module-level function reference: build a real function type from the Function's RetTy/Params
# and wrap it in a new Value (returning/bitcasting the Function itself yields illegal IR).
if trans is not None and trans.Module is not None:
fn_ref: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(trans.Module, nm_id)
if fn_ref is not None:
fn_name: str = mlirlite.function_get_name(fn_ref)
if fn_name is None: return None
# Build the function type: ret_ty + parameter ParamNode list
fn_ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(fn_ref)
if fn_ret_ty is None:
# fail-fast: a missing function return type prevents building the function pointer
self.Gen.TypeGenErr.fatal_error(node, f"function '{nm_id}' return type missing, cannot build the function pointer", "TypeError")
fn_pp: mlirlite.Param* = mlirlite.function_get_param_head(fn_ref)
fn_first_node: mlirlite.ParamNode* = None
fn_prev_node: mlirlite.ParamNode* = None
fn_pcount: int = 0
while fn_pp is not None:
fn_pty: mlirlite.LLVMType* = mlirlite.param_get_ty(fn_pp)
fn_pn: mlirlite.ParamNode* = mlirlite.new_param_node(pool, fn_pty)
if fn_pn is not None:
if fn_first_node is None: fn_first_node = fn_pn
if fn_prev_node is not None: mlirlite.paramnode_set_next(fn_prev_node, fn_pn)
fn_prev_node = fn_pn
fn_pcount += 1
fn_pp = mlirlite.param_get_next(fn_pp)
fn_ty: mlirlite.LLVMType* = mlirlite.Func(pool, fn_ret_ty, fn_first_node, fn_pcount)
if fn_ty is None: return None
# Build the function pointer Value (wrapped in new_value to avoid touching Function.Next)
fn_ref_v: mlirlite.Value* = mlirlite.new_value(pool)
if fn_ref_v is None: return None
mlirlite.value_set_ty(fn_ref_v, mlirlite.Ptr(pool, fn_ty))
mlirlite.value_set_isconst(fn_ref_v, 1)
fn_ref_name: str = pool.alloc(256)
if fn_ref_name is not None:
fn_ref_name[0] = '\0'
if self.Gen.ClassGenVTable._vtable_name_needs_quote(fn_name) != 0: viperlib.snprintf(fn_ref_name, 256, "@\"%s\"", fn_name)
else: viperlib.snprintf(fn_ref_name, 256, "@%s", fn_name)
mlirlite.value_set_name(fn_ref_v, fn_ref_name)
fn_i8ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
return mlirlite.build_bitcast(builder, fn_ref_v, fn_i8ptr_ty)
# Cross-module from-import global read (e.g. `from .tokens import _op_head`): no local SymTab
# entry exists, so emit an external reference + load; the source module provides the definition.
if trans is not None and trans._from_imports is not None:
if trans.Module is not None:
# Is a from-import name (star fallback disabled to avoid misattributing to star modules)
fi_mod: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, nm_id, 0)
if fi_mod is not None:
# Exclude registered class/enum names: these are types, not global values
fi_is_class: int = 0
if self.Gen.StructGen.get_struct_type(nm_id) is not None: fi_is_class = 1
# Exclude ALL_CAPS CDefine (already handled by local + cross-module lookup)
if self.Gen.ExprGenCdef._is_cdefine_name_pattern(nm_id) != 0: fi_is_class = 1
if fi_is_class == 0:
fi_gname: str = nm_id
fi_orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, nm_id)
if fi_orig is not None:
og_len_fi: sizet = 0
while fi_orig[og_len_fi] != ' ' and fi_orig[og_len_fi] != '\0': og_len_fi += 1
if og_len_fi > 0:
og_buf_fi: str = pool.alloc(og_len_fi + 1)
if og_buf_fi is not None:
string.strncpy(og_buf_fi, fi_orig, og_len_fi)
og_buf_fi[og_len_fi] = '\0'
fi_gname = og_buf_fi
# Reference the stub-injected global directly, or fail-fast (declaring external
# i8* masks the cause); look up by source module SHA1 after truncating fi_mod.
fi_mod_cut2: str = self._cut_from_import_mod(pool, fi_mod)
if fi_mod_cut2 is None: fi_mod_cut2 = fi_mod
gv_fi_q: str = self.Gen.FuncGenBase._mangle_name_with_sha1(StubMergerStore.LookupSha1ByModName(fi_mod_cut2), fi_gname)
gv_fi: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(trans.Module, gv_fi_q)
ref_fi: mlirlite.Value* = None
if gv_fi is not None and gv_fi.Ty is not None: ref_fi = self.Gen.ExprGenAttr.make_global_ref(pool, gv_fi.Name, gv_fi.Ty)
else:
# Dependency vpi/stub not injected yet (file-order Phase A may translate this
# before its dependency), so return None without fail-fast to avoid false errors.
return None
if ref_fi is not None and ref_fi.Ty is not None:
lty_fi: mlirlite.LLVMType* = ref_fi.Ty.Pointee
if lty_fi is not None: return mlirlite.build_load(builder, lty_fi, ref_fi)
return ref_fi
# All lookup paths failed: fail-fast (NameError) instead of returning None and crashing
# downstream. Exempt keywords/builtins (self/pool/None/_) whose absence is normal.
if nm_id is None: return None
if string.strcmp(nm_id, "CIf") == 0 or string.strcmp(nm_id, "CIfdef") == 0 or string.strcmp(nm_id, "CIfndef") == 0 or string.strcmp(nm_id, "CElif") == 0 or string.strcmp(nm_id, "CElse") == 0 or string.strcmp(nm_id, "CEndif") == 0:
# The c.CIf-family spellings were removed; report the replacement.
self.Gen.TypeGenErr.fatal_error(node, "c.CIf/c.CIfdef/c.CIfndef/c.CElif/c.CElse/c.CEndif were removed; use the cif/celif/celse statements and the defined(NAME) predicate instead (e.g. `cif defined(NAME):` ... `celse:` ...)", "NameError")
return None
if string.strcmp(nm_id, "self") == 0 or string.strcmp(nm_id, "pool") == 0 or string.strcmp(nm_id, "true") == 0 or string.strcmp(nm_id, "false") == 0 or string.strcmp(nm_id, "None") == 0 or string.strcmp(nm_id, "match") == 0 or string.strcmp(nm_id, "_") == 0:
return None
# Diagnostics: report the generic context, since "undefined name 'T'" is often far from the
# real cause (0 params = no context; params>0/args 0 = lost args; both>0 = name not a param).
ctx_txt: str = ""
if trans is not None and trans.GenericTypeParamNames is not None:
ctp_n: sizet = len(trans.GenericTypeParamNames)
cta_n: sizet = 0
if trans.GenericTypeArgs is not None: cta_n = len(trans.GenericTypeArgs)
ctx_txt = f"(current generic context: {ctp_n} type params / {cta_n} type args)"
self.Gen.TypeGenErr.fatal_error(node, f"undefined name '{nm_id}'{ctx_txt}", "NameError")
return None
# list_getitem_inline - inline list[T] __getitem__; returns the element address (i8*).
# Layout (6 fields x 8 bytes): __data__(0) __count__(8) __capacity__(16) __pool__(24) __elem_size__(32) __iter_index__(40).
def list_getitem_inline(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, lm_obj: mlirlite.Value*, idx_val: mlirlite.Value*, node: ast.AST* = None) -> mlirlite.Value*:
"""Inline list __getitem__; return the element address (i8*)."""
if builder is None or pool is None or lm_obj is None or idx_val is None: return None
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
# Load __data__ (offset 0, index 0)
g_idx0: mlirlite.Value* = mlirlite.const_int64(pool, 0)
g_dpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, g_idx0)
if g_dpp is None: return None
g_data: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, g_dpp)
# Load __elem_size__ (offset 32, index 4)
g_idx4: mlirlite.Value* = mlirlite.const_int64(pool, 4)
g_epp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, g_idx4)
if g_epp is None: return None
g_esize: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, g_epp)
if g_data is None or g_esize is None: return None
g_idx_i64: mlirlite.Value* = self.coerce_to_type(builder, idx_val, i64_ty, node)
if g_idx_i64 is None: return None
g_offset: mlirlite.Value* = mlirlite.build_mul(builder, g_idx_i64, g_esize)
if g_offset is None: return None
# Return the element address (i8*) without loading
return mlirlite.build_gep(builder, i8_ty, g_data, g_offset)
# _list_elem_type_from_name - extract the element LLVM type from a list type name.
# Name format: <sha1>.list[<elem_type>]; strstr checks the full pattern (no substring extraction).
def _list_elem_type_from_name(self, pool: memhub.MemBuddy*, struct_name: str) -> mlirlite.LLVMType*:
"""Extract the element LLVM type from a list type name."""
if pool is None or struct_name is None: return None
# Check the full pattern with strstr to avoid substring extraction
if string.strstr(struct_name, "list[int]") is not None: return mlirlite.Int32(pool)
if string.strstr(struct_name, "list[CInt]") is not None: return mlirlite.Int32(pool)
if string.strstr(struct_name, "list[CInt8T]") is not None: return mlirlite.Int8(pool)
if string.strstr(struct_name, "list[CInt16T]") is not None: return mlirlite.Int16(pool)
if string.strstr(struct_name, "list[CInt32T]") is not None: return mlirlite.Int32(pool)
if string.strstr(struct_name, "list[CInt64T]") is not None: return mlirlite.Int64(pool)
if string.strstr(struct_name, "list[CSizeT]") is not None: return mlirlite.Int64(pool)
if string.strstr(struct_name, "list[CDouble]") is not None: return mlirlite.Double(pool)
if string.strstr(struct_name, "list[CFloat]") is not None: return mlirlite.Float(pool)
if string.strstr(struct_name, "list[CChar]") is not None: return mlirlite.Int8(pool)
# Known pointer element types (str/bytes/CPtr) -> i8* (elements hold 8-byte addresses)
i8_ty_d: mlirlite.LLVMType* = mlirlite.Int8(pool)
if string.strstr(struct_name, "list[str]") is not None: return mlirlite.Ptr(pool, i8_ty_d)
if string.strstr(struct_name, "list[bytes]") is not None: return mlirlite.Ptr(pool, i8_ty_d)
if string.strstr(struct_name, "list[CPtr]") is not None: return mlirlite.Ptr(pool, i8_ty_d)
# Unknown list[T] element type (custom struct element): the slot holds an 8-byte object
# pointer, so restore Ptr(%Struct) to carry the element class for chained method dispatch.
elem_obj_ty_l: mlirlite.LLVMType* = self._list_elem_obj_ty_from_struct_name(pool, struct_name)
if elem_obj_ty_l is not None: return elem_obj_ty_l
# Struct not registered (dependency not ready): fall back to Ptr(i8) (same 8-byte slot width),
# letting AnnotClassName/method dispatch fallbacks preserve semantics; never fail-fast here.
return mlirlite.Ptr(pool, i8_ty_d)
# _list_elem_obj_ty_from_struct_name - restore the element object pointer type from a list
# specialization name (strip rawptr[...], split unions by '|', resolve against registered structs).
def _list_elem_obj_ty_from_struct_name(self, pool: memhub.MemBuddy*, struct_name: str) -> mlirlite.LLVMType*:
"""Restore the element object pointer type from a list specialization name; None on failure."""
if pool is None or struct_name is None: return None
lb_e: str = string.strchr(struct_name, '[')
if lb_e is None: return None
rb_e: str = string.strrchr(struct_name, ']')
if rb_e is None: return None
elen_e: sizet = sizet(uint64(rb_e) - uint64(lb_e)) - 1
if elen_e == 0 or elen_e > 512: return None
elem_buf_e: str = pool.alloc(elen_e + 1)
if elem_buf_e is None: return None
string.strncpy(elem_buf_e, lb_e + 1, elen_e)
elem_buf_e[elen_e] = '\0'
# Strip the pointer wrapper (elements declared as object pointers): CPtr[...] / rawptr[...]
cur_e: str = elem_buf_e
while string.strncmp(cur_e, "CPtr[", 5) == 0 or string.strncmp(cur_e, "rawptr[", 7) == 0:
if string.strncmp(cur_e, "CPtr[", 5) == 0: cur_e = cur_e + 5
else: cur_e = cur_e + 7
# Split unions by '|', trimming leading/trailing whitespace, trailing ']' and quotes; each
# segment goes to find_struct (builtin names return None; "sha1.Class" prefix matches by module).
total_e: sizet = string.strlen(cur_e)
seg_s_e: sizet = 0
i_e: sizet = 0
while i_e <= total_e:
hit_e: int = 0
if i_e == total_e: hit_e = 1
elif cur_e[i_e] == '|': hit_e = 1
if hit_e == 0:
i_e += 1
continue
b_e: sizet = i_e
a_e: sizet = seg_s_e
# Also strip a trailing '*' (normalized element text is `T*`), since find_struct only
# accepts the class name; otherwise object elements fall back to Ptr(i8), losing the class.
while b_e > a_e and (cur_e[b_e - 1] == ' ' or cur_e[b_e - 1] == ']' or cur_e[b_e - 1] == '\'' or cur_e[b_e - 1] == '"' or cur_e[b_e - 1] == '*'): b_e -= 1
while a_e < b_e and (cur_e[a_e] == ' ' or cur_e[a_e] == '\'' or cur_e[a_e] == '"'): a_e += 1
if b_e > a_e:
seg_len_e: sizet = b_e - a_e
if seg_len_e < 256:
seg_buf_e: str = pool.alloc(seg_len_e + 1)
if seg_buf_e is not None:
string.strncpy(seg_buf_e, cur_e + a_e, seg_len_e)
seg_buf_e[seg_len_e] = '\0'
s_entry_e: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(seg_buf_e)
if s_entry_e is not None and s_entry_e.Ty is not None: return mlirlite.Ptr(pool, s_entry_e.Ty)
if i_e == total_e: break
seg_s_e = i_e + 1
i_e += 1
return None
# list_setitem_inline - inline list[T] __setitem__: coerce rhs_val to i8* and store to the element address.
def list_setitem_inline(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, lm_obj: mlirlite.Value*, idx_val: mlirlite.Value*, rhs_val: mlirlite.Value*, node: ast.AST* = None) -> int:
"""Inline list __setitem__; returns 0."""
if builder is None or pool is None or lm_obj is None or idx_val is None or rhs_val is None: return 0
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
# Load __data__ (offset 0, index 0)
s_idx0: mlirlite.Value* = mlirlite.const_int64(pool, 0)
s_dpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, s_idx0)
if s_dpp is None: return 0
s_data: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, s_dpp)
# Load __elem_size__ (offset 32, index 4)
s_idx4: mlirlite.Value* = mlirlite.const_int64(pool, 4)
s_epp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, s_idx4)
if s_epp is None: return 0
s_esize: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, s_epp)
if s_data is None or s_esize is None: return 0
s_idx_i64: mlirlite.Value* = self.coerce_to_type(builder, idx_val, i64_ty, node)
if s_idx_i64 is None: return 0
s_offset: mlirlite.Value* = mlirlite.build_mul(builder, s_idx_i64, s_esize)
if s_offset is None: return 0
s_addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, s_data, s_offset)
if s_addr is None: return 0
# Element type T from the list specialization name (list[int]->i32, list[str]->i8*); a fixed
# i8* store would overwrite adjacent elements (CallGenMixin list branch, ListTest pop FAILED).
s_sty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(lm_obj)
s_name: str = None
if s_sty is not None: s_name = mlirlite.get_struct_name(s_sty)
s_elem_ty: mlirlite.LLVMType* = None
if s_name is not None: s_elem_ty = self._list_elem_type_from_name(pool, s_name)
if s_elem_ty is None: s_elem_ty = i8_ptr_ty
s_val_ptr: mlirlite.Value* = self.coerce_to_type(builder, rhs_val, s_elem_ty, node)
if s_val_ptr is None: return 0
s_ep: mlirlite.Value* = mlirlite.build_bitcast(builder, s_addr, mlirlite.Ptr(pool, s_elem_ty))
if s_ep is not None: mlirlite.build_store(builder, s_val_ptr, s_ep)
return 0
# is_list_subscript - check whether a Subscript node is list[T]; returns the list object pointer (Ptr(list[T])) or None.
def is_list_subscript(self, node: ast.AST*, trans: HT.Translator*) -> mlirlite.Value*:
"""Return the list object pointer when a Subscript is list[T]; None otherwise."""
if node is None or trans is None: return None
sub: ast.Subscript* = ast.Subscript*(node)
if sub is None or sub.value is None: return None
if sub.value.kind() != ast.ASTKind.Name: return None
nm: ast.Name* = ast.Name*(sub.value)
if nm is None or nm.id is None: return None
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is None or alloca.Ty is None: return None
if self.is_ptr_type(alloca.Ty) == 0: return None
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
if pointee is None: return None
# Check whether pointee is Ptr(Struct)
inner_ty: mlirlite.LLVMType* = None
match pointee:
case mlirlite.LLVMType.Ptr(it):
inner_ty = it
case _:
return None
if inner_ty is None: return None
# Check whether inner_ty is a Struct whose name contains "list["
struct_name: str = None
match inner_ty:
case mlirlite.LLVMType.Struct(_, _, sname):
struct_name = sname
case _:
return None
if struct_name is None: return None
if string.strstr(struct_name, "list[") is None: return None
# Is a list type: load out the list object pointer
builder: mlirlite.IRBuilder* = trans._cur_builder
return mlirlite.build_load(builder, pointee, alloca)
# _nonlocal_subscript_ptr - element pointer for a nonlocal array/pointer subscript: read the
# shared heap address from env (_load_raw_var_ptr), bitcast, then GEP (the outer alloca is unusable).
def _nonlocal_subscript_ptr(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, trans: HT.Translator*, nm_id: str, idx_val: mlirlite.Value*) -> mlirlite.Value*:
"""Element pointer for a nonlocal array/pointer subscript (env heap addr -> bitcast -> GEP)."""
if trans is None or nm_id is None or idx_val is None: return None
nl_ty: mlirlite.LLVMType* = GenApi.get_nonlocal_type(trans, nm_id)
if nl_ty is None: return None
raw_nl: mlirlite.Value* = self.Gen.StmtGenDecl._load_raw_var_ptr(trans, nm_id)
if raw_nl is None: return None
match nl_ty:
case mlirlite.LLVMType.Array(elem_ty_nl, _cnt_nl):
arr_ptr_nl: mlirlite.Value* = mlirlite.build_bitcast(builder, raw_nl, mlirlite.Ptr(pool, nl_ty))
if arr_ptr_nl is None: return None
return mlirlite.build_gep_array(builder, nl_ty, elem_ty_nl, arr_ptr_nl, idx_val)
case mlirlite.LLVMType.Ptr(inner_nl):
arr_ptr_nl2: mlirlite.Value* = mlirlite.build_bitcast(builder, raw_nl, mlirlite.Ptr(pool, nl_ty))
if arr_ptr_nl2 is None: return None
return mlirlite.build_gep(builder, inner_nl, arr_ptr_nl2, idx_val)
case _:
arr_ptr_nl3: mlirlite.Value* = mlirlite.build_bitcast(builder, raw_nl, mlirlite.Ptr(pool, nl_ty))
if arr_ptr_nl3 is None: return None
return mlirlite.build_gep(builder, nl_ty, arr_ptr_nl3, idx_val)
return None
# translate_subscript - translate a subscript expression ptr[i] / arr[i]: pointer traversal
# loads the pointer then GEP + load; arrays use a double-index GEP; list[T] goes through __getitem__.
def translate_subscript(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Translate a subscript expression; return the loaded element value."""
if node is None or builder is None: return None
sub: ast.Subscript* = ast.Subscript*(node)
if sub is None: return None
# Slice form a[start:end] (e.g. path[n:]): return an i8* base pointer plus the start offset.
# Slices must not be treated as single-index subscripts, or the argument is dropped entirely.
if sub.slice is not None and sub.slice.kind() == ast.ASTKind.Slice:
slc: ast.Slice* = ast.Slice*(sub.slice)
base_v: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub.value, None, 0, trans)
if base_v is None: return None
if slc.lower is None: return base_v
start_v: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, slc.lower, None, 0, trans)
if start_v is None: return None
i64_sc: mlirlite.LLVMType* = mlirlite.Int64(pool)
start_w: mlirlite.Value* = start_v
if start_v.Ty is not None:
bt_sc: int = self.Gen.ExprGenBase.get_llvm_type_bits(start_v.Ty)
if bt_sc != 0 and bt_sc < 64: start_w = mlirlite.build_sext(builder, start_v, i64_sc)
if start_w is None: return None
i8_sc: mlirlite.LLVMType* = mlirlite.Int8(pool)
return mlirlite.build_gep(builder, i8_sc, base_v, start_w)
# Translate the index
idx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub.slice, None, 0, trans)
if idx_val is None: return None
# Narrow integer subscripts (i8/i16) are zero-extended inside mlirlite.build_gep /
# build_gep_array (the single source of truth); doing it here would miss bare pointers.
# Nested subscript read a[i][j]: recursively take the inner pointer and load, matching
# get_subscript_ptr's nested branch.
if sub.value is not None and sub.value.kind() == ast.ASTKind.Subscript:
inner_ptr_r: mlirlite.Value* = self.get_subscript_ptr(builder, pool, mod, sub.value, trans)
if inner_ptr_r is None or inner_ptr_r.Ty is None: return None
inner_elem_r: mlirlite.LLVMType* = inner_ptr_r.Ty.Pointee
if inner_elem_r is None: return None
match inner_elem_r:
case mlirlite.LLVMType.Array(arr_ie_r, _):
elem_ptr_r: mlirlite.Value* = mlirlite.build_gep_array(builder, inner_elem_r, arr_ie_r, inner_ptr_r, idx_val)
if elem_ptr_r is not None: return mlirlite.build_load(builder, arr_ie_r, elem_ptr_r)
return None
case _:
elem_ptr_r2: mlirlite.Value* = mlirlite.build_gep(builder, inner_elem_r, inner_ptr_r, idx_val)
if elem_ptr_r2 is not None: return mlirlite.build_load(builder, inner_elem_r, elem_ptr_r2)
return None
# If value is a Name, try to infer from the alloca type
if sub.value is not None and sub.value.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(sub.value)
if nm.id is not None and trans is not None:
# rawptr[t.CVolatile | T] variable (volatile pointee): element loads (p[i] dereference)
# carry volatile; loading the pointer value itself does not.
vol_sub_rd: int = self.Gen.VarGen.var_pointee_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
# nonlocal array/pointer: read the shared heap address from env (the outer alloca
# cannot be used); the read path loads the element value from the element pointer.
if GenApi.is_nonlocal_name(trans, nm.id) != 0:
nl_ptr_r: mlirlite.Value* = self._nonlocal_subscript_ptr(builder, pool, trans, nm.id, idx_val)
if nl_ptr_r is not None and nl_ptr_r.Ty is not None:
nl_elem_ty: mlirlite.LLVMType* = nl_ptr_r.Ty.Pointee
if nl_elem_ty is not None: return mlirlite.build_load(builder, nl_elem_ty, nl_ptr_r)
return None
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is not None and alloca.Ty is not None:
if self.is_ptr_type(alloca.Ty) != 0:
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
if pointee is not None:
# Do not call pointee.kind() (avoids a cross-module LLVMType.kind reference);
# use match to inspect the pointee type.
match pointee:
case mlirlite.LLVMType.Array(elem_ty, count):
# Array traversal: getelementptr [N x elem_ty], ... , i32 0, i32 %idx
elem_ptr: mlirlite.Value* = mlirlite.build_gep_array(builder, pointee, elem_ty, alloca, idx_val)
if elem_ptr is not None:
arr_val_rd: mlirlite.Value* = mlirlite.build_load(builder, elem_ty, elem_ptr)
# BigEndian array variable: the element slot is byte-swapped; un-swap
# on load (pairs the element store in AssignGenMixin). Non-Name bases are
# not covered (matches TPC, which also keys off the base Name).
bo_arr_rd: int = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
return self.apply_byte_order_bswap(pool, builder, arr_val_rd, bo_arr_rd)
return None
case mlirlite.LLVMType.Struct(_fs_v, _fc_v, lsn_v):
# list[T] value variable (an unannotated assignment rhs returns a list
# value): the alloca holds the list object, so go straight to list __getitem__.
if lsn_v is not None:
if string.strstr(lsn_v, "list[") is not None:
elem_addr_v: mlirlite.Value* = self.list_getitem_inline(builder, pool, alloca, idx_val, node)
if elem_addr_v is not None:
elem_ty_v: mlirlite.LLVMType* = self._list_elem_type_from_name(pool, lsn_v)
if elem_ty_v is not None:
elem_ptr_ty_v: mlirlite.LLVMType* = mlirlite.Ptr(pool, elem_ty_v)
casted_v: mlirlite.Value* = mlirlite.build_bitcast(builder, elem_addr_v, elem_ptr_ty_v)
if casted_v is not None: return mlirlite.build_load(builder, elem_ty_v, casted_v)
# 值类型类实例(`d: dict = dict(bd)`):槽里就是对象本身,`d[k]` 必须按
# 类的 `__getitem__` 调用编译,self 传槽地址(= &对象)。此前这里直接
# return None,调用方(AnnAssign)又对 None 静默不写,整条语句凭空消失
# ——现场:p1/p2/p3 全未赋值,随后解引用栈垃圾断言崩溃。
cls_nm_val: str = self.Gen.StructGen.get_class_name_by_type(pool, pointee)
if not (cls_nm_val is None):
gi_lookup_val: str = pool.alloc(128)
if gi_lookup_val is not None:
viperlib.snprintf(gi_lookup_val, 128, "%s.__getitem__", cls_nm_val)
if self.Gen.CallGenBase.find_func_in_module(mod, gi_lookup_val) is not None:
arg_vals_val: sizet* = pool.alloc(8)
if not (arg_vals_val is None):
arg_vals_val[0] = sizet(idx_val)
ret_val_sub: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_val, "__getitem__", alloca, arg_vals_val, 1, trans, sub)
if not (ret_val_sub is None): return ret_val_sub
# 非 list、无 __getitem__ 的结构体值不可下标:按对象大小 GEP 是非法 IR,
# 明确 fail-fast,绝不静默返回 None(调用方会把整条语句丢掉)。
self.Gen.TypeGenErr.fatal_error(node, f"cannot subscript the value-type object '{nm.id}' of type '{lsn_v}' (the class defines no __getitem__); use a pointer variable ('T*') or define __getitem__", "TypeError")
return None
case mlirlite.LLVMType.Ptr(inner_ty):
# Check whether inner_ty is list[T]; list subscripts go through __getitem__ (inline), not pointer traversal
list_struct_name: str = None
match inner_ty:
case mlirlite.LLVMType.Struct(_, _, lsn):
list_struct_name = lsn
case _:
pass
if list_struct_name is not None:
if string.strstr(list_struct_name, "list[") is not None:
# list[T] type: inline-generate the __getitem__ logic
ptr_val_list: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
if ptr_val_list is not None:
elem_addr_list: mlirlite.Value* = self.list_getitem_inline(builder, pool, ptr_val_list, idx_val, node)
if elem_addr_list is not None:
# Extract the element type from the name, bitcast then load
elem_ty_list: mlirlite.LLVMType* = self._list_elem_type_from_name(pool, list_struct_name)
if elem_ty_list is not None:
elem_ptr_ty_list: mlirlite.LLVMType* = mlirlite.Ptr(pool, elem_ty_list)
casted_addr_list: mlirlite.Value* = mlirlite.build_bitcast(builder, elem_addr_list, elem_ptr_ty_list)
if casted_addr_list is not None: return mlirlite.build_load(builder, elem_ty_list, casted_addr_list)
return None
# inner_ty being Ptr means alloca.Ty is a triple pointer (e.g. T=AST*, T*=AST**),
# so [i] is a pointer dereference (stride 8), not a struct __getitem__ call.
if self.is_ptr_type(inner_ty) != 0:
ptr_val_pp: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
if ptr_val_pp is not None:
elem_ptr_pp: mlirlite.Value* = mlirlite.build_gep(builder, inner_ty, ptr_val_pp, idx_val)
if elem_ptr_pp is not None:
# Volatile pointee: dereference load carries volatile
return mlirlite.build_load(builder, inner_ty, elem_ptr_pp, vol_sub_rd)
return None
# Custom struct (e.g. HashTable*, JsonValue*): forward the method call only if
# the class defines __getitem__; otherwise do a plain GEP array access.
cls_nm_rd_ptr: str = self._get_custom_struct_cls_nm(pool, inner_ty)
if not (cls_nm_rd_ptr is None):
gi_lookup_rd: str = pool.alloc(128)
if gi_lookup_rd is not None:
viperlib.snprintf(gi_lookup_rd, 128, "%s.__getitem__", cls_nm_rd_ptr)
if self.Gen.CallGenBase.find_func_in_module(mod, gi_lookup_rd) is not None:
obj_ptr_rd: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
if not (obj_ptr_rd is None):
arg_vals_rd_p: sizet* = pool.alloc(8)
if not (arg_vals_rd_p is None):
arg_vals_rd_p[0] = sizet(idx_val)
ret_rd_p: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_rd_ptr, "__getitem__", obj_ptr_rd, arg_vals_rd_p, 1, trans, sub)
if not (ret_rd_p is None): return ret_rd_p
# No __getitem__: struct-pointer array access (GEP + load)
ptr_val_rd_g: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
if ptr_val_rd_g is not None:
elem_rd_g: mlirlite.Value* = mlirlite.build_gep(builder, inner_ty, ptr_val_rd_g, idx_val)
if elem_rd_g is not None:
# Volatile pointee: dereference load carries volatile
return mlirlite.build_load(builder, inner_ty, elem_rd_g, vol_sub_rd)
return None
# bytes*/str*: the alloca is i8**, so GEP with an i8* stride (8 bytes) and load i8*;
# pointer traversal applies only when inner_ty is not a custom struct.
if trans is not None:
ve_pe_ts: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if self.Gen.VarGen.var_is_ptr_element(ve_pe_ts) != 0:
# bytes*/str*/T*: the variable holds a pointer value, so elem[i] means
# load [ptr + i*8]; load the alloca first then GEP with an i8* stride.
ptr_val_pe_ts: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
if ptr_val_pe_ts is not None:
elem_ptr_ts: mlirlite.Value* = mlirlite.build_gep(builder, pointee, ptr_val_pe_ts, idx_val)
if elem_ptr_ts is not None:
# Volatile pointee: dereference load carries volatile
return mlirlite.build_load(builder, pointee, elem_ptr_ts, vol_sub_rd)
return None
# Plain pointer traversal: load the pointer value, then GEP
ptr_val: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
if ptr_val is not None:
elem_ptr2: mlirlite.Value* = mlirlite.build_gep(builder, inner_ty, ptr_val, idx_val)
if elem_ptr2 is not None:
# Volatile pointee: dereference load carries volatile
return mlirlite.build_load(builder, inner_ty, elem_ptr2, vol_sub_rd)
return None
case _:
# Union-typed variable (IsPtrElement=1, e.g. elem_ptr: T* degraded to i8*): pointee
# is i8 but elements are 8-byte pointers, so GEP/load must use an i8* stride.
if trans is not None:
ve_pe_rd: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if self.Gen.VarGen.var_is_ptr_element(ve_pe_rd) != 0:
match pointee:
case mlirlite.LLVMType.Int(bits_rd):
if bits_rd == 8:
i8_ptr_rd: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
elem_ptr_rd: mlirlite.Value* = mlirlite.build_gep(builder, i8_ptr_rd, alloca, idx_val)
if elem_ptr_rd is not None: return mlirlite.build_load(builder, i8_ptr_rd, elem_ptr_rd)
case _:
pass
# Custom class (struct type): forward the method call only if the class defines
# __getitem__; otherwise do a plain GEP array access on the struct pointer.
cls_nm_sub: str = self.Gen.StructGen.get_class_name_by_type(pool, pointee)
if not (cls_nm_sub is None):
gi_lookup_sub: str = pool.alloc(128)
if gi_lookup_sub is not None:
viperlib.snprintf(gi_lookup_sub, 128, "%s.__getitem__", cls_nm_sub)
if self.Gen.CallGenBase.find_func_in_module(mod, gi_lookup_sub) is not None:
obj_val_sub: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
if not (obj_val_sub is None):
arg_vals_sub: sizet* = pool.alloc(8)
if not (arg_vals_sub is None):
arg_vals_sub[0] = sizet(idx_val)
ret_sub: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_sub, "__getitem__", obj_val_sub, arg_vals_sub, 1, trans, sub)
if not (ret_sub is None): return ret_sub
# No __getitem__: struct-pointer array access (GEP + load)
ptr_val_sub_g: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
if ptr_val_sub_g is not None:
elem_sub_g: mlirlite.Value* = mlirlite.build_gep(builder, pointee, ptr_val_sub_g, idx_val)
if elem_sub_g is not None:
# Volatile pointee: dereference load carries volatile
return mlirlite.build_load(builder, pointee, elem_sub_g, vol_sub_rd)
return None
pass
# Generic path: translate value to obtain the pointer
ptr_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub.value, None, 0, trans)
if ptr_val is None or ptr_val.Ty is None: return None
# Check whether it is a pointer type
if self.is_ptr_type(ptr_val.Ty) != 0:
# Generic-path base (e.g. self.ptr / a complex expression): element loads carry volatile only
# when the base resolves to a VarEntry/FieldEntry marked rawptr[t.CVolatile|T]; otherwise 0.
vol_sub_gen: int = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, sub.value)
elem_ty2: mlirlite.LLVMType* = ptr_val.Ty.Pointee
if elem_ty2 is not None:
# Array type: double-index GEP + load element
match elem_ty2:
case mlirlite.LLVMType.Array(arr_elem_ty2, arr_count2):
elem_ptr3: mlirlite.Value* = mlirlite.build_gep_array(builder, elem_ty2, arr_elem_ty2, ptr_val, idx_val)
if elem_ptr3 is not None: return mlirlite.build_load(builder, arr_elem_ty2, elem_ptr3, vol_sub_gen)
return None
case _:
pass
# 64-bit scalar pointer fields keep the declared i64 element type (a pointer needs a cast);
# custom structs forward __getitem__ only if defined, else do a plain GEP array access.
cls_nm_rd_gen: str = self._get_custom_struct_cls_nm(pool, elem_ty2)
if not (cls_nm_rd_gen is None):
gi_lookup_gen: str = pool.alloc(128)
if gi_lookup_gen is not None:
viperlib.snprintf(gi_lookup_gen, 128, "%s.__getitem__", cls_nm_rd_gen)
if self.Gen.CallGenBase.find_func_in_module(mod, gi_lookup_gen) is not None:
arg_vals_rd_g: sizet* = pool.alloc(8)
if not (arg_vals_rd_g is None):
arg_vals_rd_g[0] = sizet(idx_val)
ret_rd_g: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_rd_gen, "__getitem__", ptr_val, arg_vals_rd_g, 1, trans, sub)
if not (ret_rd_g is None): return ret_rd_g
# No __getitem__: struct-pointer array access (GEP + load)
elem_ptr_gen: mlirlite.Value* = mlirlite.build_gep(builder, elem_ty2, ptr_val, idx_val)
if elem_ptr_gen is not None: return mlirlite.build_load(builder, elem_ty2, elem_ptr_gen, vol_sub_gen)
return None
# Pointer type: single-index GEP + load
elem_ptr4: mlirlite.Value* = mlirlite.build_gep(builder, elem_ty2, ptr_val, idx_val)
if elem_ptr4 is not None: return mlirlite.build_load(builder, elem_ty2, elem_ptr4, vol_sub_gen)
return None
# get_subscript_ptr - get the subscript element pointer (without loading the value) for assignment lhs
def _annot_is_64bit_scalar(self, annot: str) -> int:
"""Return 1 when the annotation is a 64-bit scalar | t.CPtr union (element type Int64)."""
if annot is None: return 0
if string.strstr(annot, "CInt64T") is not None: return 1
if string.strstr(annot, "CUInt64T") is not None: return 1
if string.strstr(annot, "CSizeT") is not None: return 1
return 0
# subscript_base_store_kind - read-only base storage classification: 1 = array slot (whole-object
# assignment allowed), 0 = pointer pointee (caller fail-fast), -1 = undeterminable.
def subscript_base_store_kind(self, pool: memhub.MemBuddy*, trans: HT.Translator*, node: ast.AST*) -> int:
"""Base storage kind of a subscript: 1=array, 0=pointer, -1=undeterminable."""
if node is None or trans is None: return -1
sub_bk: ast.Subscript* = ast.Subscript*(node)
if sub_bk is None or sub_bk.value is None: return -1
base_ty_bk: mlirlite.LLVMType* = None
if sub_bk.value.kind() == ast.ASTKind.Name:
nm_bk: ast.Name* = ast.Name*(sub_bk.value)
if nm_bk.id is None: return -1
alloca_bk: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm_bk.id)
if alloca_bk is None: alloca_bk = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm_bk.id)
if alloca_bk is None or alloca_bk.Ty is None: return -1
if self.is_ptr_type(alloca_bk.Ty) == 0: return -1
base_ty_bk = alloca_bk.Ty.Pointee
elif sub_bk.value.kind() == ast.ASTKind.Attribute:
# Single-level obj.field (e.g. self.arr): read-only, non-fatal field type resolution
fe_bk: GenTypes.FieldEntry* = self.Gen.ExprGenAttr._resolve_attr_field_entry(pool, trans, sub_bk.value)
if fe_bk is None or fe_bk.Ty is None: return -1
base_ty_bk = fe_bk.Ty
else: return -1
if base_ty_bk is None: return -1
# The base itself is an array value -> array storage
if self.is_array_type(base_ty_bk) != 0: return 1
if self.is_ptr_type(base_ty_bk) != 0:
inner_bk: mlirlite.LLVMType* = base_ty_bk.Pointee
if inner_bk is None: return -1
# Ptr(Array) -> t.CArray[T,N] local/field (array storage)
if self.is_array_type(inner_bk) != 0: return 1
# Ptr(Struct) -> rawptr[T] (pointer storage)
if inner_bk.kind() == mlirlite.LLVMType.Struct: return 0
return -1
def get_subscript_ptr(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Return the subscript element pointer for an assignment lhs."""
if node is None or builder is None: return None
sub: ast.Subscript* = ast.Subscript*(node)
if sub is None: return None
# Translate the index
idx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub.slice, None, 0, trans)
if idx_val is None: return None
# Narrow integer subscripts (i8/i16) are zero-extended inside mlirlite.build_gep /
# build_gep_array (the single source of truth); doing it here would miss bare pointers.
# Nested subscript a[i][j] = v: recursively resolve the inner subscript pointer then GEP
# (the generic translate_value path would load the inner element as a value and fail).
if sub.value is not None and sub.value.kind() == ast.ASTKind.Subscript:
inner_ptr: mlirlite.Value* = self.get_subscript_ptr(builder, pool, mod, sub.value, trans)
if inner_ptr is None or inner_ptr.Ty is None: return None
inner_elem: mlirlite.LLVMType* = inner_ptr.Ty.Pointee
if inner_elem is None: return None
match inner_elem:
case mlirlite.LLVMType.Array(arr_ie, _):
return mlirlite.build_gep_array(builder, inner_elem, arr_ie, inner_ptr, idx_val)
case _:
return mlirlite.build_gep(builder, inner_elem, inner_ptr, idx_val)
# If value is a Name, try to infer from the alloca type
if sub.value is not None and sub.value.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(sub.value)
if nm.id is not None and trans is not None:
# nonlocal array/pointer: read the shared heap address from env (the outer alloca
# cannot be used because its SSA number belongs to another function).
if GenApi.is_nonlocal_name(trans, nm.id) != 0: return self._nonlocal_subscript_ptr(builder, pool, trans, nm.id, idx_val)
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is not None and alloca.Ty is not None:
if self.is_ptr_type(alloca.Ty) != 0:
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
if pointee is not None:
# Check the pointee type with match
match pointee:
case mlirlite.LLVMType.Array(elem_ty, count):
# Array traversal
return mlirlite.build_gep_array(builder, pointee, elem_ty, alloca, idx_val)
case mlirlite.LLVMType.Ptr(inner_ty):
# inner_ty being Ptr means alloca.Ty is a triple pointer, so [i]=val is a pointer
# dereference assignment, not a __setitem__ call.
if self.is_ptr_type(inner_ty) != 0:
ptr_val_gsp: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
if ptr_val_gsp is not None: return mlirlite.build_gep(builder, inner_ty, ptr_val_gsp, idx_val)
return None
# Custom struct (e.g. HashTable*, JsonValue*): return None (triggering the
# __setitem__ overload) only if the class defines __setitem__; else plain GEP.
cls_nm_ptr_chk: str = self._get_custom_struct_cls_nm(pool, inner_ty)
if not (cls_nm_ptr_chk is None):
si_lookup_gsp: str = pool.alloc(128)
if si_lookup_gsp is not None:
viperlib.snprintf(si_lookup_gsp, 128, "%s.__setitem__", cls_nm_ptr_chk)
if self.Gen.CallGenBase.find_func_in_module(mod, si_lookup_gsp) is not None: return None
# bytes*/str*: the alloca is i8**, so GEP with an i8* stride (8 bytes) without loading;
# pointer traversal applies only when inner_ty is not a custom struct.
if trans is not None:
ve_pe: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if self.Gen.VarGen.var_is_ptr_element(ve_pe) != 0:
# bytes*/str*/T*: the variable holds a pointer value, so the subscript
# means [ptr + i*8]; load the alloca then GEP, or the write misses the data.
ptr_val_pe: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
if ptr_val_pe is not None:
# 64-bit scalar union (int64*/uint64*/sizet*): the 8-byte cell holds an i64 scalar,
# so GEP must use Int64, not a pointer, or the store fails-fast as int->ptr.
pe_annot: str = self.Gen.VarGen.var_annot_class_name(ve_pe)
if pe_annot is not None and self._annot_is_64bit_scalar(pe_annot) != 0: return mlirlite.build_gep(builder, mlirlite.Int64(pool), ptr_val_pe, idx_val)
match pointee:
case mlirlite.LLVMType.Int(bits_pe):
if bits_pe == 8:
i8_ptr_pe: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
return mlirlite.build_gep(builder, i8_ptr_pe, ptr_val_pe, idx_val)
case _:
pass
return mlirlite.build_gep(builder, pointee, ptr_val_pe, idx_val)
return None
# Plain pointer traversal: load the pointer value, then GEP
ptr_val: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
if ptr_val is None: return None
gep_r: mlirlite.Value* = mlirlite.build_gep(builder, inner_ty, ptr_val, idx_val)
return gep_r
case mlirlite.LLVMType.Struct(_fs_gsp, _fc_gsp, _lsn_gsp):
# 值类型类实例(`d: dict = dict(bd)`):槽里是对象本身,`d[k] = v`
# 只能走类的 `__setitem__`。定义了 __setitem__ 时返回 None,交由
# 赋值路径的 __setitem__ 重载分支(self 用槽地址);否则按对象数组
# GEP 会生成非法 IR,明确 fail-fast 而不是静默。
cls_nm_val_gsp: str = self.Gen.StructGen.get_class_name_by_type(pool, pointee)
if not (cls_nm_val_gsp is None):
si_lookup_val: str = pool.alloc(128)
if si_lookup_val is not None:
viperlib.snprintf(si_lookup_val, 128, "%s.__setitem__", cls_nm_val_gsp)
if self.Gen.CallGenBase.find_func_in_module(mod, si_lookup_val) is not None: return None
self.Gen.TypeGenErr.fatal_error(node, f"cannot index the value-type object '{nm.id}' of type '{_lsn_gsp}' (the class defines no __setitem__); use a pointer variable ('T*') or define __setitem__", "TypeError")
return None
case _:
# Plain scalar pointee (e.g. i64/i32/i8): GEP directly to the idx-th element; union type
# (IsPtrElement=1, pointee i8 but 8-byte pointer elements) uses an i8* stride instead.
if trans is not None:
ve_pe_cs: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if self.Gen.VarGen.var_is_ptr_element(ve_pe_cs) != 0:
# T* (degraded to i8*, e.g. _list.vp elem_ptr) holds a data pointer whose elements
# are 8-byte pointers: load the alloca then GEP with an i8* stride, or the write misses.
ptr_val_cs: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
if ptr_val_cs is not None:
match pointee:
case mlirlite.LLVMType.Int(bits_cs):
if bits_cs == 8:
i8_ptr_cs: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
return mlirlite.build_gep(builder, i8_ptr_cs, ptr_val_cs, idx_val)
case _:
pass
return mlirlite.build_gep(builder, pointee, ptr_val_cs, idx_val)
return None
return mlirlite.build_gep(builder, pointee, alloca, idx_val)
# Generic path
ptr_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub.value, None, 0, trans)
if ptr_val is None or ptr_val.Ty is None: return None
if self.is_ptr_type(ptr_val.Ty) != 0:
elem_ty2: mlirlite.LLVMType* = ptr_val.Ty.Pointee
if elem_ty2 is not None:
# Array type: double-index GEP (getelementptr [N x ty], ptr, i32 0, i32 idx)
match elem_ty2:
case mlirlite.LLVMType.Array(arr_elem_ty, arr_count):
return mlirlite.build_gep_array(builder, elem_ty2, arr_elem_ty, ptr_val, idx_val)
case _:
pass
# Custom struct (e.g. self._ht[key]): return None (triggering the HandlesAssign __setitem__
# overload) only if the class defines __setitem__; otherwise do a plain GEP array access.
cls_nm_gen: str = self._get_custom_struct_cls_nm(pool, elem_ty2)
if not (cls_nm_gen is None):
si_lookup_gen: str = pool.alloc(128)
if si_lookup_gen is not None:
viperlib.snprintf(si_lookup_gen, 128, "%s.__setitem__", cls_nm_gen)
if self.Gen.CallGenBase.find_func_in_module(mod, si_lookup_gen) is not None: return None
# Pointer type: single-index GEP (getelementptr ty, ptr, idx)
return mlirlite.build_gep(builder, elem_ty2, ptr_val, idx_val)
return None
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-523
View File
@@ -1,523 +0,0 @@
import lib.core.Gen.Generator as Generator
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import viperlib
import w32.fileio as fileio
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Handles.HandlesBase as HandlesBase
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.Stub.StubMerger as StubMerger
import lib.core.VLogger as VLogger
import lib.Projectrans.Config as Config
import lib.Projectrans.Utils as Utils
# ============================================================
# ExprGenBase - ExprGenMixin 拆分(阶段 D):表达式基础工具(类型判断/指针层级/coerce)
#
# 最底层 Base(ExprGenMixin/Const/Fstr/Ops/Cdef/Name/Attr/Cls 均 1 级继承本类)。
# 跨 Mixin 调用统一走 HandlesExpr 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class ExprGenBase:
"""表达式基础工具(类型判断/指针层级/coerce)"""
# --- docstring 登记表字段(__doc__ 魔法属性支持)---
Gen: Generator.Generator | t.CPtr
DocKeys: bytes
DocValues: bytes
DocCount: int
# --- OOM 诊断:跨模块 CDefine 查找调用计数(CONST_INT 参数翻译疑似循环查找) ---
DiagCDefineCount: int
# --- OOM 诊断:translate_name_value 调用计数(顶层 CDefine 查找 14593 次来源) ---
DiagNameValueCount: int
# --- OOM 诊断:translate_value 调用计数(CONST_INT 循环调用源定位) ---
DiagTranslateValueCount: int
# ============================================================
# P3 迁移方法(函数体来自 HandlesExpr.vp 模块级函数,
# 本模块函数调用改 self.,外部模块调用保留模块前缀)
# ============================================================
# ============================================================
# 从 AST 节点提取函数名
# ============================================================
def get_func_name(self, func_node: ast.AST | t.CPtr) -> str:
"""从函数节点提取函数名"""
if func_node is None:
return None
k: int = func_node.kind()
if k == ast.ASTKind.Name:
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(func_node)
return nm.id
elif k == ast.ASTKind.Attribute:
at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(func_node)
return at.attr
return None
# ============================================================
# 转义 LLVM IR 字符串字面量
# ============================================================
def escape_llvm_string(self, pool: memhub.MemBuddy | t.CPtr,
src: str) -> str:
"""将字符串转换为 LLVM IR 字符串字面量 c"...\\00" """
if src is None:
return None
slen: t.CSizeT = string.strlen(src)
alloc_size: t.CSizeT = 4 * slen + 8
buf: str = pool.alloc(alloc_size)
if buf is None:
return None
pos: t.CSizeT = 0
buf[pos] = 'c'
pos += 1
buf[pos] = '"'
pos += 1
for i in range(slen):
ch: t.CChar = src[i]
if ch == '\0':
break
if ch == '\"' or ch == '\\' or ch < 0x20:
buf[pos] = '\\'
pos += 1
hi: int = (ch >> 4) & 0xF
lo: int = ch & 0xF
buf[pos] = Utils.nibble_to_hex_char(hi, 1)
pos += 1
buf[pos] = Utils.nibble_to_hex_char(lo, 1)
pos += 1
else:
buf[pos] = ch
pos += 1
buf[pos] = '\\'
pos += 1
buf[pos] = '0'
pos += 1
buf[pos] = '0'
pos += 1
buf[pos] = '"'
pos += 1
buf[pos] = '\0'
return buf
# ============================================================
# 获取 LLVM 整数类型的位宽
# ============================================================
def get_llvm_type_bits(self, ty: llvmlite.LLVMType | t.CPtr) -> int:
"""获取 LLVM 整数类型的位宽,非整数类型返回 0"""
if ty is None:
return 0
match ty:
case llvmlite.LLVMType.Int(bits):
return bits
case _:
return 0
# ============================================================
# 获取 LLVM 浮点类型的位宽
# ============================================================
def get_llvm_float_bits(self, ty: llvmlite.LLVMType | t.CPtr) -> int:
"""获取 LLVM 浮点类型的位宽,非浮点类型返回 0"""
if ty is None:
return 0
match ty:
case llvmlite.LLVMType.Float(bits):
return bits
case _:
return 0
# ============================================================
# is_ptr_type - 检查类型是否为 Ptr
#
# 返回 1=是, 0=否。作为类型检查工具函数被广泛使用(50+ 处调用)。
# ============================================================
def is_ptr_type(self, ty: llvmlite.LLVMType | t.CPtr) -> int:
"""检查 ty 是否是 Ptr 类型,返回 1=是, 0=否"""
if ty is None:
return 0
match ty:
case llvmlite.LLVMType.Ptr(pointee):
return 1
case _:
return 0
# ============================================================
# get_ptr_level - 计算指针层级
#
# 返回值:
# 0 = 非指针(数值/结构体等)
# 1 = 一级指针(如 i8*, Struct*)
# 2 = 二级指针(如 i8**, Struct**)
# 3+ = 更高级指针
#
# 用于比较运算的类型安全检查:
# - 数值(0) vs 数值(0) → 允许
# - 一级指针(1) vs 一级指针(1) → 允许
# - 数值(0) vs 指针(1+) → 禁止(fast-fail)
# - 不同级指针(1 vs 2) → 禁止(fast-fail)
# ============================================================
def get_ptr_level(self, ty: llvmlite.LLVMType | t.CPtr) -> int:
"""计算指针层级,0=非指针, 1=一级指针, 2=二级指针..."""
if ty is None:
return 0
level: int = 0
cur: llvmlite.LLVMType | t.CPtr = ty
while cur is not None:
# 同类编译 bug(见 TypeGenResolve L1463 注释):case body 直接
# 更新 while 条件变量(cur = pointee)时自举编译出的 match 把赋值
# 写到 subject 快照槽,while 条件读旧槽 → 死循环。改法:case
# 绑定到局部变量,match 外更新循环变量。
nxt_cur: llvmlite.LLVMType | t.CPtr = None
match cur:
case llvmlite.LLVMType.Ptr(pointee):
level += 1
nxt_cur = pointee
case _:
break
cur = nxt_cur
return level
# ============================================================
# _is_zero_int_literal - 判断 AST 节点是否为整数字面量 0
#
# 用于指针判空比较(ptr != 0 / ptr == 0):指针与字面量 0 比较时
# 0 视为 NULL,允许并转成 null 指针(比较类型检查的例外)。
# ============================================================
def _is_zero_int_literal(self, node: ast.AST | t.CPtr) -> int:
"""判断节点是否为整数字面量 0,返回 1=是 / 0=否"""
if node is None:
return 0
if node.kind() != ast.ASTKind.Constant:
return 0
cn: ast.Constant | t.CPtr = (ast.Constant | t.CPtr)(node)
if cn is None or cn.const_kind != ast.CONST_INT:
return 0
if cn.int_val == 0:
return 1
return 0
# ============================================================
# is_array_type - 检查类型是否为 Array
#
# 返回 1=是, 0=否。类型检查工具函数。
# ============================================================
def is_array_type(self, ty: llvmlite.LLVMType | t.CPtr) -> int:
"""检查 ty 是否是 Array 类型,返回 1=是, 0=否"""
if ty is None:
return 0
match ty:
case llvmlite.LLVMType.Array(_, _):
return 1
case _:
return 0
# ============================================================
# _zext_index_to_i32 - 数组下标索引零扩展
#
# LLVM GEP 对窄整数索引默认符号扩展:i8 0xE2(226) 会被当成 -30,
# 导致数组下标越界读(字节 >= 128 的乱码根因)。
# 数组下标语义为非负,<32 位整数索引一律 zext 到 i32。
# 仅用于数组 GEP;指针负偏移(p[-1])不经过此路径,不受影响。
# ============================================================
def _zext_index_to_i32(self, builder: llvmlite.IRBuilder | t.CPtr,
idx_val: llvmlite.Value | t.CPtr) -> llvmlite.Value | t.CPtr:
"""将 <32 位整数下标零扩展到 i32(数组下标非负语义)"""
if idx_val is None or idx_val.Ty is None or builder is None:
return idx_val
idx_bits: int = self.get_llvm_type_bits(idx_val.Ty)
if idx_bits != 0 and idx_bits < 32:
i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(builder.Pool)
return llvmlite.build_zext(builder, idx_val, i32_ty)
return idx_val
# ============================================================
# _get_custom_struct_cls_nm - 检查类型是否为已注册的自定义结构体
#
# 处理两种情况:
# - ty 直接是 Struct → 检查是否注册
# - ty 是 Ptr(Struct) → 检查 pointee 是否注册
#
# 返回类名字符串或 None。用于 subscript 操作中判断是否需要
# 转发到 __getitem__/__setitem__ 而非生成 GEP。
# ============================================================
def _get_custom_struct_cls_nm(self, pool: memhub.MemBuddy | t.CPtr,
ty: llvmlite.LLVMType | t.CPtr) -> str:
"""检查 ty 或 ty 的 pointee 是否是已注册的自定义结构体,返回类名或 None"""
if ty is None:
return None
# 直接检查 ty 是否是注册的结构体
cls_nm_direct: str = self.Gen.StructGen.get_class_name_by_type(pool, ty)
if cls_nm_direct is not None:
return cls_nm_direct
# 如果 ty 是 Ptr,检查 pointee
if self.is_ptr_type(ty) != 0:
pointee_cs: llvmlite.LLVMType | t.CPtr = ty.Pointee
if pointee_cs is not None:
return self.Gen.StructGen.get_class_name_by_type(pool, pointee_cs)
return None
# ============================================================
# _is_list_subscript_base - 判断下标基座是否为 list[T] 变量
#
# 属性访问的 Subscript 基座(如 lst[i].x)需要区分:
# - list[T] 变量:translate_subscript 走 __getitem__ 内联返回元素地址
# - 普通结构体指针数组(如 pts: Point | t.CPtr):get_subscript_ptr 返回元素地址
# 路由错误会让 list[T] 被当作结构体数组 GEP,访问错位崩溃。
# ============================================================
def _is_list_subscript_base(self, pool: memhub.MemBuddy | t.CPtr,
trans: HT.Translator | t.CPtr,
sub_node: ast.AST | t.CPtr) -> int:
"""判断下标基座是否为 list[T] 变量(1=是)"""
if sub_node is None or trans is None:
return 0
sub: ast.Subscript | t.CPtr = (ast.Subscript | t.CPtr)(sub_node)
if sub is None or sub.value is None or sub.value.kind() != ast.ASTKind.Name:
return 0
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(sub.value)
if nm.id is None:
return 0
alloca: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is None or alloca.Ty is None:
return 0
if self.is_ptr_type(alloca.Ty) == 0:
return 0
pointee: llvmlite.LLVMType | t.CPtr = alloca.Ty.Pointee
if pointee is None:
return 0
match pointee:
case llvmlite.LLVMType.Ptr(inner_ty):
lsn_ls: str = None
match inner_ty:
case llvmlite.LLVMType.Struct(_, _, lsn3):
lsn_ls = lsn3
case _:
pass
if lsn_ls is not None and string.strstr(lsn_ls, "list[") is not None:
return 1
case _:
pass
return 0
# ============================================================
# _sub_base_class_name - 解析下标基座变量的结构体类名
# ============================================================
def _sub_base_class_name(self, pool: memhub.MemBuddy | t.CPtr,
trans: HT.Translator | t.CPtr,
sub_node: ast.AST | t.CPtr) -> str:
"""解析下标基座变量的结构体类名(无则 None)"""
if sub_node is None or trans is None:
return None
sub: ast.Subscript | t.CPtr = (ast.Subscript | t.CPtr)(sub_node)
if sub is None or sub.value is None or sub.value.kind() != ast.ASTKind.Name:
return None
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(sub.value)
if nm.id is None:
return None
alloca: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is None or alloca.Ty is None:
return None
if self.is_ptr_type(alloca.Ty) == 0:
return None
pointee: llvmlite.LLVMType | t.CPtr = alloca.Ty.Pointee
if pointee is None:
return None
if self.is_ptr_type(pointee) != 0:
inner: llvmlite.LLVMType | t.CPtr = pointee.Pointee
if inner is not None:
return self.Gen.StructGen.get_class_name_by_type(pool, inner)
return self.Gen.StructGen.get_class_name_by_type(pool, pointee)
# ============================================================
# _sub_base_uses_getitem - 下标基座是否应走 __getitem__ 语义
#
# 属性访问的 Subscript 基座路由依据:
# - list[T] / OOP 定义 __getitem__ 的类(如 dict[key])→ translate_value
# (translate_subscript 走 __getitem__ 内联/方法调用返回元素值或地址)
# - 普通结构体指针数组(如 pts: Point | t.CPtr)→ get_subscript_ptr
# (GEP 元素地址,避免结构体值 base=非指针 报错)
# ============================================================
def _sub_base_uses_getitem(self, pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
trans: HT.Translator | t.CPtr,
sub_node: ast.AST | t.CPtr) -> int:
"""下标基座应走 __getitem__ 语义时返回 1,否则 0(应走 GEP 地址语义)"""
if self._is_list_subscript_base(pool, trans, sub_node) != 0:
return 1
cls_ugi: str = self._sub_base_class_name(pool, trans, sub_node)
if cls_ugi is None:
return 0
lookup_ugi: str = pool.alloc(128)
if lookup_ugi is None:
return 0
viperlib.snprintf(lookup_ugi, 128, "%s.__getitem__", cls_ugi)
if self.Gen.CallGenBase.find_func_in_module(mod, lookup_ugi) is not None:
return 1
return 0
# ============================================================
# _deref_if_ptr_ptr - 如果 obj_ptr 是 Ptr(Ptr(...)),load 解引用
#
# 用于支持 X|t.CPtr 类型变量的属性访问:
# - 值类型变量 (cnt: Counter): alloca 类型是 Ptr(Struct) → 不解引用
# - 指针类型变量 (r: Vec2|t.CPtr): alloca 类型是 Ptr(Ptr(Struct)) → load 解引用
# ============================================================
def _deref_if_ptr_ptr(self, builder: llvmlite.IRBuilder | t.CPtr,
obj_ptr: llvmlite.Value | t.CPtr) -> llvmlite.Value | t.CPtr:
"""如果 obj_ptr 是 Ptr(Ptr(...)),load 解引用获取内层指针"""
if obj_ptr is None or obj_ptr.Ty is None:
return obj_ptr
if self.is_ptr_type(obj_ptr.Ty) == 0:
return obj_ptr
pointee: llvmlite.LLVMType | t.CPtr = obj_ptr.Ty.Pointee
if pointee is None:
return obj_ptr
if self.is_ptr_type(pointee) == 0:
return obj_ptr
# obj_ptr 是 Ptr(Ptr(...)),load 解引用
loaded: llvmlite.Value | t.CPtr = llvmlite.build_load(builder, pointee, obj_ptr)
if loaded is None:
return obj_ptr
return loaded
# ============================================================
# 类型强制转换:整数 sext/trunc,浮点 fpext/fptrunc,int↔float si2fp/fp2si
# ============================================================
def coerce_to_type(self, builder: llvmlite.IRBuilder | t.CPtr,
val: llvmlite.Value | t.CPtr,
target_ty: llvmlite.LLVMType | t.CPtr,
node: ast.AST | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""将 val 强制转换为 target_ty
整数: sext(扩展)/ trunc(截断)
浮点: fpext(扩展)/ fptrunc(截断)
int→float: sitofp
float→int: fptosi
node: AST 节点(用于错误定位),可为 None
"""
if val is None or target_ty is None:
return val
# 函数 → 指针: bitcast(如 CreateThread(..., _thread_alloc_free, ...) 传函数指针)
# 函数 Value 的 Ty 是 Func,既不是整数也不是指针,需在整数/浮点转换之前处理
match val.Ty:
case llvmlite.LLVMType.Func(_, _, _):
if self.is_ptr_type(target_ty) != 0:
return llvmlite.build_bitcast(builder, val, target_ty)
return val
case _:
pass
# 整数转换
val_bits: int = self.get_llvm_type_bits(val.Ty)
target_bits: int = self.get_llvm_type_bits(target_ty)
# 整数 → 指针: inttoptr(必须在纯整数转换之前检查,因为 ptr 的 bits 非零会误入 sext/trunc 路径)
# 适用于: 跨模块方法返回 i64/i32(默认推断),目标变量是指针类型
if val_bits != 0 and self.is_ptr_type(target_ty) != 0:
return llvmlite.build_inttoptr(builder, val, target_ty)
# 指针 → 整数: ptrtoint(当目标明确是整数而非指针时)
# 注意: target_bits == 8 时跳过 ptrtoint,走后面的 build_load 解引用首字符
# 修复: buf[idx] = '\0' 中 '\0' 是 CONST_STR → i8* 指针,
# ptrtoint 会截断地址低 8 位(非 0),应 load 解引用取首字符 0
if self.is_ptr_type(val.Ty) != 0 and target_bits != 0 and target_bits != 8 and self.is_ptr_type(target_ty) == 0:
return llvmlite.build_ptrtoint(builder, val, target_ty)
if val_bits != 0 and target_bits != 0:
if val_bits == target_bits:
return val
if val_bits < target_bits:
# i1 (bool) → 更宽整数用 zext(bool 语义:1 保持 1,而非 sext 的 0xFF...)
# 与 HandlesReturn 中 return 语句的 i1 扩展规则保持一致
if val_bits == 1:
return llvmlite.build_zext(builder, val, target_ty)
return llvmlite.build_sext(builder, val, target_ty)
return llvmlite.build_trunc(builder, val, target_ty)
# 浮点转换
val_fbits: int = self.get_llvm_float_bits(val.Ty)
target_fbits: int = self.get_llvm_float_bits(target_ty)
# float ↔ ptr: 禁止(无意义的转换,fast-fail 防止误走入 build_load 路径)
if (val_fbits != 0 and self.is_ptr_type(target_ty) != 0) or \
(self.is_ptr_type(val.Ty) != 0 and target_fbits != 0):
self.Gen.TypeGenErr.fatal_error(node, "unsupported type conversion: float <-> ptr", "CastError")
return val
if val_fbits != 0 and target_fbits != 0:
if val_fbits == target_fbits:
return val
if val_fbits < target_fbits:
return llvmlite.build_fpext(builder, val, target_ty)
return llvmlite.build_fptrunc(builder, val, target_ty)
# int → float
if val_bits != 0 and target_fbits != 0:
return llvmlite.build_si2fp(builder, val, target_ty)
# float → int
if val_fbits != 0 and target_bits != 0:
return llvmlite.build_fp2si(builder, val, target_ty)
# 指针 → 非指针值: build_load 解引用
# 适用于: 指针 → 整数 (如 i8* → i8), 指针 → 结构体值
# 当构造器返回 Ptr(Struct) 但目标变量是 Struct 值类型时,需要 load
if self.is_ptr_type(val.Ty) != 0 and self.is_ptr_type(target_ty) == 0:
return llvmlite.build_load(builder, target_ty, val)
# 指针 → 指针: bitcast (如 i8* → i8** 当目标是全局变量存储指针)
if self.is_ptr_type(val.Ty) != 0 and self.is_ptr_type(target_ty) != 0:
return llvmlite.build_bitcast(builder, val, target_ty)
return val
# ============================================================
# translate_ptr_arith - 指针算术: ptr + int / ptr - int
#
# 用 getelementptr(按 pointee 元素大小缩放索引),对齐 TPC
# ExprGen.emit_binary_op / HandlesAugAssign.py。
# 旧实现用 ptrtoint + add + inttoptr(按字节加):i8* 元素大小 1
# 恰好正确,但 i32*/struct* 等指针 `p + 3` 只前进 3 字节(应 12),
# 导致 StringIterTest Test 4/8 失败(指针偏移/指针算术)。
#
# 数组 pointee([N x ty]*)先 decay 为元素指针(GEP 0,0),
# 避免以整个数组为步长(对齐 TPC ExprGen L53-55)。
# ============================================================
def translate_ptr_arith(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
ptr_val: llvmlite.Value | t.CPtr,
int_val: llvmlite.Value | t.CPtr,
op: int) -> llvmlite.Value | t.CPtr:
"""指针算术: ptr + int / ptr - int → GEP(按元素大小缩放索引)"""
if ptr_val is None or int_val is None or ptr_val.Ty is None:
return None
# 提取 pointee 类型(非指针 → 不处理返回 None)
elem_ty: llvmlite.LLVMType | t.CPtr = None
match ptr_val.Ty:
case llvmlite.LLVMType.Ptr(pointee):
elem_ty = pointee
case _:
return None
if elem_ty is None:
return None
# 索引统一到 i64(对齐 TPC zext 到 i64)
i64_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
idx: llvmlite.Value | t.CPtr = self.coerce_to_type(builder, int_val, i64_ty, None)
if idx is None:
return None
# ptr - int → 负索引(LLVM GEP 索引为加法语义)
if op == ast.OpKind.Sub:
neg_idx: llvmlite.Value | t.CPtr = llvmlite.build_sub(
builder, llvmlite.const_int64(pool, 0), idx)
if neg_idx is None:
return None
idx = neg_idx
# 数组 pointee → 先 decay 为元素指针(getelementptr [N x ty], [N x ty]* %p, i32 0, i32 0)
if self.is_array_type(elem_ty) != 0:
inner_ty: llvmlite.LLVMType | t.CPtr = None
match elem_ty:
case llvmlite.LLVMType.Array(inner, _):
inner_ty = inner
case _:
return None
if inner_ty is None:
return None
decayed: llvmlite.Value | t.CPtr = llvmlite.build_gep_array(
builder, elem_ty, inner_ty, ptr_val, llvmlite.const_int64(pool, 0))
if decayed is None:
return None
return llvmlite.build_gep(builder, inner_ty, decayed, idx)
return llvmlite.build_gep(builder, elem_ty, ptr_val, idx)
def InitExprGen(self) -> int:
"""初始化 ExprGen 族字段(构造时由 Generator 调用)"""
if self is None:
return 1
self.DocKeys = None
self.DocValues = None
self.DocCount = 0
self.DiagCDefineCount = 0
self.DiagNameValueCount = 0
self.DiagTranslateValueCount = 0
return 0
File diff suppressed because it is too large. Load diff
-529
View File
@@ -1,529 +0,0 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import lib.core.Gen.CallGenBase as CallGenBase
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import viperlib
import w32.fileio as fileio
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Handles.HandlesBase as HandlesBase
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.Stub.StubMerger as StubMerger
import lib.core.VLogger as VLogger
import lib.Projectrans.Config as Config
import lib.Projectrans.Utils as Utils
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.core.Gen.ExprGenBase as ExprGenBase
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
# ============================================================
# docstring 登记表容量常量(与 ExprGenMixin 保持一致,本地副本避免
# 跨模块 CDefine 查找 bug:ExprGenCls(C) 在 ExprGenMixin(M) 之前按
# 文件序编译时,跨模块 CDefine 全局表未注册 → NameError 未定义)
# ============================================================
DOC_MAX: t.CDefine = 512
DOC_KEY_SZ: t.CDefine = 192
DOC_VAL_SZ: t.CDefine = 4096
# ============================================================
# ExprGenCls - ExprGenMixin 拆分(阶段 D):类名推断/docstring 登记
#
# 继承 ExprGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesExpr 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class ExprGenCls(ExprGenBase.ExprGenBase):
"""类名推断/docstring 登记"""
# ============================================================
# docstring 提取/登记/查找(__doc__ 魔法属性支持)
#
# 预注册阶段(BuildPipeline.TranslateFileGetTrans)扫描所有
# App/includes 文件的 AST,提取函数/类/方法的 docstring 内容
# 登记到全局表(key = "{module_sha1}.{symbol}");__doc__ 属性
# 访问时查表,命中则当前模块内联生成字符串常量(无跨模块 IR
# 符号依赖),未命中返回 null。幂等查重,多遍翻译安全。
# ============================================================
def _extract_docstring_from_body(self, body: list[ast.AST | t.CPtr] | t.CPtr) -> str:
"""从函数/类体提取 docstring:body[0] 为 Expr(Constant str) 时返回内容"""
if body is None or len(body) == 0:
return None
first: ast.AST | t.CPtr = body.get(0)
if first is None or first.kind() != ast.ASTKind.Expr:
return None
ex: ast.Expr | t.CPtr = (ast.Expr | t.CPtr)(first)
if ex is None or ex.value is None or ex.value.kind() != ast.ASTKind.Constant:
return None
cn: ast.Constant | t.CPtr = (ast.Constant | t.CPtr)(ex.value)
if cn is None or cn.const_kind != ast.CONST_STR:
return None
return cn.str_val
def register_docstring(self, module_sha1: str, symbol: str, doc: str) -> None:
"""登记 docstring(key="{module_sha1}.{symbol}"),查重幂等"""
if module_sha1 is None or symbol is None or doc is None:
return
if string.strlen(doc) == 0:
return
if self.DocKeys is None:
self.DocKeys = CompilerFactory.get_perm_pool().alloc(DOC_MAX * DOC_KEY_SZ)
self.DocValues = CompilerFactory.get_perm_pool().alloc(DOC_MAX * DOC_VAL_SZ)
if self.DocKeys is None or self.DocValues is None:
return
string.memset(self.DocKeys, 0, DOC_MAX * DOC_KEY_SZ)
string.memset(self.DocValues, 0, DOC_MAX * DOC_VAL_SZ)
if self.DocCount >= DOC_MAX:
return
ml: t.CSizeT = string.strlen(module_sha1)
sl: t.CSizeT = string.strlen(symbol)
key_buf: bytes = stdlib.malloc(ml + sl + 2)
if key_buf is None:
return
viperlib.snprintf(key_buf, ml + sl + 2, "%s.%s", module_sha1, symbol)
i: int
for i in range(self.DocCount):
if string.strcmp(self.DocKeys + i * DOC_KEY_SZ, key_buf) == 0:
stdlib.free(key_buf)
return
string.strcpy(self.DocKeys + self.DocCount * DOC_KEY_SZ, key_buf)
string.strcpy(self.DocValues + self.DocCount * DOC_VAL_SZ, doc)
self.DocCount += 1
stdlib.free(key_buf)
def lookup_docstring(self, module_sha1: str, symbol: str) -> str:
"""查找 docstring 内容(key="{module_sha1}.{symbol}"),未找到返回 None"""
if self.DocKeys is None or module_sha1 is None or symbol is None:
return None
ml: t.CSizeT = string.strlen(module_sha1)
sl: t.CSizeT = string.strlen(symbol)
key_buf: bytes = stdlib.malloc(ml + sl + 2)
if key_buf is None:
return None
viperlib.snprintf(key_buf, ml + sl + 2, "%s.%s", module_sha1, symbol)
i: int
for i in range(self.DocCount):
if string.strcmp(self.DocKeys + i * DOC_KEY_SZ, key_buf) == 0:
stdlib.free(key_buf)
return self.DocValues + i * DOC_VAL_SZ
stdlib.free(key_buf)
return None
def extract_module_docstrings(self, tree: ast.AST | t.CPtr, module_sha1: str) -> None:
"""扫描模块 AST:登记模块级函数/类/方法的 docstring(幂等)"""
if tree is None or module_sha1 is None:
return
ch: list[ast.AST | t.CPtr] | t.CPtr = tree.children
if ch is None:
return
cn: t.CSizeT = len(ch)
for ci in range(cn):
child: ast.AST | t.CPtr = ch.get(ci)
if child is None:
continue
kd: int = child.kind()
if kd == ast.ASTKind.FunctionDef:
fd: ast.FunctionDef | t.CPtr = (ast.FunctionDef | t.CPtr)(child)
if fd is not None and fd.name is not None:
fd_body: list[ast.AST | t.CPtr] | t.CPtr = fd.children
fdoc: str = self._extract_docstring_from_body(fd_body)
if fdoc is not None:
self.register_docstring(module_sha1, fd.name, fdoc)
elif kd == ast.ASTKind.ClassDef:
cd: ast.ClassDef | t.CPtr = (ast.ClassDef | t.CPtr)(child)
if cd is None or cd.name is None:
continue
cd_body0: list[ast.AST | t.CPtr] | t.CPtr = cd.children
cdoc: str = self._extract_docstring_from_body(cd_body0)
if cdoc is not None:
self.register_docstring(module_sha1, cd.name, cdoc)
# 类方法 docstring(key="{ClassName}.{method}")
cd_body: list[ast.AST | t.CPtr] | t.CPtr = cd.children
if cd_body is not None:
cbn: t.CSizeT = len(cd_body)
for mi in range(cbn):
m: ast.AST | t.CPtr = cd_body.get(mi)
if m is None or m.kind() != ast.ASTKind.FunctionDef:
continue
mfd: ast.FunctionDef | t.CPtr = (ast.FunctionDef | t.CPtr)(m)
if mfd is None or mfd.name is None:
continue
mdoc: str = self._extract_docstring_from_body(mfd.children)
if mdoc is not None:
cl_nl: t.CSizeT = string.strlen(cd.name)
mt_nl: t.CSizeT = string.strlen(mfd.name)
sym_buf: bytes = stdlib.malloc(cl_nl + mt_nl + 2)
if sym_buf is not None:
viperlib.snprintf(sym_buf, cl_nl + mt_nl + 2, "%s.%s", cd.name, mfd.name)
self.register_docstring(module_sha1, sym_buf, mdoc)
stdlib.free(sym_buf)
def _resolve_doc_symbol_name(self, trans: HT.Translator | t.CPtr, name_id: str,
out_mod: str, out_sym: str) -> int:
"""解析 Name 符号的 docstring 归属,写入 out_mod/out_sym,成功返回 1
判定顺序:本地变量(函数变量)→ 结构体类(含跨模块预注册)→ 模块名。
"""
if trans is None or name_id is None or out_mod is None or out_sym is None:
return 0
if trans.ModuleSha1 is None:
return 0
# 1. 本地变量(函数变量/全局变量)
if self.Gen.VarGen.lookup_var(trans.SymTab, name_id) is not None:
string.strcpy(out_mod, trans.ModuleSha1)
string.strcpy(out_sym, name_id)
return 1
# 2. 结构体类(entry.ModuleSha1 区分跨模块预注册的类)
se: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct(name_id)
if se is not None:
if se.ModuleSha1 is not None:
string.strcpy(out_mod, se.ModuleSha1)
else:
string.strcpy(out_mod, trans.ModuleSha1)
string.strcpy(out_sym, name_id)
return 1
# 3. 模块名(模块本身,少见);用 StubMerger 模块级函数(避免
# 跨 Mixin self 调用生成不存在符号)
mod_sha1: str = StubMergerStore.LookupSha1ByModName(name_id)
if mod_sha1 is None:
mod_sha1 = StubMergerStore.LookupSha1ByModNameSuffix(name_id)
if mod_sha1 is not None:
string.strcpy(out_mod, mod_sha1)
string.strcpy(out_sym, name_id)
return 1
# 4. 兜底当前模块
string.strcpy(out_mod, trans.ModuleSha1)
string.strcpy(out_sym, name_id)
return 1
def _resolve_doc_symbol_attr(self, trans: HT.Translator | t.CPtr,
at: ast.Attribute | t.CPtr,
out_mod: str, out_sym: str) -> int:
"""解析嵌套 Attribute 链的 docstring 归属,写入 out_mod/out_sym
支持形态:remote.remote_func / remote.RemoteClass /
remote.RemoteClass.remote_method / LocalClass.local_method 等。
最左 Name 判定:模块名 → 该模块 sha1;变量/类 → 类所属模块 sha1。
"""
if trans is None or at is None or out_mod is None or out_sym is None:
return 0
if trans.ModuleSha1 is None:
return 0
# 收集 Attribute 链(最外层 attr 在前)
DOC_CHAIN_MAX: t.CInt = 8
items: bytes = stdlib.malloc(DOC_CHAIN_MAX * 128)
if items is None:
return 0
n_items: int = 0
cur: ast.AST | t.CPtr = (ast.AST | t.CPtr)(at)
while cur is not None and cur.kind() == ast.ASTKind.Attribute and n_items < DOC_CHAIN_MAX:
cat: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(cur)
if cat.attr is None:
break
string.strcpy(items + n_items * 128, cat.attr)
n_items += 1
cur = cat.value
# 最左应为 Name
head_id: str = None
if cur is not None and cur.kind() == ast.ASTKind.Name:
hnm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(cur)
head_id = hnm.id
if head_id is None:
stdlib.free(items)
return 0
# head 判定:模块名 or 变量/类
is_mod: int = 0
mod_sha1: str = None
if self.Gen.VarGen.lookup_var(trans.SymTab, head_id) is None:
mod_sha1 = StubMergerStore.LookupSha1ByModName(head_id)
if mod_sha1 is None:
mod_sha1 = StubMergerStore.LookupSha1ByModNameSuffix(head_id)
if mod_sha1 is not None:
is_mod = 1
if is_mod != 0:
# 模块限定:symbol = items 反转(不含 head_id)
if n_items == 0:
string.strcpy(out_mod, mod_sha1)
string.strcpy(out_sym, head_id)
stdlib.free(items)
return 1
total: t.CSizeT = 0
j: int
for j in range(n_items):
total += string.strlen(items + j * 128) + 1
sym_buf: bytes = stdlib.malloc(total + 1)
if sym_buf is None:
stdlib.free(items)
return 0
sym_buf[0] = '\0'
for j in range(n_items):
if j > 0:
string.strcat(sym_buf, ".")
string.strcat(sym_buf, items + (n_items - 1 - j) * 128)
string.strcpy(out_mod, mod_sha1)
string.strcpy(out_sym, sym_buf)
stdlib.free(sym_buf)
stdlib.free(items)
return 1
# 变量/类:symbol = head_id + '.' + items 反转
total2: t.CSizeT = string.strlen(head_id)
for j in range(n_items):
total2 += string.strlen(items + j * 128) + 1
sym2: bytes = stdlib.malloc(total2 + 1)
if sym2 is None:
stdlib.free(items)
return 0
sym2[0] = '\0'
string.strcat(sym2, head_id)
for j in range(n_items):
string.strcat(sym2, ".")
string.strcat(sym2, items + (n_items - 1 - j) * 128)
se2: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct(head_id)
if se2 is not None and se2.ModuleSha1 is not None:
string.strcpy(out_mod, se2.ModuleSha1)
else:
string.strcpy(out_mod, trans.ModuleSha1)
string.strcpy(out_sym, sym2)
stdlib.free(sym2)
stdlib.free(items)
return 1
# ============================================================
# P3 迁移方法(来自 HandlesExprOps.vp:运算符重载 + 二元运算)
# ============================================================
# ============================================================
# infer_expr_class - 从表达式推断结构体类名(共享推断)
#
# 无注解赋值(HandlesAssign)与方法调用分派(CallGenMixin)共用:
# 联合类型 X|t.CPtr 简化为 Ptr(i8),不含类名,需按表达式结构推断:
# Call → 类实例化(func 为已注册结构体名);func 为 Attribute
# 时是方法调用,经 vpi 返回注解缓存解析返回类名
# Name → 源变量的 AnnotClassName
# BinOp/UnaryOp → lhs 类名 + 存在对应 dunder 方法(返回同类对象)
# Attribute → 字段注解类名(_resolve_attr_field_class)
# 仅采用已注册结构体;无法精确推断返回 None(fail-fast 由调用方暴露)。
# ============================================================
def infer_expr_class(self, pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
node: ast.AST | t.CPtr,
trans: HT.Translator | t.CPtr) -> str:
"""从表达式推断结构体类名,无法推断返回 None"""
if node is None or trans is None:
return None
k: int = node.kind()
# Call: 类实例化 / 方法调用
if k == ast.ASTKind.Call:
cl: ast.Call | t.CPtr = (ast.Call | t.CPtr)(node)
if cl is None or cl.func is None:
return None
if cl.func.kind() == ast.ASTKind.Name:
cand: str = ((ast.Name | t.CPtr)(cl.func)).id
if cand is None:
return None
if self.Gen.StructGen.find_struct(cand) is not None:
return cand
return None
if cl.func.kind() == ast.ASTKind.Attribute:
# 方法调用 a.cross(b):vpi 返回注解缓存精确解析返回类名
# (方法可返回非同类对象,如 Transform.world_position → 'Vec3')。
ca: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(cl.func)
if ca is None or ca.attr is None or ca.value is None:
return None
# 模块级函数调用(Module.func()):接收者是已导入模块名而非类对象
# (CompilerFactory.get_perm_pool())。返回注解可能是联合类型
# (memhub.MemBuddy | t.CPtr),查 vpi 模块函数返回注解(cls=None)
# 提取返回类,解决链式方法调用 .alloc() 推断失败退化裸名 @alloc
# (链接 undefined symbol: alloc 根因)。
if ca.value.kind() == ast.ASTKind.Name:
mod_nm2: str = ((ast.Name | t.CPtr)(ca.value)).id
if mod_nm2 is not None and trans._from_imports is not None:
mod_sha1_2: str = self.Gen.CallGenBase._lookup_mod_sha1(
pool, trans._from_imports, mod_nm2)
if mod_sha1_2 is not None:
temp_dir2: str = None
if self.Gen.CallGenBase.TempDir is not None:
temp_dir2 = self.Gen.CallGenBase.TempDir
if temp_dir2 is not None:
VpiReader._ensure_vpi_ret_annot_loaded(mod_sha1_2, temp_dir2)
annot_ret2: str = VpiReader._lookup_vpi_ret_annot(
mod_sha1_2, None, ca.attr)
if annot_ret2 is not None:
ret_cls2: str = self._extract_annot_class_name(pool, annot_ret2)
if ret_cls2 is not None:
return ret_cls2
obj_cls: str = self.infer_expr_class(pool, mod, ca.value, trans)
if obj_cls is None:
return None
if self._cls_has_dunder(pool, mod, obj_cls, ca.attr) == 0:
return None
cls_sha1: str = self.Gen.StructGen.get_struct_sha1(obj_cls)
if cls_sha1 is None and trans.ModuleSha1 is not None:
cls_sha1 = trans.ModuleSha1
ret_cls: str = None
if cls_sha1 is not None:
temp_dir: str = None
# 跨子对象访问经 self.Gen 取 CallGenBase.TempDir(双向引用)
if self.Gen.CallGenBase.TempDir is not None:
temp_dir = self.Gen.CallGenBase.TempDir
if temp_dir is not None:
VpiReader._ensure_vpi_ret_annot_loaded(cls_sha1, temp_dir)
annot_ret: str = VpiReader._lookup_vpi_ret_annot(
cls_sha1, obj_cls, ca.attr)
if annot_ret is not None:
ret_cls = self._extract_annot_class_name(pool, annot_ret)
return ret_cls
return None
# Name: 源变量注解
if k == ast.ASTKind.Name:
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(node)
if nm is None or nm.id is None:
return None
ve: GenTypes.VarEntry | t.CPtr = self.Gen.VarGen.lookup_var_entry(
trans.SymTab, nm.id)
if ve is None:
return None
acn_n: str = ve.AnnotClassName
if acn_n is None:
return None
# 去模块前缀:json.JsonValue → JsonValue。AnnotClassName 可能带
# 模块限定(extract_class_name_from_annotation 的 Attribute 分支返回
# "memhub.MemBuddy" 形式),而 find_struct/_cls_has_dunder/
# get_struct_sha1 查找都用裸类名(struct 注册名)。泛型名
# (list[str] 含 '[')不截断(方括号内可能有点号)。
acn_dot_n: str = string.strrchr(acn_n, 46)
if acn_dot_n is not None and string.strchr(acn_n, 91) is None:
acn_n = acn_dot_n + 1
return acn_n
# BinOp/UnaryOp: 运算符重载返回同类对象
if k == ast.ASTKind.BinOp:
bop: ast.BinOp | t.CPtr = (ast.BinOp | t.CPtr)(node)
if bop is None or bop.left is None:
return None
dunder: str = self.Gen.ExprGenOps._binop_to_dunder(bop.op)
if dunder is None:
return None
lhs_cls: str = self.infer_expr_class(pool, mod, bop.left, trans)
if lhs_cls is None or self._cls_has_dunder(pool, mod, lhs_cls, dunder) == 0:
return None
return lhs_cls
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp | t.CPtr = (ast.UnaryOp | t.CPtr)(node)
if uop is None or uop.operand is None:
return None
dunder: str = self.Gen.ExprGenOps._unaryop_to_dunder(uop.op)
if dunder is None:
return None
opr_cls: str = self.infer_expr_class(pool, mod, uop.operand, trans)
if opr_cls is None or self._cls_has_dunder(pool, mod, opr_cls, dunder) == 0:
return None
return opr_cls
# Attribute: 字段注解类名(属性链递归,如 p = r.origin)
if k == ast.ASTKind.Attribute:
return self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, node)
return None
# ============================================================
# _cls_has_dunder - 检查类(含继承链)是否存在 dunder/方法
#
# 与 try_operator_overload 的查找一致:当前模块内定义或跨模块类有
# SHA1(stub 中声明)即视为存在。仅在确实存在时采用推断类名,
# 避免把原生运算误标为类类型。
# ============================================================
def _cls_has_dunder(self, pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
class_name: str, dunder: str) -> int:
"""类及其 MRO 祖先链中是否存在 dunder 方法,存在返回 1"""
if class_name is None or dunder is None or mod is None:
return 0
lookup_name: str = pool.alloc(128)
if lookup_name is None:
return 0
viperlib.snprintf(lookup_name, 128, "%s.%s", class_name, dunder)
if self.Gen.CallGenBase.find_func_in_module(mod, lookup_name) is not None:
return 1
# 继承链祖先查找
mro_ptr: t.CSizeT | t.CPtr = self.Gen.StructGen.get_mro_chain(pool, class_name)
mi: int = 1
while mro_ptr is not None and mro_ptr[mi] != 0:
anc: str = (str | t.CPtr)(t.CVoid(mro_ptr[mi], t.CPtr))
if anc is not None:
anc_lookup: str = pool.alloc(128)
if anc_lookup is not None:
viperlib.snprintf(anc_lookup, 128, "%s.%s", anc, dunder)
if self.Gen.CallGenBase.find_func_in_module(mod, anc_lookup) is not None:
return 1
mi += 1
# 跨模块:类有 SHA1(stub 中声明该方法)视为存在
if self.Gen.StructGen.get_struct_sha1(class_name) is not None:
return 1
return 0
# ============================================================
# _extract_annot_class_name - 从返回注解文本提取结构体类名
#
# vpi 返回注解文本(如 "'Vec3' | CPtr")按 '|' 分段,每段去除
# 空格/引号后查结构体注册表:命中即类名(内置类型 str/int/
# CFloat64T/t.CPtr 等均不注册 struct,天然过滤)。"A | B" 联合
# 多候选时取首个命中段。
# ============================================================
def _extract_annot_class_name(self, pool: memhub.MemBuddy | t.CPtr,
annot: str) -> str:
"""从返回注解文本提取已注册结构体类名,无命中返回 None"""
if pool is None or annot is None:
return None
alen: t.CSizeT = string.strlen(annot)
if alen == 0:
return None
seg: str = pool.alloc(256)
if seg is None:
return None
start: t.CSizeT = 0
i: t.CSizeT
for i in range(alen):
if annot[i] == '|':
s: t.CSizeT = start
e: t.CSizeT = i
while s < e and (annot[s] == ' ' or annot[s] == '\'' or annot[s] == '"'):
s += 1
while e > s and (annot[e - 1] == ' ' or annot[e - 1] == '\'' or annot[e - 1] == '"'):
e -= 1
seglen: t.CSizeT = e - s
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
if self.Gen.StructGen.find_struct(seg) is not None:
return seg
# 模块前缀(memhub.MemBuddy → MemBuddy):find_struct 未命中
# 时去最后一个 '.' 前的模块名再试(vpi 返回注解常带模块限定)
seg_dot2: str = string.strrchr(seg, 46)
if seg_dot2 is not None and seg_dot2 != seg:
seg_bare2: str = seg_dot2 + 1
if self.Gen.StructGen.find_struct(seg_bare2) is not None:
return seg_bare2
start = i + 1
# 最后一段(无 '|' 后缀)
s: t.CSizeT = start
e: t.CSizeT = alen
while s < e and (annot[s] == ' ' or annot[s] == '\'' or annot[s] == '"'):
s += 1
while e > s and (annot[e - 1] == ' ' or annot[e - 1] == '\'' or annot[e - 1] == '"'):
e -= 1
seglen: t.CSizeT = e - s
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
if self.Gen.StructGen.find_struct(seg) is not None:
return seg
# 模块前缀(memhub.MemBuddy → MemBuddy):find_struct 未命中时
# 去最后一个 '.' 前的模块名再试(vpi 返回注解常带模块限定)
seg_dot3: str = string.strrchr(seg, 46)
if seg_dot3 is not None and seg_dot3 != seg:
seg_bare3: str = seg_dot3 + 1
if self.Gen.StructGen.find_struct(seg_bare3) is not None:
return seg_bare3
return None
-411
View File
@@ -1,411 +0,0 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import viperlib
import w32.fileio as fileio
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Handles.HandlesBase as HandlesBase
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.Stub.StubMerger as StubMerger
import lib.core.VLogger as VLogger
import lib.Projectrans.Config as Config
import lib.Projectrans.Utils as Utils
import lib.core.Gen.ExprGenBase as ExprGenBase
# ============================================================
# ExprGenConst - ExprGenMixin 拆分(阶段 D):常量/全局字符串/list 字面量翻译
#
# 继承 ExprGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesExpr 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class ExprGenConst(ExprGenBase.ExprGenBase):
"""常量/全局字符串/list 字面量翻译"""
# ============================================================
# 创建全局字符串常量
# ============================================================
def create_global_string(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
str_val: str,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""创建全局字符串常量并返回 i8* bitcast"""
escaped: str = self.escape_llvm_string(pool, str_val)
if escaped is None:
return None
slen: t.CSizeT = string.strlen(str_val)
count: int = slen + 1
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
arr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Array(pool, i8_ty, count)
# 使用模块级计数器(trans._label_counter)避免跨函数命名冲突
str_idx: int = trans._label_counter
trans._label_counter += 1
# 字符串名加 SHA1 前缀,和函数导出规则一致,避免跨模块重名
gv_name: str = pool.alloc(48)
if gv_name is None:
return None
if trans.ModuleSha1 is not None:
viperlib.snprintf(gv_name, 48, ".str.%s.%d", trans.ModuleSha1, str_idx)
else:
viperlib.snprintf(gv_name, 48, ".str.%d", str_idx)
gv: llvmlite.GlobalVariable | t.CPtr = llvmlite.new_global_variable(pool, gv_name, arr_ty)
if gv is None:
return None
llvmlite.module_add_global(mod, gv)
# 去掉 private 链接,和函数一致:stub 中 declare,text 中 define
llvmlite.global_set_unnamed_addr(gv, 1)
llvmlite.global_set_constant(gv, 1)
# LLVM 22+ 不允许 external linkage 全局变量带初始值,设为 internal
gv.Linkage = "internal"
gv.Initializer = escaped
arr_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, arr_ty)
gv_ref_name: str = pool.alloc(64)
if gv_ref_name is None:
return None
viperlib.snprintf(gv_ref_name, 64, "@%s", gv.Name)
gv_ref: llvmlite.Value | t.CPtr = llvmlite.SSAValue(pool, arr_ptr_ty, gv_ref_name)
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty)
bc: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(builder, gv_ref, i8_ptr_ty)
return bc
# ============================================================
# 翻译常量表达式
# ============================================================
def translate_constant(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
node: ast.AST | t.CPtr,
trans: HT.Translator | t.CPtr = None,
target_ty: llvmlite.LLVMType | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""翻译常量(int/str/bool)"""
cn: ast.Constant | t.CPtr = (ast.Constant | t.CPtr)(node)
if cn is None:
return None
ck: int = cn.const_kind
if cn.const_kind == ast.CONST_INT:
# 超出 i32 范围则用 i64(避免常量创建时被截断)
iv: t.CInt64T = cn.int_val
if iv > 2147483647 or iv < -2147483648:
return llvmlite.const_int64(pool, iv)
return llvmlite.const_int32(pool, iv)
elif cn.const_kind == ast.CONST_FLOAT:
# 浮点常量默认创建为 double(64 位),赋值时由 coerce_to_type 自动 fptrunc
double_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Double(pool)
return llvmlite.ConstFloat(pool, double_ty, cn.float_val)
elif cn.const_kind == ast.CONST_CHAR:
# 单引号单字符字面量:默认按字符值(i32,coerce_to_type 截断为 i8)。
# 但当目标类型是指针(str/bytes/t.CPtr 等)时,对齐 TPC 引号推断
# (HandlesExpr:"目标是 ptr 但值是 char → 走字符串路径"):物化为
# 1 字符字符串。否则 coerce inttoptr 把字符值当指针(如 '"'=0x22),
# 运行期 strlen 访问低地址 → ASan 崩溃(二代编 StringTest PyI 生成根因)。
# 1a 阶段(declare_only=1)不 LoadDepVpi(vpi 未预生成)→ 依赖函数
# found_func=None → translate_call 缺省参数补全不生效 → translate_value
# 调用参数数 < define(如 target_ty 缺省)→ x64 ABI 被调方读第 9 参
# 栈槽残留垃圾(0x4 等)。declare_only 不产出 IR,target_ty 无用,
# 未对齐(8 对齐失败)即视为垃圾跳过,避免 is_ptr_type 解引用崩溃
# (SlotsBugTest 1a string.vp 崩溃根因;1b found_func 非 None 正常填充)。
if target_ty is not None and (t.CUInt64T(target_ty) & 7) == 0 \
and self.is_ptr_type(target_ty) != 0:
cbuf: bytes = stdlib.malloc(2)
if cbuf is None:
return llvmlite.const_int32(pool, 0)
cbuf[0] = t.CChar(cn.int_val)
cbuf[1] = '\0'
r_cs: llvmlite.Value | t.CPtr = self.create_global_string(builder, pool, mod, cbuf, trans)
stdlib.free(cbuf)
return r_cs
cv: t.CInt64T = cn.int_val
return llvmlite.const_int32(pool, cv)
elif cn.const_kind == ast.CONST_STR:
sv: str = cn.str_val
if sv is None:
return None
r: llvmlite.Value | t.CPtr = self.create_global_string(builder, pool, mod, sv, trans)
return r
elif cn.const_kind == ast.CONST_BOOL:
if cn.int_val != 0:
return llvmlite.const_int32(pool, 1)
return llvmlite.const_int32(pool, 0)
elif cn.const_kind == ast.CONST_NONE:
# None → i8* null(空指针常量),用于 `p is None` / `p is not None` 比较
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
if i8_ty is None:
return None
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty)
if i8_ptr_ty is None:
return None
rv_none: llvmlite.Value | t.CPtr = llvmlite.ConstNull(pool, i8_ptr_ty, "null")
return rv_none
return None
# ============================================================
# _infer_elem_type_from_value — 从 LLVM Value 推断 list 元素类型名
#
# 用于 list 字面量 [a, b, c] 的元素类型推断
# ============================================================
def _infer_elem_type_from_value(self, pool: memhub.MemBuddy | t.CPtr,
value: llvmlite.Value | t.CPtr) -> str:
"""从 LLVM Value 的类型推断 list 元素类型名
fail-fast:值类型缺失/无法映射时返回 None(调用方携带节点报错终止),
绝不静默回退 "str"(掩盖上游 Value 类型缺失根因)。
"""
if value is None or value.Ty is None:
return None
ty: llvmlite.LLVMType | t.CPtr = value.Ty
bits: int = self.get_llvm_type_bits(ty)
if bits != 0:
if bits == 32:
return "int"
if bits == 64:
return "CSizeT"
if bits == 8:
return "CInt8T"
if bits == 16:
return "CInt16T"
return None
if self.is_ptr_type(ty) != 0:
return "str"
fbits: int = self.get_llvm_float_bits(ty)
if fbits != 0:
if fbits == 64:
return "CDouble"
if fbits == 32:
return "CFloat"
return None
# ============================================================
# _find_pool_var — 查找上下文中的 pool 变量
#
# 依次尝试 "pool", "_mbuddy", "mbuddy", "mb"
# ============================================================
def _find_pool_var(self, trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""查找上下文中的 pool 变量,返回 alloca 或 None
查找顺序: pool → _mbuddy → mbuddy → mb
兜底: 若都未找到,注册外部全局 @_mbuddy (i8*) 供函数内动态 list 创建使用。
注意: 模块级 t.CArray 初始化不走此路径(由 _init_global_array_from_list 处理),
此兜底仅用于函数内的动态 list 字面量(如 op=[...])。
@_mbuddy 由入口模块(如 Phase2.py)定义并初始化,其他模块以 external 引用。
"""
if trans is None:
return None
alloca: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_var(trans.SymTab, "pool")
if alloca is not None:
return alloca
alloca = self.Gen.VarGen.lookup_var(trans.SymTab, "_mbuddy")
if alloca is not None:
return alloca
alloca = self.Gen.VarGen.lookup_var(trans.SymTab, "mbuddy")
if alloca is not None:
return alloca
alloca = self.Gen.VarGen.lookup_var(trans.SymTab, "mb")
if alloca is not None:
return alloca
# 兜底: 创建当前模块 mangled 的 @_mbuddy 定义(linkonce_odr + zeroinitializer)
# 对齐模块级全局带 SHA1 前缀命名:裸名 @_mbuddy 依赖链接合并共享
# (--allow-multiple-definition),带前缀后每模块独立。linkonce_odr 允许
# 链接器合并同模板重复特化定义,zeroinitializer 保证编译链接通过。
# 运行时若该模块未在入口 main 赋值 _mbuddy(null pool 上动态建 list),
# 由真实崩溃暴露(fail-fast 哲学,不掩盖上游未初始化)。
pool: memhub.MemBuddy | t.CPtr = trans.Pool
if pool is None or trans.Module is None or trans.SymTab is None:
return None
# 先检查是否已注册到模块作用域(避免重复创建全局变量)
existing: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_module_var(trans.SymTab, "_mbuddy")
if existing is not None:
return existing
# 创建 i8* 类型(t.CVoid | t.CPtr = i8*)
i8_ty_fb: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_ty_fb: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty_fb)
# 当前模块 mangled 名(@<模块sha1>._mbuddy)
mb_name_fb: str = self.Gen.FuncGenBase._mangle_name(trans, "_mbuddy")
# 创建 linkonce_odr 定义(可被链接器合并,自给自足保证链接通过)
gv_ext: llvmlite.GlobalVariable | t.CPtr = llvmlite.new_global_variable(pool, mb_name_fb, i8_ptr_ty_fb)
if gv_ext is not None:
gv_ext.Linkage = "linkonce_odr"
gv_ext.Initializer = "zeroinitializer"
llvmlite.module_add_global(trans.Module, gv_ext)
# 创建 Value 引用(@<模块sha1>._mbuddy, 类型为 i8**)
i8_ptr_ptr_ty_fb: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ptr_ty_fb)
ref_name_fb: str = pool.alloc(96)
if ref_name_fb is not None:
# mangled 名带 SHA1 前缀(数字开头),引用必须加引号(见 make_global_ref)
if self.Gen.ClassGenVTable._vtable_name_needs_quote(mb_name_fb) != 0:
viperlib.snprintf(ref_name_fb, 96, "@\"%s\"", mb_name_fb)
else:
viperlib.snprintf(ref_name_fb, 96, "@%s", mb_name_fb)
gv_ref_fb: llvmlite.Value | t.CPtr = llvmlite.SSAValue(pool, i8_ptr_ptr_ty_fb, ref_name_fb)
# 注册到模块作用域,避免重复创建
self.Gen.VarGen.define_module_var(trans.SymTab, "_mbuddy", gv_ref_fb)
return gv_ref_fb
# ============================================================
# translate_list_literal — 翻译 list 字面量 [a, b, c]
#
# 将 list 字面量翻译为 list[T](pool) 构造 + append 调用序列
#
# Args:
# builder: IRBuilder
# pool: 编译器内存池(MemBuddy)
# mod: LLVMModule
# node: ast.List 节点
# elem_type_name: 元素类型名(如 "str", "int"),None 表示从元素推断
# trans: Translator 对象
#
# Returns:
# list 对象指针(Ptr(list[T] struct)),None 失败
# ============================================================
def translate_list_literal(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
node: ast.AST | t.CPtr,
elem_type_name: str,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""翻译 list 字面量 [a, b, c] → list[T](pool) + append 序列"""
if node is None or trans is None:
return None
list_node: ast.List | t.CPtr = (ast.List | t.CPtr)(node)
if list_node is None:
return None
elts: list[ast.AST | t.CPtr] | t.CPtr = list_node.elts
elts_count: t.CSizeT = 0
if elts is not None:
elts_count = len(elts)
# 推断元素类型
inferred_type_name: str = elem_type_name
first_elem_val: llvmlite.Value | t.CPtr = None
if inferred_type_name is None:
if elts_count > 0:
first_elem_node: ast.AST | t.CPtr = elts.get(0)
first_elem_val = self.Gen.ExprGen.translate_value(builder, pool, mod, first_elem_node, None, 0, trans)
if first_elem_val is None or first_elem_val.Ty is None:
# fail-fast:首元素翻译失败/Value 类型缺失,立即终止编译(暴露上游根因)
self.Gen.TypeGenErr.fatal_error(
first_elem_node, "list 字面量元素类型推断失败(Value 类型缺失)", "TypeError")
inferred_type_name = self._infer_elem_type_from_value(pool, first_elem_val)
if inferred_type_name is None:
# fail-fast:首元素 Value 类型无法映射为 list 元素类型,立即终止编译
self.Gen.TypeGenErr.fatal_error(
first_elem_node, "list 字面量元素类型推断失败(Value 类型缺失)", "TypeError")
else:
# 空 list 无元素可推断,保持默认 str(list[str])
inferred_type_name = "str"
# 查找 pool 变量
pool_alloca: llvmlite.Value | t.CPtr = self._find_pool_var(trans)
if pool_alloca is None:
self.Gen.TypeGenErr.fatal_error(node, "list literal requires pool variable in context")
return None
# load pool 变量值(i8*)
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty)
pool_val: llvmlite.Value | t.CPtr = llvmlite.build_load(builder, i8_ptr_ty, pool_alloca)
if pool_val is None:
return None
# 特化 list[T] 类(elem_size=8:str 是指针,4 会导致双元素重叠损坏)
type_args: list[str] | t.CPtr = list[str](pool, 8)
type_args.append(inferred_type_name)
spec_name: str = self.Gen.ClassGenGeneric._specialize_generic_class(trans, "list", type_args)
if spec_name is None:
self.Gen.TypeGenErr.fatal_error(node, "list literal generic specialization failed")
return None
# 查找 struct entry
struct_entry: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct(spec_name)
if struct_entry is None:
self.Gen.TypeGenErr.fatal_error(node, "list literal specialized class not registered")
return None
struct_ty: llvmlite.LLVMType | t.CPtr = struct_entry.Ty
if struct_ty is None:
return None
# alloca 临时 list 空间
# 确保跨模块结构体的完整定义在当前模块中可用(供 alloca 分配空间)
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, struct_ty)
tmp: llvmlite.Value | t.CPtr = llvmlite.build_alloca(builder, struct_ty)
if tmp is None:
return None
# 存储指针:默认用 alloca,如果有 __new__ 则用 __new__ 返回的指针
# __new__ 是可选的,仅当泛型类模板定义了 __new__ 方法时才调用
# (与 _translate_struct_ctor/_translate_generic_ctor_call 保持一致,
# 避免为无 __new__ 的类生成过度声明)
storage_ptr: llvmlite.Value | t.CPtr = tmp
has_new_flag: int = 0
if struct_entry is not None:
has_new_flag = struct_entry.HasNew
if has_new_flag != 0:
new_args: t.CSizeT | t.CPtr = pool.alloc(8 * 32)
if new_args is None:
return None
string.memset(new_args, 0, 8 * 32)
new_args[0] = t.CSizeT(pool_val)
new_ptr: llvmlite.Value | t.CPtr = self.Gen.CallGenCall._call_method_on_ptr(
pool, builder, mod, spec_name, "__new__", tmp, new_args, 1, trans)
if new_ptr is not None:
storage_ptr = new_ptr
# 调用 __before_init__(storage_ptr)
self.Gen.CallGenCall._call_method_on_ptr(
pool, builder, mod, spec_name, "__before_init__", storage_ptr, None, 0, trans)
# 调用 __init__(storage_ptr, pool_val, elem_size=0)
init_args: t.CSizeT | t.CPtr = pool.alloc(8 * 32)
if init_args is None:
return None
string.memset(init_args, 0, 8 * 32)
init_args[0] = t.CSizeT(pool_val)
elem_size_val: llvmlite.Value | t.CPtr = llvmlite.const_int64(pool, 0)
init_args[1] = t.CSizeT(elem_size_val)
self.Gen.CallGenCall._call_method_on_ptr(
pool, builder, mod, spec_name, "__init__", storage_ptr, init_args, 2, trans)
# 对每个元素调用 append(storage_ptr, elem_val)
if elts_count > 0:
# 第一个元素可能已经翻译过
if first_elem_val is not None:
append_args: t.CSizeT | t.CPtr = pool.alloc(8 * 32)
if append_args is not None:
string.memset(append_args, 0, 8 * 32)
append_args[0] = t.CSizeT(first_elem_val)
self.Gen.CallGenCall._call_method_on_ptr(
pool, builder, mod, spec_name, "append", storage_ptr, append_args, 1, trans)
# 翻译剩余元素
i: t.CSizeT = 1
while i < elts_count:
elem_node_i: ast.AST | t.CPtr = elts.get(i)
elem_val_i: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(builder, pool, mod, elem_node_i, None, 0, trans)
if elem_val_i is not None:
ap_args: t.CSizeT | t.CPtr = pool.alloc(8 * 32)
if ap_args is not None:
string.memset(ap_args, 0, 8 * 32)
ap_args[0] = t.CSizeT(elem_val_i)
self.Gen.CallGenCall._call_method_on_ptr(
pool, builder, mod, spec_name, "append", storage_ptr, ap_args, 1, trans)
i += 1
return storage_ptr
-502
View File
@@ -1,502 +0,0 @@
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import viperlib
import w32.fileio as fileio
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Handles.HandlesBase as HandlesBase
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.Stub.StubMerger as StubMerger
import lib.core.VLogger as VLogger
import lib.Projectrans.Config as Config
import lib.Projectrans.Utils as Utils
import lib.core.Gen.ExprGenBase as ExprGenBase
# ============================================================
# f-string 翻译常量(与 ExprGenMixin 保持一致,本地副本避免跨模块
# CDefine 查找 bug:ExprGenFstr(F) 在 ExprGenMixin(M) 之前按文件序
# 编译时,跨模块 CDefine 全局表未注册 → NameError 未定义)
# ============================================================
FSTR_BUF_SIZE: t.CDefine = 1024
FSTR_SPEC_MAX: t.CDefine = 64
FSTR_FMT_MAX: t.CDefine = 64
# ============================================================
# ExprGenFstr - ExprGenMixin 拆分(阶段 D):f-string/格式化值翻译
#
# 继承 ExprGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesExpr 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class ExprGenFstr(ExprGenBase.ExprGenBase):
"""f-string/格式化值翻译"""
# ============================================================
# translate_joined_str - f-string 翻译(对齐 TPC _HandleJoinedStrLlvm)
#
# f"..."(JoinedStr)→ 拼接字面量与格式说明符 → snprintf 生成 i8*
# 栈缓冲。之前 f-string 未翻译导致 print/赋值拿到 NULL 指针
# ("(null)"输出 + printf("%s", 垃圾) 崩溃 0xC0000005)。
# ============================================================
def translate_joined_str(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
node: ast.AST | t.CPtr,
trans: HT.Translator | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""f-string → snprintf 生成 i8* 栈缓冲"""
if node is None or builder is None or trans is None:
return None
js: ast.JoinedStr | t.CPtr = (ast.JoinedStr | t.CPtr)(node)
if js is None or js.values is None:
return None
# 编译期格式串缓冲(pool 分配)
fmt_buf: str = pool.alloc(FSTR_BUF_SIZE)
if fmt_buf is None:
return None
fmt_buf[0] = '\0'
# 运行时参数链表(Value.Next 链接,与 printf 调用一致)
arg_head: llvmlite.Value | t.CPtr = None
arg_tail: llvmlite.Value | t.CPtr = None
arg_count: int = 0
parts: list[ast.AST | t.CPtr] | t.CPtr = js.values
pn: t.CSizeT = len(parts)
for pi in range(pn):
part: ast.AST | t.CPtr = parts.get(pi)
if part is None:
continue
if part.kind() == ast.ASTKind.Constant:
pcn: ast.Constant | t.CPtr = (ast.Constant | t.CPtr)(part)
if pcn.const_kind == ast.CONST_STR and pcn.str_val is not None:
self._fstr_append_literal(fmt_buf, pcn.str_val)
continue
if part.kind() != ast.ASTKind.FormattedValue:
continue
fv: ast.FormattedValue | t.CPtr = (ast.FormattedValue | t.CPtr)(part)
if fv.value is None:
continue
val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, fv.value, None, 0, trans)
if val is None:
# fail-fast:f-string 格式化值翻译失败必须终止编译,绝不在
self.Gen.TypeGenErr.fatal_error(
fv.value, "f-string 格式化值翻译失败(表达式返回 None)", "TypeError")
continue
# 确定 C 格式说明符(format_spec 优先,否则按值类型推断)
c_fmt: str = None
if fv.format_spec is not None:
c_fmt = self._fstr_parse_format_spec(pool, fv.format_spec, val, fv.value)
if c_fmt is None:
c_fmt = self._fstr_infer_c_fmt(pool, val, fv.value, trans)
if c_fmt is None:
# fail-fast:f-string 格式无法推断(值类型缺失),立即终止编译
self.Gen.TypeGenErr.fatal_error(fv.value, "f-string 格式无法推断(值类型缺失)", "TypeError")
# 追加格式说明符
if string.strlen(fmt_buf) + string.strlen(c_fmt) < FSTR_BUF_SIZE - 1:
string.strcat(fmt_buf, c_fmt)
# 转换参数类型(i8/i16→i32、bool→i32、f32→f64)
cast_val: llvmlite.Value | t.CPtr = self._fstr_cast_value(
builder, pool, val, c_fmt, fv.value, trans)
if cast_val is None:
cast_val = val
cast_val.Next = None
if arg_head is None:
arg_head = cast_val
else:
arg_tail.Next = cast_val
arg_tail = cast_val
arg_count += 1
return self._fstr_finalize(builder, pool, mod, fmt_buf, arg_head, arg_tail, arg_count, trans)
def translate_formatted_value(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
node: ast.AST | t.CPtr,
trans: HT.Translator | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""翻译裸 FormattedValue(f"{expr:spec}" 无字面量时解析器返回 FormattedValue 而非 JoinedStr)"""
if node is None or builder is None or trans is None:
return None
fv: ast.FormattedValue | t.CPtr = (ast.FormattedValue | t.CPtr)(node)
if fv is None or fv.value is None:
return None
fmt_buf: str = pool.alloc(FSTR_BUF_SIZE)
if fmt_buf is None:
return None
fmt_buf[0] = '\0'
arg_head: llvmlite.Value | t.CPtr = None
arg_tail: llvmlite.Value | t.CPtr = None
arg_count: int = 0
val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, fv.value, None, 0, trans)
if val is None:
# fail-fast:裸 FormattedValue 翻译失败必须终止编译,绝不在
self.Gen.TypeGenErr.fatal_error(
fv.value, "f-string 格式化值翻译失败(表达式返回 None)", "TypeError")
else:
c_fmt: str = None
if fv.format_spec is not None:
c_fmt = self._fstr_parse_format_spec(pool, fv.format_spec, val, fv.value)
if c_fmt is None:
c_fmt = self._fstr_infer_c_fmt(pool, val, fv.value, trans)
if c_fmt is None:
# fail-fast:f-string 格式无法推断(值类型缺失),立即终止编译
self.Gen.TypeGenErr.fatal_error(fv.value, "f-string 格式无法推断(值类型缺失)", "TypeError")
if string.strlen(fmt_buf) + string.strlen(c_fmt) < FSTR_BUF_SIZE - 1:
string.strcat(fmt_buf, c_fmt)
cast_val: llvmlite.Value | t.CPtr = self._fstr_cast_value(
builder, pool, val, c_fmt, fv.value, trans)
if cast_val is None:
cast_val = val
cast_val.Next = None
arg_head = cast_val
arg_tail = cast_val
arg_count = 1
return self._fstr_finalize(builder, pool, mod, fmt_buf, arg_head, arg_tail, arg_count, trans)
def _fstr_finalize(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
fmt_buf: str,
arg_head: llvmlite.Value | t.CPtr,
arg_tail: llvmlite.Value | t.CPtr,
arg_count: int,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""f-string 尾部:全局格式串 + 固定栈缓冲 + snprintf,返回 i8*"""
i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
if fmt_buf is None or fmt_buf[0] == '\0':
# 空 f-string(f"")→ 空字符串常量
return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, "", trans)
# 格式串 → 全局字符串常量(i8*)
fmt_ptr: llvmlite.Value | t.CPtr = self.Gen.ExprGenConst.create_global_string(
builder, pool, mod, fmt_buf, trans)
if fmt_ptr is None:
return None
# 固定栈缓冲 [FSTR_BUF_SIZE x i8],GEP(0,0) 取 i8*
buf_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Array(pool, i8_ty, FSTR_BUF_SIZE)
buf_alloca: llvmlite.Value | t.CPtr = llvmlite.build_alloca(builder, buf_ty)
if buf_alloca is None:
return None
zero_i32: llvmlite.Value | t.CPtr = llvmlite.const_int32(pool, 0)
buf_ptr: llvmlite.Value | t.CPtr = llvmlite.build_gep_array(
builder, buf_ty, i8_ty, buf_alloca, zero_i32)
if buf_ptr is None:
return None
# snprintf(buf, FSTR_BUF_SIZE, fmt, args...)
size_val: llvmlite.Value | t.CPtr = llvmlite.const_int64(pool, FSTR_BUF_SIZE)
buf_ptr.Next = size_val
size_val.Next = fmt_ptr
fmt_ptr.Next = arg_head
if arg_tail is not None:
arg_tail.Next = None
llvmlite.build_call(builder, "snprintf", buf_ptr, 3 + arg_count, i32_ty, 1)
return buf_ptr
def _fstr_append_literal(self, fmt_buf: str,
lit: str) -> None:
"""追加字面量到格式串(% 转义为 %%)"""
if fmt_buf is None or lit is None:
return
cur_len: t.CSizeT = string.strlen(fmt_buf)
rem: t.CSizeT = FSTR_BUF_SIZE - 1 - cur_len
if rem == 0:
return
src_len: t.CSizeT = string.strlen(lit)
si: t.CSizeT = 0
while si < src_len and rem > 0:
ch: t.CChar = lit[si]
if ch == '%':
if rem < 2:
break
fmt_buf[cur_len] = '%'
fmt_buf[cur_len + 1] = '%'
cur_len += 2
rem -= 2
else:
fmt_buf[cur_len] = ch
cur_len += 1
rem -= 1
si += 1
fmt_buf[cur_len] = '\0'
def _fstr_append_raw(self, fmt_buf: str, s: str) -> None:
"""追加原始字符串(不做 % 转义)"""
if fmt_buf is None or s is None:
return
cur_len: t.CSizeT = string.strlen(fmt_buf)
src_len: t.CSizeT = string.strlen(s)
if cur_len + src_len < FSTR_BUF_SIZE:
string.strcpy(fmt_buf + cur_len, s)
def _fstr_parse_format_spec(self, pool: memhub.MemBuddy | t.CPtr,
spec_node: ast.AST | t.CPtr,
val: llvmlite.Value | t.CPtr,
arg_node: ast.AST | t.CPtr) -> str:
"""Python format_spec → C printf 格式串(pool 分配),无法解析返回 None"""
if spec_node is None or pool is None or val is None:
return None
# 1. 提取 spec 字符串(JoinedStr 仅常量拼接)
spec: str = pool.alloc(FSTR_SPEC_MAX)
if spec is None:
return None
spec[0] = '\0'
sk: int = spec_node.kind()
if sk == ast.ASTKind.JoinedStr:
jss: ast.JoinedStr | t.CPtr = (ast.JoinedStr | t.CPtr)(spec_node)
if jss.values is None:
return None
vn: t.CSizeT = len(jss.values)
for vi in range(vn):
vpart: ast.AST | t.CPtr = jss.values.get(vi)
if vpart is None:
continue
if vpart.kind() != ast.ASTKind.Constant:
return None
vcn: ast.Constant | t.CPtr = (ast.Constant | t.CPtr)(vpart)
if vcn.const_kind != ast.CONST_STR or vcn.str_val is None:
return None
if string.strlen(spec) + string.strlen(vcn.str_val) < FSTR_SPEC_MAX - 1:
string.strcat(spec, vcn.str_val)
elif sk == ast.ASTKind.Constant:
cn: ast.Constant | t.CPtr = (ast.Constant | t.CPtr)(spec_node)
if cn.const_kind != ast.CONST_STR or cn.str_val is None:
return None
string.strncpy(spec, cn.str_val, FSTR_SPEC_MAX - 1)
else:
return None
slen: t.CSizeT = string.strlen(spec)
if slen == 0:
return None
# 2. 逐段解析: [fill]align / sign / # / 0 / width / , / .precision / type
pos: t.CSizeT = 0
align_char: t.CChar = 0
if pos < slen:
ch0: t.CChar = spec[pos]
if ch0 == '<' or ch0 == '>' or ch0 == '^' or ch0 == '=':
align_char = ch0
pos += 1
elif pos + 1 < slen:
ch1: t.CChar = spec[pos + 1]
if ch1 == '<' or ch1 == '>' or ch1 == '^' or ch1 == '=':
align_char = ch1
pos += 2
c_flags: str = pool.alloc(FSTR_FMT_MAX)
c_width: str = pool.alloc(32)
c_prec: str = pool.alloc(32)
if c_flags is None or c_width is None or c_prec is None:
return None
c_flags[0] = '\0'
c_width[0] = '\0'
c_prec[0] = '\0'
# sign
if pos < slen and (spec[pos] == '+' or spec[pos] == '-' or spec[pos] == ' '):
if spec[pos] == '+':
string.strcat(c_flags, "+")
elif spec[pos] == ' ':
string.strcat(c_flags, " ")
pos += 1
# alt '#'
if pos < slen and spec[pos] == '#':
string.strcat(c_flags, "#")
pos += 1
# zero '0'
if pos < slen and spec[pos] == '0':
string.strcat(c_flags, "0")
pos += 1
# 左对齐 '<'/'^' → '-'(与 0 同时存在时 C 语义 '-' 优先)
if align_char == '<' or align_char == '^':
string.strcat(c_flags, "-")
# width digits
while pos < slen and spec[pos] >= '0' and spec[pos] <= '9':
wl: t.CSizeT = string.strlen(c_width)
if wl < 30:
c_width[wl] = spec[pos]
c_width[wl + 1] = '\0'
pos += 1
# comma ',' → C printf 不支持千分位,忽略
if pos < slen and spec[pos] == ',':
pos += 1
# .precision
if pos < slen and spec[pos] == '.':
string.strcat(c_prec, ".")
pos += 1
while pos < slen and spec[pos] >= '0' and spec[pos] <= '9':
pl: t.CSizeT = string.strlen(c_prec)
if pl < 30:
c_prec[pl] = spec[pos]
c_prec[pl + 1] = '\0'
pos += 1
if string.strlen(c_prec) == 1:
return None # 只有 '.' 无效
# type char
fmt_type: t.CChar = 0
if pos < slen:
fmt_type = spec[pos]
pos += 1
if pos != slen:
return None # 多余字符无法解析
# 3. 长度修饰(按值类型位宽)
len_mod: str = pool.alloc(4)
if len_mod is None:
return None
len_mod[0] = '\0'
ibits: int = self.Gen.ExprGenBase.get_llvm_type_bits(val.Ty)
if ibits == 64:
string.strcpy(len_mod, "ll")
elif ibits == 32:
string.strcpy(len_mod, "l")
elif ibits == 16:
string.strcpy(len_mod, "h")
elif ibits == 8:
string.strcpy(len_mod, "hh")
# 4. 组装 C 格式串
out: str = pool.alloc(FSTR_FMT_MAX)
if out is None:
return None
out[0] = '\0'
string.strcat(out, "%")
string.strcat(out, c_flags)
string.strcat(out, c_width)
string.strcat(out, c_prec)
if fmt_type == 0:
# 无 type:按值类型推断
fbits2: int = self.Gen.ExprGenBase.get_llvm_float_bits(val.Ty)
if ibits != 0:
if ibits == 8:
string.strcat(out, "c")
elif ibits == 1:
string.strcat(out, "d")
else:
string.strcat(out, len_mod)
string.strcat(out, "d")
elif fbits2 != 0:
string.strcat(out, "f")
elif self.Gen.ExprGenBase.is_ptr_type(val.Ty) != 0:
string.strcat(out, "s")
else:
string.strcat(out, "d")
elif fmt_type == 'c':
string.strcat(out, "c")
elif fmt_type == 'f' or fmt_type == 'F':
string.strcat(out, "f")
elif fmt_type == 'e' or fmt_type == 'E' or fmt_type == 'g' or fmt_type == 'G':
tmp_ch: str = pool.alloc(2)
if tmp_ch is None:
return None
tmp_ch[0] = fmt_type
tmp_ch[1] = '\0'
string.strcat(out, tmp_ch)
elif fmt_type == 's':
string.strcat(out, "s")
elif fmt_type == '%':
string.strcat(out, "%")
elif fmt_type == 'b':
return None # 二进制不支持
elif fmt_type == 'd' or fmt_type == 'i':
string.strcat(out, len_mod)
string.strcat(out, "d")
elif fmt_type == 'x' or fmt_type == 'X' or fmt_type == 'o' or fmt_type == 'u':
string.strcat(out, len_mod)
tmp_ch2: str = pool.alloc(2)
if tmp_ch2 is None:
return None
tmp_ch2[0] = fmt_type
tmp_ch2[1] = '\0'
string.strcat(out, tmp_ch2)
else:
return None
return out
def _fstr_infer_c_fmt(self, pool: memhub.MemBuddy | t.CPtr,
val: llvmlite.Value | t.CPtr,
arg_node: ast.AST | t.CPtr,
trans: HT.Translator | t.CPtr) -> str:
"""无 format_spec 时按值类型推断 C printf 格式(对齐 TPC 无 spec 分支,带 % 前缀)"""
if val is None or pool is None:
return None
out: str = pool.alloc(16)
if out is None:
return None
out[0] = '\0'
ibits: int = self.Gen.ExprGenBase.get_llvm_type_bits(val.Ty)
fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(val.Ty)
if ibits == 1:
return "%d" # bool
if ibits == 8:
return "%c" # 字符
if ibits != 0:
if self.Gen.ExprGenOps._node_type_unsigned(pool, arg_node, trans) != 0:
if ibits == 64:
string.strcpy(out, "%llu")
else:
string.strcpy(out, "%u")
else:
if ibits == 64:
string.strcpy(out, "%lld")
else:
string.strcpy(out, "%d")
return out
if fbits != 0:
return "%f"
if self.Gen.ExprGenBase.is_ptr_type(val.Ty) != 0:
return "%s"
# fail-fast:值类型缺失(非整数/浮点/指针),无法推断 C 格式,立即终止编译
self.Gen.TypeGenErr.fatal_error(arg_node, "f-string 格式无法推断(值类型缺失)", "TypeError")
return None
def _fstr_cast_value(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
val: llvmlite.Value | t.CPtr,
c_fmt: str,
arg_node: ast.AST | t.CPtr,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""按 C 格式转换值类型(i8/i16→i32、bool→i32、f32→f64)"""
if val is None or c_fmt is None:
return None
# 找格式类型字符(最后一个字母,忽略 l/h 长度修饰)
fmt_char: t.CChar = 0
flen: t.CSizeT = string.strlen(c_fmt)
fi: t.CSizeT = flen
while fi > 0:
fi -= 1
ch: t.CChar = c_fmt[fi]
if ch == '%':
break
if (ch >= 'a' and ch <= 'z') or (ch >= 'A' and ch <= 'Z'):
if ch != 'l' and ch != 'h':
fmt_char = ch
break
i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
if fmt_char == 's':
return val
if fmt_char == 'c':
ibits: int = self.Gen.ExprGenBase.get_llvm_type_bits(val.Ty)
if ibits != 0 and ibits < 32:
return llvmlite.build_zext(builder, val, i32_ty)
return val
if fmt_char == 'd' or fmt_char == 'i' or fmt_char == 'x' \
or fmt_char == 'X' or fmt_char == 'o' or fmt_char == 'u':
ibits = self.Gen.ExprGenBase.get_llvm_type_bits(val.Ty)
if ibits == 1:
return llvmlite.build_zext(builder, val, i32_ty)
if ibits == 8 or ibits == 16:
if self.Gen.ExprGenOps._node_type_unsigned(pool, arg_node, trans) != 0:
return llvmlite.build_zext(builder, val, i32_ty)
return llvmlite.build_sext(builder, val, i32_ty)
return val
if fmt_char == 'f' or fmt_char == 'e' or fmt_char == 'E' \
or fmt_char == 'g' or fmt_char == 'G':
fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(val.Ty)
if fbits == 32:
return llvmlite.build_fpext(builder, val, llvmlite.Double(pool))
return val
return val
-163
View File
@@ -1,163 +0,0 @@
import t, c
from stdint import *
import ast
import memhub
import string
import llvmlite
import stdlib
import stdio
import viperlib
import w32.fileio as fileio
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Handles.HandlesBase as HandlesBase
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.Stub.StubMerger as StubMerger
import lib.core.VLogger as VLogger
import lib.Projectrans.Config as Config
import lib.Projectrans.Utils as Utils
import lib.core.Gen.ExprGenBase as ExprGenBase
# ============================================================
# docstring 登记表容量(__doc__ 魔法属性支持)
#
# DOC_MAX: 登记条目上限(预注册阶段所有 App/includes 文件的
# 函数/类/方法 docstring,幂等查重)
# DOC_KEY_SZ: key "{module_sha1}.{symbol}" 缓冲宽度
# DOC_VAL_SZ: docstring 内容缓冲宽度
# ============================================================
DOC_MAX: t.CDefine = 512
DOC_KEY_SZ: t.CDefine = 192
DOC_VAL_SZ: t.CDefine = 4096
# ============================================================
# f-string 翻译常量(对齐 TPC HandlesExprFormat)
#
# FSTR_BUF_SIZE: 运行时栈缓冲大小(snprintf 结果缓冲)
# FSTR_SPEC_MAX: 编译期 format_spec 解析缓冲宽度
# FSTR_FMT_MAX: 编译期 C printf 格式串缓冲宽度
# ============================================================
FSTR_BUF_SIZE: t.CDefine = 1024
FSTR_SPEC_MAX: t.CDefine = 64
FSTR_FMT_MAX: t.CDefine = 64
# ============================================================
# ExprGenMixin - 表达式处理 Mixin(对象化 P3,wiki/14-oo-refactor.md)
#
# 从 HandlesExpr.vp(48 函数,无全局状态)与 HandlesExprOps.vp
# 迁移:模块级纯函数 → 类方法(self 替代,参数保留含 builder/pool)。
#
# 迁移原则(与 P2 一致,行为零变化):
# 1. 方法保留原函数签名(含 builder/pool 首参),仅函数体内
# 本模块函数调用改为 self.xxx(...)
# 2. 外部模块调用(HandlesExprCall.xxx / HandlesType.xxx 等)保留模块前缀
# 3. HandlesExpr.vp / HandlesExprOps.vp 原函数保留为"委托":本地
# 模块级单例 _g_gen(BuildPipeline/main 注入)cast 成 Generator 类型
# 4. ExprHandle 类保留在 HandlesExpr.vp(translator handler 模式)
#
# @t.NoVTable:静态绑定(方法沿 MRO 直接调用 + this 调整)。
# ============================================================
# ============================================================
# ExprGenMixin - 表达式/常量/下标/属性访问/运算符
# ============================================================
@t.NoVTable
class ExprGenMixin(ExprGenBase.ExprGenBase):
"""表达式处理 Mixin(纯方法化迁移,无状态字段)"""
# ============================================================
# 翻译值表达式(RHS 分派)— 模块级版本
# ============================================================
def translate_value(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
node: ast.AST | t.CPtr,
funcs_ptr: t.CPtr = None,
func_count: int = 0,
trans: HT.Translator | t.CPtr = None,
target_ty: llvmlite.LLVMType | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""翻译值表达式为 LLVM Value(深度守卫包装器,原逻辑见 _translate_value_impl)
target_ty: 目标类型上下文(如调用形参类型/赋值目标类型),用于单引号
单字符字面量(CONST_CHAR)在字符串目标下物化为 1 字符字符串(对齐 TPC)。
"""
if trans is not None:
if trans.TranslateDepth >= HT.TRANSLATE_MAX_DEPTH:
self.Gen.TypeGenErr.fatal_error(
node, "表达式翻译嵌套过深(疑似 AST 自引用环或翻译 bug)", "RecursionError")
trans.TranslateDepth += 1
rv: llvmlite.Value | t.CPtr = self._translate_value_impl(
builder, pool, mod, node, funcs_ptr, func_count, trans, target_ty)
if trans is not None:
trans.TranslateDepth -= 1
return rv
def _translate_value_impl(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
node: ast.AST | t.CPtr,
funcs_ptr: t.CPtr = None,
func_count: int = 0,
trans: HT.Translator | t.CPtr = None,
target_ty: llvmlite.LLVMType | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""翻译值表达式为 LLVM Value"""
if node is None:
return None
k: int = node.kind()
if k == ast.ASTKind.Constant:
rv: llvmlite.Value | t.CPtr = self.Gen.ExprGenConst.translate_constant(builder, pool, mod, node, trans, target_ty)
return rv
elif k == ast.ASTKind.Name:
return self.Gen.ExprGenName.translate_name_value(builder, pool, node, trans)
elif k == ast.ASTKind.BinOp:
return self.Gen.ExprGenOps.translate_binop(pool, builder, mod, node, trans)
elif k == ast.ASTKind.Call:
return self.Gen.CallGen.translate_call(pool, builder, mod, node,
funcs_ptr, func_count, trans)
elif k == ast.ASTKind.Compare:
return self.Gen.ExprGenOps.translate_compare(builder, pool, mod, node, trans)
elif k == ast.ASTKind.UnaryOp:
return self.Gen.ExprGenOps.translate_unaryop(builder, pool, mod, node, trans)
elif k == ast.ASTKind.BoolOp:
return self.Gen.ExprGenOps.translate_boolop(builder, pool, mod, node,
funcs_ptr, func_count, trans)
elif k == ast.ASTKind.Subscript:
return self.Gen.ExprGenName.translate_subscript(builder, pool, mod, node, trans)
elif k == ast.ASTKind.Attribute:
return self.Gen.ExprGenAttr.translate_attribute(builder, pool, mod, node, trans)
elif k == ast.ASTKind.List:
return self.Gen.ExprGenConst.translate_list_literal(builder, pool, mod, node, None, trans)
elif k == ast.ASTKind.IfExp:
return self.Gen.ExprGenOps.translate_ifexp(builder, pool, mod, node, trans)
elif k == ast.ASTKind.Lambda:
# 模块级委托:translate_lambda 定义在 FuncGenMixin(跨 Mixin
# 方法不能 self. 直接调用,见 HandlesExpr.translate_lambda)
return self.Gen.FuncGen.translate_lambda(builder, pool, mod, node, trans)
elif k == ast.ASTKind.JoinedStr:
return self.Gen.ExprGenFstr.translate_joined_str(builder, pool, mod, node, trans)
elif k == ast.ASTKind.FormattedValue:
# 解析器对 f"{expr:spec}"(单 FormattedValue 无字面量)返回裸 FormattedValue
# 而非 JoinedStr(见 ast/parser.vp _parse_fstring),需单独翻译
return self.Gen.ExprGenFstr.translate_formatted_value(builder, pool, mod, node, trans)
elif k == ast.ASTKind.Await:
# 协程 await(TPV-M1):await 目标是 async 函数调用时内联翻译
# (子帧创建 + resume 到 DONE + 取子帧 ret 存父帧 await 槽 + 父挂起),
# 返回子协程 ret 值(i32)。顶层 await(AnnAssign/Assign 的 value 直接
# 是 Await)由 AsyncGen 段翻译特判处理,本分支覆盖表达式上下文中的
# await(M1 覆盖有限场景,详见 AsyncGenMixin._emit_await)。
aw: ast.Await | t.CPtr = (ast.Await | t.CPtr)(node)
if aw is not None and aw.value is not None and aw.value.kind() == ast.ASTKind.Call:
aw_cl: ast.Call | t.CPtr = (ast.Call | t.CPtr)(aw.value)
if aw_cl.func is not None and aw_cl.func.kind() == ast.ASTKind.Name:
aw_nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(aw_cl.func)
if aw_nm.id is not None and trans is not None:
if self.Gen.AsyncGen.is_async_func(trans, aw_nm.id) != 0:
return self.Gen.AsyncGen._emit_await(
builder, pool, mod, trans, aw, aw_nm.id)
return None
return None
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+282
View File
@@ -0,0 +1,282 @@
from stdint import *
import ast
import mlirlite
import memhub
import string
import viperlib
import stdlib
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.Call.CallGenBase as CallGenBase
# FuncGenBase - split from FuncGenMixin (stage D): attrs / naming / mangle / create_function helpers.
novirtual class FuncGenBase(CallGenBase.CallGenBase):
"""Shared helpers for function attributes, naming, mangling and creation."""
# extract_func_attrs - extract c.Attribute attrs from decorator_list.
# Supports @c.Attribute(t.attr.xxx()/t.attr.xxx/t.attr.llvm.xxx); returns LLVM IR attr string or None.
def extract_func_attrs(self, pool: memhub.MemBuddy*, decorator_list: list[ast.AST*]*, imported_modules: str) -> str:
"""Extract c.Attribute attrs from decorators; return the LLVM IR attr string."""
if decorator_list is None: return None
dn: sizet = len(decorator_list)
if dn == 0: return None
attrs_buf: str = pool.alloc(256)
if attrs_buf is None: return None
attrs_buf[0] = '\0'
found_any: int = 0
for di in range(int(dn)):
deco: ast.AST* = decorator_list.get(di)
if deco is None: continue
dk: int = deco.kind()
# Direct form `@t.attr.<name>` (same as ClassGenBase._has_decorator): `inline def f`
# synthesizes it and does NOT require `import c`; unsupported names are ignored.
if dk == ast.ASTKind.Attribute:
dan: str = self._get_attr_name_from_node(deco)
if dan is not None and string.strcmp(dan, "section") != 0:
if self._append_llvm_attr(attrs_buf, dan) != 0: found_any = 1
continue
if dk != ast.ASTKind.Call: continue
call_node: ast.Call* = ast.Call*(deco)
if call_node.func is None: continue
cfk: int = call_node.func.kind()
# Detect the builtin Attribute(...) wrapper emitted by @__attribute__(...).
if cfk != ast.ASTKind.Name: continue
func_nm: ast.Name* = ast.Name*(call_node.func)
if func_nm.id is None: continue
if string.strcmp(func_nm.id, "Attribute") != 0: continue
# Iterate args and extract attrs
if call_node.args is None: continue
args_list: list[ast.AST*]* = call_node.args
an: sizet = len(args_list)
for ai in range(int(an)):
arg_node: ast.AST* = args_list.get(ai)
if arg_node is None: continue
# Extract attr name (t.attr.xxx / t.attr.xxx() / t.attr.llvm.xxx)
attr_name: str = self._get_attr_name_from_node(arg_node)
if attr_name is None: continue
# section string attr cannot be inlined on the signature line (llc: expected
# string constant); it must go through attr group #N, handled by FuncGenDef.
if string.strcmp(attr_name, "section") == 0: continue
# Map to LLVM attr name and append
ar: int = self._append_llvm_attr(attrs_buf, attr_name)
if ar != 0: found_any = 1
if found_any == 0: return None
return attrs_buf
def extract_func_section(self, pool: memhub.MemBuddy*, decorator_list: list[ast.AST*]*, imported_modules: str) -> str:
"""Extract @Attribute(attr.section(...)) as the attr-group text."""
if pool is None or decorator_list is None: return None
dn: sizet = len(decorator_list)
if dn == 0: return None
for di in range(int(dn)):
deco: ast.AST* = decorator_list.get(di)
if deco is None or deco.kind() != ast.ASTKind.Call: continue
call_node: ast.Call* = ast.Call*(deco)
if call_node.func is None or call_node.func.kind() != ast.ASTKind.Name: continue
func_nm: ast.Name* = ast.Name*(call_node.func)
if func_nm.id is None or string.strcmp(func_nm.id, "Attribute") != 0: continue
if call_node.args is None: continue
args_list: list[ast.AST*]* = call_node.args
an: sizet = len(args_list)
for ai in range(int(an)):
arg_node: ast.AST* = args_list.get(ai)
if arg_node is None: continue
attr_name: str = self._get_attr_name_from_node(arg_node)
if attr_name is None or string.strcmp(attr_name, "section") != 0: continue
sec_val: str = self._get_attr_call_arg_str(arg_node)
if sec_val is None: continue
sec_buf: str = pool.alloc(128)
if sec_buf is None: return None
viperlib.snprintf(sec_buf, 128, "\"section\"=\"%s\"", sec_val)
return sec_buf
return None
def _get_attr_name_from_node(self, node: ast.AST*) -> str:
"""Extract the attribute name from a Call or Attribute AST node."""
if node is None: return None
k: int = node.kind()
# Call node: t.attr.always_inline()
if k == ast.ASTKind.Call:
call: ast.Call* = ast.Call*(node)
if call.func is None or call.func.kind() != ast.ASTKind.Attribute: return None
at: ast.Attribute* = ast.Attribute*(call.func)
return at.attr
# Attribute node: t.attr.packed or t.attr.llvm.nounwind
if k == ast.ASTKind.Attribute:
at2: ast.Attribute* = ast.Attribute*(node)
return at2.attr
return None
def _get_attr_call_arg_str(self, node: ast.AST*) -> str:
"""Return the first positional string constant of a t.attr.xxx(...) call."""
if node is None or node.kind() != ast.ASTKind.Call: return None
cl: ast.Call* = ast.Call*(node)
if cl.args is None or len(cl.args) == 0: return None
a0: ast.AST* = cl.args.get(0)
if a0 is None or a0.kind() != ast.ASTKind.Constant: return None
cn: ast.Constant* = ast.Constant*(a0)
if cn.const_kind != ast.CONST_STR: return None
return cn.str_val
def _append_llvm_attr(self, attrs_buf: str, attr_name: str) -> int:
"""Map an attribute name to its LLVM name and append it; 1=appended, 0=unsupported."""
if attrs_buf is None or attr_name is None: return 0
# First determine the mapped LLVM attr name
llvm_name: str = None
if string.strcmp(attr_name, "always_inline") == 0: llvm_name = "alwaysinline"
elif string.strcmp(attr_name, "noinline") == 0: llvm_name = "noinline"
elif string.strcmp(attr_name, "noreturn") == 0: llvm_name = "noreturn"
elif string.strcmp(attr_name, "pure") == 0: llvm_name = "readonly"
elif string.strcmp(attr_name, "const") == 0: llvm_name = "readnone"
elif string.strcmp(attr_name, "nounwind") == 0: llvm_name = "nounwind"
elif string.strcmp(attr_name, "noredzone") == 0: llvm_name = "noredzone"
elif string.strcmp(attr_name, "stackrealign") == 0: llvm_name = '"stackrealign"'
elif string.strcmp(attr_name, "willreturn") == 0: llvm_name = "willreturn"
elif string.strcmp(attr_name, "mustprogress") == 0: llvm_name = "mustprogress"
else:
# packed/aligned/section/visibility/weak are unsupported as inline function attrs
return 0
# Supported attr: append a space separator if the buffer is non-empty
if attrs_buf[0] != '\0': self._append_str(attrs_buf, " ", 256)
self._append_str(attrs_buf, llvm_name, 256)
return 1
def _append_str(self, buf: str, s: str, max_len: sizet = 256) -> None:
"""Append a string to buf with a bounded write to avoid overflow."""
if buf is None or s is None: return
# Defensive: VPC default-arg registration is buggy (missing max_len falls to 0);
# fall back to 256 so the bounded-write check does not drop all attr text.
if max_len == 0: max_len = 256
cur_len: sizet = string.strlen(buf)
s_len: sizet = string.strlen(s)
if cur_len + s_len + 1 > max_len: return # No remaining capacity: drop this append to avoid a bounds write (uncertain)
string.strcpy(buf + cur_len, s)
# _mangle_name - prepend a SHA1 prefix to a name.
# Returns "sha1.name"; if ModuleSha1 is None or the name already has a SHA1 prefix, returns as-is.
def _mangle_name(self, trans: HT.Translator*, name: str) -> str:
"""Prepend the SHA1 prefix to a name (no main/export check)."""
if trans is None or name is None: return name
if trans.ModuleSha1 is None: return name
name_len: sizet = string.strlen(name)
# Skip if it already has a SHA1 prefix ("{16hex}."). [C10] Use the single source of
# truth GenApi.sha1_prefix_len (aligned with the ">= 17" check).
if GenApi.sha1_prefix_len(name) != 0: return name
sha1: str = trans.ModuleSha1
sha1_len: sizet = string.strlen(sha1)
mangled: str = stdlib.malloc(sha1_len + name_len + 2)
if mangled is None: return name
string.strcpy(mangled, sha1)
mangled[sha1_len] = '.'
string.strcpy(mangled + sha1_len + 1, name)
return mangled
# _mangle_name_with_sha1 - prepend the given SHA1 to a name.
# Used for cross-module vtable inheritance: inherited methods mangle with the parent module's SHA1.
def _mangle_name_with_sha1(self, sha1: str, name: str) -> str:
"""Prepend the given SHA1 to a name (no main/export check)."""
if sha1 is None or name is None: return name
name_len: sizet = string.strlen(name)
# Skip if it already has a SHA1 prefix ("{16hex}."). [C10] Use the single source of
# truth GenApi.sha1_prefix_len (same check as _mangle_name).
if GenApi.sha1_prefix_len(name) != 0: return name
sha1_len: sizet = string.strlen(sha1)
mangled: str = stdlib.malloc(sha1_len + name_len + 2)
if mangled is None: return name
string.strcpy(mangled, sha1)
mangled[sha1_len] = '.'
string.strcpy(mangled + sha1_len + 1, name)
return mangled
# _mangle_func_name - prepend a SHA1 prefix to a function name (with main/export checks).
# No prefix for main, when has_export != 0 (t.CExport), when already prefixed, or when ModuleSha1 is None.
def _mangle_func_name(self, trans: HT.Translator*, name: str, has_export: int) -> str:
"""Prepend the SHA1 prefix to a function name (checks main/export)."""
if trans is None or name is None: return name
if has_export != 0: return name
if string.strcmp(name, "main") == 0: return name
return self._mangle_name(trans, name)
# _body_has_raise - whether the body contains raise (non-StopIteration).
# Matches TPC HandlesFunctions.py _body_contains_raise; v1 only scans top-level raise, not nested ones.
def _body_has_raise(self, fd: ast.FunctionDef*) -> int:
"""Whether the body has a top-level non-StopIteration raise; 1=yes, 0=no."""
if fd is None or fd.children is None: return 0
body_f: list[ast.AST*]* = fd.children
bn_f: sizet = len(body_f)
for bi_f in range(int(bn_f)):
stmt_f: ast.AST* = body_f.get(bi_f)
if stmt_f is None: continue
if stmt_f.kind() != ast.ASTKind.Raise: continue
rn_f: ast.Raise* = ast.Raise*(stmt_f)
if rn_f is None or rn_f.exc is None: continue
ek_f: int = rn_f.exc.kind()
is_stop_f: int = 0
if ek_f == ast.ASTKind.Name:
enm_f: ast.Name* = ast.Name*(rn_f.exc)
if enm_f.id is not None and string.strcmp(enm_f.id, "StopIteration") == 0: is_stop_f = 1
elif ek_f == ast.ASTKind.Call:
ecl_f: ast.Call* = ast.Call*(rn_f.exc)
if ecl_f.func is not None and ecl_f.func.kind() == ast.ASTKind.Name:
ecn_f: ast.Name* = ast.Name*(ecl_f.func)
if ecn_f.id is not None and string.strcmp(ecn_f.id, "StopIteration") == 0: is_stop_f = 1
if is_stop_f == 0: return 1
return 0
# RejectCReturnDecorator - @c.CReturn was removed; fail at compile time if seen.
# Multi-return goes only through `-> tuple[...]`; any CReturn form is fail-fast, shared with FuncGenDef.
def RejectCReturnDecorator(self, node: ast.AST*, decorators: list[ast.AST*]*) -> int:
"""Fail if @c.CReturn appears in the decorators; 0=none found."""
if decorators is None: return 0
rcr_dn: sizet = len(decorators)
for rcr_i in range(int(rcr_dn)):
rcr_deco: ast.AST* = decorators.get(rcr_i)
if rcr_deco is None: continue
# Handle both @c.CReturn(...) (Call(func=Attribute)) and bare @c.CReturn (Attribute)
rcr_attr: ast.Attribute* = None
if rcr_deco.kind() == ast.ASTKind.Call:
rcr_call: ast.Call* = ast.Call*(rcr_deco)
if rcr_call.func is not None and rcr_call.func.kind() == ast.ASTKind.Attribute: rcr_attr = ast.Attribute*(rcr_call.func)
elif rcr_deco.kind() == ast.ASTKind.Attribute: rcr_attr = ast.Attribute*(rcr_deco)
if rcr_attr is None or rcr_attr.attr is None: continue
if string.strcmp(rcr_attr.attr, "CReturn") == 0:
self.Gen.TypeGenErr.fatal_error(node, "`@c.CReturn` removed: use a return annotation `-> tuple[ty1, ty2, ...]` instead (multiple return values are packed into a struct return)", "TypeError")
return 1
return 0
# Create LLVM function (simple version, declaration only)
def create_function(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, name: str, args_node: ast.AST*, ret_ty: mlirlite.LLVMType*) -> mlirlite.Function*:
"""Create an LLVM function and add its parameters."""
if name is None or mod is None: return None
func: mlirlite.Function* = mlirlite.create_function(pool, mod, name, ret_ty)
if func is None: return None
if args_node is not None:
ags: ast.Arguments* = ast.Arguments*(args_node)
if ags.args is not None:
alist: list[ast.AST*]* = ags.args
an: sizet = len(alist)
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
for ai in range(int(an)):
arg: ast.Arg* = ast.Arg*(alist.get(ai))
if arg is not None and arg.arg is not None:
# CVoid / void means no args: skip
if arg.annotation is not None:
if arg.annotation.kind() == ast.ASTKind.Name:
at_cn: ast.Name* = ast.Name*(arg.annotation)
if at_cn.id is not None and (string.strcmp(at_cn.id, "CVoid") == 0 or string.strcmp(at_cn.id, "void") == 0): continue
param_ty: mlirlite.LLVMType* = i32_ty
if arg.annotation is not None:
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, None, None, None, 0)
if resolved is None:
# [S4 fail-fast] Annotation present but unresolvable: no silent i32 fallback.
# This fn lacks trans/imports context, so failure surfaces upstream.
self.Gen.TypeGenErr.fatal_error(arg, f"function '{name}' param '{arg.arg}' type annotation failed to resolve", "NameError")
return None
param_ty = resolved
pname: str = pool.alloc(32)
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
mlirlite.add_param(pool, func, param_ty, pname)
return func
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-445
View File
@@ -1,445 +0,0 @@
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import viperlib
import stdio
import stdlib
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.Gen.CallGenBase as CallGenBase
# ============================================================
# FuncGenBase - FuncGenMixin 拆分(阶段 D):函数属性 / 命名 / mangle / create_function 基础工具
#
# 继承 CallGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesExprCall 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class FuncGenBase(CallGenBase.CallGenBase):
"""函数属性 / 命名 / mangle / create_function 基础工具"""
# ============================================================
# extract_func_attrs - 从 decorator_list 提取 c.Attribute 属性
#
# 支持 @c.Attribute(t.attr.xxx()) 和 @c.Attribute(t.attr.xxx) 形式
# 也支持 @c.Attribute(t.attr.llvm.xxx) 形式
#
# 返回 LLVM IR 属性字符串(如 "alwaysinline nounwind"),无属性返回 None
# ============================================================
def extract_func_attrs(self, pool: memhub.MemBuddy | t.CPtr,
decorator_list: list[ast.AST | t.CPtr] | t.CPtr,
imported_modules: str) -> str:
"""从 decorator_list 提取 c.Attribute 属性,返回 LLVM IR 属性字符串"""
if decorator_list is None:
return None
dn: t.CSizeT = len(decorator_list)
if dn == 0:
return None
attrs_buf: str = pool.alloc(256)
if attrs_buf is None:
return None
attrs_buf[0] = '\0'
found_any: int = 0
# [ATTR-DIAG] 运行时诊断:定位 @c.Attribute 属性为何未进入 IR(stackrealign)
cur_file: str = self.Gen.TypeGenResolve.get_current_file()
if cur_file is None:
cur_file = "?"
stdio.printf("[ATTR] extract_func_attrs: file='%s' dlist_len=%d\n", cur_file, dn)
for di in range(dn):
deco: ast.AST | t.CPtr = decorator_list.get(di)
if deco is None:
stdio.printf("[ATTR] deco[%d]=None\n", di)
continue
dk: int = deco.kind()
stdio.printf("[ATTR] deco[%d] kind=%d\n", di, dk)
if dk != ast.ASTKind.Call:
continue
call_node: ast.Call | t.CPtr = (ast.Call | t.CPtr)(deco)
if call_node.func is None:
stdio.printf("[ATTR] call.func=None\n")
continue
cfk: int = call_node.func.kind()
stdio.printf("[ATTR] call.func kind=%d\n", cfk)
if cfk != ast.ASTKind.Attribute:
continue
# 检测 c.Attribute
func_attr: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(call_node.func)
if func_attr.attr is None:
stdio.printf("[ATTR] func_attr.attr=None\n")
continue
stdio.printf("[ATTR] func_attr.attr='%s'\n", func_attr.attr)
if string.strcmp(func_attr.attr, "Attribute") != 0:
continue
if func_attr.value is None:
stdio.printf("[ATTR] func_attr.value=None\n")
continue
fvk: int = func_attr.value.kind()
stdio.printf("[ATTR] func_attr.value kind=%d\n", fvk)
if fvk != ast.ASTKind.Name:
continue
c_name: ast.Name | t.CPtr = (ast.Name | t.CPtr)(func_attr.value)
if c_name.id is None:
stdio.printf("[ATTR] c_name.id=None\n")
continue
stdio.printf("[ATTR] c_name.id='%s'\n", c_name.id)
if string.strcmp(c_name.id, "c") != 0:
continue
imp_c: int = self.Gen.ImportsGen.is_module_imported(imported_modules, "c")
stdio.printf("[ATTR] is_module_imported('c')=%d\n", imp_c)
if imp_c == 0:
continue
# 遍历参数提取属性
if call_node.args is None:
stdio.printf("[ATTR] call.args=None\n")
continue
dbuf: bytes = pool.alloc(1024)
if dbuf is not None:
dbuf[0] = '\0'
dpos: t.CSizeT = deco.dump(dbuf, 1024, 0)
if dpos < 1024:
dbuf[dpos] = '\0'
stdio.printf("[ATTR] deco.dump='%s'\n", dbuf)
args_list: list[ast.AST | t.CPtr] | t.CPtr = call_node.args
an: t.CSizeT = len(args_list)
stdio.printf("[ATTR] args_len=%d\n", an)
if call_node.keywords is not None:
kwl: t.CSizeT = len(call_node.keywords)
stdio.printf("[ATTR] keywords_len=%d\n", kwl)
for kwi in range(kwl):
kwnd: ast.AST | t.CPtr = call_node.keywords.get(kwi)
if kwnd is not None:
stdio.printf("[ATTR] kw[%d] kind=%d\n", kwi, kwnd.kind())
for ai in range(an):
arg_node: ast.AST | t.CPtr = args_list.get(ai)
if arg_node is None:
stdio.printf("[ATTR] arg[%d]=None\n", ai)
continue
stdio.printf("[ATTR] arg[%d] kind=%d\n", ai, arg_node.kind())
# 提取属性名(t.attr.xxx / t.attr.xxx() / t.attr.llvm.xxx)
attr_name: str = self._get_attr_name_from_node(arg_node)
if attr_name is None:
stdio.printf("[ATTR] arg[%d] name=None\n", ai)
continue
# 映射到 LLVM 属性名并追加
ar: int = self._append_llvm_attr(attrs_buf, attr_name)
stdio.printf("[ATTR] arg[%d] name='%s' append=%d buf='%s' blen=%zu b0=0x%02x\n",
ai, attr_name, ar, attrs_buf, string.strlen(attrs_buf), attrs_buf[0])
if ar != 0:
found_any = 1
# 追加成功后,如果不是最后一个属性,添加空格分隔
# 在下一次追加前由 _append_str 处理
stdio.printf("[ATTR] found_any=%d return='%s'\n", found_any, attrs_buf)
if found_any == 0:
return None
return attrs_buf
def _get_attr_name_from_node(self, node: ast.AST | t.CPtr) -> str:
"""从 AST 节点提取属性名
支持:
- Call(func=Attribute(...)): t.attr.always_inline() -> "always_inline"
- Attribute: t.attr.packed -> "packed"
- Attribute(t.attr.llvm.xxx): t.attr.llvm.nounwind -> "nounwind"
"""
if node is None:
return None
k: int = node.kind()
# Call 节点: t.attr.always_inline()
if k == ast.ASTKind.Call:
call: ast.Call | t.CPtr = (ast.Call | t.CPtr)(node)
if call.func is None or call.func.kind() != ast.ASTKind.Attribute:
return None
at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(call.func)
return at.attr
# Attribute 节点: t.attr.packed 或 t.attr.llvm.nounwind
if k == ast.ASTKind.Attribute:
at2: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(node)
return at2.attr
return None
def _append_llvm_attr(self, attrs_buf: str, attr_name: str) -> int:
"""将属性名映射到 LLVM 属性名并追加到缓冲区
返回 1 表示已追加,0 表示不支持该属性
"""
if attrs_buf is None or attr_name is None:
return 0
# 先确定映射的 LLVM 属性名
llvm_name: str = None
if string.strcmp(attr_name, "always_inline") == 0:
llvm_name = "alwaysinline"
elif string.strcmp(attr_name, "noinline") == 0:
llvm_name = "noinline"
elif string.strcmp(attr_name, "noreturn") == 0:
llvm_name = "noreturn"
elif string.strcmp(attr_name, "pure") == 0:
llvm_name = "readonly"
elif string.strcmp(attr_name, "const") == 0:
llvm_name = "readnone"
elif string.strcmp(attr_name, "nounwind") == 0:
llvm_name = "nounwind"
elif string.strcmp(attr_name, "noredzone") == 0:
llvm_name = "noredzone"
elif string.strcmp(attr_name, "stackrealign") == 0:
# 动态重对齐栈(LLVM 序言 and rsp,-16)。入口函数(_start)由 bootloader
# jmp 进入(rsp≡0 mod 16),而 llc 按 call 约定假设入口 rsp≡8 → 帧整体
# 偏 8 → 所有调用 rsp≡8 → 变参函数保存 xmm 的 movaps #GP。stackrealign
# 使序言动态对齐,与入口约定无关(sched started 后 movaps #GP 根因)。
# 注意:stackrealign 是 LLVM 字符串属性,必须带引号("stackrealign"),
# 与 noinline/nounwind 等枚举属性(无引号)区分,否则 llc 报
# "expected '{' in function body"。
llvm_name = '"stackrealign"'
elif string.strcmp(attr_name, "willreturn") == 0:
llvm_name = "willreturn"
elif string.strcmp(attr_name, "mustprogress") == 0:
llvm_name = "mustprogress"
else:
# packed/aligned/section/visibility/weak 等不支持作为函数内联属性
return 0
# 支持的属性:如果缓冲区非空,先追加空格分隔
if attrs_buf[0] != '\0':
self._append_str(attrs_buf, " ", 256)
self._append_str(attrs_buf, llvm_name, 256)
return 1
def _append_str(self, buf: str, s: str,
max_len: t.CSizeT = 256) -> None:
"""将字符串追加到 buf 末尾(有界,防 strcpy 越界写穿定长缓冲)"""
if buf is None or s is None:
return
# 防御:TPV 方法默认参数注册存在缺陷(缺参调用 max_len 落到 0),
# 显式兜底为 256,避免有界写入检查误判丢弃全部属性文本。
if max_len == 0:
max_len = 256
cur_len: t.CSizeT = string.strlen(buf)
s_len: t.CSizeT = string.strlen(s)
stdio.printf("[ATTR] _append_str: max_len=%zu cur=%zu slen=%zu s='%s'\n", max_len, cur_len, s_len, s)
if cur_len + s_len + 1 > max_len:
return # 无剩余容量:丢弃本次追加,避免越界写
string.strcpy(buf + cur_len, s)
# ============================================================
# _mangle_name - 为名称添加 SHA1 前缀
#
# 返回 "sha1.name" 格式的混淆名。若 ModuleSha1 为 None 或名称已带
# SHA1 前缀(16 hex + '.'),则原样返回。
# ============================================================
def _mangle_name(self, trans: HT.Translator | t.CPtr, name: str) -> str:
"""为任意名称添加 SHA1 前缀(不做 main/export 检查)"""
if trans is None or name is None:
return name
if trans.ModuleSha1 is None:
return name
name_len: t.CSizeT = string.strlen(name)
# 已带 SHA1 前缀(16 hex + '.')则跳过
if name_len > 17:
is_sha1: int = 1
for i in range(16):
c: t.CChar = name[i]
if not (('0' <= c <= '9') or ('a' <= c <= 'f')):
is_sha1 = 0
break
if is_sha1 != 0 and name[16] == '.':
return name
sha1: str = trans.ModuleSha1
sha1_len: t.CSizeT = string.strlen(sha1)
mangled: str = stdlib.malloc(sha1_len + name_len + 2)
if mangled is None:
return name
string.strcpy(mangled, sha1)
mangled[sha1_len] = '.'
string.strcpy(mangled + sha1_len + 1, name)
return mangled
# ============================================================
# _mangle_name_with_sha1 — 用指定 SHA1 为名称添加前缀
#
# 用于跨模块 vtable 继承: 继承方法需要用父模块的 SHA1 做 mangling,
# 而非当前模块的 SHA1。
# ============================================================
def _mangle_name_with_sha1(self, sha1: str, name: str) -> str:
"""用指定 SHA1 为名称添加前缀(不做 main/export 检查)"""
if sha1 is None or name is None:
return name
name_len: t.CSizeT = string.strlen(name)
# 已带 SHA1 前缀(16 hex + '.')则跳过
if name_len > 17:
is_sha1: int = 1
for i in range(16):
c2: t.CChar = name[i]
if not (('0' <= c2 <= '9') or ('a' <= c2 <= 'f')):
is_sha1 = 0
break
if is_sha1 != 0 and name[16] == '.':
return name
sha1_len: t.CSizeT = string.strlen(sha1)
mangled: str = stdlib.malloc(sha1_len + name_len + 2)
if mangled is None:
return name
string.strcpy(mangled, sha1)
mangled[sha1_len] = '.'
string.strcpy(mangled + sha1_len + 1, name)
return mangled
# ============================================================
# _mangle_func_name - 为函数名添加 SHA1 前缀(含 main/export 检查)
#
# 规则:
# - main 函数不加前缀(程序入口点)
# - has_export 非 0 时不加前缀(t.CExport 导出函数)
# - 已带 SHA1 前缀的不再加
# - ModuleSha1 为 None 时不加前缀
# ============================================================
def _mangle_func_name(self, trans: HT.Translator | t.CPtr, name: str,
has_export: int) -> str:
"""为函数名添加 SHA1 前缀(检查 main/export)"""
if trans is None or name is None:
return name
if has_export != 0:
return name
if string.strcmp(name, "main") == 0:
return name
return self._mangle_name(trans, name)
# ============================================================
# _body_has_raise - 函数体是否含 raise(非 StopIteration)
#
# 对齐 TPC HandlesFunctions.py L216-243 _body_contains_raise:函数签名
# 追加 __eh_msg_out__/__eh_code_out__ 输出参数的条件。v1 只扫函数体
# 顶层 raise(嵌套 if/for/try 内的 raise 未覆盖,后续扩展)。
# ============================================================
def _body_has_raise(self, fd: ast.FunctionDef | t.CPtr) -> int:
"""函数体顶层是否含 raise(非 StopIteration),1=是 / 0=否"""
if fd is None or fd.children is None:
return 0
body_f: list[ast.AST | t.CPtr] | t.CPtr = fd.children
bn_f: t.CSizeT = len(body_f)
for bi_f in range(bn_f):
stmt_f: ast.AST | t.CPtr = body_f.get(bi_f)
if stmt_f is None:
continue
if stmt_f.kind() != ast.ASTKind.Raise:
continue
rn_f: ast.Raise | t.CPtr = (ast.Raise | t.CPtr)(stmt_f)
if rn_f is None or rn_f.exc is None:
continue
ek_f: int = rn_f.exc.kind()
is_stop_f: int = 0
if ek_f == ast.ASTKind.Name:
enm_f: ast.Name | t.CPtr = (ast.Name | t.CPtr)(rn_f.exc)
if enm_f.id is not None and string.strcmp(enm_f.id, "StopIteration") == 0:
is_stop_f = 1
elif ek_f == ast.ASTKind.Call:
ecl_f: ast.Call | t.CPtr = (ast.Call | t.CPtr)(rn_f.exc)
if ecl_f.func is not None and ecl_f.func.kind() == ast.ASTKind.Name:
ecn_f: ast.Name | t.CPtr = (ast.Name | t.CPtr)(ecl_f.func)
if ecn_f.id is not None and string.strcmp(ecn_f.id, "StopIteration") == 0:
is_stop_f = 1
if is_stop_f == 0:
return 1
return 0
# ============================================================
# _resolve_creturn_ret_ty - @c.CReturn(ty1, ty2, ...) → 多返回 struct 类型
#
# 对齐 TPC DeclarationGenerator:CReturn 声明函数返回多个值(如
# GetMemoryStats @c.CReturn(t.CUInt64T, t.CUInt64T, t.CUInt64T) →
# {i64, i64, i64})。pre_scan 前向声明与 translate_function_def 定义
# 两阶段必须一致,否则 declare i32 + define {i64,i64,i64} → llc
# "ret doesn't match function result type"。
# ============================================================
def _resolve_creturn_ret_ty(self, pool: memhub.MemBuddy | t.CPtr,
fd: ast.FunctionDef | t.CPtr,
imported_modules: str,
from_imports: str,
trans: HT.Translator | t.CPtr) -> llvmlite.LLVMType | t.CPtr:
"""解析 @c.CReturn 装饰器为 struct 返回类型;无 CReturn 返回 None"""
if fd is None or fd.decorator_list is None:
return None
cr_head: llvmlite.ParamNode | t.CPtr = None
cr_last: llvmlite.ParamNode | t.CPtr = None
cr_count: int = 0
cr_dn: t.CSizeT = len(fd.decorator_list)
for cdi in range(cr_dn):
deco_cr: ast.AST | t.CPtr = fd.decorator_list.get(cdi)
if deco_cr is None or deco_cr.kind() != ast.ASTKind.Call:
continue
deco_call_cr: ast.Call | t.CPtr = (ast.Call | t.CPtr)(deco_cr)
if deco_call_cr.func is None or deco_call_cr.func.kind() != ast.ASTKind.Attribute:
continue
deco_fa_cr: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(deco_call_cr.func)
if deco_fa_cr.attr is None or string.strcmp(deco_fa_cr.attr, "CReturn") != 0:
continue
cr_args: list[ast.AST | t.CPtr] | t.CPtr = deco_call_cr.args
if cr_args is None:
continue
cr_an: t.CSizeT = len(cr_args)
for cai in range(cr_an):
cr_aty: llvmlite.LLVMType | t.CPtr = self.Gen.TypeGenResolve.resolve_annotation_type(
pool, cr_args.get(cai), imported_modules, from_imports, trans, 0)
if cr_aty is None:
continue
cr_pn: llvmlite.ParamNode | t.CPtr = llvmlite.new_param_node(pool, cr_aty)
if cr_pn is not None:
if cr_head is None:
cr_head = cr_pn
else:
cr_last.Next = cr_pn
cr_last = cr_pn
cr_count += 1
if cr_head is not None and cr_count > 0:
return llvmlite.Struct(pool, cr_head, cr_count, None)
return None
# ============================================================
# 创建 LLVM 函数(简单版本,仅声明)
# ============================================================
def create_function(self, pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
name: str,
args_node: ast.AST | t.CPtr,
ret_ty: llvmlite.LLVMType | t.CPtr) -> llvmlite.Function | t.CPtr:
"""创建 LLVM 函数并添加参数"""
if name is None or mod is None:
return None
func: llvmlite.Function | t.CPtr = llvmlite.create_function(pool, mod, name, ret_ty)
if func is None:
return None
if args_node is not None:
ags: ast.Arguments | t.CPtr = (ast.Arguments | t.CPtr)(args_node)
if ags.args is not None:
alist: list[ast.AST | t.CPtr] | t.CPtr = ags.args
an: t.CSizeT = len(alist)
i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
for ai in range(an):
arg: ast.Arg | t.CPtr = (ast.Arg | t.CPtr)(alist.get(ai))
if arg is not None and arg.arg is not None:
# t.CVoid 表示空参:跳过
if arg.annotation is not None:
if arg.annotation.kind() == ast.ASTKind.Attribute:
at_cf: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(arg.annotation)
if at_cf.attr is not None and string.strcmp(at_cf.attr, "CVoid") == 0:
continue
param_ty: llvmlite.LLVMType | t.CPtr = i32_ty
if arg.annotation is not None:
resolved: llvmlite.LLVMType | t.CPtr = self.Gen.TypeGenResolve.resolve_annotation_type(
pool, arg.annotation, None, None, None, 0)
if resolved is not None:
param_ty = resolved
pname: str = pool.alloc(32)
if pname is not None:
viperlib.snprintf(pname, 32, "%%%s", arg.arg)
llvmlite.add_param(pool, func, param_ty, pname)
return func
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+925 -418
View File
File diff suppressed because it is too large. Load diff
+276 -137
View File
@@ -1,193 +1,332 @@
import t, c
from stdint import *
import ast
import llvmlite
import mlirlite
import memhub
# TypeSpec: unified type descriptor (Debugger/_Type.md section 6).
# Alias TSpec because the in-module class name is also TypeSpec (same name would be a static method).
import lib.core.Gen.Type.TypeSpec as TSpec
# ============================================================
# GenTypes - Gen 侧共享数据类型与常量(去除 Handles 层迁移目标)
#
# 原分布于 Handles 目录的数据类与常量集中于此,供 Gen 子对象、
# 管线与保留实体文件引用。删除 Handles 目录后统一经本模块访问。
# ============================================================
# GenTypes - Gen-side shared data types and constants (migrated out of the Handles layer)
# ============================================================
# 类编译期元数据(原 HandlesType.CLASS_META_*)
# ============================================================
CLASS_META_PROVIDES: t.CDefine = 1
CLASS_META_REQUIRES: t.CDefine = 2
CLASS_META_REQUIRE_MUST: t.CDefine = 3
# Class compile-time metadata (formerly HandlesType.CLASS_META_*)
define CLASS_META_PROVIDES = 1
define CLASS_META_REQUIRES = 2
define CLASS_META_REQUIRE_MUST = 3
# ============================================================
# 属性/静态/类方法元数据(原 HandlesClassDef.META_*)
# ============================================================
META_STATIC: t.CDefine = 1
META_CLASS: t.CDefine = 2
META_PROP_GETTER: t.CDefine = 4
META_PROP_SETTER: t.CDefine = 8
META_PROP_DELETER: t.CDefine = 16
# Attribute/static/class-method metadata (formerly HandlesClassDef.META_*)
define META_STATIC = 1
define META_CLASS = 2
define META_PROP_GETTER = 4
define META_PROP_SETTER = 8
define META_PROP_DELETER = 16
# ============================================================
# 结构体容量(原 HandlesStruct.FIELD_MAX/FIELD_NAME_MAX/PARENT_MAX/MRO_MAX)
# ============================================================
FIELD_MAX: t.CDefine = 256
FIELD_NAME_MAX: t.CDefine = 64
PARENT_MAX: t.CDefine = 64
MRO_MAX: t.CDefine = 64
# Struct capacities (formerly HandlesStruct.FIELD_MAX/...)
define FIELD_MAX = 256
define FIELD_NAME_MAX = 64
define PARENT_MAX = 64
define MRO_MAX = 64
# ============================================================
# for 迭代序列种类(原 HandlesFor.SEQ_KIND_*)
# ============================================================
SEQ_KIND_RANGE: t.CDefine = 1
SEQ_KIND_STR: t.CDefine = 2
SEQ_KIND_LIST: t.CDefine = 3
# for-iteration sequence kinds (formerly HandlesFor.SEQ_KIND_*)
define SEQ_KIND_RANGE = 1
define SEQ_KIND_STR = 2
define SEQ_KIND_LIST = 3
# ============================================================
# TraceFrame - 仿 Python Traceback 调用栈帧(原 HandlesType.TraceFrame)
# ============================================================
# TraceFrame - Python-Traceback-like call frame (formerly HandlesType.TraceFrame)
class TraceFrame:
"""仿 Python Traceback 调用栈帧(push_trace/pop_trace 使用)"""
"""Python-Traceback-like call frame used by push_trace/pop_trace"""
file_name: str
lineno: int
col_start: int
col_end: int
# ---- 生命期标签(LifeTag):有序,越小越"短命" ----
# 逃逸检查只在 LIFE_FRAME 上触发;控制流汇合取更短者(Meet = min)。
# 详见 Debugger/ownership-and-new.md §11。
define LIFE_VALUE = 0 # 值语义(标量/聚合按值拷贝),不参与逃逸
define LIFE_FRAME = 1 # 本帧栈存储:局部变量地址、数组退让、形参副本地址
define LIFE_HEAP = 2 # 堆,hlife(释放靠手动/区域)
define LIFE_STATIC = 3 # 模块级全局 / 字符串常量 / 函数地址
define LIFE_UNKNOWN = 4 # 未知(extern 返回等),保守放行
# ============================================================
# VarEntry - 变量表条目(原 HandlesVar.VarEntry)
# ============================================================
# VarEntry - variable table entry (formerly HandlesVar.VarEntry)
class VarEntry:
"""变量表条目"""
"""Variable table entry"""
Name: str
Alloca: llvmlite.Value | t.CPtr
Used: t.CInt
AnnotClassName: str # 原始类型注解的类名(str 别名在结构体字段中触发编译器 bug,改用显式联合类型)
IsPtrElement: t.CInt # 标志: 1=注解为 bytes|t.CPtr 或 str|t.CPtr, 下标按 8 字节步长
Alloca: mlirlite.Value*
Used: int
# TypeSpec (sole type carrier since S3): filled by add_var via SpecFromLLVMType from
# alloca.Ty's pointee (PermPool, cross-file safe); annotation side augments sign/source/elem.
Spec: TSpec.TypeSpec*
# 1=load/store of this variable carries LLVM volatile (annotation union has t.CVolatile).
# Appended at the end so existing field offsets are unchanged.
IsVolatile: int
# 1=this variable is rawptr[t.CVolatile | T] (volatile pointee, like C's volatile T*):
# its dereferences carry volatile. Unlike IsVolatile, which limits the pointer variable itself.
PointeeVolatile: int
# 1=this variable is a const object (annotation has t.CConst and the type is not a
# pointer): assignment to it anywhere in scope fails fast. Pointer CConst only limits the pointee.
IsConst: int
# 生命期标签(LifeTag,见文件头 define LIFE_*):该变量**当前值**的生命期。
# 追加在末尾以保持既有字段偏移不变。默认 LIFE_UNKNOWN(未登记 = 保守放行)。
Life: int
# Byte order of this variable, from the `T | t.BigEndian` / `T | t.LittleEndian` annotation union:
# 0=native (no marker), 1=BigEndian, 2=LittleEndian. Appended at the end so existing field offsets
# are unchanged; local loads/stores bswap when 1 (16/32/64-bit ints only). Mirrors FieldEntry.ByteOrder.
ByteOrder: int
# 注:P2 的"已失效"状态**不放在这里** —— 给这个热结构加字段会改变布局、牵连整条变量注册
# 路径(现场:任何工程编译时都在"函数形参注册刚结束"处 access-violation)。改由
# VarGenMixin 的独立失效槽表 _g_freed_slots 保存(键 = 变量槽 alloca,槽本身唯一)。
# ============================================================
# Scope - scope node (formerly HandlesVar.Scope)
novirtual class Scope:
"""Scope node"""
Parent: Scope*
Vars: VarEntry*
VarCount: int
Kind: int
# ============================================================
# Scope - 作用域节点(原 HandlesVar.Scope)
# ============================================================
@t.NoVTable
class Scope:
"""作用域节点"""
Parent: Scope | t.CPtr
Vars: VarEntry | t.CPtr
VarCount: t.CInt
Kind: t.CInt
# SymbolTable - nested-scope symbol table (formerly HandlesVar.SymbolTable)
novirtual class SymbolTable:
"""Nested-scope symbol table"""
Pool: memhub.MemBuddy*
Root: Scope*
Current: Scope*
# ============================================================
# SymbolTable - 嵌套作用域符号表(原 HandlesVar.SymbolTable)
# ============================================================
@t.NoVTable
class SymbolTable:
"""嵌套作用域符号表"""
Pool: memhub.MemBuddy | t.CPtr
Root: Scope | t.CPtr
Current: Scope | t.CPtr
# ============================================================
# FieldEntry - 字段信息条目(原 HandlesStruct.FieldEntry)
# ============================================================
# FieldEntry - field info entry (formerly HandlesStruct.FieldEntry)
class FieldEntry:
Name: str # 字段名(字符串)
Index: int # 字段在结构体中的索引
Ty: llvmlite.LLVMType | t.CPtr # 字段的 LLVM 类型
DefaultVal: ast.AST | t.CPtr # 默认值 AST 节点(None=无默认值)
AnnotClassName: str # 原始类型注解的类名(str 别名在结构体字段中触发编译器 bug,改用显式联合类型)
Name: str # Field name (string)
Index: int # Field index within the struct
Ty: mlirlite.LLVMType* # Field LLVM type
DefaultVal: ast.AST* # Default-value AST node (None=no default)
# TypeSpec (sole type carrier since S3): filled by add_field via SpecFromLLVMType from
# field_ty (PermPool, safe across 1b reset); annotation side adds sign/source.
Spec: TSpec.TypeSpec*
# 1=load/store of this field via an object carries LLVM volatile (field annotation union
# has t.CVolatile, e.g. Spinlock.locked). Appended at the end so offsets are unchanged.
IsVolatile: int
# 1=this field is rawptr[t.CVolatile | T] (volatile pointee): dereferences via it
# (self.ptr[i] / self.ptr.field) carry volatile. Same distinction as VarEntry.PointeeVolatile.
PointeeVolatile: int
# 1=this field is a const object (annotation has t.CConst and the type is not a pointer):
# assigning it via an object fails fast, same as VarEntry.IsConst.
IsConst: int
# Byte order of this field, from the `X | t.BigEndian` / `X | t.LittleEndian` annotation
# union: 0=native (no marker), 1=BigEndian, 2=LittleEndian. Appended at the end so offsets
# are unchanged; consumers bswap on field load/store when 1 (16/32/64-bit ints only).
ByteOrder: int
# ============================================================
# StructEntry - 结构体注册条目(原 HandlesStruct.StructEntry)
# ============================================================
@t.NoVTable
class StructEntry:
Name: str # 类名
Ty: llvmlite.LLVMType | t.CPtr # LLVM StructType
FieldCount: int # 字段数量
Fields: FieldEntry | t.CPtr # 字段数组(FIELD_MAX 个槽位)
IsUnion: int # 1=联合体 / 0=普通结构体
IsOOP: int # 1=OOP结构体(有方法) / 0=纯内存结构体
HasInit: int # 1=有__init__方法 / 0=无
HasNew: int # 1=有__new__方法 / 0=无
HasVTable: int # 1=有虚表 / 0=无虚表
IsNoVTable: int # 1=明确标记 @t.NoVTable / 0=未标记
ParentName: str # 父类名(None=无父类,多继承时为第一个父类,兼容旧调用)
ParentNames: t.CSizeT | t.CPtr # 多继承父类名数组(每元素 str 指针,ParentCount 个;stdlib 深拷贝)
ParentCount: t.CInt # 父类数量(0=无父类,1=单继承,>1=多继承)
VTableMethodCount: int # 虚表中的方法数量
VTableMethods: str # 虚表方法名数组(每个方法名 str,VTableMethodCount 个)
ModuleSha1: str # 定义该类的模块 SHA1(None=无 SHA1)
MethodsEmitted: int # 1=方法体已翻译 / 0=未译(Phase 1a mark_only 未译,Phase B 据此补译)
SpecTriggerSha1: str # 泛型特化类的触发文件 SHA1(None=非特化类)。特化类 ModuleSha1=模板
# sha1(方法名带模板前缀),作废重译时 reset_module_methods_emitted
# 按触发文件 sha1 精确重置 MethodsEmitted,防特化方法体残留 1 不补译
# → 链接 undefined symbol(list[ast.ClassDef|t.CPtr].* 根因)
# StructEntry - struct registration entry (formerly HandlesStruct.StructEntry)
novirtual class StructEntry:
Name: str # Class name
Ty: mlirlite.LLVMType* # LLVM StructType
FieldCount: int # Field count
Fields: FieldEntry* # Field array (FIELD_MAX slots)
IsUnion: int # 1=union / 0=normal struct
IsPacked: int # 1=@c.Attribute(t.attr.packed) packed struct (no padding)
Align: int # @c.Attribute(t.attr.aligned(N)) alignment in bytes (0=default)
IsOOP: int # 1=OOP struct (has methods) / 0=plain memory struct
HasInit: int # 1=has __init__ / 0=no
HasNew: int # 1=has __new__ / 0=no
HasVTable: int # 1=has vtable / 0=no vtable
IsNoVTable: int # 1=explicitly @t.NoVTable / 0=not marked
ParentName: str # Parent name (None=none; first parent for multiple inheritance)
ParentNames: sizet* # Multi-inheritance parent name array (str pointers; stdlib-deep-copied)
ParentCount: int # Parent count (0=none, 1=single, >1=multiple)
VTableMethodCount: int # Number of methods in the vtable
VTableMethods: str # Vtable method name array (VTableMethodCount str)
ModuleSha1: str # Module SHA1 defining this class (None=no SHA1)
MethodsEmitted: int # 1=method bodies translated / 0=not (Phase 1a mark_only; Phase B fills in)
SpecTriggerSha1: str # Trigger-file SHA1 for a generic specialization (None=not specialized).
# ModuleSha1 is the template sha1; reset_module_methods_emitted resets
# MethodsEmitted by trigger sha1 to avoid leftover 1 / undefined symbol.
IsForwardDecl: int # 1=forward-declaration placeholder (Ty is opaque named struct, FieldCount=0).
# The definition side later fills the same Ty in place and clears this flag.
# ============================================================
# EnumMember - 枚举成员(原 HandlesEnum.EnumMember)
# ============================================================
# EnumMember - enum member (formerly HandlesEnum.EnumMember)
class EnumMember:
Name: str # 成员名(字符串)
Value: t.CInt64T # 成员的整数值
Ty: llvmlite.LLVMType | t.CPtr # 成员的 LLVM 类型(用于混用类型场景)
Name: str # Member name (string)
Value: int64 # Member integer value
Ty: mlirlite.LLVMType* # Member LLVM type (for mixed-type cases)
# ============================================================
# EnumEntry - 枚举类型条目(原 HandlesEnum.EnumEntry)
# ============================================================
@t.NoVTable
class EnumEntry:
Name: str # 枚举类名
BaseTy: llvmlite.LLVMType | t.CPtr # 基准类型(所有成员类型中最大的)
MemberCount: int # 成员数量
Members: EnumMember | t.CPtr # 成员数组(ENUM_MEMBER_MAX 个槽位)
# EnumEntry - enum type entry (formerly HandlesEnum.EnumEntry)
novirtual class EnumEntry:
Name: str # Enum class name
BaseTy: mlirlite.LLVMType* # Base type (largest among member types)
MemberCount: int # Member count
Members: EnumMember* # Member array (ENUM_MEMBER_MAX slots)
# ============================================================
# AsmOperand - 内联汇编操作数条目(原 HandlesExprCall.AsmOperand)
# ============================================================
# AsmOperand - inline-asm operand entry (formerly HandlesExprCall.AsmOperand)
class AsmOperand:
"""内联汇编操作数条目"""
Value: llvmlite.Value | t.CPtr # 输入值或输出 alloca
Constraint: str # 约束字符串 (如 "r", "=r")
IsOutput: int # 1=输出, 0=输入
"""Inline-asm operand entry"""
Value: mlirlite.Value* # Input value or output alloca
Constraint: str # Constraint string (e.g. "r", "=r")
IsOutput: int # 1=output, 0=input
# ============================================================
# FuncEntry - 函数表条目(原 HandlesExprCall.FuncEntry)
# ============================================================
@t.NoVTable
class FuncEntry:
"""函数表条目"""
# FuncEntry - function table entry (formerly HandlesExprCall.FuncEntry)
novirtual class FuncEntry:
"""Function table entry"""
Name: str
Func: llvmlite.Function | t.CPtr
Defaults: t.CVoid | t.CPtr # defaults 列表节点指针 (list[AST | CPtr]),None=无默认值
DefaultCount: int # 默认值数量
ParamCount: int # 形参数量(不含 self)
# TypeStr: 函数类型文本(stdlib 深拷贝,跨文件安全)
# 1b 每文件 reset_to(mark1) 回收 pool 内 Function 对象后,
# vtable 生成 find_func_global 读类型改用此文本,避免悬空。
TypeStr: str
Func: mlirlite.Function*
Defaults: t.CPtr # Defaults list node pointer (list[AST*]); None=no defaults
DefaultCount: int # Default count
ParamCount: int # Param count (excluding self)
# TypeSpec (dual-write since S3): the full signature of Kind=TS_FUNC (Ret/Params/...)
# built by update_global_func_spec after add_param; PermPool-resident, safe across 1b reset.
Spec: TSpec.TypeSpec*
# ============================================================
# FuncDefaultsEntry - 默认参数表条目(原 HandlesExprCall.FuncDefaultsEntry)
# ============================================================
@t.NoVTable
class FuncDefaultsEntry:
"""默认参数表条目"""
# FuncDefaultsEntry - default-argument entry (formerly HandlesExprCall.FuncDefaultsEntry)
novirtual class FuncDefaultsEntry:
"""Default-argument table entry"""
Name: str
Defaults: t.CVoid | t.CPtr # defaults 列表节点指针 (list[AST | CPtr])(pool 内,可能被 reset)
ModuleSha1: str # Module SHA1 defining this function (dedupes qualified names)
Defaults: t.CPtr # Defaults list node pointer (list[AST*]) (in pool, may be reset)
DefaultCount: int
ParamCount: int # 形参数量(不含 self)
# 常量快照(stdlib 分配,跨文件永久存活):
# 注册时提取每个默认值 Constant 的值,跨模块调用时优先用快照重建常量 IR,
# 避免读取已被 1b reset_to(mark1) 回收的 pool 内 AST。
ConstKinds: t.CInt | t.CPtr # const_kind 数组(DefaultCount 个 i32)
ConstInts: t.CInt64T | t.CPtr # int_val 数组(DefaultCount 个 i64)
ConstStrs: t.CSizeT | t.CPtr # str_val 指针数组(DefaultCount 个 i8*)
ParamCount: int # Param count (excluding self)
# Constant snapshot (stdlib-allocated, permanent): extracted at registration so
# cross-module calls rebuild constant IR without touching a reset AST.
ConstKinds: int* # const_kind array (DefaultCount i32)
ConstInts: int64* # int_val array (DefaultCount i64)
ConstStrs: sizet* # str_val pointer array (DefaultCount i8*)
# ============================================================
# REPL session (M2/M3): ctx buffer layout, session table entries, and result header.
define REPL_CTX_MARKER = 1397571916 # Host-side object marker (self-comparison; not a ctx header field)
define REPL_CTX_MAGIC = 0x5245504C # 'REPL' (ctx header +0, little-endian bytes R E P L)
define REPL_CTX_VERSION = 2 # ctx header +4 (1 -> 2: function pointer slot area appended at the tail)
define REPL_SLOT_SIZE = 16 # Bytes per session variable (stride)
define REPL_SLOT_BASE = 64 # Slot area start = fixed header bytes (after the +40..63 reserved area)
define REPL_CTX_PARAM = "__repl_ctx__" # Wrapper-template ctx parameter name (same source as Repl.vp)
define REPL_VAR_CAP = 64 # Max session variables (var area = 64 + 64*16 = 1088 bytes)
# Session function pointer slot area (M3): starts right after the var area, 16B stride (low 8 bytes hold the address)
define REPL_FUNC_BASE = 1088 # = REPL_SLOT_BASE + REPL_VAR_CAP*REPL_SLOT_SIZE
define REPL_FUNC_SLOT_SIZE = 16 # Bytes per session function (stride)
define REPL_FUNC_CAP = 32 # Max session functions (func area = 32*16 = 512 bytes)
# Total ctx bytes = 64 + 64*16 + 32*16 = 1600 (host Repl._REPL_CTX_SIZE must match)
define REPL_CTX_SIZE = 1600
# Max session function params (fixed-size signature token array; overflow errors at registration)
define REPL_FUNC_PARAM_MAX = 8
# Fragment entry function name prefix (Repl.vp generates "__repl_frag_%d"); distinguishes
# the fragment entry function (writes session vars) from the hoisted session function body.
define REPL_ENTRY_PREFIX = "__repl_frag_"
# Session module SHA1 (M4): fragments use this stable value so type names are byte-identical
# across fragments, which is the key for classes defined earlier to be visible later.
define REPL_SESSION_SHA1 = "71f67b7cb405f0e4"
# ctx header field offsets (one-to-one with ReplResult field order/width)
define REPL_RES_KIND_OFF = 8 # i32
define REPL_RES_A_OFF = 16 # u64
define REPL_RES_B_OFF = 24 # u64
define REPL_RES_VARCOUNT_OFF = 32 # i32
define REPL_RES_LASTFRAG_OFF = 36 # i32
# Result kind (the host echoes accordingly)
define REPL_RES_NONE = 0 # No result for this fragment (e.g. declaration only)
define REPL_RES_INT = 1 # Integer (read result_a bit pattern)
define REPL_RES_FLOAT = 2 # float64 (read result_a's double bit pattern)
define REPL_RES_STR = 3 # str/bytes (read result_a pointer + result_b length)
define REPL_RES_OBJ = 4 # Struct/other pointer (read result_a address)
# Session variable type tags (ReplVarEntry.TyTag)
define REPL_TY_INT = 1
define REPL_TY_FLOAT = 2
define REPL_TY_STR = 3 # i8* (str/bytes)
define REPL_TY_PTR = 4 # Other pointer (Bits=pointee width / TyName=struct name)
define REPL_TY_STRUCT = 5 # Struct value (inlined in the slot per declared type; <= slot capacity)
# Session variable storage kind (ReplVarEntry.Kind)
define REPL_KIND_UNTYPED = 0 # Slot registered but not yet typed (unassigned / no annotation)
define REPL_KIND_SCALAR = 1 # Value inlined in the slot (scalar/pointer/<=16B struct value)
# Session function signature return-type token (M3; used only for ReplFuncEntry.RetTag)
define REPL_TY_VOID = 0 # No return value (void; not used as a param token)
# ============================================================
# ReplVarEntry - REPL session variable entry (survives across fragments; table in PermPool)
novirtual class ReplVarEntry:
"""REPL session variable entry: name + ctx slot offset + type info"""
Name: str # Variable name (PermPool deep copy, NUL-terminated)
TyName: str # Struct name (TyTag=PTR with struct pointee / TyTag=STRUCT); else None
Offset: int # Slot offset within ctx (REPL_SLOT_BASE + i * REPL_SLOT_SIZE)
Kind: int # REPL_KIND_*
TyTag: int # REPL_TY_*
Bits: int # Scalar bit width; for TyTag=PTR the pointee width (0=unknown)
# ReplFuncEntry - REPL session function entry (M3; survives fragments, in PermPool).
# Maps name -> symbol + fragment no + slot offset + signature token for indirect calls.
novirtual class ReplFuncEntry:
"""REPL session function entry: name + export symbol + ctx function-pointer slot + signature token"""
Name: str # Session function name (= the bare name the user wrote; PermPool copy)
SymName: str # Export symbol name in the fragment DLL (PermPool copy; M3 = bare name)
FragNo: int # Fragment number defining this function (updated on redefinition)
SlotOff: int # Function pointer slot offset within ctx (REPL_FUNC_BASE + j*REPL_FUNC_SLOT_SIZE)
RetTag: int # REPL_TY_VOID / REPL_TY_INT / REPL_TY_FLOAT / REPL_TY_STR
RetBits: int # Same measure as ReplVarEntry.Bits (0 for VOID)
ParamCount: int # Param count (NOT including the injected ctx first param)
ParamTags: str # PermPool byte array (1 tag per param, REPL_FUNC_PARAM_MAX entries)
ParamBits: str # PermPool byte array (1 bits byte per param, REPL_FUNC_PARAM_MAX entries)
# ReplCtx - REPL session context (host-built, translator-read). trans.ReplCtx != None
# enables REPL mode and guards all Gen-side interception points; otherwise the old path is used.
novirtual class ReplCtx:
"""REPL mode context (host-owned, translator-read-only)"""
Marker: int # REPL_CTX_MARKER (guards against a non-ReplCtx object)
Table: ReplVarEntry* # Session table array (PermPool, capacity Cap)
Count: int # Registered entry count (= allocated slots)
Cap: int # Capacity (REPL_VAR_CAP)
CtxParam: str # Fragment function ctx param name (PermPool copy)
SlotSize: int # Slot stride (REPL_SLOT_SIZE)
# Shadow entry (host-built, PermPool): only classifies bare-expression results; it
# occupies no session slot and is not counted; Name is fixed to "<expr>".
Scratch: ReplVarEntry*
# Permanent pool provided by the host: cross-fragment data (session names, struct
# names) must be deep-copied here, since a fragment's trans.Pool is reset each line.
Perm: memhub.MemBuddy*
# Session function table (M3; host-writes, Gen-reads): capacity FuncCap, FuncCount
# registered. Entries are appended only after a fragment DLL loads and GetProcAddress hits.
FuncTable: ReplFuncEntry*
FuncCount: int
FuncCap: int # Capacity (REPL_FUNC_CAP)
# ReplResult - ctx header (host reads by field; a fragment writes by REPL_RES_*_OFF).
# Field order/width is the ctx layout: Magic, Version, Kind, Reserved0, A, B, VarCount, LastFrag.
novirtual class ReplResult:
"""Fixed ctx header (first 40 of the 64 header bytes; +40..63 reserved)"""
Magic: int # REPL_CTX_MAGIC ('REPL')
Version: int # REPL_CTX_VERSION
Kind: int # REPL_RES_*
Reserved0: int # Reserved (0)
A: int64 # result_a
B: int64 # result_b
VarCount: int # Allocated slot count (host-written, for inspection)
LastFrag: int # Most recent fragment number (host-written, for inspection)
+144 -156
View File
@@ -1,53 +1,52 @@
import string
import lib.core.CompilerFactory as CompilerFactory
import t, c
from stdint import *
import stdio
import stdlib
import memhub
import llvmlite
import mlirlite
import lib.core.Gen.BaseGenMixin as BaseGenMixin
import lib.core.Gen.TypeGenBase as TypeGenBase
import lib.core.Gen.TypeGenMixin as TypeGenMixin
import lib.core.Gen.TypeGenCDef as TypeGenCDef
import lib.core.Gen.TypeGenCType as TypeGenCType
import lib.core.Gen.TypeGenResolve as TypeGenResolve
import lib.core.Gen.TypeGenErr as TypeGenErr
import lib.core.Gen.Type.TypeGenBase as TypeGenBase
import lib.core.Gen.Type.TypeGenMixin as TypeGenMixin
import lib.core.Gen.Type.TypeGenCDef as TypeGenCDef
import lib.core.Gen.Type.TypeGenCType as TypeGenCType
import lib.core.Gen.Type.TypeGenResolve as TypeGenResolve
import lib.core.Gen.Type.TypeGenErr as TypeGenErr
import lib.core.Gen.StructGenMixin as StructGenMixin
import lib.core.Gen.ExprGenBase as ExprGenBase
import lib.core.Gen.ExprGenMixin as ExprGenMixin
import lib.core.Gen.ExprGenConst as ExprGenConst
import lib.core.Gen.ExprGenFstr as ExprGenFstr
import lib.core.Gen.ExprGenOps as ExprGenOps
import lib.core.Gen.ExprGenCdef as ExprGenCdef
import lib.core.Gen.ExprGenName as ExprGenName
import lib.core.Gen.ExprGenAttr as ExprGenAttr
import lib.core.Gen.ExprGenCls as ExprGenCls
import lib.core.Gen.CallGenBase as CallGenBase
import lib.core.Gen.CallGenCBuiltin as CallGenCBuiltin
import lib.core.Gen.CallGenLeafKit as CallGenLeafKit
import lib.core.Gen.CallGenCtor as CallGenCtor
import lib.core.Gen.CallGenCall as CallGenCall
import lib.core.Gen.CallGenMixin as CallGenMixin
import lib.core.Gen.CallGenDispatch as CallGenDispatch
import lib.core.Gen.CallGenLib as CallGenLib
import lib.core.Gen.CallGenVt as CallGenVt
import lib.core.Gen.CallGenVa as CallGenVa
import lib.core.Gen.CallGenProp as CallGenProp
import lib.core.Gen.CallGenFd as CallGenFd
import lib.core.Gen.FuncGenBase as FuncGenBase
import lib.core.Gen.FuncGenDef as FuncGenDef
import lib.core.Gen.FuncGenMixin as FuncGenMixin
import lib.core.Gen.ClassGenBase as ClassGenBase
import lib.core.Gen.ClassGenMixin as ClassGenMixin
import lib.core.Gen.ClassGenEnum as ClassGenEnum
import lib.core.Gen.ClassGenGeneric as ClassGenGeneric
import lib.core.Gen.ClassGenVTable as ClassGenVTable
import lib.core.Gen.ClassGenDef as ClassGenDef
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
import lib.core.Gen.Expr.ExprGenMixin as ExprGenMixin
import lib.core.Gen.Expr.ExprGenConst as ExprGenConst
import lib.core.Gen.Expr.ExprGenFstr as ExprGenFstr
import lib.core.Gen.Expr.ExprGenOps as ExprGenOps
import lib.core.Gen.Expr.ExprGenCdef as ExprGenCdef
import lib.core.Gen.Expr.ExprGenName as ExprGenName
import lib.core.Gen.Expr.ExprGenAttr as ExprGenAttr
import lib.core.Gen.Expr.ExprGenCls as ExprGenCls
import lib.core.Gen.Call.CallGenBase as CallGenBase
import lib.core.Gen.Call.CallGenCBuiltin as CallGenCBuiltin
import lib.core.Gen.Call.CallGenLeafKit as CallGenLeafKit
import lib.core.Gen.Call.CallGenCtor as CallGenCtor
import lib.core.Gen.Call.CallGenCall as CallGenCall
import lib.core.Gen.Call.CallGenMixin as CallGenMixin
import lib.core.Gen.Call.CallGenDispatch as CallGenDispatch
import lib.core.Gen.Call.CallGenLib as CallGenLib
import lib.core.Gen.Call.CallGenVt as CallGenVt
import lib.core.Gen.Call.CallGenVa as CallGenVa
import lib.core.Gen.Call.CallGenProp as CallGenProp
import lib.core.Gen.Call.CallGenFd as CallGenFd
import lib.core.Gen.Func.FuncGenBase as FuncGenBase
import lib.core.Gen.Func.FuncGenDef as FuncGenDef
import lib.core.Gen.Func.FuncGenMixin as FuncGenMixin
import lib.core.Gen.Class.ClassGenBase as ClassGenBase
import lib.core.Gen.Class.ClassGenMixin as ClassGenMixin
import lib.core.Gen.Class.ClassGenEnum as ClassGenEnum
import lib.core.Gen.Class.ClassGenGeneric as ClassGenGeneric
import lib.core.Gen.Class.ClassGenVTable as ClassGenVTable
import lib.core.Gen.Class.ClassGenDef as ClassGenDef
import lib.core.Gen.ImportsGenMixin as ImportsGenMixin
import lib.core.Gen.StmtGenCifMixin as StmtGenCifMixin
import lib.core.Gen.StmtGenDeclMixin as StmtGenDeclMixin
import lib.core.Gen.StmtGenMixin as StmtGenMixin
import lib.core.Gen.Stmt.StmtGenCifMixin as StmtGenCifMixin
import lib.core.Gen.Stmt.StmtGenDeclMixin as StmtGenDeclMixin
import lib.core.Gen.Stmt.StmtGenMixin as StmtGenMixin
import lib.core.Gen.MatchGenMixin as MatchGenMixin
import lib.core.Gen.AssignGenMixin as AssignGenMixin
import lib.core.Gen.AsyncGenMixin as AsyncGenMixin
@@ -55,394 +54,385 @@ import lib.core.Gen.VarGenMixin as VarGenMixin
import lib.core.Gen.TranslatorGenMixin as TranslatorGenMixin
# ============================================================
# Generator - 生成器(双向引用组合架构)
#
# 不再 46 路多继承 Mixin;改为组合:Generator 持所有 GenMixin
# 子对象指针(永久池分配,全生命周期),每个 Mixin 持 Gen 回指。
# 跨子对象访问 = self.Gen.<Mixin 字段>.<字段/方法>(链式有风险,
# 统一用中间变量取子对象再访问)。高亮可推导(显式类型注解)。
# ============================================================
@t.NoVTable
class Generator:
"""生成器:持所有 GenMixin 子对象指针(双向引用组合)"""
# Generator - generator (bidirectional-reference composition architecture)
novirtual class Generator:
"""Generator holding pointers to all GenMixin sub-objects (bidirectional composition)."""
# ---- 子对象指针(46 个,永久池) ----
BaseGen: BaseGenMixin.BaseGenMixin | t.CPtr
TypeGenBase: TypeGenBase.TypeGenBase | t.CPtr
TypeGen: TypeGenMixin.TypeGenMixin | t.CPtr
TypeGenCDef: TypeGenCDef.TypeGenCDef | t.CPtr
TypeGenCType: TypeGenCType.TypeGenCType | t.CPtr
TypeGenResolve: TypeGenResolve.TypeGenResolve | t.CPtr
TypeGenErr: TypeGenErr.TypeGenErr | t.CPtr
StructGen: StructGenMixin.StructGenMixin | t.CPtr
ExprGenBase: ExprGenBase.ExprGenBase | t.CPtr
ExprGen: ExprGenMixin.ExprGenMixin | t.CPtr
ExprGenConst: ExprGenConst.ExprGenConst | t.CPtr
ExprGenFstr: ExprGenFstr.ExprGenFstr | t.CPtr
ExprGenOps: ExprGenOps.ExprGenOps | t.CPtr
ExprGenCdef: ExprGenCdef.ExprGenCdef | t.CPtr
ExprGenName: ExprGenName.ExprGenName | t.CPtr
ExprGenAttr: ExprGenAttr.ExprGenAttr | t.CPtr
ExprGenCls: ExprGenCls.ExprGenCls | t.CPtr
CallGenBase: CallGenBase.CallGenBase | t.CPtr
CallGenCBuiltin: CallGenCBuiltin.CallGenCBuiltin | t.CPtr
CallGenLeafKit: CallGenLeafKit.CallGenLeafKit | t.CPtr
CallGenCtor: CallGenCtor.CallGenCtor | t.CPtr
CallGenCall: CallGenCall.CallGenCall | t.CPtr
CallGen: CallGenMixin.CallGenMixin | t.CPtr
CallGenDispatch: CallGenDispatch.CallGenDispatch | t.CPtr
CallGenLib: CallGenLib.CallGenLib | t.CPtr
CallGenVt: CallGenVt.CallGenVt | t.CPtr
CallGenVa: CallGenVa.CallGenVa | t.CPtr
CallGenProp: CallGenProp.CallGenProp | t.CPtr
CallGenFd: CallGenFd.CallGenFd | t.CPtr
FuncGenBase: FuncGenBase.FuncGenBase | t.CPtr
FuncGenDef: FuncGenDef.FuncGenDef | t.CPtr
FuncGen: FuncGenMixin.FuncGenMixin | t.CPtr
ClassGenBase: ClassGenBase.ClassGenBase | t.CPtr
ClassGen: ClassGenMixin.ClassGenMixin | t.CPtr
ClassGenEnum: ClassGenEnum.ClassGenEnum | t.CPtr
ClassGenGeneric: ClassGenGeneric.ClassGenGeneric | t.CPtr
ClassGenVTable: ClassGenVTable.ClassGenVTable | t.CPtr
ClassGenDef: ClassGenDef.ClassGenDef | t.CPtr
ImportsGen: ImportsGenMixin.ImportsGenMixin | t.CPtr
StmtGenCif: StmtGenCifMixin.StmtGenCifMixin | t.CPtr
StmtGenDecl: StmtGenDeclMixin.StmtGenDeclMixin | t.CPtr
StmtGen: StmtGenMixin.StmtGenMixin | t.CPtr
MatchGen: MatchGenMixin.MatchGenMixin | t.CPtr
AssignGen: AssignGenMixin.AssignGenMixin | t.CPtr
AsyncGen: AsyncGenMixin.AsyncGenMixin | t.CPtr
VarGen: VarGenMixin.VarGenMixin | t.CPtr
TranslatorGen: TranslatorGenMixin.TranslatorGenMixin | t.CPtr
# ---- sub-object pointers (46, permanent pool) ----
BaseGen: BaseGenMixin.BaseGenMixin*
TypeGenBase: TypeGenBase.TypeGenBase*
TypeGen: TypeGenMixin.TypeGenMixin*
TypeGenCDef: TypeGenCDef.TypeGenCDef*
TypeGenCType: TypeGenCType.TypeGenCType*
TypeGenResolve: TypeGenResolve.TypeGenResolve*
TypeGenErr: TypeGenErr.TypeGenErr*
StructGen: StructGenMixin.StructGenMixin*
ExprGenBase: ExprGenBase.ExprGenBase*
ExprGen: ExprGenMixin.ExprGenMixin*
ExprGenConst: ExprGenConst.ExprGenConst*
ExprGenFstr: ExprGenFstr.ExprGenFstr*
ExprGenOps: ExprGenOps.ExprGenOps*
ExprGenCdef: ExprGenCdef.ExprGenCdef*
ExprGenName: ExprGenName.ExprGenName*
ExprGenAttr: ExprGenAttr.ExprGenAttr*
ExprGenCls: ExprGenCls.ExprGenCls*
CallGenBase: CallGenBase.CallGenBase*
CallGenCBuiltin: CallGenCBuiltin.CallGenCBuiltin*
CallGenLeafKit: CallGenLeafKit.CallGenLeafKit*
CallGenCtor: CallGenCtor.CallGenCtor*
CallGenCall: CallGenCall.CallGenCall*
CallGen: CallGenMixin.CallGenMixin*
CallGenDispatch: CallGenDispatch.CallGenDispatch*
CallGenLib: CallGenLib.CallGenLib*
CallGenVt: CallGenVt.CallGenVt*
CallGenVa: CallGenVa.CallGenVa*
CallGenProp: CallGenProp.CallGenProp*
CallGenFd: CallGenFd.CallGenFd*
FuncGenBase: FuncGenBase.FuncGenBase*
FuncGenDef: FuncGenDef.FuncGenDef*
FuncGen: FuncGenMixin.FuncGenMixin*
ClassGenBase: ClassGenBase.ClassGenBase*
ClassGen: ClassGenMixin.ClassGenMixin*
ClassGenEnum: ClassGenEnum.ClassGenEnum*
ClassGenGeneric: ClassGenGeneric.ClassGenGeneric*
ClassGenVTable: ClassGenVTable.ClassGenVTable*
ClassGenDef: ClassGenDef.ClassGenDef*
ImportsGen: ImportsGenMixin.ImportsGenMixin*
StmtGenCif: StmtGenCifMixin.StmtGenCifMixin*
StmtGenDecl: StmtGenDeclMixin.StmtGenDeclMixin*
StmtGen: StmtGenMixin.StmtGenMixin*
MatchGen: MatchGenMixin.MatchGenMixin*
AssignGen: AssignGenMixin.AssignGenMixin*
AsyncGen: AsyncGenMixin.AsyncGenMixin*
VarGen: VarGenMixin.VarGenMixin*
TranslatorGen: TranslatorGenMixin.TranslatorGenMixin*
def __init__(self, pool: memhub.MemBuddy | t.CPtr):
if self is None:
return
# ---- alloc 46 个子对象 + 回指(永久池,fail-fast) ----
SubBaseGen: BaseGenMixin.BaseGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(BaseGenMixin.BaseGenMixin.__sizeof__())
def __init__(self, pool: memhub.MemBuddy*):
if self is None: return
# ---- alloc 46 sub-objects + back-pointers (permanent pool, fail-fast) ----
SubBaseGen: BaseGenMixin.BaseGenMixin* = CompilerFactory.factory.PermPool.alloc(BaseGenMixin.BaseGenMixin.__sizeof__())
if SubBaseGen is None:
stdio.printf("FAIL: BaseGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubBaseGen, 0, BaseGenMixin.BaseGenMixin.__sizeof__())
SubBaseGen.Gen = self
self.BaseGen = SubBaseGen
SubTypeGenBase: TypeGenBase.TypeGenBase | t.CPtr = CompilerFactory.get_perm_pool().alloc(TypeGenBase.TypeGenBase.__sizeof__())
SubTypeGenBase: TypeGenBase.TypeGenBase* = CompilerFactory.factory.PermPool.alloc(TypeGenBase.TypeGenBase.__sizeof__())
if SubTypeGenBase is None:
stdio.printf("FAIL: TypeGenBase alloc failed\n")
stdlib.exit(1)
string.memset(SubTypeGenBase, 0, TypeGenBase.TypeGenBase.__sizeof__())
SubTypeGenBase.Gen = self
self.TypeGenBase = SubTypeGenBase
SubTypeGen: TypeGenMixin.TypeGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(TypeGenMixin.TypeGenMixin.__sizeof__())
SubTypeGen: TypeGenMixin.TypeGenMixin* = CompilerFactory.factory.PermPool.alloc(TypeGenMixin.TypeGenMixin.__sizeof__())
if SubTypeGen is None:
stdio.printf("FAIL: TypeGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubTypeGen, 0, TypeGenMixin.TypeGenMixin.__sizeof__())
SubTypeGen.Gen = self
self.TypeGen = SubTypeGen
SubTypeGenCDef: TypeGenCDef.TypeGenCDef | t.CPtr = CompilerFactory.get_perm_pool().alloc(TypeGenCDef.TypeGenCDef.__sizeof__())
SubTypeGenCDef: TypeGenCDef.TypeGenCDef* = CompilerFactory.factory.PermPool.alloc(TypeGenCDef.TypeGenCDef.__sizeof__())
if SubTypeGenCDef is None:
stdio.printf("FAIL: TypeGenCDef alloc failed\n")
stdlib.exit(1)
string.memset(SubTypeGenCDef, 0, TypeGenCDef.TypeGenCDef.__sizeof__())
SubTypeGenCDef.Gen = self
self.TypeGenCDef = SubTypeGenCDef
SubTypeGenCType: TypeGenCType.TypeGenCType | t.CPtr = CompilerFactory.get_perm_pool().alloc(TypeGenCType.TypeGenCType.__sizeof__())
SubTypeGenCType: TypeGenCType.TypeGenCType* = CompilerFactory.factory.PermPool.alloc(TypeGenCType.TypeGenCType.__sizeof__())
if SubTypeGenCType is None:
stdio.printf("FAIL: TypeGenCType alloc failed\n")
stdlib.exit(1)
string.memset(SubTypeGenCType, 0, TypeGenCType.TypeGenCType.__sizeof__())
SubTypeGenCType.Gen = self
self.TypeGenCType = SubTypeGenCType
SubTypeGenResolve: TypeGenResolve.TypeGenResolve | t.CPtr = CompilerFactory.get_perm_pool().alloc(TypeGenResolve.TypeGenResolve.__sizeof__())
SubTypeGenResolve: TypeGenResolve.TypeGenResolve* = CompilerFactory.factory.PermPool.alloc(TypeGenResolve.TypeGenResolve.__sizeof__())
if SubTypeGenResolve is None:
stdio.printf("FAIL: TypeGenResolve alloc failed\n")
stdlib.exit(1)
string.memset(SubTypeGenResolve, 0, TypeGenResolve.TypeGenResolve.__sizeof__())
SubTypeGenResolve.Gen = self
self.TypeGenResolve = SubTypeGenResolve
SubTypeGenErr: TypeGenErr.TypeGenErr | t.CPtr = CompilerFactory.get_perm_pool().alloc(TypeGenErr.TypeGenErr.__sizeof__())
SubTypeGenErr: TypeGenErr.TypeGenErr* = CompilerFactory.factory.PermPool.alloc(TypeGenErr.TypeGenErr.__sizeof__())
if SubTypeGenErr is None:
stdio.printf("FAIL: TypeGenErr alloc failed\n")
stdlib.exit(1)
string.memset(SubTypeGenErr, 0, TypeGenErr.TypeGenErr.__sizeof__())
SubTypeGenErr.Gen = self
self.TypeGenErr = SubTypeGenErr
SubStructGen: StructGenMixin.StructGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(StructGenMixin.StructGenMixin.__sizeof__())
SubStructGen: StructGenMixin.StructGenMixin* = CompilerFactory.factory.PermPool.alloc(StructGenMixin.StructGenMixin.__sizeof__())
if SubStructGen is None:
stdio.printf("FAIL: StructGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubStructGen, 0, StructGenMixin.StructGenMixin.__sizeof__())
SubStructGen.Gen = self
self.StructGen = SubStructGen
SubExprGenBase: ExprGenBase.ExprGenBase | t.CPtr = CompilerFactory.get_perm_pool().alloc(ExprGenBase.ExprGenBase.__sizeof__())
SubExprGenBase: ExprGenBase.ExprGenBase* = CompilerFactory.factory.PermPool.alloc(ExprGenBase.ExprGenBase.__sizeof__())
if SubExprGenBase is None:
stdio.printf("FAIL: ExprGenBase alloc failed\n")
stdlib.exit(1)
string.memset(SubExprGenBase, 0, ExprGenBase.ExprGenBase.__sizeof__())
SubExprGenBase.Gen = self
self.ExprGenBase = SubExprGenBase
SubExprGen: ExprGenMixin.ExprGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(ExprGenMixin.ExprGenMixin.__sizeof__())
SubExprGen: ExprGenMixin.ExprGenMixin* = CompilerFactory.factory.PermPool.alloc(ExprGenMixin.ExprGenMixin.__sizeof__())
if SubExprGen is None:
stdio.printf("FAIL: ExprGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubExprGen, 0, ExprGenMixin.ExprGenMixin.__sizeof__())
SubExprGen.Gen = self
self.ExprGen = SubExprGen
SubExprGenConst: ExprGenConst.ExprGenConst | t.CPtr = CompilerFactory.get_perm_pool().alloc(ExprGenConst.ExprGenConst.__sizeof__())
SubExprGenConst: ExprGenConst.ExprGenConst* = CompilerFactory.factory.PermPool.alloc(ExprGenConst.ExprGenConst.__sizeof__())
if SubExprGenConst is None:
stdio.printf("FAIL: ExprGenConst alloc failed\n")
stdlib.exit(1)
string.memset(SubExprGenConst, 0, ExprGenConst.ExprGenConst.__sizeof__())
SubExprGenConst.Gen = self
self.ExprGenConst = SubExprGenConst
SubExprGenFstr: ExprGenFstr.ExprGenFstr | t.CPtr = CompilerFactory.get_perm_pool().alloc(ExprGenFstr.ExprGenFstr.__sizeof__())
SubExprGenFstr: ExprGenFstr.ExprGenFstr* = CompilerFactory.factory.PermPool.alloc(ExprGenFstr.ExprGenFstr.__sizeof__())
if SubExprGenFstr is None:
stdio.printf("FAIL: ExprGenFstr alloc failed\n")
stdlib.exit(1)
string.memset(SubExprGenFstr, 0, ExprGenFstr.ExprGenFstr.__sizeof__())
SubExprGenFstr.Gen = self
self.ExprGenFstr = SubExprGenFstr
SubExprGenOps: ExprGenOps.ExprGenOps | t.CPtr = CompilerFactory.get_perm_pool().alloc(ExprGenOps.ExprGenOps.__sizeof__())
SubExprGenOps: ExprGenOps.ExprGenOps* = CompilerFactory.factory.PermPool.alloc(ExprGenOps.ExprGenOps.__sizeof__())
if SubExprGenOps is None:
stdio.printf("FAIL: ExprGenOps alloc failed\n")
stdlib.exit(1)
string.memset(SubExprGenOps, 0, ExprGenOps.ExprGenOps.__sizeof__())
SubExprGenOps.Gen = self
self.ExprGenOps = SubExprGenOps
SubExprGenCdef: ExprGenCdef.ExprGenCdef | t.CPtr = CompilerFactory.get_perm_pool().alloc(ExprGenCdef.ExprGenCdef.__sizeof__())
SubExprGenCdef: ExprGenCdef.ExprGenCdef* = CompilerFactory.factory.PermPool.alloc(ExprGenCdef.ExprGenCdef.__sizeof__())
if SubExprGenCdef is None:
stdio.printf("FAIL: ExprGenCdef alloc failed\n")
stdlib.exit(1)
string.memset(SubExprGenCdef, 0, ExprGenCdef.ExprGenCdef.__sizeof__())
SubExprGenCdef.Gen = self
self.ExprGenCdef = SubExprGenCdef
SubExprGenName: ExprGenName.ExprGenName | t.CPtr = CompilerFactory.get_perm_pool().alloc(ExprGenName.ExprGenName.__sizeof__())
SubExprGenName: ExprGenName.ExprGenName* = CompilerFactory.factory.PermPool.alloc(ExprGenName.ExprGenName.__sizeof__())
if SubExprGenName is None:
stdio.printf("FAIL: ExprGenName alloc failed\n")
stdlib.exit(1)
string.memset(SubExprGenName, 0, ExprGenName.ExprGenName.__sizeof__())
SubExprGenName.Gen = self
self.ExprGenName = SubExprGenName
SubExprGenAttr: ExprGenAttr.ExprGenAttr | t.CPtr = CompilerFactory.get_perm_pool().alloc(ExprGenAttr.ExprGenAttr.__sizeof__())
SubExprGenAttr: ExprGenAttr.ExprGenAttr* = CompilerFactory.factory.PermPool.alloc(ExprGenAttr.ExprGenAttr.__sizeof__())
if SubExprGenAttr is None:
stdio.printf("FAIL: ExprGenAttr alloc failed\n")
stdlib.exit(1)
string.memset(SubExprGenAttr, 0, ExprGenAttr.ExprGenAttr.__sizeof__())
SubExprGenAttr.Gen = self
self.ExprGenAttr = SubExprGenAttr
SubExprGenCls: ExprGenCls.ExprGenCls | t.CPtr = CompilerFactory.get_perm_pool().alloc(ExprGenCls.ExprGenCls.__sizeof__())
SubExprGenCls: ExprGenCls.ExprGenCls* = CompilerFactory.factory.PermPool.alloc(ExprGenCls.ExprGenCls.__sizeof__())
if SubExprGenCls is None:
stdio.printf("FAIL: ExprGenCls alloc failed\n")
stdlib.exit(1)
string.memset(SubExprGenCls, 0, ExprGenCls.ExprGenCls.__sizeof__())
SubExprGenCls.Gen = self
self.ExprGenCls = SubExprGenCls
SubCallGenBase: CallGenBase.CallGenBase | t.CPtr = CompilerFactory.get_perm_pool().alloc(CallGenBase.CallGenBase.__sizeof__())
SubCallGenBase: CallGenBase.CallGenBase* = CompilerFactory.factory.PermPool.alloc(CallGenBase.CallGenBase.__sizeof__())
if SubCallGenBase is None:
stdio.printf("FAIL: CallGenBase alloc failed\n")
stdlib.exit(1)
string.memset(SubCallGenBase, 0, CallGenBase.CallGenBase.__sizeof__())
SubCallGenBase.Gen = self
self.CallGenBase = SubCallGenBase
SubCallGenCBuiltin: CallGenCBuiltin.CallGenCBuiltin | t.CPtr = CompilerFactory.get_perm_pool().alloc(CallGenCBuiltin.CallGenCBuiltin.__sizeof__())
SubCallGenCBuiltin: CallGenCBuiltin.CallGenCBuiltin* = CompilerFactory.factory.PermPool.alloc(CallGenCBuiltin.CallGenCBuiltin.__sizeof__())
if SubCallGenCBuiltin is None:
stdio.printf("FAIL: CallGenCBuiltin alloc failed\n")
stdlib.exit(1)
string.memset(SubCallGenCBuiltin, 0, CallGenCBuiltin.CallGenCBuiltin.__sizeof__())
SubCallGenCBuiltin.Gen = self
self.CallGenCBuiltin = SubCallGenCBuiltin
SubCallGenLeafKit: CallGenLeafKit.CallGenLeafKit | t.CPtr = CompilerFactory.get_perm_pool().alloc(CallGenLeafKit.CallGenLeafKit.__sizeof__())
SubCallGenLeafKit: CallGenLeafKit.CallGenLeafKit* = CompilerFactory.factory.PermPool.alloc(CallGenLeafKit.CallGenLeafKit.__sizeof__())
if SubCallGenLeafKit is None:
stdio.printf("FAIL: CallGenLeafKit alloc failed\n")
stdlib.exit(1)
string.memset(SubCallGenLeafKit, 0, CallGenLeafKit.CallGenLeafKit.__sizeof__())
SubCallGenLeafKit.Gen = self
self.CallGenLeafKit = SubCallGenLeafKit
SubCallGenCtor: CallGenCtor.CallGenCtor | t.CPtr = CompilerFactory.get_perm_pool().alloc(CallGenCtor.CallGenCtor.__sizeof__())
SubCallGenCtor: CallGenCtor.CallGenCtor* = CompilerFactory.factory.PermPool.alloc(CallGenCtor.CallGenCtor.__sizeof__())
if SubCallGenCtor is None:
stdio.printf("FAIL: CallGenCtor alloc failed\n")
stdlib.exit(1)
string.memset(SubCallGenCtor, 0, CallGenCtor.CallGenCtor.__sizeof__())
SubCallGenCtor.Gen = self
self.CallGenCtor = SubCallGenCtor
SubCallGenCall: CallGenCall.CallGenCall | t.CPtr = CompilerFactory.get_perm_pool().alloc(CallGenCall.CallGenCall.__sizeof__())
SubCallGenCall: CallGenCall.CallGenCall* = CompilerFactory.factory.PermPool.alloc(CallGenCall.CallGenCall.__sizeof__())
if SubCallGenCall is None:
stdio.printf("FAIL: CallGenCall alloc failed\n")
stdlib.exit(1)
string.memset(SubCallGenCall, 0, CallGenCall.CallGenCall.__sizeof__())
SubCallGenCall.Gen = self
self.CallGenCall = SubCallGenCall
SubCallGen: CallGenMixin.CallGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(CallGenMixin.CallGenMixin.__sizeof__())
SubCallGen: CallGenMixin.CallGenMixin* = CompilerFactory.factory.PermPool.alloc(CallGenMixin.CallGenMixin.__sizeof__())
if SubCallGen is None:
stdio.printf("FAIL: CallGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubCallGen, 0, CallGenMixin.CallGenMixin.__sizeof__())
SubCallGen.Gen = self
self.CallGen = SubCallGen
SubCallGenDispatch: CallGenDispatch.CallGenDispatch | t.CPtr = CompilerFactory.get_perm_pool().alloc(CallGenDispatch.CallGenDispatch.__sizeof__())
SubCallGenDispatch: CallGenDispatch.CallGenDispatch* = CompilerFactory.factory.PermPool.alloc(CallGenDispatch.CallGenDispatch.__sizeof__())
if SubCallGenDispatch is None:
stdio.printf("FAIL: CallGenDispatch alloc failed\n")
stdlib.exit(1)
string.memset(SubCallGenDispatch, 0, CallGenDispatch.CallGenDispatch.__sizeof__())
SubCallGenDispatch.Gen = self
self.CallGenDispatch = SubCallGenDispatch
SubCallGenLib: CallGenLib.CallGenLib | t.CPtr = CompilerFactory.get_perm_pool().alloc(CallGenLib.CallGenLib.__sizeof__())
SubCallGenLib: CallGenLib.CallGenLib* = CompilerFactory.factory.PermPool.alloc(CallGenLib.CallGenLib.__sizeof__())
if SubCallGenLib is None:
stdio.printf("FAIL: CallGenLib alloc failed\n")
stdlib.exit(1)
string.memset(SubCallGenLib, 0, CallGenLib.CallGenLib.__sizeof__())
SubCallGenLib.Gen = self
self.CallGenLib = SubCallGenLib
SubCallGenVt: CallGenVt.CallGenVt | t.CPtr = CompilerFactory.get_perm_pool().alloc(CallGenVt.CallGenVt.__sizeof__())
SubCallGenVt: CallGenVt.CallGenVt* = CompilerFactory.factory.PermPool.alloc(CallGenVt.CallGenVt.__sizeof__())
if SubCallGenVt is None:
stdio.printf("FAIL: CallGenVt alloc failed\n")
stdlib.exit(1)
string.memset(SubCallGenVt, 0, CallGenVt.CallGenVt.__sizeof__())
SubCallGenVt.Gen = self
self.CallGenVt = SubCallGenVt
SubCallGenVa: CallGenVa.CallGenVa | t.CPtr = CompilerFactory.get_perm_pool().alloc(CallGenVa.CallGenVa.__sizeof__())
SubCallGenVa: CallGenVa.CallGenVa* = CompilerFactory.factory.PermPool.alloc(CallGenVa.CallGenVa.__sizeof__())
if SubCallGenVa is None:
stdio.printf("FAIL: CallGenVa alloc failed\n")
stdlib.exit(1)
string.memset(SubCallGenVa, 0, CallGenVa.CallGenVa.__sizeof__())
SubCallGenVa.Gen = self
self.CallGenVa = SubCallGenVa
SubCallGenProp: CallGenProp.CallGenProp | t.CPtr = CompilerFactory.get_perm_pool().alloc(CallGenProp.CallGenProp.__sizeof__())
SubCallGenProp: CallGenProp.CallGenProp* = CompilerFactory.factory.PermPool.alloc(CallGenProp.CallGenProp.__sizeof__())
if SubCallGenProp is None:
stdio.printf("FAIL: CallGenProp alloc failed\n")
stdlib.exit(1)
string.memset(SubCallGenProp, 0, CallGenProp.CallGenProp.__sizeof__())
SubCallGenProp.Gen = self
self.CallGenProp = SubCallGenProp
SubCallGenFd: CallGenFd.CallGenFd | t.CPtr = CompilerFactory.get_perm_pool().alloc(CallGenFd.CallGenFd.__sizeof__())
SubCallGenFd: CallGenFd.CallGenFd* = CompilerFactory.factory.PermPool.alloc(CallGenFd.CallGenFd.__sizeof__())
if SubCallGenFd is None:
stdio.printf("FAIL: CallGenFd alloc failed\n")
stdlib.exit(1)
string.memset(SubCallGenFd, 0, CallGenFd.CallGenFd.__sizeof__())
SubCallGenFd.Gen = self
self.CallGenFd = SubCallGenFd
SubFuncGenBase: FuncGenBase.FuncGenBase | t.CPtr = CompilerFactory.get_perm_pool().alloc(FuncGenBase.FuncGenBase.__sizeof__())
SubFuncGenBase: FuncGenBase.FuncGenBase* = CompilerFactory.factory.PermPool.alloc(FuncGenBase.FuncGenBase.__sizeof__())
if SubFuncGenBase is None:
stdio.printf("FAIL: FuncGenBase alloc failed\n")
stdlib.exit(1)
string.memset(SubFuncGenBase, 0, FuncGenBase.FuncGenBase.__sizeof__())
SubFuncGenBase.Gen = self
self.FuncGenBase = SubFuncGenBase
SubFuncGenDef: FuncGenDef.FuncGenDef | t.CPtr = CompilerFactory.get_perm_pool().alloc(FuncGenDef.FuncGenDef.__sizeof__())
SubFuncGenDef: FuncGenDef.FuncGenDef* = CompilerFactory.factory.PermPool.alloc(FuncGenDef.FuncGenDef.__sizeof__())
if SubFuncGenDef is None:
stdio.printf("FAIL: FuncGenDef alloc failed\n")
stdlib.exit(1)
string.memset(SubFuncGenDef, 0, FuncGenDef.FuncGenDef.__sizeof__())
SubFuncGenDef.Gen = self
self.FuncGenDef = SubFuncGenDef
SubFuncGen: FuncGenMixin.FuncGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(FuncGenMixin.FuncGenMixin.__sizeof__())
SubFuncGen: FuncGenMixin.FuncGenMixin* = CompilerFactory.factory.PermPool.alloc(FuncGenMixin.FuncGenMixin.__sizeof__())
if SubFuncGen is None:
stdio.printf("FAIL: FuncGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubFuncGen, 0, FuncGenMixin.FuncGenMixin.__sizeof__())
SubFuncGen.Gen = self
self.FuncGen = SubFuncGen
SubClassGenBase: ClassGenBase.ClassGenBase | t.CPtr = CompilerFactory.get_perm_pool().alloc(ClassGenBase.ClassGenBase.__sizeof__())
SubClassGenBase: ClassGenBase.ClassGenBase* = CompilerFactory.factory.PermPool.alloc(ClassGenBase.ClassGenBase.__sizeof__())
if SubClassGenBase is None:
stdio.printf("FAIL: ClassGenBase alloc failed\n")
stdlib.exit(1)
string.memset(SubClassGenBase, 0, ClassGenBase.ClassGenBase.__sizeof__())
SubClassGenBase.Gen = self
self.ClassGenBase = SubClassGenBase
SubClassGen: ClassGenMixin.ClassGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(ClassGenMixin.ClassGenMixin.__sizeof__())
SubClassGen: ClassGenMixin.ClassGenMixin* = CompilerFactory.factory.PermPool.alloc(ClassGenMixin.ClassGenMixin.__sizeof__())
if SubClassGen is None:
stdio.printf("FAIL: ClassGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubClassGen, 0, ClassGenMixin.ClassGenMixin.__sizeof__())
SubClassGen.Gen = self
self.ClassGen = SubClassGen
SubClassGenEnum: ClassGenEnum.ClassGenEnum | t.CPtr = CompilerFactory.get_perm_pool().alloc(ClassGenEnum.ClassGenEnum.__sizeof__())
SubClassGenEnum: ClassGenEnum.ClassGenEnum* = CompilerFactory.factory.PermPool.alloc(ClassGenEnum.ClassGenEnum.__sizeof__())
if SubClassGenEnum is None:
stdio.printf("FAIL: ClassGenEnum alloc failed\n")
stdlib.exit(1)
string.memset(SubClassGenEnum, 0, ClassGenEnum.ClassGenEnum.__sizeof__())
SubClassGenEnum.Gen = self
self.ClassGenEnum = SubClassGenEnum
SubClassGenGeneric: ClassGenGeneric.ClassGenGeneric | t.CPtr = CompilerFactory.get_perm_pool().alloc(ClassGenGeneric.ClassGenGeneric.__sizeof__())
SubClassGenGeneric: ClassGenGeneric.ClassGenGeneric* = CompilerFactory.factory.PermPool.alloc(ClassGenGeneric.ClassGenGeneric.__sizeof__())
if SubClassGenGeneric is None:
stdio.printf("FAIL: ClassGenGeneric alloc failed\n")
stdlib.exit(1)
string.memset(SubClassGenGeneric, 0, ClassGenGeneric.ClassGenGeneric.__sizeof__())
SubClassGenGeneric.Gen = self
self.ClassGenGeneric = SubClassGenGeneric
SubClassGenVTable: ClassGenVTable.ClassGenVTable | t.CPtr = CompilerFactory.get_perm_pool().alloc(ClassGenVTable.ClassGenVTable.__sizeof__())
SubClassGenVTable: ClassGenVTable.ClassGenVTable* = CompilerFactory.factory.PermPool.alloc(ClassGenVTable.ClassGenVTable.__sizeof__())
if SubClassGenVTable is None:
stdio.printf("FAIL: ClassGenVTable alloc failed\n")
stdlib.exit(1)
string.memset(SubClassGenVTable, 0, ClassGenVTable.ClassGenVTable.__sizeof__())
SubClassGenVTable.Gen = self
self.ClassGenVTable = SubClassGenVTable
SubClassGenDef: ClassGenDef.ClassGenDef | t.CPtr = CompilerFactory.get_perm_pool().alloc(ClassGenDef.ClassGenDef.__sizeof__())
SubClassGenDef: ClassGenDef.ClassGenDef* = CompilerFactory.factory.PermPool.alloc(ClassGenDef.ClassGenDef.__sizeof__())
if SubClassGenDef is None:
stdio.printf("FAIL: ClassGenDef alloc failed\n")
stdlib.exit(1)
string.memset(SubClassGenDef, 0, ClassGenDef.ClassGenDef.__sizeof__())
SubClassGenDef.Gen = self
self.ClassGenDef = SubClassGenDef
SubImportsGen: ImportsGenMixin.ImportsGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(ImportsGenMixin.ImportsGenMixin.__sizeof__())
SubImportsGen: ImportsGenMixin.ImportsGenMixin* = CompilerFactory.factory.PermPool.alloc(ImportsGenMixin.ImportsGenMixin.__sizeof__())
if SubImportsGen is None:
stdio.printf("FAIL: ImportsGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubImportsGen, 0, ImportsGenMixin.ImportsGenMixin.__sizeof__())
SubImportsGen.Gen = self
self.ImportsGen = SubImportsGen
SubStmtGenCif: StmtGenCifMixin.StmtGenCifMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(StmtGenCifMixin.StmtGenCifMixin.__sizeof__())
SubStmtGenCif: StmtGenCifMixin.StmtGenCifMixin* = CompilerFactory.factory.PermPool.alloc(StmtGenCifMixin.StmtGenCifMixin.__sizeof__())
if SubStmtGenCif is None:
stdio.printf("FAIL: StmtGenCifMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubStmtGenCif, 0, StmtGenCifMixin.StmtGenCifMixin.__sizeof__())
SubStmtGenCif.Gen = self
self.StmtGenCif = SubStmtGenCif
SubStmtGenDecl: StmtGenDeclMixin.StmtGenDeclMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(StmtGenDeclMixin.StmtGenDeclMixin.__sizeof__())
SubStmtGenDecl: StmtGenDeclMixin.StmtGenDeclMixin* = CompilerFactory.factory.PermPool.alloc(StmtGenDeclMixin.StmtGenDeclMixin.__sizeof__())
if SubStmtGenDecl is None:
stdio.printf("FAIL: StmtGenDeclMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubStmtGenDecl, 0, StmtGenDeclMixin.StmtGenDeclMixin.__sizeof__())
SubStmtGenDecl.Gen = self
self.StmtGenDecl = SubStmtGenDecl
SubStmtGen: StmtGenMixin.StmtGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(StmtGenMixin.StmtGenMixin.__sizeof__())
SubStmtGen: StmtGenMixin.StmtGenMixin* = CompilerFactory.factory.PermPool.alloc(StmtGenMixin.StmtGenMixin.__sizeof__())
if SubStmtGen is None:
stdio.printf("FAIL: StmtGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubStmtGen, 0, StmtGenMixin.StmtGenMixin.__sizeof__())
SubStmtGen.Gen = self
self.StmtGen = SubStmtGen
SubMatchGen: MatchGenMixin.MatchGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(MatchGenMixin.MatchGenMixin.__sizeof__())
SubMatchGen: MatchGenMixin.MatchGenMixin* = CompilerFactory.factory.PermPool.alloc(MatchGenMixin.MatchGenMixin.__sizeof__())
if SubMatchGen is None:
stdio.printf("FAIL: MatchGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubMatchGen, 0, MatchGenMixin.MatchGenMixin.__sizeof__())
SubMatchGen.Gen = self
self.MatchGen = SubMatchGen
SubAssignGen: AssignGenMixin.AssignGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(AssignGenMixin.AssignGenMixin.__sizeof__())
SubAssignGen: AssignGenMixin.AssignGenMixin* = CompilerFactory.factory.PermPool.alloc(AssignGenMixin.AssignGenMixin.__sizeof__())
if SubAssignGen is None:
stdio.printf("FAIL: AssignGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubAssignGen, 0, AssignGenMixin.AssignGenMixin.__sizeof__())
SubAssignGen.Gen = self
self.AssignGen = SubAssignGen
SubVarGen: VarGenMixin.VarGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(VarGenMixin.VarGenMixin.__sizeof__())
SubVarGen: VarGenMixin.VarGenMixin* = CompilerFactory.factory.PermPool.alloc(VarGenMixin.VarGenMixin.__sizeof__())
if SubVarGen is None:
stdio.printf("FAIL: VarGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubVarGen, 0, VarGenMixin.VarGenMixin.__sizeof__())
SubVarGen.Gen = self
self.VarGen = SubVarGen
SubTranslatorGen: TranslatorGenMixin.TranslatorGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(TranslatorGenMixin.TranslatorGenMixin.__sizeof__())
SubTranslatorGen: TranslatorGenMixin.TranslatorGenMixin* = CompilerFactory.factory.PermPool.alloc(TranslatorGenMixin.TranslatorGenMixin.__sizeof__())
if SubTranslatorGen is None:
stdio.printf("FAIL: TranslatorGenMixin alloc failed\n")
stdlib.exit(1)
string.memset(SubTranslatorGen, 0, TranslatorGenMixin.TranslatorGenMixin.__sizeof__())
SubTranslatorGen.Gen = self
self.TranslatorGen = SubTranslatorGen
SubAsyncGen: AsyncGenMixin.AsyncGenMixin | t.CPtr = CompilerFactory.get_perm_pool().alloc(AsyncGenMixin.AsyncGenMixin.__sizeof__())
SubAsyncGen: AsyncGenMixin.AsyncGenMixin* = CompilerFactory.factory.PermPool.alloc(AsyncGenMixin.AsyncGenMixin.__sizeof__())
if SubAsyncGen is None:
stdio.printf("FAIL: AsyncGenMixin alloc failed\n")
stdlib.exit(1)
@@ -450,7 +440,7 @@ class Generator:
SubAsyncGen.Gen = self
self.AsyncGen = SubAsyncGen
# ---- 初始化各子对象字段(Init* 方法内联调用) ----
# ---- initialize sub-object fields (Init* methods called inline) ----
SubBaseGen.InitBaseGen(pool)
SubTypeGenBase.InitTypeGen()
SubCallGenBase.InitCallGen()
@@ -462,7 +452,5 @@ class Generator:
SubAssignGen.InitAssignGen()
# ============================================================
# 全局生成器单例(组合架构兼容层)
# ============================================================
_g_gen: Generator | t.CPtr = None
# global generator singleton (composition-architecture compatibility layer)
_g_gen: Generator* = None
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+474
View File
@@ -0,0 +1,474 @@
import lib.core.Gen.GenTypes as GenTypes
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdlib
import stdio
import viperlib
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.core.CompilerFactory as CompilerFactory
import lib.Projectrans.Config as Config
import lib.core.Gen.Stmt.StmtGenCifMixin as StmtGenCifMixin
import lib.core.Gen.GenApi as GenApi
# ============================================================
# StmtGenDeclMixin - StmtGen split 2/3: declaration/enum/nonlocal access
# ============================================================
# Constants
define ENUM_MAX = 64
define ENUM_MEMBER_MAX = 128
# ============================================================
# StmtGenDeclMixin - declaration/enum/nonlocal (mid layer)
novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
"""Declaration/enum/nonlocal access (StmtGen split 2/3, extends Cif)"""
def translate_global(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a global statement and record the global variable names"""
gn: ast.Global* = ast.Global*(node)
if gn is None: return 0
names: list[ast.AST*]* = gn.names
if names is None: return 0
n: sizet = len(names)
for i in range(int(n)):
nm_node: ast.AST* = names.get(i)
if nm_node is not None and nm_node.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(nm_node)
if nm.id is not None: GenApi.add_global_name(trans, nm.id)
return 0
def translate_nonlocal(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a nonlocal statement and record names with their types"""
nl: ast.Nonlocal* = ast.Nonlocal*(node)
if nl is None: return 0
names: list[ast.AST*]* = nl.names
if names is None: return 0
n: sizet = len(names)
for i in range(int(n)):
nm_node: ast.AST* = names.get(i)
if nm_node is not None and nm_node.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(nm_node)
if nm.id is not None:
# Look up the variable alloca in the parent scope to get its type
var_ty: mlirlite.LLVMType* = None
var_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if var_alloca is not None and var_alloca.Ty is not None:
var_ty = var_alloca.Ty.Pointee
# Treat Void as invalid (Pointee access can return Void); report and abort
# rather than emit illegal IR like "bitcast ... to void*" / "load void, void*".
if var_ty is not None:
match var_ty:
case mlirlite.LLVMType.Void():
self.Gen.TypeGenErr.fatal_error(nm_node, f"nonlocal variable {nm.id} has an invalid type (Void/unresolvable)", "TypeError")
case _:
pass
GenApi.add_nonlocal_name(trans, nm.id, var_ty)
return 0
def translate_expr_stmt(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate an expression statement (e.g. a printf call)"""
ex: ast.Expr* = ast.Expr*(node)
if ex is None: return 0
call_node: ast.AST* = ex.value
if call_node is None: return 0
ck: int = call_node.kind()
if ck != ast.ASTKind.Call:
# REPL mode (M2): bare expression statements in a fragment body (e.g. 1+2 / x / x + 1)
# are written to the ctx result area. Guarded by trans.ReplCtx; non-REPL behavior unchanged.
if trans is not None and trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_scope(trans.SymTab) != 0: self.Gen.VarGen.repl_expr_result(trans, call_node)
return 0
cl: ast.Call* = ast.Call*(call_node)
func_node: ast.AST* = cl.func
if func_node is None: return 0
func_name: str = self.Gen.ExprGenBase.get_func_name(func_node)
if func_name is None:
# Function name resolution failed: report and abort (fail-fast)
self.Gen.TypeGenErr.fatal_error(call_node, "failed to resolve the function name of the expression statement", "SyntaxError")
# printf / print take special paths (print is type-aware formatting, not mapped to printf)
if string.strcmp(func_name, "printf") == 0: self.Gen.CallGenLeafKit.translate_printf_call(trans.Pool, trans._cur_builder, trans.Module, cl, trans)
elif string.strcmp(func_name, "print") == 0: self.Gen.CallGenLeafKit.translate_print_call(trans.Pool, trans._cur_builder, trans.Module, cl, trans)
else:
# Generic call (result discarded). M3: a bare-name call to a session function also
# writes the result to ctx (classified by the session table's return token).
if trans is not None and trans.ReplCtx is not None and func_node.kind() == ast.ASTKind.Name and self.Gen.VarGen.repl_in_frag_scope(trans.SymTab) != 0:
sfn_ent: GenTypes.ReplFuncEntry* = self.Gen.VarGen.repl_find_func_entry(trans.ReplCtx, func_name)
if sfn_ent is not None and sfn_ent.RetTag != GenTypes.REPL_TY_VOID:
sfn_v: mlirlite.Value* = self.Gen.CallGen.translate_call(trans.Pool, trans._cur_builder, trans.Module, call_node, trans._funcs, trans._func_count, trans)
if sfn_v is not None:
ctx_v: mlirlite.Value* = self.Gen.VarGen.repl_ctx_ptr(trans, trans.ReplCtx, call_node)
if ctx_v is not None:
self.Gen.VarGen.repl_emit_result(trans, ctx_v, sfn_v, sfn_ent.RetTag, sfn_ent.RetBits, call_node)
return 0
self.Gen.CallGen.translate_call(trans.Pool, trans._cur_builder, trans.Module, call_node, trans._funcs, trans._func_count, trans)
return 0
def pre_scan_allocas(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Pre-scan AnnAssign statements and create their entry allocas early"""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.AnnAssign: return self.Gen.AssignGen.translate_annassign_prescan(trans, node)
if k == ast.ASTKind.If:
ifn: ast.If* = ast.If*(node)
if ifn.children is not None:
bn_pre_i: sizet = len(ifn.children)
for bi_pre_i in range(int(bn_pre_i)): self.pre_scan_allocas(trans, ifn.children.get(bi_pre_i))
if ifn.orelse is not None:
bn_pre_e: sizet = len(ifn.orelse)
for bi_pre_e in range(int(bn_pre_e)): self.pre_scan_allocas(trans, ifn.orelse.get(bi_pre_e))
return 0
if k == ast.ASTKind.For:
for_n: ast.For* = ast.For*(node)
if for_n.children is not None:
bn_pre_f: sizet = len(for_n.children)
for bi_pre_f in range(int(bn_pre_f)): self.pre_scan_allocas(trans, for_n.children.get(bi_pre_f))
if for_n.orelse is not None:
bn_pre_fe: sizet = len(for_n.orelse)
for bi_pre_fe in range(int(bn_pre_fe)): self.pre_scan_allocas(trans, for_n.orelse.get(bi_pre_fe))
return 0
if k == ast.ASTKind.While:
wh_n: ast.While* = ast.While*(node)
if wh_n.children is not None:
bn_pre_w: sizet = len(wh_n.children)
for bi_pre_w in range(int(bn_pre_w)): self.pre_scan_allocas(trans, wh_n.children.get(bi_pre_w))
if wh_n.orelse is not None:
bn_pre_we: sizet = len(wh_n.orelse)
for bi_pre_we in range(int(bn_pre_we)): self.pre_scan_allocas(trans, wh_n.orelse.get(bi_pre_we))
return 0
if k == ast.ASTKind.Try:
tr_n: ast.Try* = ast.Try*(node)
if tr_n.children is not None:
bn_pre_t: sizet = len(tr_n.children)
for bi_pre_t in range(int(bn_pre_t)): self.pre_scan_allocas(trans, tr_n.children.get(bi_pre_t))
if tr_n.handlers is not None:
bn_pre_th: sizet = len(tr_n.handlers)
for bi_pre_th in range(int(bn_pre_th)):
eh_pre: ast.AST* = tr_n.handlers.get(bi_pre_th)
if eh_pre is not None and eh_pre.kind() == ast.ASTKind.ExceptHandler:
ehc_pre: ast.ExceptHandler* = ast.ExceptHandler*(eh_pre)
if ehc_pre.children is not None:
bn_pre_ehb: sizet = len(ehc_pre.children)
for bi_pre_ehb in range(int(bn_pre_ehb)): self.pre_scan_allocas(trans, ehc_pre.children.get(bi_pre_ehb))
if tr_n.orelse is not None:
bn_pre_to: sizet = len(tr_n.orelse)
for bi_pre_to in range(int(bn_pre_to)): self.pre_scan_allocas(trans, tr_n.orelse.get(bi_pre_to))
if tr_n.finalbody is not None:
bn_pre_tf: sizet = len(tr_n.finalbody)
for bi_pre_tf in range(int(bn_pre_tf)): self.pre_scan_allocas(trans, tr_n.finalbody.get(bi_pre_tf))
return 0
if k == ast.ASTKind.With:
wth: ast.With* = ast.With*(node)
if wth.children is not None:
bn_pre_wb: sizet = len(wth.children)
for bi_pre_wb in range(int(bn_pre_wb)): self.pre_scan_allocas(trans, wth.children.get(bi_pre_wb))
return 0
if k == ast.ASTKind.Match:
mt_n: ast.Match* = ast.Match*(node)
if mt_n.cases is not None:
bn_pre_mc: sizet = len(mt_n.cases)
for bi_pre_mc in range(int(bn_pre_mc)):
mc_pre: ast.AST* = mt_n.cases.get(bi_pre_mc)
if mc_pre is not None and mc_pre.kind() == ast.ASTKind.MatchCase:
mcc_pre: ast.MatchCase* = ast.MatchCase*(mc_pre)
if mcc_pre.children is not None:
bn_pre_mcb: sizet = len(mcc_pre.children)
for bi_pre_mcb in range(int(bn_pre_mcb)): self.pre_scan_allocas(trans, mcc_pre.children.get(bi_pre_mcb))
return 0
return 0
def pre_scan_scope_decls(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Pre-scan Global/Nonlocal declarations and register scope names early"""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Global: return self.translate_global(trans, node)
if k == ast.ASTKind.Nonlocal: return self.translate_nonlocal(trans, node)
return 0
def translate_break(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a break statement: branch to the break target"""
builder: mlirlite.IRBuilder* = trans._cur_builder
func: mlirlite.Function* = trans._cur_func
pool: memhub.MemBuddy* = trans.Pool
if builder is None or func is None: return 0
# Emit br to the break target (_break_bb set by the loop / switch / match case side)
if trans._break_bb is None:
# fail-fast: with no loop / switch / match there is nowhere to jump; never silently no-op
self.Gen.TypeGenErr.fatal_error(node, "break is only allowed inside a loop or a switch/match case body (there is no target to break out of)", "SyntaxError")
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, trans._break_bb)
# Create a dead-code BB for following statements (code after break is unreachable)
cnt: int = trans._label_counter
trans._label_counter = cnt + 1
name_buf: str = pool.alloc(32)
viperlib.snprintf(name_buf, 32, "dead.%d", cnt)
dead_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
mlirlite.position_at_end(builder, dead_bb)
return 0
def translate_fallthrough(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a fallthrough statement: emits no IR, consumed by the switch side"""
if trans is None: return 0
if trans._in_switch_case == 0:
self.Gen.TypeGenErr.fatal_error(node, "fallthrough is only allowed inside a switch case body (fallthrough is completely forbidden in match; " "the semantics are falling through to the next case)", "SyntaxError")
return 0
def translate_continue(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a continue statement: branch to the loop cond/incr block"""
builder: mlirlite.IRBuilder* = trans._cur_builder
func: mlirlite.Function* = trans._cur_func
pool: memhub.MemBuddy* = trans.Pool
if builder is None or func is None: return 0
# Emit br to the continue target
if trans._continue_bb is None:
# fail-fast: a continue outside a loop has nowhere to jump; never silently no-op
self.Gen.TypeGenErr.fatal_error(node, "continue is only allowed inside a loop body (there is no enclosing loop to continue)", "SyntaxError")
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, trans._continue_bb)
# Create a dead-code BB for following statements
cnt: int = trans._label_counter
trans._label_counter = cnt + 1
name_buf: str = pool.alloc(32)
viperlib.snprintf(name_buf, 32, "dead.%d", cnt)
dead_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
mlirlite.position_at_end(builder, dead_bb)
return 0
def get_stmt_kind_name(self, node: ast.AST*) -> str:
"""Return the statement type name"""
if node is None: return None
return node.type_name()
# ============================================================
# Cluster D: nonlocal/closure env access (migrated from HandlesNonlocal.vp)
def _is_void_type(self, ty: mlirlite.LLVMType*) -> int:
"""Return 1 if the type is Void, else 0"""
if ty is None: return 0
match ty:
case mlirlite.LLVMType.Void():
return 1
case _:
return 0
def get_env_ptr_var(self, trans: HT.Translator*) -> mlirlite.Value*:
"""Get or create the _env_ptr variable holding the closure env pointer"""
env_var: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, "_env_ptr")
if env_var is not None: return env_var
# Create an alloca to store env
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
alloca: mlirlite.Value* = mlirlite.build_alloca(builder, i8_ptr_ty)
if alloca is None: return None
self.Gen.VarGen.define_var(trans.SymTab, "_env_ptr", alloca)
return alloca
def _load_raw_var_ptr(self, trans: HT.Translator*, name: str) -> mlirlite.Value*:
"""Load the raw pointer (i8*) of a nonlocal variable from the closure env"""
idx: int = GenApi.get_nonlocal_index(trans, name)
if idx < 0: return None
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
i8_ptr_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty)
# 1. Load env_ptr
env_alloca: mlirlite.Value* = self.get_env_ptr_var(trans)
if env_alloca is None: return None
env_ptr: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, env_alloca)
if env_ptr is None: return None
# 2. GEP to offset (idx * 8) - env is an array of pointers, 8 bytes per slot
offset_val: mlirlite.Value* = mlirlite.const_int32(pool, idx * 8)
addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, env_ptr, offset_val)
if addr is None: return None
# 3. Bitcast to i8** and load the raw pointer
ptr_addr: mlirlite.Value* = mlirlite.build_bitcast(builder, addr, i8_ptr_ptr_ty)
if ptr_addr is None: return None
return mlirlite.build_load(builder, i8_ptr_ty, ptr_addr)
def load_nonlocal_var(self, trans: HT.Translator*, name: str) -> mlirlite.Value*:
"""Load the value of a nonlocal variable from the closure env"""
raw_ptr: mlirlite.Value* = self._load_raw_var_ptr(trans, name)
if raw_ptr is None: return None
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
# Get the variable type (from the type info stored at registration)
var_ty: mlirlite.LLVMType* = GenApi.get_nonlocal_type(trans, name)
if var_ty is None or self._is_void_type(var_ty) != 0:
# fail-fast on a missing/invalid type, avoiding illegal IR like "load void, void*"
self.Gen.TypeGenErr.fatal_error(None, f"nonlocal variable {name} is missing its type; cannot load", "TypeError")
# bitcast raw i8* to var_ty*
var_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, var_ty)
var_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, raw_ptr, var_ptr_ty)
if var_ptr is None: return None
return mlirlite.build_load(builder, var_ty, var_ptr)
def get_nonlocal_var_ptr(self, trans: HT.Translator*, name: str) -> mlirlite.Value*:
"""Get the storage pointer (var_ty*) of a nonlocal variable for store operations"""
raw_ptr: mlirlite.Value* = self._load_raw_var_ptr(trans, name)
if raw_ptr is None: return None
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
# Get the variable type
var_ty: mlirlite.LLVMType* = GenApi.get_nonlocal_type(trans, name)
if var_ty is None or self._is_void_type(var_ty) != 0:
# Missing/invalid type aborts (fail-fast) to avoid illegal "bitcast ... to void*" IR
self.Gen.TypeGenErr.fatal_error(None, f"nonlocal variable {name} is missing its type; cannot load", "TypeError")
# bitcast raw i8* to var_ty*
var_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, var_ty)
return mlirlite.build_bitcast(builder, raw_ptr, var_ptr_ty)
# ============================================================
# Cluster E: enum registry (migrated from HandlesEnum.vp)
def init_enum_table(self, pool: memhub.MemBuddy*) -> int:
"""Initialize the enum registry; returns 1 on success"""
if self.EnumTable is not None: return 1
entry_size: sizet = GenTypes.EnumEntry.__sizeof__()
self.EnumTable = CompilerFactory.factory.PermPool.alloc(entry_size * ENUM_MAX)
if self.EnumTable is None: return 0
string.memset(self.EnumTable, 0, entry_size * ENUM_MAX)
self.EnumCount = 0
return 1
def _get_enum_entry(self, i: int) -> GenTypes.EnumEntry*:
"""Get the i-th enum entry"""
if self.EnumTable is None or i < 0 or i >= ENUM_MAX: return None
entry_size: sizet = GenTypes.EnumEntry.__sizeof__()
addr: uint64 = uint64(self.EnumTable) + i * entry_size
return GenTypes.EnumEntry*(addr)
def _get_enum_member(self, enum_entry: GenTypes.EnumEntry*, i: int) -> GenTypes.EnumMember*:
"""Get the i-th enum member entry"""
if enum_entry is None or i < 0 or i >= ENUM_MEMBER_MAX: return None
member_size: sizet = GenTypes.EnumMember.__sizeof__()
addr: uint64 = uint64(enum_entry.Members) + i * member_size
return GenTypes.EnumMember*(addr)
def register_enum(self, pool: memhub.MemBuddy*, name: str, base_ty: mlirlite.LLVMType*) -> GenTypes.EnumEntry*:
"""Register an enum type and return its EnumEntry pointer"""
if self.init_enum_table(pool) == 0: return None
# Check whether it is already registered
existing: GenTypes.EnumEntry* = self.find_enum(name)
if existing is not None: return existing
if self.EnumCount >= ENUM_MAX:
VLogger.error(f"table full, cannot register {name}", "ENUM")
return None
entry: GenTypes.EnumEntry* = self._get_enum_entry(self.EnumCount)
if entry is None: return None
# Allocate the member array (permanent pool: survives mark/reset like EnumTable)
member_size: sizet = GenTypes.EnumMember.__sizeof__()
entry.Members = CompilerFactory.factory.PermPool.alloc(member_size * ENUM_MEMBER_MAX)
if entry.Members is None: return None
string.memset(entry.Members, 0, member_size * ENUM_MEMBER_MAX)
# Copy the class name (permanent-pool deep copy)
name_len: sizet = string.strlen(name)
name_buf: str = CompilerFactory.factory.PermPool.alloc(name_len + 1)
if name_buf is not None:
string.strcpy(name_buf, name)
entry.Name = name_buf
# Deep-copy BaseTy: LLVMType is pool-allocated and would dangle after reset under
# StdlibAlloc=1. StdlibAlloc is a StructGenMixin field, so delegate via the module.
if self.Gen.StructGen.get_stdlib_alloc() != 0:
base_ty_copy: mlirlite.LLVMType* = mlirlite.DeepCopyType(CompilerFactory.factory.PermPool, base_ty)
if base_ty_copy is not None: entry.BaseTy = base_ty_copy
else: entry.BaseTy = base_ty
else: entry.BaseTy = base_ty
entry.MemberCount = 0
self.EnumCount += 1
return entry
def add_enum_member(self, pool: memhub.MemBuddy*, enum_entry: GenTypes.EnumEntry*, member_name: str, member_val: int64, member_ty: mlirlite.LLVMType*) -> int:
"""Add a member to an enum; returns the member index or -1 on failure"""
if enum_entry is None or member_name is None or member_ty is None: return -1
# Dedup: skip an existing member with the same name (cross-module enum merging, e.g.
# win32.fileio and fat32_types both define FRESULT).
for ei_d in range(int(enum_entry.MemberCount)):
ex_d: GenTypes.EnumMember* = self._get_enum_member(enum_entry, ei_d)
if ex_d is not None and ex_d.Name is not None:
if string.strcmp(ex_d.Name, member_name) == 0: return ei_d
if enum_entry.MemberCount >= ENUM_MEMBER_MAX:
VLogger.error(f"member table full for {enum_entry.Name}", "ENUM")
return -1
idx: int = enum_entry.MemberCount
me: GenTypes.EnumMember* = self._get_enum_member(enum_entry, idx)
if me is None: return -1
# Copy the member name (permanent-pool deep copy: the member table survives mark/reset)
name_len: sizet = string.strlen(member_name)
name_buf: str = CompilerFactory.factory.PermPool.alloc(name_len + 1)
if name_buf is not None:
string.strcpy(name_buf, member_name)
me.Name = name_buf
me.Value = member_val
me.Ty = member_ty
enum_entry.MemberCount = idx + 1
return idx
def find_enum(self, name: str) -> GenTypes.EnumEntry*:
"""Look up an enum by class name; returns EnumEntry or None"""
if name is None or self.EnumTable is None: return None
for i in range(int(self.EnumCount)):
entry: GenTypes.EnumEntry* = self._get_enum_entry(i)
if entry is not None and entry.Name is not None:
if string.strcmp(entry.Name, name) == 0: return entry
return None
def lookup_enum_member(self, enum_name: str, member_name: str) -> GenTypes.EnumMember*:
"""Look up a member by enum class name and member name; returns EnumMember or None"""
if enum_name is None or member_name is None: return None
entry: GenTypes.EnumEntry* = self.find_enum(enum_name)
if entry is None: return None
for mi in range(int(entry.MemberCount)):
me: GenTypes.EnumMember* = self._get_enum_member(entry, mi)
if me is not None and me.Name is not None:
if string.strcmp(me.Name, member_name) == 0: return me
return None
def is_enum_class(self, name: str) -> int:
"""Return 1 if the class name is a registered enum, else 0"""
if name is None: return 0
if self.find_enum(name) is not None: return 1
return 0
def get_enum_base_type(self, enum_name: str) -> mlirlite.LLVMType*:
"""Get the base type for an enum class name"""
entry: GenTypes.EnumEntry* = self.find_enum(enum_name)
if entry is not None: return entry.BaseTy
return None
+634
View File
@@ -0,0 +1,634 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import lib.core.Gen.Call.CallGenFd as CallGenFd
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdlib
import stdio
import viperlib
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.core.CompilerFactory as CompilerFactory
import lib.Projectrans.Config as Config
import lib.core.Gen.Stmt.StmtGenDeclMixin as StmtGenDeclMixin
import lib.core.Gen.DebugInfoGen as DebugInfoGen
# StmtGenMixin - statement/flow translation Mixin (P6 object refactor group A, top-level)
# StmtGenMixin - statement dispatch hub (top level)
novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
"""Statement/flow translation Mixin (top level, extends Decl)"""
def translate_stmt(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a single statement with a depth guard; returns the number of new variables"""
if trans is not None:
# --traceback: statement-level DWARF location (no-op outside a DI-attached
# define). Every instruction then carries ", !dbg !N".
if node is not None and trans._cur_builder is not None:
DebugInfoGen.SetStatement(trans.Pool, trans._cur_builder, node.lineno, node.col_offset + 1)
# 注意:**不要**用当前语句位置覆盖栈帧(那会把“函数定义帧”与错误帧叠成同一位置、
# 被去重合并,反而丢掉 `_start` 那帧)。栈帧保留“被调函数的定义位置”,
# “当前语句/具体表达式”由错误帧给出(见 TypeGenErr 的 CurArg 兜底)。
# 仅在语句边界清掉上一条语句遗留的实参位置,避免兜底定位串味。
if node is not None:
self.Gen.TypeGenBase.set_cur_arg(None, 0, 0)
if trans.TranslateDepth >= HT.TRANSLATE_MAX_DEPTH: self.Gen.TypeGenErr.fatal_error(node, "statement translation nested too deeply (likely an AST self-reference cycle or a translation bug)", "RecursionError")
trans.TranslateDepth += 1
mret: int = self._translate_stmt_impl(trans, node)
if trans is not None: trans.TranslateDepth -= 1
return mret
def _translate_stmt_impl(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a single statement; returns the number of new variables"""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Expr: return self.translate_expr_stmt(trans, node)
elif k == ast.ASTKind.Assign: return self.Gen.AssignGen.translate_assign(trans, node)
elif k == ast.ASTKind.AnnAssign: return self.Gen.AssignGen.translate_annassign(trans, node)
elif k == ast.ASTKind.FunctionDef:
# nested function def: hoist to a top-level function and build a closure
# 函数体翻译 = 下潜进一个源码上下文:推一帧(def 位置),供 traceback 回溯。
nfd_t: ast.FunctionDef* = ast.FunctionDef*(node)
tr_nf: str = self.Gen.TypeGenResolve.get_current_file()
tr_nl: int = 0
if nfd_t is not None: tr_nl = nfd_t.lineno
# §13 NLL:记录当前函数体(谓词 NllFrameConfined 要遍历它)
prev_nf: ast.AST* = self.Gen.StructGen.NllCurFunc
self.Gen.StructGen.NllCurFunc = node
self.Gen.TypeGenBase.push_trace(tr_nf, tr_nl)
rc_nf: int = self.Gen.FuncGen.translate_nested_function_def(trans, node)
self.Gen.TypeGenBase.pop_trace()
self.Gen.StructGen.NllCurFunc = prev_nf
return rc_nf
elif k == ast.ASTKind.Return: return self.Gen.StmtGenCif.translate_return(trans, node)
elif k == ast.ASTKind.If: return self.Gen.StmtGenCif.translate_if(trans, node)
elif k == ast.ASTKind.While:
# 循环生命期汇合:循环可能 0 次执行,出口状态须与进入前状态 Meet(见 §11 控制流 Meet)
snap_wh: sizet* = self.Gen.VarGen.life_capture(trans)
# willFree 失效槽表:同点位快照,出口与进入前状态**求交**(循环体可能 0 次执行,
# 故体内 free 不得被当成"循环后已失效")
freed_snap_wh: sizet* = self.Gen.VarGen.freed_capture(trans)
rc_wh: int = self.Gen.StmtGenCif.translate_while(trans, node)
self.Gen.VarGen.life_join(snap_wh)
self.Gen.VarGen.freed_join(freed_snap_wh)
return rc_wh
elif k == ast.ASTKind.AugAssign: return self.Gen.AssignGen.translate_augassign(trans, node)
elif k == ast.ASTKind.For:
# 同上:for 的各迭代形态(range/指针/数组/容器/解糖)统一在此收口
snap_fr: sizet* = self.Gen.VarGen.life_capture(trans)
freed_snap_fr: sizet* = self.Gen.VarGen.freed_capture(trans)
rc_fr: int = self.Gen.StmtGenCif.translate_for(trans, node)
self.Gen.VarGen.life_join(snap_fr)
self.Gen.VarGen.freed_join(freed_snap_fr)
return rc_fr
elif k == ast.ASTKind.ClassDef: return self.Gen.ClassGenDef.translate_class_def(trans, node)
elif k == ast.ASTKind.Import: return self.Gen.ImportsGen.translate_import(trans, node)
elif k == ast.ASTKind.ImportFrom:
self.Gen.ImportsGen.translate_import_from_module(trans, node)
return self.Gen.ImportsGen.translate_import_from_names(trans, node)
elif k == ast.ASTKind.Global: return self.translate_global(trans, node)
elif k == ast.ASTKind.Nonlocal: return self.translate_nonlocal(trans, node)
elif k == ast.ASTKind.Pass: return 0
elif k == ast.ASTKind.Break: return self.translate_break(trans, node)
elif k == ast.ASTKind.Continue: return self.translate_continue(trans, node)
elif k == ast.ASTKind.FallThrough: return self.translate_fallthrough(trans, node)
elif k == ast.ASTKind.Match: return self.Gen.MatchGen.translate_match(trans, node)
elif k == ast.ASTKind.Delete: return self.translate_delete(trans, node)
elif k == ast.ASTKind.Raise: return self.translate_raise(trans, node)
elif k == ast.ASTKind.Assert: return self.translate_assert(trans, node)
elif k == ast.ASTKind.Try: return self.translate_try(trans, node)
elif k == ast.ASTKind.With: return self.translate_with(trans, node)
# unknown statement type: fail-fast to expose an upstream parse problem
self.Gen.TypeGenErr.fatal_error(node, f"unknown statement type kind={k}", "SyntaxError")
return 0
def translate_delete(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a del statement; returns 0"""
if trans is None or node is None: return 0
dnode: ast.Delete* = ast.Delete*(node)
if dnode is None or dnode.targets is None: return 0
pool: memhub.MemBuddy* = trans.Pool
mod: mlirlite.LLVMModule* = trans.Module
builder: mlirlite.IRBuilder* = trans._cur_builder
if pool is None or mod is None or builder is None: return 0
tn: sizet = len(dnode.targets)
for ti in range(int(tn)):
tgt: ast.AST* = dnode.targets.get(ti)
if tgt is None: continue
# del obj -> __delete__ (matches TPC HandlesDelete.py; no-op if undefined)
if tgt.kind() == ast.ASTKind.Name:
# §2.4 判据5:slife 对象由编译器独占(出口自动 __del__ + free),手动 del 会让析构跑两次
nm_del: ast.Name* = ast.Name*(tgt)
if nm_del is not None and nm_del.id is not None:
del_slot: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm_del.id)
if self.Gen.VarGen._SlifeOwned(del_slot) != 0:
self.Gen.TypeGenErr.fatal_error(tgt, f"cannot `del {nm_del.id}`: it is a compiler-owned `slife` object (freed automatically at scope exit); a manual del would double-free it", "LifetimeError")
del_obj: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, tgt, None, 0, trans)
if del_obj is not None:
del_st_nm: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(del_obj)
if del_st_nm is not None:
del_cls_nm: str = self.Gen.StructGen.get_class_name_by_type(pool, del_st_nm)
if del_cls_nm is not None: self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, del_obj, "__delete__", trans)
# del obj.attr -> property deleter; falls back to __del__ (matches TPC)
elif tgt.kind() == ast.ASTKind.Attribute:
del_at: ast.Attribute* = ast.Attribute*(tgt)
if del_at is not None:
del_prop: int = self.Gen.CallGenProp._try_property_deleter(pool, builder, mod, del_at, trans)
if del_prop == 0:
del_av: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, del_at, None, 0, trans)
if del_av is not None: self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, del_av, "__del__", trans)
# del obj[key] -> __delitem__(obj, key) (matches TPC); ignoring Subscript
# targets would silently leave del ineffective.
elif tgt.kind() == ast.ASTKind.Subscript:
dl_sub: ast.Subscript* = ast.Subscript*(tgt)
if dl_sub is not None and dl_sub.value is not None and dl_sub.slice is not None:
dl_obj: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, dl_sub.value, None, 0, trans)
dl_idx: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, dl_sub.slice, None, 0, trans)
if dl_obj is not None and dl_idx is not None:
dl_st: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(dl_obj)
if dl_st is not None:
dl_cls: str = self.Gen.StructGen.get_class_name_by_type(pool, dl_st)
if dl_cls is not None:
dl_lookup: str = pool.alloc(128)
if dl_lookup is not None:
viperlib.snprintf(dl_lookup, 128, "%s.__delitem__", dl_cls)
dl_fn: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, dl_lookup)
if dl_fn is not None:
dl_ret: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(dl_fn)
dl_fn_name: str = mlirlite.function_get_name(dl_fn)
if dl_ret is not None and dl_fn_name is not None:
# self param (container) aligned to __delitem__'s first param
dl_self: mlirlite.Value* = dl_obj
dl_first: mlirlite.Param* = mlirlite.function_get_param_head(dl_fn)
if dl_first is not None:
dl_self_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(dl_first)
if dl_self_ty is not None: dl_self = mlirlite.build_bitcast(builder, dl_obj, dl_self_ty)
if dl_self is not None:
# param list: self -> key (separate null terminator)
dl_self.Next = dl_idx
dl_idx.Next = None
mlirlite.build_call(builder, dl_fn_name, dl_self, 2, dl_ret, 0)
return 0
# translate_assert - translate `assert cond[, msg]` into a single testcheck.check(...) call:
# a true condition is silent (ok_msg=None), a false one reports FAIL via the standard path
# (no printf/exit/exception; the checker prints nothing when the condition holds).
def translate_assert(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate an assert statement; returns 0"""
if trans is None or node is None: return 0
asn: ast.Assert* = ast.Assert*(node)
if asn is None or asn.test is None: return 0
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
if pool is None or builder is None or mod is None: return 0
# Resolve the message argument: omitted -> "assertion failed". A non-string literal is
# rejected here (clear report); a non-string expression is caught by the call's int->ptr rule.
msg_node: ast.AST* = asn.msg
if msg_node is None:
msg_node = ast.Constant(pool, ast.CONST_STR, 0, 0.0, "assertion failed", node.lineno, node.col_offset)
if msg_node is None: return 0
elif msg_node.kind() == ast.ASTKind.Constant:
msg_c: ast.Constant* = ast.Constant*(msg_node)
if msg_c is None or msg_c.const_kind != ast.CONST_STR:
self.Gen.TypeGenErr.fatal_error(msg_node, "assert message must be a string (str)", "TypeError")
return 0
# Synthesize `testcheck.check(<test>, None, <msg>)` and reuse the normal call translation
# (cross-module name resolution + argument coercion stay in one place).
none_node: ast.AST* = ast.Constant(pool, ast.CONST_NONE, 0, 0.0, None, node.lineno, node.col_offset)
if none_node is None: return 0
args_list: list[ast.AST*]* = list[ast.AST*](pool, 8)
if args_list is None: return 0
args_list.append(asn.test)
args_list.append(none_node)
args_list.append(msg_node)
mod_name_node: ast.AST* = ast.Name(pool, "testcheck", ast.ASTCtx.Load)
if mod_name_node is None: return 0
check_attr: ast.AST* = ast.Attribute(pool, mod_name_node, "check", ast.ASTCtx.Load)
if check_attr is None: return 0
call_node: ast.AST* = ast.Call(pool, check_attr, args_list, None)
if call_node is None: return 0
self.Gen.ExprGen.translate_value(builder, pool, mod, call_node, trans._funcs, trans._func_count, trans)
return 0
# translate_try - try/except translation. Exception = (i32 code, i8* msg);
# except X statically expands X and all descendant exception codes.
def translate_try(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a try/except statement; returns 0"""
if trans is None or node is None: return 0
tr: ast.Try* = ast.Try*(node)
if tr is None: return 0
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
func: mlirlite.Function* = trans._cur_func
if pool is None or builder is None or func is None: return 0
if mlirlite.builder_cur_block_is_terminated(builder) != 0: return 0
# ---- 1. exception code/message alloca (current try context) ----
i32_ty_t: mlirlite.LLVMType* = mlirlite.Int32(pool)
i8_ty_t: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty_t: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_t)
if i32_ty_t is None or i8_ptr_ty_t is None: return 0
exception_code: mlirlite.Value* = mlirlite.build_alloca(builder, i32_ty_t)
eh_message: mlirlite.Value* = mlirlite.build_alloca(builder, i8_ptr_ty_t)
if exception_code is None or eh_message is None: return 0
init_code_t: mlirlite.Value* = mlirlite.const_int32(pool, 0)
init_msg_t: mlirlite.Value* = mlirlite.ConstNull(pool, i8_ptr_ty_t, "null")
if init_code_t is not None: mlirlite.build_store(builder, init_code_t, exception_code)
if init_msg_t is not None: mlirlite.build_store(builder, init_msg_t, eh_message)
# ---- 2. block layout (unique names; create_block does not auto-number) ----
lc_t: int = trans._label_counter
trans._label_counter = lc_t + 1
bb_buf_t: str = pool.alloc(64)
if bb_buf_t is None: return 0
viperlib.snprintf(bb_buf_t, 64, "try.dispatch.%d", lc_t)
dispatch_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, bb_buf_t)
viperlib.snprintf(bb_buf_t, 64, "try.after.%d", lc_t)
after_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, bb_buf_t)
viperlib.snprintf(bb_buf_t, 64, "try.unhandled.%d", lc_t)
unhandled_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, bb_buf_t)
viperlib.snprintf(bb_buf_t, 64, "try.body.%d", lc_t)
try_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, bb_buf_t)
if dispatch_bb is None or after_bb is None or unhandled_bb is None or try_bb is None: return 0
else_bb: mlirlite.BasicBlock* = None
if tr.orelse is not None and len(tr.orelse) > 0:
viperlib.snprintf(bb_buf_t, 64, "try.else.%d", lc_t)
else_bb = mlirlite.create_block(pool, func, bb_buf_t)
n_handlers_t: sizet = 0
if tr.handlers is not None: n_handlers_t = len(tr.handlers)
# NOTE: list[ptr](pool, N)'s second arg is elem_size, not capacity; pointer
# elements must pass 8, else append strides wrongly and reads garbage.
except_bbs: list[mlirlite.BasicBlock*]* = list[mlirlite.BasicBlock*](pool, 8)
match_bbs: list[mlirlite.BasicBlock*]* = list[mlirlite.BasicBlock*](pool, 8)
if except_bbs is None or match_bbs is None: return 0
for i_t in range(int(n_handlers_t)):
viperlib.snprintf(bb_buf_t, 64, "try.except.%d.%d", lc_t, i_t)
eb_t: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, bb_buf_t)
viperlib.snprintf(bb_buf_t, 64, "try.match.%d.%d", lc_t, i_t)
mb_t: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, bb_buf_t)
except_bbs.append(eb_t)
match_bbs.append(mb_t)
# ---- 3. save outer try context, set this try context ----
old_dispatch_t: mlirlite.BasicBlock* = trans._eh_dispatch_bb
old_code_t: mlirlite.Value* = trans._eh_code_ptr
old_msg_t: mlirlite.Value* = trans._eh_msg_ptr
trans._eh_dispatch_bb = dispatch_bb
trans._eh_code_ptr = exception_code
trans._eh_msg_ptr = eh_message
# ---- 4. try body ----
mlirlite.build_br(builder, try_bb)
mlirlite.position_at_end(builder, try_bb)
if tr.children is not None:
tb_n: sizet = len(tr.children)
for tbi in range(int(tb_n)):
stmt_t: ast.AST* = tr.children.get(tbi)
if stmt_t is not None: self.translate_stmt(trans, stmt_t)
# try body completed: clear the code, go to else (if any) or After
if mlirlite.builder_cur_block_is_terminated(builder) == 0:
if init_code_t is not None: mlirlite.build_store(builder, init_code_t, exception_code)
if else_bb is not None: mlirlite.build_br(builder, else_bb)
else: mlirlite.build_br(builder, after_bb)
# ---- 5. DispatchBB: code check chain ----
mlirlite.position_at_end(builder, dispatch_bb)
if n_handlers_t == 0:
# no except (invalid try; defensive): swallow
mlirlite.build_br(builder, unhandled_bb)
else:
eh_code_val: mlirlite.Value* = mlirlite.build_load(builder, i32_ty_t, exception_code)
next_check_t: mlirlite.BasicBlock* = None
for i_t in range(int(n_handlers_t)):
hdr_t: ast.ExceptHandler* = ast.ExceptHandler*(tr.handlers.get(i_t))
match_bb_t: mlirlite.BasicBlock* = match_bbs.get(i_t)
if i_t > 0 and next_check_t is not None:
mlirlite.position_at_end(builder, next_check_t)
eh_code_val = mlirlite.build_load(builder, i32_ty_t, exception_code)
if hdr_t is None or hdr_t.type is None:
# bare except: unconditional match
mlirlite.build_br(builder, match_bb_t)
else:
# expand the match code set (handler.type + all descendant classes)
hname_t: str = None
htype_t: ast.AST* = hdr_t.type
if htype_t.kind() == ast.ASTKind.Name: hname_t = (ast.Name*(htype_t)).id
elif htype_t.kind() == ast.ASTKind.Attribute: hname_t = (ast.Attribute*(htype_t)).attr
# match code buffer must be int* (i32 stride): collect_descendant_codes
# writes 4-byte entries; reading as sizet* yields garbage.
codes_buf_t: int* = pool.alloc(64 * 4)
if codes_buf_t is None: return 0
n_codes_t: int = 0
if hname_t is not None and self.Gen.ClassGenBase.is_exception_name(hname_t) != 0: n_codes_t = self.Gen.ClassGenBase.collect_descendant_codes(hname_t, codes_buf_t, 64)
else:
c0_t: int = 1
if hname_t is not None:
c0_t = self.Gen.ClassGenBase.lookup_exception_code(hname_t)
if c0_t == 0: c0_t = 1
codes_buf_t[0] = c0_t
n_codes_t = 1
match_cond_t: mlirlite.Value* = None
for ci_t in range(int(n_codes_t)):
code_val_t: mlirlite.Value* = mlirlite.const_int32(pool, codes_buf_t[ci_t])
is_match_t: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, eh_code_val, code_val_t)
if match_cond_t is None: match_cond_t = is_match_t
elif is_match_t is not None: match_cond_t = mlirlite.build_or(builder, match_cond_t, is_match_t)
if i_t < n_handlers_t - 1:
viperlib.snprintf(bb_buf_t, 64, "try.check.%d.%d", lc_t, i_t)
next_check_t = mlirlite.create_block(pool, func, bb_buf_t)
else: next_check_t = unhandled_bb
mlirlite.build_cond_br(builder, match_cond_t, match_bb_t, next_check_t)
# MatchBB: bind handler var (e = message ptr), jump to ExceptBB
mlirlite.position_at_end(builder, match_bb_t)
if hdr_t is not None and hdr_t.name is not None and hdr_t.name[0] != '\0':
# Same-named handler vars reuse the entry alloca to avoid a non-
# dominating alloca ("does not dominate all uses").
h_ent_t: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, hdr_t.name)
if h_ent_t is not None and h_ent_t.Alloca is not None: h_alloca_t: mlirlite.Value* = h_ent_t.Alloca
else:
h_alloca_t = self.Gen.VarGen._alloca_at_entry(builder, i8_ptr_ty_t)
if h_alloca_t is not None: self.Gen.VarGen.define_var(trans.SymTab, hdr_t.name, h_alloca_t)
if h_alloca_t is not None:
h_msg_t: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty_t, eh_message)
if h_msg_t is not None: mlirlite.build_store(builder, h_msg_t, h_alloca_t)
except_bb_t: mlirlite.BasicBlock* = except_bbs.get(i_t)
mlirlite.build_br(builder, except_bb_t)
# ---- 6. UnhandledBB: no except matched -> propagate or swallow ----
mlirlite.position_at_end(builder, unhandled_bb)
if trans._cur_func_has_eh != 0 and trans._eh_msg_out_param is not None and trans._eh_code_out_param is not None:
un_msg_t: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty_t, eh_message)
un_code_t: mlirlite.Value* = mlirlite.build_load(builder, i32_ty_t, exception_code)
if un_msg_t is not None: mlirlite.build_store(builder, un_msg_t, trans._eh_msg_out_param)
if un_code_t is not None: mlirlite.build_store(builder, un_code_t, trans._eh_code_out_param)
self._eh_ret_default(trans, pool, builder)
else:
if init_code_t is not None: mlirlite.build_store(builder, init_code_t, exception_code)
mlirlite.build_br(builder, after_bb)
# ---- 7. except bodies ----
for i_t in range(int(n_handlers_t)):
hdr2_t: ast.ExceptHandler* = ast.ExceptHandler*(tr.handlers.get(i_t))
except_bb2_t: mlirlite.BasicBlock* = except_bbs.get(i_t)
mlirlite.position_at_end(builder, except_bb2_t)
if hdr2_t is not None and hdr2_t.children is not None:
hn2_t: sizet = len(hdr2_t.children)
for hi2 in range(int(hn2_t)):
stmt2_t: ast.AST* = hdr2_t.children.get(hi2)
if stmt2_t is not None: self.translate_stmt(trans, stmt2_t)
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, after_bb)
# ---- 8. else body ----
if else_bb is not None:
mlirlite.position_at_end(builder, else_bb)
if tr.orelse is not None:
en_t: sizet = len(tr.orelse)
for ei in range(int(en_t)):
stmt3_t: ast.AST* = tr.orelse.get(ei)
if stmt3_t is not None: self.translate_stmt(trans, stmt3_t)
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, after_bb)
# ---- 9. AfterBB + restore outer try context ----
mlirlite.position_at_end(builder, after_bb)
trans._eh_dispatch_bb = old_dispatch_t
trans._eh_code_ptr = old_code_t
trans._eh_msg_ptr = old_msg_t
return 0
def translate_raise(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a raise statement and dispatch the exception; returns 0"""
if trans is None or node is None: return 0
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
if pool is None or builder is None or mod is None: return 0
rn: ast.Raise* = ast.Raise*(node)
if rn is None or rn.exc is None:
# bare raise (no exc): v1 has no active exception; just return default
self._eh_ret_default(trans, pool, builder)
return 0
# OOP iter protocol: raise StopIteration -> set __stop_iter_flag__ = 1, then
# return the default value.
ek: int = rn.exc.kind()
is_stop_iter: int = 0
if ek == ast.ASTKind.Name:
enm: ast.Name* = ast.Name*(rn.exc)
if enm.id is not None and string.strcmp(enm.id, "StopIteration") == 0: is_stop_iter = 1
elif ek == ast.ASTKind.Call:
ecl: ast.Call* = ast.Call*(rn.exc)
if ecl.func is not None and ecl.func.kind() == ast.ASTKind.Name:
ecn: ast.Name* = ast.Name*(ecl.func)
if ecn.id is not None and string.strcmp(ecn.id, "StopIteration") == 0: is_stop_iter = 1
if is_stop_iter != 0:
if trans._stop_iter_flag_ptr is not None:
i1_ty_r: mlirlite.LLVMType* = mlirlite.Int1(pool)
if i1_ty_r is not None:
one_i1_r: mlirlite.Value* = mlirlite.ConstInt(pool, i1_ty_r, 1, "1")
if one_i1_r is not None: mlirlite.build_store(builder, one_i1_r, trans._stop_iter_flag_ptr)
self._eh_ret_default(trans, pool, builder)
return 0
# ---- non-StopIteration: compute exception code and message ----
exc_code: int = 1
exc_msg: mlirlite.Value* = None
if ek == ast.ASTKind.Constant:
cst_r: ast.Constant* = ast.Constant*(rn.exc)
if cst_r is not None and cst_r.const_kind == ast.CONST_INT: exc_code = int(cst_r.int_val)
elif ek == ast.ASTKind.Name:
enm2: ast.Name* = ast.Name*(rn.exc)
if enm2.id is not None:
exc_code = self.Gen.ClassGenBase.lookup_exception_code(enm2.id)
if exc_code == 0: exc_code = 1
elif ek == ast.ASTKind.Call:
ecl2: ast.Call* = ast.Call*(rn.exc)
if ecl2.func is not None and ecl2.func.kind() == ast.ASTKind.Name:
ecn2: ast.Name* = ast.Name*(ecl2.func)
if ecn2.id is not None:
exc_code = self.Gen.ClassGenBase.lookup_exception_code(ecn2.id)
if exc_code == 0: exc_code = 1
if ecl2.args is not None and len(ecl2.args) > 0: exc_msg = self.Gen.ExprGen.translate_value(builder, pool, mod, ecl2.args.get(0), None, 0, trans)
# ---- exception dispatch ----
# 1. inside try: write to the try's eh_code/eh_message alloca, branch to DispatchBB
if trans._eh_dispatch_bb is not None and trans._eh_code_ptr is not None:
code_val_r: mlirlite.Value* = mlirlite.const_int32(pool, exc_code)
if code_val_r is not None: mlirlite.build_store(builder, code_val_r, trans._eh_code_ptr)
if exc_msg is None: exc_msg = mlirlite.ConstNull(pool, mlirlite.Ptr(pool, mlirlite.Int8(pool)), "null")
if exc_msg is not None and trans._eh_msg_ptr is not None: mlirlite.build_store(builder, exc_msg, trans._eh_msg_ptr)
mlirlite.build_br(builder, trans._eh_dispatch_bb)
return 0
# 2. function has EH out params: write them (propagate to caller)
if trans._cur_func_has_eh != 0 and trans._eh_msg_out_param is not None and trans._eh_code_out_param is not None:
code_val_r2: mlirlite.Value* = mlirlite.const_int32(pool, exc_code)
if code_val_r2 is not None: mlirlite.build_store(builder, code_val_r2, trans._eh_code_out_param)
if exc_msg is None: exc_msg = mlirlite.ConstNull(pool, mlirlite.Ptr(pool, mlirlite.Int8(pool)), "null")
if exc_msg is not None: mlirlite.build_store(builder, exc_msg, trans._eh_msg_out_param)
self._eh_ret_default(trans, pool, builder)
return 0
# 3. no try and no EH params: just return the default
self._eh_ret_default(trans, pool, builder)
return 0
# _eh_ret_default - return the function's default zero value on the exception
# path (void->ret_void, ptr->null, float->0.0, else zero); skipped if terminated.
def _eh_ret_default(self, trans: HT.Translator*, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*) -> int:
"""Return the current function's default zero value on the exception path"""
if trans is None or builder is None: return 0
if mlirlite.builder_cur_block_is_terminated(builder) != 0: return 0
cur_func: mlirlite.Function* = trans._cur_func
if cur_func is None: return 0
ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(cur_func)
if ret_ty is None: return 0
# P1:异常/传播出口**也是函数出口** —— 这里是"异常路径上真正离开函数"的唯一落点
# (translate_raise 的三条分支 + try 的 UnhandledBB 传播分支都汇聚到它),所以在此逆序释放
# 本函数登记的 slife。**不能**改在 raise 语句本身释放:被本函数 except 捕获时对象仍在用
# (那一步只 br 到 DispatchBB,不走这里);同理 EH 返回值不会是 slife 对象(逃逸检查已拒)。
self.Gen.VarGen._SlifeEmitFrees(pool, builder, trans.Module, trans)
match ret_ty:
case mlirlite.LLVMType.Void():
mlirlite.build_ret_void(builder)
case mlirlite.LLVMType.Ptr(_pe):
null_v: mlirlite.Value* = mlirlite.ConstNull(pool, ret_ty, "null")
if null_v is not None: mlirlite.build_ret(builder, null_v)
case mlirlite.LLVMType.Float(_fb):
zero_f: mlirlite.Value* = mlirlite.ConstFloat(pool, ret_ty, 0.0)
if zero_f is not None: mlirlite.build_ret(builder, zero_f)
case _:
zero_v: mlirlite.Value* = mlirlite.ConstZero(pool, ret_ty)
if zero_v is not None: mlirlite.build_ret(builder, zero_v)
return 0
# translate_with - with statement translation: build context, call __enter__,
# bind asname, translate body, then call __exit__ in reverse order.
def translate_with(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a with statement; returns 0"""
if trans is None or node is None: return 0
wt: ast.With* = ast.With*(node)
if wt is None: return 0
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
if pool is None or builder is None or mod is None: return 0
items: list[ast.AST*]* = wt.items
if items is None: return 0
n: sizet = len(items)
# save the outer __with_ctx (restored after nested with exits)
old_ctx_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, "__with_ctx")
# record each item's class name (used to type __with_ctx on __exit__)
ctx_class_buf: sizet* = pool.alloc(8 * n)
if ctx_class_buf is None: return 0
string.memset(ctx_class_buf, 0, 8 * n)
# 1. all items: build context, call __enter__, bind asname
for i in range(int(n)):
item_node: ast.AST* = items.get(i)
if item_node is None: continue
if item_node.kind() != ast.ASTKind.WithItem: continue
item: ast.WithItem* = ast.WithItem*(item_node)
if item is None or item.context_expr is None: continue
# translate context_expr (object construction, e.g. MemPool(arena, 4096))
ctx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, item.context_expr, trans._funcs, trans._func_count, trans)
if ctx_val is None: continue
# Extract the class name (module-prefixed for Attribute, e.g. "memhub.MemPool");
# cross-module calls fall back to AnnotClassName by module name.
cls_name: str = None
ce: ast.AST* = item.context_expr
if ce.kind() == ast.ASTKind.Call:
cfn: ast.AST* = (ast.Call*(ce)).func
if cfn is not None:
if cfn.kind() == ast.ASTKind.Attribute:
ca: ast.Attribute* = ast.Attribute*(cfn)
if ca.value is not None and ca.value.kind() == ast.ASTKind.Name:
cmn: ast.Name* = ast.Name*(ca.value)
if cmn.id is not None:
ml2: sizet = string.strlen(cmn.id) + string.strlen(ca.attr) + 2
mbuf: str = pool.alloc(ml2)
if mbuf is not None:
viperlib.snprintf(mbuf, ml2, "%s.%s", cmn.id, ca.attr)
cls_name = mbuf
if cls_name is None: cls_name = ca.attr
elif cfn.kind() == ast.ASTKind.Name: cls_name = (ast.Name*(cfn)).id
# store the context object in __with_ctx (used to resolve self)
ctx_alloca: mlirlite.Value* = mlirlite.build_alloca(builder, ctx_val.Ty)
if ctx_alloca is None: continue
mlirlite.build_store(builder, ctx_val, ctx_alloca)
self.Gen.VarGen.define_var(trans.SymTab, "__with_ctx", ctx_alloca)
if cls_name is not None:
self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, "__with_ctx", cls_name)
ctx_class_buf[i] = sizet(cls_name)
# call __enter__()
enter_val: mlirlite.Value* = self._with_ctx_call(trans, pool, builder, mod, "__enter__")
# bind asname (__enter__ return value, or the context object on miss)
if item.optional_vars is not None:
ov: ast.AST* = item.optional_vars
if ov.kind() == ast.ASTKind.Name:
asname: str = (ast.Name*(ov)).id
if asname is not None:
bind_val: mlirlite.Value* = enter_val
if bind_val is None: bind_val = ctx_val
if bind_val is not None:
asn_alloca: mlirlite.Value* = mlirlite.build_alloca(builder, bind_val.Ty)
if asn_alloca is not None:
mlirlite.build_store(builder, bind_val, asn_alloca)
self.Gen.VarGen.define_var(trans.SymTab, asname, asn_alloca)
if cls_name is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, asname, cls_name)
# FakeDuck: __provides__ metadata -> emit a _push_provider call (matches TPC);
# the class name is de-qualified before the __provides__ lookup.
fd_cls_bare: str = cls_name
if cls_name is not None:
fd_dot: str = string.strrchr(cls_name, '.')
if fd_dot is not None: fd_cls_bare = fd_dot + 1
if fd_cls_bare is not None: self.Gen.CallGenFd._emit_provider_push(trans, pool, builder, mod, fd_cls_bare, ctx_val)
# 2. translate body
body: list[ast.AST*]* = wt.children
if body is not None:
bn: sizet = len(body)
for bi in range(int(bn)):
st: ast.AST* = body.get(bi)
if st is not None: self.translate_stmt(trans, st)
# 2.5 FakeDuck: __provides__ metadata -> emit _pop_provider calls in reverse.
for pi_pop in range(int(n)):
if ctx_class_buf[pi_pop] != 0:
cls_pop: str = rawptr(ctx_class_buf[pi_pop])
fd_cls_bare_p: str = cls_pop
if cls_pop is not None:
fd_dot_p: str = string.strrchr(cls_pop, '.')
if fd_dot_p is not None: fd_cls_bare_p = fd_dot_p + 1
if fd_cls_bare_p is not None: self.Gen.CallGenFd._emit_provider_pop(trans, pool, builder, mod, fd_cls_bare_p)
# 3. call __exit__() in reverse order (Python with semantics: LIFO)
for i in range(int(n)):
ci: int = int(n - 1 - i)
if ctx_class_buf[ci] != 0:
cls_ci: str = rawptr(ctx_class_buf[ci])
self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, "__with_ctx", cls_ci)
self._with_ctx_call(trans, pool, builder, mod, "__exit__")
# 4. restore the outer __with_ctx
if old_ctx_alloca is not None: self.Gen.VarGen.define_var(trans.SymTab, "__with_ctx", old_ctx_alloca)
return 0
def _with_ctx_call(self, trans: HT.Translator*, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, method_name: str) -> mlirlite.Value*:
"""Build and translate a __with_ctx.<method_name>() call (__enter__/__exit__)"""
if pool is None or method_name is None: return None
name_node: ast.AST* = ast.Name(pool, "__with_ctx", ast.ASTCtx.Load)
if name_node is None: return None
attr_node: ast.AST* = ast.Attribute(pool, name_node, method_name, ast.ASTCtx.Load)
if attr_node is None: return None
call_node: ast.AST* = ast.Call(pool, attr_node, None, None)
if call_node is None: return None
return self.Gen.ExprGen.translate_value(builder, pool, mod, call_node, trans._funcs, trans._func_count, trans)
File diff suppressed because it is too large. Load diff
-590
View File
@@ -1,590 +0,0 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdlib
import stdio
import viperlib
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.CompilerFactory as CompilerFactory
import lib.Projectrans.Config as Config
import lib.core.Gen.StmtGenCifMixin as StmtGenCifMixin
# ============================================================
# StmtGenDeclMixin - StmtGen 拆分 2/3:声明/枚举/nonlocal 访问
#
# 按功能域从 StmtGenMixin.vp 拆出(500~1500 行/文件架构重构,试点):
# translate_global/nonlocal/expr_stmt/pre_scan、break/continue、
# nonlocal 闭包 env 访问、枚举注册表。继承 StmtGenCifMixin。
#
# @t.NoVTable:静态绑定(方法沿 MRO 直接调用 + this 调整)。
# ============================================================
# ============================================================
# 常量(与 HandlesEnum 保持一致,本地副本避免跨模块 CDefine 查找 bug)
# ============================================================
ENUM_MAX: t.CDefine = 64
ENUM_MEMBER_MAX: t.CDefine = 128
# ============================================================
# StmtGenDeclMixin - 声明/枚举/nonlocal(中层级)
# ============================================================
@t.NoVTable
class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
"""声明/枚举/nonlocal 访问(StmtGen 拆分 2/3,继承 Cif)"""
def translate_global(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""翻译 global 语句:记录 global 变量名"""
gn: ast.Global | t.CPtr = (ast.Global | t.CPtr)(node)
if gn is None:
return 0
names: list[ast.AST | t.CPtr] | t.CPtr = gn.names
if names is None:
return 0
n: t.CSizeT = len(names)
for i in range(n):
nm_node: ast.AST | t.CPtr = names.get(i)
if nm_node is not None and nm_node.kind() == ast.ASTKind.Name:
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(nm_node)
if nm.id is not None:
HT.add_global_name(trans, nm.id)
return 0
def translate_nonlocal(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""翻译 nonlocal 语句:记录 nonlocal 变量名及其类型
类型信息通过查找父作用域的 alloca 获取(用于 env 指针数组方案的类型恢复)。
"""
nl: ast.Nonlocal | t.CPtr = (ast.Nonlocal | t.CPtr)(node)
if nl is None:
return 0
names: list[ast.AST | t.CPtr] | t.CPtr = nl.names
if names is None:
return 0
n: t.CSizeT = len(names)
for i in range(n):
nm_node: ast.AST | t.CPtr = names.get(i)
if nm_node is not None and nm_node.kind() == ast.ASTKind.Name:
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(nm_node)
if nm.id is not None:
# 查找父作用域中的变量 alloca,获取类型信息
var_ty: llvmlite.LLVMType | t.CPtr = None
var_alloca: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_var(
trans.SymTab, nm.id)
if var_alloca is not None and var_alloca.Ty is not None:
var_ty = var_alloca.Ty.Pointee
# Void 类型视为无效(Pointee 访问异常时可能返回 Void),
# 类型无效直接报错终止(fail-fast),避免生成
# "bitcast ... to void*" / "load void, void*" 非法 IR
if var_ty is not None:
match var_ty:
case llvmlite.LLVMType.Void():
self.Gen.TypeGenErr.fatal_error(nm_node, f"nonlocal 变量 '{nm.id}' 类型无效(Void/无法解析)", "TypeError")
case _:
pass
HT.add_nonlocal_name(trans, nm.id, var_ty)
return 0
def translate_expr_stmt(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""翻译表达式语句(printf 调用等)"""
ex: ast.Expr | t.CPtr = (ast.Expr | t.CPtr)(node)
if ex is None:
return 0
call_node: ast.AST | t.CPtr = ex.value
if call_node is None:
return 0
ck: int = call_node.kind()
if ck != ast.ASTKind.Call:
return 0
cl: ast.Call | t.CPtr = (ast.Call | t.CPtr)(call_node)
func_node: ast.AST | t.CPtr = cl.func
if func_node is None:
return 0
func_name: str = self.Gen.ExprGenBase.get_func_name(func_node)
if func_name is None:
# 函数名解析失败:直接报错终止(fail-fast)
self.Gen.TypeGenErr.fatal_error(call_node, "表达式语句的函数名解析失败", "SyntaxError")
# printf / print 走特殊路径(print 是类型感知格式化,不映射到 printf)
if string.strcmp(func_name, "printf") == 0:
self.Gen.CallGenLeafKit.translate_printf_call(
trans.Pool, trans._cur_builder, trans.Module, cl, trans)
elif string.strcmp(func_name, "print") == 0:
self.Gen.CallGenLeafKit.translate_print_call(
trans.Pool, trans._cur_builder, trans.Module, cl, trans)
else:
# 通用函数调用(返回值丢弃)
self.Gen.CallGen.translate_call(
trans.Pool, trans._cur_builder, trans.Module, call_node,
trans._funcs, trans._func_count, trans)
return 0
def pre_scan_allocas(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""预扫描语句中的 AnnAssign,提前创建 alloca
递归分支/循环/异常体:if/else/for/while/try 分支内的 AnnAssign 局部
也必须提前注册到 entry alloca——否则闭包隐式捕获扫描(step 8c)
在分支局部未注册时会误判为外层函数捕获(Terminal _build_path 的
i/j/cwd_len 被当 nonlocal 捕获 → 循环计数器走 env/父函数存储 →
跨函数 SSA 错乱,llc "instruction expected to be numbered")。
返回新增的变量数(调用方均忽略返回值)。
"""
if node is None:
return 0
k: int = node.kind()
if k == ast.ASTKind.AnnAssign:
return self.Gen.AssignGen.translate_annassign_prescan(trans, node)
if k == ast.ASTKind.If:
ifn: ast.If | t.CPtr = (ast.If | t.CPtr)(node)
if ifn.children is not None:
bn_pre_i: t.CSizeT = len(ifn.children)
for bi_pre_i in range(bn_pre_i):
self.pre_scan_allocas(trans, ifn.children.get(bi_pre_i))
if ifn.orelse is not None:
bn_pre_e: t.CSizeT = len(ifn.orelse)
for bi_pre_e in range(bn_pre_e):
self.pre_scan_allocas(trans, ifn.orelse.get(bi_pre_e))
return 0
if k == ast.ASTKind.For:
for_n: ast.For | t.CPtr = (ast.For | t.CPtr)(node)
if for_n.children is not None:
bn_pre_f: t.CSizeT = len(for_n.children)
for bi_pre_f in range(bn_pre_f):
self.pre_scan_allocas(trans, for_n.children.get(bi_pre_f))
if for_n.orelse is not None:
bn_pre_fe: t.CSizeT = len(for_n.orelse)
for bi_pre_fe in range(bn_pre_fe):
self.pre_scan_allocas(trans, for_n.orelse.get(bi_pre_fe))
return 0
if k == ast.ASTKind.While:
wh_n: ast.While | t.CPtr = (ast.While | t.CPtr)(node)
if wh_n.children is not None:
bn_pre_w: t.CSizeT = len(wh_n.children)
for bi_pre_w in range(bn_pre_w):
self.pre_scan_allocas(trans, wh_n.children.get(bi_pre_w))
if wh_n.orelse is not None:
bn_pre_we: t.CSizeT = len(wh_n.orelse)
for bi_pre_we in range(bn_pre_we):
self.pre_scan_allocas(trans, wh_n.orelse.get(bi_pre_we))
return 0
if k == ast.ASTKind.Try:
tr_n: ast.Try | t.CPtr = (ast.Try | t.CPtr)(node)
if tr_n.children is not None:
bn_pre_t: t.CSizeT = len(tr_n.children)
for bi_pre_t in range(bn_pre_t):
self.pre_scan_allocas(trans, tr_n.children.get(bi_pre_t))
if tr_n.handlers is not None:
bn_pre_th: t.CSizeT = len(tr_n.handlers)
for bi_pre_th in range(bn_pre_th):
eh_pre: ast.AST | t.CPtr = tr_n.handlers.get(bi_pre_th)
if eh_pre is not None and eh_pre.kind() == ast.ASTKind.ExceptHandler:
ehc_pre: ast.ExceptHandler | t.CPtr = (ast.ExceptHandler | t.CPtr)(eh_pre)
if ehc_pre.children is not None:
bn_pre_ehb: t.CSizeT = len(ehc_pre.children)
for bi_pre_ehb in range(bn_pre_ehb):
self.pre_scan_allocas(trans, ehc_pre.children.get(bi_pre_ehb))
if tr_n.orelse is not None:
bn_pre_to: t.CSizeT = len(tr_n.orelse)
for bi_pre_to in range(bn_pre_to):
self.pre_scan_allocas(trans, tr_n.orelse.get(bi_pre_to))
if tr_n.finalbody is not None:
bn_pre_tf: t.CSizeT = len(tr_n.finalbody)
for bi_pre_tf in range(bn_pre_tf):
self.pre_scan_allocas(trans, tr_n.finalbody.get(bi_pre_tf))
return 0
if k == ast.ASTKind.With:
wth: ast.With | t.CPtr = (ast.With | t.CPtr)(node)
if wth.children is not None:
bn_pre_wb: t.CSizeT = len(wth.children)
for bi_pre_wb in range(bn_pre_wb):
self.pre_scan_allocas(trans, wth.children.get(bi_pre_wb))
return 0
if k == ast.ASTKind.Match:
mt_n: ast.Match | t.CPtr = (ast.Match | t.CPtr)(node)
if mt_n.cases is not None:
bn_pre_mc: t.CSizeT = len(mt_n.cases)
for bi_pre_mc in range(bn_pre_mc):
mc_pre: ast.AST | t.CPtr = mt_n.cases.get(bi_pre_mc)
if mc_pre is not None and mc_pre.kind() == ast.ASTKind.MatchCase:
mcc_pre: ast.MatchCase | t.CPtr = (ast.MatchCase | t.CPtr)(mc_pre)
if mcc_pre.children is not None:
bn_pre_mcb: t.CSizeT = len(mcc_pre.children)
for bi_pre_mcb in range(bn_pre_mcb):
self.pre_scan_allocas(trans, mcc_pre.children.get(bi_pre_mcb))
return 0
return 0
def pre_scan_scope_decls(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""预扫描 Global/Nonlocal 声明,提前注册作用域名称"""
if node is None:
return 0
k: int = node.kind()
if k == ast.ASTKind.Global:
return self.translate_global(trans, node)
if k == ast.ASTKind.Nonlocal:
return self.translate_nonlocal(trans, node)
return 0
def translate_break(self, trans: HT.Translator | t.CPtr) -> int:
"""翻译 break 语句:跳转到循环 end 块"""
builder: llvmlite.IRBuilder | t.CPtr = trans._cur_builder
func: llvmlite.Function | t.CPtr = trans._cur_func
pool: memhub.MemBuddy | t.CPtr = trans.Pool
if builder is None or func is None:
return 0
# 发射 br 到 break 目标
if trans._break_bb is not None:
if llvmlite.builder_cur_block_is_terminated(builder) == 0:
llvmlite.build_br(builder, trans._break_bb)
# 创建死代码 BB,用于后续语句(break 后面的代码不可达)
cnt: int = trans._label_counter
trans._label_counter = cnt + 1
name_buf: str = pool.alloc(32)
viperlib.snprintf(name_buf, 32, "dead.%d", cnt)
dead_bb: llvmlite.BasicBlock | t.CPtr = llvmlite.create_block(pool, func, name_buf)
llvmlite.position_at_end(builder, dead_bb)
return 0
def translate_continue(self, trans: HT.Translator | t.CPtr) -> int:
"""翻译 continue 语句:跳转到循环 cond/incr 块"""
builder: llvmlite.IRBuilder | t.CPtr = trans._cur_builder
func: llvmlite.Function | t.CPtr = trans._cur_func
pool: memhub.MemBuddy | t.CPtr = trans.Pool
if builder is None or func is None:
return 0
# 发射 br 到 continue 目标
if trans._continue_bb is not None:
if llvmlite.builder_cur_block_is_terminated(builder) == 0:
llvmlite.build_br(builder, trans._continue_bb)
# 创建死代码 BB,用于后续语句
cnt: int = trans._label_counter
trans._label_counter = cnt + 1
name_buf: str = pool.alloc(32)
viperlib.snprintf(name_buf, 32, "dead.%d", cnt)
dead_bb: llvmlite.BasicBlock | t.CPtr = llvmlite.create_block(pool, func, name_buf)
llvmlite.position_at_end(builder, dead_bb)
return 0
def get_stmt_kind_name(self, node: ast.AST | t.CPtr) -> str:
"""获取语句类型名"""
if node is None:
return None
return node.type_name()
# ============================================================
# 簇 D:nonlocal/闭包 env 访问(迁移自 HandlesNonlocal.vp)
# ============================================================
def _is_void_type(self, ty: llvmlite.LLVMType | t.CPtr) -> int:
"""判断是否为 Void 类型,是则返回 1,否则 0"""
if ty is None:
return 0
match ty:
case llvmlite.LLVMType.Void():
return 1
case _:
return 0
def get_env_ptr_var(self, trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""获取或创建 _env_ptr 变量(存储闭包 env 指针)"""
env_var: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_var(
trans.SymTab, "_env_ptr")
if env_var is not None:
return env_var
# 创建 alloca 存储 env
pool: memhub.MemBuddy | t.CPtr = trans.Pool
builder: llvmlite.IRBuilder | t.CPtr = trans._cur_builder
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty)
alloca: llvmlite.Value | t.CPtr = llvmlite.build_alloca(builder, i8_ptr_ty)
if alloca is None:
return None
self.Gen.VarGen.define_var(trans.SymTab, "_env_ptr", alloca)
return alloca
def _load_raw_var_ptr(self, trans: HT.Translator | t.CPtr,
name: str) -> llvmlite.Value | t.CPtr:
"""从闭包 env 加载 nonlocal 变量的原始指针(i8*)
返回 i8* 类型的 Value,调用方需 bitcast 为具体类型指针后 load/store。
"""
idx: int = HT.get_nonlocal_index(trans, name)
if idx < 0:
return None
pool: memhub.MemBuddy | t.CPtr = trans.Pool
builder: llvmlite.IRBuilder | t.CPtr = trans._cur_builder
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty)
i8_ptr_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ptr_ty)
# 1. 加载 env_ptr
env_alloca: llvmlite.Value | t.CPtr = self.get_env_ptr_var(trans)
if env_alloca is None:
return None
env_ptr: llvmlite.Value | t.CPtr = llvmlite.build_load(builder, i8_ptr_ty, env_alloca)
if env_ptr is None:
return None
# 2. GEP to offset (idx * 8) — env 存储指针数组,每槽 8 字节
offset_val: llvmlite.Value | t.CPtr = llvmlite.const_int32(pool, idx * 8)
addr: llvmlite.Value | t.CPtr = llvmlite.build_gep(builder, i8_ty, env_ptr, offset_val)
if addr is None:
return None
# 3. Bitcast to i8** and load the raw pointer
ptr_addr: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(builder, addr, i8_ptr_ptr_ty)
if ptr_addr is None:
return None
return llvmlite.build_load(builder, i8_ptr_ty, ptr_addr)
def load_nonlocal_var(self, trans: HT.Translator | t.CPtr,
name: str) -> llvmlite.Value | t.CPtr:
"""从闭包 env 加载 nonlocal 变量值
通过 HT.get_nonlocal_type 获取变量类型,bitcast 原始指针后 load。
"""
raw_ptr: llvmlite.Value | t.CPtr = self._load_raw_var_ptr(trans, name)
if raw_ptr is None:
return None
pool: memhub.MemBuddy | t.CPtr = trans.Pool
builder: llvmlite.IRBuilder | t.CPtr = trans._cur_builder
# 获取变量类型(从注册时存储的类型信息)
var_ty: llvmlite.LLVMType | t.CPtr = HT.get_nonlocal_type(trans, name)
if var_ty is None or self._is_void_type(var_ty) != 0:
# 类型缺失/无效直接报错终止(fail-fast),避免生成 "load void, void*" 非法 IR
self.Gen.TypeGenErr.fatal_error(None, f"nonlocal 变量 '{name}' 类型缺失,无法 load", "TypeError")
# bitcast raw i8* to var_ty*
var_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, var_ty)
var_ptr: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(builder, raw_ptr, var_ptr_ty)
if var_ptr is None:
return None
return llvmlite.build_load(builder, var_ty, var_ptr)
def get_nonlocal_var_ptr(self, trans: HT.Translator | t.CPtr,
name: str) -> llvmlite.Value | t.CPtr:
"""获取 nonlocal 变量的存储指针(var_ty*),用于 store 操作
返回的指针指向父函数的 alloca,store 操作对父函数可见。
"""
raw_ptr: llvmlite.Value | t.CPtr = self._load_raw_var_ptr(trans, name)
if raw_ptr is None:
return None
pool: memhub.MemBuddy | t.CPtr = trans.Pool
builder: llvmlite.IRBuilder | t.CPtr = trans._cur_builder
# 获取变量类型
var_ty: llvmlite.LLVMType | t.CPtr = HT.get_nonlocal_type(trans, name)
if var_ty is None or self._is_void_type(var_ty) != 0:
# 类型缺失/无效直接报错终止(fail-fast),避免生成 "bitcast ... to void*" 非法 IR
self.Gen.TypeGenErr.fatal_error(None, f"nonlocal 变量 '{name}' 类型缺失,无法 load", "TypeError")
# bitcast raw i8* to var_ty*
var_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, var_ty)
return llvmlite.build_bitcast(builder, raw_ptr, var_ptr_ty)
# ============================================================
# 簇 E:枚举注册表(迁移自 HandlesEnum.vp)
# ============================================================
def init_enum_table(self, pool: memhub.MemBuddy | t.CPtr) -> int:
"""初始化枚举注册表,返回 1 成功
表用 stdlib 分配(跨 Phase1 1b 的 mark/reset_to 存活——枚举表是
跨文件持久表,pool 分配会被 reset 回收导致 Phase B 读垃圾,
enum_count 出现 512 等垃圾值 / entry.Name 解引用崩溃)。
"""
if self.EnumTable is not None:
return 1
entry_size: t.CSizeT = GenTypes.EnumEntry.__sizeof__()
self.EnumTable = CompilerFactory.get_perm_pool().alloc(entry_size * ENUM_MAX)
if self.EnumTable is None:
return 0
string.memset(self.EnumTable, 0, entry_size * ENUM_MAX)
self.EnumCount = 0
return 1
def _get_enum_entry(self, i: int) -> GenTypes.EnumEntry | t.CPtr:
"""获取第 i 个枚举条目"""
if self.EnumTable is None or i < 0 or i >= ENUM_MAX:
return None
entry_size: t.CSizeT = GenTypes.EnumEntry.__sizeof__()
addr: t.CUInt64T = t.CUInt64T(self.EnumTable) + i * entry_size
return (GenTypes.EnumEntry | t.CPtr)(t.CVoid(addr, t.CPtr))
def _get_enum_member(self, enum_entry: GenTypes.EnumEntry | t.CPtr,
i: int) -> GenTypes.EnumMember | t.CPtr:
"""获取枚举中第 i 个成员条目"""
if enum_entry is None or i < 0 or i >= ENUM_MEMBER_MAX:
return None
member_size: t.CSizeT = GenTypes.EnumMember.__sizeof__()
addr: t.CUInt64T = t.CUInt64T(enum_entry.Members) + i * member_size
return (GenTypes.EnumMember | t.CPtr)(t.CVoid(addr, t.CPtr))
def register_enum(self, pool: memhub.MemBuddy | t.CPtr,
name: str,
base_ty: llvmlite.LLVMType | t.CPtr) -> GenTypes.EnumEntry | t.CPtr:
"""注册枚举类型,返回 EnumEntry 指针"""
if self.init_enum_table(pool) == 0:
return None
# 检查是否已注册
existing: GenTypes.EnumEntry | t.CPtr = self.find_enum(name)
if existing is not None:
return existing
if self.EnumCount >= ENUM_MAX:
fb_et: str = VLogger.fmt_buf()
if fb_et is not None:
viperlib.snprintf(fb_et, 1024, "table full, cannot register %s", name)
VLogger.error(fb_et, "ENUM")
return None
entry: GenTypes.EnumEntry | t.CPtr = self._get_enum_entry(self.EnumCount)
if entry is None:
return None
# 分配成员数组(永久池:跨 mark/reset 存活,对齐 EnumTable 永久池化)
member_size: t.CSizeT = GenTypes.EnumMember.__sizeof__()
entry.Members = CompilerFactory.get_perm_pool().alloc(member_size * ENUM_MEMBER_MAX)
if entry.Members is None:
return None
string.memset(entry.Members, 0, member_size * ENUM_MEMBER_MAX)
# 复制类名(永久池深拷贝)
name_len: t.CSizeT = string.strlen(name)
name_buf: str = CompilerFactory.get_perm_pool().alloc(name_len + 1)
if name_buf is not None:
string.strcpy(name_buf, name)
entry.Name = name_buf
# BaseTy 深拷贝:LLVMType 是 pool 分配,StdlibAlloc=1(Phase2 mark2 后)
# 时 reset 回收悬空 → Phase B 枚举访问 ConstInt(悬空 BaseTy) 生成 void
# 值(DecoratorMarkerTest `store void 2` llc 报错根因)。Phase1 1a
# (StdlibAlloc=0,mark1 前)保持 pool 存活,无需拷贝。
# 注意:StdlibAlloc 是 StructGenMixin 字段,本 Mixin 方法体不能直接
# self.StdlibAlloc(NoVTable 按本 Mixin 字段布局编译),走模块级委托。
if self.Gen.StructGen.get_stdlib_alloc() != 0:
base_ty_copy: llvmlite.LLVMType | t.CPtr = llvmlite.DeepCopyType(
CompilerFactory.get_perm_pool(), base_ty)
if base_ty_copy is not None:
entry.BaseTy = base_ty_copy
else:
entry.BaseTy = base_ty
else:
entry.BaseTy = base_ty
entry.MemberCount = 0
self.EnumCount += 1
return entry
def add_enum_member(self, pool: memhub.MemBuddy | t.CPtr,
enum_entry: GenTypes.EnumEntry | t.CPtr,
member_name: str,
member_val: t.CInt64T,
member_ty: llvmlite.LLVMType | t.CPtr) -> int:
"""向枚举添加成员,返回成员索引(-1 失败)"""
if enum_entry is None or member_name is None or member_ty is None:
return -1
# 去重:同名成员已存在则跳过(跨模块同名枚举合并注册 / 同模块
# 重复处理幂等——如 w32.fileio 与 fat32_types 都有 FRESULT)
for ei_d in range(enum_entry.MemberCount):
ex_d: GenTypes.EnumMember | t.CPtr = self._get_enum_member(
enum_entry, ei_d)
if ex_d is not None and ex_d.Name is not None:
if string.strcmp(ex_d.Name, member_name) == 0:
return ei_d
if enum_entry.MemberCount >= ENUM_MEMBER_MAX:
fb_em: str = VLogger.fmt_buf()
if fb_em is not None:
viperlib.snprintf(fb_em, 1024, "member table full for %s", enum_entry.Name)
VLogger.error(fb_em, "ENUM")
return -1
idx: int = enum_entry.MemberCount
me: GenTypes.EnumMember | t.CPtr = self._get_enum_member(enum_entry, idx)
if me is None:
return -1
# 复制成员名(永久池深拷贝:成员表跨 mark/reset 存活)
name_len: t.CSizeT = string.strlen(member_name)
name_buf: str = CompilerFactory.get_perm_pool().alloc(name_len + 1)
if name_buf is not None:
string.strcpy(name_buf, member_name)
me.Name = name_buf
me.Value = member_val
me.Ty = member_ty
enum_entry.MemberCount = idx + 1
return idx
def find_enum(self, name: str) -> GenTypes.EnumEntry | t.CPtr:
"""按枚举类名查找,返回 EnumEntry 或 None"""
if name is None or self.EnumTable is None:
return None
for i in range(self.EnumCount):
entry: GenTypes.EnumEntry | t.CPtr = self._get_enum_entry(i)
if entry is not None and entry.Name is not None:
if string.strcmp(entry.Name, name) == 0:
return entry
return None
def lookup_enum_member(self, enum_name: str,
member_name: str) -> GenTypes.EnumMember | t.CPtr:
"""按枚举类名和成员名查找,返回 EnumMember 或 None"""
if enum_name is None or member_name is None:
return None
entry: GenTypes.EnumEntry | t.CPtr = self.find_enum(enum_name)
if entry is None:
return None
for mi in range(entry.MemberCount):
me: GenTypes.EnumMember | t.CPtr = self._get_enum_member(entry, mi)
if me is not None and me.Name is not None:
if string.strcmp(me.Name, member_name) == 0:
return me
return None
def is_enum_class(self, name: str) -> int:
"""检查类名是否为已注册枚举,返回 1=是 / 0=否"""
if name is None:
return 0
if self.find_enum(name) is not None:
return 1
return 0
def get_enum_base_type(self, enum_name: str) -> llvmlite.LLVMType | t.CPtr:
"""按枚举类名获取基准类型"""
entry: GenTypes.EnumEntry | t.CPtr = self.find_enum(enum_name)
if entry is not None:
return entry.BaseTy
return None
-758
View File
@@ -1,758 +0,0 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import lib.core.Gen.CallGenFd as CallGenFd
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdlib
import stdio
import viperlib
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.CompilerFactory as CompilerFactory
import lib.Projectrans.Config as Config
import lib.core.Gen.StmtGenDeclMixin as StmtGenDeclMixin
# ============================================================
# StmtGenMixin - 语句/流程翻译 Mixin(P6 对象化重构 A 组,拆分后顶层)
#
# 按功能域拆分(500~1500 行/文件架构重构,试点):
# 顶层保留语句分派中枢簇 A(translate_stmt/try/raise/with/delete/
# _eh_ret_default/_with_ctx_call);global/nonlocal/枚举/CIf 已拆至
# StmtGenDeclMixin / StmtGenCifMixin(本类继承链 1/3、2/3)。
# 外部引用(Generator.vp import + HandlesBody 委托)零改动。
#
# @t.NoVTable:静态绑定(方法沿 MRO 直接调用 + this 调整)。
# ============================================================
# ============================================================
# StmtGenMixin - 语句分派中枢(顶层)
# ============================================================
@t.NoVTable
class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
"""语句/流程翻译 Mixin(拆分后顶层,继承 Decl)"""
def translate_stmt(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""翻译单条语句,返回新增的变量数(深度守卫包装器,原逻辑见 _translate_stmt_impl)"""
if trans is not None:
if trans.TranslateDepth >= HT.TRANSLATE_MAX_DEPTH:
self.Gen.TypeGenErr.fatal_error(
node, "语句翻译嵌套过深(疑似 AST 自引用环或翻译 bug)", "RecursionError")
trans.TranslateDepth += 1
mret: int = self._translate_stmt_impl(trans, node)
if trans is not None:
trans.TranslateDepth -= 1
return mret
def _translate_stmt_impl(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""翻译单条语句,返回新增的变量数"""
if node is None:
return 0
k: int = node.kind()
if k == ast.ASTKind.Expr:
return self.translate_expr_stmt(trans, node)
elif k == ast.ASTKind.Assign:
return self.Gen.AssignGen.translate_assign(trans, node)
elif k == ast.ASTKind.AnnAssign:
return self.Gen.AssignGen.translate_annassign(trans, node)
elif k == ast.ASTKind.FunctionDef:
# 嵌套函数定义:提升为顶层函数 + 创建闭包
return self.Gen.FuncGen.translate_nested_function_def(trans, node)
elif k == ast.ASTKind.Return:
return self.Gen.StmtGenCif.translate_return(trans, node)
elif k == ast.ASTKind.If:
return self.Gen.StmtGenCif.translate_if(trans, node)
elif k == ast.ASTKind.While:
return self.Gen.StmtGenCif.translate_while(trans, node)
elif k == ast.ASTKind.AugAssign:
return self.Gen.AssignGen.translate_augassign(trans, node)
elif k == ast.ASTKind.For:
return self.Gen.StmtGenCif.translate_for(trans, node)
elif k == ast.ASTKind.ClassDef:
return self.Gen.ClassGenDef.translate_class_def(trans, node)
elif k == ast.ASTKind.Import:
return self.Gen.ImportsGen.translate_import(trans, node)
elif k == ast.ASTKind.ImportFrom:
self.Gen.ImportsGen.translate_import_from_module(trans, node)
return self.Gen.ImportsGen.translate_import_from_names(trans, node)
elif k == ast.ASTKind.Global:
return self.translate_global(trans, node)
elif k == ast.ASTKind.Nonlocal:
return self.translate_nonlocal(trans, node)
elif k == ast.ASTKind.Pass:
return 0
elif k == ast.ASTKind.Break:
return self.translate_break(trans)
elif k == ast.ASTKind.Continue:
return self.translate_continue(trans)
elif k == ast.ASTKind.Match:
return self.Gen.MatchGen.translate_match(trans, node)
elif k == ast.ASTKind.Delete:
return self.translate_delete(trans, node)
elif k == ast.ASTKind.Raise:
return self.translate_raise(trans, node)
elif k == ast.ASTKind.Try:
return self.translate_try(trans, node)
elif k == ast.ASTKind.With:
return self.translate_with(trans, node)
# 未知语句类型:直接报错终止(fail-fast),暴露上游解析问题
self.Gen.TypeGenErr.fatal_error(node, f"未知语句类型 kind={k}", "SyntaxError")
return 0
def translate_delete(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""翻译 del 语句,返回 0"""
if trans is None or node is None:
return 0
dnode: ast.Delete | t.CPtr = (ast.Delete | t.CPtr)(node)
if dnode is None or dnode.targets is None:
return 0
pool: memhub.MemBuddy | t.CPtr = trans.Pool
mod: llvmlite.LLVMModule | t.CPtr = trans.Module
builder: llvmlite.IRBuilder | t.CPtr = trans._cur_builder
if pool is None or mod is None or builder is None:
return 0
tn: t.CSizeT = len(dnode.targets)
for ti in range(tn):
tgt: ast.AST | t.CPtr = dnode.targets.get(ti)
if tgt is None:
continue
# del obj.attr → property deleter
if tgt.kind() == ast.ASTKind.Attribute:
del_at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(tgt)
if del_at is not None:
self.Gen.CallGenProp._try_property_deleter(
pool, builder, mod, del_at, trans)
# del obj[key] → __delitem__(obj, key)
# (对齐 TPC HandlesDelete.py L14-25;此前 Subscript 目标被静默
# 忽略,HashTableTest Test 11 的 del ht3[del_buf] 未执行 → len 不变)
elif tgt.kind() == ast.ASTKind.Subscript:
dl_sub: ast.Subscript | t.CPtr = (ast.Subscript | t.CPtr)(tgt)
if dl_sub is not None and dl_sub.value is not None and dl_sub.slice is not None:
dl_obj: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, dl_sub.value, None, 0, trans)
dl_idx: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, dl_sub.slice, None, 0, trans)
if dl_obj is not None and dl_idx is not None:
dl_st: llvmlite.LLVMType | t.CPtr = self.Gen.StructGen.get_struct_type_from_value(
dl_obj)
if dl_st is not None:
dl_cls: str = self.Gen.StructGen.get_class_name_by_type(pool, dl_st)
if dl_cls is not None:
dl_lookup: str = pool.alloc(128)
if dl_lookup is not None:
viperlib.snprintf(dl_lookup, 128, "%s.__delitem__", dl_cls)
dl_fn: llvmlite.Function | t.CPtr = self.Gen.CallGenBase.find_func_in_module(
mod, dl_lookup)
if dl_fn is not None:
dl_ret: llvmlite.LLVMType | t.CPtr = llvmlite.function_get_ret_ty(dl_fn)
dl_fn_name: str = llvmlite.function_get_name(dl_fn)
if dl_ret is not None and dl_fn_name is not None:
# self 参数(容器)按 __delitem__ 首参类型对齐
dl_self: llvmlite.Value | t.CPtr = dl_obj
dl_first: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(dl_fn)
if dl_first is not None:
dl_self_ty: llvmlite.LLVMType | t.CPtr = llvmlite.param_get_ty(dl_first)
if dl_self_ty is not None:
dl_self = llvmlite.build_bitcast(builder, dl_obj, dl_self_ty)
if dl_self is not None:
# 参数链表: self → key(独立 null 结尾)
dl_self.Next = dl_idx
dl_idx.Next = None
llvmlite.build_call(
builder, dl_fn_name, dl_self, 2, dl_ret, 0)
return 0
# ============================================================
# translate_try - try/except 语句翻译
#
# 对齐 TPC HandlesTry.py L59-214(v1 迁移)。异常 = (i32 code, i8* msg)
# 二元组;except X 编译期静态展开 X 及全部子孙异常类编码。布局:
# pre → TryBB → DispatchBB(编码检查链)→ MatchBB → ExceptBB → AfterBB
# try 体正常结束 → store 0 →(else 若有)→ AfterBB
# DispatchBB 无匹配 → UnhandledBB:函数有 EH 输出参数则写入并返回
# (向上传播),否则 store 0 → AfterBB(吞掉)
# try 上下文(DispatchBB + 两个 alloca)写入 trans._eh_*,translate_raise
# 与调用侧 EH 检测(CallGenMixin._emit_eh_guarded_call)据此重定向;
# 退出时恢复外层上下文(嵌套 try 支持)。
# v1 未实现 finalbody(TestFBProject 未使用;finally+return 交互复杂,
# 后续按 TPC HandlesTry.py L188-214 的 pending_ret 方案扩展)。
# ============================================================
def translate_try(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""翻译 try/except 语句,返回 0"""
if trans is None or node is None:
return 0
tr: ast.Try | t.CPtr = (ast.Try | t.CPtr)(node)
if tr is None:
return 0
pool: memhub.MemBuddy | t.CPtr = trans.Pool
builder: llvmlite.IRBuilder | t.CPtr = trans._cur_builder
func: llvmlite.Function | t.CPtr = trans._cur_func
if pool is None or builder is None or func is None:
return 0
if llvmlite.builder_cur_block_is_terminated(builder) != 0:
return 0
# ---- 1. 异常编码/消息 alloca(当前 try 上下文)----
i32_ty_t: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
i8_ty_t: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_ty_t: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty_t)
if i32_ty_t is None or i8_ptr_ty_t is None:
return 0
exception_code: llvmlite.Value | t.CPtr = llvmlite.build_alloca(builder, i32_ty_t)
eh_message: llvmlite.Value | t.CPtr = llvmlite.build_alloca(builder, i8_ptr_ty_t)
if exception_code is None or eh_message is None:
return 0
init_code_t: llvmlite.Value | t.CPtr = llvmlite.const_int32(pool, 0)
init_msg_t: llvmlite.Value | t.CPtr = llvmlite.ConstNull(pool, i8_ptr_ty_t, "null")
if init_code_t is not None:
llvmlite.build_store(builder, init_code_t, exception_code)
if init_msg_t is not None:
llvmlite.build_store(builder, init_msg_t, eh_message)
# ---- 2. 块布局(块名唯一化,create_block 不自动编号)----
lc_t: int = trans._label_counter
trans._label_counter = lc_t + 1
bb_buf_t: str = pool.alloc(64)
if bb_buf_t is None:
return 0
viperlib.snprintf(bb_buf_t, 64, "try.dispatch.%d", lc_t)
dispatch_bb: llvmlite.BasicBlock | t.CPtr = llvmlite.create_block(pool, func, bb_buf_t)
viperlib.snprintf(bb_buf_t, 64, "try.after.%d", lc_t)
after_bb: llvmlite.BasicBlock | t.CPtr = llvmlite.create_block(pool, func, bb_buf_t)
viperlib.snprintf(bb_buf_t, 64, "try.unhandled.%d", lc_t)
unhandled_bb: llvmlite.BasicBlock | t.CPtr = llvmlite.create_block(pool, func, bb_buf_t)
viperlib.snprintf(bb_buf_t, 64, "try.body.%d", lc_t)
try_bb: llvmlite.BasicBlock | t.CPtr = llvmlite.create_block(pool, func, bb_buf_t)
if dispatch_bb is None or after_bb is None or unhandled_bb is None or try_bb is None:
return 0
else_bb: llvmlite.BasicBlock | t.CPtr = None
if tr.orelse is not None and len(tr.orelse) > 0:
viperlib.snprintf(bb_buf_t, 64, "try.else.%d", lc_t)
else_bb = llvmlite.create_block(pool, func, bb_buf_t)
n_handlers_t: t.CSizeT = 0
if tr.handlers is not None:
n_handlers_t = len(tr.handlers)
# 注意:list[ptr](pool, N) 第二参数是 elem_size 不是容量!
# 指针元素必须显式传 8(T.__sizeof__ 对联合类型不可靠),
# 否则 append 按 N 字节步长错位存取 → 读出垃圾指针(崩溃)。
except_bbs: list[llvmlite.BasicBlock | t.CPtr] | t.CPtr = \
list[llvmlite.BasicBlock | t.CPtr](pool, 8)
match_bbs: list[llvmlite.BasicBlock | t.CPtr] | t.CPtr = \
list[llvmlite.BasicBlock | t.CPtr](pool, 8)
if except_bbs is None or match_bbs is None:
return 0
for i_t in range(n_handlers_t):
viperlib.snprintf(bb_buf_t, 64, "try.except.%d.%d", lc_t, i_t)
eb_t: llvmlite.BasicBlock | t.CPtr = llvmlite.create_block(pool, func, bb_buf_t)
viperlib.snprintf(bb_buf_t, 64, "try.match.%d.%d", lc_t, i_t)
mb_t: llvmlite.BasicBlock | t.CPtr = llvmlite.create_block(pool, func, bb_buf_t)
except_bbs.append(eb_t)
match_bbs.append(mb_t)
# ---- 3. 保存外层 try 上下文,设置本 try 上下文 ----
old_dispatch_t: llvmlite.BasicBlock | t.CPtr = trans._eh_dispatch_bb
old_code_t: llvmlite.Value | t.CPtr = trans._eh_code_ptr
old_msg_t: llvmlite.Value | t.CPtr = trans._eh_msg_ptr
trans._eh_dispatch_bb = dispatch_bb
trans._eh_code_ptr = exception_code
trans._eh_msg_ptr = eh_message
# ---- 4. Try 体 ----
llvmlite.build_br(builder, try_bb)
llvmlite.position_at_end(builder, try_bb)
if tr.children is not None:
tb_n: t.CSizeT = len(tr.children)
for tbi in range(tb_n):
stmt_t: ast.AST | t.CPtr = tr.children.get(tbi)
if stmt_t is not None:
self.translate_stmt(trans, stmt_t)
# 正常走完 try 体:清异常码,进 else(若有)或 After
if llvmlite.builder_cur_block_is_terminated(builder) == 0:
if init_code_t is not None:
llvmlite.build_store(builder, init_code_t, exception_code)
if else_bb is not None:
llvmlite.build_br(builder, else_bb)
else:
llvmlite.build_br(builder, after_bb)
# ---- 5. DispatchBB:编码检查链 ----
llvmlite.position_at_end(builder, dispatch_bb)
if n_handlers_t == 0:
# 无 except(非法 try,防御):直接吞掉
llvmlite.build_br(builder, unhandled_bb)
else:
eh_code_val: llvmlite.Value | t.CPtr = llvmlite.build_load(
builder, i32_ty_t, exception_code)
next_check_t: llvmlite.BasicBlock | t.CPtr = None
for i_t in range(n_handlers_t):
hdr_t: ast.ExceptHandler | t.CPtr = (ast.ExceptHandler | t.CPtr)(
tr.handlers.get(i_t))
match_bb_t: llvmlite.BasicBlock | t.CPtr = match_bbs.get(i_t)
if i_t > 0 and next_check_t is not None:
llvmlite.position_at_end(builder, next_check_t)
eh_code_val = llvmlite.build_load(
builder, i32_ty_t, exception_code)
if hdr_t is None or hdr_t.type is None:
# 裸 except:无条件匹配
llvmlite.build_br(builder, match_bb_t)
else:
# 展开匹配编码集(handler.type 自身 + 全部子孙异常类)
hname_t: str = None
htype_t: ast.AST | t.CPtr = hdr_t.type
if htype_t.kind() == ast.ASTKind.Name:
hname_t = ((ast.Name | t.CPtr)(htype_t)).id
elif htype_t.kind() == ast.ASTKind.Attribute:
hname_t = ((ast.Attribute | t.CPtr)(htype_t)).attr
# 匹配编码缓冲:必须是 t.CInt*(i32 步长)——collect_descendant_codes
# 按 out_codes[i](4 字节)写入;若用 t.CSizeT*(8 字节)读,
# 多码时读出 (code[i+1]<<32)|code[i] 的 64 位拼接垃圾(单码时
# 高位恰为 0 掩盖;Test 2/3/8 父类多码捕获失败的根因)。
codes_buf_t: t.CInt | t.CPtr = pool.alloc(64 * 4)
if codes_buf_t is None:
return 0
n_codes_t: int = 0
if hname_t is not None and self.Gen.ClassGenBase.is_exception_name(hname_t) != 0:
n_codes_t = self.Gen.ClassGenBase.collect_descendant_codes(
hname_t, codes_buf_t, 64)
else:
c0_t: int = 1
if hname_t is not None:
c0_t = self.Gen.ClassGenBase.lookup_exception_code(hname_t)
if c0_t == 0:
c0_t = 1
codes_buf_t[0] = c0_t
n_codes_t = 1
match_cond_t: llvmlite.Value | t.CPtr = None
for ci_t in range(n_codes_t):
code_val_t: llvmlite.Value | t.CPtr = llvmlite.const_int32(
pool, codes_buf_t[ci_t])
is_match_t: llvmlite.Value | t.CPtr = llvmlite.build_icmp(
builder, llvmlite.ICMP_EQ, eh_code_val, code_val_t)
if match_cond_t is None:
match_cond_t = is_match_t
elif is_match_t is not None:
match_cond_t = llvmlite.build_or(
builder, match_cond_t, is_match_t)
if i_t < n_handlers_t - 1:
viperlib.snprintf(bb_buf_t, 64, "try.check.%d.%d", lc_t, i_t)
next_check_t = llvmlite.create_block(pool, func, bb_buf_t)
else:
next_check_t = unhandled_bb
llvmlite.build_cond_br(
builder, match_cond_t, match_bb_t, next_check_t)
# MatchBB:绑定 handler 变量(e = 消息指针),跳 ExceptBB
llvmlite.position_at_end(builder, match_bb_t)
if hdr_t is not None and hdr_t.name is not None and hdr_t.name[0] != '\0':
# 同名 handler 变量复用函数入口 alloca(find_var 首条命中、
# 重复 define_var 不 shadow——多 try 共用 `as e` 时各 MatchBB
# 各自 store 消息,各 ExceptBB 读同一入口 alloca,避免
# 非支配 alloca 触发 "does not dominate all uses")。
h_ent_t: GenTypes.VarEntry | t.CPtr = self.Gen.VarGen.lookup_var_entry(
trans.SymTab, hdr_t.name)
if h_ent_t is not None and h_ent_t.Alloca is not None:
h_alloca_t: llvmlite.Value | t.CPtr = h_ent_t.Alloca
else:
h_alloca_t = self.Gen.VarGen._alloca_at_entry(builder, i8_ptr_ty_t)
if h_alloca_t is not None:
self.Gen.VarGen.define_var(trans.SymTab, hdr_t.name, h_alloca_t)
if h_alloca_t is not None:
h_msg_t: llvmlite.Value | t.CPtr = llvmlite.build_load(
builder, i8_ptr_ty_t, eh_message)
if h_msg_t is not None:
llvmlite.build_store(builder, h_msg_t, h_alloca_t)
except_bb_t: llvmlite.BasicBlock | t.CPtr = except_bbs.get(i_t)
llvmlite.build_br(builder, except_bb_t)
# ---- 6. UnhandledBB:无 except 匹配 → 向上传播或吞掉 ----
llvmlite.position_at_end(builder, unhandled_bb)
if trans._cur_func_has_eh != 0 and trans._eh_msg_out_param is not None \
and trans._eh_code_out_param is not None:
un_msg_t: llvmlite.Value | t.CPtr = llvmlite.build_load(
builder, i8_ptr_ty_t, eh_message)
un_code_t: llvmlite.Value | t.CPtr = llvmlite.build_load(
builder, i32_ty_t, exception_code)
if un_msg_t is not None:
llvmlite.build_store(builder, un_msg_t, trans._eh_msg_out_param)
if un_code_t is not None:
llvmlite.build_store(builder, un_code_t, trans._eh_code_out_param)
self._eh_ret_default(trans, pool, builder)
else:
if init_code_t is not None:
llvmlite.build_store(builder, init_code_t, exception_code)
llvmlite.build_br(builder, after_bb)
# ---- 7. Except 体 ----
for i_t in range(n_handlers_t):
hdr2_t: ast.ExceptHandler | t.CPtr = (ast.ExceptHandler | t.CPtr)(
tr.handlers.get(i_t))
except_bb2_t: llvmlite.BasicBlock | t.CPtr = except_bbs.get(i_t)
llvmlite.position_at_end(builder, except_bb2_t)
if hdr2_t is not None and hdr2_t.children is not None:
hn2_t: t.CSizeT = len(hdr2_t.children)
for hi2 in range(hn2_t):
stmt2_t: ast.AST | t.CPtr = hdr2_t.children.get(hi2)
if stmt2_t is not None:
self.translate_stmt(trans, stmt2_t)
if llvmlite.builder_cur_block_is_terminated(builder) == 0:
llvmlite.build_br(builder, after_bb)
# ---- 8. Else 体 ----
if else_bb is not None:
llvmlite.position_at_end(builder, else_bb)
if tr.orelse is not None:
en_t: t.CSizeT = len(tr.orelse)
for ei in range(en_t):
stmt3_t: ast.AST | t.CPtr = tr.orelse.get(ei)
if stmt3_t is not None:
self.translate_stmt(trans, stmt3_t)
if llvmlite.builder_cur_block_is_terminated(builder) == 0:
llvmlite.build_br(builder, after_bb)
# ---- 9. AfterBB + 恢复外层 try 上下文 ----
llvmlite.position_at_end(builder, after_bb)
trans._eh_dispatch_bb = old_dispatch_t
trans._eh_code_ptr = old_code_t
trans._eh_msg_ptr = old_msg_t
return 0
def translate_raise(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""翻译 raise 语句,返回 0
对齐 TPC HandlesRaise.py 的 try-except 语义(v1 迁移):
- StopIteration:置 __stop_iter_flag__ = 1 后返回默认值(迭代结束协议)
- 非 StopIteration:
1. 处于 try(trans._eh_dispatch_bb 非空):异常编码/消息写入当前
try 的 eh_code/eh_message alloca,分支到 DispatchBB 由 except 链裁决
2. 函数有 EH 输出参数(_cur_func_has_eh):编码/消息写入
__eh_code_out__/__eh_msg_out__,返回默认值(异常向调用方传播)
3. 两者皆无:仅返回默认值(v1 语义:异常被吞掉)
"""
if trans is None or node is None:
return 0
pool: memhub.MemBuddy | t.CPtr = trans.Pool
builder: llvmlite.IRBuilder | t.CPtr = trans._cur_builder
mod: llvmlite.LLVMModule | t.CPtr = trans.Module
if pool is None or builder is None or mod is None:
return 0
rn: ast.Raise | t.CPtr = (ast.Raise | t.CPtr)(node)
if rn is None or rn.exc is None:
# bare raise(无 exc):v1 无活动异常可再抛,仅返回默认值
self._eh_ret_default(trans, pool, builder)
return 0
# OOP iter 协议:raise StopIteration → 置 __stop_iter_flag__ = 1
# (对齐 TPC HandlesRaise.py L76-100),随后按迭代结束返回默认值。
ek: int = rn.exc.kind()
is_stop_iter: int = 0
if ek == ast.ASTKind.Name:
enm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(rn.exc)
if enm.id is not None and string.strcmp(enm.id, "StopIteration") == 0:
is_stop_iter = 1
elif ek == ast.ASTKind.Call:
ecl: ast.Call | t.CPtr = (ast.Call | t.CPtr)(rn.exc)
if ecl.func is not None and ecl.func.kind() == ast.ASTKind.Name:
ecn: ast.Name | t.CPtr = (ast.Name | t.CPtr)(ecl.func)
if ecn.id is not None and string.strcmp(ecn.id, "StopIteration") == 0:
is_stop_iter = 1
if is_stop_iter != 0:
if trans._stop_iter_flag_ptr is not None:
i1_ty_r: llvmlite.LLVMType | t.CPtr = llvmlite.Int1(pool)
if i1_ty_r is not None:
one_i1_r: llvmlite.Value | t.CPtr = llvmlite.ConstInt(
pool, i1_ty_r, 1, "1")
if one_i1_r is not None:
llvmlite.build_store(
builder, one_i1_r, trans._stop_iter_flag_ptr)
self._eh_ret_default(trans, pool, builder)
return 0
# ---- 非 StopIteration:计算异常编码与消息 ----
exc_code: int = 1
exc_msg: llvmlite.Value | t.CPtr = None
if ek == ast.ASTKind.Constant:
cst_r: ast.Constant | t.CPtr = (ast.Constant | t.CPtr)(rn.exc)
if cst_r is not None and cst_r.const_kind == ast.CONST_INT:
exc_code = cst_r.int_val
elif ek == ast.ASTKind.Name:
enm2: ast.Name | t.CPtr = (ast.Name | t.CPtr)(rn.exc)
if enm2.id is not None:
exc_code = self.Gen.ClassGenBase.lookup_exception_code(enm2.id)
if exc_code == 0:
exc_code = 1
elif ek == ast.ASTKind.Call:
ecl2: ast.Call | t.CPtr = (ast.Call | t.CPtr)(rn.exc)
if ecl2.func is not None and ecl2.func.kind() == ast.ASTKind.Name:
ecn2: ast.Name | t.CPtr = (ast.Name | t.CPtr)(ecl2.func)
if ecn2.id is not None:
exc_code = self.Gen.ClassGenBase.lookup_exception_code(ecn2.id)
if exc_code == 0:
exc_code = 1
if ecl2.args is not None and len(ecl2.args) > 0:
exc_msg = self.Gen.ExprGen.translate_value(
builder, pool, mod, ecl2.args.get(0), None, 0, trans)
# ---- 异常分发 ----
# 1. try 内:写入 try 的 eh_code/eh_message alloca,分支 DispatchBB
if trans._eh_dispatch_bb is not None and trans._eh_code_ptr is not None:
code_val_r: llvmlite.Value | t.CPtr = llvmlite.const_int32(pool, exc_code)
if code_val_r is not None:
llvmlite.build_store(builder, code_val_r, trans._eh_code_ptr)
if exc_msg is None:
exc_msg = llvmlite.ConstNull(
pool, llvmlite.Ptr(pool, llvmlite.Int8(pool)), "null")
if exc_msg is not None and trans._eh_msg_ptr is not None:
llvmlite.build_store(builder, exc_msg, trans._eh_msg_ptr)
llvmlite.build_br(builder, trans._eh_dispatch_bb)
return 0
# 2. 函数有 EH 输出参数:写入 out 参数(异常向调用方传播)
if trans._cur_func_has_eh != 0 and trans._eh_msg_out_param is not None \
and trans._eh_code_out_param is not None:
code_val_r2: llvmlite.Value | t.CPtr = llvmlite.const_int32(pool, exc_code)
if code_val_r2 is not None:
llvmlite.build_store(builder, code_val_r2, trans._eh_code_out_param)
if exc_msg is None:
exc_msg = llvmlite.ConstNull(
pool, llvmlite.Ptr(pool, llvmlite.Int8(pool)), "null")
if exc_msg is not None:
llvmlite.build_store(builder, exc_msg, trans._eh_msg_out_param)
self._eh_ret_default(trans, pool, builder)
return 0
# 3. 无 try 无 EH 参数:仅返回默认值
self._eh_ret_default(trans, pool, builder)
return 0
# ============================================================
# _eh_ret_default - 异常路径返回当前函数的默认零值
#
# 对齐 TPC HandlesTry.py UnhandledBB / HandlesRaise.py 的返回生成
# (void→ret_void,ptr→null,float→0.0,其余→零值)。当前块已
# 终结(前一条是 raise/return)时跳过,避免在终止块后插指令。
# ============================================================
def _eh_ret_default(self, trans: HT.Translator | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr) -> int:
"""异常路径返回当前函数默认零值,返回 0"""
if trans is None or builder is None:
return 0
if llvmlite.builder_cur_block_is_terminated(builder) != 0:
return 0
cur_func: llvmlite.Function | t.CPtr = trans._cur_func
if cur_func is None:
return 0
ret_ty: llvmlite.LLVMType | t.CPtr = llvmlite.function_get_ret_ty(cur_func)
if ret_ty is None:
return 0
match ret_ty:
case llvmlite.LLVMType.Void():
llvmlite.build_ret_void(builder)
case llvmlite.LLVMType.Ptr(_pe):
null_v: llvmlite.Value | t.CPtr = llvmlite.ConstNull(pool, ret_ty, "null")
if null_v is not None:
llvmlite.build_ret(builder, null_v)
case llvmlite.LLVMType.Float(_fb):
zero_f: llvmlite.Value | t.CPtr = llvmlite.ConstFloat(pool, ret_ty, 0.0)
if zero_f is not None:
llvmlite.build_ret(builder, zero_f)
case _:
zero_v: llvmlite.Value | t.CPtr = llvmlite.ConstZero(pool, ret_ty)
if zero_v is not None:
llvmlite.build_ret(builder, zero_v)
return 0
# ============================================================
# translate_with - with 语句翻译
#
# with expr() as x: body
# → 1. 构造 context 对象(expr() 调用)
# 2. 调用 __enter__()(沿继承链 MRO 解析,如 MemPool → MemManager)
# 3. x 绑定 __enter__ 返回值(缺失时退回 context 对象)
# 4. 翻译 body
# 5. 逆序调用 __exit__()(无异常传播,__exit__ 仅做清理如 reset)
#
# 实现:context 对象存入临时变量 __with_ctx,方法调用复用
# translate_call 的 Attribute 分派 + _translate_method_call 的 MRO 回退
# (与普通 obj.method() 调用一致)。嵌套 with 时保存外层 __with_ctx,
# 退出后恢复,避免内层覆盖导致外层 __exit__ 解析到错误对象。
# 对齐 TPC HandlesWith 语义(不含 __provides__ provider 机制)。
# ============================================================
def translate_with(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""翻译 with 语句,返回 0"""
if trans is None or node is None:
return 0
wt: ast.With | t.CPtr = (ast.With | t.CPtr)(node)
if wt is None:
return 0
pool: memhub.MemBuddy | t.CPtr = trans.Pool
builder: llvmlite.IRBuilder | t.CPtr = trans._cur_builder
mod: llvmlite.LLVMModule | t.CPtr = trans.Module
if pool is None or builder is None or mod is None:
return 0
items: list[ast.AST | t.CPtr] | t.CPtr = wt.items
if items is None:
return 0
n: t.CSizeT = len(items)
# 保存外层 __with_ctx(嵌套 with 时退出后恢复)
old_ctx_alloca: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_var(
trans.SymTab, "__with_ctx")
# 记录每个 item 的类名(__exit__ 调用时设置 __with_ctx 类型用)
ctx_class_buf: t.CSizeT | t.CPtr = pool.alloc(8 * n)
if ctx_class_buf is None:
return 0
string.memset(ctx_class_buf, 0, 8 * n)
# 1. 全部 item:构造 context 对象 + 调用 __enter__ + asname 绑定
for i in range(n):
item_node: ast.AST | t.CPtr = items.get(i)
if item_node is None:
continue
if item_node.kind() != ast.ASTKind.WithItem:
continue
item: ast.WithItem | t.CPtr = (ast.WithItem | t.CPtr)(item_node)
if item is None or item.context_expr is None:
continue
# 翻译 context_expr(对象构造,如 MemPool(arena, 4096))
ctx_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, item.context_expr,
trans._funcs, trans._func_count, trans)
if ctx_val is None:
continue
# 提取类名(注解类名,Attribute 时带模块前缀,如 "memhub.MemPool"——
# 与 AnnAssign 的 extract_class_name_from_annotation 一致,跨模块
# 方法调用靠 AnnotClassName 回退按模块名查 SHA1)
cls_name: str = None
ce: ast.AST | t.CPtr = item.context_expr
if ce.kind() == ast.ASTKind.Call:
cfn: ast.AST | t.CPtr = ((ast.Call | t.CPtr)(ce)).func
if cfn is not None:
if cfn.kind() == ast.ASTKind.Attribute:
ca: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(cfn)
if ca.value is not None and ca.value.kind() == ast.ASTKind.Name:
cmn: ast.Name | t.CPtr = (ast.Name | t.CPtr)(ca.value)
if cmn.id is not None:
ml2: t.CSizeT = string.strlen(cmn.id) + string.strlen(ca.attr) + 2
mbuf: str = pool.alloc(ml2)
if mbuf is not None:
viperlib.snprintf(mbuf, ml2, "%s.%s", cmn.id, ca.attr)
cls_name = mbuf
if cls_name is None:
cls_name = ca.attr
elif cfn.kind() == ast.ASTKind.Name:
cls_name = ((ast.Name | t.CPtr)(cfn)).id
# context 对象存入 __with_ctx(方法调用解析 self 用)
ctx_alloca: llvmlite.Value | t.CPtr = llvmlite.build_alloca(
builder, ctx_val.Ty)
if ctx_alloca is None:
continue
llvmlite.build_store(builder, ctx_val, ctx_alloca)
self.Gen.VarGen.define_var(trans.SymTab, "__with_ctx", ctx_alloca)
if cls_name is not None:
self.Gen.VarGen.set_var_annot_class_name(
trans.SymTab, "__with_ctx", cls_name)
ctx_class_buf[i] = t.CSizeT(cls_name)
# 调用 __enter__()
enter_val: llvmlite.Value | t.CPtr = self._with_ctx_call(
trans, pool, builder, mod, "__enter__")
# asname 绑定(__enter__ 返回值,缺失时退回 context 对象)
if item.optional_vars is not None:
ov: ast.AST | t.CPtr = item.optional_vars
if ov.kind() == ast.ASTKind.Name:
asname: str = ((ast.Name | t.CPtr)(ov)).id
if asname is not None:
bind_val: llvmlite.Value | t.CPtr = enter_val
if bind_val is None:
bind_val = ctx_val
if bind_val is not None:
asn_alloca: llvmlite.Value | t.CPtr = llvmlite.build_alloca(
builder, bind_val.Ty)
if asn_alloca is not None:
llvmlite.build_store(builder, bind_val, asn_alloca)
self.Gen.VarGen.define_var(trans.SymTab, asname, asn_alloca)
if cls_name is not None:
self.Gen.VarGen.set_var_annot_class_name(
trans.SymTab, asname, cls_name)
# FakeDuck: __provides__ 元数据 → 生成 _push_provider 调用
# (对齐 TPC HandlesWith.py L138-159;经 gen 单例委托 CallGenMixin。
# 类名去模块前缀后查 __provides__ 表,如 memhub.MemBuddy → MemBuddy)
fd_cls_bare: str = cls_name
if cls_name is not None:
fd_dot: str = string.strrchr(cls_name, '.')
if fd_dot is not None:
fd_cls_bare = fd_dot + 1
if fd_cls_bare is not None:
self.Gen.CallGenFd._emit_provider_push(
trans, pool, builder, mod, fd_cls_bare, ctx_val)
# 2. 翻译 body
body: list[ast.AST | t.CPtr] | t.CPtr = wt.children
if body is not None:
bn: t.CSizeT = len(body)
for bi in range(bn):
st: ast.AST | t.CPtr = body.get(bi)
if st is not None:
self.translate_stmt(trans, st)
# 2.5 FakeDuck: __provides__ 元数据 → 生成 _pop_provider 调用
# (对齐 TPC HandlesWith.py L167-174;逆序弹栈,经 gen 单例委托)
for pi_pop in range(n):
if ctx_class_buf[pi_pop] != 0:
cls_pop: str = (str | t.CPtr)(t.CVoid(ctx_class_buf[pi_pop], t.CPtr))
fd_cls_bare_p: str = cls_pop
if cls_pop is not None:
fd_dot_p: str = string.strrchr(cls_pop, '.')
if fd_dot_p is not None:
fd_cls_bare_p = fd_dot_p + 1
if fd_cls_bare_p is not None:
self.Gen.CallGenFd._emit_provider_pop(
trans, pool, builder, mod, fd_cls_bare_p)
# 3. 逆序调用 __exit__()(Python with 语义:后进先出)
for i in range(n):
ci: int = n - 1 - i
if ctx_class_buf[ci] != 0:
cls_ci: str = (str | t.CPtr)(t.CVoid(ctx_class_buf[ci], t.CPtr))
self.Gen.VarGen.set_var_annot_class_name(
trans.SymTab, "__with_ctx", cls_ci)
self._with_ctx_call(trans, pool, builder, mod, "__exit__")
# 4. 恢复外层 __with_ctx
if old_ctx_alloca is not None:
self.Gen.VarGen.define_var(trans.SymTab, "__with_ctx", old_ctx_alloca)
return 0
def _with_ctx_call(self, trans: HT.Translator | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
method_name: str) -> llvmlite.Value | t.CPtr:
"""构造并翻译 __with_ctx.<method_name>() 调用(__enter__/__exit__)"""
if pool is None or method_name is None:
return None
name_node: ast.AST | t.CPtr = ast.Name(pool, "__with_ctx", ast.ASTCtx.Load)
if name_node is None:
return None
attr_node: ast.AST | t.CPtr = ast.Attribute(
pool, name_node, method_name, ast.ASTCtx.Load)
if attr_node is None:
return None
call_node: ast.AST | t.CPtr = ast.Call(pool, attr_node, None, None)
if call_node is None:
return None
return self.Gen.ExprGen.translate_value(
builder, pool, mod, call_node,
trans._funcs, trans._func_count, trans)
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Loaded 100 of 176 files, more files were not shown because too many files have changed in this diff. Show more