WebAssembly.LinkError()报错

1、Demo介绍

对WebAssembly的工作原理及WebAssembly代码加载、运行过程不了解的话,请先看这一篇文章:
https://cunzaizhuyi.github.io/webassembly

Demo简介

实现一个小球在页面从左滚动到右,小球的速度参数由webassembly代码控制,参数有两个,一个初始速度函数speed()
,一个是加速函数addSpeed(int speed,int pace),第二个参数传加速值。webassembly代码由C语言经emscripten编译
移植而来。C代码如下。

1
2
3
4
5
6
7
int speed(){
int a = 5;
return a;
}
int addSpeed(int sp, int pace){
return sp + pace;
}

html和js部分做一些初始化canvas和获取绘图上下文等工作,拿到C代码的速度参数后,setInterval()实现不同的小圆
显示在页面,因为将速度参数赋给了圆的圆心坐标,所以就出现小球从左到右滚动效果。

HTML代码可以如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<body>
<canvas id="canvas"></canvas>
<script>
function loadWebAssembly (path, imports = {}) {
return fetch(path)
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.compile(buffer))
.then(module => {
imports.env = imports.env || {}
// 开辟内存空间
imports.env.memoryBase = imports.env.memoryBase || 0
if (!imports.env.memory) {
imports.env.memory = new WebAssembly.Memory({ initial: 256 })
}
// 创建变量映射表
imports.env.tableBase = imports.env.tableBase || 0
if (!imports.env.table) {
// 在 MVP 版本中 element 只能是 "anyfunc"
imports.env.table = new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
}
// 创建 WebAssembly 实例
return new WebAssembly.Instance(module, imports)
})
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.strokeStyle = "blue";
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(0,40,10,0,2 * Math.PI,false);
//ctx.stroke();
ctx.fill();
var startPoint = 0;
loadWebAssembly('./mathz.wasm')
.then(instance => {
var speed = instance.exports._speed;
const addSpeed = instance.exports._addSpeed;
var speedV = speed();
//speedV = addSpeed(speedV,50);
function run(ctx) {
ctx.clearRect(0,0,1000,800);
//cxt.top+=speed;
startPoint+=speedV;
ctx.beginPath();
ctx.arc(startPoint,40,10,0,2*Math.PI,true);
ctx.stroke();
ctx.closePath();
ctx.fill();
}
setInterval(function () {
run(ctx);
},50);
});
</script>
</body>

报错位置

1
2
// 创建 WebAssembly 实例
return new WebAssembly.Instance(module, imports)

报错详情:

1
2
3
Uncaught (in promise) LinkError: WebAssembly Instantiation: Import #0 module="env" function="DYNAMICTOP_PTR" error: global import must be a number
at fetch.then.then.then.module (http://localhost:63342/Webapp/examples/canvasDemo.html?_ijt=4s8b29jkrks7jco3db94qapjae:43:12)
at <anonymous>

2、错误解决过程

1
2
3
4
5
.then(module => {
imports.env = imports.env || {}
下面添加如下代码:
imports.env.DYNAMICTOP_PTR = imports.env.DYNAMICTOP_PTR||0;

之后又有了新的报错:

1
2
3
Uncaught (in promise) LinkError: WebAssembly Instantiation: Import #1 module="env" function="tempDoublePtr" error: global import must be a number
at fetch.then.then.then.module (http://localhost:63342/Webapp/ammo.js-master/examples/canvasDemo.html?_ijt=4s8b29jkrks7jco3db94qapjae:43:12)
at <anonymous>

那么照猫画虎,又在刚才加的那行代码下面加了这样一行:

1
imports.env.tempDoublePtr = imports.env.tempDoublePtr||0;

然后又有新的报错:

1
2
3
canvasDemo.html?_ijt=4s8b29jkrks7jco3db94qapjae:43 Uncaught (in promise) LinkError: WebAssembly Instantiation: Import #2 module="env" function="ABORT" error: global import must be a number
at fetch.then.then.then.module (http://localhost:63342/Webapp/ammo.js-master/examples/canvasDemo.html?_ijt=4s8b29jkrks7jco3db94qapjae:43:12)
at <anonymous>

于是,再在代码里加一行:

1
imports.env.ABORT = imports.env.ABORT||0;

很衰,继续新的报错:

1
2
3
Uncaught (in promise) TypeError: WebAssembly Instantiation: Import #5 module="global" error: module is not an object or function
at fetch.then.then.then.module (canvasDemo.html?_ijt=4s8b29jkrks7jco3db94qapjae:43)
at <anonymous>

这一次,已经不是LinkError了,而是TypeError,于是再加一行代码:

1
imports.global = imports.global||{NaN:5,Infinity:6};

注意,5和6两个数是随便写的。然后又报了最后一个错:

1
2
3
Uncaught (in promise) LinkError: WebAssembly Instantiation: Import #7 module="env" function="abortStackOverflow" error: function import requires a callable
at fetch.then.then.then.module (http://localhost:63342/Webapp/ammo.js-master/examples/canvasDemo.html?_ijt=4s8b29jkrks7jco3db94qapjae:43:12)
at <anonymous>

又加了一行:

1
imports.env.abortStackOverflow = imports.env.abortStackOverflow||new Function();

总之,就是报了5个错,在代码里加了5行,解决了问题。把这5行拼一下(实际上代码中他们就是写在一起的),如下:

1
2
3
4
5
imports.env.DYNAMICTOP_PTR = imports.env.DYNAMICTOP_PTR||0;
imports.env.tempDoublePtr = imports.env.tempDoublePtr||0;
imports.env.ABORT = imports.env.ABORT||0;
imports.global = imports.global||{NaN:5,Infinity:6};
imports.env.abortStackOverflow = imports.env.abortStackOverflow||new Function();

Demo地址:https://github.com/cunzaizhuyi/blog-assets/tree/master/WebAssembly-LinkError
浏览器运行canvasDemo.html,运行正确,小球从左到右飞进飞出。

3、WebAssembly.LinkError()错误

WebAssembly.LinkError()错误是实例化过程中发生的错误,https://cunzaizhuyi.github.io/webassembly 这篇文章说过
webassembly可以看成es6风格的模块,所以webassembly代码的实例化就类似于new 模块名()的过程,而实例化有时候需要传参
,在webassembly中传的参数一般是一个imports对象,所有参数都包裹在该对象里面,一起传过去就好了。
LinkError错误就是经常在importObj对象没有传正确的时候发生的。

4、wast文件与排错

研究WebAssembly, 起初可能觉得wast功能比较鸡肋或者多余,但是这些发生错误后,才发现,
看懂wast代码还是有必要的(不过亲自写wast代码还是觉得没必要,难度大,而且
编写的过程中易出错,使得编码开发比较耗时)。
其实Chrome浏览器也提供了wast的查看功能,但是仍然建议安装wabt工具,这样可以随时拿一个wasm文件转成wabt,任性转。
有了看懂wast的能力,就可以猜到是哪些行报错的。本例中,就是下面这些行报错的。

1
2
3
4
5
6
//wast格式
(import "env" "DYNAMICTOP_PTR" (global (;0;) i32))
(import "env" "tempDoublePtr" (global (;1;) i32))
(import "env" "ABORT" (global (;2;) i32))
(import "global" "NaN" (global (;5;) f64))
(import "global" "Infinity" (global (;6;) f64))

5、排错过程查阅的资料

(1)https://github.com/WebAssembly/design/blob/master/JS.md
在网页搜索linkerror可以看到大部分引发此类错误的原因。

(2)https://github.com/kripken/emscripten/blob/master/src/settings.js。起初,以为是编译过程中出的错,也就是说怀疑是因为编译参数设置的不合理导致的
编译出来的wasm文件不对,于是去看了这个链接,这里是emcc -s选项的所有可设参数。

(3)https://kripken.github.io/emscripten-site/docs/tools_reference/emcc.html
emcc的所有选项,(2)只是 -s 选项,-s选项是比较重要的一个选项。