Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions src/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3791,6 +3791,80 @@ uint32_t v8__ValueDeserializer__GetWireFormatVersion(
}
} // extern "C"

// v8::WasmModuleCompilation

extern "C" {

v8::WasmModuleCompilation* v8__WasmModuleCompilation__NEW() {
return new v8::WasmModuleCompilation();
}

void v8__WasmModuleCompilation__DELETE(v8::WasmModuleCompilation* self) {
delete self;
}

void v8__WasmModuleCompilation__OnBytesReceived(v8::WasmModuleCompilation* self,
const uint8_t* bytes,
size_t size) {
self->OnBytesReceived(bytes, size);
}

void v8__WasmModuleCompilation__Finish(
v8::WasmModuleCompilation* self, v8::Isolate* isolate,
void (*caching_callback)(v8::WasmStreaming::ModuleCachingInterface&),
void (*resolution_callback)(void* data, const v8::WasmModuleObject* module,
const v8::Value* error),
void* resolution_data, void (*drop_resolution_data)(void* data)) {
// Use shared_ptr to reference-count the Rust closure data through
// std::function's copy semantics. The custom deleter calls back into Rust
// to drop the boxed closure when the last copy is destroyed.
auto shared_data = std::shared_ptr<void>(
resolution_data,
[drop_resolution_data](void* p) { drop_resolution_data(p); });
self->Finish(
isolate, caching_callback,
[resolution_callback, shared_data](auto result) {
if (auto* module =
std::get_if<v8::Local<v8::WasmModuleObject>>(&result)) {
resolution_callback(shared_data.get(), local_to_ptr(*module),
nullptr);
} else {
resolution_callback(
shared_data.get(), nullptr,
local_to_ptr(std::get<v8::Local<v8::Value>>(result)));
}
});
}

void v8__WasmModuleCompilation__Abort(v8::WasmModuleCompilation* self) {
self->Abort();
}

void v8__WasmModuleCompilation__SetHasCompiledModuleBytes(
v8::WasmModuleCompilation* self) {
self->SetHasCompiledModuleBytes();
}

void v8__WasmModuleCompilation__SetMoreFunctionsCanBeSerializedCallback(
v8::WasmModuleCompilation* self,
void (*callback)(void* data, v8::CompiledWasmModule* compiled_module),
void* data, void (*drop_data)(void* data)) {
auto shared_data =
std::shared_ptr<void>(data, [drop_data](void* p) { drop_data(p); });
self->SetMoreFunctionsCanBeSerializedCallback(
[callback, shared_data](v8::CompiledWasmModule module) {
auto* heap_module = new v8::CompiledWasmModule(std::move(module));
callback(shared_data.get(), heap_module);
});
}

void v8__WasmModuleCompilation__SetUrl(v8::WasmModuleCompilation* self,
const char* url, size_t length) {
self->SetUrl(url, length);
}

} // extern "C"

// v8::CompiledWasmModule

extern "C" {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ pub use value_serializer::ValueSerializerHelper;
pub use value_serializer::ValueSerializerImpl;
pub use wasm::CompiledWasmModule;
pub use wasm::ModuleCachingInterface;
pub use wasm::WasmModuleCompilation;
pub use wasm::WasmStreaming;

/// https://v8.dev/docs/version-numbers
Expand Down
251 changes: 249 additions & 2 deletions src/wasm.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license.

use std::ffi::c_void;
use std::ptr::null;
use std::ptr::null_mut;

use crate::ArrayBuffer;
use crate::Isolate;
use crate::Local;
use crate::PinScope;
use crate::Value;
Expand All @@ -18,8 +23,6 @@ use crate::support::Opaque;
use crate::support::ToCFn;
use crate::support::UnitType;
use crate::support::char;
use std::ptr::null;
use std::ptr::null_mut;

// Type-erased std::shared_ptr<v8::WasmStreaming>. Assumes it's safe
// to move around (no backlinks). Not generally true for shared_ptrs
Expand Down Expand Up @@ -275,6 +278,210 @@ impl Drop for CompiledWasmModule {
}
}

// Type-erased v8::WasmModuleCompilation allocated on the C++ heap.
#[repr(C)]
struct InternalWasmModuleCompilation(Opaque);

/// An interface for asynchronous WebAssembly module compilation, to be used
/// e.g. for implementing source phase imports.
///
/// Note: This interface is experimental and can change or be removed without
/// notice.
pub struct WasmModuleCompilation(*mut InternalWasmModuleCompilation);

// OnBytesReceived can be called from any thread per V8 documentation.
unsafe impl Send for WasmModuleCompilation {}

impl WasmModuleCompilation {
/// Start an asynchronous module compilation. This can be called on any
/// thread.
#[inline(always)]
pub fn new() -> Self {
unsafe { WasmModuleCompilation(v8__WasmModuleCompilation__NEW()) }
}

/// Pass a new chunk of bytes to WebAssembly compilation. The buffer is
/// owned by the caller and will not be accessed after this call returns.
/// Can be called from any thread.
#[inline(always)]
pub fn on_bytes_received(&mut self, data: &[u8]) {
unsafe {
v8__WasmModuleCompilation__OnBytesReceived(
self.0,
data.as_ptr(),
data.len(),
);
}
}

/// Finish compilation. Must be called on the main thread after all bytes
/// were passed to [`Self::on_bytes_received`].
///
/// The `resolution_callback` will eventually be called with either the
/// compiled module or a compilation error. The callback receives `&Isolate`
/// so that [`crate::Global`] handles can be created from the [`Local`]
/// handles to persist them beyond the callback.
///
/// Must not be called after [`Self::abort`].
#[inline(always)]
pub fn finish(
self,
scope: &mut PinScope,
caching_callback: Option<ModuleCachingCallback>,
resolution_callback: impl FnOnce(
&Isolate,
Result<Local<'_, WasmModuleObject>, Local<'_, Value>>,
) + 'static,
) {
// Capture the isolate pointer in the closure so it doesn't need to be
// threaded through C++.
let isolate_ptr = scope.get_isolate_ptr();
let wrapped = move |module: *const WasmModuleObject,
error: *const Value| {
let isolate = unsafe { Isolate::from_raw_ptr(isolate_ptr) };
if !module.is_null() {
resolution_callback(
&isolate,
Ok(unsafe { Local::from_raw(module) }.unwrap()),
);
} else {
resolution_callback(
&isolate,
Err(unsafe { Local::from_raw(error) }.unwrap()),
);
}
};

// Double-box with Option: the outer Box gives us a thin pointer suitable
// for void*. The Option allows the trampoline to .take() the closure
// (FnOnce semantics) without freeing the outer allocation, which is
// ref-counted by shared_ptr on the C++ side.
let boxed: Box<
Option<Box<dyn FnOnce(*const WasmModuleObject, *const Value)>>,
> = Box::new(Some(Box::new(wrapped)));
let data = Box::into_raw(boxed) as *mut c_void;

unsafe {
v8__WasmModuleCompilation__Finish(
self.0,
scope.get_isolate_ptr(),
caching_callback,
resolution_trampoline,
data,
drop_resolution_data,
);
}
}

/// Abort compilation. Can be called from any thread.
/// Must not be called repeatedly, or after [`Self::finish`].
#[inline(always)]
pub fn abort(self) {
unsafe { v8__WasmModuleCompilation__Abort(self.0) }
}

/// Mark that the embedder has (potentially) cached compiled module bytes
/// (i.e. a serialized [`CompiledWasmModule`]) that could match this
/// compilation request. This will cause V8 to skip streaming compilation.
/// The embedder should then pass a caching callback to [`Self::finish`].
#[inline(always)]
pub fn set_has_compiled_module_bytes(&mut self) {
unsafe {
v8__WasmModuleCompilation__SetHasCompiledModuleBytes(self.0);
}
}

/// Sets a callback which is called whenever a significant number of new
/// functions are ready for serialization.
#[inline(always)]
pub fn set_more_functions_can_be_serialized_callback(
&mut self,
callback: impl Fn(CompiledWasmModule) + Send + 'static,
) {
let boxed: Box<Box<dyn Fn(CompiledWasmModule) + Send>> =
Box::new(Box::new(callback));
let data = Box::into_raw(boxed) as *mut c_void;

unsafe {
v8__WasmModuleCompilation__SetMoreFunctionsCanBeSerializedCallback(
self.0,
serialization_trampoline,
data,
drop_serialization_data,
);
}
}

/// Sets the UTF-8 encoded source URL for the `Script` object. This must
/// be called before [`Self::finish`].
#[inline(always)]
pub fn set_url(&mut self, url: &str) {
// V8 requires the url to be null terminated.
let null_terminated_url = format!("{url}\0");
unsafe {
v8__WasmModuleCompilation__SetUrl(
self.0,
null_terminated_url.as_ptr() as *const char,
url.len(),
);
}
}
}

impl Default for WasmModuleCompilation {
fn default() -> Self {
Self::new()
}
}

impl Drop for WasmModuleCompilation {
fn drop(&mut self) {
unsafe { v8__WasmModuleCompilation__DELETE(self.0) }
}
}

unsafe extern "C" fn resolution_trampoline(
data: *mut c_void,
module: *const WasmModuleObject,
error: *const Value,
) {
// Take the closure out of the Option without freeing the outer Box.
// The outer Box is ref-counted by shared_ptr on the C++ side and will
// be freed via drop_resolution_data when the last copy is destroyed.
let slot = unsafe {
&mut *(data
as *mut Option<Box<dyn FnOnce(*const WasmModuleObject, *const Value)>>)
};
let callback = slot.take().unwrap();
callback(module, error);
}

unsafe extern "C" fn drop_resolution_data(data: *mut c_void) {
let _ = unsafe {
Box::from_raw(
data
as *mut Option<
Box<dyn FnOnce(*const WasmModuleObject, *const Value)>,
>,
)
};
}

unsafe extern "C" fn serialization_trampoline(
data: *mut c_void,
compiled_module: *mut InternalCompiledWasmModule,
) {
let callback =
unsafe { &**(data as *const Box<dyn Fn(CompiledWasmModule) + Send>) };
callback(CompiledWasmModule(compiled_module));
}

unsafe extern "C" fn drop_serialization_data(data: *mut c_void) {
let _ = unsafe {
Box::from_raw(data as *mut Box<dyn Fn(CompiledWasmModule) + Send>)
};
}

impl WasmMemoryObject {
/// Returns underlying ArrayBuffer.
#[inline(always)]
Expand Down Expand Up @@ -380,4 +587,44 @@ unsafe extern "C" {
fn v8__WasmMemoryObject__Buffer(
this: *const WasmMemoryObject,
) -> *mut ArrayBuffer;

fn v8__WasmModuleCompilation__NEW() -> *mut InternalWasmModuleCompilation;
fn v8__WasmModuleCompilation__DELETE(
this: *mut InternalWasmModuleCompilation,
);
fn v8__WasmModuleCompilation__OnBytesReceived(
this: *mut InternalWasmModuleCompilation,
bytes: *const u8,
size: usize,
);
fn v8__WasmModuleCompilation__Finish(
this: *mut InternalWasmModuleCompilation,
isolate: *mut RealIsolate,
caching_callback: Option<ModuleCachingCallback>,
resolution_callback: unsafe extern "C" fn(
*mut c_void,
*const WasmModuleObject,
*const Value,
),
resolution_data: *mut c_void,
drop_resolution_data: unsafe extern "C" fn(*mut c_void),
);
fn v8__WasmModuleCompilation__Abort(this: *mut InternalWasmModuleCompilation);
fn v8__WasmModuleCompilation__SetHasCompiledModuleBytes(
this: *mut InternalWasmModuleCompilation,
);
fn v8__WasmModuleCompilation__SetMoreFunctionsCanBeSerializedCallback(
this: *mut InternalWasmModuleCompilation,
callback: unsafe extern "C" fn(
*mut c_void,
*mut InternalCompiledWasmModule,
),
data: *mut c_void,
drop_data: unsafe extern "C" fn(*mut c_void),
);
fn v8__WasmModuleCompilation__SetUrl(
this: *mut InternalWasmModuleCompilation,
url: *const char,
length: usize,
);
}
Loading
Loading