earendil 发布 djs:用 Rust 写的 JavaScript/TypeScript 引擎,替代 QuickJS 运行 pi codemode 脚本
来源标题:Djs: A JavaScript and ts engine for pi's codemode by earendil
earendil 发布 djs,一个用 Rust 编写的小型 JavaScript/TypeScript 引擎,专门在 WebAssembly 沙箱中运行语言模型在 pi codemode 里生成的脚本,取代 QuickJS。其设计围绕 codemode 脚本短小、调用慢速工具、数据量大且需随时暂停恢复的特点:状态存放在单一可复制的字节数组中,JavaScript 调用不占用 Rust 栈,宿主可直接从内存读取值,源码一次编译为字节码。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
djs 是一个用 Rust 编写的小型 JavaScript 和 TypeScript 引擎。它只为一件事而存在:在 WebAssembly 沙箱中运行语言模型在 pi 的 codemode 里编写的脚本,替代 QuickJS。本指南解释它如何工作,更重要的是,它为何被设计成这样。djs 中几乎所有不寻常的决策都源于这一个任务,所以本页从这里开始。
简而言之。codemode 脚本很短,调用慢速工具,移动大量数据,并且必须能在任意时刻停止和恢复。因此,djs 将所有状态保存在一个可复制的字节数组中,从不用 Rust 栈执行 JavaScript 调用,让宿主直接从内存中读取值,并且一趟将源码编译为字节码。
模型不是逐个调用工具,而是编写一个小程序来调用它们、合并结果并返回想要展示的内容:
// 宿主提供的工具的替身;在 pi 中它是 tools.search。
const search = async (q) => [
{ name: "djs", stars: 120, lang: "Rust" },
{ name: "quickjs", stars: 9000, lang: "C" },
];
const hits = await search("small js engines");
const rust = hits.filter((h) => h.lang === "Rust");
console.log(${rust.length} of ${hits.length} in Rust:, rust.map((h) => h.name).join(", "));
return rust;
输出字节码堆栈与快照
1 of 2 in Rust: djs
function 0 <anonymous>: params=2 locals=4 stack=6 captures=0
handler [3, 94) -> 94 depth 0
0 async_start loc0 (<state>)
3 fclosure child0
8 set_loc1 (search) ; 6:20
9 undefined
10 push_const8 #0 ("small js engines")
12 call1 ; 6:26
13 await loc0 (<state>) ; 6:14
16 set_loc2 (hits) ; 7:14
17 get_method "filter" ic0 ; 7:18
24 fclosure child1
29 call1 ; 7:25
30 put_loc3 (rust)
31 get_arg1 ; 8:1
32 get_method "log" ic1 ; 8:8
39 get_loc3 (rust) ; 8:16
40 get_field "length" ic2 ; 8:20
47 to_string ; 8:14
48 push_const8 #1 (" of ")
50 get_loc2 (hits) ; 8:34
51 get_field "length" ic3 ; 8:38
58 to_string ; 8:32
59 push_const8 #2 (" in Rust:")
61 concat 4
64 get_loc3 (rust) ; 8:58
65 get_method "map" ic4 ; 8:62
72 fclosure child2
77 call1 ; 8:66
78 get_method "join" ic5 ; 8:81
85 push_const8 #3 (", ")
87 call1 ; 8:86
88 call2 ; 8:12
89 drop
90 get_loc3 (rust) ; 9:8
91 async_return loc0 (<state>)
94 async_reject loc0 (<state>)
function 0.0 search: params=1 locals=1 stack=3 captures=0
handler [3, 68) -> 68 depth 0
0 async_start loc0 (<state>)
3 object
4 push_const8 #0 ("djs")
6 define_field "name" ic0
13 push_i8 120
15 define_field "stars" ic1
22 push_const8 #1 ("Rust")
24 define_field "lang" ic2
31 object
32 push_const8 #2 ("quickjs")
34 define_field "name" ic3
41 push_i32 9000
46 define_field "stars" ic4
53 push_const8 #3 ("C")
55 define_field "lang" ic5
62 array_from 2
65 async_return loc0 (<state>)
68 async_reject loc0 (<state>)
function 0.1 <anonymous>: params=1 locals=0 stack=2 captures=0
0 get_arg0 ; 7:33
1 get_field "lang" ic0 ; 7:34
8 push_const8 #0 ("Rust")
10 strict_eq ; 7:40
11 return
function 0.2 <anonymous>: params=1 locals=0 stack=1 captures=0
0 get_arg0 ; 8:74
1 get_field "name" ic0 ; 8:75
8 return
运行示例以遍历 VM 的 arena。
运行示例以查看它挂起并生成快照。
这类脚本有一个特定的特征,而它的每一部分都成为了设计目标:
这些目标按优先级排序;下一章《djs 为哪些目标优化》会逐一解释。还有两个约束塑造了一切:wasm32 才是真正重要的目标(内存和速度以 V8 的 wasm 层级来评判,D016),并且行为要与 QuickJS 一致,连错误消息都一致,因为模型会阅读这些消息,而 pi 的测试也期望如此。
djs 的各个部分及其共享的 arena 宿主(JavaScript) pi 的 codemode,一个 Sandbox 脚本源码 JS 或 TypeScript 工具函数 稍后应答 线路编码器 值输入 堆读取器 值输出,就地读取 djs-core:Rust,no_std,运行在 wasm 中 编译器 token 到字节码 解释器 燃料计数,无递归 复制式 GC 压缩 arena 待处理的宿主调用 和收件箱 函数模板 字节码、常量 值栈、帧 每次调用 realm、内建对象 基础快照 任务队列 promise 反应 对象、字符串 形状、绳索、数组 原子 属性键 句柄 宿主持有的值 arena:一个字节数组,包含所有 VM 状态,通过 u32 偏移链接 快照就是这些字节的副本 调用 结果 读取 djs 的各个部分及其共享的 arena djs 始终处于严格模式,没有 ES 模块,没有 Intl,也没有 JIT,还有其它一些有意省略的东西;非目标章节列出了它们以及原因。
导览从底层开始:arena 与值、单元与对象、编译器、解释器、挂起、收集器、快照、内建对象、确定性以及宿主接口。每一章都解释其权衡:该设计付出什么代价,以及哪些替代方案未被采纳。各章在关键之处解释每个决策;决策的 ID(D1050)链接到决策日志中的条目,该日志是构建 djs 期间保存的记录,包含完整推理及其背后的测量数据。
标记为 live 的代码示例会在文档站点的浏览器中于 djs-wasm 里运行。每个示例下方显示的输出是在检查本指南时用此构建的 djs 运行该示例所产生的,因此它不会偏离 djs 的实际行为。
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。