From babe41a99013a592e174bd5819464c456e9eccbd Mon Sep 17 00:00:00 2001 From: Andreu Botella Date: Fri, 17 Sep 2021 18:02:40 +0200 Subject: [PATCH] feat: Add `v8::CompiledWasmModule` (#776) `v8::CompiledWasmModule` is a representation of a compiled WebAssembly module, which can be shared by multiple `v8::WasmModuleObject`s. Closes #759. --- src/binding.cc | 25 ++++++++++++++++ src/data.rs | 1 + src/lib.rs | 1 + src/wasm.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++ tests/test_api.rs | 62 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 165 insertions(+) diff --git a/src/binding.cc b/src/binding.cc index b0da6fe6..faaa8411 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -2747,3 +2747,28 @@ bool v8__ValueDeserializer__ReadRawBytes(v8::ValueDeserializer* self, return self->ReadRawBytes(length, data); } } // extern "C" + +// v8::CompiledWasmModule + +extern "C" { +const v8::WasmModuleObject* v8__WasmModuleObject__FromCompiledModule(v8::Isolate* isolate, + const v8::CompiledWasmModule* compiled_module) { + return maybe_local_to_ptr(v8::WasmModuleObject::FromCompiledModule(isolate, *compiled_module)); +} + +v8::CompiledWasmModule* v8__WasmModuleObject__GetCompiledModule(const v8::WasmModuleObject* self) { + v8::CompiledWasmModule cwm = ptr_to_local(self)->GetCompiledModule(); + return new v8::CompiledWasmModule(std::move(cwm)); +} + +const uint8_t* v8__CompiledWasmModule__GetWireBytesRef(v8::CompiledWasmModule* self, + size_t* length) { + v8::MemorySpan span = self->GetWireBytesRef(); + *length = span.size(); + return span.data(); +} + +void v8__CompiledWasmModule__DELETE(v8::CompiledWasmModule* self) { + delete self; +} +} // extern "C" \ No newline at end of file diff --git a/src/data.rs b/src/data.rs index 7006a6c5..cf86e096 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1381,6 +1381,7 @@ impl_partial_eq! { Value for SymbolObject use identity } impl_partial_eq! { Object for SymbolObject use identity } impl_partial_eq! { SymbolObject for SymbolObject use identity } +/// An instance of WebAssembly.Module. #[repr(C)] #[derive(Debug)] pub struct WasmModuleObject(Opaque); diff --git a/src/lib.rs b/src/lib.rs index 38a1c609..5b638045 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -141,6 +141,7 @@ pub use value_deserializer::ValueDeserializerImpl; pub use value_serializer::ValueSerializer; pub use value_serializer::ValueSerializerHelper; pub use value_serializer::ValueSerializerImpl; +pub use wasm::CompiledWasmModule; pub use wasm::WasmStreaming; // TODO(piscisaureus): Ideally this trait would not be exported. diff --git a/src/wasm.rs b/src/wasm.rs index 44ed1180..9ca47b6a 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -4,10 +4,12 @@ use crate::function::FunctionCallbackArguments; use crate::function::FunctionCallbackInfo; use crate::scope::CallbackScope; use crate::scope::HandleScope; +use crate::support::Opaque; use crate::support::UnitType; use crate::Isolate; use crate::Local; use crate::Value; +use crate::WasmModuleObject; use std::ptr::null; use std::ptr::null_mut; @@ -62,6 +64,66 @@ impl Drop for WasmStreaming { } } +impl WasmModuleObject { + /// Efficiently re-create a WasmModuleObject, without recompiling, from + /// a CompiledWasmModule. + pub fn from_compiled_module<'s>( + scope: &mut HandleScope<'s>, + compiled_module: &CompiledWasmModule, + ) -> Option> { + unsafe { + scope.cast_local(|sd| { + v8__WasmModuleObject__FromCompiledModule( + sd.get_isolate_ptr(), + compiled_module.0, + ) + }) + } + } + + /// Get the compiled module for this module object. The compiled module can be + /// shared by several module objects. + pub fn get_compiled_module(&self) -> CompiledWasmModule { + let ptr = unsafe { v8__WasmModuleObject__GetCompiledModule(self) }; + CompiledWasmModule(ptr) + } +} + +// Type-erased v8::CompiledWasmModule. We need this because the C++ +// v8::CompiledWasmModule must be destructed because its private fields hold +// pointers that must be freed, but v8::CompiledWasmModule itself doesn't have +// a destructor. Therefore, in order to avoid memory leaks, the Rust-side +// CompiledWasmModule must be a pointer to a C++ allocation of +// v8::CompiledWasmModule. +#[repr(C)] +struct InternalCompiledWasmModule(Opaque); + +/// Wrapper around a compiled WebAssembly module, which is potentially shared by +/// different WasmModuleObjects. +pub struct CompiledWasmModule(*mut InternalCompiledWasmModule); + +impl CompiledWasmModule { + /// Get the (wasm-encoded) wire bytes that were used to compile this module. + pub fn get_wire_bytes_ref(&self) -> &[u8] { + use std::convert::TryInto; + let mut len = 0isize; + unsafe { + let ptr = v8__CompiledWasmModule__GetWireBytesRef(self.0, &mut len); + std::slice::from_raw_parts(ptr, len.try_into().unwrap()) + } + } +} + +// TODO(andreubotella): Safety??? +unsafe impl Send for CompiledWasmModule {} +unsafe impl Sync for CompiledWasmModule {} + +impl Drop for CompiledWasmModule { + fn drop(&mut self) { + unsafe { v8__CompiledWasmModule__DELETE(self.0) } + } +} + pub(crate) fn trampoline() -> extern "C" fn(*const FunctionCallbackInfo) where F: UnitType + Fn(&mut HandleScope, Local, WasmStreaming), @@ -102,4 +164,18 @@ extern "C" { this: *mut WasmStreamingSharedPtr, exception: *const Value, ); + + fn v8__WasmModuleObject__FromCompiledModule( + isolate: *mut Isolate, + compiled_module: *const InternalCompiledWasmModule, + ) -> *const WasmModuleObject; + fn v8__WasmModuleObject__GetCompiledModule( + this: *const WasmModuleObject, + ) -> *mut InternalCompiledWasmModule; + + fn v8__CompiledWasmModule__GetWireBytesRef( + this: *mut InternalCompiledWasmModule, + length: *mut isize, + ) -> *const u8; + fn v8__CompiledWasmModule__DELETE(this: *mut InternalCompiledWasmModule); } diff --git a/tests/test_api.rs b/tests/test_api.rs index 01dba2a0..ac135fca 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -5408,3 +5408,65 @@ fn counter_lookup_callback() { assert_ne!(count, 0); } + +#[test] +fn compiled_wasm_module() { + let _setup_guard = setup(); + + let compiled_module = { + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + let module: v8::Local = eval( + scope, + r#" + new WebAssembly.Module(Uint8Array.from([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x07, 0x03, 0x66, 0x6F, 0x6F, 0x62, 0x61, 0x72 + ])); + "#, + ) + .unwrap() + .try_into() + .unwrap(); + + module.get_compiled_module() + }; + + assert_eq!( + compiled_module.get_wire_bytes_ref(), + &[ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 0x03, 0x66, + 0x6F, 0x6F, 0x62, 0x61, 0x72 + ] + ); + + { + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + let global = context.global(scope); + + let module = + v8::WasmModuleObject::from_compiled_module(scope, &compiled_module) + .unwrap(); + + let key = v8::String::new(scope, "module").unwrap().into(); + global.set(scope, key, module.into()); + + let foo_ab: v8::Local = + eval(scope, "WebAssembly.Module.customSections(module, 'foo')[0]") + .unwrap() + .try_into() + .unwrap(); + let foo_bs = foo_ab.get_backing_store(); + let foo_section = unsafe { + std::slice::from_raw_parts(foo_bs.data() as *mut u8, foo_bs.byte_length()) + }; + assert_eq!(foo_section, b"bar"); + } +}