X

曜彤.手记

随记,关于互联网技术、产品与创业

吉 ICP 备10004938号

Wasm3 架构剖析


本文将剖析 Wasm3 虚拟机架构的组成结构,以及其性能优化的细节。

Wasm3 是一个高性能的 WebAssembly 解释器。在 Github 上可以看到,它比大多数其他类型的 Wasm 解释器在解释执行 Wasm 字节码的速度上快 8 倍左右;相对的,会比带有 JIT 的 Wasm 编译器慢 4-5 倍。而比单纯的 Native Code 慢 12 倍左右。从整体上来看作为一个解释器,这样的数据其实是十分不错的。

Wasm3 的两个动机

M3 (Massey Meta Machine)

Wasm3 使用 C 语言开发。其内部使用了名为 “Meta-Machine” 的执行方式,而非传统解释器的 “for-loop” 方式(大部分 overhead 来自于 branch-misprediction)。

降低字节码解码时的性能损耗

紧密的操作(Operations)序列

void *Operation_Whatever(pc_t pc, u64 * sp, u8 * mem, reg_t r0, f64 fp0);

执行结果

由于所有的 Operation 函数都具有标准化的签名,并且参数是通过尾调用方式传递到下一个函数的,因此 M3 的“虚拟”寄存器最终将直接映射到真实的物理 CPU 寄存器。

例如,这是在 X86 上编译的 M3 中的“按位或”运算。

m3`op_u64_Or_sr:
    0x1000062c0 <+0>:  movslq (%rdi), %rax             ; load operand stack offset.
    0x1000062c3 <+3>:  orq    (%rsi,%rax,8), %rcx      ; or r0 with stack operand.
    0x1000062c7 <+7>:  movq   0x8(%rdi), %rax          ; fetch next operation.
    0x1000062cb <+11>: addq   $0x10, %rdi              ; increment program counter.
    0x1000062cf <+15>: jmpq   *%rax                    ; jump to next operation.

寄存器和操作复杂性

栈的使用

传统的解释器可以保存其状态,脱离其处理循环并将程序控制返回给客户端代码。在 M3 中情况并非如此,因为 C 的堆栈可能会长时间循环缠绕。

用于其他语言的 M3 策略

Gestalt M3 解释器的工作原理与此 Wasm 版本略有不同。在 Gestalt 下,所有类型(if / else / try)的块结构都会展开堆栈。这在某种程度上降低了原始 X86 性能。

但是,这为解释器增加了一个非常棒的属性。语言源代码中块结构的词法作用域将直接映射到解释器中。所有 Opcode/Operation 最终都具有可选的序言/结尾结构。这使得诸如在 Gestalt 中实现引用计数对象之类的事情变得轻而易举。没有此属性,编译器本身将不得不跟踪作用域并故意插入减少引用的操作码。相反,“CreateObject” 操作也是退出/返回路径上的 “DestroyObject” 操作。

以下是一些伪代码:

return_t Operation_NewObject(registers...) {
  Object o = runtime->CreateObject(registers...);

  *stack[index] = o;

  return_t r = CallNextOperation(registers...);  // executes to the end of the scope/block/curly-brace & returns.

  if (o->ReferenceCount() == 0)
    runtime->DestroyObject(registers..., o);   // calls o's destructor and frees memory.

  return r;
}


这是文章底线,下面是评论
  暂无评论,欢迎勾搭 :)