24 KiB
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.vpkbd_tid/mse_tid、desktop 7 个、uhci_irq、usb_mousemse_slot_id等模块级 -1 全局。
- 现象:
desktop.vp中 7 个= -1的模块级全局全部编译成global i32 zeroinitializer(值 0)。 - IR:
temp\1e\1eb7b5be276f0a8f.llL2250/2251/2268/2269/2277/2280/2291 - 源码:
services\desktop.vpL113_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当未知转义保留为\+x2 字节 → 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.llL2275-2276(global [128 x i8] [i8 0, ...]) - 源码:
services\desktop.vpL296-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.llL1302-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.llL1296-1307(create_file)、L1431-1442(open) - 源码:
drivers\devfs\devfs.vpL218-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。因此 pagingsave_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.llL2241-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.llL2241-2242、L2411-2414- 源码:
paging\paging.vpL425、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 MakeRangeROwhile va < vaddr_end生成icmp ult i64(temp\7a\7ad9eacd898aa9e8.text.llL1267)。
- IR:
temp\20\20f811d509884de2.text.llL2252(while va < vaddr_end→icmp slt i64)、L1267(MakeRangeRO 同样) - 源码:
paging\paging.vpL260、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 正确符号扩展。重编后%delse 分支出现trunc i64 to i32+sext i32 to i64(temp\bb\bba983c61a970f7c.text.llL993-994)。
- IR:
temp\bb\bba983c61a970f7c.text.llL985-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把负数当大正数输出(desktopdirty=%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.llL110)。
- IR:
temp\20\20f811d509884de2.text.llL110(asm sideeffect "mov al,0...rep stosb", "r,~{ecx},~{rdi}"缺 al/rax) - 源码:
paging\paging.vpL68-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长度上限 + 解引用为空提前终止(devfsresult是 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.vpL35-47)中for i in result编译为全量 256 槽迭代(temp\81\815a07186c8962c8.text.llL477icmp slt i32 %31, 256),result为未清零栈数组(L470-472 load+store 整个 [256 x i8*]),split 只填 token 并设尾哨兵,其余槽位为栈垃圾;samestr→strcmp无 NULL 防护(includes\string.vpL84-85)→ 遍历到哨兵/垃圾槽时 #PF。- 旧编译器若清零数组或按 count 迭代,行为完全不同 —— 最像"新版崩溃"的直接触发点。
B2. 对象型全局 X: T = T() 构造语义不保真(双 storage + 栈临时 self + c.Addr 张冠李戴)
✅ 已修复(TransPyV 待提交,待重编验证): 根因(devfs devfs_init IR
temp\92\920070a06bc630a1.text.llL57-70 实锤):
- 类型比较是地址比较:AssignGenMixin 三处 storage 绑定分支用
t.CUInt64T(ty1) == t.CUInt64T(ty2)(或ty1 == ty2)判类型相等,而 llvmlite 类型非 canonical(includes\llvmlite\__types.vpPtr/Struct每次pool.alloc新实例)。X = T()构造调用的 rhs 类型实例(CallGenCtor 的 struct_ty)与 storage 类型实例(handle_module_level_var 的 resolved_stg_ty)不同 → 绑定失败 →@X = &栈临时(悬垂),@X.stg恒全 0(IR L61-67);- 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.llL4612-4615)把Scheduler.__init__的 self 编成主模块栈上的 920 字节 alloca; - IR(
temp\3d\3d4afd9cdffeba6d.llL748-751)生成两个独立 storage:_sched_storage.stg与_sched_ptr.stg,__init__用一次"全结构体值拷贝"从前者拷到后者 ——_sched_ptr != &_sched_storage,语义不保真;_sched_storage.stg恒全 0; - 若
__init__写self.xxx(未走全局指针重定向),数据落在栈临时对象上 → 全局 storage 全 0 → 崩溃。
- IR(
- devfs 同构(
temp\0f\...text.llL55-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.llL487declare @memset(i8*,i32,i32)、L1811call ... i32 32;定义temp\b7\b758eeb73e54002a.llL938define @memset(i8*,i32,i64)):CallGenBase._ensure_method_declare(L494)建跨模块 declare 时默认用实参类型,仅当 call_name 带 sha1 前缀(L575call_name[16]=='.')才查 vpi 定义侧参数注解修正宽度。裸名 CExport 函数(string.memset→@memset,string.vpdef 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.llL487),调用create_process中string.memset(p.name, 0, 32)(L1811)传 i32 长度; - 实际定义在
temp\b7\b758eeb73e54002a.llL938 为@memset(i8*, i32, i64)(内部按 i64 读长度);sched 模块声明却是正确的 i64。 - 当前调用点碰巧生成
mov ecx, 32(清高位)未炸;一旦优化产生非清零高位路径,rep stosb写穿内存。
B4. sched.vp:208 create_thread 栈分配失败不恢复 thread_count(源码 bug)
✅ 已修复(ViperOS 待提交):
sched.vp:208if 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)等装饰器被编译器忽略——
- packed:LLVMType/NamedTypeNode 无 packed 概念,类型定义恒输出
{...}(自然对齐)。idt_entry碰巧 16 字节无差异(temp\58\58700b4401f1b4eb.llL5),但 gdt/elf/fat32 等二进制格式结构体若字段变化即插填充 → 磁盘/硬件布局错乱;- aligned:
fpu_context(FXSAVE 区域需 16 对齐)无全局/alloca 对齐 → fxsave/fxrstor #GP 风险;- 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 布局错位)。已补:ClassGenDefPhase B existing 路径existing.IsPacked = 1(L131)+VpiReader._inject_class_struct_declareentry_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_typesStruct 分支 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.llL5)。当前字段自然对齐碰巧与 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(prologuesub 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.llL427-476):result: t.CArray[str, 256]→%3=alloca [256 x i8*]→for i in result:→ L474-476 整数组 load + 非 entryalloca+ 整数组 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 编译器侧,按优先级)
- 全局初始化器生成(A1/A2):修复 vpi → LLVM IR 阶段负常量与字符数组初始化数据的丢失 —— 最高优先,直接影响所有
= -1与字符表。 - 联合指针类型解析(A3/A4):
T | t.CPtr(含str | t.CPtr)联合类型应保留"裸指针"语义(i8* 元素步长 1),而非统一解析为 i8**;c.Set(联合指针形参, v)应写所指地址而非改指针变量。 - 对象型全局构造(B2):
X: T = T()的__init__self 应指向全局值 storage(&X.stg);P = c.Addr(X)应生成store @X.stg → @P,而非独立.ptr.stg+ 结构体值拷贝。 - 类型保真(A5/A6):无符号比较用
icmp ult;%d/%i非 long 分支补trunc+sext;统一memset声明;补 AllocPage 的~{al}clobber。