Skip to content

自举编译器实现笔记 ​

本文档是玄铁自举编译器(xuantie_compiler/ 目录下由玄铁自身编写的编译器)的实现笔记,面向未来的维护者和协作者。 自举编译器由 KimiK3 自主开发完成主体架构。本笔记基于 2026-05-04 实际代码审计编写。 本文档由DeepSeekV4Pro生成。


编译器总览与架构 ​

编译流程 ​

玄铁源码 (.xt) → 词法.xt → 语法分析.xt → 抽象语法树.xt → 编译.xt(四趟)→ LLVM IR → clang/gcc → 原生二进制

自举编译器是一个用玄铁自身编写的四趟编译器,核心文件:

文件行数职责
词法.xt462词法分析,将 UTF-8 字符转为 Token 流
抽象语法树.xt478所有 AST 节点类型定义
语法分析.xt1310递归下降 + Pratt Parsing 语法分析
编译.xt3633核心:四趟编译——预扫描、语义分析、类型推导、IR 生成
玄铁.xt / xt.xt / 入口.xt110-222驱动入口,CLI 参数解析、调用编译器、执行 clang/gcc

与种子编译器的关键差异 ​

维度种子编译器 (Go)自举编译器 (玄铁)
编译趟数单趟(代码生成即类型追踪)四趟(预扫描→语义→类型推导→IR)
类型追踪内联在 compileExpression 返回值独立的 推导程序类型 pass + 变量类型表
前向引用AST 节点天然支持通过全局预扫描收集符号解决
模块系统ImportExpression 内联处理全局预扫描递归 + 程序缓存 + 语义分析 拓扑排序
运算符重载InfixExpression + magicMethod fallback编译中缀 先试 xt_dict_get 动态分发,再 fallback 编译内建中缀
内存管理Go GCArena 内存池(xt_arena_new/xt_arena_use)

编译器结构体关键字段 ​

出自 编译.xt L1-L40:

xuantie
型 编译器 {
    设 结果输出列表   // 当前函数/主域的 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 结构 ​

xuantie
型 标记 {
    设 标记类型 = ""   // Token 类型关键字,如 "标识符"、"数字"、"函数定义"
    设 标记字面量 = "" // 原始文本
    设 标记行 = 0      // 行号(1-based)
    设 标记列 = 0      // 列号(1-based)
}

词法分析器工作机制 ​

字符流模型:将源码字符串转为字符列表 字符列表,通过 读字符() 单字符前进,窥视字符() 预读下一字符。

关键字识别(词法.xt L409-L464):所有中文关键字通过硬编码匹配表识别。例如:

"设" → "变量声明"、"函" → "函数定义"、"型" → "型定义"
"若" → "如果"、"遍历" → "遍历循环"、"匹配" → "匹配"

字符串处理(词法.xt L230-L296):

  • 支持 "..." 和 '''...''' 两种引号风格
  • 支持插值标记 #{}——词法器不解析插值,将整段字符串(含 #{表达式} 原文)作为一个 Token 返回,插值解析在语法分析阶段处理

标识符规则(词法.xt L88-L114):

  • 排除所有 ASCII 符号和数字后,剩余字符(含所有多字节 UTF-8 字节)视为"字母"
  • 支持以 ? 结尾(如 存在?、包含?)

注释:// 到行尾,在 读标记() 中处理。


抽象语法树(抽象语法树.xt) ​

位于 抽象语法树.xt,478 行。

基础节点 ​

xuantie
型 基础节点 {
    设 节点行       // 源码行号
    设 节点列       // 源码列号
    设 节点类别     // "函数定义"、"中缀"、"标识符" 等——编译器的核心分发依据
    设 推导类型     // 第三趟类型推导的结果("整"、"字"、"逻"、"未知")
}

关键设计:所有节点继承 基础节点,节点类别 是编译器 匹配 语句的唯一分发键。这是理解整个编译器控制流的核心——不是靠 Go 的类型 switch,而是靠字符串匹配。

关键节点类型速查 ​

节点类型节点类别关键字段
整数节点"整数"整数值
标识符节点"标识符"标识符名称
中缀表达式节点"中缀"中缀左、中缀运算符、中缀右
前缀表达式节点"前缀"操作符、右节点
调用表达式节点"调用表达式"函数表达式、调用参数
成员访问节点"成员访问"左表达式、成员名称
函数定义节点"函数定义"函数名、参数列表、返回类型、函数体块
变量声明语句节点"变量声明"声明名称、声明值、注解类型
块语句节点"块"块语句列表
若语句节点"若语句"若条件、若结果块、抑分支列表、若否则块
遍历循环节点"遍历循环"迭代变量名、迭代目标、循环块
引入表达式节点"引入"路径、别名

函数定义节点的参数表示 ​

重要:在自举编译器的 AST 中,函数参数用 标识符节点 的列表表示,类型注解存在 标识符节点.推导类型 字段中(语法分析.xt L502):

xuantie
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) ​

流程:

  1. 解析函数名(允许 造 作为构造函数名)
  2. 解析泛型参数 <T>
  3. 解析参数列表 (name: Type, ...)
  4. 解析返回类型注解 : Type
  5. 解析函数体 { ... }

类型注解处理:每个参数的 推导类型 字段被重用存储解析到的类型名(如 "整"),这是后续类型推导和代码生成的关键数据。

解析中缀(语法分析.xt L651-L718) ​

Token生成的 AST 节点
(调用表达式节点
[索引表达式节点
.成员访问节点
=中缀表达式节点(运算符="=")
是中缀表达式节点(运算符="是")
..范围表达式节点
?问号后缀节点
其他中缀表达式节点(运算符=原始文本)

编译器(编译.xt)—— 四趟编译详解 ​

位于 编译.xt,3633 行,是自举编译器最大的文件。

编译入口:编译程序 ​

xuantie
函 编译程序(程序节点, 源文件路径) {
    // 第一趟:全局预扫描(收集符号)
    此.全局预扫描(程序节点, "", 源文件路径)
    // 第二趟:语义分析(类型成员解析 + 拓扑排序)
    此.语义分析(程序节点)
    // 第三趟:类型推导(Static Type Inference)
    此.推导程序类型(程序节点)
    // 第四趟:代码生成(IR Emit)
    // ...遍历程序语句列表,调用 编译语句()...
    // 最后拼接 IR 头部(类型声明、外部函数声明、字符串常量)+ 函数体 + main
}

第一趟:全局预扫描 ​

函数位置:编译.xt L239。

职责:在代码生成前收集所有顶层符号,解决前向引用问题。

匹配的节点类别:

  • "函数定义" → 注册到 函数表 和 符号表
  • "型定义" → 注册到 类型表,预扫描所有方法签名
  • "接口定义" → 注册到 类型表
  • "外部函数" → 调用 编译外部函数定义 生成声明
  • "表达式语句" 且是 "引入" → 递归预扫描引入的模块(预扫描引入)
  • "变量声明" → 全局变量注册到 符号表

引入机制:预扫描引入:

  1. 按主文件路径解析相对路径
  2. 检查 程序缓存——若已解析过则复用 AST
  3. 否则 文件.读 + 造 词法分析器 + 造 语法分析器 + 解析程序()
  4. 将解析结果缓存到 程序缓存
  5. 递归调用 全局预扫描,带上命名空间前缀

第二趟:语义分析 ​

函数位置:编译.xt L319。

职责:解析类型成员、处理继承链、插入 此 参数。

流程:

  1. 递归处理引入模块的语义分析
  2. 对型定义做拓扑排序(先解析父类,再解析子类)
  3. 解析型成员 中:
    • 继承父类的属性和方法列表(扁平化到 最终属性列表 / 最终方法列表)
    • 为每个方法的参数列表插入 此 参数(如果没有的话)
    • 处理方法重写:用 原始函数名 匹配,覆盖同名的父类方法

关键细节:语义分析修改了 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:

xuantie
若 非 此.是标量类型(类型) {
    此.写出("  call void @xt_retain(i64 " & 寄存器 & ")")
}
返 [寄存器, 类型]

中缀表达式 → 先试运算符重载(编译中缀 L2974),无重载则走 编译内建中缀(L3045):

编译内建中缀 有双重路径:

  1. 静态优化路径:节点.中缀左.推导类型 == "整" 且 节点.中缀右.推导类型 == "整" → 原生 LLVM 指令(add/sub/mul/icmp...)
  2. 通用路径: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 输出与主函数组装 ​

编译程序 的末尾按顺序输出:

  1. Target triple 和 data layout
  2. 类型声明(%XTObject、%XTString、%XTArray……)
  3. 外部运行时函数声明(declare ... @xt_...)
  4. 外部 FFI 函数声明
  5. 字符串常量(@str.N = ... constant ...)
  6. 函数定义(此.函数输出)
  7. 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 中:

xuantie
p节点.推导类型 = 此.解析类型()

这与种子编译器 Parameter.DataType 专用字段不同。自举编译器复用了 推导类型 字段来存原始类型名。这意味着第三趟 推导程序类型 pass 可能会覆盖参数上已经存好的类型注解——如果该标识符节点同时被类型推导遍历到的话。但目前没有观察到覆盖,因为推导 pass 不会遍历函数参数节点(只遍历语句和表达式)。

变量类型表的生命周期 ​

变量类型表 是全局共享的,但在 编译函数定义 中会被临时清空并只恢复全局变量:

xuantie
此.变量类型表 = {}
遍历 键 于 之前的符号表 {
    若 存址.截取(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。

编译中缀的静态优化路径依赖推导类型 ​

编译内建中缀 L3050:

xuantie
若 节点.中缀左.推导类型 == "整" 且 节点.中缀右.推导类型 == "整" {

这里比较的是第三趟推导 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):

  1. 6 种类型定义全部添加 magic 字段(+1 个 i32)
  2. 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 处)

验证:修复后 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:

llvm
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 前(对比):

llvm
; 旧 IR:参数被当堆对象 retain,比较走 xt_compare,
; 算术走 xt_sub/xt_add 运行时函数,递归返回值当 i64 传递不脱壳
; fib(30) ~365ms

v0.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) ​

位置:llvm.go L1646-L1653

现象:文本.字节(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) ​

位置:llvm.go L1753-L1765

现象:数组遍历正常完成,但到字典遍历时崩溃(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 ​

位置:llvm.go L1774/L1796

现象:截取 方法不带结束参数时,访问 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):

  1. 成员访问处理器(长度/大小/字节数):字符串→xt_string_byte_length(返回已标记XTValue,直接store),数组→xt_array_length(返回raw size_t,经写_存储_xt标记),字典保持xt_dict_size
  2. 遍历处理器:数组长度→xt_array_length,字符串长度→xt_string_byte_length,字典键集长度→xt_array_length
  3. 截取默认结束参数:改为xt_string_byte_length + ashr脱壳
  4. 新增xt_array_length到C运行时(xt_runtime.h/c),编译新的xt_runtime.o
  5. 新增@xt_array_length的IR声明到自举编译器输出头

【已修复 v0.17.9】语法分析器解析尝试捕捉不支持括号语法导致死循环 ​

位置:语法分析.xt L1245-L1262

现象:编译任何含尝试 { } 捕捉 (e) { }的代码时,自举编译器无输出直接超时,内存超20GB被系统kill。

根因:解析尝试捕捉只处理捕捉 标识符(无括号),未处理捕捉 (标识符)(带括号)。当遇到(时,代码未消费它就直接调用解析块语句(),后者第一行进标记()无条件吃掉当前标记(误吃(),导致解析器状态完全混乱→死循环→Arena无限扩容。

修复:在检查标识符之前先检查左圆括号,若存在则消费(→读标识符→消费)。

【已修复 v0.17.9】含?/包含?成员方法缺少字符串支持导致Segfault ​

位置:编译.xt L2874-L2891

现象:文本.包含?("玄铁")和文本.含?("玄铁")均导致ACCESS_VIOLATION崩溃。

根因:1) 成员方法匹配只检查含?不检查包含?;2) 无条件调用xt_dict_contains,对字符串完全无效——xt_dict_contains内部检查type_id不是字典/实例时返回0,但LLVM IR中对字符串指针误调用xt_dict_contains可能导致后续操作异常。

修复(参照Go种子llvm.go L1697-L1725):

  1. 匹配条件扩展为成员名 == "含?" 或 成员名 == "包含?"
  2. 新增类型分派:读type_id→是字符串(type_id=3)则走xt_string_contains(参数转%XTString*),否则走xt_dict_contains
  3. 用LLVM phi节点合并两分支的布尔结果
  4. 新增@xt_dict_contains的IR声明

【已修复 v0.17.9】语法分析器多处小修复 ​

解析返回语句first集缺少函数定义(语法分析.xt L488):返 函(...){}中函标记类型为函数定义但不在first集中,导致匿名函数作为返回值时表达式不被解析。修复:first集添加"函数定义"。

遍历循环变量名过度限制(语法分析.xt L371-L375):遍历 任务 于 列表中任务是保留关键字被拦截。实际上在迭代变量位置没有歧义,允许关键字作为变量名。修复:将关键字检查从"拒绝"改为"接受"。

【已修复 v0.17.9】索引赋值 x[y] = z 缺少字典类型分派 ​

位置:编译.xt L3328-L3335

现象:映射["地"] = "中国" 无效——字典大小不变(保持2),遍历字典崩溃。

根因:编译赋值处理索引赋值时,无条件调用xt_array_set——该函数内部检查type_id!=5时直接返回,对字典完全无效。

修复(参照Go种子llvm.go L2655-L2697):

  1. 读type_id做类型分派:字典(type_id=6)→xt_dict_set,数组(type_id=5)→xt_array_set
  2. 用br分派到两个基本块,最后汇合到merge块统一release对象和索引

回归验证:Test 03 全部通过(字典大小=3、遍历字典输出地/名/版、复杂嵌套=30),退出码0。

【已实现 v0.17.9】编译严格度三档开关系统 ​

位置:语法分析.xt L7-L12、编译.xt L4-L35、玄铁.xt L40-L90

设计依据:02_类型系统哲学设计.md 第1节"多档开关"。

实现:

  1. 解析器和编译器各新增严格度字段(默认"默认")
  2. CLI参数支持中英文和拼音:--默认/--moren、--严格/--yange、--禁止/--jinzhi
  3. 等级提升逻辑:默认保持原始等级,严格提升一档(宽松→警告、警告→错误),禁止全部→错误
  4. 添加警告和添加类型警告根据严格度决定:错误级→调用终 1终止编译,警告级→输出,宽松级→静默

验证:使用Test 01的W004在三种模式下测试——默认输出警告(编译继续)、严格→错误(中断)、禁止→错误(中断),全部正确。

【已实现 v0.17.9】隐式类型检测(W001-W003) ​

位置:编译.xt L3560-L3580

实现:在推导语句类型的类型推导阶段中加入检测:

  • W001:变量声明设 x = 100无: 整注解,且推导类型非"i64"和"未知"时触发(宽松级,默认静默)
  • W002:函数参数无类型注解时触发(警告级),检测参数.推导类型 == "未知"(AST节点默认值)
  • W003:函数无返回类型注解时触发(警告级),检测语句.返回类型 == ""

注意:参数推导类型默认值为"未知"(基础节点初始化),而非"",W002需与"未知"比较。

【已实现 v0.17.9】未使用变量检测(W101) ​

位置:编译.xt L1585(标记)、编译.xt L1280(检测)

实现:

  1. 编译器新增已用变量字典,在标识符加载路径(编译表达式→标识符→符号表.含?)中标记此.已用变量[变量名] = 真
  2. 在编译函数定义末尾(函数IR生成完成后、状态恢复前),遍历变量类型表:排除全局变量(地址前缀@)和函数参数,检查是否在已用变量中
  3. W101为宽松级,默认静默;严格模式→警告输出

局限:当前使用函数定义节点的行号作为W101的行号(节点.节点行),而非实际变量声明行号;变量类型表中不追踪每个变量的源码位置。

【已实现 v0.17.9】@允许 注解抑制语法 ​

位置:词法.xt L394-L400、语法分析.xt L231-L245、语法分析.xt L68-L72

设计依据:02_类型系统哲学设计.md 第44-49行"注解式"。

实现:

  1. 词法分析器读标记中新增@字符处理:读@后读标识符,若为允许则返回"注解允许"令牌类型
  2. 解析器结构体新增当前抑制列表字段;解析语句中新增"注解允许"分支:消费@允许令牌,解析(code1, code2, ...)抑制参数列表,递归解析下一条语句后将抑制列表附加到其解析结果
  3. 添加警告方法在判断严格度之前先检查抑制列表:若警告码或其类别名在列表中,静默跳过

语法:@允许(W004) 或 @允许(逻辑语法),放在被抑制的语句前一行。

注意:@允许只抑制紧跟的下一条语句。若需要抑制多条语句,每条前均需放置。

【已实现 v0.17.9】玄铁.配置.toml 项目配置文件 ​

位置:玄铁.xt L17-L69

设计依据:02_类型系统哲学设计.md 第14-23行"项目配置文件"。

实现:

  1. 新增顶层函数解析配置(内容):简单的TOML子集解析器,支持[节]标题(跳过)、键 = "值"格式(去除空格和双引号)
  2. 主函数在读取源文件前,查找源文件同目录下的玄铁.配置.toml,若存在则解析
  3. 配置优先级:命令行参数 > 配置文件 > 默认值。仅当命令行严格度为"默认"且配置文件指定了严格度时,才用配置文件覆盖

支持格式:

toml
[编译]
严格度 = "严格"

局限:数组值(警告即错误 = [...])尚未实现,可在后续版本补全。

【待实现】未使用导入检测(W102) ​

位置:待定

现象:引 "模块.xt" 引入的模块未被实际使用时,编译器不提示。

设计:在全局预扫描阶段记录所有引入路径,编译完成后对比实际使用的跨模块符号(函数调用、类型引用、变量访问),对未使用模块按W102输出警告(宽松级,默认静默)。

阻塞依赖:需要跨模块符号追踪基础设施(记录每个符号的来源模块)。

【已修复 v0.17.9】关键字节点表达式求值跳过了符号表变量查找 ​

位置:编译.xt L1625-L1670

现象:遍历 任务 于 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(编译匿名函数修改)

现象:函() { 累计 = 累计 + 增量 } 中 累计 是外层函数局部变量,匿名函数体内引用时报"未知标识符"。

根因:编译匿名函数创建独立作用域时只复制了全局变量(@前缀),外层局部变量未被继承。

修复:

  1. 新增收集捕获变量辅助函数——递归遍历匿名函数体AST(覆盖所有表达式和语句节点类型),收集外层符号表中存在的局部变量名(排除@前缀的全局变量)
  2. 为每个捕获变量创建全局桥接变量(@"closure_xxx" = global i64 0),在全局声明列表记录,编译程序末尾统一输出到模块级IR
  3. 在外层函数中生成load+store指令将当前值写入全局桥接(直接追加到之前的输出列表避免上下文切换问题)
  4. 在匿名函数入口从全局加载到闭包内新alloca
  5. 在匿名函数体编译后、返回检查前,将捕获变量的当前值写回全局桥接

局限:

  • 寄存器名使用%"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) 等泛型语法全部报解析错误。

根因:

  1. 解析泛型参数列表只接受标识符类型——<整>中的整是类型关键字而非标识符,被拒绝
  2. 解析中缀无<处理——Pratt解析器把交换<整>中的<当作比较运算符
  3. 变量声明中结果等保留关键字被拒绝
  4. 比较运算符a < b中的<被泛型处理误消费→死循环

修复:

  1. 新增是类型关键字函数:识别整、字、小数类型、逻、数组、字典、结果、字节、任务、道
  2. 解析泛型参数列表:接受标识符或类型关键字;前瞻窥视标记——若不是标识符/类型关键字/大于则立即返回空(不消费<,留给比较运算符)
  3. 解析中缀新增"小于"处理:调用解析泛型参数列表→有参数且后跟(→泛型函数调用;有参数→泛型类型引用;无参数→退化为比较(手动消费<,解析右表达式,创建中缀节点)
  4. 解析变量声明:允许保留关键字用作变量名(编译器通过符号表优先匹配变量)

编译器侧:泛型参数采用类型擦除——所有类型在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节"弱引用修饰符"。

现象:弱 设 父节点: 父节点? 语法完全不支持,编译器报解析错误。

实现:

  1. 运行时:新增XTWeakSlot结构体(旁路链表节点),全局链表g_weak_slots;xt_weak_init(slot_addr, obj)将槽位地址注册到链表;xt_weak_clear(obj)遍历链表清零匹配槽位→摘除释放;xt_free_obj开头调用xt_weak_clear(obj)
  2. AST:变量声明语句节点新增弱引用字段(默认假)
  3. 语法分析:解析变量声明签名改为(级别, 弱参);解析语句新增"弱引用"分支→消费弱→调用解析变量声明(级别, 真);解析带修饰符语句支持公 弱 设组合;继续解析型定义支持类属性弱引用;所有原有调用点传假
  4. 编译器:新增弱引用变量集字典字段;编译变量声明语句若语句.弱引用→生成xt_weak_init+xt_release+移除作用域追踪+记录弱引用变量集;编译赋值标识符分支若目标为弱引用变量→直接store跳过release+retain

局限(MVP不处理):

  • 成员属性弱引用(子.父节点 = 父)走xt_dict_set路径,不自动调用xt_weak_init
  • 弱引用重赋值时旧槽位未从旁路链表中注销
  • 应用层需自行空检查弱引用值

回归验证:Test 17(弱引用)全部通过退出码0:声明、重赋值、空弱引用、归零验证(覆盖强引用后弱引用自动置空)。

ARC 行为细节:

弱引用声明时的 ARC 操作:

  1. 注册局部变量:创建 alloca,存储值(不 retain——弱引用不持有所有权)
  2. xt_weak_init(slot, obj):将 alloca 地址注册到对象的旁路链表中
  3. xt_release(obj):释放表达式 load 时 编译表达式 自动生成的 xt_retain(表达式加载非标量值时会 retain→ref+1,此处 release 抵消之),对象 refcount 回到只有强引用持有的状态
  4. 从作用域追踪移除 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](严格度=默认):循环引用风险
  类型 `节点` 的字段 `父: 节点?` 可能与字段 `子: 数组<节点>` 形成循环引用
  建议:把 `父` 字段标注为 `弱`
  修复:弱 设 父: 节点?
  注:在 --严格 模式下此警告将成为错误

检测算法:

  1. 在 语义分析 阶段,为每个 型定义 构建引用图:节点=类型名,边=该类型中引用其他类型的字段
  2. 跑 Tarjan 强连通分量(SCC)算法或简化版环检测(DFS + 回溯边检测)
  3. 发现的环中,若某条边标注了 弱→跳过;若所有边都未标 弱→输出 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】泛型参数可空修饰符 ? ​

位置:语法分析.xt L326-L338

语法规则:? 紧跟在一个类型后面,修饰的是它紧邻的那个类型。

节点?              ← 节点 可空
数组<节点>?        ← 数组 可空(整个数组可能为空)
数组<节点?>        ← 数组里的元素可空(数组本身存在)
数组<节点?>?       ← 数组可空,且元素也可空
字典<字, 节点?>    ← 字典里 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),弱引用语义未生效。

修复:

  1. 运行时:新增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
  2. 编译器:新增弱引用字段表(型名→{字段名: 真});编译型定义遍历最终属性列表收集弱字段;编译实例化字段初始化循环中检查属性.弱引用→调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(第二前瞻标记):

  1. 结构体新增字段、构造器多调一次 进标记()、进标记() 级联 窥视标记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 含 / 却被误判为已解析,未拼接父文件目录。

修复:

  1. 编译.xt L307:条件改为 非 此.是绝对路径(原始路径),复用已有的绝对路径判断函数
  2. 保存 原始路径 变量防止被覆盖,错误信息扩展为四行:来源文件、引入路径(原始)、解析为(完整路径)、修复提示
  3. Test/26_数组高阶方法.xt:路径从 "./lib/数组.xt" 修正为 "../lib/数组.xt"

回归验证:30/30 全部通过。新增 Test 30 故意写错路径——编译器输出清晰错误(来源文件、原始路径、解析后路径、修复提示)并退出码 1。

【已修复 v0.18.7】嵌套遍历中外层变量不可见 + 遍历体内 alloca 支配性 ​

日期:2026-05-16(发现)→ 2026-06-21(修复确认)

现象(已修复):

  1. 外层 遍历 项 于 列表 { 遍历 已有 于 输出 { ...项... } } — 内层 遍历 体中无法访问外层 遍历 变量 项,编译器报"未知标识符"
  2. 设 在 遍历 体内声明后用于 若 条件分支,触发 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-182xt_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=3s[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-4949/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) ​

#问题文件:行号说明
C1ftell 返回值未检查xt_runtime.c:1500ftell 返回 -1 时 malloc(0) + 越界 fread → 堆溢出
C2JSON 反序列化空输入越界写xt_runtime.c:2115输入 " 时 inner[-1] 写入 → 堆损坏
C3xt_release 中 old_ref==0 的释放后使用xt_runtime.c:844已释放对象上 atomic_store(&obj->ref_count, 0)
C4Arena/非 Arena 混合数组扩容xt_runtime.c:932对 arena 内存调用 realloc 或泄漏 malloc 指针
C5XT_FALSE/XT_TRUE 当堆指针解引用分布多处XT_IS_PTR(0x2) 为真,但解引用地址 0x2 段错误
C6sprintf 缓冲区溢出xt_runtime.c:1706err_msg[128] + temp_path[MAX_PATH] → 栈溢出
C7JSON 序列化不转义特殊字符xt_runtime.c:2053双引号/反斜杠不转义 → 格式错误的 JSON

Go 种子编译器 ​

#问题文件:行号说明
G1LLVM visitedImports 静默丢弃符号llvm.go:977重复导入返回 "0" 但不传播符号 → 链接失败
G2函数指针 bitcast 无符号表验证llvm.go:1060标识符不在符号表时直接 bitcast → 损坏的 IR
G3bitcast 签名动态构建无验证llvm.go:1327参数计数不一致时签名不匹配 → 未定义行为
G4XTResult getelementptr 偏移量错误llvm.go:2449索引 2 可能读取错误的字段
G5Eval 并发 env map 无同步evaluator.go:43+goroutine 并行写共享 env → 数据竞争
G6evalParallelExpression 数据竞争evaluator.go:1174多 goroutine 共享父 env 指针
G7evalAsyncExpression 共享 env 引用evaluator.go:1157asyncEnv 持有父 env 的 PARENT_ENV 指针

TiePM / 铁铺 ​

#问题文件:行号说明
T1发布 API 无认证main.go:81任何人可向索引注入任意包元数据
T2JSON 解析失败未捕获核心.xt:238解JSON() 结果未检查 → 运行时崩溃
T3请求体读取错误被丢弃main.go:87io.ReadAll 的 _ 吞掉错误
T4哈希失败时返回真绕过验证铁铺.xt:269certutil 失败 → 返 真 → 跳过完整性检查
T5发布 TOCTOU 竞态条件main.go:106-121loadIndex/saveIndex 之间无锁 → 包丢失
T6包名路径注入任意文件写入核心.xt:389..\..\Windows\System32 包名 → 目录遍历

渲染桥 (渲染桥.c) ​

#问题文件:行号说明
R1xt_make_float 永久内存泄漏渲染桥.c:72每帧 calloc + IMMORTAL refcount → 无限泄漏

HIGH 级别(强烈建议修复) ​

展开全部 45 个 HIGH 级别问题

C 运行时 ​

#问题位置
H1通道 send/receive 引用泄漏:发送 retain 但接收不 releasext_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
H6xt_compare 在 XT_FALSE/XT_TRUE 上段错误xt_runtime.c:1307
H7xt_math_abs INT64_MIN 未定义行为xt_runtime.c:1836

Go 种子编译器 ​

#问题位置
H8expressionCode 默认返回 "nil" 字面量compiler.go:1249
H9mapTypeAnnotation 默认返回 "i64"(静默)llvm.go:299
H10三个后端的导入路径解析不一致(evaluator/Go编译器/LLVM编译器)evaluator.go:462 / compiler.go:1343 / llvm.go:972
H11导入时 c.program 被替换/恢复,符号表被污染compiler.go:1413-1478
H12PTR/PATH 断言未检查 nilcompiler.go:539
H13ensureRawI64 对指针值做 ashr 破坏对齐llvm.go:501
H14字符串插值未闭合时返回部分解析字面量parser.go:897
H15LLVM 符号表快照/恢复机制:函数内变量泄漏到外部llvm.go:883-885
H16parseTypeAnnotation 未识别的类型静默返回空字符串parser.go:1522
H17硬编码 raylib 路径 C:/raylib/main.go:214
H18硬编码 w64devkit gcc 路径main.go:246
H19LLVM target triple 硬编码 x86_64-windows-gnullvm.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 DoSmain.go:87
H32loadIndex() 错误被忽略 → nil map panicmain.go:106
H33MkdirAll 错误被忽略 → 后续静默失败main.go:186
H34依赖解析循环检测可通过缓存绕过核心.xt:352
H35不支持 > / <= / ^ / ~> 版本约束核心.xt:100
H36空哈希完全绕过验证核心.xt:396
H37certutil 是 Windows 专有工具核心.xt:485
H38核心.xt/铁铺.xt 临时路径不一致各初始化函数
H39TOML 值中 替换(" ", "") 破坏英文文本空格核心.xt:152
H40全局可变状态无锁(依赖解析缓存等)核心.xt:279-282

渲染桥 ​

#问题位置
H41LoadTexture/LoadSound calloc 返回值未检查 NULL渲染桥.c:261
H42Unload 函数可双重释放(无引用计数保护)渲染桥.c:268
H43XTString/XTFloat 结构体布局必须精确匹配运行时渲染桥.c:28-42
H44DrawTextureEx 中 scale 参数 XT_TO_INT 截断浮点渲染桥.c:294
H45DrawRectangleRec 浮点参数精度丢失渲染桥.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 — 十六进制编码硬编码 \XX 3 字节格式假设

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 换行

隐式契约补充(新增于审计) ​

#契约持有方依赖方违反后果
IC1XTString.data 永远非 NULLxt_runtime.c所有字符串操作函数段错误
IC2XTFloat 结构体布局xt_runtime.c渲染桥.c数据损坏
IC3==/!= 不触发运算符重载编译.xt:3517用户代码_等_ 方法被忽略
IC4xt_dict_get 对缺失键返回精确整数零xt_runtime.c编译.xt:3544运算符重载回退失败
IC5解JSON() 永远返回有效字典(不抛异常)核心.xt:238铁铺 CLI运行时崩溃
IC6certutil 在所有 Windows 系统上可用核心.xt:485铁铺 CLI哈希验证静默绕过
IC7%XTObject LLVM 类型布局编译.xt:598编译.xt GEP 访问读取错误 type_id
IC8\XX 十六进制格式每字节正好 3 字符编译.xt:278编译.xt:699字符串长度计算错误
IC9raylib libraylib.a 由 w64devkit gcc 15.2 编译C:\raylibmain.go 链接步骤链接失败(符号不匹配)
IC10入口alloca列表 中的 alloca 行格式为 %"name" = alloca i64编译.xt:1669编译.xt:190去重检测失效

修复优先级建议 ​

立即修复(阻塞安全/正确性):

  1. T1+T4+T6(TiePM 认证+哈希绕过+路径注入)—— 供应链安全
  2. C5(XT_FALSE/XT_TRUE 段错误)—— 影响 20+ 运行时函数
  3. G5+G6+G7(Eval 并发数据竞争)—— 异步/并行代码不可靠
  4. 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) ​

已修复 ​

#问题修复日期备注
C5XT_FALSE/XT_TRUE 段错误05-23XT_IS_REAL_PTR(v) 宏替代 !XT_IS_INT(v) || v == XT_NULL,34 处替换
C1ftell 返回 -1 堆溢出05-23xt_file_read: 校验 fseek/ftell/malloc 返回值,用实际 fread 字节数
C2JSON 反序列化 inner[-1] 越界写05-23xt_json_deserialize: 前置校验 len>=2 + 闭合引号检测
C3xt_release old_ref==0 释放后使用05-23删除 else if (old_ref == 0) 分支
C4Arena/非Arena 混合 realloc05-23XTArray 新增 elements_in_arena 标志位,扩容时正确分发
C6sprintf 栈溢出05-23sprintf→snprintf,缓冲区扩容
C7JSON 序列化不转义05-23逐字符转义:" \ \n \r \t + 控制字符 \u00XX
T1-T6TiePM 供应链安全05-23认证+校验+TOCTOU+路径消毒
G5-G7Eval 并发数据竞争05-23cloneEnv() 深拷贝替代 extendEnv()

已知取舍(可能给未来埋坑) ​

来源取舍风险建议
C4Arena 活跃时对原本 malloc 的数组 elements 扩容,旧缓冲区泄漏arena 模式在程序启动后通常不变,实践中极少触发后续可添加 fallback free 路径
C4XTArray.elements_in_arena 加在 struct 末尾自举编译器 IR 中 %XTArray 类型定义为 6 字段,C 侧现为 7 字段。若未来编译器直接用 GEP 访问数组字段可能偏移错误目前编译器通过 C 运行时函数访问数组,不直接 GEP,安全
C7JSON 序列化转义缓冲区按最坏情况分配(length*6+3),大字符串内存消耗高10MB 字符串需要 60MB 临时缓冲区后续可改用两遍扫描(第一遍计大小,第二遍写入)
C2xt_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 / POSIX pthread
  • 固定大小线程池(默认 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_CHANNEL case 销毁 mutex

网络栈 (Phase 1/2) ​

组件文件说明
网络层runtime/xt_net.h/cWinsock2/POSIX socket 跨平台封装
HTTP GETxt_net_http_getDNS→socket→GET /path HTTP/1.1→响应体
TCP 连接xt_net_connect解析 host:port→DNS→socket→connect→返回 XTSocket
TCP 监听xt_net_listenbind+listen→独立线程 accept 循环→回调处理
Socket GCXTSocket+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_CHANNEL case

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)时暴露出四个连锁缺陷:

  1. clang 重定义错误:外函数被重复声明
  2. 运行时堆损坏:call i64 调用 void 函数后 xt_release 释放垃圾值
  3. X 按钮无响应:布尔返回类型未正确映射 i1/select
  4. 中文不显示:字体码点范围不足 + .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):

  1. 检测 lib/渲染/渲染桥.c 和 libraylib.a 是否存在
  2. 若存在,用 clang 编译渲染桥为 .o
  3. gcc 链接时追加渲染桥 .o + libraylib.a + -lopengl32 -lgdi32 -lwinmm

渲染桥修复 ​

文件:渲染桥.c

16.4.1 Bool 返回值

6 个函数从返回 XT_FROM_BOOL(...) 改为 ? 1 : 0:

  • XT_WindowShouldClose、XT_IsWindowReady
  • XT_IsKeyDown、XT_IsKeyPressed
  • XT_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 端) ​

状态:已复现,部分根因定位,未修复。

复现步骤:

  1. 在 编译.xt 末尾(} 闭合类型后)添加独立函数
  2. 独立函数体内使用 设 列表 = [] 声明数组变量
  3. go run main.go tie xuantie_compiler/玄铁.xt 编译

症状:

llvm
; 生成的 LLVM IR 中 funcAllocas 只包含参数 alloca,缺少 body 内变量 alloca
; [READ] funcAllocas len=108 content=  %"输入" = alloca i64\n  store i64 0, i64* %"输入"
; ↑ %"列表" alloca 消失了

; 后续 body 代码 load/store %"列表" → undefined value 错误

已确认的事实:

  1. emitAlloca("%\"列表\"") 被调用且写入 c.allocaOutput → debug 日志确认
  2. c.allocaSet["%\"列表\""] 在此时未被占用(无 DUPLICATE 日志)
  3. 但 funcAllocas = c.allocaOutput.String() 读取时不包含 %"列表"
  4. __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 的汉字提示)。

基于 VitePress 静态构建 · 文档内容与仓库 GUIDE/ 同源

玄铁玄铁 XUANTIE · 中文系统级编程语言MARK 玄·折 120×120 · STROKE 8 · NO CURVES