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 committed Jan 24, 2016
1 parent 83e43fb commit 96934cb
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 @@ -175,6 +177,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 @@ -507,15 +509,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 @@ -525,6 +547,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 @@ -677,6 +712,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'
});
});

2 comments on commit 96934cb

@ehjoon
Copy link

@ehjoon ehjoon commented on 96934cb Jan 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this patch. I found strange thing. I load cached data from file and reuse it when node process restart. The problem is that the cache data which size is under 8K is always rejected with sanity check error 'MAGIC_NUMBER_MISMATCH'. cached_data_buf from GetCachedData is wrong completly. Thanks.

@bnoordhuis
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't reproduce what you're saying. Are you sure you're not mangling the data when writing it to disk, e.g., by passing an encoding to fs.writeFile()? If it's not that, can you open an issue and include a minimal test case?

Please sign in to comment.