Skip to main content

v8/
wasm.rs

1// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license.
2
3use std::ffi::c_void;
4use std::ptr::null;
5use std::ptr::null_mut;
6
7use crate::ArrayBuffer;
8use crate::Isolate;
9use crate::Local;
10use crate::PinScope;
11use crate::Value;
12use crate::WasmMemoryObject;
13use crate::WasmModuleObject;
14use crate::binding::const_memory_span_t;
15use crate::function::FunctionCallbackArguments;
16use crate::function::FunctionCallbackInfo;
17use crate::isolate::RealIsolate;
18use crate::scope::GetIsolate;
19use crate::scope::callback_scope;
20use crate::support::MapFnFrom;
21use crate::support::MapFnTo;
22use crate::support::Opaque;
23use crate::support::ToCFn;
24use crate::support::UnitType;
25use crate::support::char;
26
27// Type-erased std::shared_ptr<v8::WasmStreaming>. Assumes it's safe
28// to move around (no backlinks). Not generally true for shared_ptrs
29// but it is in this case - other shared_ptrs that point to the same
30// v8::WasmStreaming exist but are managed by V8 and don't leak out.
31//
32// We don't use crate::support::SharedPtr because it only allows
33// immutable borrows and derefs to avoid aliasing but that's not
34// a problem here, only a single instance outside V8 exists.
35//
36// Note: uses *mut u8 rather than e.g. usize to enforce !Send and !Sync.
37#[repr(C)]
38struct WasmStreamingSharedPtr([*mut u8; 2]);
39
40/// The V8 interface for WebAssembly streaming compilation.
41/// When streaming compilation is initiated, V8 passes a [Self]
42/// object to the embedder such that the embedder can pass the
43/// input bytes for streaming compilation to V8.
44#[repr(C)]
45pub struct WasmStreaming<const HAS_COMPILED_MODULE_BYTES: bool>(
46  WasmStreamingSharedPtr,
47);
48
49impl<const HAS_COMPILED_MODULE_BYTES: bool>
50  WasmStreaming<HAS_COMPILED_MODULE_BYTES>
51{
52  /// Pass a new chunk of bytes to WebAssembly streaming compilation.
53  #[inline(always)]
54  pub fn on_bytes_received(&mut self, data: &[u8]) {
55    unsafe {
56      v8__WasmStreaming__OnBytesReceived(
57        &mut self.0,
58        data.as_ptr(),
59        data.len(),
60      );
61    }
62  }
63
64  /// Abort streaming compilation. If {exception} has a value, then the promise
65  /// associated with streaming compilation is rejected with that value. If
66  /// {exception} does not have value, the promise does not get rejected.
67  #[inline(always)]
68  pub fn abort(mut self, exception: Option<Local<Value>>) {
69    let exception = exception.map_or(null(), |v| &*v as *const Value);
70    unsafe { v8__WasmStreaming__Abort(&mut self.0, exception) }
71  }
72
73  /// Sets the UTF-8 encoded source URL for the `Script` object. This must be
74  /// called before [`Self::finish()`].
75  #[inline(always)]
76  pub fn set_url(&mut self, url: &str) {
77    // Although not documented, V8 requires the url to be null terminated.
78    // See https://chromium-review.googlesource.com/c/v8/v8/+/3289148.
79    let null_terminated_url = format!("{url}\0");
80    unsafe {
81      v8__WasmStreaming__SetUrl(
82        &mut self.0,
83        null_terminated_url.as_ptr() as *const char,
84        url.len(),
85      );
86    }
87  }
88}
89
90impl WasmStreaming<false> {
91  /// {Finish} should be called after all received bytes where passed to
92  /// {OnBytesReceived} to tell V8 that there will be no more bytes. {Finish}
93  /// must not be called after {Abort} has been called already.
94  /// If {SetHasCompiledModuleBytes()} was called before, a {caching_callback}
95  /// can be passed which can inspect the full received wire bytes and set cached
96  /// module bytes which will be deserialized then. This callback will happen
97  /// synchronously within this call; the callback is not stored.
98  #[inline(always)]
99  pub fn finish(mut self) {
100    unsafe { v8__WasmStreaming__Finish(&mut self.0, None) }
101  }
102
103  /// Mark that the embedder has (potentially) cached compiled module bytes (i.e.
104  /// a serialized {CompiledWasmModule}) that could match this streaming request.
105  /// This will cause V8 to skip streaming compilation.
106  /// The embedder should then pass a callback to the {Finish} method to pass the
107  /// serialized bytes, after potentially checking their validity against the
108  /// full received wire bytes.
109  #[inline(always)]
110  pub fn set_has_compiled_module_bytes(mut self) -> WasmStreaming<true> {
111    unsafe {
112      v8__WasmStreaming__SetHasCompiledModuleBytes(&mut self.0);
113      std::mem::transmute(self)
114    }
115  }
116}
117
118impl WasmStreaming<true> {
119  /// {Finish} should be called after all received bytes where passed to
120  /// {OnBytesReceived} to tell V8 that there will be no more bytes. {Finish}
121  /// must not be called after {Abort} has been called already.
122  /// If {SetHasCompiledModuleBytes()} was called before, a {caching_callback}
123  /// can be passed which can inspect the full received wire bytes and set cached
124  /// module bytes which will be deserialized then. This callback will happen
125  /// synchronously within this call; the callback is not stored.
126  #[inline(always)]
127  pub fn finish<F>(mut self, f: F)
128  where
129    F: MapFnTo<ModuleCachingCallback>,
130  {
131    unsafe { v8__WasmStreaming__Finish(&mut self.0, Some(f.map_fn_to())) }
132  }
133}
134
135impl<const HAS_COMPILED_MODULE_BYTES: bool> Drop
136  for WasmStreaming<HAS_COMPILED_MODULE_BYTES>
137{
138  fn drop(&mut self) {
139    unsafe { v8__WasmStreaming__shared_ptr_DESTRUCT(&mut self.0) }
140  }
141}
142
143impl WasmModuleObject {
144  /// Efficiently re-create a WasmModuleObject, without recompiling, from
145  /// a CompiledWasmModule.
146  #[inline(always)]
147  pub fn from_compiled_module<'s>(
148    scope: &PinScope<'s, '_>,
149    compiled_module: &CompiledWasmModule,
150  ) -> Option<Local<'s, WasmModuleObject>> {
151    unsafe {
152      scope.cast_local(|sd| {
153        v8__WasmModuleObject__FromCompiledModule(
154          sd.get_isolate_ptr(),
155          compiled_module.0,
156        )
157      })
158    }
159  }
160
161  /// Get the compiled module for this module object. The compiled module can be
162  /// shared by several module objects.
163  #[inline(always)]
164  pub fn get_compiled_module(&self) -> CompiledWasmModule {
165    let ptr = unsafe { v8__WasmModuleObject__GetCompiledModule(self) };
166    CompiledWasmModule(ptr)
167  }
168
169  /// Compile a Wasm module from the provided uncompiled bytes.
170  #[inline(always)]
171  pub fn compile<'s>(
172    scope: &PinScope<'s, '_>,
173    wire_bytes: &[u8],
174  ) -> Option<Local<'s, WasmModuleObject>> {
175    unsafe {
176      scope.cast_local(|sd| {
177        v8__WasmModuleObject__Compile(
178          sd.get_isolate_ptr(),
179          wire_bytes.as_ptr(),
180          wire_bytes.len(),
181        )
182      })
183    }
184  }
185}
186
187#[repr(C)]
188pub struct ModuleCachingInterface(Opaque);
189
190impl ModuleCachingInterface {
191  /// Get the full wire bytes, to check against the cached version.
192  #[inline(always)]
193  pub fn get_wire_bytes(&self) -> &[u8] {
194    unsafe {
195      let span = v8__ModuleCachingInterface__GetWireBytes(self);
196      std::slice::from_raw_parts(span.data, span.size)
197    }
198  }
199
200  /// Pass serialized (cached) compiled module bytes, to be deserialized and
201  /// used as the result of this streaming compilation.
202  /// The passed bytes will only be accessed inside this callback, i.e.
203  /// lifetime can end after the call.
204  /// The return value indicates whether V8 could use the passed bytes; {false}
205  /// would be returned on e.g. version mismatch.
206  /// This method can only be called once.
207  #[inline(always)]
208  pub fn set_cached_compiled_module_bytes(&mut self, bytes: &[u8]) -> bool {
209    unsafe {
210      v8__ModuleCachingInterface__SetCachedCompiledModuleBytes(
211        self,
212        const_memory_span_t {
213          data: bytes.as_ptr(),
214          size: bytes.len(),
215        },
216      )
217    }
218  }
219}
220
221pub type ModuleCachingCallback =
222  unsafe extern "C" fn(*mut ModuleCachingInterface);
223
224impl<F> MapFnFrom<F> for ModuleCachingCallback
225where
226  F: UnitType + Fn(&mut ModuleCachingInterface),
227{
228  fn mapping() -> Self {
229    let f = |mci: *mut ModuleCachingInterface| {
230      (F::get())(unsafe { &mut *mci });
231    };
232    f.to_c_fn()
233  }
234}
235
236// Type-erased v8::CompiledWasmModule. We need this because the C++
237// v8::CompiledWasmModule must be destructed because its private fields hold
238// pointers that must be freed, but v8::CompiledWasmModule itself doesn't have
239// a destructor. Therefore, in order to avoid memory leaks, the Rust-side
240// CompiledWasmModule must be a pointer to a C++ allocation of
241// v8::CompiledWasmModule.
242#[repr(C)]
243struct InternalCompiledWasmModule(Opaque);
244
245/// Wrapper around a compiled WebAssembly module, which is potentially shared by
246/// different WasmModuleObjects.
247pub struct CompiledWasmModule(*mut InternalCompiledWasmModule);
248
249impl CompiledWasmModule {
250  /// Get the (wasm-encoded) wire bytes that were used to compile this module.
251  #[inline(always)]
252  pub fn get_wire_bytes_ref(&self) -> &[u8] {
253    let mut len = 0isize;
254    unsafe {
255      let ptr = v8__CompiledWasmModule__GetWireBytesRef(self.0, &mut len);
256      std::slice::from_raw_parts(ptr, len.try_into().unwrap())
257    }
258  }
259
260  #[inline(always)]
261  pub fn source_url(&self) -> &str {
262    let mut len = 0;
263    unsafe {
264      let ptr = v8__CompiledWasmModule__SourceUrl(self.0, &mut len);
265      let bytes = std::slice::from_raw_parts(ptr as _, len);
266      std::str::from_utf8_unchecked(bytes)
267    }
268  }
269}
270
271// TODO(andreubotella): Safety???
272unsafe impl Send for CompiledWasmModule {}
273unsafe impl Sync for CompiledWasmModule {}
274
275impl Drop for CompiledWasmModule {
276  fn drop(&mut self) {
277    unsafe { v8__CompiledWasmModule__DELETE(self.0) }
278  }
279}
280
281// Type-erased v8::WasmModuleCompilation allocated on the C++ heap.
282#[repr(C)]
283struct 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.
290pub struct WasmModuleCompilation(*mut InternalWasmModuleCompilation);
291
292// OnBytesReceived can be called from any thread per V8 documentation.
293unsafe impl Send for WasmModuleCompilation {}
294
295impl 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    // Capture the isolate pointer in the closure so it doesn't need to be
337    // threaded through C++.
338    let isolate_ptr = scope.get_isolate_ptr();
339    let wrapped = move |module: *const WasmModuleObject,
340                        error: *const Value| {
341      let isolate = unsafe { Isolate::from_raw_ptr(isolate_ptr) };
342      if !module.is_null() {
343        resolution_callback(
344          &isolate,
345          Ok(unsafe { Local::from_raw(module) }.unwrap()),
346        );
347      } else {
348        resolution_callback(
349          &isolate,
350          Err(unsafe { Local::from_raw(error) }.unwrap()),
351        );
352      }
353    };
354
355    // Double-box with Option: the outer Box gives us a thin pointer suitable
356    // for void*. The Option allows the trampoline to .take() the closure
357    // (FnOnce semantics) without freeing the outer allocation, which is
358    // ref-counted by shared_ptr on the C++ side.
359    #[allow(clippy::type_complexity)]
360    let boxed: Box<
361      Option<Box<dyn FnOnce(*const WasmModuleObject, *const Value)>>,
362    > = Box::new(Some(Box::new(wrapped)));
363    let data = Box::into_raw(boxed) as *mut c_void;
364
365    unsafe {
366      v8__WasmModuleCompilation__Finish(
367        self.0,
368        scope.get_isolate_ptr(),
369        caching_callback,
370        resolution_trampoline,
371        data,
372        drop_resolution_data,
373      );
374    }
375  }
376
377  /// Abort compilation. Can be called from any thread.
378  /// Must not be called repeatedly, or after [`Self::finish`].
379  #[inline(always)]
380  pub fn abort(self) {
381    unsafe { v8__WasmModuleCompilation__Abort(self.0) }
382  }
383
384  /// Mark that the embedder has (potentially) cached compiled module bytes
385  /// (i.e. a serialized [`CompiledWasmModule`]) that could match this
386  /// compilation request. This will cause V8 to skip streaming compilation.
387  /// The embedder should then pass a caching callback to [`Self::finish`].
388  #[inline(always)]
389  pub fn set_has_compiled_module_bytes(&mut self) {
390    unsafe {
391      v8__WasmModuleCompilation__SetHasCompiledModuleBytes(self.0);
392    }
393  }
394
395  /// Sets a callback which is called whenever a significant number of new
396  /// functions are ready for serialization.
397  #[inline(always)]
398  pub fn set_more_functions_can_be_serialized_callback(
399    &mut self,
400    callback: impl Fn(CompiledWasmModule) + Send + 'static,
401  ) {
402    let boxed: Box<Box<dyn Fn(CompiledWasmModule) + Send>> =
403      Box::new(Box::new(callback));
404    let data = Box::into_raw(boxed) as *mut c_void;
405
406    unsafe {
407      v8__WasmModuleCompilation__SetMoreFunctionsCanBeSerializedCallback(
408        self.0,
409        serialization_trampoline,
410        data,
411        drop_serialization_data,
412      );
413    }
414  }
415
416  /// Sets the UTF-8 encoded source URL for the `Script` object. This must
417  /// be called before [`Self::finish`].
418  #[inline(always)]
419  pub fn set_url(&mut self, url: &str) {
420    // V8 requires the url to be null terminated.
421    let null_terminated_url = format!("{url}\0");
422    unsafe {
423      v8__WasmModuleCompilation__SetUrl(
424        self.0,
425        null_terminated_url.as_ptr() as *const char,
426        url.len(),
427      );
428    }
429  }
430}
431
432impl Default for WasmModuleCompilation {
433  fn default() -> Self {
434    Self::new()
435  }
436}
437
438impl Drop for WasmModuleCompilation {
439  fn drop(&mut self) {
440    unsafe { v8__WasmModuleCompilation__DELETE(self.0) }
441  }
442}
443
444unsafe extern "C" fn resolution_trampoline(
445  data: *mut c_void,
446  module: *const WasmModuleObject,
447  error: *const Value,
448) {
449  // Take the closure out of the Option without freeing the outer Box.
450  // The outer Box is ref-counted by shared_ptr on the C++ side and will
451  // be freed via drop_resolution_data when the last copy is destroyed.
452  let slot = unsafe {
453    &mut *(data
454      as *mut Option<Box<dyn FnOnce(*const WasmModuleObject, *const Value)>>)
455  };
456  let callback = slot.take().unwrap();
457  callback(module, error);
458}
459
460unsafe extern "C" fn drop_resolution_data(data: *mut c_void) {
461  let _ = unsafe {
462    Box::from_raw(
463      data
464        as *mut Option<Box<dyn FnOnce(*const WasmModuleObject, *const Value)>>,
465    )
466  };
467}
468
469unsafe extern "C" fn serialization_trampoline(
470  data: *mut c_void,
471  compiled_module: *mut InternalCompiledWasmModule,
472) {
473  let callback =
474    unsafe { &**(data as *const Box<dyn Fn(CompiledWasmModule) + Send>) };
475  callback(CompiledWasmModule(compiled_module));
476}
477
478unsafe extern "C" fn drop_serialization_data(data: *mut c_void) {
479  let _ = unsafe {
480    Box::from_raw(data as *mut Box<dyn Fn(CompiledWasmModule) + Send>)
481  };
482}
483
484impl WasmMemoryObject {
485  /// Returns underlying ArrayBuffer.
486  #[inline(always)]
487  pub fn buffer(&self) -> Local<'_, ArrayBuffer> {
488    unsafe { Local::from_raw(v8__WasmMemoryObject__Buffer(self)) }.unwrap()
489  }
490}
491
492pub(crate) fn trampoline<F>()
493-> unsafe extern "C" fn(*const FunctionCallbackInfo)
494where
495  F: UnitType
496    + for<'a, 'b, 'c> Fn(
497      &'c mut PinScope<'a, 'b>,
498      Local<'a, Value>,
499      WasmStreaming<false>,
500    ),
501{
502  unsafe extern "C" fn c_fn<F>(info: *const FunctionCallbackInfo)
503  where
504    F: UnitType
505      + for<'a, 'b, 'c> Fn(
506        &'c mut PinScope<'a, 'b>,
507        Local<'a, Value>,
508        WasmStreaming<false>,
509      ),
510  {
511    let info = unsafe { &*info };
512    callback_scope!(unsafe scope, info);
513    let args = FunctionCallbackArguments::from_function_callback_info(info);
514    let data = args.data();
515    let zero = null_mut();
516    let mut that = WasmStreamingSharedPtr([zero, zero]);
517    unsafe {
518      v8__WasmStreaming__Unpack(scope.get_isolate_ptr(), &*data, &mut that);
519    };
520    let source = args.get(0);
521    (F::get())(scope, source, WasmStreaming(that));
522  }
523  c_fn::<F>
524}
525
526unsafe extern "C" {
527  fn v8__WasmStreaming__Unpack(
528    isolate: *mut RealIsolate,
529    value: *const Value,
530    that: *mut WasmStreamingSharedPtr, // Out parameter.
531  );
532  fn v8__WasmStreaming__shared_ptr_DESTRUCT(this: *mut WasmStreamingSharedPtr);
533  fn v8__WasmStreaming__SetHasCompiledModuleBytes(
534    this: *mut WasmStreamingSharedPtr,
535  );
536  fn v8__WasmStreaming__OnBytesReceived(
537    this: *mut WasmStreamingSharedPtr,
538    data: *const u8,
539    len: usize,
540  );
541  fn v8__WasmStreaming__Finish(
542    this: *mut WasmStreamingSharedPtr,
543    callback: Option<ModuleCachingCallback>,
544  );
545  fn v8__WasmStreaming__Abort(
546    this: *mut WasmStreamingSharedPtr,
547    exception: *const Value,
548  );
549  fn v8__WasmStreaming__SetUrl(
550    this: *mut WasmStreamingSharedPtr,
551    url: *const char,
552    len: usize,
553  );
554
555  fn v8__ModuleCachingInterface__GetWireBytes(
556    interface: *const ModuleCachingInterface,
557  ) -> const_memory_span_t;
558  fn v8__ModuleCachingInterface__SetCachedCompiledModuleBytes(
559    interface: *mut ModuleCachingInterface,
560    bytes: const_memory_span_t,
561  ) -> bool;
562
563  fn v8__WasmModuleObject__FromCompiledModule(
564    isolate: *mut RealIsolate,
565    compiled_module: *const InternalCompiledWasmModule,
566  ) -> *const WasmModuleObject;
567  fn v8__WasmModuleObject__GetCompiledModule(
568    this: *const WasmModuleObject,
569  ) -> *mut InternalCompiledWasmModule;
570  fn v8__WasmModuleObject__Compile(
571    isolate: *mut RealIsolate,
572    wire_bytes_data: *const u8,
573    length: usize,
574  ) -> *mut WasmModuleObject;
575
576  fn v8__CompiledWasmModule__GetWireBytesRef(
577    this: *mut InternalCompiledWasmModule,
578    length: *mut isize,
579  ) -> *const u8;
580  fn v8__CompiledWasmModule__SourceUrl(
581    this: *mut InternalCompiledWasmModule,
582    length: *mut usize,
583  ) -> *const char;
584  fn v8__CompiledWasmModule__DELETE(this: *mut InternalCompiledWasmModule);
585
586  fn v8__WasmMemoryObject__Buffer(
587    this: *const WasmMemoryObject,
588  ) -> *mut ArrayBuffer;
589
590  fn v8__WasmModuleCompilation__NEW() -> *mut InternalWasmModuleCompilation;
591  fn v8__WasmModuleCompilation__DELETE(
592    this: *mut InternalWasmModuleCompilation,
593  );
594  fn v8__WasmModuleCompilation__OnBytesReceived(
595    this: *mut InternalWasmModuleCompilation,
596    bytes: *const u8,
597    size: usize,
598  );
599  fn v8__WasmModuleCompilation__Finish(
600    this: *mut InternalWasmModuleCompilation,
601    isolate: *mut RealIsolate,
602    caching_callback: Option<ModuleCachingCallback>,
603    resolution_callback: unsafe extern "C" fn(
604      *mut c_void,
605      *const WasmModuleObject,
606      *const Value,
607    ),
608    resolution_data: *mut c_void,
609    drop_resolution_data: unsafe extern "C" fn(*mut c_void),
610  );
611  fn v8__WasmModuleCompilation__Abort(this: *mut InternalWasmModuleCompilation);
612  fn v8__WasmModuleCompilation__SetHasCompiledModuleBytes(
613    this: *mut InternalWasmModuleCompilation,
614  );
615  fn v8__WasmModuleCompilation__SetMoreFunctionsCanBeSerializedCallback(
616    this: *mut InternalWasmModuleCompilation,
617    callback: unsafe extern "C" fn(
618      *mut c_void,
619      *mut InternalCompiledWasmModule,
620    ),
621    data: *mut c_void,
622    drop_data: unsafe extern "C" fn(*mut c_void),
623  );
624  fn v8__WasmModuleCompilation__SetUrl(
625    this: *mut InternalWasmModuleCompilation,
626    url: *const char,
627    length: usize,
628  );
629}