Skip to content

Commit 522bb60

Browse files
committed
Add support for core re-entrancy + test
1 parent 192cfd4 commit 522bb60

File tree

7 files changed

+297
-117
lines changed

7 files changed

+297
-117
lines changed

crates/wasmtime/src/runtime/func.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,7 +1388,7 @@ impl Func {
13881388
flat_params,
13891389
&mut caller.store.0,
13901390
)?;
1391-
rr::core_hooks::replay_host_func_return(values_vec, &mut caller.store.0)?;
1391+
rr::core_hooks::replay_host_func_return(values_vec, &mut caller)?;
13921392
}
13931393

13941394
// Restore our `val_vec` back into the store so it's usable for the next
@@ -2544,7 +2544,7 @@ impl HostContext {
25442544
// Replay the return values
25452545
rr::core_hooks::replay_host_func_return(
25462546
unsafe { &mut args.as_mut()[..num_results] },
2547-
caller.store.0,
2547+
&mut caller,
25482548
)?;
25492549
}
25502550

crates/wasmtime/src/runtime/rr/core.rs

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -346,22 +346,6 @@ pub trait Replayer: Iterator<Item = Result<RREvent, ReplayError>> {
346346
T::try_from(self.next_event()?).map_err(|e| e.into())
347347
}
348348

349-
/// Pop the next replay event and calls `f` with a desired type conversion
350-
///
351-
/// ## Errors
352-
///
353-
/// See [`next_event_typed`](Replayer::next_event_typed)
354-
#[inline]
355-
fn next_event_and<T, F>(&mut self, f: F) -> Result<(), ReplayError>
356-
where
357-
T: TryFrom<RREvent>,
358-
ReplayError: From<<T as TryFrom<RREvent>>::Error>,
359-
F: FnOnce(T) -> Result<(), ReplayError>,
360-
{
361-
let call_event = self.next_event_typed()?;
362-
Ok(f(call_event)?)
363-
}
364-
365349
/// Conditionally process the next validation recorded event and if
366350
/// replay validation is enabled, run the validation check
367351
///
@@ -591,6 +575,18 @@ mod tests {
591575
use tempfile::{NamedTempFile, TempPath};
592576
use wasmtime_environ::FuncIndex;
593577

578+
impl ReplayBuffer {
579+
fn next_event_and<T, F>(&mut self, f: F) -> Result<(), ReplayError>
580+
where
581+
T: TryFrom<RREvent>,
582+
ReplayError: From<<T as TryFrom<RREvent>>::Error>,
583+
F: FnOnce(T) -> Result<(), ReplayError>,
584+
{
585+
let event = self.next_event_typed::<T>()?;
586+
f(event)
587+
}
588+
}
589+
594590
fn rr_harness<S, T>(record_fn: S, replay_fn: T) -> Result<()>
595591
where
596592
S: FnOnce(&mut RecordBuffer) -> Result<()>,

crates/wasmtime/src/runtime/rr/hooks/core_hooks.rs

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use crate::rr::FlatBytes;
22
#[cfg(feature = "rr")]
33
use crate::rr::{
4-
RRFuncArgVals, ResultEvent, common_events::HostFuncEntryEvent,
5-
common_events::HostFuncReturnEvent, common_events::WasmFuncReturnEvent,
6-
core_events::WasmFuncEntryEvent,
4+
RREvent, RRFuncArgVals, ReplayError, ReplayHostContext, Replayer, ResultEvent,
5+
common_events::HostFuncEntryEvent, common_events::HostFuncReturnEvent,
6+
common_events::WasmFuncReturnEvent, core_events::WasmFuncEntryEvent,
77
};
88
use crate::store::StoreOpaque;
9-
use crate::{FuncType, StoreContextMut, ValRaw, WasmFuncOrigin, prelude::*};
9+
use crate::{Caller, FuncType, StoreContextMut, ValRaw, WasmFuncOrigin, prelude::*};
10+
#[cfg(feature = "rr")]
11+
use wasmtime_environ::EntityIndex;
1012

1113
/// Record and replay hook operation for core wasm function entry events
1214
///
@@ -121,15 +123,74 @@ where
121123

122124
/// Replay hook operation for host function return events
123125
#[inline]
124-
pub fn replay_host_func_return<T>(args: &mut [T], store: &mut StoreOpaque) -> Result<()>
126+
pub fn replay_host_func_return<T, U: 'static>(
127+
args: &mut [T],
128+
caller: &mut Caller<'_, U>,
129+
) -> Result<()>
125130
where
126131
T: FlatBytes,
127132
{
128133
#[cfg(feature = "rr")]
129-
store.next_replay_event_and(|event: HostFuncReturnEvent| {
130-
event.args.into_raw_slice(args);
131-
Ok(())
132-
})?;
133-
let _ = (args, store);
134+
{
135+
// Core wasm can be re-entrant, so we need to check for this
136+
let mut complete = false;
137+
while !complete {
138+
let buf = caller.store.0.replay_buffer_mut().unwrap();
139+
let event = buf.next_event()?;
140+
match event {
141+
RREvent::HostFuncReturn(event) => {
142+
event.args.into_raw_slice(args);
143+
complete = true;
144+
}
145+
// Re-entrant call into wasm function: this resembles the implementation in [`ReplayInstance`]
146+
RREvent::CoreWasmFuncEntry(event) => {
147+
let entity = EntityIndex::from(event.origin.index);
148+
149+
// SAFETY: The store's data is always of type `ReplayHostContext<T>` when created by
150+
// the replay driver. As an additional guarantee, we assert that replay is indeed
151+
// truly enabled.
152+
assert!(caller.store.0.replay_enabled());
153+
let replay_data = unsafe {
154+
let raw_ptr: *const U = caller.store.data();
155+
&*(raw_ptr as *const ReplayHostContext)
156+
};
157+
158+
// Grab the correct module instance
159+
let instance = replay_data.get_module_instance(event.origin.instance)?;
160+
161+
let mut store = &mut caller.store;
162+
let func = instance
163+
._get_export(store.0, entity)
164+
.into_func()
165+
.ok_or(ReplayError::InvalidCoreFuncIndex(entity))?;
166+
167+
let params_ty = func.ty(&store).params().collect::<Vec<_>>();
168+
169+
// Obtain the argument values for function call
170+
let mut results = vec![crate::Val::I64(0); func.ty(&store).results().len()];
171+
let params = event.args.to_val_vec(&mut store, params_ty);
172+
173+
// Call the function
174+
//
175+
// This is almost a mirror of the usage in [`crate::Func::call_impl`]
176+
func.call_impl_check_args(&mut store, &params, &mut results)?;
177+
unsafe {
178+
func.call_impl_do_call(
179+
&mut store,
180+
params.as_slice(),
181+
results.as_mut_slice(),
182+
)?;
183+
}
184+
}
185+
_ => {
186+
bail!(
187+
"Unexpected event during core wasm host function replay: {:?}",
188+
event
189+
);
190+
}
191+
}
192+
}
193+
}
194+
let _ = (args, caller);
134195
Ok(())
135196
}

crates/wasmtime/src/runtime/rr/replay_driver.rs

Lines changed: 90 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,73 @@ impl ReplayEnvironment {
4343
}
4444

4545
/// Instantiate a new [`ReplayInstance`] using a [`ReplayReader`] in context of this environment
46-
pub fn instantiate(&self, reader: impl ReplayReader + 'static) -> Result<ReplayInstance<()>> {
47-
let store = Store::new(&self.engine, ());
48-
ReplayInstance::<()>::from_environment_and_store(self.clone(), store, reader)
46+
pub fn instantiate(&self, reader: impl ReplayReader + 'static) -> Result<ReplayInstance> {
47+
self.instantiate_with(reader, |_| Ok(()), |_| Ok(()), |_| Ok(()))
4948
}
5049

51-
/// Like [`Self::instantiate`] but allows providing a custom [`Store`] generator
52-
pub fn instantiate_with_store<T>(
50+
/// Like [`Self::instantiate`] but allows providing a custom modifier functions for
51+
/// [`Store`], [`crate::Linker`], and [`component::Linker`] within the replay
52+
pub fn instantiate_with(
5353
&self,
54-
store_gen: impl FnOnce() -> Store<T>,
5554
reader: impl ReplayReader + 'static,
56-
) -> Result<ReplayInstance<T>> {
57-
ReplayInstance::from_environment_and_store(self.clone(), store_gen(), reader)
55+
store_fn: impl FnOnce(&mut Store<ReplayHostContext>) -> Result<()>,
56+
module_linker_fn: impl FnOnce(&mut crate::Linker<ReplayHostContext>) -> Result<()>,
57+
component_linker_fn: impl FnOnce(&mut component::Linker<ReplayHostContext>) -> Result<()>,
58+
) -> Result<ReplayInstance> {
59+
let mut store = Store::new(
60+
&self.engine,
61+
ReplayHostContext {
62+
module_instances: BTreeMap::new(),
63+
},
64+
);
65+
store_fn(&mut store)?;
66+
store.init_replaying(reader, self.settings.clone())?;
67+
68+
ReplayInstance::from_environment_and_store(
69+
self.clone(),
70+
store,
71+
module_linker_fn,
72+
component_linker_fn,
73+
)
74+
}
75+
}
76+
77+
/// The host context tied to the store during replay.
78+
///
79+
/// This context encapsulates the state from the replay environment that are
80+
/// required to be accessible within the Store. This is an opaque type from the
81+
/// public API perspective.
82+
pub struct ReplayHostContext {
83+
/// A tracker of instantiated modules.
84+
///
85+
/// Core wasm modules can be re-entrant and invoke methods from other instances, and this
86+
/// needs to be accessible within host functions
87+
module_instances: BTreeMap<InstanceId, crate::Instance>,
88+
}
89+
90+
impl ReplayHostContext {
91+
/// Insert a module instance into the context's tracking map.
92+
fn insert_module_instance(&mut self, id: InstanceId, instance: crate::Instance) {
93+
self.module_instances.insert(id, instance);
94+
}
95+
96+
/// Get a module instance from the context's tracking map
97+
///
98+
/// This is necessary for core wasm to identify re-entrant calls during replay.
99+
pub(crate) fn get_module_instance(
100+
&self,
101+
id: InstanceId,
102+
) -> Result<&crate::Instance, ReplayError> {
103+
self.module_instances
104+
.get(&id)
105+
.ok_or(ReplayError::MissingModuleInstance(id.as_u32()))
58106
}
59107
}
60108

61-
/// A [`ReplayInstance`] is an object providing a opaquely managed, replayable [`Store`]
109+
/// A [`ReplayInstance`] is an object providing a opaquely managed, replayable [`Store`].
62110
///
63111
/// Debugger capabilities in the future will interact with this object for
64-
/// inserting breakpoints, snapshotting, and restoring state
112+
/// inserting breakpoints, snapshotting, and restoring state.
65113
///
66114
/// # Example
67115
///
@@ -122,32 +170,36 @@ impl ReplayEnvironment {
122170
/// # Ok(())
123171
/// # }
124172
/// ```
125-
pub struct ReplayInstance<T: 'static> {
173+
pub struct ReplayInstance {
126174
env: Arc<ReplayEnvironment>,
127-
store: Store<T>,
128-
component_linker: component::Linker<T>,
129-
module_linker: crate::Linker<T>,
175+
store: Store<ReplayHostContext>,
176+
component_linker: component::Linker<ReplayHostContext>,
177+
module_linker: crate::Linker<ReplayHostContext>,
130178
module_instances: BTreeMap<InstanceId, crate::Instance>,
131179
component_instances: BTreeMap<ComponentInstanceId, component::Instance>,
132180
}
133181

134-
impl<T: 'static> ReplayInstance<T> {
182+
impl ReplayInstance {
135183
fn from_environment_and_store(
136184
env: ReplayEnvironment,
137-
mut store: Store<T>,
138-
reader: impl ReplayReader + 'static,
185+
store: Store<ReplayHostContext>,
186+
module_linker_fn: impl FnOnce(&mut crate::Linker<ReplayHostContext>) -> Result<()>,
187+
component_linker_fn: impl FnOnce(&mut component::Linker<ReplayHostContext>) -> Result<()>,
139188
) -> Result<Self> {
140189
let env = Arc::new(env);
141-
store.init_replaying(reader, env.settings.clone())?;
142-
let mut module_linker = crate::Linker::<T>::new(&env.engine);
190+
let mut module_linker = crate::Linker::<ReplayHostContext>::new(&env.engine);
143191
// Replays shouldn't use any imports, so stub them all out as traps
144192
for module in env.modules.values() {
145193
module_linker.define_unknown_imports_as_traps(module)?;
146194
}
147-
let mut component_linker = component::Linker::<T>::new(&env.engine);
195+
module_linker_fn(&mut module_linker)?;
196+
197+
let mut component_linker = component::Linker::<ReplayHostContext>::new(&env.engine);
148198
for component in env.components.values() {
149199
component_linker.define_unknown_imports_as_traps(component)?;
150200
}
201+
component_linker_fn(&mut component_linker)?;
202+
151203
Ok(Self {
152204
env,
153205
store,
@@ -158,23 +210,23 @@ impl<T: 'static> ReplayInstance<T> {
158210
})
159211
}
160212

161-
/// Obtain a reference to the internal [`Store`]
162-
pub fn store(&self) -> &Store<T> {
213+
/// Obtain a reference to the internal [`Store`].
214+
pub fn store(&self) -> &Store<ReplayHostContext> {
163215
&self.store
164216
}
165217

166-
/// Consume the [`ReplayInstance`] and extract the internal [`Store`]
167-
pub fn extract_store(self) -> Store<T> {
218+
/// Consume the [`ReplayInstance`] and extract the internal [`Store`].
219+
pub fn extract_store(self) -> Store<ReplayHostContext> {
168220
self.store
169221
}
170222

171-
/// Run a single top-level event from the instance
223+
/// Run a single top-level event from the instance.
172224
///
173225
/// "Top-level" events are those explicitly invoked events, namely:
174226
/// * Instantiation events (component/module)
175227
/// * Wasm function begin events (`ComponentWasmFuncBegin` for components and `CoreWasmFuncEntry` for core)
176228
///
177-
/// All other events are transparently dispatched under the context of these top-level events
229+
/// All other events are transparently dispatched under the context of these top-level events.
178230
fn run_single_top_level_event(&mut self, rr_event: RREvent) -> Result<()> {
179231
match rr_event {
180232
RREvent::ComponentInstantiation(event) => {
@@ -281,7 +333,12 @@ impl<T: 'static> ReplayInstance<T> {
281333
instance: instance.id(),
282334
})?;
283335

336+
// Insert into host context tracking as well
284337
self.module_instances.insert(instance.id(), instance);
338+
self.store
339+
.as_context_mut()
340+
.data_mut()
341+
.insert_module_instance(instance.id(), instance);
285342
}
286343
RREvent::CoreWasmFuncEntry(event) => {
287344
// Grab the correct module instance
@@ -321,12 +378,9 @@ impl<T: 'static> ReplayInstance<T> {
321378
Ok(())
322379
}
323380

324-
/// Exactly like [`Self::run_single_top_level_event`] but uses async stores and calls
381+
/// Exactly like [`Self::run_single_top_level_event`] but uses async stores and calls.
325382
#[cfg(feature = "async")]
326-
async fn run_single_top_level_event_async(&mut self, rr_event: RREvent) -> Result<()>
327-
where
328-
T: Send,
329-
{
383+
async fn run_single_top_level_event_async(&mut self, rr_event: RREvent) -> Result<()> {
330384
match rr_event {
331385
RREvent::ComponentInstantiation(event) => {
332386
// Find matching component from environment to instantiate
@@ -441,6 +495,10 @@ impl<T: 'static> ReplayInstance<T> {
441495
})?;
442496

443497
self.module_instances.insert(instance.id(), instance);
498+
self.store
499+
.as_context_mut()
500+
.data_mut()
501+
.insert_module_instance(instance.id(), instance);
444502
}
445503
RREvent::CoreWasmFuncEntry(event) => {
446504
// Grab the correct module instance
@@ -500,10 +558,7 @@ impl<T: 'static> ReplayInstance<T> {
500558

501559
/// Exactly like [`Self::run_to_completion`] but uses async stores and calls
502560
#[cfg(feature = "async")]
503-
pub async fn run_to_completion_async(&mut self) -> Result<()>
504-
where
505-
T: Send,
506-
{
561+
pub async fn run_to_completion_async(&mut self) -> Result<()> {
507562
while let Some(rr_event) = self
508563
.store
509564
.as_context_mut()

crates/wasmtime/src/runtime/store.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,24 +1965,6 @@ impl StoreOpaque {
19651965
}
19661966
}
19671967

1968-
/// Process the next replay event from the store's replay buffer
1969-
///
1970-
/// Convenience wrapper around [`Replayer::next_event_and`]
1971-
#[cfg(feature = "rr")]
1972-
#[inline]
1973-
pub(crate) fn next_replay_event_and<T, F>(&mut self, f: F) -> Result<(), ReplayError>
1974-
where
1975-
T: TryFrom<RREvent>,
1976-
ReplayError: From<<T as TryFrom<RREvent>>::Error>,
1977-
F: FnOnce(T) -> Result<(), ReplayError>,
1978-
{
1979-
if let Some(buf) = self.replay_buffer_mut() {
1980-
buf.next_event_and(f)
1981-
} else {
1982-
Ok(())
1983-
}
1984-
}
1985-
19861968
/// Process the next replay event as a validation event from the store's replay buffer
19871969
/// and if validation is enabled on replay, and run the validation check
19881970
///

0 commit comments

Comments
 (0)