Skip to main content

wasm_bindgen/convert/
slices.rs

1use alloc::boxed::Box;
2use alloc::string::String;
3use alloc::vec::Vec;
4use core::mem::{self, MaybeUninit};
5use core::ops::{Deref, DerefMut};
6use core::str;
7
8use crate::__rt::{marker::ErasableGeneric, WasmWord};
9use crate::__wbindgen_copy_to_typed_array;
10use crate::convert::{
11    js_value_vector_from_abi, js_value_vector_into_abi, FromWasmAbi, IntoWasmAbi,
12    LongRefFromWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi, RefFromWasmAbi, RefMutFromWasmAbi,
13    UpcastFrom, VectorFromWasmAbi, VectorIntoWasmAbi, WasmAbi,
14};
15use crate::describe::*;
16use crate::JsValue;
17
18use cfg_if::cfg_if;
19
20/// # ⚠️ Unstable
21///
22/// This is part of the internal [`convert`](crate::convert) module, **no
23/// stability guarantees** are provided. Use at your own risk. See its
24/// documentation for more details.
25// note: `WasmAbi` types do not need to be FFI-safe themselves, it's just more
26// convenient to directly write `WasmSlice` in some of the manually-written FFI
27// functions in `lib.rs` rather than `WasmRet<WasmSlice>`.
28#[repr(C)]
29#[derive(Clone, Copy)]
30pub struct WasmSlice {
31    pub ptr: WasmWord,
32    pub len: WasmWord,
33}
34
35impl WasmSlice {
36    #[inline]
37    pub fn from_usize(ptr: usize, len: usize) -> Self {
38        Self {
39            ptr: WasmWord::from_usize(ptr),
40            len: WasmWord::from_usize(len),
41        }
42    }
43}
44
45impl WasmAbi for WasmSlice {
46    /// `self.ptr`
47    type Prim1 = <WasmWord as WasmAbi>::Prim1;
48    /// `self.len`
49    type Prim2 = <WasmWord as WasmAbi>::Prim1;
50    type Prim3 = ();
51    type Prim4 = ();
52
53    #[inline]
54    fn split(self) -> (Self::Prim1, Self::Prim2, (), ()) {
55        (self.ptr.split().0, self.len.split().0, (), ())
56    }
57
58    #[inline]
59    fn join(ptr: Self::Prim1, len: Self::Prim2, _: (), _: ()) -> Self {
60        Self {
61            ptr: WasmWord::join(ptr, (), (), ()),
62            len: WasmWord::join(len, (), (), ()),
63        }
64    }
65}
66
67#[inline]
68fn null_slice() -> WasmSlice {
69    WasmSlice::from_usize(0, 0)
70}
71
72pub struct WasmMutSlice {
73    pub slice: WasmSlice,
74    pub idx: u32,
75}
76
77impl WasmAbi for WasmMutSlice {
78    /// `self.slice.ptr`
79    type Prim1 = <WasmSlice as WasmAbi>::Prim1;
80    /// `self.slice.len`
81    type Prim2 = <WasmSlice as WasmAbi>::Prim2;
82    /// `self.idx`
83    type Prim3 = u32;
84    type Prim4 = ();
85
86    #[inline]
87    fn split(self) -> (Self::Prim1, Self::Prim2, u32, ()) {
88        let (ptr, len, (), ()) = self.slice.split();
89        (ptr, len, self.idx, ())
90    }
91
92    #[inline]
93    fn join(ptr: Self::Prim1, len: Self::Prim2, idx: u32, _: ()) -> Self {
94        Self {
95            slice: WasmSlice::join(ptr, len, (), ()),
96            idx,
97        }
98    }
99}
100
101/// The representation of a mutable slice passed from JS to Rust.
102pub struct MutSlice<T> {
103    /// A copy of the data in the JS typed array.
104    contents: Box<[T]>,
105    /// A reference to the original JS typed array.
106    js: JsValue,
107}
108
109impl<T> Drop for MutSlice<T> {
110    fn drop(&mut self) {
111        let byte_slice = unsafe {
112            core::slice::from_raw_parts(
113                self.contents.as_ptr() as *const u8,
114                self.contents.len() * mem::size_of::<T>(),
115            )
116        };
117        __wbindgen_copy_to_typed_array(byte_slice, &self.js);
118    }
119}
120
121impl<T> Deref for MutSlice<T> {
122    type Target = [T];
123
124    fn deref(&self) -> &[T] {
125        &self.contents
126    }
127}
128
129impl<T> DerefMut for MutSlice<T> {
130    fn deref_mut(&mut self) -> &mut [T] {
131        &mut self.contents
132    }
133}
134
135macro_rules! vectors {
136    ($($t:ty)*) => ($(
137        vectors_internal!($t);
138        vectors_internal!(MaybeUninit<$t>);
139    )*)
140}
141
142macro_rules! vectors_internal {
143    ($t:ty) => {
144        impl WasmDescribeVector for $t {
145            #[cfg_attr(wasm_bindgen_unstable_test_coverage, coverage(off))]
146            fn describe_vector() {
147                inform(VECTOR);
148                <$t>::describe();
149            }
150        }
151
152        impl VectorIntoWasmAbi for $t {
153            type Abi = WasmSlice;
154
155            #[inline]
156            fn vector_into_abi(vector: Box<[$t]>) -> WasmSlice {
157                let ptr = vector.as_ptr();
158                let len = vector.len();
159                mem::forget(vector);
160                WasmSlice::from_usize(ptr as usize, len)
161            }
162        }
163
164        impl VectorFromWasmAbi for $t {
165            type Abi = WasmSlice;
166
167            #[inline]
168            unsafe fn vector_from_abi(js: WasmSlice) -> Box<[$t]> {
169                let ptr = js.ptr.into_usize() as *mut $t;
170                let len = js.len.into_usize();
171                Vec::from_raw_parts(ptr, len, len).into_boxed_slice()
172            }
173        }
174
175        impl<'a> IntoWasmAbi for &'a [$t] {
176            type Abi = WasmSlice;
177
178            #[inline]
179            fn into_abi(self) -> WasmSlice {
180                WasmSlice::from_usize(self.as_ptr() as usize, self.len())
181            }
182        }
183
184        impl<'a> OptionIntoWasmAbi for &'a [$t] {
185            #[inline]
186            fn none() -> WasmSlice {
187                null_slice()
188            }
189        }
190
191        impl<'a> IntoWasmAbi for &'a mut [$t] {
192            type Abi = WasmSlice;
193
194            #[inline]
195            fn into_abi(self) -> WasmSlice {
196                (&*self).into_abi()
197            }
198        }
199
200        impl<'a> OptionIntoWasmAbi for &'a mut [$t] {
201            #[inline]
202            fn none() -> WasmSlice {
203                null_slice()
204            }
205        }
206
207        impl RefFromWasmAbi for [$t] {
208            type Abi = WasmSlice;
209            type Anchor = Box<[$t]>;
210
211            #[inline]
212            unsafe fn ref_from_abi(js: WasmSlice) -> Box<[$t]> {
213                <Box<[$t]>>::from_abi(js)
214            }
215        }
216
217        impl RefMutFromWasmAbi for [$t] {
218            type Abi = WasmMutSlice;
219            type Anchor = MutSlice<$t>;
220
221            #[inline]
222            unsafe fn ref_mut_from_abi(js: WasmMutSlice) -> MutSlice<$t> {
223                let contents = <Box<[$t]>>::from_abi(js.slice);
224                let js = JsValue::from_abi(js.idx);
225                MutSlice { contents, js }
226            }
227        }
228
229        impl LongRefFromWasmAbi for [$t] {
230            type Abi = WasmSlice;
231            type Anchor = Box<[$t]>;
232
233            #[inline]
234            unsafe fn long_ref_from_abi(js: WasmSlice) -> Box<[$t]> {
235                Self::ref_from_abi(js)
236            }
237        }
238    };
239}
240
241vectors! {
242    u8 i8 u16 i16 u32 i32 u64 i64 usize isize f32 f64
243}
244
245impl WasmDescribeVector for String {
246    #[cfg_attr(wasm_bindgen_unstable_test_coverage, coverage(off))]
247    fn describe_vector() {
248        inform(VECTOR);
249        inform(NAMED_EXTERNREF);
250        // Trying to use an actual loop for this breaks the Wasm interpreter.
251        inform(6);
252        inform('s' as u32);
253        inform('t' as u32);
254        inform('r' as u32);
255        inform('i' as u32);
256        inform('n' as u32);
257        inform('g' as u32);
258    }
259}
260
261impl VectorIntoWasmAbi for String {
262    type Abi = <Box<[JsValue]> as IntoWasmAbi>::Abi;
263
264    fn vector_into_abi(vector: Box<[Self]>) -> Self::Abi {
265        js_value_vector_into_abi(vector)
266    }
267}
268
269impl VectorFromWasmAbi for String {
270    type Abi = <Box<[JsValue]> as FromWasmAbi>::Abi;
271
272    unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]> {
273        js_value_vector_from_abi(js)
274    }
275}
276
277cfg_if! {
278    if #[cfg(feature = "enable-interning")] {
279        #[inline]
280        fn unsafe_get_cached_str(x: &str) -> Option<WasmSlice> {
281            // This uses 0 for the ptr as an indication that it is a JsValue and not a str.
282            crate::cache::intern::unsafe_get_str(x).map(|x| WasmSlice::from_usize(0, x as usize))
283        }
284
285    } else {
286        #[inline]
287        fn unsafe_get_cached_str(_x: &str) -> Option<WasmSlice> {
288            None
289        }
290    }
291}
292
293impl<T> IntoWasmAbi for Vec<T>
294where
295    Box<[T]>: IntoWasmAbi<Abi = WasmSlice>,
296{
297    type Abi = <Box<[T]> as IntoWasmAbi>::Abi;
298
299    #[inline]
300    fn into_abi(self) -> Self::Abi {
301        self.into_boxed_slice().into_abi()
302    }
303}
304
305impl<T> OptionIntoWasmAbi for Vec<T>
306where
307    Box<[T]>: IntoWasmAbi<Abi = WasmSlice>,
308{
309    #[inline]
310    fn none() -> WasmSlice {
311        null_slice()
312    }
313}
314
315/// Internal trait used by the `slice_to_array` macro codegen.
316///
317/// Produces the wire representation JS observes when an outgoing `&[T]`
318/// argument is rendered as a plain `Array`. There are two impl shapes,
319/// neither of which requires `T: Clone`:
320///
321/// * For primitive numeric `T` (`u8`, `i32`, `f64`, ...) the wire is a
322///   borrow of the slice memory directly — no allocation, no copy. The
323///   JS-side shim performs `Array.from(typedArrayView)` to materialise
324///   the JS `Array` and never frees the buffer.
325/// * For everything else (`String`, `JsValue`, imported types, exported
326///   types) the wire is a freshly allocated `Box<[u32]>` of externref
327///   indices — one per element, constructed via `&T -> JsValue` (which
328///   for handle-shaped types is a refcount bump on the existing JS
329///   slot, and for `String` / value-shaped types creates a fresh JS
330///   value). The JS-side shim reads the indices into a JS `Array` and
331///   frees the index buffer.
332///
333/// Both shapes carry the same `WasmSlice` (ptr + len) on the wire. The
334/// cli-support side picks the right JS shim based on the element
335/// `VectorKind` recovered from the descriptor.
336///
337/// Not user-facing: users opt in via `#[wasm_bindgen(slice_to_array)]`
338/// on an imported function or `extern "C"` block.
339pub trait VectorRefIntoWasmAbi {
340    /// Construct the wire representation for `Some(slice)`. The returned
341    /// `WasmSlice` is either a borrow of the input slice (primitive
342    /// case) or a buffer JS owns and frees (handle-shaped case).
343    fn slice_into_abi(slice: &[Self]) -> WasmSlice
344    where
345        Self: Sized;
346
347    /// Wire representation for `None` (used by `Option<&[T]>`). A null
348    /// `WasmSlice` (`ptr == 0`) is the convention shared with every
349    /// other vector-like ABI in the crate.
350    #[inline]
351    fn slice_none() -> WasmSlice
352    where
353        Self: Sized,
354    {
355        null_slice()
356    }
357}
358
359macro_rules! vector_ref_into_wasm_abi_primitive {
360    ($($t:ty)*) => ($(
361        impl VectorRefIntoWasmAbi for $t {
362            #[inline]
363            fn slice_into_abi(slice: &[Self]) -> WasmSlice {
364                // Borrow of the slice memory; the JS shim does
365                // `Array.from(view)` and never frees.
366                WasmSlice::from_usize(slice.as_ptr() as usize, slice.len())
367            }
368        }
369    )*);
370}
371
372vector_ref_into_wasm_abi_primitive!(u8 i8 u16 i16 u32 i32 u64 i64 usize isize f32 f64);
373
374impl<T> VectorRefIntoWasmAbi for T
375where
376    for<'a> &'a T: Into<JsValue>,
377{
378    #[inline]
379    fn slice_into_abi(slice: &[Self]) -> WasmSlice {
380        // Build a fresh `[JsValue]` buffer one element at a time. The
381        // existing `Vec<JsValue>` ABI hands off the buffer to JS; the
382        // JS shim drops each externref slot it reads and frees the
383        // buffer.
384        let js_vals: Box<[JsValue]> = slice.iter().map(Into::into).collect();
385        js_vals.into_abi()
386    }
387}
388
389impl<T> FromWasmAbi for Vec<T>
390where
391    Box<[T]>: FromWasmAbi<Abi = WasmSlice>,
392{
393    type Abi = <Box<[T]> as FromWasmAbi>::Abi;
394
395    #[inline]
396    unsafe fn from_abi(js: Self::Abi) -> Self {
397        <Box<[T]>>::from_abi(js).into()
398    }
399}
400
401impl<T> OptionFromWasmAbi for Vec<T>
402where
403    Box<[T]>: FromWasmAbi<Abi = WasmSlice>,
404{
405    #[inline]
406    fn is_none(abi: &WasmSlice) -> bool {
407        abi.ptr.is_zero()
408    }
409}
410
411impl IntoWasmAbi for String {
412    type Abi = <Vec<u8> as IntoWasmAbi>::Abi;
413
414    #[inline]
415    fn into_abi(self) -> Self::Abi {
416        // This is safe because the JsValue is immediately looked up in the heap and
417        // then returned, so use-after-free cannot occur.
418        unsafe_get_cached_str(&self).unwrap_or_else(|| self.into_bytes().into_abi())
419    }
420}
421
422impl OptionIntoWasmAbi for String {
423    #[inline]
424    fn none() -> Self::Abi {
425        null_slice()
426    }
427}
428
429impl FromWasmAbi for String {
430    type Abi = <Vec<u8> as FromWasmAbi>::Abi;
431
432    #[inline]
433    unsafe fn from_abi(js: Self::Abi) -> Self {
434        String::from_utf8_unchecked(<Vec<u8>>::from_abi(js))
435    }
436}
437
438impl OptionFromWasmAbi for String {
439    #[inline]
440    fn is_none(slice: &WasmSlice) -> bool {
441        slice.ptr.is_zero()
442    }
443}
444
445impl<'a> IntoWasmAbi for &'a str {
446    type Abi = <&'a [u8] as IntoWasmAbi>::Abi;
447
448    #[inline]
449    fn into_abi(self) -> Self::Abi {
450        // This is safe because the JsValue is immediately looked up in the heap and
451        // then returned, so use-after-free cannot occur.
452        unsafe_get_cached_str(self).unwrap_or_else(|| self.as_bytes().into_abi())
453    }
454}
455
456impl OptionIntoWasmAbi for &str {
457    #[inline]
458    fn none() -> Self::Abi {
459        null_slice()
460    }
461}
462
463impl RefFromWasmAbi for str {
464    type Abi = <[u8] as RefFromWasmAbi>::Abi;
465    type Anchor = Box<str>;
466
467    #[inline]
468    unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor {
469        mem::transmute::<Box<[u8]>, Box<str>>(<Box<[u8]>>::from_abi(js))
470    }
471}
472
473impl LongRefFromWasmAbi for str {
474    type Abi = <[u8] as RefFromWasmAbi>::Abi;
475    type Anchor = Box<str>;
476
477    #[inline]
478    unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor {
479        Self::ref_from_abi(js)
480    }
481}
482
483unsafe impl ErasableGeneric for &str {
484    type Repr = &'static str;
485}
486
487unsafe impl<T: ErasableGeneric> ErasableGeneric for Box<[T]> {
488    type Repr = Box<[T::Repr]>;
489}
490
491impl UpcastFrom<&str> for &str {}
492
493impl<T, Target> UpcastFrom<Box<[T]>> for Box<[Target]> where Target: UpcastFrom<T> {}
494
495unsafe impl<T: ErasableGeneric> ErasableGeneric for Vec<T> {
496    type Repr = Vec<T::Repr>;
497}
498
499impl<T, Target> UpcastFrom<Vec<T>> for Vec<Target> where Target: UpcastFrom<T> {}
500
501impl<T: VectorIntoWasmAbi> IntoWasmAbi for Box<[T]> {
502    type Abi = <T as VectorIntoWasmAbi>::Abi;
503
504    fn into_abi(self) -> Self::Abi {
505        T::vector_into_abi(self)
506    }
507}
508
509impl<T> OptionIntoWasmAbi for Box<[T]>
510where
511    Self: IntoWasmAbi<Abi = WasmSlice>,
512{
513    fn none() -> WasmSlice {
514        null_slice()
515    }
516}
517
518impl<T: VectorFromWasmAbi> FromWasmAbi for Box<[T]> {
519    type Abi = <T as VectorFromWasmAbi>::Abi;
520
521    unsafe fn from_abi(js: Self::Abi) -> Self {
522        T::vector_from_abi(js)
523    }
524}
525
526impl<T> OptionFromWasmAbi for Box<[T]>
527where
528    Self: FromWasmAbi<Abi = WasmSlice>,
529{
530    fn is_none(slice: &WasmSlice) -> bool {
531        slice.ptr.is_zero()
532    }
533}
534
535impl<T: ErasableGeneric<Repr = JsValue> + WasmDescribe> VectorFromWasmAbi for T {
536    type Abi = WasmSlice;
537
538    #[inline]
539    unsafe fn vector_from_abi(js: WasmSlice) -> Box<[Self]> {
540        let ptr = js.ptr.into_usize() as *mut T;
541        let len = js.len.into_usize();
542        Vec::from_raw_parts(ptr, len, len).into_boxed_slice()
543    }
544}
545
546impl<T: ErasableGeneric<Repr = JsValue> + WasmDescribe> VectorIntoWasmAbi for T {
547    type Abi = WasmSlice;
548
549    #[inline]
550    fn vector_into_abi(vector: Box<[T]>) -> WasmSlice {
551        let ptr = vector.as_ptr();
552        let len = vector.len();
553        mem::forget(vector);
554        WasmSlice::from_usize(ptr as usize, len)
555    }
556}
557
558// JsValue-like slice support (Rust-to-JS only)
559// JsValue-like are repr(transparent) over u32, so &[JsValue] is a contiguous array of heap indices
560
561unsafe impl<T: ErasableGeneric> ErasableGeneric for &[T] {
562    type Repr = &'static [T::Repr];
563}
564
565impl<'a, T, Target> UpcastFrom<&'a [T]> for &'a [Target] where Target: UpcastFrom<T> {}
566
567impl<T: ErasableGeneric<Repr = JsValue> + WasmDescribe> IntoWasmAbi for &[T] {
568    type Abi = WasmSlice;
569
570    #[inline]
571    fn into_abi(self) -> WasmSlice {
572        WasmSlice::from_usize(self.as_ptr() as usize, self.len())
573    }
574}
575
576impl<T: ErasableGeneric<Repr = JsValue> + WasmDescribe> OptionIntoWasmAbi for &[T] {
577    #[inline]
578    fn none() -> WasmSlice {
579        null_slice()
580    }
581}