# Working with Wasm Using JSVM-API
## Introduction
JSVM-API provides APIs for compiling the WebAssembly (Wasm) bytecode, optimizing Wasm functions, and serializing and deserializing Wasm caches.
> **NOTE**
To run the Wasm bytecode, the application must have the JIT permission. For details about how to apply for the permission, see [Requesting the JIT Permission](jsvm-apply-jit-profile.md).
## Basic Concepts
- Wasm module: a binary format that contains compiled Wasm code. You can use **OH_JSVM_CompileWasmModule** to create a Wasm module from Wasm bytecode or Wasm cache, and use **OH_JSVM_IsWasmModuleObject** to check whether a **JSVM_Value** is a Wasm module.
- Wasm function: a function defined in a Wasm module. The functions in a Wasm module can be used by external code after being imported. You can use **OH_JSVM_CompileWasmFunction** to convert Wasm bytecode into the format that JSVM can execute efficiently.
- Wasm cache: data generated by serializing the bytecode in a Wasm module. The cache holds the compiled Wasm code so that it can be reused, eliminating the need for recompiling the code. You can use **OH_JSVM_CreateWasmCache** (with **cacheType** set to **JSVM_CACHE_TYPE_WASM**) to create a Wasm cache instance and use **OH_JSVM_ReleaseCache** to release it.
## Available APIs
| API | Description |
| --------------------------- | ------------------------------------------------------------------------------------ |
| OH_JSVM_CompileWasmModule | Compiles the Wasm bytecode into a Wasm module. If the **cache** parameter is passed in, the cache will be deserialized into a Wasm module first. The compilation is performed when the deserialization fails.|
| OH_JSVM_CompileWasmFunction | Compiles the function with the specified ID in a Wasm module into the optimized machine code. Currently, only the highest optimization level is enabled. The validity of the function ID is ensured by the caller. |
| OH_JSVM_IsWasmModuleObject | Checks whether the input value is a Wasm module. |
| OH_JSVM_CreateWasmCache | Serializes the machine code in a Wasm module into a Wasm cache. If the Wasm module does not contain machine code, the serialization will fail. |
| OH_JSVM_ReleaseCache | Releases a Wasm cache instance created by JSVM-API. The **cacheType** and **cacheData** passed in must match. Otherwise, undefined behavior may occur. |
## Code Cache Verification Specifications
| Specification | Description |
| ---------- | ------------------------------------------------ |
| Integrity verification | Executed by the developer. |
| Compatibility verification | Checks whether the JSVM version and compilation options of the generated cache are the same as the current one.|
| Consistency verification | Executed by the developer. |
## Example
If you are just starting out with JSVM-API, see [JSVM-API Development Process](use-jsvm-process.md). The following demonstrates only the C++ code involved in the APIs for Wasm.
CPP code:
```cpp
// hello.cpp
#include "napi/native_api.h"
#include "ark_runtime/jsvm.h"
#include
#ifndef CHECK
#define CHECK(cond) \
do { \
if (!(cond)) { \
OH_LOG_ERROR(LOG_APP, "CHECK FAILED"); \
abort(); \
} \
} while (0)
#endif
// Check whether a JSVM_Value is a Wasm module.
static bool IsWasmModuleObject(JSVM_Env env, JSVM_Value value) {
bool result;
JSVM_Status status = OH_JSVM_IsWasmModuleObject(env, value, &result);
CHECK(status == JSVM_OK);
return result;
}
// Create a JSVM string from a C string.
static JSVM_Value CreateString(JSVM_Env env, const char *str) {
JSVM_Value jsvmStr;
JSVM_Status status = OH_JSVM_CreateStringUtf8(env, str, JSVM_AUTO_LENGTH, &jsvmStr);
CHECK(status == JSVM_OK);
return jsvmStr;
}
// Create a JSVM number from a C int32_t value.
static JSVM_Value CreateInt32(JSVM_Env env, int32_t val) {
JSVM_Value jsvmInt32;
JSVM_Status status = OH_JSVM_CreateInt32(env, val, &jsvmInt32);
CHECK(status == JSVM_OK);
return jsvmInt32;
}
// Instantiate the Wasm module.
static JSVM_Value InstantiateWasmModule(JSVM_Env env, JSVM_Value wasmModule) {
JSVM_Status status = JSVM_OK;
JSVM_Value globalThis;
status = OH_JSVM_GetGlobal(env, &globalThis);
CHECK(status == JSVM_OK);
JSVM_Value webAssembly;
status = OH_JSVM_GetProperty(env, globalThis, CreateString(env, "WebAssembly"), &webAssembly);
CHECK(status == JSVM_OK);
JSVM_Value webAssemblyInstance;
status = OH_JSVM_GetProperty(env, webAssembly, CreateString(env, "Instance"), &webAssemblyInstance);
CHECK(status == JSVM_OK);
JSVM_Value instance;
JSVM_Value argv[] = {wasmModule};
status = OH_JSVM_NewInstance(env, webAssemblyInstance, 1, argv, &instance);
CHECK(status == JSVM_OK);
return instance;
}
// Obtain the Wasm bytecode (add module).
static std::vector GetAddWasmBuffer() {
// The following is the text format of the Wasm bytecode corresponding to wasmBuffer, which contains only the add function.
// (module
// (func $add (param $lhs i32) (param $rhs i32) (result i32)
// local.get $lhs
// local.get $rhs
// i32.add
// )
// (export "add" (func $add))
// )
std::vector wasmBuffer = {0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01,
0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07,
0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01,
0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b};
return wasmBuffer;
}
// Verify the Wasm instance function (add module).
static void VerifyAddWasmInstance(JSVM_Env env, JSVM_Value wasmInstance) {
JSVM_Status status = JSVM_OK;
// Obtain the exports.add function from the Wasm instance.
JSVM_Value exports;
status = OH_JSVM_GetProperty(env, wasmInstance, CreateString(env, "exports"), &exports);
CHECK(status == JSVM_OK);
JSVM_Value add;
status = OH_JSVM_GetProperty(env, exports, CreateString(env, "add"), &add);
CHECK(status == JSVM_OK);
// Run the exports.add(1, 2). The expected result is 3.
JSVM_Value undefined;
OH_JSVM_GetUndefined(env, &undefined);
JSVM_Value one = CreateInt32(env, 1);
JSVM_Value two = CreateInt32(env, 2);
JSVM_Value argv[] = {one, two};
JSVM_Value result;
status = OH_JSVM_CallFunction(env, undefined, add, 2, argv, &result);
CHECK(status == JSVM_OK);
int32_t resultInt32;
OH_JSVM_GetValueInt32(env, result, &resultInt32);
CHECK(resultInt32 == 3);
}
// Wasm demo main function.
static JSVM_Value WasmDemo(JSVM_Env env, JSVM_CallbackInfo info) {
JSVM_Status status = JSVM_OK;
std::vector wasmBuffer = GetAddWasmBuffer();
uint8_t *wasmBytecode = wasmBuffer.data();
size_t wasmBytecodeLength = wasmBuffer.size();
JSVM_Value wasmModule;
// Obtain the Wasm module based on the Wasm bytecode.
status = OH_JSVM_CompileWasmModule(env, wasmBytecode, wasmBytecodeLength, NULL, 0, NULL, &wasmModule);
CHECK(status == JSVM_OK);
CHECK(IsWasmModuleObject(env, wasmModule));
// Perform compilation optimization on the first function (add) defined in the Wasm module.
int32_t functionIndex = 0;
// Currently, only high-level optimization is supported. That is, the effect is the same no matter whether JSVM_WASM_OPT_BASELINE or JSVM_WASM_OPT_HIGH is passed in.
status = OH_JSVM_CompileWasmFunction(env, wasmModule, functionIndex, JSVM_WASM_OPT_HIGH);
CHECK(status == JSVM_OK);
// Instantiate the compiled Wasm module.
JSVM_Value wasmInstance = InstantiateWasmModule(env, wasmModule);
// Verify the function in the instantiated Wasm instance.
VerifyAddWasmInstance(env, wasmInstance);
// Create a Wasm cache.
const uint8_t *wasmCacheData = NULL;
size_t wasmCacheLength = 0;
status = OH_JSVM_CreateWasmCache(env, wasmModule, &wasmCacheData, &wasmCacheLength);
CHECK(status == JSVM_OK);
// The Wasm cache is created successfully.
CHECK(wasmCacheData != NULL);
CHECK(wasmCacheLength > 0);
// Assign a value to the Wasm cache to simulate cache persistence. In actual scenarios, the Wasm cache may be saved to a file.
std::vector cacheBuffer(wasmCacheData, wasmCacheData + wasmCacheLength);
// Once the cache is saved, it needs to be released explicitly to avoid memory leaks.
// Note that the input JSVM_CacheType must match the cache data.
status = OH_JSVM_ReleaseCache(env, wasmCacheData, JSVM_CACHE_TYPE_WASM);
CHECK(status == JSVM_OK);
// Deserialize the Wasm code to generate a Wasm module.
bool cacheRejected;
JSVM_Value wasmModule2;
status = OH_JSVM_CompileWasmModule(env, wasmBytecode, wasmBytecodeLength, cacheBuffer.data(), cacheBuffer.size(),
&cacheRejected, &wasmModule2);
CHECK(status == JSVM_OK);
// If the input Wasm cache is matched and the internal verification (such as the version) is successful, the cache will be accepted.
CHECK(cacheRejected == false);
CHECK(IsWasmModuleObject(env, wasmModule2));
// For wasmModule2 (obtained through deserialization), perform the same operations, including function compilation, instantiation, and verification.
status = OH_JSVM_CompileWasmFunction(env, wasmModule2, functionIndex, JSVM_WASM_OPT_HIGH);
CHECK(status == JSVM_OK);
JSVM_Value wasmInstance2 = InstantiateWasmModule(env, wasmModule);
VerifyAddWasmInstance(env, wasmInstance2);
JSVM_Value result;
OH_JSVM_GetBoolean(env, true, &result);
return result;
}
// Register a WasmDemo callback.
static JSVM_CallbackStruct param[] = {
{.data = nullptr, .callback = WasmDemo}
};
static JSVM_CallbackStruct *method = param;
// Register the C++ WasmDemo callback as a JSVM globalThis.wasmDemo property for the JS to call.
static JSVM_PropertyDescriptor descriptor[] = {
{"wasmDemo", nullptr, method++, nullptr, nullptr, nullptr, JSVM_DEFAULT},
};
// Call the C++ callback from JS.
const char *srcCallNative = R"JS(wasmDemo())JS";
```