//! Wasmtime debugger functionality.
//!
//! This crate builds on top of the core Wasmtime crate's
//! guest-debugger APIs to present an environment where a debugger
//! runs as a "co-running process" and sees the debuggee as a a
//! provider of a stream of events, on which actions can be taken
//! between each event.
//!
//! In the future, this crate will also provide a WIT-level API and
//! world in which to run debugger components.

use std::{
    any::Any,
    future::Future,
    pin::Pin,
    sync::{
        Arc,
        atomic::{AtomicBool, Ordering},
    },
};
use tokio::{
    sync::{Mutex, mpsc},
    task::JoinHandle,
};
use wasmtime::{
    AsContextMut, DebugEvent, DebugHandler, Engine, ExnRef, OwnedRooted, Result, Store,
    StoreContextMut, Trap,
};

mod host;
pub use host::{DebuggerComponent, add_debuggee, add_to_linker, wit};

/// A `Debuggee` wraps up state associated with debugging the code
/// running in a single `Store`.
///
/// It acts as a Future combinator, wrapping an inner async body that
/// performs some actions on a store. Those actions are subject to the
/// debugger, and debugger events will be raised as appropriate. From
/// the "outside" of this combinator, it is always in one of two
/// states: running or paused. When paused, it acts as a
/// `StoreContextMut` and can allow examining the paused execution's
/// state. One runs until the next event suspends execution by
/// invoking `Debuggee::run`.
pub struct Debuggee<T: Send + 'static> {
    /// A handle to the Engine that the debuggee store lives within.
    engine: Engine,
    /// State: either a task handle or the store when passed out of
    /// the complete task.
    state: DebuggeeState,
    /// The store, once complete.
    store: Option<Store<T>>,
    in_tx: mpsc::Sender<Command<T>>,
    out_rx: mpsc::Receiver<Response<T>>,
    handle: Option<JoinHandle<Result<()>>>,
    /// Flag shared with the inner handler: set to `true` by
    /// `interrupt()` so the next epoch yield is surfaced as an
    /// `Interrupted` event rather than eaten by the handler. Epoch
    /// yields serve two purposes, namely ensuring regular yields to
    /// the event loop and enacting an explicit interrupt, and this
    /// flag distinguishes those cases.
    interrupt_pending: Arc<AtomicBool>,
}

/// State machine from the perspective of the outer logic.
///
/// The intermediate states here, and the separation of these states
/// from the `JoinHandle` above, are what allow us to implement a
/// cancel-safe version of `Debuggee::run` below.
///
/// The state diagram for the outer logic is:
///
/// ```plain
///              (start)
///                 v
///                 |
/// .--->---------. v
/// |     .----<  Paused  <-----------------------------------------------.
/// |     |         v                                                     |
/// |     |         | (async fn run() starts, sends Command::Continue)    |
/// |     |         |                                                     |
/// |     |         v                                                     ^
/// |     |      Running                                                  |
/// |     |       v v (async fn run() receives Response::Paused, returns) |
/// |     |       | |_____________________________________________________|
/// |     |       |
/// |     |       | (async fn run() receives Response::Finished, returns)
/// |     |       v
/// |     |     Complete
/// |     |
/// ^     | (async fn with_store() starts, sends Command::Query)
/// |     v
/// |   Queried
/// |     |
/// |     | (async fn with_store() receives Response::QueryResponse, returns)
/// `---<-'
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum DebuggeeState {
    /// Inner body has just been started.
    Initial,
    /// Inner body is running in an async task and not in a debugger
    /// callback. Outer logic is waiting for a `Response::Paused` or
    /// `Response::Complete`.
    Running,
    /// Inner body is running in an async task and at a debugger
    /// callback (or in the initial trampoline waiting for the first
    /// `Continue`). `Response::Paused` has been received. Outer
    /// logic has not sent any commands.
    Paused,
    /// We have sent a command to the inner body and are waiting for a
    /// response.
    Queried,
    /// Inner body is complete (has sent `Response::Finished` and we
    /// have received it). We may or may not have joined yet; if so,
    /// the `Option<JoinHandle<...>>` will be `None`.
    Complete,
}

/// Message from "outside" to the debug hook.
///
/// The `Query` catch-all with a boxed closure is a little janky, but
/// is the way that we provide access
/// from outside to the Store (which is owned by `inner` above)
/// only during pauses. Note that the future cannot take full
/// ownership or a mutable borrow of the Store, because it cannot
/// hold this across async yield points.
///
/// Instead, the debugger body sends boxed closures which take the
/// Store as a parameter (lifetime-limited not to escape that
/// closure) out to this crate's implementation that runs inside of
/// debugger-instrumentation callbacks (which have access to the
/// Store during their duration). We send return values
/// back. Return values are boxed Any values.
///
/// If we wanted to make this a little more principled, we could
/// come up with a Command/Response pair of enums for all possible
/// closures and make everything more statically typed and less
/// Box'd, but that would severely restrict the flexibility of the
/// abstraction here and essentially require writing a full proxy
/// of the debugger API.
///
/// Furthermore, we expect to rip this out eventually when we move
/// the debugger over to an async implementation based on
/// `run_concurrent` and `Accessor`s (see #11896). Building things
/// this way now will actually allow a less painful transition at
/// that time, because we will have a bunch of closures accessing
/// the store already and we can run those "with an accessor"
/// instead.
enum Command<T: 'static> {
    Continue,
    Query(Box<dyn FnOnce(StoreContextMut<'_, T>) -> Box<dyn Any + Send> + Send>),
}

enum Response<T: 'static> {
    Paused(DebugRunResult),
    QueryResponse(Box<dyn Any + Send>),
    Finished(Store<T>),
}

struct HandlerInner<T: Send + 'static> {
    in_rx: Mutex<mpsc::Receiver<Command<T>>>,
    out_tx: mpsc::Sender<Response<T>>,
    interrupt_pending: Arc<AtomicBool>,
}

struct Handler<T: Send + 'static>(Arc<HandlerInner<T>>);

impl<T: Send + 'static> std::clone::Clone for Handler<T> {
    fn clone(&self) -> Self {
        Handler(self.0.clone())
    }
}

impl<T: Send + 'static> DebugHandler for Handler<T> {
    type Data = T;
    async fn handle(&self, mut store: StoreContextMut<'_, T>, event: DebugEvent<'_>) {
        let mut in_rx = self.0.in_rx.lock().await;

        let result = match event {
            DebugEvent::HostcallError(_) => DebugRunResult::HostcallError,
            DebugEvent::Exception(exn) => DebugRunResult::Exception(exn),
            DebugEvent::Trap(trap) => DebugRunResult::Trap(trap),
            DebugEvent::Breakpoint => DebugRunResult::Breakpoint,
            DebugEvent::EpochYield => {
                // Only pause on epoch yields that were requested via
                // interrupt(). Other epoch ticks simply yield to the
                // event loop (functionality already implemented in
                // core Wasmtime; no need to do that yield here in the
                // debug handler).
                if !self.0.interrupt_pending.swap(false, Ordering::SeqCst) {
                    return;
                }
                DebugRunResult::EpochYield
            }
        };
        if self.0.out_tx.send(Response::Paused(result)).await.is_err() {
            // Outer Debuggee has been dropped: just continue
            // executing.
            return;
        }

        while let Some(cmd) = in_rx.recv().await {
            match cmd {
                Command::Query(closure) => {
                    let result = closure(store.as_context_mut());
                    if self
                        .0
                        .out_tx
                        .send(Response::QueryResponse(result))
                        .await
                        .is_err()
                    {
                        // Outer Debuggee has been dropped: just
                        // continue executing.
                        return;
                    }
                }
                Command::Continue => {
                    break;
                }
            }
        }
    }
}

impl<T: Send + 'static> Debuggee<T> {
    /// Create a new Debugger that attaches to the given Store and
    /// runs the given inner body.
    ///
    /// The debugger is always in one of two states: running or
    /// paused.
    ///
    /// When paused, the holder of this object can invoke
    /// `Debuggee::run` to enter the running state. The inner body
    /// will run until paused by a debug event. While running, the
    /// future returned by either of these methods owns the `Debuggee`
    /// and hence no other methods can be invoked.
    ///
    /// When paused, the holder of this object can access the `Store`
    /// indirectly by providing a closure
    pub fn new<F>(mut store: Store<T>, inner: F) -> Debuggee<T>
    where
        F: for<'a> FnOnce(
                &'a mut Store<T>,
            ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
            + Send
            + 'static,
    {
        let engine = store.engine().clone();
        let (in_tx, in_rx) = mpsc::channel(1);
        let (out_tx, out_rx) = mpsc::channel(1);
        let interrupt_pending = Arc::new(AtomicBool::new(false));

        let handle = tokio::spawn({
            let interrupt_pending = interrupt_pending.clone();
            async move {
                // Create the handler that's invoked from within the async
                // debug-event callback.
                let out_tx_clone = out_tx.clone();
                let handler = Handler(Arc::new(HandlerInner {
                    in_rx: Mutex::new(in_rx),
                    out_tx,
                    interrupt_pending,
                }));

                // Emulate a breakpoint at startup.
                log::trace!("inner debuggee task: first breakpoint");
                handler
                    .handle(store.as_context_mut(), DebugEvent::Breakpoint)
                    .await;
                log::trace!("inner debuggee task: first breakpoint resumed");

                // Now invoke the actual inner body.
                store.set_debug_handler(handler);
                log::trace!("inner debuggee task: running `inner`");
                let result = inner(&mut store).await;
                log::trace!("inner debuggee task: done with `inner`");
                let _ = out_tx_clone.send(Response::Finished(store)).await;
                result
            }
        });

        Debuggee {
            engine,
            state: DebuggeeState::Initial,
            store: None,
            in_tx,
            out_rx,
            interrupt_pending,
            handle: Some(handle),
        }
    }

    /// Is the inner body done running?
    pub fn is_complete(&self) -> bool {
        match self.state {
            DebuggeeState::Complete => true,
            _ => false,
        }
    }

    /// Get the Engine associated with the debuggee.
    pub fn engine(&self) -> &Engine {
        &self.engine
    }

    /// Get the interrupt-pending flag. Setting this to `true` causes
    /// the next epoch yield to surface as an `Interrupted` event.
    pub fn interrupt_pending(&self) -> &Arc<AtomicBool> {
        &self.interrupt_pending
    }

    async fn wait_for_initial(&mut self) -> Result<()> {
        if let DebuggeeState::Initial = &self.state {
            // Need to receive and discard first `Paused`.
            let response = self
                .out_rx
                .recv()
                .await
                .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
            assert!(matches!(response, Response::Paused(_)));
            self.state = DebuggeeState::Paused;
        }
        Ok(())
    }

    /// Run the inner body until the next debug event.
    ///
    /// This method is cancel-safe, and no events will be lost.
    pub async fn run(&mut self) -> Result<DebugRunResult> {
        log::trace!("running: state is {:?}", self.state);

        self.wait_for_initial().await?;

        match self.state {
            DebuggeeState::Initial => unreachable!(),
            DebuggeeState::Paused => {
                log::trace!("sending Continue");
                self.in_tx
                    .send(Command::Continue)
                    .await
                    .map_err(|_| wasmtime::format_err!("Failed to send over debug channel"))?;
                log::trace!("sent Continue");

                // If that `send` was canceled, the command was not
                // sent, so it's fine to remain in `Paused`. If it
                // succeeded and we reached here, transition to
                // `Running` so we don't re-send.
                self.state = DebuggeeState::Running;
            }
            DebuggeeState::Running => {
                // Previous `run()` must have been canceled; no action
                // to take here.
            }
            DebuggeeState::Queried => {
                // We expect to receive a `QueryResponse`; drop it if
                // the query was canceled, then transition back to
                // `Paused`.
                log::trace!("in Queried; receiving");
                let response =
                    self.out_rx.recv().await.ok_or_else(|| {
                        wasmtime::format_err!("Premature close of debugger channel")
                    })?;
                log::trace!("in Queried; received, dropping");
                assert!(matches!(response, Response::QueryResponse(_)));
                self.state = DebuggeeState::Paused;

                // Now send a `Continue`, as above.
                log::trace!("in Paused; sending Continue");
                self.in_tx
                    .send(Command::Continue)
                    .await
                    .map_err(|_| wasmtime::format_err!("Failed to send over debug channel"))?;
                self.state = DebuggeeState::Running;
            }
            DebuggeeState::Complete => {
                panic!("Cannot `run()` an already-complete Debuggee");
            }
        }

        // At this point, the inner task is in Running state. We
        // expect to receive a message when it next pauses or
        // completes. If this `recv()` is canceled, no message is
        // lost, and the state above accurately reflects what must be
        // done on the next `run()`.
        log::trace!("waiting for response");
        let response = self
            .out_rx
            .recv()
            .await
            .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;

        match response {
            Response::Finished(store) => {
                log::trace!("got Finished");
                self.state = DebuggeeState::Complete;
                self.store = Some(store);
                Ok(DebugRunResult::Finished)
            }
            Response::Paused(result) => {
                log::trace!("got Paused");
                self.state = DebuggeeState::Paused;
                Ok(result)
            }
            Response::QueryResponse(_) => {
                panic!("Invalid debug response");
            }
        }
    }

    /// Run the debugger body until completion, with no further events.
    pub async fn finish(&mut self) -> Result<()> {
        if self.is_complete() {
            return Ok(());
        }
        loop {
            match self.run().await? {
                DebugRunResult::Finished => break,
                e => {
                    log::trace!("finish: event {e:?}");
                }
            }
        }
        if let Some(handle) = self.handle.take() {
            handle.await??;
        }
        assert!(self.is_complete());
        Ok(())
    }

    /// Perform some action on the contained `Store` while not running.
    ///
    /// This may only be invoked before the inner body finishes and
    /// when it is paused; that is, when the `Debuggee` is initially
    /// created and after any call to `run()` returns a result other
    /// than `DebugRunResult::Finished`. If an earlier `run()`
    /// invocation was canceled, it must be re-invoked and return
    /// successfully before a query is made.
    ///
    /// This is cancel-safe; if canceled, the result of the query will
    /// be dropped.
    pub async fn with_store<
        F: FnOnce(StoreContextMut<'_, T>) -> R + Send + 'static,
        R: Send + 'static,
    >(
        &mut self,
        f: F,
    ) -> Result<R> {
        if let Some(store) = self.store.as_mut() {
            return Ok(f(store.as_context_mut()));
        }

        self.wait_for_initial().await?;

        match self.state {
            DebuggeeState::Initial => unreachable!(),
            DebuggeeState::Queried => {
                // Earlier query canceled; drop its response first.
                let response =
                    self.out_rx.recv().await.ok_or_else(|| {
                        wasmtime::format_err!("Premature close of debugger channel")
                    })?;
                assert!(matches!(response, Response::QueryResponse(_)));
                self.state = DebuggeeState::Paused;
            }
            DebuggeeState::Running => {
                // Results from a canceled `run()`; `run()` must
                // complete before this can be invoked.
                panic!("Cannot query in Running state");
            }
            DebuggeeState::Complete => {
                panic!("Cannot query when complete");
            }
            DebuggeeState::Paused => {
                // OK -- this is the state we want.
            }
        }

        log::trace!("sending query in with_store");
        self.in_tx
            .send(Command::Query(Box::new(|store| Box::new(f(store)))))
            .await
            .map_err(|_| wasmtime::format_err!("Premature close of debugger channel"))?;
        self.state = DebuggeeState::Queried;

        let response = self
            .out_rx
            .recv()
            .await
            .ok_or_else(|| wasmtime::format_err!("Premature close of debugger channel"))?;
        let Response::QueryResponse(resp) = response else {
            wasmtime::bail!("Incorrect response from debugger task");
        };
        self.state = DebuggeeState::Paused;

        Ok(*resp.downcast::<R>().expect("type mismatch"))
    }
}

/// The result of one call to `Debuggee::run()`.
///
/// This is similar to `DebugEvent` but without the lifetime, so it
/// can be sent across async tasks, and incorporates the possibility
/// of completion (`Finished`) as well.
#[derive(Debug)]
pub enum DebugRunResult {
    /// Execution of the inner body finished.
    Finished,
    /// An error was raised by a hostcall.
    HostcallError,
    /// Wasm execution was interrupted by an epoch change.
    EpochYield,
    /// An exception is thrown by Wasm. The current state is at the
    /// throw-point.
    Exception(OwnedRooted<ExnRef>),
    /// A Wasm trap occurred.
    Trap(Trap),
    /// A breakpoint was reached.
    Breakpoint,
}

#[cfg(test)]
mod test {
    use super::*;
    use wasmtime::*;

    #[tokio::test]
    #[cfg_attr(miri, ignore)]
    async fn basic_debugger() -> wasmtime::Result<()> {
        let _ = env_logger::try_init();

        let mut config = Config::new();
        config.guest_debug(true);
        let engine = Engine::new(&config)?;
        let module = Module::new(
            &engine,
            r#"
                (module
                  (func (export "main") (param i32 i32) (result i32)
                    local.get 0
                    local.get 1
                    i32.add))
            "#,
        )?;

        let mut store = Store::new(&engine, ());
        let instance = Instance::new_async(&mut store, &module, &[]).await?;
        let main = instance.get_func(&mut store, "main").unwrap();

        let mut debuggee = Debuggee::new(store, move |store| {
            Box::pin(async move {
                let mut results = [Val::I32(0)];
                store.edit_breakpoints().unwrap().single_step(true).unwrap();
                main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
                    .await?;
                assert_eq!(results[0].unwrap_i32(), 3);
                main.call_async(&mut *store, &[Val::I32(3), Val::I32(4)], &mut results[..])
                    .await?;
                assert_eq!(results[0].unwrap_i32(), 7);
                Ok(())
            })
        });

        let event = debuggee.run().await?;
        assert!(matches!(event, DebugRunResult::Breakpoint));
        // At (before executing) first `local.get`.
        debuggee
            .with_store(|mut store| {
                let frame = store.debug_exit_frames().next().unwrap();
                assert_eq!(
                    frame
                        .wasm_function_index_and_pc(&mut store)
                        .unwrap()
                        .unwrap()
                        .0
                        .as_u32(),
                    0
                );
                assert_eq!(
                    frame
                        .wasm_function_index_and_pc(&mut store)
                        .unwrap()
                        .unwrap()
                        .1
                        .raw(),
                    36
                );
                assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
                assert_eq!(frame.num_stacks(&mut store).unwrap(), 0);
                assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
                assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
                let frame = frame.parent(&mut store).unwrap();
                assert!(frame.is_none());
            })
            .await?;

        let event = debuggee.run().await?;
        // At second `local.get`.
        assert!(matches!(event, DebugRunResult::Breakpoint));
        debuggee
            .with_store(|mut store| {
                let frame = store.debug_exit_frames().next().unwrap();
                assert_eq!(
                    frame
                        .wasm_function_index_and_pc(&mut store)
                        .unwrap()
                        .unwrap()
                        .0
                        .as_u32(),
                    0
                );
                assert_eq!(
                    frame
                        .wasm_function_index_and_pc(&mut store)
                        .unwrap()
                        .unwrap()
                        .1
                        .raw(),
                    38
                );
                assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
                assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
                assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
                assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
                assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
                let frame = frame.parent(&mut store).unwrap();
                assert!(frame.is_none());
            })
            .await?;

        let event = debuggee.run().await?;
        // At `i32.add`.
        assert!(matches!(event, DebugRunResult::Breakpoint));
        debuggee
            .with_store(|mut store| {
                let frame = store.debug_exit_frames().next().unwrap();
                assert_eq!(
                    frame
                        .wasm_function_index_and_pc(&mut store)
                        .unwrap()
                        .unwrap()
                        .0
                        .as_u32(),
                    0
                );
                assert_eq!(
                    frame
                        .wasm_function_index_and_pc(&mut store)
                        .unwrap()
                        .unwrap()
                        .1
                        .raw(),
                    40
                );
                assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
                assert_eq!(frame.num_stacks(&mut store).unwrap(), 2);
                assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
                assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
                assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 1);
                assert_eq!(frame.stack(&mut store, 1).unwrap().unwrap_i32(), 2);
                let frame = frame.parent(&mut store).unwrap();
                assert!(frame.is_none());
            })
            .await?;

        let event = debuggee.run().await?;
        // At return point.
        assert!(matches!(event, DebugRunResult::Breakpoint));
        debuggee
            .with_store(|mut store| {
                let frame = store.debug_exit_frames().next().unwrap();
                assert_eq!(
                    frame
                        .wasm_function_index_and_pc(&mut store)
                        .unwrap()
                        .unwrap()
                        .0
                        .as_u32(),
                    0
                );
                assert_eq!(
                    frame
                        .wasm_function_index_and_pc(&mut store)
                        .unwrap()
                        .unwrap()
                        .1
                        .raw(),
                    41
                );
                assert_eq!(frame.num_locals(&mut store).unwrap(), 2);
                assert_eq!(frame.num_stacks(&mut store).unwrap(), 1);
                assert_eq!(frame.local(&mut store, 0).unwrap().unwrap_i32(), 1);
                assert_eq!(frame.local(&mut store, 1).unwrap().unwrap_i32(), 2);
                assert_eq!(frame.stack(&mut store, 0).unwrap().unwrap_i32(), 3);
                let frame = frame.parent(&mut store).unwrap();
                assert!(frame.is_none());
            })
            .await?;

        // Now disable breakpoints before continuing. Second call should proceed with no more events.
        debuggee
            .with_store(|store| {
                store
                    .edit_breakpoints()
                    .unwrap()
                    .single_step(false)
                    .unwrap();
            })
            .await?;

        let event = debuggee.run().await?;
        assert!(matches!(event, DebugRunResult::Finished));

        assert!(debuggee.is_complete());

        Ok(())
    }

    #[tokio::test]
    #[cfg_attr(miri, ignore)]
    async fn early_finish() -> Result<()> {
        let _ = env_logger::try_init();

        let mut config = Config::new();
        config.guest_debug(true);
        let engine = Engine::new(&config)?;
        let module = Module::new(
            &engine,
            r#"
                (module
                  (func (export "main") (param i32 i32) (result i32)
                    local.get 0
                    local.get 1
                    i32.add))
            "#,
        )?;

        let mut store = Store::new(&engine, ());
        let instance = Instance::new_async(&mut store, &module, &[]).await?;
        let main = instance.get_func(&mut store, "main").unwrap();

        let mut debuggee = Debuggee::new(store, move |store| {
            Box::pin(async move {
                let mut results = [Val::I32(0)];
                store.edit_breakpoints().unwrap().single_step(true).unwrap();
                main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
                    .await?;
                assert_eq!(results[0].unwrap_i32(), 3);
                Ok(())
            })
        });

        debuggee.finish().await?;
        assert!(debuggee.is_complete());

        Ok(())
    }

    #[tokio::test]
    #[cfg_attr(miri, ignore)]
    async fn drop_debuggee_and_store() -> Result<()> {
        let _ = env_logger::try_init();

        let mut config = Config::new();
        config.guest_debug(true);
        let engine = Engine::new(&config)?;
        let module = Module::new(
            &engine,
            r#"
                (module
                  (func (export "main") (param i32 i32) (result i32)
                    local.get 0
                    local.get 1
                    i32.add))
            "#,
        )?;

        let mut store = Store::new(&engine, ());
        let instance = Instance::new_async(&mut store, &module, &[]).await?;
        let main = instance.get_func(&mut store, "main").unwrap();

        let mut debuggee = Debuggee::new(store, move |store| {
            Box::pin(async move {
                let mut results = [Val::I32(0)];
                store.edit_breakpoints().unwrap().single_step(true).unwrap();
                main.call_async(&mut *store, &[Val::I32(1), Val::I32(2)], &mut results[..])
                    .await?;
                assert_eq!(results[0].unwrap_i32(), 3);
                Ok(())
            })
        });

        // Step once, then drop everything at the end of this
        // function. Wasmtime's fiber cleanup should safely happen
        // without attempting to raise debug async handler calls with
        // missing async context.
        let _ = debuggee.run().await?;

        Ok(())
    }
}
