Posts for: #Wasm

Wasm Internals - Overview

Wasm 的历史发展

早期(Wasm MVP - 2017)

  • 诞生背景: Wasm 的设计目标是为了替代 asm.js,提供更小、更快、更安全的 Web 二进制格式。
  • 核心模块的初步定义: MVP(Minimum Viable Product)阶段定义了 Wasm Core Module 的基本结构:函数、内存、表、导入、导出、全局变量等。
  • 主要用例: 游戏引擎、音视频编解码、计算密集型任务等。
  • 限制:
    • 没有模块化系统: 模块之间没有标准的链接机制,只能通过宿主环境(如 JavaScript)进行协调。
    • 缺乏垃圾回收(GC): 需要手动内存管理或使用语言自带的 GC 机制(如 Emscripten 的 mimalloc)。
    • 没有线程: 无法直接利用多核 CPU。
    • 没有宿主 API 标准化: 模块与宿主环境的交互方式高度依赖宿主(如浏览器),没有统一的接口定义。
    • 没有组件模型: 模块重用和组合非常困难。

中期(MVP 之后 - 持续演进)

Wasm 社区和工作组认识到 MVP 的局限性,并开始着手扩展 Wasm 的能力,这直接影响了 Core Module 的能力:

  • 多值(Multiple Returns & Parameters): 允许函数返回多个值,接收多个参数,提高表达能力。
  • 引用类型(Reference Types): 引入了 externref 和 funcref,允许 Wasm 直接引用宿主对象和函数,而无需通过数字 ID 传递,为未来的 GC 和组件模型打下基础。
  • 固定大小的 SIMD(Fixed-width SIMD): 引入了新的指令集,允许在 Wasm 中进行向量化操作,进一步提升某些计算密集型任务的性能。
  • 线程(Threads): 引入了共享内存和原子操作,允许 Wasm 模块在多线程环境下运行,极大地提升了并行计算能力。
  • 内存增长和限制(Memory Growth and Limits): 提供了更灵活的内存管理机制。
  • Tail Calls(尾调用): 优化了函数调用的性能。

近期和未来(Wasm Component Model)

这是 Wasm 发展中最重要的方向之一,旨在解决 Core Module 在模块化和互操作性方面的根本性问题:

Wasm Internals: Stack Machine

Wasm 中的“栈机”(Stack Machine),这正是其核心执行模型之一。Wasm 是一种基于栈的虚拟机,这意味着它的所有操作都通过从一个操作数栈中弹出值、执行操作并将结果压回栈中来完成。它没有传统的“寄存器”概念。

什么是栈机?

在计算机科学中,栈机是一种计算模型,其中指令操作数被隐含地从一个被称为“操作数栈”的内存区域中获取,并且结果被隐含地压回这个栈。这种模型与基于寄存器或基于累加器的模型形成对比。

Wasm 栈机的工作原理

Wasm 模块中的函数是由一系列指令组成的。这些指令会操作一个中央的操作数栈。

  1. 操作数栈(Operand Stack)

    • 这是 Wasm 执行函数时最核心的数据结构。
    • 所有的操作数(如整数、浮点数)和操作结果都临时存储在这个栈上。
    • 指令不会像在注册机中那样直接指定操作数的位置(如“将 R1 的值加到 R2”)。相反,它们会假定操作数已经在栈的顶部。
  2. 局部变量(Local Variables)

    • 除了操作数栈,每个函数调用还有一个独立的“局部变量”区域。
    • 局部变量是命名的存储位置,可以在函数的整个执行过程中被访问和修改。
    • 虽然局部变量不是栈的一部分,但有很多指令允许你将局部变量的值压入栈中,或者将栈顶部的值存储到局部变量中。
  3. 参数(Parameters)

    • 函数的参数在函数被调用时,会被初始化为局部变量的一部分(通常是前几个局部变量)。
    • 它们也可以被认为是函数执行上下文的一部分。
  4. 指令的操作

    • 压栈(Push):很多指令会将值压入栈中。例如:
      • i32.const 42:将整数常量 42 压入栈。
      • local.get <idx>:获取索引为 <idx> 的局部变量的值并压入栈。
    • 弹栈(Pop):大多数操作指令会从栈顶弹出所需数量的操作数。例如:
      • i32.add:弹出栈顶的两个 i32 整数,将它们相加,然后将结果压回栈。
      • if/else/loop 等控制流指令的条件值也会从栈中弹出。
    • 复合操作:一些指令可能弹出一个值,执行一些副作用(如内存写入),而不压入任何新值。例如:
      • i32.store:弹出内存地址和要存储的值,将值写入内存。

栈机模型的优势与特点

  1. 紧凑性(Compactness)

    • 指令更短,因为它们不需要编码操作数的位置。例如,一个加法操作,在寄存器机中可能需要指定两个源寄存器和一个目标寄存器;在栈机中,它只是一个简单的 add 指令。
    • 这有助于生成更小的二进制代码,对于 Web 环境中的快速下载和解析非常有利。
  2. 简化编译器后端(Simplified Compiler Backends)

    • IR(中间表示)到指令的映射通常更直接。许多高级语言的语义本身就可以很容易地映射到栈操作。
    • 这使得将 C/C++/Rust 等语言编译到 Wasm 变得相对容易。
  3. 易于验证(Easy to Validate)

    • Wasm 在加载时会进行严格的类型检查和结构验证。栈机模型使得验证其类型安全变得相对容易。例如,当检查 i32.add 指令时,验证器只需确保栈顶有两个 i32 类型的值。
    • 这对于沙盒环境中的安全性至关重要。
  4. 独立于目标架构(Architecture-Independent)