使用 WebAssembly 在 Rust 中尝试 Hello World 时出现 LinkError
LinkError when trying Hello World in Rust with WebAssembly
我正在尝试 运行 使用 WebAssembly 在 Rust 中制作的 hello world 程序,但是当我尝试加载该程序时收到错误消息。
我在遵循我发现的一些教程时能够得到它 运行ning,问题是他们使用 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 模块导入您未正确定义的内容时,您会收到此 linker 错误。 Emscripten 生成一大堆 JS 代码,它定义了 WASM 模块需要的所有导入(在这种情况下是 "easy",因为 Emscripten 自己也会生成 WASM 模块)。
现在,您要么使用 Emscripten 运行时(JS 文件),要么必须自己做很多事情。
我会尽量详细解释,请耐心等待:
汇编和 WASM
Assembly 是 机器代码 的人类可读形式(但这两个术语经常互换使用,所以我们不关心在此 post 中,只需将其命名为 assembly)。程序集是为 machine/the CPU 设计的,因此非常简单。汇编基本上是一个指令列表,其中每条指令执行特定的、微小的事情。例如,有一条指令将两个数相加,在不同的地址执行指令等。
值得注意的是缺少 print
指令。 print
的某些功能是完全不同的抽象级别,并且比单个指令做的要多得多。另外,"printing" 是什么意思?我们希望我们的程序可以访问某种控制台。重复重要部分:WASM 没有 print
指令或任何类似的指令!
打印之类的东西需要环境提供。对于大多数程序和大多数计算机科学来说,这个环境就是操作系统。它管理 "console" 并让您打印。然而,您的 WASM 程序的直接环境是浏览器!所以浏览器必须为您提供一种打印方式。
正在链接
链接 是将来自不同 modules/compilation 单元的 ("resolving") 导入和导出相互连接的过程。例如,当你在 Rust 中使用 extern crate
s 和在 C++ 中编译多个 .cpp
文件时,linking 是必需的。
当您实例化 WASM 模块时,这也是必需的,因为该模块可能有导入。在我们执行模块之前需要解析这些导入。
那么你的模块有导入吗?我们来看一下!您可以使用工具 wasm-dis
(反汇编程序)将二进制 wasm
代码转换为或多或少可读的汇编代码:$ wasm-dis hello.wasm > hello.wast
。查看此文件,我们可以看到以下内容:
(import "env" "DYNAMICTOP_PTR" (global $import[=10=] i32))
(import "env" "STACKTOP" (global $import i32))
(import "env" "STACK_MAX" (global $import i32))
(import "env" "abort" (func $import (param i32)))
...
(58 more)
即使不知道如何阅读这种 wast
格式,我们也可以做出合理的猜测并假设您的模块确实导入了一些东西。我们应该知道,因为我们要打印并且没有 print
指令!
(你可能想知道为什么没有 (import "env" "print" ...)
。我不能完全解释这个,但原因基本上是:它比那更复杂。Emscripten 只使用一小组重要的导入并使用这些导入访问环境中的其他功能。)
与 WASM(和 Emscripten)链接
WASM 中的链接由 WebAssembly.instantiate()
method 完成。正如您在 linked 文档中所见,此方法采用 importObject
。未能在此对象中定义 function/value,每次导入 WASM 模块都会导致 WebAssembly.LinkError
。有道理。
如果你想实例化你的文件 hello.wasm
定义的 WASM 模块,你必须定义所有 62 个导入。这看起来真的很烦人,对吧?事实上,你并不真的期望这样做:这就是为什么 Emscripten 为你生成了必要的 JS 代码! Emscripten 生成的 WASM 模块应该使用 Emscripten 生成的 JS 加载器加载!
在正常程序中打印?
值得一看程序 运行 在本机环境(操作系统)中如何进行打印。他们肯定还需要 link 适应环境(即操作系统),对吧?并不真地。
虽然像 Rust、C 和 C++ 这样的编程语言确实有一个用于打印的标准库,但这个标准库不是操作系统的一部分。它只是使用操作系统本身。最后,为了打印,使用了系统调用。系统调用使用 CPU 中断来调用操作系统的功能。这有一些优点(例如你不需要 link 你的程序与操作系统),但也有一些重要的缺点(例如它不是很快)。
据我所知,这些类型的系统调用在 WASM 中是不可能的(至少现在是这样)。
不使用 Emscripten
编译为 WASM 需要做两件事:
- WASM 代码生成:您的编译器必须吐出 WASM 代码
- 链接:因为它通常不止一个 crate,我们需要 link(如上所述)
Emscripten 两者兼而有之,并且可以将代码生成与 linking 相匹配,因为这两部分都是由 Emscripten 完成的。有其他选择吗?
是的!您正在寻找的是 Rust 的 wasm32-unknown-unknown
目标。该目标使用 LLVM 的 WASM 后端来生成代码。使用此目标,您可以完全无需 Emscripten 即可生成小型 WASM 模块。更重要的是:您也可以自己编写 JS 加载程序,因为 您 决定您的导入,并且不会神奇地添加任何内容。
要了解有关这个令人兴奋的话题的更多信息,我建议您访问 hellorust.com。在该网站上,您可以找到有关如何设置构建环境的简单示例和说明。
¹ Emscripten 不直接生成 WASM。它生成 asm.js 代码,然后将其转换为 WASM。
我正在尝试 运行 使用 WebAssembly 在 Rust 中制作的 hello world 程序,但是当我尝试加载该程序时收到错误消息。
我在遵循我发现的一些教程时能够得到它 运行ning,问题是他们使用 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 模块导入您未正确定义的内容时,您会收到此 linker 错误。 Emscripten 生成一大堆 JS 代码,它定义了 WASM 模块需要的所有导入(在这种情况下是 "easy",因为 Emscripten 自己也会生成 WASM 模块)。
现在,您要么使用 Emscripten 运行时(JS 文件),要么必须自己做很多事情。
我会尽量详细解释,请耐心等待:
汇编和 WASM
Assembly 是 机器代码 的人类可读形式(但这两个术语经常互换使用,所以我们不关心在此 post 中,只需将其命名为 assembly)。程序集是为 machine/the CPU 设计的,因此非常简单。汇编基本上是一个指令列表,其中每条指令执行特定的、微小的事情。例如,有一条指令将两个数相加,在不同的地址执行指令等。
值得注意的是缺少 print
指令。 print
的某些功能是完全不同的抽象级别,并且比单个指令做的要多得多。另外,"printing" 是什么意思?我们希望我们的程序可以访问某种控制台。重复重要部分:WASM 没有 print
指令或任何类似的指令!
打印之类的东西需要环境提供。对于大多数程序和大多数计算机科学来说,这个环境就是操作系统。它管理 "console" 并让您打印。然而,您的 WASM 程序的直接环境是浏览器!所以浏览器必须为您提供一种打印方式。
正在链接
链接 是将来自不同 modules/compilation 单元的 ("resolving") 导入和导出相互连接的过程。例如,当你在 Rust 中使用 extern crate
s 和在 C++ 中编译多个 .cpp
文件时,linking 是必需的。
当您实例化 WASM 模块时,这也是必需的,因为该模块可能有导入。在我们执行模块之前需要解析这些导入。
那么你的模块有导入吗?我们来看一下!您可以使用工具 wasm-dis
(反汇编程序)将二进制 wasm
代码转换为或多或少可读的汇编代码:$ wasm-dis hello.wasm > hello.wast
。查看此文件,我们可以看到以下内容:
(import "env" "DYNAMICTOP_PTR" (global $import[=10=] i32))
(import "env" "STACKTOP" (global $import i32))
(import "env" "STACK_MAX" (global $import i32))
(import "env" "abort" (func $import (param i32)))
...
(58 more)
即使不知道如何阅读这种 wast
格式,我们也可以做出合理的猜测并假设您的模块确实导入了一些东西。我们应该知道,因为我们要打印并且没有 print
指令!
(你可能想知道为什么没有 (import "env" "print" ...)
。我不能完全解释这个,但原因基本上是:它比那更复杂。Emscripten 只使用一小组重要的导入并使用这些导入访问环境中的其他功能。)
与 WASM(和 Emscripten)链接
WASM 中的链接由 WebAssembly.instantiate()
method 完成。正如您在 linked 文档中所见,此方法采用 importObject
。未能在此对象中定义 function/value,每次导入 WASM 模块都会导致 WebAssembly.LinkError
。有道理。
如果你想实例化你的文件 hello.wasm
定义的 WASM 模块,你必须定义所有 62 个导入。这看起来真的很烦人,对吧?事实上,你并不真的期望这样做:这就是为什么 Emscripten 为你生成了必要的 JS 代码! Emscripten 生成的 WASM 模块应该使用 Emscripten 生成的 JS 加载器加载!
在正常程序中打印?
值得一看程序 运行 在本机环境(操作系统)中如何进行打印。他们肯定还需要 link 适应环境(即操作系统),对吧?并不真地。
虽然像 Rust、C 和 C++ 这样的编程语言确实有一个用于打印的标准库,但这个标准库不是操作系统的一部分。它只是使用操作系统本身。最后,为了打印,使用了系统调用。系统调用使用 CPU 中断来调用操作系统的功能。这有一些优点(例如你不需要 link 你的程序与操作系统),但也有一些重要的缺点(例如它不是很快)。
据我所知,这些类型的系统调用在 WASM 中是不可能的(至少现在是这样)。
不使用 Emscripten
编译为 WASM 需要做两件事:
- WASM 代码生成:您的编译器必须吐出 WASM 代码
- 链接:因为它通常不止一个 crate,我们需要 link(如上所述)
Emscripten 两者兼而有之,并且可以将代码生成与 linking 相匹配,因为这两部分都是由 Emscripten 完成的。有其他选择吗?
是的!您正在寻找的是 Rust 的 wasm32-unknown-unknown
目标。该目标使用 LLVM 的 WASM 后端来生成代码。使用此目标,您可以完全无需 Emscripten 即可生成小型 WASM 模块。更重要的是:您也可以自己编写 JS 加载程序,因为 您 决定您的导入,并且不会神奇地添加任何内容。
要了解有关这个令人兴奋的话题的更多信息,我建议您访问 hellorust.com。在该网站上,您可以找到有关如何设置构建环境的简单示例和说明。
¹ Emscripten 不直接生成 WASM。它生成 asm.js 代码,然后将其转换为 WASM。