1
0
Fork 0
mirror of https://github.com/rubyjs/mini_racer synced 2023-03-27 23:21:28 -04:00

Merge pull request #21 from wk8/wk8/snapshots

Making it possible to use V8 snapshots
This commit is contained in:
Sam 2016-06-20 09:37:43 +10:00 committed by GitHub
commit bddbdcd284
3 changed files with 213 additions and 24 deletions

View file

@ -26,6 +26,11 @@ typedef struct {
bool interrupted;
} ContextInfo;
typedef struct {
const char* data;
int raw_size;
} SnapshotInfo;
typedef struct {
bool parsed;
bool executed;
@ -46,6 +51,7 @@ static VALUE rb_eScriptTerminatedError;
static VALUE rb_eParseError;
static VALUE rb_eScriptRuntimeError;
static VALUE rb_cJavaScriptFunction;
static VALUE rb_eSnapshotError;
static VALUE rb_cDateTime = Qnil;
@ -299,6 +305,91 @@ static void unblock_eval(void *ptr) {
eval->context_info->interrupted = true;
}
static VALUE rb_snapshot_size(VALUE self, VALUE str) {
SnapshotInfo* snapshot_info;
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
return INT2NUM(snapshot_info->raw_size);
}
static VALUE rb_snapshot_load(VALUE self, VALUE str) {
SnapshotInfo* snapshot_info;
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
init_v8();
StartupData startup_data = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
if (startup_data.data == NULL && startup_data.raw_size == 0) {
rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
}
snapshot_info->data = startup_data.data;
snapshot_info->raw_size = startup_data.raw_size;
return Qnil;
}
static VALUE rb_snapshot_warmup(VALUE self, VALUE str) {
SnapshotInfo* snapshot_info;
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
init_v8();
StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
StartupData warm_startup_data = V8::WarmUpSnapshotDataBlob(cold_startup_data, RSTRING_PTR(str));
if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
} else {
delete[] snapshot_info->data;
snapshot_info->data = warm_startup_data.data;
snapshot_info->raw_size = warm_startup_data.raw_size;
}
return Qnil;
}
static VALUE rb_context_init_with_snapshot(VALUE self, VALUE snapshot) {
ContextInfo* context_info;
Data_Get_Struct(self, ContextInfo, context_info);
init_v8();
context_info->allocator = new ArrayBufferAllocator();
context_info->interrupted = false;
Isolate::CreateParams create_params;
create_params.array_buffer_allocator = context_info->allocator;
StartupData startup_data;
if (!NIL_P(snapshot)) {
SnapshotInfo* snapshot_info;
Data_Get_Struct(snapshot, SnapshotInfo, snapshot_info);
startup_data = {snapshot_info->data, snapshot_info->raw_size};
create_params.snapshot_blob = &startup_data;
}
context_info->isolate = Isolate::New(create_params);
Locker lock(context_info->isolate);
Isolate::Scope isolate_scope(context_info->isolate);
HandleScope handle_scope(context_info->isolate);
Local<Context> context = Context::New(context_info->isolate);
context_info->context = new Persistent<Context>();
context_info->context->Reset(context_info->isolate, context);
if (Qnil == rb_cDateTime && rb_funcall(rb_cObject, rb_intern("const_defined?"), 1, rb_str_new2("DateTime")) == Qtrue)
{
rb_cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
}
return Qnil;
}
static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
@ -553,13 +644,18 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
void deallocate(void * data) {
ContextInfo* context_info = (ContextInfo*)data;
{
Locker lock(context_info->isolate);
if (context_info->isolate) {
Locker lock(context_info->isolate);
}
}
{
context_info->context->Reset();
delete context_info->context;
if (context_info->context) {
context_info->context->Reset();
delete context_info->context;
}
}
{
@ -578,39 +674,37 @@ void deallocate_external_function(void * data) {
xfree(data);
}
void deallocate_snapshot(void * data) {
SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
delete[] snapshot_info->data;
xfree(snapshot_info);
}
VALUE allocate_external_function(VALUE klass) {
VALUE* self = ALLOC(VALUE);
return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
}
VALUE allocate(VALUE klass) {
init_v8();
ContextInfo* context_info = ALLOC(ContextInfo);
context_info->allocator = new ArrayBufferAllocator();
context_info->allocator = NULL;
context_info->interrupted = false;
Isolate::CreateParams create_params;
create_params.array_buffer_allocator = context_info->allocator;
context_info->isolate = Isolate::New(create_params);
Locker lock(context_info->isolate);
Isolate::Scope isolate_scope(context_info->isolate);
HandleScope handle_scope(context_info->isolate);
Local<Context> context = Context::New(context_info->isolate);
context_info->context = new Persistent<Context>();
context_info->context->Reset(context_info->isolate, context);
if (Qnil == rb_cDateTime && rb_funcall(rb_cObject, rb_intern("const_defined?"), 1, rb_str_new2("DateTime")) == Qtrue)
{
rb_cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
}
context_info->isolate = NULL;
context_info->context = NULL;
return Data_Wrap_Struct(klass, NULL, deallocate, (void*)context_info);
}
VALUE allocate_snapshot(VALUE klass) {
SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
snapshot_info->data = NULL;
snapshot_info->raw_size = 0;
return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
}
static VALUE
rb_context_stop(VALUE self) {
ContextInfo* context_info;
@ -625,20 +719,28 @@ extern "C" {
{
VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
VALUE rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
VALUE rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eStandardError);
rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eStandardError);
VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
rb_define_alloc_func(rb_cContext, allocate);
rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 1);
rb_define_private_method(rb_cContext, "init_with_snapshot",(VALUE(*)(...))&rb_context_init_with_snapshot, 1);
rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
rb_define_method(rb_cSnapshot, "warmup", (VALUE(*)(...))&rb_snapshot_warmup, 1);
rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
}
}

View file

@ -7,6 +7,7 @@ module MiniRacer
class EvalError < StandardError; end
class ScriptTerminatedError < EvalError; end
class ParseError < EvalError; end
class SnapshotError < StandardError; end
class RuntimeError < EvalError
def initialize(message)
@ -80,9 +81,18 @@ module MiniRacer
@timeout = nil
@current_exception = nil
snapshot = nil
if options
@timeout = options[:timeout]
snapshot = options[:snapshot]
end
unless snapshot.nil? || snapshot.is_a?(Snapshot)
raise ArgumentError, "snapshot must be a MiniRacer::Snapshot object, passed a #{snapshot.inspect}"
end
# defined in the C class
init_with_snapshot(snapshot)
end
def load(filename)
@ -106,4 +116,11 @@ module MiniRacer
end
# `size` and `warmup` public methods are defined in the C class
class Snapshot
def initialize(str = '')
# defined in the C class
load(str)
end
end
end

View file

@ -287,4 +287,74 @@ raise FooError, "I like foos"
end
end
def test_it_can_use_snapshots
snapshot = MiniRacer::Snapshot.new('function hello() { return "world"; }; var foo = "bar";')
context = MiniRacer::Context.new(snapshot: snapshot)
assert_equal "world", context.eval("hello()")
assert_equal "bar", context.eval("foo")
end
def test_snapshot_size
snapshot = MiniRacer::Snapshot.new('var foo = "bar";')
# for some reason sizes seem to change across runs, so we just
# check it's a positive integer
assert(snapshot.size > 0)
end
def test_invalid_snapshots_throw_an_exception
assert_raises(MiniRacer::SnapshotError) do
MiniRacer::Snapshot.new('var foo = bar;')
end
end
def test_an_empty_snapshot_is_valid
MiniRacer::Snapshot.new('')
MiniRacer::Snapshot.new
end
def test_snapshots_can_be_warmed_up_with_no_side_effects
# shamelessly insipired by https://github.com/v8/v8/blob/5.3.254/test/cctest/test-serialize.cc#L792-L854
snapshot_source = <<-JS
function f() { return Math.sin(1); }
var a = 5;
JS
snapshot = MiniRacer::Snapshot.new(snapshot_source)
warmump_source = <<-JS
Math.tan(1);
var a = f();
Math.sin = 1;
JS
snapshot.warmup(warmump_source)
context = MiniRacer::Context.new(snapshot: snapshot)
assert_equal 5, context.eval("a")
assert_equal "function", context.eval("typeof(Math.sin)")
end
def test_invalid_warmup_sources_throw_an_exception
assert_raises(MiniRacer::SnapshotError) do
MiniRacer::Snapshot.new('Math.sin = 1;').warmup('var a = Math.sin(1);')
end
end
def test_warming_up_with_invalid_source_does_not_affect_the_snapshot_internal_state
snapshot = MiniRacer::Snapshot.new('Math.sin = 1;')
begin
snapshot.warmup('var a = Math.sin(1);')
rescue
# do nothing
end
context = MiniRacer::Context.new(snapshot: snapshot)
assert_equal 1, context.eval("Math.sin")
end
end