piccolo - 一个无栈 Lua 解释器

这是一篇关于piccolo(一种用纯 Rust 编写的 Lua 解释器)的博客文章,主要内容总结如下:

  • piccolo介绍piccolo是用纯 Rust 编写的 Lua 语言解释器,注重安全沙箱和弹性,使用gc-arena进行垃圾回收,可在交互式 REPL 中尝试。作者认为 REPL 很神奇,能让程序更像有生命的东西,与用户更亲近。
  • 历史:2019 年作者开始写piccolo,当时注意到没人能在主要用安全 Rust 实现的情况下,为某些语言创建有竞争力的虚拟机,尤其是垃圾回收问题。piccolo最初设计类似 PUC-Rio 的 Lua,带有类似的垃圾收集器,但要主要用于沙箱化不受信任的脚本,且用大部分安全的 Rust 实现。最初因遇到很多问题放弃,后因 Ruffle 使用gc-arena并解决问题而重新开始,但其仍受gc-arena“Mutation XOR Collection”设计的限制。
  • “Stackless”解释器设计piccolo的“stackless”设计是其最大亮点之一,类似其他“stackless”解释器,如 Stackless Python,Lua 运行时不依赖 Rust 函数调用栈,执行可随时暂停,核心设计与 Async Rust 相似,所有长时间运行的操作都可重新表示为需轮询完成的对象。
  • Stackless 的好处

    • 取消(Cancellation)piccolo的轮询执行风格使代码可随时取消,如在 REPL 中运行永不返回的 Lua 代码,可通过点击中断按钮停止,这与 PUC-Rio Lua 的lua_sethook功能类似,但piccolo的结构使其更自然,无需提供lua_hook等效功能。
    • 抢先式并发(Pre-emptive Concurrency):Lua 的协程提供协作式多任务,但piccolo可通过多个独立的piccolo::Executor“tasklets”和复用piccolo::Executor::step调用来实现抢先式多任务,让 Lua 代码像 Rust“任务”一样并发运行,且能保证piccolo::Executor::step在有界时间内返回,即使 Lua 代码不合作。
    • 燃料、节奏和自定义调度(Fuel, Pacing, and Custom Scheduling)piccolo中的Fuel参数用于控制Executor::step的执行时间,回调可消耗或中断燃料,这使能让 Lua 任务并发运行,并可根据需要调整任务的时间分配,如在游戏引擎中为脚本分配燃料,还可自定义调度以适应不同情况,如在热循环中使print回调立即返回。
    • “对称”协程和coroutine.yieldto:Lua 通常有“不对称”协程,只能向调用者 yield,而“对称”协程可向任何其他协程 yield。piccolo通过其Executor设计可轻松提供coroutine.yieldto函数,实现全对称协程,其实现原理与piccolo的栈无关设计相关,coroutine.yieldto的实现将控制流重新表示为Executor的结构,与之前对代码的转换类似。
  • “大谎言”:实际上 PUC-Rio Lua 已能实现piccolo约 70%的功能,piccolo的设计是 PUC-Rio Lua 工作方式的自然结论。通过在 PUC-Rio Lua 中使用lua_Hook函数插入coroutine.yield可实现简单的任务系统,但存在一些限制,如在 C 回调中调用lua_yield受限,要使 stdlib 中的所有函数都可挂起需要大量工作,包括重新实现coroutine库等。
  • Rust 协程、Lua 协程和 Snarfing:将 PUC-Rio Lua 转换为类似piccolo的系统很困难,piccolo中的Sequence trait 与Future trait 相似,其最初设计意图是让 Lua 从 Rust 中“snarf”协程,即利用 Rust 协程实现piccolo,但目前无法让 Rust 协程实现gc_arena::Collect,导致使用gc-arenapiccolo很痛苦,需要手动进行转换。
  • 展望:作者认为 Rust 的核心思想是作为其他语言的“最后一站”,适合用于不同系统的集成,但不适合所有场景。Lua 能轻松拥有多个解释器副本的特点与系统编程的理念相契合,作者希望打造一个能在 Rust 中像 PUC-Rio 在 C 中一样适配任何地方的 Lua 版本,认为栈无关解释器更适合此目标,同时提到协程是系统编程中应有的抽象,Rust 若能让协程实现Collect,将带来有趣的新能力。
阅读 6
0 条评论