Skip to content

Commit

Permalink
vm: introduce cachedData/produceCachedData
Browse files Browse the repository at this point in the history
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 <info@bnoordhuis.nl>
  • Loading branch information
indutny authored and rvagg committed Feb 9, 2016
1 parent 60d2048 commit d1cacb8
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 3 deletions.
6 changes: 6 additions & 0 deletions doc/api/vm.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ The options when creating a script are:
code are controlled by the options to the script's methods.
- `timeout`: a number of milliseconds to execute `code` before terminating
execution. If execution is terminated, an [`Error`][] will be thrown.
- `cachedData`: an optional `Buffer` with V8's code cache data for the supplied
source. When supplied `cachedDataRejected` value will be set to either
`true` or `false` depending on acceptance of the data by V8.
- `produceCachedData`: if `true` and no `cachedData` is present - a `Buffer`
with V8's code cache data will be produced and stored in `cachedData` property
of the returned `vm.Script` instance.

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

Expand Down
3 changes: 3 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ namespace node {
V(buffer_string, "buffer") \
V(bytes_string, "bytes") \
V(bytes_parsed_string, "bytesParsed") \
V(cached_data_string, "cachedData") \
V(cached_data_rejected_string, "cachedDataRejected") \
V(callback_string, "callback") \
V(change_string, "change") \
V(oncertcb_string, "oncertcb") \
Expand Down Expand Up @@ -174,6 +176,7 @@ namespace node {
V(preference_string, "preference") \
V(priority_string, "priority") \
V(processed_string, "processed") \
V(produce_cached_data_string, "produceCachedData") \
V(prototype_string, "prototype") \
V(raw_string, "raw") \
V(rdev_string, "rdev") \
Expand Down
78 changes: 75 additions & 3 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace node {

using v8::AccessType;
using v8::Array;
using v8::ArrayBuffer;
using v8::Boolean;
using v8::Context;
using v8::Debug;
Expand Down Expand Up @@ -40,6 +41,7 @@ using v8::ScriptCompiler;
using v8::ScriptOrigin;
using v8::String;
using v8::TryCatch;
using v8::Uint8Array;
using v8::UnboundScript;
using v8::V8;
using v8::Value;
Expand Down Expand Up @@ -496,15 +498,35 @@ class ContextifyScript : public BaseObject {
Local<Integer> lineOffset = GetLineOffsetArg(args, 1);
Local<Integer> columnOffset = GetColumnOffsetArg(args, 1);
bool display_errors = GetDisplayErrorsArg(args, 1);
MaybeLocal<Uint8Array> cached_data_buf = GetCachedData(env, args, 1);
bool produce_cached_data = GetProduceCachedData(env, args, 1);
if (try_catch.HasCaught()) {
try_catch.ReThrow();
return;
}

ScriptCompiler::CachedData* cached_data = nullptr;
if (!cached_data_buf.IsEmpty()) {
ArrayBuffer::Contents contents =
cached_data_buf.ToLocalChecked()->Buffer()->GetContents();
cached_data = new ScriptCompiler::CachedData(
static_cast<uint8_t*>(contents.Data()), contents.ByteLength());
}

ScriptOrigin origin(filename, lineOffset, columnOffset);
ScriptCompiler::Source source(code, origin);
Local<UnboundScript> v8_script =
ScriptCompiler::CompileUnbound(env->isolate(), &source);
ScriptCompiler::Source source(code, origin, cached_data);
ScriptCompiler::CompileOptions compile_options =
ScriptCompiler::kNoCompileOptions;

if (source.GetCachedData() != nullptr)
compile_options = ScriptCompiler::kConsumeCodeCache;
else if (produce_cached_data)
compile_options = ScriptCompiler::kProduceCodeCache;

Local<UnboundScript> v8_script = ScriptCompiler::CompileUnbound(
env->isolate(),
&source,
compile_options);

if (v8_script.IsEmpty()) {
if (display_errors) {
Expand All @@ -514,6 +536,19 @@ class ContextifyScript : public BaseObject {
return;
}
contextify_script->script_.Reset(env->isolate(), v8_script);

if (compile_options == ScriptCompiler::kConsumeCodeCache) {
args.This()->Set(
env->cached_data_rejected_string(),
Boolean::New(env->isolate(), source.GetCachedData()->rejected));
} else if (compile_options == ScriptCompiler::kProduceCodeCache) {
const ScriptCompiler::CachedData* cached_data = source.GetCachedData();
MaybeLocal<Object> buf = Buffer::Copy(
env,
reinterpret_cast<const char*>(cached_data->data),
cached_data->length);
args.This()->Set(env->cached_data_string(), buf.ToLocalChecked());
}
}


Expand Down Expand Up @@ -666,6 +701,43 @@ class ContextifyScript : public BaseObject {
}


static MaybeLocal<Uint8Array> GetCachedData(
Environment* env,
const FunctionCallbackInfo<Value>& args,
const int i) {
if (!args[i]->IsObject()) {
return MaybeLocal<Uint8Array>();
}
Local<Value> value = args[i].As<Object>()->Get(env->cached_data_string());
if (value->IsUndefined()) {
return MaybeLocal<Uint8Array>();
}

if (!value->IsUint8Array()) {
Environment::ThrowTypeError(
args.GetIsolate(),
"options.cachedData must be a Buffer instance");
return MaybeLocal<Uint8Array>();
}

return value.As<Uint8Array>();
}


static bool GetProduceCachedData(
Environment* env,
const FunctionCallbackInfo<Value>& args,
const int i) {
if (!args[i]->IsObject()) {
return false;
}
Local<Value> value =
args[i].As<Object>()->Get(env->produce_cached_data_string());

return value->IsTrue();
}


static Local<Integer> GetLineOffsetArg(
const FunctionCallbackInfo<Value>& args,
const int i) {
Expand Down
37 changes: 37 additions & 0 deletions test/parallel/test-vm-cached-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';
require('../common');
const assert = require('assert');
const vm = require('vm');
const Buffer = require('buffer').Buffer;

const originalSource = '(function bcd() { return \'original\'; })';

// It should produce code cache
const original = new vm.Script(originalSource, {
produceCachedData: true
});
assert(original.cachedData instanceof Buffer);

assert.equal(original.runInThisContext()(), 'original');

// It should consume code cache
const success = new vm.Script(originalSource, {
cachedData: original.cachedData
});
assert(!success.cachedDataRejected);

assert.equal(success.runInThisContext()(), 'original');

// It should reject invalid code cache
const reject = new vm.Script('(function abc() { return \'invalid\'; })', {
cachedData: original.cachedData
});
assert(reject.cachedDataRejected);
assert.equal(reject.runInThisContext()(), 'invalid');

// It should throw on non-Buffer cachedData
assert.throws(() => {
new vm.Script('function abc() {}', {
cachedData: 'ohai'
});
});

0 comments on commit d1cacb8

Please sign in to comment.