From 9a84aa784b0c97d52a9e4ff6a212d91438d7526c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 16 Apr 2025 13:12:44 -0700 Subject: [PATCH 1/2] 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. --- crates/c-api/include/wasmtime.hh | 97 ---------------------- crates/c-api/include/wasmtime/error.h | 113 ++++++++++++++++++++++++++ crates/c-api/tests/CMakeLists.txt | 18 ++-- crates/c-api/tests/error.cc | 21 +++++ 4 files changed, 143 insertions(+), 106 deletions(-) create mode 100644 crates/c-api/tests/error.cc diff --git a/crates/c-api/include/wasmtime.hh b/crates/c-api/include/wasmtime.hh index 09befd55a585..010e95cb04cb 100644 --- a/crates/c-api/include/wasmtime.hh +++ b/crates/c-api/include/wasmtime.hh @@ -130,103 +130,6 @@ private: #endif -class Trace; - -/** - * \brief Errors coming from Wasmtime - * - * This class represents an error that came from Wasmtime and contains a textual - * description of the error that occurred. - */ -class Error { - struct deleter { - void operator()(wasmtime_error_t *p) const { wasmtime_error_delete(p); } - }; - - std::unique_ptr ptr; - -public: - /// \brief Creates an error from the raw C API representation - /// - /// Takes ownership of the provided `error`. - Error(wasmtime_error_t *error) : ptr(error) {} - - /// \brief Returns the error message associated with this error. - std::string message() const { - wasm_byte_vec_t msg_bytes; - wasmtime_error_message(ptr.get(), &msg_bytes); - auto ret = std::string(msg_bytes.data, msg_bytes.size); - wasm_byte_vec_delete(&msg_bytes); - return ret; - } - - /// If this trap represents a call to `exit` for WASI, this will return the - /// optional error code associated with the exit trap. - std::optional i32_exit() const { - int32_t status = 0; - if (wasmtime_error_exit_status(ptr.get(), &status)) { - return status; - } - return std::nullopt; - } - - /// Returns the trace of WebAssembly frames associated with this error. - /// - /// Note that the `trace` cannot outlive this error object. - Trace trace() const; -}; - -/// \brief Used to print an error. -inline std::ostream &operator<<(std::ostream &os, const Error &e) { - os << e.message(); - return os; -} - -/** - * \brief Fallible result type used for Wasmtime. - * - * This type is used as the return value of many methods in the Wasmtime API. - * This behaves similarly to Rust's `Result` and will be replaced with a - * C++ standard when it exists. - */ -template class [[nodiscard]] Result { - std::variant data; - -public: - /// \brief Creates a `Result` from its successful value. - Result(T t) : data(std::move(t)) {} - /// \brief Creates a `Result` from an error value. - Result(E e) : data(std::move(e)) {} - - /// \brief Returns `true` if this result is a success, `false` if it's an - /// error - explicit operator bool() const { return data.index() == 0; } - - /// \brief Returns the error, if present, aborts if this is not an error. - E &&err() { return std::get(std::move(data)); } - /// \brief Returns the error, if present, aborts if this is not an error. - const E &&err() const { return std::get(std::move(data)); } - - /// \brief Returns the success, if present, aborts if this is an error. - T &&ok() { return std::get(std::move(data)); } - /// \brief Returns the success, if present, aborts if this is an error. - const T &&ok() const { return std::get(std::move(data)); } - - /// \brief Returns the success, if present, aborts if this is an error. - T unwrap() { - if (*this) { - return this->ok(); - } - unwrap_failed(); - } - -private: - [[noreturn]] void unwrap_failed() { - fprintf(stderr, "error: %s\n", this->err().message().c_str()); // NOLINT - std::abort(); - } -}; - /// \brief Strategies passed to `Config::strategy` enum class Strategy { /// Automatically selects the compilation strategy diff --git a/crates/c-api/include/wasmtime/error.h b/crates/c-api/include/wasmtime/error.h index ad9172543629..01a87c6df969 100644 --- a/crates/c-api/include/wasmtime/error.h +++ b/crates/c-api/include/wasmtime/error.h @@ -75,4 +75,117 @@ WASM_API_EXTERN void wasmtime_error_wasm_trace(const wasmtime_error_t *, } // extern "C" #endif +#ifdef __cplusplus + +#include +#include +#include +#include + +namespace wasmtime { + +class Trace; + +/** + * \brief Errors coming from Wasmtime + * + * This class represents an error that came from Wasmtime and contains a textual + * description of the error that occurred. + */ +class Error { + struct deleter { + void operator()(wasmtime_error_t *p) const { wasmtime_error_delete(p); } + }; + + std::unique_ptr ptr; + +public: + /// \brief Creates an error from the raw C API representation + /// + /// Takes ownership of the provided `error`. + Error(wasmtime_error_t *error) : ptr(error) {} + + /// \brief Creates an error with the provided message. + Error(const std::string &s) : ptr(wasmtime_error_new(s.c_str())) {} + + /// \brief Returns the error message associated with this error. + std::string message() const { + wasm_byte_vec_t msg_bytes; + wasmtime_error_message(ptr.get(), &msg_bytes); + auto ret = std::string(msg_bytes.data, msg_bytes.size); + wasm_byte_vec_delete(&msg_bytes); + return ret; + } + + /// If this trap represents a call to `exit` for WASI, this will return the + /// optional error code associated with the exit trap. + std::optional i32_exit() const { + int32_t status = 0; + if (wasmtime_error_exit_status(ptr.get(), &status)) { + return status; + } + return std::nullopt; + } + + /// Returns the trace of WebAssembly frames associated with this error. + /// + /// Note that the `trace` cannot outlive this error object. + Trace trace() const; +}; + +/// \brief Used to print an error. +inline std::ostream &operator<<(std::ostream &os, const Error &e) { + os << e.message(); + return os; +} + +/** + * \brief Fallible result type used for Wasmtime. + * + * This type is used as the return value of many methods in the Wasmtime API. + * This behaves similarly to Rust's `Result` and will be replaced with a + * C++ standard when it exists. + */ +template class [[nodiscard]] Result { + std::variant data; + +public: + /// \brief Creates a `Result` from its successful value. + Result(T t) : data(std::move(t)) {} + /// \brief Creates a `Result` from an error value. + Result(E e) : data(std::move(e)) {} + + /// \brief Returns `true` if this result is a success, `false` if it's an + /// error + explicit operator bool() const { return data.index() == 0; } + + /// \brief Returns the error, if present, aborts if this is not an error. + E &&err() { return std::get(std::move(data)); } + /// \brief Returns the error, if present, aborts if this is not an error. + const E &&err() const { return std::get(std::move(data)); } + + /// \brief Returns the success, if present, aborts if this is an error. + T &&ok() { return std::get(std::move(data)); } + /// \brief Returns the success, if present, aborts if this is an error. + const T &&ok() const { return std::get(std::move(data)); } + + /// \brief Returns the success, if present, aborts if this is an error. + T unwrap() { + if (*this) { + return this->ok(); + } + unwrap_failed(); + } + +private: + [[noreturn]] void unwrap_failed() { + fprintf(stderr, "error: %s\n", this->err().message().c_str()); // NOLINT + std::abort(); + } +}; + +} // namespace wasmtime + +#endif // __cplusplus + #endif // WASMTIME_ERROR_H diff --git a/crates/c-api/tests/CMakeLists.txt b/crates/c-api/tests/CMakeLists.txt index fb5d0fc5cff3..99e845443180 100644 --- a/crates/c-api/tests/CMakeLists.txt +++ b/crates/c-api/tests/CMakeLists.txt @@ -9,20 +9,20 @@ FetchContent_MakeAvailable(googletest) include(GoogleTest) -function(add_capi_test name path) - add_executable(test-${name} ${path}) +function(add_capi_test name) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "FILES") + add_executable(test-${name} ${arg_FILES}) target_link_libraries(test-${name} PRIVATE wasmtime-cpp gtest_main) gtest_discover_tests(test-${name}) endfunction() -add_capi_test(simple simple.cc) -add_capi_test(types types.cc) -add_capi_test(func func.cc) -add_capi_test(component-instantiate component/instantiate.cc) +add_capi_test(simple FILES simple.cc) +add_capi_test(types FILES types.cc) +add_capi_test(func FILES func.cc) +add_capi_test(component-instantiate FILES component/instantiate.cc) +add_capi_test(error FILES error.cc) # Add a custom test where two files include `wasmtime.hh` and are compiled into # the same executable (basically makes sure any defined functions in the header # are tagged with `inline`). -add_executable(test-double-include double-include-a.cc double-include-b.cc) -target_link_libraries(test-double-include PRIVATE wasmtime-cpp gtest_main) -gtest_discover_tests(test-double-include) +add_capi_test(test-double-include FILES double-include-a.cc double-include-b.cc) diff --git a/crates/c-api/tests/error.cc b/crates/c-api/tests/error.cc new file mode 100644 index 000000000000..c671c654a8ac --- /dev/null +++ b/crates/c-api/tests/error.cc @@ -0,0 +1,21 @@ +#include +#include + +using namespace wasmtime; + +TEST(Result, Simple) { + Result ok_result(1); + EXPECT_TRUE(ok_result); + EXPECT_EQ(ok_result.ok(), 1); + EXPECT_EQ(ok_result.unwrap(), 1); + + Result err_result("x"); + EXPECT_FALSE(err_result); + EXPECT_EQ(err_result.err(), "x"); +} + +TEST(Error, Simple) { + Error err("hello"); + EXPECT_EQ(err.message(), "hello"); + EXPECT_FALSE(err.i32_exit()); +} From 090ac381b828f3c544efa634628d5034dd87ad61 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 21 Apr 2025 07:31:44 -0700 Subject: [PATCH 2/2] Split out `error.hh` to its own file --- crates/c-api/include/wasmtime.hh | 3 +- crates/c-api/include/wasmtime/error.h | 113 ----------------------- crates/c-api/include/wasmtime/error.hh | 119 +++++++++++++++++++++++++ crates/c-api/tests/error.cc | 2 +- 4 files changed, 122 insertions(+), 115 deletions(-) create mode 100644 crates/c-api/include/wasmtime/error.hh diff --git a/crates/c-api/include/wasmtime.hh b/crates/c-api/include/wasmtime.hh index 010e95cb04cb..34721510c00c 100644 --- a/crates/c-api/include/wasmtime.hh +++ b/crates/c-api/include/wasmtime.hh @@ -47,7 +47,8 @@ #endif #endif -#include "wasmtime.h" +#include +#include namespace wasmtime { diff --git a/crates/c-api/include/wasmtime/error.h b/crates/c-api/include/wasmtime/error.h index 01a87c6df969..ad9172543629 100644 --- a/crates/c-api/include/wasmtime/error.h +++ b/crates/c-api/include/wasmtime/error.h @@ -75,117 +75,4 @@ WASM_API_EXTERN void wasmtime_error_wasm_trace(const wasmtime_error_t *, } // extern "C" #endif -#ifdef __cplusplus - -#include -#include -#include -#include - -namespace wasmtime { - -class Trace; - -/** - * \brief Errors coming from Wasmtime - * - * This class represents an error that came from Wasmtime and contains a textual - * description of the error that occurred. - */ -class Error { - struct deleter { - void operator()(wasmtime_error_t *p) const { wasmtime_error_delete(p); } - }; - - std::unique_ptr ptr; - -public: - /// \brief Creates an error from the raw C API representation - /// - /// Takes ownership of the provided `error`. - Error(wasmtime_error_t *error) : ptr(error) {} - - /// \brief Creates an error with the provided message. - Error(const std::string &s) : ptr(wasmtime_error_new(s.c_str())) {} - - /// \brief Returns the error message associated with this error. - std::string message() const { - wasm_byte_vec_t msg_bytes; - wasmtime_error_message(ptr.get(), &msg_bytes); - auto ret = std::string(msg_bytes.data, msg_bytes.size); - wasm_byte_vec_delete(&msg_bytes); - return ret; - } - - /// If this trap represents a call to `exit` for WASI, this will return the - /// optional error code associated with the exit trap. - std::optional i32_exit() const { - int32_t status = 0; - if (wasmtime_error_exit_status(ptr.get(), &status)) { - return status; - } - return std::nullopt; - } - - /// Returns the trace of WebAssembly frames associated with this error. - /// - /// Note that the `trace` cannot outlive this error object. - Trace trace() const; -}; - -/// \brief Used to print an error. -inline std::ostream &operator<<(std::ostream &os, const Error &e) { - os << e.message(); - return os; -} - -/** - * \brief Fallible result type used for Wasmtime. - * - * This type is used as the return value of many methods in the Wasmtime API. - * This behaves similarly to Rust's `Result` and will be replaced with a - * C++ standard when it exists. - */ -template class [[nodiscard]] Result { - std::variant data; - -public: - /// \brief Creates a `Result` from its successful value. - Result(T t) : data(std::move(t)) {} - /// \brief Creates a `Result` from an error value. - Result(E e) : data(std::move(e)) {} - - /// \brief Returns `true` if this result is a success, `false` if it's an - /// error - explicit operator bool() const { return data.index() == 0; } - - /// \brief Returns the error, if present, aborts if this is not an error. - E &&err() { return std::get(std::move(data)); } - /// \brief Returns the error, if present, aborts if this is not an error. - const E &&err() const { return std::get(std::move(data)); } - - /// \brief Returns the success, if present, aborts if this is an error. - T &&ok() { return std::get(std::move(data)); } - /// \brief Returns the success, if present, aborts if this is an error. - const T &&ok() const { return std::get(std::move(data)); } - - /// \brief Returns the success, if present, aborts if this is an error. - T unwrap() { - if (*this) { - return this->ok(); - } - unwrap_failed(); - } - -private: - [[noreturn]] void unwrap_failed() { - fprintf(stderr, "error: %s\n", this->err().message().c_str()); // NOLINT - std::abort(); - } -}; - -} // namespace wasmtime - -#endif // __cplusplus - #endif // WASMTIME_ERROR_H diff --git a/crates/c-api/include/wasmtime/error.hh b/crates/c-api/include/wasmtime/error.hh new file mode 100644 index 000000000000..86f5dd4d882f --- /dev/null +++ b/crates/c-api/include/wasmtime/error.hh @@ -0,0 +1,119 @@ +/** + * \file wasmtime/error.hh + */ + +#ifndef WASMTIME_ERROR_HH +#define WASMTIME_ERROR_HH + +#include +#include +#include +#include +#include + +namespace wasmtime { + +class Trace; + +/** + * \brief Errors coming from Wasmtime + * + * This class represents an error that came from Wasmtime and contains a textual + * description of the error that occurred. + */ +class Error { + struct deleter { + void operator()(wasmtime_error_t *p) const { wasmtime_error_delete(p); } + }; + + std::unique_ptr ptr; + +public: + /// \brief Creates an error from the raw C API representation + /// + /// Takes ownership of the provided `error`. + Error(wasmtime_error_t *error) : ptr(error) {} + + /// \brief Creates an error with the provided message. + Error(const std::string &s) : ptr(wasmtime_error_new(s.c_str())) {} + + /// \brief Returns the error message associated with this error. + std::string message() const { + wasm_byte_vec_t msg_bytes; + wasmtime_error_message(ptr.get(), &msg_bytes); + auto ret = std::string(msg_bytes.data, msg_bytes.size); + wasm_byte_vec_delete(&msg_bytes); + return ret; + } + + /// If this trap represents a call to `exit` for WASI, this will return the + /// optional error code associated with the exit trap. + std::optional i32_exit() const { + int32_t status = 0; + if (wasmtime_error_exit_status(ptr.get(), &status)) { + return status; + } + return std::nullopt; + } + + /// Returns the trace of WebAssembly frames associated with this error. + /// + /// Note that the `trace` cannot outlive this error object. + Trace trace() const; +}; + +/// \brief Used to print an error. +inline std::ostream &operator<<(std::ostream &os, const Error &e) { + os << e.message(); + return os; +} + +/** + * \brief Fallible result type used for Wasmtime. + * + * This type is used as the return value of many methods in the Wasmtime API. + * This behaves similarly to Rust's `Result` and will be replaced with a + * C++ standard when it exists. + */ +template class [[nodiscard]] Result { + std::variant data; + +public: + /// \brief Creates a `Result` from its successful value. + Result(T t) : data(std::move(t)) {} + /// \brief Creates a `Result` from an error value. + Result(E e) : data(std::move(e)) {} + + /// \brief Returns `true` if this result is a success, `false` if it's an + /// error + explicit operator bool() const { return data.index() == 0; } + + /// \brief Returns the error, if present, aborts if this is not an error. + E &&err() { return std::get(std::move(data)); } + /// \brief Returns the error, if present, aborts if this is not an error. + const E &&err() const { return std::get(std::move(data)); } + + /// \brief Returns the success, if present, aborts if this is an error. + T &&ok() { return std::get(std::move(data)); } + /// \brief Returns the success, if present, aborts if this is an error. + const T &&ok() const { return std::get(std::move(data)); } + + /// \brief Returns the success, if present, aborts if this is an error. + T unwrap() { + if (*this) { + return this->ok(); + } + unwrap_failed(); + } + +private: + [[noreturn]] void unwrap_failed() { + fprintf(stderr, "error: %s\n", this->err().message().c_str()); // NOLINT + std::abort(); + } +}; + +} // namespace wasmtime + +#endif // WASMTIME_ERROR_HH + diff --git a/crates/c-api/tests/error.cc b/crates/c-api/tests/error.cc index c671c654a8ac..03496004752a 100644 --- a/crates/c-api/tests/error.cc +++ b/crates/c-api/tests/error.cc @@ -1,4 +1,4 @@ -#include +#include #include using namespace wasmtime;