diff --git a/src/binding.cc b/src/binding.cc index 2fefb8ca..291ed64f 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -36,6 +36,9 @@ static_assert(sizeof(v8::TryCatch) == sizeof(size_t) * 6, static_assert(sizeof(v8::Location) == sizeof(size_t) * 1, "Location size mismatch"); +static_assert(sizeof(v8::SnapshotCreator) == sizeof(size_t) * 1, + "SnapshotCreator size mismatch"); + extern "C" { void v8__V8__SetFlagsFromCommandLine(int* argc, char** argv) { @@ -111,6 +114,12 @@ void v8__Isolate__CreateParams__SET__array_buffer_allocator( self.array_buffer_allocator = value; } +// This function does not take ownership of the StartupData. +void v8__Isolate__CreateParams__SET__snapshot_blob( + v8::Isolate::CreateParams& self, v8::StartupData* snapshot_blob) { + self.snapshot_blob = snapshot_blob; +} + void v8__HandleScope__CONSTRUCT(uninit_t& buf, v8::Isolate* isolate) { construct_in_place(buf, isolate); @@ -325,7 +334,8 @@ size_t v8__ArrayBufferView__ByteOffset(v8::ArrayBufferView& self) { return self.ByteOffset(); } -size_t v8__ArrayBufferView__CopyContents(v8::ArrayBufferView& self, void* dest, int byte_length) { +size_t v8__ArrayBufferView__CopyContents(v8::ArrayBufferView& self, void* dest, + int byte_length) { return self.CopyContents(dest, byte_length); } @@ -626,6 +636,29 @@ void v8__PropertyCallbackInfo__GetReturnValue( *out = self.GetReturnValue(); } +void v8__SnapshotCreator__CONSTRUCT(uninit_t& buf) { + construct_in_place(buf); +} + +void v8__SnapshotCreator__DESTRUCT(v8::SnapshotCreator& self) { + self.~SnapshotCreator(); +} + +v8::Isolate* v8__SnapshotCreator__GetIsolate(v8::SnapshotCreator& self) { + return self.GetIsolate(); +} + +void v8__SnapshotCreator__SetDefaultContext(v8::SnapshotCreator& self, + v8::Local context) { + self.SetDefaultContext(context); +} + +v8::StartupData v8__SnapshotCreator__CreateBlob( + v8::SnapshotCreator* self, + v8::SnapshotCreator::FunctionCodeHandling function_code_handling) { + return self->CreateBlob(function_code_handling); +} + v8::Platform* v8__platform__NewDefaultPlatform() { // TODO: support optional arguments. return v8::platform::NewDefaultPlatform().release(); diff --git a/src/isolate.rs b/src/isolate.rs index 4bbd7279..21d09d0a 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -6,6 +6,7 @@ use crate::support::Opaque; use crate::support::UniqueRef; use crate::Local; use crate::Message; +use crate::StartupData; use crate::Value; use std::ops::Deref; use std::ops::DerefMut; @@ -44,7 +45,10 @@ extern "C" { this: &mut CreateParams, value: *mut Allocator, ); - + fn v8__Isolate__CreateParams__SET__snapshot_blob( + this: &mut CreateParams, + snapshot_blob: *mut StartupData, + ); } #[repr(C)] @@ -199,6 +203,25 @@ impl CreateParams { ) }; } + + /// Hand startup data to V8, in case the embedder has chosen to build + /// V8 with external startup data. + /// + /// Note: + /// - By default the startup data is linked into the V8 library, in which + /// case this function is not meaningful. + /// - If this needs to be called, it needs to be called before V8 + /// tries to make use of its built-ins. + /// - To avoid unnecessary copies of data, V8 will point directly into the + /// given data blob, so pretty please keep it around until V8 exit. + /// - Compression of the startup blob might be useful, but needs to + /// handled entirely on the embedders' side. + /// - The call will abort if the data is invalid. + pub fn set_snapshot_blob(&mut self, snapshot_blob: &mut StartupData) { + unsafe { + v8__Isolate__CreateParams__SET__snapshot_blob(self, &mut *snapshot_blob) + }; + } } impl Delete for CreateParams { diff --git a/src/lib.rs b/src/lib.rs index 6e493759..cc39140b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ mod promise; mod property; mod script; mod script_or_module; +mod snapshot; mod string; mod support; mod try_catch; @@ -68,6 +69,7 @@ pub use promise::{ pub use property::PropertyCallbackInfo; pub use script::{Script, ScriptOrigin}; pub use script_or_module::ScriptOrModule; +pub use snapshot::{FunctionCodeHandling, SnapshotCreator, StartupData}; pub use string::NewStringType; pub use string::String; pub use try_catch::{TryCatch, TryCatchScope}; diff --git a/src/snapshot.rs b/src/snapshot.rs new file mode 100644 index 00000000..ed63e150 --- /dev/null +++ b/src/snapshot.rs @@ -0,0 +1,83 @@ +use crate::support::int; +use crate::Context; +use crate::Isolate; +use crate::Local; +use std::mem::MaybeUninit; + +extern "C" { + fn v8__SnapshotCreator__CONSTRUCT(buf: &mut MaybeUninit); + fn v8__SnapshotCreator__DESTRUCT(this: &mut SnapshotCreator); + fn v8__SnapshotCreator__GetIsolate( + this: &mut SnapshotCreator, + ) -> &mut Isolate; + fn v8__SnapshotCreator__CreateBlob( + this: *mut SnapshotCreator, + function_code_handling: FunctionCodeHandling, + ) -> StartupData; + fn v8__SnapshotCreator__SetDefaultContext( + this: &mut SnapshotCreator, + context: *mut Context, + ); +} + +#[derive(Debug)] +#[repr(C)] +pub struct StartupData { + pub data: *const u8, + pub raw_size: int, +} + +#[repr(C)] +pub enum FunctionCodeHandling { + Clear, + Keep, +} + +/// Helper class to create a snapshot data blob. +#[repr(C)] +pub struct SnapshotCreator([usize; 1]); + +impl Default for SnapshotCreator { + /// Create and enter an isolate, and set it up for serialization. + /// The isolate is created from scratch. + fn default() -> Self { + let mut snapshot_creator: MaybeUninit = MaybeUninit::uninit(); + + unsafe { + v8__SnapshotCreator__CONSTRUCT(&mut snapshot_creator); + snapshot_creator.assume_init() + } + } +} + +impl Drop for SnapshotCreator { + fn drop(&mut self) { + unsafe { v8__SnapshotCreator__DESTRUCT(self) }; + } +} + +impl SnapshotCreator { + /// Set the default context to be included in the snapshot blob. + /// The snapshot will not contain the global proxy, and we expect one or a + /// global object template to create one, to be provided upon deserialization. + pub fn set_default_context<'sc>(&mut self, mut context: Local<'sc, Context>) { + unsafe { v8__SnapshotCreator__SetDefaultContext(self, &mut *context) }; + } + + /// Creates a snapshot data blob. + /// This must not be called from within a handle scope. + /// + /// Returns { nullptr, 0 } on failure, and a startup snapshot on success. + /// The caller acquires ownership of the data array in the return value. + pub fn create_blob( + &mut self, + function_code_handling: FunctionCodeHandling, + ) -> StartupData { + unsafe { v8__SnapshotCreator__CreateBlob(self, function_code_handling) } + } + + /// Returns the isolate prepared by the snapshot creator. + pub fn get_isolate(&mut self) -> &Isolate { + unsafe { v8__SnapshotCreator__GetIsolate(self) } + } +} diff --git a/tests/test_api.rs b/tests/test_api.rs index 11da729d..e02dd407 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -1028,3 +1028,55 @@ fn array_buffer_view() { isolate.exit(); drop(g); } + +#[test] +fn snapshot_creator() { + let g = setup(); + // First we create the snapshot, there is a single global variable 'a' set to + // the value 3. + let mut startup_data = { + let mut snapshot_creator = v8::SnapshotCreator::default(); + let isolate = snapshot_creator.get_isolate(); + let mut locker = v8::Locker::new(&isolate); + v8::HandleScope::enter(&mut locker, |scope| { + let mut context = v8::Context::new(scope); + context.enter(); + + let source = v8::String::new(scope, "a = 1 + 2").unwrap(); + let mut script = + v8::Script::compile(scope, context, source, None).unwrap(); + script.run(scope, context).unwrap(); + + snapshot_creator.set_default_context(context); + + context.exit(); + }); + + snapshot_creator.create_blob(v8::FunctionCodeHandling::Clear) + }; + assert!(startup_data.raw_size > 0); + // Now we try to load up the snapshot and check that 'a' has the correct + // value. + { + let mut params = v8::Isolate::create_params(); + params.set_array_buffer_allocator(v8::Allocator::new_default_allocator()); + params.set_snapshot_blob(&mut startup_data); + let isolate = v8::Isolate::new(params); + let mut locker = v8::Locker::new(&isolate); + v8::HandleScope::enter(&mut locker, |scope| { + let mut context = v8::Context::new(scope); + context.enter(); + let source = v8::String::new(scope, "a === 3").unwrap(); + let mut script = + v8::Script::compile(scope, context, source, None).unwrap(); + let result = script.run(scope, context).unwrap(); + let true_val: Local = cast(v8::new_true(scope)); + assert!(result.same_value(true_val)); + context.exit(); + }); + } + + // TODO(ry) startup_data is getting leaked and is not cleaned up properly! + // It must be freed using c++ delete. + drop(g); +}