这篇文章没有被放到Hello, world里面,是因为我暂且还不知道这个有什么用。
WebAssembly,是用来在网站上运行一些非JavaScript语言程序的方法。
准备开发环境
按照这篇文章里面说的做。除了那个sdk-incoming-64bit
实在是找不到,我把这个参数替换成了emscripten-master-64bit
和latest
两个参数。
过程中你就顺带着装好了一个nodeJS
和一个Clang
。我发现LLVM
这个东西的版本很奇怪,Ubuntu 18.04的默认软件仓库里面的emscripten
中版本是3.4;默认源中clang
中版本是6;LOJ上是7;我现在日常在用的是8;手机里的是9;这个sdk中版本是10,然而令人尴尬的是em++
嫌版本10太低了,它期望的是11。
C++部分
干任何事之前,请记得调用
source PATH/TO/EMSDK/emsdk_env.sh --build=Release
否则会使用你的默认编译器。
我的入门程序是参照网上的,写了一个求斐波那契数。然而我对那个$O(ans)$的实现很看不起,就写了个完全没卡过常的矩乘。
有两个注意点:一个是程序中所有不需要导出的变量和函数都要开成static
;另一个是所有要导出的变量和函数都要开成extern "C"
,因为C++默认会对函数名称进行一些修饰,使得函数重载和名字空间可以正常工作,然而C不会。
clang++
的头文件是没有bits/stdc++.h
的,你平常可以使用是因为一般clang会用系统的默认头文件,GNU/Linux的默认头文件当然是g++的了。
先放C++代码:
#include<cstring>
#include<initializer_list>
using ll=long long;
static const ll mod=998244353;
static ll ma[2][2],mb[2][2],mc[2][2];
static void po(int x){
if(!x){
mb[0][0]=mb[1][1]=1;
mb[1][0]=mb[0][1]=0;
return;
}
po(x/2);
memset(mc,0,sizeof mc);
mc[0][0]=mc[0][1]=mc[1][0]=mc[1][1]=0;
for(int i:{0,1})for(int k:{0,1})for(int j:{0,1})
(mc[i][j]+=mb[i][k]*mb[k][j])%=mod;
memcpy(mb,mc,sizeof mc);
if(x&1){
memset(mc,0,sizeof mc);
mc[0][0]=mc[0][1]=mc[1][0]=mc[1][1]=0;
for(int i:{0,1})for(int k:{0,1})for(int j:{0,1})
(mc[i][j]+=mb[i][k]*ma[k][j])%=mod;
memcpy(mb,mc,sizeof mc);
}
}
extern"C"{
int fib(int x){
ma[0][0]=0;
ma[1][1]=ma[0][1]=ma[1][0]=1;
po(x);
return mb[1][0];
}
}
这里的fib
声明是extern "C" int fib(int)
,不用long long
是因为需要额外写一个setTempRet0
函数接收返回值的高32位。
然后编译。如果生成的文件过大,这里可以把O3改成Os。
em++ 1.cpp -s WASM=1 -s SIDE_MODULE=1 -O3 -o 1.html -std=c++14
JS部分
JS的作用基本上是网页上的数据与C++函数通信的中介。这是真正痛苦的地方。网上的介绍貌似基本都不是很行。我主要是参考的这个实现。然而此实现有两个问题:一是导出的函数名前方貌似不需要下划线(应该是extern "C"
的原因?);二是没有分配栈空间。所以会出现错误:LinkError: WebAssembly.Instance(): Import #0 module="env" function="stackSave" error: function import requires a callable
。
我把这个错误扔到Google上面搜,没有一个解决方案可以帮助我。仅有的几个实现都把这些函数声明成了abort("...")
或者return 0;
,都不能解决我的问题。最后我猜到这个应该是指定的栈空间大小,就随便填了个$1000$上去,运行成功了!
// 在声明imports.env之后加上这句
imports.env.stackSave = imports.env.stackRestore = function(){return 1000;};
写完之后肯定是要测试一下效果的,不过有一个奇怪的限制是这个html不能双击打开,必须要先启动一个服务器,比如python3 -m http.server
,然后才能查看。
我就把这个实现扔到了本网站上。然后发现一旦把layout
设置成page
,就会默认显示在网站首页的右上角。我觉得很丑,就新增了一个hidden
属性。
速度测试
这个”速度测试”是WebAssembly和本地c++编译运行的对比。WebAssembly肯定比js快得多这不用说了吧。
为了测速度,继续用矩乘实现貌似不是很行。于是我写了一个简单的$O(ans)$递归,当$n=40$时直接C++编译运行用了473ms,Chrome上用了811ms,Firefox上用了732ms。