javascript - 使用WebAssembly在Rust中尝试Hello World时出现LinkError

标签 javascript rust webassembly

我正在尝试运行用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查看时,我无法确切知道它可能是什么。

最佳答案

概括

您必须定义一堆由WASM模块导入的函数和值。当WASM模块导入未正确定义的内容时,会出现此链接器错误。 Emscripten生成了一大堆JS代码,这些代码定义了WASM模块所需的所有导入(在这种情况下,这很容易,因为Emscripten还会生成WASM模块本身)。

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

我将尝试更详细地解释,请耐心等待:

组装和WASM

汇编是机器代码的人类可读形式(但是两个术语经常互换使用,因此我们也不会在本文中关注它,而将其称为汇编)。汇编是为要执行的机器/CPU设计的,因此非常简单。汇编基本上是指令列表,其中每个指令都执行特定的微小任务。例如,有一条指令将两个数字相加,在不同的地址执行指令,依此类推。

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

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

连结中

链接是将来自不同模块/编译单元的导入和导出相互连接(“解析”)的过程。例如,当您在Rust中使用extern crate以及在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一起加载!

在正常程序中打印?

值得一看的是在 native 环境(操作系统)中运行的程序如何进行打印。当然,它们也需要与环境(即操作系统)链接,对吗?并不真地。

尽管诸如Rust,C和C++之类的编程语言确实具有用于打印的标准库,但该标准库不是操作系统的一部分。它仅使用操作系统本身。最后,为了打印,使用了系统调用。 Syscall使用CPU中断来调用操作系统的功能。这有一些优点(例如,您不需要将程序链接到操作系统),但也有一些重要的缺点(例如,速度不是很快)。

AFAIK,WASM无法使用这类系统调用(至少到目前为止)。

不使用Emscripten

编译为WASM需要做两件事:
  • WASM代码生成:您的编译器必须吐出WASM代码
  • 链接:由于通常不止一个 crate ,因此我们需要链接(如上所述)

  • Emscripten既可以做到¹,又可以使代码生成与链接匹配,因为这两个部分均由Emscripten完成。有其他选择吗?

    是的!您正在寻找的是Rust的wasm32-unknown-unknown目标。该目标使用LLVM的WASM后端进行代码生成。使用此目标,您可以完全生成小型WASM模块,而无需Emscripten。更重要的是:您也可以自己编写JS-loader,因为您可以决定导入内容,而不会添加任何神奇的东西。

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

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

    关于javascript - 使用WebAssembly在Rust中尝试Hello World时出现LinkError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46258900/

    相关文章:

    javascript - 将 2 个参数在 2 个不同的复选框中传递给一个函数

    javascript - 为什么当我声明类字段时 jshint 会抛出错误?

    javascript - 如何在没有 jQuery 的情况下向 <html> 元素添加类?

    rust - 从 actix-web HttpRequest 返回一个 JsonValue 对象

    rust - "fn main() -> ! {...}"时无法运行功能模块

    go - 如何将 Go 及其 Assets 文件捆绑到 WASM 中?

    javascript - 外部 Controller 指令中 Controller 的调用函数

    docker - docker 中的 Rust actix_web 无法实现,为什么?

    go - 如何使用wasm中的<>运算符评估js值?

    rust - Rust 和 Webassemble 的加法