wasmtime/
signatures.rs

1//! Implement a registry of function signatures, for fast indirect call
2//! signature checking.
3
4use std::{
5    collections::{hash_map::Entry, HashMap},
6    sync::RwLock,
7};
8use std::{convert::TryFrom, sync::Arc};
9use wasmtime_environ::{ModuleTypes, PrimaryMap, SignatureIndex, WasmFuncType};
10use wasmtime_runtime::{VMSharedSignatureIndex, VMTrampoline};
11
12/// Represents a collection of shared signatures.
13///
14/// This is used to register shared signatures with a shared signature registry.
15///
16/// The collection will unregister any contained signatures with the registry
17/// when dropped.
18#[derive(Debug)]
19pub struct SignatureCollection {
20    registry: Arc<RwLock<SignatureRegistryInner>>,
21    signatures: PrimaryMap<SignatureIndex, VMSharedSignatureIndex>,
22    trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
23}
24
25impl SignatureCollection {
26    /// Creates a signature collection for a module given the module's signatures
27    /// and trampolines.
28    pub fn new_for_module(
29        registry: &SignatureRegistry,
30        types: &ModuleTypes,
31        trampolines: impl Iterator<Item = (SignatureIndex, VMTrampoline)>,
32    ) -> Self {
33        let (signatures, trampolines) = registry
34            .0
35            .write()
36            .unwrap()
37            .register_for_module(types, trampolines);
38
39        Self {
40            registry: registry.0.clone(),
41            signatures,
42            trampolines,
43        }
44    }
45
46    /// Treats the signature collection as a map from a module signature index to
47    /// registered shared signature indexes.
48    ///
49    /// This is used for looking up module shared signature indexes during module
50    /// instantiation.
51    pub fn as_module_map(&self) -> &PrimaryMap<SignatureIndex, VMSharedSignatureIndex> {
52        &self.signatures
53    }
54
55    /// Gets the shared signature index given a module signature index.
56    pub fn shared_signature(&self, index: SignatureIndex) -> Option<VMSharedSignatureIndex> {
57        self.signatures.get(index).copied()
58    }
59
60    /// Gets a trampoline for a registered signature.
61    pub fn trampoline(&self, index: VMSharedSignatureIndex) -> Option<VMTrampoline> {
62        self.trampolines.get(&index).copied()
63    }
64}
65
66impl Drop for SignatureCollection {
67    fn drop(&mut self) {
68        if !self.signatures.is_empty() || !self.trampolines.is_empty() {
69            self.registry.write().unwrap().unregister_signatures(self);
70        }
71    }
72}
73
74#[derive(Debug)]
75struct RegistryEntry {
76    references: usize,
77    ty: WasmFuncType,
78}
79
80#[derive(Debug, Default)]
81struct SignatureRegistryInner {
82    map: HashMap<WasmFuncType, VMSharedSignatureIndex>,
83    entries: Vec<Option<RegistryEntry>>,
84    free: Vec<VMSharedSignatureIndex>,
85}
86
87impl SignatureRegistryInner {
88    fn register_for_module(
89        &mut self,
90        types: &ModuleTypes,
91        trampolines: impl Iterator<Item = (SignatureIndex, VMTrampoline)>,
92    ) -> (
93        PrimaryMap<SignatureIndex, VMSharedSignatureIndex>,
94        HashMap<VMSharedSignatureIndex, VMTrampoline>,
95    ) {
96        let mut sigs = PrimaryMap::default();
97        let mut map = HashMap::default();
98
99        for (idx, ty) in types.wasm_signatures() {
100            let b = sigs.push(self.register(ty));
101            assert_eq!(idx, b);
102        }
103
104        for (index, trampoline) in trampolines {
105            map.insert(sigs[index], trampoline);
106        }
107
108        (sigs, map)
109    }
110
111    fn register(&mut self, ty: &WasmFuncType) -> VMSharedSignatureIndex {
112        let len = self.map.len();
113
114        let index = match self.map.entry(ty.clone()) {
115            Entry::Occupied(e) => *e.get(),
116            Entry::Vacant(e) => {
117                let (index, entry) = match self.free.pop() {
118                    Some(index) => (index, &mut self.entries[index.bits() as usize]),
119                    None => {
120                        // Keep `index_map` len under 2**32 -- VMSharedSignatureIndex::new(std::u32::MAX)
121                        // is reserved for VMSharedSignatureIndex::default().
122                        assert!(
123                            len < std::u32::MAX as usize,
124                            "Invariant check: index_map.len() < std::u32::MAX"
125                        );
126                        debug_assert_eq!(len, self.entries.len());
127
128                        let index = VMSharedSignatureIndex::new(u32::try_from(len).unwrap());
129                        self.entries.push(None);
130
131                        (index, self.entries.last_mut().unwrap())
132                    }
133                };
134
135                // The entry should be missing for one just allocated or
136                // taken from the free list
137                assert!(entry.is_none());
138
139                *entry = Some(RegistryEntry {
140                    references: 0,
141                    ty: ty.clone(),
142                });
143
144                *e.insert(index)
145            }
146        };
147
148        self.entries[index.bits() as usize]
149            .as_mut()
150            .unwrap()
151            .references += 1;
152
153        index
154    }
155
156    fn unregister_signatures(&mut self, collection: &SignatureCollection) {
157        // If the collection has a populated signatures map, use it to deregister
158        // This is always 1:1 from entry to registration
159        if !collection.signatures.is_empty() {
160            for (_, index) in collection.signatures.iter() {
161                self.unregister_entry(*index, 1);
162            }
163        } else {
164            // Otherwise, use the trampolines map, which has reference counts related
165            // to the stored index
166            for (index, _) in collection.trampolines.iter() {
167                self.unregister_entry(*index, 1);
168            }
169        }
170    }
171
172    fn unregister_entry(&mut self, index: VMSharedSignatureIndex, count: usize) {
173        let removed = {
174            let entry = self.entries[index.bits() as usize].as_mut().unwrap();
175
176            debug_assert!(entry.references >= count);
177            entry.references -= count;
178
179            if entry.references == 0 {
180                self.map.remove(&entry.ty);
181                self.free.push(index);
182                true
183            } else {
184                false
185            }
186        };
187
188        if removed {
189            self.entries[index.bits() as usize] = None;
190        }
191    }
192}
193
194// `SignatureRegistryInner` implements `Drop` in debug builds to assert that
195// all signatures have been unregistered for the registry.
196#[cfg(debug_assertions)]
197impl Drop for SignatureRegistryInner {
198    fn drop(&mut self) {
199        assert!(
200            self.map.is_empty() && self.free.len() == self.entries.len(),
201            "signature registry not empty"
202        );
203    }
204}
205
206/// Implements a shared signature registry.
207///
208/// WebAssembly requires that the caller and callee signatures in an indirect
209/// call must match. To implement this efficiently, keep a registry of all
210/// signatures, shared by all instances, so that call sites can just do an
211/// index comparison.
212#[derive(Debug)]
213pub struct SignatureRegistry(Arc<RwLock<SignatureRegistryInner>>);
214
215impl SignatureRegistry {
216    /// Creates a new shared signature registry.
217    pub fn new() -> Self {
218        Self(Arc::new(RwLock::new(SignatureRegistryInner::default())))
219    }
220
221    /// Looks up a function type from a shared signature index.
222    pub fn lookup_type(&self, index: VMSharedSignatureIndex) -> Option<WasmFuncType> {
223        self.0
224            .read()
225            .unwrap()
226            .entries
227            .get(index.bits() as usize)
228            .and_then(|e| e.as_ref().map(|e| &e.ty).cloned())
229    }
230
231    /// Registers a single function with the collection.
232    ///
233    /// Returns the shared signature index for the function.
234    pub fn register(&self, ty: &WasmFuncType) -> VMSharedSignatureIndex {
235        self.0.write().unwrap().register(ty)
236    }
237
238    /// Registers a single function with the collection.
239    ///
240    /// Returns the shared signature index for the function.
241    pub unsafe fn unregister(&self, sig: VMSharedSignatureIndex) {
242        self.0.write().unwrap().unregister_entry(sig, 1)
243    }
244}