第六章 WebAssembly 多语言/宿主环境中的使用

826 阅读19分钟

作者:Wilson

1. 前言

WebAssembly (WASM) 的一个优势就是能够支持将不同语言编译成 WASM 代码,然后在不同的宿主环境中运行。这样就可以在不同的宿主环境中运行不同语言编写的 WASM 代码,比如在浏览器中运行 C/C++ 代码,或者在 Node.js 中运行 Rust 代码。那么,这些不同语言编写的 WASM 代码是如何运行在不同的宿主环境中的呢?在这一章中,我们将会重点介绍这些不同的语言和宿主环境中的 WASM 运行机制。

2. 环境介绍

2.1 背景介绍

在介绍不同环境使用 WASM 之前,我们需要了解一下相关的原理和工具。一般来说,WASM 和宿主机(Host)环境有两种交互方式。第一种方式是其它语言可以通过 WASM 的运行时( Runtime )调用 WASM 模块(WASM Module)通过 export 导出到 Runtime 外部的函数。第二种方式是在 WASM Module 执行的时候通过 import 调用 Host 端提供给 WASM 的 Host 函数调用。import/export 的原理如下图所示:

6-1.png

图 1. WASM 中 import/export 的原理

这部分我们仅仅做一些简单介绍,至于更多的细节,在后续章节中会有详细介绍。

为了在 WASM 模块和主机环境进行交互,用户可以通过自己实现 hostcall WASM export 的函数来实现。如果有大量的函数需要实现,这个过程会比较繁琐也极易出错。解决方法则是利用如 wit-bindgen 来自动帮用户生成相关的代码来提高效率。

wit-bindgen

wit-bindgen 是一种用于针对于 wit 的语言绑定生成器( wit 是 WebAssembly Interface Types(界面类型)的缩写)。 WebAssembly Interface Types 将提供丰富的 API 和复杂类型,能让 WASM 模组与使用其他语言撰写的 WASM 模组顺畅互通,甚至还能直接与以各程序语言原生 Runtime 执行的模块沟通,并且也让 WASM 模块能和主机系统通信。简而言之,wit 使 WASM 组件能够更好地相互协作,具有丰富的 API 和复杂的类型。

有一个网站是在线演示如何把 wit 文件生成绑定语言的代码的:bytecodealliance.github.io/wit-bindgen… 更多的信息可以从这个在线演示来了解使用效果。

6-2.png

图 2. wit-bindgen 在线演示

具体的一个使用例子可以参考:blog.mediosz.club/2022/11/17/…

waPC

wasmcloud 是一个基于 WASM 的分布式计算平台。比较有创新的地方在于,它制定了一个 waPC 标准,用于 Guest 和 Host 的安全过程调用,来解决当前 WASI 等特性不完善的问题。

“waPC” 全称为 “WebAssembly Procedure Calls”,同 RPC 协议类似,通过它我们可以在任意的运行时和 WASM 模块之间进行双向的方法过程调用。由于现阶段 WASM 标准的限制,在诸如 Wasmtime 、 Wasm3 等底层 WASM 运行时中,运行时在调用 WASM 模块中导出的方法时只能够互相传递最基本的数值(整型、浮点型)类型数据,而 waPC 作为一种协议便旨在解决此类问题。waPC 希望能够获得同 gRPC 一样的灵活性,使得在运行时宿主和 WASM 模块之间的双向通信不再需要与具体的平台耦合。只要双方都遵循该协议所规定的通信规范,便可以双向地传递任意的有效载荷数据(比如“字符串”),并以同样的方式从响应中获得返回的有效载荷。

2.2 Web 环境中的使用

WASM 正在成为一个行业标准。它是一种经过验证的,可以在 Web 中运行大型复杂应用程序的方法。2021年,苹果和微软相继推出了 Safari 和 Edge 新版本,均提供了对 WASM 的支持。鉴于 Mozilla Firefox 和Google Chrome 此前均已支持 WASM ,这使得四种主流浏览器都可以在 Web 上运行编译为 WASM 格式的代码。那为什么各个浏览器厂商都相继支持 WASM , 在 Web 端能带来什么样的好处呢?我们需要来理解下 WASM 在 Web 中解决的问题和实际的一些用例。

通常我们将 Web 平台可以看做有两个部分:

  1. 一个虚拟机( VM )用于运行 Web 应用代码,例如 JS 引擎运行 JS 代码

  2. 一系列 Web API,Web 应用可以调用这些 API 来控制 Web 浏览器/设备 的功能,来做某些事情(DOM、CSSOM、WebGL、IndexedDB、Web Audio API 等)

长期以来,VM 只能加载 JavaScript 代码运行, JavaScript 之前能满足用户的大部分需求,随着 Web 中在运行越来越复杂的应用,如 3D 游戏、VR/AR、计算机视觉、图片/视频编辑、这里遇到了各种性能问题,以及其他需要原生性能的领域。同时,下载、解析和编译大体积的 JavaScript 应用是很困难的,在一些资源更加受限的平台上,如移动设备等,则会更加放大这种性能瓶颈。随着 WASM 的出现,上述提到的 VM 现在可以加载两种类型的代码执行:JavaScript 和 WASM 。 WASM 在 Web 的使用有以下好处:

  • 在不使用插件的情况下获得接近本机的性能

  • 在浏览器的安全沙箱中执行,既保证高性能又保障安全性

  • 可以选择使用 JavaScript 之外的其他语言,如将 WASM 作为 C 和 C++ 的编译器目标,还可提供额外的语言支持。

安全和跨语言已经在其他章节覆盖,我们不再赘述,关于 JavaScript 性能提升部分,我们特别说明一下。

在浏览器中,对 JavaScript 源码进行解析,生成抽象语法树或者字节码(Parse),JIT 编译器会对生成的代码进行编译优化,当然后当发生去优化时,再去重新编译优化,最后执行。而基于 WASM 则省去了比较耗时的解析和编译的过程,是直接生成的二进制可执行机器码进行执行。

2.2.1 Web 环境中使用 WASM

在这里,我们介绍一个在 Web 环境中使用 WASM 的例子。这个例子用 C 语言实现了一个简单的加法函数,之后在浏览器的 JavaScript 环境中调用这个函数。

首先,我们需要确保我们使用的浏览器是支持 WASM 的,Firefox 52+ 和 Chrome 57+ 是默认支持 WASM 的。

我们在写 C 语言代码时,可以选择使用本地的编译环境,也可以使用在线的编译器。建立本地的编译环境相对来说比较繁琐,这里我们使用在线的编译器,可以直接在浏览器中编译 C/C++ 生成 WASM 二进制文件。

打开 mbebenita.github.io/WasmExplore… 输入我们的函数代码:

#include <stdio.h>

float add(float a, float b) {
  return a + b;
}

编译后会自动生成 wat 和 WASM 文件。对于 C++ 来说,一般会自动生成类似的函数名_Z3addff,我们后续还要使用,如下图所示,然后点击 Download。

6-3.png

图 3. 在线 WASM 编译界面

在下载 add.wasm 文件后,我们需要在 HTML 中引入这个文件,然后在 JavaScript 中调用这个函数。在和 add.wasm 同一个目录中,我们新建一个 index.html 文件,然后在 body 中引入 add.wasm 文件。具体方式是:我们使用 fetch api 拿到本地文件,然后初始化 WASM 进行函数调用。 如下所示:

<!DOCTYPE html>
   <head>
      <meta charset="utf-8">
      <title>WebAssembly Add example</title>
      <style>
         div {
            font-size : 30px; text-align : center; color:blue;
         }
      </style>
   </head>
   <body>
      <div id="textcontent"></div>
      <script>
         let sum;
         fetch("add.wasm")
            .then((response) => response.arrayBuffer())
            .then((bytes) => WebAssembly.instantiate(bytes))
            .then((results) => {
                sum = results.instance.exports._Z3addff(13, 12);
                console.log("The result of 13 + 12 = " +sum);
                document.getElementById("textcontent").innerHTML = "The result of 13 + 12 = " +sum;
         });
      </script>
   </body>
</html>

最后,在同目录中运行命令:

python3 -m http.server

打开 http://localhost:8000/ 即可看到效果。

6-4.png

图 4. 输出效果

如果需要更进一步去 debug WASM 代码,也可以使用浏览器本身自带的 debugger, 详情请见: developer.chrome.com/blog/wasm-d…

2.3 运行时直接运行 WASM

WASM 最初是设计在浏览器中运行的,随着 WASM 社区的不断发展,目前也发展出多个不同的浏览器之外的运行时,较为流行的有 Wasmtime 、 WasmEdge 、 WAMR 、 WAVM 等,不同的 WASM 运行时在使用上大同小异,这里主要选择 Wasmtime 和 WasmEdge 进行介绍。

Wasmtime

Wasmtime 是字节码联盟 (Bytecode Alliance) 使用 Rust 编写的标准化 WASM 运行时, 具有快速,安全,可配置的特点,提供了大量 WASI 接口接入实现,并提供了 Rust 、 C/C++ 、 Python 、 Go 等多种语言的接入支持。

下面演示一个基于 Rust 实现的,简单例子。在开始之前,我们需要先安装 Rust 环境,具体安装方式请见: www.rust-lang.org/tools/insta…

首先,需要添加 Rust WASM 目标支持:

rustup target add wasm32-wasi

之后,通过 Cargo 创建一个新的项目:

cargo new hello-world

创建之后,你能看到生成了一个 hello-world 目录,该目录的结构如下:

hello-world/
├── Cargo.lock
├── Cargo.toml
└── src
   └── main.rs

进入该hello-world目录,然后在 src 目录下创建一个 main.rs 文件,然后在 main.rs 文件中写入如下代码:

fn main() {
    println!("Hello, world!");
}

下一步就是编译这个项目。编译仍然是通过 Cargo 来完成的,但是需要指定编译目标为 WASM:

cargo build --target wasm32-wasi

最后我们能够发现生成的 WASM 文件在 target/wasm32-wasi/debug 目录下,文件名为 hello_world.wasm,我们可以通过 wasmtime 命令来运行该 WASM 文件:

wasmtime target/wasm32-wasi/debug/hello_world.wasm

大部分时候,我们会需要将 JIT 的文件编译为 AOT 格式的文件从而优化执行效率,这里我们可以使用 wasmtime compile 命令来完成:

wasmtime compile target/wasm32-wasi/debug/hello_world.wasm

AOT 编译后的文件会生成在 ./hello_world.cwasm 文件中,我们可以通过 wasmtime 命令来运行该 AOT 文件:

wasmtime hello_world.cwasm

WasmEdge

上例中生成的 hello_world.wasm 文件,我们也可以通过 WasmEdge 运行时来运行,WasmEdge 是一个高性能的 WASM 运行时,支持 WASI 接口,提供了多种语言的接入支持,包括 Rust 、 C/C++ 、 Python 、 Go 等。

首先,需要安装 WasmEdge 。具体安装方式请见:wasmedge.org/book/en/qui…

之后,我们可以通过 wasmedge 命令来运行 hello_world.wasm 文件:

wasmedge target/wasm32-wasi/debug/hello_world.wasm

同样的, WasmEdge 提供了对于多种格式的支持以及 AOT 编译,具体请见:wasmedge.org/book/en/cli…

2.4 嵌入式 WASM

Wams3 是一种用 C 语言编写的轻量级、可移植且高效的 WASM 运行时。它旨在用作独立运行时或嵌入到其他应用程序中,并且侧重于简单性和安全性。Wasm3 是 WASM 规范的一个实现,它被设计为小巧轻便,使其非常适合在各种上下文中使用。它具有小代码量和低内存占用,并且很容易集成到其他应用程序中。此外,Wasm3 还注重安全性,具有沙盒和内存安全等功能。

下图为 Wasm3 的页面:

6-5.png

图 5. Wasm3

Wasm3 广泛支持各种嵌入式系统,包括基于 ESP32,nRF52840 在内的硬件板。如果需要得到一个完整的支持列表,可以访问:github.com/wasm3/wasm3…

下面我们展示一个使用 Arduino 来编译使用 Wasm3 的例子。首先,需要安装 Arduino IDE ,具体安装方式请见:www.arduino.cc/en/software 。之后,我们需要在 Arduino 中安装 Wasm3 的 Arduino 库,这一步可以在 Arduino IDE 中的 工具 -> 管理库 中完成。页面如下:

6-6.png

图 6. Wasm3-Arduino 安装

安装好 Wasm3 的支持之后,我们可以在 Arduino 中编写代码来使用 Wasm3 来运行 WASM 文件。下面是一个简单的例子。完整代码在 samples/Wasm_Blink 目录中。或是访问:github.com/wasm3/wasm3…

/*
 * Wasm3 - high performance WebAssembly interpreter written in C.
 * Copyright © 2020 Volodymyr Shymanskyy, Steven Massey.
 * All rights reserved.
 */

#include <wasm3.h>
#include <m3_env.h>

/*
 * Configuration
 */

... //此部分省略

/*
 * WebAssembly app
 *
 * This is essentially a simple "Blink" sketch, compiled to WebAssembly
 * You can build a wasm binary using C/C++, Rust, AssemblyScript, TinyGo, ...
 * See https://github.com/wasm3/wasm3-arduino/tree/master/wasm_apps for details
 */

unsigned char app_wasm[] = { // 编译后的WASM代码
  0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x11, 0x04, 0x60,
  ... // 此部分省略
  0x0c, 0x00, 0x0b, 0x00, 0x0b
};
unsigned int app_wasm_len = 197;

/*
 * API bindings
 *
 * Note: each RawFunction should complete with one of these calls:
 *   m3ApiReturn(val)   - Returns a value
 *   m3ApiSuccess()     - Returns void (and no traps)
 *   m3ApiTrap(trap)    - Returns a trap
 */

m3ApiRawFunction(m3_arduino_millis)
{
    ... // 此部分省略
}

m3ApiRawFunction(m3_arduino_delay)
{
    ... // 此部分省略
}

... // 此部分省略

m3ApiRawFunction(m3_arduino_pinMode)
{
    ... // 此部分省略
}

m3ApiRawFunction(m3_arduino_digitalWrite)
{
    ... // 此部分省略
}

m3ApiRawFunction(m3_arduino_getPinLED)
{
    ... // 此部分省略
}

m3ApiRawFunction(m3_arduino_print)
{
    ... // 此部分省略
}

// 注册对应的函数调用
M3Result  LinkArduino  (IM3Runtime runtime)
{
    IM3Module module = runtime->modules;
    const char* arduino = "arduino";

    m3_LinkRawFunction (module, arduino, "millis",           "i()",    &m3_arduino_millis);
    m3_LinkRawFunction (module, arduino, "delay",            "v(i)",   &m3_arduino_delay);
    m3_LinkRawFunction (module, arduino, "pinMode",          "v(ii)",  &m3_arduino_pinMode);
    m3_LinkRawFunction (module, arduino, "digitalWrite",     "v(ii)",  &m3_arduino_digitalWrite);

    // Test functions
    m3_LinkRawFunction (module, arduino, "getPinLED",        "i()",    &m3_arduino_getPinLED);
    m3_LinkRawFunction (module, arduino, "print",            "v(*i)",  &m3_arduino_print);

    return m3Err_none;
}

/*
 * Engine start, liftoff!
 */

#define FATAL(func, msg) { Serial.print("Fatal: " func " "); Serial.println(msg); return; }

void wasm_task(void*)
{
    M3Result result = m3Err_none;

    // 创建wasm3的环境
    IM3Environment env = m3_NewEnvironment ();
    if (!env) FATAL("NewEnvironment", "failed");

    IM3Runtime runtime = m3_NewRuntime (env, WASM_STACK_SLOTS, NULL);
    if (!runtime) FATAL("NewRuntime", "failed");

#ifdef WASM_MEMORY_LIMIT
    runtime->memoryLimit = WASM_MEMORY_LIMIT;
#endif

    IM3Module module;
    result = m3_ParseModule (env, &module, app_wasm, app_wasm_len);
    if (result) FATAL("ParseModule", result);

    result = m3_LoadModule (runtime, module);
    if (result) FATAL("LoadModule", result);

    // 注册wasm3对应的host函数
    result = LinkArduino (runtime);
    if (result) FATAL("LinkArduino", result);

    // 找到主函数并执行
    IM3Function f;
    result = m3_FindFunction (&f, runtime, "_start");
    if (result) FATAL("FindFunction", result);

    Serial.println("Running WebAssembly...");

    result = m3_CallV (f);

    // Should not arrive here

    ... // 此部分省略
}

void setup()
{
    Serial.begin(115200);
    delay(100);

    // Wait for serial port to connect
    // Needed for native USB port only
    while(!Serial) {}

    Serial.println("\nWasm3 v" M3_VERSION " (" M3_ARCH "), build " __DATE__ " " __TIME__);

#ifdef ESP32
    // On ESP32, we can launch in a separate thread
    xTaskCreate(&wasm_task, "wasm3", NATIVE_STACK_SIZE, NULL, 5, NULL);
#else
    wasm_task(NULL);
#endif
}

void loop()
{
    delay(100);
}

代码编译之后就可以上传到目标板上。下图为使用 Arduino IDE 编译代码的界面。这里需要注意的是,并不是所有的 Arduino 板都支持该编译基于 Wasm3 的代码。其中一个主要的失败原因是开发板没有足够的存储空间。

6-7.png

图 7. Arduino 中上传编译好的代码

2.5 插件系统中使用 WASM

2.5.1 Envoy 中的 WASM

截至 2019 年初,Envoy 是一个静态编译的二进制文件,其所有扩展都编译在构建时。这意味着提供自定义扩展的项目(例如 Istio )必须维护和分发自己的二进制文件,而不是使用官方和未修改的 Envoy 二进制文件。

对于无法控制其部署的项目,这甚至更成问题,因为任何更新和/或对扩展的错误修复需要构建新的二进制文件,生成版本,分发它,以及更重要的是,在生产中重新部署它。

这也意味着在部署的扩展和配置它们的控制平面之间经常存在版本差异。

虽然部分问题可以使用动态可加载的 C++ 扩展来解决,但这在目前还不是一个可行的解决方案,因为,由于 Envoy 开发的速度很快,没有针对于扩展的稳定 ABI,甚至是 API,而且更新 Envoy 往往需要代码更改,这使得更新成为一个手动过程。

Envoy WASM 扩展是一种 Filter,可通过 WASM ABI 将 Envoy 内部 C++ API “翻译” 到 WASM 运行时。 目前Envoy 支持以下4种 WASM 运行时:

6-8.png

图 8. Envoy 支持的 WASM 运行时

Envoy 中支持使用 WASM 开发的 Network Filter 和 HTTP Filter。

开发 Envoy 的 WASM 插件,理论上可以采用任何开发语言。目前已有不同语言实现的 Envoy Proxy WASM SDK 可供使用,如:

  • proxy-wasm-cpp-sdk

  • proxy-wasm-rust-sdk

  • AssemblyScript

  • proxy-wasm-go-sdk

2.5.2 Proxy-WASM 的架构

使用 Proxy-WASM ,开发人员可以使用他们选择的编程语言编写代理扩展, 理想情况下,使用提供的特定于语言的库。然后将这些扩展编译为便携式 WASM 模块,并以该格式分发。

在代理端,一旦加载了 WASM 模块(直接从磁盘加载或从 xDS 上的控制平面),它经过验证是否符合定义的 Proxy-WASM 接口,并且 使用嵌入式 WASM 运行时实例化,该运行时在每个运行时中创建一个新的 WASM 虚拟机 工作线程。

对于 Envoy 的每个扩展类型,Proxy-WASM 创建了一个填充程序来转换扩展的接口 到 Proxy-WASM 调用,因此这些接口与本机(C++)Envoy 中使用的接口非常相似 扩展,拥抱事件驱动的编程模型。下图为 Proxy-WASM 整体架构:

6-9.png

图 9. Proxy-WASM 整体架构

2.5.3 简单的 WASM Proxy Filter的例子

这里我们使用一个基于 Go 语言开发的例子。使用的例子来自于:github.com/tetratelabs… 编译的过程需要使用 TinyGo 。关于 TinyGo 编译环境的设置,请参考相关的文档。在设置好之后,进入http_headers目录并执行如下命令来生成对应的http-headers.wasm文件:

tinygo build -o ./http-headers.wasm -scheduler=none -target=wasi ./main.go

接着,需要将 WASM 文件挂载到目标 Pod 的 Sidecar (即: istio-proxy )容器中。以文件的方式,创建 ConfigMap。

kubectl create cm http-headers-wasm --from-file=http-headers.wasm

然后通过修改 Deployment 并加入如下注释来将 WASM 文件挂在到目标 Pod 的 Sidecar 容器中。

sidecar.istio.io/userVolume: '[{"name":"wasmfilters-dir","configMap": {"name": "http-headers-wasm"}}]'
sidecar.istio.io/userVolumeMount: '[{"mountPath":"/var/local/lib/wasm-filters","name":"wasmfilters-dir"}]'

最后,创建 EnvoyFilter 来加载 WASM 文件。

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
    name: http-headers-filter
spec:
    configPatches:
        - applyTo: HTTP_FILTER
            match:
                context: SIDECAR_INBOUND
                proxy:
                    proxyVersion: ^1.11.*
                listener:
                    portNumber: 8080
                    filterChain:
                        filter:
                            name: envoy.filters.network.http_connection_manager
                            subFilter:
                                name: envoy.filters.http.router
        patch:
            operation: INSERT_BEFORE
            value:
                name: envoy.filters.http.wasm
                typed_config:
                    "@type": type.googleapis.com/udpa.type.v1.TypedStruct
                    type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
                    value:
                        config:
                            # root_id: add_header
                            vm_config:
                                code:
                                    local:
                                        filename: /var/local/lib/wasm-filters/http-headers.wasm
                                runtime: envoy.wasm.runtime.v8
                                # vm_id: "my_vm_id"
                                allow_precompiled: false
    workloadSelector:
        labels:
            app: go-httpbin

其中需要注意的是有以下三点:

  • proxyVersion 与 istio-proxy 版本保持一致。

  • filename 需要与前面 Deployment 中的 Annotation 保持一致。

  • workloadSelector 设置为目标 Pod 的 label。

为了检查 WASM Filter 是否生效,可以通过查看响应的 Header 来验证。

2.6 WASM 嵌入其它语言

WASM 二进制文件可以被嵌入到其它语言中。我们最常用的就是直接使用 WASM Runtime 来执行 WASM 二进制文件。而这种方式,我们常用的是 Rust 和 C/C++ 语言。对于这种情况,我们这里就不再介绍了。在这一节中,我们介绍一下如何在 Go 、 Java 和 Python 语言中使用 Wasmtime 来执行 WASM 二进制文件。

Go 语言

Wasmtime-Go 是一个在 Go 语言中载入和运行 WASM 的库。它是基于 CGO 来调用 Wasmtime 的 C API 来调用 WASM 中的函数。

安装 Wasmtime-Go:

go get -u github.com/bytecodealliance/wasmtime-go

在此 Repository 中,预编译的 Wasmtime 二进制文件已经包含。所以,用户并不需要直接安装 Wasmtime。但是,目前仅有 Linux x86_64 、 macOS x86_64 和 Windows x86_64 的支持。

这里我们介绍一个 Go 的例子。这里我们参考了[1]。首先,我们需要创建一个 Go 的项目。可以使用如下命令来创建一个 Go 项目:

mkdir hello-wasm
cd hello-wasm
go mod init hello-wasm

然后,创建一个名为main.go的文件,内容如下:

package main

import (
    "fmt"
    "github.com/bytecodealliance/wasmtime-go"
)

func main() {
    engine := wasmtime.NewEngine()
    // Almost all operations in wasmtime require a contextual `store`
    // argument to share, so create that first
    store := wasmtime.NewStore(engine)
    // Compiling modules requires WebAssembly binary input, but the wasmtime
    // package also supports converting the WebAssembly text format to the
    // binary format.
    wasm, err := wasmtime.Wat2Wasm(`
      (module
        (import "" "hello" (func $hello))
        (func (export "run")
          (call $hello))
      )
    `)
    check(err)
    // Once we have our binary `wasm` we can compile that into a `*Module`
    // which represents compiled JIT code.
    module, err := wasmtime.NewModule(engine, wasm)
    check(err)
    // Our `hello.wat` file imports one item, so we create that function
    // here.
    item := wasmtime.WrapFunc(store, func() {
        fmt.Println("Hello from Go!")
    })
    // Next up we instantiate a module which is where we link in all our
    // imports. We've got one import so we pass that in here.
    instance, err := wasmtime.NewInstance(store, module, []*wasmtime.Extern{item.AsExtern()})
    check(err)
    // After we've instantiated we can lookup our `run` function and call
    // it.
    run := instance.GetExport("run").Func()
    _, err = run.Call()
    check(err)
}

func check(e error) {
    if e != nil {
        panic(e)
    }
}

然后,运行go run main.go,可以看到输出:

Hello from Go!

Java 语言

Wasmer 支持 WebAssembly 的 Java 扩展库 Wasmer JNI。它支持JIT(及时)和AOT(提前)编译以及适合您需求的可插拔编译器。Wasmer 带有 3 个后端:Singlepass、Cranelift 与 LLVM。 Wasmer 不仅速度快,而且设计为高度可定制:

  1. 可插拔编译器:引擎使用编译器将Web汇编转换为执行代码。
  • wasmer-compiler-singlepass 提供了快速的编译时间,但未优化的运行时速度,

  • wasmer-compiler-cranelift 提供了编译时和运行时性能之间的正确平衡,对开发很有用,

  • wasmer-compiler-llvm 以最快的运行时速度提供深度优化的执行代码,非常适合生产环境。

  1. Headless 模式:一旦编译了一个 WASM 模块,就可以在一个文件中序列化它,然后在打开 Headless 模式的情况下使用 Wasmer 执行它。Headless Wasmer 没有编译器,这使得它更便携,加载速度更快。它是在受限环境中的理想选择。

  2. 交叉编译:大多数编译器都支持交叉编译。这意味着可以预先编译一个针对不同架构或平台的 WASM 模块,并对其进行序列化,然后在目标架构和平台上运行它。

  3. 在 JavaScript 环境中运行 Wasmer :使用 js Cargo 功能,可以使用 Wasmer 编译 Rust 程序到 WASM。在这种情况下,生成的 WASM 模块将期望在 JavaScript 环境中运行,如浏览器、Node.js、Deno 等。在这个特定的场景中,没有可用的引擎或编译器,它使用的是 JavaScript 环境中的引擎或编译器。

Wasmer JNI 库目前使用 Cranelift 后端。Wasmer 默认附带 Cranelift 编译器,因为它非常适合开发目的。比如执行一个 helloworld.wasm 的文件。

import org.wasmer.Instance;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

class SimpleExample { public static void main(String[] args) throws IOException {
    // 读取WASM汇编码
    byte[] bytes = Files.readAllBytes(Paths.get("helloworld.wasm"));        
    // 实例化WASM汇编模块
    Instance instance = new Instance(bytes);        
    // 测试WASM例子API
    Integer result = (Integer) instance.exports.getFunction("sum").apply(5, 37)[0];
    assert result == 42;        
    instance.close();    
    }
}

Python 语言

pywasm 是使用 Python 语言编写的 WebAssembly 解释器。它是一个纯 Python 实现,没有任何依赖项。它可以在 CPython 和 PyPy 上运行。它还可以在 CPython 上使用 C 扩展进行加速。

下面的代码展示了如何使用 pywasm 解释器执行一个简单的 WebAssembly 模块。

int sum(int a)  {
  int s = 0;
  for (int i=0; i < a; i++) {
    s += i;
  }
  return s;
}

首先,需要把上面的 C 代码编译成 WASM 模块,然后使用 pywasm 解释器执行。

clang --target=wasm32 -nostdlib -Wl,--no-entry -Wl,--export-all -o sum.wasm sum.c

之后,使用 pywasm 解释器执行 WASM 模块。

import pywasm

runtime = pywasm.load('sum.wasm')
r = runtime.exec('sum', [100])
print(r)

3. 总结

WASM 是一个比较新的技术,目前还处于发展阶段。除了在 Web 场景中使用外, WASM 也在各种其它场景中被使用。在本章中,我们介绍了一下如何在这些不同的场景中具体使用 WASM 以及不同语言调用 WASM 的方法。在未来,WASM 会被广泛应用到各种场景中,我们也会持续关注 WASM 的发展。

4. 参考文献

[1]. 《WebAssembly运行时--Wasmtime》: studygolang.com/articles/30…