Skip to content

Commit 445cc87

Browse files
authored
Fix a "trampoline missing" panic with components (#4296)
One test case I wrote recently was to import a lowered function into a wasm module and then immediately export it. This previously didn't work because trampoline lookup would fail as the original `VMCallerCheckedAnyfunc` function pointer points into the `trampoline_obj` of a component which wasn't registered with the `ModuleRegistry`. This plumbs through the necessary configuration to get that all hooked up.
1 parent 6778b4f commit 445cc87

File tree

7 files changed

+204
-28
lines changed

7 files changed

+204
-28
lines changed

crates/wasmtime/src/component/component.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::signatures::SignatureCollection;
22
use crate::{Engine, Module};
33
use anyhow::{bail, Context, Result};
4+
use std::collections::HashMap;
45
use std::fs;
56
use std::ops::Range;
67
use std::path::Path;
@@ -164,13 +165,33 @@ impl Component {
164165
let code = trampoline_obj.publish()?;
165166
let text = wasmtime_jit::subslice_range(code.text, code.mmap);
166167

168+
// This map is used to register all known tramplines in the
169+
// `SignatureCollection` created below. This is later consulted during
170+
// `ModuleRegistry::lookup_trampoline` if a trampoline needs to be
171+
// located for a signature index where the original function pointer
172+
// is that of the `trampolines` created above.
173+
//
174+
// This situation arises when a core wasm module imports a lowered
175+
// function and then immediately exports it. Wasmtime will lookup an
176+
// entry trampoline for the exported function which is actually a
177+
// lowered host function, hence an entry in the `trampolines` variable
178+
// above, and the type of that function will be stored in this
179+
// `vmtrampolines` map since the type is guaranteed to have escaped
180+
// from at least one of the modules we compiled prior.
181+
let mut vmtrampolines = HashMap::new();
182+
for (_, module) in static_modules.iter() {
183+
for (idx, trampoline, _) in module.compiled_module().trampolines() {
184+
vmtrampolines.insert(idx, trampoline);
185+
}
186+
}
187+
167188
// FIXME: for the same reason as above where each module is
168189
// re-registering everything this should only be registered once. This
169190
// is benign for now but could do with refactorings later on.
170191
let signatures = SignatureCollection::new_for_module(
171192
engine.signatures(),
172193
types.module_types(),
173-
[].into_iter(),
194+
vmtrampolines.into_iter(),
174195
);
175196

176197
Ok(Component {
@@ -202,9 +223,13 @@ impl Component {
202223
&self.inner.signatures
203224
}
204225

226+
pub(crate) fn text(&self) -> &[u8] {
227+
&self.inner.trampoline_obj.mmap()[self.inner.text.clone()]
228+
}
229+
205230
pub(crate) fn trampoline_ptr(&self, index: LoweredIndex) -> NonNull<VMFunctionBody> {
206231
let info = &self.inner.trampolines[index];
207-
let text = &self.inner.trampoline_obj.mmap()[self.inner.text.clone()];
232+
let text = self.text();
208233
let trampoline = &text[info.start as usize..][..info.length as usize];
209234
NonNull::new(trampoline.as_ptr() as *mut VMFunctionBody).unwrap()
210235
}

crates/wasmtime/src/component/instance.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ impl<'a> Instantiator<'a> {
210210
imports: &'a PrimaryMap<RuntimeImportIndex, RuntimeImport>,
211211
) -> Instantiator<'a> {
212212
let env_component = component.env_component();
213+
store.modules_mut().register_component(component);
213214
Instantiator {
214215
component,
215216
imports,

crates/wasmtime/src/instance.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ impl Instance {
242242

243243
// Register the module just before instantiation to ensure we keep the module
244244
// properly referenced while in use by the store.
245-
store.modules_mut().register(module);
245+
store.modules_mut().register_module(module);
246246

247247
// The first thing we do is issue an instance allocation request
248248
// to the instance allocator. This, on success, will give us an

crates/wasmtime/src/module/registry.rs

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Implements a registry of modules for a store.
22
3+
#[cfg(feature = "component-model")]
4+
use crate::component::Component;
35
use crate::{Engine, Module};
46
use std::{
57
collections::BTreeMap,
@@ -24,12 +26,21 @@ lazy_static::lazy_static! {
2426
#[derive(Default)]
2527
pub struct ModuleRegistry {
2628
// Keyed by the end address of the module's code in memory.
27-
modules_with_code: BTreeMap<usize, Module>,
29+
//
30+
// The value here is the start address and the module/component it
31+
// corresponds to.
32+
modules_with_code: BTreeMap<usize, (usize, ModuleOrComponent)>,
2833

2934
// Preserved for keeping data segments alive or similar
3035
modules_without_code: Vec<Module>,
3136
}
3237

38+
enum ModuleOrComponent {
39+
Module(Module),
40+
#[cfg(feature = "component-model")]
41+
Component(Component),
42+
}
43+
3344
fn start(module: &Module) -> usize {
3445
assert!(!module.compiled_module().code().is_empty());
3546
module.compiled_module().code().as_ptr() as usize
@@ -42,15 +53,23 @@ impl ModuleRegistry {
4253
}
4354

4455
fn module(&self, pc: usize) -> Option<&Module> {
45-
let (end, module) = self.modules_with_code.range(pc..).next()?;
46-
if pc < start(module) || *end < pc {
56+
match self.module_or_component(pc)? {
57+
ModuleOrComponent::Module(m) => Some(m),
58+
#[cfg(feature = "component-model")]
59+
ModuleOrComponent::Component(_) => None,
60+
}
61+
}
62+
63+
fn module_or_component(&self, pc: usize) -> Option<&ModuleOrComponent> {
64+
let (end, (start, module)) = self.modules_with_code.range(pc..).next()?;
65+
if pc < *start || *end < pc {
4766
return None;
4867
}
4968
Some(module)
5069
}
5170

5271
/// Registers a new module with the registry.
53-
pub fn register(&mut self, module: &Module) {
72+
pub fn register_module(&mut self, module: &Module) {
5473
let compiled_module = module.compiled_module();
5574

5675
// If there's not actually any functions in this module then we may
@@ -61,39 +80,70 @@ impl ModuleRegistry {
6180
// modules and retain them.
6281
if compiled_module.finished_functions().len() == 0 {
6382
self.modules_without_code.push(module.clone());
64-
return;
83+
} else {
84+
// The module code range is exclusive for end, so make it inclusive as it
85+
// may be a valid PC value
86+
let start_addr = start(module);
87+
let end_addr = start_addr + compiled_module.code().len() - 1;
88+
self.register(
89+
start_addr,
90+
end_addr,
91+
ModuleOrComponent::Module(module.clone()),
92+
);
6593
}
94+
}
6695

67-
// The module code range is exclusive for end, so make it inclusive as it
68-
// may be a valid PC value
69-
let start_addr = start(module);
70-
let end_addr = start_addr + compiled_module.code().len() - 1;
96+
#[cfg(feature = "component-model")]
97+
pub fn register_component(&mut self, component: &Component) {
98+
// If there's no text section associated with this component (e.g. no
99+
// lowered functions) then there's nothing to register, otherwise it's
100+
// registered along the same lines as modules above.
101+
//
102+
// Note that empty components don't need retaining here since it doesn't
103+
// have data segments like empty modules.
104+
let text = component.text();
105+
if text.is_empty() {
106+
return;
107+
}
108+
let start = text.as_ptr() as usize;
109+
self.register(
110+
start,
111+
start + text.len() - 1,
112+
ModuleOrComponent::Component(component.clone()),
113+
);
114+
}
71115

116+
/// Registers a new module with the registry.
117+
fn register(&mut self, start_addr: usize, end_addr: usize, item: ModuleOrComponent) {
72118
// Ensure the module isn't already present in the registry
73119
// This is expected when a module is instantiated multiple times in the
74120
// same store
75-
if let Some(m) = self.modules_with_code.get(&end_addr) {
76-
assert_eq!(start(m), start_addr);
121+
if let Some((other_start, _)) = self.modules_with_code.get(&end_addr) {
122+
assert_eq!(*other_start, start_addr);
77123
return;
78124
}
79125

80126
// Assert that this module's code doesn't collide with any other
81127
// registered modules
82-
if let Some((_, prev)) = self.modules_with_code.range(end_addr..).next() {
83-
assert!(start(prev) > end_addr);
128+
if let Some((_, (prev_start, _))) = self.modules_with_code.range(start_addr..).next() {
129+
assert!(*prev_start > end_addr);
84130
}
85131
if let Some((prev_end, _)) = self.modules_with_code.range(..=start_addr).next_back() {
86132
assert!(*prev_end < start_addr);
87133
}
88134

89-
let prev = self.modules_with_code.insert(end_addr, module.clone());
135+
let prev = self.modules_with_code.insert(end_addr, (start_addr, item));
90136
assert!(prev.is_none());
91137
}
92138

93139
/// Looks up a trampoline from an anyfunc.
94140
pub fn lookup_trampoline(&self, anyfunc: &VMCallerCheckedAnyfunc) -> Option<VMTrampoline> {
95-
let module = self.module(anyfunc.func_ptr.as_ptr() as usize)?;
96-
module.signatures().trampoline(anyfunc.type_index)
141+
let signatures = match self.module_or_component(anyfunc.func_ptr.as_ptr() as usize)? {
142+
ModuleOrComponent::Module(m) => m.signatures(),
143+
#[cfg(feature = "component-model")]
144+
ModuleOrComponent::Component(c) => c.signatures(),
145+
};
146+
signatures.trampoline(anyfunc.type_index)
97147
}
98148
}
99149

crates/wasmtime/src/signatures.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use wasmtime_runtime::{VMSharedSignatureIndex, VMTrampoline};
1919
pub struct SignatureCollection {
2020
registry: Arc<RwLock<SignatureRegistryInner>>,
2121
signatures: PrimaryMap<SignatureIndex, VMSharedSignatureIndex>,
22-
trampolines: HashMap<VMSharedSignatureIndex, (usize, VMTrampoline)>,
22+
trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
2323
}
2424

2525
impl SignatureCollection {
@@ -59,9 +59,7 @@ impl SignatureCollection {
5959

6060
/// Gets a trampoline for a registered signature.
6161
pub fn trampoline(&self, index: VMSharedSignatureIndex) -> Option<VMTrampoline> {
62-
self.trampolines
63-
.get(&index)
64-
.map(|(_, trampoline)| *trampoline)
62+
self.trampolines.get(&index).copied()
6563
}
6664
}
6765

@@ -93,7 +91,7 @@ impl SignatureRegistryInner {
9391
trampolines: impl Iterator<Item = (SignatureIndex, VMTrampoline)>,
9492
) -> (
9593
PrimaryMap<SignatureIndex, VMSharedSignatureIndex>,
96-
HashMap<VMSharedSignatureIndex, (usize, VMTrampoline)>,
94+
HashMap<VMSharedSignatureIndex, VMTrampoline>,
9795
) {
9896
let mut sigs = PrimaryMap::default();
9997
let mut map = HashMap::default();
@@ -104,7 +102,7 @@ impl SignatureRegistryInner {
104102
}
105103

106104
for (index, trampoline) in trampolines {
107-
map.insert(sigs[index], (1, trampoline));
105+
map.insert(sigs[index], trampoline);
108106
}
109107

110108
(sigs, map)
@@ -165,8 +163,8 @@ impl SignatureRegistryInner {
165163
} else {
166164
// Otherwise, use the trampolines map, which has reference counts related
167165
// to the stored index
168-
for (index, (count, _)) in collection.trampolines.iter() {
169-
self.unregister_entry(*index, *count);
166+
for (index, _) in collection.trampolines.iter() {
167+
self.unregister_entry(*index, 1);
170168
}
171169
}
172170
}

tests/all/component_model/func.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use anyhow::Result;
33
use std::rc::Rc;
44
use std::sync::Arc;
55
use wasmtime::component::*;
6-
use wasmtime::{Store, Trap, TrapCode};
6+
use wasmtime::{Store, StoreContextMut, Trap, TrapCode};
77

88
const CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000;
99
const CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000;
@@ -1858,3 +1858,58 @@ fn invalid_alignment() -> Result<()> {
18581858

18591859
Ok(())
18601860
}
1861+
1862+
#[test]
1863+
fn drop_component_still_works() -> Result<()> {
1864+
let component = r#"
1865+
(component
1866+
(import "f" (func $f))
1867+
1868+
(core func $f_lower
1869+
(canon lower (func $f))
1870+
)
1871+
(core module $m
1872+
(import "" "" (func $f))
1873+
1874+
(func $f2
1875+
call $f
1876+
call $f
1877+
)
1878+
1879+
(export "f" (func $f2))
1880+
)
1881+
(core instance $i (instantiate $m
1882+
(with "" (instance
1883+
(export "" (func $f_lower))
1884+
))
1885+
))
1886+
(func (export "f")
1887+
(canon lift
1888+
(core func $i "f")
1889+
)
1890+
)
1891+
)
1892+
"#;
1893+
1894+
let (mut store, instance) = {
1895+
let engine = super::engine();
1896+
let component = Component::new(&engine, component)?;
1897+
let mut store = Store::new(&engine, 0);
1898+
let mut linker = Linker::new(&engine);
1899+
linker
1900+
.root()
1901+
.func_wrap("f", |mut store: StoreContextMut<'_, u32>| -> Result<()> {
1902+
*store.data_mut() += 1;
1903+
Ok(())
1904+
})?;
1905+
let instance = linker.instantiate(&mut store, &component)?;
1906+
(store, instance)
1907+
};
1908+
1909+
let f = instance.get_typed_func::<(), (), _>(&mut store, "f")?;
1910+
assert_eq!(*store.data(), 0);
1911+
f.call(&mut store, ())?;
1912+
assert_eq!(*store.data(), 2);
1913+
1914+
Ok(())
1915+
}

tests/all/component_model/import.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,3 +614,50 @@ fn bad_import_alignment() -> Result<()> {
614614
assert!(trap.to_string().contains("pointer not aligned"), "{}", trap);
615615
Ok(())
616616
}
617+
618+
#[test]
619+
fn no_actual_wasm_code() -> Result<()> {
620+
let component = r#"
621+
(component
622+
(import "f" (func $f))
623+
624+
(core func $f_lower
625+
(canon lower (func $f))
626+
)
627+
(core module $m
628+
(import "" "" (func $f))
629+
(export "f" (func $f))
630+
)
631+
(core instance $i (instantiate $m
632+
(with "" (instance
633+
(export "" (func $f_lower))
634+
))
635+
))
636+
(func (export "thunk")
637+
(canon lift
638+
(core func $i "f")
639+
)
640+
)
641+
)
642+
"#;
643+
644+
let engine = super::engine();
645+
let component = Component::new(&engine, component)?;
646+
let mut store = Store::new(&engine, 0);
647+
let mut linker = Linker::new(&engine);
648+
linker
649+
.root()
650+
.func_wrap("f", |mut store: StoreContextMut<'_, u32>| -> Result<()> {
651+
*store.data_mut() += 1;
652+
Ok(())
653+
})?;
654+
655+
let instance = linker.instantiate(&mut store, &component)?;
656+
let thunk = instance.get_typed_func::<(), (), _>(&mut store, "thunk")?;
657+
658+
assert_eq!(*store.data(), 0);
659+
thunk.call(&mut store, ())?;
660+
assert_eq!(*store.data(), 1);
661+
662+
Ok(())
663+
}

0 commit comments

Comments
 (0)