我正试图用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
Assembly是机器代码的人类可读形式(但这两个术语经常可互换使用,所以我们在这篇文章中也不在乎,只将其称为Assembly)。程序集是为机器/CPU执行而设计的,因此它非常简单。汇编基本上是一个指令列表,每个指令都做一件特定的、微小的事情。例如,有一条指令将两个数字相加,在不同的地址执行指令,等等
值得注意的是缺少一条print
指令。print
的某些功能是一个完全不同的抽象级别,它所做的远不止一条指令。此外,我们所说的"印刷"是什么意思?我们希望我们的程序能够访问某种控制台。重复重要部分:WASM没有print
指令或任何类似的指令
打印之类的东西需要由环境提供。对于大多数程序和大多数计算机科学来说,这种环境只是操作系统。它管理"控制台",让你打印。然而,WASM程序的直接环境是浏览器!因此,浏览器必须为您提供一种打印方式。
链接
链接是将不同模块/编译单元的导入和导出相互连接("解析")的过程。例如,当您在Rust中使用extern crate
s以及在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()
方法完成的。正如您在链接的文档中看到的,此方法采用importObject
。未能在此对象中定义函数/值(WASM模块的每个导入一个函数/值),将导致WebAssembly.LinkError
。有道理。
如果要实例化文件hello.wasm
定义的WASM模块,则必须定义所有62个导入。这看起来真的很烦人,对吧?事实上,你并没有真正被期望这样做:这就是为什么Emscripten为你生成了必要的JS代码Emscripten生成的WASM模块应该用Emscripten产生的JS加载程序加载
在正常程序中打印
值得一看的是,在本机环境(操作系统)中运行的程序是如何进行打印的。它们肯定也需要与环境(即操作系统)联系起来,对吧?不是。
虽然像Rust、C和C++这样的编程语言确实有一个用于打印的标准库,但这个标准库不是操作系统的一部分。它只是使用操作系统本身。最后,为了打印,使用了一个系统调用。系统调用使用CPU中断来调用操作系统的一个函数。这有一些优点(例如,您不需要将程序与操作系统链接),但也有一些重要的缺点(例如,速度不是很快)。
AFAIK,这些类型的系统调用在WASM中是不可能的(至少到目前为止)。
不使用Emscripten
编译到WASM需要两件主要的事情:
- WASM代码生成:编译器必须吐出WASM代码
- 链接:由于它通常是多个板条箱,我们需要链接(如上所述)
Emscripten同时执行这两个任务,并且可以将代码生成与链接相匹配,因为这两个部分都是由Emscripten完成的。有其他选择吗?
是的!您正在寻找的是Rust的wasm32-unknown-unknown
目标。这个目标使用LLVM的WASM后端来生成代码。有了这个目标,您就可以在完全没有Emscripten的情况下生成小型WASM模块。更重要的是:您也可以自己编写JS加载程序,因为您决定导入,并且不会神奇地添加任何内容。
要了解更多关于这个令人兴奋的主题的信息,我建议您访问helloust.com。在该网站上,您可以找到关于如何设置构建环境的简单示例和说明。
ČEmscripten不会直接生成WASM。它生成asm.js代码,然后将其转换为WASM。