Skip to content

Commit 96934cb

Browse files
committed
vm: introduce cachedData/produceCachedData
Introduce `cachedData`/`produceCachedData` options for `v8.Script`. Could be used to consume/produce V8's code cache for speeding up compilation of known code. PR-URL: #4777 Reviewed-By: Ben Noordhuis <[email protected]>
1 parent 83e43fb commit 96934cb

File tree

4 files changed

+121
-3
lines changed

4 files changed

+121
-3
lines changed

doc/api/vm.markdown

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ The options when creating a script are:
3838
code are controlled by the options to the script's methods.
3939
- `timeout`: a number of milliseconds to execute `code` before terminating
4040
execution. If execution is terminated, an [`Error`][] will be thrown.
41+
- `cachedData`: an optional `Buffer` with V8's code cache data for the supplied
42+
source. When supplied `cachedDataRejected` value will be set to either
43+
`true` or `false` depending on acceptance of the data by V8.
44+
- `produceCachedData`: if `true` and no `cachedData` is present - a `Buffer`
45+
with V8's code cache data will be produced and stored in `cachedData` property
46+
of the returned `vm.Script` instance.
4147

4248
### script.runInContext(contextifiedSandbox[, options])
4349

src/env.h

+3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ namespace node {
6161
V(buffer_string, "buffer") \
6262
V(bytes_string, "bytes") \
6363
V(bytes_parsed_string, "bytesParsed") \
64+
V(cached_data_string, "cachedData") \
65+
V(cached_data_rejected_string, "cachedDataRejected") \
6466
V(callback_string, "callback") \
6567
V(change_string, "change") \
6668
V(oncertcb_string, "oncertcb") \
@@ -175,6 +177,7 @@ namespace node {
175177
V(preference_string, "preference") \
176178
V(priority_string, "priority") \
177179
V(processed_string, "processed") \
180+
V(produce_cached_data_string, "produceCachedData") \
178181
V(prototype_string, "prototype") \
179182
V(raw_string, "raw") \
180183
V(rdev_string, "rdev") \

src/node_contextify.cc

+75-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ namespace node {
1313

1414
using v8::AccessType;
1515
using v8::Array;
16+
using v8::ArrayBuffer;
1617
using v8::Boolean;
1718
using v8::Context;
1819
using v8::Debug;
@@ -40,6 +41,7 @@ using v8::ScriptCompiler;
4041
using v8::ScriptOrigin;
4142
using v8::String;
4243
using v8::TryCatch;
44+
using v8::Uint8Array;
4345
using v8::UnboundScript;
4446
using v8::V8;
4547
using v8::Value;
@@ -507,15 +509,35 @@ class ContextifyScript : public BaseObject {
507509
Local<Integer> lineOffset = GetLineOffsetArg(args, 1);
508510
Local<Integer> columnOffset = GetColumnOffsetArg(args, 1);
509511
bool display_errors = GetDisplayErrorsArg(args, 1);
512+
MaybeLocal<Uint8Array> cached_data_buf = GetCachedData(env, args, 1);
513+
bool produce_cached_data = GetProduceCachedData(env, args, 1);
510514
if (try_catch.HasCaught()) {
511515
try_catch.ReThrow();
512516
return;
513517
}
514518

519+
ScriptCompiler::CachedData* cached_data = nullptr;
520+
if (!cached_data_buf.IsEmpty()) {
521+
ArrayBuffer::Contents contents =
522+
cached_data_buf.ToLocalChecked()->Buffer()->GetContents();
523+
cached_data = new ScriptCompiler::CachedData(
524+
static_cast<uint8_t*>(contents.Data()), contents.ByteLength());
525+
}
526+
515527
ScriptOrigin origin(filename, lineOffset, columnOffset);
516-
ScriptCompiler::Source source(code, origin);
517-
Local<UnboundScript> v8_script =
518-
ScriptCompiler::CompileUnbound(env->isolate(), &source);
528+
ScriptCompiler::Source source(code, origin, cached_data);
529+
ScriptCompiler::CompileOptions compile_options =
530+
ScriptCompiler::kNoCompileOptions;
531+
532+
if (source.GetCachedData() != nullptr)
533+
compile_options = ScriptCompiler::kConsumeCodeCache;
534+
else if (produce_cached_data)
535+
compile_options = ScriptCompiler::kProduceCodeCache;
536+
537+
Local<UnboundScript> v8_script = ScriptCompiler::CompileUnbound(
538+
env->isolate(),
539+
&source,
540+
compile_options);
519541

520542
if (v8_script.IsEmpty()) {
521543
if (display_errors) {
@@ -525,6 +547,19 @@ class ContextifyScript : public BaseObject {
525547
return;
526548
}
527549
contextify_script->script_.Reset(env->isolate(), v8_script);
550+
551+
if (compile_options == ScriptCompiler::kConsumeCodeCache) {
552+
args.This()->Set(
553+
env->cached_data_rejected_string(),
554+
Boolean::New(env->isolate(), source.GetCachedData()->rejected));
555+
} else if (compile_options == ScriptCompiler::kProduceCodeCache) {
556+
const ScriptCompiler::CachedData* cached_data = source.GetCachedData();
557+
MaybeLocal<Object> buf = Buffer::Copy(
558+
env,
559+
reinterpret_cast<const char*>(cached_data->data),
560+
cached_data->length);
561+
args.This()->Set(env->cached_data_string(), buf.ToLocalChecked());
562+
}
528563
}
529564

530565

@@ -677,6 +712,43 @@ class ContextifyScript : public BaseObject {
677712
}
678713

679714

715+
static MaybeLocal<Uint8Array> GetCachedData(
716+
Environment* env,
717+
const FunctionCallbackInfo<Value>& args,
718+
const int i) {
719+
if (!args[i]->IsObject()) {
720+
return MaybeLocal<Uint8Array>();
721+
}
722+
Local<Value> value = args[i].As<Object>()->Get(env->cached_data_string());
723+
if (value->IsUndefined()) {
724+
return MaybeLocal<Uint8Array>();
725+
}
726+
727+
if (!value->IsUint8Array()) {
728+
Environment::ThrowTypeError(
729+
args.GetIsolate(),
730+
"options.cachedData must be a Buffer instance");
731+
return MaybeLocal<Uint8Array>();
732+
}
733+
734+
return value.As<Uint8Array>();
735+
}
736+
737+
738+
static bool GetProduceCachedData(
739+
Environment* env,
740+
const FunctionCallbackInfo<Value>& args,
741+
const int i) {
742+
if (!args[i]->IsObject()) {
743+
return false;
744+
}
745+
Local<Value> value =
746+
args[i].As<Object>()->Get(env->produce_cached_data_string());
747+
748+
return value->IsTrue();
749+
}
750+
751+
680752
static Local<Integer> GetLineOffsetArg(
681753
const FunctionCallbackInfo<Value>& args,
682754
const int i) {

test/parallel/test-vm-cached-data.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const vm = require('vm');
5+
const Buffer = require('buffer').Buffer;
6+
7+
const originalSource = '(function bcd() { return \'original\'; })';
8+
9+
// It should produce code cache
10+
const original = new vm.Script(originalSource, {
11+
produceCachedData: true
12+
});
13+
assert(original.cachedData instanceof Buffer);
14+
15+
assert.equal(original.runInThisContext()(), 'original');
16+
17+
// It should consume code cache
18+
const success = new vm.Script(originalSource, {
19+
cachedData: original.cachedData
20+
});
21+
assert(!success.cachedDataRejected);
22+
23+
assert.equal(success.runInThisContext()(), 'original');
24+
25+
// It should reject invalid code cache
26+
const reject = new vm.Script('(function abc() { return \'invalid\'; })', {
27+
cachedData: original.cachedData
28+
});
29+
assert(reject.cachedDataRejected);
30+
assert.equal(reject.runInThisContext()(), 'invalid');
31+
32+
// It should throw on non-Buffer cachedData
33+
assert.throws(() => {
34+
new vm.Script('function abc() {}', {
35+
cachedData: 'ohai'
36+
});
37+
});

0 commit comments

Comments
 (0)