Skip to content

Commit 27564a4

Browse files
devsnekMylesBorins
authored andcommitted
vm: add code cache support for SourceTextModule
PR-URL: #31278 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent c252356 commit 27564a4

File tree

8 files changed

+162
-10
lines changed

8 files changed

+162
-10
lines changed

doc/api/errors.md

+10
Original file line numberDiff line numberDiff line change
@@ -1988,6 +1988,16 @@ the following reasons:
19881988
* It is being linked (`linkingStatus` is `'linking'`)
19891989
* Linking has failed for this module (`linkingStatus` is `'errored'`)
19901990

1991+
<a id="ERR_VM_MODULE_CACHED_DATA_REJECTED"></a>
1992+
### `ERR_VM_MODULE_CACHED_DATA_REJECTED`
1993+
1994+
The `cachedData` option passed to a module constructor is invalid.
1995+
1996+
<a id="ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA"></a>
1997+
### `ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA`
1998+
1999+
Cached data cannot be created for modules which have already been evaluated.
2000+
19912001
<a id="ERR_VM_MODULE_DIFFERENT_CONTEXT"></a>
19922002
### `ERR_VM_MODULE_DIFFERENT_CONTEXT`
19932003

doc/api/vm.md

+26
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,10 @@ defined in the ECMAScript specification.
566566
* `identifier` {string} String used in stack traces.
567567
**Default:** `'vm:module(i)'` where `i` is a context-specific ascending
568568
index.
569+
* `cachedData` {Buffer|TypedArray|DataView} Provides an optional `Buffer` or
570+
`TypedArray`, or `DataView` with V8's code cache data for the supplied
571+
source. The `code` must be the same as the module from which this
572+
`cachedData` was created.
569573
* `context` {Object} The [contextified][] object as returned by the
570574
`vm.createContext()` method, to compile and evaluate this `Module` in.
571575
* `lineOffset` {integer} Specifies the line number offset that is displayed
@@ -621,6 +625,28 @@ const contextifiedObject = vm.createContext({ secret: 42 });
621625
})();
622626
```
623627
628+
### `sourceTextModule.createCachedData()`
629+
<!-- YAML
630+
added: REPLACEME
631+
-->
632+
633+
* Returns: {Buffer}
634+
635+
Creates a code cache that can be used with the SourceTextModule constructor's
636+
`cachedData` option. Returns a Buffer. This method may be called any number
637+
of times before the module has been evaluated.
638+
639+
```js
640+
// Create an initial module
641+
const module = new vm.SourceTextModule('const a = 1;');
642+
643+
// Create cached data from this module
644+
const cachedData = module.createCachedData();
645+
646+
// Create a new module using the cached data. The code must be the same.
647+
const module2 = new vm.SourceTextModule('const a = 1;', { cachedData });
648+
```
649+
624650
## Class: `vm.SyntheticModule`
625651
<!-- YAML
626652
added: v13.0.0

lib/internal/errors.js

+2
Original file line numberDiff line numberDiff line change
@@ -1351,6 +1351,8 @@ E('ERR_VALID_PERFORMANCE_ENTRY_TYPE',
13511351
E('ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING',
13521352
'A dynamic import callback was not specified.', TypeError);
13531353
E('ERR_VM_MODULE_ALREADY_LINKED', 'Module has already been linked', Error);
1354+
E('ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA',
1355+
'Cached data cannot be created for a module which has been evaluated', Error);
13541356
E('ERR_VM_MODULE_DIFFERENT_CONTEXT',
13551357
'Linked modules must use the same context', Error);
13561358
E('ERR_VM_MODULE_LINKING_ERRORED',

lib/internal/vm/module.js

+27-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ const {
1111
} = primordials;
1212

1313
const { isContext } = internalBinding('contextify');
14-
const { isModuleNamespaceObject } = require('internal/util/types');
14+
const {
15+
isModuleNamespaceObject,
16+
isArrayBufferView,
17+
} = require('internal/util/types');
1518
const {
1619
getConstructorOf,
1720
customInspectSymbol,
@@ -21,6 +24,7 @@ const {
2124
ERR_INVALID_ARG_TYPE,
2225
ERR_VM_MODULE_ALREADY_LINKED,
2326
ERR_VM_MODULE_DIFFERENT_CONTEXT,
27+
ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA,
2428
ERR_VM_MODULE_LINKING_ERRORED,
2529
ERR_VM_MODULE_NOT_MODULE,
2630
ERR_VM_MODULE_STATUS,
@@ -107,7 +111,8 @@ class Module {
107111

108112
if (sourceText !== undefined) {
109113
this[kWrap] = new ModuleWrap(identifier, context, sourceText,
110-
options.lineOffset, options.columnOffset);
114+
options.lineOffset, options.columnOffset,
115+
options.cachedData);
111116

112117
binding.callbackMap.set(this[kWrap], {
113118
initializeImportMeta: options.initializeImportMeta,
@@ -253,6 +258,7 @@ class SourceTextModule extends Module {
253258
importModuleDynamically,
254259
context,
255260
identifier,
261+
cachedData,
256262
} = options;
257263

258264
validateInt32(lineOffset, 'options.lineOffset');
@@ -271,12 +277,21 @@ class SourceTextModule extends Module {
271277
importModuleDynamically);
272278
}
273279

280+
if (cachedData !== undefined && !isArrayBufferView(cachedData)) {
281+
throw new ERR_INVALID_ARG_TYPE(
282+
'options.cachedData',
283+
['Buffer', 'TypedArray', 'DataView'],
284+
cachedData
285+
);
286+
}
287+
274288
super({
275289
sourceText,
276290
context,
277291
identifier,
278292
lineOffset,
279293
columnOffset,
294+
cachedData,
280295
initializeImportMeta,
281296
importModuleDynamically,
282297
});
@@ -348,6 +363,16 @@ class SourceTextModule extends Module {
348363
}
349364
return super.error;
350365
}
366+
367+
createCachedData() {
368+
const { status } = this;
369+
if (status === 'evaluating' ||
370+
status === 'evaluated' ||
371+
status === 'errored') {
372+
throw new ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA();
373+
}
374+
return this[kWrap].createCachedData();
375+
}
351376
}
352377

353378
class SyntheticModule extends Module {

src/module_wrap.cc

+65-8
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
#include "env.h"
44
#include "memory_tracker-inl.h"
5+
#include "node_contextify.h"
56
#include "node_errors.h"
7+
#include "node_internals.h"
8+
#include "node_process.h"
69
#include "node_url.h"
7-
#include "util-inl.h"
8-
#include "node_contextify.h"
910
#include "node_watchdog.h"
10-
#include "node_process.h"
11+
#include "util-inl.h"
1112

1213
#include <sys/stat.h> // S_IFDIR
1314

@@ -22,6 +23,7 @@ using node::contextify::ContextifyContext;
2223
using node::url::URL;
2324
using node::url::URL_FLAGS_FAILED;
2425
using v8::Array;
26+
using v8::ArrayBufferView;
2527
using v8::Context;
2628
using v8::Function;
2729
using v8::FunctionCallbackInfo;
@@ -44,6 +46,7 @@ using v8::Promise;
4446
using v8::ScriptCompiler;
4547
using v8::ScriptOrigin;
4648
using v8::String;
49+
using v8::UnboundModuleScript;
4750
using v8::Undefined;
4851
using v8::Value;
4952

@@ -131,7 +134,7 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
131134
// new ModuleWrap(url, context, exportNames, syntheticExecutionFunction)
132135
CHECK(args[3]->IsFunction());
133136
} else {
134-
// new ModuleWrap(url, context, source, lineOffset, columOffset)
137+
// new ModuleWrap(url, context, source, lineOffset, columOffset, cachedData)
135138
CHECK(args[2]->IsString());
136139
CHECK(args[3]->IsNumber());
137140
line_offset = args[3].As<Integer>();
@@ -167,6 +170,17 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
167170
module = Module::CreateSyntheticModule(isolate, url, export_names,
168171
SyntheticModuleEvaluationStepsCallback);
169172
} else {
173+
ScriptCompiler::CachedData* cached_data = nullptr;
174+
if (!args[5]->IsUndefined()) {
175+
CHECK(args[5]->IsArrayBufferView());
176+
Local<ArrayBufferView> cached_data_buf = args[5].As<ArrayBufferView>();
177+
uint8_t* data = static_cast<uint8_t*>(
178+
cached_data_buf->Buffer()->GetBackingStore()->Data());
179+
cached_data =
180+
new ScriptCompiler::CachedData(data + cached_data_buf->ByteOffset(),
181+
cached_data_buf->ByteLength());
182+
}
183+
170184
Local<String> source_text = args[2].As<String>();
171185
ScriptOrigin origin(url,
172186
line_offset, // line offset
@@ -178,8 +192,15 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
178192
False(isolate), // is WASM
179193
True(isolate), // is ES Module
180194
host_defined_options);
181-
ScriptCompiler::Source source(source_text, origin);
182-
if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) {
195+
ScriptCompiler::Source source(source_text, origin, cached_data);
196+
ScriptCompiler::CompileOptions options;
197+
if (source.GetCachedData() == nullptr) {
198+
options = ScriptCompiler::kNoCompileOptions;
199+
} else {
200+
options = ScriptCompiler::kConsumeCodeCache;
201+
}
202+
if (!ScriptCompiler::CompileModule(isolate, &source, options)
203+
.ToLocal(&module)) {
183204
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
184205
CHECK(!try_catch.Message().IsEmpty());
185206
CHECK(!try_catch.Exception().IsEmpty());
@@ -189,6 +210,13 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
189210
}
190211
return;
191212
}
213+
if (options == ScriptCompiler::kConsumeCodeCache &&
214+
source.GetCachedData()->rejected) {
215+
THROW_ERR_VM_MODULE_CACHED_DATA_REJECTED(
216+
env, "cachedData buffer was rejected");
217+
try_catch.ReThrow();
218+
return;
219+
}
192220
}
193221
}
194222

@@ -1507,8 +1535,7 @@ MaybeLocal<Value> ModuleWrap::SyntheticModuleEvaluationStepsCallback(
15071535
return ret;
15081536
}
15091537

1510-
void ModuleWrap::SetSyntheticExport(
1511-
const v8::FunctionCallbackInfo<v8::Value>& args) {
1538+
void ModuleWrap::SetSyntheticExport(const FunctionCallbackInfo<Value>& args) {
15121539
Isolate* isolate = args.GetIsolate();
15131540
Local<Object> that = args.This();
15141541

@@ -1528,6 +1555,35 @@ void ModuleWrap::SetSyntheticExport(
15281555
USE(module->SetSyntheticModuleExport(isolate, export_name, export_value));
15291556
}
15301557

1558+
void ModuleWrap::CreateCachedData(const FunctionCallbackInfo<Value>& args) {
1559+
Isolate* isolate = args.GetIsolate();
1560+
Local<Object> that = args.This();
1561+
1562+
ModuleWrap* obj;
1563+
ASSIGN_OR_RETURN_UNWRAP(&obj, that);
1564+
1565+
CHECK(!obj->synthetic_);
1566+
1567+
Local<Module> module = obj->module_.Get(isolate);
1568+
1569+
CHECK_LT(module->GetStatus(), v8::Module::Status::kEvaluating);
1570+
1571+
Local<UnboundModuleScript> unbound_module_script =
1572+
module->GetUnboundModuleScript();
1573+
std::unique_ptr<ScriptCompiler::CachedData> cached_data(
1574+
ScriptCompiler::CreateCodeCache(unbound_module_script));
1575+
Environment* env = Environment::GetCurrent(args);
1576+
if (!cached_data) {
1577+
args.GetReturnValue().Set(Buffer::New(env, 0).ToLocalChecked());
1578+
} else {
1579+
MaybeLocal<Object> buf =
1580+
Buffer::Copy(env,
1581+
reinterpret_cast<const char*>(cached_data->data),
1582+
cached_data->length);
1583+
args.GetReturnValue().Set(buf.ToLocalChecked());
1584+
}
1585+
}
1586+
15311587
void ModuleWrap::Initialize(Local<Object> target,
15321588
Local<Value> unused,
15331589
Local<Context> context,
@@ -1543,6 +1599,7 @@ void ModuleWrap::Initialize(Local<Object> target,
15431599
env->SetProtoMethod(tpl, "instantiate", Instantiate);
15441600
env->SetProtoMethod(tpl, "evaluate", Evaluate);
15451601
env->SetProtoMethod(tpl, "setExport", SetSyntheticExport);
1602+
env->SetProtoMethodNoSideEffect(tpl, "createCachedData", CreateCachedData);
15461603
env->SetProtoMethodNoSideEffect(tpl, "getNamespace", GetNamespace);
15471604
env->SetProtoMethodNoSideEffect(tpl, "getStatus", GetStatus);
15481605
env->SetProtoMethodNoSideEffect(tpl, "getError", GetError);

src/module_wrap.h

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class ModuleWrap : public BaseObject {
7575
v8::Local<v8::Context> context, v8::Local<v8::Module> module);
7676
static void SetSyntheticExport(
7777
const v8::FunctionCallbackInfo<v8::Value>& args);
78+
static void CreateCachedData(const v8::FunctionCallbackInfo<v8::Value>& args);
7879

7980
static v8::MaybeLocal<v8::Module> ResolveCallback(
8081
v8::Local<v8::Context> context,

src/node_errors.h

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ void PrintErrorString(const char* format, ...);
5959
V(ERR_TLS_INVALID_PROTOCOL_METHOD, TypeError) \
6060
V(ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER, TypeError) \
6161
V(ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED, Error) \
62+
V(ERR_VM_MODULE_CACHED_DATA_REJECTED, Error) \
6263

6364
#define V(code, type) \
6465
inline v8::Local<v8::Value> code(v8::Isolate* isolate, \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict';
2+
3+
// Flags: --experimental-vm-modules
4+
5+
require('../common');
6+
7+
const assert = require('assert');
8+
const { SourceTextModule } = require('vm');
9+
10+
{
11+
const m = new SourceTextModule('const a = 1');
12+
const cachedData = m.createCachedData();
13+
14+
new SourceTextModule('const a = 1', { cachedData });
15+
16+
assert.throws(() => {
17+
new SourceTextModule('differentSource', { cachedData });
18+
}, {
19+
code: 'ERR_VM_MODULE_CACHED_DATA_REJECTED',
20+
});
21+
}
22+
23+
assert.rejects(async () => {
24+
const m = new SourceTextModule('const a = 1');
25+
await m.link(() => {});
26+
m.evaluate();
27+
m.createCachedData();
28+
}, {
29+
code: 'ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA',
30+
});

0 commit comments

Comments
 (0)