Skip to content

Commit 0467d6f

Browse files
authored
Sketch out merging the C/C++ APIs (#10600)
* Sketch out merging the C/C++ APIs With the merging of the C++ API into this repository in #10582 it opens up some interesting questions about how to organize the C++ API. Externally it was all entirely a single file, but naturally this isn't great for evolution as it's just one giant tangled header. Instead this commit sketches out a possible different path forward which is to provide the C++ API as a sibling to the C API in preexisting header files. For example this moves the `Error` class to the `error.h` header file as an example. My rough hope would be that in the long-term we could deprecate/remove the `wasmtime.hh` header file and instead "just" have all the C++ APIs in the normal header files (e.g. `wasmtime.h`). Additionally the split of the C API in separate header files would be amenable to a similar split of the C++ API too where the API you see is basically conditional on the language mode of whatever's including the headers. I'll note though I've not seen prior art in doing this. I'm not aware of any other project which exports both a C and C++ API in its header files. That being said I'm not sure how many other projects would fall in such a bucket. * Split out `error.hh` to its own file
1 parent 806eca8 commit 0467d6f

File tree

4 files changed

+151
-107
lines changed

4 files changed

+151
-107
lines changed

crates/c-api/include/wasmtime.hh

Lines changed: 2 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
#endif
4848
#endif
4949

50-
#include "wasmtime.h"
50+
#include <wasmtime.h>
51+
#include <wasmtime/error.hh>
5152

5253
namespace wasmtime {
5354

@@ -130,103 +131,6 @@ private:
130131

131132
#endif
132133

133-
class Trace;
134-
135-
/**
136-
* \brief Errors coming from Wasmtime
137-
*
138-
* This class represents an error that came from Wasmtime and contains a textual
139-
* description of the error that occurred.
140-
*/
141-
class Error {
142-
struct deleter {
143-
void operator()(wasmtime_error_t *p) const { wasmtime_error_delete(p); }
144-
};
145-
146-
std::unique_ptr<wasmtime_error_t, deleter> ptr;
147-
148-
public:
149-
/// \brief Creates an error from the raw C API representation
150-
///
151-
/// Takes ownership of the provided `error`.
152-
Error(wasmtime_error_t *error) : ptr(error) {}
153-
154-
/// \brief Returns the error message associated with this error.
155-
std::string message() const {
156-
wasm_byte_vec_t msg_bytes;
157-
wasmtime_error_message(ptr.get(), &msg_bytes);
158-
auto ret = std::string(msg_bytes.data, msg_bytes.size);
159-
wasm_byte_vec_delete(&msg_bytes);
160-
return ret;
161-
}
162-
163-
/// If this trap represents a call to `exit` for WASI, this will return the
164-
/// optional error code associated with the exit trap.
165-
std::optional<int32_t> i32_exit() const {
166-
int32_t status = 0;
167-
if (wasmtime_error_exit_status(ptr.get(), &status)) {
168-
return status;
169-
}
170-
return std::nullopt;
171-
}
172-
173-
/// Returns the trace of WebAssembly frames associated with this error.
174-
///
175-
/// Note that the `trace` cannot outlive this error object.
176-
Trace trace() const;
177-
};
178-
179-
/// \brief Used to print an error.
180-
inline std::ostream &operator<<(std::ostream &os, const Error &e) {
181-
os << e.message();
182-
return os;
183-
}
184-
185-
/**
186-
* \brief Fallible result type used for Wasmtime.
187-
*
188-
* This type is used as the return value of many methods in the Wasmtime API.
189-
* This behaves similarly to Rust's `Result<T, E>` and will be replaced with a
190-
* C++ standard when it exists.
191-
*/
192-
template <typename T, typename E = Error> class [[nodiscard]] Result {
193-
std::variant<T, E> data;
194-
195-
public:
196-
/// \brief Creates a `Result` from its successful value.
197-
Result(T t) : data(std::move(t)) {}
198-
/// \brief Creates a `Result` from an error value.
199-
Result(E e) : data(std::move(e)) {}
200-
201-
/// \brief Returns `true` if this result is a success, `false` if it's an
202-
/// error
203-
explicit operator bool() const { return data.index() == 0; }
204-
205-
/// \brief Returns the error, if present, aborts if this is not an error.
206-
E &&err() { return std::get<E>(std::move(data)); }
207-
/// \brief Returns the error, if present, aborts if this is not an error.
208-
const E &&err() const { return std::get<E>(std::move(data)); }
209-
210-
/// \brief Returns the success, if present, aborts if this is an error.
211-
T &&ok() { return std::get<T>(std::move(data)); }
212-
/// \brief Returns the success, if present, aborts if this is an error.
213-
const T &&ok() const { return std::get<T>(std::move(data)); }
214-
215-
/// \brief Returns the success, if present, aborts if this is an error.
216-
T unwrap() {
217-
if (*this) {
218-
return this->ok();
219-
}
220-
unwrap_failed();
221-
}
222-
223-
private:
224-
[[noreturn]] void unwrap_failed() {
225-
fprintf(stderr, "error: %s\n", this->err().message().c_str()); // NOLINT
226-
std::abort();
227-
}
228-
};
229-
230134
/// \brief Strategies passed to `Config::strategy`
231135
enum class Strategy {
232136
/// Automatically selects the compilation strategy
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* \file wasmtime/error.hh
3+
*/
4+
5+
#ifndef WASMTIME_ERROR_HH
6+
#define WASMTIME_ERROR_HH
7+
8+
#include <memory>
9+
#include <optional>
10+
#include <string>
11+
#include <variant>
12+
#include <wasmtime/error.h>
13+
14+
namespace wasmtime {
15+
16+
class Trace;
17+
18+
/**
19+
* \brief Errors coming from Wasmtime
20+
*
21+
* This class represents an error that came from Wasmtime and contains a textual
22+
* description of the error that occurred.
23+
*/
24+
class Error {
25+
struct deleter {
26+
void operator()(wasmtime_error_t *p) const { wasmtime_error_delete(p); }
27+
};
28+
29+
std::unique_ptr<wasmtime_error_t, deleter> ptr;
30+
31+
public:
32+
/// \brief Creates an error from the raw C API representation
33+
///
34+
/// Takes ownership of the provided `error`.
35+
Error(wasmtime_error_t *error) : ptr(error) {}
36+
37+
/// \brief Creates an error with the provided message.
38+
Error(const std::string &s) : ptr(wasmtime_error_new(s.c_str())) {}
39+
40+
/// \brief Returns the error message associated with this error.
41+
std::string message() const {
42+
wasm_byte_vec_t msg_bytes;
43+
wasmtime_error_message(ptr.get(), &msg_bytes);
44+
auto ret = std::string(msg_bytes.data, msg_bytes.size);
45+
wasm_byte_vec_delete(&msg_bytes);
46+
return ret;
47+
}
48+
49+
/// If this trap represents a call to `exit` for WASI, this will return the
50+
/// optional error code associated with the exit trap.
51+
std::optional<int32_t> i32_exit() const {
52+
int32_t status = 0;
53+
if (wasmtime_error_exit_status(ptr.get(), &status)) {
54+
return status;
55+
}
56+
return std::nullopt;
57+
}
58+
59+
/// Returns the trace of WebAssembly frames associated with this error.
60+
///
61+
/// Note that the `trace` cannot outlive this error object.
62+
Trace trace() const;
63+
};
64+
65+
/// \brief Used to print an error.
66+
inline std::ostream &operator<<(std::ostream &os, const Error &e) {
67+
os << e.message();
68+
return os;
69+
}
70+
71+
/**
72+
* \brief Fallible result type used for Wasmtime.
73+
*
74+
* This type is used as the return value of many methods in the Wasmtime API.
75+
* This behaves similarly to Rust's `Result<T, E>` and will be replaced with a
76+
* C++ standard when it exists.
77+
*/
78+
template <typename T, typename E = Error> class [[nodiscard]] Result {
79+
std::variant<T, E> data;
80+
81+
public:
82+
/// \brief Creates a `Result` from its successful value.
83+
Result(T t) : data(std::move(t)) {}
84+
/// \brief Creates a `Result` from an error value.
85+
Result(E e) : data(std::move(e)) {}
86+
87+
/// \brief Returns `true` if this result is a success, `false` if it's an
88+
/// error
89+
explicit operator bool() const { return data.index() == 0; }
90+
91+
/// \brief Returns the error, if present, aborts if this is not an error.
92+
E &&err() { return std::get<E>(std::move(data)); }
93+
/// \brief Returns the error, if present, aborts if this is not an error.
94+
const E &&err() const { return std::get<E>(std::move(data)); }
95+
96+
/// \brief Returns the success, if present, aborts if this is an error.
97+
T &&ok() { return std::get<T>(std::move(data)); }
98+
/// \brief Returns the success, if present, aborts if this is an error.
99+
const T &&ok() const { return std::get<T>(std::move(data)); }
100+
101+
/// \brief Returns the success, if present, aborts if this is an error.
102+
T unwrap() {
103+
if (*this) {
104+
return this->ok();
105+
}
106+
unwrap_failed();
107+
}
108+
109+
private:
110+
[[noreturn]] void unwrap_failed() {
111+
fprintf(stderr, "error: %s\n", this->err().message().c_str()); // NOLINT
112+
std::abort();
113+
}
114+
};
115+
116+
} // namespace wasmtime
117+
118+
#endif // WASMTIME_ERROR_HH
119+

crates/c-api/tests/CMakeLists.txt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ FetchContent_MakeAvailable(googletest)
99

1010
include(GoogleTest)
1111

12-
function(add_capi_test name path)
13-
add_executable(test-${name} ${path})
12+
function(add_capi_test name)
13+
cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "FILES")
14+
add_executable(test-${name} ${arg_FILES})
1415
target_link_libraries(test-${name} PRIVATE wasmtime-cpp gtest_main)
1516
gtest_discover_tests(test-${name})
1617
endfunction()
1718

18-
add_capi_test(simple simple.cc)
19-
add_capi_test(types types.cc)
20-
add_capi_test(func func.cc)
21-
add_capi_test(component-instantiate component/instantiate.cc)
19+
add_capi_test(simple FILES simple.cc)
20+
add_capi_test(types FILES types.cc)
21+
add_capi_test(func FILES func.cc)
22+
add_capi_test(component-instantiate FILES component/instantiate.cc)
23+
add_capi_test(error FILES error.cc)
2224

2325
# Add a custom test where two files include `wasmtime.hh` and are compiled into
2426
# the same executable (basically makes sure any defined functions in the header
2527
# are tagged with `inline`).
26-
add_executable(test-double-include double-include-a.cc double-include-b.cc)
27-
target_link_libraries(test-double-include PRIVATE wasmtime-cpp gtest_main)
28-
gtest_discover_tests(test-double-include)
28+
add_capi_test(test-double-include FILES double-include-a.cc double-include-b.cc)

crates/c-api/tests/error.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include <wasmtime/error.hh>
2+
#include <gtest/gtest.h>
3+
4+
using namespace wasmtime;
5+
6+
TEST(Result, Simple) {
7+
Result<int> ok_result(1);
8+
EXPECT_TRUE(ok_result);
9+
EXPECT_EQ(ok_result.ok(), 1);
10+
EXPECT_EQ(ok_result.unwrap(), 1);
11+
12+
Result<int, std::string> err_result("x");
13+
EXPECT_FALSE(err_result);
14+
EXPECT_EQ(err_result.err(), "x");
15+
}
16+
17+
TEST(Error, Simple) {
18+
Error err("hello");
19+
EXPECT_EQ(err.message(), "hello");
20+
EXPECT_FALSE(err.i32_exit());
21+
}

0 commit comments

Comments
 (0)