Skip to content

Commit 0dbb6f3

Browse files
authored
Exceptions: implement C API. (#12861)
* Exceptions: implement C API. This PR implements C (and C++) API support for Wasm exceptions, one final remaining hurdle (aside from fuzz-testing) for making exceptions tier-1 and on-by-default. * Review feedback, and add exnref case to `wasmtime_val_t`. * Review feedback: GC feature guard. * clang-format * add docs to exn.hh. * Remove tag size asserts: broken on 32-bit platforms, but not needed for correctness wrt C struct.
1 parent 7e5d864 commit 0dbb6f3

26 files changed

Lines changed: 965 additions & 10 deletions

crates/c-api/include/wasmtime.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@
190190
#include <wasmtime/config.h>
191191
#include <wasmtime/engine.h>
192192
#include <wasmtime/error.h>
193+
#include <wasmtime/exn.h>
193194
#include <wasmtime/extern.h>
194195
#include <wasmtime/func.h>
195196
#include <wasmtime/global.h>
@@ -201,6 +202,7 @@
201202
#include <wasmtime/sharedmemory.h>
202203
#include <wasmtime/store.h>
203204
#include <wasmtime/table.h>
205+
#include <wasmtime/tag.h>
204206
#include <wasmtime/trap.h>
205207
#include <wasmtime/val.h>
206208
#include <wasmtime/async.h>

crates/c-api/include/wasmtime.hh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include <wasmtime/config.hh>
3939
#include <wasmtime/engine.hh>
4040
#include <wasmtime/error.hh>
41+
#include <wasmtime/exn.hh>
4142
#include <wasmtime/extern.hh>
4243
#include <wasmtime/func.hh>
4344
#include <wasmtime/global.hh>
@@ -47,6 +48,7 @@
4748
#include <wasmtime/module.hh>
4849
#include <wasmtime/store.hh>
4950
#include <wasmtime/table.hh>
51+
#include <wasmtime/tag.hh>
5052
#include <wasmtime/trap.hh>
5153
#include <wasmtime/types.hh>
5254
#include <wasmtime/val.hh>
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/**
2+
* \file wasmtime/exn.h
3+
*
4+
* \brief Wasmtime APIs for WebAssembly exception objects.
5+
*
6+
* Exception objects carry a tag and a set of field values. They are
7+
* allocated on the GC heap within a store.
8+
*
9+
* ## Throwing from host functions
10+
*
11+
* To throw an exception from a host function implemented via the C API:
12+
*
13+
* 1. Create an exception object with #wasmtime_exn_new.
14+
* 2. Set it as the pending exception with #wasmtime_context_set_exception.
15+
* 3. Return a trap from the host callback (e.g. via `wasmtime_trap_new`).
16+
*
17+
* The runtime will propagate the exception through WebAssembly
18+
* `try_table`/`catch` blocks.
19+
*
20+
* ## Catching exceptions from Wasm
21+
*
22+
* When a call to a WebAssembly function (e.g. via #wasmtime_func_call)
23+
* returns a trap, check for a pending exception:
24+
*
25+
* 1. Call #wasmtime_context_has_exception or #wasmtime_context_take_exception.
26+
* 2. If present, examine the exception's tag and fields.
27+
*/
28+
29+
#ifndef WASMTIME_EXN_H
30+
#define WASMTIME_EXN_H
31+
32+
#include <wasmtime/conf.h>
33+
34+
#ifdef WASMTIME_FEATURE_GC
35+
36+
#include <wasm.h>
37+
#include <wasmtime/error.h>
38+
#include <wasmtime/store.h>
39+
#include <wasmtime/tag.h>
40+
#include <wasmtime/val.h>
41+
42+
#ifdef __cplusplus
43+
extern "C" {
44+
#endif
45+
46+
/**
47+
* \typedef wasmtime_exn_t
48+
* \brief An opaque type representing a WebAssembly exception object.
49+
*
50+
* Exception objects are allocated on the GC heap and referenced through
51+
* this handle. The handle is owned by the caller and must be freed
52+
* with #wasmtime_exn_delete.
53+
*/
54+
typedef struct wasmtime_exn wasmtime_exn_t;
55+
56+
/**
57+
* \brief Deletes a #wasmtime_exn_t.
58+
*/
59+
WASM_API_EXTERN void wasmtime_exn_delete(wasmtime_exn_t *exn);
60+
61+
/**
62+
* \brief Creates a new exception object.
63+
*
64+
* \param store the store context
65+
* \param tag the tag to associate with this exception
66+
* \param fields pointer to an array of field values matching the tag's
67+
* payload signature
68+
* \param nfields the number of elements in `fields`
69+
* \param exn_ret on success, set to the newly allocated exception.
70+
* The caller owns the returned pointer and must free it with
71+
* #wasmtime_exn_delete.
72+
*
73+
* \return NULL on success, or an error on failure.
74+
*/
75+
WASM_API_EXTERN wasmtime_error_t *wasmtime_exn_new(wasmtime_context_t *store,
76+
const wasmtime_tag_t *tag,
77+
const wasmtime_val_t *fields,
78+
size_t nfields,
79+
wasmtime_exn_t **exn_ret);
80+
81+
/**
82+
* \brief Returns the tag associated with this exception.
83+
*
84+
* \param store the store context
85+
* \param exn the exception to query
86+
* \param tag_ret on success, filled with the exception's tag
87+
*
88+
* \return NULL on success, or an error on failure.
89+
*/
90+
WASM_API_EXTERN wasmtime_error_t *wasmtime_exn_tag(wasmtime_context_t *store,
91+
const wasmtime_exn_t *exn,
92+
wasmtime_tag_t *tag_ret);
93+
94+
/**
95+
* \brief Returns the number of fields in this exception.
96+
*
97+
* \param store the store context
98+
* \param exn the exception to query
99+
*/
100+
WASM_API_EXTERN size_t wasmtime_exn_field_count(wasmtime_context_t *store,
101+
const wasmtime_exn_t *exn);
102+
103+
/**
104+
* \brief Reads a field value from this exception by index.
105+
*
106+
* \param store the store context
107+
* \param exn the exception to query
108+
* \param index the field index (0-based)
109+
* \param val_ret on success, filled with the field value
110+
* (caller-owned on return).
111+
*
112+
* \return NULL on success, or an error if the index is out of bounds.
113+
*/
114+
WASM_API_EXTERN wasmtime_error_t *wasmtime_exn_field(wasmtime_context_t *store,
115+
const wasmtime_exn_t *exn,
116+
size_t index,
117+
wasmtime_val_t *val_ret);
118+
119+
/**
120+
* \brief Sets the pending exception on the store and returns a trap.
121+
*
122+
* This transfers ownership of `exn` to the store. After this call,
123+
* the caller must not use or free `exn`.
124+
*
125+
* Returns a `wasm_trap_t` that the host callback MUST return to signal
126+
* to the Wasm runtime that an exception was thrown. The caller owns
127+
* the returned trap.
128+
*
129+
* \param store the store context
130+
* \param exn the exception to throw (ownership transferred)
131+
* \return a trap to return from the host callback (caller-owned)
132+
*/
133+
WASM_API_EXTERN wasm_trap_t *
134+
wasmtime_context_set_exception(wasmtime_context_t *store, wasmtime_exn_t *exn);
135+
136+
/**
137+
* \brief Takes the pending exception from the store, if any.
138+
*
139+
* If there is a pending exception, removes it from the store and
140+
* returns it. The caller owns the returned pointer and must free it
141+
* with #wasmtime_exn_delete.
142+
*
143+
* \param store the store context
144+
* \param exn_ret on success, set to the exception (caller-owned)
145+
*
146+
* \return true if there was a pending exception, false otherwise.
147+
*/
148+
WASM_API_EXTERN bool wasmtime_context_take_exception(wasmtime_context_t *store,
149+
wasmtime_exn_t **exn_ret);
150+
151+
/**
152+
* \brief Tests whether there is a pending exception on the store.
153+
*
154+
* \param store the store context
155+
*
156+
* \return true if a pending exception is set, false otherwise.
157+
*/
158+
WASM_API_EXTERN bool wasmtime_context_has_exception(wasmtime_context_t *store);
159+
160+
#ifdef __cplusplus
161+
} // extern "C"
162+
#endif
163+
164+
#endif // WASMTIME_FEATURE_GC
165+
166+
#endif // WASMTIME_EXN_H
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
* \file wasmtime/exn.hh
3+
*/
4+
5+
#ifndef WASMTIME_EXN_HH
6+
#define WASMTIME_EXN_HH
7+
8+
#include <wasmtime/conf.h>
9+
10+
#ifdef WASMTIME_FEATURE_GC
11+
12+
#include <vector>
13+
#include <wasmtime/error.hh>
14+
#include <wasmtime/exn.h>
15+
#include <wasmtime/store.hh>
16+
#include <wasmtime/tag.hh>
17+
#include <wasmtime/val.hh>
18+
19+
namespace wasmtime {
20+
21+
/**
22+
* \brief A WebAssembly exception object.
23+
*
24+
* Exception objects carry a tag and a set of field values. They are
25+
* allocated on the GC heap within a store.
26+
*
27+
* This type owns its underlying `wasmtime_exn_t` handle. When it goes out
28+
* of scope the handle is freed.
29+
*/
30+
class Exn {
31+
struct deleter {
32+
void operator()(wasmtime_exn_t *p) const { wasmtime_exn_delete(p); }
33+
};
34+
35+
std::unique_ptr<wasmtime_exn_t, deleter> ptr;
36+
37+
public:
38+
/// Takes ownership of a raw `wasmtime_exn_t` pointer.
39+
explicit Exn(wasmtime_exn_t *raw) : ptr(raw) {}
40+
41+
/// Constructs a new exception.
42+
Exn(Exn &&other) = default;
43+
/// Moves an exception.
44+
Exn &operator=(Exn &&other) = default;
45+
Exn(const Exn &) = delete;
46+
Exn &operator=(const Exn &) = delete;
47+
/// Destroys an exception.
48+
~Exn() = default;
49+
50+
/**
51+
* \brief Create a new exception object.
52+
*
53+
* \param cx the store in which to allocate the exception
54+
* \param tag the tag to associate with this exception
55+
* \param fields the field values matching the tag's payload signature
56+
*/
57+
static Result<Exn> create(Store::Context cx, const Tag &tag,
58+
const std::vector<Val> &fields) {
59+
wasmtime_exn_t *exn = nullptr;
60+
auto *error = wasmtime_exn_new(
61+
cx.capi(), &tag.capi(),
62+
reinterpret_cast<const wasmtime_val_t *>(fields.data()), fields.size(),
63+
&exn);
64+
if (error != nullptr) {
65+
return Error(error);
66+
}
67+
return Exn(exn);
68+
}
69+
70+
/// Returns the tag associated with this exception.
71+
Result<Tag> tag(Store::Context cx) const {
72+
wasmtime_tag_t tag;
73+
auto *error = wasmtime_exn_tag(cx.capi(), ptr.get(), &tag);
74+
if (error != nullptr) {
75+
return Error(error);
76+
}
77+
return Tag(tag);
78+
}
79+
80+
/// Returns the number of fields in this exception.
81+
size_t field_count(Store::Context cx) const {
82+
return wasmtime_exn_field_count(cx.capi(), ptr.get());
83+
}
84+
85+
/// Reads a field value by index.
86+
Result<Val> field(Store::Context cx, size_t index) const {
87+
wasmtime_val_t val;
88+
auto *error = wasmtime_exn_field(cx.capi(), ptr.get(), index, &val);
89+
if (error != nullptr) {
90+
return Error(error);
91+
}
92+
return Val(val);
93+
}
94+
95+
/// Returns the raw underlying C API pointer (non-owning).
96+
wasmtime_exn_t *capi() const { return ptr.get(); }
97+
98+
/// Releases ownership of the underlying C API pointer.
99+
wasmtime_exn_t *release() { return ptr.release(); }
100+
};
101+
102+
inline Trap Store::Context::throw_exception(Exn exn) {
103+
return Trap(wasmtime_context_set_exception(capi(), exn.release()));
104+
}
105+
106+
inline std::optional<Exn> Store::Context::take_exception() {
107+
wasmtime_exn_t *exn = nullptr;
108+
if (wasmtime_context_take_exception(capi(), &exn)) {
109+
return Exn(exn);
110+
}
111+
return std::nullopt;
112+
}
113+
114+
inline bool Store::Context::has_exception() {
115+
return wasmtime_context_has_exception(capi());
116+
}
117+
118+
} // namespace wasmtime
119+
120+
#endif // WASMTIME_FEATURE_GC
121+
122+
#endif // WASMTIME_EXN_HH

crates/c-api/include/wasmtime/extern.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <wasmtime/module.h>
1111
#include <wasmtime/sharedmemory.h>
1212
#include <wasmtime/store.h>
13+
#include <wasmtime/tag.h>
1314

1415
#ifdef __cplusplus
1516
extern "C" {
@@ -105,6 +106,9 @@ typedef uint8_t wasmtime_extern_kind_t;
105106
/// \brief Value of #wasmtime_extern_kind_t meaning that #wasmtime_extern_t is a
106107
/// shared memory
107108
#define WASMTIME_EXTERN_SHAREDMEMORY 4
109+
/// \brief Value of #wasmtime_extern_kind_t meaning that #wasmtime_extern_t is a
110+
/// tag
111+
#define WASMTIME_EXTERN_TAG 5
108112

109113
/**
110114
* \typedef wasmtime_extern_union_t
@@ -127,6 +131,8 @@ typedef union wasmtime_extern_union {
127131
wasmtime_memory_t memory;
128132
/// Field used if #wasmtime_extern_t::kind is #WASMTIME_EXTERN_SHAREDMEMORY
129133
struct wasmtime_sharedmemory *sharedmemory;
134+
/// Field used if #wasmtime_extern_t::kind is #WASMTIME_EXTERN_TAG
135+
wasmtime_tag_t tag;
130136
} wasmtime_extern_union_t;
131137

132138
/**

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <wasmtime/global.hh>
1212
#include <wasmtime/memory.hh>
1313
#include <wasmtime/table.hh>
14+
#include <wasmtime/tag.hh>
1415

1516
namespace wasmtime {
1617

@@ -27,6 +28,8 @@ static Extern cvt_extern(wasmtime_extern_t &e) {
2728
return Memory(e.of.memory);
2829
case WASMTIME_EXTERN_TABLE:
2930
return Table(e.of.table);
31+
case WASMTIME_EXTERN_TAG:
32+
return Tag(e.of.tag);
3033
}
3134
std::abort();
3235
}
@@ -44,6 +47,9 @@ static void cvt_extern(const Extern &e, wasmtime_extern_t &raw) {
4447
} else if (const auto *memory = std::get_if<Memory>(&e)) {
4548
raw.kind = WASMTIME_EXTERN_MEMORY;
4649
raw.of.memory = memory->capi();
50+
} else if (const auto *tag = std::get_if<Tag>(&e)) {
51+
raw.kind = WASMTIME_EXTERN_TAG;
52+
raw.of.tag = tag->capi();
4753
} else {
4854
std::abort();
4955
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ class Global;
1212
class Func;
1313
class Memory;
1414
class Table;
15+
class Tag;
1516

1617
/// \typedef Extern
1718
/// \brief Representation of an external WebAssembly item
18-
typedef std::variant<Func, Global, Memory, Table> Extern;
19+
typedef std::variant<Func, Global, Memory, Table, Tag> Extern;
1920

2021
} // namespace wasmtime
2122

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <wasmtime/module.hh>
1414
#include <wasmtime/store.hh>
1515
#include <wasmtime/table.hh>
16+
#include <wasmtime/tag.hh>
1617

1718
namespace wasmtime {
1819

0 commit comments

Comments
 (0)