• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 使用JSVM-API接口进行WebAssembly模块相关开发
2<!--Kit: NDK Development-->
3<!--Subsystem: arkcompiler-->
4<!--Owner: @yuanxiaogou; @string_sz-->
5<!--Designer: @knightaoko-->
6<!--Tester: @test_lzz-->
7<!--Adviser: @fang-jinxu-->
8
9
10## 简介
11
12JSVM-API WebAssembly 接口提供了 WebAssembly 字节码编译、WebAssembly 函数优化、WebAssembly cache 序列化和反序列化的能力。
13权限要求:WebAssembly相关接口需要应用拥有JIT权限才能执行,可参考[JSVM 申请JIT权限指导](jsvm-apply-jit-profile.md)申请对应权限。
14运行限制:当前 JSVM 版本在坚盾守护模式下将禁用 WebAssembly 全部功能模块。开发者需针对此限制进行应用兼容性评估,具体技术规范详见[JSVM 坚盾守护模式](jsvm-secure-shield-mode.md)。
15
16## 基本概念
17
18- **wasm module**:表示一个 WebAssembly 模块,(WebAssembly 简称为wasm),通过`OH_JSVM_CompileWasmModule`可以将wasm字节码或wasm cache创建为wasm module。通过 `OH_JSVM_IsWasmModuleObject` 接口可以判断一个 JSVM_Value 是否是一个 wasm module。
19- **wasm function**:表示 wasm module 中定义的函数,wasm function 在导出后被外部代码使用。`OH_JSVM_CompileWasmFunction` 接口提供了将 wasm function 编译为优化后的机器码的能力,方便开发者对指定 wasm function 提前编译和函数粒度的并行编译。
20- **wasm cache**:对 wasm module 中的机器码进行序列化,生成的数据被称为 wasm cache。wasm cache 的创建和释放接口分别为 `OH_JSVM_CreateWasmCache` 和 `OH_JSVM_ReleaseCache` (对应的 cacheType 为 `JSVM_CACHE_TYPE_WASM`)。
21
22## 接口说明
23
24| 接口                          | 功能说明                                                                                 |
25| --------------------------- | ------------------------------------------------------------------------------------ |
26| OH_JSVM_CompileWasmModule   | 将 wasm 字节码同步编译为 wasm module。如果提供了 cache 参数,先尝试将 cache 反序列为 wasm module,反序列化失败后再执行编译。如果没有 JIT 权限支持,则打印一行日志提示开发者。 |
27| OH_JSVM_CompileWasmFunction | 将 wasm module 中指定编号的函数编译为优化后的机器码,目前只使能了最高的优化等级,函数编号的合法性由接口调用者保证。如果没有 JIT 权限支持,则打印一行日志提示开发者。                     |
28| OH_JSVM_IsWasmModuleObject  | 判断传入的值是否是wasm module。                                                             |
29| OH_JSVM_CreateWasmCache     | 将 wasm module 中的机器码序列化为 wasm cache,如果 wasm module 不包含机器码,会导致序列化失败。如果没有 JIT 权限支持,则打印一行日志提示开发者。                    |
30| OH_JSVM_ReleaseCache        | 释放由 JSVM 接口生成的 cache。传入的 cacheType 和 cacheData 必须匹配,否则会产生未定义行为。                      |
31
32## code cache 校验规格说明
33| 规格        | 规格说明                                         |
34| ---------- | ------------------------------------------------ |
35| 完整性校验  | 由用户保证                                        |
36| 兼容性校验  | 校验生成 cache 的 JSVM 版本与编译选项是否与当前一致 |
37| 一致性校验  | 由用户保证                                        |
38
39## 使用示例
40
41参考 [使用JSVM-API实现JS与C/C++语言交互开发流程](use-jsvm-process.md) 了解 JSVM-API 接口开发流程。本文仅展示接口对应的 C++ 代码。
42
43cpp 部分代码:
44
45```cpp
46// hello.cpp
47#include "napi/native_api.h"
48#include "ark_runtime/jsvm.h"
49#include <hilog/log.h>
50#include <vector>
51
52#ifndef CHECK_STATUS
53#define CHECK_STATUS(cond)                           \
54    do {                                             \
55        if (!(cond)) {                               \
56            OH_LOG_ERROR(LOG_APP, "CHECK FAILED");   \
57        }                                            \
58    } while (0)
59#endif
60
61// 判断一个 JSVM_Value 是否是 wasm module
62static bool IsWasmModuleObject(JSVM_Env env, JSVM_Value value) {
63    bool result = false;
64    JSVM_Status status = OH_JSVM_IsWasmModuleObject(env, value, &result);
65    CHECK_STATUS(status == JSVM_OK);
66    return result;
67}
68
69// 由 C 字符串创建 JSVM string
70static JSVM_Value CreateString(JSVM_Env env, const char *str) {
71    JSVM_Value jsvmStr;
72    JSVM_Status status = OH_JSVM_CreateStringUtf8(env, str, JSVM_AUTO_LENGTH, &jsvmStr);
73    CHECK_STATUS(status == JSVM_OK);
74    return jsvmStr;
75}
76
77// 由 C int32_t 创建 JSVM number
78static JSVM_Value CreateInt32(JSVM_Env env, int32_t val) {
79    JSVM_Value jsvmInt32;
80    JSVM_Status status = OH_JSVM_CreateInt32(env, val, &jsvmInt32);
81    CHECK_STATUS(status == JSVM_OK);
82    return jsvmInt32;
83}
84
85// 对 wasm module 进行实例化
86static JSVM_Value InstantiateWasmModule(JSVM_Env env, JSVM_Value wasmModule) {
87    JSVM_Status status = JSVM_OK;
88    JSVM_Value globalThis;
89    status = OH_JSVM_GetGlobal(env, &globalThis);
90    CHECK_STATUS(status == JSVM_OK);
91
92    JSVM_Value webAssembly;
93    status = OH_JSVM_GetProperty(env, globalThis, CreateString(env, "WebAssembly"), &webAssembly);
94    CHECK_STATUS(status == JSVM_OK);
95
96    JSVM_Value webAssemblyInstance;
97    status = OH_JSVM_GetProperty(env, webAssembly, CreateString(env, "Instance"), &webAssemblyInstance);
98    CHECK_STATUS(status == JSVM_OK);
99
100    JSVM_Value instance;
101    JSVM_Value argv[] = {wasmModule};
102    status = OH_JSVM_NewInstance(env, webAssemblyInstance, 1, argv, &instance);
103    CHECK_STATUS(status == JSVM_OK);
104    return instance;
105}
106
107// 获取 wasm 字节码 (add 模块)
108static std::vector<uint8_t> GetAddWasmBuffer() {
109    // 以下 wasmBuffer 对应的 wasm 字节码文本格式如下所示,只包含了一个函数 add
110    // (module
111    //   (func $add (param $lhs i32) (param $rhs i32) (result i32)
112    //     local.get $lhs
113    //     local.get $rhs
114    //     i32.add
115    //   )
116    //   (export "add" (func $add))
117    // )
118    std::vector<uint8_t> wasmBuffer = {0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01,
119                                       0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07,
120                                       0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01,
121                                       0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b};
122    return wasmBuffer;
123}
124
125// 验证 wasm instance 功能 (add 模块)
126static void VerifyAddWasmInstance(JSVM_Env env, JSVM_Value wasmInstance) {
127    JSVM_Status status = JSVM_OK;
128    // 从 wasm instance 获取 exports.add 函数
129    JSVM_Value exports;
130    status = OH_JSVM_GetProperty(env, wasmInstance, CreateString(env, "exports"), &exports);
131    CHECK_STATUS(status == JSVM_OK);
132
133    JSVM_Value add;
134    status = OH_JSVM_GetProperty(env, exports, CreateString(env, "add"), &add);
135    CHECK_STATUS(status == JSVM_OK);
136
137    // 执行 exports.add(1, 2),期望得到结果 3
138    JSVM_Value undefined;
139    OH_JSVM_GetUndefined(env, &undefined);
140    JSVM_Value one = CreateInt32(env, 1);
141    JSVM_Value two = CreateInt32(env, 2);
142    JSVM_Value argv[] = {one, two};
143    JSVM_Value result;
144    status = OH_JSVM_CallFunction(env, undefined, add, 2, argv, &result);
145    CHECK_STATUS(status == JSVM_OK);
146    int32_t resultInt32 = 0;
147    OH_JSVM_GetValueInt32(env, result, &resultInt32);
148    CHECK_STATUS(resultInt32 == 3);
149}
150
151// WebAssembly demo 主函数
152static JSVM_Value WasmDemo(JSVM_Env env, JSVM_CallbackInfo info) {
153    JSVM_Status status = JSVM_OK;
154    std::vector<uint8_t> wasmBuffer = GetAddWasmBuffer();
155    uint8_t *wasmBytecode = wasmBuffer.data();
156    size_t wasmBytecodeLength = wasmBuffer.size();
157    JSVM_Value wasmModule;
158    // 根据 wasm 字节码得到 wasm module
159    status = OH_JSVM_CompileWasmModule(env, wasmBytecode, wasmBytecodeLength, NULL, 0, NULL, &wasmModule);
160    CHECK_STATUS(status == JSVM_OK);
161    CHECK_STATUS(IsWasmModuleObject(env, wasmModule));
162
163    // 对当前 wasm module 中定义的第一个函数 (即 add) 执行编译优化
164    int32_t functionIndex = 0;
165    // 注意:当前只支持 high level optimization,即传入 JSVM_WASM_OPT_BASELINE 和传入 JSVM_WASM_OPT_HIGH 效果是一样的
166    status = OH_JSVM_CompileWasmFunction(env, wasmModule, functionIndex, JSVM_WASM_OPT_HIGH);
167    CHECK_STATUS(status == JSVM_OK);
168    // 对编译得到的 wasm module 进行实例化
169    JSVM_Value wasmInstance = InstantiateWasmModule(env, wasmModule);
170    // 对实例化的 wasm instance 中的函数进行功能验证
171    VerifyAddWasmInstance(env, wasmInstance);
172
173    // 创建 wasm cache
174    const uint8_t *wasmCacheData = NULL;
175    size_t wasmCacheLength = 0;
176    status = OH_JSVM_CreateWasmCache(env, wasmModule, &wasmCacheData, &wasmCacheLength);
177    CHECK_STATUS(status == JSVM_OK);
178    // 期望 wasm cache 创建成功
179    CHECK_STATUS(wasmCacheData != NULL);
180    CHECK_STATUS(wasmCacheLength > 0);
181
182    // 通过将 wasm cache 赋值来模拟 cache 持久化,实际使用场景可能将 wasm cache 保存到文件
183    std::vector<uint8_t> cacheBuffer(wasmCacheData, wasmCacheData + wasmCacheLength);
184
185    // cache 一旦保存完成后,需要显式释放,以免发生内存泄露
186    // 注意:传入的 JSVM_CacheType 必须匹配
187    status = OH_JSVM_ReleaseCache(env, wasmCacheData, JSVM_CACHE_TYPE_WASM);
188    CHECK_STATUS(status == JSVM_OK);
189
190    // 使用 wasm code 反序列化来生成 wasm module
191    bool cacheRejected = false;
192    JSVM_Value wasmModule2;
193    status = OH_JSVM_CompileWasmModule(env, wasmBytecode, wasmBytecodeLength, cacheBuffer.data(), cacheBuffer.size(),
194                                       &cacheRejected, &wasmModule2);
195
196    // 传入的 wasm cache 如果是匹配的,且内部校验通过 (如版本),则会接受 cache
197    CHECK_STATUS(!cacheRejected);
198    CHECK_STATUS(IsWasmModuleObject(env, wasmModule2));
199
200    // 对反序列化得到的 wasmModule2 进行同样的操作:函数编译、实例化、验证功能,期望也都是通过的
201    status = OH_JSVM_CompileWasmFunction(env, wasmModule2, functionIndex, JSVM_WASM_OPT_HIGH);
202    CHECK_STATUS(status == JSVM_OK);
203
204    JSVM_Value wasmInstance2 = InstantiateWasmModule(env, wasmModule2);
205    VerifyAddWasmInstance(env, wasmInstance2);
206
207    JSVM_Value result;
208    OH_JSVM_GetBoolean(env, true, &result);
209    OH_LOG_INFO(LOG_APP, "JSVM resultInt: %{public}d", result);
210    return result;
211}
212
213// WasmDemo 方法注册回调
214static JSVM_CallbackStruct param[] = {
215    {.data = nullptr, .callback = WasmDemo}
216};
217static JSVM_CallbackStruct *method = param;
218// 将 C++ callback WasmDemo 函数注册为 JSVM globalThis.wasmDemo 属性,供 JS 侧调用
219static JSVM_PropertyDescriptor descriptor[] = {
220    {"wasmDemo", nullptr, method++, nullptr, nullptr, nullptr, JSVM_DEFAULT},
221};
222
223// 样例测试js
224const char *srcCallNative = R"JS(wasmDemo())JS";
225```
226<!-- @[jsvm_wasm](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/JSVMAPI/JsvmUsageGuide/UsageInstructionsOne/webassembly/src/main/cpp/hello.cpp) -->
227预期输出
228```
229JSVM resultInt: 975178312
230```