实验:以绝对坦诚作为构建系统
如今,很多人都有由 AI 构建的项目,还都称 "它能运行!" 可这评判标准是什么呢?这个实验源于观察 Bun 团队用 JavaScript 运行时进行真实世界测试套件测试的启发:别让 AI 给自己的作业打分。PHP 自带约 22000 个 `.phpt` 文件的测试套件,这是 PHP 内核团队过去三十年编写的。这些测试不是我写的,也不是 AI 写的,它们涵盖了 PHP 语言的各种刁钻角落,从 `DateTime` 的夏令时计算,到 `var_dump()` 对浮点数的具体输出。这个测试套件就是评判标准,计分板会运行所有测试,每次运行后,通过率都会 [自动更新到仓库中](https://github.com/ekinertac/Phargo/blob/master/PROGRESS.md)。目前的成绩是:22037 个测试中通过了 3844 个,占整个上游 PHP 测试套件的 17.4%。实际上限大概在 40 - 45%,因为剩下的测试套件针对 C 扩展(GD、curl、SOAP、国际化、MySQL 驱动等),不在我们范围内。我的工作流程简单得有些尴尬,AI 对整个测试集进行失败统计、修复、运行计分板等,若通过率上升就提交代码、推送,下降就再检查。
评判标准无法被收买,但测试框架却对我撒了谎
早期,通过率停滞不前,一些明显简单的测试一直失败,差异对比显示输出与预期完全相同。后来发现是回车符的问题,测试集以 CRLF 行尾格式检出,计分板逐字节比较输出,而 PHP 自己的测试运行器会对行尾进行规范化处理,我们的没有,导致测试集中几乎所有多行测试都默默地失败了好几周。只加了一行规范化代码,数百个测试瞬间就变绿了。这个教训让我们明白要检验检验方式,现在我们完全按照 `run-tests.php` 的方式进行规范化处理,此后每次出现可疑的停滞,都会先问是引擎有问题,还是计分板在说谎。
PHP 的测试套件就像一个附有说明的雷区
运行别人 22000 个文件的测试集,有些文件像炸弹,比如针对古老内存漏洞的回归测试会分配巨大的结构体,生成器测试会无限扩展等。我有惨痛经历,开发机硬重启了,因为一个生成器测试让引擎把内存占满了。这次事件让引擎变得格外谨慎,设置了全局内存分配上限、步数限制、对字符串长度等进行限制,计分板还会记录当前运行的测试名称。这些工作决定了项目是 "研究项目" 还是实用工具。
那些悄悄失效的特性
测试集总能揪出看似存在、能正常解析和运行,但实际上毫无作用的特性,几个月里揭露了很多问题,如 `clone` 求值结果为 `NULL`,`unset($arr[$key])` 不起作用等。这些问题在演示和像我这样不懂 Rust 的人代码审查时能蒙混过关,但在测试集面前无所遁形。这就是实验的核心观点,22000 个测试替我审查代码。
然后它成功运行了 WordPress 页面
我们的终极目标是让它支持 WordPress,它是 PHP 兼容性的最终挑战。让 `wp-load.php` 启动遇到了一连串阻碍,安装程序还把数据库搞坏了,是 AI 发现并修复了问题。后来 `wp_install()` 完成了,首页渲染出来了。完全坦白,全新安装可以运行,首页能从数据库中渲染出来,`/wp-admin/` 也能正常渲染,但 REST API 还未探索,且目前比真正的 PHP 慢约 55 倍,不过新的字节码虚拟机在微基准测试中已经能达到 PHP 8.5 的 1 - 3 倍速度,接下来就要攻克这个问题。
这到底意味着什么
一开始想弄清楚 AI 是否能编写语言引擎,其实真正的问题是无法验证代码的人如何确保项目的可靠性,答案是用别人编写的测试、一个只有在实际情况改变时才会变动的数字,以及无论结果好坏都将其推送到公共仓库的做法。我还是不懂 Rust,现在引擎有大约 24000 行 Rust 代码,计分板通过率能提升,说不定未来 WordPress 能在浏览器里通过编译成 WASM 的 Rust 引擎运行。关注通过率的提升:[github.com/ekinertac/Phargo](https://github.com/ekinertac/Phargo)。这篇文章也是先用大语言模型(LLM)起草,然后我再编辑,机器负责编写,我负责指明方向,这就是整个项目的精髓。