Files
TransPyC/VKERNELERR.md
T

24 KiB
Raw Blame History

VKERNELERR — VKernel LLVM IR 与源码对照:TPV 编译器 codegen 缺陷清单

背景:ViperOS 内核(VKernel)用旧版 TPC 编译可完全正常运行;用新版 TPV/TPC 编译后出现概率性崩溃、内存破坏(.rodata 格式串被逐步覆写)、desktop 启动约 11s 后挂死。怀疑编译器的 LLVM IR 生成存在回归,本文件记录对照结果。

对照对象:ViperOS\VKernel\Kernel\*.vp(源码)↔ .tpv_build\temp\<hash>\*.ll / *.text.ll / *.vpi(IR)。模块哈希映射见 _sha1_map.txt。

验证状态标记:✅ 已亲自复核 IR 行号;📎 子代理报告(建议复核)。


一、确凿的 codegen 缺陷(✅ 已复核)

A1. 全局变量初始化常量 -1 被静默改为 0(zeroinitializer)

✅ 已修复(TransPyV commit 4db68ff,待重编验证):TranslatorGenMixin.handle_module_level_var 的初始值解析(AnnAssign/Assign 分支)新增 UnaryOp 处理(extract_cdefine_int_value),负数字面量 -1 现在正确提取。覆盖 main.vp kbd_tid/mse_tid、desktop 7 个、uhci _irq、usb_mouse mse_slot_id 等模块级 -1 全局。

  • 现象:desktop.vp 中 7 个 = -1 的模块级全局全部编译成 global i32 zeroinitializer(值 0)。
  • IR:temp\1e\1eb7b5be276f0a8f.ll L2250/2251/2268/2269/2277/2280/2291
  • 源码:services\desktop.vp L113 _dirty_y_max=-1、L114 _vblank_ok=-1、L276 _last_hover_win=-1、L277 _focused_win=-1、L550 _drag_idx=-1、L561 _rsz_drag_idx=-1、L1282 _cached_top_win=-1
  • 影响:
    • _drag_idx=0 → _update_mouse 中 if _drag_idx >= 0 恒真 → 窗口 0 被无条件拖拽(改写 aw2.x/y)
    • _rsz_drag_idx=0 → 无条件进入窗口缩放分支
    • _focused_win=0 → 键盘事件错误路由到窗口 0
    • _last_hover_win=0 / _cached_top_win=0 → 错误 hover/active 判定
    • _vblank_ok=0 → vblank 同步被跳过
  • 性质:vpi 层初始值确为 -1,是 vpi → LLVM IR 阶段负常量丢失(同一类病灶见 A2)。
  • 严重性:高(行为改变,最可疑的"新版崩溃"诱因之一)

A2. 字符数组全局初始化数据全部丢失(128×2 字节全 0)

✅ 已修复(TransPyV commit 4db68ff,验证中):_build_array_initializer_text 的数组元素提取新增 CONST_CHAR 分支(int_val 即字符码)。

🔧 补充修复(includes commit 49445ea):验证发现 '\x1b' 仍为 0 —— 根因是 lexer 不支持 \xNN 十六进制转义(lexer.vp 把 \x1b 当未知转义保留为 \+x 2 字节 → esc_len≠1 → 被判为 CONST_STR 而非 CONST_CHAR)。已加 _hex_val + \xNN 转义分支(对齐 Python)。需重编 TPV + ViperOS 后验证 _ps2_set1[1] = 27。

验证记录(✅ 全部通过 2026-08-30):重编后 _ps2_set1[1] = 27(ESC,\xNN 修复生效),'1'=49、'2'=50、'q'=113、'['=91、'\n'=10、'\b'=8、'\t'=9 全部与源码一致;_ps2_set1_shift 同理('!'=33、'@'=64、'Q'=81、'{'=123)。A1/A2 均验证通过。

  • 现象:_ps2_set1 / _ps2_set1_shift 两张 128 字节 PS/2 扫描码表编译成全部 i8 0。
  • IR:temp\1e\1eb7b5be276f0a8f.ll L2275-2276(global [128 x i8] [i8 0, ...])
  • 源码:services\desktop.vp L296-306、L308-318('\x1b','1','2',... 真实数据)
  • 影响:_ps2_scancode_to_ascii 恒返回 0,PS/2 键盘输入完全失效(USB 键盘走 _hid_to_ascii 不受影响)。
  • 性质:与 A1 同类 —— 全局初始化器数据在 IR 生成阶段被丢弃。数值字面量数组(_CURSOR_*)正常,唯独字符字面量数组全丢。
  • 严重性:高(确凿的初始化数据丢失,TPC 前端/IR 生成回归)

A3. (源码误用,非编译器 bug)str | t.CPtr 联合类型按 i8** 处理下标/算术

⚠️ 2026-08-30 用户确认:str | t.CPtr 预期语义就是 i8**(指针的指针)。编译器把 last_slash(str | t.CPtr 注解)按下标写 8 字节、+1 偏移 8 —— 符合类型语义。问题是内核源码把 strrchr 的 str(i8*)返回值赋给 i8** 变量却按 i8* 使用。

✅ 已修复(ViperOS commit 333adb0,验证通过 2026-08-30):devfs.vp 4 处(L218/224 create_file、L253/259 open)last_slash/file_name 注解 str | t.CPtr → 裸 str。重编后 IR(temp\81\815a07186c8962c8.text.ll L1302-1313)last_slash[0]='\0' 为 1 字节 store i8 0, i8*、+1 为 1 字节 getelementptr i8。

修复方向(内核侧):devfs.vp L218/224(create_file)、L253/259(open)改用裸 str(i8*):

last_slash: str = string.strrchr(path_copy, '/')
file_name: str = last_slash + 1

编译器加固点:i8* → i8** 赋值当前做隐式 bitcast —— 按隐式转换规则"不同级指针不允许隐式转换"应 fail-fast 报错(避免同类误用被静默编译)。

  • 现象:devfs create_file/open 中 last_slash: str | t.CPtr(i8**):
    • last_slash[0] = '\0' → 编译成 getelementptr i8*, i8** %24, i32 0 + store i8* null, i8** %25 —— 8 字节写
    • file_name = last_slash + 1 → getelementptr i8*, i8** %29, i64 1 —— 偏移 8 字节
  • IR:temp\0f\0f22b8ba53f4d613.text.ll L1296-1307(create_file)、L1431-1442(open)
  • 源码:drivers\devfs\devfs.vp L218-224、L253-259(strrchr 返回 str=i8*,赋给 i8** 变量)
  • 后果链:文件名开头 8 字节被清 0 → 所有设备注册成空名 → 第 2 个 create_file 的 find_in_dir("") 命中第一个空名返回 -1 → devfs 只能注册 1 个空名设备;open() 恒命中空名文件。
  • 严重性:高(devfs 功能整体崩坏;但非编译器行为错误,是源码类型误用)

A4. (源码误用,非编译器 bug)c.Set(裸指针形参, 值) 只改指针变量本身,不是写所指地址

⚠️ 2026-08-30 用户澄清 c.Set 语义:c.Set(a, b) = a = b(含把指针变量本身改成 b);c.Set(c.Deref(a), b) = a = b。因此 paging save_pt_range 的写法是内核源码误用*,编译器行为符合语义。

✅ 已修复(ViperOS commit 463a200,验证通过 2026-08-30):paging.vp L426/L444 c.Set(save_count, ...) → c.Set(c.Deref(save_count), ...)。重编后 IR(temp\7a\7ad9eacd898aa9e8.text.ll L2241-2243)store i32 0, i32* %21(写所指地址),不再 inttoptr 改指针变量。

保留记录供排查参考:paging save_pt_range 的 out 参数 save_count: t.CUInt32T | t.CPtr:

  • c.Set(save_count, t.CUInt32T(0)) 编译成 inttoptr i32 0 to i32* + store i32* %20, i32** %3 —— 把指针变量置成 NULL
  • 后续 c.DerefAs(save_count) 解引用 NULL → #PF
  • 修复方向(内核侧):应写 c.Set(c.Deref(save_count), t.CUInt32T(0))、c.Set(c.Deref(save_count), t.CUInt32T(saved + 1))
  • IR:temp\20\20f811d509884de2.text.ll L2241-2242、L2411-2414
  • 源码:paging\paging.vp L425、L443(写),L439(读用 DerefAs)
  • 严重性:高(函数一旦被调用必然 NULL 解引用崩溃;非编译器问题)

A5. t.CUInt64T 无符号循环比较被编译成有符号 icmp slt

✅ 已修复(TransPyV commit a0d6dce,验证通过 2026-08-30):ExprGenOps.translate_compare 比较 predicate 按 _node_type_unsigned(Name→变量 AnnotClassName / Call→转换目标 / Attribute→类型名 / 递归)判定操作数无符号性,无符号则选 ULT/ULE/UGT/UGE。重编后 paging MakeRangeRO while va < vaddr_end 生成 icmp ult i64(temp\7a\7ad9eacd898aa9e8.text.ll L1267)。

  • IR:temp\20\20f811d509884de2.text.ll L2252(while va < vaddr_end → icmp slt i64)、L1267(MakeRangeRO 同样)
  • 源码:paging\paging.vp L260、L427(va/vaddr_end 均为 t.CUInt64T)
  • 影响:地址跨 2^63 边界时语义错误(扫错范围/立即退出)。当前 ro_start/ro_end 同为高地址(同号)碰巧正确。
  • 严重性:中(类型保真缺陷,当前未触发)

A6. %d/%i 非 long 分支缺 32 位截断+符号扩展(与 %u/%x 不对称)

✅ 已修复(TransPyV commit a0d6dce,验证通过 2026-08-30):CallGenVa._translate_va_arg_sugar/_translate_arg_sugar 读 8 字节变参槽后 build_trunc 回目标宽度(<64 位整数),调用方 t.CInt64T(i32) 再 sext 正确符号扩展。重编后 %d else 分支出现 trunc i64 to i32 + sext i32 to i64(temp\bb\bba983c61a970f7c.text.ll L993-994)。

  • IR:temp\bb\bba983c61a970f7c.text.ll L985-994 —— long 分支(if.then.47)与 else 分支(if.else.47)逐指令相同 va_arg i64,else 分支缺 trunc to i32 + sext to i64(对比 is_char/is_short 分支 L1000-1013 有 trunc+sext)
  • 源码:vpsdk\stdio.vp(else: num_l = t.CInt64T(va_arg(ap, t.CInt)) 语义要求读 32 位再符号扩展)
  • 对照:%u/%x/%X/%o/%b 的 else 分支均有 trunc i64 to i32 + zext i32 to i64,唯独 %d/%i 缺失
  • 影响:调用方 int 参数高 32 位含垃圾时,%d 把负数当大正数输出(desktop dirty=%d vsync=%d、elf/mm 大量 %d)。
  • 严重性:中高(明确的 codegen 语义错误;x86 写 32 位寄存器清高 32 位所以当前碰巧多数正确)

A7. AllocPage 内联汇编丢 ~{al} clobber

✅ 已修复(TransPyV commit d14415a,验证通过 2026-08-30):CallGenCBuiltin._resolve_register 约束映射表补全 8 位寄存器 clobber(AL/AH/BL/BH/CL/CH/DL/DH,对齐 t.py 标准常量集)。重编后 paging AllocPage 约束为 "r,~{al},~{ecx},~{rdi}"(temp\7a\7ad9eacd898aa9e8.text.ll L110)。

  • IR:temp\20\20f811d509884de2.text.ll L110(asm sideeffect "mov al,0...rep stosb", "r,~{ecx},~{rdi}" 缺 al/rax)
  • 源码:paging\paging.vp L68-72(op 列表含 CLOBBER_AL)
  • 影响:LLVM 不知道 AL 被改,可能把页地址 $0 分配进 RAX 被 mov al,0 破坏。当前因 pt_pool 4K 对齐低字节恰为 0 而未炸。
  • 严重性:中(约束错误,应修)

二、高嫌疑(📎 子代理报告,建议复核/对照旧产物)

B1. for x in ptr/数组 缺"解引用为空终止"语义 → devfs find 全量遍历崩溃

✅ 已修复(TransPyV commit 9b46e1d,待重编验证):用户澄清语言语义 —— for x in ptr 遍历到解引用为空即终止,空值宽度按元素类型(整数→0、指针→null、浮点→0.0)。编译器实现:

  • _handle_ptr_iter(指针路径):元素类型放宽为整数/指针/浮点(此前指针元素 i8** 场景 fatal_error)
  • _handle_array_iter(数组路径):cond 拆块,idx < count 长度上限 + 解引用为空提前终止(devfs result 是 CArray[str,256],split 只填 token + NULL 哨兵 → 遍历到哨兵停)

ViperOS commit c6a4063:devfs find 恢复 for i in result(不再 range(token)),靠编译器 NULL 终止语义工作。B1 是 A3 修复后"跑到 [STK] 就崩溃"的直接嫌疑(open("/dev/serial0") → find("/dev", root) → 旧语义全量 256 槽 → strcmp(NULL) → #PF)。

  • devfs_directory.find(drivers\devfs\devfs.vp L35-47)中 for i in result 编译为全量 256 槽迭代(temp\81\815a07186c8962c8.text.ll L477 icmp slt i32 %31, 256),result 为未清零栈数组(L470-472 load+store 整个 [256 x i8*]),split 只填 token 并设尾哨兵,其余槽位为栈垃圾;samestr→strcmp 无 NULL 防护(includes\string.vp L84-85)→ 遍历到哨兵/垃圾槽时 #PF。
  • 旧编译器若清零数组或按 count 迭代,行为完全不同 —— 最像"新版崩溃"的直接触发点。

B2. 对象型全局 X: T = T() 构造语义不保真(双 storage + 栈临时 self + c.Addr 张冠李戴)

✅ 已修复(TransPyV 待提交,待重编验证): 根因(devfs devfs_init IR temp\92\920070a06bc630a1.text.ll L57-70 实锤):

  1. 类型比较是地址比较:AssignGenMixin 三处 storage 绑定分支用 t.CUInt64T(ty1) == t.CUInt64T(ty2)(或 ty1 == ty2)判类型相等,而 llvmlite 类型非 canonical(includes\llvmlite\__types.vp Ptr/Struct 每次 pool.alloc 新实例)。X = T() 构造调用的 rhs 类型实例(CallGenCtor 的 struct_ty)与 storage 类型实例(handle_module_level_var 的 resolved_stg_ty)不同 → 绑定失败 → @X = &栈临时(悬垂),@X.stg 恒全 0(IR L61-67);
  2. c.Addr 误伤绑定:DevFSObject = c.Addr(_DevFSObject_instance) 的 rhs 类型 pointee 恰是 resolved_stg_ty 同实例 → 误命中绑定 → @DevFSObject = &@DevFSObject.stg(应为 &@_DevFSObject_instance.stg)+ 值复制零对象(IR L58-60)→ DevFSObject 与 _DevFSObject_instance 脱钩。 修复(AssignGenMixin.vp):
  • 新增 _stg_ty_equal:同实例快路径 + TypePrint 内容比较(命名结构体比名、匿名比布局),替换三处地址比较(L1242/L1320/L1956);
  • 新增 _is_addrlike_call:识别 c.Addr/c.Deref/c.Cast/c.Ptr 显式指针调用,绑定分支排除之(L1231/L1309/L1945)——X = c.Addr(Y) 改走普通 store(@X = &Y.stg,X 与 Y 指向同一对象)。 效果:X = T() 绑定 storage(构造结果落 BSS,全局不悬垂);X = c.Addr(Y) 直接存指针(不再张冠李戴)。
  • sched.vp:231 _sched_storage = Scheduler() / _sched_ptr = c.Addr(_sched_storage):
    • IR(temp\77\775840c75419f22c.ll L4612-4615)把 Scheduler.__init__ 的 self 编成主模块栈上的 920 字节 alloca;
    • IR(temp\3d\3d4afd9cdffeba6d.ll L748-751)生成两个独立 storage:_sched_storage.stg 与 _sched_ptr.stg,__init__ 用一次"全结构体值拷贝"从前者拷到后者 —— _sched_ptr != &_sched_storage,语义不保真;_sched_storage.stg 恒全 0;
    • 若 __init__ 写 self.xxx(未走全局指针重定向),数据落在栈临时对象上 → 全局 storage 全 0 → 崩溃。
  • devfs 同构(temp\0f\...text.ll L55-72):DevFSObject = c.Addr(_DevFSObject_instance) 的 store 目标是 @DevFSObject.stg(应为 @_DevFSObject_instance.stg);serial0_file = devfs_file() 等指针全局指向已失效的栈帧(L277-371)。
  • 性质:模块级值对象构造的通用 codegen 模式,极脆弱。当前因 __init__ 恰好不写 self / 引用点恰好在栈帧存活期而"侥幸工作"。

B3. memset 跨模块声明签名不一致(i32 vs i64 长度参数)

✅ 已修复(TransPyV 待提交,待重编验证): 根因(新产物实锤:process temp\ed\edb4366475576ebf.ll L487 declare @memset(i8*,i32,i32)、L1811 call ... i32 32;定义 temp\b7\b758eeb73e54002a.ll L938 define @memset(i8*,i32,i64)): CallGenBase._ensure_method_declare(L494)建跨模块 declare 时默认用实参类型,仅当 call_name 带 sha1 前缀(L575 call_name[16]=='.')才查 vpi 定义侧参数注解修正宽度。裸名 CExport 函数(string.memset → @memset,string.vp def memset(...) -> ... | t.CExport)无前缀 → 提取定义侧模块失败 → 第三参字面量 32=i32 固化窄。sched 未调用 memset → 无调用侧 declare → vpi stub 注入 i64(正确)。 当前碰巧安全:x86-64 写 32 位寄存器(mov edx)自动清高 32 位 → 定义读 rdx=32;但 IR 层签名不一致是未定义行为隐患(优化路径读高位垃圾写穿内存)。 修复:

  • CallGenBase._ensure_method_declare:裸名(无 .)且是已知 C 库函数 → 直接走 CallGenLib._ensure_c_lib_declare 标准签名(memset 第三参 i64);
  • CallGenLib._promote_clib_call_args:跨模块调用点把第三参窄整数实参提升 i64(常量 → i64 常量,SSA → zext),重建实参链;
  • CallGenMixin 跨模块调用出口(L1364-1370)先提升实参再建 declare。 预期:process memset declare/call 均 (i8*, i32, i64),与定义一致。
  • process 模块声明 declare i8* @memset(i8*, i32, i32)(temp\ed\edb4366475576ebf.ll L487),调用 create_process 中 string.memset(p.name, 0, 32)(L1811)传 i32 长度;
  • 实际定义在 temp\b7\b758eeb73e54002a.ll L938 为 @memset(i8*, i32, i64)(内部按 i64 读长度);sched 模块声明却是正确的 i64。
  • 当前调用点碰巧生成 mov ecx, 32(清高位)未炸;一旦优化产生非清零高位路径,rep stosb 写穿内存。

B4. sched.vp:208 create_thread 栈分配失败不恢复 thread_count(源码 bug)

✅ 已修复(ViperOS 待提交):sched.vp:208 if not stack_ptr: 前 s.thread_count -= 1 回退槽位(对齐 process.vp create_process 失败路径)。线程槽位不再泄漏,调度器不会扫描未初始化槽位。

  • 对比 process.vp:200 失败路径有回退。线程槽位泄漏 → thread_count 漂移 → 调度器扫描未初始化槽位。

B5. t.attr 系列属性丢失(packed/aligned/section 等)

✅ 已修复(TransPyV + includes 待提交): 根因:@c.Attribute(t.attr.packed) 等装饰器被编译器忽略——

  1. packed:LLVMType/NamedTypeNode 无 packed 概念,类型定义恒输出 {...}(自然对齐)。idt_entry 碰巧 16 字节无差异(temp\58\58700b4401f1b4eb.ll L5),但 gdt/elf/fat32 等二进制格式结构体若字段变化即插填充 → 磁盘/硬件布局错乱;
  2. aligned:fpu_context(FXSAVE 区域需 16 对齐)无全局/alloca 对齐 → fxsave/fxrstor #GP 风险;
  3. section:main.vp boot 的 t.attr.section(".text.startup") 被 _append_llvm_attr 拒绝 → 函数放错段。 修复:
  • includes/llvmlite:NamedTypeNode.IsPacked(输出 %"name" = type <{...}>,LLVM packed struct 无填充)+ module_set_named_type_packed;GlobalVariable.Align(输出 , align N);PrintStructDefinition/_print_struct_body 加 packed 参数;
  • TPV 编译器:StructEntry.IsPacked/Align;ClassGenBase 新增 _class_has_tattr/_tattr_name/_class_aligned_value/_tattr_aligned_int 解析 @c.Attribute(t.attr.*);ClassGenDef 注册 packed 类型 + 记录 aligned;TranslatorGenMixin 全局/storage 施加对齐;FuncGenBase.extract_func_section + llvmlite.function_set_attr_group:section 走属性组 #N(define f() ... #1 + attributes #1 = { "section"=".text.startup" })——LLVM 拒绝内联 section="..."(llc: expected string constant,boot 函数首编实测)。
  • stub packed 最终根因(2026-08-30 二次验证补):定义模块 packed 已生效(<{...}>),但跨模块 stub 注入仍 {...}——StructTable 每 Phase 重建,Phase B existing 是新建 entry,module_set_named_type_packed 只设节点不设 entry.IsPacked;VpiReader 注入的 entry_c.IsPacked 同样缺失 → 后续跨模块 ensure_struct_def_in_module 按 entry.IsPacked 判定读到 0 → stub 非 packed(GEP 布局错位)。已补:ClassGenDef Phase B existing 路径 existing.IsPacked = 1(L131)+ VpiReader._inject_class_struct_declare entry_c.IsPacked = 1(L1762)。
  • stub packed 终局根因(2026-08-30 静态定位+验证):上述 IsPacked 补设后,per_cpu_data/gdt_entry 在值类型注入路径仍非 packed——VpiReader._ensure_full_value_types(vpi 全局变量 @_per_cpu = global %per_cpu_data、@gdt = global [7 x %gdt_entry] 及值返回类型注入完整定义)只 module_add_named_type 不 module_set_named_type_packed → 引用模块节点 IsPacked=0 → 输出 {…}。而 _tss/idt_entry(字段/属性引用触发 ensure 路径)packed、17/fpu.vp(代码引用触发 ensure)packed——同模块两种结果根因。已修:_ensure_full_value_types Struct 分支 add 后按 full_se_h.IsPacked 同步 set_packed。重编验证:全部引用模块 per_cpu_data/gdt_entry/idt_entry 输出 <{...}>,非 packed 残留 0。 说明:naked/aligned(函数)LLVM IR 无对应属性(GCC 扩展),当前无实际危害(naked 函数体是纯 c.Asm+ret,llc 无局部变量不生成序言);stackrealign/noinline/nounwind/always_inline 等已有映射。
  • idt_entry 的 @c.Attribute(t.attr.packed) 未体现在 IR type(temp\58\58700b4401f1b4eb.ll L5)。当前字段自然对齐碰巧与 packed 相同(16 字节),改动字段即插填充。

B6. 非 entry 块 alloca → 动态 alloca → 运行时大 sub rsp → 概率性栈越界 #PF(2026-08-30 定位)

✅ 已修复(TransPyV 待提交):StmtGenCifMixin._handle_array_iter(数组 for-in)与 _handle_list_iter(列表字面量 for-in)的临时数组 alloca 原用 llvmlite.build_alloca 在 for 语句所在块(非 entry)创建。旧版 LLVM 将非 entry alloca 降为 DynamicAlloca → 运行时 sub rsp, sizeof([N x elem]) → 调用栈深时 rsp 骤降踩未映射栈页 → 概率性 #PF。已改 VarGen._alloca_at_entry(entry 静态 alloca,进帧由 LLVM 统一布局)。

实测崩溃样本(serial.log):[0E][PF] rip=0x103002 err=0x2 rsp=0x7bf10,ret 链 0x104948 → 0x10003e,[main] sched started 之后,devfs 路径解析链:

  • 反汇编 kernel.bin 偏移 0x2150 = devfs_directory.find(prologue sub rsp 0x1038;strchr(path,'/');split(path,"/",result@rbp-0x1008)),调用者 0x48b0 = devfs open 路径解析(strchr('/') + 63 项 vtable 表 + callq *0x10(%rax));
  • 崩溃指令 0x3002 movq %rcx, -0x668(%rax) 位于 %30 动态 alloca(sub rsp 0x800)的整数组初始化拷贝(%29=load [256 x i8*] %3; %30=alloca; store 展开的逐条 movq);
  • IR(temp/92/920070a06bc630a1.text.ll L427-476):result: t.CArray[str, 256] → %3=alloca [256 x i8*] → for i in result: → L474-476 整数组 load + 非 entry alloca + 整数组 store;
  • llc 22.1.8(msys2 clang64)同 IR 会把该非 entry alloca 静态化(rbp 基址拷贝)——故 TPV 配套旧 LLVM 是直接触发点。 概率性机制:调用深度/路径分段数不同 → rsp 位置不同 → 动态 alloca 后是否越过栈映射页界决定是否 #PF。

三、与运行时崩溃现象(serial.log)的关联分析

现象 最相关发现 推理
.rodata 格式串逐步被覆写(win=' / work=,) A3 类(联合指针 8 字节写错位) 越界写源在 .rodata 附近或指针算术错位;.rodata 因 2MB 大页未真正只读而静默吞掉写(ProtectKernelRO 已修复分裂,下轮可 #PF 定位写源)
desktop 启动 ~11s 挂死 A1(_drag_idx/_rsz_drag_idx=0) 窗口 0 被无条件拖拽/缩放,读写 _wins[0] 未初始化字段,可能放大为越界写
早期 sched started 后立即崩溃(devfs find rax=0) B1(find 遍历未初始化栈数组) open("/dev/...") 多级路径 strcmp NULL → #PF
eyboard 测试打印错乱(a[4] 应打印 o) 编译器联合/str 索引问题 c.Addr(a[4]) 对 `t.CConst
设备文件/键盘输入异常 A2(PS/2 表全 0)、A3(devfs 空名) 初始化数据丢失 + 文件名 8 字节写

四、建议修复方向(TPV 编译器侧,按优先级)

  1. 全局初始化器生成(A1/A2):修复 vpi → LLVM IR 阶段负常量与字符数组初始化数据的丢失 —— 最高优先,直接影响所有 = -1 与字符表。
  2. 联合指针类型解析(A3/A4):T | t.CPtr(含 str | t.CPtr)联合类型应保留"裸指针"语义(i8* 元素步长 1),而非统一解析为 i8**;c.Set(联合指针形参, v) 应写所指地址而非改指针变量。
  3. 对象型全局构造(B2):X: T = T() 的 __init__ self 应指向全局值 storage(&X.stg);P = c.Addr(X) 应生成 store @X.stg → @P,而非独立 .ptr.stg + 结构体值拷贝。
  4. 类型保真(A5/A6):无符号比较用 icmp ult;%d/%i 非 long 分支补 trunc+sext;统一 memset 声明;补 AllocPage 的 ~{al} clobber。