使用自主 AI 开发循环构建 Ada 编译器

最近,我一直在开发 adac,这是一个使用 Ada 编写的 Ada 编译器。

目前,该编译器只支持 Ada 2022 的一个很小的子集。它可以通过相互分离的 前端、抽象语法树、语义分析、自定义中间表示和本机后端,编译一个最小化的 过程。当前的本机后端能够为 x86-64 Linux 和 FreeBSD 生成程序。

从技术上说,这样的描述是准确的,但它遗漏了这个项目中最令我感兴趣的部分。

对我而言,adac 不仅仅是又一次构建 Ada 编译器的尝试。它同时也是一项关于 自主 AI 辅助软件开发的实验。

核心问题是:

当代码仓库本身定义了架构、工程规则、验证要求、工作选择策略以及停止条件 时,AI 智能体能否在一个系统软件项目中持续取得连贯的长期进展?

这不同于通过一个大型提示词要求 AI 生成编译器,也不同于不断接受生成的 代码,直到某些东西看起来能够运行。

目标不是取消工程纪律,而是将足够多的工程纪律编码到代码仓库中,使 AI 智能体能够在这些约束之内工作。

将代码仓库作为运行契约

大多数软件仓库包含源代码、测试,也许还有一份路线图。在 adac 中,仓库 还定义了开发工作必须以何种方式进行。

项目文档描述了架构边界、所有权规则、失败类别、支持的目标平台、验证要求 以及里程碑完成标准。

AGENTS.md 文件定义了一套自主开发循环。一个通用的实现指令大致可以启动 如下流程:

  1. 检查工作树、最近的提交、路线图和相关文档。
  2. 运行仓库的完整检查,以建立一个已知的基准状态。
  3. 找出路线图中最早尚未满足的前置条件。
  4. 选择能够推进该前置条件的最小完整工作项。
  5. 定义契约、成功标准、受影响的边界以及所需的重构。
  6. 当契约发生变化时,在实现之前先更新文档。
  7. 将变更实现为一个完整的纵向切片。
  8. 添加正向测试和负向测试。
  9. 先运行针对性测试,再运行完整验证套件。
  10. 审查完整 diff,并且只提交连贯且通过验证的变更。
  11. 重新评估路线图,然后继续下一个工作项。

当实现已经获得授权时,智能体不应在生成计划之后就停止。它应继续工作, 直到遇到文档中定义的停止条件。

这些停止条件同样是明确的。例如,当无法从现有契约中安全推导出架构决策、 破坏性操作需要批准、用户的修改与所选任务发生重叠,或者当前环境无法验证 结果时,都应停止工作。

这一点很重要,因为没有停止规则的自主开发并不是真正的自主工程。那只是不受 监督的修改。

不是“生成一个编译器”

编译器很适合作为这项实验的对象,因为它很难容忍肤浅的实现。

仅仅让解析器接受某些语法是不够的。一个语言特性可能需要经过多个彼此独立的 阶段:

source
  -> lexer
  -> parser
  -> AST
  -> semantic analysis
  -> custom IR
  -> backend
  -> executable

如果一个特性只存在于解析器中,那么它还没有完成。

它还必须得到正确表示、验证、降低到更低层表示、生成和测试。当源程序无效时, 还必须被正确拒绝。内部编译器错误必须与普通源代码诊断保持可区分性。不支持的目标 平台必须在生成误导性输出之前失败。部分构建产物不得作为成功结果发布。

这些要求使智能体更难通过彼此断开的代码制造出进展的假象。

仓库明确要求纵向功能切片。每一个实现的功能都必须贯穿其所需的全部编译器 阶段,并同时包含诊断和测试。

这条规则旨在防止 AI 生成的系统代码中一种常见的失败模式:存在大量看似 合理的组件,但它们并没有组成一个可靠的系统。

最近的工作:先做基础设施,再做功能

目前可见的 Ada 子集仍然被有意保持在较小范围。

当前,adac 可以编译一个包含 nullreturn 等简单语句的最小过程。 这远远不足以编译实用的 Ada 程序,我也不会将它介绍为一个可用的通用 编译器。

最近的开发工作反而集中在安全扩展所需的基础设施上。

这些工作包括:

  • 显式的编译上下文;
  • 由上下文拥有的诊断状态;
  • 用于语法节点、符号和语义实体的稳定标识符;
  • 附加到语法对象上的源代码范围;
  • 编译器阶段边界处的验证;
  • 有界的源代码输入;
  • 有界的 AST 节点创建;
  • 有界的驻留符号增长;
  • 对格式错误或过量输入的受控拒绝;
  • 确定性测试;
  • 用于恢复被中断自主工作的规则。

这些并不是令人兴奋的面向用户功能,也无法产生引人注目的演示程序。

但是,它们解决的是以后会变得昂贵的问题:不明确的所有权、进程级全局状态、 无限制的内存增长、无效的内部表示、意外发布的部分输出,以及在中断后无法 安全恢复的开发会话。

对于一个自主开发的系统而言,这样的基础可能比快速扩大语法覆盖范围更加 重要。

AI 智能体可以很快地添加功能。更困难的问题是,确保每一次添加之后,仓库 仍然处于另一次智能体调用可以检查、理解、验证并继续工作的状态。

资源限制也是正确性的一部分

最近的一个开发领域是显式资源限制。

编译器现在针对源代码字符数、AST 节点数以及不同驻留符号数制定了有界策略。 这些限制属于编译上下文,而不是隐藏的全局行为。

对于如此小的编译器来说,这听起来也许像是过早加固,但它有多个目的。

首先,源代码必须被视为不可信输入。格式错误或带有攻击性的源文件不应能够 无限制地扩张内部结构。

其次,资源耗尽需要一个明确的失败路径。它不应演变成分配器崩溃、无效的 编译器状态或只发布了一部分的输出文件。

第三,显式限制使行为变得可测试。测试可以故意使用很小的预算,并验证 编译器是否在正确的边界失败,同时不会破坏内部存储。

最后,资源契约为自主智能体提供了更清晰的框架。它不应在每个看似可能失败 的地方随意增加模糊的防御性检查,而必须维护一项具有明确所有权和可观察 行为的文档化策略。

人类仍然承担责任

将其称为自主开发,并不意味着人类会消失。

我仍然负责定义长期方向,决定哪些产品目标真正重要,审查架构选择,调整 仓库契约,并在必要时停止工作或改变方向。

这项实验并不是为了验证 AI 是否能够独立决定应当存在什么样的软件。

它要验证的是:在不要求人类选择并监督每一次单独修改的情况下,AI 能否 执行工程循环中有意义的一部分。

一个有用的自主智能体应当能够:

  • 检查当前状态,而不是依赖过时的对话上下文;
  • 从文档化的前置条件中选择工作;
  • 识别何时在结构上必须进行重构;
  • 同时更新契约与实现;
  • 验证完整结果;
  • 只提交连贯的变更;
  • 在中断后恢复工作;
  • 在没有权限作出决定时停止。

这比“AI 可以独自构建软件”的说法更加有限,但也更加具体、更容易验证。

为什么选择 Ada?

Ada 之所以适合这个项目,有多个原因。

它鼓励显式接口、强类型、受约束的表示,以及对正确性的谨慎推理。这些特性 不仅对编译器实现有用,在试图约束 AI 生成的变更时也同样有用。

Ada 还拥有成熟的语言规范,其社区通常十分重视精确语义、可移植性、安全性 以及长期可维护性。

因此,很难躲在一个成功的玩具示例背后。如果 adac 要成长为一个真正有用的 编译器,它需要的不只是语法识别。它还需要可靠的语义分析、经过验证的中间 表示、正确的运行时行为、定义明确的 ABI 边界,以及对多个目标平台的规范化 支持。

因此,Ada 与这项开发实验异常契合。

长期方向

长期目标是构建一个能够自举、跨平台,并拥有自身目标无关中间表示的 Ada 编译器。

路线图包括:

  • 更广泛的 Ada 2022 前端覆盖;
  • 标量类型、表达式、控制流和子程序调用;
  • 带类型的中间表示和控制流中间表示;
  • 可靠的本机后端;
  • 包和分离编译;
  • 复合类型和存储模型;
  • 异常、泛型和标签类型;
  • 分阶段自举;
  • 包括 WebAssembly 在内的额外目标平台;
  • 可选的 LLVM 后端;
  • 风格检查器和格式化工具等辅助工具。

路线图以能力为基础,而不是以日期为基础。只有当一个里程碑的契约、实现、 验证器、诊断和测试都满足文档化的退出标准时,它才算完成。

这种区别很重要。自主开发不应以制造大量提交或勾选表面功能名称为优化目标, 而应以经过验证的能力为目标。

我真正测试的是什么

现在就声称这种方法有效还为时过早。

编译器仍然很小,许多最困难的语言特性还在前方。名称解析、重载解析、包、 泛型、异常、存储管理、分离编译、ABI 细节和自举,将对架构与开发过程施加 更大的压力。

真正值得关注的失败也许并不是明显的代码生成错误。

它们可能表现为:

  • 在许多单独看起来都合理的提交中逐渐发生的架构漂移;
  • 不再与实现一致的文档;
  • 只验证示例而不验证契约的测试;
  • 损害长期可维护性的局部修复;
  • 缺乏充分证据的过度重构;
  • 为了避免较大的 diff 而进行的不充分重构;
  • 应当停止却仍然继续运行的自主循环;
  • 无法区分未完成工作与已完成工作的智能体。

这些正是我希望由仓库定义的流程暴露出来的问题。

因此,adac 同时是两个项目。

一个是 Ada 编译器。

另一个则试图探索:有多少工程判断可以被表达为仓库状态、可执行验证,以及 面向 AI 智能体的明确运行规则。

编译器将是可见的结果。

开发历史也许会成为更有趣的产物。

源代码已发布在 GitHub:

github.com/hodong-kim/adac