1# Working with VM Snapshots Using JSVM-API
2
3## Introduction
4
5A 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.
6
7## Basic Concepts
8
9- VM startup snapshot: a snapshot of the VM status at a specific time, including all internal status and data of the VM. The snapshot can be used to quickly restore the VM to the state it was when the snapshot was created.
10
11It helps simplify complex programming tasks and shorten the creation time of a JS context, making the application more efficient and stable.
12
13## Available APIs
14
15| API                      | Description                      |
16|----------------------------|-------------------------------|
17| OH_JSVM_CreateSnapshot     | Creates a VM startup snapshot.       |
18|OH_JSVM_CreateEnvFromSnapshot| Creates an environment based on the start snapshot of a VM.|
19## Example
20
21### OH_JSVM_CreateSnapshot & OH_JSVM_CreateEnvFromSnapshot
22
23Use **OH_JSVM_CreateSnapshot** to create a VM startup snapshot.
24
25CPP code:
26
27**NOTE**<br>Register the external dependencies of the JSVM with **initOptions.externalReferences** in **OH_JSVM_Init**.
28```cpp
29// hello.cpp
30#include "napi/native_api.h"
31#include "ark_runtime/jsvm.h"
32#include <hilog/log.h>
33#include <fstream>
34
35#define LOG_DEMAIN 0x0202
36#define LOG_TAG "TEST_TAG"
37
38static int g_aa = 0;
39
40#define CHECK_RET(theCall)                                                                                             \
41    do {                                                                                                               \
42        JSVM_Status cond = theCall;                                                                                    \
43        if ((cond) != JSVM_OK) {                                                                                       \
44            const JSVM_ExtendedErrorInfo *info;                                                                        \
45            OH_JSVM_GetLastErrorInfo(env, &info);                                                                      \
46            OH_LOG_ERROR(LOG_APP, "jsvm fail file: %{public}s line: %{public}d ret = %{public}d message = %{public}s", \
47                         __FILE__, __LINE__, cond, info != nullptr ? info->errorMessage : "");                         \
48            return -1;                                                                                                 \
49        }                                                                                                              \
50    } while (0)
51
52#define CHECK(theCall)                                                                                                 \
53    do {                                                                                                               \
54        JSVM_Status cond = theCall;                                                                                    \
55        if ((cond) != JSVM_OK) {                                                                                       \
56            OH_LOG_ERROR(LOG_APP, "jsvm fail file: %{public}s line: %{public}d ret = %{public}d", __FILE__, __LINE__,  \
57                         cond);                                                                                        \
58            return -1;                                                                                                 \
59        }                                                                                                              \
60    } while (0)
61
62// Call theCall and check whether the return value is JSVM_OK.
63// If no, call GET_AND_THROW_LAST_ERROR to process the error and return retVal.
64#define JSVM_CALL_BASE(env, theCall, retVal)                                                                           \
65    do {                                                                                                               \
66        JSVM_Status cond = theCall;                                                                                    \
67        if (cond != JSVM_OK) {                                                                                         \
68            const JSVM_ExtendedErrorInfo *info;                                                                        \
69            OH_JSVM_GetLastErrorInfo(env, &info);                                                                      \
70            OH_LOG_ERROR(LOG_APP, "jsvm fail file: %{public}s line: %{public}d ret = %{public}d message = %{public}s", \
71                         __FILE__, __LINE__, cond, info != nullptr ? info->errorMessage : "");                         \
72            return retVal;                                                                                             \
73        }                                                                                                              \
74    } while (0)
75
76// Simplified version of JSVM_CALL_BASE, which returns nullptr.
77#define JSVM_CALL(theCall) JSVM_CALL_BASE(env, theCall, nullptr)
78
79static const int MAX_BUFFER_SIZE = 128;
80// Allow the JSVM to call the CreateHelloString() function when needs, using the callback struct and external references.
81static JSVM_Value CreateHelloString(JSVM_Env env, JSVM_CallbackInfo info) {
82    JSVM_Value outPut;
83    OH_JSVM_CreateStringUtf8(env, "Hello world!", JSVM_AUTO_LENGTH, &outPut);
84    return outPut;
85}
86// Enable the JSVM to call the bound function through external references.
87static JSVM_CallbackStruct helloCb = {CreateHelloString, nullptr};
88
89static intptr_t externals[] = {
90    (intptr_t)&helloCb,
91    0,
92};
93
94static JSVM_Value RunVMScript(JSVM_Env env, std::string &src) {
95    // Open the handle scope.
96    JSVM_HandleScope handleScope;
97    OH_JSVM_OpenHandleScope(env, &handleScope);
98    JSVM_Value jsStr = nullptr;
99    OH_JSVM_CreateStringUtf8(env, src.c_str(), src.size(), &jsStr);
100    // Compile the JS code.
101    JSVM_Script script;
102    OH_JSVM_CompileScript(env, jsStr, nullptr, 0, true, nullptr, &script);
103    // Execute the JS code.
104    JSVM_Value result = nullptr;
105    OH_JSVM_RunScript(env, script, &result);
106    // Close the handle scope.
107    OH_JSVM_CloseHandleScope(env, handleScope);
108    return result;
109}
110// Define OH_JSVM_CreateSnapshot.
111static void CreateVMSnapshot() {
112    // Create a JSVM instance and open the VM scope.
113    JSVM_VM vm;
114    JSVM_CreateVMOptions vmOptions;
115    memset(&vmOptions, 0, sizeof(vmOptions));
116    // Use isForSnapshotting to set whether the VM is used for creating snapshots.
117    vmOptions.isForSnapshotting = true;
118    OH_JSVM_CreateVM(&vmOptions, &vm);
119    JSVM_VMScope vmScope;
120    OH_JSVM_OpenVMScope(vm, &vmScope);
121    // Create a JS environment and open the environment scope.
122    JSVM_Env env;
123    // Register the native function as a method that can be called from JS.
124    JSVM_PropertyDescriptor descriptor[] = {
125        {"createHelloString", nullptr, &helloCb, nullptr, nullptr, nullptr, JSVM_DEFAULT},
126    };
127    OH_JSVM_CreateEnv(vm, 1, descriptor, &env);
128    JSVM_EnvScope envScope;
129    OH_JSVM_OpenEnvScope(env, &envScope);
130    // Use OH_JSVM_CreateSnapshot to create a VM startup snapshot.
131    const char *blobData = nullptr;
132    size_t blobSize = 0;
133    JSVM_Env envs[1] = {env};
134    OH_JSVM_CreateSnapshot(vm, 1, envs, &blobData, &blobSize);
135    // Save the snapshot to a file.
136    // Save the snapshot data to the /data/storage/el2/base/files/test_blob.bin directory, which is a sandbox directory.
137    // For example, the bundle name is com.example.jsvm. The snapshot file is saved in /data/app/el2/100/base/com.example.jsvm/files/test_blob.bin.
138    std::ofstream file("/data/storage/el2/base/files/test_blob.bin",
139                       std::ios::out | std::ios::binary | std::ios::trunc);
140    file.write(blobData, blobSize);
141    file.close();
142    // Close and destroy the environment and the VM.
143    OH_JSVM_CloseEnvScope(env, envScope);
144    OH_JSVM_DestroyEnv(env);
145    OH_JSVM_CloseVMScope(vm, vmScope);
146    OH_JSVM_DestroyVM(vm);
147}
148
149static void RunVMSnapshot() {
150    // The lifespan of blobData cannot be shorter than that of the VM.
151    // Read the snapshot from the file.
152    std::vector<char> blobData;
153    std::ifstream file("/data/storage/el2/base/files/test_blob.bin", std::ios::in | std::ios::binary | std::ios::ate);
154    size_t blobSize = file.tellg();
155    blobData.resize(blobSize);
156    file.seekg(0, std::ios::beg);
157    file.read(blobData.data(), blobSize);
158    file.close();
159    OH_LOG_INFO(LOG_APP, "Test JSVM RunVMSnapshot read file blobSize = : %{public}ld", blobSize);
160    // Use the snapshot data to create a VM instance.
161    JSVM_VM vm;
162    JSVM_CreateVMOptions vmOptions;
163    memset(&vmOptions, 0, sizeof(vmOptions));
164    vmOptions.snapshotBlobData = blobData.data();
165    vmOptions.snapshotBlobSize = blobSize;
166    OH_JSVM_CreateVM(&vmOptions, &vm);
167    JSVM_VMScope vmScope;
168    OH_JSVM_OpenVMScope(vm, &vmScope);
169    // Create an environment from the snapshot.
170    JSVM_Env env;
171    OH_JSVM_CreateEnvFromSnapshot(vm, 0, &env);
172    JSVM_EnvScope envScope;
173    OH_JSVM_OpenEnvScope(env, &envScope);
174    // Execute the JS script. createHelloString() is defined in env of the snapshot record.
175    std::string src = "createHelloString()";
176    JSVM_Value result = RunVMScript(env, src);
177    // Check the script execution result before closing the environment.
178    char str[MAX_BUFFER_SIZE];
179    OH_JSVM_GetValueStringUtf8(env, result, str, MAX_BUFFER_SIZE, nullptr);
180    if (strcmp(str, "Hello world!") !=0) {
181        OH_LOG_ERROR(LOG_APP, "jsvm fail file: %{public}s line: %{public}d", __FILE__, __LINE__);
182    }
183    // Close and destroy the environment and the VM.
184    OH_JSVM_CloseEnvScope(env, envScope);
185    OH_JSVM_DestroyEnv(env);
186    OH_JSVM_CloseVMScope(vm, vmScope);
187    OH_JSVM_DestroyVM(vm);
188    return;
189}
190
191static JSVM_Value AdjustExternalMemory(JSVM_Env env, JSVM_CallbackInfo info) {
192    // If external dependencies exist when creating a VM snapshot, register the external dependencies with initOptions.externalReferences in OH_JSVM_Init.
193    // Create a VM snapshot and save it to a file.
194    CreateVMSnapshot();
195    // The snapshot records the specific JS execution environment and can be used to quickly restore the JS execution context environment across processes.
196    RunVMSnapshot();
197    JSVM_Value result = nullptr;
198    OH_JSVM_CreateInt32(env, 0, &result);
199    return result;
200}
201
202static JSVM_CallbackStruct param[] = {
203    {.data = nullptr, .callback = AdjustExternalMemory},
204};
205static JSVM_CallbackStruct *method = param;
206// Set a property descriptor named adjustExternalMemory and associate it with a callback. This allows the AdjustExternalMemory callback to be called from JS.
207static JSVM_PropertyDescriptor descriptor[] = {
208    {"adjustExternalMemory", nullptr, method, nullptr, nullptr, nullptr, JSVM_DEFAULT},
209};
210
211// Call the C++ code from JS.
212const char *srcCallNative = R"JS(adjustExternalMemory();)JS";
213
214static int32_t TestJSVM() {
215    JSVM_InitOptions initOptions = {0};
216    JSVM_VM vm;
217    JSVM_Env env = nullptr;
218    JSVM_VMScope vmScope;
219    JSVM_EnvScope envScope;
220    JSVM_HandleScope handleScope;
221    JSVM_Value result;
222    // Initialize the JSVM instance.
223    if (g_aa == 0) {
224        g_aa++;
225        initOptions.externalReferences = externals;
226      int argc = 0;
227      char **argv = nullptr;
228      initOptions.argc = &argc;
229      initOptions.argv = argv;
230      CHECK(OH_JSVM_Init(&initOptions));
231    }
232    // Create a JSVM environment.
233    CHECK(OH_JSVM_CreateVM(nullptr, &vm));
234    CHECK(OH_JSVM_CreateEnv(vm, sizeof(descriptor) / sizeof(descriptor[0]), descriptor, &env));
235    CHECK(OH_JSVM_OpenVMScope(vm, &vmScope));
236    CHECK_RET(OH_JSVM_OpenEnvScope(env, &envScope));
237    CHECK_RET(OH_JSVM_OpenHandleScope(env, &handleScope));
238
239    // Call the test function through the script.
240    JSVM_Script script;
241    JSVM_Value jsSrc;
242    CHECK_RET(OH_JSVM_CreateStringUtf8(env, srcCallNative, JSVM_AUTO_LENGTH, &jsSrc));
243    CHECK_RET(OH_JSVM_CompileScript(env, jsSrc, nullptr, 0, true, nullptr, &script));
244    CHECK_RET(OH_JSVM_RunScript(env, script, &result));
245
246    // Destroy the JSVM environment.
247    CHECK_RET(OH_JSVM_CloseHandleScope(env, handleScope));
248    CHECK_RET(OH_JSVM_CloseEnvScope(env, envScope));
249    CHECK(OH_JSVM_CloseVMScope(vm, vmScope));
250    CHECK(OH_JSVM_DestroyEnv(env));
251    CHECK(OH_JSVM_DestroyVM(vm));
252    return 0;
253}
254
255static napi_value RunTest(napi_env env, napi_callback_info info)
256{
257    TestJSVM();
258    return nullptr;
259}
260
261EXTERN_C_START
262static napi_value Init(napi_env env, napi_value exports) {
263    OH_LOG_INFO(LOG_APP, "JSVM Init");
264    napi_property_descriptor desc[] = {{"runTest", nullptr, RunTest, nullptr, nullptr, nullptr, napi_default, nullptr},
265    };
266
267    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
268    return exports;
269}
270EXTERN_C_END
271
272static napi_module demoModule = {
273    .nm_version = 1,
274    .nm_flags = 0,
275    .nm_filename = nullptr,
276    .nm_register_func = Init,
277    .nm_modname = "entry",
278    .nm_priv = ((void *)0),
279    .reserved = {0},
280};
281
282extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); }
283```
284
285ArkTS code:
286
287```ts
288@Entry
289@Component
290struct Index {
291  @State message: string = 'Hello World';
292
293  build() {
294    Row() {
295      Column() {
296        Text(this.message)
297          .fontSize(50)
298          .fontWeight(FontWeight.Bold)
299          .onClick(() => {
300            // runtest
301            napitest.runTest();
302          })
303      }
304      .width('100%')
305    }
306    .height('100%')
307  }
308}
309```
310**Expected output**:
311Output in the log:<br>Test JSVM RunVMSnapshot read file blobSize =: 300064
312