自举编译器实现笔记
本文档是玄铁自举编译器(
xuantie_compiler/目录下由玄铁自身编写的编译器)的实现笔记,面向未来的维护者和协作者。 自举编译器由 KimiK3 自主开发完成主体架构。本笔记基于 2026-05-04 实际代码审计编写。 本文档由DeepSeekV4Pro生成。
编译器总览与架构
编译流程
玄铁源码 (.xt) → 词法.xt → 语法分析.xt → 抽象语法树.xt → 编译.xt(四趟)→ LLVM IR → clang/gcc → 原生二进制自举编译器是一个用玄铁自身编写的四趟编译器,核心文件:
| 文件 | 行数 | 职责 |
|---|---|---|
词法.xt | 462 | 词法分析,将 UTF-8 字符转为 Token 流 |
抽象语法树.xt | 478 | 所有 AST 节点类型定义 |
语法分析.xt | 1310 | 递归下降 + Pratt Parsing 语法分析 |
编译.xt | 3633 | 核心:四趟编译——预扫描、语义分析、类型推导、IR 生成 |
玄铁.xt / xt.xt / 入口.xt | 110-222 | 驱动入口,CLI 参数解析、调用编译器、执行 clang/gcc |
与种子编译器的关键差异
| 维度 | 种子编译器 (Go) | 自举编译器 (玄铁) |
|---|---|---|
| 编译趟数 | 单趟(代码生成即类型追踪) | 四趟(预扫描→语义→类型推导→IR) |
| 类型追踪 | 内联在 compileExpression 返回值 | 独立的 推导程序类型 pass + 变量类型表 |
| 前向引用 | AST 节点天然支持 | 通过全局预扫描收集符号解决 |
| 模块系统 | ImportExpression 内联处理 | 全局预扫描递归 + 程序缓存 + 语义分析 拓扑排序 |
| 运算符重载 | InfixExpression + magicMethod fallback | 编译中缀 先试 xt_dict_get 动态分发,再 fallback 编译内建中缀 |
| 内存管理 | Go GC | Arena 内存池(xt_arena_new/xt_arena_use) |
编译器结构体关键字段
出自 编译.xt L1-L40:
型 编译器 {
设 结果输出列表 // 当前函数/主域的 IR 行列表
设 结果输出 // 最终拼接的完整 IR 字符串
设 寄存器数 // 递增计数器,生成 %t1, %t2...
设 标签数 // 递增计数器,生成 if.then.1, loop.cond.2...
设 字符串映射 // 字符串常量去重 → 全局别名映射
设 作用域栈 // [[addrReg...]],每层作用域需要 ARC 释放的寄存器列表
设 符号表 // 变量名 → alloca 寄存器地址或全局符号名
设 变量类型表 // 变量名 → 类型("raw_i64" / "i64" / "i1" / "型:类名")
设 函数表 // 函数名 → 真,标记已声明的函数
设 类型表 // 类型名 → AST 节点,存储型定义
设 函数输出 // 所有函数定义的 IR 文本
设 外部函数输出列表 // 外部函数声明的 IR 行列表
设 当前型名称 // 编译型定义方法时的上下文
设 是全局 // 真 = 在主域,假 = 在函数/方法内
设 已访引入 // 防止重复引入
设 程序缓存 // 文件路径 → AST 程序节点,避免重复解析
设 已访语义 // 防止语义分析死循环
设 已访别名 // 追踪模块别名,防止被误当作函数
设 主文件路径 // 用于相对路径解析
设 断标签栈 // break 目标标签栈
设 续标签栈 // continue 目标标签栈
}词法分析器(词法.xt)
位于 词法.xt,462 行。
Token 结构
型 标记 {
设 标记类型 = "" // Token 类型关键字,如 "标识符"、"数字"、"函数定义"
设 标记字面量 = "" // 原始文本
设 标记行 = 0 // 行号(1-based)
设 标记列 = 0 // 列号(1-based)
}词法分析器工作机制
字符流模型:将源码字符串转为字符列表 字符列表,通过 读字符() 单字符前进,窥视字符() 预读下一字符。
关键字识别(词法.xt L409-L464):所有中文关键字通过硬编码匹配表识别。例如:
"设" → "变量声明"、"函" → "函数定义"、"型" → "型定义"
"若" → "如果"、"遍历" → "遍历循环"、"匹配" → "匹配"字符串处理(词法.xt L230-L296):
- 支持
"..."和'''...'''两种引号风格 - 支持插值标记
#{}——词法器不解析插值,将整段字符串(含#{表达式}原文)作为一个 Token 返回,插值解析在语法分析阶段处理
标识符规则(词法.xt L88-L114):
- 排除所有 ASCII 符号和数字后,剩余字符(含所有多字节 UTF-8 字节)视为"字母"
- 支持以
?结尾(如存在?、包含?)
注释:// 到行尾,在 读标记() 中处理。
抽象语法树(抽象语法树.xt)
位于 抽象语法树.xt,478 行。
基础节点
型 基础节点 {
设 节点行 // 源码行号
设 节点列 // 源码列号
设 节点类别 // "函数定义"、"中缀"、"标识符" 等——编译器的核心分发依据
设 推导类型 // 第三趟类型推导的结果("整"、"字"、"逻"、"未知")
}关键设计:所有节点继承 基础节点,节点类别 是编译器 匹配 语句的唯一分发键。这是理解整个编译器控制流的核心——不是靠 Go 的类型 switch,而是靠字符串匹配。
关键节点类型速查
| 节点类型 | 节点类别 | 关键字段 |
|---|---|---|
整数节点 | "整数" | 整数值 |
标识符节点 | "标识符" | 标识符名称 |
中缀表达式节点 | "中缀" | 中缀左、中缀运算符、中缀右 |
前缀表达式节点 | "前缀" | 操作符、右节点 |
调用表达式节点 | "调用表达式" | 函数表达式、调用参数 |
成员访问节点 | "成员访问" | 左表达式、成员名称 |
函数定义节点 | "函数定义" | 函数名、参数列表、返回类型、函数体块 |
变量声明语句节点 | "变量声明" | 声明名称、声明值、注解类型 |
块语句节点 | "块" | 块语句列表 |
若语句节点 | "若语句" | 若条件、若结果块、抑分支列表、若否则块 |
遍历循环节点 | "遍历循环" | 迭代变量名、迭代目标、循环块 |
引入表达式节点 | "引入" | 路径、别名 |
函数定义节点的参数表示
重要:在自举编译器的 AST 中,函数参数用 标识符节点 的列表表示,类型注解存在 标识符节点.推导类型 字段中(语法分析.xt L502):
p节点.推导类型 = 此.解析类型() // 存的是原始类型名,如 "整"、"字"这与种子编译器(Go)的 Parameter.DataType 不同——自举编译器复用了 AST 节点上已有的 推导类型 字段来存储参数类型注解,而不是新增一个专门的字段。
语法分析器(语法分析.xt)
位于 语法分析.xt,1310 行。
架构
- 语句级:
解析语句()通过匹配 类型分发("变量声明"→解析变量声明、"函数定义"→解析函数定义……) - 表达式级:Pratt Parsing(算子优先级解析),
解析表达式(优先级)+解析中缀(左) - 前瞻:
当前标记+窥视标记(一个 token 的前瞻缓冲)
Pratt 优先级表
出自 语法分析.xt L18-L27:
"或": 1, "且": 2, "等于": 3
"相等"/"不等"/"是": 4
"小于"/"大于"/"小于等于"/"大于等于"/"范围": 5
"加"/"减"/"连接"/"位或"/"异或": 6
"乘"/"除"/"取模"/"位与"/"左移"/"右移": 7
"左圆括号"/"左方括号": 8
"点"/"问号": 9解析函数定义(语法分析.xt L466-L528)
流程:
- 解析函数名(允许
造作为构造函数名) - 解析泛型参数
<T> - 解析参数列表
(name: Type, ...) - 解析返回类型注解
: Type - 解析函数体
{ ... }
类型注解处理:每个参数的 推导类型 字段被重用存储解析到的类型名(如 "整"),这是后续类型推导和代码生成的关键数据。
解析中缀(语法分析.xt L651-L718)
| Token | 生成的 AST 节点 |
|---|---|
( | 调用表达式节点 |
[ | 索引表达式节点 |
. | 成员访问节点 |
= | 中缀表达式节点(运算符="=") |
是 | 中缀表达式节点(运算符="是") |
.. | 范围表达式节点 |
? | 问号后缀节点 |
| 其他 | 中缀表达式节点(运算符=原始文本) |
编译器(编译.xt)—— 四趟编译详解
位于 编译.xt,3633 行,是自举编译器最大的文件。
编译入口:编译程序
函 编译程序(程序节点, 源文件路径) {
// 第一趟:全局预扫描(收集符号)
此.全局预扫描(程序节点, "", 源文件路径)
// 第二趟:语义分析(类型成员解析 + 拓扑排序)
此.语义分析(程序节点)
// 第三趟:类型推导(Static Type Inference)
此.推导程序类型(程序节点)
// 第四趟:代码生成(IR Emit)
// ...遍历程序语句列表,调用 编译语句()...
// 最后拼接 IR 头部(类型声明、外部函数声明、字符串常量)+ 函数体 + main
}第一趟:全局预扫描
函数位置:编译.xt L239。
职责:在代码生成前收集所有顶层符号,解决前向引用问题。
匹配的节点类别:
"函数定义"→ 注册到函数表和符号表"型定义"→ 注册到类型表,预扫描所有方法签名"接口定义"→ 注册到类型表"外部函数"→ 调用编译外部函数定义生成声明"表达式语句"且是"引入"→ 递归预扫描引入的模块(预扫描引入)"变量声明"→ 全局变量注册到符号表
引入机制:预扫描引入:
- 按主文件路径解析相对路径
- 检查
程序缓存——若已解析过则复用 AST - 否则
文件.读+造 词法分析器+造 语法分析器+解析程序() - 将解析结果缓存到
程序缓存 - 递归调用
全局预扫描,带上命名空间前缀
第二趟:语义分析
函数位置:编译.xt L319。
职责:解析类型成员、处理继承链、插入 此 参数。
流程:
- 递归处理引入模块的语义分析
- 对型定义做拓扑排序(先解析父类,再解析子类)
解析型成员中:- 继承父类的属性和方法列表(扁平化到
最终属性列表/最终方法列表) - 为每个方法的参数列表插入
此参数(如果没有的话) - 处理方法重写:用
原始函数名匹配,覆盖同名的父类方法
- 继承父类的属性和方法列表(扁平化到
关键细节:语义分析修改了 AST 节点——给方法加了 此 参数后,函数定义的 参数列表 变化了。这影响后续类型推导和代码生成的参数遍历。
第三趟:类型推导
函数位置:编译.xt L3405。
职责:遍历 AST 树,为每个表达式节点打上 推导类型 标签。
推导规则:
- 字面量:
"整数"→"整"、"小数"→"小数"、"字符串"→"字" - 标识符:查
变量类型表 - 中缀表达式:加减乘除 →
"整"或"小数";比较/逻辑 →"逻";连接 →"字" - 调用表达式:查函数返回值类型(
变量类型表) - 成员调用:
.字节、.长度、.字符数→"整";.包含?、.成功→"逻"
用途:代码生成阶段的 编译内建中缀 会读取 节点.中缀左.推导类型 == "整" 来决定是否走原生指令路径。
第四趟:代码生成 —— compileStatement
位于 编译.xt L622。匹配 节点.节点类别 分发:
| 节点类别 | 处理函数 | 行号 |
|---|---|---|
"显示语句" | 编译显示语句 | 约 L1422 |
"表达式语句" | 编译表达式语句 | L3225 |
"变量声明" | 编译变量声明语句 | L1334 |
"若语句" | 编译若语句 | L994 |
"当语句" | 编译当语句 | L1103 |
"无限循环" | 编译无限循环 | L1128 |
"遍历循环" | 编译遍历循环 | L798 |
"中断语句" / "继续语句" | 编译中断/继续语句 | L1142/L1151 |
"函数定义" | 编译函数定义 | L1160 |
"型定义" | 编译型定义 | L1241 |
"块" | 内联进作用域循环 | L645 |
"返回语句" | 编译返回语句 | ~L1258 |
"匹配语句" | 编译匹配语句 | L717 |
代码生成 —— compileExpression
位于 编译.xt L1471。返回 [regIR, type] 二元组。
关键路径:
标识符 → 查 符号表 找到 alloca 地址 → load i64 → 查 变量类型表 获取类型 → 标量类型跳过 retain:
若 非 此.是标量类型(类型) {
此.写出(" call void @xt_retain(i64 " & 寄存器 & ")")
}
返 [寄存器, 类型]中缀表达式 → 先试运算符重载(编译中缀 L2974),无重载则走 编译内建中缀(L3045):
编译内建中缀 有双重路径:
- 静态优化路径:
节点.中缀左.推导类型 == "整"且节点.中缀右.推导类型 == "整"→ 原生 LLVM 指令(add/sub/mul/icmp...) - 通用路径:
xt_eq、xt_compare运行时函数调用
调用表达式 → 编译调用表达式(L2895+):三路径分发——① 类名静态调用(按钮.渲染(此));② 函数表 命中后直接 call + 脱壳;③ 动态调用(函数指针 inttoptr)。printf 有特殊的 xt_ffi_printf 包装。
关键辅助函数
| 函数 | 作用 |
|---|---|
符(值) | 将任意值安全转为 IR 字符串,遇到 空 返回 "%NULL_SYMBOL_ERR" 防止污染 IR |
标(值) | 同 符,用于标签 |
确保I64 | 将任意类型值转为标记 i64(raw_i64→装箱、i1→select、函数名→ptrtoint) |
确保RawI64 | 将标记 i64 脱壳为原始 i64(ashr i64 %x, 1) |
是标量类型 | 判断 raw_i64/i1/double |
写_释放_xt | 按类型释放:标量→无操作,i64→xt_release |
写_存储_xt | 按类型存储到 alloca 地址:标量存原值,i64 存标记值 |
生成布尔条件 | 将任意类型转为 i1 条件值 |
最后是返回 | 检查输出列表最后一行是否是 ret/br/unreachable |
IR 输出与主函数组装
编译程序 的末尾按顺序输出:
- Target triple 和 data layout
- 类型声明(
%XTObject、%XTString、%XTArray……) - 外部运行时函数声明(
declare ... @xt_...) - 外部 FFI 函数声明
- 字符串常量(
@str.N = ... constant ...) - 函数定义(
此.函数输出) define i32 @main入口
注意:v0.17.7 已修复类结构体定义与 C 运行时的对齐问题(见 7.12),所有 6 种 LLVM 类型定义现已与 Go 种子编译器完全一致。
驱动入口(玄铁.xt / xt.xt / 入口.xt)
自举编译器的入口有两个版本:
- 玄铁.xt(222 行):含 Arena 内存池 + 完整的 clang/gcc 调用流程,是正式版本
- xt.xt(110 行):使用
编译.生成原生程序()封装的简化版本
命令行流程(玄铁.xt)
解析参数 → 创建 Arena(10MB) → 文件.读 → 造词法器 → 造语法分析器 → 解析程序
→ 造编译器 → 编译.编译程序 → 文件.写(IR) → clang -c → gcc 链接 → 清理关键: 玄铁.xt 默认使用 -Og 优化等级(L34),而非 -O2。且优化等级变量名是 优化等级 而非 优化等级_str,存储如 "-Og" 这样的字符串。
已知陷阱与隐形契约
【已修复 v0.17.5】compileFunctionDefinition 未读取参数类型注解
位置:编译.xt L1197
修复前:全部参数硬编码 "i64",无视类型注解。 修复后:新增 映射类型注解(注解) 函数(整→raw_i64、小数→double、逻→i1),编译函数定义 的参数循环读取 arg节点2.推导类型,标量参数先 ashr 脱壳再存储,跳过 xt_retain 和 追踪对象。同时注册返回类型到 函数返回类型表。
【已修复 v0.17.5】编译赋值未处理标量变量存储
位置:编译.xt L3232
修复前:store i64 xt_val 存标记值。 修复后:标量变量存储原始值(翻译右[0]),非标量存标记值并 retain。
【已修复 v0.17.5】推导程序类型结果未被充分利用
v0.17.5 改动:编译中缀 新增标量快速路径——双方都是标量时跳过 xt_dict_get + xt_string_new 动态分发;编译调用表达式 的 函数表 分支和标识符分支新增脱壳逻辑(查 节点.推导类型 == "整" 或 函数返回类型表)。
形参类型注解存储在标识符节点的推导类型字段
语法分析器 L502 中:
p节点.推导类型 = 此.解析类型()这与种子编译器 Parameter.DataType 专用字段不同。自举编译器复用了 推导类型 字段来存原始类型名。这意味着第三趟 推导程序类型 pass 可能会覆盖参数上已经存好的类型注解——如果该标识符节点同时被类型推导遍历到的话。但目前没有观察到覆盖,因为推导 pass 不会遍历函数参数节点(只遍历语句和表达式)。
变量类型表的生命周期
变量类型表 是全局共享的,但在 编译函数定义 中会被临时清空并只恢复全局变量:
此.变量类型表 = {}
遍历 键 于 之前的符号表 {
若 存址.截取(0, 1) == "@" { // 全局符号
此.变量类型表[键] = 之前的类型表[键]
}
}这意味着局部变量类型不会跨函数传播——这是正确的行为(局部变量本来就不该跨函数可见)。v0.17.5 已通过 函数返回类型表 解决了跨函数返回值类型传播的需求,无需修改此机制。
【已修复 v0.17.5】种子编译器 遍历 缺少 continueLabels / breakLabels
compileForStatement 在修复前缺少 continueLabels、breakLabels 和 loopDepths 的 push/pop。当 续/断 出现在 遍历 循环中时,continueLabels 栈顶是外层循环的目标标签,导致跳转目标错误。
修复:在 遍历 的条件分支后提前生成 stepLabel,同步 push 三个栈——breakLabels(endLabel)、continueLabels(stepLabel)、loopDepths,循环结束后 pop。
影响:这是 出作用域 两阶段 Arena 释放导致无限循环的真正根因——续 在嵌套 当→遍历 中错误跳转到外层 当 的条件检查,跳过 游标 = 游标 - 1。自举编译器的 生成循环逻辑(编译.xt L937-L938)本身已有正确的 续标签栈/断标签栈 推送,不受此问题影响。
对应测试:16_控制流测试.xt 覆盖 5 个场景:单层 续/断、嵌套 续/断 标签隔离、当 内嵌 遍历 内的 断。
【已修复 v0.17.5】符() 和 标() 的空值静默容错
这两个包装函数(L188-L212)用于防止编译器内部状态异常导致的 IR 污染。
修复前:遇到 空 值或字符串 "空" 时静默返回 "%NULL_SYMBOL_ERR" / "NULL_LABEL_ERR" 等错误哨兵串,这些串被写入 IR 后 clang 报错信息难以追溯原始根因。
修复后:遇到空值时立即 示() 中文错误信息 + 终 1 强制退出,在编译器层面就暴露内部状态异常,而非让 clang 去解析非法 IR。
编译中缀的静态优化路径依赖推导类型
若 节点.中缀左.推导类型 == "整" 且 节点.中缀右.推导类型 == "整" {这里比较的是第三趟推导 pass 打上的 推导类型(值为 "整"),而不是实际的 IR 类型("raw_i64" vs "i64")。如果某个整数变量因为类型注解缺失导致 推导类型 是 "未知",即使运行时它确实是整数,这个静态优化路径也不会被触发。
UAF 崩溃(玄铁.xt 主函数)
来自 INVESTIGATION_xt.md:玄铁.xt 的 主() 函数在手动 xt_arena_destroy(池) 后,函数末尾编译器自动插入的 ARC 释放代码仍尝试 xt_release(池),导致 Use-After-Free。
【已修复 v0.17.5】解析器未拦截关键字作为标识符
位置:语法分析.xt L84-L105(新加的 是保留关键字 辅助函数)、L278(变量声明分支)、L349(遍历循环分支)
修复前:词法器将 结果、任务、道、字节、整、字、逻 等 15 个常用字映射为特殊 token 类型(非 "标识符"),但解析器的变量声明(设 结果 = ...)、遍历循环(遍历 任务 于 ...)、函数定义等位置只接受 "标识符" 类型,导致含关键字的测试报出令人困惑的"期望标识符"错误。
修复后:新增 是保留关键字(类型) 函数(检测15个关键字类型),在变量声明和遍历循环的标识符检查分支中优先拦截并输出中文友好报错:'结果' 是玄铁语言的保留关键字,不能用作变量名或标识符。请换一个名字。,与种子编译器的错误提示对齐。
【经验教训】种子编译器的 当+遍历+若/续 嵌套 IR 生成缺陷
最初在 v0.17.5 两阶段 Arena 释放中暴露,后来发现根因是种子编译器的 compileForStatement 缺少 continueLabels/breakLabels 栈推送(参见 7.6),已在 llvm.go L2528 修复。该缺陷不会再造成新的影响。
【已修复 v0.17.7】自举编译器 LLVM 类型结构体缺少 magic 字段
位置:编译.xt L473-L478。
修复前:自举编译器的全部 6 种 LLVM 类型定义(XTObject/XTTring/XTTray/XTCDict/XTTFunction/XTResult)只有 2 个头部字段({ i32, i32 } = ref_count + type_id),缺少 magic 字段。Go 种子编译器正确定义为 3 个头部字段({ i32, i32, i32 } = magic + ref_count + type_id)。C 运行时 XTObject 结构体也有 magic 字段。这导致所有通过 getelementptr 访问 type_id 的代码(索引 i32 1)实际读到了 ref_count(offset 4 而非 offset 8)。
影响链:ref_count 值通常是 1 → 不等于 type_id 的任何有效值(数组=5、字典=6、字符串=3)→ 所有类型分发判断全部失败 → 遍历 循环体从不执行、运算符重载检查永远走到 fallback 路径。
修复(v0.17.7):
- 6 种类型定义全部添加
magic字段(+1 个i32) - 28 处
getelementptr字段索引全面修正:- XTObject type_id:
i32 1→i32 2(8 处) - XTTring data:
i32 2→i32 3(1 处) - XTTring length:
i32 3→i32 4(3 处) - XTArray length:
i32 3→i32 4(3 处) - XTFunction funcPtr:
i32 2→i32 3(1 处) - XTResult isSuccess:
i32 2→i32 3(5 处) - XTResult value:
i32 4→i32 5(4 处) - XTResult error:
i32 5→i32 6(3 处)
- XTObject type_id:
验证:修复后 Test 16(控制流测试)全部通过(偶数和=6、阶段和=72),遍历 循环正常执行。
【已修复 v0.17.7】变量类型表中 raw_i64 与标记值不一致
位置:编译.xt L1389-L1395(编译变量声明语句)、编译.xt L3296-L3301(编译赋值)。
修复前:编译变量声明语句 先将表达式值通过 确保I64 标签化(raw_i64 → tagged pointer),然后存入 变量类型表 的类型仍是 翻译结果[1](如 "raw_i64")。后续读取时 确保RawI64("raw_i64") 做 pass-through(不脱壳),导致标签值参与原生运算、再次被标签后变成三倍编码。
修复:当表达式返回类型为 "raw_i64" 时,变量类型表 存储 "i64"(标记值类型),因为值已被 确保I64 转化为标记指针。编译赋值 同理。
注意:修复中 设 类型名 在 若/否 块内声明。玄铁语言的 设 是函数级作用域(评估器 evalBlock 不创建新 Environment),块内声明的变量在块外可见——测试 16i 通过(局部变量 x+y=7)证实了这一点。
【经验教训】自举编译器类型定义必须与 C 运行时严格对齐
本缺陷(7.12)揭示了自举编译器与种子编译器之间的"静默分歧":当 Go 种子编译器正确生成 IR 时(因为它的类型定义与运行时对齐),被测产物运行正常;但当自举编译器生成的产物运行时(用自举编译器自己的类型定义),字段偏移全部错位。这种 bug 极难发现——用种子编译器编译的测试全部通过,只有用自举编译器编译的产物才暴露问题。
代码生成 IR 示例 (v0.17.5 修复后实测)
以 函 斐波那契(n: 整): 整 { 若 n <= 1 { 返 n } 返 斐波那契(n-1) + 斐波那契(n-2) } 为例,自举编译器 v0.17.5 实测 生成的 IR:
define i64 @"斐波那契"(i64 %"n_arg") {
entry:
%"n" = alloca i64
%t1 = ashr i64 %"n_arg", 1 ; 参数脱壳!
store i64 %t1, i64* %"n" ; 存 raw_i64
; 无 xt_retain!
%t2 = load i64, i64* %"n"
%t3 = icmp sle i64 %t2, 1 ; 原生比较!
; if.then: shl+or 装箱返回
%t4 = load i64, i64* %"n"
%t5 = shl i64 %t4, 1
%t6 = or i64 %t5, 1
ret i64 %t6
; n-1: 原生减法
%t8 = sub i64 %t7, 1 ; 原生 sub!
; 递归调用 + 脱壳
%t11 = call i64 @"斐波那契"(i64 %t10)
call void @xt_release(i64 %t10) ; 释放临时装箱值
%t30 = ashr i64 %t11, 1 ; 递归返回自动脱壳!
; n-2: 同样原生
%t31 = ashr i64 %t16, 1 ; 第二个递归返回值脱壳
%t32 = add i64 %t30, %t31 ; 原生 add!v0.17.5 前(对比):
; 旧 IR:参数被当堆对象 retain,比较走 xt_compare,
; 算术走 xt_sub/xt_add 运行时函数,递归返回值当 i64 传递不脱壳
; fib(30) ~365msv0.17.5 实测: fib(30) = ~6ms,fib(40) = ~740ms。
与种子编译器的功能差距总结 (v0.17.5)
| 功能 | 种子编译器 | 自举编译器 | 影响 |
|---|---|---|---|
| 参数类型注解读取 | ✅ v0.17.4 | ✅ v0.17.5:映射类型注解 + 脱壳 | fib 提速 60x |
| 返回值类型脱壳 | ✅ v0.17.4 | ✅ v0.17.5:函数返回类型表 + 调用处脱壳 | 递归返回走 raw |
| 标量赋值存储 | ✅ v0.17.4 | ✅ v0.17.5:存原始值 | 循环计数器正确 |
| 函数内 raw 运算 | ✅ 完整 | ✅ 完整(标量中缀快速路径) | 原生 LLVM 指令 |
| 跨函数 raw 通信 | ❌ ABI 限制 | ❌ ABI 限制 | 递归调用边界装箱不可避免 |
| IR 类型声明一致性 | ✅ 正确 | ✅ v0.17.7:全部 6 种类型与 Go 编译器对齐 | 自举产物运行正确 |
已知问题与待修复清单 (v0.17.8)
Test 02 字符串测试 — 崩溃(字节 方法) — 已修复 (v0.17.8)
字节 方法)现象:文本.字节(0) 返回"非法地址"而非实际字节值 72。
根因:Go 种子编译器的 字节() 方法处理器在调用 xt_string_get_byte 后,错误地对返回值做了 ashr i64 ..., 1(反标签化)。但 xt_string_get_byte 内部已经调用 xt_int_new((int64_t)str->data[idx]) 返回了一个 XTInt 对象指针(已经过 ensureI64 标签化),多余的右移操作破坏了指针高位,xt_obj_to_string 收到无效地址后输出"非法地址"。
修复:移除了 字节() 处理器中 resRaw = ashr i64 resI64, 1 这一行,直接将 xt_string_get_byte 返回值存入结果地址。
回归验证:Test 02 全部通过,第一个字节正确输出 72。
Test 03 集合测试 — 崩溃(字典遍历) — 已修复 (v0.17.8)
现象:数组遍历正常完成,但到字典遍历时崩溃(ACCESS_VIOLATION)。
根因:Go 种子编译器没有将 键() 作为内置方法处理。当执行 映射.键() 时,走的是动态字典查找路径——尝试从字典中查找 key="键",找不到返回 null。随后 for 循环对 null 解引用,调用 load i32, i32* inttoptr (i64 0 to i32*) → ACCESS_VIOLATION。
修复:在 llvm.go 的 遍历 语句编译方法中,添加了 键() 和 值() 两个字典内置方法的 IR 生成逻辑(call %XTArray* @xt_dict_keys / @xt_dict_values),与自举编译器(编译.xt L2904-L2913)的行为对齐。同时新增了 xt_dict_values 函数的 LLVM IR 声明。
回归验证:Test 03 全部通过,字典遍历正确输出 版=0.17, 地=中国, 名=玄铁。
自举编译器无法编译自身(编译.xt)
位置:.\玄铁.exe tie xuantie_compiler\xt.xt → 编译 编译.xt 时报错
现象:Go 种子编译器编译的自举编译器(玄铁.exe)在编译 编译.xt 时,在第 3531 行附近报解析错误:期望当前标记为 箭头,但得到 竖线。
根因:自举编译器的语法分析器不支持 Go 种子编译器使用的箭头匹配语法(->),以及管道/竖线表达式。自举编译器的 匹配 语句只实现了基本的「模式 → 表达式」形式,不支持 -> 箭头语法。而 编译.xt 内部使用了种子编译器语法分析器支持的高级匹配特性。
影响:当前自举链路不完整——新功能必须在 编译.xt 和 Go 种子编译器两处同步修改。语法分析.xt 需要补全匹配语法支持才能实现完全自举。
阻塞依赖:语法分析.xt 的匹配语法增强
种子编译器 XTString 字段偏移可疑 bug
现象:截取 方法不带结束参数时,访问 XTString length 的 IR 代码是 getelementptr ... i32 3,但 i32 3 在新布局中对应 data 指针而非 length(i32 4 才是 length)。然而变量名命名为 lenPtr,且 load i64 从 i8* 类型的 data 字段加载。
严重性:疑似 bug 但未看到实际崩溃——可能是因为 load i64 从 data 指针读到的是有效的 length(内存布局巧合),或者是 截取 内部通过 strlen 重新计算了长度。
验证方法:写一个测试用例,"abcdef".截取(2) 期望 "cdef"。如果输出正确,说明运行时函数内部不依赖此 length。
阻塞依赖:无
【已修复 v0.17.9】自举编译器GEP读取对象字段返回值错误
位置:编译.xt L1716-L1775、编译.xt L885-L925、编译.xt L2577
现象:字节数/长度/大小全部输出"空",遍历循环体从不执行。Go种子使用xt_string_byte_length等C函数正常。
根因:自举编译器通过GEP直接读LLVM结构体字段(如getelementptr %XTString, ..., i32 4→load i64→shl+or标记化),但运行时实际加载到的值为0。虽然LLVM类型布局与C结构体在偏移量上匹配(已验证size_t=8字节,offset=24),但GEP方式在特定条件下返回错误值。改用C运行时函数后问题消失。
修复(v0.17.9):
- 成员访问处理器(
长度/大小/字节数):字符串→xt_string_byte_length(返回已标记XTValue,直接store),数组→xt_array_length(返回raw size_t,经写_存储_xt标记),字典保持xt_dict_size - 遍历处理器:数组长度→
xt_array_length,字符串长度→xt_string_byte_length,字典键集长度→xt_array_length 截取默认结束参数:改为xt_string_byte_length+ashr脱壳- 新增
xt_array_length到C运行时(xt_runtime.h/c),编译新的xt_runtime.o - 新增
@xt_array_length的IR声明到自举编译器输出头
【已修复 v0.17.9】语法分析器解析尝试捕捉不支持括号语法导致死循环
现象:编译任何含尝试 { } 捕捉 (e) { }的代码时,自举编译器无输出直接超时,内存超20GB被系统kill。
根因:解析尝试捕捉只处理捕捉 标识符(无括号),未处理捕捉 (标识符)(带括号)。当遇到(时,代码未消费它就直接调用解析块语句(),后者第一行进标记()无条件吃掉当前标记(误吃(),导致解析器状态完全混乱→死循环→Arena无限扩容。
修复:在检查标识符之前先检查左圆括号,若存在则消费(→读标识符→消费)。
【已修复 v0.17.9】含?/包含?成员方法缺少字符串支持导致Segfault
现象:文本.包含?("玄铁")和文本.含?("玄铁")均导致ACCESS_VIOLATION崩溃。
根因:1) 成员方法匹配只检查含?不检查包含?;2) 无条件调用xt_dict_contains,对字符串完全无效——xt_dict_contains内部检查type_id不是字典/实例时返回0,但LLVM IR中对字符串指针误调用xt_dict_contains可能导致后续操作异常。
修复(参照Go种子llvm.go L1697-L1725):
- 匹配条件扩展为
成员名 == "含?" 或 成员名 == "包含?" - 新增类型分派:读
type_id→是字符串(type_id=3)则走xt_string_contains(参数转%XTString*),否则走xt_dict_contains - 用LLVM
phi节点合并两分支的布尔结果 - 新增
@xt_dict_contains的IR声明
【已修复 v0.17.9】语法分析器多处小修复
解析返回语句first集缺少函数定义(语法分析.xt L488):返 函(...){}中函标记类型为函数定义但不在first集中,导致匿名函数作为返回值时表达式不被解析。修复:first集添加"函数定义"。
遍历循环变量名过度限制(语法分析.xt L371-L375):遍历 任务 于 列表中任务是保留关键字被拦截。实际上在迭代变量位置没有歧义,允许关键字作为变量名。修复:将关键字检查从"拒绝"改为"接受"。
【已修复 v0.17.9】索引赋值 x[y] = z 缺少字典类型分派
现象:映射["地"] = "中国" 无效——字典大小不变(保持2),遍历字典崩溃。
根因:编译赋值处理索引赋值时,无条件调用xt_array_set——该函数内部检查type_id!=5时直接返回,对字典完全无效。
修复(参照Go种子llvm.go L2655-L2697):
- 读
type_id做类型分派:字典(type_id=6)→xt_dict_set,数组(type_id=5)→xt_array_set - 用br分派到两个基本块,最后汇合到merge块统一release对象和索引
回归验证:Test 03 全部通过(字典大小=3、遍历字典输出地/名/版、复杂嵌套=30),退出码0。
【已实现 v0.17.9】编译严格度三档开关系统
位置:语法分析.xt L7-L12、编译.xt L4-L35、玄铁.xt L40-L90
设计依据:02_类型系统哲学设计.md 第1节"多档开关"。
实现:
- 解析器和编译器各新增
严格度字段(默认"默认") - CLI参数支持中英文和拼音:
--默认/--moren、--严格/--yange、--禁止/--jinzhi - 等级提升逻辑:
默认保持原始等级,严格提升一档(宽松→警告、警告→错误),禁止全部→错误 添加警告和添加类型警告根据严格度决定:错误级→调用终 1终止编译,警告级→输出,宽松级→静默
验证:使用Test 01的W004在三种模式下测试——默认输出警告(编译继续)、严格→错误(中断)、禁止→错误(中断),全部正确。
【已实现 v0.17.9】隐式类型检测(W001-W003)
实现:在推导语句类型的类型推导阶段中加入检测:
- W001:变量声明
设 x = 100无: 整注解,且推导类型非"i64"和"未知"时触发(宽松级,默认静默) - W002:函数参数无类型注解时触发(警告级),检测
参数.推导类型 == "未知"(AST节点默认值) - W003:函数无返回类型注解时触发(警告级),检测
语句.返回类型 == ""
注意:参数推导类型默认值为"未知"(基础节点初始化),而非"",W002需与"未知"比较。
【已实现 v0.17.9】未使用变量检测(W101)
位置:编译.xt L1585(标记)、编译.xt L1280(检测)
实现:
- 编译器新增
已用变量字典,在标识符加载路径(编译表达式→标识符→符号表.含?)中标记此.已用变量[变量名] = 真 - 在
编译函数定义末尾(函数IR生成完成后、状态恢复前),遍历变量类型表:排除全局变量(地址前缀@)和函数参数,检查是否在已用变量中 - W101为宽松级,默认静默;严格模式→警告输出
局限:当前使用函数定义节点的行号作为W101的行号(节点.节点行),而非实际变量声明行号;变量类型表中不追踪每个变量的源码位置。
【已实现 v0.17.9】@允许 注解抑制语法
位置:词法.xt L394-L400、语法分析.xt L231-L245、语法分析.xt L68-L72
设计依据:02_类型系统哲学设计.md 第44-49行"注解式"。
实现:
- 词法分析器
读标记中新增@字符处理:读@后读标识符,若为允许则返回"注解允许"令牌类型 - 解析器结构体新增
当前抑制列表字段;解析语句中新增"注解允许"分支:消费@允许令牌,解析(code1, code2, ...)抑制参数列表,递归解析下一条语句后将抑制列表附加到其解析结果 添加警告方法在判断严格度之前先检查抑制列表:若警告码或其类别名在列表中,静默跳过
语法:@允许(W004) 或 @允许(逻辑语法),放在被抑制的语句前一行。
注意:@允许只抑制紧跟的下一条语句。若需要抑制多条语句,每条前均需放置。
【已实现 v0.17.9】玄铁.配置.toml 项目配置文件
设计依据:02_类型系统哲学设计.md 第14-23行"项目配置文件"。
实现:
- 新增顶层函数
解析配置(内容):简单的TOML子集解析器,支持[节]标题(跳过)、键 = "值"格式(去除空格和双引号) - 主函数在读取源文件前,查找源文件同目录下的
玄铁.配置.toml,若存在则解析 - 配置优先级:命令行参数 > 配置文件 > 默认值。仅当命令行严格度为
"默认"且配置文件指定了严格度时,才用配置文件覆盖
支持格式:
[编译]
严格度 = "严格"局限:数组值(警告即错误 = [...])尚未实现,可在后续版本补全。
【待实现】未使用导入检测(W102)
位置:待定
现象:引 "模块.xt" 引入的模块未被实际使用时,编译器不提示。
设计:在全局预扫描阶段记录所有引入路径,编译完成后对比实际使用的跨模块符号(函数调用、类型引用、变量访问),对未使用模块按W102输出警告(宽松级,默认静默)。
阻塞依赖:需要跨模块符号追踪基础设施(记录每个符号的来源模块)。
【已修复 v0.17.9】关键字节点表达式求值跳过了符号表变量查找
现象:遍历 任务 于 arr { 任务() } 中 任务 作为迭代变量,但 编译表达式 将 任务 匹配为关键字节点(节点类别 = "任务"),直接返回类型ID常量 ["21", "i64"],导致后续 任务() 将 21 当作函数指针调用而崩溃。
根因:解析器在表达式上下文中将 任务、道、字节、整、字 等关键字创建为特殊节点类别(如 节点类别 = "任务"),编译器匹配时直接走关键字常量分支,跳过了 标识符 分支中的符号表检查。
修复:在 编译表达式 的关键字节点匹配分支("整"、"字"、"道"、"字节"、"任务")中,先检查 此.符号表.含?(节点.标识符名称),若关键字名称已被注册为变量(如 for-each 迭代变量),则按普通标识符加载,否则返回关键字常量。
回归验证:遍历 任务 于 arr { 任务() } 正常执行;Test 15(综合测试)全部通过,退出码0;Test 08(异步测试)修复后退出码0。
【已实现 v0.17.9】闭包变量捕获(Tests 04/13)
位置:编译.xt L2370-L2420(收集捕获变量)、编译.xt L2420-L2500(编译匿名函数修改)
现象:函() { 累计 = 累计 + 增量 } 中 累计 是外层函数局部变量,匿名函数体内引用时报"未知标识符"。
根因:编译匿名函数创建独立作用域时只复制了全局变量(@前缀),外层局部变量未被继承。
修复:
- 新增
收集捕获变量辅助函数——递归遍历匿名函数体AST(覆盖所有表达式和语句节点类型),收集外层符号表中存在的局部变量名(排除@前缀的全局变量) - 为每个捕获变量创建全局桥接变量(
@"closure_xxx" = global i64 0),在全局声明列表记录,编译程序末尾统一输出到模块级IR - 在外层函数中生成
load+store指令将当前值写入全局桥接(直接追加到之前的输出列表避免上下文切换问题) - 在匿名函数入口从全局加载到闭包内新alloca
- 在匿名函数体编译后、返回检查前,将捕获变量的当前值写回全局桥接
局限:
- 寄存器名使用
%"capture_变量名"格式(加引号支持中文),不依赖编译器全局计数器 - 闭包内显式
返后写回全局指令位于ret之后(LLVM不可达),导致跨调用的可变状态在返后不持久化(如计数器的计累加) - 无
返的闭包(如x = 新值)状态持久化正常(写回在编译语句之后执行)
回归验证:Test 04(函数式)全部通过退出码0,Test 13(闭包)全部通过退出码0。
【已实现 v0.17.9】泛型语法支持(Test 12)
位置:语法分析.xt L291-L325、语法分析.xt L797-L830、语法分析.xt L395-L405
现象:型 盒子<T>、造 盒子<整>(123)、交换<整>(10, 20) 等泛型语法全部报解析错误。
根因:
解析泛型参数列表只接受标识符类型——<整>中的整是类型关键字而非标识符,被拒绝解析中缀无<处理——Pratt解析器把交换<整>中的<当作比较运算符- 变量声明中
结果等保留关键字被拒绝 - 比较运算符
a < b中的<被泛型处理误消费→死循环
修复:
- 新增
是类型关键字函数:识别整、字、小数类型、逻、数组、字典、结果、字节、任务、道 解析泛型参数列表:接受标识符或类型关键字;前瞻窥视标记——若不是标识符/类型关键字/大于则立即返回空(不消费<,留给比较运算符)解析中缀新增"小于"处理:调用解析泛型参数列表→有参数且后跟(→泛型函数调用;有参数→泛型类型引用;无参数→退化为比较(手动消费<,解析右表达式,创建中缀节点)解析变量声明:允许保留关键字用作变量名(编译器通过符号表优先匹配变量)
编译器侧:泛型参数采用类型擦除——所有类型在IR层映射为i64。泛型实例化造 盒子<整>(123)由编译实例化正常处理,泛型函数调用交换<整>(10,20)由编译调用表达式正常处理。
回归验证:Test 12(泛型)全部通过退出码0:整盒内容=123、字盒内容=玄铁、交换结果正常。
【已实现 v0.17.9】弱引用支持(Test 17)
位置:runtime/xt_runtime.h L95-L106、runtime/xt_runtime.c L575-L600、抽象语法树.xt L124、语法分析.xt L220-L240、编译.xt L1463-L1480、编译.xt L3468-L3476
设计依据:实现清单和计划.md 第9.6节"ARC弱引用语法"、第4节"弱引用修饰符"。
现象:弱 设 父节点: 父节点? 语法完全不支持,编译器报解析错误。
实现:
- 运行时:新增
XTWeakSlot结构体(旁路链表节点),全局链表g_weak_slots;xt_weak_init(slot_addr, obj)将槽位地址注册到链表;xt_weak_clear(obj)遍历链表清零匹配槽位→摘除释放;xt_free_obj开头调用xt_weak_clear(obj) - AST:
变量声明语句节点新增弱引用字段(默认假) - 语法分析:
解析变量声明签名改为(级别, 弱参);解析语句新增"弱引用"分支→消费弱→调用解析变量声明(级别, 真);解析带修饰符语句支持公 弱 设组合;继续解析型定义支持类属性弱引用;所有原有调用点传假 - 编译器:新增
弱引用变量集字典字段;编译变量声明语句若语句.弱引用→生成xt_weak_init+xt_release+移除作用域追踪+记录弱引用变量集;编译赋值标识符分支若目标为弱引用变量→直接store跳过release+retain
局限(MVP不处理):
- 成员属性弱引用(
子.父节点 = 父)走xt_dict_set路径,不自动调用xt_weak_init - 弱引用重赋值时旧槽位未从旁路链表中注销
- 应用层需自行
空检查弱引用值
回归验证:Test 17(弱引用)全部通过退出码0:声明、重赋值、空弱引用、归零验证(覆盖强引用后弱引用自动置空)。
ARC 行为细节:
弱引用声明时的 ARC 操作:
注册局部变量:创建 alloca,存储值(不 retain——弱引用不持有所有权)xt_weak_init(slot, obj):将 alloca 地址注册到对象的旁路链表中xt_release(obj):释放表达式 load 时编译表达式自动生成的xt_retain(表达式加载非标量值时会 retain→ref+1,此处 release 抵消之),对象 refcount 回到只有强引用持有的状态- 从作用域追踪移除 alloca:防止出作用域时编译器对弱引用变量自动调用
xt_release
赋值给弱引用变量时(编译赋值):
- 检查
弱引用变量集,若命中→直接store,跳过旧值 release 和新值 retain - 注:当前未调用
xt_weak_init更新槽位,重赋值后旧对象释放时不会清零新值
对象释放→弱引用归零的完整链路:
强引用 = 新对象(赋值)
→ xt_release(旧对象), refcount: 1→0
→ xt_free_obj(旧对象)
→ xt_weak_clear(旧对象)
→ 遍历 g_weak_slots 链表
→ *slot_addr = XT_NULL(归零!)【已实现 v0.18.0】循环引用风险检测(W401)
设计依据:Claude 建议的编译期静态分析——当编译器检测到可能形成循环引用的字段关系时,给出警告提示开发者使用 弱 修饰符。
目标警告格式:
警告 [W401](严格度=默认):循环引用风险
类型 `节点` 的字段 `父: 节点?` 可能与字段 `子: 数组<节点>` 形成循环引用
建议:把 `父` 字段标注为 `弱`
修复:弱 设 父: 节点?
注:在 --严格 模式下此警告将成为错误检测算法:
- 在
语义分析阶段,为每个型定义构建引用图:节点=类型名,边=该类型中引用其他类型的字段 - 跑 Tarjan 强连通分量(SCC)算法或简化版环检测(DFS + 回溯边检测)
- 发现的环中,若某条边标注了
弱→跳过;若所有边都未标弱→输出 W401
实施预估:约 300-500 行代码,新增 构建引用图 和 检测环 两个辅助函数,放在 语义分析 阶段末尾执行。
实现(v0.18.0):在编译.xt新增提取引用类型名(支持直接类型名、数组<型>元素类型、字典<K,V>value类型解析)和检测循环引用(遍历型定义构建引用图,检测2-节点环)两个函数;在编译程序中语义分析后调用;语法分析.xt和编译.xt两处警告码表新增W401(类别:"循环引用",默认等级:"警告")。
局限:
- 仅检测2-节点环(A↔B),不处理3-节点及以上环
数组<型>的容器元素类型因解析器在注解类型字段只存基类型名(数组),无法提取泛型参数,仅直接类型引用(设 x: 型名)可被检测- 依赖
注解类型字段——无显式类型注解的字段被跳过
回归验证:Test 24(循环引用检测)——互相强引用触发W401(两条警告),一方弱引用不触发;24/24全部通过。
【已实现 v0.18.0】泛型参数可空修饰符 ?
语法规则:? 紧跟在一个类型后面,修饰的是它紧邻的那个类型。
节点? ← 节点 可空
数组<节点>? ← 数组 可空(整个数组可能为空)
数组<节点?> ← 数组里的元素可空(数组本身存在)
数组<节点?>? ← 数组可空,且元素也可空
字典<字, 节点?> ← 字典里 value 可空
字典<字, 节点>? ← 整个字典可空实现:在 解析泛型参数列表 的首参数和逗号分隔参数两处,读取类型关键字/标识符后检查下一个标记是否为 "问号",若是则将 ? 拼接到参数名字符串中并消费标记。
文档:03_泛型编程指南.md 第 3.1 节详细说明。
【已实现 v0.18.0】类成员弱引用支持(P0)
位置:runtime/xt_runtime.h L95-L112、runtime/xt_runtime.c L590-L650、编译.xt L1352-L1370、编译.xt L3562-L3580
现象:型 子节点 { 弱 设 父: 父节点? } 类成员弱引用在实例化和赋值时走 xt_dict_set(总是 retain),弱引用语义未生效。
修复:
- 运行时:新增
xt_dict_set_weak(dict, key, value)——存值但不 retain,不 release 旧值(弱引用核心语义);新增xt_dict_weak_init(dict, key, obj)——注册字典弱引用槽位(XTWeakSlot 的 slot_addr=NULL, dict_val=dict, dict_key=key);扩展xt_weak_clear——若 slot_addr 非空则清零 alloca(原有逻辑),否则调用xt_dict_set_weak将字典条目置为 XT_NULL - 编译器:新增
弱引用字段表(型名→{字段名: 真});编译型定义遍历最终属性列表收集弱字段;编译实例化字段初始化循环中检查属性.弱引用→调xt_dict_set_weak+xt_dict_weak_init;编译赋值成员访问分支检查弱引用字段表→弱字段走弱引用路径
ARC 链路(字典方式):
弱对象被释放 → xt_release → refcount: 1→0 → xt_free_obj → xt_weak_clear
→ 遍历 g_weak_slots → ws->slot_addr==NULL → xt_dict_set_weak(dict, key, XT_NULL)
→ 字典条目清零回归验证:Test 17 新增测试 6(类成员弱引用+循环引用),17/17 全部通过。
【已实现 v0.18.0】弱引用强制可空类型检测(W005/P1)
位置:语法分析.xt L465-L469、语法分析.xt L25
设计依据:Claude 建议——弱 修饰的字段必须是可选类型,因为引用的对象随时可能被释放而变空。
实现:在解析变量声明末尾,若弱参为真且注解非空且不含?,调用添加警告("W005", ...)。W005 默认等级为"警告"(默认模式下输出提示但不中断编译)。
警告示例:
警告 [W005]:第 2 行、第 7 列
类别: 弱引用语法(缺少可空修饰)
弱引用字段应使用可空类型,建议在类型注解中添加 ? 后缀【已修复 v0.18.1】< 比较运算符被误解析为泛型尖括号
日期:2026-05-16
现象:当 i < n { 触发解析错误 "期望当前标记为 大于,但得到 左花括号"。所有含 < 比较的玄铁代码作为主文件均无法编译。
根因:语法分析.xt:解析泛型参数列表() 只有单标记前瞻(窥视标记)。遇到 < 后跟标识符(如变量名 n)时无法区分是泛型 盒子<节点> 还是比较 i < n。函数贪心消费 < 和标识符后才在 line 352 发现没有 >,此时标记已消费无法回溯。解析中缀 line 853-858 的"退化比较"补救代码假设标记未被消费,但实际已被消费,进标记() 反而吃掉额外标记。
修复:为语法分析器新增 窥视标记2(第二前瞻标记):
- 结构体新增字段、构造器多调一次
进标记()、进标记()级联窥视标记2 解析泛型参数列表()中,当窥视标记为普通标识符(非类型关键字)时,再检查窥视标记2:只有>/,/?才进入泛型解析,其余一律提前返回留给比较运算符
影响范围:该 bug 也导致了循环体编译不正常——sort/去重运行时结果错误,因为 当 循环条件的标记被错误消费导致循环逻辑异常。
回归验证:01-29 全部测试通过,Test/28_简单比较.xt 和 Test/29_函数内比较.xt(新增比较测试)通过。
【已修复 v0.18.1】模块路径解析未以源文件目录为基准
发现日期:2026-05-16 | 修复日期:2026-05-16
现象:Test/26_数组高阶方法.xt 中 引 "./lib/数组.xt" 能编译成功,但 Test/ 目录下并无 lib/ 子目录。正确路径应为 "../lib/数组.xt"。
根因:编译.xt:预扫描引入(L307)用 非 节点.路径.包含?("\\") 且 非 节点.路径.包含?("/") 判断路径是否已解析——含 / 或 \ 就被当作"已解析"跳过。但 ./lib/数组.xt 含 / 却被误判为已解析,未拼接父文件目录。
修复:
编译.xtL307:条件改为非 此.是绝对路径(原始路径),复用已有的绝对路径判断函数- 保存
原始路径变量防止被覆盖,错误信息扩展为四行:来源文件、引入路径(原始)、解析为(完整路径)、修复提示 Test/26_数组高阶方法.xt:路径从"./lib/数组.xt"修正为"../lib/数组.xt"
回归验证:30/30 全部通过。新增 Test 30 故意写错路径——编译器输出清晰错误(来源文件、原始路径、解析后路径、修复提示)并退出码 1。
【已修复 v0.18.7】嵌套遍历中外层变量不可见 + 遍历体内 alloca 支配性
日期:2026-05-16(发现)→ 2026-06-21(修复确认)
现象(已修复):
- 外层
遍历 项 于 列表 { 遍历 已有 于 输出 { ...项... } }— 内层遍历体中无法访问外层遍历变量项,编译器报"未知标识符" 设在遍历体内声明后用于若条件分支,触发 LLVM "Instruction does not dominate all uses!" 错误
根因:编译遍历循环 中迭代变量和循环体内 设 声明的 alloca 被插入到循环体基本块(非 entry 块),LLVM 要求 alloca 必须在入口块。
修复:入口alloca列表 机制(§12.2)——迭代变量 alloca(编译.xt L1022-1044)和局部变量 alloca(L1696-1702)均通过 入口alloca列表 推迟到函数 entry 块写入,确保 LLVM 支配性。
原绕过方式(已移除):
嵌套遍历 → 改用外→遍历+ 内当组合lib/数组/数组.xt的去重已恢复自然嵌套遍历写法遍历体内→设→ 提升变量声明到函数作用域过滤已恢复设 满足在遍历体内声明
验证测试:Test 31(→通过)、Test 32(→通过,之前预期失败)、Test 26 数组高阶方法(→通过,含恢复自然写法的 过滤/去重)
内存管理架构(2026-05-21 审计)
整体架构
玄铁自举编译器采用 Arena(区域分配器) 作为核心内存管理策略。不依赖引用计数(ARC)做实际内存回收——ARC 的 retain/release 在 Arena 分配的对象上均为空操作。
玄铁.xt → xt_arena_new(1GB) → xt_arena_use(池) → 全局 Arena 激活
↓
所有 xt_malloc / xt_string_new / xt_array_new 等
全部从 Arena 分配
↓
编译器运行完毕 → xt_arena_destroy(池) → 一次性回收关键参与者
| 组件 | 文件/位置 | 职责 |
|---|---|---|
| Arena 初始化 | 玄铁.xt L180-182 | xt_arena_new(1073741824) + xt_arena_use(池) |
| 对象分配入口 | runtime/xt_runtime.c xt_malloc() | 检测 g_current_arena → Arena 分配或 malloc fallback |
| Arena 分配 | runtime/xt_runtime.c xt_arena_alloc() | 设置 ref_count = XT_REF_COUNT_IMMORTAL (0x7FFFFFFF) |
| 不可销毁标记 | runtime/xt_runtime.c xt_retain_forever() | 将任意对象的 ref_count 强设为 IMMORTAL |
| 运行时对象 | xt_array_new/xt_dict_new/xt_string_new | 均通过 xt_malloc → Arena 分配 |
| 编译器内部容器 | 编译.xt 造() | 结果输出列表、函数输出列表、符号表、字符串映射 等 20+ 字典/数组全部 Arena 分配 |
ARC 的实际角色
在 Arena 激活期间:
- xt_retain:检查
ref_count >= XT_REF_COUNT_IMMORTAL→ 直接返回,不增加计数 - xt_release:同上检查 → 直接返回,不减少计数、不释放对象
- xt_free_obj:永远不会被调用
因此 ARC 的 retain/release 对编译器来说纯为运行时开销,不产生任何内存管理效果。内存管理完全依赖 Arena 的一次性回收。
编译器数据结构的内存生命周期
编译器 造() 创建的所有容器(L50-L84):
此.结果输出列表 = [] // 数组
此.字符串映射 = {} // 字典
此.符号表 = {} // 字典
此.类型表 = {} // 字典
此.程序缓存 = {} // 字典
此.入口alloca列表 = [] // 数组
...(约 20 个容器)全部通过 xt_array_new / xt_dict_new → xt_malloc → xt_arena_alloc 分配,引用计数为 IMMORTAL。编译器运行期间永不释放,随 Arena 销毁一次性回收。
预扫描与程序缓存的内存行为
预扫描引入(预扫描引入)处理 4 个被引入模块(词法.xt、抽象语法树.xt、语法分析.xt、编译.xt)。当前使用 程序缓存 策略:解析一次、缓存 AST、代码生成时复用。
AST 全部在 Arena 中。类型表通过指针引用 AST 中的类型定义节点(不复制)。4 个模块 + 1 个主文件的完整 AST 均在 1GB Arena 内。
注意:bc78c6a commit 曾尝试将 程序缓存 改为 源码缓存(只缓存源文本、不缓存 AST)以减少预扫描内存,但这导致代码生成阶段重新解析、语义分析添加的 此 参数丢失。已回退到程序缓存。
隐式契约与设计不变量
| 契约 | 说明 | 违反后果 |
|---|---|---|
| Arena 必须在使用前激活 | xt_arena_use(池) 必须在 造 编译器() 之前调用 | 对象落入 malloc,ARC 实际生效,可能 double-free |
此 参数由语义分析添加 | 解析型成员 遍历类型表,为每个方法前插 此 参数 | 代码生成时 此 找不到 → 编译错误 |
| 程序缓存 AST 与类型表共享节点 | 类型表条目是程序缓存 AST 的子节点引用,不是副本 | 如重新解析,类型表指针悬挂 |
| 入口alloca列表 必须 save/restore | 编译函数定义 递归调用时需保存/恢复外层列表 | 外层函数的 alloca 被内层覆盖 → LLVM 支配性错误 |
返 [expr, type] 不可内联数组字面量 | 产物中该模式返回值为空;必须先 设 再 返 | 调用者收到 XT_NULL → 编译失败 |
禁用运算符重载于 ==/!= 非标量 | 运算符重载产生内联 alloca i64,在 3+ 若/抑 条件下堆损坏 | 运行时 xt_check_obj 失败 |
| 字符串索引必须处理 type=3 | s[i] 需检查 XT_TYPE_STRING(3),调用 xt_string_get_char | 误调 xt_array_get → 访问违例 |
所有 alloca 需配 store i64 0 零初始化 | entry alloca 列表和参数 alloca 后跟随 store i64 0 | 未初始化内存被 ARC release 时读到垃圾值 |
解析中缀 default case 使用 优先级-1 | ==/!= 在优先级=4 时递归返回崩溃 | 解析器访问违例 |
自举推进记录(2026-05-17 ~ 2026-05-21)
会话概述
本次会话历时 4 天,目标是使自举编译器(Go seed 构建的玄铁.exe)成功编译自身并产生可工作的产物。
根因分析与修复
以下按发现顺序列出,标注置信度。
入口 alloca 支配性错误(10.25b 修复)
根因:注册局部变量(编译.xt L1513-1551)在当前位置内联发出 alloca。当 设 位于 遍历 体内或 若 分支内时,alloca 落在非 entry 块,LLVM 验证失败。
修复:
- 新增
入口alloca列表字段和分配入口alloca方法(后改为内联调用) 编译遍历循环、注册局部变量的 alloca 全部写入入口列表编译函数定义在 entry: 标签后输出所有入口 alloca编译程序在主函数入口预置入口 alloca- 所有 alloca 后跟
store i64 0零初始化
置信度:高(95%)——修复后 LLVM 支配性错误消失,clang 验证通过。
==/!= 运算符重载导致 3+ 若/抑 条件下堆损坏
根因:编译中缀表达式 对 ==/!= 走运算符重载路径(xt_dict_get(左, "_等_")),该路径创建内联 alloca i64(resAddr),在 3+ 若/抑 条件评估时产生堆损坏。
修复:在运算符重载入口处添加 early-return:当 节点.中缀运算符 == "==" 或 "!=" 时,直接调用 编译内建中缀(其 == 路径天然走 @xt_eq,不创建内联 alloca)。
置信度:高(90%)——修复后 3 条件测试从崩溃变为正常。
返 [expr, "type"] 产物返回空值
根因:编译返回语句 调用 编译表达式(节点.返回值) 递归编译。产物中将 返 [xt_val, "i64"] 的数组字面量传递给 返 时,递归 编译表达式 在产物中返回空值。分离为独立变量后正常。
推断:产物中内联数组字面量在 匹配 语句的 case 体内创建时,寄存器/变量作用域存在问题。
修复:全局替换 编译.xt 中 74 处 返 [变量, "type"] 为 设 _r = [变量, "type"] ; 返 _r。
置信度:中(65%)——修复有效但根因未完全理解。可能与产物中 匹配 case 体的寄存器分配有关。
字符串索引 s[i] 不处理 XT_TYPE_STRING
根因:索引编译代码(编译.xt L1983-2004)仅检查 type_id==6(字典),否则调用 xt_array_get。对 type_id==3(字符串)误调 xt_array_get,将 XTString 强制当作 XTArray 解析,导致访问违例。
修复:在 dict 和 array 之间插入 string 检查分支,调用 xt_string_get_char。
置信度:高(95%)。
解析中缀 default case 在优先级=4 递归崩溃
根因:解析表达式(优先级) 在 infix 循环中递归调用自身。==/!= 的优先级为 4,与 {(优先级 0)的 break 判断 4 >= 0 正确,但递归返回时崩溃(仅在自举产物中)。
修复:将 解析表达式(优先级) 改为 解析表达式(优先级 - 1)。
置信度:中(60%)——修复有效但精确根因未完全定位。可能与 4 >= 0 的比较在产物中生成的代码有关。
入口alloca列表 递归覆盖
根因:编译函数定义 递归调用时重置 此.入口alloca列表 = [],外层函数(如 编译程序)积累的入口 alloca 被覆盖。
修复:在 编译函数定义 开头保存 之前的入口alloca列表,结尾恢复。
置信度:高(85%)。
繁体字 設 导致 Go seed 链接失败
根因:修复脚本错误使用了繁体中文 設 代替简体 设,导致 Go seed 生成的 IR 引用不存在的 @設 符号。
修复:設 → 设。
解析遍历循环 缺少 设 声明
根因:语法分析.xt L539 的 变量名 = ... 声明了 变量名 但漏写 设。Go seed 宽容处理,自举编译器要求显式声明。
修复:变量名 = ... → 设 变量名 = ...。
当前状态
| 指标 | 状态 |
|---|---|
| Go seed → 玄铁.exe 自举编译 | 成功(零错误,~2.6s) |
| 回归测试 01-49 | 49/49 PASS (100%) |
产物 --help | 正常 |
| 产物编译简单文件 | Phase 1-4 通过,Phase 5 崩溃(0xC0000005) |
已知局限(2026-05-21 更新)
12.4.1 产物 Phase 5 代码生成访问违例
产物(自举编译器编译的玄铁.exe)在 编译程序 Phase 5 入口后立即崩溃(MEM:5代码生成开始 后的第一行代码即触发 0xC0000005)。
手动用 clang/gcc 链接产物 IR 后,二进制 --help 正常,可进入编译流程([D1]-[D4] 全部打印)。Phase 5 内部崩溃的具体代码行尚未定位。
推测:与产物中 编译程序 函数的 prologue/局部变量初始化有关。可能涉及入口 alloca 机制在递归编译场景下的状态残留。
12.4.2 产物解析器产生额外的垃圾语句
产物解析器在文件末尾产生一个额外的 表达式语句,其标识符名称为 空 字符的重复串。Go seed 解析器无此行为。
当前绕过:在 编译程序 代码生成循环中过滤掉标识符名称为空或含 空空 的表达式语句。
推测根因:产物中解析器的 EOF token 或空标记处理有未初始化内存。
12.4.3 产物 执 函数无法执行 clang/gcc
产物中 执(clang指令) 调用静默失败,无错误信息。但手动执行相同的 clang 命令可成功。执("cmd /c mkdir ...") 可正常工作。
推测:长命令字符串(含完整路径)在产物中有长度限制或内存损坏。
隐式契约补充(见 §11.6)
全项目安全审计报告(2026-05-23)
审计范围:runtime/C 运行时、Go 种子编译器、自举编译器、TiePM 包管理器、铁铺官网、渲染库 C 桥接层 审计方法:逐文件通读,按 CLAUDE.md 守则进行事实检索→置信度评估→自我纠错 共计发现:约 150 个问题(CRITICAL 21 / HIGH 45 / MEDIUM 50 / LOW 35)
CRITICAL 级别(必须立即修复)
C 运行时 (xt_runtime.c)
| # | 问题 | 文件:行号 | 说明 |
|---|---|---|---|
| C1 | ftell 返回值未检查 | xt_runtime.c:1500 | ftell 返回 -1 时 malloc(0) + 越界 fread → 堆溢出 |
| C2 | JSON 反序列化空输入越界写 | xt_runtime.c:2115 | 输入 " 时 inner[-1] 写入 → 堆损坏 |
| C3 | xt_release 中 old_ref==0 的释放后使用 | xt_runtime.c:844 | 已释放对象上 atomic_store(&obj->ref_count, 0) |
| C4 | Arena/非 Arena 混合数组扩容 | xt_runtime.c:932 | 对 arena 内存调用 realloc 或泄漏 malloc 指针 |
| C5 | XT_FALSE/XT_TRUE 当堆指针解引用 | 分布多处 | XT_IS_PTR(0x2) 为真,但解引用地址 0x2 段错误 |
| C6 | sprintf 缓冲区溢出 | xt_runtime.c:1706 | err_msg[128] + temp_path[MAX_PATH] → 栈溢出 |
| C7 | JSON 序列化不转义特殊字符 | xt_runtime.c:2053 | 双引号/反斜杠不转义 → 格式错误的 JSON |
Go 种子编译器
| # | 问题 | 文件:行号 | 说明 |
|---|---|---|---|
| G1 | LLVM visitedImports 静默丢弃符号 | llvm.go:977 | 重复导入返回 "0" 但不传播符号 → 链接失败 |
| G2 | 函数指针 bitcast 无符号表验证 | llvm.go:1060 | 标识符不在符号表时直接 bitcast → 损坏的 IR |
| G3 | bitcast 签名动态构建无验证 | llvm.go:1327 | 参数计数不一致时签名不匹配 → 未定义行为 |
| G4 | XTResult getelementptr 偏移量错误 | llvm.go:2449 | 索引 2 可能读取错误的字段 |
| G5 | Eval 并发 env map 无同步 | evaluator.go:43+ | goroutine 并行写共享 env → 数据竞争 |
| G6 | evalParallelExpression 数据竞争 | evaluator.go:1174 | 多 goroutine 共享父 env 指针 |
| G7 | evalAsyncExpression 共享 env 引用 | evaluator.go:1157 | asyncEnv 持有父 env 的 PARENT_ENV 指针 |
TiePM / 铁铺
| # | 问题 | 文件:行号 | 说明 |
|---|---|---|---|
| T1 | 发布 API 无认证 | main.go:81 | 任何人可向索引注入任意包元数据 |
| T2 | JSON 解析失败未捕获 | 核心.xt:238 | 解JSON() 结果未检查 → 运行时崩溃 |
| T3 | 请求体读取错误被丢弃 | main.go:87 | io.ReadAll 的 _ 吞掉错误 |
| T4 | 哈希失败时返回真绕过验证 | 铁铺.xt:269 | certutil 失败 → 返 真 → 跳过完整性检查 |
| T5 | 发布 TOCTOU 竞态条件 | main.go:106-121 | loadIndex/saveIndex 之间无锁 → 包丢失 |
| T6 | 包名路径注入任意文件写入 | 核心.xt:389 | ..\..\Windows\System32 包名 → 目录遍历 |
渲染桥 (渲染桥.c)
| # | 问题 | 文件:行号 | 说明 |
|---|---|---|---|
| R1 | xt_make_float 永久内存泄漏 | 渲染桥.c:72 | 每帧 calloc + IMMORTAL refcount → 无限泄漏 |
HIGH 级别(强烈建议修复)
展开全部 45 个 HIGH 级别问题
C 运行时
| # | 问题 | 位置 |
|---|---|---|
| H1 | 通道 send/receive 引用泄漏:发送 retain 但接收不 release | xt_runtime.c:1622/1632 |
| H2 | 数组弹出留下悬空槽 | xt_runtime.c:960 |
| H3 | 通道操作完全无锁 | xt_runtime.c:1617-1633 |
| H4 | 弱引用清除非原子 | xt_runtime.c:666 |
| H5 | 全局可变状态无锁(g_current_arena 等) | xt_runtime.c:50-52 |
| H6 | xt_compare 在 XT_FALSE/XT_TRUE 上段错误 | xt_runtime.c:1307 |
| H7 | xt_math_abs INT64_MIN 未定义行为 | xt_runtime.c:1836 |
Go 种子编译器
| # | 问题 | 位置 |
|---|---|---|
| H8 | expressionCode 默认返回 "nil" 字面量 | compiler.go:1249 |
| H9 | mapTypeAnnotation 默认返回 "i64"(静默) | llvm.go:299 |
| H10 | 三个后端的导入路径解析不一致(evaluator/Go编译器/LLVM编译器) | evaluator.go:462 / compiler.go:1343 / llvm.go:972 |
| H11 | 导入时 c.program 被替换/恢复,符号表被污染 | compiler.go:1413-1478 |
| H12 | PTR/PATH 断言未检查 nil | compiler.go:539 |
| H13 | ensureRawI64 对指针值做 ashr 破坏对齐 | llvm.go:501 |
| H14 | 字符串插值未闭合时返回部分解析字面量 | parser.go:897 |
| H15 | LLVM 符号表快照/恢复机制:函数内变量泄漏到外部 | llvm.go:883-885 |
| H16 | parseTypeAnnotation 未识别的类型静默返回空字符串 | parser.go:1522 |
| H17 | 硬编码 raylib 路径 C:/raylib/ | main.go:214 |
| H18 | 硬编码 w64devkit gcc 路径 | main.go:246 |
| H19 | LLVM target triple 硬编码 x86_64-windows-gnu | llvm.go:143 |
| H20 | 词法器 readString 中 \# 双重转义链 | lexer.go:492 |
自举编译器 (编译.xt)
| # | 问题 | 位置 |
|---|---|---|
| H21 | 弱引用字段实例化后立即 xt_release → 悬挂指针 | 编译.xt:1497-1504 |
| H22 | 弱引用局部变量声明后立即 release | 编译.xt:1617-1619 |
| H23 | 断/续 在嵌套若块内只弹出一层作用域 → ARC 泄漏 | 编译.xt:1299 |
| H24 | ==/!= 绕过运算符重载(已知堆损坏 bug 的绕过) | 编译.xt:3517 |
| H25 | 运算符重载路径右操作数 xt_release 缺失 → 内存泄漏 | 编译.xt:3548-3555 |
| H26 | %XTObject 布局硬编码(GEP 偏移固定) | 编译.xt:598-603 |
| H27 | 非入口块内 alloca 指令可能被 LLVM 拒绝 | 编译.xt:2280+ |
| H28 | 最后是返回 仅检查 ret i64,遗漏 ret i32 | 编译.xt:1238 |
TiePM / 铁铺
| # | 问题 | 位置 |
|---|---|---|
| H29 | 发布名称/版本/哈希无语义验证 | main.go:101 |
| H30 | 下载端点开放重定向(仓库 URL 未验证) | main.go:166 |
| H31 | 请求体大小无限制 → OOM DoS | main.go:87 |
| H32 | loadIndex() 错误被忽略 → nil map panic | main.go:106 |
| H33 | MkdirAll 错误被忽略 → 后续静默失败 | main.go:186 |
| H34 | 依赖解析循环检测可通过缓存绕过 | 核心.xt:352 |
| H35 | 不支持 > / <= / ^ / ~> 版本约束 | 核心.xt:100 |
| H36 | 空哈希完全绕过验证 | 核心.xt:396 |
| H37 | certutil 是 Windows 专有工具 | 核心.xt:485 |
| H38 | 核心.xt/铁铺.xt 临时路径不一致 | 各初始化函数 |
| H39 | TOML 值中 替换(" ", "") 破坏英文文本空格 | 核心.xt:152 |
| H40 | 全局可变状态无锁(依赖解析缓存等) | 核心.xt:279-282 |
渲染桥
| # | 问题 | 位置 |
|---|---|---|
| H41 | LoadTexture/LoadSound calloc 返回值未检查 NULL | 渲染桥.c:261 |
| H42 | Unload 函数可双重释放(无引用计数保护) | 渲染桥.c:268 |
| H43 | XTString/XTFloat 结构体布局必须精确匹配运行时 | 渲染桥.c:28-42 |
| H44 | DrawTextureEx 中 scale 参数 XT_TO_INT 截断浮点 | 渲染桥.c:294 |
| H45 | DrawRectangleRec 浮点参数精度丢失 | 渲染桥.c:183 |
MEDIUM 级别(计划修复)
展开全部 50 个 MEDIUM 级别问题
C 运行时(18 个)
- XT_FALSE/XT_TRUE 在众多函数中当堆指针(影响 20+ 函数)
- xt_file_read 未检查 fread 返回值
- xt_ffi_printf 接受用户控制格式字符串(格式字符串漏洞)
- xt_time_ms/micro 未检查 QueryPerformanceFrequency → 除零风险
- xt_eq/xt_compare 未检查字符串 data 为 NULL
- xt_result_new 未初始化 padding 字段
- xt_dict_values 跳过 type_id 检查(与 xt_dict_keys 不一致)
- xt_string_new_len 在 malloc 失败时返回 data==NULL 的字符串
- xt_string_split 使用 MSVC 特有的 strtok_s(3 参数 vs C11 4 参数)
- xt_string_to_hex_string 整数溢出风险(32 位平台)
- xt_math_random 无种子初始化
- xt_arena_destroy 中 ref_count 重置为 2 的脆弱假设
- XT_FROM_INT 对负值左移(未定义行为)
- 整个代码库中匿名结构体重复定义(XTFloat)
- xt_int_to_string 使用
%lld依赖 MinGW 扩展 - xt_channel_send/receive 环形缓冲区溢出检测缺失
- xt_dict_set 未检查 capacity==0(与 xt_dict_get 不一致)
- 多处硬编码幻数(对齐/容量/缓冲区大小)
Go 种子编译器(13 个)
- compiler.go:44 — filepath.Abs 错误丢弃
- compiler.go:1287 — isBarePackageName 对 Unicode 路径段的处理
- compiler.go:840 — TypeDefinitionStatement 前向引用可能失败
- compiler.go:1351 — 循环依赖检测依赖路径规范化
- evaluator.go:1276 — MemberCallExpression "执行" 忽略返回值
- evaluator.go:795 — TryCatch 不清理文件描述符
- evaluator.go:2250 — evalStringIndexExpression nil 类型断言
- evaluator.go:2309 — evalBlock 空块时返回 nil result
- lexer.go:63 — readDocComment 返回原始字节含 \r
- lexer.go:63 — readDocComment 在 EOF 无换行时行为未定义
- parser.go:549 — 接口内部仅支持方法签名
- compiler.go:405 — 5 秒硬编码连接超时
- main.go:335 — filepath.Abs 错误丢弃
自举编译器(8 个)
- 编译.xt:190 — 入口alloca已存在前缀匹配可能误判
- 编译.xt:784 — 出作用域栈重建 O(n) 拷贝
- 编译.xt:774 — 退出作用域对标记整数也调用 xt_release(功能安全但冗余)
- 编译.xt:921 — 匹配语句类型映射缺少默认情况
- 编译.xt:3653 — 未知中缀运算符静默返回零
- 编译.xt:543 — 类型推导被跳过(SKIPPED 桩)
- 编译.xt:3544 — 依赖 xt_dict_get 返回精确零判断键不存在
- 编译.xt:612 — 假定 xt_string_new 接受以 null 结尾的 UTF-8
- 语法分析.xt:755 — 深度嵌套前缀表达式有栈溢出风险(递归下降)
- 词法.xt:121 — 所有非 ASCII 字节视为标识符字符
- 编译.xt:278 — 十六进制编码硬编码
\XX3 字节格式假设
TiePM(11 个)
- main.go — 缺少 Content-Security-Policy 头部
- index.html:93 —
javascript:URI 在仓库链接中可执行 - 核心.xt:301 — 版本冲突检测比较字符串而非语义范围(假阳性)
- 核心.xt:439 — 依赖键过滤黑名单过于宽泛
- 核心.xt:405 — 解压失败时压缩包未清理
- 核心.xt — 非原子索引写入(直接覆盖 JSON 文件)
- 核心.xt:143 — TOML 解析器不支持子节
[a.b] - 核心.xt:147 — 第一个
=处分裂,错过值内部的= - main.go — 下载端点缺少速率限制
- main.go:169/182 — 错误响应格式不一致(JSON vs 纯文本)
- main.go — 发布时不检查 Content-Type
渲染桥(3 个)
- 渲染桥.c:183 — DrawRectangleRec 使用 XT_TO_INT 丢失浮点精度
- 渲染桥.c — 所有 calloc 调用点未检查 NULL
- 渲染桥.c — raylib 绘制函数非线程安全(假设单线程)
LOW 级别(技术债/改进建议)
共约 35 个 LOW 级别问题,主要包括:
- XT_TO_INT 对有符号类型右移是实现定义行为(依赖算术移位)
- 各文件中多处硬编码幻数(缓冲区大小 512/1024、通道容量 100、数组最小容量 4 等)
- xt_dict_set 与 xt_dict_get 的 capacity 检查不一致
- xt_string_substring 未显式验证 start/end 顺序
- xt_http_request 硬编码模拟响应(非真实 HTTP)
- 词法器 lookupKeyword 对未知标识符静默返回 TOKEN_IDENT
- lexer readDocComment 返回包含
///前缀的原始行内容 - LLVM 编译器 string map 无限增长
- 编译器 emitAlloca 的 store i64 0 后立即 store xt_val(冗余)
- 入口 alloca 列表保存/恢复正确但无文档
主函数 IR 包含死代码ret i32 0(在call exit/unreachable之后)- 编译器全局类/函数 map 无并发保护(当前单线程下无影响)
- TOML 解析器空节头
[]可能导致无效子串 - TOML 解析器仅支持 LF 换行
隐式契约补充(新增于审计)
| # | 契约 | 持有方 | 依赖方 | 违反后果 |
|---|---|---|---|---|
| IC1 | XTString.data 永远非 NULL | xt_runtime.c | 所有字符串操作函数 | 段错误 |
| IC2 | XTFloat 结构体布局 | xt_runtime.c | 渲染桥.c | 数据损坏 |
| IC3 | ==/!= 不触发运算符重载 | 编译.xt:3517 | 用户代码 | _等_ 方法被忽略 |
| IC4 | xt_dict_get 对缺失键返回精确整数零 | xt_runtime.c | 编译.xt:3544 | 运算符重载回退失败 |
| IC5 | 解JSON() 永远返回有效字典(不抛异常) | 核心.xt:238 | 铁铺 CLI | 运行时崩溃 |
| IC6 | certutil 在所有 Windows 系统上可用 | 核心.xt:485 | 铁铺 CLI | 哈希验证静默绕过 |
| IC7 | %XTObject LLVM 类型布局 | 编译.xt:598 | 编译.xt GEP 访问 | 读取错误 type_id |
| IC8 | \XX 十六进制格式每字节正好 3 字符 | 编译.xt:278 | 编译.xt:699 | 字符串长度计算错误 |
| IC9 | raylib libraylib.a 由 w64devkit gcc 15.2 编译 | C:\raylib | main.go 链接步骤 | 链接失败(符号不匹配) |
| IC10 | 入口alloca列表 中的 alloca 行格式为 %"name" = alloca i64 | 编译.xt:1669 | 编译.xt:190 | 去重检测失效 |
修复优先级建议
立即修复(阻塞安全/正确性):
- T1+T4+T6(TiePM 认证+哈希绕过+路径注入)—— 供应链安全
- C5(XT_FALSE/XT_TRUE 段错误)—— 影响 20+ 运行时函数
- G5+G6+G7(Eval 并发数据竞争)—— 异步/并行代码不可靠
- H21+H22(弱引用 release 悬挂指针)—— 需对照运行时验证
本迭代修复(影响功能正确性): 5. G1+G2+G3+G4(LLVM IR 生成错误) 6. C1+C2+C3+C6(运行时堆损坏) 7. T3+T5(TiePM 数据丢失 + 崩溃) 8. R1(渲染桥内存泄漏) 9. H23(断/续作用域泄漏)
下迭代修复(质量提升): 10. 所有 HIGH 级别问题 11. 硬编码路径消除(raylib/w64devkit) 12. TOML 解析器重写(空格破坏、子节、等号分裂) 13. 运算符重载堆损坏根因修复(而非 ==/!= 绕过)
CRITICAL 问题修复追踪(2026-05-23)
已修复
| # | 问题 | 修复日期 | 备注 |
|---|---|---|---|
| C5 | XT_FALSE/XT_TRUE 段错误 | 05-23 | XT_IS_REAL_PTR(v) 宏替代 !XT_IS_INT(v) || v == XT_NULL,34 处替换 |
| C1 | ftell 返回 -1 堆溢出 | 05-23 | xt_file_read: 校验 fseek/ftell/malloc 返回值,用实际 fread 字节数 |
| C2 | JSON 反序列化 inner[-1] 越界写 | 05-23 | xt_json_deserialize: 前置校验 len>=2 + 闭合引号检测 |
| C3 | xt_release old_ref==0 释放后使用 | 05-23 | 删除 else if (old_ref == 0) 分支 |
| C4 | Arena/非Arena 混合 realloc | 05-23 | XTArray 新增 elements_in_arena 标志位,扩容时正确分发 |
| C6 | sprintf 栈溢出 | 05-23 | sprintf→snprintf,缓冲区扩容 |
| C7 | JSON 序列化不转义 | 05-23 | 逐字符转义:" \ \n \r \t + 控制字符 \u00XX |
| T1-T6 | TiePM 供应链安全 | 05-23 | 认证+校验+TOCTOU+路径消毒 |
| G5-G7 | Eval 并发数据竞争 | 05-23 | cloneEnv() 深拷贝替代 extendEnv() |
已知取舍(可能给未来埋坑)
| 来源 | 取舍 | 风险 | 建议 |
|---|---|---|---|
| C4 | Arena 活跃时对原本 malloc 的数组 elements 扩容,旧缓冲区泄漏 | arena 模式在程序启动后通常不变,实践中极少触发 | 后续可添加 fallback free 路径 |
| C4 | XTArray.elements_in_arena 加在 struct 末尾 | 自举编译器 IR 中 %XTArray 类型定义为 6 字段,C 侧现为 7 字段。若未来编译器直接用 GEP 访问数组字段可能偏移错误 | 目前编译器通过 C 运行时函数访问数组,不直接 GEP,安全 |
| C7 | JSON 序列化转义缓冲区按最坏情况分配(length*6+3),大字符串内存消耗高 | 10MB 字符串需要 60MB 临时缓冲区 | 后续可改用两遍扫描(第一遍计大小,第二遍写入) |
| C2 | xt_json_deserialize 对未闭合字符串"原样返回"而非报错 | 调用者可能误用损坏数据 | 后续应返回 XT_NULL 或 Result 错误 |
| H21-H22 | 弱引用 release 后依赖 xt_free_obj→xt_weak_clear 清槽——单线程下安全,多线程下 xt_weak_clear 遍历全局链表无锁 | 当前玄铁为单线程,多线程后需加锁 | 后续引入线程时优先修此 |
| 异步捕获 | 异步块嵌套在匿名函数/回调函数内时无法捕获外部变量 | 顶层作用域正常工作,嵌套场景受限 | 需修改语义分析器 pass 支持 |
网络与并发进展 (2026-05-24)
线程池 (Phase 1)
runtime/xt_threadpool.h/c:工作窃取线程池,M 个 worker 线程执行 N 个用户任务- 平台抽象:Windows
CRITICAL_SECTION+CONDITION_VARIABLE/ POSIXpthread - 固定大小线程池(默认 CPU 核数),全局任务队列 + mutex + condvar
- API:
xt_threadpool_init(0),xt_threadpool_submit(func, arg),xt_threadpool_wait(task_id)
异步/等待/并行 (Phase 2)
- Go 种子 LLVM 编译器:
compileAsync/compileParallel生成独立 LLVM 函数 +xt_async_spawn提交到线程池 - 自举编译器:
编译异步/编译并行移除同步降级,生成真并发 IR - C 运行时:
xt_async_spawn(func_ptr, arg)分配 XTTask + 提交线程池,xt_async_wait(task)阻塞等待结果 - 异步闭包捕获:
已知局限(v0.18.5):嵌套在回调函数内受限于语义分析 pass→ 已修复 v0.18.7- 根因:
收集捕获变量的 AST 遍历 switch 仅覆盖 15 种节点类型,显示语句(示(...))等 14 种缺失——命中即中断遍历,内部标识符从未被扫描 - 修复:补全显示语句、匹配、数组/字典字面量、异步/等待/并行、匿名函数等 14 种节点
- 验证:嵌套
函 f(x) { 异步 { 示(x) } }编译运行正常,Test 58/59 无退化
- 测试:58_并发异步测试、59_并发通道压力通过
通道线程安全 (Phase 1/2)
XTChannel新增xt_chan_mutex_t mu字段xt_channel_send/xt_channel_receive全程持锁xt_channel_new用calloc零初始化 buffer,send 前释放旧值防泄漏xt_free_obj中XT_TYPE_CHANNELcase 销毁 mutex
网络栈 (Phase 1/2)
| 组件 | 文件 | 说明 |
|---|---|---|
| 网络层 | runtime/xt_net.h/c | Winsock2/POSIX socket 跨平台封装 |
| HTTP GET | xt_net_http_get | DNS→socket→GET /path HTTP/1.1→响应体 |
| TCP 连接 | xt_net_connect | 解析 host:port→DNS→socket→connect→返回 XTSocket |
| TCP 监听 | xt_net_listen | bind+listen→独立线程 accept 循环→回调处理 |
| Socket GC | XTSocket+XT_TYPE_SOCKET | 纳入 XT ARC 管理,xt_release 归零自动关闭 |
Socket/Result 方法表
xt_get_member扩展:XT_TYPE_SOCKET→.读/.写/.发/.关,XT_TYPE_RESULT→.接着/.否则- 方法实现为 C 函数包装,通过
xt_func_new返回XTFunction xt_obj_to_string新增XT_TYPE_SOCKET/XT_TYPE_CHANNELcase
IM 示例
examples/im/server.xt:TCP 监听 :8000,连接处理+输入管理循环examples/im/client.xt:TCP 连接+消息收发- 均通过自举编译器编译生成可执行文件
测试结果
63/63 PASS(含新增 58_并发异步、59_并发通道压力、60_弱引用并发释放),零退化。
自举编译器外部函数调用 ABI 修复 + 渲染库 v1.1 字体扩展 (2026-06-20)
背景
用自举编译器编译渲染库程序(如 旋转球体.xt)时暴露出四个连锁缺陷:
- clang 重定义错误:外函数被重复声明
- 运行时堆损坏:
call i64调用 void 函数后xt_release释放垃圾值 - X 按钮无响应:布尔返回类型未正确映射
i1/select - 中文不显示:字体码点范围不足 +
.ttc不被 raylib 支持
修复:编译.xt — 外部函数声明去重 + 返回类型映射
文件:编译.xt
16.2.1 去重(L861)
编译外部函数定义 在预扫描和代码生成两趟均被调用,将同一外函数声明追加两次到 外部函数输出列表。clang(D:\LLVM 版本)不允许重复 declare,即使签名相同。
修复:追加前检查 此.函数表[名],已存在则直接返空。
16.2.2 返回类型映射(L866-L870)
原代码注释"空类型映射已移除(字面量在产物中有字节数bug)",导致所有 : 空 外函数均被声明为 i64。同时 布尔 类型也缺少映射。
修复:
空 → void
布尔 → i1
整 → i64
字 → i8*并将原始返回类型存入 此.函数返回类型表[名],供调用处查询。
16.2.3 调用点:提前返回路径(L2992-L3044)
展开限定名 对简单标识符直接返回名称,导致所有函数调用走此路径——原代码硬编码 call i64。
修复:查 函数返回类型表,根据返回类型生成正确的 LLVM call 指令:
void→call void @fn(...),返回["0","i64"]哨兵i1→call i1 @fn(...)+select i1 → i64 4, i64 2转为 XT 布尔值- 其他类型同上逻辑
16.2.4 调用点:动态分派路径(L3573+)
原代码同样硬编码 call i64。同 §16.2.3 修复。
修复:玄铁.xt — 渲染库链接支持
文件:玄铁.xt
自举编译器原先不支持渲染库程序链接——未编译 渲染桥.c,未链接 libraylib.a 和 OpenGL。
新增逻辑(L301-L334):
- 检测
lib/渲染/渲染桥.c和libraylib.a是否存在 - 若存在,用 clang 编译渲染桥为
.o - gcc 链接时追加渲染桥
.o+libraylib.a+-lopengl32 -lgdi32 -lwinmm
渲染桥修复
文件:渲染桥.c
16.4.1 Bool 返回值
6 个函数从返回 XT_FROM_BOOL(...) 改为 ? 1 : 0:
XT_WindowShouldClose、XT_IsWindowReadyXT_IsKeyDown、XT_IsKeyPressedXT_IsMouseButtonDown、XT_IsMouseButtonPressed
原因:LLVM IR 声明返回 i1,XT_TRUE(0x4)/XT_FALSE(0x2) 最低位均为 0,读为恒定 false。
16.4.2 XTString 布局修正
桥梁中原 XTString 使用柔性数组 char data[],与运行时 char* data 指针布局不一致。xt_get_cstr 误将指针地址当字符串返回。
修复:对齐运行时布局——{uint32_t magic, ref_count, type_id; char* data; size_t length; uint8_t data_in_arena}。
16.4.3 Unicode 窗口标题
XT_SetWindowTitle 原用 raylib SetWindowTitle(内部走 ANSI API),UTF-8 中文标题乱码。
修复:直接调用 Win32 MultiByteToWideChar(CP_UTF8) + SetWindowTextW。
字体 API 新增
16.5.1 XT_LoadFont — 全范围字体加载
加载 ASCII(32-126) + CJK 统一汉字基本区(U+4E00–U+9FFF, ~21K 码点)。纹理约 8192×4096。适用场景:需要渲染任意中文内容,不预知字符集。
16.5.2 XT_LoadFontEx — 精准字体加载
从参考文本(第三参数)自动解码 UTF-8 提取唯一码点,仅加载 ASCII + 参考文本所需字符。纹理约 256×256。适用场景:已知渲染文本内容,追求纹理精简和渲染清晰度。
16.5.3 新增的渲染.xt 公开 API
| API | 桥函数 | 说明 |
|---|---|---|
加载字体(文件, 字号) | XT_LoadFont | 全 CJK 范围 |
加载字体精准(文件, 字号, 参考文本) | XT_LoadFontEx | 精准码点提取 |
释字体(句柄) | XT_UnloadFont | 释放字体资源 |
绘字体文字(句柄, 文本, x, y, 字号, 间距, r, g, b, a) | XT_DrawTextEx | 指定字体渲染 |
绘椭圆(cx, cy, rh, rv, r, g, b, a) | XT_DrawEllipse | 实心椭圆 |
已修复的已知局限
| 原编号 | 问题 | 修复 |
|---|---|---|
| §12.4.1 | 产物 Phase 5 代码生成访问违例 | call void + bool 转换修复 |
| 新发现 | 渲染桥 XTString 布局导致字符串乱码 | 结构体对齐运行时 |
| 新发现 | .ttc 字体不被 raylib LoadFontEx 支持 | 改用 .ttf + 新增精准加载 |
| 新发现 | 自举编译器不支持渲染链接 | 玄铁.xt 新增渲染桥自动链接 |
已知局限(2026-06-21 更新)
- HarmonyOS Sans SC 2021 版:全 CJK 范围缺约 90 字形(20997/21087),不影响常用中文
- 全范围字体纹理较大:8192×4096(32MB),字号 ≥28px 可保证可读清晰度;精准模式可降至 256×256
.ttc字体不支持:raylib LoadFontEx 仅支持.ttf/.otf- 产物解析器垃圾语句(§12.4.2):自举编译器产物在文件末尾产生额外
表达式语句,需在代码生成循环中过滤 - 产物
执无法执行 clang/gcc(§12.4.3):长命令字符串在产物中有长度限制或内存损坏 - 产物无法完全自举(§12.4.2+§12.4.3 合效应):自举链路不完整
本会话修复汇总(2026-06-21)
| 问题 | 修复位置 | 说明 |
|---|---|---|
| Test 50-55 崩溃 | 编译.xt 方法分派 | 新增 .转字()/.转JSON()/.解JSON() 编译期特判,绕过动态 xt_dict_get → NULL 崩溃 |
| Test 55 弱引用崩溃 | 编译.xt L1836 | 移除全局变量类型查询的 存址[0]=='@' 条件,弱字段检测恢复 |
==/!= 重载失败 | 编译.xt L3760 | 删除 ==/!= 强制走内建路径的绕过代码,恢复 _等_ 重载 |
!= 重载 | 编译.xt L3746 | 新增 != → _等_ 魔术方法映射 + XOR 取反 |
| §10.25 嵌套遍历+alloca | — | 确认 入口alloca列表 机制已修复,移除 lib/数组/数组.xt 绕过代码 |
| §15.2 异步闭包捕获 | 编译.xt 收集捕获变量 | 补全 14 种缺失 AST 节点类型 |
替换() 仅替换首个 | xt_runtime.c | 改为统计全部出现次数→一次分配→全部替换 |
| 路径斜杠规范化 | 编译.xt + 语法分析.xt | 新增 规范化路径 + W006 警告码 |
| 渲染库链接 | 玄铁.xt | 自举编译器自动检测渲染桥+libraylib.a 并链接 |
2026-07-20 深度审计:编译器 Bug 深挖与意外发现
审计目标
调查"在 型 编译器 { } 内新增方法导致 编译异步 丢失 %this_arg 参数"的根因,以及独立函数 设 声明触发 %variable undefined 的根因。
%this_arg 丢失问题
状态:在当前代码版本(commit b80e230,含 P6 统一编译路径重构)无法复现。
| 测试 | 方法数 | 结果 |
|---|---|---|
| 基线 | 86 | ✅ |
| +1 空方法(类型末尾) | 87 | ✅ |
| +5 空方法 | 88 | ✅ |
+1 复杂方法(匹配+遍历+若) | 87 | ✅ |
+1 方法在 编译异步 之前 | 87 | ✅ |
+2 参数递归方法(自调用+遍历) | 88 | ✅ |
| 跨方法调用 | 88 | ✅ |
| 最大测试 | 92 | ✅ |
推论:该 bug 极可能只影响 P6 重构前的旧版 编译异步。重构(删除双路径分支、重写 poll 函数生成)消除了触发条件。原始根因未定位。
建议:如需确凿证据,回退到 51af43f(P6 前)复现后调试。
独立函数 设 声明 Bug(GSC compileFunctionStatement 端)
状态:已复现,部分根因定位,未修复。
复现步骤:
- 在
编译.xt末尾(}闭合类型后)添加独立函数 - 独立函数体内使用
设 列表 = []声明数组变量 go run main.go tie xuantie_compiler/玄铁.xt编译
症状:
; 生成的 LLVM IR 中 funcAllocas 只包含参数 alloca,缺少 body 内变量 alloca
; [READ] funcAllocas len=108 content= %"输入" = alloca i64\n store i64 0, i64* %"输入"
; ↑ %"列表" alloca 消失了
; 后续 body 代码 load/store %"列表" → undefined value 错误已确认的事实:
emitAlloca("%\"列表\"")被调用且写入c.allocaOutput→ debug 日志确认c.allocaSet["%\"列表\""]在此时未被占用(无 DUPLICATE 日志)- 但
funcAllocas = c.allocaOutput.String()读取时不包含%"列表" __ALLOCAS_MARKER__替换成功执行(最终 IR 无残留 marker)
假说:emitAlloca 写入后、funcAllocas 读取前,c.allocaOutput 被某处代码 Reset(c.allocaOutput = bytes.Buffer{})。所有 5 处 Reset 点(compileFunctionStatement、compileMethodStatement、compileAsync、compileAnonymous、compileParallel)均不应被 设 列表 = []; 列表.追加(输入) 这条简单语句触发。
待做:在全部 5 处 c.allocaOutput = bytes.Buffer{} 赋值处加 stack trace dump,确定哪处被意外触发。
GSC 独立 函 的 Parser 限制
发现:GSC 的 parser(parser/parser.go,版本 546d497)不支持直接编译文件中顶层独立 函。
函 测试独立(输入) { ... }在独立的_t.xt中编译 → 解析错误- 但
引入的文件中顶层独立函可以正常编译(如编译.xt末尾的收集等待节点) - 这意味独立函数只能存在于被
引的模块中,不能作为主入口文件的第一层语句
影响:将来如果需要在主入口文件中定义顶层辅助函数,需先让 GSC parser 支持或改为 引 入方式。
编译.xt 文件大小与服务方法数
事实:当前 编译.xt 文件有 4827 行,型 编译器 { } 内含 86 个方法,} 闭合后还有 1 个独立函数(收集等待节点)。
方法细节:
- 方法参数最多 6 个(
生成循环逻辑、编译内建中缀、生成原生程序) - 方法名全为中文,在 LLVM IR 中以 UTF-8 编码的
@"编译器_xxx"形式出现 - 新增方法加到类型末尾(
}前)目前安全(至少测到 92 方法)
Python heredoc \n 与 Go string literal 转义陷阱
发现:通过 bash heredoc 传递 Python 脚本修改 Go 源码时,\n 转义极易出现"多一层或少一层"的错误。
根因:
- bash heredoc
<< 'PYEOF'不做 shell 转义 → Python 收到字面\n - Python 中
b"\\n"(2 字符\+n)→ 写入文件为\n(1 字节 0x0A,因为 Python 的 bytes string 中\n就是换行符) - Python 中
b"\\\\n"(4 字符\\n)→ 写入文件为\\n(2 字节 0x5C 0x6E) - 但 Go 源码中
\\n被 Go 编译器解释为\+n(字面反斜杠+n),不是换行 - 要在 Go 源码中表达"字符串值中包含字面
\n两个字符",需要\\\\n(Go 源码中的 4 字符)
教训:跨三层转义(bash→Python→Go→文件)时直接用 hex 字节(bytes([0x5C, 0x6E]))避免所有歧义。 | 外部函数 ABI | 编译.xt | 去重 + 空→void/布尔→i1 映射 + 两调用路径类型感知 |
2026-10-04 修复:执() 失败值空指针 —— 运行时契约化 + CWD 探测改 xt_cwd
缺陷
执() 的 spawn 失败(收窄 PATH 下 cmd 不可达等)返回 结果(0, NULL, 错误串);语言层存在 结果.值.替换(...) 这类不判空、直接对字符串方法发起调用的调用点(编译.xt 规范化绝对路径、 铁铺安装目录探测),对 NULL 调方法 → 编译期段错误(实测 exit 139)。
关键区分:属性访问(.值.长度)已有守卫(xt_member_missing,干净报错并给汉字提示); 方法调用路径没有同款守卫 —— 崩溃只发生在后者,与 289/348 两处的 .替换 形状吻合。
修复(两层)
runtime/xt_runtime.c:执()全部 13 处失败返回的值槽 NULL → 空串,并写失败契约注释 ("结果.值 一律空串,绝不交 NULL")。语言层一切字符串运算对失败结果同样安全(用户程序受益, 不止编译器内部)。编译.xt规范化绝对路径:CWD 探测弃用执("cmd /c cd")/执("pwd"),改 runtime 内建xt_cwd()(与玄铁.xt项目模式的同款修正一致——那里记录过同源事故:"失败被忽略,空值被 拼成 空\玄铁.配置.toml,把取不到 CWD 伪装成其它错误");取不到即明确报错退出,不静默降级。
验证
- 自举链 GSC→s1..s4 + DDC:s3==s4 逐字节一致(md5
ac52e9252ee59aa7505d24183fa69d1a), 冒烟与 pao 冒烟全过(1m34s)。 - 全量回归 0/141 异常(弹窗类 96/97/98 按 CI 同款跳过)。
- A/B 对照:收窄 PATH 编译 —— 旧编译器段错误 139 / 新编译器 rc=1 干净报错(报 clang 不可达 并保留 IR);用户程序
执(不存在命令).值.长度—— 旧版干净中止 / 新版得空串、程序继续。
残留(未动,待定)
其余 31 处内建(文件.读/写、类型转换等)失败值仍为 NULL,方法派发路径仍无守卫。若继续收口: 统一失败值空串(改动面大),或给方法派发补同款守卫(单点,可复用 xt_member_missing 的汉字提示)。