#bytecode #interpreter #codec #decoder #instructions #encoder #pulley

不使用 std pulley-interpreter

轮式解释器,其字节码定义、编码器、解码器等

1个不稳定版本

0.0.0 2024年7月30日

96#bytecode

Download history 104/week @ 2024-07-25 28/week @ 2024-08-01

132 每月下载次数

Apache-2.0 WITH LLVM-exception

94KB
2K SLoC

轮式

可移植、通用、低级执行策略

可移植的字节码和快速解释器

一个字节码联盟项目

build status zulip chat supported rustc stable Documentation Status

聊天

关于

轮式是Wasmtime中使用的可移植字节码和快速解释器。

轮式的主要目标是可移植性,次要目标是快速解释。

轮式不是为了成为一个简单的参考解释器,支持动态切换到即时编译代码,甚至不是世界上最快的解释器。

有关轮式的动机、目标和非目标的更多详细信息,请参阅最初提出轮式的字节码联盟RFC

状态

轮式仍然是一个非常正在进行中的项目!期待字节码的详细信息将发生变化,指令将出现和消失,API将进行全面改造。

示例

以下是轮式当前对 f(a, b) = a + b 的反汇编

       0: 11 1f f0 ff ff ff ff ff ff ff   xconst64 x31, 18446744073709551600
       a: 12 20 20 1f                     xadd32 sp, sp, x31
       e: 32 20 08 21                     store64_offset8 sp, 8, lr
      12: 30 20 22                        store64 sp, fp
      15: 0b 22 20                        xmov fp, sp
      18: 12 00 00 01                     xadd32 x0, x0, x1
      1c: 0b 20 22                        xmov sp, fp
      1f: 25 21 20 08                     load64_offset8 lr, sp, 8
      23: 22 22 20                        load64 fp, sp
      26: 0e 1f 10                        xconst8 x31, 16
      29: 12 20 20 1f                     xadd32 sp, sp, x31
      2d: 00                              ret

请注意,这里有一些可以改进的地方

  • 我们可以避免分配和释放栈帧,因为这个函数的主体没有使用任何栈槽。
  • 我们可以对常数进行符号扩展,而不是零扩展,这样 -16 就会以单字节编码而不是八字节编码。
  • 由于所有函数的整个前导和尾随指令序列在模数大小立即数上是相同的,我们可以将它们合并成超级指令。

如上所述,Pulley 非常是一个正在进行中的项目。

原则

以下是一些我们试图遵循的通用、不完整且有时相互矛盾的原则,这些原则用于设计 Pulley 字节码格式及其解释器

  • 字节码应简单且易于在软件中解码。例如,我们应该避免过于复杂的位打包,并且只有当基准测试和配置文件显示它有好处时才使用这种技术。

  • 解释器永远不会实现 enum Instruction { .. } 值。相反,它在每个操作码处理程序中按需解码立即数和操作数。这避免了构建不必要的临时存储和多次根据操作码进行分支。

  • 由于我们从不实现 enum Instruction { .. } 值,所以我们不必担心未使用的填充或一个非常大的指令使所有其他小指令的大小膨胀。简单来说:我们可以利用可变长度编码,其中一些指令只需要一个字节,而其他指令需要多个字节。这有助于保持字节码紧凑和缓存高效。

  • 我们倾向于定义超级指令(有时称为“宏操作”),这些指令在单个指令中执行多个操作的工作。我们在解释器循环的每次迭代中执行的工作越多,其开销对我们的影响就越小。此外,作为主要的 Pulley 字节码生成器,Cranelift 可以利用 ISLE 降低模式轻松识别发射超级指令的机会。

  • 通常,我们不定义子操作码。在评估任何给定指令时,应该只有一个分支,即初始操作码。例如,我们没有通用的 load 指令,后面跟着一个子操作码来区分不同的寻址模式。相反,我们有许多不同类型的 load 指令,每种寻址模式都有自己的指令。

    一个例外是常规操作和扩展操作之间的划分。常规操作是一个单个 u8 操作码,其中 255 为所有扩展操作保留,并且在 255 常规操作码之后跟一个 u16 操作码。这使最常见的指令额外小巧,并为定义无限数量的额外、较少使用的操作提供了一种压力释放阀。

  • 我们努力尽可能减少样板代码,并试图避免在整个代码库中重复匹配每个操作码。我们通过大量使用 macro_rules 来实现这一点,在更高阶宏中定义字节码,然后自动从这个定义中推导出反汇编器、解码器、编码器等。这还避免了编码器和解码器之间的任何漂移,例如。

依赖关系

~105–275KB