【问题标题】:LinkError when trying Hello World in Rust with WebAssembly使用 WebAssembly 在 Rust 中尝试 Hello World 时出现 LinkError
【发布时间】:2018-02-25 18:53:18
【问题描述】:

我正在尝试使用 WebAssembly 运行一个用 Rust 制作的 hello world 程序,但是当我尝试加载该程序时收到一条错误消息。

按照我发现的一些教程,我能够让它运行,问题是他们使用 Emscripten 创建 JavaScript 和 HTML 来加载代码,但是这个 JavaScript 和 HTML 包含大量的样板文件和其他东西。我有点迷路了,而是想尝试获得一个我正在加载自己的非常简单的示例。

我运行以下代码来编译 hello.wasm

echo 'fn main() { println!("Hello, Emscripten!"); }' > hello.rs
rustc --target=wasm32-unknown-emscripten hello.rs

为了加载 hello.wasm,我从 Mozilla WebAssembly 文档中获取了示例并尝试运行它。

var importObject = {
  imports: {
    imported_func: function(arg) {
      console.log(arg);
    }
  }
};

fetch('hello.wasm').then(response =>
  response.arrayBuffer()
).then(bytes =>
  WebAssembly.instantiate(bytes, importObject)
).then(results => {
  // Do something with the compiled results!
});

WebAssembly.instantiate 崩溃:

LinkError: Import #0 module="env" error: module is not an object or function

我发现这个错误与缺少的东西有关,应该加载样板代码,但是通过自动生成的 HTML 和 JavaScript,我无法弄清楚它到底是什么。

【问题讨论】:

    标签: javascript rust webassembly


    【解决方案1】:

    总结

    您必须定义一组由 WASM 模块导入的函数和值。当 WASM 模块导入您未正确定义的内容时,您会收到此链接器错误。 Emscripten 生成一大堆 JS 代码,这些代码定义了 WASM 模块所需的所有导入(在这种情况下这很“简单”,因为 Emscripten 也自己生成 WASM 模块)。

    现在,您要么使用 Emscripten 运行时(JS 文件),要么必须自己做很多事情。

    我会尽量详细解释,请多多包涵:


    组装和 WASM

    Assembly机器码 的人类可读形式(但是这两个术语经常互换使用,所以我们在这篇文章中也不会关心,直接称它为组装)。汇编是为机器/ CPU 执行而设计的,因此非常简单。汇编基本上是一个指令列表,其中每条指令都执行特定的微小操作。例如,有一条指令将两个数字相加,执行不同地址的指令等等。

    值得注意的是缺少print 指令。 print 的某些功能是完全不同的抽象级别,它的功能远不止一条指令。此外,我们所说的“打印”是什么意思?我们希望我们的程序可以访问某种控制台。重复重要的部分:WASM 没有 print 指令或类似的指令!

    诸如打印之类的东西需要由环境提供。对于大多数程序和大多数计算机科学来说,这个环境只是操作系统。它管理“控制台”并让您打印。但是,WASM 程序的直接环境是浏览器!因此,浏览器必须为您提供一种打印方式。

    链接

    链接 是将来自不同模块/编译单元的导入和导出相互连接(“解析”)的过程。例如,当您在 Rust 中使用 extern crates 以及在 C++ 中编译多个 .cpp 文件时,链接是必需的。

    当你实例化一个 WASM 模块时也是必要的,因为模块可能有导入。这些导入需要在我们执行模块之前解决。

    那么你的模块有导入吗?我们来看一下!您可以使用工具wasm-dis(反汇编程序)将二进制wasm 代码转换为或多或少可读的汇编代码:$ wasm-dis hello.wasm > hello.wast。查看这个文件,我们可以看到以下内容:

    (import "env" "DYNAMICTOP_PTR" (global $import$0 i32))
    (import "env" "STACKTOP" (global $import$1 i32))
    (import "env" "STACK_MAX" (global $import$2 i32))
    (import "env" "abort" (func $import$3 (param i32)))
    ...
    (58 more)
    

    即使不知道如何阅读这种wast 格式,我们也可以做出合理的猜测并假设您的模块确实导入了东西。我们应该知道,因为我们要打印并且没有print 指令!

    (你可能想知道为什么没有(import "env" "print" ...)。我无法完全解释这一点,但原因基本上是:它比这更复杂。Emscripten 只使用了一小部分重要的导入并使用这些导入来访问环境中的其他功能。)

    与 WASM(和 Emscripten)链接

    WASM 中的链接由WebAssembly.instantiate() method 完成。正如您在链接文档中所见,此方法采用importObject。未能在此对象中定义函数/值,每次导入 WASM 模块都会导致 WebAssembly.LinkError。有道理。

    如果要实例化文件 hello.wasm 定义的 WASM 模块,则必须定义所有 62 个导入。这看起来真的很烦人,对吧?事实上,你并不真正希望这样做:这就是 Emscripten 为你生成必要的 JS 代码的原因! Emscripten 生成的 WASM 模块应该使用 Emscripten 生成的 JS-loader 加载!

    在普通程序中打印?

    值得看看在本地环境(操作系统)中运行的程序是如何进行打印的。它们肯定也需要与环境(即操作系统)相关联,对吧?并不真地。

    虽然像 Rust、C 和 C++ 这样的编程语言确实有一个用于打印的标准库,但这个标准库不是操作系统的一部分。它只使用操作系统本身。最后,为了打印,使用了系统调用。系统调用使用 CPU 中断来调用操作系统的功能。这有一些优点(例如,您不需要将程序与操作系统链接),但也有一些重要的缺点(例如,它不是很快)。

    AFAIK,这些类型的系统调用在 WASM 中是不可能的(至少目前是这样)。

    不使用 Emscripten

    编译成 WASM 需要两个主要的东西:

    1. WASM 代码生成:您的编译器必须输出 WASM 代码
    2. 链接:由于通常不止一个 crate,我们需要链接(如上所述)

    Emscripten 两者都做¹,并且可以将代码生成与链接相匹配,因为这两个部分都是由 Emscripten 完成的。有替代品吗?

    是的!您正在寻找的是 Rust 的 wasm32-unknown-unknown 目标。该目标使用 LLVM 的 WASM 后端进行代码生成。有了这个目标,你可以完全不使用 Emscripten 生成小型 WASM 模块。更重要的是:您也可以自己编写 JS-loader,因为 决定您的导入,并且没有任何神奇的添加。

    要了解有关这个令人兴奋的主题的更多信息,我建议您访问hellorust.com。在该网站上,您可以找到有关如何设置构建环境的简单示例和说明。


    ¹ Emscripten 不直接生成 WASM。它生成 asm.js 代码,然后将其转换为 WASM。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-05-17
      • 2014-11-28
      • 2012-04-03
      • 2021-09-26
      • 2015-11-19
      • 2011-06-15
      • 2020-11-28
      • 2021-07-20
      相关资源
      最近更新 更多