Rust到wasm踩坑实例

本文共阅读

简介

听说rust支持编译wasm了,这对身为前端的rust爱好者的小生来说,简直就是一个大喜讯,屁颠屁颠的开始了测试,然而各种踩坑,不过好在最终还是有所收获。

在rust1.14版本以上,如果想要编译成wasm,那么需要首先添加交叉编译平台,

rustup target add wasm32-unknown-emscripten

然后在rustc编译时加上--target=wasm32-unknown-emscripten就可以通知rustc编译成wasm支持的格式。然而这其中确实踩了好多的坑,在此记录。

目录

  1. 关于SIDE_MODULE的误解
  2. 关于输出文件的后缀
  3. 关于返回字符串
  4. 关于具体文件大小

1.关于SIDE_MODULE的误解

在emcc中有一个编译选项叫SIDE_MODULE,可以使编译出的wasm只暴露出一系列的函数以供调用,不需要提供一个main函数来导入直接执行。这里小生产生了一个误解,以为可以像js一样,直接提供一个很小的导入库来提供一些高性能函数库。但实际测试时不对的,side_module的wasm文件必须被注入到一个拥有main函数的wasm的实例中,也就是调用compile函数时放进import参数位置。在生成wast文件阅读后发现,side_module在编译后是不会携带本地各个链接库的环境的,只有在非side_module的文件中才会携带,也就说如果你需要内存分配,需要malloc之类的库函数调用,就一定会有一个wasm是拥有main函数的,即使main函数内容为空也行。
如果出现了module="env" function="_malloc" error: function import requires a callable这类的错误提示,function部分只要是系统函数,就说明是环境问题,缺少了对应的库,其实只需要有一个非side_module的wasm,将你的报错的wasm注入进这个wasm中就可以了。这个错误困扰了笔者好久,而且这个错误还直接百度不到,就很坑。另外,如果function部分不是一个函数名,而是一堆大写英文字母,说明是一些动态指针的问题貌似,这个就需要加上-O参数开启编译优化就好了,至于是O1还是O3,这些倒是无所谓。而且测试开启编译优化后能够让200k左右的wasm文件下降到100多k左右,还是很推荐的

同时,在rust的环境中式无法将一个非side_module的rs文件编译成wasm文件的,稚只能编译成js或者html文件

2.关于输出文件的后缀

在编译的时候支持使用-o参数指定三种输出的文件名(emcc和rustc都支持),其中编译成.wasm文件时,只会输出一个单独的wasm文件,但是在rust中必须指定SIDE_MODULE才能输出成wasm文件。
.js文件,在选择输出为js时,其实为输出一个js文件和一个wasm文件还有一个.asm.js文件,当然,.asm.js文件在测试时用处不大,一般情况下只需要在html文件中加载对应的js文件就行了,js文件会自动去加载对应的wasm文件。关于这个js文件,小生确实是感觉它实在是太大了,其实必要性没有那么大。说白了,这个js文件的目的就是帮你导入wasm,帮你实例化对象,为你提供了一系列工具函数来负责管理内存以及暴露的接口函数之类的,甚至他内部有自己的cwrap函数来重包装暴露的C语言接口,类似于napi里的wrap一样,包装后可以直接获取String返回值,仿佛和原生返回String数据一般。
然而,这个库足足有200k,加上200k的wasm文件就已经有400k了,就算编译优化,两者加起来也有200k之多,确实太不划算了,里面很多函数实际上使用频率并不高。实际上可以只把一些有用的函数提取出来,需要的时候再对应定制就好了。
至于.html,不外乎就是多生成了一个html,作为一个测试页面来看的。

3.关于返回字符串

在正常来说,wasm暴露出的接口参数与返回值都只能是数值的,但是有些时候可能需要用到字符串乃至于更为复杂的数据来交流,这就得用到内存。在运行wasm时,内存会划出一片区域来供其使用,所有的内存申请都在这片内存中划分,所以设计思路便是在c++或者rust中申请一部分内存,然后存储数据,返回指针对应的地址就可以了。比如:

1
2
3
4
5
6
7
8
9
10
#include <stdlib.h>
#include <emscripten/emscripten.h>
char *p="lala";
char* EMSCRIPTEN_KEEPALIVE say() {
//p=(char *)malloc(sizeof(char)*5);
return p;
}
int main(){
return 1;
}

但是,在Rust中,返回指针会出现问题,用强制转换成裸指针后,还是不得不面临生命周期的问题,在处理了很久后还是很难找到一个非常简便的做法。在看了很多的文章后终于找到了一个还不错感觉比较丑但是行之有效的方式,就是利用rust的ffi库对返回值进行封装,返回一个可以接受的c的类型。当然,理论上也可以在前端使用cwrap工具函数包装,来减少自己去查找内存的麻烦。参考这个大神的文章

4.关于具体文件大小

不知道是不是因为大多数主机没有rust标准库的原因,编译出的wasm始终是比c方式编译出的wasm要大一些,在不加编译优化参数时,甚至都快比c方式编译出的打出一倍左右了。

5.待续

本文链接: http://www.yixuankeer.win/2017/06/18/Rust到wasm踩坑实例/
ps:文章评论需翻墙可见