wasm_bindgen/
externref.rs

1use crate::JsValue;
2
3use alloc::slice;
4use alloc::vec::Vec;
5use core::cell::Cell;
6use core::cmp::max;
7
8externs! {
9    #[link(wasm_import_module = "__wbindgen_externref_xform__")]
10    extern "C" {
11        fn __wbindgen_externref_table_grow(delta: usize) -> i32;
12        fn __wbindgen_externref_table_set_null(idx: usize) -> ();
13    }
14}
15
16pub struct Slab {
17    data: Vec<usize>,
18    head: usize,
19    base: usize,
20}
21
22impl Slab {
23    fn new() -> Slab {
24        Slab {
25            data: Vec::new(),
26            head: 0,
27            base: 0,
28        }
29    }
30
31    fn alloc(&mut self) -> usize {
32        let ret = self.head;
33        if ret == self.data.len() {
34            let curr_len = self.data.len();
35            if curr_len == self.data.capacity() {
36                let extra = max(128, curr_len);
37                let r = unsafe { __wbindgen_externref_table_grow(extra) };
38                if r == -1 {
39                    internal_error("table grow failure")
40                }
41                if self.base == 0 {
42                    self.base = r as usize;
43                } else if self.base + self.data.len() != r as usize {
44                    internal_error("someone else allocated table entries?")
45                }
46
47                if self.data.try_reserve_exact(extra).is_err() {
48                    internal_error("allocation failure");
49                }
50            }
51
52            // custom condition to ensure `push` below doesn't call `reserve` in
53            // optimized builds which pulls in lots of panic infrastructure
54            if self.data.len() >= self.data.capacity() {
55                internal_error("push should be infallible now")
56            }
57            self.data.push(ret + 1);
58        }
59
60        // usage of `get_mut` thwarts panicking infrastructure in optimized
61        // builds
62        match self.data.get_mut(ret) {
63            Some(slot) => self.head = *slot,
64            None => internal_error("ret out of bounds"),
65        }
66        ret + self.base
67    }
68
69    fn dealloc(&mut self, slot: usize) {
70        if slot < self.base {
71            internal_error("free reserved slot");
72        }
73        let slot = slot - self.base;
74
75        // usage of `get_mut` thwarts panicking infrastructure in optimized
76        // builds
77        match self.data.get_mut(slot) {
78            Some(ptr) => {
79                *ptr = self.head;
80                self.head = slot;
81            }
82            None => internal_error("slot out of bounds"),
83        }
84    }
85
86    fn live_count(&self) -> u32 {
87        let mut free_count = 0;
88        let mut next = self.head;
89        while next < self.data.len() {
90            debug_assert!((free_count as usize) < self.data.len());
91            free_count += 1;
92            match self.data.get(next) {
93                Some(n) => next = *n,
94                None => internal_error("slot out of bounds"),
95            };
96        }
97        self.data.len() as u32 - free_count
98    }
99}
100
101fn internal_error(msg: &str) -> ! {
102    cfg_if::cfg_if! {
103        if #[cfg(debug_assertions)] {
104            super::throw_str(msg)
105        } else if #[cfg(feature = "std")] {
106            std::process::abort();
107        } else if #[cfg(all(
108            target_arch = "wasm32",
109            any(target_os = "unknown", target_os = "none")
110        ))] {
111            core::arch::wasm32::unreachable();
112        } else {
113            unreachable!()
114        }
115    }
116}
117
118// Management of `externref` is always thread local since an `externref` value
119// can't cross threads in wasm. Indices as a result are always thread-local.
120#[cfg(feature = "std")]
121std::thread_local!(static HEAP_SLAB: Cell<Slab> = Cell::new(Slab::new()));
122#[cfg(not(feature = "std"))]
123#[cfg_attr(target_feature = "atomics", thread_local)]
124static HEAP_SLAB: crate::__rt::LazyCell<Cell<Slab>> =
125    crate::__rt::LazyCell::new(|| Cell::new(Slab::new()));
126
127#[no_mangle]
128pub extern "C" fn __externref_table_alloc() -> usize {
129    HEAP_SLAB
130        .try_with(|slot| {
131            let mut slab = slot.replace(Slab::new());
132            let ret = slab.alloc();
133            slot.replace(slab);
134            ret
135        })
136        .unwrap_or_else(|_| internal_error("tls access failure"))
137}
138
139#[no_mangle]
140pub extern "C" fn __externref_table_dealloc(idx: usize) {
141    if idx < super::JSIDX_RESERVED as usize {
142        return;
143    }
144    // clear this value from the table so while the table slot is un-allocated
145    // we don't keep around a strong reference to a potentially large object
146    unsafe {
147        __wbindgen_externref_table_set_null(idx);
148    }
149    HEAP_SLAB
150        .try_with(|slot| {
151            let mut slab = slot.replace(Slab::new());
152            slab.dealloc(idx);
153            slot.replace(slab);
154        })
155        .unwrap_or_else(|_| internal_error("tls access failure"))
156}
157
158#[no_mangle]
159pub unsafe extern "C" fn __externref_drop_slice(ptr: *mut JsValue, len: usize) {
160    for slot in slice::from_raw_parts_mut(ptr, len) {
161        __externref_table_dealloc(slot.idx as usize);
162    }
163}
164
165// Implementation of `__wbindgen_externref_heap_live_count` for when we are using
166// `externref` instead of the JS `heap`.
167#[no_mangle]
168pub unsafe extern "C" fn __externref_heap_live_count() -> u32 {
169    HEAP_SLAB
170        .try_with(|slot| {
171            let slab = slot.replace(Slab::new());
172            let count = slab.live_count();
173            slot.replace(slab);
174            count
175        })
176        .unwrap_or_else(|_| internal_error("tls access failure"))
177}