Files
TransPyC/VKERNELERR.md
T

208 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](file:///d:/Users/TermiNexus/Desktop/TransPyC/includes/ast/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\*):
> ```python
> 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|str` 的地址计算错误(同类 A3 联合解析) |
| 设备文件/键盘输入异常 | 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。