diff --git a/src/binding.cc b/src/binding.cc index 322b16ef..1049682a 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -64,6 +64,30 @@ static_assert(sizeof(v8::CFunction) == sizeof(size_t) * 2, static_assert(sizeof(three_pointers_t) == sizeof(v8_inspector::StringView), "StringView size mismatch"); +#if INTPTR_MAX == INT64_MAX // 64-bit platforms +static_assert(sizeof(v8::ScriptCompiler::CachedData) == 24, + "CachedData size mismatch"); +static_assert(offsetof(v8::ScriptCompiler::CachedData, data) == 0, + "CachedData.data offset mismatch"); +static_assert(offsetof(v8::ScriptCompiler::CachedData, length) == 8, + "CachedData.length offset mismatch"); +static_assert(offsetof(v8::ScriptCompiler::CachedData, rejected) == 12, + "CachedData.rejected offset mismatch"); +static_assert(offsetof(v8::ScriptCompiler::CachedData, buffer_policy) == 16, + "CachedData.buffer_policy offset mismatch"); +#else +static_assert(sizeof(v8::ScriptCompiler::CachedData) == 16, + "CachedData size mismatch"); +static_assert(offsetof(v8::ScriptCompiler::CachedData, data) == 0, + "CachedData.data offset mismatch"); +static_assert(offsetof(v8::ScriptCompiler::CachedData, length) == 4, + "CachedData.length offset mismatch"); +static_assert(offsetof(v8::ScriptCompiler::CachedData, rejected) == 8, + "CachedData.rejected offset mismatch"); +static_assert(offsetof(v8::ScriptCompiler::CachedData, buffer_policy) == 12, + "CachedData.buffer_policy offset mismatch"); +#endif + enum InternalSlots { kSlotDynamicImport = 0, kNumInternalSlots, @@ -329,15 +353,32 @@ void v8__Global__Reset(const v8::Data* data) { void v8__ScriptCompiler__Source__CONSTRUCT( uninit_t* buf, const v8::String& source_string, - const v8::ScriptOrigin& origin) { + const v8::ScriptOrigin& origin, + v8::ScriptCompiler::CachedData* cached_data) { construct_in_place( - buf, ptr_to_local(&source_string), origin); + buf, ptr_to_local(&source_string), origin, cached_data); } void v8__ScriptCompiler__Source__DESTRUCT(v8::ScriptCompiler::Source* self) { self->~Source(); } +v8::ScriptCompiler::CachedData* v8__ScriptCompiler__CachedData__NEW( + const uint8_t* data, int length) { + return new v8::ScriptCompiler::CachedData( + data, length, v8::ScriptCompiler::CachedData::BufferNotOwned); +} + +void v8__ScriptCompiler__CachedData__DELETE( + v8::ScriptCompiler::CachedData* self) { + delete self; +} + +const v8::ScriptCompiler::CachedData* v8__ScriptCompiler__Source__GetCachedData( + const v8::ScriptCompiler::Source* source) { + return source->GetCachedData(); +} + const v8::Module* v8__ScriptCompiler__CompileModule( v8::Isolate* isolate, v8::ScriptCompiler::Source* source, v8::ScriptCompiler::CompileOptions options, @@ -1573,6 +1614,17 @@ const v8::Script* v8__UnboundScript__BindToCurrentContext( return local_to_ptr(ptr_to_local(&unbound_script)->BindToCurrentContext()); } +v8::ScriptCompiler::CachedData* v8__UnboundScript__CreateCodeCache( + const v8::UnboundScript& unbound_script) { + return v8::ScriptCompiler::CreateCodeCache(ptr_to_local(&unbound_script)); +} + +v8::ScriptCompiler::CachedData* v8__UnboundModuleScript__CreateCodeCache( + const v8::UnboundModuleScript& unbound_module_script) { + return v8::ScriptCompiler::CreateCodeCache( + ptr_to_local(&unbound_module_script)); +} + const v8::Value* v8__Script__Run(const v8::Script& script, const v8::Context& context) { return maybe_local_to_ptr(ptr_to_local(&script)->Run(ptr_to_local(&context))); @@ -2119,7 +2171,13 @@ MaybeBool v8__Module__SetSyntheticModuleExport(const v8::Module& self, isolate, ptr_to_local(export_name), ptr_to_local(export_value))); } -const v8::String* v8__ModuleRequest__GetSpecifier(const v8::ModuleRequest& self) { +const v8::UnboundModuleScript* v8__Module__GetUnboundModuleScript( + const v8::Module& self) { + return local_to_ptr(ptr_to_local(&self)->GetUnboundModuleScript()); +} + +const v8::String* v8__ModuleRequest__GetSpecifier( + const v8::ModuleRequest& self) { return local_to_ptr(self.GetSpecifier()); } @@ -2127,7 +2185,8 @@ int v8__ModuleRequest__GetSourceOffset(const v8::ModuleRequest& self) { return self.GetSourceOffset(); } -const v8::FixedArray* v8__ModuleRequest__GetImportAssertions(const v8::ModuleRequest& self) { +const v8::FixedArray* v8__ModuleRequest__GetImportAssertions( + const v8::ModuleRequest& self) { return local_to_ptr(self.GetImportAssertions()); } diff --git a/src/lib.rs b/src/lib.rs index 7a8c484a..c0b1b146 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,7 @@ mod support; mod symbol; mod template; mod typed_array; +mod unbound_module_script; mod unbound_script; mod value; mod value_deserializer; @@ -125,6 +126,7 @@ pub use scope::EscapableHandleScope; pub use scope::HandleScope; pub use scope::TryCatch; pub use script::ScriptOrigin; +pub use script_compiler::CachedData; pub use snapshot::FunctionCodeHandling; pub use snapshot::SnapshotCreator; pub use snapshot::StartupData; diff --git a/src/module.rs b/src/module.rs index cbe5fac2..995804cc 100644 --- a/src/module.rs +++ b/src/module.rs @@ -18,6 +18,7 @@ use crate::Local; use crate::Module; use crate::ModuleRequest; use crate::String; +use crate::UnboundModuleScript; use crate::Value; /// Called during Module::instantiate_module. Provided with arguments: @@ -177,6 +178,9 @@ extern "C" { export_name: *const String, export_value: *const Value, ) -> MaybeBool; + fn v8__Module__GetUnboundModuleScript( + this: *const Module, + ) -> *const UnboundModuleScript; fn v8__Location__GetLineNumber(this: *const Location) -> int; fn v8__Location__GetColumnNumber(this: *const Location) -> int; fn v8__ModuleRequest__GetSpecifier( @@ -422,6 +426,17 @@ impl Module { } .into() } + + pub fn get_unbound_module_script<'s>( + &self, + scope: &mut HandleScope<'s>, + ) -> Local<'s, UnboundModuleScript> { + unsafe { + scope + .cast_local(|_| v8__Module__GetUnboundModuleScript(self)) + .unwrap() + } + } } impl Hash for Module { diff --git a/src/script_compiler.rs b/src/script_compiler.rs index a7162d9c..7b78da79 100644 --- a/src/script_compiler.rs +++ b/src/script_compiler.rs @@ -1,21 +1,29 @@ // Copyright 2019-2021 the Deno authors. All rights reserved. MIT license. -use std::mem::MaybeUninit; +use std::{marker::PhantomData, mem::MaybeUninit}; -use crate::HandleScope; use crate::Isolate; use crate::Local; use crate::Module; use crate::ScriptOrigin; use crate::String; +use crate::{HandleScope, UniqueRef}; extern "C" { fn v8__ScriptCompiler__Source__CONSTRUCT( buf: *mut MaybeUninit, source_string: *const String, origin: *const ScriptOrigin, + cached_data: *mut CachedData, ); fn v8__ScriptCompiler__Source__DESTRUCT(this: *mut Source); - + fn v8__ScriptCompiler__Source__GetCachedData<'a>( + this: *const Source, + ) -> *const CachedData<'a>; + fn v8__ScriptCompiler__CachedData__NEW<'a>( + data: *const u8, + length: i32, + ) -> *mut CachedData<'a>; + fn v8__ScriptCompiler__CachedData__DELETE<'a>(this: *mut CachedData<'a>); fn v8__ScriptCompiler__CompileModule( isolate: *mut Isolate, source: *mut Source, @@ -29,15 +37,87 @@ extern "C" { #[derive(Debug)] pub struct Source([usize; 8]); +/// Compilation data that the embedder can cache and pass back to speed up future +/// compilations. The data is produced if the CompilerOptions passed to the compilation +/// functions in ScriptCompiler contains produce_data_to_cache = true. The data to cache +/// can then can be retrieved from UnboundScript. +#[repr(C)] +#[derive(Debug)] +pub struct CachedData<'a> { + data: *const u8, + length: i32, + rejected: bool, + buffer_policy: BufferPolicy, + _phantom: PhantomData<&'a ()>, +} + +impl<'a> Drop for CachedData<'a> { + fn drop(&mut self) { + unsafe { + v8__ScriptCompiler__CachedData__DELETE(self); + } + } +} + +impl<'a> CachedData<'a> { + pub fn new(data: &'a [u8]) -> UniqueRef { + unsafe { + UniqueRef::from_raw(v8__ScriptCompiler__CachedData__NEW( + data.as_ptr(), + data.len() as i32, + )) + } + } +} + +impl<'a> std::ops::Deref for CachedData<'a> { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + unsafe { std::slice::from_raw_parts(self.data, self.length as usize) } + } +} + +#[repr(C)] +#[derive(Debug)] +enum BufferPolicy { + BufferNotOwned = 0, + BufferOwned, +} + impl Source { - // TODO(ry) cached_data pub fn new(source_string: Local, origin: &ScriptOrigin) -> Self { let mut buf = MaybeUninit::::uninit(); unsafe { - v8__ScriptCompiler__Source__CONSTRUCT(&mut buf, &*source_string, origin); + v8__ScriptCompiler__Source__CONSTRUCT( + &mut buf, + &*source_string, + origin, + std::ptr::null_mut(), + ); buf.assume_init() } } + + pub fn new_with_cached_data( + source_string: Local, + origin: &ScriptOrigin, + cached_data: UniqueRef, + ) -> Self { + let mut buf = MaybeUninit::::uninit(); + unsafe { + v8__ScriptCompiler__Source__CONSTRUCT( + &mut buf, + &*source_string, + origin, + cached_data.into_raw(), // Source constructor takes ownership. + ); + buf.assume_init() + } + } + + pub fn get_cached_data(&self) -> &CachedData { + unsafe { &*v8__ScriptCompiler__Source__GetCachedData(self) } + } } impl Drop for Source { diff --git a/src/support.rs b/src/support.rs index 969d7b5e..306f402f 100644 --- a/src/support.rs +++ b/src/support.rs @@ -106,12 +106,12 @@ impl From> for UniquePtr { pub struct UniqueRef(NonNull); impl UniqueRef { - unsafe fn try_from_raw(ptr: *mut T) -> Option { + pub(crate) unsafe fn try_from_raw(ptr: *mut T) -> Option { assert_unique_ptr_layout_compatible::(); NonNull::new(ptr).map(Self) } - pub unsafe fn from_raw(ptr: *mut T) -> Self { + pub(crate) unsafe fn from_raw(ptr: *mut T) -> Self { assert_unique_ptr_layout_compatible::(); Self::try_from_raw(ptr).unwrap() } diff --git a/src/unbound_module_script.rs b/src/unbound_module_script.rs new file mode 100644 index 00000000..55bf93d3 --- /dev/null +++ b/src/unbound_module_script.rs @@ -0,0 +1,20 @@ +use crate::CachedData; +use crate::UnboundModuleScript; +use crate::UniqueRef; + +extern "C" { + fn v8__UnboundModuleScript__CreateCodeCache( + script: *const UnboundModuleScript, + ) -> *mut CachedData<'static>; +} + +impl UnboundModuleScript { + /// Creates and returns code cache for the specified unbound_module_script. + /// This will return nullptr if the script cannot be serialized. The + /// CachedData returned by this function should be owned by the caller. + pub fn create_code_cache(&self) -> Option>> { + unsafe { + UniqueRef::try_from_raw(v8__UnboundModuleScript__CreateCodeCache(self)) + } + } +} diff --git a/src/unbound_script.rs b/src/unbound_script.rs index 1f453209..d34f4887 100644 --- a/src/unbound_script.rs +++ b/src/unbound_script.rs @@ -1,12 +1,16 @@ -use crate::HandleScope; +use crate::CachedData; use crate::Local; use crate::Script; use crate::UnboundScript; +use crate::{HandleScope, UniqueRef}; extern "C" { fn v8__UnboundScript__BindToCurrentContext( script: *const UnboundScript, ) -> *const Script; + fn v8__UnboundScript__CreateCodeCache( + script: *const UnboundScript, + ) -> *mut CachedData<'static>; } impl UnboundScript { @@ -20,4 +24,11 @@ impl UnboundScript { } .unwrap() } + + /// Creates and returns code cache for the specified unbound_script. + /// This will return nullptr if the script cannot be serialized. The + /// CachedData returned by this function should be owned by the caller. + pub fn create_code_cache(&self) -> Option>> { + unsafe { UniqueRef::try_from_raw(v8__UnboundScript__CreateCodeCache(self)) } + } } diff --git a/tests/test_api.rs b/tests/test_api.rs index 365a75e2..81ee9b6a 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -4782,3 +4782,115 @@ fn icu_collator() { let script = v8::Script::compile(scope, source, None).unwrap(); assert!(script.run(scope).is_some()); } + +fn create_module<'s>( + scope: &mut v8::HandleScope<'s, v8::Context>, + source: &str, + code_cache: Option>, + options: v8::script_compiler::CompileOptions, +) -> v8::Local<'s, v8::Module> { + let source = v8::String::new(scope, source).unwrap(); + let resource_name = v8::String::new(scope, "").unwrap(); + let source_map_url = v8::undefined(scope); + let script_origin = v8::ScriptOrigin::new( + scope, + resource_name.into(), + 0, + 0, + false, + 0, + source_map_url.into(), + false, + false, + true, + ); + let source = match code_cache { + Some(x) => v8::script_compiler::Source::new_with_cached_data( + source, + &script_origin, + x, + ), + None => v8::script_compiler::Source::new(source, &script_origin), + }; + let module = v8::script_compiler::compile_module2( + scope, + source, + options, + v8::script_compiler::NoCacheReason::NoReason, + ) + .unwrap(); + module +} + +fn create_unbound_module_script<'s>( + scope: &mut v8::HandleScope<'s, v8::Context>, + source: &str, + code_cache: Option>, +) -> v8::Local<'s, v8::UnboundModuleScript> { + let module = create_module( + scope, + source, + code_cache, + v8::script_compiler::CompileOptions::NoCompileOptions, + ); + module.get_unbound_module_script(scope) +} + +#[test] +fn unbound_module_script_conversion() { + let _setup_guard = setup(); + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let mut scope = v8::ContextScope::new(scope, context); + create_unbound_module_script(&mut scope, "'Hello ' + value", None); +} + +#[test] +fn code_cache() { + fn resolve_callback<'a>( + _context: v8::Local<'a, v8::Context>, + _specifier: v8::Local<'a, v8::String>, + _import_assertions: v8::Local<'a, v8::FixedArray>, + _referrer: v8::Local<'a, v8::Module>, + ) -> Option> { + None + } + + const CODE: &str = "export const hello = 'world';"; + let _setup_guard = setup(); + + let code_cache = { + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let mut scope = v8::ContextScope::new(scope, context); + let unbound_module_script = + create_unbound_module_script(&mut scope, CODE, None); + unbound_module_script.create_code_cache().unwrap().to_vec() + }; + + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let mut scope = v8::ContextScope::new(scope, context); + let module = create_module( + &mut scope, + CODE, + Some(v8::CachedData::new(&code_cache)), + v8::script_compiler::CompileOptions::ConsumeCodeCache, + ); + let mut scope = v8::HandleScope::new(&mut scope); + module + .instantiate_module(&mut scope, resolve_callback) + .unwrap(); + module.evaluate(&mut scope).unwrap(); + let top = + v8::Local::::try_from(module.get_module_namespace()).unwrap(); + + let key = v8::String::new(&mut scope, "hello").unwrap(); + let value = + v8::Local::::try_from(top.get(&mut scope, key.into()).unwrap()) + .unwrap(); + assert_eq!(&value.to_rust_string_lossy(&mut scope), "world"); +}