Skip to content

WebAssembly 初体验

Posted on:2022.10.31

TOC

Open TOC

ref

十分钟搞懂 WebAssembly

为什么说 WASM 是 Web 的未来

Emscripten 使用入门

setup

首先需要安装 Emscripten

install emscripten

Emscripten 能够帮助我们将 C/C++ 代码编译为 WebAssembly 代码,同时帮助我们生成部分所需的 JavaScript 胶水代码

最简单的用法就是让 Emscripten 执行一段 C/C++ 代码,考虑如下一段代码

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdlib.h>
static void *f;
void open_file(const char *filename) {
int fd = open(filename, O_RDWR);
off_t size;
if (fd < 0) {
exit(1);
}
size = lseek(fd, 0, SEEK_END);
f = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (f == (void *)(-1)) {
exit(1);
}
}
int main() {
printf("Hello World!\n");
open_file("story");
printf("story[%d] = %c\n", 7, ((char *)f)[7]);
return 0;
}

使用如下的命令编译,story 为一个文本文件

emcc hello.c -o hello.html --preload-file story

其中 --preload-file 会在编译时将文件打包进 Emscripten 虚拟出的内存文件系统中,以供代码进行 I/O 操作

https://emscripten.org/docs/porting/files/packaging_files.html#packaging-using-emcc

使用 js runtime 运行上述程序

> node hello.js
Hello World!
story[7] = w

上述代码的输出会输出在 console 中

另外也可以部署生成的 html 模板文件,其中会模拟出一个 terminal 显示相同的内容

cmake

为了与之前的项目对接,使用 cmake 构建出 wasm 和 js 代码,CMakeLists.txt 如下

cmake_minimum_required(VERSION 3.24)
project(fat12-wasm-shell)
set(CMAKE_CXX_STANDARD 17)
add_executable(${PROJECT_NAME} wasm-shell.cpp ../core/core.cpp ../core/command.cpp ../utils/utils.cpp)
set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS " \
-s EXPORTED_FUNCTIONS=\"['_Init','_ExecuteQuery','_free']\" \
-s EXPORTED_RUNTIME_METHODS=\"['ccall','cwrap','allocateUTF8','UTF8ToString']\" \
--preload-file a.img \
")
add_definitions("-DWASM")

主要关注链接阶段的命令行选项

在 build 文件夹下使用如下命令构建

emcmake cmake ..
make

然后是封装好的 C/C++ 代码接口,以 Init 为例,为了避免 name mangling,需要使用 extern "C"

extern "C" {
auto Init() -> int {
parser = std::make_shared<fat12_parser>("a.img"); // hard code
return 0;
}
}

最后是 html 文件,为了能够自定义 web shell,我们需要手写 html 文件,其中通过 JavaScript 胶水代码调用封装好的 C/C++ 代码接口

此处参考了 BusTub web shell,利用 jquery.terminal 模拟 terminal

比较 tricky 的地方 JS 代码如何调用 C++ 代码

Module['onRuntimeInitialized'] = function () {
const executeQuery = Module.cwrap('ExecuteQuery', 'number', ['string', 'number', 'number'])
const initialize = Module.cwrap('Init', 'number', [])
window.executeQuery = (x) => {
const bufferSize = 64 * 1024
let output = "\0".repeat(bufferSize)
let ptrOutput = Module.allocateUTF8(output)
const retCode = executeQuery(x, ptrOutput, bufferSize)
output = Module.UTF8ToString(ptrOutput)
Module._free(ptrOutput)
return [retCode, output]
}
initialize()
}

此处使用 cwrap 将 C++ 导出函数封装为 JS 函数

var func = Module.cwrap(ident, returnType, argTypes);

参数含义如下

实际上 ExecuteQuery 的函数签名如下

auto ExecuteQuery(const char *input, char *output, [[maybe_unused]] u64 len) -> int;

deploy

考虑使用 server-static 进行部署

npm install server-static -g

编写配置文件 static-server.config.js 如下

module.exports = {
port: 4000,
entry: "index.html",
target: "http://localhost:8080",
slient: true,
};

然后键入 server-static 即可

部署在云服务器上,只需要如下文件

$ tree
.
├── fat12-wasm-shell.data
├── fat12-wasm-shell.js
├── fat12-wasm-shell.wasm
├── index.html
└── static-server.config.js

然后键入

nohup static-server > fat12-wasm-shell.log 2>&1 &

欢迎游玩 FAT12 Shell 🤣

todo