diff --git a/src/binding.cc b/src/binding.cc index e2013496..d5344cbe 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1670,6 +1670,13 @@ void v8__Context__SetPromiseHooks(v8::Context& self, v8::Function& init_hook, ptr_to_local(&after_hook), ptr_to_local(&resolve_hook)); } +const v8::Context* v8__Context__FromSnapshot(v8::Isolate* isolate, + size_t context_snapshot_index) { + v8::MaybeLocal maybe_local = + v8::Context::FromSnapshot(isolate, context_snapshot_index); + return maybe_local_to_ptr(maybe_local); +} + const v8::String* v8__Message__Get(const v8::Message& self) { return local_to_ptr(self.Get()); } @@ -2316,8 +2323,10 @@ bool v8__Proxy__IsRevoked(const v8::Proxy& self) { void v8__Proxy__Revoke(const v8::Proxy& self) { ptr_to_local(&self)->Revoke(); } void v8__SnapshotCreator__CONSTRUCT(uninit_t* buf, - const intptr_t* external_references) { - construct_in_place(buf, external_references); + const intptr_t* external_references, + v8::StartupData* existing_blob) { + construct_in_place(buf, external_references, + existing_blob); } void v8__SnapshotCreator__DESTRUCT(v8::SnapshotCreator* self) { @@ -2351,6 +2360,11 @@ void v8__SnapshotCreator__SetDefaultContext(v8::SnapshotCreator* self, self->SetDefaultContext(ptr_to_local(&context), SerializeInternalFields); } +size_t v8__SnapshotCreator__AddContext(v8::SnapshotCreator* self, + const v8::Context& context) { + return self->AddContext(ptr_to_local(&context), SerializeInternalFields); +} + size_t v8__SnapshotCreator__AddData_to_isolate(v8::SnapshotCreator* self, const v8::Data& data) { return self->AddData(ptr_to_local(&data)); diff --git a/src/context.rs b/src/context.rs index 37b65e25..4f230705 100644 --- a/src/context.rs +++ b/src/context.rs @@ -44,6 +44,10 @@ extern "C" { index: c_int, value: *mut c_void, ); + fn v8__Context__FromSnapshot( + isolate: *mut Isolate, + context_snapshot_index: usize, + ) -> *const Context; } impl Context { @@ -302,6 +306,20 @@ impl Context { }; } } + + /// Create a new context from a (non-default) context snapshot. There + /// is no way to provide a global object template since we do not create + /// a new global object from template, but we can reuse a global object. + pub fn from_snapshot<'s>( + scope: &mut HandleScope<'s, ()>, + context_snapshot_index: usize, + ) -> Option> { + unsafe { + scope.cast_local(|sd| { + v8__Context__FromSnapshot(sd.get_isolate_mut(), context_snapshot_index) + }) + } + } } struct ContextAnnex { diff --git a/src/isolate.rs b/src/isolate.rs index dc4811c6..0f1dd66c 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -7,6 +7,7 @@ use crate::isolate_create_params::CreateParams; use crate::promise::PromiseRejectMessage; use crate::scope::data::ScopeData; use crate::snapshot::SnapshotCreator; +use crate::support::Allocated; use crate::support::MapFnFrom; use crate::support::MapFnTo; use crate::support::Opaque; @@ -543,6 +544,17 @@ impl Isolate { SnapshotCreator::new(external_references) } + #[allow(clippy::new_ret_no_self)] + pub fn snapshot_creator_from_existing_snapshot( + existing_snapshot_blob: impl Allocated<[u8]>, + external_references: Option<&'static ExternalReferences>, + ) -> OwnedIsolate { + SnapshotCreator::from_existing_snapshot( + existing_snapshot_blob, + external_references, + ) + } + /// Initial configuration parameters for a new Isolate. #[inline(always)] pub fn create_params() -> CreateParams { @@ -1144,6 +1156,24 @@ impl Isolate { snapshot_creator.set_default_context(context); } + /// Add additional context to be included in the snapshot blob. + /// The snapshot will include the global proxy. + /// + /// Returns the index of the context in the snapshot blob. + /// + /// # Panics + /// + /// Panics if the isolate was not created using [`Isolate::snapshot_creator`] + #[inline(always)] + pub fn add_context(&mut self, context: Local) -> usize { + let snapshot_creator = self + .get_annex_mut() + .maybe_snapshot_creator + .as_mut() + .unwrap(); + snapshot_creator.add_context(context) + } + /// Attach arbitrary `v8::Data` to the isolate snapshot, which can be /// retrieved via `HandleScope::get_context_data_from_snapshot_once()` after /// deserialization. This data does not survive when a new snapshot is created @@ -1412,7 +1442,6 @@ impl OwnedIsolate { ) -> Option { let mut snapshot_creator = self.get_annex_mut().maybe_snapshot_creator.take().unwrap(); - ScopeData::get_root_mut(&mut self); unsafe { self.cxx_isolate.as_mut().clear_scope_and_annex() }; // The isolate is owned by the snapshot creator; we need to forget it // here as the snapshot creator will drop it when running the destructor. diff --git a/src/isolate_create_params.rs b/src/isolate_create_params.rs index c2408382..82e199a0 100644 --- a/src/isolate_create_params.rs +++ b/src/isolate_create_params.rs @@ -237,7 +237,7 @@ pub(crate) mod raw { } impl StartupData { - pub(super) fn boxed_header(data: &Allocation<[u8]>) -> Box { + pub(crate) fn boxed_header(data: &Allocation<[u8]>) -> Box { Box::new(Self { data: &data[0] as *const _ as *const char, raw_size: int::try_from(data.len()).unwrap(), diff --git a/src/snapshot.rs b/src/snapshot.rs index 007125bd..eb0928b8 100644 --- a/src/snapshot.rs +++ b/src/snapshot.rs @@ -1,8 +1,11 @@ use crate::external_references::ExternalReferences; +use crate::isolate_create_params::raw; use crate::scope::data::ScopeData; use crate::support::char; use crate::support::int; use crate::support::intptr_t; +use crate::support::Allocated; +use crate::support::Allocation; use crate::Context; use crate::Data; use crate::Isolate; @@ -13,11 +16,13 @@ use std::borrow::Borrow; use std::convert::TryFrom; use std::mem::MaybeUninit; use std::ops::Deref; +use std::ptr::null; extern "C" { fn v8__SnapshotCreator__CONSTRUCT( buf: *mut MaybeUninit, external_references: *const intptr_t, + existing_blob: *const raw::StartupData, ); fn v8__SnapshotCreator__DESTRUCT(this: *mut SnapshotCreator); fn v8__SnapshotCreator__GetIsolate( @@ -31,6 +36,10 @@ extern "C" { this: *mut SnapshotCreator, context: *const Context, ); + fn v8__SnapshotCreator__AddContext( + this: *mut SnapshotCreator, + context: *const Context, + ) -> usize; fn v8__SnapshotCreator__AddData_to_isolate( this: *mut SnapshotCreator, data: *const Data, @@ -52,6 +61,12 @@ pub struct StartupData { raw_size: int, } +impl Drop for StartupData { + fn drop(&mut self) { + unsafe { v8__StartupData__DESTRUCT(self) } + } +} + impl Deref for StartupData { type Target = [u8]; fn deref(&self) -> &Self::Target { @@ -73,12 +88,6 @@ impl Borrow<[u8]> for StartupData { } } -impl Drop for StartupData { - fn drop(&mut self) { - unsafe { v8__StartupData__DESTRUCT(self) } - } -} - #[repr(C)] #[derive(Debug)] pub enum FunctionCodeHandling { @@ -92,12 +101,34 @@ pub enum FunctionCodeHandling { pub(crate) struct SnapshotCreator([usize; 1]); impl SnapshotCreator { - /// Create and enter an isolate, and set it up for serialization. + /// Create an isolate, and set it up for serialization. /// The isolate is created from scratch. #[inline(always)] #[allow(clippy::new_ret_no_self)] pub(crate) fn new( external_references: Option<&'static ExternalReferences>, + ) -> OwnedIsolate { + Self::new_impl(external_references, None::<&[u8]>) + } + + /// Create an isolate, and set it up for serialization. + /// The isolate is created from scratch. + #[inline(always)] + #[allow(clippy::new_ret_no_self)] + pub(crate) fn from_existing_snapshot( + existing_snapshot_blob: impl Allocated<[u8]>, + external_references: Option<&'static ExternalReferences>, + ) -> OwnedIsolate { + Self::new_impl(external_references, Some(existing_snapshot_blob)) + } + + /// Create and enter an isolate, and set it up for serialization. + /// The isolate is created from scratch. + #[inline(always)] + #[allow(clippy::new_ret_no_self)] + fn new_impl( + external_references: Option<&'static ExternalReferences>, + existing_snapshot_blob: Option>, ) -> OwnedIsolate { let mut snapshot_creator: MaybeUninit = MaybeUninit::uninit(); let external_references_ptr = if let Some(er) = external_references { @@ -105,10 +136,24 @@ impl SnapshotCreator { } else { std::ptr::null() }; + + let snapshot_blob_ptr; + let snapshot_allocations; + if let Some(snapshot_blob) = existing_snapshot_blob { + let data = Allocation::of(snapshot_blob); + let header = Allocation::of(raw::StartupData::boxed_header(&data)); + snapshot_blob_ptr = &*header as *const _; + snapshot_allocations = Some((header, data)); + } else { + snapshot_blob_ptr = null(); + snapshot_allocations = None; + } + let snapshot_creator = unsafe { v8__SnapshotCreator__CONSTRUCT( &mut snapshot_creator, external_references_ptr, + snapshot_blob_ptr, ); snapshot_creator.assume_init() }; @@ -117,7 +162,7 @@ impl SnapshotCreator { unsafe { v8__SnapshotCreator__GetIsolate(&snapshot_creator) }; let mut owned_isolate = OwnedIsolate::new(isolate_ptr); ScopeData::new_root(&mut owned_isolate); - owned_isolate.create_annex(Box::new(())); + owned_isolate.create_annex(Box::new(snapshot_allocations)); owned_isolate.set_snapshot_creator(snapshot_creator); owned_isolate } @@ -138,6 +183,15 @@ impl SnapshotCreator { unsafe { v8__SnapshotCreator__SetDefaultContext(self, &*context) }; } + /// Add additional context to be included in the snapshot blob. + /// The snapshot will include the global proxy. + /// + /// Returns the index of the context in the snapshot blob. + #[inline(always)] + pub(crate) fn add_context(&mut self, context: Local) -> usize { + unsafe { v8__SnapshotCreator__AddContext(self, &*context) } + } + /// Attach arbitrary `v8::Data` to the isolate snapshot, which can be /// retrieved via `HandleScope::get_context_data_from_snapshot_once()` after /// deserialization. This data does not survive when a new snapshot is created diff --git a/tests/test_api.rs b/tests/test_api.rs index 8a6078c0..d4bb9170 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -3787,6 +3787,22 @@ fn snapshot_creator() { let context_data_index_2; let startup_data = { let mut snapshot_creator = v8::Isolate::snapshot_creator(None); + { + let scope = &mut v8::HandleScope::new(&mut snapshot_creator); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + eval(scope, "b = 2 + 3").unwrap(); + scope.set_default_context(context); + } + + snapshot_creator + .create_blob(v8::FunctionCodeHandling::Clear) + .unwrap() + }; + + let startup_data = { + let mut snapshot_creator = + v8::Isolate::snapshot_creator_from_existing_snapshot(startup_data, None); { // Check that the SnapshotCreator isolate has been set up correctly. let _ = snapshot_creator.thread_safe_handle(); @@ -3794,10 +3810,7 @@ fn snapshot_creator() { let scope = &mut v8::HandleScope::new(&mut snapshot_creator); let context = v8::Context::new(scope); let scope = &mut v8::ContextScope::new(scope, context); - - let source = v8::String::new(scope, "a = 1 + 2").unwrap(); - let script = v8::Script::compile(scope, source, None).unwrap(); - script.run(scope).unwrap(); + eval(scope, "a = 1 + 2").unwrap(); scope.set_default_context(context); @@ -3822,9 +3835,11 @@ fn snapshot_creator() { let scope = &mut v8::HandleScope::new(isolate); let context = v8::Context::new(scope); let scope = &mut v8::ContextScope::new(scope, context); - let source = v8::String::new(scope, "a === 3").unwrap(); - let script = v8::Script::compile(scope, source, None).unwrap(); - let result = script.run(scope).unwrap(); + let result = eval(scope, "a === 3").unwrap(); + let true_val = v8::Boolean::new(scope, true).into(); + assert!(result.same_value(true_val)); + + let result = eval(scope, "b === 5").unwrap(); let true_val = v8::Boolean::new(scope, true).into(); assert!(result.same_value(true_val)); @@ -3851,6 +3866,154 @@ fn snapshot_creator() { } } +#[test] +fn snapshot_creator_multiple_contexts() { + let _setup_guard = setup(); + let startup_data = { + let mut snapshot_creator = v8::Isolate::snapshot_creator(None); + { + let mut scope = v8::HandleScope::new(&mut snapshot_creator); + let context = v8::Context::new(&mut scope); + let scope = &mut v8::ContextScope::new(&mut scope, context); + eval(scope, "globalThis.__bootstrap = { defaultContextProp: 1};") + .unwrap(); + { + let value = + eval(scope, "globalThis.__bootstrap.defaultContextProp").unwrap(); + let one_val = v8::Number::new(scope, 1.0).into(); + assert!(value.same_value(one_val)); + } + scope.set_default_context(context); + } + { + let scope = &mut v8::HandleScope::new(&mut snapshot_creator); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + eval(scope, "globalThis.__bootstrap = { context0Prop: 2 };").unwrap(); + { + let value = eval(scope, "globalThis.__bootstrap.context0Prop").unwrap(); + let two_val = v8::Number::new(scope, 2.0).into(); + assert!(value.same_value(two_val)); + } + assert_eq!(0, scope.add_context(context)); + } + + snapshot_creator + .create_blob(v8::FunctionCodeHandling::Clear) + .unwrap() + }; + + let startup_data = { + let mut snapshot_creator = + v8::Isolate::snapshot_creator_from_existing_snapshot(startup_data, None); + { + let scope = &mut v8::HandleScope::new(&mut snapshot_creator); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + { + let value = + eval(scope, "globalThis.__bootstrap.defaultContextProp").unwrap(); + let one_val = v8::Number::new(scope, 1.0).into(); + assert!(value.same_value(one_val)); + } + { + let value = eval(scope, "globalThis.__bootstrap.context0Prop").unwrap(); + assert!(value.is_undefined()); + } + { + eval(scope, "globalThis.__bootstrap.defaultContextProp2 = 3;").unwrap(); + let value = + eval(scope, "globalThis.__bootstrap.defaultContextProp2").unwrap(); + let three_val = v8::Number::new(scope, 3.0).into(); + assert!(value.same_value(three_val)); + } + scope.set_default_context(context); + } + { + let scope = &mut v8::HandleScope::new(&mut snapshot_creator); + let context = v8::Context::from_snapshot(scope, 0).unwrap(); + let scope = &mut v8::ContextScope::new(scope, context); + { + let value = + eval(scope, "globalThis.__bootstrap.defaultContextProp").unwrap(); + assert!(value.is_undefined()); + } + { + let value = eval(scope, "globalThis.__bootstrap.context0Prop").unwrap(); + let two_val = v8::Number::new(scope, 2.0).into(); + assert!(value.same_value(two_val)); + } + { + eval(scope, "globalThis.__bootstrap.context0Prop2 = 4;").unwrap(); + let value = + eval(scope, "globalThis.__bootstrap.context0Prop2").unwrap(); + let four_val = v8::Number::new(scope, 4.0).into(); + assert!(value.same_value(four_val)); + } + assert_eq!(scope.add_context(context), 0); + } + snapshot_creator + .create_blob(v8::FunctionCodeHandling::Clear) + .unwrap() + }; + { + let params = v8::Isolate::create_params().snapshot_blob(startup_data); + let isolate = &mut v8::Isolate::new(params); + { + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + { + let value = eval(scope, "globalThis.__bootstrap.context0Prop").unwrap(); + assert!(value.is_undefined()); + } + { + let value = + eval(scope, "globalThis.__bootstrap.context0Prop2").unwrap(); + assert!(value.is_undefined()); + } + { + let value = + eval(scope, "globalThis.__bootstrap.defaultContextProp").unwrap(); + let one_val = v8::Number::new(scope, 1.0).into(); + assert!(value.same_value(one_val)); + } + { + let value = + eval(scope, "globalThis.__bootstrap.defaultContextProp2").unwrap(); + let three_val = v8::Number::new(scope, 3.0).into(); + assert!(value.same_value(three_val)); + } + } + { + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::from_snapshot(scope, 0).unwrap(); + let scope = &mut v8::ContextScope::new(scope, context); + { + let value = + eval(scope, "globalThis.__bootstrap.defaultContextProp").unwrap(); + assert!(value.is_undefined()); + } + { + let value = + eval(scope, "globalThis.__bootstrap.defaultContextProp2").unwrap(); + assert!(value.is_undefined()); + } + { + let value = eval(scope, "globalThis.__bootstrap.context0Prop").unwrap(); + let two_val = v8::Number::new(scope, 2.0).into(); + assert!(value.same_value(two_val)); + } + { + let value = + eval(scope, "globalThis.__bootstrap.context0Prop2").unwrap(); + let four_val = v8::Number::new(scope, 4.0).into(); + assert!(value.same_value(four_val)); + } + } + } +} + #[test] fn external_references() { let _setup_guard = setup(); @@ -5350,14 +5513,10 @@ fn module_snapshot() { let true_val = v8::Boolean::new(scope, true).into(); - let source = v8::String::new(scope, "a === 3").unwrap(); - let script = v8::Script::compile(scope, source, None).unwrap(); - let result = script.run(scope).unwrap(); + let result = eval(scope, "a === 3").unwrap(); assert!(result.same_value(true_val)); - let source = v8::String::new(scope, "b === 42").unwrap(); - let script = v8::Script::compile(scope, source, None).unwrap(); - let result = script.run(scope).unwrap(); + let result = eval(scope, "b === 42").unwrap(); assert!(result.same_value(true_val)); } }