Compare commits

...
94 Commits
Author SHA1 Message Date
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
77 changed files with 19244 additions and 17370 deletions

No files matched your search

+138 -70
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.GenApi as GenApi
import t, c
from stdint import *
import stdio
import string
@@ -262,8 +261,8 @@ class BuildResult:
OutputPath: str
ErrorMsg: str
def __new__(self) -> t.CPtr:
return t.CPtr(_mbuddy.alloc(BuildResult.__sizeof__()))
def __new__(self) -> rawptr:
return rawptr(_mbuddy.alloc(BuildResult.__sizeof__()))
def __init__(self):
self.Success = 0
@@ -576,12 +575,12 @@ def _run_cmd_wait(cmd: str) -> int:
string.memset(&sa, 0, win32.win32base.SECURITY_ATTRIBUTES.__sizeof__())
string.memset(&pi, 0, win32process.PROCESS_INFORMATION.__sizeof__())
sa.nLength = win32.win32base.SECURITY_ATTRIBUTES.__sizeof__()
sa.lpSecurityDescriptor = t.CPtr(0)
sa.lpSecurityDescriptor = None
sa.bInheritHandle = 1
si.cb = win32process.STARTUPINFOA.__sizeof__()
# create_flags=0: the child inherits the parent console (output shown, like system);
# si.dwFlags stays 0 after memset (no STARTF_USESTDHANDLES, no std handles).
ok: int = win32process.CreateProcessA(t.CPtr(0), cmd_line, t.CPtr(0), t.CPtr(0), 1, 0, t.CPtr(0), t.CPtr(0), &si, &pi)
ok: int = win32process.CreateProcessA(None, cmd_line, None, None, 1, 0, None, None, &si, &pi)
# CreateProcessA failure diagnostics: read GetLastError immediately (later calls overwrite
# it). Use warning, not error (error would sys.exit in a worker thread).
if ok == 0:
@@ -597,10 +596,10 @@ def _run_cmd_wait(cmd: str) -> int:
return int(exit_code)
def _llc_task_worker(arg: t.CPtr) -> t.CPtr:
def _llc_task_worker(arg: rawptr) -> rawptr:
"""llc thread entry: only calls _run_cmd_wait, no pool allocation (thread-safe)."""
task: LlcTask* = (LlcTask*)(arg)
if task is None: return t.CPtr(0)
if task is None: return None
# MLIR backend: validate (mlir-opt) -> lower (mlir-translate), then ASan opt -> llc
if task.MlirOptCmd is not None:
mo_rc: int = _run_cmd_wait(task.MlirOptCmd)
@@ -608,21 +607,21 @@ def _llc_task_worker(arg: t.CPtr) -> t.CPtr:
task.ExitCode = mo_rc
task.FailStage = LLC_STAGE_MLIR_OPT
task.Result = 1
return t.CPtr(0)
return None
if task.MlirTranslateCmd is not None:
mt_rc: int = _run_cmd_wait(task.MlirTranslateCmd)
if mt_rc != 0:
task.ExitCode = mt_rc
task.FailStage = LLC_STAGE_MLIR_TRANSLATE
task.Result = 1
return t.CPtr(0)
return None
if task.OptCmd is not None:
opt_rc: int = _run_cmd_wait(task.OptCmd)
if opt_rc != 0:
task.ExitCode = opt_rc
task.FailStage = LLC_STAGE_ASAN_OPT
task.Result = 1
return t.CPtr(0)
return None
llc_rc: int = _run_cmd_wait(task.LlcCmd)
task.ExitCode = llc_rc
if llc_rc != 0:
@@ -631,7 +630,7 @@ def _llc_task_worker(arg: t.CPtr) -> t.CPtr:
else:
task.FailStage = LLC_STAGE_NONE
task.Result = 0
return t.CPtr(0)
return None
def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int:
@@ -650,7 +649,7 @@ def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int:
return 0
if max_threads <= 0: max_threads = 1
# Thread-handle array: list[str] (str=bytes=i8*, explicit elem_size=8) stores handles;
# list[t.CPtr] append crashes reading page zero. Grab the handle right after start().
# list[rawptr] append crashes reading page zero. Grab the handle right after start().
handles: list[str]* = list[str](_mbuddy, 8)
if handles is None:
VLogger.error("run_llc_parallel: failed to allocate thread handle array", "PhaseB")
@@ -664,13 +663,13 @@ def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int:
# spawn this batch
for j in range(int(batch_n)):
tsk: LlcTask* = (LlcTask*)(uint64(tasks) + sizet(batch_start + j) * slot_size)
th: vthreading.Thread* = vthreading.Thread(_llc_task_worker, t.CPtr(tsk))
th: vthreading.Thread* = vthreading.Thread(_llc_task_worker, rawptr(tsk))
th.start()
# Read the handle right after start() (the Thread object's stack slot may be
# reused; reading self.handle at join could see a cleared/overwritten value).
handles.append(t.CPtr(th.handle))
handles.append(rawptr(th.handle))
for j in range(int(batch_n)):
h_j: t.CPtr = t.CPtr(handles.get(batch_start + j))
h_j: rawptr = rawptr(handles.get(batch_start + j))
wr: uint32 = win32sync.WaitForSingleObject(h_j, 600000)
if wr != 0: VLogger.error("llc thread join timeout/failure (not finished within 600 seconds)", "PhaseB")
win32.win32base.CloseHandle(h_j)
@@ -1052,8 +1051,8 @@ class TbPeLayout:
SizeOfImage: uint64
TextRva: uint64
def __new__(self) -> t.CPtr:
return t.CPtr(_mbuddy.alloc(TbPeLayout.__sizeof__()))
def __new__(self) -> rawptr:
return rawptr(_mbuddy.alloc(TbPeLayout.__sizeof__()))
def __init__(self):
self.Ok = 0
@@ -1071,8 +1070,8 @@ class TbDump:
FuncCap: int
FuncCount: int
def __new__(self) -> t.CPtr:
return t.CPtr(_mbuddy.alloc(TbDump.__sizeof__()))
def __new__(self) -> rawptr:
return rawptr(_mbuddy.alloc(TbDump.__sizeof__()))
def __init__(self):
self.RowBuf = None
@@ -1090,8 +1089,8 @@ class TbPool:
Offs: bytes
OffCount: int
def __new__(self) -> t.CPtr:
return t.CPtr(_mbuddy.alloc(TbPool.__sizeof__()))
def __new__(self) -> rawptr:
return rawptr(_mbuddy.alloc(TbPool.__sizeof__()))
def __init__(self):
self.Pool = None
@@ -1118,9 +1117,12 @@ def _tb_rd_u32(buf: bytes, off: sizet) -> uint32:
def _tb_rd_u64(buf: bytes, off: sizet) -> uint64:
lo: uint64 = uint64(_tb_rd_u32(buf, off))
hi: uint64 = uint64(_tb_rd_u32(buf, off + 4))
return lo | (hi << 32)
# Assemble via memcpy instead of u32 widening + shifts: the manual form left 0xFFFFFFFF in
# the high half (a stale register/slot), which then looked like a wild pointer in the
# stackmap tables. memcpy is exact and little-endian on x86-64.
v: uint64 = 0
string.memcpy(&v, buf + off, 8)
return v
def _tb_wr_u32(buf: bytes, off: sizet, v: uint64):
@@ -1131,8 +1133,18 @@ def _tb_wr_u32(buf: bytes, off: sizet, v: uint64):
def _tb_wr_u64(buf: bytes, off: sizet, v: uint64):
_tb_wr_u32(buf, off, v & 0xFFFFFFFF)
_tb_wr_u32(buf, off + 4, (v >> 32) & 0xFFFFFFFF)
string.memcpy(buf + off, &v, 8)
def _tb_wr_ptr(buf: bytes, off: sizet, p: str):
"""Store a string pointer into a byte table without a ptr->int cast.
Do NOT write `memcpy(buf + off, &p, 8)`: passing `&p` (i8**) to a `rawptr` parameter is
currently compiled as a dereference (it copies the string's first 8 bytes instead of the
pointer value), so go through a typed slot instead.
"""
slot: str* = str*(buf + off)
slot[0] = p
# ---- tiny string helpers (no re module) ----
@@ -1142,6 +1154,18 @@ def _tb_cc(s: str, i: sizet) -> int:
return int(s[i]) & 0xFF
def _tb_str_ok(s: str) -> int:
"""Return 1 when s is a plausible user-space string pointer (not 0, -1 or garbage).
DWARF/objdump text is parsed into pointer-bearing tables; a single bad slot would later be
dereferenced by _tb_cc/strlen and crash the compiler with ASan "unknown address 0xffff...".
Callers use this to reject such a slot at the boundary instead of storing it.
"""
if s is None: return 0
v: uint64 = uint64(s)
return 1 if v >= 65536 and v <= 0x7FFFFFFFFFFF else 0
def _tb_contains(s: str, sub: str) -> int:
if s is None or sub is None: return 0
return 1 if string.strstr(s, sub) is not None else 0
@@ -1331,7 +1355,13 @@ def _tb_quoted_after(mb: memhub.MemBuddy*, s: str, key: str) -> str:
def _tb_dir_lookup(dirs: bytes, count: int, idx: int) -> str:
for i in range(int(count)):
if int(_tb_rd_u32(dirs, sizet(i) * TB_DIR_SIZE)) == idx: return rawptr(_tb_rd_u64(dirs, sizet(i) * TB_DIR_SIZE + 8))
if int(_tb_rd_u32(dirs, sizet(i) * TB_DIR_SIZE)) == idx:
p: uint64 = _tb_rd_u64(dirs, sizet(i) * TB_DIR_SIZE + 8)
# fail-fast: a wild name pointer would crash inside _tb_is_abs_path/strlen later.
if p < 65536 or p > 0x7FFFFFFFFFFF:
VLogger.error(f"--traceback: internal error: DWARF directory record {i} has an invalid name pointer ({p})", "TRACEBACK")
return None
return rawptr(p)
return None
@@ -1345,8 +1375,20 @@ def _tb_resolve_file(mb: memhub.MemBuddy*, files: bytes, fcount: int, dirs: byte
"""Resolve a file-table entry to a path (absolute name wins, else dir join)."""
roff: sizet = sizet(frec) * TB_FILE_SIZE
cached: uint64 = _tb_rd_u64(files, roff + 24)
if cached != 0: return rawptr(cached)
name: str = rawptr(_tb_rd_u64(files, roff + 8))
if cached != 0:
# fail-fast: a wild cached path would crash inside strlen/is_abs_path below; report the
# record that carries it together with the table state instead.
if cached < 65536 or cached > 0x7FFFFFFFFFFF:
VLogger.error(f"--traceback: internal error: DWARF file record {frec} has an invalid cached path pointer ({cached})", "TRACEBACK")
return None
return rawptr(cached)
nptr: uint64 = _tb_rd_u64(files, roff + 8)
if nptr < 65536 or nptr > 0x7FFFFFFFFFFF:
ridx: uint64 = uint64(_tb_rd_u32(files, roff))
rdir: uint64 = uint64(_tb_rd_u32(files, roff + 4))
VLogger.error(f"--traceback: internal error: DWARF file record {frec} has an invalid name pointer ({nptr}); record idx={ridx} dir_index={rdir}, records in table={fcount}", "TRACEBACK")
return None
name: str = rawptr(nptr)
path: str = name
if name is not None and _tb_is_abs_path(name) == 0:
didx: int = int(_tb_rd_u32(files, roff + 4))
@@ -1357,7 +1399,7 @@ def _tb_resolve_file(mb: memhub.MemBuddy*, files: bytes, fcount: int, dirs: byte
if root is not None and _tb_is_abs_path(root) != 0: base = _tb_join(mb, root, base)
path = _tb_join(mb, base, name)
if path is None or string.strlen(path) == 0: path = "??"
_tb_wr_u64(files, roff + 24, uint64(path))
_tb_wr_ptr(files, roff + 24, path)
return path
@@ -1371,6 +1413,10 @@ def _tb_grow_rec(mb: memhub.MemBuddy*, buf: bytes, old_cap: int, rec_size: int,
def _tb_emit_row(mb: memhub.MemBuddy*, d: TbDump*, addr: uint64, path: str, line: int, fname: str = None) -> int:
if d is None: return 1
# Every row must carry a file path. A caller that could not resolve one passes None (or a
# wild sentinel); storing that as-is makes the later strlen() sizing pass fault. Normalise to
# the stackmap's existing "??" convention (the DWARF path already does the same).
if _tb_str_ok(path) == 0: path = "??"
if d.RowCount >= d.RowCap:
ncap: int = d.RowCap * 2
nb: bytes = _tb_grow_rec(mb, d.RowBuf, d.RowCap, TB_ROW_SIZE, ncap)
@@ -1381,11 +1427,10 @@ def _tb_emit_row(mb: memhub.MemBuddy*, d: TbDump*, addr: uint64, path: str, line
d.RowCap = ncap
off: sizet = sizet(d.RowCount) * TB_ROW_SIZE
_tb_wr_u64(d.RowBuf, off, addr)
_tb_wr_u64(d.RowBuf, off + TB_ROW_OFF_FILE, uint64(path))
_tb_wr_ptr(d.RowBuf, off + TB_ROW_OFF_FILE, path)
_tb_wr_u32(d.RowBuf, off + TB_ROW_OFF_LINE, uint64(line))
fv: uint64 = 0
if fname is not None: fv = uint64(fname)
_tb_wr_u64(d.RowBuf, off + TB_ROW_OFF_FUNC, fv)
if _tb_str_ok(fname) != 0: _tb_wr_ptr(d.RowBuf, off + TB_ROW_OFF_FUNC, fname)
else: _tb_wr_u64(d.RowBuf, off + TB_ROW_OFF_FUNC, 0)
d.RowCount = d.RowCount + 1
d.RawRowCount = d.RawRowCount + 1
return 0
@@ -1404,7 +1449,7 @@ def _tb_emit_func(mb: memhub.MemBuddy*, d: TbDump*, low: uint64, high: uint64, n
off: sizet = sizet(d.FuncCount) * TB_FUNC_SIZE
_tb_wr_u64(d.FuncBuf, off, low)
_tb_wr_u64(d.FuncBuf, off + 8, high)
_tb_wr_u64(d.FuncBuf, off + 16, uint64(name))
_tb_wr_ptr(d.FuncBuf, off + 16, name)
d.FuncCount = d.FuncCount + 1
return 0
@@ -1549,10 +1594,10 @@ def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump*:
if in_line != 0:
if _tb_startswith(st, "debug_line[") != 0:
# new CU: commit pending file, reset both tables
if pkind == 1 and pname is not None and file_count < TB_FILE_CAP:
if pkind == 1 and _tb_str_ok(pname) != 0 and file_count < TB_FILE_CAP:
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx))
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir))
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, uint64(pname))
_tb_wr_ptr(files, sizet(file_count) * TB_FILE_SIZE + 8, pname)
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0))
file_count += 1
pkind = 0
@@ -1562,10 +1607,10 @@ def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump*:
file_count = 0
continue
if _tb_startswith(st, "file_names[") != 0:
if pkind == 1 and pname is not None and file_count < TB_FILE_CAP:
if pkind == 1 and _tb_str_ok(pname) != 0 and file_count < TB_FILE_CAP:
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx))
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir))
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, uint64(pname))
_tb_wr_ptr(files, sizet(file_count) * TB_FILE_SIZE + 8, pname)
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0))
file_count += 1
pname = None
@@ -1574,10 +1619,10 @@ def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump*:
pidx = _tb_bracket_idx(st)
continue
if _tb_startswith(st, "directory_names[") != 0 or _tb_startswith(st, "include_directories[") != 0:
if pkind == 1 and pname is not None and file_count < TB_FILE_CAP:
if pkind == 1 and _tb_str_ok(pname) != 0 and file_count < TB_FILE_CAP:
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx))
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir))
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, uint64(pname))
_tb_wr_ptr(files, sizet(file_count) * TB_FILE_SIZE + 8, pname)
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0))
file_count += 1
pname = None
@@ -1586,9 +1631,13 @@ def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump*:
val: str = _tb_value_after_sep(_tb_after_bracket(st))
if val is not None and _tb_len_trimmed(val) > 0:
if dir_count < TB_DIR_CAP:
_tb_wr_u32(dirs, sizet(dir_count) * TB_DIR_SIZE, uint64(pidx))
_tb_wr_u64(dirs, sizet(dir_count) * TB_DIR_SIZE + 8, uint64(_tb_unquote(mb, val)))
dir_count += 1
dname: str = _tb_unquote(mb, val)
if _tb_str_ok(dname) != 0:
_tb_wr_u32(dirs, sizet(dir_count) * TB_DIR_SIZE, uint64(pidx))
_tb_wr_ptr(dirs, sizet(dir_count) * TB_DIR_SIZE + 8, dname)
dir_count += 1
else:
VLogger.warning("--traceback: ignoring a DWARF directory entry whose name could not be copied", "TRACEBACK")
pkind = 0
else: pkind = 2
continue
@@ -1596,9 +1645,9 @@ def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump*:
val2: str = _tb_unquote(mb, _tb_ltrim(st + 5))
if pkind == 1: pname = val2
else:
if dir_count < TB_DIR_CAP and val2 is not None:
if dir_count < TB_DIR_CAP and _tb_str_ok(val2) != 0:
_tb_wr_u32(dirs, sizet(dir_count) * TB_DIR_SIZE, uint64(pidx))
_tb_wr_u64(dirs, sizet(dir_count) * TB_DIR_SIZE + 8, uint64(val2))
_tb_wr_ptr(dirs, sizet(dir_count) * TB_DIR_SIZE + 8, val2)
dir_count += 1
pkind = 0
continue
@@ -1608,10 +1657,10 @@ def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump*:
pdir = _tb_parse_dec(_tb_ltrim(st + 10))
continue
if _tb_startswith(st, "Address") != 0 and _tb_contains(st, "Column") != 0:
if pkind == 1 and pname is not None and file_count < TB_FILE_CAP:
if pkind == 1 and _tb_str_ok(pname) != 0 and file_count < TB_FILE_CAP:
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx))
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir))
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, uint64(pname))
_tb_wr_ptr(files, sizet(file_count) * TB_FILE_SIZE + 8, pname)
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0))
file_count += 1
pkind = 0
@@ -1619,10 +1668,10 @@ def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump*:
pdir = 0
continue
if _tb_cc(linebuf, 0) == 48 and _tb_cc(linebuf, 1) == 120:
if pkind == 1 and pname is not None and file_count < TB_FILE_CAP:
if pkind == 1 and _tb_str_ok(pname) != 0 and file_count < TB_FILE_CAP:
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx))
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir))
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, uint64(pname))
_tb_wr_ptr(files, sizet(file_count) * TB_FILE_SIZE + 8, pname)
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0))
file_count += 1
pkind = 0
@@ -1657,7 +1706,7 @@ def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump*:
# ---- DWARF debug_info subprogram parsing ----
if _tb_contains(linebuf, "DW_TAG_") != 0:
if in_prog != 0 and cur_has_low != 0 and cname is not None and string.strlen(cname) > 0:
if in_prog != 0 and cur_has_low != 0 and _tb_str_ok(cname) != 0 and string.strlen(cname) > 0:
hi: uint64 = cur_low + 1
if cur_has_high != 0:
hi = cur_high
@@ -1734,8 +1783,8 @@ class TbCallParse:
Grp: bytes # array of TB_GRP_SIZE slots
GrpCap: int
def __new__(self) -> t.CPtr:
return t.CPtr(_mbuddy.alloc(TbCallParse.__sizeof__()))
def __new__(self) -> rawptr:
return rawptr(_mbuddy.alloc(TbCallParse.__sizeof__()))
def __init__(self):
self.Cr = None
@@ -1776,8 +1825,8 @@ def _tb_grp_get(grp: bytes, cap: int, caller: str, callee: str, do_insert: int)
if uint64(_tb_rd_u32(grp, off + TB_GRP_OFF_USED)) == 0:
if do_insert == 0: return -1
_tb_wr_u32(grp, off + TB_GRP_OFF_USED, uint64(1))
_tb_wr_u64(grp, off + TB_GRP_OFF_CALLER, uint64(caller))
_tb_wr_u64(grp, off + TB_GRP_OFF_CALLEE, uint64(callee))
_tb_wr_ptr(grp, off + TB_GRP_OFF_CALLER, caller)
_tb_wr_ptr(grp, off + TB_GRP_OFF_CALLEE, callee)
_tb_wr_u32(grp, off + TB_GRP_OFF_HEAD, uint64(TB_IDX_NONE))
_tb_wr_u32(grp, off + TB_GRP_OFF_TAIL, uint64(TB_IDX_NONE))
_tb_wr_u32(grp, off + TB_GRP_OFF_CURSOR, uint64(TB_IDX_NONE))
@@ -1811,9 +1860,9 @@ def _tb_cp_add_cr(mb: memhub.MemBuddy*, cp: TbCallParse*, caller: str, callee: s
cp.Cr = nb
cp.CrCap = ncap
off: sizet = sizet(cp.CrN) * TB_CR_SIZE
_tb_wr_u64(cp.Cr, off + TB_CR_OFF_CALLER, uint64(caller))
_tb_wr_u64(cp.Cr, off + TB_CR_OFF_CALLEE, uint64(callee))
_tb_wr_u64(cp.Cr, off + TB_CR_OFF_FILE, uint64(file))
_tb_wr_ptr(cp.Cr, off + TB_CR_OFF_CALLER, caller)
_tb_wr_ptr(cp.Cr, off + TB_CR_OFF_CALLEE, callee)
_tb_wr_ptr(cp.Cr, off + TB_CR_OFF_FILE, file)
_tb_wr_u32(cp.Cr, off + TB_CR_OFF_LINE, uint64(line))
_tb_wr_u32(cp.Cr, off + TB_CR_OFF_NEXT, uint64(TB_IDX_NONE))
cp.CrN = cp.CrN + 1
@@ -1830,8 +1879,8 @@ def _tb_cp_add_fr(mb: memhub.MemBuddy*, cp: TbCallParse*, sym: str, file: str, d
cp.Fr = nb
cp.FrCap = ncap
off: sizet = sizet(cp.FrN) * TB_FR_SIZE
_tb_wr_u64(cp.Fr, off + TB_FR_OFF_SYM, uint64(sym))
_tb_wr_u64(cp.Fr, off + TB_FR_OFF_FILE, uint64(file))
_tb_wr_ptr(cp.Fr, off + TB_FR_OFF_SYM, sym)
_tb_wr_ptr(cp.Fr, off + TB_FR_OFF_FILE, file)
_tb_wr_u32(cp.Fr, off + TB_FR_OFF_DEFLINE, uint64(defline))
cp.FrN = cp.FrN + 1
return 0
@@ -1853,6 +1902,10 @@ def _tb_read_whole(mb: memhub.MemBuddy*, path: str) -> str:
got: LONG = f.read_all(buf, sizet(sz) + 2)
f.close()
if got < 0: return None
# NUL-terminate at the actually read length. The pool buffer is NOT zero-filled (MemBuddy's
# diagnostic fill is 0xCD), so without this the caller's strlen() walks into uninitialised
# bytes and can run past the end of the arena.
buf[got] = 0
return buf
@@ -1873,7 +1926,7 @@ def _tb_parse_f_line(mb: memhub.MemBuddy*, s: str, a: sizet, b: sizet, cp: TbCal
defline: int = int(_tb_parse_dec(s + p))
p = f3 + 1
file: str = _tb_dup_span(mb, s + p, b - p)
if sym is None or file is None: return 0
if _tb_str_ok(sym) == 0 or _tb_str_ok(file) == 0: return 0
return _tb_cp_add_fr(mb, cp, sym, file, defline)
@@ -1899,7 +1952,7 @@ def _tb_parse_c_line(mb: memhub.MemBuddy*, s: str, a: sizet, b: sizet, cp: TbCal
line: int = int(_tb_parse_dec(s + p))
p = e4 + 1
file: str = _tb_dup_span(mb, s + p, b - p)
if caller is None or callee is None or file is None: return 0
if _tb_str_ok(caller) == 0 or _tb_str_ok(callee) == 0 or _tb_str_ok(file) == 0: return 0
return _tb_cp_add_cr(mb, cp, caller, callee, file, line)
@@ -1967,7 +2020,7 @@ def _tb_build_groups(mb: memhub.MemBuddy*, cp: TbCallParse*) -> int:
def _tb_emit_call_row(mb: memhub.MemBuddy*, d: TbDump*, cp: TbCallParse*, img_lo: uint64, caller: str, callee: str, ret_va: uint64) -> int:
"""Emit a row for a return address from the next unconsumed C record of (caller, callee)."""
if caller is None or callee is None or cp.Grp is None or ret_va < img_lo: return 0
if _tb_str_ok(caller) == 0 or _tb_str_ok(callee) == 0 or cp.Grp is None or ret_va < img_lo: return 0
slot: int = _tb_grp_get(cp.Grp, cp.GrpCap, caller, callee, 0)
if slot < 0: return 0
goff: sizet = sizet(slot) * TB_GRP_SIZE
@@ -1984,11 +2037,11 @@ def _tb_emit_call_row(mb: memhub.MemBuddy*, d: TbDump*, cp: TbCallParse*, img_lo
def _tb_emit_entry_row(mb: memhub.MemBuddy*, d: TbDump*, cp: TbCallParse*, img_lo: uint64, addr: uint64, sym: str) -> int:
"""Emit a fallback function-entry row for an objdump label sym (file/line from the F record)."""
if sym is None or cp.Fr is None or cp.FrN <= 0 or addr < img_lo: return 0
if _tb_str_ok(sym) == 0 or cp.Fr is None or cp.FrN <= 0 or addr < img_lo: return 0
for i in range(int(cp.FrN)):
off: sizet = sizet(i) * TB_FR_SIZE
s: str = rawptr(_tb_rd_u64(cp.Fr, off + TB_FR_OFF_SYM))
if s is not None and string.strcmp(s, sym) == 0:
if _tb_str_ok(s) != 0 and string.strcmp(s, sym) == 0:
file: str = rawptr(_tb_rd_u64(cp.Fr, off + TB_FR_OFF_FILE))
dl: int = int(_tb_rd_u32(cp.Fr, off + TB_FR_OFF_DEFLINE))
return _tb_emit_row(mb, d, addr, file, dl, sym)
@@ -2066,7 +2119,7 @@ def _tb_scan_objdump(mb: memhub.MemBuddy*, dump: str, img_lo: uint64, d: TbDump*
j: sizet = lb
while j < le and _tb_cc(dump, j) != 62: j += 1
cur_func = _tb_dup_span(mb, dump + lb, j - lb)
if cur_func is not None: _tb_emit_entry_row(mb, d, cp, img_lo, laddr, cur_func)
if _tb_str_ok(cur_func) != 0: _tb_emit_entry_row(mb, d, cp, img_lo, laddr, cur_func)
continue
# ---- instruction line `ADDR: bytes mnemonic operands` ----
k: sizet = ls
@@ -2275,7 +2328,14 @@ def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int:
pool_need: sizet = 1
i = 0
while i < uniq:
pth: str = rawptr(_tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE))
fptr: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE)
# fail-fast: Windows x64 user addresses stay below 0x7FFF_FFFF_FFFF; anything else is a
# wild pointer (0, -1 or an uninitialised slot). Report the record instead of letting
# strlen walk off into unmapped memory (ASan access-violation at 0xffffffffffffffff).
if fptr < 65536 or fptr > 0x7FFFFFFFFFFF:
VLogger.error(f"--traceback: internal error: line record {i} carries an invalid file-path pointer ({fptr})", "TRACEBACK")
return 1
pth: str = rawptr(fptr)
pool_need += string.strlen(pth) + 1
i += 1
# our rows carry the function name directly (string pool); DWARF rows take it from the subprogram table.
@@ -2283,13 +2343,21 @@ def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int:
while i < uniq:
fp: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FUNC)
if fp != 0:
if fp > 0x7FFFFFFFFFFF:
VLogger.error(f"--traceback: internal error: line record {i} carries an invalid function-name pointer ({fp})", "TRACEBACK")
return 1
fns: str = rawptr(fp)
if fns is not None: pool_need += string.strlen(fns) + 1
pool_need += string.strlen(fns) + 1
i += 1
i = 0
while i < fkept:
nm0: str = rawptr(_tb_rd_u64(d.FuncBuf, sizet(i) * TB_FUNC_SIZE + 16))
if nm0 is not None: pool_need += string.strlen(nm0) + 1
nmf: uint64 = _tb_rd_u64(d.FuncBuf, sizet(i) * TB_FUNC_SIZE + 16)
if nmf != 0:
if nmf > 0x7FFFFFFFFFFF:
VLogger.error(f"--traceback: internal error: subprogram record {i} carries an invalid name pointer ({nmf})", "TRACEBACK")
return 1
nm0: str = rawptr(nmf)
pool_need += string.strlen(nm0) + 1
i += 1
p: TbPool* = TbPool()
if p is None:
+45 -44
View File
@@ -1,11 +1,10 @@
import t, c
from stdint import *
import string
import stdlib
import memhub
import viperlib
import w32.fileio as fileio
import w32.win32base
import win32.fileio as fileio
import win32.win32base
import ast
import mlirlite
import lib.core.VLogger as VLogger
@@ -133,7 +132,7 @@ def _BreakContainerCyclesInDeps(temp_dir: str) -> int:
if StubMerger._includes_deps_sha1 is None or StubMerger._includes_deps_str is None: return 0
cnt: sizet = StubMerger._includes_deps_count
ci: sizet
for ci in range(cnt):
for ci in range(int(cnt)):
sha1_m: str = StubMerger._includes_deps_sha1 + sizet(ci) * 17
deps_m: str = StubMerger._includes_deps_str + sizet(ci) * StubMerger.MAX_DEPS_STR_LEN
if sha1_m is None or deps_m is None: continue
@@ -195,7 +194,7 @@ def _BuildReachableResult(mb: memhub.MemBuddy*, includes_dir: str) -> IncludesSc
if result is None: return None
entry_size: sizet = IncludesScanner.FileEntry.__sizeof__()
i: sizet
for i in range(store_count):
for i in range(int(store_count)):
sidx: sizet = sizet(i) * 17
if StubMerger._sha1_store_arr[sidx] == '\0': continue
# Only includes entries (non-empty incdir); App entries do not enter result
@@ -274,7 +273,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
# On-demand dependency scan (App-first): seed the queue from App imports, then BFS-resolve
# reachable includes only (read + SHA1 + import parse); unreferenced ones are never read.
t_scan0: uint64 = w32.win32base.GetTickCount64()
t_scan0: uint64 = win32.win32base.GetTickCount64()
reachable_set: str = None
reachable_count: int = 0
use_reachable: int = 0
@@ -298,7 +297,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
else:
VLogger.warning("SourceDir unavailable; cannot build the dependency graph; skipping Phase1", "Phase1")
return 0
t_scan1: uint64 = w32.win32base.GetTickCount64()
t_scan1: uint64 = win32.win32base.GetTickCount64()
VLogger.info(f"dependency-graph scan total time: {t_scan1 - t_scan0} ms (reachable {reachable_count} includes)", "Phase1")
# Build the reachable FileEntry list from the Sha1Store; 1a/1b-pre/1b iterate result
@@ -350,26 +349,28 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
p1_chkbase_addr: uint64 = uint64(mb.chk_base)
VLogger.info(f"[EVIDENCE] mb={p1_mb_addr} base={p1_base_addr} usable={p1_usable_addr} " f"usable_size={mb.usable_size} max_order={mb.max_order} self_check={p1_sc} " f"detection={mb.detection}", "Phase1")
VLogger.info(f"[EVIDENCE] chk_base={p1_chkbase_addr} chk_depth={mb.chk_depth} " f"chk_cap={mb.chk_cap} free_lists[clamp={p1_mo}]={p1_fl_head}", "Phase1")
p1_sz_cn: sizet = sizet(Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN)
p1_sz_cn: sizet = sizet(Phase1.MAX_CLASSES) * Phase1.CLASS_NAME_LEN
p1_sz_ds: sizet = sizet(Phase1.MAX_CLASSES * Phase1.SHA1_LEN)
p1_sz_tp: sizet = sizet(4 * Phase1.MAX_FILE_DEPS)
p1_sz_bx: sizet = sizet(4)
p1_o_cn: int = mb._order_for_size(p1_sz_cn + 16)
p1_o_ds: int = mb._order_for_size(p1_sz_ds + 16)
p1_o_tp: int = mb._order_for_size(p1_sz_tp + 16)
p1_o_bx: int = mb._order_for_size(p1_sz_bx + 16)
p1_o_cn: int = int(mb._order_for_size(p1_sz_cn + 16))
p1_o_ds: int = int(mb._order_for_size(p1_sz_ds + 16))
p1_o_tp: int = int(mb._order_for_size(p1_sz_tp + 16))
p1_o_bx: int = int(mb._order_for_size(p1_sz_bx + 16))
p1_fl_cn: uint64 = uint64(mb.free_lists[p1_o_cn])
p1_fl_ds: uint64 = uint64(mb.free_lists[p1_o_ds])
VLogger.info(f"[EVIDENCE] order(class_names {p1_sz_cn}B+16)={p1_o_cn} fl={p1_fl_cn} " f"order(def_sha1s {p1_sz_ds}B+16)={p1_o_ds} fl={p1_fl_ds} " f"order(topo {p1_sz_tp}B+16)={p1_o_tp} order(box {p1_sz_bx}B+16)={p1_o_bx} " f"(fl==0 only means that order is empty and can still be split by a higher order; if alloc fails while dump_stats shows free>0 that is the definitive proof of metadata corruption; " f"any order>max_order={mb.max_order} means an abnormal size)", "Phase1")
# Pre-scan class inheritance and build the topo order (flat byte arrays avoid str-pointer NULL crashes)
class_names_buf: bytes = mb.alloc(Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN)
parent_names_buf: bytes = mb.alloc(Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN)
# 取消整数提升后,模块属性 CDefine 按最小位宽物化为 i16/i8;512*64=32768 恰好越出 i16 有符号上限而回绕成负值,
# 首乘数显式 sizet() 加宽到 64 位。下面 def_sha1s_buf 的 512*17=8704 未溢出,无需改。
class_names_buf: bytes = mb.alloc(sizet(Phase1.MAX_CLASSES) * Phase1.CLASS_NAME_LEN)
parent_names_buf: bytes = mb.alloc(sizet(Phase1.MAX_CLASSES) * Phase1.CLASS_NAME_LEN)
def_sha1s_buf: bytes = mb.alloc(Phase1.MAX_CLASSES * Phase1.SHA1_LEN)
topo_order: t.CPtr = mb.alloc(4 * Phase1.MAX_FILE_DEPS)
topo_count_box: t.CPtr = mb.alloc(4)
generic_file_indices: t.CPtr = mb.alloc(4 * Phase1.MAX_FILE_DEPS)
generic_file_count_box: t.CPtr = mb.alloc(4)
topo_order: rawptr = mb.alloc(4 * Phase1.MAX_FILE_DEPS)
topo_count_box: rawptr = mb.alloc(4)
generic_file_indices: rawptr = mb.alloc(4 * Phase1.MAX_FILE_DEPS)
generic_file_count_box: rawptr = mb.alloc(4)
if generic_file_indices is not None: string.memset(generic_file_indices, 0, 4 * Phase1.MAX_FILE_DEPS)
if generic_file_count_box is not None: string.memset(generic_file_count_box, 0, 4)
topo_count: int = 0
@@ -380,8 +381,8 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
if parent_names_buf is None: VLogger.error("class-inheritance pre-scan flat buffer allocation failed: parent_names_buf is NULL; aborting compilation", "Phase1")
if def_sha1s_buf is None: VLogger.error("class-inheritance pre-scan flat buffer allocation failed: def_sha1s_buf is NULL; aborting compilation", "Phase1")
string.memset(class_names_buf, 0, Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN)
string.memset(parent_names_buf, 0, Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN)
string.memset(class_names_buf, 0, sizet(Phase1.MAX_CLASSES) * Phase1.CLASS_NAME_LEN)
string.memset(parent_names_buf, 0, sizet(Phase1.MAX_CLASSES) * Phase1.CLASS_NAME_LEN)
string.memset(def_sha1s_buf, 0, Phase1.MAX_CLASSES * Phase1.SHA1_LEN)
if topo_order is not None: string.memset(topo_order, 0, 4 * Phase1.MAX_FILE_DEPS)
if topo_count_box is not None: string.memset(topo_count_box, 0, 4)
@@ -394,7 +395,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
td_len_ts_call: sizet = string.strlen(temp_dir)
ret_topo: int = Phase1Topo._TopoSortFiles(mb, result, reachable_set, reachable_count, use_reachable, sha1_set, set_count, class_names_buf, parent_names_buf, def_sha1s_buf, class_count_scanned, topo_order, topo_count_box, temp_dir, td_len_ts_call)
if ret_topo == 0:
tc_ptr: t.CPtr = (int*)(topo_count_box)
tc_ptr: int* = (int*)(topo_count_box)
if tc_ptr is not None: topo_count = tc_ptr[0]
VLogger.info(f"topological sort: {class_count_scanned} classes, {topo_count} files processed in dependency order", "Phase1")
else:
@@ -415,17 +416,17 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
GenApi.set_stdlib_alloc(1)
# Iterate files: topo order preferred, falling back to alphabetical
iter_count: int = topo_count if topo_count > 0 else result.Count
iter_count: int = int(topo_count if topo_count > 0 else result.Count)
# 1a total-time start (struct/enum/union registration)
t_a0: uint64 = w32.win32base.GetTickCount64()
for i in range(iter_count):
t_a0: uint64 = win32.win32base.GetTickCount64()
for i in range(int(iter_count)):
# Per-file mark: free this file's AST after translating (registered data is in perm_pool)
p1a_mark: sizet = mb.mark()
# Get the file index: topo order or alphabetical
file_idx: int = i
file_idx: int = int(i)
if topo_count > 0:
od_addr: uint64 = uint64(topo_order) + i * 4
od_ptr: t.CPtr = (int*)(t.CPtr(od_addr))
od_ptr: int* = int*(od_addr)
if od_ptr is not None: file_idx = od_ptr[0]
entry_addr_r: uint64 = uint64(result.Entries) + file_idx * entry_size
@@ -451,7 +452,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
continue
# Per-file timer start (read + parse, to locate 1a stalls)
t_fa0: uint64 = w32.win32base.GetTickCount64()
t_fa0: uint64 = win32.win32base.GetTickCount64()
# Read file contents
file_path_r: str = entry_r.Path
@@ -496,7 +497,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
continue
# Timer 1: read + parse done
t_fa1: uint64 = w32.win32base.GetTickCount64()
t_fa1: uint64 = win32.win32base.GetTickCount64()
# Create Translator with _declare_only=1 (register struct/enum/union)
tr_r: HandlesTranslator.Translator* = HandlesTranslator.Translator()
@@ -515,7 +516,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
GenApi.reset_visible_structs(mb, 0)
ret_r: int = tr_r.translate(tree_r)
# Timer 2: translate(declare=1) returned
t_fa2: uint64 = w32.win32base.GetTickCount64()
t_fa2: uint64 = win32.win32base.GetTickCount64()
if ret_r != 0: p1a_fl += 1
else: p1a_reg += 1
@@ -540,7 +541,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
mb.reset_to(p1a_mark)
# 1a total time (struct/enum/union registration, incl. per-file read+parse+declaration)
t_a1: uint64 = w32.win32base.GetTickCount64()
t_a1: uint64 = win32.win32base.GetTickCount64()
VLogger.info(f"1a total time: {t_a1-t_a0} ms (registering struct/enum, reg={p1a_reg} skp={p1a_skp})", "Phase1")
if p1a_fl > 0:
@@ -567,8 +568,8 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
p1bpre_failed: int = 0
dir_len_pv: sizet = string.strlen(temp_dir)
# 1b-pre total-time start (vpi pre-generation)
t_pv0: uint64 = w32.win32base.GetTickCount64()
for i_pv in range(result.Count):
t_pv0: uint64 = win32.win32base.GetTickCount64()
for i_pv in range(int(result.Count)):
entry_addr_pv: uint64 = uint64(result.Entries) + i_pv * entry_size
entry_pv: IncludesScanner.FileEntry* = IncludesScanner.FileEntry*(entry_addr_pv)
if entry_pv is None or entry_pv.Sha1 is None:
@@ -663,7 +664,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
mb.reset_to(mark1)
# 1b-pre total time (vpi pre-generation)
t_pv1: uint64 = w32.win32base.GetTickCount64()
t_pv1: uint64 = win32.win32base.GetTickCount64()
VLogger.info(f"1b-pre total time: {t_pv1-t_pv0} ms (vpi pre-generation)", "Phase1")
if p1bpre_failed > 0:
@@ -676,16 +677,16 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
skipped: int = 0
failed: int = 0
# 1b total-time start (full translation)
t_b0: uint64 = w32.win32base.GetTickCount64()
t_b0: uint64 = win32.win32base.GetTickCount64()
loop_n_b: int = result.Count
loop_n_b: int = int(result.Count)
if topo_count > 0: loop_n_b = topo_count
for i in range(loop_n_b):
for i in range(int(loop_n_b)):
# Get the entry (topo: file_idx from topo_order, else the scan index)
file_idx_b: int = i
file_idx_b: int = int(i)
if topo_count > 0:
od_addr_b: uint64 = uint64(topo_order) + sizet(i) * 4
od_ptr_b: t.CPtr = (int*)(od_addr_b)
od_ptr_b: int* = (int*)(od_addr_b)
if od_ptr_b is not None: file_idx_b = od_ptr_b[0]
entry_addr: uint64 = uint64(result.Entries) + sizet(file_idx_b) * entry_size
entry: IncludesScanner.FileEntry* = IncludesScanner.FileEntry*(entry_addr)
@@ -793,7 +794,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
stdlib.free(stub_path)
# Per-file timer start: read (parse/translate/dump segments, matching Phase2 instrumentation)
t_f0: uint64 = w32.win32base.GetTickCount64()
t_f0: uint64 = win32.win32base.GetTickCount64()
# Read file contents
file_path: str = entry.Path
@@ -847,7 +848,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
continue
# Timer 1: read + lexer/tokenize/parse done
t_f1: uint64 = w32.win32base.GetTickCount64()
t_f1: uint64 = win32.win32base.GetTickCount64()
# Register CTypedef by scanning source text: AST string fields (attr/id) point into src_buf
# and dangle across phases (reading attr faults), so match "NAME: t.CTypedef = TYPE" lines.
@@ -901,7 +902,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
ret: int = tr.translate(tree)
# Timer 2: translate returned
t_f2: uint64 = w32.win32base.GetTickCount64()
t_f2: uint64 = win32.win32base.GetTickCount64()
# Diagnostic: translate returned normally (if it hangs inside, this line is not printed,
# distinguishing an internal hang from a later dump hang).
if ret == 0: VLogger.info(f"translation complete (translate returned): {file_path}", "Phase1")
@@ -996,7 +997,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
stdlib.free(vpcall_text_p1)
# Timer 3: dump (stub+text) done; print the per-file three-segment timing
t_f3: uint64 = w32.win32base.GetTickCount64()
t_f3: uint64 = win32.win32base.GetTickCount64()
VLogger.info(f"[{i+1}/{loop_n_b}] translating: {file_path} (parse={t_f1-t_f0} ms, translate={t_f2-t_f1} ms, dump={t_f3-t_f2} ms, total={t_f3-t_f0} ms)", "Phase1")
# save dependencies(_imported_modules) for Phase B
@@ -1023,14 +1024,14 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
# tables moved to stdlib). Memory sentinels locate overruns deterministically.
chk_rc: int = mb.check_alive_blocks(mark1)
if chk_rc != 0: VLogger.error(f"memory corruption (live block header, rc={chk_rc}): {file_path}", "Phase1")
# Out-of-bounds writes are the crash root cause (unrelated to reclamation/UAF, confirmed
# Out-of-bounds writes are the crash root cause (unrelated to reclamation/UAF, confirmed
# by A/B tests); reset_to plus the tail canary pins the offending file.
mb.reset_to(mark1)
sc_rc: int = mb.self_check()
if sc_rc != 0: VLogger.error(f"memory corruption (free list, rc={sc_rc}): {file_path}", "Phase1")
# 1b total time (full translation + dump)
t_b1: uint64 = w32.win32base.GetTickCount64()
t_b1: uint64 = win32.win32base.GetTickCount64()
VLogger.info(f"1b total time: {t_b1-t_b0} ms (full translation, trans={translated} skip={skipped} fail={failed})", "Phase1")
# Release the topo-sort resources used by Phase 1b
+19 -36
View File
@@ -1,31 +1,14 @@
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 mlirlite
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HandlesTranslator
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
@@ -40,7 +23,7 @@ import lib.core.Gen.GenApi as GenApi
# _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: t.CPtr, generic_file_count_box: t.CPtr) -> int:
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
@@ -50,7 +33,7 @@ def _ScanClassInheritance(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResu
entry_size: sizet = IncludesScanner.FileEntry.__sizeof__()
class_count: int = 0
for i in range(result.Count):
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()
@@ -106,7 +89,7 @@ def _ScanClassInheritance(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResu
ch: list[ast.AST*]* = tree.children
if ch is not None:
cn: sizet = len(ch)
for ci in range(cn):
for ci in range(int(cn)):
child: ast.AST* = ch.get(ci)
if child is None: continue
kd: int = child.kind()
@@ -121,12 +104,12 @@ def _ScanClassInheritance(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResu
if tp_scan is not None:
file_has_generic = 1
if generic_file_count_box is not None:
gfc_ptr_scan: t.CPtr = (int*)(generic_file_count_box)
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: uint64 = uint64(generic_file_indices) + gfc_scan * 4
gi_ptr_scan: t.CPtr = (int*)(gi_addr_scan)
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
@@ -226,7 +209,7 @@ def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy*, result: IncludesScanne
if perm is None: return 0
p1e_size: sizet = IncludesScanner.FileEntry.__sizeof__()
handled_count: int = 0
for i in range(result.Count):
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
@@ -269,7 +252,7 @@ def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy*, result: IncludesScanne
ch_pg: list[ast.AST*]* = tree_pg.children
if ch_pg is not None:
cn_pg: sizet = len(ch_pg)
for ci_pg in range(cn_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()
@@ -298,7 +281,7 @@ def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy*, result: IncludesScanne
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(class_count):
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
return None
@@ -322,7 +305,7 @@ def _AnnIsContainer(ann: ast.AST*) -> int:
return 0
def _ScanTreeForContainers(node: ast.AST*, need_list_box: t.CPtr, need_dict_box: t.CPtr, depth: int = 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.
@@ -349,12 +332,12 @@ def _ScanTreeForContainers(node: ast.AST*, need_list_box: t.CPtr, need_dict_box:
if cl_args_s is not None:
na_s: sizet = len(cl_args_s)
i_a2: sizet
for i_a2 in range(na_s): _ScanTreeForContainers(cl_args_s.get(i_a2), need_list_box, need_dict_box, depth + 1)
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: sizet = len(cl_kws_s)
i_k2: sizet
for i_k2 in range(nk_s):
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:
@@ -364,7 +347,7 @@ def _ScanTreeForContainers(node: ast.AST*, need_list_box: t.CPtr, need_dict_box:
if tgts_s is not None:
nt_s: sizet = len(tgts_s)
i_t2: sizet
for i_t2 in range(nt_s): _ScanTreeForContainers(tgts_s.get(i_t2), need_list_box, need_dict_box, depth + 1)
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* = ast.AnnAssign*(node)
_ScanTreeForContainers(aa_s2.annotation, need_list_box, need_dict_box, depth + 1)
@@ -378,7 +361,7 @@ def _ScanTreeForContainers(node: ast.AST*, need_list_box: t.CPtr, need_dict_box:
if fa_s is not None:
nf_s: sizet = len(fa_s)
i_f2: sizet
for i_f2 in range(nf_s):
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:
@@ -387,7 +370,7 @@ def _ScanTreeForContainers(node: ast.AST*, need_list_box: t.CPtr, need_dict_box:
if bases_s is not None:
nb_s: sizet = len(bases_s)
i_b2: sizet
for i_b2 in range(nb_s): _ScanTreeForContainers(bases_s.get(i_b2), need_list_box, need_dict_box, depth + 1)
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* = ast.Attribute*(node)
_ScanTreeForContainers(at_s.value, need_list_box, need_dict_box, depth + 1)
@@ -397,7 +380,7 @@ def _ScanTreeForContainers(node: ast.AST*, need_list_box: t.CPtr, need_dict_box:
if elts_s is not None:
ne_s: sizet = len(elts_s)
i_e2: sizet
for i_e2 in range(ne_s): _ScanTreeForContainers(elts_s.get(i_e2), need_list_box, need_dict_box, depth + 1)
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* = ast.UnaryOp*(node)
_ScanTreeForContainers(uop_s.operand, need_list_box, need_dict_box, depth + 1)
@@ -414,18 +397,18 @@ def _ScanTreeForContainers(node: ast.AST*, need_list_box: t.CPtr, need_dict_box:
if cps_s is not None:
nc_s: sizet = len(cps_s)
i_c2: sizet
for i_c2 in range(nc_s): _ScanTreeForContainers(cps_s.get(i_c2), need_list_box, need_dict_box, depth + 1)
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* = ast.BoolOp*(node)
bvs_s: list[ast.AST*]* = bo_s.values
if bvs_s is not None:
nv_s: sizet = len(bvs_s)
i_v2: sizet
for i_v2 in range(nv_s): _ScanTreeForContainers(bvs_s.get(i_v2), need_list_box, need_dict_box, depth + 1)
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*]* = node.children
n_s: sizet = len(ch_s)
ci_s: sizet
for ci_s in range(n_s): _ScanTreeForContainers(ch_s.get(ci_s), need_list_box, need_dict_box, depth + 1)
for ci_s in range(int(n_s)): _ScanTreeForContainers(ch_s.get(ci_s), need_list_box, need_dict_box, depth + 1)
+45 -59
View File
@@ -1,30 +1,14 @@
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 mlirlite
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HandlesTranslator
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 - topologically sort source files by class inheritance and import dependencies.
@@ -32,7 +16,7 @@ import lib.Phase.Phase1 as Phase1
# _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: t.CPtr, out_count: t.CPtr, temp_dir: str, td_len_ts: sizet) -> int:
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
@@ -41,10 +25,12 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, re
# 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)
@@ -55,14 +41,14 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, re
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 map (simple linear search; file count is small)
for i in range(result.Count):
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
@@ -83,7 +69,7 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, re
file_count += 1
# 2. Build dependency edges: for each class, find the parent's defining file and add a child->parent edge
for ci in range(class_count):
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
@@ -100,19 +86,19 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, re
if string.strcmp(parent_sha1, child_def_sha1) == 0: continue
# Find the child file's index and add the parent file SHA1 to its dependency list
for fi in range(file_count):
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
# Read the current dependency count
cnt_addr: uint64 = uint64(file_dep_counts) + fi * 4
cnt_ptr: t.CPtr = (int*)(cnt_addr)
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
@@ -132,7 +118,7 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, re
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):
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)
@@ -153,16 +139,16 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, re
stdlib.free(dname_imp)
if dsha1_imp is None: continue
# Find the dependency file's index in the list (skip self)
for fj_imp in range(file_count):
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: t.CPtr = (int*)(cnt_addr_imp)
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
@@ -179,11 +165,11 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, re
# 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(file_count):
for fi in range(int(file_count)):
cnt_addr3: uint64 = uint64(file_dep_counts) + fi * 4
cnt_ptr3: t.CPtr = (int*)(cnt_addr3)
cnt_ptr3: int* = (int*)(cnt_addr3)
ind_addr3: uint64 = uint64(file_indegrees) + fi * 4
ind_ptr3: t.CPtr = (int*)(ind_addr3)
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. Topological sort output
@@ -191,27 +177,27 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, re
changed: int = 1
while changed != 0:
changed = 0
for fi in range(file_count):
for fi in range(int(file_count)):
# Check processed flag
proc_addr4: uint64 = uint64(file_processed) + fi * 4
proc_ptr4: t.CPtr = (int*)(proc_addr4)
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: t.CPtr = (int*)(ind_addr4)
ind_ptr4: int* = (int*)(ind_addr4)
if ind_ptr4 is None or ind_ptr4[0] > 0: continue
# 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):
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:
# Write into the output index array
out_idx_addr: uint64 = uint64(out_order) + out_idx * 4
out_int_ptr: t.CPtr = (int*)(out_idx_addr)
out_int_ptr: int* = (int*)(out_idx_addr)
if out_int_ptr is not None: out_int_ptr[0] = ri
out_idx += 1
break
@@ -221,56 +207,56 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, re
changed = 1
# Decrement the in-degree of files that depend on this one
for fj in range(file_count):
for fj in range(int(file_count)):
proc_addr5: uint64 = uint64(file_processed) + fj * 4
proc_ptr5: t.CPtr = (int*)(proc_addr5)
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: t.CPtr = (int*)(cnt_addr5)
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: uint64 = uint64(file_indegrees) + fj * 4
ind_ptr5: t.CPtr = (int*)(ind_addr5)
ind_ptr5: int* = (int*)(ind_addr5)
if ind_ptr5 is not None: ind_ptr5[0] = ind_ptr5[0] - 1
break
# 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):
for fi in range(int(file_count)):
proc_addr7: uint64 = uint64(file_processed) + fi * 4
proc_ptr7: t.CPtr = (int*)(proc_addr7)
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
# Compute each remaining file's dependents count (among the remaining files)
for i_a in range(rem_count):
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: uint64 = uint64(file_dep_counts) + fi_b * 4
cnt_ptr_b: t.CPtr = (int*)(cnt_addr_b)
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
# Bubble sort descending (more dependents first)
for i_a in range(rem_count):
for j_a in range(rem_count - 1):
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]
@@ -279,19 +265,19 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, re
rem_depcnt[j_a] = rem_depcnt[j_a + 1]
rem_depcnt[j_a + 1] = tmp_d
# Emit the remaining files in order
for i_a in range(rem_count):
for i_a in range(int(rem_count)):
fi_a2: int = rem_idx[i_a]
proc_addr8: uint64 = uint64(file_processed) + fi_a2 * 4
proc_ptr8: t.CPtr = (int*)(proc_addr8)
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):
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: uint64 = uint64(out_order) + out_idx * 4
out_int_ptr2: t.CPtr = (int*)(out_idx_addr2)
out_int_ptr2: int* = (int*)(out_idx_addr2)
if out_int_ptr2 is not None: out_int_ptr2[0] = ri
out_idx += 1
break
@@ -300,7 +286,7 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: IncludesScanner.ScanResult*, re
stdlib.free(rem_depcnt)
# Write the output count
out_count_ptr: t.CPtr = (int*)(out_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
-1
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import memhub
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+17 -18
View File
@@ -1,13 +1,12 @@
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 - project utility functions (SHA1 hashing, directory cleanup)
@@ -19,9 +18,9 @@ _mbuddy: memhub.MemBuddy*
# 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 '0' + n
if uppercase != 0: return 'A' + (n - 10)
return 'a' + (n - 10)
if n < 10: return char('0' + n)
if uppercase != 0: return char('A' + (n - 10))
return char('a' + (n - 10))
# bytes_to_hex - convert a byte sequence to a hex string
@@ -33,7 +32,7 @@ def bytes_to_hex(pool: memhub.MemBuddy*, digest: bytes, nbytes: sizet, uppercase
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):
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)
@@ -129,7 +128,7 @@ def PathToModuleName(path: str, strip_includes_prefix: int) -> str:
# Copy and replace / with .
result: str = stdlib.malloc(plen + 1)
if result is None: return None
for ri in range(plen):
for ri in range(int(plen)):
ch: char = path[path_start + ri]
if ch == '/' or ch == '\\': result[ri] = '.'
else: result[ri] = ch
@@ -146,14 +145,14 @@ def CleanDir(dir_path: str) -> int:
if pattern is None: return -1
viperlib.snprintf(pattern, dir_len + 8, "%s/*", dir_path)
find_data: w32.win32file.WIN32_FIND_DATAA* = 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
@@ -170,21 +169,21 @@ def CleanDir(dir_path: str) -> int:
full_path: bytes = JoinPath(dir_path, fname)
if full_path is not None:
# Check whether this is a directory
is_dir: uint32 = find_data.dwFileAttributes & w32.win32file.FILE_ATTRIBUTE_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
# Remove the now-empty subdirectory
w32.win32file.RemoveDirectoryA(full_path)
win32.win32file.RemoveDirectoryA(full_path)
else:
# Delete the file
if w32.win32file.DeleteFileA(full_path) != 0: deleted += 1
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
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
from .Config import Load_project_config, resolve_paths
File diff suppressed because it is too large. Load diff
+117 -15
View File
@@ -1,7 +1,6 @@
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.GenTypes as GenTypes
import lib.core.CompilerFactory as CompilerFactory
import t, c
from stdint import *
import ast
import string
@@ -400,6 +399,11 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
cls_len2: sizet = 0
if cur_cls is not None: cls_len2 = string.strlen(cur_cls)
key_len: sizet = sha1_len + 1 + cls_len2 + 1 + mname_len
# fail-fast: cache entries are fixed-size slots. Skipping a too-long key or
# return text leaves the method without a cached return type, and callers
# then fall back to i32 (pointer returns get truncated).
if key_len >= VPI_KEY_LEN or rtype_len >= VPI_RET_LEN:
VLogger.error(f"return-annotation cache slot too small (key {key_len}/{VPI_KEY_LEN} bytes, return {rtype_len}/{VPI_RET_LEN} bytes); shorten the class/method name or the return annotation", "VpiReader")
if key_len < VPI_KEY_LEN and rtype_len < VPI_RET_LEN:
# copy method name to a temp buffer (vpi_buf+dstart is not NUL-terminated)
mname_buf: bytes = stdlib.malloc(mname_len + 1)
@@ -575,6 +579,10 @@ def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
while a_start < s_end and vpi_buf[a_start] == ' ': a_start += 1
a_end: sizet = s_end
while a_end > a_start and vpi_buf[a_end - 1] == ' ': a_end -= 1
# fail-fast: dropping a segment shifts every later parameter's annotation onto
# the wrong parameter (declare/define type mismatch), which is worse than a miss.
if plist_pos + (a_end - a_start) + 1 >= VPI_PARAM_LEN:
VLogger.error(f"parameter annotation list exceeds the {VPI_PARAM_LEN}-byte cache slot; shorten this method's annotations", "VpiReader")
if plist_pos + (a_end - a_start) + 1 < VPI_PARAM_LEN:
if seg_written > 0:
plist[plist_pos] = '@'
@@ -592,6 +600,10 @@ def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
cls_len2: sizet = 0
if cur_cls is not None: cls_len2 = string.strlen(cur_cls)
key_len: sizet = sha1_len + 1 + cls_len2 + 1 + mname_len
if key_len >= VPI_PARAM_KEY_LEN:
# fail-fast: a skipped key silently loses the whole parameter annotation
# list (the caller then rebuilds declare params from the degraded text).
VLogger.error(f"parameter-annotation cache key exceeds {VPI_PARAM_KEY_LEN} bytes (class/method name too long)", "VpiReader")
if key_len < VPI_PARAM_KEY_LEN:
mname_buf: bytes = stdlib.malloc(mname_len + 1)
if mname_buf is not None:
@@ -2090,14 +2102,36 @@ def _vpi_register_typedef_closure(pool: memhub.MemBuddy*, import_mods: str, temp
if aa_r.target.kind() != ast.ASTKind.Name: continue
cdef_t: ast.Name* = ast.Name*(aa_r.target)
if cdef_t.id is None: continue
if aa_r.value.kind() == ast.ASTKind.Constant:
cn_v: ast.Constant* = ast.Constant*(aa_r.value)
if cn_v.const_kind == ast.CONST_FLOAT: GenApi.register_cdefine_double(pool, cdef_t.id, cn_v.float_val)
# 与定义端同口径:剥掉 `float(...)`/`double(...)` 包裹与负浮点字面量
# `UnaryOp(USub, Constant(float))`;否则 `X: double = -100.0` 既不注册
# 浮点(静默丢失),又可能被误当整型去走常驻提取。
vnode_r: ast.AST* = aa_r.value
neg_r: int = 0
if vnode_r.kind() == ast.ASTKind.Call:
cl_r: ast.Call* = ast.Call*(vnode_r)
if cl_r.args is not None and len(cl_r.args) > 0:
fa_r: ast.AST* = cl_r.args.get(0)
if fa_r is not None and fa_r.kind() == ast.ASTKind.Constant: vnode_r = fa_r
if vnode_r.kind() == ast.ASTKind.UnaryOp:
uu_r: ast.UnaryOp* = ast.UnaryOp*(vnode_r)
if uu_r.op == ast.OpKind.USub and uu_r.operand is not None and uu_r.operand.kind() == ast.ASTKind.Constant:
ic_r: ast.Constant* = ast.Constant*(uu_r.operand)
if ic_r.const_kind == ast.CONST_FLOAT:
vnode_r = uu_r.operand
neg_r = 1
if vnode_r.kind() == ast.ASTKind.Constant:
cn_v: ast.Constant* = ast.Constant*(vnode_r)
if cn_v.const_kind == ast.CONST_FLOAT:
fv_r: double = cn_v.float_val
if neg_r != 0: fv_r = -fv_r
# 浮点宽度同样由声明注解裁决(与定义方同一套),
# 否则 `define PI: float = ...` 的使用方会把 f32 当 double
GenApi.register_cdefine_double(pool, cdef_t.id, fv_r, GenApi.cdefine_decl_float_bits(pool, aa_r.annotation))
elif cn_v.const_kind == ast.CONST_INT:
cdef_val: int64 = GenApi.extract_cdefine_int_value(aa_r.value)
cdef_val: int64 = GenApi.extract_cdefine_int_value(vnode_r)
# 位宽与定义方同一套裁决(注解 → 右侧强转 → 值的最小可存宽度),
# 否则使用方拿 bits=0 只能兜底,跨模块位宽会与定义方不一致
GenApi.register_cdefine_constant(pool, cdef_t.id, cdef_val, GenApi.cdefine_decl_bits(pool, aa_r.annotation, aa_r.value))
GenApi.register_cdefine_constant(pool, cdef_t.id, cdef_val, GenApi.cdefine_decl_bits(pool, aa_r.annotation, vnode_r))
# recurse into child imports (cycle guarded by the visited marks)
if sub_imports[0] != '\0': _vpi_register_typedef_closure(pool, sub_imports, temp_dir, depth + 1)
stdlib.free(sub_imports)
@@ -2409,23 +2443,42 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, v
if cdef_tgt.id is not None:
# Skip string CDefines (version numbers etc.): extract_cdefine_int_value
# fails fast on string constants, and count refs only need integer CDefines.
# 与定义端 `_register_cdefine_from_value` 同口径:先剥掉 `float(...)`/`double(...)`
# 强转包裹与负浮点字面量的 `UnaryOp(USub, Constant(float))`,否则
# `X: double = -100.0` 会被判成"非 float"而落到整型提取 ⇒ fatal。
vnode: ast.AST* = aa.value
cdef_neg_f: int = 0
if vnode.kind() == ast.ASTKind.Call:
cl_w2: ast.Call* = ast.Call*(vnode)
if cl_w2.args is not None and len(cl_w2.args) > 0:
fa2: ast.AST* = cl_w2.args.get(0)
if fa2 is not None and fa2.kind() == ast.ASTKind.Constant: vnode = fa2
if vnode.kind() == ast.ASTKind.UnaryOp:
uu2: ast.UnaryOp* = ast.UnaryOp*(vnode)
if uu2.op == ast.OpKind.USub and uu2.operand is not None and uu2.operand.kind() == ast.ASTKind.Constant:
ic2: ast.Constant* = ast.Constant*(uu2.operand)
if ic2.const_kind == ast.CONST_FLOAT:
vnode = uu2.operand
cdef_neg_f = 1
cdef_is_str: int = 0
cdef_is_float: int = 0
cdef_float_val: double = 0.0
if aa.value.kind() == ast.ASTKind.Constant:
cn_v: ast.Constant* = ast.Constant*(aa.value)
if vnode.kind() == ast.ASTKind.Constant:
cn_v: ast.Constant* = ast.Constant*(vnode)
if cn_v.const_kind == ast.CONST_STR: cdef_is_str = 1
elif cn_v.const_kind == ast.CONST_FLOAT:
cdef_is_float = 1
cdef_float_val = cn_v.float_val
if cdef_neg_f != 0: cdef_float_val = -cdef_float_val
if cdef_is_str == 0 and cdef_is_float != 0:
# Float CDefine (e.g. FRAGMENTATION_THRESHOLD: t.CDefine = 0.7): register via
# register_cdefine_double (integer extraction would fail fast).
GenApi.register_cdefine_double(pool, cdef_tgt.id, cdef_float_val)
# 注解声明 float 时带上 f32 种类(与定义方同一套裁决)。
GenApi.register_cdefine_double(pool, cdef_tgt.id, cdef_float_val, GenApi.cdefine_decl_float_bits(pool, aa.annotation))
elif cdef_is_str == 0:
cdef_val: int64 = GenApi.extract_cdefine_int_value(aa.value)
cdef_val: int64 = GenApi.extract_cdefine_int_value(vnode)
# 位宽与定义方同一套裁决(注解 → 右侧强转 → 值的最小可存宽度)
GenApi.register_cdefine_constant(pool, cdef_tgt.id, cdef_val, GenApi.cdefine_decl_bits(pool, aa.annotation, aa.value))
GenApi.register_cdefine_constant(pool, cdef_tgt.id, cdef_val, GenApi.cdefine_decl_bits(pool, aa.annotation, vnode))
else:
_inject_global_variable_declare(pool, mod, aa, trans, stub_imported_modules, stub_from_imports, i32_ty, i8_ptr_ty, src_sha1)
continue
@@ -2628,6 +2681,12 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModul
if existing_c is not None and existing_c.Ty is not None:
if existing_c.IsForwardDecl != 0: fill_fwd_c = 1
else:
# entry 已在全局 StructTable(可能由别的模块先注入/注册),但**本模块**的 NamedTypeHead
# 不保证有它的完整定义:只有被按值使用(值返回类型 / 值字段 / 数组元素)时打印才回查登记
# 表,而指针引用只会登记 opaque 前向声明 → 打印签名/类型时报 "still opaque"。故这里补齐
# 本模块的完整定义(幂等:add_named_type 同名去重,完整定义替换 opaque)。
mlirlite.module_add_named_type(mod, pool, existing_c.Ty)
if existing_c.IsPacked != 0: mlirlite.module_set_named_type_packed(mod, pool, existing_c.Ty, 1)
GenApi.add_visible_struct(pool, real_name)
return 1
# Skip REnum classes (class X(t.REnum)): their layout is computed from variants by
@@ -2645,9 +2704,25 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModul
# 2. collect field types (AnnAssign children, mirroring ClassGenDef.translate_class_def)
field_names_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
field_types_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
# Per-field marker buffers, index-aligned with field_names_buf / add_field order. The .vpi text
# keeps each field's raw annotation (Converter._PyiTypeStr renders BinOp/Attribute verbatim, so
# `X | t.BigEndian` / `t.CVolatile | int` survive), so the same markers ClassGenDef extracts on
# the definition side must be re-extracted here; otherwise a struct referenced across a module
# boundary loses its volatile/const/byte-order semantics and the same field compiles differently
# per module (the bug this fixes).
field_volatile_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
field_pointee_volatile_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
field_const_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
field_byteorder_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
if field_names_buf is None or field_types_buf is None: return 0
if field_volatile_buf is None or field_pointee_volatile_buf is None: return 0
if field_const_buf is None or 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_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
cls_children_c: list[ast.AST*]* = class_cd.children
if cls_children_c is not None:
@@ -2668,6 +2743,18 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModul
if field_count < GenTypes.FIELD_MAX:
field_names_buf[field_count] = sizet(m_tgt_c.id)
field_types_buf[field_count] = sizet(field_ty_c)
# Re-extract the field-level markers from the raw annotation, exactly as
# ClassGenDef.translate_class_def does on the definition side (same predicates, same
# precedence), so a cross-module reference gets identical field semantics.
annot_fc: ast.AST* = m_aa_c.annotation
if annot_fc is not None:
if GenApi.has_decorator_marker(annot_fc, "CVolatile", 0) != 0: field_volatile_buf[field_count] = 1
if GenApi.has_pointee_volatile_marker(annot_fc) != 0: field_pointee_volatile_buf[field_count] = 1
if GenApi.is_const_object_marker(annot_fc, field_ty_c) != 0: field_const_buf[field_count] = 1
# Byte order: 1 = t.BigEndian / 2 = t.LittleEndian (0 stays native);
# annotation `|` means "and", so `X | t.BigEndian` is a BigEndian X.
if GenApi.has_decorator_marker(annot_fc, "BigEndian", 0) != 0: field_byteorder_buf[field_count] = 1
elif GenApi.has_decorator_marker(annot_fc, "LittleEndian", 0) != 0: field_byteorder_buf[field_count] = 2
field_count += 1
# 3. The vpi stub has no class fields -> field_count is 0; registering an empty class would make
# the source module hit an empty struct and raise InternalCompilerError, so do not register.
@@ -2734,7 +2821,17 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModul
for afi_c in range(int(field_count)):
af_name_c: str = str(field_names_buf[afi_c])
af_ty_c: mlirlite.LLVMType* = (mlirlite.LLVMType*)(field_types_buf[afi_c])
GenApi.add_field(pool, entry_c, af_name_c, af_ty_c)
af_idx_c: int = GenApi.add_field(pool, entry_c, af_name_c, af_ty_c)
# Restore the per-field markers AFTER add_field, because StructGenMixin.add_field always
# resets them to 0 (it cannot see the annotation). Buffers are index-aligned with add_field
# order, so afi_c indexes them directly (mirrors ClassGenDef's post-add_field set).
if af_idx_c >= 0 and GenApi._g_gen is not None:
af_fe_c: GenTypes.FieldEntry* = GenApi._g_gen.StructGen._get_field_entry(entry_c, af_idx_c)
if af_fe_c is not None:
if field_volatile_buf[afi_c] != 0: af_fe_c.IsVolatile = 1
if field_pointee_volatile_buf[afi_c] != 0: af_fe_c.PointeeVolatile = 1
if field_const_buf[afi_c] != 0: af_fe_c.IsConst = 1
if field_byteorder_buf[afi_c] != 0: af_fe_c.ByteOrder = int(field_byteorder_buf[afi_c])
GenApi.add_visible_struct(pool, real_name)
return 1
@@ -2837,10 +2934,10 @@ def _inject_global_variable_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMMo
gv_ty_v: mlirlite.LLVMType* = resolved_v
match resolved_v:
case mlirlite.LLVMType.Struct(_, _, _):
# Object classes (@t.Object/@t.CVTable: object-as-pointer) -> Ptr(struct), matching
# handle_module_level_var; plain t.CStruct value classes keep a value instance.
# 与 handle_module_level_var 保持同一口径:只有 `__new__`(自定义分配)的类才以指针表示;
# 仅"有方法 / 带 vtable"(IsOOP / HasVTable)的类,模块级全局同样按值本位存储。
gv_entry_v: GenTypes.StructEntry* = GenApi.find_struct_by_type(resolved_v)
if gv_entry_v is not None and (gv_entry_v.HasNew != 0 or gv_entry_v.HasVTable != 0 or gv_entry_v.IsOOP != 0): gv_ty_v = mlirlite.Ptr(pool, resolved_v)
if gv_entry_v is not None and gv_entry_v.HasNew != 0: gv_ty_v = mlirlite.Ptr(pool, resolved_v)
else: gv_ty_v = resolved_v
case mlirlite.LLVMType.Array(aelem_v, acount_v):
# Fixed-size arrays (CArray[T, N]) keep the full [N x T] type, matching
@@ -3074,6 +3171,11 @@ def _inject_method_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, m_
if ret_ty is None and string.strcmp(m_fd.name, "__init__") == 0: ret_ty = mlirlite.Void(pool)
if ret_ty is None: ret_ty = i32_ty
# 跨模块方法**按值返回类**时,引用模块必须登记该具名结构体的完整定义:否则打印方法签名回查登记表,
# 命中的只是指针引用登记的 opaque 前向声明 → MLIR 报 "still opaque at print time"。与
# _inject_function_declare 同口径(那里对值返回类型调 _ensure_full_value_types)。
_ensure_full_value_types(mod, pool, ret_ty)
# create the declare
func: mlirlite.Function* = mlirlite.create_declare(pool, mod, mangled_name, ret_ty)
if func is None: return
+16 -17
View File
@@ -1,13 +1,12 @@
import lib.core.Gen.GenApi as GenApi
import t, c
from stdint import *
import memhub
import string
import stdlib
import mlirlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import win32.win32file as win32file
import win32.win32base as win32base
import win32.fileio as fileio
import viperlib
import sys
import ast
@@ -31,7 +30,7 @@ def _IsAppModule(mod_name: str) -> int:
"""Return whether mod_name is an App source file (store hit with empty incdir)."""
if mod_name is None: return 0
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None: return 0
for i in range(StubMerger._sha1_store_count):
for i in range(int(StubMerger._sha1_store_count)):
midx: sizet = sizet(i) * 64
if string.strcmp(StubMerger._sha1_store_mod + midx, mod_name) == 0:
iidx: sizet = sizet(i) * StubMerger.MAX_INC_DIR_LEN
@@ -100,7 +99,7 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str,
scan_res_r: IncludesScanner.ScanResult* = IncludesScanner.scan_includes(mb, source_dir)
if scan_res_r is not None:
es_r: sizet = IncludesScanner.FileEntry.__sizeof__()
for e_i_r in range(scan_res_r.Count):
for e_i_r in range(int(scan_res_r.Count)):
e_r: IncludesScanner.FileEntry* = IncludesScanner.FileEntry*(uint64(scan_res_r.Entries) + uint64(e_i_r) * es_r)
if e_r is None or e_r.Path is None: continue
# File-level mark: reclaim the AST right after parsing (keeping all App ASTs resident would OOM)
@@ -197,9 +196,9 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str,
# 2b. --traceback: force the traceback runtime module into the reachable set (TPC: used_includes.add('traceback')).
# Its Install() symbol and the @llvm.global_ctors ctor come only from compiling this module, so a missing file is fail-fast.
if BuildPipeline._traceback_mode != 0:
tb_abs: t.CPtr = stdlib.malloc(8)
tb_rel: t.CPtr = stdlib.malloc(8)
tb_inc: t.CPtr = stdlib.malloc(8)
tb_abs: rawptr = stdlib.malloc(8)
tb_rel: rawptr = stdlib.malloc(8)
tb_inc: rawptr = stdlib.malloc(8)
tb_found: int = -1
if tb_abs is not None and tb_rel is not None and tb_inc is not None:
tb_abs_slot: sizet* = (sizet*)(tb_abs)
@@ -230,7 +229,7 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str,
processed[0] = '\0'
proc_len: sizet = 0
reachable_count: int = 0
wl_pos: t.CPtr = stdlib.malloc(8)
wl_pos: rawptr = stdlib.malloc(8)
if wl_pos is None:
stdlib.free(worklist)
stdlib.free(processed)
@@ -289,9 +288,9 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str,
continue
# includes: resolve on demand via the filesystem (unreferenced includes are never touched)
out_abs: t.CPtr = stdlib.malloc(8)
out_rel: t.CPtr = stdlib.malloc(8)
out_inc: t.CPtr = stdlib.malloc(8)
out_abs: rawptr = stdlib.malloc(8)
out_rel: rawptr = stdlib.malloc(8)
out_inc: rawptr = stdlib.malloc(8)
if out_abs is None or out_rel is None or out_inc is None:
if out_abs is not None: stdlib.free(out_abs)
if out_rel is not None: stdlib.free(out_rel)
@@ -299,10 +298,10 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str,
stdlib.free(mod_buf)
continue
# Defensive init of the output slots (the success path always writes all). The slot union must use (sizet*):
# t.CPtr* collapses to t.CPtr and [0] becomes a 1-byte access, truncating the pointer (0x2e crash root cause).
abs_slot: sizet* = (sizet*)(out_abs)
rel_slot: sizet* = (sizet*)(out_rel)
inc_slot: sizet* = (sizet*)(out_inc)
# rawptr* collapses to rawptr and [0] becomes a 1-byte access, truncating the pointer (0x2e crash root cause).
abs_slot: sizet* = sizet*(out_abs)
rel_slot: sizet* = sizet*(out_rel)
inc_slot: sizet* = sizet*(out_inc)
abs_slot[0] = 0
rel_slot[0] = 0
inc_slot[0] = 0
+124 -125
View File
@@ -1,125 +1,124 @@
import t, c
from stdint import *
import memhub
import string
import stdlib
import mlirlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import viperlib
import sys
import ast
import lib.core.VLogger as VLogger
import lib.core.IncludesScanner as IncludesScanner
import lib.core.Gen.Translator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.Stub.StubMerger.StubMergerTopo as StubMergerTopo
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger as StubMerger
# StubMergerCollect - slice-path construction and stub-file SHA1 collection.
# Functions live in this module; cross-module globals are accessed via StubMerger._xxx.
# _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)
# 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: sizet = 0
if subdir_buf is not None:
sub_len = string.strlen(subdir_buf)
# 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: 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)
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)
return path
# _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 TransPyV'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: sizet = sizet(i) * 17
string.strcpy(sha1_set + sizet(count) * 17, StubMerger._sha1_store_arr + idx)
count += 1
return count
# _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:
"""Whether sha1 is present in the set."""
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
return 0
# 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
# 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
viperlib.snprintf(map_path, dir_len + 32, "%s/_sha1_map.txt", temp_dir)
# Open the file for writing (CREATE_ALWAYS)
f: fileio.File* = fileio.File(map_path, fileio.MODE.W)
if f.closed: return 1
# 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: 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
viperlib.snprintf(line_buf, 512, "%s:includes/%s\n", ent_w.Sha1, ent_w.RelPath)
line_len_w: sizet = string.strlen(line_buf)
f.write(line_buf, line_len_w)
written_count += 1
f.close()
return 0
from stdint import *
import memhub
import string
import stdlib
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.Gen.Translator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.Stub.StubMerger.StubMergerTopo as StubMergerTopo
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger as StubMerger
# StubMergerCollect - slice-path construction and stub-file SHA1 collection.
# Functions live in this module; cross-module globals are accessed via StubMerger._xxx.
# _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)
# 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: sizet = 0
if subdir_buf is not None:
sub_len = string.strlen(subdir_buf)
# 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: 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)
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)
return path
# _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(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 - check whether a SHA1 is in the set.
def _is_in_sha1_set(sha1: str, sha1_set: str, set_count: int) -> int:
"""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 - 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
# 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
viperlib.snprintf(map_path, dir_len + 32, "%s/_sha1_map.txt", temp_dir)
# Open the file for writing (CREATE_ALWAYS)
f: fileio.File* = fileio.File(map_path, fileio.MODE.W)
if f.closed: return 1
# 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(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
viperlib.snprintf(line_buf, 512, "%s:includes/%s\n", ent_w.Sha1, ent_w.RelPath)
line_len_w: sizet = string.strlen(line_buf)
f.write(line_buf, line_len_w)
written_count += 1
f.close()
return 0
+7 -8
View File
@@ -1,13 +1,12 @@
import lib.core.Gen.GenApi as GenApi
import t, c
from stdint import *
import memhub
import string
import stdlib
import mlirlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import win32.win32file as win32file
import win32.win32base as win32base
import win32.fileio as fileio
import viperlib
import sys
import ast
@@ -186,7 +185,7 @@ def _BuildIncludesSha1Map(temp_dir: str, td_len: sizet, sha1_list: str, mod_list
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):
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
@@ -201,7 +200,7 @@ def _FindSha1ByModName(sha1_arr: str, mod_arr: str, count: int, mod_name: str) -
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(count):
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).
@@ -213,7 +212,7 @@ def _FindSha1ByModName(sha1_arr: str, mod_arr: str, count: int, mod_name: str) -
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):
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
@@ -226,7 +225,7 @@ def _FindSha1ByModName(sha1_arr: str, mod_arr: str, count: int, mod_name: str) -
def _AddSha1ToSet(set_buf: str, count: int, sha1: str) -> int:
"""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(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:
+34 -30
View File
@@ -1,12 +1,11 @@
import t, c
from stdint import *
import memhub
import string
import stdlib
import mlirlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import win32.win32file as win32file
import win32.win32base as win32base
import win32.fileio as fileio
import viperlib
import sys
import ast
@@ -136,17 +135,19 @@ def PopulateSha1MapStore(scan_result: IncludesScanner.ScanResult*) -> int:
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)
# 取消 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: sizet = IncludesScanner.FileEntry.__sizeof__()
for i in range(scan_result.Count):
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)
@@ -198,13 +199,14 @@ def AppendToSha1MapStore(sha1: str, rel_path: str) -> int:
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)
# 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
# module name: rel_path "lib/core/StubMerger.vp" -> "lib.core.StubMerger"
@@ -216,7 +218,7 @@ def AppendToSha1MapStore(sha1: str, rel_path: str) -> int:
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(StubMerger._sha1_store_count):
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
@@ -255,17 +257,18 @@ def AddIncludeToSha1MapStore(sha1: str, rel_path: str, inc_dir: str, mod_name: s
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)
# 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
# dedup when both SHA1 and module name match (aligned with AppendToSha1MapStore)
for i in range(StubMerger._sha1_store_count):
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
@@ -343,7 +346,7 @@ def RegisterGenericOwner(cls_name: str, mod_sha1: str) -> int:
string.memset(_generic_owner_sha1s, 0, GENERIC_OWNER_MAX * 17)
_generic_owner_count = 0
i_go: int
for i_go in range(_generic_owner_count):
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
@@ -358,7 +361,7 @@ 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(_generic_owner_count):
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
# ============================================================
@@ -371,7 +374,7 @@ def LookupSha1ByModNameSuffix(mod_suffix: str) -> str:
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):
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: sizet = string.strlen(mod_name_sf)
@@ -416,7 +419,7 @@ def LookupSha1ByAlias(from_imports: str, alias: str) -> str:
if colon < total_len and from_imports[colon] == ':' and local_len == alias_len:
# compare local_name == alias
Matched: int = 1
for li in range(alias_len):
for li in range(int(alias_len)):
if from_imports[ci + li] != alias[li]:
Matched = 0
break
@@ -457,7 +460,7 @@ def LookupModNameByAlias(from_imports: str, alias: str) -> str:
if colon < total_len and from_imports[colon] == ':' and local_len == alias_len:
# compare local_name == alias
Matched: int = 1
for li in range(alias_len):
for li in range(int(alias_len)):
if from_imports[ci + li] != alias[li]:
Matched = 0
break
@@ -484,14 +487,15 @@ def PopulateIncludesDeps(sha1: str, deps_str: str) -> int:
# 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)
# 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
# dedup: skip if SHA1 already present
for i in range(StubMerger._includes_deps_count):
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
@@ -512,7 +516,7 @@ def LookupIncludesDeps(sha1: str) -> str:
"""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(StubMerger._includes_deps_count):
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: sizet = sizet(i) * StubMerger.MAX_DEPS_STR_LEN
@@ -550,7 +554,7 @@ def LookupSha1RelPath(sha1: str) -> str:
"""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(StubMerger._sha1_store_count):
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: sizet = sizet(i) * StubMerger.MAX_REL_PATH_LEN
@@ -564,7 +568,7 @@ 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(StubMerger._sha1_store_count):
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
+17 -35
View File
@@ -1,29 +1,11 @@
import t, c
from stdint import *
import memhub
import string
import stdlib
import mlirlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import viperlib
import sys
import ast
import lib.core.VLogger as VLogger
import lib.core.IncludesScanner as IncludesScanner
import lib.core.Gen.Translator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger as StubMerger
# StubMergerTopo - reverse-topological sort of reachable includes (dependencies first).
# Functions live in this module; cross-module globals are accessed via StubMerger._xxx.
@@ -33,9 +15,9 @@ import lib.Stub.StubMerger as StubMerger
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)))
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)
@@ -43,7 +25,7 @@ def BuildTopoSortedIndices(store_count: int, store_sha1_arr: bytes, store_mod_ar
return 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
@@ -51,18 +33,18 @@ def BuildTopoSortedIndices(store_count: int, store_sha1_arr: bytes, store_mod_ar
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):
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
# Compute indegree: number of direct deps of m within the reachable set
for i_r in range(rel_count):
si: int = rel_idx[i_r]
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)
@@ -81,13 +63,13 @@ def BuildTopoSortedIndices(store_count: int, store_sha1_arr: bytes, store_mod_ar
stdlib.free(dname_buf)
if dsha1 is not None and StubMergerCollect._is_in_sha1_set(dsha1, reachable_set, reachable_count) != 0:
# Use an intermediate int to work around a union-pointer arithmetic codegen bug
indg: int = indegree[si]
indg: int = int(indegree[si])
indg = indg + 1
indegree[si] = indg
# 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
@@ -96,14 +78,14 @@ def BuildTopoSortedIndices(store_count: int, store_sha1_arr: bytes, store_mod_ar
# 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
# 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(rel_count):
si3: int = rel_idx[i_r]
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
@@ -119,7 +101,7 @@ def BuildTopoSortedIndices(store_count: int, store_sha1_arr: bytes, store_mod_ar
if dlen3 == mod_m_len and string.strncmp(deps_si3 + dstart3, mod_m, mod_m_len) == 0: found_dep = 1
if found_dep == 1:
# Use an intermediate int decrement to work around a union-pointer arithmetic codegen bug
indg3: int = indegree[si3]
indg3: int = int(indegree[si3])
indg3 = indg3 - 1
indegree[si3] = indg3
if indegree[si3] == 0:
@@ -127,8 +109,8 @@ def BuildTopoSortedIndices(store_count: int, store_sha1_arr: bytes, store_mod_ar
q_tail += 1
q_head += 1
# On cycles, append remaining entries in store order to avoid skipping compilation
for i_r in range(rel_count):
si4: int = rel_idx[i_r]
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
-17
View File
@@ -1,22 +1,5 @@
import t, c
from stdint import *
import memhub
import string
import stdlib
import mlirlite
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import viperlib
import sys
import ast
import lib.core.VLogger as VLogger
import lib.core.IncludesScanner as IncludesScanner
import lib.core.Gen.Translator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config
# StubMerger - merges dependency stub declarations into the main IR (Phase2 core):
-1
View File
@@ -1,4 +1,3 @@
import t, c
import memhub
+25 -9
View File
@@ -1,9 +1,8 @@
import t, c
from stdint import *
import string
import stdio
import stdlib
import w32.fileio as fileio
import win32.fileio as fileio
import lib.core.VLogger as VLogger
@@ -38,10 +37,10 @@ define DIAG_MAX_FRAMES = 32 # 调用链上限(与 TypeGenMixin.TRACE_MAX 对
# (import 触发依赖加载 / 泛型特化 / 函数体翻译)时的触发点,
# 用于"从错误发生点向外展示触发过程"。
# 所有字符串都用 stdlib 深拷贝:编译期池会回绕,不能借用池内指针。
class DiagEntry:
struct DiagEntry:
# 字段名不要与"本模块 import 的类名"重名:TPC 的类型/符号表按**非限定名**索引
# (Gen.structs / SymbolTable / _mangle_func_name),曾把本字段的 `File` 与
# `w32.fileio.File` 撞在一起,导致同模块 `fileio.File(...)` 的构造调用把
# `win32.fileio.File` 撞在一起,导致同模块 `fileio.File(...)` 的构造调用把
# `File.__before_init__` 的 self 形参解析成 `i8**`(llvmlite: "Type of #1 arg
# mismatch: i8** != %"...fileio.File"*")。故此处用 FileName 而非 File。
FileName: str # 当前帧(错误点)文件
@@ -138,19 +137,36 @@ def add_frame(ent: DiagEntry*, file_name: str, lineno: int) -> int:
# 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:
emit_frame(str(ent.FrameFiles[i_d]), int(ent.FrameLines[i_d]))
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:
emit_frame(str(ent.FrameFiles[i_u]), int(ent.FrameLines[i_u]))
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)
# 泄漏点帧紧跟在"错误点"之后(往下再一行),再印类别: 消息
@@ -203,7 +219,7 @@ def read_source_line(file_path: str, lineno: int) -> str:
# Scan line by line for the target lineno
cur_line: int = 1
line_start: sizet = 0
for i in range(bytes_read):
for i in range(int(bytes_read)):
if buf[i] == '\n':
if cur_line == lineno:
line_len: sizet = i - line_start
@@ -273,7 +289,7 @@ def emit_current(file_name: str, lineno: int, col_start: int, col_end: int) -> i
if col_start > 0 and col_end >= col_start:
stdio.printf(" ")
j: int
for j in range(col_end):
for j in range(int(col_end)):
if j >= col_start - 1 and j < col_end: stdio.printf("^")
else: stdio.printf(" ")
stdio.printf("\n")
@@ -304,7 +320,7 @@ def emit_parse(file_path: str, err_line: int, err_col: int, err_msg: str) -> int
# 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(err_col): stdio.printf(" ")
for _ in range(int(err_col)): stdio.printf(" ")
stdio.printf("^\n")
stdlib.free(src)
else: stdio.printf(' File "%s"\n', file_path)
File diff suppressed because it is too large. Load diff
+6 -7
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
@@ -635,8 +634,8 @@ novirtual class AsyncGenMixin(CallGenBase.CallGenBase):
# M7 frame pool: i64 head (0=empty); first 8 bytes hold next (state/done zeroed on reuse)
pool_gv: mlirlite.GlobalVariable* = self._get_coro_pool(pool, mod, trans, name)
# GlobalVariable is not a Value: build a Value view (Ty=Ptr(i64), Name="@coro_pool_<name>")
pool_ref_name: str = pool.alloc(96)
if pool_ref_name is not None: viperlib.snprintf(pool_ref_name, 96, "@coro_pool_%s", name)
pool_ref_name: str = pool.alloc(string.strlen(name) + 16)
if pool_ref_name is not None: viperlib.snprintf(pool_ref_name, string.strlen(name) + 16, "@coro_pool_%s", name)
pool_ref: mlirlite.Value* = mlirlite.SSAValue(pool, i64_ptr_ty, pool_ref_name)
pool_head: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, pool_ref)
if pool_head is None: return 0
@@ -700,8 +699,8 @@ novirtual class AsyncGenMixin(CallGenBase.CallGenBase):
# M7: per-frame-type global free-list pool head (i64, 0 = empty)
def _get_coro_pool(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, name: str) -> mlirlite.GlobalVariable*:
"""Get or create the coro_pool_<name> global (i64 pool head)"""
pn_buf: str = pool.alloc(128)
if pn_buf is not None: viperlib.snprintf(pn_buf, 128, "coro_pool_%s", name)
pn_buf: str = pool.alloc(string.strlen(name) + 16)
if pn_buf is not None: viperlib.snprintf(pn_buf, string.strlen(name) + 16, "coro_pool_%s", name)
# idempotent: create/destroy both call this, so scan the module global list (no find_global API)
g: mlirlite.GlobalVariable* = mod.GlobalHead
while g is not None:
@@ -1664,8 +1663,8 @@ novirtual class AsyncGenMixin(CallGenBase.CallGenBase):
pool_gv: mlirlite.GlobalVariable* = self._get_coro_pool(pool, mod, trans, async_name)
# GlobalVariable is not a Value: build a Value view (Ty=Ptr(i64), Name="@coro_pool_<name>")
i64_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i64_ty)
pool_ref_name: str = pool.alloc(96)
if pool_ref_name is not None: viperlib.snprintf(pool_ref_name, 96, "@coro_pool_%s", async_name)
pool_ref_name: str = pool.alloc(string.strlen(async_name) + 16)
if pool_ref_name is not None: viperlib.snprintf(pool_ref_name, string.strlen(async_name) + 16, "@coro_pool_%s", async_name)
pool_ref: mlirlite.Value* = mlirlite.SSAValue(pool, i64_ptr_ty, pool_ref_name)
pool_head: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, pool_ref)
node_next: mlirlite.Value* = mlirlite.build_bitcast(builder, frame_ptr, i64_ptr_ty)
-1
View File
@@ -1,6 +1,5 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import t, c
from stdint import *
import memhub
import mlirlite
+126 -123
View File
@@ -2,7 +2,6 @@ import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.Translator as HT
import t, c
from stdint import *
import ast
import mlirlite
@@ -12,8 +11,8 @@ import string
import stdio
import stdlib
import viperlib
import w32.fileio as fileio
import w32.win32file as win32file
import win32.fileio as fileio
import win32.win32file as win32file
import lib.core.VLogger as VLogger
import lib.Stub.StubMerger as StubMerger
import lib.core.CompilerFactory as CompilerFactory
@@ -130,9 +129,9 @@ novirtual class CallGenBase:
def _auto_addr_mode(self, name: str, idx: int) -> int:
"""Return the value-reference mode for parameter idx of a callee symbol name (0 if none/unknown)."""
if self.AutoAddrModes is None or name is None or idx < 0: return 0
v: t.CPtr = self.AutoAddrModes.__getitem__(name)
v: rawptr = self.AutoAddrModes.__getitem__(name)
if v is None: return 0
modes: str = t.CPtr(v)
modes: str = rawptr(v)
i: int = 0
while i < idx and modes[i] != char(0): i += 1
if i != idx: return 0
@@ -146,6 +145,10 @@ novirtual class CallGenBase:
trans: HT.Translator*, callee_name: str, arg_idx: int, arg_node: ast.AST*,
fpty: mlirlite.LLVMType*, use_ctx: str = "param") -> mlirlite.Value*:
"""Translate a call argument; value-reference params take the address (ref) or a temp copy (temp)."""
# 记录“当前实参位置”:被调者内部若因**合成节点**(lineno==0,如构造器内联出的参数赋值)
# 报错,可据此把报错精确定位到这个实参表达式(如 `&_on_submit`),而不是退回函数定义行。
if arg_node is not None:
self.Gen.TypeGenBase.set_cur_arg(self.Gen.TypeGenResolve.get_current_file(), arg_node.lineno, arg_node.col_offset + 1)
mode: int = self._auto_addr_mode(callee_name, arg_idx)
if mode == 0:
return self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, None, 0, trans, fpty, use_ctx)
@@ -246,7 +249,7 @@ novirtual class CallGenBase:
mode: int = self.Gen.CallGenBase._auto_addr_mode(callee_name, idx)
if mode == 0: mode = self.Gen.CallGenBase._vpi_auto_addr_mode(callee_name, trans.ModuleSha1, idx)
if mode != 0:
arg_i: int = idx - start_idx
arg_i: int = int(idx - start_idx)
arg_node: ast.AST* = None
if arg_nodes is not None and arg_i < len(arg_nodes): arg_node = arg_nodes.get(arg_i)
node_v = self.Gen.CallGenBase._apply_auto_addr_arg(builder, pool, mod, trans, mode, arg_node, cur)
@@ -275,7 +278,7 @@ novirtual class CallGenBase:
sha1_arr_ptr: bytes = StubMergerStore.GetSha1StoreArrPtr()
count: int = StubMergerStore.GetSha1StoreCount()
if rel_arr_ptr is None or sha1_arr_ptr is None or count <= 0: return None
for i in range(count):
for i in range(int(count)):
sidx: sizet = sizet(i) * 17
if string.strcmp(sha1_arr_ptr + sidx, mod_sha1) != 0: continue
ridx: sizet = sizet(i) * StubMerger.MAX_REL_PATH_LEN
@@ -286,7 +289,7 @@ novirtual class CallGenBase:
if pkg_len == 0: return None
pkg_buf: str = pool.alloc(pkg_len + 1)
if pkg_buf is None: return None
for pi in range(pkg_len):
for pi in range(int(pkg_len)):
if rel[pi] == '/': pkg_buf[pi] = '.'
else: pkg_buf[pi] = rel[pi]
pkg_buf[pkg_len] = '\0'
@@ -344,7 +347,7 @@ novirtual class CallGenBase:
fl2_od: sizet = fend_od - fstart_od
if fl2_od == name_len:
fd_eq_od: int = 1
for fdi_od in range(fl2_od):
for fdi_od in range(int(fl2_od)):
if buf[fstart_od + fdi_od] != func_name[fdi_od]:
fd_eq_od = 0
break
@@ -418,7 +421,7 @@ novirtual class CallGenBase:
as_pos += 1
if cand_len == name_len:
m_ok: int = 1
for ki in range(name_len):
for ki in range(int(name_len)):
if buf[cand_start + ki] != func_name[ki]:
m_ok = 0
break
@@ -490,7 +493,7 @@ novirtual class CallGenBase:
"""Whether (sha1, func_name) is a CExport function (reads the Gen.CallGenBase source of truth), falling back to the vpi return annotation on a registry miss."""
if self.Gen is None or sha1 is None or func_name is None: return 0
if self.Gen.CallGenBase.CexportSha1Arr is not None:
for i in range(self.Gen.CallGenBase.CexportCount):
for i in range(int(self.Gen.CallGenBase.CexportCount)):
idx: sizet = sizet(i) * 17
if string.strcmp(self.Gen.CallGenBase.CexportSha1Arr + idx, sha1) == 0:
idx2: sizet = sizet(i) * 64
@@ -510,7 +513,7 @@ novirtual class CallGenBase:
string.memset(self.Gen.CallGenBase.StaticSha1Arr, 0, _CEXPORT_MAX * 17)
string.memset(self.Gen.CallGenBase.StaticNameArr, 0, _CEXPORT_MAX * 64)
# idempotent: repeated (sha1, name) registration returns a hit
for i in range(self.Gen.CallGenBase.StaticCount):
for i in range(int(self.Gen.CallGenBase.StaticCount)):
si: sizet = sizet(i) * 17
if string.strcmp(self.Gen.CallGenBase.StaticSha1Arr + si, sha1) == 0:
sn: sizet = sizet(i) * 64
@@ -532,7 +535,7 @@ novirtual class CallGenBase:
"""Whether (sha1, name) is a registered static symbol, the cross-module reference blocker; pairs (sha1, name) exactly to avoid same-name mistakes."""
if self.Gen is None or sha1 is None or name is None: return 0
if self.Gen.CallGenBase.StaticSha1Arr is None: return 0
for i in range(self.Gen.CallGenBase.StaticCount):
for i in range(int(self.Gen.CallGenBase.StaticCount)):
si: sizet = sizet(i) * 17
if string.strcmp(self.Gen.CallGenBase.StaticSha1Arr + si, sha1) == 0:
sn: sizet = sizet(i) * 64
@@ -576,7 +579,7 @@ novirtual class CallGenBase:
while mod_name_raw[mod_len] != ' ' and mod_name_raw[mod_len] != '\0' and mod_name_raw[mod_len] != ':': mod_len += 1
mod_name_buf: str = pool.alloc(mod_len + 1)
if mod_name_buf is None: return None
for mi in range(mod_len): mod_name_buf[mi] = mod_name_raw[mi]
for mi in range(int(mod_len)): mod_name_buf[mi] = mod_name_raw[mi]
mod_name_buf[mod_len] = '\0'
# strip leading dots of relative import
base_mod: str = mod_name_buf
@@ -628,7 +631,7 @@ novirtual class CallGenBase:
suffix_start_o: sizet = cur_len - name_len
if cur_name[suffix_start_o - 1] == '.':
match_o: int = 1
for i in range(name_len):
for i in range(int(name_len)):
if cur_name[suffix_start_o + i] != name[i]:
match_o = 0
break
@@ -638,7 +641,7 @@ novirtual class CallGenBase:
suffix_start: sizet = cur_len - name_len
if cur_name[suffix_start - 1] == '.':
Matched: int = 1
for i in range(name_len):
for i in range(int(name_len)):
if cur_name[suffix_start + i] != name[i]:
Matched = 0
break
@@ -794,110 +797,110 @@ novirtual class CallGenBase:
if annot is None: return None
return VpiReader._resolve_vpi_ret_annot_str(pool, annot, i32_ty, i8_ptr_ty)
def _vpi_escape_packed(self, sha1: str, cls_name: str, method_name: str) -> int:
"""跨模块逃逸摘要查询(v3):按 (定义模块 sha1, Class?, method) 查 .vpi 缓存;0=无摘要。
用"本模块已加载"备忘包住 _ensure_vpi_ret_annot_loaded(其内部是 O(N) 前缀判定):
调用点每次都要查摘要,不做备忘会把编译拖慢一个量级。"""
if sha1 is None or method_name is None: return 0
if VpiReader._vpi_escape_module_loaded(sha1) == 0:
if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0: return 0
# ⚠ 同 lookup_ret_kind_via_vpi:**加载成功才记备忘**。失败也记会把该模块毒化成"已加载",
# 使之后所有跨模块查询(含 willFree 的 _vpi_willfree,它共用这条备忘)永远查空。
if VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir) == 0: return 0
VpiReader._vpi_escape_mark_module(sha1)
return VpiReader._lookup_vpi_escape_packed(sha1, cls_name, method_name)
# lookup_escape_dir_via_vpi - 跨模块声明式指令契约(@life(escape(...)) / @life(noescape(...)))。
# 本文件指令表按文件重置,跨模块调用点必为空,只能靠 .vpi 行尾的 esce=/escn= 传递;
# noescape != 0 取"强制不逃逸位",否则取"强制逃逸位"。0 = 无该标注(含该模块未加载过)。
def lookup_escape_dir_via_vpi(self, cls_sha1: str, class_name: str, func_name: str, noescape: int) -> int:
"""对方模块 .vpi 里落的契约掩码;0 = 无该标注"""
if func_name is None or cls_sha1 is None: return 0
return VpiReader._lookup_vpi_escape_dir(cls_sha1, class_name, func_name, noescape)
# lookup_ret_kind_via_vpi - 跨模块 `@life(ret(...))` 返回值生命期契约(A2,§14)。
# 与 escape/noescape 同一条 .vpi 通道(行尾 escr=);0 = 无声明(含该模块未加载过)。
# 注意:本查询可能在调用点**之前**(life_of_expr 判右值生命期)发生,不能假定调用点的
# 逃逸查询已把对方 vpi 载入,故这里自己走一次"本模块已加载"备忘 + 按需加载。
def lookup_ret_kind_via_vpi(self, cls_sha1: str, class_name: str, func_name: str) -> int:
"""对方模块 .vpi 里落的 ret_kind;0 = 无声明"""
if func_name is None or cls_sha1 is None: return 0
if VpiReader._vpi_escape_module_loaded(cls_sha1) == 0:
if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0: return 0
# ⚠ 只有**加载成功**才能记入"已加载"备忘。_ensure 失败(该模块的 vpi 此刻还没生成 /
# 打不开)时若也记上,后续所有跨模块查询(逃逸摘要 / willFree / ret)都会**跳过真正的
# 加载** → 缓存恒空 → 判据静默失效。现场:本查询可能在极早的翻译阶段先跑(life_of_expr
# 判右值生命期),把尚无 vpi 的模块毒化成"已加载" → EscapeXmodTest(esc)/
# NewLifetimeWillFreeTest(wf)双双漏检。
if VpiReader._ensure_vpi_ret_annot_loaded(cls_sha1, self.Gen.CallGenBase.TempDir) == 0: return 0
VpiReader._vpi_escape_mark_module(cls_sha1)
return VpiReader._lookup_vpi_escape_dir(cls_sha1, class_name, func_name, 2)
def lookup_escape_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> int:
"""跨模块逃逸摘要(v3):本类 + MRO 祖先各自的模块 sha1 依次查 .vpi,返回打包值(0=无摘要)。
本文件的摘要表按文件重置,跨模块调用点上必为空,只能靠 .vpi 传递;
键与方法路径一致:"Class.method"(方法)/ 裸名(模块级函数,此时 cls_name=None)。"""
if method_name is None: return 0
if cls_sha1 is not None:
p0: int = self._vpi_escape_packed(cls_sha1, cls_name, method_name)
if p0 != 0: return p0
if cls_name is not None:
mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, cls_name)
mi: int = 1
while mro is not None and mro[mi] != 0:
anc: str = rawptr(mro[mi])
if anc is not None and string.strcmp(anc, cls_name) != 0:
sha_a: str = self.Gen.StructGen.get_struct_sha1(anc)
if sha_a is not None:
pa: int = self._vpi_escape_packed(sha_a, anc, method_name)
if pa != 0: return pa
mi += 1
return 0
def _vpi_willfree(self, sha1: str, cls_name: str, method_name: str) -> int:
"""跨模块 willFree 标注查询(P2):按 (定义模块 sha1, Class?, method) 查 .vpi 缓存;0=无标注。
与逃逸摘要共用"本模块已加载"备忘:_ensure_vpi_ret_annot_loaded 的单一入口会把
ret/param/esc/wf 一起喂齐,所以这里只需保证模块被加载过一次。"""
if sha1 is None or method_name is None: return 0
if VpiReader._vpi_escape_module_loaded(sha1) == 0:
if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0: return 0
VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir)
VpiReader._vpi_escape_mark_module(sha1)
return VpiReader._lookup_vpi_willfree(sha1, cls_name, method_name)
def lookup_escape_clears_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, class_name: str, func_name: str, cargs: list[ast.AST*]*, can: sizet, param_base: int) -> int:
"""跨模块常量特化:把实参字面量装进常量环境,再向 vpi 缓存要"该清掉的位掩码";0 = 无命中
跨模块拿不到被调者函数体(只有 .vpi),所以用落盘的守卫表(`escg=`)而不是重算摘要。
没有常量实参、或该函数没落过守卫记录 → 返回 0(调用方原样保留掩码,保守)。"""
if cls_sha1 is None or func_name is None or cargs is None or can == 0: return 0
if GenApi.build_const_env(cargs, can, param_base) == 0: return 0
fenv: sizet* = GenApi.const_env_ptr()
if fenv is None: return 0
return VpiReader._lookup_vpi_escape_clears(cls_sha1, class_name, func_name, fenv)
def lookup_willfree_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> int:
"""跨模块 willFree 掩码(P2):本类 + MRO 祖先各自的模块 sha1 依次查 .vpi;0=无标注"""
if method_name is None: return 0
if cls_sha1 is not None:
p0: int = self._vpi_willfree(cls_sha1, cls_name, method_name)
if p0 != 0: return p0
if cls_name is not None:
mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, cls_name)
mi: int = 1
while mro is not None and mro[mi] != 0:
anc: str = rawptr(mro[mi])
if anc is not None and string.strcmp(anc, cls_name) != 0:
sha_a: str = self.Gen.StructGen.get_struct_sha1(anc)
if sha_a is not None:
pa: int = self._vpi_willfree(sha_a, anc, method_name)
if pa != 0: return pa
mi += 1
return 0
def _vpi_escape_packed(self, sha1: str, cls_name: str, method_name: str) -> int:
"""跨模块逃逸摘要查询(v3):按 (定义模块 sha1, Class?, method) 查 .vpi 缓存;0=无摘要。
用"本模块已加载"备忘包住 _ensure_vpi_ret_annot_loaded(其内部是 O(N) 前缀判定):
调用点每次都要查摘要,不做备忘会把编译拖慢一个量级。"""
if sha1 is None or method_name is None: return 0
if VpiReader._vpi_escape_module_loaded(sha1) == 0:
if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0: return 0
# ⚠ 同 lookup_ret_kind_via_vpi:**加载成功才记备忘**。失败也记会把该模块毒化成"已加载",
# 使之后所有跨模块查询(含 willFree 的 _vpi_willfree,它共用这条备忘)永远查空。
if VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir) == 0: return 0
VpiReader._vpi_escape_mark_module(sha1)
return VpiReader._lookup_vpi_escape_packed(sha1, cls_name, method_name)
# lookup_escape_dir_via_vpi - 跨模块声明式指令契约(@life(escape(...)) / @life(noescape(...)))。
# 本文件指令表按文件重置,跨模块调用点必为空,只能靠 .vpi 行尾的 esce=/escn= 传递;
# noescape != 0 取"强制不逃逸位",否则取"强制逃逸位"。0 = 无该标注(含该模块未加载过)。
def lookup_escape_dir_via_vpi(self, cls_sha1: str, class_name: str, func_name: str, noescape: int) -> int:
"""对方模块 .vpi 里落的契约掩码;0 = 无该标注"""
if func_name is None or cls_sha1 is None: return 0
return VpiReader._lookup_vpi_escape_dir(cls_sha1, class_name, func_name, noescape)
# lookup_ret_kind_via_vpi - 跨模块 `@life(ret(...))` 返回值生命期契约(A2,§14)。
# 与 escape/noescape 同一条 .vpi 通道(行尾 escr=);0 = 无声明(含该模块未加载过)。
# 注意:本查询可能在调用点**之前**(life_of_expr 判右值生命期)发生,不能假定调用点的
# 逃逸查询已把对方 vpi 载入,故这里自己走一次"本模块已加载"备忘 + 按需加载。
def lookup_ret_kind_via_vpi(self, cls_sha1: str, class_name: str, func_name: str) -> int:
"""对方模块 .vpi 里落的 ret_kind;0 = 无声明"""
if func_name is None or cls_sha1 is None: return 0
if VpiReader._vpi_escape_module_loaded(cls_sha1) == 0:
if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0: return 0
# ⚠ 只有**加载成功**才能记入"已加载"备忘。_ensure 失败(该模块的 vpi 此刻还没生成 /
# 打不开)时若也记上,后续所有跨模块查询(逃逸摘要 / willFree / ret)都会**跳过真正的
# 加载** → 缓存恒空 → 判据静默失效。现场:本查询可能在极早的翻译阶段先跑(life_of_expr
# 判右值生命期),把尚无 vpi 的模块毒化成"已加载" → EscapeXmodTest(esc)/
# NewLifetimeWillFreeTest(wf)双双漏检。
if VpiReader._ensure_vpi_ret_annot_loaded(cls_sha1, self.Gen.CallGenBase.TempDir) == 0: return 0
VpiReader._vpi_escape_mark_module(cls_sha1)
return VpiReader._lookup_vpi_escape_dir(cls_sha1, class_name, func_name, 2)
def lookup_escape_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> int:
"""跨模块逃逸摘要(v3):本类 + MRO 祖先各自的模块 sha1 依次查 .vpi,返回打包值(0=无摘要)。
本文件的摘要表按文件重置,跨模块调用点上必为空,只能靠 .vpi 传递;
键与方法路径一致:"Class.method"(方法)/ 裸名(模块级函数,此时 cls_name=None)。"""
if method_name is None: return 0
if cls_sha1 is not None:
p0: int = self._vpi_escape_packed(cls_sha1, cls_name, method_name)
if p0 != 0: return p0
if cls_name is not None:
mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, cls_name)
mi: int = 1
while mro is not None and mro[mi] != 0:
anc: str = rawptr(mro[mi])
if anc is not None and string.strcmp(anc, cls_name) != 0:
sha_a: str = self.Gen.StructGen.get_struct_sha1(anc)
if sha_a is not None:
pa: int = self._vpi_escape_packed(sha_a, anc, method_name)
if pa != 0: return pa
mi += 1
return 0
def _vpi_willfree(self, sha1: str, cls_name: str, method_name: str) -> int:
"""跨模块 willFree 标注查询(P2):按 (定义模块 sha1, Class?, method) 查 .vpi 缓存;0=无标注。
与逃逸摘要共用"本模块已加载"备忘:_ensure_vpi_ret_annot_loaded 的单一入口会把
ret/param/esc/wf 一起喂齐,所以这里只需保证模块被加载过一次。"""
if sha1 is None or method_name is None: return 0
if VpiReader._vpi_escape_module_loaded(sha1) == 0:
if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0: return 0
VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir)
VpiReader._vpi_escape_mark_module(sha1)
return VpiReader._lookup_vpi_willfree(sha1, cls_name, method_name)
def lookup_escape_clears_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, class_name: str, func_name: str, cargs: list[ast.AST*]*, can: sizet, param_base: int) -> int:
"""跨模块常量特化:把实参字面量装进常量环境,再向 vpi 缓存要"该清掉的位掩码";0 = 无命中
跨模块拿不到被调者函数体(只有 .vpi),所以用落盘的守卫表(`escg=`)而不是重算摘要。
没有常量实参、或该函数没落过守卫记录 → 返回 0(调用方原样保留掩码,保守)。"""
if cls_sha1 is None or func_name is None or cargs is None or can == 0: return 0
if GenApi.build_const_env(cargs, can, param_base) == 0: return 0
fenv: sizet* = GenApi.const_env_ptr()
if fenv is None: return 0
return VpiReader._lookup_vpi_escape_clears(cls_sha1, class_name, func_name, fenv)
def lookup_willfree_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> int:
"""跨模块 willFree 掩码(P2):本类 + MRO 祖先各自的模块 sha1 依次查 .vpi;0=无标注"""
if method_name is None: return 0
if cls_sha1 is not None:
p0: int = self._vpi_willfree(cls_sha1, cls_name, method_name)
if p0 != 0: return p0
if cls_name is not None:
mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, cls_name)
mi: int = 1
while mro is not None and mro[mi] != 0:
anc: str = rawptr(mro[mi])
if anc is not None and string.strcmp(anc, cls_name) != 0:
sha_a: str = self.Gen.StructGen.get_struct_sha1(anc)
if sha_a is not None:
pa: int = self._vpi_willfree(sha_a, anc, method_name)
if pa != 0: return pa
mi += 1
return 0
# _vpi_method_param_annot: on-demand lookup of the vpi param annotation string
def _vpi_method_param_annot(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> str:
"""Load and query the vpi parameter annotation string on demand; None on miss"""
@@ -1021,7 +1024,7 @@ novirtual class CallGenBase:
# trim trailing whitespace
e: sizet = end
while e > s:
c2: int = buf[e - 1]
c2: int = int(buf[e - 1])
if c2 != ' ' and c2 != '\t': break
e -= 1
@@ -1083,7 +1086,7 @@ novirtual class CallGenBase:
stdlib.free(nm_buf)
if nm_ty is not None:
nm_wrap: mlirlite.LLVMType* = nm_ty
for nm_i in range(nm_star):
for nm_i in range(int(nm_star)):
nm_wrap = mlirlite.Ptr(pool, nm_wrap)
return nm_wrap
@@ -1294,7 +1297,7 @@ novirtual class CallGenBase:
if use_bare != 0: viperlib.snprintf(key_buf, key_len, "%s.%s", class_sha1, method_name)
else:
viperlib.snprintf(key_buf, key_len, "%s.%s.%s", class_sha1, class_name, method_name)
v: t.CPtr = ht.__getitem__(key_buf)
v: rawptr = ht.__getitem__(key_buf)
stdlib.free(key_buf)
if v is None: return None
return rawptr(v)
+113 -65
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import mlirlite
@@ -20,12 +19,18 @@ define ASM_MAX_OPERANDS = 16
# Clobber (~{...}) capacity is decoupled from the operand limit: vipersimd x8
# needs 21 entries, so ASM_MAX_OPERANDS=16 silently truncated it to 16.
define ASM_MAX_CLOBBERS = 32
define ASM_TEXT_BUF_SIZE = 2048
# ASM_TEXT_BUF_SIZE holds the whole inline-asm template (a kernel switch statement
# template is already 2245 bytes). Overflow is fail-fast in _asm_append_cstr, never
# silently truncated: a cut-in-half template used to surface as a bogus llc parse
# error (e.g. a dangling `mov` with its operands chopped off).
define ASM_TEXT_BUF_SIZE = 8192
# Constraint buffer base size; the real capacity scales with operand/clobber count.
define ASM_CONST_BUF_SIZE = 512
define ASM_ARGS_BUF_SIZE = 512
define ASM_LINE_BUF_SIZE = 4096
define LLVMIR_MAX_OPS = 16
define LLVMIR_TEMPLATE_BUF = 512
# Raw LLVM IR templates (c.LLVMIR) are multi-instruction text, not one-liners.
define LLVMIR_TEMPLATE_BUF = 4096
# CallGenCBuiltin - c.*/asm/llvmir builtins
@@ -441,15 +446,22 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
return loaded
def _asm_append_cstr(self, dst: str, dst_size: sizet, src: str):
"""Append the C string src to the end of dst."""
def _asm_append_cstr(self, dst: str, dst_size: sizet, src: str, what: str = "inline asm"):
"""Append the C string src to the end of dst; fail-fast when it does not fit.
Silent truncation corrupts the generated text (a template cut in the middle of an
instruction yields a bogus llc/verifier error much later), so overflow is reported
here at the real cause.
"""
if dst is None or src is None: return
dlen: sizet = string.strlen(dst)
slen: sizet = string.strlen(src)
remain: sizet = dst_size - dlen
if remain <= 0: return
# Capacity includes the terminating NUL.
if dlen + slen + 1 > dst_size:
self.Gen.TypeGenErr.fatal_error(None, f"{what} text does not fit the fixed buffer: {dlen} byte(s) already used + {slen} byte(s) to append + 1 NUL > capacity {dst_size} byte(s); shorten or split the template (silent truncation would generate broken code)", "TypeError")
return
i: sizet = 0
while i < slen and i + 1 < remain:
while i < slen:
dst[dlen + i] = src[i]
i += 1
dst[dlen + i] = '\0'
@@ -492,9 +504,14 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
return asm_text
cur = cur.Next
if matched == 0:
if out_pos + 1 < asm_size * 2:
out[out_pos] = asm_text[i]
out_pos += 1
# The output buffer is 2x the input, so this cannot trigger for a plain copy;
# keep it fail-fast so a future longer rewrite (mangled expansion) can never
# silently drop characters.
if out_pos + 1 >= asm_size * 2:
self.Gen.TypeGenErr.fatal_error(None, f"inline asm text overflows the symbol-mangling buffer (capacity {asm_size * 2} byte(s)); shorten the template", "TypeError")
return asm_text
out[out_pos] = asm_text[i]
out_pos += 1
i += 1
out[out_pos] = '\0'
return out
@@ -647,7 +664,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
buf: str = pool.alloc(llen + rlen + 1)
if buf is None: return ""
string.strcpy(buf, left_str)
for i in range(rlen): buf[llen + i] = right_str[i]
for i in range(int(rlen)): buf[llen + i] = right_str[i]
buf[llen + rlen] = '\0'
return buf
return ""
@@ -670,6 +687,16 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
if mod_nm2.id is not None and string.strcmp(mod_nm2.id, "ASM_DESCR") == 0: return self._resolve_register(at.attr)
return ""
# 裸枚举名(不带 ASM_DESCR. / t. 前缀):`op=[CLOBBER_MEMORY, CLOBBER_RAX]`。
# 只有确实解析成 ASM_DESCR 成员时才接受(_resolve_register 对未知名给不出非空串),
# 否则返回空串交由调用方按"未知约束"处理,避免把普通变量名误当约束串。
if k == ast.ASTKind.Name:
nm_b: ast.Name* = ast.Name*(node)
if nm_b.id is not None:
rs_b: str = self._resolve_register(nm_b.id)
if rs_b is not None and rs_b[0] != '\0': return rs_b
return ""
# Constant: string constant
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(node)
@@ -719,13 +746,17 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
def _asm_add_operand(self, operands: GenTypes.AsmOperand*, count: int, val: mlirlite.Value*, constraint: str, is_output: int) -> int:
"""Append an operand, dedup by value and direction, and return the new count."""
if count >= ASM_MAX_OPERANDS: return count
# fail-fast: the operand array is fixed at ASM_MAX_OPERANDS slots; dropping the
# excess operand here would silently change the generated constraint/argument lists.
if count >= ASM_MAX_OPERANDS:
self.Gen.TypeGenErr.fatal_error(None, f"inline asm has more than {ASM_MAX_OPERANDS} operands (compiler limit); split the asm block into several ones", "TypeError")
return count
# fail-fast: AsmInp/AsmOut operands must not carry SHA1-mangled symbols;
# LLVM MC cannot parse digit-leading names, so mark them t.CExport.
if val is not None and val.Name is not None:
if self._asm_name_is_mangled(val.Name) != 0: self.Gen.TypeGenErr.fatal_error(None, f"__asm__ operand (AsmInp/AsmOut) does not allow a symbol with a SHA1 mangling prefix '{val.Name}'; mark the corresponding function/global definition as t.CExport (bare name)", "TypeError")
if val is not None:
for ei in range(count):
for ei in range(int(count)):
eop: GenTypes.AsmOperand* = self._asm_get_operand(operands, ei)
if eop is not None and eop.Value == val and eop.IsOutput == is_output: return count
op: GenTypes.AsmOperand* = self._asm_get_operand(operands, count)
@@ -754,8 +785,8 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
string.memset(operands, 0, operand_size * ASM_MAX_OPERANDS)
# Clobber name array: 8 bytes/slot, capacity ASM_MAX_CLOBBERS, decoupled from
# the operand limit; a single-level t.CPtr buffer with slots cast to str*.
clobbers: t.CPtr = pool.alloc(8 * ASM_MAX_CLOBBERS)
# the operand limit; a single-level rawptr buffer with slots cast to str*.
clobbers: rawptr = pool.alloc(8 * ASM_MAX_CLOBBERS)
if clobbers is None: return None
string.memset(clobbers, 0, 8 * ASM_MAX_CLOBBERS)
@@ -792,7 +823,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
jvalues: list[ast.AST*]* = js.values
if jvalues is not None:
jvlen: sizet = len(jvalues)
for vi in range(jvlen):
for vi in range(int(jvlen)):
part: ast.AST* = jvalues.get(vi)
if part is None: continue
pk: int = part.kind()
@@ -878,7 +909,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
keywords: list[ast.AST*]* = cl.keywords
if keywords is not None:
kwlen: sizet = len(keywords)
for ki in range(kwlen):
for ki in range(int(kwlen)):
kw: ast.Keyword* = ast.Keyword*(keywords.get(ki))
if kw is None or kw.arg is None or kw.value is None: continue
kwk: int = kw.value.kind()
@@ -890,7 +921,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
out_elts: list[ast.AST*]* = out_lst.elts
if out_elts is None: continue
oelen: sizet = len(out_elts)
for oi in range(oelen):
for oi in range(int(oelen)):
oelt: ast.AST* = out_elts.get(oi)
if oelt is None or oelt.kind() != ast.ASTKind.Call: continue
ocall: ast.Call* = ast.Call*(oelt)
@@ -919,7 +950,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
in_elts: list[ast.AST*]* = in_lst.elts
if in_elts is None: continue
ielen: sizet = len(in_elts)
for ii in range(ielen):
for ii in range(int(ielen)):
ielt: ast.AST* = in_elts.get(ii)
if ielt is None or ielt.kind() != ast.ASTKind.Call: continue
icall: ast.Call* = ast.Call*(ielt)
@@ -941,16 +972,20 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
cl_elts: list[ast.AST*]* = cl_lst.elts
if cl_elts is None: continue
clen: sizet = len(cl_elts)
for ci in range(clen):
for ci in range(int(clen)):
celt: ast.AST* = cl_elts.get(ci)
if celt is None: continue
cstr: str = self._resolve_asm_descr(pool, celt)
if cstr is None or cstr[0] == '\0': continue
if clobber_count < ASM_MAX_CLOBBERS:
cl_addr: uint64 = uint64(clobbers) + clobber_count * 8
cl_ptr: str* = (str*)(cl_addr)
cl_ptr[0] = cstr
clobber_count += 1
# fail-fast: the clobber slot array is fixed at ASM_MAX_CLOBBERS;
# dropping the excess would silently weaken the clobber list.
if clobber_count >= ASM_MAX_CLOBBERS:
self.Gen.TypeGenErr.fatal_error(celt, f"inline asm has more than {ASM_MAX_CLOBBERS} clobbers (compiler limit); remove duplicates or split the asm block", "TypeError")
return None
cl_addr: uint64 = uint64(clobbers) + clobber_count * 8
cl_ptr: str* = (str*)(cl_addr)
cl_ptr[0] = cstr
clobber_count += 1
# 3. Parse the second positional arg as the clobber list
if len(cargs) >= 2:
@@ -960,22 +995,24 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
pos_elts: list[ast.AST*]* = pos_lst.elts
if pos_elts is not None:
plen: sizet = len(pos_elts)
for pi in range(plen):
for pi in range(int(plen)):
pelt: ast.AST* = pos_elts.get(pi)
if pelt is None: continue
pstr: str = self._resolve_asm_descr(pool, pelt)
if pstr is None or pstr[0] == '\0': continue
if clobber_count < ASM_MAX_CLOBBERS:
pcl_addr: uint64 = uint64(clobbers) + clobber_count * 8
pcl_ptr: str* = (str*)(pcl_addr)
pcl_ptr[0] = pstr
clobber_count += 1
if clobber_count >= ASM_MAX_CLOBBERS:
self.Gen.TypeGenErr.fatal_error(pelt, f"inline asm has more than {ASM_MAX_CLOBBERS} clobbers (compiler limit); remove duplicates or split the asm block", "TypeError")
return None
pcl_addr: uint64 = uint64(clobbers) + clobber_count * 8
pcl_ptr: str* = (str*)(pcl_addr)
pcl_ptr[0] = pstr
clobber_count += 1
# 4. Count outputs/inputs and determine the return type
output_count: int = 0
input_count: int = 0
first_output_idx: int = -1
for oi2 in range(operand_count):
for oi2 in range(int(operand_count)):
op2: GenTypes.AsmOperand* = self._asm_get_operand(operands, oi2)
if op2.IsOutput != 0:
if first_output_idx < 0: first_output_idx = oi2
@@ -993,7 +1030,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
out_pn_last: mlirlite.ParamNode* = None
out_pn_n: int = 0
out_seq: int = 0
for oi5 in range(operand_count):
for oi5 in range(int(operand_count)):
op5: GenTypes.AsmOperand* = self._asm_get_operand(operands, oi5)
if op5 is None or op5.IsOutput == 0: continue
pty5: mlirlite.LLVMType* = None
@@ -1033,7 +1070,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
match ret_ty:
case mlirlite.LLVMType.Void():
ret_re: mlirlite.LLVMType* = None
for re_i in range(operand_count):
for re_i in range(int(operand_count)):
re_op: GenTypes.AsmOperand* = self._asm_get_operand(operands, re_i)
if re_op is None or re_op.IsOutput == 0: continue
if re_op.Value is not None and re_op.Value.Ty is not None and re_op.Value.Ty.Pointee != 0:
@@ -1052,19 +1089,19 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
new_idx: int = 0
# Outputs first
for oi3 in range(operand_count):
for oi3 in range(int(operand_count)):
op3: GenTypes.AsmOperand* = self._asm_get_operand(operands, oi3)
if op3.IsOutput != 0:
new_order[new_idx] = oi3
new_idx += 1
# Then inputs
for oi4 in range(operand_count):
for oi4 in range(int(operand_count)):
op4: GenTypes.AsmOperand* = self._asm_get_operand(operands, oi4)
if op4.IsOutput == 0:
new_order[new_idx] = oi4
new_idx += 1
# Build the orig -> new mapping
for ni2 in range(operand_count): orig_to_new[new_order[ni2]] = ni2
for ni2 in range(int(operand_count)): orig_to_new[new_order[ni2]] = ni2
# 4.6 Generate asm text (second f-string pass, $N from reordered indices).
# The project uses Intel syntax via the IR inteldialect keyword.
@@ -1076,7 +1113,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
if jvalues2 is not None:
jvlen2: sizet = len(jvalues2)
fv_fi: int = 0
for vi2 in range(jvlen2):
for vi2 in range(int(jvlen2)):
part2: ast.AST* = jvalues2.get(vi2)
if part2 is None: continue
pk2: int = part2.kind()
@@ -1096,47 +1133,53 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
cn4: ast.Constant* = ast.Constant*(tmpl_node)
if cn4.const_kind == ast.CONST_STR and cn4.str_val is not None: self._asm_append_cstr(asm_text, ASM_TEXT_BUF_SIZE, cn4.str_val)
# 5. Build the constraint string in reordered order (outputs first)
const_buf: str = pool.alloc(ASM_CONST_BUF_SIZE)
# 5. Build the constraint string in reordered order (outputs first).
# Capacity scales with the operand/clobber count: a 32-entry clobber list alone is
# ~300 bytes, so the old fixed 512-byte cap could silently cut it. Overflow of this
# (or any asm text buffer) is fail-fast in _asm_append_cstr.
const_cap: sizet = ASM_CONST_BUF_SIZE + sizet(operand_count + clobber_count + 1) * 64
const_buf: str = pool.alloc(const_cap)
if const_buf is None: return None
const_buf[0] = '\0'
first_const: int = 1
for ci2 in range(operand_count):
for ci2 in range(int(operand_count)):
orig_i: int = new_order[ci2]
cop: GenTypes.AsmOperand* = self._asm_get_operand(operands, orig_i)
cons_val: str = cop.Constraint
if cons_val is None: cons_val = "r"
if first_const == 0: self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, ",")
if first_const == 0: self._asm_append_cstr(const_buf, const_cap, ",")
first_const = 0
if cop.IsOutput != 0:
# Output constraint: ensure it starts with =
if cons_val[0] != '=': self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, "=")
self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, cons_val)
else: self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, cons_val)
if cons_val[0] != '=': self._asm_append_cstr(const_buf, const_cap, "=")
self._asm_append_cstr(const_buf, const_cap, cons_val)
else: self._asm_append_cstr(const_buf, const_cap, cons_val)
# Append clobber constraints ~{name}
for cidx in range(clobber_count):
for cidx in range(int(clobber_count)):
cl_addr2: uint64 = uint64(clobbers) + cidx * 8
cname: str = (str*)(cl_addr2)[0]
if cname is None or cname[0] == '\0': continue
if first_const == 0: self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, ",")
if first_const == 0: self._asm_append_cstr(const_buf, const_cap, ",")
first_const = 0
self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, "~{")
self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, cname)
self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, "}")
self._asm_append_cstr(const_buf, const_cap, "~{")
self._asm_append_cstr(const_buf, const_cap, cname)
self._asm_append_cstr(const_buf, const_cap, "}")
# 6. Build the argument list text (input operands only): each arg is type text (an
# inlined named struct can reach 512 bytes) plus the value name, so scale the buffer.
asm_arg_cap: sizet = sizet(operand_count + 1) * 544 + 64
# 6. Build the argument list text (input operands only): each arg is type text
# (TypePrint is given a 4096-byte buffer below) plus the value name, so size the
# buffer from that worst case instead of underestimating it (544 bytes/operand
# silently cut wide inlined named structs).
asm_arg_cap: sizet = sizet(operand_count + 1) * 4224 + 64
args_buf: str = pool.alloc(asm_arg_cap)
if args_buf is None: return None
args_buf[0] = '\0'
first_arg: int = 1
for ai3 in range(operand_count):
for ai3 in range(int(operand_count)):
orig_i2: int = new_order[ai3]
aop: GenTypes.AsmOperand* = self._asm_get_operand(operands, orig_i2)
if aop.IsOutput != 0: continue
@@ -1197,7 +1240,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
if is_void == 0 and first_output_idx >= 0:
result_val: mlirlite.Value* = mlirlite.SSAValue(pool, ret_ty, ssa_name)
out_emit_idx: int = 0
for oi_out in range(operand_count):
for oi_out in range(int(operand_count)):
orig_o: int = new_order[oi_out]
oo: GenTypes.AsmOperand* = self._asm_get_operand(operands, orig_o)
if oo is None or oo.IsOutput == 0: continue
@@ -1205,12 +1248,17 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
# Multi-output: extractvalue index out_emit_idx, then store
if oo.Value is not None and oo.Value.Ty is not None and oo.Value.Ty.Pointee != 0:
ev_name: str = pool.alloc(32)
ev_line: str = pool.alloc(ASM_LINE_BUF_SIZE)
# Size the line from its actual parts: ret_ty_buf alone can be ~4096
# bytes, so the old fixed cap could cut the instruction in half.
ev_cap: sizet = ASM_LINE_BUF_SIZE + 64
if ret_ty_buf is not None: ev_cap += string.strlen(ret_ty_buf)
if ssa_name is not None: ev_cap += string.strlen(ssa_name)
ev_line: str = pool.alloc(ev_cap)
if ev_name is not None and ev_line is not None:
# ssa_name is "%N"; build a unique "%asm.N.e{i}" (letter-
# initial) so %N.e{i} is not parsed as id plus a bad suffix.
viperlib.snprintf(ev_name, 32, "%%asm.%s.e%d", ssa_name + 1, out_emit_idx)
viperlib.snprintf(ev_line, ASM_LINE_BUF_SIZE, "%s = extractvalue %s %s, %d", ev_name, ret_ty_buf, ssa_name, out_emit_idx)
viperlib.snprintf(ev_line, ev_cap, "%s = extractvalue %s %s, %d", ev_name, ret_ty_buf, ssa_name, out_emit_idx)
mlirlite.block_append_text(pool, builder.CurBlock, ev_line)
ev_val: mlirlite.Value* = mlirlite.SSAValue(pool, oo.Value.Ty.Pointee, ev_name)
mlirlite.build_store(builder, ev_val, oo.Value)
@@ -1253,7 +1301,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
jvalues: list[ast.AST*]* = js.values
if jvalues is not None:
jvlen: sizet = len(jvalues)
for vi in range(jvlen):
for vi in range(int(jvlen)):
part: ast.AST* = jvalues.get(vi)
if part is None: continue
pk: int = part.kind()
@@ -1261,7 +1309,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
if pk == ast.ASTKind.Constant:
# String literal fragment
cn: ast.Constant* = ast.Constant*(part)
if cn.str_val is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, cn.str_val)
if cn.str_val is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, cn.str_val, "c.LLVMIR")
elif pk == ast.ASTKind.FormattedValue:
fv: ast.FormattedValue* = ast.FormattedValue*(part)
@@ -1279,7 +1327,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
if string.strcmp(attr_nm, "LInp") == 0:
# Input operand: translate to an LLVM value reference text (%x or constant)
inp_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, val_node, None, 0, trans)
if inp_val is not None and inp_val.Name is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, inp_val.Name)
if inp_val is not None and inp_val.Name is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, inp_val.Name, "c.LLVMIR")
elif string.strcmp(attr_nm, "LOut") == 0:
# Output operand: allocate a unique SSA register
@@ -1293,28 +1341,28 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
reg_buf: str = pool.alloc(32)
if reg_buf is not None:
viperlib.snprintf(reg_buf, 32, "%%llvmir_%d", self.Gen.CallGenBase.LLVMIRRegSeq)
self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, reg_buf)
self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, reg_buf, "c.LLVMIR")
out_alloca[out_count] = sizet(alloca)
out_regname[out_count] = sizet(reg_buf)
out_count += 1
continue
# Non-LInp/LOut f-string expression: translate and embed the value text
ival: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, fexpr, None, 0, trans)
if ival is not None and ival.Name is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, ival.Name)
if ival is not None and ival.Name is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, ival.Name, "c.LLVMIR")
elif tk == ast.ASTKind.Constant:
cn2: ast.Constant* = ast.Constant*(first_arg)
if cn2.str_val is not None:
# A bare strcpy is unbounded and could overflow the pool block, so
# use the same bounded append as the JoinedStr branch.
self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, cn2.str_val)
self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, cn2.str_val, "c.LLVMIR")
# 2. Embed the template text directly (no instruction parsing) + LOut writeback
mlirlite.block_append_text(pool, builder.CurBlock, text)
# LOut writeback: store <ty> %llvmirN, <ty>* %var
oi: int
for oi in range(out_count):
for oi in range(int(out_count)):
oa: mlirlite.Value* = mlirlite.Value*(out_alloca[oi])
rn: str = str(out_regname[oi])
if oa is None or oa.Ty is None or oa.Ty.Pointee == 0 or rn is None: continue
+67 -60
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import mlirlite
@@ -53,7 +52,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
method_index: int = -1
if vc_entry is not None and vc_entry.VTableMethods is not None:
methods_arr: sizet* = (sizet*)(vc_entry.VTableMethods)
for mi in range(vc_entry.VTableMethodCount):
for mi in range(int(vc_entry.VTableMethodCount)):
mname_addr: sizet = methods_arr[mi]
if mname_addr == 0: continue
mname: str = rawptr(mname_addr)
@@ -87,7 +86,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
# rawptr[ParamNode]); an earlier Function* annotation violated ptr rules.
vt_first: mlirlite.ParamNode* = None
vt_prev: mlirlite.ParamNode* = None
for vi in range(method_count):
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
@@ -116,7 +115,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
fp_prev: mlirlite.ParamNode* = None
fp_count: int = 0
cur_val: mlirlite.Value* = args_head
for vi in range(args_count):
for vi in range(int(args_count)):
if cur_val is None: break
cur_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(cur_val)
fpn: mlirlite.ParamNode* = mlirlite.new_param_node(pool, cur_ty)
@@ -165,23 +164,23 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
# 跨模块:本文件指令表为空,改查对方模块 .vpi 的 esce=/escn=
f_ci = self.Gen.CallGenBase.lookup_escape_dir_via_vpi(sha_ci, class_name, method_name, 0)
n_ci = self.Gen.CallGenBase.lookup_escape_dir_via_vpi(sha_ci, class_name, method_name, 1)
if f_ci != 0 or n_ci != 0: esc_mask_ci = (esc_mask_ci | f_ci) & (~n_ci)
# 同命裁剪(构造器路径):被构造对象的生命期由**调用方**(CallGenCtor)算出并经
# recv_life 传入 —— 这里只有 self_ptr(LLVM 值),拿不到 AST/生命期。
# recv_life == -1 = 未知 → **不做裁剪**(与接这条之前的行为完全一致)。
# 对象在本帧(裸 `T()` / `new stack` / `slife`)时,其余实参也都在本帧即可清位。
if recv_life >= 0 and esc_mask_ci != 0:
if recv_life == GenTypes.LIFE_FRAME or recv_life == GenTypes.LIFE_VALUE:
hard_ci: int = GenApi.get_param_escapes_hard_method(pool, class_name, method_name)
fld_ci: int = esc_mask_ci & (~hard_ci)
if fld_ci != 0:
can_ci2: sizet = len(arg_nodes)
cl_ci: int = self.Gen.CallGen._FrameArgsEscapeClearsMethod(trans, None, arg_nodes, can_ci2, fld_ci)
if f_ci != 0 or n_ci != 0: esc_mask_ci = (esc_mask_ci | f_ci) & (~n_ci)
# 同命裁剪(构造器路径):被构造对象的生命期由**调用方**(CallGenCtor)算出并经
# recv_life 传入 —— 这里只有 self_ptr(LLVM 值),拿不到 AST/生命期。
# recv_life == -1 = 未知 → **不做裁剪**(与接这条之前的行为完全一致)。
# 对象在本帧(裸 `T()` / `new stack` / `slife`)时,其余实参也都在本帧即可清位。
if recv_life >= 0 and esc_mask_ci != 0:
if recv_life == GenTypes.LIFE_FRAME or recv_life == GenTypes.LIFE_VALUE:
hard_ci: int = GenApi.get_param_escapes_hard_method(pool, class_name, method_name)
fld_ci: int = esc_mask_ci & (~hard_ci)
if fld_ci != 0:
can_ci2: sizet = len(arg_nodes)
cl_ci: int = self.Gen.CallGen._FrameArgsEscapeClearsMethod(trans, None, arg_nodes, can_ci2, fld_ci)
if cl_ci != 0: esc_mask_ci = esc_mask_ci & (~cl_ci)
if esc_mask_ci != 0:
dummy_ptr_ci: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
can_ci: sizet = len(arg_nodes)
for ai_ci in range(can_ci):
for ai_ci in range(int(can_ci)):
if ((esc_mask_ci >> int(ai_ci + 1)) & 1) != 0:
GenApi.fail_if_stack_addr_escape(arg_nodes.get(ai_ci), "a constructor parameter stored into a field", trans, dummy_ptr_ci)
@@ -323,7 +322,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
tail: mlirlite.Value* = self_ptr
if extra_args is not None and extra_count > 0:
for ei in range(extra_count):
for ei in range(int(extra_count)):
arg_addr: sizet = extra_args[ei]
if arg_addr == 0: continue
av: mlirlite.Value* = mlirlite.Value*(arg_addr)
@@ -343,7 +342,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
while tail is not None and mlirlite.value_get_next(tail) is not None: tail = mlirlite.value_get_next(tail)
# Default-argument filling: if the call site supplies fewer args than the
# definition, fill the missing ones (defined default, else zero).
mdf_defaults: t.CPtr = None
mdf_defaults: rawptr = None
mdf_default_count: int = 0
mdf_param_count: int = 0
if trans is not None and trans._funcs is not None:
@@ -485,19 +484,27 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
pc_node: mlirlite.Value* = head
new_head_pc: mlirlite.Value* = head
prev_pc: mlirlite.Value* = None
pc_idx: int = 0
while pc_node is not None:
pc_next_save: mlirlite.Value* = pc_node.Next
coerced_pc: mlirlite.Value* = pc_node
# 每个实参带上**它自己的 AST 节点**(arg_nodes 与链上 slot 1.. 对应,slot 0 是
# self):转换失败时报错才能定位到这个实参表达式(如 `&_on_submit`),而不是
# 因 node=None 退回"函数定义行"、只剩一帧。
pc_arg_node: ast.AST* = node
if pc_idx >= 1 and arg_nodes is not None and (pc_idx - 1) < len(arg_nodes):
pc_arg_node = arg_nodes.get(pc_idx - 1)
if pc_param is not None:
pty_pc: mlirlite.LLVMType* = mlirlite.param_get_ty(pc_param)
if pty_pc is not None:
c_pc: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, pc_node, pty_pc, None)
c_pc: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, pc_node, pty_pc, pc_arg_node)
if c_pc is not None: coerced_pc = c_pc
pc_param = mlirlite.param_get_next(pc_param)
coerced_pc.Next = None
if prev_pc is None: new_head_pc = coerced_pc
else: prev_pc.Next = coerced_pc
prev_pc = coerced_pc
pc_idx += 1
pc_node = pc_next_save
head = new_head_pc
@@ -510,7 +517,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
cmop_has_vt = cmop_entry.HasVTable
if cmop_has_vt == 1 and cmop_entry.VTableMethods is not None:
cmop_methods_arr: sizet* = (sizet*)(cmop_entry.VTableMethods)
for cmop_mi in range(cmop_entry.VTableMethodCount):
for cmop_mi in range(int(cmop_entry.VTableMethodCount)):
cmop_mname_addr: sizet = cmop_methods_arr[cmop_mi]
if cmop_mname_addr == 0: continue
cmop_mname: str = rawptr(cmop_mname_addr)
@@ -547,17 +554,17 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
if uint64(self_ptr.Ty) != uint64(cmop_i8_ptr_ty): cmop_bar_ptr = mlirlite.build_bitcast(builder, self_ptr, cmop_i8_ptr_ty)
if cmop_bar_ptr is not None: mlirlite.build_load(builder, cmop_i8_ty, cmop_bar_ptr, 1)
return call_res_cmop
def _MarkWillFreeArgName(self, trans: HT.Translator*, arg_node: ast.AST*) -> int:
"""P2 willFree:把实参变量槽标为"已失效";只处理简单变量名(1=标记过 / 0=未处理)。
与模块级调用同一口径(VarEntry.Freed):该槽其后被读 / 取地址 / 再传时,由
ExprGenName.translate_name_value 或 ExprGenOps 的 AddrOf 分支报 LifetimeError。"""
if trans is None or arg_node is None: return 0
if arg_node.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(arg_node)
if nm.id is None: return 0
return self.Gen.VarGen._FreedMark(self.Gen.VarGen.lookup_var(trans.SymTab, nm.id))
def _MarkWillFreeArgName(self, trans: HT.Translator*, arg_node: ast.AST*) -> int:
"""P2 willFree:把实参变量槽标为"已失效";只处理简单变量名(1=标记过 / 0=未处理)。
与模块级调用同一口径(VarEntry.Freed):该槽其后被读 / 取地址 / 再传时,由
ExprGenName.translate_name_value 或 ExprGenOps 的 AddrOf 分支报 LifetimeError。"""
if trans is None or arg_node is None: return 0
if arg_node.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(arg_node)
if nm.id is None: return 0
return self.Gen.VarGen._FreedMark(self.Gen.VarGen.lookup_var(trans.SymTab, nm.id))
# _translate_method_call - translate obj.method(args) into a call with the
# object pointer (obj_ptr, an alloca of Ptr(StructTy)) as the self argument.
@@ -578,27 +585,27 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
# 跨模块回退(v3):本文件摘要表按文件重置,跨模块只能查 .vpi 落盘的摘要
sha_mc = self.Gen.StructGen.get_struct_sha1(class_name)
pk_mc: int = self.Gen.CallGenBase.lookup_escape_via_vpi(pool, sha_mc, class_name, method_name)
if pk_mc != 0: esc_mask_mc = int(pk_mc & 4294967295)
# 跨模块常量特化:用实参常量套用对方模块 .vpi 落盘的守卫记录(形参位含 self 偏移)
if esc_mask_mc != 0:
cl_mc: int = self.Gen.CallGenBase.lookup_escape_clears_via_vpi(pool, sha_mc, class_name, method_name, cargs_mc, len(cargs_mc), 1)
if pk_mc != 0: esc_mask_mc = int(pk_mc & 4294967295)
# 跨模块常量特化:用实参常量套用对方模块 .vpi 落盘的守卫记录(形参位含 self 偏移)
if esc_mask_mc != 0:
cl_mc: int = self.Gen.CallGenBase.lookup_escape_clears_via_vpi(pool, sha_mc, class_name, method_name, cargs_mc, len(cargs_mc), 1)
if cl_mc != 0: esc_mask_mc = esc_mask_mc & (~cl_mc)
# 跨过程常量特化(P3 收尾):同文件能拿到方法体时,用常量实参重算摘要(形参位含 self 偏移)
if esc_mask_mc != 0:
can_rf: sizet = len(cargs_mc)
esc_mask_mc = GenApi.refine_escape_mask_method(pool, class_name, method_name, cargs_mc, can_rf, esc_mask_mc)
# 同命裁剪(**方法路径,此前缺失**):只清"写进形参对象字段"那一类位 —— 若其余实参(含
# 接收者)也都在本帧,则接收者与本帧同命、并不真的逃逸。现场:`h.set_q(l)`,h 是
# `new heap slife S()`(生命期本就是 FRAME)、l 是栈数组 → 两都在本帧 → 不该报。
if esc_mask_mc != 0:
hard_mc: int = GenApi.get_param_escapes_hard_method(pool, class_name, method_name)
fld_mc: int = esc_mask_mc & (~hard_mc)
if fld_mc != 0:
recv_mc: ast.AST* = None
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
recv_mc = ast.Attribute*(cl.func).value
can_sf: sizet = len(cargs_mc)
cl_sfm: int = self.Gen.CallGen._FrameArgsEscapeClearsMethod(trans, recv_mc, cargs_mc, can_sf, fld_mc)
esc_mask_mc = GenApi.refine_escape_mask_method(pool, class_name, method_name, cargs_mc, can_rf, esc_mask_mc)
# 同命裁剪(**方法路径,此前缺失**):只清"写进形参对象字段"那一类位 —— 若其余实参(含
# 接收者)也都在本帧,则接收者与本帧同命、并不真的逃逸。现场:`h.set_q(l)`,h 是
# `new heap slife S()`(生命期本就是 FRAME)、l 是栈数组 → 两都在本帧 → 不该报。
if esc_mask_mc != 0:
hard_mc: int = GenApi.get_param_escapes_hard_method(pool, class_name, method_name)
fld_mc: int = esc_mask_mc & (~hard_mc)
if fld_mc != 0:
recv_mc: ast.AST* = None
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
recv_mc = ast.Attribute*(cl.func).value
can_sf: sizet = len(cargs_mc)
cl_sfm: int = self.Gen.CallGen._FrameArgsEscapeClearsMethod(trans, recv_mc, cargs_mc, can_sf, fld_mc)
if cl_sfm != 0: esc_mask_mc = esc_mask_mc & (~cl_sfm)
# 声明式指令最终裁决(@life(escape/noescape):unsafe 但强制),在推断/回退之后最后套用
f_mc: int = GenApi.get_param_escape_force_method(pool, class_name, method_name)
@@ -611,7 +618,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
if esc_mask_mc != 0:
dummy_ptr_mc: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
can_mc: sizet = len(cargs_mc)
for ai_mc in range(can_mc):
for ai_mc in range(int(can_mc)):
if ((esc_mask_mc >> int(ai_mc + 1)) & 1) != 0:
GenApi.fail_if_stack_addr_escape(cargs_mc.get(ai_mc), "an escaping method parameter", trans, dummy_ptr_mc)
@@ -1002,7 +1009,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
# fields, so this needs no byte adjust; pass it through unchanged.
if anc_entry_th is not None and anc_entry_th.FieldCount > 0:
anc_first_name_th: str = None
for afi_th in range(anc_entry_th.FieldCount):
for afi_th in range(int(anc_entry_th.FieldCount)):
afe_th: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(anc_entry_th, afi_th)
if afe_th is not None:
afn_th: str = self.Gen.StructGen.get_field_name_ptr(afe_th)
@@ -1090,7 +1097,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
pname_k: str = mlirlite.param_get_name(cur_param_k)
if pname_k is not None:
ki_k: int
for ki_k in range(kwc):
for ki_k in range(int(kwc)):
kw_k: ast.Keyword* = ast.Keyword*(kw_calls.get(ki_k))
if kw_k is None or kw_k.arg is None or kw_k.value is None: continue
if self._kwarg_name_matches(kw_k.arg, pname_k) == 1:
@@ -1127,7 +1134,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
cur_param_k = mlirlite.param_get_next(cur_param_k)
else:
# Positional arguments (original logic).
for ai in range(can):
for ai in range(int(can)):
arg_node: ast.AST* = cargs.get(ai)
if arg_node is None:
# (a) Missing arg node (AST list hole): fail fast, never skip silently
@@ -1183,7 +1190,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
# Default-argument filling when the call supplies fewer args than the
# definition; look up the function's defaults info.
mdf_defaults: t.CPtr = None
mdf_defaults: rawptr = None
mdf_default_count: int = 0
mdf_param_count: int = 0
if trans is not None and trans._funcs is not None:
@@ -1313,7 +1320,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
mc_has_vt = mc_entry.HasVTable
if mc_has_vt == 1 and mc_entry.VTableMethods is not None:
mc_methods_arr: sizet* = (sizet*)(mc_entry.VTableMethods)
for mc_mi in range(mc_entry.VTableMethodCount):
for mc_mi in range(int(mc_entry.VTableMethodCount)):
mc_mname_addr: sizet = mc_methods_arr[mc_mi]
if mc_mname_addr == 0: continue
mc_mname: str = rawptr(mc_mname_addr)
@@ -1569,7 +1576,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
anc_entry_sp: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(anc)
if anc_entry_sp is not None and anc_entry_sp.FieldCount > 0:
first_name_sp: str = None
for afi_sp in range(anc_entry_sp.FieldCount):
for afi_sp in range(int(anc_entry_sp.FieldCount)):
afe_sp: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(anc_entry_sp, afi_sp)
if afe_sp is not None:
afn_sp: str = self.Gen.StructGen.get_field_name_ptr(afe_sp)
@@ -1620,7 +1627,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
head: mlirlite.Value* = self_arg
tail: mlirlite.Value* = self_arg
arg_count_sp: int = 1
for ai_sp in range(can_sp):
for ai_sp in range(int(can_sp)):
arg_node_sp: ast.AST* = cargs_sp.get(ai_sp)
if arg_node_sp is None: continue
# Fetch the param type first (char literal -> str param needs a materialized string).
@@ -1687,11 +1694,11 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
if class_name is None: return 0
if self.Gen is None: return 0
n: int = self.Gen.StructGen.get_struct_count()
for j in range(n):
for j in range(int(n)):
e: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(j)
if e is not None and e.Name is not None:
pc: int = self.Gen.StructGen.get_parent_count(e.Name)
for pj in range(pc):
for pj in range(int(pc)):
pn: str = self.Gen.StructGen.get_parent_name_at(e.Name, pj)
if pn is not None:
if string.strcmp(pn, class_name) == 0: return 1
@@ -1704,7 +1711,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
if class_name is None or cand_name is None or pool is None: return 0
if self.Gen is None: return 0
n: int = self.Gen.StructGen.get_struct_count()
for j in range(n):
for j in range(int(n)):
k_entry: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(j)
if k_entry is not None and k_entry.Name is not None:
mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, k_entry.Name)
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+4 -5
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
@@ -91,7 +90,7 @@ novirtual class CallGenFd(CallGenBase.CallGenBase):
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(fd_can):
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)
@@ -153,7 +152,7 @@ novirtual class CallGenFd(CallGenBase.CallGenBase):
cur_func: mlirlite.Function* = trans._cur_func
if cur_func is None: return 0
injected: int = 0
for ji in range(cnt):
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)
@@ -218,7 +217,7 @@ novirtual class CallGenFd(CallGenBase.CallGenBase):
# 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(fd_pc):
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)
@@ -243,5 +242,5 @@ novirtual class CallGenFd(CallGenBase.CallGenBase):
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(fd_pc): mlirlite.build_call(builder, fd_pon, None, 0, ret_i32_q, 0)
for _pi in range(int(fd_pc)): mlirlite.build_call(builder, fd_pon, None, 0, ret_i32_q, 0)
return fd_pc
+40 -24
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
@@ -65,7 +64,7 @@ def _gfn_idx_find(funcs: GenTypes.FuncEntry*, idx_base: sizet*, cap: int, name:
mask: sizet = sizet(cap - 1)
si: sizet = sizet(h) & mask
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
for i in range(cap):
for i in range(int(cap)):
slot: sizet* = _gfn_idx_slot(idx_base, si)
sv: sizet = slot[0]
if sv == 0: return -1
@@ -84,7 +83,7 @@ def _gfn_idx_insert(idx_base: sizet*, cap: int, slot_index: int, name: str) -> i
h: uint64 = _gfn_hash(name)
mask: sizet = sizet(cap - 1)
si: sizet = sizet(h) & mask
for i in range(cap):
for i in range(int(cap)):
slot: sizet* = _gfn_idx_slot(idx_base, si)
if slot[0] == 0:
slot[0] = sizet(slot_index + 1)
@@ -117,6 +116,23 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
# ============================================================
def _emit_eh_guarded_call(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, func: mlirlite.Function*, call_name: str, head: mlirlite.Value*, count: int, ret_ty: mlirlite.LLVMType*, is_variadic: int) -> mlirlite.Value*:
"""Call a function with try-except EH detection; returns the call result (positioned at ok block)"""
# Argument-count check (fail-fast at the front end instead of leaking to the LLVM verifier).
# Callee params include the injected EH out-params (%__eh_msg_out__/__eh_code_out__), which the
# caller does not pass explicitly (they are appended later in this function), so subtract them.
if func is not None and is_variadic == 0:
n_declared: int = 0
p_chk: mlirlite.Param* = mlirlite.function_get_param_head(func)
while p_chk is not None:
n_declared += 1
p_chk = mlirlite.param_get_next(p_chk)
if self._func_has_eh_params(func) == 1: n_declared = n_declared - 2
if n_declared >= 0 and count != n_declared:
fname_chk: str = mlirlite.function_get_name(func)
if fname_chk is None: fname_chk = call_name
self.Gen.TypeGenErr.fatal_error(
None,
f"Argument count mismatch calling '{fname_chk}': the callee declares {n_declared} parameter(s) but the call passes {count} argument(s)",
"TypeError")
if trans is None or self._func_has_eh_params(func) == 0: return mlirlite.build_call(builder, call_name, head, count, ret_ty, is_variadic)
# ---- 1. determine output pointers ----
i8_ty_g: mlirlite.LLVMType* = mlirlite.Int8(pool)
@@ -289,7 +305,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
elts: list[ast.AST*]* = tp.elts
if elts is not None:
n: sizet = len(elts)
for i in range(n):
for i in range(int(n)):
if count >= 15: break
elt: ast.AST* = elts.get(i)
if elt is None: continue
@@ -328,7 +344,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
cur: str = rawptr(cur_addr)
if cur is None: continue
dup: int = 0
for vi in range(vcount):
for vi in range(int(vcount)):
va: sizet = visited[vi]
if va != 0:
vn: str = rawptr(va)
@@ -347,12 +363,12 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
# enqueue subclasses: full scan over registered structs whose ParentNames
# contain cur (by ordinal, not by name -- do not switch to find_struct).
scnt: int = self.Gen.StructGen.get_struct_count()
for i in range(scnt):
for i in range(int(scnt)):
e2: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(i)
if e2 is None or e2.Name is None: continue
if e2.ParentCount > 0 and e2.ParentNames is not None:
parr: sizet* = (sizet*)(e2.ParentNames)
for j in range(e2.ParentCount):
for j in range(int(e2.ParentCount)):
pa: sizet = parr[j]
if pa != 0:
pn: str = rawptr(pa)
@@ -456,7 +472,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
param_node: mlirlite.ParamNode* = mlirlite.new_param_node(pool, i8_ptr_ty)
pn_tail: mlirlite.ParamNode* = param_node
if can > 0 and cargs is not None and trans is not None:
for ai in range(can):
for ai in range(int(can)):
arg: ast.AST* = cargs.get(ai)
if arg is None: continue
arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans)
@@ -516,7 +532,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
"""Return whether name is registered as a closure variable name (1 = yes, 0 = no)"""
if name is None or self.ClosureNames is None: return 0
i: sizet
for i in range(self.ClosureCount):
for i in range(int(self.ClosureCount)):
if string.strcmp(self.ClosureNames + i * 64, name) == 0: return 1
return 0
@@ -555,7 +571,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
return 1
# idempotence/conflict check: linear scan of registered bare names (cap 64, cheap)
i: sizet
for i in range(self.FuncptrCount):
for i in range(int(self.FuncptrCount)):
idx_hit: sizet = sizet(i) * 64
if string.strcmp(self.FuncptrNames + idx_hit, name) == 0:
prev_ret: mlirlite.LLVMType* = (mlirlite.LLVMType*)(self.FuncptrRets[i])
@@ -580,7 +596,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
def lookup_func_ptr_ret(self, name: str) -> mlirlite.LLVMType*:
"""Look up a function-pointer variable's return type; None = unregistered (defaults to i32)"""
if name is None or self.FuncptrNames is None: return None
for i in range(self.FuncptrCount):
for i in range(int(self.FuncptrCount)):
idx: sizet = sizet(i) * 64
if string.strcmp(self.FuncptrNames + idx, name) == 0: return (mlirlite.LLVMType*)(self.FuncptrRets[i])
return None
@@ -602,7 +618,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
tail: mlirlite.Value* = None
arg_count: int = 0
ai: sizet
for ai in range(can):
for ai in range(int(can)):
arg: ast.AST* = cargs.get(ai)
if arg is not None:
arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans)
@@ -621,7 +637,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
fp_first: mlirlite.ParamNode* = None
fp_prev: mlirlite.ParamNode* = None
cur_v: mlirlite.Value* = head
for pi in range(arg_count):
for pi in range(int(arg_count)):
if cur_v is None: break
cur_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(cur_v)
pn2: mlirlite.ParamNode* = mlirlite.new_param_node(pool, cur_ty)
@@ -666,7 +682,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
# translate extra args and link them
prev: mlirlite.Value* = fmt_ptr
for ai in range(1, can):
for ai in range(1, int(can)):
arg: ast.AST* = cargs.get(ai)
if arg is not None:
arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans)
@@ -708,7 +724,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
arg_tail: mlirlite.Value* = None
arg_count: int = 1 # the format string is printf arg 1
for ai in range(can):
for ai in range(int(can)):
arg: ast.AST* = cargs.get(ai)
if arg is None: continue
arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans)
@@ -830,7 +846,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
return None
# Single pass: record the first same-bare-name entry as fallback; return this module's entry early.
fallback_entry: GenTypes.FuncEntry* = None
for i in range(func_count):
for i in range(int(func_count)):
entry_addr: uint64 = uint64(funcs) + i * entry_size
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(entry_addr)
if entry is not None and entry.Name is not None:
@@ -846,7 +862,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
return None
def add_func_to_table(self, funcs: GenTypes.FuncEntry*, func_count: int, name: str, func: mlirlite.Function*, max_count: int, defaults: t.CPtr = None, default_count: int = 0, param_count: int = 0, pool: memhub.MemBuddy* = None, def_sha1: str = None) -> int:
def add_func_to_table(self, funcs: GenTypes.FuncEntry*, func_count: int, name: str, func: mlirlite.Function*, max_count: int, defaults: rawptr = None, default_count: int = 0, param_count: int = 0, pool: memhub.MemBuddy* = None, def_sha1: str = None) -> int:
"""Add a function to a func table; returns 0 on success"""
if name is None or func is None or funcs is None: return 1
if func_count >= max_count: return 1
@@ -983,7 +999,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
entry_g: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(self.GlobalFuncs) + gi * entry_size)
return entry_g
return None
for i in range(self.GlobalFuncCount):
for i in range(int(self.GlobalFuncCount)):
entry_addr: uint64 = uint64(self.GlobalFuncs) + i * entry_size
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(entry_addr)
if entry.Name is not None:
@@ -1004,7 +1020,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
return 1
def _register_global_defaults(self, name: str, defaults: t.CPtr, default_count: int, param_count: int, def_sha1: str = None) -> int:
def _register_global_defaults(self, name: str, defaults: rawptr, default_count: int, param_count: int, def_sha1: str = None) -> int:
"""Register a function's default arguments in the global table; returns 0 on success"""
if name is None or defaults is None: return 1
if self._init_global_defaults_table() == 0: return 1
@@ -1049,9 +1065,9 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
string.memset(kinds, 0, default_count * 4)
string.memset(ints, 0, default_count * 8)
string.memset(strs, 0, default_count * 8)
dl: list[ast.AST*]* = (list[ast.AST*] | t.CPtr)(uint64(defaults))
dl: list[ast.AST*]* = list[ast.AST*]*(uint64(defaults))
di: int
for di in range(default_count):
for di in range(int(default_count)):
dnode: ast.AST* = dl.get(di)
if dnode is not None and dnode.kind() == ast.ASTKind.Constant:
dcn: ast.Constant* = ast.Constant*(dnode)
@@ -1099,7 +1115,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
"""Find default-argument info by name and optional defining-module SHA1, or None"""
if name is None or self.GlobalDefaults is None: return None
entry_size: sizet = GenTypes.FuncDefaultsEntry.__sizeof__()
for i in range(self.GlobalDefaultsCount):
for i in range(int(self.GlobalDefaultsCount)):
entry_addr: uint64 = uint64(self.GlobalDefaults) + i * entry_size
entry: GenTypes.FuncDefaultsEntry* = GenTypes.FuncDefaultsEntry*(entry_addr)
if entry.Name is None: continue
@@ -1139,7 +1155,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
"""Look up a name in a func table; returns the full FuncEntry or None"""
if name is None or funcs is None: return None
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
for i in range(func_count):
for i in range(int(func_count)):
entry_addr: uint64 = uint64(funcs) + i * entry_size
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(entry_addr)
if entry.Name is not None:
@@ -1151,7 +1167,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
"""Find a func table entry by bare name and defining-module SHA1, or None"""
if name is None or funcs is None: return None
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
for i in range(func_count):
for i in range(int(func_count)):
entry_addr: uint64 = uint64(funcs) + i * entry_size
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(entry_addr)
if entry is None or entry.Name is None: continue
+4 -5
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import mlirlite
@@ -151,7 +150,7 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
i2 += 1
return new_head
# _coerce_args_to_def_params: coerce cross-module call args to vpi definition-side param
# _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"""
@@ -231,7 +230,7 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
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
# _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"""
@@ -405,7 +404,7 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
# list iteration is unsupported; use range + get instead
api_count: sizet = len(win32_apis)
for ai in range(api_count):
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
@@ -543,7 +542,7 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
# Find the matching keyword argument
param_name: str = mlirlite.param_get_name(cur_param)
if param_name is not None:
for ki in range(kw_count):
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:
File diff suppressed because it is too large. Load diff
+6 -7
View File
@@ -1,6 +1,5 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.GenApi as GenApi
import t, c
from stdint import *
import ast
import mlirlite
@@ -74,10 +73,10 @@ novirtual class CallGenProp(CallGenBase.CallGenBase):
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)
# 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
@@ -120,7 +119,7 @@ novirtual class CallGenProp(CallGenBase.CallGenBase):
n_av: sizet = string.strlen(annot_val)
last_dot_av: sizet = n_av
depth_av: int = 0
for ai_av in range(n_av):
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
@@ -226,7 +225,7 @@ novirtual class CallGenProp(CallGenBase.CallGenBase):
can: sizet = 0
if cargs is not None: can = len(cargs)
tail: mlirlite.Value* = head
for ai in range(can):
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)
+6 -7
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import mlirlite
@@ -17,7 +16,7 @@ novirtual class CallGenVa(CallGenBase.CallGenBase):
# _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: t.CPtr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
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)
@@ -37,7 +36,7 @@ novirtual class CallGenVa(CallGenBase.CallGenBase):
# 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: t.CPtr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
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)
@@ -67,7 +66,7 @@ novirtual class CallGenVa(CallGenBase.CallGenBase):
# 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: t.CPtr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
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
@@ -104,7 +103,7 @@ novirtual class CallGenVa(CallGenBase.CallGenBase):
# 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: t.CPtr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
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)
@@ -122,7 +121,7 @@ novirtual class CallGenVa(CallGenBase.CallGenBase):
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: t.CPtr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
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)
@@ -180,7 +179,7 @@ novirtual class CallGenVa(CallGenBase.CallGenBase):
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)
# 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
+1 -2
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import mlirlite
@@ -35,7 +34,7 @@ novirtual class CallGenVt(CallGenBase.CallGenBase):
# 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(method_count):
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
+32 -33
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import memhub
@@ -164,7 +163,7 @@ novirtual class ClassGenBase:
bases: list[ast.AST*]* = cd.bases
bn: sizet = len(bases)
if bn == 0: return 0
for bi in range(bn):
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
@@ -202,7 +201,7 @@ novirtual class ClassGenBase:
bases: list[ast.AST*]* = cd.bases
bn: sizet = len(bases)
if bn == 0: return 0
for bi in range(bn):
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
@@ -240,7 +239,7 @@ novirtual class ClassGenBase:
bases: list[ast.AST*]* = cd.bases
bn: sizet = len(bases)
if bn == 0: return 0
for bi in range(bn):
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
@@ -250,7 +249,7 @@ novirtual class ClassGenBase:
if decorator_list is None: return 0
dn: sizet = len(decorator_list)
if dn == 0: return 0
for di in range(dn):
for di in range(int(dn)):
deco: ast.AST* = decorator_list.get(di)
if deco is None: continue
k: int = deco.kind()
@@ -285,7 +284,7 @@ novirtual class ClassGenBase:
if decorator_list is None: return 0
dn: sizet = len(decorator_list)
if dn == 0: return 0
for di in range(dn):
for di in range(int(dn)):
deco: ast.AST* = decorator_list.get(di)
if deco is None: continue
k: int = deco.kind()
@@ -297,7 +296,7 @@ novirtual class ClassGenBase:
if at0.attr is None or string.strcmp(at0.attr, "Attribute") != 0: continue
if cl.args is None: continue
an: sizet = len(cl.args)
for ai in range(an):
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:
@@ -324,7 +323,7 @@ novirtual class ClassGenBase:
if decorator_list is None: return 0
dn: sizet = len(decorator_list)
if dn == 0: return 0
for di in range(dn):
for di in range(int(dn)):
deco: ast.AST* = decorator_list.get(di)
if deco is None: continue
if deco.kind() == ast.ASTKind.Call:
@@ -334,7 +333,7 @@ novirtual class ClassGenBase:
if at0.attr is None or string.strcmp(at0.attr, "Attribute") != 0: continue
if cl.args is None: continue
an: sizet = len(cl.args)
for ai in range(an):
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)
@@ -349,7 +348,7 @@ novirtual class ClassGenBase:
if decorator_list is None: return meta
dn: sizet = len(decorator_list)
if dn == 0: return meta
for di in range(dn):
for di in range(int(dn)):
deco: ast.AST* = decorator_list.get(di)
if deco is None: continue
k: int = deco.kind()
@@ -390,7 +389,7 @@ novirtual class ClassGenBase:
self.ClassMetaCount = 0
if self.ClassMetaCount >= CLASS_META_MAX: return -1
# Idempotent: skip duplicate (kind, cls, field).
for i in range(self.ClassMetaCount):
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
@@ -406,7 +405,7 @@ novirtual class ClassGenBase:
"""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(self.ClassMetaCount):
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
@@ -414,7 +413,7 @@ novirtual class ClassGenBase:
"""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(self.ClassMetaCount):
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
@@ -434,7 +433,7 @@ novirtual class ClassGenBase:
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(self.DecoratorFuncCount):
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)
@@ -447,13 +446,13 @@ novirtual class ClassGenBase:
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(self.DecoratorFuncCount):
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(self.DecoratorFuncCount):
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
# ============================================================
@@ -478,7 +477,7 @@ novirtual class ClassGenBase:
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(self.ExceptionCount):
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
@@ -512,13 +511,13 @@ novirtual class ClassGenBase:
bi: int = self._builtin_exception_code(name)
if bi != 0: return bi
if self.ExceptionNames is None: return 0
for i in range(self.ExceptionCount):
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(self.ExceptionCount):
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
@@ -529,7 +528,7 @@ novirtual class ClassGenBase:
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(self.ExceptionCount):
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:
@@ -545,7 +544,7 @@ novirtual class ClassGenBase:
if code_e != 0 and self._exception_codes_tmp_count < max_codes:
# Deduplicate (guards against inheritance cycles).
dup: int = 0
for i in range(self._exception_codes_tmp_count):
for i in range(int(self._exception_codes_tmp_count)):
if out_codes[i] == code_e:
dup = 1
break
@@ -554,7 +553,7 @@ novirtual class ClassGenBase:
self._exception_codes_tmp_count += 1
# Recurse over user exceptions whose parent is name.
if self.ExceptionNames is None: return
for i in range(self.ExceptionCount):
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':
@@ -564,7 +563,7 @@ novirtual class ClassGenBase:
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(bn_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
@@ -581,7 +580,7 @@ novirtual class ClassGenBase:
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(bn_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
@@ -606,7 +605,7 @@ novirtual class ClassGenBase:
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(en_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)
@@ -629,7 +628,7 @@ novirtual class ClassGenBase:
viperlib.snprintf(key_buf, cl_len + mn_len + 2, "%s.%s", class_name, method_name)
# Update duplicate entry.
i: int
for i in range(self.MethodMetaCount):
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,
@@ -655,7 +654,7 @@ novirtual class ClassGenBase:
viperlib.snprintf(key_buf, cl_len + mn_len + 2, "%s.%s", class_name, method_name)
found: int = 0
i: int
for i in range(self.MethodMetaCount):
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]
@@ -728,7 +727,7 @@ novirtual class ClassGenBase:
bases: list[ast.AST*]* = cd.bases
bn: sizet = len(bases)
if bn == 0: return None
for bi in range(bn):
for bi in range(int(bn)):
base_node: ast.AST* = bases.get(bi)
if base_node is None: continue
_fi_pc: str = None
@@ -762,7 +761,7 @@ novirtual class ClassGenBase:
bases: list[ast.AST*]* = cd.bases
bn: sizet = len(bases)
count: int = 0
for bi in range(bn):
for bi in range(int(bn)):
if count >= max_parents: break
base_node: ast.AST* = bases.get(bi)
if base_node is None: continue
@@ -779,7 +778,7 @@ novirtual class ClassGenBase:
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(count):
for fi in range(int(count)):
addr: sizet = names_buf[fi]
if addr != 0:
existing: str = rawptr(addr)
@@ -789,7 +788,7 @@ novirtual class ClassGenBase:
"""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(cn):
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
@@ -801,7 +800,7 @@ novirtual class ClassGenBase:
bases: list[ast.AST*]* = cd.bases
bn: sizet = len(bases)
if bn == 0: return
for bi in range(bn):
for bi in range(int(bn)):
base_node: ast.AST* = bases.get(bi)
if base_node is None: continue
bk: int = base_node.kind()
@@ -831,7 +830,7 @@ novirtual class ClassGenBase:
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(cn):
for ci in range(int(cn)):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
if stmt.kind() != ast.ASTKind.FunctionDef: continue
+23 -7
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
@@ -162,9 +161,13 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
# 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)
@@ -172,13 +175,14 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
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(parents_count):
for pi in range(int(parents_count)):
pname_addr: sizet = parents_buf[pi]
if pname_addr == 0: continue
pname: str = rawptr(pname_addr)
@@ -194,7 +198,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
# entries survive passes and the existing early-return never repairs it).
return 0
pfc: int = parent_entry.FieldCount
for pfi in range(pfc):
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)
@@ -221,13 +225,15 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
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(cn):
for ci in range(int(cn)):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk: int = stmt.kind()
@@ -340,11 +346,17 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
# 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:
@@ -373,7 +385,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
prev_node = vtable_node
total_field_count = field_count + 1
for fi in range(field_count):
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)
@@ -439,7 +451,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
# 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(field_count):
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)
@@ -492,7 +504,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
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(field_count):
for fi in range(int(field_count)):
fname_addr: sizet = field_names_buf[fi]
if fname_addr == 0: continue
fname: str = rawptr(fname_addr)
@@ -518,6 +530,10 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
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)
+46 -34
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
@@ -168,7 +167,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
"""Count visible names in an element table (shared by Tuple/List)."""
if elts is None: return 0
n: int = 0
for i in range(len(elts)): n += self._enum_target_visible_names(elts.get(i))
for i in range(int(len(elts))): n += self._enum_target_visible_names(elts.get(i))
return n
def _enum_target_visible_names(self, node: ast.AST*) -> int:
"""Count visible names at a member-name position (Name / Tuple / List items, Starred recursion)."""
@@ -189,14 +188,14 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
"""Total source-visible member names in an enum class body (including forms the registrar rejects)."""
if children is None: return 0
n: int = 0
for ci in range(len(children)):
for ci in range(int(len(children))):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk: int = stmt.kind()
if sk == ast.ASTKind.Assign:
ag: ast.Assign* = ast.Assign*(stmt)
if ag.targets is not None:
for ti in range(len(ag.targets)): n += self._enum_target_visible_names(ag.targets.get(ti))
for ti in range(int(len(ag.targets))): n += self._enum_target_visible_names(ag.targets.get(ti))
elif sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
n += self._enum_target_visible_names(aa.target)
@@ -207,7 +206,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
def _enum_first_unregistered_node(self, children: list[ast.AST*]*) -> ast.AST*:
"""First statement with visible member names the registrar rejects, for fail-fast line location; None if none."""
if children is None: return None
for ci in range(len(children)):
for ci in range(int(len(children))):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk: int = stmt.kind()
@@ -220,7 +219,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
elif sk == ast.ASTKind.Assign:
ag: ast.Assign* = ast.Assign*(stmt)
if ag.targets is not None:
for ti in range(len(ag.targets)):
for ti in range(int(len(ag.targets))):
tgt: ast.AST* = ag.targets.get(ti)
if tgt is not None and tgt.kind() != ast.ASTKind.Name and self._enum_target_visible_names(tgt) > 0: return stmt
return None
@@ -253,7 +252,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
member_count: int = 0
next_val: int64 = 0
for ci in range(cn):
for ci in range(int(cn)):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk: int = stmt.kind()
@@ -320,7 +319,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
next_val = cur_val_a + 1
# Store per target (non-Name targets are caught by the registration-side fallback check)
for ti in range(tn):
for ti in range(int(tn)):
t0: ast.AST* = tgts.get(ti)
if t0 is None or t0.kind() != ast.ASTKind.Name: continue
tgt_a: ast.Name* = ast.Name*(t0)
@@ -349,7 +348,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
# Determine the base type: the widest member type in bits
base_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
max_bits: int = 0
for mi in range(member_count):
for mi in range(int(member_count)):
ty_addr: sizet = member_types_buf[mi]
if ty_addr == 0: continue
mty: mlirlite.LLVMType* = (mlirlite.LLVMType*)(ty_addr)
@@ -365,7 +364,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
return 0
# Add members
for mi in range(member_count):
for mi in range(int(member_count)):
fname_addr: sizet = member_names_buf[mi]
if fname_addr == 0: continue
mname: str = rawptr(fname_addr)
@@ -486,7 +485,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
field_count: int = 0
max_size: int = 0
for ci in range(cn):
for ci in range(int(cn)):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk: int = stmt.kind()
@@ -572,7 +571,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
entry.IsUnion = 1
# Add field info (all fields have Index=0 because they share offset 0)
for fi in range(field_count):
for fi in range(int(field_count)):
fname_addr: sizet = field_names_buf[fi]
if fname_addr == 0: continue
fname: str = rawptr(fname_addr)
@@ -665,7 +664,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
variant_count: int = 0
max_field_count: int = 0 # max field count across all variants
for ci in range(cn):
for ci in range(int(cn)):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk: int = stmt.kind()
@@ -679,7 +678,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
variant_children: list[ast.AST*]* = variant_cd.children
vcn: sizet = len(variant_children)
field_idx: int = 0
for vi in range(vcn):
for vi in range(int(vcn)):
vstmt: ast.AST* = variant_children.get(vi)
if vstmt is None: continue
vk: int = vstmt.kind()
@@ -737,9 +736,9 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
position_max_sizes[0] = sizet(4)
# Positions 1+: take the max field size across all variants at each position
for pos in range(1, total_positions):
for pos in range(1, int(total_positions)):
pos_max: sizet = 0
for vi in range(variant_count):
for vi in range(int(variant_count)):
if vi >= 64:
break # variant_field_sizes 64-class slot cap; guards against out-of-bounds reads
field_pos: int = pos - 1 # variant fields start at position 0
@@ -757,7 +756,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
first_node: mlirlite.ParamNode* = None
tail_node: mlirlite.ParamNode* = None
for pos in range(total_positions):
for pos in range(int(total_positions)):
psize: int = int(position_max_sizes[pos])
field_ty: mlirlite.LLVMType* = i32_ty
if psize > 4: field_ty = i64_ty
@@ -815,7 +814,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
self.Gen.StructGen.add_field(pool, entry, "__tag", i32_ty, None, None)
# Add payload fields (positions 1+, i32 or i64)
for pos in range(1, total_positions):
for pos in range(1, int(total_positions)):
psize2: int = int(position_max_sizes[pos])
payload_ty: mlirlite.LLVMType* = i32_ty
if psize2 > 4: payload_ty = i64_ty
@@ -839,7 +838,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
children: list[ast.AST*]* = cd.children
if children is None: return
cn: sizet = len(children)
for ci in range(cn):
for ci in range(int(cn)):
stmt: ast.AST* = children.get(ci)
if stmt is None or stmt.kind() != ast.ASTKind.ClassDef: continue
variant_cd: ast.ClassDef* = ast.ClassDef*(stmt)
@@ -847,7 +846,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
vchildren: list[ast.AST*]* = variant_cd.children
vcn: sizet = len(vchildren)
field_idx: int = 0
for vi in range(vcn):
for vi in range(int(vcn)):
vstmt: ast.AST* = vchildren.get(vi)
if vstmt is None: continue
if vstmt.kind() != ast.ASTKind.AnnAssign: continue
@@ -902,7 +901,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
# VLogger.error(f"[RENUM-FAIL] RCV={rcv_chk} abnormal (perm_pool address expected after lazy init)", "RENUM")
# Check whether already registered (avoid duplicate registration)
for i in range(self.RenumClassCount):
for i in range(int(self.RenumClassCount)):
existing: str = str(self.RenumClassNames[i])
if existing is not None and string.strcmp(existing, class_name) == 0: return
@@ -930,7 +929,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
cn: sizet = len(children)
# First pass: find the upper bound of Assign variants' explicit integer values (stays -1 if none)
max_explicit: int = -1
for ci in range(cn):
for ci in range(int(cn)):
stmt_p: ast.AST* = children.get(ci)
if stmt_p is None or stmt_p.kind() != ast.ASTKind.Assign: continue
ag_p: ast.Assign* = ast.Assign*(stmt_p)
@@ -948,7 +947,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
slot: int = 0
next_class_tag: int = max_explicit + 1
variant_index: int = 0
for ci in range(cn):
for ci in range(int(cn)):
if slot >= variant_count: break
if slot >= RENUM_VARIANT_PER_CLASS:
# Fixed 32 slots per class: exceeding it would overflow into the next class's slots; report fail-fast
@@ -1006,9 +1005,9 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
"""Find the REnum class name owning a variant name; None if absent"""
if variant_name is None or self.RenumClassNames is None: return None
for i in range(self.RenumClassCount):
for i in range(int(self.RenumClassCount)):
vcount: int = int(self.RenumClassVariantCounts[i])
for j in range(vcount):
for j in range(int(vcount)):
name_offset: sizet = i * RENUM_VARIANT_PER_CLASS + j
vname: str = str(self.RenumVariantNames[name_offset])
if vname is not None and string.strcmp(vname, variant_name) == 0:
@@ -1042,21 +1041,21 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
def _is_renum_class_name(self, class_name: str) -> int:
"""Check whether a name is a registered REnum class, 1=yes / 0=no"""
if class_name is None or self.RenumClassNames is None: return 0
for i in range(self.RenumClassCount):
for i in range(int(self.RenumClassCount)):
existing: str = str(self.RenumClassNames[i])
if existing is not None and string.strcmp(existing, class_name) == 0: return 1
return 0
def _lookup_renum_variant_in_class(self, class_name: str, variant_name: str) -> int:
"""Find a variant in a given REnum class, return its tag value or -1"""
if class_name is None or variant_name is None or self.RenumClassNames is None: return -1
for i in range(self.RenumClassCount):
for i in range(int(self.RenumClassCount)):
existing: str = str(self.RenumClassNames[i])
if existing is None or string.strcmp(existing, class_name) != 0: continue
vcount: int = int(self.RenumClassVariantCounts[i])
for j in range(vcount):
for j in range(int(vcount)):
name_offset: sizet = i * RENUM_VARIANT_PER_CLASS + j
vname: str = str(self.RenumVariantNames[name_offset])
if vname is not None and string.strcmp(vname, variant_name) == 0: return self.RenumVariantTags[name_offset]
if vname is not None and string.strcmp(vname, variant_name) == 0: return int(self.RenumVariantTags[name_offset])
return -1
return -1
def _register_renum_field_alias(self, pool: memhub.MemBuddy*, class_name: str, field_name: str, payload_idx: int, field_ty: mlirlite.LLVMType* = None, variant_name: str = None):
@@ -1100,11 +1099,24 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
# Dedupe check (pointer-free): the class slot holds a perm_pool string copy compared with strcmp,
# so it no longer relies on pointer equality (registration uses AST pointers, lookup uses pool copies).
for i in range(self.RenumAliasCount):
for i in range(int(self.RenumAliasCount)):
cls_existing_ptr: str = rawptr(self.RenumAliasClass[i])
if cls_existing_ptr is not None and string.strcmp(cls_existing_ptr, class_name) == 0:
fld_existing_ptr: str = rawptr(self.RenumAliasField[i])
if fld_existing_ptr is not None and string.strcmp(fld_existing_ptr, field_name) == 0:
# **变体名也必须一致**:同一 REnum 的不同变体可有**同名字段**
# (LLVMType.Int.Bits 与 LLVMType.Float.Bits)。旧判据只比 (class, field),
# 第二个同名字段被当成重复记录直接丢弃 ⇒ _lookup_renum_member_ty(class,
# variant, idx) 查不到该记录 ⇒ match 绑定退化为按**物理槽类型**读取
# (max-payload 槽是 i8*/i64)⇒ 赋给 i32 时误报"隐式窄化禁止"(假阳性)。
_vn_existing: str = rawptr(self.RenumAliasVariant[i])
_vn_same: int = 0
if variant_name is None:
_vn_same = 1 if _vn_existing is None else 0
elif _vn_existing is not None:
_vn_same = 1 if string.strcmp(_vn_existing, variant_name) == 0 else 0
if _vn_same == 0:
continue
# Existing record: if the first pass left the type slot 0 (self-referencing field, see
# _refill_renum_field_types), backfill the real type now; a non-zero slot is left as is.
if self.RenumAliasTy[i] == 0 and field_ty is not None:
@@ -1148,17 +1160,17 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
def _lookup_renum_field_alias(self, class_name: str, field_name: str) -> int:
"""Look up an REnum variant field name's payload index, return it or -1 (class/field compared by content)."""
if class_name is None or field_name is None or self.RenumAliasClass is None: return -1
for i in range(self.RenumAliasCount):
for i in range(int(self.RenumAliasCount)):
cls_existing_ptr: str = rawptr(self.RenumAliasClass[i])
if cls_existing_ptr is not None and string.strcmp(cls_existing_ptr, class_name) == 0:
# field_name is compared by strcmp content (pointers differ, content is the same)
fld_existing_ptr: str = rawptr(self.RenumAliasField[i])
if fld_existing_ptr is not None and string.strcmp(fld_existing_ptr, field_name) == 0: return self.RenumAliasIndex[i]
if fld_existing_ptr is not None and string.strcmp(fld_existing_ptr, field_name) == 0: return int(self.RenumAliasIndex[i])
return -1
def _lookup_renum_field_alias_ty(self, class_name: str, field_name: str) -> mlirlite.LLVMType*:
"""Look up an REnum variant member's actual LLVM type, or None when the type slot is 0 (binding falls back to the slot type)."""
if class_name is None or field_name is None or self.RenumAliasTy is None: return None
for i in range(self.RenumAliasCount):
for i in range(int(self.RenumAliasCount)):
cls_existing_ptr: str = rawptr(self.RenumAliasClass[i])
if cls_existing_ptr is not None and string.strcmp(cls_existing_ptr, class_name) == 0:
fld_existing_ptr: str = rawptr(self.RenumAliasField[i])
@@ -1170,7 +1182,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
"""Look up a variant member's actual LLVM type by (class, variant name, payload index) for match binding; None if unresolved."""
if class_name is None or variant_name is None or self.RenumAliasVariant is None: return None
if payload_idx < 1: return None
for i in range(self.RenumAliasCount):
for i in range(int(self.RenumAliasCount)):
cls_existing_ptr: str = rawptr(self.RenumAliasClass[i])
if cls_existing_ptr is None or string.strcmp(cls_existing_ptr, class_name) != 0: continue
if self.RenumAliasVariant[i] == 0: continue
+33 -34
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
@@ -65,7 +64,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
# Assign the list to a local to trigger type inference (avoids field __len__ failure)
names_local: list[str]* = self.GenericClassNames
cn: sizet = len(names_local)
for i in range(cn):
for i in range(int(cn)):
nm: str = names_local.get(i)
if nm is not None and string.strcmp(nm, cd.name) == 0:
# A duplicate name must not simply return: the first-registered node may lack
@@ -92,13 +91,13 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
# Compute total length: each name length + (n-1) spaces + NUL
total: sizet = 1
ti: sizet
for ti in range(tp_n):
for ti in range(int(tp_n)):
tp_name: str = tp.get(ti)
if tp_name is not None: total += string.strlen(tp_name) + 1
buf: str = CompilerFactory.factory.PermPool.alloc(total)
if buf is None: return None
buf[0] = '\0'
for ti in range(tp_n):
for ti in range(int(tp_n)):
tp_name = tp.get(ti)
if tp_name is None: continue
if ti > 0 and buf[0] != '\0': string.strcat(buf, " ")
@@ -136,7 +135,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
keys_local: list[str]* = self.GenericFuncKeys
kn: sizet = len(keys_local)
ki: sizet
for ki in range(kn):
for ki in range(int(kn)):
knm: str = keys_local.get(ki)
if knm is not None and string.strcmp(knm, key_buf) == 0: return
tp_names_buf: str = self._copy_type_params_stdlib(fd.type_params)
@@ -153,7 +152,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
nodes_local: list[ast.FunctionDef*]* = self.GenericFuncNodes
kn: sizet = len(keys_local)
i: sizet
for i in range(kn):
for i in range(int(kn)):
knm: str = keys_local.get(i)
if knm is not None and string.strcmp(knm, key) == 0: return nodes_local.get(i)
return None
@@ -165,7 +164,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
sha1s_local: list[str]* = self.GenericFuncSha1s
kn: sizet = len(keys_local)
i: sizet
for i in range(kn):
for i in range(int(kn)):
knm: str = keys_local.get(i)
if knm is not None and string.strcmp(knm, key) == 0: return sha1s_local.get(i)
return None
@@ -177,7 +176,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
files_local: list[str]* = self.GenericFuncFiles
kn: sizet = len(keys_local)
i: sizet
for i in range(kn):
for i in range(int(kn)):
knm: str = keys_local.get(i)
if knm is not None and string.strcmp(knm, key) == 0: return files_local.get(i)
return None
@@ -188,7 +187,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
keys_local: list[str]* = self.GenericFuncKeys
kn: sizet = len(keys_local)
i: sizet
for i in range(kn):
for i in range(int(kn)):
knm: str = keys_local.get(i)
if knm is not None and string.strcmp(knm, key) == 0:
if i >= GENERIC_TEMPLATE_MAX: return None
@@ -199,7 +198,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
if tp_list is None: return None
seg_start: sizet = 0
blen: sizet = string.strlen(buf_tp)
for pos in range(blen + 1):
for pos in range(int(blen + 1)):
if buf_tp[pos] == ' ' or buf_tp[pos] == '\0':
seg_len: sizet = pos - seg_start
if seg_len > 0:
@@ -221,7 +220,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
alist: list[ast.AST*]* = ags.args
an: sizet = len(alist)
ai: sizet
for ai in range(an):
for ai in range(int(an)):
pa: ast.AST* = alist.get(ai)
if pa is None or pa.kind() != ast.ASTKind.Arg: continue
ag: ast.Arg* = ast.Arg*(pa)
@@ -342,7 +341,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
body: list[ast.AST*]* = fd.children
bn: sizet = len(body)
bi: sizet
for bi in range(bn):
for bi in range(int(bn)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: new_fd.append(stmt)
return new_fd
@@ -388,7 +387,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
alist: list[ast.AST*]* = ags.args
an: sizet = len(alist)
ai: sizet
for ai in range(an):
for ai in range(int(an)):
pa: ast.AST* = alist.get(ai)
if pa is None or pa.kind() != ast.ASTKind.Arg: continue
arg: ast.Arg* = ast.Arg*(pa)
@@ -412,7 +411,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
if mode_buf_g is not None:
mode_cnt_g: int = 0
has_ref_g: int = 0
for ai_g in range(an):
for ai_g in range(int(an)):
pa_g: ast.AST* = alist.get(ai_g)
if pa_g is None or pa_g.kind() != ast.ASTKind.Arg: continue
arg_g: ast.Arg* = ast.Arg*(pa_g)
@@ -452,7 +451,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
# is replaced with the concrete type name (nested specialization: generic fn calling generic fn).
ta_cnt_g: sizet = len(type_args)
ta_i_g: sizet
for ta_i_g in range(ta_cnt_g):
for ta_i_g in range(int(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)
@@ -549,7 +548,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
names_local: list[str]* = self.GenericClassNames
cn: sizet = len(names_local)
i: sizet
for i in range(cn):
for i in range(int(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
@@ -562,7 +561,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
if tp_list is None: return None
seg_start: sizet = 0
blen: sizet = string.strlen(buf_tp)
for pos in range(blen + 1):
for pos in range(int(blen + 1)):
if buf_tp[pos] == ' ' or buf_tp[pos] == '\0':
seg_len: sizet = pos - seg_start
if seg_len > 0:
@@ -579,10 +578,10 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
if self.GenericClassNames is None or class_name is None: return None
# Assign the list to a local to trigger type inference (avoids field __len__ failure)
names_local: list[str]* = self.GenericClassNames
nodes_local: list[ast.ClassDef*] | t.CPtr = self.GenericClassNodes
nodes_local: list[ast.ClassDef*]* = self.GenericClassNodes
cn: sizet = len(names_local)
i: sizet
for i in range(cn):
for i in range(int(cn)):
nm: str = names_local.get(i)
if nm is not None and string.strcmp(nm, class_name) == 0:
node: ast.ClassDef* = nodes_local.get(i)
@@ -596,7 +595,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
sha1s_local: list[str]* = self.GenericClassSha1s
cn: sizet = len(names_local)
i: sizet
for i in range(cn):
for i in range(int(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)
@@ -609,7 +608,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
files_local: list[str]* = self.GenericClassFiles
cn: sizet = len(names_local)
i: sizet
for i in range(cn):
for i in range(int(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)
@@ -624,7 +623,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
norm_args: list[str]* = list[str](pool, 8)
if norm_args is None: return class_name
i: sizet
for i in range(n):
for i in range(int(n)):
ta_raw: str = type_args.get(i)
if ta_raw is None: continue
ta_norm: str = self.Gen.TypeGenResolve._norm_type_arg_name(pool, ta_raw, from_imports)
@@ -634,7 +633,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
if n == 0: return class_name
# Compute total length: class_name + "[" + arg0 + "," + arg1 + ... + "]" + NUL
total_len: sizet = string.strlen(class_name) + 2 # "[" and "]"
for i in range(n):
for i in range(int(n)):
if i > 0:
total_len += 1 # ","
ta: str = norm_args.get(i)
@@ -646,7 +645,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
mangled[0] = '\0'
string.strcat(mangled, class_name)
string.strcat(mangled, "[")
for i in range(n):
for i in range(int(n)):
if i > 0: string.strcat(mangled, ",")
ta: str = norm_args.get(i)
if ta is not None: string.strcat(mangled, ta)
@@ -663,7 +662,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
if nm.id is not None:
n: sizet = len(tp_names) if tp_names is not None else 0
i: sizet
for i in range(n):
for i in range(int(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)
@@ -701,7 +700,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
tup_elts: list[ast.AST*]* = tup.elts
en: sizet = len(tup_elts)
ei: sizet
for ei in range(en):
for ei in range(int(en)):
el: ast.AST* = tup_elts.get(ei)
new_el: ast.AST* = self._clone_annotation(pool, el, tp_names, type_args)
if new_el is not None: new_elts.append(new_el)
@@ -725,7 +724,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
old_arg_list: list[ast.AST*]* = old_args.args
an: sizet = len(old_arg_list)
ai: sizet
for ai in range(an):
for ai in range(int(an)):
arg_node: ast.AST* = old_arg_list.get(ai)
if arg_node is None: continue
if arg_node.kind() == ast.ASTKind.Arg:
@@ -744,7 +743,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
cd_children: list[ast.AST*]* = cd.children
cn: sizet = len(cd_children)
ci: sizet
for ci in range(cn):
for ci in range(int(cn)):
child: ast.AST* = cd_children.get(ci)
if child is None: continue
ck: int = child.kind()
@@ -767,7 +766,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
fd_children: list[ast.AST*]* = fd.children
bcn: sizet = len(fd_children)
bci: sizet
for bci in range(bcn):
for bci in range(int(bcn)):
body_stmt: ast.AST* = fd_children.get(bci)
if body_stmt is not None: new_fd.append(body_stmt)
new_cd.append(new_fd)
@@ -779,7 +778,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
"""Find a cached specialization name; None when absent."""
if self.SpecKeys is None or spec_key is None: return None
i: int
for i in range(self.SpecCount):
for i in range(int(self.SpecCount)):
key_addr: sizet = self.SpecKeys[i]
if key_addr != 0:
k: str = rawptr(key_addr)
@@ -865,7 +864,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
# concrete type name to avoid ndarray[T] double-recursion crashes; in-place edits are side-effect free.
ta_cnt_g: sizet = len(type_args)
ta_i_g: sizet
for ta_i_g in range(ta_cnt_g):
for ta_i_g in range(int(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)
@@ -929,7 +928,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
sk_n: sizet = len(type_args)
sk_total: sizet = string.strlen(class_name) + 2 # "<" and ">"
sk_i: sizet
for sk_i in range(sk_n):
for sk_i in range(int(sk_n)):
if sk_i > 0:
sk_total += 1 # ","
sk_ta: str = type_args.get(sk_i)
@@ -942,7 +941,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
spec_key[0] = '\0'
string.strcat(spec_key, class_name)
string.strcat(spec_key, "<")
for sk_i in range(sk_n):
for sk_i in range(int(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)
@@ -1026,7 +1025,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
if string.strcmp(class_name, "GSListNode") == 0:
result_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(spec_name)
if result_entry is not None:
for ri in range(result_entry.FieldCount):
for ri in range(int(result_entry.FieldCount)):
rfe: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(result_entry, ri)
if rfe is not None:
rfn: str = self.Gen.StructGen.get_field_name_ptr(rfe)
-1
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
from . import ClassGenBase
+198 -46
View File
@@ -1,6 +1,5 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Type.TypeSpec as TSpec
import t, c
from stdint import *
import ast
import mlirlite
@@ -19,8 +18,8 @@ import lib.core.Gen.DebugInfoGen as DebugInfoGen
# Method metadata flag: TLS (@t.TLS decorator; local copy avoids a cross-module CDefine lookup).
define META_TLS = 32
# Max number of vtable methods.
define VTBL_METHOD_MAX = 32
# Vtable method tables are sized per class at collection time (parents' count + own methods);
# there is deliberately no fixed VTBL_METHOD_MAX.
define SCOPE_FUNCTION = 1
# ============================================================
@@ -28,16 +27,21 @@ define SCOPE_FUNCTION = 1
novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
"""vtable collection, generation, and method translation."""
# _vtable_strcat - append src to the end of dst.
# _vtable_strcat - append src to the end of dst; fail-fast when it does not fit.
def _vtable_strcat(self, dst: str, dst_size: sizet, src: str):
"""Append src to the end of dst."""
"""Append src to the end of dst.
A truncated vtable initializer stays plausible LLVM syntax but is cut in the middle of a
bitcast, which then surfaces as a verifier error pointing at an unrelated method.
"""
if dst is None or src is None: return
dlen: sizet = string.strlen(dst)
slen: sizet = string.strlen(src)
remain: sizet = dst_size - dlen
if remain <= 0: return
if dlen + slen + 1 > dst_size:
self.Gen.TypeGenErr.fatal_error(None, f"vtable initializer text exceeds its buffer: {dlen} byte(s) used + {slen} byte(s) to append + 1 NUL > capacity {dst_size} byte(s); the class has too many virtual methods for the fixed buffer", "OverflowError")
return
i: sizet = 0
while i < slen and i + 1 < remain:
while i < slen:
dst[dlen + i] = src[i]
i += 1
dst[dlen + i] = '\0'
@@ -62,15 +66,35 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
if children is None: return 0
cn: sizet = len(children)
# Capacity comes from the class itself: the parents' already-collected vtable methods
# (an upper bound — dedupe/override only shrink it) plus the number of own method
# definitions. A fixed 32 silently dropped every later method, after which a call missed
# the slot and fell back to a static call of the base implementation.
parent_count: int = self.Gen.StructGen.get_parent_count(class_name)
vt_cap: int = 0
if parent_count > 0:
for pi0 in range(int(parent_count)):
pname0: str = self.Gen.StructGen.get_parent_name_at(class_name, pi0)
if pname0 is None: continue
pe0: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(pname0, trans.ModuleSha1)
if pe0 is None: pe0 = self.Gen.StructGen.find_struct(pname0)
if pe0 is not None and pe0.VTableMethodCount > 0: vt_cap = vt_cap + pe0.VTableMethodCount
for ci0 in range(int(cn)):
stmt0: ast.AST* = children.get(ci0)
if stmt0 is not None and stmt0.kind() == ast.ASTKind.FunctionDef: vt_cap = vt_cap + 1
vt_cap = vt_cap + 1 # one spare slot
if vt_cap < 1: vt_cap = 1
vt_bytes: sizet = sizet(8 * vt_cap)
# Allocate with PermPool: VTableMethods must survive per-file pool resets.
full_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(8 * VTBL_METHOD_MAX)
method_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(8 * VTBL_METHOD_MAX)
full_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(vt_bytes)
method_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(vt_bytes)
# Record each method's parent SHA1 (non-zero = inherited, mangle with parent module SHA1).
method_parent_sha1s: sizet* = CompilerFactory.factory.PermPool.alloc(8 * VTBL_METHOD_MAX)
method_parent_sha1s: sizet* = CompilerFactory.factory.PermPool.alloc(vt_bytes)
if full_names_buf is None or method_names_buf is None or method_parent_sha1s is None: return 0
string.memset(full_names_buf, 0, 8 * VTBL_METHOD_MAX)
string.memset(method_names_buf, 0, 8 * VTBL_METHOD_MAX)
string.memset(method_parent_sha1s, 0, 8 * VTBL_METHOD_MAX)
string.memset(full_names_buf, 0, vt_bytes)
string.memset(method_names_buf, 0, vt_bytes)
string.memset(method_parent_sha1s, 0, vt_bytes)
method_count: int = 0
@@ -78,7 +102,6 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
# subclass, others the parent; SHA1-aware lookup avoids cross-module same-name mismatches.
vt_self_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(class_name, trans.ModuleSha1)
# Multiple inheritance: walk parents in declaration order, dedupe by short name (first wins).
parent_count: int = self.Gen.StructGen.get_parent_count(class_name)
if parent_count > 0:
for pi in range(int(parent_count)):
pname: str = self.Gen.StructGen.get_parent_name_at(class_name, pi)
@@ -92,7 +115,10 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
if vt_parent_entry is not None: parent_vt_count = vt_parent_entry.VTableMethodCount
if parent_vt_count > 0:
for pvi in range(int(parent_vt_count)):
if method_count >= VTBL_METHOD_MAX: break
if method_count >= vt_cap:
# Unreachable with the upper-bound sizing above; stay loud rather than
# writing past the PermPool array.
self.Gen.TypeGenErr.fatal_error(cd, f"internal error: vtable of '{class_name}' exceeded its computed capacity {vt_cap}", "InternalCompilerError")
pmethod_name: str = None
if vt_parent_entry is not None: pmethod_name = self.Gen.StructGen.get_vtable_method_name(pname, pvi)
if pmethod_name is None: continue
@@ -160,7 +186,10 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
if stmt.kind() != ast.ASTKind.FunctionDef: continue
fd: ast.FunctionDef* = ast.FunctionDef*(stmt)
if fd is None or fd.name is None: continue
if method_count >= VTBL_METHOD_MAX: break
if method_count >= vt_cap:
# Unreachable with the upper-bound sizing above (own methods were counted);
# stay loud rather than writing past the PermPool array.
self.Gen.TypeGenErr.fatal_error(cd, f"internal error: vtable of '{class_name}' exceeded its computed capacity {vt_cap}", "InternalCompilerError")
mname: str = fd.name
# Skip constructors and special methods (they must not enter the vtable).
@@ -219,7 +248,12 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
mod: mlirlite.LLVMModule* = trans.Module
# Collect virtual methods (inherited + own) into Vt* buffers and set entry.VTableMethodCount/VTableMethods.
if self._collect_vtable_methods(trans, cd, class_name) == 0: return 0
# 带 virtual 标记的类(调用方已保证 HasVTable==1)必须**始终**发射自己的 @"<sha1>.类名.vtable"
# 全局量,即便 VTableMethodCount==0:isinstance() 在运行期要引用该符号(CallGenLeafKit._make_vtable_ref_i8),
# 缺定义会在链接期 undefined symbol。故这里不因「收集到 0 个虚方法」而提前返回,而是继续构造一个
# 零字段的 vtable(!llvm.struct<()> 合法),其形状与 VTableMethodCount==0 / 对象布局保持一致
# (对象仅在槽位处保存 vtable 指针,vtable 自身元素个数不影响对象布局)。
self._collect_vtable_methods(trans, cd, class_name)
full_names_buf: sizet* = self.VtFullNames
method_names_buf: sizet* = self.VtMethodNames
method_parent_sha1s: sizet* = self.VtParentSha1s
@@ -242,18 +276,28 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
vtable_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, first_node, method_count, None)
if vtable_ty is None: return 0
# Build the initializer string; 16384 bytes so large vtables (MemBuddy 28+) are not truncated.
init_buf: str = pool.alloc(16384)
# Size the initializer from the real method list: each entry is
# "i8* bitcast (<func ty>* @<mangled> to i8*)", where the function type text alone can
# reach 1024 bytes and the mangled name carries a SHA1 prefix. The old fixed 16384 bytes
# silently cut long vtables in the middle of a bitcast.
init_cap: sizet = 64
for si in range(int(method_count)):
name_addr0: sizet = full_names_buf[si]
if name_addr0 == 0:
init_cap = init_cap + 12
else:
init_cap = init_cap + 1024 + 96 + string.strlen(rawptr(name_addr0)) + 128
init_buf: str = pool.alloc(init_cap)
if init_buf is None: return 0
init_buf[0] = '\0'
self._vtable_strcat(init_buf, 16384, "{ ")
self._vtable_strcat(init_buf, init_cap, "{ ")
for i in range(int(method_count)):
if i > 0: self._vtable_strcat(init_buf, 16384, ", ")
if i > 0: self._vtable_strcat(init_buf, init_cap, ", ")
full_name_addr: sizet = full_names_buf[i]
if full_name_addr == 0:
self._vtable_strcat(init_buf, 16384, "i8* null")
self._vtable_strcat(init_buf, init_cap, "i8* null")
continue
full_name: str = rawptr(full_name_addr)
@@ -273,7 +317,7 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
ge: GenTypes.FuncEntry* = self.Gen.CallGenLeafKit.find_func_entry_global(full_name)
func_ty_buf: str = pool.alloc(1024)
if func_ty_buf is None:
self._vtable_strcat(init_buf, 16384, "i8* null")
self._vtable_strcat(init_buf, init_cap, "i8* null")
continue
func_ty_buf[0] = '\0'
if ge is None or ge.Spec is None or TSpec.SpecToLLVMText(ge.Spec, func_ty_buf, 1024) != 0:
@@ -281,18 +325,18 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
continue
# Build the bitcast string: i8* bitcast (func_ty* @mangled to i8*).
self._vtable_strcat(init_buf, 16384, "i8* bitcast (")
self._vtable_strcat(init_buf, 16384, func_ty_buf)
self._vtable_strcat(init_buf, 16384, "* @")
self._vtable_strcat(init_buf, init_cap, "i8* bitcast (")
self._vtable_strcat(init_buf, init_cap, func_ty_buf)
self._vtable_strcat(init_buf, init_cap, "* @")
if self._vtable_name_needs_quote(mangled_name) != 0:
self._vtable_strcat(init_buf, 16384, "\"")
self._vtable_strcat(init_buf, 16384, mangled_name)
self._vtable_strcat(init_buf, 16384, "\"")
else: self._vtable_strcat(init_buf, 16384, mangled_name)
self._vtable_strcat(init_buf, 16384, " to i8*)")
self._vtable_strcat(init_buf, init_cap, "\"")
self._vtable_strcat(init_buf, init_cap, mangled_name)
self._vtable_strcat(init_buf, init_cap, "\"")
else: self._vtable_strcat(init_buf, init_cap, mangled_name)
self._vtable_strcat(init_buf, init_cap, " to i8*)")
self._vtable_strcat(init_buf, 16384, " }")
self._vtable_strcat(init_buf, init_cap, " }")
# 4. Build the vtable global variable name.
vtable_name_buf: str = pool.alloc(128)
@@ -350,14 +394,30 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
if fd_meta != 0: self.Gen.ClassGenBase.register_method_meta(class_name, fd_check.name, fd_meta)
if has_method == 0:
# Subclass has no own methods but may inherit parent virtual methods; use oop_entry.ParentName directly.
parent_name: str = None
if oop_entry is not None: parent_name = oop_entry.ParentName
if parent_name is not None:
parent_vt_count: int = self.Gen.StructGen.get_vtable_method_count(parent_name)
if parent_vt_count > 0:
has_method = 1 # Mark as having methods (inherited virtual methods).
if has_method == 0: return 0
# 语义规则(OOP):类体内没有任何函数定义(无 def)且**不带 virtual 标记**的 class 一律视为
# struct,不做 OOP 合成:
# - 不标记 IsOOP(构造点据此走「分配 + 零/默认值」路径,不再引用 __init__/__before_init__);
# - 不合成 __before_init__;不合成默认 __init__。
# has_method==0 恰好等价于「类体里没有 FunctionDef」(Pass 1 见到任一个 def 就置 1)。
#
# 「带 virtual 标记」在本工程里没有独立的「虚类」位,统一走 entry.HasVTable==1:
# - 该标记由 ClassGenDef 依据 ClassGenBase._detect_vtable_status 置位(ClassGenDef.vp:487);
# - _detect_vtable_status 覆盖:类级 @t.CVTable(`virtual class` 的语法糖,parser.vp:556-621 合成)、
# 方法级 @t.CVTable(`virtual def` 同款合成)、以及继承链上父类 HasVTable==1 的推导
# (ClassGenBase.vp:883-916)。
# 即「显式 virtual 标记 或 由虚父类推导出的虚表」这一口径,且不含纯数据类。
#
# 带 virtual 标记的类不计入上面的「无 def 按 struct」规则:无论类体有无 def,都必须照常构造
# __before_init__ 与 __init__,并仍标为 OOP。理由(为什么 virtual 类必须保留默认构造):
# 1) 语义上它就是多态类,构造时必须初始化 vptr(否则虚调用 / isinstance 会在运行期错判或解引用空指针);
# 2) 该类的 __before_init__ 承载「写 vtable 指针 + 字段零填 + 默认值」,与是否有自有方法无关。
# 故这里只对「既无 def 又无 virtual 标记」的纯数据类提前 return;带标记者继续走下方常规
# OOP 合成(标 IsOOP、发 vtable、发 __before_init__、发默认 __init__)。vtable 全局量的发射
# 由 _generate_vtable 负责(它即便方法数为 0 也需发射,见该函数注释)。
pd_has_vt: int = 0
if oop_entry is not None: pd_has_vt = oop_entry.HasVTable
if pd_has_vt != 1:
return 0
# Mark the struct as OOP.
if oop_entry is not None:
@@ -422,6 +482,33 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
# Emit __before_init__ (zero-fill + defaults + vtable pointer).
self._generate_before_init(trans, struct_ty, class_name)
# Emit a default (empty) __init__ when neither this class nor any MRO ancestor defines one.
# Why: an explicit `obj.__init__()` in source lowers to a direct call @SHA1.Class.__init__
# (the call site is never narrowed to a no-op), so the symbol must exist or ld.lld fails
# with "undefined symbol: ...__init__". The body is intentionally empty -- __before_init__
# owns zero-fill / defaults / vtable setup, and a class without a user __init__ has no init
# logic to run (matches the documented placement-new idiom `obj.__before_init__()` then
# `obj.__init__(args)`).
# Skipped when an ancestor has a user __init__: emitting a child default would shadow it and
# silently drop the inherited initialization for explicit `child.__init__()` calls.
di_needed: int = 0
if has_init == 0:
di_needed = 1
di_pool: memhub.MemBuddy* = trans.Pool
if di_pool is not None:
di_mro: sizet* = self.Gen.StructGen.get_mro_chain(di_pool, class_name)
di_mi: int = 1
while di_mro is not None and di_mro[di_mi] != 0:
di_anc: str = rawptr(di_mro[di_mi])
if di_anc is not None and string.strcmp(di_anc, class_name) != 0:
di_anc_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(di_anc)
if di_anc_entry is not None and di_anc_entry.HasInit != 0:
di_needed = 0
break
di_mi += 1
if di_needed != 0:
self._generate_default_init(trans, struct_ty, class_name)
return 0
# _translate_method - emit define <ret_ty> @SHA1.ClassName.method_name(Ptr(StructTy) %self, ...).
# self is registered as an SSA Ptr(struct_ty) value (no alloca) so self.field can GEP directly.
@@ -528,7 +615,7 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
if decl_reuse == 0:
# Default-parameter info (args[0] is self for normal methods; @staticmethod has no self).
# Assign the attribute to a typed list[...]* local before __len__() or GEP base becomes i32 0.
md_defaults: t.CPtr = None
md_defaults: rawptr = None
md_default_count: int = 0
md_param_count: int = 0
md_args_node: ast.Arguments* = fd.args
@@ -646,18 +733,22 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
# @t.TLS one-time init guard (same implementation as FuncGenMixin.translate_function_def, also for methods).
if is_tls_m != 0:
tls_m_flag_name: str = pool.alloc(128)
# Exact capacities: the reference used to be 64 while the definition was 128, so a long
# method name produced a reference to a different (nonexistent) global.
tls_m_flag_cap: sizet = string.strlen(method_name) + 24
tls_m_flag_name: str = pool.alloc(tls_m_flag_cap)
if tls_m_flag_name is not None:
viperlib.snprintf(tls_m_flag_name, 128, "__tls_init_done_%s", method_name)
viperlib.snprintf(tls_m_flag_name, tls_m_flag_cap, "__tls_init_done_%s", method_name)
tls_m_gv: mlirlite.GlobalVariable* = mlirlite.create_global_int(pool, mod, tls_m_flag_name, 1, 0)
if tls_m_gv is not None:
i1_ty_m: mlirlite.LLVMType* = mlirlite.Int1(pool)
if i1_ty_m is not None:
# GlobalVariable is not a Value subclass: build a Value view before load/store.
tls_m_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i1_ty_m)
tls_m_rn: str = pool.alloc(64)
tls_m_rn_cap: sizet = string.strlen(tls_m_flag_name) + 2
tls_m_rn: str = pool.alloc(tls_m_rn_cap)
if tls_m_rn is not None:
viperlib.snprintf(tls_m_rn, 64, "@%s", tls_m_flag_name)
viperlib.snprintf(tls_m_rn, tls_m_rn_cap, "@%s", tls_m_flag_name)
tls_m_ref: mlirlite.Value* = mlirlite.SSAValue(pool, tls_m_ptr_ty, tls_m_rn)
if tls_m_ref is not None:
tls_m_ld: mlirlite.Value* = mlirlite.build_load(func_builder, i1_ty_m, tls_m_ref)
@@ -1046,4 +1137,65 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
# 3. ret void
mlirlite.build_ret_void(func_builder)
return 0
# _generate_default_init - emit define void @SHA1.ClassName.__init__(Ptr(StructTy) %self) { ret void }.
# Companion to _generate_before_init: exists purely so that explicit `obj.__init__()` source calls
# (which lower to a direct @SHA1.Class.__init__ call) link even when the class defines no __init__.
# The body must stay empty: __before_init__ already owns zero-fill / defaults / vtable setup.
def _generate_default_init(self, trans: HT.Translator*, struct_ty: mlirlite.LLVMType*, class_name: str) -> int:
"""Emit an empty default __init__ so explicit obj.__init__() call sites link."""
if trans is None or struct_ty is None or class_name is None: return 0
pool: memhub.MemBuddy* = trans.Pool
mod: mlirlite.LLVMModule* = trans.Module
funcs_ptr: GenTypes.FuncEntry* = trans._funcs
func_count: int = trans._func_count
self_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, struct_ty)
void_ty: mlirlite.LLVMType* = mlirlite.Void(pool)
# Build the function name: ClassName.__init__(SHA1 namespace applied by _mangle_name).
full_name_buf: str = pool.alloc(128)
if full_name_buf is None: return 0
viperlib.snprintf(full_name_buf, 128, "%s.__init__", class_name)
full_name: str = full_name_buf
mangled_name: str = self.Gen.FuncGenBase._mangle_name(trans, full_name)
# A call site may have forward-declared __init__ first; upgrade the declaration to a
# definition (clear IsDeclared). If a body already exists, bail to avoid a duplicate symbol.
di_func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name)
di_decl_reuse: int = 0
if di_func is not None:
if di_func.IsDeclared == 0: return 0
di_decl_reuse = 1
else:
di_func = mlirlite.create_function(pool, mod, mangled_name, void_ty)
if di_func is None: return 0
func: mlirlite.Function* = di_func
max_funcs: int = 1024
rc_add: int = self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, func_count, full_name, func, max_funcs, None, 0, 0, pool)
if rc_add == 0: trans._func_count = func_count + 1
if di_decl_reuse != 0:
# Reusing the declaration: name/type its first param as %self (printer only emits named params).
di_first_param: mlirlite.Param* = mlirlite.function_get_param_head(func)
if di_first_param is not None:
di_first_param.Ty = self_ptr_ty
di_first_param.Name = "%self"
func.IsDeclared = 0
else:
mlirlite.add_param(pool, func, self_ptr_ty, "%self")
self.Gen.CallGenLeafKit.update_global_func_spec(full_name, func)
entry_blk: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "entry")
if entry_blk is None: return 0
func_builder: mlirlite.IRBuilder* = mlirlite.new_builder(pool, func)
if func_builder is None: return 0
mlirlite.position_at_end(func_builder, entry_blk)
mlirlite.build_ret_void(func_builder)
return 0
+4 -5
View File
@@ -1,11 +1,10 @@
import t, c
from stdint import *
import mlirlite
import memhub
import string
import stdlib
import viperlib
import w32.win32file as w32file
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.
@@ -137,11 +136,11 @@ def _EnsureModuleNodes(pool: memhub.MemBuddy*) -> int:
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 w32file low-level call (as in Repl.vp), not os._mbuddy which the compiler process may not have initialized.
# 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 = w32file.GetCurrentDirectoryA(ULONG(4111), CwdBuf)
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'
@@ -149,7 +148,7 @@ def _EnsureModuleNodes(pool: memhub.MemBuddy*) -> int:
# Normalize all backslashes (the CWD segment may contain '\' too).
flen: sizet = string.strlen(nbuf)
ni: sizet = 0
for ni in range(flen):
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
+40 -9
View File
@@ -1,11 +1,9 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import stdlib
import viperlib
import lib.core.Gen.Translator as HT
@@ -106,6 +104,10 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
if string.strcmp(name, "CRef") == 0: return 1
if string.strcmp(name, "CTempRef") == 0: return 1
if string.strcmp(name, "CAutoPtr") == 0: return 1
# Byte-order markers (t.BigEndian / t.LittleEndian): decorator markers used as
# `X | t.BigEndian`; recognized here so they are not taken for an unregistered class.
if string.strcmp(name, "BigEndian") == 0: return 1
if string.strcmp(name, "LittleEndian") == 0: return 1
return 0
def _attr_module_path_str(self, pool: memhub.MemBuddy*, node: ast.AST*) -> str:
"""Convert a module-reference AST into a dotted module path; None for non-module forms."""
@@ -441,7 +443,10 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
if cdef_str_v is not None: return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, cdef_str_v, trans)
# cross-module float CDefine (e.g. vipermath.U_M_PI) → float constant
dchk_ma: double = self.Gen.TypeGenCDef.lookup_cdefine_double(at.attr)
if self.Gen.TypeGenCDef.is_cdefine_double() != 0: return mlirlite.ConstFloat(pool, mlirlite.Double(pool), dchk_ma)
# f32 声明的常量直接出 f32 常量(否则 f32 字面量会被提升成 double)
if self.Gen.TypeGenCDef.is_cdefine_double() != 0:
if self.Gen.TypeGenCDef.is_cdefine_float32() != 0: return mlirlite.ConstFloat(pool, mlirlite.Float(pool), dchk_ma)
return mlirlite.ConstFloat(pool, mlirlite.Double(pool), dchk_ma)
return self.Gen.ExprGenCdef._const_int_for_cdefine(pool, at.attr, cdef_ma_val)
# check whether nm_ma.id.at.attr is an imported submodule (e.g. win32.win32file);
# for a submodule do not create a global, let the nested attribute path handle it
@@ -557,9 +562,11 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
viperlib.snprintf(tmp_fi, tmp_fi_len, "%s:%s", at.attr, qual_buf)
cdef_val_nested = self.Gen.ExprGenCdef._lookup_cross_module_cdefine(pool, at.attr, tmp_fi)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
# cross-module float CDefine → float constant
# cross-module float CDefine → float constant (f32 声明直接出 f32)
dchk_nst: double = self.Gen.TypeGenCDef.lookup_cdefine_double(at.attr)
if self.Gen.TypeGenCDef.is_cdefine_double() != 0: return mlirlite.ConstFloat(pool, mlirlite.Double(pool), dchk_nst)
if self.Gen.TypeGenCDef.is_cdefine_double() != 0:
if self.Gen.TypeGenCDef.is_cdefine_float32() != 0: return mlirlite.ConstFloat(pool, mlirlite.Float(pool), dchk_nst)
return mlirlite.ConstFloat(pool, mlirlite.Double(pool), dchk_nst)
return self.Gen.ExprGenCdef._const_int_for_cdefine(pool, at.attr, cdef_val_nested)
# CDefine not found: fail-fast and stop compiling (no more undefined global
# references, which would make llc report "use of undefined value").
@@ -644,8 +651,13 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
decl_ptr_rn: mlirlite.LLVMType* = mlirlite.Ptr(pool, decl_ty_rn)
if decl_ptr_rn is not None:
cast_ptr_rn: mlirlite.Value* = mlirlite.build_bitcast(builder, alias_ptr, decl_ptr_rn)
if cast_ptr_rn is not None: return mlirlite.build_load(builder, decl_ty_rn, cast_ptr_rn)
return mlirlite.build_load(builder, alias_fe.Ty, alias_ptr, vol_ba_rd)
if cast_ptr_rn is not None:
rn_alias_val: mlirlite.Value* = mlirlite.build_load(builder, decl_ty_rn, cast_ptr_rn)
# BigEndian REnum payload alias: un-swap the load (pairing the write side).
return self.apply_byte_order_bswap(pool, builder, rn_alias_val, alias_fe.ByteOrder)
rn_slot_val: mlirlite.Value* = mlirlite.build_load(builder, alias_fe.Ty, alias_ptr, vol_ba_rd)
# BigEndian REnum slot: un-swap the load (pairing the write side).
return self.apply_byte_order_bswap(pool, builder, rn_slot_val, alias_fe.ByteOrder)
# property getter fallback: when obj.attr is a @property/@x.getter method, emit a call
# (try before reporting a field-not-found error, to avoid a misleading message)
if field_info is None and at.value is not None and trans is not None:
@@ -705,7 +717,10 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
if self.Gen.StructGen.is_union_by_type(struct_ty) == 1:
field_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, field_ty)
casted: mlirlite.Value* = mlirlite.build_bitcast(builder, obj_ptr, field_ptr_ty)
if casted is not None: return mlirlite.build_load(builder, field_ty, casted, field_vol_rd)
if casted is not None:
union_val_rd: mlirlite.Value* = mlirlite.build_load(builder, field_ty, casted, field_vol_rd)
# BigEndian field: the slot holds the byte-swapped pattern, so un-swap the load.
return self.apply_byte_order_bswap(pool, builder, union_val_rd, field_info.ByteOrder)
return None
# normal struct: GEP + load
field_idx: int = field_info.Index
@@ -720,11 +735,17 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
decl_ptr_rd: mlirlite.LLVMType* = mlirlite.Ptr(pool, decl_ty_rd)
if decl_ptr_rd is not None:
cast_ptr_rd: mlirlite.Value* = mlirlite.build_bitcast(builder, field_ptr, decl_ptr_rd)
if cast_ptr_rd is not None: return mlirlite.build_load(builder, decl_ty_rd, cast_ptr_rd, field_vol_rd)
if cast_ptr_rd is not None:
rn_val_rd: mlirlite.Value* = mlirlite.build_load(builder, decl_ty_rd, cast_ptr_rd, field_vol_rd)
# BigEndian REnum payload slot: un-swap the load, pairing with the write side.
return self.apply_byte_order_bswap(pool, builder, rn_val_rd, field_info.ByteOrder)
# an array-typed field cannot be loaded as an SSA value; return the field pointer for
# later indexing: self.state[0] → GEP the state field → GEP the array element
if self.is_array_type(field_ty) != 0: return field_ptr
loaded_val: mlirlite.Value* = mlirlite.build_load(builder, field_ty, field_ptr, field_vol_rd)
# BigEndian field: the memory slot holds the byte-swapped pattern, so un-swap the
# loaded value; a no-op for non-16/32/64-bit ints (pointers/aggregates unaffected).
loaded_val = self.apply_byte_order_bswap(pool, builder, loaded_val, field_info.ByteOrder)
# a union-typed field (e.g. Token*) is compiled as i8*; when AnnotClassName points to a
# concrete struct type, bitcast the loaded pointer (value→pointer bitcast would be invalid).
fc_annot: str = self.Gen.StructGen.get_field_annot_class_name(field_info)
@@ -930,6 +951,16 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
fe: GenTypes.FieldEntry* = self._resolve_attr_field_entry(pool, trans, attr_node)
return self.Gen.StructGen.field_is_const(fe)
# attr_field_byte_order - the field's byte order (0=native / 1=BigEndian / 2=LittleEndian) for the
# write path, where get_attribute_ptr resolves the pointer but not the FieldEntry. Mirrors
# attr_field_is_volatile/attr_field_is_const (same _resolve_attr_field_entry resolution: only a
# Name base resolves; a nested/global receiver yields 0 -> no swap, never a wrong swap).
def attr_field_byte_order(self, pool: memhub.MemBuddy*, trans: HT.Translator*, attr_node: ast.AST*) -> int:
"""Return the obj.field field's byte order (0 = native/unknown)."""
fe: GenTypes.FieldEntry* = self._resolve_attr_field_entry(pool, trans, attr_node)
if fe is None: return 0
return fe.ByteOrder
def attr_field_decl_src(self, pool: memhub.MemBuddy*, trans: HT.Translator*, attr_node: ast.AST*) -> str:
"""Return the field annotation source name for const diagnostics, or None."""
fe: GenTypes.FieldEntry* = self._resolve_attr_field_entry(pool, trans, attr_node)
+136 -29
View File
@@ -1,11 +1,9 @@
import lib.core.Gen.Generator as Generator
import t, c
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdlib
import viperlib
import lib.core.Gen.Translator as HT
import lib.Projectrans.Utils as Utils
@@ -67,7 +65,7 @@ novirtual class ExprGenBase:
pos += 1
buf[pos] = '"'
pos += 1
for i in range(slen):
for i in range(int(slen)):
ch: char = src[i]
if ch == '\0': break
if ch == '\"' or ch == '\\' or ch < 0x20:
@@ -121,6 +119,61 @@ novirtual class ExprGenBase:
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:
@@ -428,15 +481,23 @@ novirtual class ExprGenBase:
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"
if code == PC_DEPTH: why = f"pointer level differs (source level {self.get_ptr_level(src_ty)} -> target level {self.get_ptr_level(dst_ty)})"
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)
return GenApi.escape_fstring_copy(f"implicit pointer-to-pointer conversion is forbidden: {why}; source type {s_txt} -> target type {d_txt}")
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*:
@@ -455,7 +516,7 @@ novirtual class ExprGenBase:
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
# 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
@@ -474,6 +535,23 @@ novirtual class ExprGenBase:
"""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."""
@@ -490,7 +568,7 @@ novirtual class ExprGenBase:
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
# 聚合目标(结构体/数组)不接受标量(整数/浮点)初始化或赋值:标量→聚合在
# TPV 无转换语义;旧行为把标量原样交给 build_store,而 build_store 不做类型
# VPC 无转换语义;旧行为把标量原样交给 build_store,而 build_store 不做类型
# 校验,落成 `store i32 0, ptr %x`——只写聚合首元素字节并抬高对齐,其余部分
# 保持未初始化(历史现场:`l: [10]char = 0` → 打印 "Hello"+乱码)。
# 聚合零初始化必须写 `= None`(上方 IsNoneValue → _NoneValueAsTarget 分支)。
@@ -526,20 +604,16 @@ novirtual class ExprGenBase:
return val
if val_bits != 0 and target_bits != 0:
if val_bits == target_bits: return val
# Target i1 (bool / t.CBool): truth normalization `x != 0 -> 1/0`
# (C 6.3.1.2), not trunc; keeps parity with `bool(x)` and condition paths.
if target_bits == 1:
zero_i1: mlirlite.Value* = mlirlite.const_int(builder.Pool, val_bits, 0)
return mlirlite.build_icmp(builder, mlirlite.ICMP_NE, val, zero_i1)
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 或加宽目标类型。仅两类豁免:
# ① 目标 i1:真值归一化(x != 0),语义明确,不是位截断(已在上方返回);
# ② 源是**编译期常量且值能落进目标位宽**(如字面量 5 → i32),无信息损失。
# 位宽高→低会丢高位,必须显式 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))
@@ -547,6 +621,15 @@ novirtual class ExprGenBase:
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.
@@ -564,19 +647,34 @@ novirtual class ExprGenBase:
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 (TPV never passes aggregates by value).
# 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 "construction materialization" (see _ctor_call_agg_ty).
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
# 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)
@@ -588,14 +686,23 @@ novirtual class ExprGenBase:
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: aggregate targets keep the implicit load only
# for construction materialization; real Foo* values must be dereferenced.
# 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:
# Rule 3: implicit ptr -> value for non-basic types (Foo* -> Foo) is
# forbidden, except "construction materialization" (see _ctor_call_agg_ty).
# ① 同型指针 → 值拷贝(覆盖裸类形参的 `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)}")
@@ -620,7 +727,7 @@ novirtual class ExprGenBase:
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
# 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."""
+51 -13
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import mlirlite
@@ -95,6 +94,24 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase):
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:
@@ -147,7 +164,9 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase):
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)
self.Gen.TypeGenCDef.register_cdefine_double(pool, name, xd_v)
# 缓存里带宽度种类(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)
@@ -286,6 +305,9 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase):
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
@@ -321,6 +343,9 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase):
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
@@ -578,7 +603,9 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase):
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).
self.Gen.TypeGenCDef.register_cdefine_double(pool, name, ldbl)
# 注解声明为 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
@@ -800,7 +827,8 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase):
cbuf_d: str = pool.alloc(64)
if cbuf_d is not None:
stdio.snprintf(cbuf_d, 64, "%.17g", dval_cc)
self.Gen.TypeGenCDef.register_xmod_cdefine(ckey2, cbuf_d, 1, 0)
# 浮点种类随缓存一起带上(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)
@@ -842,17 +870,27 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase):
# ============================================================
# cdefine_use_width_value - materialize CDefine constants at the use-site width (TPC-aligned)
def _cdefine_bits_value(self, value: int64, bits: int) -> int64:
"""把值按位宽**截断**后再重解释为该位宽的有符号表示(与 AssignGenMixin._normalize_cdefine_bits 同口径)。
"""把值按位宽处理:**只有在当前位宽的两种解释下都装不下时才截断**,否则原样保留。
「左侧只会看到截断了的数据」:超宽的值先按 bits 取低位(WORD(0x80000000) → 0),
而不是只对"刚好越界一点"的值做重解释。
实现用左移后算术右移,而不是 `value & 0xFFFFFFFF` 这类大掩码字面量:
掩码字面量(如 4294967295)超出 int32 范围,其加宽方向在 TPC/VPC 上可能判成 sext,
会得到 0xFFFFFFFFFFFFFFFF 而不是 0xFFFFFFFF,导致截断失效。
「左侧只会看到截断了的数据」:超宽的值先按 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: return (value << 56) >> 56
if bits == 16: return (value << 48) >> 48
if bits == 32: return (value << 32) >> 32
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*:
+9 -10
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import mlirlite
@@ -54,7 +53,7 @@ novirtual class ExprGenCls(ExprGenBase.ExprGenBase):
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(self.DocCount):
for i in range(int(self.DocCount)):
if string.strcmp(self.DocKeys + i * DOC_KEY_SZ, key_buf) == 0:
stdlib.free(key_buf)
return
@@ -70,7 +69,7 @@ novirtual class ExprGenCls(ExprGenBase.ExprGenBase):
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(self.DocCount):
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
@@ -82,7 +81,7 @@ novirtual class ExprGenCls(ExprGenBase.ExprGenBase):
ch: list[ast.AST*]* = tree.children
if ch is None: return
cn: sizet = len(ch)
for ci in range(cn):
for ci in range(int(cn)):
child: ast.AST* = ch.get(ci)
if child is None: continue
kd: int = child.kind()
@@ -102,7 +101,7 @@ novirtual class ExprGenCls(ExprGenBase.ExprGenBase):
cd_body: list[ast.AST*]* = cd.children
if cd_body is not None:
cbn: sizet = len(cd_body)
for mi in range(cbn):
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)
@@ -183,13 +182,13 @@ novirtual class ExprGenCls(ExprGenBase.ExprGenBase):
stdlib.free(items)
return 1
total: sizet = 0
for j in range(n_items): total += string.strlen(items + j * 128) + 1
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(n_items):
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)
@@ -199,14 +198,14 @@ novirtual class ExprGenCls(ExprGenBase.ExprGenBase):
return 1
# variable/class: symbol = head_id + '.' + items reversed
total2: sizet = string.strlen(head_id)
for j in range(n_items): total2 += string.strlen(items + j * 128) + 1
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(n_items):
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)
@@ -333,7 +332,7 @@ novirtual class ExprGenCls(ExprGenBase.ExprGenBase):
if seg is None: return None
start: sizet = 0
i: sizet
for i in range(alen):
for i in range(int(alen)):
if annot[i] == '|':
s: sizet = start
e: sizet = i
+39 -4
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import mlirlite
@@ -24,7 +23,7 @@ novirtual class ExprGenConst(ExprGenBase.ExprGenBase):
if escaped is None: return None
slen: sizet = string.strlen(str_val)
count: int = slen + 1
count: int = int(slen + 1)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
arr_ty: mlirlite.LLVMType* = mlirlite.Array(pool, i8_ty, count)
@@ -60,6 +59,25 @@ novirtual class ExprGenConst(ExprGenBase.ExprGenBase):
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)."""
@@ -70,11 +88,28 @@ novirtual class ExprGenConst(ExprGenBase.ExprGenBase):
# 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:
# Float constants default to double (64-bit); coerce_to_type fptruncs on assignment
# 目标类型是 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:
@@ -267,7 +302,7 @@ novirtual class ExprGenConst(ExprGenBase.ExprGenBase):
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, elts_count):
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:
+49 -22
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import mlirlite
@@ -26,8 +25,25 @@ novirtual class ExprGenFstr(ExprGenBase.ExprGenBase):
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(FSTR_BUF_SIZE)
fmt_buf: str = pool.alloc(fmt_cap)
if fmt_buf is None: return None
fmt_buf[0] = '\0'
@@ -36,15 +52,13 @@ novirtual class ExprGenFstr(ExprGenBase.ExprGenBase):
arg_tail: mlirlite.Value* = None
arg_count: int = 0
parts: list[ast.AST*]* = js.values
pn: sizet = len(parts)
for pi in range(pn):
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, pcn.str_val)
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)
@@ -66,7 +80,7 @@ novirtual class ExprGenFstr(ExprGenBase.ExprGenBase):
# 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
if string.strlen(fmt_buf) + string.strlen(c_fmt) < FSTR_BUF_SIZE - 1: string.strcat(fmt_buf, c_fmt)
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
@@ -82,7 +96,10 @@ novirtual class ExprGenFstr(ExprGenBase.ExprGenBase):
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
fmt_buf: str = pool.alloc(FSTR_BUF_SIZE)
# 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
@@ -103,7 +120,7 @@ novirtual class ExprGenFstr(ExprGenBase.ExprGenBase):
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")
if string.strlen(fmt_buf) + string.strlen(c_fmt) < FSTR_BUF_SIZE - 1: string.strcat(fmt_buf, c_fmt)
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
@@ -149,34 +166,38 @@ novirtual class ExprGenFstr(ExprGenBase.ExprGenBase):
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, lit: str) -> None:
"""Append a literal to the format string, escaping percent signs."""
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)
rem: sizet = FSTR_BUF_SIZE - 1 - cur_len
if rem == 0: return
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 and rem > 0:
while si < src_len:
ch: char = 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:
"""Append a raw string without percent escaping."""
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 < FSTR_BUF_SIZE: string.strcpy(fmt_buf + cur_len, 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
@@ -189,16 +210,22 @@ novirtual class ExprGenFstr(ExprGenBase.ExprGenBase):
jss: ast.JoinedStr* = ast.JoinedStr*(spec_node)
if jss.values is None: return None
vn: sizet = len(jss.values)
for vi in range(vn):
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
if string.strlen(spec) + string.strlen(vcn.str_val) < FSTR_SPEC_MAX - 1: string.strcat(spec, vcn.str_val)
# 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)
+4 -5
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import memhub
@@ -28,7 +27,7 @@ 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: t.CPtr = None, func_count: int = 0, trans: HT.Translator* = None, target_ty: mlirlite.LLVMType* = None, use_ctx: str = None) -> mlirlite.Value*:
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:
@@ -38,7 +37,7 @@ novirtual class ExprGenMixin(ExprGenBase.ExprGenBase):
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: t.CPtr = None, func_count: int = 0, trans: HT.Translator* = None, target_ty: mlirlite.LLVMType* = None, use_ctx: str = None) -> mlirlite.Value*:
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()
@@ -46,12 +45,12 @@ novirtual class ExprGenMixin(ExprGenBase.ExprGenBase):
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)
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)
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)
+54 -7
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
@@ -44,7 +43,7 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
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).
# 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:
@@ -55,7 +54,11 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
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")
if self.Gen.TypeGenCDef.is_cdefine_double() != 0: return mlirlite.ConstFloat(pool, mlirlite.Double(pool), cdef_val_d)
# 浮点常量:声明为 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)
@@ -81,7 +84,10 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
# 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)
if self.Gen.TypeGenCDef.is_cdefine_double() != 0: return mlirlite.ConstFloat(pool, mlirlite.Double(pool), dchk_nm)
# 同本模块路径: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
@@ -173,10 +179,14 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
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)
return mlirlite.build_load(builder, load_ty, alloca, vol_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.
@@ -463,7 +473,7 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
# 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
# _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)."""
@@ -561,7 +571,13 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
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: return mlirlite.build_load(builder, elem_ty, elem_ptr)
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
@@ -575,6 +591,24 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
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
@@ -859,6 +893,19 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
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.
+332 -74
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
@@ -84,13 +83,22 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
mlirlite.position_at_end(builder, else_blk_for_phi)
orelse_val = self.coerce_to_type(builder, orelse_val, body_val.Ty, node)
elif body_bits > orelse_bits and orelse_bits > 0:
# Coerce orelse_val in the else block
mlirlite.position_at_end(builder, else_blk_for_phi)
orelse_val = self.coerce_to_type(builder, orelse_val, body_val.Ty, node)
# 宽侧是「能无损落进窄侧」的编译期常量时,把**常量**降到窄侧(与二元运算同一口径),
# 避免 `tab[..] if c else '='`(i8 元素 vs i32 字符字面量)被抬成 i32、再赋回 i8 时误报窄化。
if self._const_fits_int_type(body_val, orelse_val.Ty) != 0:
mlirlite.position_at_end(builder, then_blk_for_phi)
body_val = self.coerce_to_type(builder, body_val, orelse_val.Ty, node)
else:
mlirlite.position_at_end(builder, else_blk_for_phi)
orelse_val = self.coerce_to_type(builder, orelse_val, body_val.Ty, node)
elif orelse_bits > body_bits and body_bits > 0:
# Back in the then block, coerce body_val
mlirlite.position_at_end(builder, then_blk_for_phi)
body_val = self.coerce_to_type(builder, body_val, orelse_val.Ty, node)
# 同口径:宽侧(orelse)是能落进窄侧的常量时,把它降下来(在 else 块内完成强转)
if self._const_fits_int_type(orelse_val, body_val.Ty) != 0:
mlirlite.position_at_end(builder, else_blk_for_phi)
orelse_val = self.coerce_to_type(builder, orelse_val, body_val.Ty, node)
else:
mlirlite.position_at_end(builder, then_blk_for_phi)
body_val = self.coerce_to_type(builder, body_val, orelse_val.Ty, node)
elif body_bits == orelse_bits and body_bits > 0:
# Same bits but different types (e.g. i64 vs Ptr): null cannot be used in an i64 PHI.
# This branch should not trigger (case 0 covers it); kept as a defensive guard.
@@ -126,6 +134,15 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
if narrow_val is None or narrow_val.Ty is None: return 0
narrow_bits: int = self.get_llvm_type_bits(narrow_val.Ty)
if narrow_bits == 1: return 1
# 编译期 CDefine 常量(裸名 `N` 或模块属性 `mod.N`)单独判定,必须**早于**下方「i8 默认 zext」:
# · 负值 → sext(有符号语义,如 `define Z_DEFAULT_COMPRESSION = (-1)` 必须变 i32 -1,而非 255);
# · 非负 → zext(无符号位模式,如 `define WINDOW_SIZE = (1 << 15)` 必须变 32768,而非 -32768)。
# 否则这两类常量拓宽时必有一类被弄错(i8 位型走「默认 zext」;i16 位型若判不出无符号则走 sext)。
if narrow_node is not None:
cd_v_zw: int64 = self._cdefine_int_of(narrow_node)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
if cd_v_zw < 0: return 0
return 1
if narrow_bits == 8:
# i8 defaults to unsigned-byte handling (systems convention), but an explicitly signed source annotation
# must sext to preserve sign: `int32(v)` with v: int8 = -50 must stay -50, not become 206.
@@ -141,14 +158,42 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
# Name/Subscript, unsignedness comes from trans.SymTab; without trans it returns 0 and sext is wrongly used.
if narrow_node is not None:
if self._node_type_unsigned(pool, narrow_node, trans) != 0: return 1
# A CDefine constant with the high bit set (0x80000000..0xFFFFFFFF) -> zext. CDefine is a compile-time value,
# so _node_type_unsigned finds no annotation; the negative-signed range means an unsigned sentinel (e.g. 0xFFFFFFFF).
if narrow_node is not None and narrow_node.kind() == ast.ASTKind.Name:
cdef_nm_zw: ast.Name* = ast.Name*(narrow_node)
if cdef_nm_zw.id is not None:
cdv_zw: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(cdef_nm_zw.id)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
if cdv_zw > 0x7FFFFFFF: return 1
# A non-negative CDefine whose minimal width's high bit is set (0x80000000 in 32 bits, or
# 50000 in 16 bits) is materialized as an unsigned bit pattern -> zext. Covered by
# _node_type_unsigned below (which delegates to _cdefine_unsigned_bitpattern); keep return 0.
return 0
# _cdefine_int_of - 取节点引用的 CDefine 常量值。支持两种引用形态:
# · 裸名 `N`
# · 模块属性 `mod.N`(跨模块经 .vpi 注册;常量名就是 at.attr)
# 未命中时 `Gen.TypeGenCDef.is_cdefine_found()` 为 0 —— 本函数每次都刷新该标志(空名也走一次 lookup)。
def _cdefine_int_of(self, node: ast.AST*) -> int64:
"""CDefine integer value referenced by node (bare name or `mod.N`); check is_cdefine_found()."""
nm_cd: str = ""
if node is not None:
if node.kind() == ast.ASTKind.Name:
n_cd: ast.Name* = ast.Name*(node)
if n_cd.id is not None: nm_cd = n_cd.id
elif node.kind() == ast.ASTKind.Attribute:
a_cd: ast.Attribute* = ast.Attribute*(node)
if a_cd.attr is not None: nm_cd = a_cd.attr
return self.Gen.TypeGenCDef.lookup_cdefine_constant(nm_cd)
# _cdefine_unsigned_bitpattern - 1 if node 是**非负 CDefine 常量**,且按它的最小可容纳位宽 W_min
# 取无符号位模式时 high bit 已置位(如 50000 → W_min=16 → 0xC350)。规范:非负数按无符号位模式
# 物化、负值按有符号解释(Debugger/define-constants.md §7.2)。这类常量在**宽化**与**比较**
# (for-range 边界)时必须按无符号处理,否则 32768..65535 的常量会被读成负数(range 变 0 次)。
def _cdefine_unsigned_bitpattern(self, node: ast.AST*) -> int:
"""1 = node is a non-negative CDefine whose minimal width's high bit is set; else 0."""
v_cd: int64 = self._cdefine_int_of(node)
if self.Gen.TypeGenCDef.is_cdefine_found() == 0: return 0
if v_cd < 0: return 0
w_cd: int = self.Gen.AssignGen.define_min_width(v_cd)
# 该位宽的有符号上限 2^(w-1)-1;超过即 high bit 置位(只写安全字面量,避免 1<<63 / 4294967295 之类)
if w_cd == 8:
if v_cd > 127: return 1
elif w_cd == 16:
if v_cd > 32767: return 1
elif w_cd == 32:
if v_cd > 2147483647: return 1
return 0
def _node_type_unsigned(self, pool: memhub.MemBuddy*, node: ast.AST*, trans: HT.Translator* = None) -> int:
"""Return 1 if the AST node resolves to an unsigned integer type, else 0."""
@@ -162,6 +207,17 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
mod_nm: ast.Name* = ast.Name*(at.value)
if mod_nm.id is not None and string.strcmp(mod_nm.id, "t") == 0 and at.attr is not None:
if self._type_name_unsigned(at.attr) != 0: return 1
return 0
# Plain function call: widen by the callee's **declared return type**. Without this,
# `uint64(f())` with `def f() -> uint32` emitted `sext i32 -> i64` (verified in the
# generated IR), so a u32 with bit 31 set grew 0xFFFFFFFF in the high half — a real
# value 0x88691F61 became 0xFFFFFFFF88691F61 (stackmap pointers were mangled this way).
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
func_nm_u: ast.Name* = ast.Name*(cl.func)
if func_nm_u.id is not None:
fe_fn_u: GenTypes.FuncEntry* = self.Gen.CallGenLeafKit.find_func_entry_global(func_nm_u.id)
if fe_fn_u is not None and fe_fn_u.Spec is not None:
if TSpec.SpecIsUnsigned(fe_fn_u.Spec.Ret) != 0: return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
@@ -177,6 +233,9 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
vcn_u: str = self.Gen.VarGen.var_annot_class_name(ve)
if vcn_u is not None:
if self._type_name_unsigned(vcn_u) != 0: return 1
# 3) 非负 CDefine 常量:按最小可容纳位宽取无符号位模式(Debugger/define-constants.md §7.2)。
# 例:define N = 50000 → W_min=16、值在 32768..65535 ⇒ 宽化/比较必须按无符号。
if self._cdefine_unsigned_bitpattern(node) != 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
@@ -202,15 +261,13 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
if self._annot_str_unsigned(scn_u) != 0: return 1
return 0
if k == ast.ASTKind.Attribute:
# Struct field (e.g. viperio.Buf's self.capacity/length: sizet): get the class name from the receiver's AnnotClassName, then look up the field annotation to decide unsignedness. Read-only; returns 0 on failure, so find_struct + manual field walk is used instead of lookup_field_by_class.
# Struct field (e.g. viperio.Buf's self.capacity/length: sizet): decide unsignedness from the
# *field's* annotation, resolved through the receiver's struct class name. The receiver is not
# limited to a plain variable name: it may be a nested field (a.b.f), an index/deref
# (arr[i].f / ptr[i].f) or a module-qualified global (pkg.mod.g[i].f). Read-only; 0 = unresolved.
at_u: ast.Attribute* = ast.Attribute*(node)
if at_u.value is not None and at_u.attr is not None and trans is not None:
cls_nm_u: str = None
if at_u.value.kind() == ast.ASTKind.Name:
vn_u: ast.Name* = ast.Name*(at_u.value)
if vn_u.id is not None:
ve_u: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, vn_u.id)
if ve_u is not None: cls_nm_u = self.Gen.VarGen.var_annot_class_name(ve_u)
cls_nm_u: str = self._attr_receiver_class_name(pool, at_u.value, trans)
if cls_nm_u is not None:
# Take the bare class name (before the dot): look up by bare name only, avoiding module-qualified resolution
bare_u: str = cls_nm_u
@@ -230,6 +287,72 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
break
return 0
return 0
# _attr_receiver_class_name - struct class name of an attribute receiver expression. Resolves by the
# receiver's *type*, not by shape: variable/field annotation class name (Name / nested Attribute), the
# same resolution one array/pointer level down for an index/deref base (arr[i] / ptr[i]), or the global's
# element type for a module-qualified global (Mod.g / pkg.mod.g). None when unresolved.
def _attr_receiver_class_name(self, pool: memhub.MemBuddy*, node: ast.AST*, trans: HT.Translator*) -> str:
"""Struct class name of an attribute receiver (Name / Attribute / Subscript); None if unresolved."""
if node is None or trans is None: return None
k: int = node.kind()
if k == ast.ASTKind.Subscript:
# arr[i].f / ptr[i].f: the element sits one array/pointer level below the base, and the base's
# class-name resolution already denotes that element struct (the levels are transparent here),
# so resolve the subscript base directly.
sub_rc: ast.Subscript* = ast.Subscript*(node)
if sub_rc.value is None: return None
return self._attr_receiver_class_name(pool, sub_rc.value, trans)
if k == ast.ASTKind.Attribute:
# Module-qualified global (Mod.g / pkg.mod.g): the class name comes from the global's element type.
gc_rc: str = self._module_global_element_class(pool, trans, ast.Attribute*(node))
if gc_rc is not None: return gc_rc
# Object field chain (a.b.f): the field's annotation class name (recursive).
return self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, node, 1)
if k == ast.ASTKind.Name:
nm_rc: ast.Name* = ast.Name*(node)
if nm_rc.id is None: return None
ve_rc: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_rc.id)
if ve_rc is None: return None
return self.Gen.VarGen.var_annot_class_name(ve_rc)
return None
# _module_global_element_class - element class name of a module-qualified global `Mod.g`. The module path
# may be multi-component (e.g. `intr.idt.idt`: package intr -> submodule idt -> global idt). Mirrors
# ExprGenAttr._resolve_module_global_class_name but accepts a dotted module path and also unwraps an array
# element, so a module-global array such as `idt: [256]idt_entry` resolves to 'idt_entry'. None otherwise.
def _module_global_element_class(self, pool: memhub.MemBuddy*, trans: HT.Translator*, at: ast.Attribute*) -> str:
"""Element class name of the module-qualified global `at.value.at.attr`; None when not a global."""
if at is None or at.value is None or at.attr is None or trans is None: return None
if trans._imported_modules is None or trans.Module is None: return None
mod_path: str = self.Gen.ExprGenAttr._attr_module_path_str(pool, at.value)
if mod_path is None: return None
# restore import aliases (import X.Y as Z) so the import/SHA1 checks resolve
if trans._from_imports is not None:
orig_mg: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, mod_path, 0)
if orig_mg is not None:
oml_mg: sizet = 0
while orig_mg[oml_mg] != ' ' and orig_mg[oml_mg] != '\0' and orig_mg[oml_mg] != ':': oml_mg += 1
if oml_mg > 0:
om_buf_mg: str = pool.alloc(oml_mg + 1)
if om_buf_mg is not None:
string.strncpy(om_buf_mg, orig_mg, oml_mg)
om_buf_mg[oml_mg] = '\0'
mod_path = om_buf_mg
# package submodules (`intr.idt`) count as known when the package is imported
if self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, mod_path) == 0: return None
gv: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(trans.Module, self.Gen.FuncGenBase._mangle_name_with_sha1(self.Gen.ExprGenAttr._lookup_module_sha1(mod_path), at.attr))
if gv is None: gv = self.Gen.ExprGenAttr.find_global_in_module(trans.Module, at.attr)
if gv is None or gv.Ty is None: return None
# a global is usually `Element*` / `Element**` or an array `[N]Element`: descend to the leaf type
# and take its registered struct class name.
elem_ty: mlirlite.LLVMType* = gv.Ty
lvl_mg: int = 0
while elem_ty is not None and lvl_mg < 4:
match elem_ty:
case mlirlite.LLVMType.Ptr(inner_mg): elem_ty = inner_mg
case mlirlite.LLVMType.Array(elem_mg, _): elem_ty = elem_mg
case _: return self.Gen.StructGen.get_class_name_by_type(pool, elem_ty)
lvl_mg += 1
return None
def _node_type_signed(self, pool: memhub.MemBuddy*, node: ast.AST*, trans: HT.Translator* = None) -> int:
"""Return 1 if the source node is explicitly a signed integer, else 0."""
if node is None: return 0
@@ -541,7 +664,7 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
return mlirlite.build_icmp(builder, predicate, lhs, rhs)
# translate_unaryop - UnaryOp(op, operand)
# ============================================================
def translate_unaryop(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
def translate_unaryop(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 unary operations (-x, +x, not x, ~x)."""
uo: ast.UnaryOp* = ast.UnaryOp*(node)
if uo is None: return None
@@ -554,29 +677,44 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
ovl_u: mlirlite.Value* = self.try_unary_operator_overload(pool, builder, mod, operand, uo.op, trans)
if ovl_u is not None: return ovl_u
# C 语义整数提升(与算术/比较同一口径):一元 +/-/~ 先把窄于 32 位的整数操作数提升到 i32,
# 这样 `0 - x` / `x ^ -1` 的常量与操作数同宽(否则是 i32 常量对 i8/i16 操作数)。
# `not x` 不需要:结果为 i1,操作数宽度不影响语义。
# 一元 +/-/~ 的整数提升按**符号性**分档(见 wiki/Implicit.md):
# · **u 类(无符号)不提升**:保持原位宽(`~uint16` 仍是 uint16、`-uint8` 在 u8 内取反),
# 位图/掩码写法因此能「同宽运算」;
# · **i 类(有符号)提升到 i32**:防负数陷阱(如 int8 -128 取负会溢出,提升后 = 128)。
# 未标注符号时按 i 类处理(保守,守住负数陷阱)。`not x` 不需要:结果为 i1,与位宽无关。
if uo.op == ast.OpKind.USub or uo.op == ast.OpKind.UAdd or uo.op == ast.OpKind.Invert:
u_bits: int = self.get_llvm_type_bits(operand.Ty)
if u_bits != 0 and u_bits < 32:
u_i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
u_probe: mlirlite.Value* = mlirlite.const_int32(pool, 0)
if self.should_zext_widen(pool, operand, u_probe, uo.operand, trans) != 0: operand = mlirlite.build_zext(builder, operand, u_i32_ty)
else: operand = mlirlite.build_sext(builder, operand, u_i32_ty)
u_is_uns: int = self._node_type_unsigned(pool, uo.operand, trans)
if u_is_uns == 0:
u_i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
u_probe: mlirlite.Value* = mlirlite.const_int32(pool, 0)
if self.should_zext_widen(pool, operand, u_probe, uo.operand, trans) != 0: operand = mlirlite.build_zext(builder, operand, u_i32_ty)
else: operand = mlirlite.build_sext(builder, operand, u_i32_ty)
if uo.op == ast.OpKind.USub:
# -x
# Float type uses fneg (emit fneg directly to avoid 0.0 - x type mismatch)
operand_fbits: int = self.get_llvm_float_bits(operand.Ty)
if operand_fbits > 0: return mlirlite.build_fneg(builder, operand)
# Integer: -x = 0 - x, choosing the zero constant by bit width (avoid i32/i64 mismatch)
if operand_fbits > 0:
# 编译期常量折叠:-c 直接产出常量并保持 IsConst
if operand.IsConst == 1:
# 目标类型是 32 位浮点(`float(-3.5)` / `x: float = -3.5`)→ 直接出 f32 常量,
# 不再"f64 常量 + fptrunc"(与 ExprGenConst.translate_constant 的浮点字面量同口径)。
# 位宽安全:ConstFloat 的十六进制位数只由 MlirTypeText(ty) 决定(与 MLIR 渲染器
# 取类型文本同一个函数),故不会再出现"f32 类型配 16 位文本"那类位宽错配。
if target_ty is not None and self.get_llvm_float_bits(target_ty) == 32:
return mlirlite.ConstFloat(pool, target_ty, -operand.FloatVal)
return mlirlite.ConstFloat(pool, operand.Ty, -operand.FloatVal)
return mlirlite.build_fneg(builder, operand)
# 编译期常量折叠:-c 直接产出常量(IsConst=1)。否则 `v: int8 = -50` 会被当成运行时 i32 值,
# 触发“隐式窄化禁止”,尽管其数值完全落在 i8 内(隐式窄化的“编译期常量”豁免失效)。
operand_bits: int = self.get_llvm_type_bits(operand.Ty)
if operand_bits != 0 and operand.IsConst == 1:
return mlirlite.const_int(pool, operand_bits, -operand.IntVal)
# Integer: -x = 0 - x;0 常量必须与**当前**操作数同宽(u 类不提升后可能是 i8/i16/i32/i64)
zero: mlirlite.Value* = mlirlite.const_int32(pool, 0)
if operand_bits == 64: zero = mlirlite.const_int64(pool, 0)
elif operand_bits > 0 and operand_bits != 32:
# Other widths (i8/i16 etc.): use i32 zero; operand will be coerced
zero = mlirlite.const_int32(pool, 0)
if operand_bits != 0 and operand_bits != 32: zero = mlirlite.const_int(pool, operand_bits, 0)
return mlirlite.build_sub(builder, zero, operand)
elif uo.op == ast.OpKind.UAdd:
# +x = x
@@ -590,8 +728,12 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
elif uo.op == ast.OpKind.Invert:
# ~x = x ^ -1
operand_bits_inv: int = self.get_llvm_type_bits(operand.Ty)
# 编译期常量折叠:~c 直接产出常量并保持 IsConst(与 -c 相同的窄化豁免口径)
if operand_bits_inv != 0 and operand.IsConst == 1:
return mlirlite.const_int(pool, operand_bits_inv, operand.IntVal ^ -1)
# -1 掩码必须与**当前**操作数同宽(u 类不提升后可能是 i8/i16/i32/i64)
neg1: mlirlite.Value* = mlirlite.const_int32(pool, -1)
if operand_bits_inv == 64: neg1 = mlirlite.const_int64(pool, -1)
if operand_bits_inv != 0 and operand_bits_inv != 32: neg1 = mlirlite.const_int(pool, operand_bits_inv, -1)
return mlirlite.build_xor(builder, operand, neg1)
elif uo.op == ast.OpKind.Deref:
# *p -> load by the pointee type (aligned with translate_c_deref)
@@ -639,6 +781,13 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
case mlirlite.LLVMType.Struct(_, _, _):
obj_ty_ao: mlirlite.LLVMType* = mlirlite.Ptr(pool, ao2)
return mlirlite.build_load(builder, obj_ty_ao, v_ao)
case mlirlite.LLVMType.Func(_, _, _):
# 函数指针变量(嵌套函数名在符号表里是 Ptr(Func)):变量本身就是
# 那个函数地址,`&f` 应给出它的值 Ptr(Func)(一级),而不是变量
# 地址 Ptr(Ptr(Func))(二级)——否则传给 callback: rawptr 会因
# 级数不符报 CastError(source level 2 -> target level 1)。
fp_ty_ao: mlirlite.LLVMType* = mlirlite.Ptr(pool, ao2)
return mlirlite.build_load(builder, fp_ty_ao, v_ao)
case _:
pass
case _:
@@ -696,7 +845,7 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
return tmp_slot_ao
# translate_boolop - BoolOp(op, values): short-circuit evaluation returning i1.
# and: a false short-circuits to merge (returns a's i1); or: a true short-circuits; the last value is evaluated then branches to merge; merge phis all i1 edges.
def translate_boolop(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, funcs_ptr: t.CPtr = None, func_count: int = 0, trans: HT.Translator* = None) -> mlirlite.Value*:
def translate_boolop(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) -> mlirlite.Value*:
"""Translate boolean and/or with short-circuit evaluation, returning i1."""
bo: ast.BoolOp* = ast.BoolOp*(node)
if bo is None: return None
@@ -1098,18 +1247,51 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
if node is not None: col_s = node.col_offset + 1
self.Gen.TypeGenErr.fatal_error(node, f"[VPC] illegal double-pointer arithmetic type: op = '{op_lbl}'{aug_note}, " f"left operand type = {lty_lbl}, right operand type = {rty_lbl}. " f"in this language str is i8*, with no `+` concatenation semantics (`str + str` and pointer +/- pointer " f"both degrade to raw pointer add/sub => illegal IR: opt reports " f"invalid operand type for instruction). " f"silent degradation is forbidden, compilation aborted. " f"fix: build strings with f-string interpolation, string.strcat, or viperlib.snprintf.", "TypeError", col_s, col_s)
return 1
# binop_needs_int_promotion - whether this op takes C-style integer promotion (to >= 32 bits)
def binop_needs_int_promotion(self, op: int) -> int:
"""C 语义整数提升的适用范围:**算术与移位**为 1(窄于 32 位先提升再算,避免 8/16 位回绕),
**位运算(& | ^)为 0**——按既有约定"两侧同宽就不提升"(WORD | WORD 保持 u16)。"""
if op == ast.OpKind.Add or op == ast.OpKind.Sub or op == ast.OpKind.Mult: return 1
if op == ast.OpKind.Div or op == ast.OpKind.FloorDiv or op == ast.OpKind.Mod: return 1
if op == ast.OpKind.LShift or op == ast.OpKind.RShift: return 1
return 0
# _binop_work_bits - 二元运算的「工作位宽」(**不做 C 式整数提升**)
# 本语言取消了 C 的「小整型先提升到 int(i32)」语义:i8/i16 不再预先抬到 32 位再算。
# · 有上下文目标位宽(赋值/形参/返回目标)→ 以它为基础;
# · 无目标 → 取两侧操作数位宽的较大者,等宽化逻辑随后把**较小**的一侧拓宽到此位宽(结果位宽 = 较大者);
# · 移位 → **计数(rhs)不代表结果位宽**,不参与比较;结果位宽 = 左操作数位宽(无目标时)。
# 任一侧位宽为 0(未知/未定)会天然退化为另一侧(0 不抬高 max);只有连目标与两侧位宽**全都缺失**
# (类型信息完全缺失)时才回退到 32 —— 这只是可编译性兜底,不代表"提升"。
# 注意:本 helper 的 max 不含「常量豁免」——「能无损落进另一侧位宽的常量不抬高工作位宽」这条规则
# 由等宽化处的 _const_operand_demotes 实施(见下方),因为本语言非移位运算的实际结果位宽就是
# 「两侧中较宽的一侧」,等宽化处才是决定点;移位本就不让计数参与,故与此无关。
def _binop_work_bits(self, op: int, tgt_bits: int, lhs_bits: int, rhs_bits: int) -> int:
"""Working bit width for translate_binop (no C-style integer promotion)."""
w: int = tgt_bits
if lhs_bits > w: w = lhs_bits
if op != ast.OpKind.LShift and op != ast.OpKind.RShift:
if rhs_bits > w: w = rhs_bits
if w == 0: w = 32
return w
# _const_operand_demotes - 位宽上更宽的一侧是编译期常量时,它能否**降为另一侧位宽**,
# 从而不抬高运算的工作位宽(常量适配的唯一收口处,位运算与算术共用)。
# 用户规则:常量应隐式适配另一侧位宽——`baseport(u16) + 7` 的 `7` 默认是 i32,但应适配 u16,
# 使整个加法保持在 u16(结果位宽 16),而不是把 u16 变量抬到 i32 后在赋值/传参处报隐式窄化。
# 判定:
# 1) 常量值必须能**无损**落进另一侧位宽(复用 _const_fits_int_type);否则常量照常计入 max
# (如 `u8 变量 + 0x1234`:0x1234 落不进 u8,仍把变量拓宽到 i32,该报窄化就照旧报)。
# 2) 位运算 & | ^:无进位、结果不会超出参与值位宽,常量总能无损降——既有行为,保持不变。
# 3) 算术 + - * / %:另一侧也是常量时**不降**——`(MEMBUDDY_MAX_ORDERS + 1) * 8` 这类窄常量
# 乘积若逐个降宽会在 i8 回绕(老 bug:`i8(32)+1 → 33`、`×8` 溢出成 8,见 includes/memhub.vp),
# 必须保持「不降宽」的既有行为。
# 4) 算术:常量位宽不高于目标位宽时也不降——此时 max(目标位宽, 另一侧位宽) 已不小于常量位宽,
# 常量并没有抬高工作位宽(见 _binop_work_bits),无需也不应改变结果。
def _const_operand_demotes(self, is_bitwise: int, const_val: mlirlite.Value*, const_bits: int, other_val: mlirlite.Value*, other_ty: mlirlite.LLVMType*, tgt_bits: int) -> int:
"""1 = the wider-side constant may be demoted to the other side's type (no work-width raise)."""
if const_val is None or other_val is None: return 0
if const_val.IsConst != 1: return 0
if self._const_fits_int_type(const_val, other_ty) == 0: return 0
if is_bitwise != 0: return 1
if other_val.IsConst == 1: return 0
if const_bits <= tgt_bits: return 0
return 1
# translate_binop - binary operations with automatic type promotion
# ============================================================
def translate_binop(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
def translate_binop(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None, target_ty: mlirlite.LLVMType* = None) -> mlirlite.Value*:
"""Translate a binary operation with automatic type promotion."""
binop: ast.BinOp* = ast.BinOp*(node)
if binop is None: return None
@@ -1118,8 +1300,9 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
rhs_node: ast.AST* = binop.right
op: int = binop.op
# Translate rhs first (only once, to avoid repeating side effects)
rhs: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, rhs_node, None, 0, trans)
# Translate rhs first (only once, to avoid repeating side effects).
# target_ty 下传:内层运算(含移位)据此采用「目标感知工作位宽」。
rhs: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, rhs_node, None, 0, trans, target_ty)
if rhs is None: return None
# === Operator overload path 1: lhs is a Name bound to a struct variable ===
@@ -1139,7 +1322,7 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
if ovl_result is not None: return ovl_result
# Translate lhs normally
lhs: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, lhs_node, None, 0, trans)
lhs: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, lhs_node, None, 0, trans, target_ty)
if lhs is None: return None
# === Operator overload path 2: lhs is Ptr(Struct) (e.g. after loading a Counter* variable) ===
@@ -1166,6 +1349,59 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
lhs_fbits: int = self.get_llvm_float_bits(lhs.Ty)
rhs_fbits: int = self.get_llvm_float_bits(rhs.Ty)
# 纯常量整型算式(两侧都是编译期整型常量):按**精确值**折叠并按值定宽,
# 禁止按操作数的最小宽度回绕(现场:`1024 * 1024 * 4` 按 i16 算成 0 ⇒
# WINBUF_MAX_BYTES=0 ⇒ 窗口缓冲只剩几字节 ⇒ 内核 memcpy 240KB 越界读/三重故障)。
if lhs_bits > 0 and rhs_bits > 0 and lhs.IsConst == 1 and rhs.IsConst == 1:
cf_ok: int = 1
cf_v: int64 = 0
if op == ast.OpKind.Add: cf_v = lhs.IntVal + rhs.IntVal
elif op == ast.OpKind.Sub: cf_v = lhs.IntVal - rhs.IntVal
elif op == ast.OpKind.Mult: cf_v = lhs.IntVal * rhs.IntVal
elif op == ast.OpKind.BitOr: cf_v = lhs.IntVal | rhs.IntVal
elif op == ast.OpKind.BitAnd: cf_v = lhs.IntVal & rhs.IntVal
elif op == ast.OpKind.BitXor: cf_v = lhs.IntVal ^ rhs.IntVal
elif op == ast.OpKind.Div or op == ast.OpKind.FloorDiv or op == ast.OpKind.Mod:
# 除法/整除/取模也做静态折叠(与 + - * 完全同口径:算精确值,再由下面按值自动提升位宽)。
# 语义**与运行期一致**:Div/FloorDiv → sdiv(向零截断),Mod → srem(余数符号随被除数)
# —— 宿主侧的 `//` / `%` 正是这两个语义(本语言把 FloorDiv 也映射成 sdiv,
# 见本文件运行期分支;AssignGenMixin 的常量求值器同款)。无符号位型常量按规范
# 恒为非负,负值常量必属有符号操作数集 ⇒ 与运行期 udiv/urem 的取值一致。
# 除零:绝不交给宿主(Python 会直接把编译器打挂)——先关掉折叠再 fail-fast
# (诊断文本与 AssignGenMixin 的常量求值器一致)。
if rhs.IntVal == 0:
cf_ok = 0
self.Gen.TypeGenErr.fatal_error(node, "constant expression division by zero: the right operand of '/', '//' or '%' evaluates to 0 at compile time.", "ValueError")
elif op == ast.OpKind.Mod: cf_v = lhs.IntVal % rhs.IntVal
else: cf_v = lhs.IntVal // rhs.IntVal
elif op == ast.OpKind.LShift or op == ast.OpKind.RShift:
if rhs.IntVal >= 0 and rhs.IntVal < 64:
if op == ast.OpKind.LShift: cf_v = lhs.IntVal << rhs.IntVal
else: cf_v = lhs.IntVal >> rhs.IntVal
else: cf_ok = 0
else: cf_ok = 0
if cf_ok != 0:
# 定宽:下限取操作数较宽者,只有精确值装不下才加宽(避免把 `A + 1` 这类
# 常量算式整体抬成 i64,引出新的“隐式窄化”报错)。
cf_w: int = lhs_bits if lhs_bits > rhs_bits else rhs_bits
if cf_v != 0:
t_cf: int64 = cf_v
if cf_v < 0: t_cf = cf_v ^ int64(-1)
vw_cf: int = 1 # 符号位
# 固定 64 次上界(i64 最多 64 位):不用 `while t != 0` —— 某些代码生成下
# i64 的移位/比较不收敛,会死循环(现场:主编译器卡死在 ExprGenOps 死循环里)。
bi_cf: int = 0
while bi_cf < 64 and t_cf > 0:
vw_cf += 1
t_cf = t_cf >> 1
bi_cf += 1
if vw_cf > cf_w: cf_w = vw_cf
if cf_w <= 8: cf_w = 8
elif cf_w <= 16: cf_w = 16
elif cf_w <= 32: cf_w = 32
else: cf_w = 64
return mlirlite.const_int(pool, cf_w, cf_v)
# Float operation: use float instructions when either operand is float (must precede pointer arithmetic, since float has 0 int bits and would be mistaken for a pointer).
if lhs_fbits != 0 or rhs_fbits != 0:
# Determine the target float type (use the larger bit width)
@@ -1202,29 +1438,50 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
if binop_unsigned == 0 and rhs_node is not None:
if self._node_type_unsigned(pool, rhs_node, trans) != 0: binop_unsigned = 1
# C 语义整数提升(仅算术/移位;位运算 & | ^ 保持"同宽不提升"):
# 两侧都窄于 32 位时先提升到 32 位再算,避免 8/16 位回绕(如两个 16 位 define 的
# `MAX_INCLUDES * MAX_REL_PATH_LEN` 会算成 0)。提升后把 lhs_bits/rhs_bits 记为 32,
# 让下面的等宽化逻辑不再重复处理(否则会对同宽 i32 再做一次无意义转换)。
if self.binop_needs_int_promotion(op) != 0:
if lhs_bits != 0 and rhs_bits != 0 and lhs_bits < 32 and rhs_bits < 32:
i32_ty_pr: mlirlite.LLVMType* = mlirlite.Int32(pool)
# 加宽方向与下面的等宽化同一判据(i1/i8 默认 zext、带符号注解 sext、无符号 typedef zext);
# probe 只是给 should_zext_widen 提供"宽侧类型",其常量值不参与判据。
i32_probe: mlirlite.Value* = mlirlite.const_int32(pool, 0)
if self.should_zext_widen(pool, lhs, i32_probe, lhs_node, trans) != 0: lhs = mlirlite.build_zext(builder, lhs, i32_ty_pr)
else: lhs = self.coerce_to_type(builder, lhs, i32_ty_pr, node, trans)
if self.should_zext_widen(pool, rhs, i32_probe, rhs_node, trans) != 0: rhs = mlirlite.build_zext(builder, rhs, i32_ty_pr)
else: rhs = self.coerce_to_type(builder, rhs, i32_ty_pr, node, trans)
lhs_bits = 32
rhs_bits = 32
# 本语言**不做 C 式整数提升**:小整型(i8/i16 等)不再像 C 那样先抬到 int(i32) 再运算。
# · 两侧位宽相同 → 直接按该位宽运算(`uint16 + uint16` 结果仍是 i16);
# · 两侧位宽不同 → 由下方「等宽化」把**较小**的一侧拓宽到较大的一侧(结果位宽 = 较大者)。
# 既有能力保留:**上下文目标位宽感知**(有明确赋值目标时工作位宽取其位宽)与
# **位运算常量无损适配**(& | ^ 时能无损落进另一侧位宽的常量去适配另一侧,见下)。
if lhs_bits > rhs_bits:
if self.should_zext_widen(pool, rhs, lhs, rhs_node, trans) != 0: rhs = mlirlite.build_zext(builder, rhs, lhs.Ty)
else: rhs = self.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
elif rhs_bits > lhs_bits:
if self.should_zext_widen(pool, lhs, rhs, lhs_node, trans) != 0: lhs = mlirlite.build_zext(builder, lhs, rhs.Ty)
else: lhs = self.coerce_to_type(builder, lhs, rhs.Ty, node, trans)
# 运算的「工作位宽」:优先取上下文(赋值/形参/返回目标)位宽;无上下文时取两侧操作数的较大位宽
# (移位只取左操作数位宽,计数不参与)。于是 `s = A ^ B`(s: ushort)直接算到 16 位,不会先被
# 抬成 32 位、再在赋值处撞「隐式窄化」;而 `m: uint = (uchar(x) << 16) | …` 目标为 uint,按 32 位算。
tgt_bits: int = 0
if target_ty is not None and self.is_ptr_type(target_ty) == 0: tgt_bits = self.get_llvm_type_bits(target_ty)
work_bits: int = self._binop_work_bits(op, tgt_bits, lhs_bits, rhs_bits)
# 移位:结果位宽取**左操作数**位宽(计数宽度不代表结果类型,不参与)。有目标位宽时把左操作数
# 拓宽到目标位宽,使 `m: uint = uchar(x) << 16` 这类写法移出的位不丢(与目标感知工作位宽一致);
# 无目标时 work_bits = 左操作数位宽,故不再有「先抬到 i32」的旧行为。
if op == ast.OpKind.LShift or op == ast.OpKind.RShift:
if lhs_bits != 0 and lhs_bits < work_bits:
work_ty_sh: mlirlite.LLVMType* = self._int_type_of_bits(pool, work_bits)
sh_probe: mlirlite.Value* = mlirlite.const_int32(pool, 0)
if self.should_zext_widen(pool, lhs, sh_probe, lhs_node, trans) != 0: lhs = mlirlite.build_zext(builder, lhs, work_ty_sh)
else: lhs = self.coerce_to_type(builder, lhs, work_ty_sh, node, trans)
lhs_bits = work_bits
# 计数一律适配到左操作数类型:常量按「无损」适配;运行时更宽的值按 C 语义截到该宽度
# (计数不是"值",不受隐式窄化规则约束)。
if lhs_bits != 0 and rhs_bits != 0 and rhs.Ty != lhs.Ty:
if rhs_bits < lhs_bits or self._const_fits_int_type(rhs, lhs.Ty) != 0: rhs = self.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
else: rhs = mlirlite.build_trunc(builder, rhs, lhs.Ty)
rhs_bits = lhs_bits
else:
# 「常量适配」:一侧是能无损落进另一侧位宽的编译期常量时,把**常量**降到另一侧位宽,使其
# 不抬高工作位宽(用户规则:常量应隐式适配另一侧,`baseport(u16) + 7` 结果仍是 u16)。
# 也覆盖既有的位运算(& | ^)常量无损适配,二者共用 _const_operand_demotes 一处判定;
# 算术的两处例外(另一侧是常量 / 常量未超过目标位宽)见该 helper 的注释。
is_bitwise_op: int = 0
if op == ast.OpKind.BitAnd or op == ast.OpKind.BitOr or op == ast.OpKind.BitXor: is_bitwise_op = 1
if lhs_bits > rhs_bits:
if self._const_operand_demotes(is_bitwise_op, lhs, lhs_bits, rhs, rhs.Ty, tgt_bits) != 0: lhs = self.coerce_to_type(builder, lhs, rhs.Ty, node, trans)
elif self.should_zext_widen(pool, rhs, lhs, rhs_node, trans) != 0: rhs = mlirlite.build_zext(builder, rhs, lhs.Ty)
else: rhs = self.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
elif rhs_bits > lhs_bits:
if self._const_operand_demotes(is_bitwise_op, rhs, rhs_bits, lhs, lhs.Ty, tgt_bits) != 0: rhs = self.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
elif self.should_zext_widen(pool, lhs, rhs, lhs_node, trans) != 0: lhs = mlirlite.build_zext(builder, lhs, rhs.Ty)
else: lhs = self.coerce_to_type(builder, lhs, rhs.Ty, node, trans)
# Bitwise type safety: when both sides are pointers (lhs_bits==0 and rhs_bits==0), bitwise ops are illegal (LLVM
# requires integer operands). Common trigger: a failed CDefine lookup returns pointer null, then `|` with a pointer.
@@ -1233,8 +1490,9 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
# fail-fast: illegal bitwise operand types; abort compilation (expose the upstream root cause)
self.Gen.TypeGenErr.fatal_error(node, "illegal bitwise operand types: both lhs/rhs are non-integer types (pointer or struct), cannot perform bitwise operations", "TypeError")
# 移位:计数静态可确定时做范围检查(与 AugAssign 的 <<= / >>= 共用);
# 计数宽度本身豁免,按提升/等宽化后的左操作数有效位宽比较(C 语义下 u8 << 8 合法)。
# 移位:计数静态可确定时做范围检查(与 AugAssign 的 <<= / >>= 共用);计数宽度本身豁免,
# 按等宽化后的左操作数有效位宽比较。注意:不再做整数提升后,`uchar(x) << 8` 的左操作数是 i8,
# 计数 8 >= 8 会被如实拦下(这是新语义下的真实越界,而非旧 C 语义"先提升到 i32"后的合法移位)。
if op == ast.OpKind.LShift or op == ast.OpKind.RShift:
self.Gen.AssignGen.require_shift_count_in_range(pool, trans, node, rhs_node, self.get_llvm_type_bits(lhs.Ty))
+292 -291
View File
@@ -1,292 +1,293 @@
import t, c
from stdint import *
import ast
import mlirlite
import memhub
import string
import viperlib
import stdio
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(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()
if cfk != ast.ASTKind.Attribute: continue
# Detect c.Attribute
func_attr: ast.Attribute* = ast.Attribute*(call_node.func)
if func_attr.attr is None: continue
if string.strcmp(func_attr.attr, "Attribute") != 0: continue
if func_attr.value is None: continue
fvk: int = func_attr.value.kind()
if fvk != ast.ASTKind.Name: continue
c_name: ast.Name* = ast.Name*(func_attr.value)
if c_name.id is None: continue
if string.strcmp(c_name.id, "c") != 0: continue
imp_c: int = self.Gen.ImportsGen.is_module_imported(imported_modules, "c")
if imp_c == 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(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 @c.Attribute(t.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(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.Attribute: continue
func_attr: ast.Attribute* = ast.Attribute*(call_node.func)
if func_attr.attr is None or string.strcmp(func_attr.attr, "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(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: TPV 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(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(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(an):
arg: ast.Arg* = ast.Arg*(alist.get(ai))
if arg is not None and arg.arg is not None:
# t.CVoid means no args: skip
if arg.annotation is not None:
if arg.annotation.kind() == ast.ASTKind.Attribute:
at_cf: ast.Attribute* = ast.Attribute*(arg.annotation)
if at_cf.attr is not None and string.strcmp(at_cf.attr, "CVoid") == 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)
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()
if cfk != ast.ASTKind.Attribute: continue
# Detect c.Attribute
func_attr: ast.Attribute* = ast.Attribute*(call_node.func)
if func_attr.attr is None: continue
if string.strcmp(func_attr.attr, "Attribute") != 0: continue
if func_attr.value is None: continue
fvk: int = func_attr.value.kind()
if fvk != ast.ASTKind.Name: continue
c_name: ast.Name* = ast.Name*(func_attr.value)
if c_name.id is None: continue
if string.strcmp(c_name.id, "c") != 0: continue
# 不要求 `import c`:`@__attribute__(...)` 会被 ast/parser.vp(_DesugarMagicAttribute)
# 脱糖成 `@c.Attribute(...)`,这里的 `c.` 前缀是编译器自己产生的(用户写的是
# `@__attribute__`,并非 `@c.Attribute`)。若在此要求 `import c`,用户明明没引入 c
# 就会把整条装饰器静默丢弃(`stackrealign`/`always_inline` 等函数属性全失效)。
# extract_func_section 本就没有这个守卫,二者保持一致。
# 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 @c.Attribute(t.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.Attribute: continue
func_attr: ast.Attribute* = ast.Attribute*(call_node.func)
if func_attr.attr is None or string.strcmp(func_attr.attr, "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:
# t.CVoid means no args: skip
if arg.annotation is not None:
if arg.annotation.kind() == ast.ASTKind.Attribute:
at_cf: ast.Attribute* = ast.Attribute*(arg.annotation)
if at_cf.attr is not None and string.strcmp(at_cf.attr, "CVoid") == 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
+45 -11
View File
@@ -1,6 +1,5 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Func.FuncGenMixin as FuncGenMixin
import t, c
from stdint import *
import ast
import mlirlite
@@ -173,7 +172,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
# Extract default-argument info. Note: assign the attribute to an explicitly typed list[...]* local before
# calling __len__(), otherwise the list type is not recognized and the GEP base becomes i32 0 (llc error).
fd_args_node: ast.Arguments* = fd.args
fd_defaults: t.CPtr = None
fd_defaults: rawptr = None
fd_default_count: int = 0
fd_param_count: int = 0
if fd_args_node is not None:
@@ -462,6 +461,33 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
if func is not None and mlirlite.function_is_declared(func) != 0:
# Reuse the forward declare: clear IsDeclared and turn it into a define
func.IsDeclared = 0
# 参数名对齐(根因修复):复用的前置声明未必是 forward_declare_functions 按源码建的,也可能是
# **模块前言**里 Translator 建的 C 库声明(如 free 用合成名 "p")。而函数体是用**源码形参名**
# %<arg.arg> 生成参数 SSA 引用的(见下方 alloca 段的 SSAValue)。若签名沿用声明里的旧名,就会
# 打印出 `llvm.func @f(%p) { ... %ptr ... }`:入口实参是 %p,体内 %ptr 却在 f 内未定义,
# mlir-opt 会把 %ptr 解析到后续同名函数的入口实参并报 "region entry argument '%ptr' is already
# in use"(现场:VKernel 的 free 头 %p / 体 %ptr,被解析到 _slab_find_page(%ptr))。
# 故这里按源码形参名重写复用到的形参名,使签名与函数体一致。forward_declare_functions 建的前置
# 声明本就用源码名,此处对其幂等(同名重写)。
reused_p: mlirlite.Param* = mlirlite.function_get_param_head(func)
if args_node is not None:
reuse_ags: ast.Arguments* = ast.Arguments*(args_node)
if reuse_ags.args is not None:
reuse_list: list[ast.AST*]* = reuse_ags.args
reuse_n: sizet = len(reuse_list)
for reuse_i in range(int(reuse_n)):
rearg: ast.Arg* = ast.Arg*(reuse_list.get(reuse_i))
if rearg is None or rearg.arg is None: continue
# CVoid means empty param: not part of the signature (mirrors the add_param loops above)
if rearg.annotation is not None and self.Gen.TypeGenResolve._is_t_attr(rearg.annotation, "CVoid", imported_modules) != 0: continue
if reused_p is None:
# 复用声明形参少于定义:签名与函数体必然错配,禁止静默,fail-fast
self.Gen.TypeGenErr.fatal_error(rearg, f"reused declaration for '{fd.name}' has fewer parameters than the definition", "TypeError")
return 0
reuse_pname: str = pool.alloc(32)
if reuse_pname is not None: viperlib.snprintf(reuse_pname, 32, "%%%s", rearg.arg)
reused_p.Name = reuse_pname
reused_p = mlirlite.param_get_next(reused_p)
# Set decorator attributes (not set when the forward declare was created)
func_attrs_reuse: str = self.Gen.FuncGenBase.extract_func_attrs(pool, fd.decorator_list, imported_modules)
if func_attrs_reuse is not None: mlirlite.function_set_attrs(func, func_attrs_reuse)
@@ -483,7 +509,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
if func_section is not None: mlirlite.function_set_attr_group(func, func_section)
# Extract default-argument info
tfd_defaults: t.CPtr = None
tfd_defaults: rawptr = None
tfd_default_count: int = 0
tfd_param_count: int = 0
if args_node is not None:
@@ -572,9 +598,12 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
# @t.TLS one-time-init guard (aligned with TPC HandlesFunctions.py): the first call runs the body and sets
# __tls_init_done_{name}=1; later calls return the default. Without it every call re-runs TlsAlloc and loses state.
if has_tls != 0:
tls_flag_name: str = pool.alloc(128)
# Exact capacity: the reference below used to be 64 while the definition was 128, so a
# long function name produced a reference to a different (nonexistent) global.
tls_flag_cap: sizet = string.strlen(fd.name) + 24
tls_flag_name: str = pool.alloc(tls_flag_cap)
if tls_flag_name is not None:
viperlib.snprintf(tls_flag_name, 128, "__tls_init_done_%s", fd.name)
viperlib.snprintf(tls_flag_name, tls_flag_cap, "__tls_init_done_%s", fd.name)
tls_flag_gv: mlirlite.GlobalVariable* = mlirlite.create_global_int(pool, mod, tls_flag_name, 1, 0)
if tls_flag_gv is not None:
i1_ty_t: mlirlite.LLVMType* = mlirlite.Int1(pool)
@@ -582,9 +611,10 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
# GlobalVariable is not a Value subclass (different field layout), so build_load/store reading
# ptr.Ty/ptr.Name as a Value misaligns. As in create_global_string, build a Value view (Ty=Ptr(i1), Name="@name").
tls_gv_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i1_ty_t)
tls_ref_name: str = pool.alloc(64)
tls_ref_cap: sizet = string.strlen(tls_flag_name) + 2
tls_ref_name: str = pool.alloc(tls_ref_cap)
if tls_ref_name is not None:
viperlib.snprintf(tls_ref_name, 64, "@%s", tls_flag_name)
viperlib.snprintf(tls_ref_name, tls_ref_cap, "@%s", tls_flag_name)
tls_flag_ref: mlirlite.Value* = mlirlite.SSAValue(pool, tls_gv_ptr_ty, tls_ref_name)
if tls_flag_ref is not None:
tls_flag_ld: mlirlite.Value* = mlirlite.build_load(func_builder, i1_ty_t, tls_flag_ref)
@@ -967,13 +997,17 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
# Recursion guard flag (outermost): @__decor_rec_{wrapper} = global i8 0
rec_flag_ref: mlirlite.Value* = None
rec_flag_name: str = pool.alloc(128)
# Exact capacities: definition and reference used different fixed sizes (128 vs 96), so a
# long wrapper name produced two different globals (guard silently bypassed / undefined).
rec_flag_cap: sizet = string.strlen(wrapper_name) + 24
rec_flag_name: str = pool.alloc(rec_flag_cap)
if rec_flag_name is not None:
viperlib.snprintf(rec_flag_name, 128, "__decor_rec_%s", wrapper_name)
viperlib.snprintf(rec_flag_name, rec_flag_cap, "__decor_rec_%s", wrapper_name)
mlirlite.create_global_int(pool, mod, rec_flag_name, 8, 0)
rec_ref_nm: str = pool.alloc(96)
rec_ref_cap: sizet = string.strlen(rec_flag_name) + 2
rec_ref_nm: str = pool.alloc(rec_ref_cap)
if rec_ref_nm is not None:
viperlib.snprintf(rec_ref_nm, 96, "@%s", rec_flag_name)
viperlib.snprintf(rec_ref_nm, rec_ref_cap, "@%s", rec_flag_name)
rec_flag_ref = mlirlite.SSAValue(pool, mlirlite.Ptr(pool, i8_ty), rec_ref_nm)
wb: mlirlite.IRBuilder* = mlirlite.new_builder(pool, wrapper)
+15 -13
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
@@ -83,7 +82,7 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
# types go into the param_tys buffer (CVoid skipped, aligning with alloca indices).
imported_modules: str = trans._imported_modules
from_imports: str = trans._from_imports
param_tys: t.CPtr = pool.alloc(8 * 64)
param_tys: rawptr = pool.alloc(8 * 64)
if param_tys is not None: string.memset(param_tys, 0, 8 * 64)
param_count: int = 0
if fd.args is not None:
@@ -192,8 +191,8 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
old_insw: int = trans._in_switch_case
# Separate nonlocal buffer: sharing [0] would overwrite the parent's set (ClosureTest2
# Test 7: level2's b clobbers level1's a). Restored after translation.
saved_nl_names: t.CPtr = trans._nonlocal_names
saved_nl_types: t.CPtr = trans._nonlocal_types
saved_nl_names: rawptr = trans._nonlocal_names
saved_nl_types: rawptr = trans._nonlocal_types
# 7. Switch to the nested function scope and clear scope names
trans._cur_func = func
@@ -381,12 +380,15 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
fn_func_ty: mlirlite.LLVMType* = mlirlite.Func(pool, i32_ty, fn_param_node, 1)
fn_func_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, fn_func_ty)
# Create the function reference Value (type i32(i8*)*, using the SHA1 mangled name)
fn_ptr_name: str = pool.alloc(64)
# Create the function reference Value (type i32(i8*)*, using the SHA1 mangled name).
# Exact capacity: the mangled name (SHA1 prefix + closure name) can exceed the old 64-byte
# buffer, and a truncated name makes closure slot 0 point at a nonexistent symbol.
fn_ptr_cap: sizet = string.strlen(mangled_promoted) + 4
fn_ptr_name: str = pool.alloc(fn_ptr_cap)
if fn_ptr_name is not None:
# SHA1-prefixed names contain '.' so they need quoting (e.g. @"sha1.__closure_func")
if string.strchr(mangled_promoted, '.') is not None: viperlib.snprintf(fn_ptr_name, 64, "@\"%s\"", mangled_promoted)
else: viperlib.snprintf(fn_ptr_name, 64, "@%s", mangled_promoted)
if string.strchr(mangled_promoted, '.') is not None: viperlib.snprintf(fn_ptr_name, fn_ptr_cap, "@\"%s\"", mangled_promoted)
else: viperlib.snprintf(fn_ptr_name, fn_ptr_cap, "@%s", mangled_promoted)
fn_ptr_val: mlirlite.Value* = mlirlite.SSAValue(pool, fn_func_ptr_ty, fn_ptr_name)
# bitcast the function pointer to i8* (the closure stores i8*)
@@ -775,7 +777,7 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
# ============================================================
# _collect_chain_nonlocals - recursively gather nonlocal names in the nested chain (deduped)
def _collect_chain_nonlocals(self, trans: HT.Translator*, node: ast.AST*, names: t.CPtr, count: int) -> int:
def _collect_chain_nonlocals(self, trans: HT.Translator*, node: ast.AST*, names: rawptr, count: int) -> int:
"""Recursively collect all nonlocal declaration names in a nested FunctionDef chain (deduped)"""
if node is None or names is None: return count
fd: ast.FunctionDef* = ast.FunctionDef*(node)
@@ -895,7 +897,7 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
param_names: str = pool.alloc(32 * 64) # 64 params x 32-byte names
if param_names is None: return None
string.memset(param_names, 0, 32 * 64)
param_tys: t.CPtr = pool.alloc(8 * 64) # LLVMType pointer slots
param_tys: rawptr = pool.alloc(8 * 64) # LLVMType pointer slots
if param_tys is None: return None
string.memset(param_tys, 0, 8 * 64)
param_count: int = 0
@@ -932,10 +934,10 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
cap_names: str = pool.alloc(64 * 64) # 64 captures x 64-byte names
if cap_names is None: return None
string.memset(cap_names, 0, 64 * 64)
cap_allocas: t.CPtr = pool.alloc(8 * 64) # Parent alloca pointer slots
cap_allocas: rawptr = pool.alloc(8 * 64) # Parent alloca pointer slots
if cap_allocas is None: return None
string.memset(cap_allocas, 0, 8 * 64)
cap_tys: t.CPtr = pool.alloc(8 * 64) # Capture variable type slots
cap_tys: rawptr = pool.alloc(8 * 64) # Capture variable type slots
if cap_tys is None: return None
string.memset(cap_tys, 0, 8 * 64)
cap_count: int = self._lambda_scan_body(trans, lam.body, param_names, param_count, cap_names, cap_allocas, cap_tys, 0)
@@ -1145,7 +1147,7 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
# ============================================================
# _lambda_scan_body - recursively collect outer Name refs into cap_names/cap_allocas/cap_tys
def _lambda_scan_body(self, trans: HT.Translator*, node: ast.AST*, param_names: str, param_count: int, cap_names: str, cap_allocas: t.CPtr, cap_tys: t.CPtr, cap_count: int) -> int:
def _lambda_scan_body(self, trans: HT.Translator*, node: ast.AST*, param_names: str, param_count: int, cap_names: str, cap_allocas: rawptr, cap_tys: rawptr, cap_count: int) -> int:
"""Recursively scan lambda body Name references and collect captured variables"""
if node is None or trans is None: return cap_count
k: int = node.kind()
+71 -50
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import mlirlite
@@ -136,6 +135,20 @@ def has_decorator_marker(node: ast.AST*, marker_name: str, depth: int = 0) -> in
return 0
# ---------------- has_pointee_volatile_marker (delegates to TypeGenErr.has_pointee_volatile_marker) ----------------
def has_pointee_volatile_marker(node: ast.AST*) -> int:
"""Delegate to gen.has_pointee_volatile_marker (rawptr[t.CVolatile | T] field/var pointee)"""
if _g_gen is not None: return _g_gen.TypeGenErr.has_pointee_volatile_marker(node)
return 0
# ---------------- is_const_object_marker (delegates to TypeGenErr.is_const_object_marker) ----------------
def is_const_object_marker(node: ast.AST*, var_ty: mlirlite.LLVMType*) -> int:
"""Delegate to gen.is_const_object_marker (t.CConst non-pointer object field/var)"""
if _g_gen is not None: return _g_gen.TypeGenErr.is_const_object_marker(node, var_ty)
return 0
# ---------------- _get_type_name_from_annotation (delegates to TypeGenResolve._get_type_name_from_annotation) ----------------
def _get_type_name_from_annotation(pool: memhub.MemBuddy*, node: ast.AST*) -> str:
"""Delegate to gen._get_type_name_from_annotation"""
@@ -178,9 +191,17 @@ def cdefine_decl_bits(pool: memhub.MemBuddy*, annot: ast.AST*, value_node: ast.A
# ---------------- register_cdefine_double (delegates to TypeGenCDef.register_cdefine_double) ----------------
def register_cdefine_double(pool: memhub.MemBuddy*, name: str, value: double):
"""Delegate to gen.register_cdefine_double"""
if _g_gen is not None: _g_gen.TypeGenCDef.register_cdefine_double(pool, name, value)
def register_cdefine_double(pool: memhub.MemBuddy*, name: str, value: double, is_f32: int = 0):
"""Delegate to gen.register_cdefine_double (is_f32: 1=float 声明,0=double)"""
if _g_gen is not None: _g_gen.TypeGenCDef.register_cdefine_double(pool, name, value, is_f32)
# ---------------- cdefine_decl_float_bits (delegates to AssignGenMixin.cdefine_decl_float_bits) ----------------
def cdefine_decl_float_bits(pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""define 声明的**浮点**宽度裁决:注解显式写了 float → 1(f32),double/未写 → 0。
跨模块(.vpi 重解析 define 的 AST)注册必须用它,否则使用方把 f32 常量当 double 物化。"""
if _g_gen is not None: return _g_gen.AssignGen.cdefine_decl_float_bits(pool, annot)
return 0
# ---------------- is_cdefine_annotation (delegates to AssignGenMixin.is_cdefine_annotation) ----------------
@@ -665,52 +686,52 @@ def _WillFreeArgIndex(fn: ast.FunctionDef*, arg: ast.AST*) -> int:
return idx
def get_param_escapes_hard(name: str) -> int:
"""本文件函数"硬逃逸位"掩码(全局写 / 返回形参 / 传递闭包 / base 未定的字段写);0 = 无记录"""
if _g_gen is not None: return _g_gen.StructGen.get_param_escapes_hard(name)
return 0
def get_param_escapes_hard_method(pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int:
"""方法版硬逃逸位:查 "Class.method" 并沿 MRO 回退"""
if _g_gen is not None: return _g_gen.StructGen.get_param_escapes_hard_method(pool, class_name, method_name)
return 0
# ---- 声明式指令(@life(escape/noescape))的最终裁决掩码 ----
# 契约表与推断表分开:调用点在所有推断/裁剪之后最后套用,任何推导都不能推翻。
def get_param_escape_force(name: str) -> int:
"""`@life(escape(...))` 强制逃逸位掩码;0 = 无该标注"""
if _g_gen is not None: return _g_gen.StructGen.get_param_escape_force(name)
return 0
def get_param_noescape_force(name: str) -> int:
"""`@life(noescape(...))` 强制不逃逸位掩码;0 = 无该标注"""
if _g_gen is not None: return _g_gen.StructGen.get_param_noescape_force(name)
return 0
def get_param_escape_force_method(pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int:
"""方法版 `@life(escape(...))` 掩码:查 "Class.method" 并沿 MRO 回退"""
if _g_gen is not None: return _g_gen.StructGen.get_param_escape_force_method(pool, class_name, method_name)
return 0
def get_param_noescape_force_method(pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int:
"""方法版 `@life(noescape(...))` 掩码:查 "Class.method" 并沿 MRO 回退"""
if _g_gen is not None: return _g_gen.StructGen.get_param_noescape_force_method(pool, class_name, method_name)
return 0
# escape_leak_line - **错误路径专用**:被调者第 want_pi 号形参的"首个逃逸写入点"行号。
# 只在 fail-fast 报错时调用(会重扫一次被调者函数体);跨模块拿不到 AST 时返回 0。
def escape_leak_line(name: str, want_pi: int) -> int:
"""形参 want_pi 的逃逸点行号;0 = 未知(含跨模块)"""
if _g_gen is not None: return _g_gen.StructGen.escape_leak_line(name, want_pi)
return 0
def get_param_escapes_hard(name: str) -> int:
"""本文件函数"硬逃逸位"掩码(全局写 / 返回形参 / 传递闭包 / base 未定的字段写);0 = 无记录"""
if _g_gen is not None: return _g_gen.StructGen.get_param_escapes_hard(name)
return 0
def get_param_escapes_hard_method(pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int:
"""方法版硬逃逸位:查 "Class.method" 并沿 MRO 回退"""
if _g_gen is not None: return _g_gen.StructGen.get_param_escapes_hard_method(pool, class_name, method_name)
return 0
# ---- 声明式指令(@life(escape/noescape))的最终裁决掩码 ----
# 契约表与推断表分开:调用点在所有推断/裁剪之后最后套用,任何推导都不能推翻。
def get_param_escape_force(name: str) -> int:
"""`@life(escape(...))` 强制逃逸位掩码;0 = 无该标注"""
if _g_gen is not None: return _g_gen.StructGen.get_param_escape_force(name)
return 0
def get_param_noescape_force(name: str) -> int:
"""`@life(noescape(...))` 强制不逃逸位掩码;0 = 无该标注"""
if _g_gen is not None: return _g_gen.StructGen.get_param_noescape_force(name)
return 0
def get_param_escape_force_method(pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int:
"""方法版 `@life(escape(...))` 掩码:查 "Class.method" 并沿 MRO 回退"""
if _g_gen is not None: return _g_gen.StructGen.get_param_escape_force_method(pool, class_name, method_name)
return 0
def get_param_noescape_force_method(pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int:
"""方法版 `@life(noescape(...))` 掩码:查 "Class.method" 并沿 MRO 回退"""
if _g_gen is not None: return _g_gen.StructGen.get_param_noescape_force_method(pool, class_name, method_name)
return 0
# escape_leak_line - **错误路径专用**:被调者第 want_pi 号形参的"首个逃逸写入点"行号。
# 只在 fail-fast 报错时调用(会重扫一次被调者函数体);跨模块拿不到 AST 时返回 0。
def escape_leak_line(name: str, want_pi: int) -> int:
"""形参 want_pi 的逃逸点行号;0 = 未知(含跨模块)"""
if _g_gen is not None: return _g_gen.StructGen.escape_leak_line(name, want_pi)
return 0
def get_param_willfree(name: str) -> int:
"""本文件函数的 willFree 形参掩码(第 k 位=第 k 个形参在本调用后失效);无记录返回 0"""
if _g_gen is not None: return _g_gen.StructGen.get_param_willfree(name)
+8 -1
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import ast
import mlirlite
@@ -79,6 +78,10 @@ class VarEntry:
# 生命期标签(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,槽本身唯一)。
@@ -121,6 +124,10 @@ class FieldEntry:
# 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
# ============================================================
-1
View File
@@ -1,6 +1,5 @@
import string
import lib.core.CompilerFactory as CompilerFactory
import t, c
from stdint import *
import stdio
import stdlib
+25 -17
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.Generator as Generator
import t, c
from stdint import *
import ast
import memhub
@@ -9,7 +8,7 @@ import stdlib
import viperlib
import stdio
import sys
import w32.fileio as fileio
import win32.fileio as fileio
import lib.Stub.StubMerger as StubMerger
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.Translator as HT
@@ -165,7 +164,7 @@ novirtual class ImportsGenMixin:
word_len: sizet = ci - word_start
if word_len == name_len:
Matched: int = 1
for ei in range(name_len):
for ei in range(int(name_len)):
if cur[word_start + ei] != name[ei]:
Matched = 0
break
@@ -179,7 +178,7 @@ novirtual class ImportsGenMixin:
if name is None: return 0
if StubMergerStore.LookupSha1ByModName(name) is None: return 0
nl: sizet = string.strlen(name)
for dot_pos in range(nl):
for dot_pos in range(int(nl)):
if name[dot_pos] == '.':
pfx: str = stdlib.malloc(dot_pos + 1)
if pfx is not None:
@@ -213,7 +212,7 @@ novirtual class ImportsGenMixin:
# lookup; else fall back to scanning. orig None means a first occurrence with no alias.
if from_imports == self.CurFromImportsPtr and self.CurFromImportsOrigs is not None:
if self.CurFromImportsOrigs.__contains__(local_name) != 0:
v_o: t.CPtr = self.CurFromImportsOrigs.__getitem__(local_name)
v_o: rawptr = self.CurFromImportsOrigs.__getitem__(local_name)
if v_o is not None: return rawptr(v_o)
# Index miss or None value never returns None directly (the real string may still
# contain the name); fall through to scanning (correctness first).
@@ -238,7 +237,7 @@ novirtual class ImportsGenMixin:
# Compare the name
if entry_name_len == name_len:
Matched: int = 1
for ei in range(name_len):
for ei in range(int(name_len)):
if cur[ci + ei] != local_name[ei]:
Matched = 0
break
@@ -275,10 +274,10 @@ novirtual class ImportsGenMixin:
name_is_star: int = 0
if string.strlen(name) == 1 and name[0] == '*': name_is_star = 1
if name_is_star == 0:
v_f: t.CPtr = self.CurFromImportsMods.__getitem__(name)
v_f: rawptr = self.CurFromImportsMods.__getitem__(name)
if v_f is not None: return rawptr(v_f)
if allow_star_fallback != 0:
sv_f: t.CPtr = self.CurFromImportsMods.__getitem__("*")
sv_f: rawptr = self.CurFromImportsMods.__getitem__("*")
if sv_f is not None: return rawptr(sv_f)
# Neither exact nor star hit in the index never returns None (an incomplete index
# misses imported names, causing _op_head NameError); fall through to scanning.
@@ -305,7 +304,7 @@ novirtual class ImportsGenMixin:
# Compare the name
elif entry_name_len == name_len:
Matched: int = 1
for ei in range(name_len):
for ei in range(int(name_len)):
if cur[ci + ei] != name[ei]:
Matched = 0
break
@@ -327,6 +326,15 @@ novirtual class ImportsGenMixin:
def register_reexport(self, mod_sha1: str, func_name: str, source_module: str) -> int:
"""Register a re-export mapping; returns 0 on success, -1 on failure."""
if mod_sha1 is None or func_name is None or source_module is None: return -1
# fail-fast: the table is a flat slab with fixed slots (17 bytes for the SHA1, 64 for the
# name/module). strncpy used to clamp silently, after which lookup_reexport's full-name
# strcmp never matched (or matched another module sharing the 63-byte prefix).
if string.strlen(mod_sha1) > 16:
self.Gen.TypeGenErr.fatal_error(None, "re-export module SHA1 is longer than its 16-byte table slot", "OverflowError")
if string.strlen(func_name) > 62:
self.Gen.TypeGenErr.fatal_error(None, f"re-exported name '{func_name}' exceeds the 62-byte table slot; shorten the identifier", "OverflowError")
if string.strlen(source_module) > 62:
self.Gen.TypeGenErr.fatal_error(None, f"re-export source module '{source_module}' exceeds the 62-byte table slot; shorten the module name", "OverflowError")
# Lazy initialization
if self.ReexportSha1s is None:
self.ReexportSha1s = CompilerFactory.factory.PermPool.alloc(MAX_REEXPORT * 17)
@@ -338,7 +346,7 @@ novirtual class ImportsGenMixin:
string.memset(self.ReexportSrcs, 0, MAX_REEXPORT * 64)
if self.ReexportCount >= MAX_REEXPORT: return -1
# Idempotency check: look for an existing identical mapping
for i in range(self.ReexportCount):
for i in range(int(self.ReexportCount)):
sidx: sizet = sizet(i) * 17
fidx: sizet = sizet(i) * 64
if string.strcmp(self.ReexportSha1s + sidx, mod_sha1) == 0:
@@ -365,7 +373,7 @@ novirtual class ImportsGenMixin:
"""Return the source module name for a re-export mapping, or None."""
if mod_sha1 is None or func_name is None: return None
if self.ReexportSha1s is None or self.ReexportCount <= 0: return None
for i in range(self.ReexportCount):
for i in range(int(self.ReexportCount)):
sidx: sizet = sizet(i) * 17
fidx: sizet = sizet(i) * 64
if string.strcmp(self.ReexportSha1s + sidx, mod_sha1) == 0:
@@ -389,7 +397,7 @@ novirtual class ImportsGenMixin:
pkg_len: sizet = string.strlen(pkg)
last_dot: sizet = 0
found: int = 0
for i in range(pkg_len):
for i in range(int(pkg_len)):
if pkg[i] == '.':
last_dot = i
found = 1
@@ -435,7 +443,7 @@ novirtual class ImportsGenMixin:
# Find the last / or \
last_sep: sizet = 0
found: int = 0
for i in range(rlen):
for i in range(int(rlen)):
ch: char = rel_path[i]
if ch == '/' or ch == '\\':
last_sep = i
@@ -447,7 +455,7 @@ novirtual class ImportsGenMixin:
dir_len: sizet = last_sep
buf: str = pool.alloc(dir_len + 1)
if buf is None: return None
for j in range(dir_len):
for j in range(int(dir_len)):
ch2: char = rel_path[j]
if ch2 == '/' or ch2 == '\\': buf[j] = '.'
else: buf[j] = ch2
@@ -565,7 +573,7 @@ novirtual class ImportsGenMixin:
pool_val: memhub.MemBuddy* = trans.Pool
ctx: StubMergerStore.ImportCtx* = StubMergerStore.AllocImportCtx(pool_val, trans._imported_modules, trans._from_imports)
if ctx is None: return 0
for ni in range(nn):
for ni in range(int(nn)):
alias: ast.Alias* = ast.Alias*(names.get(ni))
if alias is not None and alias.name is not None:
# Validate the module SHA1 at import time (fast-fail, full translation only):
@@ -595,7 +603,7 @@ novirtual class ImportsGenMixin:
names_m: list[ast.AST*]* = impf.names
if names_m is not None:
nn_m: sizet = len(names_m)
for ni_m in range(nn_m):
for ni_m in range(int(nn_m)):
alias_m: ast.Alias* = ast.Alias*(names_m.get(ni_m))
if alias_m is not None and alias_m.name is not None:
rl_m: sizet = string.strlen(resolved)
@@ -637,7 +645,7 @@ novirtual class ImportsGenMixin:
names: list[ast.AST*]* = impf.names
if names is not None:
nn: sizet = len(names)
for ni in range(nn):
for ni in range(int(nn)):
alias: ast.Alias* = ast.Alias*(names.get(ni))
if alias is not None and alias.name is not None:
local_name: str = alias.name
File diff suppressed because it is too large. Load diff
+170 -95
View File
@@ -1,6 +1,5 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import t, c
from stdint import *
import ast
import mlirlite
@@ -102,11 +101,11 @@ novirtual class StmtGenCifMixin:
"""Resolve a macro name via local CDefine, cross-module CDefine, then platform macro."""
self.CifEvalOk = 0
if name is None or trans is None: return 0
v: int = self.Gen.TypeGenCDef.lookup_cdefine_constant(name)
v: int = int(self.Gen.TypeGenCDef.lookup_cdefine_constant(name))
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
self.CifEvalOk = 1
return v
v2: int = self.Gen.ExprGenCdef._lookup_cross_module_cdefine(pool, name, trans._from_imports, trans._imported_modules)
v2: int = int(self.Gen.ExprGenCdef._lookup_cross_module_cdefine(pool, name, trans._from_imports, trans._imported_modules))
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
self.CifEvalOk = 1
return v2
@@ -121,7 +120,7 @@ novirtual class StmtGenCifMixin:
if at.value.kind() != ast.ASTKind.Name: return 0
vn: ast.Name* = ast.Name*(at.value)
if vn.id is None: return 0
v: int = self.Gen.TypeGenCDef.lookup_cdefine_constant(at.attr)
v: int = int(self.Gen.TypeGenCDef.lookup_cdefine_constant(at.attr))
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
self.CifEvalOk = 1
return v
@@ -134,14 +133,14 @@ novirtual class StmtGenCifMixin:
while resolved[rm_len] != ' ' and resolved[rm_len] != '\0' and resolved[rm_len] != ':': rm_len += 1
rm_buf: str = pool.alloc(rm_len + 1)
if rm_buf is not None:
for rm_i in range(rm_len): rm_buf[rm_i] = resolved[rm_i]
for rm_i in range(int(rm_len)): rm_buf[rm_i] = resolved[rm_i]
rm_buf[rm_len] = '\0'
real_mod = rm_buf
fi_len: sizet = string.strlen(at.attr) + string.strlen(real_mod) + 2
fi: str = pool.alloc(fi_len)
if fi is None: return 0
viperlib.snprintf(fi, fi_len, "%s:%s", at.attr, real_mod)
v2: int = self.Gen.ExprGenCdef._lookup_cross_module_cdefine(pool, at.attr, fi)
v2: int = int(self.Gen.ExprGenCdef._lookup_cross_module_cdefine(pool, at.attr, fi))
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
self.CifEvalOk = 1
return v2
@@ -158,7 +157,8 @@ novirtual class StmtGenCifMixin:
# integer and boolean literals are compile-time constants (True/False carry 1/0)
if cn.const_kind == ast.CONST_INT or cn.const_kind == ast.CONST_BOOL:
self.CifEvalOk = 1
return cn.int_val
# IntVal(i64) → int(i32):显式收窄(本函数返回 int)
return int(cn.int_val)
self.CifEvalOk = 0
return 0
if k == ast.ASTKind.Call: return self._cif_eval_call(pool, trans, node)
@@ -192,13 +192,13 @@ novirtual class StmtGenCifMixin:
return 0
bn: sizet = len(vals)
if bo.op == ast.OpKind.And:
for bi in range(bn):
for bi in range(int(bn)):
rv: int = self._cif_eval_node(pool, trans, vals.get(bi))
if self.CifEvalOk == 0: return 0
if rv == 0: return 0
return 1
if bo.op == ast.OpKind.Or:
for bi in range(bn):
for bi in range(int(bn)):
rv: int = self._cif_eval_node(pool, trans, vals.get(bi))
if self.CifEvalOk == 0: return 0
if rv != 0: return 1
@@ -216,7 +216,7 @@ novirtual class StmtGenCifMixin:
return 0
cc: sizet = len(comps)
ci: sizet
for ci in range(cc):
for ci in range(int(cc)):
rv: int = self._cif_eval_node(pool, trans, comps.get(ci))
if self.CifEvalOk == 0: return 0
op_id: int = ops_list.get(ci)
@@ -293,7 +293,7 @@ novirtual class StmtGenCifMixin:
# way a bare macro name resolves; a resolvable value means the macro exists.
if arg0 is not None and arg0.kind() == ast.ASTKind.Attribute:
self._cif_eval_node(pool, trans, arg0)
dotted_defined: int = self.CifEvalOk
dotted_defined: int = int(self.CifEvalOk)
self.CifEvalOk = 1
return 1 if dotted_defined != 0 else 0
name_arg: str = None
@@ -307,7 +307,7 @@ novirtual class StmtGenCifMixin:
self.Gen.TypeGenErr.fatal_error(cl, "defined() requires a bare macro name, e.g. defined(NAME)", "SyntaxError")
return 0
self._cif_name_value(pool, trans, name_arg)
defined: int = self.CifEvalOk
defined: int = int(self.CifEvalOk)
# the defined() test itself is evaluable even when the macro is absent (defined = 0)
self.CifEvalOk = 1
return 1 if defined != 0 else 0
@@ -381,7 +381,7 @@ novirtual class StmtGenCifMixin:
if self.CifEvalOk == 0: return 0
return 1 if v != 0 else 0
# @CIfdef / @CIfndef: test whether the macro name is defined
defined: int = 0
defined: sizet = 0
if arg0 is not None and arg0.kind() == ast.ASTKind.Name:
nm0: ast.Name* = ast.Name*(arg0)
if nm0.id is None: return 0
@@ -433,13 +433,13 @@ novirtual class StmtGenCifMixin:
if rt.value is not None:
# f-string escape (return f"..."): its buffer lives on this frame's stack and dies on
# return; copy it out explicitly (GenApi.escape_fstring_copy) before returning it.
GenApi.fail_if_fstring_escape(rt.value, "function return value")
# A2(§14.3 第 6 条,方案 A):定义端对 `return <本帧存储>` 的**声明感知** fail-fast。
# A2 移除了"一律拦"的旧判据(定义端分不清"交出本帧"与"借出调用方的存储"),
# 但也不能一律放:判据收敛到被调者自己的 `@life(ret(...))` —— `ret(frame)` 或
# `ret(borrows=<该形参>)` 放行;无声明 / 声明为非帧 → LifetimeError。
# 实现见 AssignGenMixin.check_return_frame_decl。
self.Gen.AssignGen.check_return_frame_decl(trans, rt.value)
GenApi.fail_if_fstring_escape(rt.value, "function return value")
# A2(§14.3 第 6 条,方案 A):定义端对 `return <本帧存储>` 的**声明感知** fail-fast。
# A2 移除了"一律拦"的旧判据(定义端分不清"交出本帧"与"借出调用方的存储"),
# 但也不能一律放:判据收敛到被调者自己的 `@life(ret(...))` —— `ret(frame)` 或
# `ret(borrows=<该形参>)` 放行;无声明 / 声明为非帧 → LifetimeError。
# 实现见 AssignGenMixin.check_return_frame_decl。
self.Gen.AssignGen.check_return_frame_decl(trans, rt.value)
# return a, b → pack an anonymous struct value (multi-return, return type tuple[T1,T2])
if rt.value.kind() == ast.ASTKind.Tuple:
rtp: ast.Tuple* = ast.Tuple*(rt.value)
@@ -453,7 +453,7 @@ novirtual class StmtGenCifMixin:
rt_head: mlirlite.ParamNode* = None
rt_tail: mlirlite.ParamNode* = None
rvi: sizet
for rvi in range(rt_n):
for rvi in range(int(rt_n)):
ev_i: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, rtp_elts.get(rvi), trans._funcs, trans._func_count, trans)
if ev_i is None:
rt_val_ok = 0
@@ -462,7 +462,7 @@ novirtual class StmtGenCifMixin:
if rt_val_ok != 0:
# build the anonymous struct type (field types = element value types)
rvi2: sizet
for rvi2 in range(rt_n):
for rvi2 in range(int(rt_n)):
rv_i: mlirlite.Value* = mlirlite.Value*((rt_vals[rvi2]))
if rv_i is None or rv_i.Ty is None:
rt_val_ok = 0
@@ -480,7 +480,7 @@ novirtual class StmtGenCifMixin:
if rt_struct_ty is not None: rt_tmp = mlirlite.build_alloca(builder, rt_struct_ty)
if rt_tmp is not None:
rvi3: sizet
for rvi3 in range(rt_n):
for rvi3 in range(int(rt_n)):
rv3: mlirlite.Value* = mlirlite.Value*(rt_vals[rvi3])
rfp: mlirlite.Value* = mlirlite.build_gep_struct(builder, rt_struct_ty, rv3.Ty, rt_tmp, rvi3)
if rfp is None:
@@ -510,6 +510,15 @@ novirtual class StmtGenCifMixin:
ret_val_ty: mlirlite.LLVMType* = None
cur_func_rv: mlirlite.Function* = trans._cur_func
if cur_func_rv is not None: ret_val_ty = mlirlite.function_get_ret_ty(cur_func_rv)
# `return <闭包变量>` 且返回注解是 t.Callable:闭包对象是 {fn_ptr, env_ptr},
# 不是裸函数指针。调用方把结果存进 t.Callable 变量后 `f()` 会按裸函数指针直调,
# 跳到堆地址崩溃(ClosureCallTest 现场)。这里明确 fail-fast,要求用 rawptr/t.CPtr。
if rt.value.kind() == ast.ASTKind.Name and trans is not None and trans.CurFuncRetAnnot is not None:
clo_nm_rv: ast.Name* = ast.Name*(rt.value)
if clo_nm_rv.id is not None and self.Gen.CallGenLeafKit.is_closure_name(clo_nm_rv.id) != 0:
clo_ret_rv: mlirlite.LLVMType* = self.Gen.TypeGenErr.try_extract_callable_ret(pool, trans.CurFuncRetAnnot, trans._imported_modules, trans._from_imports, trans)
if clo_ret_rv is not None:
self.Gen.TypeGenErr.fatal_error(rt, f"the return annotation is t.Callable (a bare function pointer) but the returned value is the closure variable '{clo_nm_rv.id}'. A closure is a (fn_ptr, env_ptr) object; storing it where a t.Callable is expected makes the caller jump into heap data. Annotate the return as rawptr / t.CPtr instead", "TypeError")
# [C1] return-annotation context: record this return's direct value node so a bare
# generic ctor (`return Stack()` in `-> Stack[int]`) specializes via CurFuncRetAnnot.
trans.CurRetNode = rt.value
@@ -581,7 +590,7 @@ novirtual class StmtGenCifMixin:
ret_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(ret_ty_rt)
val_is_ptr: int = self.Gen.ExprGenBase.is_ptr_type(val.Ty)
ret_is_ptr: int = self.Gen.ExprGenBase.is_ptr_type(ret_ty_rt)
# 按值返回聚合(数组/结构体):聚合变量在 TPV 中以 alloca 指针表示,返回类型是
# 按值返回聚合(数组/结构体):聚合变量在 VPC 中以 alloca 指针表示,返回类型是
# 聚合值时须 load 出整个聚合(与上方多返回值 struct 的 `build_load(... rt_struct_ty ...)`
# → ret 同形)。仅当被指类型与返回类型是同一聚合时成立;其余 Foo*→Foo 仍由
# coerce_to_type fail-fast,不放开隐式解引用。
@@ -663,7 +672,7 @@ novirtual class StmtGenCifMixin:
if body_cif is not None:
bc_cif: sizet = len(body_cif)
bi_cif: sizet
for bi_cif in range(bc_cif):
for bi_cif in range(int(bc_cif)):
stmt_cif: ast.AST* = body_cif.get(bi_cif)
if stmt_cif is not None: self.Gen.StmtGen.translate_stmt(trans, stmt_cif)
else:
@@ -677,7 +686,7 @@ novirtual class StmtGenCifMixin:
elif orelse_cif is not None:
oc_cif: sizet = len(orelse_cif)
oi_cif: sizet
for oi_cif in range(oc_cif):
for oi_cif in range(int(oc_cif)):
stmt_cif2: ast.AST* = orelse_cif.get(oi_cif)
if stmt_cif2 is not None: self.Gen.StmtGen.translate_stmt(trans, stmt_cif2)
return 0
@@ -729,7 +738,7 @@ novirtual class StmtGenCifMixin:
body: list[ast.AST*]* = if_node.children
if body is not None:
body_count: sizet = len(body)
for bi in range(body_count):
for bi in range(int(body_count)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
@@ -758,7 +767,7 @@ novirtual class StmtGenCifMixin:
trans._ctrl_merge_reachable = 1
mlirlite.position_at_end(builder, else_bb)
else_count: sizet = len(orelse_list)
for ei in range(else_count):
for ei in range(int(else_count)):
stmt: ast.AST* = orelse_list.get(ei)
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
if mlirlite.builder_cur_block_is_terminated(builder) == 0:
@@ -769,23 +778,23 @@ novirtual class StmtGenCifMixin:
mlirlite.build_br(builder, merge_bb)
else_branches_merge = 1
# 生命期汇合:有 else → 与 then 末状态 Meet;无 else → 与 if 前状态 Meet(else 路径=跳过)
if has_else == 1: self.Gen.VarGen.life_join(life_snap1)
else: self.Gen.VarGen.life_join(life_snap0)
# willFree 失效槽表合流:只有"到达合流的路径"参与(否则会把"分支体内 free 后 return"
# 的失效态错误地带到合流之后——fileio.vp 的 `if …: free(buf); return None` 即此现场)。
# 两分支都到达 → 求交(curr=else 末态 ∩ then 末态);
# 仅 then 到达 → 取 then 末态;
# 仅 else/无 else → 取 else 末态(无 else 时即 if 前状态)。
if has_else == 1:
if then_branches_merge != 0 and else_branches_merge != 0:
self.Gen.VarGen.freed_join(freed_snap1)
elif then_branches_merge != 0:
self.Gen.VarGen.freed_restore(freed_snap1)
else:
self.Gen.VarGen.freed_restore(freed_snap0)
# 生命期汇合:有 else → 与 then 末状态 Meet;无 else → 与 if 前状态 Meet(else 路径=跳过)
if has_else == 1: self.Gen.VarGen.life_join(life_snap1)
else: self.Gen.VarGen.life_join(life_snap0)
# willFree 失效槽表合流:只有"到达合流的路径"参与(否则会把"分支体内 free 后 return"
# 的失效态错误地带到合流之后——fileio.vp 的 `if …: free(buf); return None` 即此现场)。
# 两分支都到达 → 求交(curr=else 末态 ∩ then 末态);
# 仅 then 到达 → 取 then 末态;
# 仅 else/无 else → 取 else 末态(无 else 时即 if 前状态)。
if has_else == 1:
if then_branches_merge != 0 and else_branches_merge != 0:
self.Gen.VarGen.freed_join(freed_snap1)
elif then_branches_merge != 0:
self.Gen.VarGen.freed_restore(freed_snap1)
else:
self.Gen.VarGen.freed_restore(freed_snap0)
# 7. merge reachability (used by tail-flow checks): without else cond_br always reaches
# merge; with else, any branch br reaching merge suffices; only all-return is unreachable.
trans._ctrl_merge_reachable = 1
@@ -820,7 +829,7 @@ novirtual class StmtGenCifMixin:
mlirlite.position_at_end(builder, else_bb)
if orelse_list is not None:
n: sizet = len(orelse_list)
for i in range(n):
for i in range(int(n)):
stmt: ast.AST* = orelse_list.get(i)
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
if mlirlite.builder_cur_block_is_terminated(builder) == 0:
@@ -886,7 +895,7 @@ novirtual class StmtGenCifMixin:
body: list[ast.AST*]* = while_node.children
if body is not None:
body_count: sizet = len(body)
for bi in range(body_count):
for bi in range(int(body_count)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
@@ -924,7 +933,7 @@ novirtual class StmtGenCifMixin:
ch: list[ast.AST*]* = node.children
if ch is not None:
n: sizet = len(ch)
for bi in range(n):
for bi in range(int(n)):
c: ast.AST* = ch.get(bi)
if c is None: continue
k: int = c.kind()
@@ -937,7 +946,7 @@ novirtual class StmtGenCifMixin:
"""Return 1 if the statement list contains a break belonging to this loop level."""
if body is None: return 0
n: sizet = len(body)
for bi in range(n):
for bi in range(int(n)):
s: ast.AST* = body.get(bi)
if s is None: continue
k: int = s.kind()
@@ -1115,8 +1124,28 @@ novirtual class StmtGenCifMixin:
skc_r: ast.Constant* = ast.Constant*(step_arg_node)
if skc_r.int_val < 0: step_neg_const = 1
# 3.5 loop-var storage width follows range arg type (u64→i64 / u16→i16 / u8→i8 / else i32);
# a fixed i32 alloca truncates uint64 bounds. Applies only to undeclared loop vars.
# 3.4 range 边界统一提升到**至少 i32**(仿 C 整数提升):i8/i16 边界不再以窄位宽参与比较
# 与自增。拓宽方向由 coerce_to_type → should_zext_widen 决定:非负 define 常量是**无符号
# 位模式**(Debugger/define-constants.md §7.2),必须 zext(define N = 50000 → 16 位 0xC350
# → 50000,而非 sext 成 -15536)。u64 边界保持 i64(不动,避免截断)。
if stop_val is not None and stop_val.Ty is not None:
sb_p4: int = self.Gen.ExprGenBase.get_llvm_type_bits(stop_val.Ty)
if sb_p4 != 0 and sb_p4 < 32:
nd_s4: ast.AST* = args.get(0)
if arg_count >= 2: nd_s4 = args.get(1)
stop_val = self.Gen.ExprGenBase.coerce_to_type(builder, stop_val, i32_ty, nd_s4, trans)
if start_val is not None and start_val.Ty is not None:
sa_p4: int = self.Gen.ExprGenBase.get_llvm_type_bits(start_val.Ty)
if sa_p4 != 0 and sa_p4 < 32:
start_val = self.Gen.ExprGenBase.coerce_to_type(builder, start_val, i32_ty, args.get(0), trans)
if step_val is not None and step_val.Ty is not None:
sp_p4: int = self.Gen.ExprGenBase.get_llvm_type_bits(step_val.Ty)
if sp_p4 != 0 and sp_p4 < 32:
step_val = self.Gen.ExprGenBase.coerce_to_type(builder, step_val, i32_ty, args.get(2), trans)
# 3.5 loop-var storage width: 边界已提升到 ≥i32,所以只有 i64 边界需要 i64 循环变量,
# 其余一律 i32(不再有 u16→i16 / u8→i8 档:窄位宽循环变量会把边界截断)。
# Applies only to undeclared loop vars.
loop_store_ty: mlirlite.LLVMType* = i32_ty
range_arg_bits: int = 0
if arg_count == 1:
@@ -1128,8 +1157,6 @@ novirtual class StmtGenCifMixin:
if stop_val is not None and stop_val.Ty is not None: stop_bits_rt = self.Gen.ExprGenBase.get_llvm_type_bits(stop_val.Ty)
range_arg_bits = start_bits_rt if start_bits_rt > stop_bits_rt else stop_bits_rt
if range_arg_bits == 64: loop_store_ty = mlirlite.Int64(pool)
elif range_arg_bits == 16: loop_store_ty = mlirlite.Int16(pool)
elif range_arg_bits == 8: loop_store_ty = mlirlite.Int8(pool)
# 3.6 loop unsignedness: an unsigned loop-var annotation or any unsigned range bound uses
# icmp ult/ugt instead of slt/sgt (via ExprGenOps._node_type_unsigned).
@@ -1138,7 +1165,7 @@ novirtual class StmtGenCifMixin:
if self.Gen.ExprGenOps._node_type_unsigned(pool, target, trans) != 0: loop_unsigned = 1
if loop_unsigned == 0:
args_u: sizet
for args_u in range(arg_count):
for args_u in range(int(arg_count)):
if self.Gen.ExprGenOps._node_type_unsigned(pool, args.get(args_u), trans) != 0:
loop_unsigned = 1
break
@@ -1250,7 +1277,7 @@ novirtual class StmtGenCifMixin:
body: list[ast.AST*]* = for_node.children
if body is not None:
body_count: sizet = len(body)
for bi in range(body_count):
for bi in range(int(body_count)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
@@ -1325,6 +1352,22 @@ novirtual class StmtGenCifMixin:
return GenTypes.SEQ_KIND_STR
return 0
# _range_work_bits - range 工作位宽:取各已求值参数位宽的最大值,下限 i32(32)。
# 与 `for i in range(...)` 的 loop_store_ty 同口径,避免内部 i64 索引写回 i32 循环变量时误报窄化。
def _range_work_bits(self, a: mlirlite.Value*, b: mlirlite.Value*, c: mlirlite.Value*) -> int:
"""Working integer width for a range() source: max of the provided argument widths, at least 32."""
bits: int = 32
if a is not None and a.Ty is not None:
ab: int = self.Gen.ExprGenBase.get_llvm_type_bits(a.Ty)
if ab > bits: bits = ab
if b is not None and b.Ty is not None:
bb: int = self.Gen.ExprGenBase.get_llvm_type_bits(b.Ty)
if bb > bits: bits = bb
if c is not None and c.Ty is not None:
cb: int = self.Gen.ExprGenBase.get_llvm_type_bits(c.Ty)
if cb > bits: bits = cb
return bits
def _build_seq_loop(self, trans: HT.Translator*, for_node: ast.For*, t1: str, t2: str, src1: ast.AST*, src2: ast.AST*, n_srcs: int) -> int:
"""Build a sequence iteration loop for enumerate (n_srcs=1) or zip (n_srcs=2)."""
pool: memhub.MemBuddy* = trans.Pool
@@ -1347,34 +1390,45 @@ novirtual class StmtGenCifMixin:
l1_data: mlirlite.Value* = None
l1_esize: mlirlite.Value* = None
elem1_ty: mlirlite.LLVMType* = i8_ptr_ty
# range 的工作位宽/类型(随参数位宽,仿 `for i in range(...)`);非 range 源不使用。
r1_wty: mlirlite.LLVMType* = None
r1_bits: int = 0
if kind1 == GenTypes.SEQ_KIND_RANGE:
elem1_ty = i32_ty
r1_start = mlirlite.const_int64(pool, 0)
r1_step = mlirlite.const_int64(pool, 1)
r1_stop = mlirlite.const_int64(pool, 0)
# assign to a temp before taking args (chained cast attrs misinfer field offsets)
# 先求值参数,再由参数位宽决定工作类型:i32 字面量就按 i32 算,
# 避免内部 i64 索引写回 i32 循环变量时触发隐式窄化误报。
call_src1: ast.Call* = ast.Call*(src1)
cargs1: list[ast.AST*]* = call_src1.args
a1: mlirlite.Value* = None
b1: mlirlite.Value* = None
c1: mlirlite.Value* = None
n1: sizet = 0
if cargs1 is not None:
n1: sizet = len(cargs1)
n1 = len(cargs1)
if n1 >= 1:
sv: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs1.get(0), trans._funcs, trans._func_count, trans)
if sv is None:
a1 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs1.get(0), trans._funcs, trans._func_count, trans)
if a1 is None:
# fail-fast: range argument failed to translate
self.Gen.TypeGenErr.fatal_error(cargs1.get(0), "failed to translate the range argument", "TypeError")
r1_stop = self.Gen.ExprGenBase.coerce_to_type(builder, sv, i64_ty, cargs1.get(0))
if n1 >= 2:
sv2: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs1.get(1), trans._funcs, trans._func_count, trans)
if sv2 is None:
b1 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs1.get(1), trans._funcs, trans._func_count, trans)
if b1 is None:
# fail-fast: range argument failed to translate
self.Gen.TypeGenErr.fatal_error(cargs1.get(1), "failed to translate the range argument", "TypeError")
r1_start = self.Gen.ExprGenBase.coerce_to_type(builder, sv2, i64_ty, cargs1.get(1))
if n1 >= 3:
sv3: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs1.get(2), trans._funcs, trans._func_count, trans)
if sv3 is None:
c1 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs1.get(2), trans._funcs, trans._func_count, trans)
if c1 is None:
# fail-fast: range argument failed to translate
self.Gen.TypeGenErr.fatal_error(cargs1.get(2), "failed to translate the range argument", "TypeError")
r1_step = self.Gen.ExprGenBase.coerce_to_type(builder, sv3, i64_ty, cargs1.get(2))
r1_bits = self._range_work_bits(a1, b1, c1)
r1_wty = self.Gen.ExprGenBase._int_type_of_bits(pool, r1_bits)
if r1_wty is None: r1_wty = i32_ty
elem1_ty = r1_wty
r1_start = mlirlite.const_int(pool, r1_bits, 0)
r1_step = mlirlite.const_int(pool, r1_bits, 1)
r1_stop = mlirlite.const_int(pool, r1_bits, 0)
if a1 is not None: r1_stop = self.Gen.ExprGenBase.coerce_to_type(builder, a1, r1_wty, cargs1.get(0))
if b1 is not None: r1_start = self.Gen.ExprGenBase.coerce_to_type(builder, b1, r1_wty, cargs1.get(1))
if c1 is not None: r1_step = self.Gen.ExprGenBase.coerce_to_type(builder, c1, r1_wty, cargs1.get(2))
elif kind1 == GenTypes.SEQ_KIND_STR:
elem1_ty = i8_ty
s1_ptr = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, src1, trans._funcs, trans._func_count, trans)
@@ -1397,37 +1451,47 @@ novirtual class StmtGenCifMixin:
r2_start: mlirlite.Value* = None
r2_stop: mlirlite.Value* = None
r2_step: mlirlite.Value* = None
# range 的工作位宽/类型(source 2 对称处理)。
r2_wty: mlirlite.LLVMType* = None
r2_bits: int = 0
if n_srcs >= 2:
kind2 = self._seq_kind(trans, src2)
if kind2 == 0: self.Gen.TypeGenErr.fatal_error(for_node, "sequence iteration: source 2 type is not supported")
if kind2 == GenTypes.SEQ_KIND_RANGE:
elem2_ty = i32_ty
r2_start = mlirlite.const_int64(pool, 0)
r2_step = mlirlite.const_int64(pool, 1)
r2_stop = mlirlite.const_int64(pool, 0)
# assign to a temp before taking args (chained cast attrs misinfer field offsets)
# 与 source 1 同口径:工作类型随参数位宽,避免 i64 索引写回窄位宽循环变量误报。
call_src2: ast.Call* = ast.Call*(src2)
cargs2: list[ast.AST*]* = call_src2.args
a2: mlirlite.Value* = None
b2: mlirlite.Value* = None
c2: mlirlite.Value* = None
n2: sizet = 0
if cargs2 is not None:
n2: sizet = len(cargs2)
n2 = len(cargs2)
if n2 >= 1:
svz: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs2.get(0), trans._funcs, trans._func_count, trans)
if svz is None:
a2 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs2.get(0), trans._funcs, trans._func_count, trans)
if a2 is None:
# fail-fast: range argument failed to translate
self.Gen.TypeGenErr.fatal_error(cargs2.get(0), "failed to translate the range argument", "TypeError")
r2_stop = self.Gen.ExprGenBase.coerce_to_type(builder, svz, i64_ty, cargs2.get(0))
if n2 >= 2:
svz2: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs2.get(1), trans._funcs, trans._func_count, trans)
if svz2 is None:
b2 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs2.get(1), trans._funcs, trans._func_count, trans)
if b2 is None:
# fail-fast: range argument failed to translate
self.Gen.TypeGenErr.fatal_error(cargs2.get(1), "failed to translate the range argument", "TypeError")
r2_start = self.Gen.ExprGenBase.coerce_to_type(builder, svz2, i64_ty, cargs2.get(1))
if n2 >= 3:
svz3: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs2.get(2), trans._funcs, trans._func_count, trans)
if svz3 is None:
c2 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs2.get(2), trans._funcs, trans._func_count, trans)
if c2 is None:
# fail-fast: range argument failed to translate
self.Gen.TypeGenErr.fatal_error(cargs2.get(2), "failed to translate the range argument", "TypeError")
r2_step = self.Gen.ExprGenBase.coerce_to_type(builder, svz3, i64_ty, cargs2.get(2))
r2_bits = self._range_work_bits(a2, b2, c2)
r2_wty = self.Gen.ExprGenBase._int_type_of_bits(pool, r2_bits)
if r2_wty is None: r2_wty = i32_ty
elem2_ty = r2_wty
r2_start = mlirlite.const_int(pool, r2_bits, 0)
r2_step = mlirlite.const_int(pool, r2_bits, 1)
r2_stop = mlirlite.const_int(pool, r2_bits, 0)
if a2 is not None: r2_stop = self.Gen.ExprGenBase.coerce_to_type(builder, a2, r2_wty, cargs2.get(0))
if b2 is not None: r2_start = self.Gen.ExprGenBase.coerce_to_type(builder, b2, r2_wty, cargs2.get(1))
if c2 is not None: r2_step = self.Gen.ExprGenBase.coerce_to_type(builder, c2, r2_wty, cargs2.get(2))
elif kind2 == GenTypes.SEQ_KIND_STR:
elem2_ty = i8_ty
s2_ptr = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, src2, trans._funcs, trans._func_count, trans)
@@ -1487,7 +1551,11 @@ novirtual class StmtGenCifMixin:
mlirlite.position_at_end(builder, cond_bb)
cur_idx: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, idx_alloca)
done1: mlirlite.Value* = None
if kind1 == GenTypes.SEQ_KIND_RANGE: done1 = mlirlite.build_icmp(builder, mlirlite.ICMP_SLT, cur_idx, r1_stop)
if kind1 == GenTypes.SEQ_KIND_RANGE:
# range 的索引按工作位宽截断后再比较(索引是编译器内部量,不涉及用户窄化)
idx1_cmp: mlirlite.Value* = cur_idx
if r1_bits != 64: idx1_cmp = mlirlite.build_trunc(builder, cur_idx, r1_wty)
done1 = mlirlite.build_icmp(builder, mlirlite.ICMP_SLT, idx1_cmp, r1_stop)
elif kind1 == GenTypes.SEQ_KIND_STR:
ch_ptr1: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, s1_ptr, cur_idx)
ch1: mlirlite.Value* = mlirlite.build_load(builder, i8_ty, ch_ptr1)
@@ -1497,7 +1565,10 @@ novirtual class StmtGenCifMixin:
cont: mlirlite.Value* = done1
if n_srcs >= 2:
done2: mlirlite.Value* = None
if kind2 == GenTypes.SEQ_KIND_RANGE: done2 = mlirlite.build_icmp(builder, mlirlite.ICMP_SLT, cur_idx, r2_stop)
if kind2 == GenTypes.SEQ_KIND_RANGE:
idx2_cmp: mlirlite.Value* = cur_idx
if r2_bits != 64: idx2_cmp = mlirlite.build_trunc(builder, cur_idx, r2_wty)
done2 = mlirlite.build_icmp(builder, mlirlite.ICMP_SLT, idx2_cmp, r2_stop)
elif kind2 == GenTypes.SEQ_KIND_STR:
ch_ptr2: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, s2_ptr, cur_idx)
ch2: mlirlite.Value* = mlirlite.build_load(builder, i8_ty, ch_ptr2)
@@ -1512,7 +1583,9 @@ novirtual class StmtGenCifMixin:
# source-1 element
v1: mlirlite.Value* = None
if kind1 == GenTypes.SEQ_KIND_RANGE:
step1: mlirlite.Value* = mlirlite.build_mul(builder, cur_idx, r1_step)
idx1_b: mlirlite.Value* = cur_idx
if r1_bits != 64: idx1_b = mlirlite.build_trunc(builder, cur_idx, r1_wty)
step1: mlirlite.Value* = mlirlite.build_mul(builder, idx1_b, r1_step)
v1 = mlirlite.build_add(builder, r1_start, step1)
elif kind1 == GenTypes.SEQ_KIND_STR:
ch_ptr1b: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, s1_ptr, cur_idx)
@@ -1539,7 +1612,9 @@ novirtual class StmtGenCifMixin:
# source-2 element → t2
v2: mlirlite.Value* = None
if kind2 == GenTypes.SEQ_KIND_RANGE:
step2: mlirlite.Value* = mlirlite.build_mul(builder, cur_idx, r2_step)
idx2_b: mlirlite.Value* = cur_idx
if r2_bits != 64: idx2_b = mlirlite.build_trunc(builder, cur_idx, r2_wty)
step2: mlirlite.Value* = mlirlite.build_mul(builder, idx2_b, r2_step)
v2 = mlirlite.build_add(builder, r2_start, step2)
elif kind2 == GenTypes.SEQ_KIND_STR:
ch_ptr2b: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, s2_ptr, cur_idx)
@@ -1562,7 +1637,7 @@ novirtual class StmtGenCifMixin:
body: list[ast.AST*]* = for_node.children
if body is not None:
body_count: sizet = len(body)
for bi in range(body_count):
for bi in range(int(body_count)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
@@ -1687,7 +1762,7 @@ novirtual class StmtGenCifMixin:
body: list[ast.AST*]* = for_node.children
if body is not None:
body_count: sizet = len(body)
for bi in range(body_count):
for bi in range(int(body_count)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
@@ -1816,7 +1891,7 @@ novirtual class StmtGenCifMixin:
body: list[ast.AST*]* = for_node.children
if body is not None:
body_count: sizet = len(body)
for bi in range(body_count):
for bi in range(int(body_count)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
@@ -1971,7 +2046,7 @@ novirtual class StmtGenCifMixin:
body: list[ast.AST*]* = for_node.children
if body is not None:
body_count: sizet = len(body)
for bi in range(body_count):
for bi in range(int(body_count)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
@@ -2068,7 +2143,7 @@ novirtual class StmtGenCifMixin:
body: list[ast.AST*]* = for_node.children
if body is not None:
body_count: sizet = len(body)
for bi in range(body_count):
for bi in range(int(body_count)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
@@ -2202,7 +2277,7 @@ novirtual class StmtGenCifMixin:
body: list[ast.AST*]* = for_node.children
if body is not None:
body_count: sizet = len(body)
for bi in range(body_count):
for bi in range(int(body_count)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
trans._break_bb = old_break
+20 -21
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
@@ -37,7 +36,7 @@ novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
names: list[ast.AST*]* = gn.names
if names is None: return 0
n: sizet = len(names)
for i in range(n):
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)
@@ -51,7 +50,7 @@ novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
names: list[ast.AST*]* = nl.names
if names is None: return 0
n: sizet = len(names)
for i in range(n):
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)
@@ -121,67 +120,67 @@ novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
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(bn_pre_i): self.pre_scan_allocas(trans, ifn.children.get(bi_pre_i))
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(bn_pre_e): self.pre_scan_allocas(trans, ifn.orelse.get(bi_pre_e))
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(bn_pre_f): self.pre_scan_allocas(trans, for_n.children.get(bi_pre_f))
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(bn_pre_fe): self.pre_scan_allocas(trans, for_n.orelse.get(bi_pre_fe))
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(bn_pre_w): self.pre_scan_allocas(trans, wh_n.children.get(bi_pre_w))
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(bn_pre_we): self.pre_scan_allocas(trans, wh_n.orelse.get(bi_pre_we))
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(bn_pre_t): self.pre_scan_allocas(trans, tr_n.children.get(bi_pre_t))
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(bn_pre_th):
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(bn_pre_ehb): self.pre_scan_allocas(trans, ehc_pre.children.get(bi_pre_ehb))
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(bn_pre_to): self.pre_scan_allocas(trans, tr_n.orelse.get(bi_pre_to))
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(bn_pre_tf): self.pre_scan_allocas(trans, tr_n.finalbody.get(bi_pre_tf))
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(bn_pre_wb): self.pre_scan_allocas(trans, wth.children.get(bi_pre_wb))
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(bn_pre_mc):
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(bn_pre_mcb): self.pre_scan_allocas(trans, mcc_pre.children.get(bi_pre_mcb))
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
@@ -415,8 +414,8 @@ novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
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.
# w32.fileio and fat32_types both define FRESULT).
for ei_d in range(enum_entry.MemberCount):
# 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
@@ -445,7 +444,7 @@ novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
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(self.EnumCount):
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
@@ -456,7 +455,7 @@ novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
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(entry.MemberCount):
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
+62 -57
View File
@@ -1,7 +1,6 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import lib.core.Gen.Call.CallGenFd as CallGenFd
import t, c
from stdint import *
import ast
import mlirlite
@@ -32,6 +31,12 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
# 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)
@@ -113,7 +118,7 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
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(tn):
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)
@@ -171,49 +176,49 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
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_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:
@@ -265,7 +270,7 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
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(n_handlers_t):
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)
@@ -286,7 +291,7 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
mlirlite.position_at_end(builder, try_bb)
if tr.children is not None:
tb_n: sizet = len(tr.children)
for tbi in range(tb_n):
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
@@ -303,7 +308,7 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
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(n_handlers_t):
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:
@@ -332,7 +337,7 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
codes_buf_t[0] = c0_t
n_codes_t = 1
match_cond_t: mlirlite.Value* = None
for ci_t in range(n_codes_t):
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
@@ -371,13 +376,13 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
mlirlite.build_br(builder, after_bb)
# ---- 7. except bodies ----
for i_t in range(n_handlers_t):
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(hn2_t):
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)
@@ -387,7 +392,7 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
mlirlite.position_at_end(builder, else_bb)
if tr.orelse is not None:
en_t: sizet = len(tr.orelse)
for ei in range(en_t):
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)
@@ -527,7 +532,7 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
string.memset(ctx_class_buf, 0, 8 * n)
# 1. all items: build context, call __enter__, bind asname
for i in range(n):
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
@@ -591,12 +596,12 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
body: list[ast.AST*]* = wt.children
if body is not None:
bn: sizet = len(body)
for bi in range(bn):
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(n):
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
@@ -606,8 +611,8 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
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(n):
ci: int = n - 1 - i
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)
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+55 -11
View File
@@ -1,5 +1,4 @@
import lib.core.Gen.Generator as Generator
import t, c
from stdint import *
import ast
import mlirlite
@@ -548,7 +547,22 @@ novirtual class TranslatorGenMixin:
# resolved struct value in Ptr; pure t.CStruct keeps a value instance.
if resolved.kind() == mlirlite.LLVMType.Struct:
gv_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(resolved)
if gv_entry is not None and (gv_entry.HasNew != 0 or gv_entry.HasVTable != 0 or gv_entry.IsOOP != 0):
# [TEMP-DIAG] 模块级 struct 全局的类型决策(定位完删除):
# hasnew/hasvtable/isoop 任一为真 ⇒ 全局被包成 Ptr(指针槽),
# 与"局部变量=值本体"不一致 ⇒ `CAM = V3(...)` 被当成把栈地址交给全局。
gv_hn: int = -1; gv_hv: int = -1; gv_oo: int = -1
if gv_entry is not None:
gv_hn = gv_entry.HasNew; gv_hv = gv_entry.HasVTable; gv_oo = gv_entry.IsOOP
stdio.printf("[GVAR-DIAG] name=%s hasnew=%d hasvtable=%d isoop=%d\n",
var_name, gv_hn, gv_hv, gv_oo)
if gv_entry is not None and gv_entry.HasNew != 0:
# 只有定义了 `__new__`(自定义分配器,对象存储可能不在本帧)的类才**必须**
# 以指针表示。仅"有方法 / 带 vtable"(IsOOP / HasVTable)的类,值本体内联
# 存储同样可工作(方法调用把 &obj 作 self 传入,vptr 亦可内联在值里),
# 因此模块级全局也按**值本位**存储,与局部变量(`alloca %T`)语义一致:
# `CAM: V3` ⇒ `@CAM = global %V3`,`CAM = V3(...)` 即整块就地写入
# (不再产生"把本帧对象地址存进全局"的悬垂,也不再让
# check_return_frame_decl 误报 LifetimeError)。
# Object-class global (object-as-pointer): remember the Struct value type
# so a BSS storage global can hold the instance; otherwise @g is NULL.
resolved_stg_ty = resolved
@@ -595,8 +609,18 @@ novirtual class TranslatorGenMixin:
init_sym_node = uop_g.operand
init_kind = 5
else:
init_val = self.Gen.AssignGen.extract_cdefine_int_value(aa.value)
init_kind = 1
# 负浮点字面量 `-100.0` = UnaryOp(USub, Constant(float)):按**浮点初始化**
# 处理(init_kind=4,值稍后从源码重读,故负号会被保留);
# 否则会被送进整型常量提取器 ⇒ TypeError: CDefine value extraction failed。
is_neg_f: int = 0
if uop_g.op == ast.OpKind.USub and uop_g.operand is not None and uop_g.operand.kind() == ast.ASTKind.Constant:
opf_d: ast.Constant* = ast.Constant*(uop_g.operand)
if opf_d.const_kind == ast.CONST_FLOAT: is_neg_f = 1
if is_neg_f != 0:
init_kind = 4
else:
init_val = self.Gen.AssignGen.extract_cdefine_int_value(aa.value)
init_kind = 1
elif val_kind == ast.ASTKind.List:
init_list_node = ast.List*(aa.value)
init_kind = 2
@@ -633,8 +657,18 @@ novirtual class TranslatorGenMixin:
init_sym_node = uop_g2.operand
init_kind = 5
else:
init_val = self.Gen.AssignGen.extract_cdefine_int_value(asgn.value)
init_kind = 1
# 同 AnnAssign 分支:负浮点字面量按**浮点初始化**处理;无注解时还要把类型
# 推断为 double(否则默认 i32),否则 value 会被送进整型常量提取器 ⇒ fatal。
is_neg_f2: int = 0
if uop_g2.op == ast.OpKind.USub and uop_g2.operand is not None and uop_g2.operand.kind() == ast.ASTKind.Constant:
opf_d2: ast.Constant* = ast.Constant*(uop_g2.operand)
if opf_d2.const_kind == ast.CONST_FLOAT: is_neg_f2 = 1
if is_neg_f2 != 0:
init_kind = 4
var_ty = mlirlite.Double(pool)
else:
init_val = self.Gen.AssignGen.extract_cdefine_int_value(asgn.value)
init_kind = 1
elif val_kind2 == ast.ASTKind.List:
init_list_node = ast.List*(asgn.value)
init_kind = 2
@@ -727,12 +761,22 @@ novirtual class TranslatorGenMixin:
viperlib.snprintf(init_buf, 48, "%lld", init_val)
gv.Initializer = init_buf
elif init_kind == 2:
# List literal -> array initializer [N x elem_ty] [elem_ty val0, ...]
arr_init: str = self._build_array_initializer_text(pool, var_ty, init_list_node)
if arr_init is not None: gv.Initializer = arr_init
# 空字面量 `= []`:整块清零 ⇒ 直接 zeroinitializer。
# 若走下面的"逐元素展开",`[N]uint8` 这类大数组会生成**百万级**元素文本
# (`[i8 0, i8 0, …]`)——既撞上 MLIR 侧上限(too many global array elements),
# 又让每个元素 ~5KB 的渲染开销把编译池吃光
# (现场:n=1048576、src_head=[i8 0, i8 0, …]、[MEM] 池耗尽)。
ln_c: ast.List* = ast.List*(init_list_node)
elts_c: list[ast.AST*]* = None
if ln_c is not None: elts_c = ln_c.elts
if init_list_node is None or elts_c is None or len(elts_c) == 0:
gv.Initializer = "zeroinitializer"
else:
# Initializer build failed: error out (fail-fast) instead of falling back to zeroinitializer
self.Gen.TypeGenErr.fatal_error(node, f"failed to build the initializer of module-level variable '{var_name}'", "TypeError")
arr_init: str = self._build_array_initializer_text(pool, var_ty, init_list_node)
if arr_init is not None: gv.Initializer = arr_init
else:
# Initializer build failed: error out (fail-fast) instead of falling back
self.Gen.TypeGenErr.fatal_error(node, f"failed to build the initializer of module-level variable '{var_name}'", "TypeError")
elif init_kind == 3:
# String literal initializer: for array types (t.CArray[char, N]) use a byte-array
# initializer (content + zero padding); a string-pointer GEP initializer would mismatch.
+22 -8
View File
@@ -1,15 +1,11 @@
import lib.core.Gen.Type.TypeGenMixin as TypeGenMixin
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import t, c
from stdint import *
import ast
import mlirlite
import memhub
import hashtable
import string
import viperlib
import stdio
import stdlib
import lib.core.VLogger as VLogger
import lib.Stub.StubMerger as StubMerger
@@ -28,6 +24,11 @@ novirtual class TypeGenBase:
Gen: Generator.Generator*
TraceFrames: GenTypes.TraceFrame* # malloc array (TypeGenMixin.TRACE_MAX slots)
TraceCount: int
# --- 当前实参位置:翻译调用实参时记录。报错节点是**合成**的(lineno==0,如构造器内联出的
# 参数赋值)时,用它兜底定位到具体表达式(如 `&_on_submit`),而不是回退到函数定义行。---
CurArgFile: str
CurArgLine: int
CurArgCol: int
# --- Global generic specialization context (was _g_generic_tp_names/_g_generic_type_args) ---
GenericTpNames: list[str]*
@@ -40,10 +41,13 @@ novirtual class TypeGenBase:
# --- Global CDefine compile-time constant table (was _g_cdefine_*) ---
CDefineNames: list[str]*
CDefineValues: list[str]*
# 浮点常量的**宽度种类**:1=f64(double,默认),2=f32(float);0=非浮点(整型/字符串)。
# 判定"是不是浮点常量"一律用 `!= 0`(既有调用处口径不变),只有浮点常量物化处区分 64/32:
# `define PI: float = 3.14159...` 必须直接出 f32 常量,否则与 f32 字面量混算会多出 fpext。
CDefineDoubleFlags: list[int]*
CDefineStrFlags: list[int]* # 1=string value (e.g. ZLIB_VERSION: t.CDefine = "1.3.2")
# 值表达式的**位宽**(bit);0=未知(按值推断)。来源:右侧显式强转(如 WORD(0x0080))优先,
# 否则声明注解(define X: WORD = ... 的 T)。物化时据此定宽,避免小位宽常量被提升为 i32。
CDefineStrFlags: list[int]* # 1=string value (e.g. ZLIB_VERSION: t.CDefine = "1.3.2")
# 值表达式的**位宽**(bit);0=未知(按值推断)。来源:右侧显式强转(如 WORD(0x0080))优先,
# 否则声明注解(define X: WORD = ... 的 T)。物化时据此定宽,避免小位宽常量被提升为 i32。
CDefineBits: list[int]*
CDefineIsDouble: int
CDefineIsStr: int
@@ -58,6 +62,7 @@ novirtual class TypeGenBase:
# Not cleared by reset_to(mark2)/clear_cdefine_constants, so CONST_INT lookup happens once.
XmodCdefNames: list[str]*
XmodCdefValues: list[str]*
# 同 CDefineDoubleFlags 的编码:1=f64,2=f32,0=非浮点。
XmodCdefDoubleFlags: list[int]*
XmodCdefStrFlags: list[int]*
@@ -107,6 +112,9 @@ novirtual class TypeGenBase:
if self is None: return 1
self.TraceFrames = None
self.TraceCount = 0
self.CurArgFile = None
self.CurArgLine = 0
self.CurArgCol = 0
self.GenericTpNames = None
self.GenericTypeArgs = None
self.GenericCtxStack = None
@@ -114,7 +122,7 @@ novirtual class TypeGenBase:
self.CDefineNames = None
self.CDefineValues = None
self.CDefineDoubleFlags = None
self.CDefineStrFlags = None
self.CDefineStrFlags = None
self.CDefineBits = None
self.CDefineIsDouble = 0
self.CDefineIsStr = 0
@@ -199,6 +207,12 @@ novirtual class TypeGenBase:
frame.lineno = 0
frame.col_start = 0
frame.col_end = 0
# set_cur_arg - 记录正在翻译的实参位置(file/line/col),供“合成节点无行号”时兜底定位。
def set_cur_arg(self, file_name: str, lineno: int, col_start: int) -> None:
"""Remember the argument position currently being translated."""
self.CurArgFile = file_name
self.CurArgLine = lineno
self.CurArgCol = col_start
def clear_trace(self) -> None:
"""Clear the trace stack (called before translating each file)."""
for i in range(int(self.TraceCount)):
+44 -16
View File
@@ -1,15 +1,9 @@
import t, c
from stdint import *
import ast
import mlirlite
import memhub
import string
import viperlib
import stdio
import stdlib
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.Stub.StubMerger as StubMerger
import lib.core.CompilerFactory as CompilerFactory
@@ -23,6 +17,14 @@ define CDEF_IDX_FNV_OFFSET = 2166136261
define CDEF_IDX_FNV_PRIME = 16777619
define CDEF_IDX_FNV_MASK = 4294967295
# CDefine 浮点常量的宽度种类(CDefineDoubleFlags / XmodCdefDoubleFlags 条目编码)。
# 1=f64(double,默认),2=f32(float);0 表示非浮点。既有读取处一律按 `!= 0` 判定
# "是浮点常量",故 f32 天然兼容;只有浮点常量的物化处需要区分 64/32。
# 必须写明 `: int`:无注解 define 按**值的最小可存宽度**物化(1/2 → i8),
# 直接当实参调用 `list[int].append(x: int)` 会落成 `i8 != i32`(mlir-opt 校验失败)。
define CDEFINE_FLOAT_KIND_F64: int = 1
define CDEFINE_FLOAT_KIND_F32: int = 2
# TypeGenCDef - split from TypeGenMixin (stage D): CDefine compile-time constant table (register/lookup/is/set/clear)
novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
@@ -148,8 +150,12 @@ novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
if self.CDefineDoubleFlags is not None: self.CDefineDoubleFlags.append(0)
if self.CDefineStrFlags is not None: self.CDefineStrFlags.append(0)
if self.CDefineBits is not None: self.CDefineBits.append(bits)
def register_cdefine_double(self, pool: memhub.MemBuddy*, name: str, value: double) -> None:
"""Register a float CDefine constant; store the value as %f text and flag it."""
def register_cdefine_double(self, pool: memhub.MemBuddy*, name: str, value: double, is_f32: int = 0) -> None:
"""Register a float CDefine constant; store the value as %f text and flag it.
is_f32: 1 表示该常量声明为 32 位 float(`define PI: float = ...` / `float(...)`),
物化时直接出 f32 常量;0(默认)表示 double。
"""
if name is None: return
if self.CDefineNames is None:
tp: memhub.MemBuddy* = self._cdefine_table_pool(pool)
@@ -185,7 +191,10 @@ novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
stdio.snprintf(val_buf, 32, "%.17g", value)
self.CDefineValues.append(val_buf)
else: self.CDefineValues.append("0")
if self.CDefineDoubleFlags is not None: self.CDefineDoubleFlags.append(1)
# 浮点宽度种类:f32=2,f64=1(判定处一律 `!= 0`,故与旧数据兼容)。
if self.CDefineDoubleFlags is not None:
if is_f32 != 0: self.CDefineDoubleFlags.append(CDEFINE_FLOAT_KIND_F32)
else: self.CDefineDoubleFlags.append(CDEFINE_FLOAT_KIND_F64)
if self.CDefineStrFlags is not None: self.CDefineStrFlags.append(0)
if self.CDefineBits is not None: self.CDefineBits.append(0)
# fail-fast: a failed CDefine value buffer must abort compilation
@@ -241,7 +250,11 @@ novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
self.CDefineFound = 1
# Cross-module CDefine lookup cache (PermPool-allocated, reused across files)
def register_xmod_cdefine(self, name: str, value_str: str, is_double: int, is_str: int) -> None:
"""Cache a cross-module CDefine lookup result in the perm_pool global cache."""
"""Cache a cross-module CDefine lookup result in the perm_pool global cache.
is_double 沿用 CDefineDoubleFlags 的编码(1=f64,2=f32,0=非浮点),
这样跨模块读取 float 声明的常量时也能保持 f32。
"""
if name is None or value_str is None: return
# Dedupe: skip if cached (first lookup wins; CDefines are globally unique)
if self.XmodCdefNames is not None:
@@ -285,7 +298,8 @@ novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
if val_str is not None:
self.CDefineFound = 1
if self.XmodCdefStrFlags is not None and self.XmodCdefStrFlags.get(i) == 1: self.CDefineIsStr = 1
elif self.XmodCdefDoubleFlags is not None and self.XmodCdefDoubleFlags.get(i) == 1: self.CDefineIsDouble = 1
elif self.XmodCdefDoubleFlags is not None and self.XmodCdefDoubleFlags.get(i) != 0:
self.CDefineIsDouble = int(self.XmodCdefDoubleFlags.get(i))
return val_str
return None
def lookup_cdefine_constant(self, name: str) -> int64:
@@ -316,8 +330,9 @@ novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
# callers should use lookup_cdefine_string instead.
self.CDefineIsStr = 1
return 0
if self.CDefineDoubleFlags is not None and self.CDefineDoubleFlags.get(i) == 1:
self.CDefineIsDouble = 1
if self.CDefineDoubleFlags is not None and self.CDefineDoubleFlags.get(i) != 0:
# 浮点常量(f64=1 / f32=2):把种类原样带给 is_cdefine_double()/is_cdefine_float32()
self.CDefineIsDouble = int(self.CDefineDoubleFlags.get(i))
return string.atoll(val_str)
return string.atoll(val_str)
return 0
@@ -368,8 +383,9 @@ novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
if self.CDefineStrFlags is not None and self.CDefineStrFlags.get(i) == 1:
self.CDefineIsStr = 1
return 0.0
if self.CDefineDoubleFlags is not None and self.CDefineDoubleFlags.get(i) == 1:
self.CDefineIsDouble = 1
if self.CDefineDoubleFlags is not None and self.CDefineDoubleFlags.get(i) != 0:
# 种类原样带回(1=f64 / 2=f32),物化处据此决定出 f64 还是 f32 常量。
self.CDefineIsDouble = int(self.CDefineDoubleFlags.get(i))
ret_d: double = string.atof(val_str)
return ret_d
ret_i2: double = double(string.atoll(val_str))
@@ -409,6 +425,18 @@ novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
def is_cdefine_double(self) -> int:
"""Whether the last CDefine hit was a float constant."""
return self.CDefineIsDouble
def is_cdefine_float32(self) -> int:
"""Whether the last CDefine hit was an f32 (`float`) constant; 0 = f64/非浮点。
物化浮点常量时必须先看本函数:`define PI: float = ...` 要直接出 f32 常量,
否则与 f32 字面量混算会被提升成 double(现场:33 处 cvtss2sd)。
"""
if self.CDefineIsDouble == CDEFINE_FLOAT_KIND_F32: return 1
return 0
def xmod_float_kind(self) -> int:
"""跨模块缓存写入用的浮点种类(1=f64 / 2=f32):把编码留在本模块,写入方不必跨模块引用 define。"""
if self.CDefineIsDouble == CDEFINE_FLOAT_KIND_F32: return CDEFINE_FLOAT_KIND_F32
return CDEFINE_FLOAT_KIND_F64
def is_cdefine_found(self) -> int:
"""Whether the last CDefine lookup found a value."""
return self.CDefineFound
@@ -420,7 +448,7 @@ novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
if self.CDefineNames is not None: self.CDefineNames.clear()
if self.CDefineValues is not None: self.CDefineValues.clear()
if self.CDefineDoubleFlags is not None: self.CDefineDoubleFlags.clear()
if self.CDefineStrFlags is not None: self.CDefineStrFlags.clear()
if self.CDefineStrFlags is not None: self.CDefineStrFlags.clear()
if self.CDefineBits is not None: self.CDefineBits.clear()
# Clear index slots (store index+1; 0=empty) and reset Built to 0 (empty table and index
# in sync, matching a freshly built table before its first register).
File diff suppressed because it is too large. Load diff
+39 -17
View File
@@ -1,6 +1,4 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import t, c
from stdint import *
import ast
import mlirlite
@@ -8,13 +6,7 @@ import memhub
import string
import viperlib
import stdio
import stdlib
import sys
import w32.fileio as fileio
import lib.core.VLogger as VLogger
import lib.core.Diag as Diag
import lib.Stub.StubMerger as StubMerger
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.Translator as HT
import lib.core.Gen.Type.TypeGenBase as TypeGenBase
@@ -47,10 +39,23 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
# 当前帧(错误点)位置:优先 node,其次调用栈最内层有行号的帧,最后 current_file。
lineno: int = 0
if node is not None: lineno = node.lineno
raw_line: int = lineno # [TEMP-DIAG] 兜底前的原始节点行号(定位完删除)
file_name: str = self.Gen.TypeGenResolve.get_current_file()
if file_name is None: file_name = "<unknown>"
# Line-number fallback: cloned/synthesized nodes may have lineno 0 (e.g. ast.Arg built by
# generic specialization hardcodes 0). Fall back to the trace stack's last valid frame.
# 报错位置兜底(“节点位置不可信”时优先用正在翻译的实参位置,精确定位到
# `&_on_submit` 这类表达式;见 TypeGenBase.set_cur_arg)。判据取宽:节点无行号、
# 或**没有更深的可用位置**(节点行号不深于栈最内层帧的行号 —— 函数体里的真实
# 表达式行号必然大于函数定义行,而 FunctionDef 自身恰好等于定义行/更浅),
# 或栈为空(顶层函数体翻译时可能没有帧)时,都采用实参位置。
deeper_known: int = 0
if lineno > 0 and self.Gen.TypeGenBase.TraceCount > 0:
tfc: GenTypes.TraceFrame* = self.Gen.TypeGenBase._get_trace_frame(self.Gen.TypeGenBase.TraceCount - 1)
if tfc is not None and tfc.lineno > 0 and lineno > tfc.lineno: deeper_known = 1
if (lineno <= 0 or deeper_known == 0) and self.Gen.TypeGenBase.CurArgLine > 0:
if self.Gen.TypeGenBase.CurArgFile is not None:
file_name = self.Gen.TypeGenBase.CurArgFile
lineno = self.Gen.TypeGenBase.CurArgLine
col_start = self.Gen.TypeGenBase.CurArgCol
if lineno <= 0 and self.Gen.TypeGenBase.TraceCount > 0:
ti_b: int = self.Gen.TypeGenBase.TraceCount - 1
while ti_b >= 0:
@@ -72,9 +77,17 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
Diag.finish()
return 0
# [TEMP-DIAG] 定位“报错位置”问题用(定位完删除):把决策输入打出来
# node=节点原始行号(0=合成) curarg=当前实参行号 trace=栈深 top=栈最内层行号
ti_top: int = -1
if self.Gen.TypeGenBase.TraceCount > 0:
tf_d: GenTypes.TraceFrame* = self.Gen.TypeGenBase._get_trace_frame(self.Gen.TypeGenBase.TraceCount - 1)
if tf_d is not None: ti_top = tf_d.lineno
stdio.printf("[TEMP-DIAG] node=%d curarg=%d trace=%d top=%d -> shown=%d\n",
raw_line, self.Gen.TypeGenBase.CurArgLine, self.Gen.TypeGenBase.TraceCount, ti_top, lineno)
Diag.begin(ent, err_type, msg)
# 调用链帧(按"自外向内"顺序)**由本类遍历**(只有它拿得到 TraceFrame),填入 entry。
for i in range(self.Gen.TypeGenBase.TraceCount):
for i in range(int(self.Gen.TypeGenBase.TraceCount)):
frame: GenTypes.TraceFrame* = self.Gen.TypeGenBase._get_trace_frame(i)
if frame is not None and frame.file_name is not None:
Diag.add_frame(ent, frame.file_name, frame.lineno)
@@ -102,10 +115,19 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
# The subscript test uses TypeGenResolve as the single source of truth (the t.Callable
# Attribute form is equivalent to the bare-name callable / Callable forms)
if self.Gen.TypeGenResolve._IsCallableSubscriptValue(sub_cr.value) == 0: return None
# slice must be Tuple(elts=[args_list, ret_node])
# slice must be Tuple(elts=[args_list, ret_node]); the one-element form
# `callable[[A, B]]` omits the return type and means the default int (i32) — the same
# degradation a missing annotation gets elsewhere. It must still register, otherwise the
# indirect call fails fast with "Return type ... is not registered".
if sub_cr.slice is None or sub_cr.slice.kind() != ast.ASTKind.Tuple: return None
tp_cr: ast.Tuple* = ast.Tuple*(sub_cr.slice)
if tp_cr.elts is None or len(tp_cr.elts) != 2: return None
if tp_cr.elts is None: return None
# The one-element form `callable[[A, B]]` omits the return type, which means **void**
# (no return value). It must still register, so the indirect call `f()` is statically
# typed as void and using it as a value (e.g. `v: int = f()`) is rejected instead of
# silently assuming int.
if len(tp_cr.elts) == 1: return mlirlite.Void(pool)
if len(tp_cr.elts) != 2: return None
ret_node_cr: ast.AST* = tp_cr.elts.get(1)
if ret_node_cr is None: return None
return self.Gen.TypeGenResolve.resolve_annotation_type(pool, ret_node_cr, imported_modules, from_imports, trans, 0)
@@ -214,7 +236,7 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
ret_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
bn: sizet = len(body)
for bi in range(bn):
for bi in range(int(bn)):
stmt: ast.AST* = body.get(bi)
if stmt is not None and stmt.kind() == ast.ASTKind.Return:
rt: ast.Return* = ast.Return*(stmt)
@@ -229,7 +251,7 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
"""Whether the body has a return of a non-None value, recursively checking control-flow statements but skipping nested defs."""
if body is None: return 0
bn: sizet = len(body)
for bi in range(bn):
for bi in range(int(bn)):
stmt: ast.AST* = body.get(bi)
if stmt is None: continue
k: int = stmt.kind()
@@ -260,7 +282,7 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
# name + ':' + bits + ',', plus NUL. Use an upper bound, not an exact size.
bound: sizet = 1 # trailing NUL
pre_i: sizet
for pre_i in range(an):
for pre_i in range(int(an)):
pre_arg: ast.Arg* = ast.Arg*(alist.get(pre_i))
if pre_arg is None or pre_arg.arg is None: continue
bound += string.strlen(pre_arg.arg) + 1 + 12 + 1
@@ -271,7 +293,7 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
# 2) Single pass: resolve type -> format entry in place -> append immediately (no truncation)
cnt: sizet = 0
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
for ai in range(an):
for ai in range(int(an)):
arg: ast.Arg* = ast.Arg*(alist.get(ai))
if arg is None or arg.arg is None: continue
param_ty: mlirlite.LLVMType* = i32_ty
-13
View File
@@ -1,17 +1,4 @@
import t, c
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import sys
import stdlib
import viperlib
import w32.fileio as fileio
import lib.core.CompilerFactory as CompilerFactory
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.core.Gen.Type.TypeGenBase as TypeGenBase
File diff suppressed because it is too large. Load diff
+9 -12
View File
@@ -1,4 +1,3 @@
import t, c
from stdint import *
import mlirlite
import memhub
@@ -42,7 +41,7 @@ def _spec_name_eq(text: str, start: sizet, end: sizet, lit: str) -> int:
if end <= start: return 0
ln: sizet = string.strlen(lit)
if end - start != ln: return 0
for k in range(ln):
for k in range(int(ln)):
if text[start + k] != lit[k]: return 0
return 1
@@ -115,8 +114,7 @@ def SpecSignFromTypeName(name: str) -> int:
# TypeSpec - type descriptor. PtrLevel alone is ambiguous for Ptr(i8), so element
# semantics use the (ElemBits, IsElemPtr, DerefBase) triple replacing IsPtrElement.
@t.NoVTable
class TypeSpec:
novirtual class TypeSpec:
"""Unified type descriptor"""
Kind: int # one of TS_*
Bits: int # bit width; 0 for non-int/float
@@ -140,8 +138,7 @@ class TypeSpec:
# TypeSpecArena - interning table (object-owned, not global): Keys/Specs/Count/Cap.
@t.NoVTable
class TypeSpecArena:
novirtual class TypeSpecArena:
"""TypeSpec interning table; dedups by canonical text key (one instance per type)"""
Keys: bytes
Specs: sizet*
@@ -185,7 +182,7 @@ def _spec_index_find(arena: TypeSpecArena*, key: str, h: uint64) -> int:
if arena.Index is None or arena.IndexCap <= 0: return -1
mask: sizet = sizet(arena.IndexCap - 1)
idx: sizet = sizet(h) & mask
for i in range(arena.IndexCap):
for i in range(int(arena.IndexCap)):
slot: sizet* = (sizet*)(uint64(arena.Index) + uint64(idx * 8))
sv: sizet = slot[0]
if sv == 0: return -1
@@ -203,7 +200,7 @@ def _spec_index_insert(arena: TypeSpecArena*, key: str, h: uint64, ei: int) -> i
if arena.Index is None or arena.IndexCap <= 0: return 1
mask: sizet = sizet(arena.IndexCap - 1)
idx: sizet = sizet(h) & mask
for i in range(arena.IndexCap):
for i in range(int(arena.IndexCap)):
slot: sizet* = (sizet*)(uint64(arena.Index) + uint64(idx * 8))
if slot[0] == 0:
slot[0] = sizet(ei + 1)
@@ -422,7 +419,7 @@ def _spec_to_text(sp: TypeSpec*, buf: str, buf_size: sizet, depth: int) -> int:
pos: sizet = 0
pos = _spec_text_append(buf, buf_size, pos, "fn(")
if pos >= buf_size: return 1
for i in range(sp.ParamCount):
for i in range(int(sp.ParamCount)):
if i > 0:
pos = _spec_text_append(buf, buf_size, pos, ",")
if pos >= buf_size: return 1
@@ -455,7 +452,7 @@ def _spec_text_append(buf: str, buf_size: sizet, pos: sizet, text: str) -> sizet
"""Append text at pos in buf; buf_size sentinel when full, else the new position"""
if buf is None or text is None: return buf_size
tlen: sizet = string.strlen(text)
for i in range(tlen):
for i in range(int(tlen)):
if pos + 1 >= buf_size:
buf[pos] = '\0'
return buf_size
@@ -618,7 +615,7 @@ def InternSpec(arena: TypeSpecArena*, sp: TypeSpec*) -> TypeSpec*:
return (TypeSpec*)(uint64(slot_e[0]))
if arena.Index is None:
# index unavailable: fall back to the linear scan
for i in range(arena.Count):
for i in range(int(arena.Count)):
exist_key: str = _spec_key_at(arena.Keys, i)
if exist_key is not None and string.strcmp(exist_key, key_buf) == 0:
slot_e2: sizet* = (sizet*)(uint64(arena.Specs) + uint64(i * 8))
@@ -871,7 +868,7 @@ def _spec_llvm_text(sp: TypeSpec*, buf: str, buf_size: sizet, depth: int) -> int
if sp.Kind == TS_FUNC:
if _spec_llvm_text(sp.Ret, buf, buf_size, depth + 1) != 0: return 1
if _spec_llvm_append(buf, buf_size, " (") != 0: return 1
for i in range(sp.ParamCount):
for i in range(int(sp.ParamCount)):
if i > 0:
if _spec_llvm_append(buf, buf_size, ", ") != 0: return 1
ps: TypeSpec* = _spec_param_at(sp, i)
+31 -5
View File
@@ -3,7 +3,6 @@ import lib.core.Gen.Generator as Generator
import lib.core.Gen.Type.TypeSpec as TSpec
from MatchGenMixin import SCOPE_MATCH
import lib.core.Gen.Translator as HT
import t, c
from stdint import *
import ast
import mlirlite
@@ -216,6 +215,23 @@ novirtual class VarGenMixin:
if entry is None: return 0
return entry.IsConst
# set_var_byte_order - register a local variable's byte order (VarEntry.ByteOrder) from the
# `T | t.BigEndian` / `T | t.LittleEndian` annotation union: 1=BigEndian / 2=LittleEndian.
# The value is only 1/2 when a marker is present, so the local load/store paths bswap only then.
def set_var_byte_order(self, symtab: GenTypes.SymbolTable*, name: str, byte_order: int) -> int:
"""Set a local variable's byte order (VarEntry.ByteOrder), return 0=success / 1=failure"""
if symtab is None or name is None: return 1
entry: GenTypes.VarEntry* = self.lookup_var_entry(symtab, name)
if entry is None: return 1
entry.ByteOrder = byte_order
return 0
# var_byte_order - whether a variable is big/little-endian (unified read entry for load/store).
def var_byte_order(self, entry: GenTypes.VarEntry*) -> int:
"""The variable entry's byte order (0=native / 1=BigEndian / 2=LittleEndian; 0 when None)"""
if entry is None: return 0
return entry.ByteOrder
# ============================================================
# 生命期标签读写(LifeTag,见 GenTypes define LIFE_*;架构见 ownership-and-new.md §11)
# ============================================================
@@ -320,6 +336,8 @@ novirtual class VarGenMixin:
entry.IsConst = 0
# 生命期标签显式初始化:未登记 = LIFE_UNKNOWN(保守放行)。
entry.Life = GenTypes.LIFE_UNKNOWN
# 字节序显式初始化:未标注 = 0 (native),只有 set_var_byte_order 才置 1/2(避免槽复用残留)。
entry.ByteOrder = 0
# [S3 dual write] Value-type Spec (alloca.Ty is Ptr(value type), so take its pointee)
entry.Spec = self._spec_of_var_alloca(alloca)
return 0
@@ -915,7 +933,7 @@ novirtual class VarGenMixin:
# ---- repl_try_session_func_call - indirect call to a session function (M3 core) ----
# On a hit, load the address from the ctx slot, prepend ctx, coerce args to the table signature, and call_indirect (hit=1).
def repl_try_session_func_call(self, trans: HT.Translator*, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, func_name: str, cargs: list[ast.AST*]*, can: int, kw_count: int, node: ast.AST*, hit: t.CPtr) -> mlirlite.Value*:
def repl_try_session_func_call(self, trans: HT.Translator*, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, func_name: str, cargs: list[ast.AST*]*, can: int, kw_count: int, node: ast.AST*, hit: rawptr) -> mlirlite.Value*:
"""Session function call: return the call result on hit (void returns None but hit is set)"""
if trans is None or trans.ReplCtx is None or pool is None or builder is None: return None
if func_name is None or hit is None: return None
@@ -946,7 +964,7 @@ novirtual class VarGenMixin:
self.Gen.TypeGenErr.fatal_error(node, f"REPL: failed to read the address of session function '{func_name}'", "InternalCompilerError")
return None
# (2) Resolve param types before building the signature (token -> LLVM type; unsupported -> error)
ptypes: t.CPtr = pool.alloc(8 * GenTypes.REPL_FUNC_PARAM_MAX)
ptypes: rawptr = pool.alloc(8 * GenTypes.REPL_FUNC_PARAM_MAX)
if ptypes is None: return None
string.memset(ptypes, 0, 8 * GenTypes.REPL_FUNC_PARAM_MAX)
ai_chk: int
@@ -1328,7 +1346,7 @@ novirtual class VarGenMixin:
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
pv: mlirlite.Value* = val
if val.Ty is not None and self.Gen.ExprGenBase.is_ptr_type(val.Ty) != 0 and self.Gen.AssignGen._stg_ty_equal(pool, val.Ty, i8_ptr_ty) == 0:
# Skip when already an i8 pointer (str/bytes/t.CPtr) - LLVM rejects same-type bitcast
# Skip when already an i8 pointer (str/bytes/rawptr) - LLVM rejects same-type bitcast
pv = mlirlite.build_bitcast(builder, val, i8_ptr_ty)
if pv is None: return 1
# Pointer -> i64 (ptrtoint): the host reads it as an integer then inttoptrs it back to an address
@@ -1517,7 +1535,15 @@ novirtual class VarGenMixin:
return 1
cur_val: mlirlite.Value* = self.repl_try_read(trans, name, node)
if cur_val is None: return 1
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, rhs_node, None, 0, trans)
# 与 `x = x op y` 同口径:RHS 在左值槽的 LLVM 类型(target_ty)上下文中翻译,使嵌套 binop /
# CDefine 的目标感知与常量适配生效,避免 REPL 会话变量的增强赋值与二元形态口径不一致
# (对齐 AssignGenMixin 的字段/下标/解引用/变量四个站点)。cur_val 已由 repl_try_read 成功
# 加载,故槽类型必可解析;缺失即内部错误 fail-fast(绝不退回 None 或 i32 兜底)。
slot_ty: mlirlite.LLVMType* = self.repl_slot_ty(pool, entry)
if slot_ty is None:
self.Gen.TypeGenErr.fatal_error(node, f"REPL: the slot type of session variable '{name}' is not available", "TypeError")
return 1
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, rhs_node, None, 0, trans, slot_ty)
if rhs_val is None:
self.Gen.TypeGenErr.fatal_error(node, "REPL: failed to translate the right-hand side of the augmented assignment", "TypeError")
return 1
+404 -393
View File
@@ -1,393 +1,404 @@
import t, c
from stdint import *
import memhub
import string
import stdio
import stdlib
import w32.win32file as win32file
import w32.win32base as win32base
import hashlib
import viperlib
import lib.Projectrans.Utils as Utils
import lib.core.VLogger as VLogger
import lib.Projectrans.Config as Config
# IncludesScanner - recursive scan of the includes directory (Win32 FindFirstFileA/
# FindNextFileA); collects .vp paths and SHA1s for Phase1 stub gen / Phase2 merge.
# global mbuddy pointer
_mbuddy: memhub.MemBuddy*
# max file path length
define MAX_PATH_LEN = 512
# max files per scan
define MAX_FILES = 1024
# FileEntry - file entry (path + SHA1)
novirtual class FileEntry:
"""A scanned file entry."""
Path: str # absolute file path
Sha1: str # first 16 SHA1 chars (16 bytes + null)
RelPath: str # path relative to the includes root
ModuleName: str # module name (e.g. "ast.parser")
IncDir: str # owning includes root dir (multi-root support)
# ScanResult - scan result
novirtual class ScanResult:
"""A collection of scan results."""
Entries: FileEntry* # FileEntry array
Count: int
Capacity: int
# create_scan_result - create the scan result container
def create_scan_result(pool: memhub.MemBuddy*) -> ScanResult*:
"""Create the scan result container with MAX_FILES preallocated slots."""
if pool is None: return None
size: sizet = MAX_FILES * FileEntry.__sizeof__()
# use stdlib.malloc to avoid exhausting the mbuddy pool (consistent with StubMerger)
entries: FileEntry* = stdlib.malloc(size)
if entries is None: return None
string.memset(entries, 0, size)
result: ScanResult* = stdlib.malloc(ScanResult.__sizeof__())
if result is None: return None
string.memset(result, 0, ScanResult.__sizeof__())
result.Entries = entries
result.Count = 0
result.Capacity = MAX_FILES
return result
# add_file_entry - append a file entry to the scan result
def add_file_entry(result: ScanResult*, pool: memhub.MemBuddy*, abs_path: str, rel_path: str, sha1: str, inc_dir: str) -> int:
"""Append a file entry to the scan result; inc_dir is the owning includes root."""
if result is None or pool is None: return 1
if result.Count >= result.Capacity: return 1
# compute module name: '/' -> '.', strip .vp/.py (Utils handles __init__.vp)
mod_buf: str = Utils.PathToModuleName(rel_path, 0)
if mod_buf is None: return 1
# get the entry address
entry_size: sizet = FileEntry.__sizeof__()
entry_addr: uint64 = uint64(result.Entries) + result.Count * entry_size
entry: FileEntry* = (FileEntry*)(entry_addr)
if entry is None: return 1
# copy the path string
abs_len: sizet = string.strlen(abs_path)
abs_buf: str = stdlib.malloc(abs_len + 1)
if abs_buf is None: return 1
string.strcpy(abs_buf, abs_path)
entry.Path = abs_buf
# copy the SHA1
sha1_buf: str = stdlib.malloc(17)
if sha1_buf is None: return 1
string.strcpy(sha1_buf, sha1)
entry.Sha1 = sha1_buf
# copy the relative path
rel_buf: str = stdlib.malloc(string.strlen(rel_path) + 1)
if rel_buf is None: return 1
string.strcpy(rel_buf, rel_path)
entry.RelPath = rel_buf
# copy the module name
entry.ModuleName = mod_buf
# copy the owning includes root dir (used by Phase2 path joining)
if inc_dir is not None:
inc_len: sizet = string.strlen(inc_dir)
inc_buf: str = stdlib.malloc(inc_len + 1)
if inc_buf is None: return 1
string.strcpy(inc_buf, inc_dir)
entry.IncDir = inc_buf
result.Count += 1
return 0
# compute_file_sha1 - read file content and compute SHA1 (CRLF->LF, matching Projectrans.py)
def compute_file_sha1(pool: memhub.MemBuddy*, file_path: str) -> str:
"""Read a file, normalize CRLF to LF, and return the first 16 SHA1 hex chars."""
if pool is None or file_path is None: return None
# read file content; BUF_SIZE must match SRC_BUF_SIZE (1MB) or trailing edits won't change SHA1
BUF_SIZE: sizet = 1048576
buf: bytes = Utils.read_file_to_buf(file_path, BUF_SIZE)
if buf is None: return None
# strip \r in place (CRLF -> LF), matching Projectrans.py text-mode reads
buf_len: sizet = string.strlen(buf)
write_pos: sizet = 0
for ch in buf:
if ch != '\r':
buf[write_pos] = ch
write_pos += 1
# add a null terminator
buf[write_pos] = 0
# compute SHA1
ctx: hashlib.sha1* = hashlib.sha1()
if ctx is None:
stdlib.free(buf)
return None
ctx.update(buf)
digest: bytes = stdlib.malloc(hashlib.SHA1_DIGEST_LEN)
if digest is None:
stdlib.free(buf)
return None
ctx.final(digest)
# convert to hex (first 8 bytes = 16 hex chars; stdlib allocation unchanged)
hex_buf: str = Utils.bytes_to_hex(pool, digest, 8, 0, 1)
stdlib.free(buf)
stdlib.free(digest)
return hex_buf
# scan_directory_recursive - recursively scan a directory (FindFirstFileA/FindNextFileA)
def scan_directory_recursive(pool: memhub.MemBuddy*, root_dir: str, rel_prefix: str, result: ScanResult*, inc_dir: str) -> int:
"""Recursively collect .vp/.py files under a directory; inc_dir is the includes root."""
if pool is None or root_dir is None or result is None: return 1
# subdir recursion failure count (not ignored; fail-fast at the summary)
subdir_failed: int = 0
# build search pattern: root_dir/*
root_len: sizet = string.strlen(root_dir)
pattern: bytes = stdlib.malloc(root_len + 4)
if pattern is None: return 1
viperlib.snprintf(pattern, root_len + 4, "%s/*", root_dir)
# find_data buffer +16 slack: TPV's WIN32_FIND_DATAA.__sizeof__() undercounts Windows writes
fd_size_s: sizet = win32file.WIN32_FIND_DATAA.__sizeof__()
find_data: win32file.WIN32_FIND_DATAA* = stdlib.malloc(fd_size_s + 16)
if find_data is None: return 1
string.memset(find_data, 0, fd_size_s + 16)
handle: win32base.HANDLE = win32file.FindFirstFileA(pattern, find_data)
if handle == win32base.INVALID_HANDLE_VALUE: return 1
# iterate all files and subdirectories
while True:
# skip . and ..
fname: str = find_data.cFileName
if fname is not None:
fname0: char = fname[0]
if fname0 == '.':
fname1: char = fname[1]
if fname1 == '\0':
# "."
if win32file.FindNextFileA(handle, find_data) == 0: break
continue
elif fname1 == '.':
fname2: char = fname[2]
if fname2 == '\0':
# ".."
if win32file.FindNextFileA(handle, find_data) == 0: break
continue
# check whether it is a directory
is_dir: int = find_data.dwFileAttributes & win32file.FILE_ATTRIBUTE_DIRECTORY
# build the full path
fname_len: sizet = string.strlen(fname)
full_path: bytes = Utils.JoinPath(root_dir, fname)
if full_path is None: break
# build the relative path
prefix_len: sizet = 0
if rel_prefix is not None: prefix_len = string.strlen(rel_prefix)
rel_path: bytes = None
if rel_prefix is not None and prefix_len > 0: rel_path = Utils.JoinPath(rel_prefix, fname)
else:
rel_path = stdlib.malloc(fname_len + 1)
if rel_path is None: break
string.strcpy(rel_path, fname)
if rel_path is None: break
if is_dir != 0:
# recurse into the subdirectory
if scan_directory_recursive(pool, full_path, rel_path, result, inc_dir) != 0: subdir_failed += 1
else:
# check for a .vp source file (Viper uses .vp only; no longer .py)
is_py: int = 0
if Utils.is_vp_at(fname, 0, fname_len) != 0: is_py = 1
if is_py != 0:
# skip files under __pycache__
is_pycache: int = 0
if rel_prefix is not None:
if string.strcmp(rel_prefix, "__pycache__") == 0: is_pycache = 1
if is_pycache == 0:
# compute SHA1
sha1: str = compute_file_sha1(pool, full_path)
if sha1 is not None:
add_file_entry(result, pool, full_path, rel_path, sha1, inc_dir)
VLogger.debug(f"scan: {rel_path} -> {sha1}", "scan")
else: VLogger.warning(f"SHA1 computation failed: {full_path}", "scan")
# continue to the next file
if win32file.FindNextFileA(handle, find_data) == 0: break
win32file.FindClose(handle)
if subdir_failed > 0: VLogger.error(f"subdirectory scan failed for {subdir_failed} entries", "scan")
return 0
# scan_includes - scan entry point: return all .vp file paths and SHA1s
def scan_includes(pool: memhub.MemBuddy*, includes_dirs: str) -> ScanResult*:
"""Scan space-separated includes dirs and return all scanned .vp/.py files."""
if pool is None or includes_dirs is None: return None
result: ScanResult* = create_scan_result(pool)
if result is None: return None
# parse space-separated includes dirs, recursively scanning and merging each
dirs_total: sizet = string.strlen(includes_dirs)
d_i: sizet = 0
while d_i < dirs_total:
while d_i < dirs_total and includes_dirs[d_i] == ' ': d_i += 1
if d_i >= dirs_total: break
d_start: sizet = d_i
while d_i < dirs_total and includes_dirs[d_i] != ' ': d_i += 1
d_len: sizet = d_i - d_start
if d_len == 0: continue
dir_buf: bytes = stdlib.malloc(d_len + 1)
if dir_buf is None: VLogger.error("failed to allocate the includes directory; aborting scan", "scan")
string.strncpy(dir_buf, includes_dirs + d_start, d_len)
dir_buf[d_len] = '\0'
scan_directory_recursive(pool, dir_buf, None, result, dir_buf)
stdlib.free(dir_buf)
return result
# find_entry_by_module - find a file entry by module name
def find_entry_by_module(result: ScanResult*, module_name: str) -> FileEntry*:
"""Find a file entry by module name."""
if result is None or module_name is None: return None
entry_size: sizet = FileEntry.__sizeof__()
for i in range(result.Count):
entry_addr: uint64 = uint64(result.Entries) + i * entry_size
entry: FileEntry* = (FileEntry*)(entry_addr)
if entry is not None:
if entry.ModuleName is not None:
if string.strcmp(entry.ModuleName, module_name) == 0: return entry
return None
# _PathExistsA - path existence check (GetFileAttributesA)
def _PathExistsA(path: str) -> int:
"""Return 1 if the path exists, else 0, using GetFileAttributesA."""
if path is None: return 0
attr: ULONG = win32file.GetFileAttributesA(path)
if attr == 0xFFFFFFFF: # INVALID_FILE_ATTRIBUTES
return 0
return 1
# _TryResolveModuleFile - try one candidate path and write outputs (1=hit, 0=miss);
# out_abs_path/out_rel_path/out_inc_dir are t.CPtr output slots.
def _TryResolveModuleFile(inc_dir: str, rel_seg: bytes, rel_len: sizet, suffix: str, out_abs_path: t.CPtr, out_rel_path: t.CPtr, out_inc_dir: t.CPtr) -> int:
if inc_dir is None or rel_seg is None or suffix is None: return 0
inc_len: sizet = string.strlen(inc_dir)
suf_len: sizet = string.strlen(suffix)
# candidate absolute path: {inc}/{rel}{suffix} (hand-built to avoid %. *s)
cand_len: sizet = inc_len + 1 + rel_len + suf_len
cand: bytes = stdlib.malloc(cand_len + 1)
if cand is None: return 0
string.strcpy(cand, inc_dir)
cand[inc_len] = '/'
string.strncpy(cand + inc_len + 1, rel_seg, rel_len)
string.strcpy(cand + inc_len + 1 + rel_len, suffix)
if _PathExistsA(cand) == 0:
stdlib.free(cand)
return 0
# path relative to the includes root: {rel}{suffix}
rel_path: bytes = stdlib.malloc(rel_len + suf_len + 1)
if rel_path is None:
stdlib.free(cand)
return 0
string.strncpy(rel_path, rel_seg, rel_len)
rel_path[rel_len] = '\0'
string.strcat(rel_path, suffix)
# Deep-copy the owning includes root dir; allocate everything before writing
# slots to avoid a half-written state when inc allocation fails.
inc_buf: bytes = None
if out_inc_dir is not None:
inc_buf = stdlib.malloc(inc_len + 1)
if inc_buf is None:
stdlib.free(cand)
stdlib.free(rel_path)
return 0
string.strcpy(inc_buf, inc_dir)
# Write output slots (stdlib deep copies; caller frees). Slots must be cast to
# (sizet*): t.CPtr* folds to t.CPtr and [0] becomes a 1-byte access.
if out_abs_path is not None:
abs_slot: sizet* = (sizet*)(out_abs_path)
abs_slot[0] = sizet(cand)
else: stdlib.free(cand)
if out_rel_path is not None:
rel_slot: sizet* = (sizet*)(out_rel_path)
rel_slot[0] = sizet(rel_path)
else: stdlib.free(rel_path)
if out_inc_dir is not None and inc_buf is not None:
inc_slot: sizet* = (sizet*)(out_inc_dir)
inc_slot[0] = sizet(inc_buf)
return 1
# ResolveModuleToPath - module name -> file path, resolved on demand (no full scan).
# Tries each includes dir, preferring file modules over packages. Returns 0 found, 1 not.
def ResolveModuleToPath(includes_dirs: str, mod_name: str, out_abs_path: t.CPtr, out_rel_path: t.CPtr, out_inc_dir: t.CPtr) -> int:
if includes_dirs is None or mod_name is None: return 1
mn_len: sizet = string.strlen(mod_name)
if mn_len == 0: return 1
# mod_name -> relative path segment ('.' -> '/'): ast.parser -> ast/parser
rel_seg: bytes = stdlib.malloc(mn_len + 1)
if rel_seg is None: return 1
rel_len: sizet = 0
for mi in range(mn_len):
ch_m: char = mod_name[mi]
if ch_m == '.': rel_seg[rel_len] = '/'
else: rel_seg[rel_len] = ch_m
rel_len += 1
rel_seg[rel_len] = '\0'
found: int = 0
d_total: sizet = string.strlen(includes_dirs)
d_i: sizet = 0
while d_i < d_total:
while d_i < d_total and includes_dirs[d_i] == ' ': d_i += 1
if d_i >= d_total: break
d_start: sizet = d_i
while d_i < d_total and includes_dirs[d_i] != ' ': d_i += 1
d_len: sizet = d_i - d_start
if d_len == 0: continue
inc_dir_buf: bytes = stdlib.malloc(d_len + 1)
if inc_dir_buf is None: continue
string.strncpy(inc_dir_buf, includes_dirs + d_start, d_len)
inc_dir_buf[d_len] = '\0'
# candidate 1: {inc}/{rel}.vp (file module)
if _TryResolveModuleFile(inc_dir_buf, rel_seg, rel_len, ".vp", out_abs_path, out_rel_path, out_inc_dir) != 0:
found = 1
stdlib.free(inc_dir_buf)
break
# candidate 2: {inc}/{rel}/__init__.vp (package module)
if _TryResolveModuleFile(inc_dir_buf, rel_seg, rel_len, "/__init__.vp", out_abs_path, out_rel_path, out_inc_dir) != 0:
found = 1
stdlib.free(inc_dir_buf)
if found != 0: break
stdlib.free(rel_seg)
if found != 0: return 0
return 1
from stdint import *
import memhub
import string
import stdlib
import win32.win32file as win32file
import win32.win32base as win32base
import win32.fileio as fileio
import hashlib
import viperlib
import lib.Projectrans.Utils as Utils
import lib.core.VLogger as VLogger
# IncludesScanner - recursive scan of the includes directory (Win32 FindFirstFileA/
# FindNextFileA); collects .vp paths and SHA1s for Phase1 stub gen / Phase2 merge.
# global mbuddy pointer
_mbuddy: memhub.MemBuddy*
# max file path length
define MAX_PATH_LEN = 512
# max files per scan
define MAX_FILES = 1024
# FileEntry - file entry (path + SHA1)
novirtual class FileEntry:
"""A scanned file entry."""
Path: str # absolute file path
Sha1: str # first 16 SHA1 chars (16 bytes + null)
RelPath: str # path relative to the includes root
ModuleName: str # module name (e.g. "ast.parser")
IncDir: str # owning includes root dir (multi-root support)
# ScanResult - scan result
novirtual class ScanResult:
"""A collection of scan results."""
Entries: FileEntry* # FileEntry array
Count: int
Capacity: int
# create_scan_result - create the scan result container
def create_scan_result(pool: memhub.MemBuddy*) -> ScanResult*:
"""Create the scan result container with MAX_FILES preallocated slots."""
if pool is None: return None
size: sizet = MAX_FILES * FileEntry.__sizeof__()
# use stdlib.malloc to avoid exhausting the mbuddy pool (consistent with StubMerger)
entries: FileEntry* = stdlib.malloc(size)
if entries is None: return None
string.memset(entries, 0, size)
result: ScanResult* = stdlib.malloc(ScanResult.__sizeof__())
if result is None: return None
string.memset(result, 0, ScanResult.__sizeof__())
result.Entries = entries
result.Count = 0
result.Capacity = MAX_FILES
return result
# add_file_entry - append a file entry to the scan result
def add_file_entry(result: ScanResult*, pool: memhub.MemBuddy*, abs_path: str, rel_path: str, sha1: str, inc_dir: str) -> int:
"""Append a file entry to the scan result; inc_dir is the owning includes root."""
if result is None or pool is None: return 1
if result.Count >= result.Capacity: return 1
# compute module name: '/' -> '.', strip .vp/.py (Utils handles __init__.vp)
mod_buf: str = Utils.PathToModuleName(rel_path, 0)
if mod_buf is None: return 1
# get the entry address
entry_size: sizet = FileEntry.__sizeof__()
entry_addr: uint64 = uint64(result.Entries) + result.Count * entry_size
entry: FileEntry* = (FileEntry*)(entry_addr)
if entry is None: return 1
# copy the path string
abs_len: sizet = string.strlen(abs_path)
abs_buf: str = stdlib.malloc(abs_len + 1)
if abs_buf is None: return 1
string.strcpy(abs_buf, abs_path)
entry.Path = abs_buf
# copy the SHA1
sha1_buf: str = stdlib.malloc(17)
if sha1_buf is None: return 1
string.strcpy(sha1_buf, sha1)
entry.Sha1 = sha1_buf
# copy the relative path
rel_buf: str = stdlib.malloc(string.strlen(rel_path) + 1)
if rel_buf is None: return 1
string.strcpy(rel_buf, rel_path)
entry.RelPath = rel_buf
# copy the module name
entry.ModuleName = mod_buf
# copy the owning includes root dir (used by Phase2 path joining)
if inc_dir is not None:
inc_len: sizet = string.strlen(inc_dir)
inc_buf: str = stdlib.malloc(inc_len + 1)
if inc_buf is None: return 1
string.strcpy(inc_buf, inc_dir)
entry.IncDir = inc_buf
result.Count += 1
return 0
# compute_file_sha1 - read file content and compute SHA1 (CRLF->LF, matching Projectrans.py)
def compute_file_sha1(pool: memhub.MemBuddy*, file_path: str) -> str:
"""Read a file, normalize CRLF to LF, and return the first 16 SHA1 hex chars."""
if pool is None or file_path is None: return None
# read file content; BUF_SIZE must match SRC_BUF_SIZE (1MB) or trailing edits won't change SHA1
BUF_SIZE: sizet = 1048576
# Refuse larger files: hashing only the first 1 MiB gives two files that differ later the same
# SHA1, and the SHA1 keys the stub/cache files (colliding modules would reuse each other's IR).
sf_sz: fileio.File* = fileio.File(file_path, fileio.MODE.R)
if sf_sz.closed: return None
too_big: int = 0
if sf_sz.size() >= BUF_SIZE: too_big = 1
sf_sz.close()
if too_big != 0:
VLogger.error(f"source file exceeds the {BUF_SIZE}-byte limit and cannot be hashed completely: {file_path}", "IncludesScanner")
return None
buf: bytes = Utils.read_file_to_buf(file_path, BUF_SIZE)
if buf is None: return None
# strip \r in place (CRLF -> LF), matching Projectrans.py text-mode reads
buf_len: sizet = string.strlen(buf)
write_pos: sizet = 0
for ch in buf:
if ch != '\r':
buf[write_pos] = ch
write_pos += 1
# add a null terminator
buf[write_pos] = 0
# compute SHA1
ctx: hashlib.sha1* = hashlib.sha1()
if ctx is None:
stdlib.free(buf)
return None
ctx.update(buf)
digest: bytes = stdlib.malloc(hashlib.SHA1_DIGEST_LEN)
if digest is None:
stdlib.free(buf)
return None
ctx.final(digest)
# convert to hex (first 8 bytes = 16 hex chars; stdlib allocation unchanged)
hex_buf: str = Utils.bytes_to_hex(pool, digest, 8, 0, 1)
stdlib.free(buf)
stdlib.free(digest)
return hex_buf
# scan_directory_recursive - recursively scan a directory (FindFirstFileA/FindNextFileA)
def scan_directory_recursive(pool: memhub.MemBuddy*, root_dir: str, rel_prefix: str, result: ScanResult*, inc_dir: str) -> int:
"""Recursively collect .vp/.py files under a directory; inc_dir is the includes root."""
if pool is None or root_dir is None or result is None: return 1
# subdir recursion failure count (not ignored; fail-fast at the summary)
subdir_failed: int = 0
# build search pattern: root_dir/*
root_len: sizet = string.strlen(root_dir)
pattern: bytes = stdlib.malloc(root_len + 4)
if pattern is None: return 1
viperlib.snprintf(pattern, root_len + 4, "%s/*", root_dir)
# find_data buffer +16 slack: VPC's WIN32_FIND_DATAA.__sizeof__() undercounts Windows writes
fd_size_s: sizet = win32file.WIN32_FIND_DATAA.__sizeof__()
find_data: win32file.WIN32_FIND_DATAA* = stdlib.malloc(fd_size_s + 16)
if find_data is None: return 1
string.memset(find_data, 0, fd_size_s + 16)
handle: win32base.HANDLE = win32file.FindFirstFileA(pattern, find_data)
if handle == win32base.INVALID_HANDLE_VALUE: return 1
# iterate all files and subdirectories
while True:
# skip . and ..
fname: str = find_data.cFileName
if fname is not None:
fname0: char = fname[0]
if fname0 == '.':
fname1: char = fname[1]
if fname1 == '\0':
# "."
if win32file.FindNextFileA(handle, find_data) == 0: break
continue
elif fname1 == '.':
fname2: char = fname[2]
if fname2 == '\0':
# ".."
if win32file.FindNextFileA(handle, find_data) == 0: break
continue
# check whether it is a directory
is_dir: int = find_data.dwFileAttributes & win32file.FILE_ATTRIBUTE_DIRECTORY
# build the full path
fname_len: sizet = string.strlen(fname)
full_path: bytes = Utils.JoinPath(root_dir, fname)
if full_path is None: break
# build the relative path
prefix_len: sizet = 0
if rel_prefix is not None: prefix_len = string.strlen(rel_prefix)
rel_path: bytes = None
if rel_prefix is not None and prefix_len > 0: rel_path = Utils.JoinPath(rel_prefix, fname)
else:
rel_path = stdlib.malloc(fname_len + 1)
if rel_path is None: break
string.strcpy(rel_path, fname)
if rel_path is None: break
if is_dir != 0:
# recurse into the subdirectory
if scan_directory_recursive(pool, full_path, rel_path, result, inc_dir) != 0: subdir_failed += 1
else:
# check for a .vp source file (Viper uses .vp only; no longer .py)
is_py: int = 0
if Utils.is_vp_at(fname, 0, fname_len) != 0: is_py = 1
if is_py != 0:
# skip files under __pycache__
is_pycache: int = 0
if rel_prefix is not None:
if string.strcmp(rel_prefix, "__pycache__") == 0: is_pycache = 1
if is_pycache == 0:
# compute SHA1
sha1: str = compute_file_sha1(pool, full_path)
if sha1 is not None:
# fail-fast: a full table used to drop the file silently, so it never got a
# SHA1 entry / dependency edge and was therefore never compiled.
if add_file_entry(result, pool, full_path, rel_path, sha1, inc_dir) != 0:
VLogger.error(f"includes scan could not register '{full_path}' (the {MAX_FILES}-file capacity or an allocation failed); raise MAX_FILES", "scan")
VLogger.debug(f"scan: {rel_path} -> {sha1}", "scan")
else: VLogger.warning(f"SHA1 computation failed: {full_path}", "scan")
# continue to the next file
if win32file.FindNextFileA(handle, find_data) == 0: break
win32file.FindClose(handle)
if subdir_failed > 0: VLogger.error(f"subdirectory scan failed for {subdir_failed} entries", "scan")
return 0
# scan_includes - scan entry point: return all .vp file paths and SHA1s
def scan_includes(pool: memhub.MemBuddy*, includes_dirs: str) -> ScanResult*:
"""Scan space-separated includes dirs and return all scanned .vp/.py files."""
if pool is None or includes_dirs is None: return None
result: ScanResult* = create_scan_result(pool)
if result is None: return None
# parse space-separated includes dirs, recursively scanning and merging each
dirs_total: sizet = string.strlen(includes_dirs)
d_i: sizet = 0
while d_i < dirs_total:
while d_i < dirs_total and includes_dirs[d_i] == ' ': d_i += 1
if d_i >= dirs_total: break
d_start: sizet = d_i
while d_i < dirs_total and includes_dirs[d_i] != ' ': d_i += 1
d_len: sizet = d_i - d_start
if d_len == 0: continue
dir_buf: bytes = stdlib.malloc(d_len + 1)
if dir_buf is None: VLogger.error("failed to allocate the includes directory; aborting scan", "scan")
string.strncpy(dir_buf, includes_dirs + d_start, d_len)
dir_buf[d_len] = '\0'
scan_directory_recursive(pool, dir_buf, None, result, dir_buf)
stdlib.free(dir_buf)
return result
# find_entry_by_module - find a file entry by module name
def find_entry_by_module(result: ScanResult*, module_name: str) -> FileEntry*:
"""Find a file entry by module name."""
if result is None or module_name is None: return None
entry_size: sizet = FileEntry.__sizeof__()
for i in range(int(result.Count)):
entry_addr: uint64 = uint64(result.Entries) + i * entry_size
entry: FileEntry* = (FileEntry*)(entry_addr)
if entry is not None:
if entry.ModuleName is not None:
if string.strcmp(entry.ModuleName, module_name) == 0: return entry
return None
# _PathExistsA - path existence check (GetFileAttributesA)
def _PathExistsA(path: str) -> int:
"""Return 1 if the path exists, else 0, using GetFileAttributesA."""
if path is None: return 0
attr: ULONG = win32file.GetFileAttributesA(path)
if attr == 0xFFFFFFFF: # INVALID_FILE_ATTRIBUTES
return 0
return 1
# _TryResolveModuleFile - try one candidate path and write outputs (1=hit, 0=miss);
# out_abs_path/out_rel_path/out_inc_dir are rawptr output slots.
def _TryResolveModuleFile(inc_dir: str, rel_seg: bytes, rel_len: sizet, suffix: str, out_abs_path: rawptr, out_rel_path: rawptr, out_inc_dir: rawptr) -> int:
if inc_dir is None or rel_seg is None or suffix is None: return 0
inc_len: sizet = string.strlen(inc_dir)
suf_len: sizet = string.strlen(suffix)
# candidate absolute path: {inc}/{rel}{suffix} (hand-built to avoid %. *s)
cand_len: sizet = inc_len + 1 + rel_len + suf_len
cand: bytes = stdlib.malloc(cand_len + 1)
if cand is None: return 0
string.strcpy(cand, inc_dir)
cand[inc_len] = '/'
string.strncpy(cand + inc_len + 1, rel_seg, rel_len)
string.strcpy(cand + inc_len + 1 + rel_len, suffix)
if _PathExistsA(cand) == 0:
stdlib.free(cand)
return 0
# path relative to the includes root: {rel}{suffix}
rel_path: bytes = stdlib.malloc(rel_len + suf_len + 1)
if rel_path is None:
stdlib.free(cand)
return 0
string.strncpy(rel_path, rel_seg, rel_len)
rel_path[rel_len] = '\0'
string.strcat(rel_path, suffix)
# Deep-copy the owning includes root dir; allocate everything before writing
# slots to avoid a half-written state when inc allocation fails.
inc_buf: bytes = None
if out_inc_dir is not None:
inc_buf = stdlib.malloc(inc_len + 1)
if inc_buf is None:
stdlib.free(cand)
stdlib.free(rel_path)
return 0
string.strcpy(inc_buf, inc_dir)
# Write output slots (stdlib deep copies; caller frees). Slots must be cast to
# (sizet*): rawptr* folds to rawptr and [0] becomes a 1-byte access.
if out_abs_path is not None:
abs_slot: sizet* = (sizet*)(out_abs_path)
abs_slot[0] = sizet(cand)
else: stdlib.free(cand)
if out_rel_path is not None:
rel_slot: sizet* = (sizet*)(out_rel_path)
rel_slot[0] = sizet(rel_path)
else: stdlib.free(rel_path)
if out_inc_dir is not None and inc_buf is not None:
inc_slot: sizet* = (sizet*)(out_inc_dir)
inc_slot[0] = sizet(inc_buf)
return 1
# ResolveModuleToPath - module name -> file path, resolved on demand (no full scan).
# Tries each includes dir, preferring file modules over packages. Returns 0 found, 1 not.
def ResolveModuleToPath(includes_dirs: str, mod_name: str, out_abs_path: rawptr, out_rel_path: rawptr, out_inc_dir: rawptr) -> int:
if includes_dirs is None or mod_name is None: return 1
mn_len: sizet = string.strlen(mod_name)
if mn_len == 0: return 1
# mod_name -> relative path segment ('.' -> '/'): ast.parser -> ast/parser
rel_seg: bytes = stdlib.malloc(mn_len + 1)
if rel_seg is None: return 1
rel_len: sizet = 0
for mi in range(int(mn_len)):
ch_m: char = mod_name[mi]
if ch_m == '.': rel_seg[rel_len] = '/'
else: rel_seg[rel_len] = ch_m
rel_len += 1
rel_seg[rel_len] = '\0'
found: int = 0
d_total: sizet = string.strlen(includes_dirs)
d_i: sizet = 0
while d_i < d_total:
while d_i < d_total and includes_dirs[d_i] == ' ': d_i += 1
if d_i >= d_total: break
d_start: sizet = d_i
while d_i < d_total and includes_dirs[d_i] != ' ': d_i += 1
d_len: sizet = d_i - d_start
if d_len == 0: continue
inc_dir_buf: bytes = stdlib.malloc(d_len + 1)
if inc_dir_buf is None: continue
string.strncpy(inc_dir_buf, includes_dirs + d_start, d_len)
inc_dir_buf[d_len] = '\0'
# candidate 1: {inc}/{rel}.vp (file module)
if _TryResolveModuleFile(inc_dir_buf, rel_seg, rel_len, ".vp", out_abs_path, out_rel_path, out_inc_dir) != 0:
found = 1
stdlib.free(inc_dir_buf)
break
# candidate 2: {inc}/{rel}/__init__.vp (package module)
if _TryResolveModuleFile(inc_dir_buf, rel_seg, rel_len, "/__init__.vp", out_abs_path, out_rel_path, out_inc_dir) != 0:
found = 1
stdlib.free(inc_dir_buf)
if found != 0: break
stdlib.free(rel_seg)
if found != 0: return 0
return 1
+2 -1
View File
@@ -571,7 +571,8 @@ def ReplBaselineCapture() -> int:
if val is None: continue
kind: int = 0
if cdef_str is not None and int(cdef_str.get(i)) == 1: kind = 2
elif cdef_dbl is not None and int(cdef_dbl.get(i)) == 1: kind = 1
# 浮点条目:1=f64 / 2=f32(见 TypeGenCDef 的 CDEFINE_FLOAT_KIND_*),非 0 即浮点
elif cdef_dbl is not None and int(cdef_dbl.get(i)) != 0: kind = 1
nc: str = _repl_str_dup(_perm_pool, nm)
vc: str = _repl_str_dup(_perm_pool, val)
if nc is None or vc is None: continue
+246 -244
View File
@@ -1,244 +1,246 @@
import t, c
from stdint import *
import stdio
import stdlib
import memhub
import sys
import w32.win32console as w32cmd
import w32.win32file as w32file
import w32.win32base as win32base
# VLogger - native logger with colored Windows console output.
# Format: ├ LEVEL: message or ├ LEVEL[category]: message (aligned with TPC lib/core/VLogger.py).
# Error recovery (REPL/JIT): after error, longjmp back to the REPL setjmp point.
# mingw-w64 x86_64: longjmp is void longjmp(jmp_buf, int); setjmp expands to _setjmp(buf, NULL).
extern def longjmp(buf: rawptr, val: int)
# REPL recovery-point pointer: registered at session start, cleared at session end.
# Covers the whole fragment pipeline; batch mode never sets it, so it stays None and exits via sys.exit(1).
_repl_jmpbuf: rawptr = None
# repl_abort - the single "terminate after diagnosis" exit in TPV.
# Caller already printed the diagnostic; returns to REPL via longjmp if a recovery point is set, else sys.exit(1).
def repl_abort() -> int:
"""Unified exit after diagnosis: longjmp to the REPL if a recovery point is set, else exit."""
if _repl_jmpbuf is not None:
stdio.fflush(None)
longjmp(_repl_jmpbuf, 1)
sys.exit(1)
return 0
def set_repl_jmpbuf(buf: t.CPtr) -> int:
"""Register the REPL jmp_buf; idempotent at each fragment entry."""
global _repl_jmpbuf
_repl_jmpbuf = buf
return 0
def clear_repl_jmpbuf() -> int:
"""Clear the registered jmp_buf at REPL session end."""
global _repl_jmpbuf
_repl_jmpbuf = None
return 0
# Log levels
enum LogLevel:
DEBUG, INFO, WARNING, ERROR, SUCCESS, CRITICAL
# Win32 console foreground color attributes
define FOREGROUND_BLUE = 0x0001
define FOREGROUND_GREEN = 0x0002
define FOREGROUND_RED = 0x0004
define FOREGROUND_INTENSITY = 0x0008
define FOREGROUND_WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
define FOREGROUND_CYAN = FOREGROUND_BLUE | FOREGROUND_GREEN
define FOREGROUND_YELLOW = FOREGROUND_RED | FOREGROUND_GREEN
define FOREGROUND_MAGENTA = FOREGROUND_RED | FOREGROUND_BLUE
# Win32 console background color attributes
define BACKGROUND_RED = 0x0040
define BACKGROUND_GREEN = 0x0020
define BACKGROUND_BLUE = 0x0010
define BACKGROUND_INTENSITY = 0x0080
define BACKGROUND_WHITE = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
novirtual class Logger:
_level: int
_console_handle: win32base.HANDLE
_use_color: int
jit_flag: bool
__mbuddy__: memhub.MemBuddy*
def __init__(self, level: int = 1):
self._level = level
self._console_handle = w32file.GetStdHandle(w32file.STD_OUTPUT_HANDLE)
self._use_color = 1
self.jit_flag = False
self.__mbuddy__ = _mbuddy
def _set_color(self, attr: WORD) -> int:
uc: int = self._use_color
if uc:
ch: win32base.HANDLE = self._console_handle
ret: int = w32cmd.SetConsoleTextAttribute(ch, attr)
return ret
return 0
def _reset_color(self) -> int:
if self._use_color: return w32cmd.SetConsoleTextAttribute(self._console_handle, FOREGROUND_WHITE)
return 0
# _log - core log output with segmented coloring (prefix symbol / level tag / message).
# Format: ├ LEVEL: msg or ├ LEVEL[category]: msg.
def _log(self, level: int, prefix_sym: str, level_str: str, msg: str, sym_color: WORD, level_color: WORD, category: str = "") -> int:
# Color the prefix symbol
self._set_color(sym_color)
stdio.printf("%s ", prefix_sym)
# Color the level tag
self._set_color(level_color)
stdio.printf("%s", level_str)
self._reset_color()
# Optional category
if category is not None and category[0] != 0: stdio.printf(" [%s]", category)
stdio.printf(": %s\n", msg)
return 0
def debug(self, msg: str, category: str = "") -> int:
return self._log(LogLevel.DEBUG, "├", "DEBUG", msg, FOREGROUND_INTENSITY, FOREGROUND_INTENSITY, category)
def info(self, msg: str, category: str = "") -> int:
ret: int = self._log(LogLevel.INFO, "├", "INFO", msg, FOREGROUND_GREEN, FOREGROUND_GREEN | FOREGROUND_INTENSITY, category)
return ret
def warning(self, msg: str, category: str = "") -> int:
return self._log(LogLevel.WARNING, "├", "WARN", msg, FOREGROUND_YELLOW, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, category)
def error(self, msg: str, category: str = "") -> int:
ret: int = self._log(LogLevel.ERROR, "├", "ERROR", msg, FOREGROUND_RED, FOREGROUND_RED | FOREGROUND_INTENSITY, category)
# JIT/REPL: diagnostic already printed; jump back to the REPL loop via the unified exit.
if self.jit_flag:
repl_abort()
return ret
# Batch mode: terminate immediately on error.
sys.exit(1)
return ret
def success(self, msg: str, category: str = "") -> int:
return self._log(LogLevel.SUCCESS, "├", "SUCCESS", msg, FOREGROUND_GREEN, FOREGROUND_GREEN | FOREGROUND_INTENSITY, category)
def critical(self, msg: str, category: str = "") -> int:
# CRITICAL uses the ╠ prefix (red-background white label, aligned with TPC style).
return self._log(LogLevel.CRITICAL, "╠", "CRITICAL", msg, FOREGROUND_RED, BACKGROUND_RED | FOREGROUND_WHITE | FOREGROUND_INTENSITY, category)
def set_level(self, level: int) -> int:
self._level = level
return 0
def set_jit_flag(self, v: bool) -> int:
"""Set the JIT/REPL flag: True makes error longjmp back to the REPL loop."""
self.jit_flag = v
return 0
# Advanced log methods.
def banner(self, msg: str) -> int:
"""Print a blue-highlighted section banner."""
self._set_color(FOREGROUND_BLUE | FOREGROUND_INTENSITY)
stdio.printf("\n=== %s ===\n\n", msg)
self._reset_color()
return 0
def compile_error(self, msg: str, file: str = "", line: int = 0) -> int:
"""Format a compile error with a red-background header, location and message."""
self._set_color(BACKGROUND_RED | FOREGROUND_WHITE | FOREGROUND_INTENSITY)
stdio.printf(" COMPILE ERROR ")
self._set_color(FOREGROUND_RED | FOREGROUND_INTENSITY)
stdio.printf("\n")
if file is not None:
if file[0] != 0 and line > 0: stdio.printf(" location: %s:%d\n", file, line)
elif file[0] != 0: stdio.printf(" file: %s\n", file)
stdio.printf(" error: %s\n", msg)
self._reset_color()
return 0
def compile_warning(self, msg: str, file: str = "", line: int = 0) -> int:
"""Format a compile warning with a yellow background, location and message."""
self._set_color(BACKGROUND_RED | BACKGROUND_GREEN | FOREGROUND_INTENSITY)
stdio.printf(" COMPILE WARNING ")
self._set_color(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY)
stdio.printf("\n")
if file is not None:
if file[0] != 0 and line > 0: stdio.printf(" location: %s:%d\n", file, line)
elif file[0] != 0: stdio.printf(" file: %s\n", file)
stdio.printf(" warning: %s\n", msg)
self._reset_color()
return 0
# Global mbuddy pointer (injected by lib.InitLib).
_mbuddy: memhub.MemBuddy*
# Global logger pointer
logger: Logger*
log: Logger*
def init_logger():
"""Initialize the global logger instance."""
global logger, log
raw: t.CPtr = _mbuddy.alloc(Logger.__sizeof__())
# if raw is None: return None (memory management still unclear)
logger = raw
logger.__before_init__()
logger.__init__(LogLevel.INFO)
log = logger
# if fmtbuffer is None: return None
# --nowarn-register switch: suppresses Phase A dependency WARN output (output only).
_nowarn_register: int = 0
# Module-level convenience functions (aligned with TPC lib.core.VLogger; no get_logger() needed).
def info(msg: str, category: str = "") -> int:
"""Log at INFO level."""
if log is not None: return log.info(msg, category)
return 0
def warning(msg: str, category: str = "") -> int:
"""Log at WARN level."""
if log is not None: return log.warning(msg, category)
return 0
def error(msg: str, category: str = "") -> int:
"""Log at ERROR level: exit in batch mode, longjmp to REPL under JIT/REPL."""
if log is not None: return log.error(msg, category)
# When the logger is unavailable, still use the unified exit (REPL can recover if registered)
repl_abort()
return 0
def success(msg: str, category: str = "") -> int:
"""Log at SUCCESS level."""
if log is not None: return log.success(msg, category)
return 0
def critical(msg: str, category: str = "") -> int:
"""Log at CRITICAL level (fatal, red background)."""
if log is not None: return log.critical(msg, category)
return 0
def debug(msg: str, category: str = "") -> int:
"""Log at DEBUG level."""
if log is not None: return log.debug(msg, category)
return 0
from stdint import *
import stdio
import memhub
import sys
import win32.win32console as win32cmd
import win32.win32file as win32file
import win32.win32base as win32base
# VLogger - native logger with colored Windows console output.
# Format: ├ LEVEL: message or ├ LEVEL[category]: message (aligned with TPC lib/core/VLogger.py).
# Error recovery (REPL/JIT): after error, longjmp back to the REPL setjmp point.
# mingw-w64 x86_64: longjmp is void longjmp(jmp_buf, int); setjmp expands to _setjmp(buf, NULL).
extern def longjmp(buf: rawptr, val: int)
# REPL recovery-point pointer: registered at session start, cleared at session end.
# Covers the whole fragment pipeline; batch mode never sets it, so it stays None and exits via sys.exit(1).
_repl_jmpbuf: rawptr = None
# repl_abort - the single "terminate after diagnosis" exit in VPC.
# Caller already printed the diagnostic; returns to REPL via longjmp if a recovery point is set, else sys.exit(1).
def repl_abort() -> int:
"""Unified exit after diagnosis: longjmp to the REPL if a recovery point is set, else exit."""
if _repl_jmpbuf is not None:
stdio.fflush(None)
longjmp(_repl_jmpbuf, 1)
sys.exit(1)
return 0
def set_repl_jmpbuf(buf: rawptr) -> int:
"""Register the REPL jmp_buf; idempotent at each fragment entry."""
global _repl_jmpbuf
_repl_jmpbuf = buf
return 0
def clear_repl_jmpbuf() -> int:
"""Clear the registered jmp_buf at REPL session end."""
global _repl_jmpbuf
_repl_jmpbuf = None
return 0
# Log levels
enum LogLevel:
DEBUG, INFO, WARNING, ERROR, SUCCESS, CRITICAL
# Win32 console foreground color attributes
# 值用 WORD(...) 显式定宽为 uint16(与 _log 的 sym_color/level_color 形参一致)。
# 注意:仅写 `define X: WORD = 0x0002` 不够 —— define 的宽度由**右侧表达式的类型**
# 决定(win32file.vp 的 `define STD_OUTPUT_HANDLE: DWORD = DWORD(-10)` 同款),
# 否则常量仍是 i32 → 传给 WORD 形参即 32→16 隐式窄化。
define FOREGROUND_BLUE: WORD = WORD(0x0001)
define FOREGROUND_GREEN: WORD = WORD(0x0002)
define FOREGROUND_RED: WORD = WORD(0x0004)
define FOREGROUND_INTENSITY: WORD = WORD(0x0008)
define FOREGROUND_WHITE: WORD = WORD(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
define FOREGROUND_CYAN: WORD = WORD(FOREGROUND_BLUE | FOREGROUND_GREEN)
define FOREGROUND_YELLOW: WORD = WORD(FOREGROUND_RED | FOREGROUND_GREEN)
define FOREGROUND_MAGENTA: WORD = WORD(FOREGROUND_RED | FOREGROUND_BLUE)
# Win32 console background color attributes
define BACKGROUND_RED: WORD = WORD(0x0040)
define BACKGROUND_GREEN: WORD = WORD(0x0020)
define BACKGROUND_BLUE: WORD = WORD(0x0010)
define BACKGROUND_INTENSITY: WORD = WORD(0x0080)
define BACKGROUND_WHITE: WORD = WORD(BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
novirtual class Logger:
_level: int
_console_handle: win32base.HANDLE
_use_color: int
jit_flag: bool
__mbuddy__: memhub.MemBuddy*
def __init__(self, level: int = 1):
self._level = level
self._console_handle = win32file.GetStdHandle(win32file.STD_OUTPUT_HANDLE)
self._use_color = 1
self.jit_flag = False
self.__mbuddy__ = _mbuddy
def _set_color(self, attr: WORD) -> int:
uc: int = self._use_color
if uc:
ch: win32base.HANDLE = self._console_handle
ret: int = win32cmd.SetConsoleTextAttribute(ch, attr)
return ret
return 0
def _reset_color(self) -> int:
if self._use_color: return win32cmd.SetConsoleTextAttribute(self._console_handle, FOREGROUND_WHITE)
return 0
# _log - core log output with segmented coloring (prefix symbol / level tag / message).
# Format: ├ LEVEL: msg or ├ LEVEL[category]: msg.
def _log(self, level: int, prefix_sym: str, level_str: str, msg: str, sym_color: WORD, level_color: WORD, category: str = "") -> int:
# Color the prefix symbol
self._set_color(sym_color)
stdio.printf("%s ", prefix_sym)
# Color the level tag
self._set_color(level_color)
stdio.printf("%s", level_str)
self._reset_color()
# Optional category
if category is not None and category[0] != 0: stdio.printf(" [%s]", category)
stdio.printf(": %s\n", msg)
return 0
def debug(self, msg: str, category: str = "") -> int:
return self._log(LogLevel.DEBUG, "├", "DEBUG", msg, FOREGROUND_INTENSITY, FOREGROUND_INTENSITY, category)
def info(self, msg: str, category: str = "") -> int:
ret: int = self._log(LogLevel.INFO, "├", "INFO", msg, FOREGROUND_GREEN, WORD(FOREGROUND_GREEN | FOREGROUND_INTENSITY), category)
return ret
def warning(self, msg: str, category: str = "") -> int:
return self._log(LogLevel.WARNING, "├", "WARN", msg, FOREGROUND_YELLOW, WORD(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY), category)
def error(self, msg: str, category: str = "") -> int:
ret: int = self._log(LogLevel.ERROR, "├", "ERROR", msg, FOREGROUND_RED, WORD(FOREGROUND_RED | FOREGROUND_INTENSITY), category)
# JIT/REPL: diagnostic already printed; jump back to the REPL loop via the unified exit.
if self.jit_flag:
repl_abort()
return ret
# Batch mode: terminate immediately on error.
sys.exit(1)
return ret
def success(self, msg: str, category: str = "") -> int:
return self._log(LogLevel.SUCCESS, "├", "SUCCESS", msg, FOREGROUND_GREEN, WORD(FOREGROUND_GREEN | FOREGROUND_INTENSITY), category)
def critical(self, msg: str, category: str = "") -> int:
# CRITICAL uses the ╠ prefix (red-background white label, aligned with TPC style).
return self._log(LogLevel.CRITICAL, "╠", "CRITICAL", msg, FOREGROUND_RED, WORD(BACKGROUND_RED | FOREGROUND_WHITE | FOREGROUND_INTENSITY), category)
def set_level(self, level: int) -> int:
self._level = level
return 0
def set_jit_flag(self, v: bool) -> int:
"""Set the JIT/REPL flag: True makes error longjmp back to the REPL loop."""
self.jit_flag = v
return 0
# Advanced log methods.
def banner(self, msg: str) -> int:
"""Print a blue-highlighted section banner."""
self._set_color(FOREGROUND_BLUE | FOREGROUND_INTENSITY)
stdio.printf("\n=== %s ===\n\n", msg)
self._reset_color()
return 0
def compile_error(self, msg: str, file: str = "", line: int = 0) -> int:
"""Format a compile error with a red-background header, location and message."""
self._set_color(BACKGROUND_RED | FOREGROUND_WHITE | FOREGROUND_INTENSITY)
stdio.printf(" COMPILE ERROR ")
self._set_color(FOREGROUND_RED | FOREGROUND_INTENSITY)
stdio.printf("\n")
if file is not None:
if file[0] != 0 and line > 0: stdio.printf(" location: %s:%d\n", file, line)
elif file[0] != 0: stdio.printf(" file: %s\n", file)
stdio.printf(" error: %s\n", msg)
self._reset_color()
return 0
def compile_warning(self, msg: str, file: str = "", line: int = 0) -> int:
"""Format a compile warning with a yellow background, location and message."""
self._set_color(BACKGROUND_RED | BACKGROUND_GREEN | FOREGROUND_INTENSITY)
stdio.printf(" COMPILE WARNING ")
self._set_color(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY)
stdio.printf("\n")
if file is not None:
if file[0] != 0 and line > 0: stdio.printf(" location: %s:%d\n", file, line)
elif file[0] != 0: stdio.printf(" file: %s\n", file)
stdio.printf(" warning: %s\n", msg)
self._reset_color()
return 0
# Global mbuddy pointer (injected by lib.InitLib).
_mbuddy: memhub.MemBuddy*
# Global logger pointer
logger: Logger*
log: Logger*
def init_logger():
"""Initialize the global logger instance."""
global logger, log
raw: rawptr = _mbuddy.alloc(Logger.__sizeof__())
# if raw is None: return None (memory management still unclear)
logger = raw
logger.__before_init__()
logger.__init__(LogLevel.INFO)
log = logger
# if fmtbuffer is None: return None
# --nowarn-register switch: suppresses Phase A dependency WARN output (output only).
_nowarn_register: int = 0
# Module-level convenience functions (aligned with TPC lib.core.VLogger; no get_logger() needed).
def info(msg: str, category: str = "") -> int:
"""Log at INFO level."""
if log is not None: return log.info(msg, category)
return 0
def warning(msg: str, category: str = "") -> int:
"""Log at WARN level."""
if log is not None: return log.warning(msg, category)
return 0
def error(msg: str, category: str = "") -> int:
"""Log at ERROR level: exit in batch mode, longjmp to REPL under JIT/REPL."""
if log is not None: return log.error(msg, category)
# When the logger is unavailable, still use the unified exit (REPL can recover if registered)
repl_abort()
return 0
def success(msg: str, category: str = "") -> int:
"""Log at SUCCESS level."""
if log is not None: return log.success(msg, category)
return 0
def critical(msg: str, category: str = "") -> int:
"""Log at CRITICAL level (fatal, red background)."""
if log is not None: return log.critical(msg, category)
return 0
def debug(msg: str, category: str = "") -> int:
"""Log at DEBUG level."""
if log is not None: return log.debug(msg, category)
return 0
+31 -31
View File
@@ -1,14 +1,11 @@
"""Compiler-side implementation of the `viperbuild` built-in macro library (--src Pass 0)."""
import t, c
from stdint import *
from w32.win32base import *
from win32.win32base import *
import memhub
import string
import stdio
import stdlib
import viperlib
import ast
import w32.win32file as win32file
import win32.win32file as win32file
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config
import lib.core.VLogger as VLogger
@@ -131,7 +128,7 @@ def _BaseNameOf(path: str) -> str:
if path is None: return None
n: sizet = string.strlen(path)
start: sizet = 0
for i in range(n):
for i in range(int(n)):
ch: char = path[i]
if ch == '/' or ch == '\\': start = i + 1
return _DupStd(path + start)
@@ -166,7 +163,7 @@ def _FindInList(names: list[str]*, name: str) -> int:
"""Linear-search names for name; return its index, or -1 if not found."""
if names is None or name is None: return -1
n: sizet = names.__len__()
for i in range(n):
for i in range(int(n)):
s: str = names.get(i)
if s is not None:
if string.strcmp(s, name) == 0: return int(i)
@@ -199,7 +196,7 @@ def _SplitInto(out: list[str]*, joined: str) -> int:
n: sizet = string.strlen(joined)
if n == 0: return 1
start: sizet = 0
for i in range(n + 1):
for i in range(int(n + 1)):
if i == n or joined[i] == char(VB_SEP_CODE):
seg: str = stdlib.malloc(i - start + 1)
if seg is None: return 0
@@ -227,7 +224,7 @@ def _JoinSep(lst: list[str]*) -> str:
buf: str = _scratch.alloc(total + 1)
if buf is None: return None
pos: sizet = 0
for i in range(n):
for i in range(int(n)):
s = lst.get(i)
if s is not None:
sl: sizet = string.strlen(s)
@@ -255,7 +252,7 @@ def _JoinSpace(lst: list[str]*) -> str:
buf: str = stdlib.malloc(total + 1)
if buf is None: return None
pos: sizet = 0
for i in range(n):
for i in range(int(n)):
s = lst.get(i)
if s is not None:
sl: sizet = string.strlen(s)
@@ -274,7 +271,7 @@ def _FirstSpaceTok(joined: str) -> str:
n: sizet = string.strlen(joined)
if n == 0: return None
sp: sizet = n
for i in range(n):
for i in range(int(n)):
if joined[i] == ' ':
sp = i
break
@@ -337,7 +334,7 @@ def _EvalList(node: ast.AST*, out: list[str]*) -> int:
lst: ast.List* = ast.List*(node)
if lst is None or lst.elts is None: return 0
n: sizet = lst.elts.__len__()
for i in range(n):
for i in range(int(n)):
e: ast.AST* = lst.elts.get(i)
if e is None: return 0
if e.kind() == ast.ASTKind.Starred:
@@ -409,7 +406,7 @@ def _HandleImport(stmt: ast.Import*) -> int:
global _alias, _saw_viperbuild
if stmt is None or stmt.names is None: return 0
n: sizet = stmt.names.__len__()
for i in range(n):
for i in range(int(n)):
al: ast.Alias* = ast.Alias*(stmt.names.get(i))
if al is not None and al.name is not None:
if string.strcmp(al.name, "viperbuild") == 0:
@@ -434,7 +431,7 @@ def _HandleImportFrom(stmt: ast.ImportFrom*) -> int:
_saw_viperbuild = 1
if stmt.names is None: return 0
n: sizet = stmt.names.__len__()
for i in range(n):
for i in range(int(n)):
al: ast.Alias* = ast.Alias*(stmt.names.get(i))
if al is not None and al.name is not None:
if string.strcmp(al.name, "Project") == 0:
@@ -487,7 +484,7 @@ def _ApplyCtorArgs(cl: ast.Call*) -> int:
_c_version = _DupStd(_g_out_str)
if cl.keywords is None: return 0
n: sizet = cl.keywords.__len__()
for i in range(n):
for i in range(int(n)):
kw: ast.Keyword* = ast.Keyword*(cl.keywords.get(i))
if kw is not None and kw.arg is not None and kw.value is not None:
if string.strcmp(kw.arg, "name") == 0:
@@ -640,7 +637,7 @@ def _HandleConfigCall(cl: ast.Call*) -> int:
"""Handle a proj.config(kw=...) call."""
if cl is None or cl.keywords is None: return 0
n: sizet = cl.keywords.__len__()
for i in range(n):
for i in range(int(n)):
kw: ast.Keyword* = ast.Keyword*(cl.keywords.get(i))
if kw is not None and kw.arg is not None and kw.value is not None:
if _SetField(kw.arg, kw.value) != 0: return 1
@@ -657,7 +654,7 @@ def _HandleBuildCall(cl: ast.Call*) -> int:
_c_has_build = 1
if cl.keywords is None: return 0
n: sizet = cl.keywords.__len__()
for i in range(n):
for i in range(int(n)):
kw: ast.Keyword* = ast.Keyword*(cl.keywords.get(i))
if kw is not None and kw.arg is not None and kw.value is not None:
if string.strcmp(kw.arg, "triple") == 0:
@@ -687,11 +684,11 @@ def _CallArgsHaveProjRef(cl: ast.Call*) -> int:
if cl is None: return 0
if cl.args is not None:
n: sizet = cl.args.__len__()
for i in range(n):
for i in range(int(n)):
if _IsProjRef(cl.args.get(i)) != 0: return 1
if cl.keywords is not None:
n = cl.keywords.__len__()
for i in range(n):
for i in range(int(n)):
kw: ast.Keyword* = ast.Keyword*(cl.keywords.get(i))
if kw is not None and kw.value is not None:
if _IsProjRef(kw.value) != 0: return 1
@@ -775,7 +772,7 @@ def _ScanFile(pool: memhub.MemBuddy*, path: str) -> int:
rc: int = 0
if body is not None:
cn: sizet = len(body)
for ci in range(cn):
for ci in range(int(cn)):
child: ast.AST* = body.get(ci)
if child is not None:
kd: int = child.kind()
@@ -796,9 +793,9 @@ def _ResolveModuleToFile(module_name: str) -> str:
"""Resolve a module name to a local project file path; None if not found."""
if module_name is None or _search_dirs is None: return None
if string.strlen(module_name) == 0: return None
out_abs: t.CPtr = stdlib.malloc(8)
out_rel: t.CPtr = stdlib.malloc(8)
out_inc: t.CPtr = stdlib.malloc(8)
out_abs: rawptr = stdlib.malloc(8)
out_rel: rawptr = stdlib.malloc(8)
out_inc: rawptr = stdlib.malloc(8)
if out_abs is None or out_rel is None or out_inc is None:
if out_abs is not None: stdlib.free(out_abs)
if out_rel is not None: stdlib.free(out_rel)
@@ -853,7 +850,7 @@ def _RunBfs(pool: memhub.MemBuddy*, entry_path: str) -> int:
if rc == 1: return 1
# Resolve module names collected from this file
pn: sizet = _pending.__len__()
for pi in range(pn):
for pi in range(int(pn)):
mod: str = _pending.get(pi)
if mod is not None:
dep: str = _ResolveModuleToFile(mod)
@@ -904,9 +901,9 @@ def _ProbeIncludes() -> str:
exe_dir: str = _DirOf(exe_buf)
stdlib.free(exe_buf)
if exe_dir is None: return None
for k in range(3 + 1):
for k in range(int(3 + 1)):
base: str = _DupStd(exe_dir)
for j in range(k):
for j in range(int(k)):
base = _Join2(base, "..")
cand: str = _Join2(base, "includes")
if _IsDir(cand) != 0: return cand
@@ -919,7 +916,7 @@ def _DeriveOutputFromEntry(entry_path: str) -> str:
if base is None: return "app.exe"
n: sizet = string.strlen(base)
cut: sizet = n
for i in range(n):
for i in range(int(n)):
if base[i] == '.':
cut = i
break
@@ -949,7 +946,7 @@ def _FinalizeCfg(entry_path: str) -> int:
# ---- Defaults ----
if _c_source is None: _c_source = _DupStd(_entry_dir)
if _c_buildroot is None: _c_buildroot = _Join2(_c_source, ".tpv_build")
if _c_buildroot is None: _c_buildroot = _Join2(_c_source, "__vpcbuild__")
if _c_includes is None:
probed: str = _ProbeIncludes()
if probed is None:
@@ -958,7 +955,7 @@ def _FinalizeCfg(entry_path: str) -> int:
_c_includes = probed
if _c_compiler is None: _c_compiler = _DupStd("llc -filetype=obj -relocation-model=pic")
if _c_linker is None:
# Built-in default = the verified link args from this repo's TransPyV/project.vpj.
# Built-in default = the verified link args from this repo's ViperC/project.vpj.
# Do NOT add -lgcc: msys64/mingw64 + lld has no libgcc and fails with "unable to find library -lgcc".
_c_linker = _DupStd("clang++ -fuse-ld=lld -lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -Wl,--stack,0x1000000 -Wl,--no-insert-timestamp -Wl,--export-all-symbols -g")
if _c_output is None: _c_output = _DeriveOutputFromEntry(entry_path)
@@ -1009,7 +1006,7 @@ def _SplitToolCmd(joined: str, which: int) -> None:
if joined is None: return
n: sizet = string.strlen(joined)
sp: sizet = n
for i in range(n):
for i in range(int(n)):
if joined[i] == ' ':
sp = i
break
@@ -1086,7 +1083,10 @@ def DiscoverAndApply(mb: memhub.MemBuddy*, entry_path: str) -> int:
if arena is None:
VLogger.error("viperbuild: scratch memory allocation failed", "viperbuild")
return 1
_scratch = memhub.MemBuddy(arena, VB_SCRATCH, 0, 0)
# Heap-allocate the manager: `_scratch` is a module-level global, so a frame-local
# `MemBuddy` temp would hand out a dangling address once this frame returns (LifetimeError).
# `hlife` is explicit so NLL frame-demotion cannot apply.
_scratch = new heap hlife memhub.MemBuddy(arena, VB_SCRATCH, 0, 0)
if _scratch is None:
VLogger.error("viperbuild: scratch pool initialization failed", "viperbuild")
return 1
+21 -24
View File
@@ -19,9 +19,8 @@ import string
import stdlib
import memhub
import argparse
import w32.win32console as w32cmd
import w32.win32base as w32base
import w32.fileio as fileio
import win32.win32console as win32cmd
import win32.win32base as win32base
import sys
import ast
import os
@@ -31,14 +30,12 @@ import os.path
import shutil
import mlirlite
import subprocess
import viperlib
import t, c
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
import lib.Phase.Phase1.Phase1Main as Phase1Main
# Main Pool 1 GiB
# Main Pool 1 GiB(不要简单调大:2 GiB 会让 MemBuddy 内部索引/位宽溢出 ——
# ASan: access-violation at memhub.vp:858 _fl_push,就在 __init__ 阶段。
# 池耗尽的正解是修泄漏:mlirlite 全局数组 dense 渲染的"每元素 ~5KB 不复用",见 includes e9e1933。)
define POOL_SIZE = 1073741824
# Permanent memory pool size: 2^29 + 264 = 512MB usable, then change to 1 GiB
define PERM_POOL_SIZE = 1073741824
@@ -47,11 +44,11 @@ define CODE_PAGE = 65001
# Source code buffer size (1MB)
define SRC_BUF_SIZE = 1048576
# Version (dev/beta/rc)
V_TPV_VERSION: __attribute__(section(".PRVERSN")) []char = "0.0.5-dev.479"
V_VPC_VERSION: __attribute__(section(".PRVERSN")) []char = "0.0.5-dev.479"
define V_VIPER_VERSION = "1.0.5-dev.970 2026-09-28 Non-LTS"
define V_RUNTIME_VERSION = "0.2.3+19-Non-LTS"
define V_TPC_COMMIT = "e376b949c9"
define V_TPV_COMMIT = "ab0f37e1a8"
define V_VPC_COMMIT = "ab0f37e1a8"
define V_RUNTIME_COMMIT = "f087aa88e2"
define V_TARGET = "x86_64-w64-windows-gnu"
define V_LLVM = "22.1.8"
@@ -62,11 +59,11 @@ define REPL_PROJ_DIAG_CAP = 2048
static a: int
# main: Command-line entry
def main() -> int:
w32cmd.SetConsoleOutputCP(CODE_PAGE)
w32cmd.SetConsoleCP(CODE_PAGE)
win32cmd.SetConsoleOutputCP(CODE_PAGE)
win32cmd.SetConsoleCP(CODE_PAGE)
detect_flag: int = 0
cmd_line_d: str = w32base.GetCommandLineA()
cmd_line_d: str = win32base.GetCommandLineA()
if cmd_line_d is not None and string.strstr(cmd_line_d, "--detection") is not None: detect_flag = 1
# --detection-deep: heavy diagnostics (per-alloc probe + periodic full-table scan; extremely slow, off by default); read via raw command-line strstr before MemBuddy
@@ -137,13 +134,13 @@ def main() -> int:
# initialize VLogger and print the startup log
VLogger.init_logger()
log: VLogger.Logger = VLogger.logger
if log is None: log.error("TransPyV Failed to parse parameters")
if log is None: log.error("ViperC Failed to parse parameters")
# initialize command-line args (Windows: GetCommandLineA, POSIX: /proc/self/cmdline)
sys._init_argv()
# create the argument parser
parser: argparse.ArgumentParser = argparse.ArgumentParser("TransPyV", "TransPyV command-line parameter parser", pool=mb)
parser: argparse.ArgumentParser = argparse.ArgumentParser("ViperC", "ViperC command-line parameter parser", pool=mb)
# register arguments (matching Projectrans.py main())
parser.add_argument("--project", None, argparse.STRING, 0, None, False, argparse.STORE, "Project VPJ path, by default it looks in the current directory")
@@ -166,7 +163,7 @@ def main() -> int:
parser.add_argument("--detection-deep", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Deep detection: probe every allocation and enable the periodic full-table scan (implies --detection; extremely slow, diagnostics only)")
parser.add_argument("--show-args", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show all parsed arguments")
parser.add_argument("--repl", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Start the interactive REPL (M2 session variables + M3 cross-fragment functions: fragment -> DLL -> call)")
parser.add_argument("--build-dir", None, argparse.STRING, 0, None, False, argparse.STORE, "Override project.vpj build_dir (default ./.tpv_build; relative paths resolve against the project dir)")
parser.add_argument("--build-dir", None, argparse.STRING, 0, None, False, argparse.STORE, "Override project.vpj build_dir (default ./__vpcbuild__; relative paths resolve against the project dir)")
parser.add_argument("--no-llvmpeephole", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Disable all local mlirlite peephole passes (T2 store/load forward & ret-zero, T3 no-op bitcast) in FunctionPrint serialization")
parser.add_argument("--version", "-v", argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show version and exit")
parser.add_argument("--help", "-h", argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show help and exit")
@@ -186,7 +183,7 @@ def main() -> int:
Copyright (C) 2026 Viper Project
License MIT <https://opensource.org/licenses/MIT>
GVSDS(TM) Viper SE Runtime Environment and Library (VRTE) %s
Viper Frontend Compiler TransPyV %s
Viper Frontend Compiler ViperC %s
Author: terminexus@gvsds.com
Tpv commit: %s
@@ -194,7 +191,7 @@ Tpc commit: %s
VRTE commit: %s
Target: %s
""", V_VIPER_VERSION, V_RUNTIME_VERSION, V_TPV_VERSION, V_TPV_COMMIT, V_TPC_COMMIT, V_RUNTIME_COMMIT, V_TARGET, V_LLVM)
""", V_VIPER_VERSION, V_RUNTIME_VERSION, V_VPC_VERSION, V_VPC_COMMIT, V_TPC_COMMIT, V_RUNTIME_COMMIT, V_TARGET, V_LLVM)
return 0
# --repl guard: mutually exclusive with --phase2command / --asan, checked first
@@ -212,7 +209,7 @@ Target: %s
stdio.printf("FAIL: --repl and --asan are mutually exclusive\n")
stdio.printf(" Reason: --asan first instruments the fragment .ll with ASan, but the fragment artifact is a runtime\n")
stdio.printf(" DLL loaded by LoadLibraryA, and the host process does not link the ASan runtime; instrumentation semantics are undefined\n")
stdio.printf(" Fix: drop --asan (to debug memory issues, build an ASan TransPyV in project mode)\n")
stdio.printf(" Fix: drop --asan (to debug memory issues, build an ASan ViperC in project mode)\n")
argparse.release(args)
return 2
# --traceback is also rejected under --repl: the fragment is a runtime-loaded
@@ -221,7 +218,7 @@ Target: %s
stdio.printf("FAIL: --repl and --traceback are mutually exclusive\n")
stdio.printf(" Reason: --traceback relies on startup-ctor injection + a per-module DWARF side file,\n")
stdio.printf(" but the fragment is a runtime LoadLibraryA-loaded DLL; the two semantics are undefined\n")
stdio.printf(" Fix: drop --traceback (for crash stacks, build a --traceback TransPyV in project mode)\n")
stdio.printf(" Fix: drop --traceback (for crash stacks, build a --traceback ViperC in project mode)\n")
argparse.release(args)
return 2
@@ -229,7 +226,7 @@ Target: %s
# after project-config load and before Phase1/Phase2, then returns.
# print parse result
# log.banner("TransPyV argument parse result")
# log.banner("ViperC argument parse result")
# string arguments
project: str = args.get_str("project")
@@ -328,7 +325,7 @@ Target: %s
else:
stdio.printf("FAIL: --repl could not find the project file repl.vpj\n")
stdio.printf("%s", repl_diag_buf)
stdio.printf(" You can also pass --project to specify the project file explicitly (e.g. TransPyV/project.vpj in this repo)\n")
stdio.printf(" You can also pass --project to specify the project file explicitly (e.g. ViperC/project.vpj in this repo)\n")
argparse.release(args)
return 1
# load project config (explicit --project / repl.vpj found by --repl)
@@ -350,7 +347,7 @@ Target: %s
# extract project.vpj's dir so relative paths resolve against it
proj_len: sizet = string.strlen(proj_path)
slash_pos: sizet = proj_len
for i in range(proj_len):
for i in range(int(proj_len)):
idx: sizet = proj_len - 1 - i
ch: char = proj_path[idx]
if ch == '/' or ch == '\\':
@@ -474,7 +471,7 @@ Target: %s
mf_ret: int = Phase2.RunMultiFileProject(mb, Config.SourceDir, mf_temp, mf_output, mf_cc, mf_cc_flags, mf_linker, mf_linker_flags, mf_linker_out, mf_includes_bin, Config.IncludesDirs, do_phase1, do_phase2, log, args, mf_static_libs, mf_dynamic_libs)
argparse.release(args)
log.success("TransPyV complete")
log.success("ViperC complete")
return mf_ret
# Reaching here means no buildable project root (SourceDir missing): fail fast, no silent "done"