Skip to content

Commit 6dc5c32

Browse files
bartlomiejuclaude
andcommitted
Add Rust bindings for v8::WasmModuleCompilation
Expose the new experimental WasmModuleCompilation API from V8 14.6, which provides asynchronous WebAssembly module compilation without requiring a Promise-based streaming interface. This is intended for use cases like source phase imports. Bindings include: new, on_bytes_received, finish, abort, set_has_compiled_module_bytes, set_more_functions_can_be_serialized_callback, and set_url. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ea5769e commit 6dc5c32

File tree

4 files changed

+364
-2
lines changed

4 files changed

+364
-2
lines changed

src/binding.cc

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3791,6 +3791,82 @@ uint32_t v8__ValueDeserializer__GetWireFormatVersion(
37913791
}
37923792
} // extern "C"
37933793

3794+
// v8::WasmModuleCompilation
3795+
3796+
extern "C" {
3797+
3798+
v8::WasmModuleCompilation* v8__WasmModuleCompilation__NEW() {
3799+
return new v8::WasmModuleCompilation();
3800+
}
3801+
3802+
void v8__WasmModuleCompilation__DELETE(v8::WasmModuleCompilation* self) {
3803+
delete self;
3804+
}
3805+
3806+
void v8__WasmModuleCompilation__OnBytesReceived(
3807+
v8::WasmModuleCompilation* self, const uint8_t* bytes, size_t size) {
3808+
self->OnBytesReceived(bytes, size);
3809+
}
3810+
3811+
void v8__WasmModuleCompilation__Finish(
3812+
v8::WasmModuleCompilation* self, v8::Isolate* isolate,
3813+
void (*caching_callback)(v8::WasmStreaming::ModuleCachingInterface&),
3814+
void (*resolution_callback)(void* data, v8::Isolate* isolate,
3815+
const v8::WasmModuleObject* module,
3816+
const v8::Value* error),
3817+
void* resolution_data) {
3818+
v8::WasmModuleCompilation::ModuleCachingCallback cc;
3819+
if (caching_callback) {
3820+
cc = [caching_callback](v8::WasmStreaming::ModuleCachingInterface& mci) {
3821+
caching_callback(mci);
3822+
};
3823+
}
3824+
self->Finish(
3825+
isolate, cc,
3826+
[resolution_callback, resolution_data, isolate](
3827+
std::variant<v8::Local<v8::WasmModuleObject>, v8::Local<v8::Value>>
3828+
result) {
3829+
if (auto* module =
3830+
std::get_if<v8::Local<v8::WasmModuleObject>>(&result)) {
3831+
resolution_callback(resolution_data, isolate, local_to_ptr(*module),
3832+
nullptr);
3833+
} else {
3834+
resolution_callback(
3835+
resolution_data, isolate, nullptr,
3836+
local_to_ptr(std::get<v8::Local<v8::Value>>(result)));
3837+
}
3838+
});
3839+
}
3840+
3841+
void v8__WasmModuleCompilation__Abort(v8::WasmModuleCompilation* self) {
3842+
self->Abort();
3843+
}
3844+
3845+
void v8__WasmModuleCompilation__SetHasCompiledModuleBytes(
3846+
v8::WasmModuleCompilation* self) {
3847+
self->SetHasCompiledModuleBytes();
3848+
}
3849+
3850+
void v8__WasmModuleCompilation__SetMoreFunctionsCanBeSerializedCallback(
3851+
v8::WasmModuleCompilation* self,
3852+
void (*callback)(void* data, v8::CompiledWasmModule* compiled_module),
3853+
void* data, void (*drop_data)(void* data)) {
3854+
auto shared_data =
3855+
std::shared_ptr<void>(data, [drop_data](void* p) { drop_data(p); });
3856+
self->SetMoreFunctionsCanBeSerializedCallback(
3857+
[callback, shared_data](v8::CompiledWasmModule module) {
3858+
auto* heap_module = new v8::CompiledWasmModule(std::move(module));
3859+
callback(shared_data.get(), heap_module);
3860+
});
3861+
}
3862+
3863+
void v8__WasmModuleCompilation__SetUrl(v8::WasmModuleCompilation* self,
3864+
const char* url, size_t length) {
3865+
self->SetUrl(url, length);
3866+
}
3867+
3868+
} // extern "C"
3869+
37943870
// v8::CompiledWasmModule
37953871

37963872
extern "C" {

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ pub use value_serializer::ValueSerializerHelper;
185185
pub use value_serializer::ValueSerializerImpl;
186186
pub use wasm::CompiledWasmModule;
187187
pub use wasm::ModuleCachingInterface;
188+
pub use wasm::WasmModuleCompilation;
188189
pub use wasm::WasmStreaming;
189190

190191
/// https://v8.dev/docs/version-numbers

src/wasm.rs

Lines changed: 235 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license.
22

3+
use std::ffi::c_void;
4+
use std::ptr::null;
5+
use std::ptr::null_mut;
6+
37
use crate::ArrayBuffer;
8+
use crate::Isolate;
49
use crate::Local;
510
use crate::PinScope;
611
use crate::Value;
@@ -18,8 +23,6 @@ use crate::support::Opaque;
1823
use crate::support::ToCFn;
1924
use crate::support::UnitType;
2025
use crate::support::char;
21-
use std::ptr::null;
22-
use std::ptr::null_mut;
2326

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

281+
// Type-erased v8::WasmModuleCompilation allocated on the C++ heap.
282+
#[repr(C)]
283+
struct InternalWasmModuleCompilation(Opaque);
284+
285+
/// An interface for asynchronous WebAssembly module compilation, to be used
286+
/// e.g. for implementing source phase imports.
287+
///
288+
/// Note: This interface is experimental and can change or be removed without
289+
/// notice.
290+
pub struct WasmModuleCompilation(*mut InternalWasmModuleCompilation);
291+
292+
// OnBytesReceived can be called from any thread per V8 documentation.
293+
unsafe impl Send for WasmModuleCompilation {}
294+
295+
impl WasmModuleCompilation {
296+
/// Start an asynchronous module compilation. This can be called on any
297+
/// thread.
298+
#[inline(always)]
299+
pub fn new() -> Self {
300+
unsafe { WasmModuleCompilation(v8__WasmModuleCompilation__NEW()) }
301+
}
302+
303+
/// Pass a new chunk of bytes to WebAssembly compilation. The buffer is
304+
/// owned by the caller and will not be accessed after this call returns.
305+
/// Can be called from any thread.
306+
#[inline(always)]
307+
pub fn on_bytes_received(&mut self, data: &[u8]) {
308+
unsafe {
309+
v8__WasmModuleCompilation__OnBytesReceived(
310+
self.0,
311+
data.as_ptr(),
312+
data.len(),
313+
);
314+
}
315+
}
316+
317+
/// Finish compilation. Must be called on the main thread after all bytes
318+
/// were passed to [`Self::on_bytes_received`].
319+
///
320+
/// The `resolution_callback` will eventually be called with either the
321+
/// compiled module or a compilation error. The callback receives `&Isolate`
322+
/// so that [`crate::Global`] handles can be created from the [`Local`]
323+
/// handles to persist them beyond the callback.
324+
///
325+
/// Must not be called after [`Self::abort`].
326+
#[inline(always)]
327+
pub fn finish(
328+
self,
329+
scope: &mut PinScope,
330+
caching_callback: Option<ModuleCachingCallback>,
331+
resolution_callback: impl FnOnce(
332+
&Isolate,
333+
Result<Local<'_, WasmModuleObject>, Local<'_, Value>>,
334+
) + 'static,
335+
) {
336+
// Double-box: the outer Box gives us a thin pointer suitable for void*.
337+
let boxed: Box<
338+
Box<
339+
dyn FnOnce(
340+
&Isolate,
341+
Result<Local<'_, WasmModuleObject>, Local<'_, Value>>,
342+
),
343+
>,
344+
> = Box::new(Box::new(resolution_callback));
345+
let data = Box::into_raw(boxed) as *mut c_void;
346+
347+
unsafe {
348+
v8__WasmModuleCompilation__Finish(
349+
self.0,
350+
scope.get_isolate_ptr(),
351+
caching_callback,
352+
resolution_trampoline,
353+
data,
354+
);
355+
}
356+
}
357+
358+
/// Abort compilation. Can be called from any thread.
359+
/// Must not be called repeatedly, or after [`Self::finish`].
360+
#[inline(always)]
361+
pub fn abort(self) {
362+
unsafe { v8__WasmModuleCompilation__Abort(self.0) }
363+
}
364+
365+
/// Mark that the embedder has (potentially) cached compiled module bytes
366+
/// (i.e. a serialized [`CompiledWasmModule`]) that could match this
367+
/// compilation request. This will cause V8 to skip streaming compilation.
368+
/// The embedder should then pass a caching callback to [`Self::finish`].
369+
#[inline(always)]
370+
pub fn set_has_compiled_module_bytes(&mut self) {
371+
unsafe {
372+
v8__WasmModuleCompilation__SetHasCompiledModuleBytes(self.0);
373+
}
374+
}
375+
376+
/// Sets a callback which is called whenever a significant number of new
377+
/// functions are ready for serialization.
378+
#[inline(always)]
379+
pub fn set_more_functions_can_be_serialized_callback(
380+
&mut self,
381+
callback: impl Fn(CompiledWasmModule) + Send + 'static,
382+
) {
383+
let boxed: Box<Box<dyn Fn(CompiledWasmModule) + Send>> =
384+
Box::new(Box::new(callback));
385+
let data = Box::into_raw(boxed) as *mut c_void;
386+
387+
unsafe {
388+
v8__WasmModuleCompilation__SetMoreFunctionsCanBeSerializedCallback(
389+
self.0,
390+
serialization_trampoline,
391+
data,
392+
drop_serialization_data,
393+
);
394+
}
395+
}
396+
397+
/// Sets the UTF-8 encoded source URL for the `Script` object. This must
398+
/// be called before [`Self::finish`].
399+
#[inline(always)]
400+
pub fn set_url(&mut self, url: &str) {
401+
// V8 requires the url to be null terminated.
402+
let null_terminated_url = format!("{url}\0");
403+
unsafe {
404+
v8__WasmModuleCompilation__SetUrl(
405+
self.0,
406+
null_terminated_url.as_ptr() as *const char,
407+
url.len(),
408+
);
409+
}
410+
}
411+
}
412+
413+
impl Default for WasmModuleCompilation {
414+
fn default() -> Self {
415+
Self::new()
416+
}
417+
}
418+
419+
impl Drop for WasmModuleCompilation {
420+
fn drop(&mut self) {
421+
unsafe { v8__WasmModuleCompilation__DELETE(self.0) }
422+
}
423+
}
424+
425+
unsafe extern "C" fn resolution_trampoline(
426+
data: *mut c_void,
427+
isolate: *mut RealIsolate,
428+
module: *const WasmModuleObject,
429+
error: *const Value,
430+
) {
431+
let callback: Box<
432+
Box<
433+
dyn FnOnce(
434+
&Isolate,
435+
Result<Local<'_, WasmModuleObject>, Local<'_, Value>>,
436+
),
437+
>,
438+
> = unsafe { Box::from_raw(data as *mut _) };
439+
let isolate = unsafe { Isolate::from_raw_ptr(isolate) };
440+
if !module.is_null() {
441+
callback(
442+
&isolate,
443+
Ok(unsafe { Local::from_raw(module) }.unwrap()),
444+
);
445+
} else {
446+
callback(
447+
&isolate,
448+
Err(unsafe { Local::from_raw(error) }.unwrap()),
449+
);
450+
}
451+
}
452+
453+
unsafe extern "C" fn serialization_trampoline(
454+
data: *mut c_void,
455+
compiled_module: *mut InternalCompiledWasmModule,
456+
) {
457+
let callback = unsafe {
458+
&**(data as *const Box<dyn Fn(CompiledWasmModule) + Send>)
459+
};
460+
callback(CompiledWasmModule(compiled_module));
461+
}
462+
463+
unsafe extern "C" fn drop_serialization_data(data: *mut c_void) {
464+
let _ = unsafe {
465+
Box::from_raw(data as *mut Box<dyn Fn(CompiledWasmModule) + Send>)
466+
};
467+
}
468+
278469
impl WasmMemoryObject {
279470
/// Returns underlying ArrayBuffer.
280471
#[inline(always)]
@@ -380,4 +571,46 @@ unsafe extern "C" {
380571
fn v8__WasmMemoryObject__Buffer(
381572
this: *const WasmMemoryObject,
382573
) -> *mut ArrayBuffer;
574+
575+
fn v8__WasmModuleCompilation__NEW() -> *mut InternalWasmModuleCompilation;
576+
fn v8__WasmModuleCompilation__DELETE(
577+
this: *mut InternalWasmModuleCompilation,
578+
);
579+
fn v8__WasmModuleCompilation__OnBytesReceived(
580+
this: *mut InternalWasmModuleCompilation,
581+
bytes: *const u8,
582+
size: usize,
583+
);
584+
fn v8__WasmModuleCompilation__Finish(
585+
this: *mut InternalWasmModuleCompilation,
586+
isolate: *mut RealIsolate,
587+
caching_callback: Option<ModuleCachingCallback>,
588+
resolution_callback: unsafe extern "C" fn(
589+
*mut c_void,
590+
*mut RealIsolate,
591+
*const WasmModuleObject,
592+
*const Value,
593+
),
594+
resolution_data: *mut c_void,
595+
);
596+
fn v8__WasmModuleCompilation__Abort(
597+
this: *mut InternalWasmModuleCompilation,
598+
);
599+
fn v8__WasmModuleCompilation__SetHasCompiledModuleBytes(
600+
this: *mut InternalWasmModuleCompilation,
601+
);
602+
fn v8__WasmModuleCompilation__SetMoreFunctionsCanBeSerializedCallback(
603+
this: *mut InternalWasmModuleCompilation,
604+
callback: unsafe extern "C" fn(
605+
*mut c_void,
606+
*mut InternalCompiledWasmModule,
607+
),
608+
data: *mut c_void,
609+
drop_data: unsafe extern "C" fn(*mut c_void),
610+
);
611+
fn v8__WasmModuleCompilation__SetUrl(
612+
this: *mut InternalWasmModuleCompilation,
613+
url: *const char,
614+
length: usize,
615+
);
383616
}

0 commit comments

Comments
 (0)