目录

Working with VM Snapshots Using JSVM-API

Introduction

A JavaScript virtual machine (JSVM) snapshot records the state of a JSVM at a particular time point. The snapshot file contains information, such as the current heap memory, execution context, and function closure.

Basic Concepts

It helps simplify complex programming tasks and shorten the creation time of a JS context, making the application more efficient and stable.

Available APIs

API Description
OH_JSVM_CreateSnapshot Creates a VM startup snapshot.
OH_JSVM_CreateEnvFromSnapshot Creates an environment based on the start snapshot of a VM.

Example

[h2]OH_JSVM_CreateSnapshot & OH_JSVM_CreateEnvFromSnapshot

Use OH_JSVM_CreateSnapshot to create a VM startup snapshot.

CPP code:

Note: During OH_JSVM_Init, register JSVM's external dependencies with initOptions.externalReferences.

#include "napi/native_api.h"
#include "ark_runtime/jsvm.h"
#include <hilog/log.h>
#include <fstream>

#define LOG_DOMAIN 0x0202
#define LOG_TAG "TEST_TAG"

static int g_aa = 0;

#define CHECK_RET(theCall)                                                                           \
    do {                                                                                             \
        JSVM_Status cond = theCall;                                                                  \
        if ((cond) != JSVM_OK) {                                                                     \
            const JSVM_ExtendedErrorInfo *info;                                                      \
            OH_JSVM_GetLastErrorInfo(env, &info);                                                    \
            OH_LOG_ERROR(LOG_APP,                                                                    \
                "jsvm fail file: %{public}s line: %{public}d ret = %{public}d message = %{public}s", \
                __FILE__,                                                                            \
                __LINE__,                                                                            \
                cond,                                                                                \
                info != nullptr ? info->errorMessage : "");                                          \
            return -1;                                                                               \
        }                                                                                            \
    } while (0)

#define CHECK(theCall)                                                                                              \
    do {                                                                                                            \
        JSVM_Status cond = theCall;                                                                                 \
        if ((cond) != JSVM_OK) {                                                                                    \
            OH_LOG_ERROR(                                                                                           \
                LOG_APP, "jsvm fail file: %{public}s line: %{public}d ret = %{public}d", __FILE__, __LINE__, cond); \
            return -1;                                                                                              \
        }                                                                                                           \
    } while (0)

// Used to call theCall and check whether its return value is JSVM_OK.
// If not, call GET_AND_THROW_LAST_ERROR to handle the error and return retVal.
#define JSVM_CALL_BASE(env, theCall, retVal)                                                         \
    do {                                                                                             \
        JSVM_Status cond = theCall;                                                                  \
        if (cond != JSVM_OK) {                                                                       \
            const JSVM_ExtendedErrorInfo *info;                                                      \
            OH_JSVM_GetLastErrorInfo(env, &info);                                                    \
            OH_LOG_ERROR(LOG_APP,                                                                    \
                "jsvm fail file: %{public}s line: %{public}d ret = %{public}d message = %{public}s", \
                __FILE__,                                                                            \
                __LINE__,                                                                            \
                cond,                                                                                \
                info != nullptr ? info->errorMessage : "");                                          \
            return retVal;                                                                           \
        }                                                                                            \
    } while (0)

// A simplified version of JSVM_CALL_BASE that returns nullptr.
#define JSVM_CALL(theCall) JSVM_CALL_BASE(env, theCall, nullptr)

static const int MAX_BUFFER_SIZE = 128;
// The CreateHelloString() function must be bound to the JSVM virtual machine for normal creation of the OH_JSVM_CreateSnapshot virtual machine snapshot.
static JSVM_Value CreateHelloString(JSVM_Env env, JSVM_CallbackInfo info)
{
    JSVM_Value outPut;
    OH_JSVM_CreateStringUtf8(env, "Hello world!", JSVM_AUTO_LENGTH, &outPut);
    return outPut;
}
// Provides external references so that the JavaScript environment can call the bound function.
static JSVM_CallbackStruct helloCb = {CreateHelloString, nullptr};

static intptr_t g_externals[] = {
    (intptr_t)&helloCb,
    0,
};

static JSVM_Value RunVMScript(JSVM_Env env, std::string &src)
{
    // Open the handleScope scope.
    JSVM_HandleScope handleScope;
    OH_JSVM_OpenHandleScope(env, &handleScope);
    JSVM_Value jsStr = nullptr;
    OH_JSVM_CreateStringUtf8(env, src.c_str(), src.size(), &jsStr);
    // Compile the JavaScript code.
    JSVM_Script script;
    OH_JSVM_CompileScript(env, jsStr, nullptr, 0, true, nullptr, &script);
    // Execute the JavaScript code.
    JSVM_Value result = nullptr;
    OH_JSVM_RunScript(env, script, &result);
    // Close the handleScope scope.
    OH_JSVM_CloseHandleScope(env, handleScope);
    return result;
}
// Sample method for OH_JSVM_CreateSnapshot.
static void CreateVMSnapshot()
{
    // Create a JavaScript virtual machine instance and open the VM scope.
    JSVM_VM vm;
    JSVM_CreateVMOptions vmOptions;
    memset(&vmOptions, 0, sizeof(vmOptions));
    // isForSnapshotting sets whether this virtual machine is used for creating snapshots.
    vmOptions.isForSnapshotting = true;
    OH_JSVM_CreateVM(&vmOptions, &vm);
    JSVM_VMScope vmScope;
    OH_JSVM_OpenVMScope(vm, &vmScope);
    // Create a JavaScript environment and open the environment scope.
    JSVM_Env env;
    // Register the native function as a JavaScript-callable method.
    JSVM_PropertyDescriptor descriptor[] = {
        {"createHelloString", nullptr, &helloCb, nullptr, nullptr, nullptr, JSVM_DEFAULT},
    };
    OH_JSVM_CreateEnv(vm, 1, descriptor, &env);
    JSVM_EnvScope envScope;
    OH_JSVM_OpenEnvScope(env, &envScope);
    // Use OH_JSVM_CreateSnapshot to create a startup snapshot of the virtual machine.
    const char *blobData = nullptr;
    size_t blobSize = 0;
    JSVM_Env envs[1] = {env};
    OH_JSVM_CreateSnapshot(vm, 1, envs, &blobData, &blobSize);
    // Save the snapshot to a file.
    // Save the snapshot data. /data/storage/el2/base/files/test_blob.bin is the sandbox path.
    // Taking the bundle name com.example.jsvm as an example, the actual file is saved to /data/app/el2/100/base/com.example.jsvm/files/test_blob.bin.
    std::ofstream file("/data/storage/el2/base/files/test_blob.bin",
                       std::ios::out | std::ios::binary | std::ios::trunc);
    file.write(blobData, blobSize);
    file.close();
    // Close and destroy the environment and virtual machine.
    OH_JSVM_CloseEnvScope(env, envScope);
    OH_JSVM_DestroyEnv(env);
    OH_JSVM_CloseVMScope(vm, vmScope);
    OH_JSVM_DestroyVM(vm);
}

static void RunVMSnapshot()
{
    // The lifecycle of blobData must not be shorter than that of vm.
    // Read the snapshot from the file.
    std::vector<char> blobData;
    std::ifstream file("/data/storage/el2/base/files/test_blob.bin", std::ios::in | std::ios::binary | std::ios::ate);
    size_t blobSize = file.tellg();
    blobData.resize(blobSize);
    file.seekg(0, std::ios::beg);
    file.read(blobData.data(), blobSize);
    file.close();
    OH_LOG_INFO(LOG_APP, "Test JSVM RunVMSnapshot read file blobSize = : %{public}ld", blobSize);
    // Create a virtual machine instance using the snapshot data.
    JSVM_VM vm;
    JSVM_CreateVMOptions vmOptions;
    memset(&vmOptions, 0, sizeof(vmOptions));
    vmOptions.snapshotBlobData = blobData.data();
    vmOptions.snapshotBlobSize = blobSize;
    OH_JSVM_CreateVM(&vmOptions, &vm);
    JSVM_VMScope vmScope;
    OH_JSVM_OpenVMScope(vm, &vmScope);
    // Create the environment env from the snapshot.
    JSVM_Env env;
    OH_JSVM_CreateEnvFromSnapshot(vm, 0, &env);
    JSVM_EnvScope envScope;
    OH_JSVM_OpenEnvScope(env, &envScope);
    // Execute the JS script. createHelloString() is defined in the env recorded by the snapshot.
    std::string src = "createHelloString()";
    JSVM_Value result = RunVMScript(env, src);
    // Check the script execution result before closing the environment.
    char str[MAX_BUFFER_SIZE];
    OH_JSVM_GetValueStringUtf8(env, result, str, MAX_BUFFER_SIZE, nullptr);
    if (strcmp(str, "Hello world!") != 0) {
        OH_LOG_ERROR(LOG_APP, "jsvm fail file: %{public}s line: %{public}d", __FILE__, __LINE__);
    }
    // Close and destroy the environment and virtual machine.
    OH_JSVM_CloseEnvScope(env, envScope);
    OH_JSVM_DestroyEnv(env);
    OH_JSVM_CloseVMScope(vm, vmScope);
    OH_JSVM_DestroyVM(vm);
    return;
}

static JSVM_Value AdjustExternalMemory(JSVM_Env env, JSVM_CallbackInfo info)
{
    // When creating a virtual machine snapshot, if there are external dependencies, register them in initOptions.externalReferences during OH_JSVM_Init.
    // Create a virtual machine snapshot and save it to a file.
    CreateVMSnapshot();
    // A snapshot can record a specific JS execution environment and can be used across processes to quickly restore the JS execution context.
    RunVMSnapshot();
    JSVM_Value result = nullptr;
    OH_JSVM_CreateInt32(env, 0, &result);
    return result;
}

static JSVM_CallbackStruct param[] = {
    {.data = nullptr, .callback = AdjustExternalMemory},
};
static JSVM_CallbackStruct *method = param;
// Alias for the AdjustExternalMemory method, for JS invocation.
static JSVM_PropertyDescriptor descriptor[] = {
    {"adjustExternalMemory", nullptr, method, nullptr, nullptr, nullptr, JSVM_DEFAULT},
};

// Sample test JS.
const char *SRC_CALL_NATIVE = R"JS(adjustExternalMemory();)JS";

static int32_t TestJSVM()
{
    JSVM_InitOptions initOptions = {0};
    JSVM_VM vm;
    JSVM_Env env = nullptr;
    JSVM_VMScope vmScope;
    JSVM_EnvScope envScope;
    JSVM_HandleScope handleScope;
    JSVM_Value result;
    // Initialize the JavaScript engine instance.
    if (g_aa == 0) {
        g_aa++;
        initOptions.externalReferences = g_externals;
        int argc = 0;
        char **argv = nullptr;
        initOptions.argc = &argc;
        initOptions.argv = argv;
        CHECK(OH_JSVM_Init(&initOptions));
    }
    // Create the JSVM environment.
    CHECK(OH_JSVM_CreateVM(nullptr, &vm));
    CHECK(OH_JSVM_OpenVMScope(vm, &vmScope));
    CHECK(OH_JSVM_CreateEnv(vm, sizeof(descriptor) / sizeof(descriptor[0]), descriptor, &env));
    CHECK_RET(OH_JSVM_OpenEnvScope(env, &envScope));
    CHECK_RET(OH_JSVM_OpenHandleScope(env, &handleScope));

    // Call the test function through the script.
    JSVM_Script script;
    JSVM_Value jsSrc;
    CHECK_RET(OH_JSVM_CreateStringUtf8(env, SRC_CALL_NATIVE, JSVM_AUTO_LENGTH, &jsSrc));
    CHECK_RET(OH_JSVM_CompileScript(env, jsSrc, nullptr, 0, true, nullptr, &script));
    CHECK_RET(OH_JSVM_RunScript(env, script, &result));

    // Destroy the JSVM environment.
    CHECK_RET(OH_JSVM_CloseHandleScope(env, handleScope));
    CHECK_RET(OH_JSVM_CloseEnvScope(env, envScope));
    CHECK(OH_JSVM_DestroyEnv(env));
    CHECK(OH_JSVM_CloseVMScope(vm, vmScope));
    CHECK(OH_JSVM_DestroyVM(vm));
    return 0;
}

static napi_value RunTest(napi_env env, napi_callback_info info)
{
    TestJSVM();
    return nullptr;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    OH_LOG_INFO(LOG_APP, "JSVM Init");
    napi_property_descriptor desc[] = {
        {"runTest", nullptr, RunTest, nullptr, nullptr, nullptr, napi_default, nullptr},
    };

    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "createsnapshot",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); }

ArkTS code:

import napitest from 'libentry.so';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            // runtest
            napitest.runTest();
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

Execution Result

Log output when the screen is clicked once:

Test JSVM RunVMSnapshot read file blobSize = : 300064

Log output when the screen is clicked multiple times:

Test JSVM RunVMSnapshot read file blobSize = : 300176
Test JSVM RunVMSnapshot read file blobSize = : 300064
Test JSVM RunVMSnapshot read file blobSize = : 300160
Test JSVM RunVMSnapshot read file blobSize = : 300032
Test JSVM RunVMSnapshot read file blobSize = : 300176
Test JSVM RunVMSnapshot read file blobSize = : 300048

blobSize is the snapshot file size obtained through file.tellg() when the file is read. Therefore, different values are displayed.