# Working with Wasm Using JSVM-API ## Introduction JSVM-API provides APIs for compiling WebAssembly (Wasm) bytecode, optimizing Wasm functions, and serializing and deserializing Wasm caches. ## 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 a 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. | ## 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"; ```