WebAssembly , 以编译Quickjs 为例, 有效编写编译脚本

3,661 阅读3分钟

摘要

这里就不在介绍 WebAssemblyQuickjs 是什么了,我觉得前人的介绍都特别棒.在这里主要是想和大家交流交流,怎么有效的去玩这项技术!

Safe - wasm - jsvm

仓库地址: github.com/1160007652/…

如何调用Quickjs,实现eval执行js代码.

#include <emscripten.h>
#include <string.h>
#include "../lib/quickjs-2020-03-16/quickjs.h"

#ifndef EM_PORT_API
#    if defined(__EMSCRIPTEN__)
#        if defined(__cplusplus)
#            define EM_PORT_API(rettype) extern "C" rettype EMSCRIPTEN_KEEPALIVE
#        else
#            define EM_PORT_API(rettype) rettype EMSCRIPTEN_KEEPALIVE
#        endif
#    else
#        if defined(__cplusplus)
#            define EM_PORT_API(rettype) extern "C" rettype
#        else
#            define EM_PORT_API(rettype) rettype
#        endif
#    endif
#endif

// 导出 api 供 js 调用
EM_PORT_API(const char*) eval(const char* str) {
	
	// 运行时环境
    JSRuntime* runtime = JS_NewRuntime();

	// 获取运行上下文
	JSContext* ctx = JS_NewContext(runtime);

	// 在此上下文中eval 执行js 代码
	JSValue result = JS_Eval(ctx, str, strlen(str), "<evalScript>", JS_EVAL_TYPE_GLOBAL);

	// 异常情况
	if (JS_IsException(result)) {
		JSValue realException = JS_GetException(ctx);
		return JS_ToCString(ctx, realException);
	}
	
	JSValue json = JS_JSONStringify(ctx, result, JS_UNDEFINED, JS_UNDEFINED);
	
	// 释放内存
	JS_FreeValue(ctx, result);
	
    return JS_ToCString(ctx, json);

}

wasm编译脚本

#!/bin/bash

set -e

# 编译quickjs 根目录
export QUICKJSHOME="./lib/quickjs-2020-03-16"

export inputFile="./src-wasm/main.cpp ${QUICKJSHOME}/quickjs.c ${QUICKJSHOME}/cutils.c ${QUICKJSHOME}/libregexp.c ${QUICKJSHOME}/libbf.c ${QUICKJSHOME}/libunicode.c"

# export inputFile="./src-wasm/main.cpp"


export outputFile="./public/build/main-wasm.js"

export OPTIMIZE="-DCONFIG_VERSION=\"1.0.0\" -lm -Oz -Wall -Werror --llvm-lto 1 -fno-exceptions"
export LDFLAGS="${OPTIMIZE}"
export CFLAGS="${OPTIMIZE}"
export CXXFLAGS="${OPTIMIZE}"

echo -e "\033[?25l" # 隐藏光标
echo -e "\033[44;37m ============================================= \033[0m"
echo ''
echo -e "\033[32m[ wasm ] 开始构建   \033[0m"
echo -e "\033[1A"
(
  # Compile C/C++ code
  docker run --rm -it -v `pwd`:/src apiaryio/emcc emcc \
    ${inputFile} \
    ${OPTIMIZE} \
    -s MALLOC=emmalloc \
    -s STRICT=1 \
    -s EXPORT_ES6=0 \
    -s SINGLE_FILE=1 \
    -s INLINING_LIMIT=0 \
    -s NO\_FILESYSTEM=1 \
    -s ALLOW_MEMORY_GROWTH=1 \
    -s ASSERTIONS=1 \
    -s NO_EXIT_RUNTIME=0  \
    -s WASM=1 \
    -s ENVIRONMENT="web" \
    -s EXPORT_NAME='SafeJSModule' \
    -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
    --memory-init-file 0 \
    --bind \
    -o ${outputFile}
)

#  -s MALLOC=emmalloc
#  -s FILESYSTEM=0
#  -s STRICT=1   删除对所有已弃用的构建选项的支持。这可确保您的代码以向前兼容的方式构建。
#  -s ALLOW_MEMORY_GROWTH=1   允许内存在必要时自动增长
#  -s EXPORT_ES6=1   将JavaScript代码转换为ES6模块,其默认导出适用于任何捆绑器
#  -s FILESYSTEM=0  是否启用文件系统
#  --cpuprofiler cpu 分析器
#  --memoryprofiler 内存 分析器
#  --threadprofiler 多线程 分析器

echo -e "\033[32m[ wasm ] 构建完成   \033[0m"
echo -e "\033[32m[ Path ] ${outputFile}   \033[0m"

echo -e "\033[32m[ wasm ] Gzip压缩   \033[0m"
echo -e "\033[1A"
(
  gzip -n -c ${outputFile} > ${outputFile}.gz
)
echo -e "\033[32m[ Path ] ${outputFile}.gz   \033[0m"

echo ''
echo -e "\033[44;37m ============================================= \033[0m"
echo -e "\033[?25h" # 显示光标

asm编译脚本

#!/bin/bash

set -e

# 编译quickjs 根目录
export QUICKJSHOME="./lib/quickjs-2020-03-16"

export inputFile="./src-wasm/main.cpp ${QUICKJSHOME}/quickjs.c ${QUICKJSHOME}/cutils.c ${QUICKJSHOME}/libregexp.c ${QUICKJSHOME}/libbf.c ${QUICKJSHOME}/libunicode.c"
export outputFile="./public/build/main-asm.js"

export OPTIMIZE="-DCONFIG_VERSION=\"1.0.0\" -lm -Oz -Wall -Werror --llvm-lto 1 -fno-exceptions"
export LDFLAGS="${OPTIMIZE}"
export CFLAGS="${OPTIMIZE}"
export CXXFLAGS="${OPTIMIZE}"

echo -e "\033[?25l" # 隐藏光标
echo -e "\033[44;37m ============================================= \033[0m"
echo ''
echo -e "\033[32m[ asm ] 开始构建   \033[0m"
echo -e "\033[1A"
(
  # Compile C/C++ code
  docker run --rm -it -v `pwd`:/src apiaryio/emcc emcc \
    ${inputFile} \
    ${OPTIMIZE} \
    -s MALLOC=emmalloc \
    -s EXPORT_ES6=0 \
    -s SINGLE_FILE=1 \
    -s INLINING_LIMIT=0 \
    -s NO\_FILESYSTEM=1 \
    -s ASSERTIONS=1 \
    -s NO_EXIT_RUNTIME=1  \
    -s WASM=0 \
    -s ENVIRONMENT="web" \
    -s EXPORT_NAME='SafeJSModule' \
    -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
    --memory-init-file 0 \
    --bind \
    -o ${outputFile}
)

    
#  -s MODULARIZE=1
#  -s ENVIRONMENT="web"  

#  -s STRICT=1   删除对所有已弃用的构建选项的支持。这可确保您的代码以向前兼容的方式构建。
#  -s ALLOW_MEMORY_GROWTH=1   允许内存在必要时自动增长
#  -s EXPORT_ES6=1   将JavaScript代码转换为ES6模块,其默认导出适用于任何捆绑器
#  -s FILESYSTEM=0  是否启用文件系统

echo -e "\033[32m[ asm ] 构建完成   \033[0m"
echo -e "\033[32m[ Path ] ${outputFile}   \033[0m"

echo -e "\033[32m[ asm ] Gzip压缩   \033[0m"
echo -e "\033[1A"
(
  gzip -n -c ${outputFile} > ${outputFile}.gz
)
echo -e "\033[32m[ Path ] ${outputFile}.gz   \033[0m"

echo ''
echo -e "\033[44;37m ============================================= \033[0m"
echo -e "\033[?25h" # 显示光标

如何调用脚本

因为需要启动服务,调用脚本所以初始化了一个npm 环境.

 "scripts": {
    "server": "http-server ./public -p 8088 -g --cors -o",
    "asm": "./config/asm_build.sh",
    "wasm": "./config/wasm_build.sh",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

启动server服务,测试结果

npm run server

编译quickjs 为wasm 模块

npm run wasm

编译quickjs 为asm 模块

npm run asm

优化考虑

  1. 因为有些工程,是引入了 polyfill 库的,所以在编译wasm时,去除polyfill的注入, 由调用者在外部提供运行环境.
loadScript("//cdn.bootcss.com/babel-polyfill/7.4.4/polyfill.min.js", function(){
    loadScript("../build/main-asm.js");
});
  1. 由于现在的运行环境都会启用Gzip进行压缩,所以打包的时候,直接编译出两种结果,一种是为压缩的,一种是压缩的.

编译结果

运行效果

wasm

wasm-network
wasm-sources

asm

asm-network
asm-sources

心得

1. 如何区分模块,避免与前端中的其他模块名产生冲突.

-s EXPORT_NAME='SafeJSModule'

EXPORT_NAME, 得作用就是为了编译出来的模块名

2. 如何区分编译出来的模块是wasm、还是asm?

-s WASM=1

WASM, { 1: wasm 模块,0: asm 模块 }

3. 如何指导编译出来的模块运行宿主环境.

-s ENVIRONMENT="web"

ENVIRONMENT, 得作用就是为了设置宿主环境的