Skip to content

Commit 1f2feaf

Browse files
authored
Expose epoch_deadline_callback in C++ api (bytecodealliance#11945)
* Expose epoch_deadline_callback and raw_store in C++ api * Fix bug and add comprehensive test for Store::epoch_deadline_callback
1 parent d55a5c8 commit 1f2feaf

File tree

2 files changed

+116
-0
lines changed

2 files changed

+116
-0
lines changed

crates/c-api/include/wasmtime/store.hh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ namespace wasmtime {
1818

1919
class Caller;
2020

21+
/// \brief An enum for the behavior before extending the epoch deadline.
22+
enum class DeadlineKind {
23+
/// Directly continue to updating the deadline and executing WebAssembly.
24+
Continue = WASMTIME_UPDATE_DEADLINE_CONTINUE,
25+
/// Yield control (via async support) then update the deadline.
26+
Yield = WASMTIME_UPDATE_DEADLINE_YIELD,
27+
};
28+
2129
/**
2230
* \brief Owner of all WebAssembly objects
2331
*
@@ -193,6 +201,29 @@ public:
193201
tables, memories);
194202
}
195203

204+
/// \brief Configures epoch deadline callback to C function.
205+
///
206+
/// This function configures a store-local callback function that will be
207+
/// called when the running WebAssembly function has exceeded its epoch
208+
/// deadline. That function can:
209+
/// - return an error to terminate the function
210+
/// - set the delta argument and return DeadlineKind::Continue to update the
211+
/// epoch deadline delta and resume function execution.
212+
/// - set the delta argument, update the epoch deadline, and return
213+
/// DeadlineKind::Yield to yield (via async support) and resume function
214+
/// execution.
215+
template <typename F,
216+
std::enable_if_t<std::is_invocable_r_v<Result<DeadlineKind>, F,
217+
Context, uint64_t &>,
218+
bool> = true>
219+
void epoch_deadline_callback(F &&f) {
220+
wasmtime_store_epoch_deadline_callback(
221+
ptr.get(), raw_epoch_callback<std::remove_reference_t<F>>,
222+
std::make_unique<std::remove_reference_t<F>>(std::forward<F>(f))
223+
.release(),
224+
raw_epoch_finalizer<std::remove_reference_t<F>>);
225+
}
226+
196227
/// Explicit function to acquire a `Context` from this store.
197228
Context context() { return this; }
198229

@@ -205,6 +236,27 @@ public:
205236

206237
/// \brief Returns the underlying C API pointer.
207238
wasmtime_store_t *capi() { return ptr.get(); }
239+
240+
private:
241+
template <typename F>
242+
static wasmtime_error_t *
243+
raw_epoch_callback(wasmtime_context_t *context, void *data,
244+
uint64_t *epoch_deadline_delta,
245+
wasmtime_update_deadline_kind_t *update_kind) {
246+
auto &callback = *static_cast<F *>(data);
247+
Context ctx(context);
248+
auto result = callback(ctx, *epoch_deadline_delta);
249+
250+
if (!result) {
251+
return result.err().release();
252+
}
253+
*update_kind = static_cast<wasmtime_update_deadline_kind_t>(result.ok());
254+
return nullptr;
255+
}
256+
257+
template <typename F> static void raw_epoch_finalizer(void *data) {
258+
std::unique_ptr<F> _ptr(static_cast<F *>(data));
259+
}
208260
};
209261

210262
} // namespace wasmtime

crates/c-api/tests/store.cc

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
#include <wasmtime/store.hh>
22

33
#include <gtest/gtest.h>
4+
#include <wasmtime/config.hh>
5+
#include <wasmtime/error.hh>
6+
#include <wasmtime/func.hh>
7+
#include <wasmtime/instance.hh>
8+
#include <wasmtime/module.hh>
49

510
using namespace wasmtime;
611

12+
template <typename T, typename E> T unwrap(Result<T, E> result) {
13+
if (result) {
14+
return result.ok();
15+
}
16+
std::cerr << "error: " << result.err().message() << "\n";
17+
std::abort();
18+
}
19+
720
TEST(Store, Smoke) {
821
Engine engine;
922
Store store(engine);
@@ -17,3 +30,54 @@ TEST(Store, Smoke) {
1730
store.context().set_fuel(1).err();
1831
store.context().set_epoch_deadline(1);
1932
}
33+
34+
TEST(Store, EpochDeadlineCallback) {
35+
Config config;
36+
config.epoch_interruption(true);
37+
Engine engine(std::move(config));
38+
39+
size_t num_calls = 0;
40+
Store store(engine);
41+
store.epoch_deadline_callback(
42+
[&num_calls](wasmtime::Store::Context /* context */,
43+
uint64_t &epoch_deadline_delta)
44+
-> wasmtime::Result<wasmtime::DeadlineKind> {
45+
epoch_deadline_delta += 1;
46+
num_calls += 1;
47+
return wasmtime::DeadlineKind::Continue;
48+
});
49+
50+
store.context().set_epoch_deadline(1);
51+
52+
Module m = unwrap(Module::compile(engine, "(module (func (export \"f\")))"));
53+
Instance i = unwrap(Instance::create(store, m, {}));
54+
55+
auto f = std::get<Func>(*i.get(store, "f"));
56+
57+
unwrap(f.call(store, {}));
58+
ASSERT_EQ(num_calls, 0);
59+
60+
engine.increment_epoch();
61+
unwrap(f.call(store, {}));
62+
ASSERT_EQ(num_calls, 1);
63+
64+
/// epoch_deadline_delta increased by 1 in the callback
65+
unwrap(f.call(store, {}));
66+
ASSERT_EQ(num_calls, 1);
67+
68+
engine.increment_epoch();
69+
unwrap(f.call(store, {}));
70+
ASSERT_EQ(num_calls, 2);
71+
72+
store.epoch_deadline_callback(
73+
[](wasmtime::Store::Context /* context */, uint64_t &epoch_deadline_delta)
74+
-> wasmtime::Result<wasmtime::DeadlineKind> {
75+
return Error("error from callback");
76+
});
77+
78+
engine.increment_epoch();
79+
auto result = f.call(store, {});
80+
EXPECT_FALSE(result);
81+
EXPECT_TRUE(result.err().message().find("error from callback") !=
82+
std::string::npos);
83+
}

0 commit comments

Comments
 (0)