diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 102bb1dec790..3f95a62ff986 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -666,7 +666,7 @@ jobs: # Rust programs. Note that this only executes if the `determine` step told # us to test the capi which is off-by-default for PRs. - run: rustup target add wasm32-wasip2 # wasip2 target needed by example programs - - run: cmake -Sexamples -Bexamples/build -DBUILD_SHARED_LIBS=OFF + - run: cmake -Sexamples -Bexamples/build -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTS=ON - run: cmake --build examples/build --config Debug - run: cmake -E env CTEST_OUTPUT_ON_FAILURE=1 cmake --build examples/build --config Debug --target RUN_TESTS if: runner.os == 'Windows' diff --git a/crates/c-api/CMakeLists.txt b/crates/c-api/CMakeLists.txt index 09e8104a2710..89953f55ca86 100644 --- a/crates/c-api/CMakeLists.txt +++ b/crates/c-api/CMakeLists.txt @@ -3,6 +3,7 @@ project(wasmtime C) set(WASMTIME_USER_CARGO_BUILD_OPTIONS "" CACHE STRING "Additional cargo flags (such as --features) to apply to the build command") option(BUILD_SHARED_LIBS "Build using shared libraries" OFF) +option(BUILD_TESTS "Build tests" OFF) option(WASMTIME_ALWAYS_BUILD "If cmake should always invoke cargo to build wasmtime" ON) option(WASMTIME_FASTEST_RUNTIME "Set flags designed to optimize runtime performance" OFF) set(WASMTIME_TARGET "" CACHE STRING "Rust target to build for") @@ -129,3 +130,46 @@ add_custom_target(headers-to-doc -DWASMTIME_HEADER_DST=${CMAKE_BINARY_DIR}/include -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/install-headers.cmake DEPENDS ${headers}) + +if (NOT CMAKE_CXX_STANDARD) + message(STATUS "Cannot detect C++ Standard. Switching to C++17 by default !!") + set(CMAKE_CXX_STANDARD 17) +endif() +message(STATUS "CMAKE_CXX_STANDARD is ${CMAKE_CXX_STANDARD}") +if (NOT CMAKE_CXX_STANDARD GREATER_EQUAL 17) + message(FATAL_ERROR "WASMTIME_CPP library does not support ${CMAKE_CXX_STANDARD}") +endif() +set(CMAKE_CXX_STANDARD_REQUIRED True) + +option(ENABLE_CODE_ANALYSIS "Run code analysis" OFF) +message(STATUS "ENABLE_CODE_ANALYSIS ${ENABLE_CODE_ANALYSIS}") + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + add_compile_options (-fdiagnostics-color=always) +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + add_compile_options (-fcolor-diagnostics) +endif () + +add_library(wasmtime-cpp INTERFACE) +target_link_libraries(wasmtime-cpp INTERFACE wasmtime) +if (MSVC) + target_compile_options(wasmtime-cpp INTERFACE /DWASM_API_EXTERN= /DWASI_API_EXTERN=) + target_link_libraries(wasmtime-cpp INTERFACE ws2_32 bcrypt advapi32 userenv ntdll shell32 ole32) +else() + target_link_libraries(wasmtime-cpp INTERFACE stdc++ pthread) +endif() + +target_include_directories( + wasmtime-cpp + INTERFACE + ${PROJECT_SOURCE_DIR}/include) + +if (BUILD_TESTS) + message(STATUS "Building tests") + + enable_language(CXX) + set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) + + enable_testing() + add_subdirectory(tests) +endif() diff --git a/crates/c-api/README.md b/crates/c-api/README.md index 7a858cab86e2..a9f22f12ad6f 100644 --- a/crates/c-api/README.md +++ b/crates/c-api/README.md @@ -38,6 +38,10 @@ These commands will produce the following files: * `artifacts/include/**.h`: Header files for working with Wasmtime. +## Using in a C++ Project + +A header only C++ API is also offered as `wasmtime.hh`, which is built on top of the C API. Its located next to the C headers when you download a pre-built library, or when building from source. C++17 is required. + ## Using in a Rust Project If you have a Rust crate that contains bindings to a C or C++ library that uses Wasmtime, you can link the Wasmtime C API using Cargo. diff --git a/crates/c-api/doxygen.conf.in b/crates/c-api/doxygen.conf.in index 4417b81819f4..5ce790eb6961 100644 --- a/crates/c-api/doxygen.conf.in +++ b/crates/c-api/doxygen.conf.in @@ -893,7 +893,8 @@ INPUT_ENCODING = UTF-8 # *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, # *.ucf, *.qsf and *.ice. -FILE_PATTERNS = *.h +FILE_PATTERNS = *.h \ + *.hh # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -924,7 +925,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = */wasm.hh # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -944,7 +945,11 @@ EXCLUDE_SYMBOLS = assertions \ WASMTIME_CONFIG_PROP \ WASMTIME_POOLING_ALLOCATION_CONFIG_PROP \ own \ - wasm_*_enum + wasm_*_enum \ + wasmtime::detail \ + CASE_KIND_PRINT_NAME \ + CASE_KIND_TO_C \ + CASE_C_TO_KIND # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include diff --git a/crates/c-api/include/wasmtime.hh b/crates/c-api/include/wasmtime.hh new file mode 100644 index 000000000000..09befd55a585 --- /dev/null +++ b/crates/c-api/include/wasmtime.hh @@ -0,0 +1,3374 @@ +/** + * This project is a C++ API for + * [Wasmtime](https://github.com/bytecodealliance/wasmtime). Support for the + * C++ API is exclusively built on the [C API of + * Wasmtime](https://docs.wasmtime.dev/c-api/), so the C++ support for this is + * simply a single header file. To use this header file, though, it must be + * combined with the header and binary of Wasmtime's C API. Note, though, that + * while this header is built on top of the `wasmtime.h` header file you should + * only need to use the contents of this header file to interact with Wasmtime. + * + * Examples can be [found + * online](https://github.com/bytecodealliance/wasmtime/tree/main/examples) + * and otherwise be sure to check out the + * [README](https://github.com/bytecodealliance/wasmtime/blob/main/crates/c-api/README.md) + * for simple usage instructions. Otherwise you can dive right in to the + * reference documentation of \ref wasmtime.hh + * + * \example hello.cc + * \example gcd.cc + * \example linking.cc + * \example memory.cc + * \example interrupt.cc + * \example externref.cc + */ + +/** + * \file wasmtime.hh + */ + +#ifndef WASMTIME_HH +#define WASMTIME_HH + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __has_include +#if __has_include() +#include +#endif +#endif + +#include "wasmtime.h" + +namespace wasmtime { + +#ifdef __cpp_lib_span + +/// \brief Alias to C++20 std::span when it is available +template +using Span = std::span; + +#else + +/// \brief Means number of elements determined at runtime +inline constexpr size_t dynamic_extent = + std::numeric_limits::max(); + +/** + * \brief Span class used when c++20 is not available + * @tparam T Type of data + * @tparam Extent Static size of data referred by Span class + */ +template class Span; + +/// \brief Check whether a type is `Span` +template struct IsSpan : std::false_type {}; + +template +struct IsSpan> : std::true_type {}; + +template class Span { + static_assert(Extent == dynamic_extent, + "The current implementation supports dynamic-extent span only"); + +public: + /// \brief Type used to iterate over this span (a raw pointer) + using iterator = T *; + + /// \brief Constructor of Span class + Span(T *t, std::size_t n) : ptr_{t}, size_{n} {} + + /// \brief Constructor of Span class for containers + template ::value && + std::is_pointer_v().data())> && + std::is_convertible_v< + std::remove_pointer_t< + decltype(std::declval().data())> (*)[], + T (*)[]> && + std::is_convertible_v().size()), + std::size_t>, + int> = 0> + Span(C &range) : ptr_{range.data()}, size_{range.size()} {} + + /// \brief Returns item by index + T &operator[](ptrdiff_t idx) const { + return ptr_[idx]; // NOLINT + } + + /// \brief Returns pointer to data + T *data() const { return ptr_; } + + /// \brief Returns number of data that referred by Span class + std::size_t size() const { return size_; } + + /// \brief Returns begin iterator + iterator begin() const { return ptr_; } + + /// \brief Returns end iterator + iterator end() const { + return ptr_ + size_; // NOLINT + } + + /// \brief Returns size in bytes + std::size_t size_bytes() const { return sizeof(T) * size_; } + +private: + T *ptr_; + std::size_t size_; +}; + +#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 + Auto = WASMTIME_STRATEGY_AUTO, + /// Requires Cranelift to be used for compilation + Cranelift = WASMTIME_STRATEGY_CRANELIFT, +}; + +/// \brief Values passed to `Config::cranelift_opt_level` +enum class OptLevel { + /// No extra optimizations performed + None = WASMTIME_OPT_LEVEL_NONE, + /// Optimize for speed + Speed = WASMTIME_OPT_LEVEL_SPEED, + /// Optimize for speed and generated code size + SpeedAndSize = WASMTIME_OPT_LEVEL_SPEED_AND_SIZE, +}; + +/// \brief Values passed to `Config::profiler` +enum class ProfilingStrategy { + /// No profiling enabled + None = WASMTIME_PROFILING_STRATEGY_NONE, + /// Profiling hooks via perf's jitdump + Jitdump = WASMTIME_PROFILING_STRATEGY_JITDUMP, + /// Profiling hooks via VTune + Vtune = WASMTIME_PROFILING_STRATEGY_VTUNE, +}; + +/** + * \brief Pool allocation configuration for Wasmtime. + * + * For more information be sure to consult the [rust + * documentation](https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html). + */ +class PoolAllocationConfig { + friend class Config; + + struct deleter { + void operator()(wasmtime_pooling_allocation_config_t *p) const { + wasmtime_pooling_allocation_config_delete(p); + } + }; + + std::unique_ptr ptr; + +public: + PoolAllocationConfig() : ptr(wasmtime_pooling_allocation_config_new()) {} + + /// \brief Configures the maximum number of “unused warm slots” to retain in + /// the pooling allocator. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.max_unused_warm_slots. + void max_unused_warm_slots(uint32_t max) { + wasmtime_pooling_allocation_config_max_unused_warm_slots_set(ptr.get(), + max); + } + + /// \brief The target number of decommits to do per batch. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.decommit_batch_size. + void decommit_batch_size(size_t batch_size) { + wasmtime_pooling_allocation_config_decommit_batch_size_set(ptr.get(), + batch_size); + } + + /// \brief How much memory, in bytes, to keep resident for async stacks + /// allocated with the pooling allocator. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.async_stack_keep_resident. + void async_stack_keep_resident(size_t size) { + wasmtime_pooling_allocation_config_async_stack_keep_resident_set(ptr.get(), + size); + } + + /// \brief How much memory, in bytes, to keep resident for each linear memory + /// after deallocation. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.linear_memory_keep_resident. + void linear_memory_keep_resident(size_t size) { + wasmtime_pooling_allocation_config_linear_memory_keep_resident_set( + ptr.get(), size); + } + + /// \brief How much memory, in bytes, to keep resident for each table after + /// deallocation. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.table_keep_resident. + void table_keep_resident(size_t size) { + wasmtime_pooling_allocation_config_table_keep_resident_set(ptr.get(), size); + } + + /// \brief The maximum number of concurrent component instances supported + /// (default is 1000). + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.total_component_instances. + void total_component_instances(uint32_t count) { + wasmtime_pooling_allocation_config_total_component_instances_set(ptr.get(), + count); + } + + /// \brief The maximum size, in bytes, allocated for a component instance’s + /// VMComponentContext metadata. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.max_component_instance_size. + void max_component_instance_size(size_t size) { + wasmtime_pooling_allocation_config_max_component_instance_size_set( + ptr.get(), size); + } + + /// \brief The maximum number of core instances a single component may contain + /// (default is unlimited). + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.max_core_instances_per_component. + void max_core_instances_per_component(uint32_t count) { + wasmtime_pooling_allocation_config_max_core_instances_per_component_set( + ptr.get(), count); + } + + /// \brief The maximum number of Wasm linear memories that a single component + /// may transitively contain (default is unlimited). + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.max_memories_per_component. + void max_memories_per_component(uint32_t count) { + wasmtime_pooling_allocation_config_max_memories_per_component_set(ptr.get(), + count); + } + + /// \brief The maximum number of tables that a single component may + /// transitively contain (default is unlimited). + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.max_tables_per_component. + void max_tables_per_component(uint32_t count) { + wasmtime_pooling_allocation_config_max_tables_per_component_set(ptr.get(), + count); + } + + /// \brief The maximum number of concurrent Wasm linear memories supported + /// (default is 1000). + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.total_memories. + void total_memories(uint32_t count) { + wasmtime_pooling_allocation_config_total_memories_set(ptr.get(), count); + } + + /// \brief The maximum number of concurrent tables supported (default is + /// 1000). + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.total_tables. + void total_tables(uint32_t count) { + wasmtime_pooling_allocation_config_total_tables_set(ptr.get(), count); + } + + /// \brief The maximum number of execution stacks allowed for asynchronous + /// execution, when enabled (default is 1000). + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.total_stacks. + void total_stacks(uint32_t count) { + wasmtime_pooling_allocation_config_total_stacks_set(ptr.get(), count); + } + + /// \brief The maximum number of concurrent core instances supported (default + /// is 1000). + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.total_core_instances. + void total_core_instances(uint32_t count) { + wasmtime_pooling_allocation_config_total_core_instances_set(ptr.get(), + count); + } + + /// \brief The maximum size, in bytes, allocated for a core instance’s + /// VMContext metadata. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.max_core_instance_size. + void max_core_instance_size(size_t size) { + wasmtime_pooling_allocation_config_max_core_instance_size_set(ptr.get(), + size); + } + + /// \brief The maximum number of defined tables for a core module (default is + /// 1). + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.max_tables_per_module. + void max_tables_per_module(uint32_t tables) { + wasmtime_pooling_allocation_config_max_tables_per_module_set(ptr.get(), + tables); + } + + /// \brief The maximum table elements for any table defined in a module + /// (default is 20000). + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.table_elements. + void table_elements(size_t elements) { + wasmtime_pooling_allocation_config_table_elements_set(ptr.get(), elements); + } + + /// \brief The maximum number of defined linear memories for a module (default + /// is 1). + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.max_memories_per_module. + void max_memories_per_module(uint32_t memories) { + wasmtime_pooling_allocation_config_max_memories_per_module_set(ptr.get(), + memories); + } + + /// \brief The maximum byte size that any WebAssembly linear memory may grow + /// to. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.max_memory_size. + void max_memory_size(size_t bytes) { + wasmtime_pooling_allocation_config_max_memory_size_set(ptr.get(), bytes); + } + + /// \brief The maximum number of concurrent GC heaps supported (default is + /// 1000). + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.PoolingAllocationConfig.html#method.total_gc_heaps. + void total_gc_heaps(uint32_t count) { + wasmtime_pooling_allocation_config_total_gc_heaps_set(ptr.get(), count); + } +}; + +/** + * \brief Configuration for Wasmtime. + * + * This class is used to configure Wasmtime's compilation and various other + * settings such as enabled WebAssembly proposals. + * + * For more information be sure to consult the [rust + * documentation](https://docs.wasmtime.dev/api/wasmtime/struct.Config.html). + */ +class Config { + friend class Engine; + + struct deleter { + void operator()(wasm_config_t *p) const { wasm_config_delete(p); } + }; + + std::unique_ptr ptr; + +public: + /// \brief Creates configuration with all the default settings. + Config() : ptr(wasm_config_new()) {} + + /// \brief Configures whether dwarf debuginfo is emitted for assisting + /// in-process debugging. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.debug_info + void debug_info(bool enable) { + wasmtime_config_debug_info_set(ptr.get(), enable); + } + + /// \brief Configures whether epochs are enabled which can be used to + /// interrupt currently executing WebAssembly. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.epoch_interruption + void epoch_interruption(bool enable) { + wasmtime_config_epoch_interruption_set(ptr.get(), enable); + } + + /// \brief Configures whether WebAssembly code will consume fuel and trap when + /// it runs out. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.consume_fuel + void consume_fuel(bool enable) { + wasmtime_config_consume_fuel_set(ptr.get(), enable); + } + + /// \brief Configures the maximum amount of native stack wasm can consume. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.max_wasm_stack + void max_wasm_stack(size_t stack) { + wasmtime_config_max_wasm_stack_set(ptr.get(), stack); + } + + /// \brief Configures whether the WebAssembly threads proposal is enabled + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.wasm_threads + void wasm_threads(bool enable) { + wasmtime_config_wasm_threads_set(ptr.get(), enable); + } + + /// \brief Configures whether the WebAssembly reference types proposal is + /// enabled + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.wasm_reference_types + void wasm_reference_types(bool enable) { + wasmtime_config_wasm_reference_types_set(ptr.get(), enable); + } + + /// \brief Configures whether the WebAssembly simd proposal is enabled + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.wasm_simd + void wasm_simd(bool enable) { + wasmtime_config_wasm_simd_set(ptr.get(), enable); + } + + /// \brief Configures whether the WebAssembly bulk memory proposal is enabled + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.wasm_bulk_memory + void wasm_bulk_memory(bool enable) { + wasmtime_config_wasm_bulk_memory_set(ptr.get(), enable); + } + + /// \brief Configures whether the WebAssembly multi value proposal is enabled + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.wasm_multi_value + void wasm_multi_value(bool enable) { + wasmtime_config_wasm_multi_value_set(ptr.get(), enable); + } + + /// \brief Configures whether the WebAssembly Garbage Collection proposal will be enabled + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.wasm_gc + void wasm_gc(bool enable) { + wasmtime_config_wasm_gc_set(ptr.get(), enable); + } + + /// \brief Configures whether the WebAssembly function references proposal will be enabled + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.wasm_function_references + void wasm_function_references(bool enable) { + wasmtime_config_wasm_function_references_set(ptr.get(), enable); + } + + /// \brief Configure whether wasmtime should compile a module using multiple threads. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.parallel_compilation + void parallel_compilation(bool enable) { + wasmtime_config_parallel_compilation_set(ptr.get(), enable); + } + + /// \brief Configures compilation strategy for wasm code. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.strategy + void strategy(Strategy strategy) { + wasmtime_config_strategy_set(ptr.get(), + static_cast(strategy)); + } + + /// \brief Configures whether cranelift's debug verifier is enabled + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.cranelift_debug_verifier + void cranelift_debug_verifier(bool enable) { + wasmtime_config_cranelift_debug_verifier_set(ptr.get(), enable); + } + + /// \brief Configures cranelift's optimization level + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.cranelift_opt_level + void cranelift_opt_level(OptLevel level) { + wasmtime_config_cranelift_opt_level_set( + ptr.get(), static_cast(level)); + } + + /// \brief Configures an active wasm profiler + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.profiler + void profiler(ProfilingStrategy profiler) { + wasmtime_config_profiler_set( + ptr.get(), static_cast(profiler)); + } + + /// \brief Configures the size of the initial linear memory allocation. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.memory_reservation + void memory_reservation(size_t size) { + wasmtime_config_memory_reservation_set(ptr.get(), size); + } + + /// \brief Configures the size of memory's guard region + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.memory_guard_size + void memory_guard_size(size_t size) { + wasmtime_config_memory_guard_size_set(ptr.get(), size); + } + + /// \brief Loads the default cache configuration present on the system. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.cache_config_load_default + Result cache_load_default() { + auto *error = wasmtime_config_cache_config_load(ptr.get(), nullptr); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } + + /// \brief Loads cache configuration from the specified filename. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.cache_config_load + Result cache_load(const std::string &path) { + auto *error = wasmtime_config_cache_config_load(ptr.get(), path.c_str()); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } + + /// \brief Enables and configures the pooling allocation strategy. + /// + /// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.allocation_strategy + void pooling_allocation_strategy(const PoolAllocationConfig &config) { + wasmtime_pooling_allocation_strategy_set(ptr.get(), config.ptr.get()); + } +}; + +/** + * \brief Global compilation state in Wasmtime. + * + * Created with either default configuration or with a specified instance of + * configuration, an `Engine` is used as an umbrella "session" for all other + * operations in Wasmtime. + */ +class Engine { + friend class Store; + friend class Module; + friend class Linker; + + struct deleter { + void operator()(wasm_engine_t *p) const { wasm_engine_delete(p); } + }; + + std::unique_ptr ptr; + +public: + /// \brief Creates an engine with default compilation settings. + Engine() : ptr(wasm_engine_new()) {} + /// \brief Creates an engine with the specified compilation settings. + explicit Engine(Config config) + : ptr(wasm_engine_new_with_config(config.ptr.release())) {} + + /// \brief Increments the current epoch which may result in interrupting + /// currently executing WebAssembly in connected stores if the epoch is now + /// beyond the configured threshold. + void increment_epoch() const { wasmtime_engine_increment_epoch(ptr.get()); } +}; + +/** + * \brief Converts the WebAssembly text format into the WebAssembly binary + * format. + * + * This will parse the text format and attempt to translate it to the binary + * format. Note that the text parser assumes that all WebAssembly features are + * enabled and will parse syntax of future proposals. The exact syntax here + * parsed may be tweaked over time. + * + * Returns either an error if parsing failed or the wasm binary. + */ +inline Result> wat2wasm(std::string_view wat) { + wasm_byte_vec_t ret; + auto *error = wasmtime_wat2wasm(wat.data(), wat.size(), &ret); + if (error != nullptr) { + return Error(error); + } + std::vector vec; + // NOLINTNEXTLINE TODO can this be done without triggering lints? + Span raw(reinterpret_cast(ret.data), ret.size); + vec.assign(raw.begin(), raw.end()); + wasm_byte_vec_delete(&ret); + return vec; +} + +/// Different kinds of types accepted by Wasmtime. +enum class ValKind { + /// WebAssembly's `i32` type + I32, + /// WebAssembly's `i64` type + I64, + /// WebAssembly's `f32` type + F32, + /// WebAssembly's `f64` type + F64, + /// WebAssembly's `v128` type from the simd proposal + V128, + /// WebAssembly's `externref` type from the reference types + ExternRef, + /// WebAssembly's `funcref` type from the reference types + FuncRef, +}; + +/// Helper X macro to construct statement for each enumerator in `ValKind`. +/// X(enumerator in `ValKind`, name string, enumerator in `wasm_valkind_t`) +#define WASMTIME_FOR_EACH_VAL_KIND(X) \ + X(I32, "i32", WASM_I32) \ + X(I64, "i64", WASM_I64) \ + X(F32, "f32", WASM_F32) \ + X(F64, "f64", WASM_F64) \ + X(ExternRef, "externref", WASM_EXTERNREF) \ + X(FuncRef, "funcref", WASM_FUNCREF) \ + X(V128, "v128", WASMTIME_V128) + +/// \brief Used to print a ValKind. +inline std::ostream &operator<<(std::ostream &os, const ValKind &e) { + switch (e) { +#define CASE_KIND_PRINT_NAME(kind, name, ignore) \ + case ValKind::kind: \ + os << name; \ + break; + WASMTIME_FOR_EACH_VAL_KIND(CASE_KIND_PRINT_NAME) +#undef CASE_KIND_PRINT_NAME + default: + abort(); + } + return os; +} + +/** + * \brief Type information about a WebAssembly value. + * + * Currently mostly just contains the `ValKind`. + */ +class ValType { + friend class TableType; + friend class GlobalType; + friend class FuncType; + + struct deleter { + void operator()(wasm_valtype_t *p) const { wasm_valtype_delete(p); } + }; + + std::unique_ptr ptr; + + static wasm_valkind_t kind_to_c(ValKind kind) { + switch (kind) { +#define CASE_KIND_TO_C(kind, ignore, ckind) \ + case ValKind::kind: \ + return ckind; + WASMTIME_FOR_EACH_VAL_KIND(CASE_KIND_TO_C) +#undef CASE_KIND_TO_C + default: + abort(); + } + } + +public: + /// \brief Non-owning reference to a `ValType`, must not be used after the + /// original `ValType` is deleted. + class Ref { + friend class ValType; + + const wasm_valtype_t *ptr; + + public: + /// \brief Instantiates from the raw C API representation. + Ref(const wasm_valtype_t *ptr) : ptr(ptr) {} + /// Copy constructor + Ref(const ValType &ty) : Ref(ty.ptr.get()) {} + + /// \brief Returns the corresponding "kind" for this type. + ValKind kind() const { + switch (wasm_valtype_kind(ptr)) { +#define CASE_C_TO_KIND(kind, ignore, ckind) \ + case ckind: \ + return ValKind::kind; + WASMTIME_FOR_EACH_VAL_KIND(CASE_C_TO_KIND) +#undef CASE_C_TO_KIND + } + std::abort(); + } + }; + + /// \brief Non-owning reference to a list of `ValType` instances. Must not be + /// used after the original owner is deleted. + class ListRef { + const wasm_valtype_vec_t *list; + + public: + /// Creates a list from the raw underlying C API. + ListRef(const wasm_valtype_vec_t *list) : list(list) {} + + /// This list iterates over a list of `ValType::Ref` instances. + typedef const Ref *iterator; + + /// Pointer to the beginning of iteration + iterator begin() const { + return reinterpret_cast(&list->data[0]); // NOLINT + } + + /// Pointer to the end of iteration + iterator end() const { + return reinterpret_cast(&list->data[list->size]); // NOLINT + } + + /// Returns how many types are in this list. + size_t size() const { return list->size; } + }; + +private: + Ref ref; + ValType(wasm_valtype_t *ptr) : ptr(ptr), ref(ptr) {} + +public: + /// Creates a new type from its kind. + ValType(ValKind kind) : ValType(wasm_valtype_new(kind_to_c(kind))) {} + /// Copies a `Ref` to a new owned value. + ValType(Ref other) : ValType(wasm_valtype_copy(other.ptr)) {} + /// Copies one type to a new one. + ValType(const ValType &other) : ValType(wasm_valtype_copy(other.ptr.get())) {} + /// Copies the contents of another type into this one. + ValType &operator=(const ValType &other) { + ptr.reset(wasm_valtype_copy(other.ptr.get())); + ref = other.ref; + return *this; + } + ~ValType() = default; + /// Moves the memory owned by another value type into this one. + ValType(ValType &&other) = default; + /// Moves the memory owned by another value type into this one. + ValType &operator=(ValType &&other) = default; + + /// \brief Returns the underlying `Ref`, a non-owning reference pointing to + /// this instance. + Ref *operator->() { return &ref; } + /// \brief Returns the underlying `Ref`, a non-owning reference pointing to + /// this instance. + Ref *operator*() { return &ref; } +}; + +/** + * \brief Type information about a WebAssembly linear memory + */ +class MemoryType { + friend class Memory; + + struct deleter { + void operator()(wasm_memorytype_t *p) const { wasm_memorytype_delete(p); } + }; + + std::unique_ptr ptr; + +public: + /// \brief Non-owning reference to a `MemoryType`, must not be used after the + /// original owner has been deleted. + class Ref { + friend class MemoryType; + + const wasm_memorytype_t *ptr; + + public: + /// Creates a reference from the raw C API representation. + Ref(const wasm_memorytype_t *ptr) : ptr(ptr) {} + /// Creates a reference from an original `MemoryType`. + Ref(const MemoryType &ty) : Ref(ty.ptr.get()) {} + + /// Returns the minimum size, in WebAssembly pages, of this memory. + uint64_t min() const { return wasmtime_memorytype_minimum(ptr); } + + /// Returns the maximum size, in WebAssembly pages, of this memory, if + /// specified. + std::optional max() const { + uint64_t max = 0; + auto present = wasmtime_memorytype_maximum(ptr, &max); + if (present) { + return max; + } + return std::nullopt; + } + + /// Returns whether or not this is a 64-bit memory type. + bool is_64() const { return wasmtime_memorytype_is64(ptr); } + + /// Returns whether or not this is a shared memory type. + bool is_shared() const { return wasmtime_memorytype_isshared(ptr); } + }; + +private: + Ref ref; + MemoryType(wasm_memorytype_t *ptr) : ptr(ptr), ref(ptr) {} + +public: + /// Creates a new 32-bit wasm memory type with the specified minimum number of + /// pages for the minimum size. The created type will have no maximum memory + /// size. + explicit MemoryType(uint32_t min) + : MemoryType(wasmtime_memorytype_new(min, false, 0, false, false)) {} + /// Creates a new 32-bit wasm memory type with the specified minimum number of + /// pages for the minimum size, and maximum number of pages for the max size. + MemoryType(uint32_t min, uint32_t max) + : MemoryType(wasmtime_memorytype_new(min, true, max, false, false)) {} + + /// Same as the `MemoryType` constructor, except creates a 64-bit memory. + static MemoryType New64(uint64_t min) { + return MemoryType(wasmtime_memorytype_new(min, false, 0, true, false)); + } + + /// Same as the `MemoryType` constructor, except creates a 64-bit memory. + static MemoryType New64(uint64_t min, uint64_t max) { + return MemoryType(wasmtime_memorytype_new(min, true, max, true, false)); + } + + /// Creates a new wasm memory type from the specified ref, making a fresh + /// owned value. + MemoryType(Ref other) : MemoryType(wasm_memorytype_copy(other.ptr)) {} + /// Copies the provided type into a new type. + MemoryType(const MemoryType &other) + : MemoryType(wasm_memorytype_copy(other.ptr.get())) {} + /// Copies the provided type into a new type. + MemoryType &operator=(const MemoryType &other) { + ptr.reset(wasm_memorytype_copy(other.ptr.get())); + return *this; + } + ~MemoryType() = default; + /// Moves the type information from another type into this one. + MemoryType(MemoryType &&other) = default; + /// Moves the type information from another type into this one. + MemoryType &operator=(MemoryType &&other) = default; + + /// \brief Returns the underlying `Ref`, a non-owning reference pointing to + /// this instance. + Ref *operator->() { return &ref; } + /// \brief Returns the underlying `Ref`, a non-owning reference pointing to + /// this instance. + Ref *operator*() { return &ref; } +}; + +/** + * \brief Type information about a WebAssembly table. + */ +class TableType { + friend class Table; + + struct deleter { + void operator()(wasm_tabletype_t *p) const { wasm_tabletype_delete(p); } + }; + + std::unique_ptr ptr; + +public: + /// Non-owning reference to a `TableType`, must not be used after the original + /// owner is deleted. + class Ref { + friend class TableType; + + const wasm_tabletype_t *ptr; + + public: + /// Creates a reference from the raw underlying C API representation. + Ref(const wasm_tabletype_t *ptr) : ptr(ptr) {} + /// Creates a reference to the provided `TableType`. + Ref(const TableType &ty) : Ref(ty.ptr.get()) {} + + /// Returns the minimum size of this table type, in elements. + uint32_t min() const { return wasm_tabletype_limits(ptr)->min; } + + /// Returns the maximum size of this table type, in elements, if present. + std::optional max() const { + const auto *limits = wasm_tabletype_limits(ptr); + if (limits->max == wasm_limits_max_default) { + return std::nullopt; + } + return limits->max; + } + + /// Returns the type of value that is stored in this table. + ValType::Ref element() const { return wasm_tabletype_element(ptr); } + }; + +private: + Ref ref; + TableType(wasm_tabletype_t *ptr) : ptr(ptr), ref(ptr) {} + +public: + /// Creates a new table type from the specified value type and minimum size. + /// The returned table will have no maximum size. + TableType(ValType ty, uint32_t min) : ptr(nullptr), ref(nullptr) { + wasm_limits_t limits; + limits.min = min; + limits.max = wasm_limits_max_default; + ptr.reset(wasm_tabletype_new(ty.ptr.release(), &limits)); + ref = ptr.get(); + } + /// Creates a new table type from the specified value type, minimum size, and + /// maximum size. + TableType(ValType ty, uint32_t min, uint32_t max) // NOLINT + : ptr(nullptr), ref(nullptr) { + wasm_limits_t limits; + limits.min = min; + limits.max = max; + ptr.reset(wasm_tabletype_new(ty.ptr.release(), &limits)); + ref = ptr.get(); + } + /// Clones the given reference into a new table type. + TableType(Ref other) : TableType(wasm_tabletype_copy(other.ptr)) {} + /// Copies another table type into this one. + TableType(const TableType &other) + : TableType(wasm_tabletype_copy(other.ptr.get())) {} + /// Copies another table type into this one. + TableType &operator=(const TableType &other) { + ptr.reset(wasm_tabletype_copy(other.ptr.get())); + return *this; + } + ~TableType() = default; + /// Moves the table type resources from another type to this one. + TableType(TableType &&other) = default; + /// Moves the table type resources from another type to this one. + TableType &operator=(TableType &&other) = default; + + /// \brief Returns the underlying `Ref`, a non-owning reference pointing to + /// this instance. + Ref *operator->() { return &ref; } + /// \brief Returns the underlying `Ref`, a non-owning reference pointing to + /// this instance. + Ref *operator*() { return &ref; } +}; + +/** + * \brief Type information about a WebAssembly global + */ +class GlobalType { + friend class Global; + + struct deleter { + void operator()(wasm_globaltype_t *p) const { wasm_globaltype_delete(p); } + }; + + std::unique_ptr ptr; + +public: + /// Non-owning reference to a `Global`, must not be used after the original + /// owner is deleted. + class Ref { + friend class GlobalType; + const wasm_globaltype_t *ptr; + + public: + /// Creates a new reference from the raw underlying C API representation. + Ref(const wasm_globaltype_t *ptr) : ptr(ptr) {} + /// Creates a new reference to the specified type. + Ref(const GlobalType &ty) : Ref(ty.ptr.get()) {} + + /// Returns whether or not this global type is mutable. + bool is_mutable() const { + return wasm_globaltype_mutability(ptr) == WASM_VAR; + } + + /// Returns the type of value stored within this global type. + ValType::Ref content() const { return wasm_globaltype_content(ptr); } + }; + +private: + Ref ref; + GlobalType(wasm_globaltype_t *ptr) : ptr(ptr), ref(ptr) {} + +public: + /// Creates a new global type from the specified value type and mutability. + GlobalType(ValType ty, bool mut) + : GlobalType(wasm_globaltype_new( + ty.ptr.release(), + (wasm_mutability_t)(mut ? WASM_VAR : WASM_CONST))) {} + /// Clones a reference into a uniquely owned global type. + GlobalType(Ref other) : GlobalType(wasm_globaltype_copy(other.ptr)) {} + /// Copies other type information into this one. + GlobalType(const GlobalType &other) + : GlobalType(wasm_globaltype_copy(other.ptr.get())) {} + /// Copies other type information into this one. + GlobalType &operator=(const GlobalType &other) { + ptr.reset(wasm_globaltype_copy(other.ptr.get())); + return *this; + } + ~GlobalType() = default; + /// Moves the underlying type information from another global into this one. + GlobalType(GlobalType &&other) = default; + /// Moves the underlying type information from another global into this one. + GlobalType &operator=(GlobalType &&other) = default; + + /// \brief Returns the underlying `Ref`, a non-owning reference pointing to + /// this instance. + Ref *operator->() { return &ref; } + /// \brief Returns the underlying `Ref`, a non-owning reference pointing to + /// this instance. + Ref *operator*() { return &ref; } +}; + +/** + * \brief Type information for a WebAssembly function. + */ +class FuncType { + friend class Func; + friend class Linker; + + struct deleter { + void operator()(wasm_functype_t *p) const { wasm_functype_delete(p); } + }; + + std::unique_ptr ptr; + +public: + /// Non-owning reference to a `FuncType`, must not be used after the original + /// owner has been deleted. + class Ref { + friend class FuncType; + const wasm_functype_t *ptr; + + public: + /// Creates a new reference from the underlying C API representation. + Ref(const wasm_functype_t *ptr) : ptr(ptr) {} + /// Creates a new reference to the given type. + Ref(const FuncType &ty) : Ref(ty.ptr.get()) {} + + /// Returns the list of types this function type takes as parameters. + ValType::ListRef params() const { return wasm_functype_params(ptr); } + + /// Returns the list of types this function type returns. + ValType::ListRef results() const { return wasm_functype_results(ptr); } + }; + +private: + Ref ref; + FuncType(wasm_functype_t *ptr) : ptr(ptr), ref(ptr) {} + +public: + /// Creates a new function type from the given list of parameters and results. + FuncType(std::initializer_list params, + std::initializer_list results) + : ref(nullptr) { + *this = FuncType::from_iters(params, results); + } + + /// Copies a reference into a uniquely owned function type. + FuncType(Ref other) : FuncType(wasm_functype_copy(other.ptr)) {} + /// Copies another type's information into this one. + FuncType(const FuncType &other) + : FuncType(wasm_functype_copy(other.ptr.get())) {} + /// Copies another type's information into this one. + FuncType &operator=(const FuncType &other) { + ptr.reset(wasm_functype_copy(other.ptr.get())); + return *this; + } + ~FuncType() = default; + /// Moves type information from another type into this one. + FuncType(FuncType &&other) = default; + /// Moves type information from another type into this one. + FuncType &operator=(FuncType &&other) = default; + + /// Creates a new function type from the given list of parameters and results. + template + static FuncType from_iters(P params, R results) { + wasm_valtype_vec_t param_vec; + wasm_valtype_vec_t result_vec; + wasm_valtype_vec_new_uninitialized(¶m_vec, params.size()); + wasm_valtype_vec_new_uninitialized(&result_vec, results.size()); + size_t i = 0; + + for (auto val : params) { + param_vec.data[i++] = val.ptr.release(); // NOLINT + } + i = 0; + for (auto val : results) { + result_vec.data[i++] = val.ptr.release(); // NOLINT + } + + return wasm_functype_new(¶m_vec, &result_vec); + } + + /// \brief Returns the underlying `Ref`, a non-owning reference pointing to + /// this instance. + Ref *operator->() { return &ref; } + /// \brief Returns the underlying `Ref`, a non-owning reference pointing to + /// this instance. + Ref *operator*() { return &ref; } +}; + +/** + * \brief Type information about a WebAssembly import. + */ +class ImportType { +public: + /// Non-owning reference to an `ImportType`, must not be used after the + /// original owner is deleted. + class Ref { + friend class ExternType; + + const wasm_importtype_t *ptr; + + // TODO: can this circle be broken another way? + const wasm_externtype_t *raw_type() { return wasm_importtype_type(ptr); } + + public: + /// Creates a new reference from the raw underlying C API representation. + Ref(const wasm_importtype_t *ptr) : ptr(ptr) {} + + /// Returns the module name associated with this import. + std::string_view module() { + const auto *name = wasm_importtype_module(ptr); + return std::string_view(name->data, name->size); + } + + /// Returns the field name associated with this import. + std::string_view name() { + const auto *name = wasm_importtype_name(ptr); + return std::string_view(name->data, name->size); + } + }; + + /// An owned list of `ImportType` instances. + class List { + friend class Module; + wasm_importtype_vec_t list; + + public: + /// Creates an empty list + List() : list{} { + list.size = 0; + list.data = nullptr; + } + List(const List &other) = delete; + /// Moves another list into this one. + List(List &&other) noexcept : list(other.list) { other.list.size = 0; } + ~List() { + if (list.size > 0) { + wasm_importtype_vec_delete(&list); + } + } + + List &operator=(const List &other) = delete; + /// Moves another list into this one. + List &operator=(List &&other) noexcept { + std::swap(list, other.list); + return *this; + } + + /// Iterator type, which is a list of non-owning `ImportType::Ref` + /// instances. + typedef const Ref *iterator; + /// Returns the start of iteration. + iterator begin() const { + return reinterpret_cast(&list.data[0]); // NOLINT + } + /// Returns the end of iteration. + iterator end() const { + return reinterpret_cast(&list.data[list.size]); // NOLINT + } + /// Returns the size of this list. + size_t size() const { return list.size; } + }; +}; + +/** + * \brief Type information about a WebAssembly export + */ +class ExportType { + +public: + /// \brief Non-owning reference to an `ExportType`. + /// + /// Note to get type information you can use `ExternType::from_export`. + class Ref { + friend class ExternType; + + const wasm_exporttype_t *ptr; + + const wasm_externtype_t *raw_type() { return wasm_exporttype_type(ptr); } + + public: + /// Creates a new reference from the raw underlying C API representation. + Ref(const wasm_exporttype_t *ptr) : ptr(ptr) {} + + /// Returns the name of this export. + std::string_view name() { + const auto *name = wasm_exporttype_name(ptr); + return std::string_view(name->data, name->size); + } + }; + + /// An owned list of `ExportType` instances. + class List { + friend class Module; + wasm_exporttype_vec_t list; + + public: + /// Creates an empty list + List() : list{} { + list.size = 0; + list.data = nullptr; + } + List(const List &other) = delete; + /// Moves another list into this one. + List(List &&other) noexcept : list(other.list) { other.list.size = 0; } + ~List() { + if (list.size > 0) { + wasm_exporttype_vec_delete(&list); + } + } + + List &operator=(const List &other) = delete; + /// Moves another list into this one. + List &operator=(List &&other) noexcept { + std::swap(list, other.list); + return *this; + } + + /// Iterator type, which is a list of non-owning `ExportType::Ref` + /// instances. + typedef const Ref *iterator; + /// Returns the start of iteration. + iterator begin() const { + return reinterpret_cast(&list.data[0]); // NOLINT + } + /// Returns the end of iteration. + iterator end() const { + return reinterpret_cast(&list.data[list.size]); // NOLINT + } + /// Returns the size of this list. + size_t size() const { return list.size; } + }; +}; + +/** + * \brief Generic type of a WebAssembly item. + */ +class ExternType { + friend class ExportType; + friend class ImportType; + +public: + /// \typedef Ref + /// \brief Non-owning reference to an item's type + /// + /// This cannot be used after the original owner has been deleted, and + /// otherwise this is used to determine what the actual type of the outer item + /// is. + typedef std::variant + Ref; + + /// Extract the type of the item imported by the provided type. + static Ref from_import(ImportType::Ref ty) { + // TODO: this would ideally be some sort of implicit constructor, unsure how + // to do that though... + return ref_from_c(ty.raw_type()); + } + + /// Extract the type of the item exported by the provided type. + static Ref from_export(ExportType::Ref ty) { + // TODO: this would ideally be some sort of implicit constructor, unsure how + // to do that though... + return ref_from_c(ty.raw_type()); + } + +private: + static Ref ref_from_c(const wasm_externtype_t *ptr) { + switch (wasm_externtype_kind(ptr)) { + case WASM_EXTERN_FUNC: + return wasm_externtype_as_functype_const(ptr); + case WASM_EXTERN_GLOBAL: + return wasm_externtype_as_globaltype_const(ptr); + case WASM_EXTERN_TABLE: + return wasm_externtype_as_tabletype_const(ptr); + case WASM_EXTERN_MEMORY: + return wasm_externtype_as_memorytype_const(ptr); + } + std::abort(); + } +}; + +/** + * \brief Non-owning reference to a WebAssembly function frame as part of a + * `Trace` + * + * A `FrameRef` represents a WebAssembly function frame on the stack which was + * collected as part of a trap. + */ +class FrameRef { + wasm_frame_t *frame; + +public: + /// Returns the WebAssembly function index of this function, in the original + /// module. + uint32_t func_index() const { return wasm_frame_func_index(frame); } + /// Returns the offset, in bytes from the start of the function in the + /// original module, to this frame's program counter. + size_t func_offset() const { return wasm_frame_func_offset(frame); } + /// Returns the offset, in bytes from the start of the original module, + /// to this frame's program counter. + size_t module_offset() const { return wasm_frame_module_offset(frame); } + + /// Returns the name, if present, associated with this function. + /// + /// Note that this requires that the `name` section is present in the original + /// WebAssembly binary. + std::optional func_name() const { + const auto *name = wasmtime_frame_func_name(frame); + if (name != nullptr) { + return std::string_view(name->data, name->size); + } + return std::nullopt; + } + + /// Returns the name, if present, associated with this function's module. + /// + /// Note that this requires that the `name` section is present in the original + /// WebAssembly binary. + std::optional module_name() const { + const auto *name = wasmtime_frame_module_name(frame); + if (name != nullptr) { + return std::string_view(name->data, name->size); + } + return std::nullopt; + } +}; + +/** + * \brief An owned vector of `FrameRef` instances representing the WebAssembly + * call-stack on a trap. + * + * This can be used to iterate over the frames of a trap and determine what was + * running when a trap happened. + */ +class Trace { + friend class Trap; + friend class Error; + + wasm_frame_vec_t vec; + + Trace(wasm_frame_vec_t vec) : vec(vec) {} + +public: + ~Trace() { wasm_frame_vec_delete(&vec); } + + Trace(const Trace &other) = delete; + Trace(Trace &&other) = delete; + Trace &operator=(const Trace &other) = delete; + Trace &operator=(Trace &&other) = delete; + + /// Iterator used to iterate over this trace. + typedef const FrameRef *iterator; + + /// Returns the start of iteration + iterator begin() const { + return reinterpret_cast(&vec.data[0]); // NOLINT + } + /// Returns the end of iteration + iterator end() const { + return reinterpret_cast(&vec.data[vec.size]); // NOLINT + } + /// Returns the size of this trace, or how many frames it contains. + size_t size() const { return vec.size; } +}; + +inline Trace Error::trace() const { + wasm_frame_vec_t frames; + wasmtime_error_wasm_trace(ptr.get(), &frames); + return Trace(frames); +} + +/** + * \brief Information about a WebAssembly trap. + * + * Traps can happen during normal wasm execution (such as the `unreachable` + * instruction) but they can also happen in host-provided functions to a host + * function can simulate raising a trap. + * + * Traps have a message associated with them as well as a trace of WebAssembly + * frames on the stack. + */ +class Trap { + friend class Linker; + friend class Instance; + friend class Func; + template friend class TypedFunc; + + struct deleter { + void operator()(wasm_trap_t *p) const { wasm_trap_delete(p); } + }; + + std::unique_ptr ptr; + + Trap(wasm_trap_t *ptr) : ptr(ptr) {} + +public: + /// Creates a new host-defined trap with the specified message. + explicit Trap(std::string_view msg) + : Trap(wasmtime_trap_new(msg.data(), msg.size())) {} + + /// Returns the descriptive message associated with this trap + std::string message() const { + wasm_byte_vec_t msg; + wasm_trap_message(ptr.get(), &msg); + std::string ret(msg.data, msg.size - 1); + wasm_byte_vec_delete(&msg); + return ret; + } + + /// Returns the trace of WebAssembly frames associated with this trap. + /// + /// Note that the `trace` cannot outlive this error object. + Trace trace() const { + wasm_frame_vec_t frames; + wasm_trap_trace(ptr.get(), &frames); + return Trace(frames); + } +}; + +/// Structure used to represent either a `Trap` or an `Error`. +struct TrapError { + /// Storage for what this trap represents. + std::variant data; + + /// Creates a new `TrapError` from a `Trap` + TrapError(Trap t) : data(std::move(t)) {} + /// Creates a new `TrapError` from an `Error` + TrapError(Error e) : data(std::move(e)) {} + + /// Dispatches internally to return the message associated with this error. + std::string message() const { + if (const auto *trap = std::get_if(&data)) { + return trap->message(); + } + if (const auto *error = std::get_if(&data)) { + return std::string(error->message()); + } + std::abort(); + } +}; + +/// Result used by functions which can fail because of invariants being violated +/// (such as a type error) as well as because of a WebAssembly trap. +template using TrapResult = Result; + +/** + * \brief Representation of a compiled WebAssembly module. + * + * This type contains JIT code of a compiled WebAssembly module. A `Module` is + * connected to an `Engine` and can only be instantiated within that `Engine`. + * You can inspect a `Module` for its type information. This is passed as an + * argument to other APIs to instantiate it. + */ +class Module { + friend class Store; + friend class Instance; + friend class Linker; + + struct deleter { + void operator()(wasmtime_module_t *p) const { wasmtime_module_delete(p); } + }; + + std::unique_ptr ptr; + + Module(wasmtime_module_t *raw) : ptr(raw) {} + +public: + /// Copies another module into this one. + Module(const Module &other) : ptr(wasmtime_module_clone(other.ptr.get())) {} + /// Copies another module into this one. + Module &operator=(const Module &other) { + ptr.reset(wasmtime_module_clone(other.ptr.get())); + return *this; + } + ~Module() = default; + /// Moves resources from another module into this one. + Module(Module &&other) = default; + /// Moves resources from another module into this one. + Module &operator=(Module &&other) = default; + + /** + * \brief Compiles a module from the WebAssembly text format. + * + * This function will automatically use `wat2wasm` on the input and then + * delegate to the #compile function. + */ + static Result compile(Engine &engine, std::string_view wat) { + auto wasm = wat2wasm(wat); + if (!wasm) { + return wasm.err(); + } + auto bytes = wasm.ok(); + return compile(engine, bytes); + } + + /** + * \brief Compiles a module from the WebAssembly binary format. + * + * This function compiles the provided WebAssembly binary specified by `wasm` + * within the compilation settings configured by `engine`. This method is + * synchronous and will not return until the module has finished compiling. + * + * This function can fail if the WebAssembly binary is invalid or doesn't + * validate (or similar). + */ + static Result compile(Engine &engine, Span wasm) { + wasmtime_module_t *ret = nullptr; + auto *error = + wasmtime_module_new(engine.ptr.get(), wasm.data(), wasm.size(), &ret); + if (error != nullptr) { + return Error(error); + } + return Module(ret); + } + + /** + * \brief Validates the provided WebAssembly binary without compiling it. + * + * This function will validate whether the provided binary is indeed valid + * within the compilation settings of the `engine` provided. + */ + static Result validate(Engine &engine, Span wasm) { + auto *error = + wasmtime_module_validate(engine.ptr.get(), wasm.data(), wasm.size()); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } + + /** + * \brief Deserializes a previous list of bytes created with `serialize`. + * + * This function is intended to be much faster than `compile` where it uses + * the artifacts of a previous compilation to quickly create an in-memory + * module ready for instantiation. + * + * It is not safe to pass arbitrary input to this function, it is only safe to + * pass in output from previous calls to `serialize`. For more information see + * the Rust documentation - + * https://docs.wasmtime.dev/api/wasmtime/struct.Module.html#method.deserialize + */ + static Result deserialize(Engine &engine, Span wasm) { + wasmtime_module_t *ret = nullptr; + auto *error = wasmtime_module_deserialize(engine.ptr.get(), wasm.data(), + wasm.size(), &ret); + if (error != nullptr) { + return Error(error); + } + return Module(ret); + } + + /** + * \brief Deserializes a module from an on-disk file. + * + * This function is the same as `deserialize` except that it reads the data + * for the serialized module from the path on disk. This can be faster than + * the alternative which may require copying the data around. + * + * It is not safe to pass arbitrary input to this function, it is only safe to + * pass in output from previous calls to `serialize`. For more information see + * the Rust documentation - + * https://docs.wasmtime.dev/api/wasmtime/struct.Module.html#method.deserialize + */ + static Result deserialize_file(Engine &engine, + const std::string &path) { + wasmtime_module_t *ret = nullptr; + auto *error = + wasmtime_module_deserialize_file(engine.ptr.get(), path.c_str(), &ret); + if (error != nullptr) { + return Error(error); + } + return Module(ret); + } + + /// Returns the list of types imported by this module. + ImportType::List imports() const { + ImportType::List list; + wasmtime_module_imports(ptr.get(), &list.list); + return list; + } + + /// Returns the list of types exported by this module. + ExportType::List exports() const { + ExportType::List list; + wasmtime_module_exports(ptr.get(), &list.list); + return list; + } + + /** + * \brief Serializes this module to a list of bytes. + * + * The returned bytes can then be used to later pass to `deserialize` to + * quickly recreate this module in a different process perhaps. + */ + Result> serialize() const { + wasm_byte_vec_t bytes; + auto *error = wasmtime_module_serialize(ptr.get(), &bytes); + if (error != nullptr) { + return Error(error); + } + std::vector ret; + // NOLINTNEXTLINE TODO can this be done without triggering lints? + Span raw(reinterpret_cast(bytes.data), bytes.size); + ret.assign(raw.begin(), raw.end()); + wasm_byte_vec_delete(&bytes); + return ret; + } +}; + +/** + * \brief Configuration for an instance of WASI. + * + * This is inserted into a store with `Store::Context::set_wasi`. + */ +class WasiConfig { + friend class Store; + + struct deleter { + void operator()(wasi_config_t *p) const { wasi_config_delete(p); } + }; + + std::unique_ptr ptr; + +public: + /// Creates a new configuration object with default settings. + WasiConfig() : ptr(wasi_config_new()) {} + + /// Configures the argv explicitly with the given string array. + void argv(const std::vector &args) { + std::vector ptrs; + ptrs.reserve(args.size()); + for (const auto &arg : args) { + ptrs.push_back(arg.c_str()); + } + + wasi_config_set_argv(ptr.get(), (int)args.size(), ptrs.data()); + } + + /// Configures the argv for wasm to be inherited from this process itself. + void inherit_argv() { wasi_config_inherit_argv(ptr.get()); } + + /// Configures the environment variables available to wasm, specified here as + /// a list of pairs where the first element of the pair is the key and the + /// second element is the value. + void env(const std::vector> &env) { + std::vector names; + std::vector values; + for (const auto &[name, value] : env) { + names.push_back(name.c_str()); + values.push_back(value.c_str()); + } + wasi_config_set_env(ptr.get(), (int)env.size(), names.data(), + values.data()); + } + + /// Indicates that the entire environment of this process should be inherited + /// by the wasi configuration. + void inherit_env() { wasi_config_inherit_env(ptr.get()); } + + /// Configures the provided file to be used for the stdin of this WASI + /// configuration. + [[nodiscard]] bool stdin_file(const std::string &path) { + return wasi_config_set_stdin_file(ptr.get(), path.c_str()); + } + + /// Configures this WASI configuration to inherit its stdin from the host + /// process. + void inherit_stdin() { return wasi_config_inherit_stdin(ptr.get()); } + + /// Configures the provided file to be created and all stdout output will be + /// written there. + [[nodiscard]] bool stdout_file(const std::string &path) { + return wasi_config_set_stdout_file(ptr.get(), path.c_str()); + } + + /// Configures this WASI configuration to inherit its stdout from the host + /// process. + void inherit_stdout() { return wasi_config_inherit_stdout(ptr.get()); } + + /// Configures the provided file to be created and all stderr output will be + /// written there. + [[nodiscard]] bool stderr_file(const std::string &path) { + return wasi_config_set_stderr_file(ptr.get(), path.c_str()); + } + + /// Configures this WASI configuration to inherit its stdout from the host + /// process. + void inherit_stderr() { return wasi_config_inherit_stderr(ptr.get()); } + + /// Opens `path` to be opened as `guest_path` in the WASI pseudo-filesystem. + [[nodiscard]] bool preopen_dir(const std::string &path, + const std::string &guest_path, + size_t dir_perms, + size_t file_perms) { + return wasi_config_preopen_dir(ptr.get(), path.c_str(), guest_path.c_str(), dir_perms, file_perms); + } +}; + +class Caller; +template class TypedFunc; + +/** + * \brief Owner of all WebAssembly objects + * + * A `Store` owns all WebAssembly objects such as instances, globals, functions, + * memories, etc. A `Store` is one of the main central points about working with + * WebAssembly since it's an argument to almost all APIs. The `Store` serves as + * a form of "context" to give meaning to the pointers of `Func` and friends. + * + * A `Store` can be sent between threads but it cannot generally be shared + * concurrently between threads. Memory associated with WebAssembly instances + * will be deallocated when the `Store` is deallocated. + */ +class Store { + struct deleter { + void operator()(wasmtime_store_t *p) const { wasmtime_store_delete(p); } + }; + + std::unique_ptr ptr; + + static void finalizer(void *ptr) { + std::unique_ptr _ptr(static_cast(ptr)); + } + +public: + /// Creates a new `Store` within the provided `Engine`. + explicit Store(Engine &engine) + : ptr(wasmtime_store_new(engine.ptr.get(), nullptr, finalizer)) {} + + /** + * \brief An interior pointer into a `Store`. + * + * A `Context` object is created from either a `Store` or a `Caller`. It is an + * interior pointer into a `Store` and cannot be used outside the lifetime of + * the original object it was created from. + * + * This object is an argument to most APIs in Wasmtime but typically doesn't + * need to be constructed explicitly since it can be created from a `Store&` + * or a `Caller&`. + */ + class Context { + friend class Global; + friend class Table; + friend class Memory; + friend class Func; + friend class Instance; + friend class Linker; + friend class ExternRef; + friend class Val; + wasmtime_context_t *ptr; + + Context(wasmtime_context_t *ptr) : ptr(ptr) {} + + public: + /// Creates a context referencing the provided `Store`. + Context(Store &store) : Context(wasmtime_store_context(store.ptr.get())) {} + /// Creates a context referencing the provided `Store`. + Context(Store *store) : Context(*store) {} + /// Creates a context referencing the provided `Caller`. + Context(Caller &caller); + /// Creates a context referencing the provided `Caller`. + Context(Caller *caller); + + /// Runs a garbage collection pass in the referenced store to collect loose + /// `externref` values, if any are available. + void gc() { wasmtime_context_gc(ptr); } + + /// Injects fuel to be consumed within this store. + /// + /// Stores start with 0 fuel and if `Config::consume_fuel` is enabled then + /// this is required if you want to let WebAssembly actually execute. + /// + /// Returns an error if fuel consumption isn't enabled. + Result set_fuel(uint64_t fuel) { + auto *error = wasmtime_context_set_fuel(ptr, fuel); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } + + /// Returns the amount of fuel consumed so far by executing WebAssembly. + /// + /// Returns `std::nullopt` if fuel consumption is not enabled. + Result get_fuel() const { + uint64_t fuel = 0; + auto *error = wasmtime_context_get_fuel(ptr, &fuel); + if (error != nullptr) { + return Error(error); + } + return fuel; + } + + /// Set user specified data associated with this store. + void set_data(std::any data) const { + finalizer(static_cast(wasmtime_context_get_data(ptr))); + wasmtime_context_set_data( + ptr, std::make_unique(std::move(data)).release()); + } + + /// Get user specified data associated with this store. + std::any &get_data() const { + return *static_cast(wasmtime_context_get_data(ptr)); + } + + /// Configures the WASI state used by this store. + /// + /// This will only have an effect if used in conjunction with + /// `Linker::define_wasi` because otherwise no host functions will use the + /// WASI state. + Result set_wasi(WasiConfig config) { + auto *error = wasmtime_context_set_wasi(ptr, config.ptr.release()); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } + + /// Configures this store's epoch deadline to be the specified number of + /// ticks beyond the engine's current epoch. + /// + /// By default the deadline is the current engine's epoch, immediately + /// interrupting code if epoch interruption is enabled. This must be called + /// to extend the deadline to allow interruption. + void set_epoch_deadline(uint64_t ticks_beyond_current) { + wasmtime_context_set_epoch_deadline(ptr, ticks_beyond_current); + } + + /// Returns the raw context pointer for the C API. + wasmtime_context_t *raw_context() { return ptr; } + }; + + /// \brief Provides limits for a store. Used by hosts to limit resource + /// consumption of instances. Use negative value to keep the default value + /// for the limit. + /// + /// \param memory_size the maximum number of bytes a linear memory can grow + /// to. Growing a linear memory beyond this limit will fail. By default, + /// linear memory will not be limited. + /// + /// \param table_elements the maximum number of elements in a table. + /// Growing a table beyond this limit will fail. By default, table elements + /// will not be limited. + /// + /// \param instances the maximum number of instances that can be created + /// for a Store. Module instantiation will fail if this limit is exceeded. + /// This value defaults to 10,000. + /// + /// \param tables the maximum number of tables that can be created for a + /// Store. Module instantiation will fail if this limit is exceeded. This + /// value defaults to 10,000. + /// + /// \param memories the maximum number of linear + /// memories that can be created for a Store. Instantiation will fail with an + /// error if this limit is exceeded. This value defaults to 10,000. + /// + /// Use any negative value for the parameters that should be kept on + /// the default values. + /// + /// Note that the limits are only used to limit the creation/growth of + /// resources in the future, this does not retroactively attempt to apply + /// limits to the store. + void limiter(int64_t memory_size, int64_t table_elements, int64_t instances, + int64_t tables, int64_t memories) { + wasmtime_store_limiter(ptr.get(), memory_size, table_elements, instances, + tables, memories); + } + + /// Explicit function to acquire a `Context` from this store. + Context context() { return this; } +}; + +/** + * \brief Representation of a WebAssembly `externref` value. + * + * This class represents an value that cannot be forged by WebAssembly itself. + * All `ExternRef` values are guaranteed to be created by the host and its + * embedding. It's suitable to place private data structures in here which + * WebAssembly will not have access to, only other host functions will have + * access to them. + * + * Note that `ExternRef` values are rooted within a `Store` and must be manually + * unrooted via the `unroot` function. If this is not used then values will + * never be candidates for garbage collection. + */ +class ExternRef { + friend class Val; + + wasmtime_externref_t val; + + static void finalizer(void *ptr) { + std::unique_ptr _ptr(static_cast(ptr)); + } + +public: + /// Creates a new `ExternRef` directly from its C-API representation. + explicit ExternRef(wasmtime_externref_t val) : val(val) {} + + /// Creates a new `externref` value from the provided argument. + /// + /// Note that `val` should be safe to send across threads and should own any + /// memory that it points to. Also note that `ExternRef` is similar to a + /// `std::shared_ptr` in that there can be many references to the same value. + template explicit ExternRef(Store::Context cx, T val) { + void *ptr = std::make_unique(std::move(val)).release(); + bool ok = wasmtime_externref_new(cx.ptr, ptr, finalizer, &this->val); + if (!ok) { + fprintf(stderr, "failed to allocate a new externref"); + abort(); + } + } + + /// Creates a new `ExternRef` which is separately rooted from this one. + ExternRef clone(Store::Context cx) { + wasmtime_externref_t other; + wasmtime_externref_clone(cx.ptr, &val, &other); + return ExternRef(other); + } + + /// Returns the underlying host data associated with this `ExternRef`. + std::any &data(Store::Context cx) { + return *static_cast(wasmtime_externref_data(cx.ptr, &val)); + } + + /// Unroots this value from the context provided, enabling a future GC to + /// collect the internal object if there are no more references. + void unroot(Store::Context cx) { + wasmtime_externref_unroot(cx.ptr, &val); + } + + /// Returns the raw underlying C API value. + /// + /// This class still retains ownership of the pointer. + const wasmtime_externref_t *raw() const { return &val; } +}; + +class Func; +class Global; +class Instance; +class Memory; +class Table; + +/// \typedef Extern +/// \brief Representation of an external WebAssembly item +typedef std::variant Extern; + +/// \brief Container for the `v128` WebAssembly type. +struct V128 { + /// \brief The little-endian bytes of the `v128` value. + wasmtime_v128 v128; + + /// \brief Creates a new zero-value `v128`. + V128() : v128{} { memset(&v128[0], 0, sizeof(wasmtime_v128)); } + + /// \brief Creates a new `V128` from its C API representation. + V128(const wasmtime_v128 &v) : v128{} { + memcpy(&v128[0], &v[0], sizeof(wasmtime_v128)); + } +}; + +/** + * \brief Representation of a generic WebAssembly value. + * + * This is roughly equivalent to a tagged union of all possible WebAssembly + * values. This is later used as an argument with functions, globals, tables, + * etc. + * + * Note that a `Val` can represent owned GC pointers. In this case the `unroot` + * method must be used to ensure that they can later be garbage-collected. + */ +class Val { + friend class Global; + friend class Table; + friend class Func; + + wasmtime_val_t val; + + Val() : val{} { + val.kind = WASMTIME_I32; + val.of.i32 = 0; + } + Val(wasmtime_val_t val) : val(val) {} + +public: + /// Creates a new `i32` WebAssembly value. + Val(int32_t i32) : val{} { + val.kind = WASMTIME_I32; + val.of.i32 = i32; + } + /// Creates a new `i64` WebAssembly value. + Val(int64_t i64) : val{} { + val.kind = WASMTIME_I64; + val.of.i64 = i64; + } + /// Creates a new `f32` WebAssembly value. + Val(float f32) : val{} { + val.kind = WASMTIME_F32; + val.of.f32 = f32; + } + /// Creates a new `f64` WebAssembly value. + Val(double f64) : val{} { + val.kind = WASMTIME_F64; + val.of.f64 = f64; + } + /// Creates a new `v128` WebAssembly value. + Val(const V128 &v128) : val{} { + val.kind = WASMTIME_V128; + memcpy(&val.of.v128[0], &v128.v128[0], sizeof(wasmtime_v128)); + } + /// Creates a new `funcref` WebAssembly value. + Val(std::optional func); + /// Creates a new `funcref` WebAssembly value which is not `ref.null func`. + Val(Func func); + /// Creates a new `externref` value. + Val(std::optional ptr) : val{} { + val.kind = WASMTIME_EXTERNREF; + if (ptr) { + val.of.externref = ptr->val; + } else { + wasmtime_externref_set_null(&val.of.externref); + } + } + /// Creates a new `externref` WebAssembly value which is not `ref.null + /// extern`. + Val(ExternRef ptr); + + /// Returns the kind of value that this value has. + ValKind kind() const { + switch (val.kind) { + case WASMTIME_I32: + return ValKind::I32; + case WASMTIME_I64: + return ValKind::I64; + case WASMTIME_F32: + return ValKind::F32; + case WASMTIME_F64: + return ValKind::F64; + case WASMTIME_FUNCREF: + return ValKind::FuncRef; + case WASMTIME_EXTERNREF: + return ValKind::ExternRef; + case WASMTIME_V128: + return ValKind::V128; + } + std::abort(); + } + + /// Returns the underlying `i32`, requires `kind() == KindI32` or aborts the + /// process. + int32_t i32() const { + if (val.kind != WASMTIME_I32) { + std::abort(); + } + return val.of.i32; + } + + /// Returns the underlying `i64`, requires `kind() == KindI64` or aborts the + /// process. + int64_t i64() const { + if (val.kind != WASMTIME_I64) { + std::abort(); + } + return val.of.i64; + } + + /// Returns the underlying `f32`, requires `kind() == KindF32` or aborts the + /// process. + float f32() const { + if (val.kind != WASMTIME_F32) { + std::abort(); + } + return val.of.f32; + } + + /// Returns the underlying `f64`, requires `kind() == KindF64` or aborts the + /// process. + double f64() const { + if (val.kind != WASMTIME_F64) { + std::abort(); + } + return val.of.f64; + } + + /// Returns the underlying `v128`, requires `kind() == KindV128` or aborts + /// the process. + V128 v128() const { + if (val.kind != WASMTIME_V128) { + std::abort(); + } + return val.of.v128; + } + + /// Returns the underlying `externref`, requires `kind() == KindExternRef` or + /// aborts the process. + /// + /// Note that `externref` is a nullable reference, hence the `optional` return + /// value. + std::optional externref(Store::Context cx) const { + if (val.kind != WASMTIME_EXTERNREF) { + std::abort(); + } + if (val.of.externref.store_id == 0) { + return std::nullopt; + } + wasmtime_externref_t other; + wasmtime_externref_clone(cx.ptr, &val.of.externref, &other); + return ExternRef(other); + } + + /// Returns the underlying `funcref`, requires `kind() == KindFuncRef` or + /// aborts the process. + /// + /// Note that `funcref` is a nullable reference, hence the `optional` return + /// value. + std::optional funcref() const; + + /// Unroots any GC references this `Val` points to within the `cx` provided. + void unroot(Store::Context cx) { + wasmtime_val_unroot(cx.ptr, &val); + } +}; + +/** + * \brief Structure provided to host functions to lookup caller information or + * acquire a `Store::Context`. + * + * This structure is passed to all host functions created with `Func`. It can be + * used to create a `Store::Context`. + */ +class Caller { + friend class Func; + friend class Store; + wasmtime_caller_t *ptr; + Caller(wasmtime_caller_t *ptr) : ptr(ptr) {} + +public: + /// Attempts to load an exported item from the calling instance. + /// + /// For more information see the Rust documentation - + /// https://docs.wasmtime.dev/api/wasmtime/struct.Caller.html#method.get_export + std::optional get_export(std::string_view name); + + /// Explicitly acquire a `Store::Context` from this `Caller`. + Store::Context context() { return this; } +}; + +inline Store::Context::Context(Caller &caller) + : Context(wasmtime_caller_context(caller.ptr)) {} +inline Store::Context::Context(Caller *caller) : Context(*caller) {} + +namespace detail { + +/// A "trait" for native types that correspond to WebAssembly types for use with +/// `Func::wrap` and `TypedFunc::call` +template struct WasmType { static const bool valid = false; }; + +/// Helper macro to define `WasmType` definitions for primitive types like +/// int32_t and such. +// NOLINTNEXTLINE +#define NATIVE_WASM_TYPE(native, valkind, field) \ + template <> struct WasmType { \ + static const bool valid = true; \ + static const ValKind kind = ValKind::valkind; \ + static void store(Store::Context cx, wasmtime_val_raw_t *p, \ + const native &t) { \ + p->field = t; \ + } \ + static native load(Store::Context cx, wasmtime_val_raw_t *p) { \ + return p->field; \ + } \ + }; + +NATIVE_WASM_TYPE(int32_t, I32, i32) +NATIVE_WASM_TYPE(uint32_t, I32, i32) +NATIVE_WASM_TYPE(int64_t, I64, i64) +NATIVE_WASM_TYPE(uint64_t, I64, i64) +NATIVE_WASM_TYPE(float, F32, f32) +NATIVE_WASM_TYPE(double, F64, f64) + +#undef NATIVE_WASM_TYPE + +/// Type information for `externref`, represented on the host as an optional +/// `ExternRef`. +template <> struct WasmType> { + static const bool valid = true; + static const ValKind kind = ValKind::ExternRef; + static void store(Store::Context cx, wasmtime_val_raw_t *p, + const std::optional &ref) { + if (ref) { + p->externref = wasmtime_externref_to_raw(cx.raw_context(), ref->raw()); + } else { + p->externref = 0; + } + } + static std::optional load(Store::Context cx, + wasmtime_val_raw_t *p) { + if (p->externref == 0) { + return std::nullopt; + } + wasmtime_externref_t val; + wasmtime_externref_from_raw(cx.raw_context(), p->externref, &val); + return ExternRef(val); + } +}; + +/// Type information for the `V128` host value used as a wasm value. +template <> struct WasmType { + static const bool valid = true; + static const ValKind kind = ValKind::V128; + static void store(Store::Context cx, wasmtime_val_raw_t *p, const V128 &t) { + memcpy(&p->v128[0], &t.v128[0], sizeof(wasmtime_v128)); + } + static V128 load(Store::Context cx, wasmtime_val_raw_t *p) { return p->v128; } +}; + +/// A "trait" for a list of types and operations on them, used for `Func::wrap` +/// and `TypedFunc::call` +/// +/// The base case is a single type which is a list of one element. +template struct WasmTypeList { + static const bool valid = WasmType::valid; + static const size_t size = 1; + static bool matches(ValType::ListRef types) { + return WasmTypeList>::matches(types); + } + static void store(Store::Context cx, wasmtime_val_raw_t *storage, + const T &t) { + WasmType::store(cx, storage, t); + } + static T load(Store::Context cx, wasmtime_val_raw_t *storage) { + return WasmType::load(cx, storage); + } + static std::vector types() { return {WasmType::kind}; } +}; + +/// std::monostate translates to an empty list of types. +template <> struct WasmTypeList { + static const bool valid = true; + static const size_t size = 0; + static bool matches(ValType::ListRef types) { return types.size() == 0; } + static void store(Store::Context cx, wasmtime_val_raw_t *storage, + const std::monostate &t) {} + static std::monostate load(Store::Context cx, wasmtime_val_raw_t *storage) { + return std::monostate(); + } + static std::vector types() { return {}; } +}; + +/// std::tuple<> translates to the corresponding list of types +template struct WasmTypeList> { + static const bool valid = (WasmType::valid && ...); + static const size_t size = sizeof...(T); + static bool matches(ValType::ListRef types) { + if (types.size() != size) { + return false; + } + size_t n = 0; + return ((WasmType::kind == types.begin()[n++].kind()) && ...); + } + static void store(Store::Context cx, wasmtime_val_raw_t *storage, + const std::tuple &t) { + size_t n = 0; + std::apply( + [&](const auto &...val) { + (WasmType::store(cx, &storage[n++], val), ...); // NOLINT + }, + t); + } + static std::tuple load(Store::Context cx, wasmtime_val_raw_t *storage) { + size_t n = 0; + return std::tuple{WasmType::load(cx, &storage[n++])...}; // NOLINT + } + static std::vector types() { return {WasmType::kind...}; } +}; + +/// A "trait" for what can be returned from closures specified to `Func::wrap`. +/// +/// The base case here is a bare return value like `int32_t`. +template struct WasmHostRet { + using Results = WasmTypeList; + + template + static std::optional invoke(F f, Caller cx, wasmtime_val_raw_t *raw, + A... args) { + auto ret = f(args...); + Results::store(cx, raw, ret); + return std::nullopt; + } +}; + +/// Host functions can return nothing +template <> struct WasmHostRet { + using Results = WasmTypeList>; + + template + static std::optional invoke(F f, Caller cx, wasmtime_val_raw_t *raw, + A... args) { + f(args...); + return std::nullopt; + } +}; + +// Alternative method of returning "nothing" (also enables `std::monostate` in +// the `R` type of `Result` below) +template <> struct WasmHostRet : public WasmHostRet {}; + +/// Host functions can return a result which allows them to also possibly return +/// a trap. +template struct WasmHostRet> { + using Results = WasmTypeList; + + template + static std::optional invoke(F f, Caller cx, wasmtime_val_raw_t *raw, + A... args) { + Result ret = f(args...); + if (!ret) { + return ret.err(); + } + Results::store(cx, raw, ret.ok()); + return std::nullopt; + } +}; + +template struct WasmHostFunc; + +/// Base type information for host free-function pointers being used as wasm +/// functions +template struct WasmHostFunc { + using Params = WasmTypeList>; + using Results = typename WasmHostRet::Results; + + template + static std::optional invoke(F &f, Caller cx, wasmtime_val_raw_t *raw) { + auto params = Params::load(cx, raw); + return std::apply( + [&](const auto &...val) { + return WasmHostRet::invoke(f, cx, raw, val...); + }, + params); + } +}; + +/// Function type information, but with a `Caller` first parameter +template +struct WasmHostFunc : public WasmHostFunc { + // Override `invoke` here to pass the `cx` as the first parameter + template + static std::optional invoke(F &f, Caller cx, wasmtime_val_raw_t *raw) { + auto params = WasmTypeList>::load(cx, raw); + return std::apply( + [&](const auto &...val) { + return WasmHostRet::invoke(f, cx, raw, cx, val...); + }, + params); + } +}; + +/// Function type information, but with a class method. +template +struct WasmHostFunc : public WasmHostFunc {}; + +/// Function type information, but with a const class method. +template +struct WasmHostFunc : public WasmHostFunc {}; + +/// Function type information, but as a host method with a `Caller` first +/// parameter. +template +struct WasmHostFunc + : public WasmHostFunc {}; + +/// Function type information, but as a host const method with a `Caller` +/// first parameter. +template +struct WasmHostFunc + : public WasmHostFunc {}; + +/// Base type information for host callables being used as wasm +/// functions +template +struct WasmHostFunc> + : public WasmHostFunc {}; + +} // namespace detail + +using namespace detail; + +/** + * \brief Representation of a WebAssembly function. + * + * This class represents a WebAssembly function, either created through + * instantiating a module or a host function. + * + * Note that this type does not itself own any resources. It points to resources + * owned within a `Store` and the `Store` must be passed in as the first + * argument to the functions defined on `Func`. Note that if the wrong `Store` + * is passed in then the process will be aborted. + */ +class Func { + friend class Val; + friend class Instance; + friend class Linker; + template friend class TypedFunc; + + wasmtime_func_t func; + + template + static wasm_trap_t *raw_callback(void *env, wasmtime_caller_t *caller, + const wasmtime_val_t *args, size_t nargs, + wasmtime_val_t *results, size_t nresults) { + static_assert(alignof(Val) == alignof(wasmtime_val_t)); + static_assert(sizeof(Val) == sizeof(wasmtime_val_t)); + F *func = reinterpret_cast(env); // NOLINT + Span args_span(reinterpret_cast(args), // NOLINT + nargs); + Span results_span(reinterpret_cast(results), // NOLINT + nresults); + Result result = + (*func)(Caller(caller), args_span, results_span); + if (!result) { + return result.err().ptr.release(); + } + return nullptr; + } + + template + static wasm_trap_t * + raw_callback_unchecked(void *env, wasmtime_caller_t *caller, + wasmtime_val_raw_t *args_and_results, + size_t nargs_and_results) { + using HostFunc = WasmHostFunc; + Caller cx(caller); + F *func = reinterpret_cast(env); // NOLINT + auto trap = HostFunc::invoke(*func, cx, args_and_results); + if (trap) { + return trap->ptr.release(); + } + return nullptr; + } + + template static void raw_finalize(void *env) { + std::unique_ptr ptr(reinterpret_cast(env)); // NOLINT + } + +public: + /// Creates a new function from the raw underlying C API representation. + Func(wasmtime_func_t func) : func(func) {} + + /** + * \brief Creates a new host-defined function. + * + * This constructor is used to create a host function within the store + * provided. This is how WebAssembly can call into the host and make use of + * external functionality. + * + * > **Note**: host functions created this way are more flexible but not + * > as fast to call as those created by `Func::wrap`. + * + * \param cx the store to create the function within + * \param ty the type of the function that will be created + * \param f the host callback to be executed when this function is called. + * + * The parameter `f` is expected to be a lambda (or a lambda lookalike) which + * takes three parameters: + * + * * The first parameter is a `Caller` to get recursive access to the store + * and other caller state. + * * The second parameter is a `Span` which is the list of + * parameters to the function. These parameters are guaranteed to be of the + * types specified by `ty` when constructing this function. + * * The last argument is `Span` which is where to write the return + * values of the function. The function must produce the types of values + * specified by `ty` or otherwise a trap will be raised. + * + * The parameter `f` is expected to return `Result`. + * This allows `f` to raise a trap if desired, or otherwise return no trap and + * finish successfully. If a trap is raised then the results pointer does not + * need to be written to. + */ + template , F, Caller, + Span, Span>, + bool> = true> + Func(Store::Context cx, const FuncType &ty, F f) : func({}) { + wasmtime_func_new(cx.ptr, ty.ptr.get(), raw_callback, + std::make_unique(f).release(), raw_finalize, &func); + } + + /** + * \brief Creates a new host function from the provided callback `f`, + * inferring the WebAssembly function type from the host signature. + * + * This function is akin to the `Func` constructor except that the WebAssembly + * type does not need to be specified and additionally the signature of `f` + * is different. The main goal of this function is to enable WebAssembly to + * call the function `f` as-fast-as-possible without having to validate any + * types or such. + * + * The function `f` can optionally take a `Caller` as its first parameter, + * but otherwise its arguments are translated to WebAssembly types: + * + * * `int32_t`, `uint32_t` - `i32` + * * `int64_t`, `uint64_t` - `i64` + * * `float` - `f32` + * * `double` - `f64` + * * `std::optional` - `funcref` + * * `std::optional` - `externref` + * * `wasmtime::V128` - `v128` + * + * The function may only take these arguments and if it takes any other kinds + * of arguments then it will fail to compile. + * + * The function may return a few different flavors of return values: + * + * * `void` - interpreted as returning nothing + * * Any type above - interpreted as a singular return value. + * * `std::tuple` where `T` is one of the valid argument types - + * interpreted as returning multiple values. + * * `Result` where `T` is another valid return type - interpreted as + * a function that returns `T` to wasm but is optionally allowed to also + * raise a trap. + * + * It's recommended, if possible, to use this function over the `Func` + * constructor since this is generally easier to work with and also enables + * a faster path for WebAssembly to call this function. + */ + template ::Params::valid, bool> = true, + std::enable_if_t::Results::valid, bool> = true> + static Func wrap(Store::Context cx, F f) { + using HostFunc = WasmHostFunc; + auto params = HostFunc::Params::types(); + auto results = HostFunc::Results::types(); + auto ty = FuncType::from_iters(params, results); + wasmtime_func_t func; + wasmtime_func_new_unchecked(cx.ptr, ty.ptr.get(), raw_callback_unchecked, + std::make_unique(f).release(), + raw_finalize, &func); + return func; + } + + /** + * \brief Invoke a WebAssembly function. + * + * This function will execute this WebAssembly function. This function muts be + * defined within the `cx`'s store provided. The `params` argument is the list + * of parameters that are passed to the wasm function, and the types of the + * values within `params` must match the type signature of this function. + * + * This may return one of three values: + * + * * First the function could succeed, returning a vector of values + * representing the results of the function. + * * Otherwise a `Trap` might be generated by the WebAssembly function. + * * Finally an `Error` could be returned indicating that `params` were not of + * the right type. + * + * > **Note**: for optimized calls into WebAssembly where the function + * > signature is statically known it's recommended to use `Func::typed` and + * > `TypedFunc::call`. + */ + template + TrapResult> call(Store::Context cx, const I &begin, + const I &end) const { + std::vector raw_params; + raw_params.reserve(end - begin); + for (auto i = begin; i != end; i++) { + raw_params.push_back(i->val); + } + size_t nresults = this->type(cx)->results().size(); + std::vector raw_results(nresults); + + wasm_trap_t *trap = nullptr; + auto *error = + wasmtime_func_call(cx.ptr, &func, raw_params.data(), raw_params.size(), + raw_results.data(), raw_results.capacity(), &trap); + if (error != nullptr) { + return TrapError(Error(error)); + } + if (trap != nullptr) { + return TrapError(Trap(trap)); + } + + std::vector results; + results.reserve(nresults); + for (size_t i = 0; i < nresults; i++) { + results.push_back(raw_results[i]); + } + return results; + } + + /** + * \brief Helper function for `call(Store::Context cx, const I &begin, const I + * &end)` + * + * \see call(Store::Context cx, const I &begin, const I &end) + */ + TrapResult> call(Store::Context cx, + const std::vector ¶ms) const { + return this->call(cx, params.begin(), params.end()); + } + + /** + * \brief Helper function for `call(Store::Context cx, const I &begin, const I + * &end)` + * + * \see call(Store::Context cx, const I &begin, const I &end) + */ + TrapResult> + call(Store::Context cx, const std::initializer_list ¶ms) const { + return this->call(cx, params.begin(), params.end()); + } + + /// Returns the type of this function. + FuncType type(Store::Context cx) const { + return wasmtime_func_type(cx.ptr, &func); + } + + /** + * \brief Statically checks this function against the provided types. + * + * This function will check whether it takes the statically known `Params` + * and returns the statically known `Results`. If the type check succeeds then + * a `TypedFunc` is returned which enables a faster method of invoking + * WebAssembly functions. + * + * The `Params` and `Results` specified as template parameters here are the + * parameters and results of the wasm function. They can either be a bare + * type which means that the wasm takes/returns one value, or they can be a + * `std::tuple` of types to represent multiple arguments or multiple + * returns. + * + * The valid types for this function are those mentioned as the arguments + * for `Func::wrap`. + */ + template ::valid, bool> = true, + std::enable_if_t::valid, bool> = true> + Result, Trap> typed(Store::Context cx) const { + auto ty = this->type(cx); + if (!WasmTypeList::matches(ty->params()) || + !WasmTypeList::matches(ty->results())) { + return Trap("static type for this function does not match actual type"); + } + TypedFunc ret(*this); + return ret; + } + + /// Returns the raw underlying C API function this is using. + const wasmtime_func_t &raw_func() const { return func; } +}; + +/** + * \brief A version of a WebAssembly `Func` where the type signature of the + * function is statically known. + */ +template class TypedFunc { + friend class Func; + Func f; + TypedFunc(Func func) : f(func) {} + +public: + /** + * \brief Calls this function with the provided parameters. + * + * This function is akin to `Func::call` except that since static type + * information is available it statically takes its parameters and statically + * returns its results. + * + * Note that this function still may return a `Trap` indicating that calling + * the WebAssembly function failed. + */ + TrapResult call(Store::Context cx, Params params) const { + std::array::size, + WasmTypeList::size)> + storage; + wasmtime_val_raw_t *ptr = storage.data(); + if (ptr == nullptr) + ptr = reinterpret_cast(alignof(wasmtime_val_raw_t)); + WasmTypeList::store(cx, ptr, params); + wasm_trap_t *trap = nullptr; + auto *error = wasmtime_func_call_unchecked( + cx.raw_context(), &f.func, ptr, storage.size(), &trap); + if (error != nullptr) { + return TrapError(Error(error)); + } + if (trap != nullptr) { + return TrapError(Trap(trap)); + } + return WasmTypeList::load(cx, ptr); + } + + /// Returns the underlying un-typed `Func` for this function. + const Func &func() const { return f; } +}; + +inline Val::Val(std::optional func) : val{} { + val.kind = WASMTIME_FUNCREF; + if (func) { + val.of.funcref = (*func).func; + } else { + wasmtime_funcref_set_null(&val.of.funcref); + } +} + +inline Val::Val(Func func) : Val(std::optional(func)) {} +inline Val::Val(ExternRef ptr) : Val(std::optional(ptr)) {} + +inline std::optional Val::funcref() const { + if (val.kind != WASMTIME_FUNCREF) { + std::abort(); + } + if (val.of.funcref.store_id == 0) { + return std::nullopt; + } + return Func(val.of.funcref); +} + +/// Definition for the `funcref` native wasm type +template <> struct detail::WasmType> { + /// @private + static const bool valid = true; + /// @private + static const ValKind kind = ValKind::FuncRef; + /// @private + static void store(Store::Context cx, wasmtime_val_raw_t *p, + const std::optional func) { + if (func) { + p->funcref = wasmtime_func_to_raw(cx.raw_context(), &func->raw_func()); + } else { + p->funcref = 0; + } + } + /// @private + static std::optional load(Store::Context cx, wasmtime_val_raw_t *p) { + if (p->funcref == 0) { + return std::nullopt; + } + wasmtime_func_t ret; + wasmtime_func_from_raw(cx.raw_context(), p->funcref, &ret); + return ret; + } +}; + +/** + * \brief A WebAssembly global. + * + * This class represents a WebAssembly global, either created through + * instantiating a module or a host global. Globals contain a WebAssembly value + * and can be read and optionally written to. + * + * Note that this type does not itself own any resources. It points to resources + * owned within a `Store` and the `Store` must be passed in as the first + * argument to the functions defined on `Global`. Note that if the wrong `Store` + * is passed in then the process will be aborted. + */ +class Global { + friend class Instance; + wasmtime_global_t global; + +public: + /// Creates as global from the raw underlying C API representation. + Global(wasmtime_global_t global) : global(global) {} + + /** + * \brief Create a new WebAssembly global. + * + * \param cx the store in which to create the global + * \param ty the type that this global will have + * \param init the initial value of the global + * + * This function can fail if `init` does not have a value that matches `ty`. + */ + static Result create(Store::Context cx, const GlobalType &ty, + const Val &init) { + wasmtime_global_t global; + auto *error = wasmtime_global_new(cx.ptr, ty.ptr.get(), &init.val, &global); + if (error != nullptr) { + return Error(error); + } + return Global(global); + } + + /// Returns the type of this global. + GlobalType type(Store::Context cx) const { + return wasmtime_global_type(cx.ptr, &global); + } + + /// Returns the current value of this global. + Val get(Store::Context cx) const; + + /// Sets this global to a new value. + /// + /// This can fail if `val` has the wrong type or if this global isn't mutable. + Result set(Store::Context cx, const Val &val) const { + auto *error = wasmtime_global_set(cx.ptr, &global, &val.val); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } +}; + +/** + * \brief A WebAssembly table. + * + * This class represents a WebAssembly table, either created through + * instantiating a module or a host table. Tables are contiguous vectors of + * WebAssembly reference types, currently either `externref` or `funcref`. + * + * Note that this type does not itself own any resources. It points to resources + * owned within a `Store` and the `Store` must be passed in as the first + * argument to the functions defined on `Table`. Note that if the wrong `Store` + * is passed in then the process will be aborted. + */ +class Table { + friend class Instance; + wasmtime_table_t table; + +public: + /// Creates a new table from the raw underlying C API representation. + Table(wasmtime_table_t table) : table(table) {} + + /** + * \brief Creates a new host-defined table. + * + * \param cx the store in which to create the table. + * \param ty the type of the table to be created + * \param init the initial value for all table slots. + * + * Returns an error if `init` has the wrong value for the `ty` specified. + */ + static Result create(Store::Context cx, const TableType &ty, + const Val &init) { + wasmtime_table_t table; + auto *error = wasmtime_table_new(cx.ptr, ty.ptr.get(), &init.val, &table); + if (error != nullptr) { + return Error(error); + } + return Table(table); + } + + /// Returns the type of this table. + TableType type(Store::Context cx) const { + return wasmtime_table_type(cx.ptr, &table); + } + + /// Returns the size, in elements, that the table currently has. + uint64_t size(Store::Context cx) const { + return wasmtime_table_size(cx.ptr, &table); + } + + /// Loads a value from the specified index in this table. + /// + /// Returns `std::nullopt` if `idx` is out of bounds. + std::optional get(Store::Context cx, uint64_t idx) const { + Val val; + if (wasmtime_table_get(cx.ptr, &table, idx, &val.val)) { + return val; + } + return std::nullopt; + } + + /// Stores a value into the specified index in this table. + /// + /// Returns an error if `idx` is out of bounds or if `val` has the wrong type. + Result set(Store::Context cx, uint64_t idx, + const Val &val) const { + auto *error = wasmtime_table_set(cx.ptr, &table, idx, &val.val); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } + + /// Grow this table. + /// + /// \param cx the store that owns this table. + /// \param delta the number of new elements to be added to this table. + /// \param init the initial value of all new elements in this table. + /// + /// Returns an error if `init` has the wrong type for this table. Otherwise + /// returns the previous size of the table before growth. + Result grow(Store::Context cx, uint64_t delta, + const Val &init) const { + uint64_t prev = 0; + auto *error = wasmtime_table_grow(cx.ptr, &table, delta, &init.val, &prev); + if (error != nullptr) { + return Error(error); + } + return prev; + } +}; + +// gcc 8.3.0 seems to require that this comes after the definition of `Table`. I +// don't know why... +inline Val Global::get(Store::Context cx) const { + Val val; + wasmtime_global_get(cx.ptr, &global, &val.val); + return val; +} + +/** + * \brief A WebAssembly linear memory. + * + * This class represents a WebAssembly memory, either created through + * instantiating a module or a host memory. + * + * Note that this type does not itself own any resources. It points to resources + * owned within a `Store` and the `Store` must be passed in as the first + * argument to the functions defined on `Table`. Note that if the wrong `Store` + * is passed in then the process will be aborted. + */ +class Memory { + friend class Instance; + wasmtime_memory_t memory; + +public: + /// Creates a new memory from the raw underlying C API representation. + Memory(wasmtime_memory_t memory) : memory(memory) {} + + /// Creates a new host-defined memory with the type specified. + static Result create(Store::Context cx, const MemoryType &ty) { + wasmtime_memory_t memory; + auto *error = wasmtime_memory_new(cx.ptr, ty.ptr.get(), &memory); + if (error != nullptr) { + return Error(error); + } + return Memory(memory); + } + + /// Returns the type of this memory. + MemoryType type(Store::Context cx) const { + return wasmtime_memory_type(cx.ptr, &memory); + } + + /// Returns the size, in WebAssembly pages, of this memory. + uint64_t size(Store::Context cx) const { + return wasmtime_memory_size(cx.ptr, &memory); + } + + /// Returns a `span` of where this memory is located in the host. + /// + /// Note that embedders need to be very careful in their usage of the returned + /// `span`. It can be invalidated with calls to `grow` and/or calls into + /// WebAssembly. + Span data(Store::Context cx) const { + auto *base = wasmtime_memory_data(cx.ptr, &memory); + auto size = wasmtime_memory_data_size(cx.ptr, &memory); + return {base, size}; + } + + /// Grows the memory by `delta` WebAssembly pages. + /// + /// On success returns the previous size of this memory in units of + /// WebAssembly pages. + Result grow(Store::Context cx, uint64_t delta) const { + uint64_t prev = 0; + auto *error = wasmtime_memory_grow(cx.ptr, &memory, delta, &prev); + if (error != nullptr) { + return Error(error); + } + return prev; + } +}; + +/** + * \brief A WebAssembly instance. + * + * This class represents a WebAssembly instance, created by instantiating a + * module. An instance is the collection of items exported by the module, which + * can be accessed through the `Store` that owns the instance. + * + * Note that this type does not itself own any resources. It points to resources + * owned within a `Store` and the `Store` must be passed in as the first + * argument to the functions defined on `Instance`. Note that if the wrong + * `Store` is passed in then the process will be aborted. + */ +class Instance { + friend class Linker; + friend class Caller; + + wasmtime_instance_t instance; + + static Extern cvt(wasmtime_extern_t &e) { + switch (e.kind) { + case WASMTIME_EXTERN_FUNC: + return Func(e.of.func); + case WASMTIME_EXTERN_GLOBAL: + return Global(e.of.global); + case WASMTIME_EXTERN_MEMORY: + return Memory(e.of.memory); + case WASMTIME_EXTERN_TABLE: + return Table(e.of.table); + } + std::abort(); + } + + static void cvt(const Extern &e, wasmtime_extern_t &raw) { + if (const auto *func = std::get_if(&e)) { + raw.kind = WASMTIME_EXTERN_FUNC; + raw.of.func = func->func; + } else if (const auto *global = std::get_if(&e)) { + raw.kind = WASMTIME_EXTERN_GLOBAL; + raw.of.global = global->global; + } else if (const auto *table = std::get_if
(&e)) { + raw.kind = WASMTIME_EXTERN_TABLE; + raw.of.table = table->table; + } else if (const auto *memory = std::get_if(&e)) { + raw.kind = WASMTIME_EXTERN_MEMORY; + raw.of.memory = memory->memory; + } else { + std::abort(); + } + } + +public: + /// Creates a new instance from the raw underlying C API representation. + Instance(wasmtime_instance_t instance) : instance(instance) {} + + /** + * \brief Instantiates the module `m` with the provided `imports` + * + * \param cx the store in which to instantiate the provided module + * \param m the module to instantiate + * \param imports the list of imports to use to instantiate the module + * + * This `imports` parameter is expected to line up 1:1 with the imports + * required by the `m`. The type of `m` can be inspected to determine in which + * order to provide the imports. Note that this is a relatively low-level API + * and it's generally recommended to use `Linker` instead for name-based + * instantiation. + * + * This function can return an error if any of the `imports` have the wrong + * type, or if the wrong number of `imports` is provided. + */ + static TrapResult create(Store::Context cx, const Module &m, + const std::vector &imports) { + std::vector raw_imports; + for (const auto &item : imports) { + raw_imports.push_back(wasmtime_extern_t{}); + auto &last = raw_imports.back(); + Instance::cvt(item, last); + } + wasmtime_instance_t instance; + wasm_trap_t *trap = nullptr; + auto *error = wasmtime_instance_new(cx.ptr, m.ptr.get(), raw_imports.data(), + raw_imports.size(), &instance, &trap); + if (error != nullptr) { + return TrapError(Error(error)); + } + if (trap != nullptr) { + return TrapError(Trap(trap)); + } + return Instance(instance); + } + + /** + * \brief Load an instance's export by name. + * + * This function will look for an export named `name` on this instance and, if + * found, return it as an `Extern`. + */ + std::optional get(Store::Context cx, std::string_view name) { + wasmtime_extern_t e; + if (!wasmtime_instance_export_get(cx.ptr, &instance, name.data(), + name.size(), &e)) { + return std::nullopt; + } + return Instance::cvt(e); + } + + /** + * \brief Load an instance's export by index. + * + * This function will look for the `idx`th export of this instance. This will + * return both the name of the export as well as the exported item itself. + */ + std::optional> get(Store::Context cx, + size_t idx) { + wasmtime_extern_t e; + // I'm not sure why clang-tidy thinks this is using va_list or anything + // related to that... + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + char *name = nullptr; + size_t len = 0; + if (!wasmtime_instance_export_nth(cx.ptr, &instance, idx, &name, &len, + &e)) { + return std::nullopt; + } + std::string_view n(name, len); + return std::pair(n, Instance::cvt(e)); + } +}; + +inline std::optional Caller::get_export(std::string_view name) { + wasmtime_extern_t item; + if (wasmtime_caller_export_get(ptr, name.data(), name.size(), &item)) { + return Instance::cvt(item); + } + return std::nullopt; +} + +/** + * \brief Helper class for linking modules together with name-based resolution. + * + * This class is used for easily instantiating `Module`s by defining names into + * the linker and performing name-based resolution during instantiation. A + * `Linker` can also be used to link in WASI functions to instantiate a module. + */ +class Linker { + struct deleter { + void operator()(wasmtime_linker_t *p) const { wasmtime_linker_delete(p); } + }; + + std::unique_ptr ptr; + +public: + /// Creates a new linker which will instantiate in the given engine. + explicit Linker(Engine &engine) + : ptr(wasmtime_linker_new(engine.ptr.get())) {} + + /// Configures whether shadowing previous names is allowed or not. + /// + /// By default shadowing is not allowed. + void allow_shadowing(bool allow) { + wasmtime_linker_allow_shadowing(ptr.get(), allow); + } + + /// Defines the provided item into this linker with the given name. + Result define(Store::Context cx, std::string_view module, + std::string_view name, const Extern &item) { + wasmtime_extern_t raw; + Instance::cvt(item, raw); + auto *error = + wasmtime_linker_define(ptr.get(), cx.ptr, module.data(), module.size(), + name.data(), name.size(), &raw); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } + + /// Defines WASI functions within this linker. + /// + /// Note that `Store::Context::set_wasi` must also be used for instantiated + /// modules to have access to configured WASI state. + Result define_wasi() { + auto *error = wasmtime_linker_define_wasi(ptr.get()); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } + + /// Defines all exports of the `instance` provided in this linker with the + /// given module name of `name`. + Result + define_instance(Store::Context cx, std::string_view name, Instance instance) { + auto *error = wasmtime_linker_define_instance( + ptr.get(), cx.ptr, name.data(), name.size(), &instance.instance); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } + + /// Instantiates the module `m` provided within the store `cx` using the items + /// defined within this linker. + TrapResult instantiate(Store::Context cx, const Module &m) { + wasmtime_instance_t instance; + wasm_trap_t *trap = nullptr; + auto *error = wasmtime_linker_instantiate(ptr.get(), cx.ptr, m.ptr.get(), + &instance, &trap); + if (error != nullptr) { + return TrapError(Error(error)); + } + if (trap != nullptr) { + return TrapError(Trap(trap)); + } + return Instance(instance); + } + + /// Defines instantiations of the module `m` within this linker under the + /// given `name`. + Result module(Store::Context cx, std::string_view name, + const Module &m) { + auto *error = wasmtime_linker_module(ptr.get(), cx.ptr, name.data(), + name.size(), m.ptr.get()); + if (error != nullptr) { + return Error(error); + } + return std::monostate(); + } + + /// Attempts to load the specified named item from this linker, returning + /// `std::nullopt` if it was not defined. + [[nodiscard]] std::optional + get(Store::Context cx, std::string_view module, std::string_view name) { + wasmtime_extern_t item; + if (wasmtime_linker_get(ptr.get(), cx.ptr, module.data(), module.size(), + name.data(), name.size(), &item)) { + return Instance::cvt(item); + } + return std::nullopt; + } + + /// Defines a new function in this linker in the style of the `Func` + /// constructor. + template , F, Caller, + Span, Span>, + bool> = true> + Result func_new(std::string_view module, + std::string_view name, const FuncType &ty, + F&& f) { + + auto *error = wasmtime_linker_define_func( + ptr.get(), module.data(), module.length(), name.data(), name.length(), + ty.ptr.get(), Func::raw_callback>, std::make_unique>(std::forward(f)).release(), + Func::raw_finalize>); + + if (error != nullptr) { + return Error(error); + } + + return std::monostate(); + } + + /// Defines a new function in this linker in the style of the `Func::wrap` + /// constructor. + template ::Params::valid, bool> = true, + std::enable_if_t::Results::valid, bool> = true> + Result func_wrap(std::string_view module, + std::string_view name, F&& f) { + using HostFunc = WasmHostFunc; + auto params = HostFunc::Params::types(); + auto results = HostFunc::Results::types(); + auto ty = FuncType::from_iters(params, results); + auto *error = wasmtime_linker_define_func_unchecked( + ptr.get(), module.data(), module.length(), name.data(), name.length(), + ty.ptr.get(), Func::raw_callback_unchecked>, + std::make_unique>(std::forward(f)).release(), Func::raw_finalize>); + + if (error != nullptr) { + return Error(error); + } + + return std::monostate(); + } + + /// Loads the "default" function, according to WASI commands and reactors, of + /// the module named `name` in this linker. + Result get_default(Store::Context cx, std::string_view name) { + wasmtime_func_t item; + auto *error = wasmtime_linker_get_default(ptr.get(), cx.ptr, name.data(), + name.size(), &item); + if (error != nullptr) { + return Error(error); + } + return Func(item); + } +}; + +} // namespace wasmtime + +#endif // WASMTIME_HH diff --git a/crates/c-api/tests/CMakeLists.txt b/crates/c-api/tests/CMakeLists.txt new file mode 100644 index 000000000000..4e9818228e7b --- /dev/null +++ b/crates/c-api/tests/CMakeLists.txt @@ -0,0 +1,27 @@ +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/release-1.12.1.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +include(GoogleTest) + +function(add_capi_test name) + add_executable(test-${name} ${name}.cc) + target_link_libraries(test-${name} PRIVATE wasmtime-cpp gtest_main) + gtest_discover_tests(test-${name}) +endfunction() + +add_capi_test(simple) +add_capi_test(types) +add_capi_test(func) + +# 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) diff --git a/crates/c-api/tests/double-include-a.cc b/crates/c-api/tests/double-include-a.cc new file mode 100644 index 000000000000..692ea14064ab --- /dev/null +++ b/crates/c-api/tests/double-include-a.cc @@ -0,0 +1,9 @@ +#include +#include + +using namespace wasmtime; + +TEST(Store, WorksInFileA) { + Engine engine; + Store store(engine); +} diff --git a/crates/c-api/tests/double-include-b.cc b/crates/c-api/tests/double-include-b.cc new file mode 100644 index 000000000000..e45f0902a82f --- /dev/null +++ b/crates/c-api/tests/double-include-b.cc @@ -0,0 +1,9 @@ +#include +#include + +using namespace wasmtime; + +TEST(Store, WorksInFileB) { + Engine engine; + Store store(engine); +} diff --git a/crates/c-api/tests/func.cc b/crates/c-api/tests/func.cc new file mode 100644 index 000000000000..fec1d696292e --- /dev/null +++ b/crates/c-api/tests/func.cc @@ -0,0 +1,259 @@ +#include +#include + +using namespace wasmtime; +using empty_t = std::tuple<>; + +TEST(TypedFunc, Smoke) { + Engine engine; + Store store(engine); + Func thunk( + store, FuncType({}, {}), + [](auto caller, auto params, auto results) { return std::monostate(); }); + + EXPECT_FALSE((thunk.typed(store))); + EXPECT_FALSE((thunk.typed>(store))); + EXPECT_FALSE((thunk.typed(store))); + EXPECT_TRUE((thunk.typed(store))); + + Func pi32( + store, FuncType({ValKind::I32}, {}), + [](auto caller, auto params, auto results) { return std::monostate(); }); + + EXPECT_FALSE((pi32.typed(store))); + EXPECT_TRUE((pi32.typed, empty_t>(store))); + EXPECT_TRUE((pi32.typed(store))); + EXPECT_TRUE((pi32.typed, empty_t>(store))); + EXPECT_TRUE((pi32.typed(store))); + + Func rets( + store, FuncType({}, {ValKind::F32, ValKind::F64}), + [](auto caller, auto params, auto results) { return std::monostate(); }); + + EXPECT_FALSE((rets.typed>(store))); + EXPECT_FALSE((rets.typed(store))); + EXPECT_TRUE((rets.typed>(store))); +} + +TEST(TypedFunc, Call) { + Engine engine; + Store store(engine); + + { + Func thunk(store, FuncType({}, {}), + [](auto caller, auto params, auto results) { + return std::monostate(); + }); + auto func = thunk.typed(store).unwrap(); + empty_t result = func.call(store, empty_t()).unwrap(); + } + + { + Func f(store, FuncType({ValKind::I32}, {}), + [](auto caller, auto params, auto results) { + EXPECT_EQ(params[0].i32(), 1); + return std::monostate(); + }); + + f.typed(store).unwrap().call(store, 1).unwrap(); + f.typed, empty_t>(store) + .unwrap() + .call(store, {1}) + .unwrap(); + } + { + Func f(store, + FuncType({ValKind::F32, ValKind::I64}, {ValKind::I32, ValKind::F64}), + [](auto caller, auto params, auto results) { + EXPECT_EQ(params[0].f32(), 1); + EXPECT_EQ(params[1].i64(), 2); + results[0] = int32_t(3); + results[1] = double(4); + return std::monostate(); + }); + + auto func = + f.typed, std::tuple>( + store) + .unwrap(); + auto result = func.call(store, {1, 2}).unwrap(); + EXPECT_EQ(std::get<0>(result), 3); + EXPECT_EQ(std::get<1>(result), 4); + } + + { + FuncType ty({ValKind::ExternRef, ValKind::ExternRef}, + {ValKind::ExternRef, ValKind::ExternRef}); + Func f(store, ty, [](auto caller, auto params, auto results) { + caller.context().gc(); + EXPECT_TRUE(params[0].externref(caller)); + EXPECT_EQ(std::any_cast(params[0].externref(caller)->data(caller)), 100); + EXPECT_FALSE(params[1].externref(caller)); + results[0] = ExternRef(caller, int(3)); + results[1] = std::optional(std::nullopt); + caller.context().gc(); + return std::monostate(); + }); + + using ExternRefPair = + std::tuple, std::optional>; + auto func = f.typed(store).unwrap(); + auto result = + func.call(store, {ExternRef(store, int(100)), std::nullopt}).unwrap(); + store.context().gc(); + EXPECT_EQ(std::any_cast(std::get<0>(result)->data(store)), 3); + EXPECT_EQ(std::get<1>(result), std::nullopt); + } + + { + Func f2(store, FuncType({}, {}), + [](auto caller, auto params, auto results) { + return std::monostate(); + }); + + FuncType ty({ValKind::FuncRef, ValKind::FuncRef}, + {ValKind::FuncRef, ValKind::FuncRef}); + + Func f(store, ty, [&](auto caller, auto params, auto results) { + EXPECT_TRUE(params[0].funcref()); + Func param = *params[0].funcref(); + param.typed(caller) + .unwrap() + .call(caller, empty_t()) + .unwrap(); + EXPECT_FALSE(params[1].funcref()); + results[0] = f2; + results[1] = std::optional(std::nullopt); + return std::monostate(); + }); + + using FuncPair = std::tuple, std::optional>; + auto func = f.typed(store).unwrap(); + auto result = func.call(store, {f2, std::nullopt}).unwrap(); + /* EXPECT_EQ(std::any_cast(std::get<0>(result)->data()), 3); */ + Func result_f = *std::get<0>(result); + result_f.typed(store) + .unwrap() + .call(store, empty_t()) + .unwrap(); + EXPECT_EQ(std::get<1>(result), std::nullopt); + } + + { + FuncType ty({ValKind::V128}, {ValKind::V128}); + + Func f(store, ty, [&](auto caller, auto params, auto results) { + V128 ret; + for (int i = 0; i < 16; i++) { + EXPECT_EQ(params[0].v128().v128[i], 1); + ret.v128[i] = 2; + } + results[0] = ret; + return std::monostate(); + }); + + V128 param; + for (int i = 0; i < 16; i++) { + param.v128[i] = 1; + } + auto func = f.typed(store).unwrap(); + auto result = func.call(store, {param}).unwrap(); + for (int i = 0; i < 16; i++) { + EXPECT_EQ(result.v128[i], 2); + } + } +} + +void assert_types_eq(ValType::ListRef actual, + std::initializer_list expected) { + EXPECT_EQ(expected.size(), actual.size()); + std::vector actual_vec; + for (auto ty : actual) { + actual_vec.push_back(ty.kind()); + } + std::vector expected_vec(expected); + EXPECT_EQ(actual_vec, expected_vec); +} + +void assert_func_type(FuncType actual, std::initializer_list params, + std::initializer_list results) { + assert_types_eq(actual->params(), params); + assert_types_eq(actual->results(), results); +} + +TEST(TypedFunc, WrapAndTypes) { + Engine engine; + Store store(engine); + Func f = Func::wrap(store, []() {}); + assert_func_type(f.type(store), {}, {}); + f = Func::wrap(store, []() { return int32_t(1); }); + assert_func_type(f.type(store), {}, {ValKind::I32}); + f = Func::wrap(store, []() { return int64_t(1); }); + assert_func_type(f.type(store), {}, {ValKind::I64}); + f = Func::wrap(store, []() { return float(1); }); + assert_func_type(f.type(store), {}, {ValKind::F32}); + f = Func::wrap(store, []() { return double(1); }); + assert_func_type(f.type(store), {}, {ValKind::F64}); + f = Func::wrap(store, []() { return V128(); }); + assert_func_type(f.type(store), {}, {ValKind::V128}); + f = Func::wrap(store, + []() { return std::make_tuple(int32_t(1), int32_t(2)); }); + assert_func_type(f.type(store), {}, {ValKind::I32, ValKind::I32}); + f = Func::wrap(store, []() { return std::optional(std::nullopt); }); + assert_func_type(f.type(store), {}, {ValKind::FuncRef}); + f = Func::wrap(store, + []() { return std::optional(std::nullopt); }); + assert_func_type(f.type(store), {}, {ValKind::ExternRef}); + f = Func::wrap( + store, []() { return Result(std::monostate()); }); + assert_func_type(f.type(store), {}, {}); + f = Func::wrap(store, []() { return Result(1); }); + assert_func_type(f.type(store), {}, {ValKind::I32}); + f = Func::wrap(store, []() { return Result(1); }); + assert_func_type(f.type(store), {}, {ValKind::F32}); + f = Func::wrap(store, []() { + return Result, Trap>({1, 2}); + }); + assert_func_type(f.type(store), {}, {ValKind::I32, ValKind::I32}); + + f = Func::wrap(store, [](int32_t a) {}); + assert_func_type(f.type(store), {ValKind::I32}, {}); + f = Func::wrap(store, [](int64_t a) {}); + assert_func_type(f.type(store), {ValKind::I64}, {}); + f = Func::wrap(store, [](float a) {}); + assert_func_type(f.type(store), {ValKind::F32}, {}); + f = Func::wrap(store, [](double a) {}); + assert_func_type(f.type(store), {ValKind::F64}, {}); + f = Func::wrap(store, [](V128 a) {}); + assert_func_type(f.type(store), {ValKind::V128}, {}); + f = Func::wrap(store, [](std::optional a) {}); + assert_func_type(f.type(store), {ValKind::FuncRef}, {}); + f = Func::wrap(store, [](std::optional a) {}); + assert_func_type(f.type(store), {ValKind::ExternRef}, {}); + f = Func::wrap(store, [](Caller a) {}); + assert_func_type(f.type(store), {}, {}); + f = Func::wrap(store, [](Caller a, int32_t b) {}); + assert_func_type(f.type(store), {ValKind::I32}, {}); +} + +TEST(TypedFunc, WrapRuntime) { + Engine engine; + Store store(engine); + Func f = Func::wrap(store, []() {}); + f.typed(store).unwrap().call(store, empty_t()).unwrap(); + + f = Func::wrap(store, []() { return int32_t(1); }); + int32_t i = + f.typed(store).unwrap().call(store, empty_t()).unwrap(); + EXPECT_EQ(i, 1); + + f = Func::wrap(store, [](Caller cx, int32_t i) { EXPECT_EQ(i, 2); }); + f.typed(store).unwrap().call(store, 2).unwrap(); + + f = Func::wrap(store, [](Caller cx, int32_t i, int32_t j) { return i + j; }); + auto ret = f.typed, int32_t>(store) + .unwrap() + .call(store, {1, 2}) + .unwrap(); + EXPECT_EQ(ret, 3); +} diff --git a/crates/c-api/tests/simple.cc b/crates/c-api/tests/simple.cc new file mode 100644 index 000000000000..f1ee106f91cb --- /dev/null +++ b/crates/c-api/tests/simple.cc @@ -0,0 +1,549 @@ +#include +#include + +using namespace wasmtime; + +template T unwrap(Result result) { + if (result) { + return result.ok(); + } + std::cerr << "error: " << result.err().message() << "\n"; + std::abort(); +} + +TEST(Store, Smoke) { + Engine engine; + Store store(engine); + Store store2 = std::move(store); + Store store3(std::move(store2)); + + store = Store(engine); + store.limiter(-1, -1, -1, -1, -1); + store.context().gc(); + store.context().get_fuel().err(); + store.context().set_fuel(1).err(); + store.context().set_epoch_deadline(1); +} + +TEST(Engine, Smoke) { + Engine engine; + Config config; + engine = Engine(std::move(config)); +} + +TEST(PoolAllocationConfig, Smoke) { + PoolAllocationConfig config; + config.max_unused_warm_slots(1); + config.decommit_batch_size(2); + config.async_stack_keep_resident(3); + config.linear_memory_keep_resident(4); + config.table_keep_resident(5); + config.total_component_instances(6); + config.max_component_instance_size(7); + config.max_core_instances_per_component(8); + config.max_memories_per_component(9); + config.max_tables_per_component(10); + config.total_memories(11); + config.total_tables(12); + config.total_stacks(13); + config.total_core_instances(14); + config.max_core_instance_size(15); + config.max_tables_per_module(16); + config.table_elements(17); + config.max_memories_per_module(18); + config.max_memory_size(19); + config.total_gc_heaps(20); + + PoolAllocationConfig config2 = std::move(config); + PoolAllocationConfig config3(std::move(config)); +} + +TEST(Config, Smoke) { + Config config; + config.debug_info(false); + config.epoch_interruption(false); + config.consume_fuel(false); + config.max_wasm_stack(100); + config.wasm_threads(false); + config.wasm_reference_types(false); + config.wasm_simd(false); + config.wasm_bulk_memory(false); + config.wasm_multi_value(false); + config.strategy(Strategy::Auto); + config.cranelift_debug_verifier(false); + config.cranelift_opt_level(OptLevel::Speed); + config.profiler(ProfilingStrategy::None); + config.memory_reservation(0); + config.memory_guard_size(0); + auto result = config.cache_load_default(); + config.cache_load("nonexistent").err(); + + PoolAllocationConfig pooling_config; + config.pooling_allocation_strategy(pooling_config); + + Config config2 = std::move(config); + Config config3(std::move(config)); +} + +TEST(wat2wasm, Smoke) { + wat2wasm("(module)").ok(); + wat2wasm("xxx").err(); +} + +TEST(Trap, Smoke) { + Trap t("foo"); + EXPECT_EQ(t.message(), "foo"); + EXPECT_EQ(t.trace().size(), 0); + + Engine engine; + Module m = unwrap( + Module::compile(engine, "(module (func (export \"\") unreachable))")); + Store store(engine); + Instance i = unwrap(Instance::create(store, m, {})); + auto func = std::get(*i.get(store, "")); + auto trap = std::get(func.call(store, {}).err().data); + auto trace = trap.trace(); + EXPECT_EQ(trace.size(), 1); + auto frame = *trace.begin(); + EXPECT_EQ(frame.func_name(), std::nullopt); + EXPECT_EQ(frame.module_name(), std::nullopt); + EXPECT_EQ(frame.func_index(), 0); + EXPECT_EQ(frame.func_offset(), 1); + EXPECT_EQ(frame.module_offset(), 29); + for (auto &frame : trace) { + } + + EXPECT_TRUE(func.call(store, {}).err().message().find("unreachable") != + std::string::npos); + EXPECT_EQ(func.call(store, {1}).err().message(), + "expected 0 arguments, got 1"); +} + +TEST(Module, Smoke) { + Engine engine; + Module::compile(engine, "(module)").ok(); + Module::compile(engine, "wat").err(); + + auto wasm = wat2wasm("(module)").ok(); + Module::compile(engine, wasm).ok(); + std::vector emptyWasm; + Module::compile(engine, emptyWasm).err(); + + Module::validate(engine, wasm).ok(); + Module::validate(engine, emptyWasm).err(); + + Module m2 = unwrap(Module::compile(engine, "(module)")); + Module m3 = m2; + Module m4(m3); + m4 = m2; + Module m5(std::move(m3)); + m4 = std::move(m5); +} + +TEST(Module, Serialize) { + Engine engine; + Module m = unwrap(Module::compile(engine, "(module)")); + auto bytes = unwrap(m.serialize()); + m = unwrap(Module::deserialize(engine, bytes)); + std::string path("test_deserialize_file.cwasm"); + auto fh = ::fopen(path.c_str(), "wb"); + ::fwrite(bytes.data(), sizeof(uint8_t), bytes.size(), fh); + ::fclose(fh); + m = unwrap(Module::deserialize_file(engine, path)); + ::remove(path.c_str()); +} + +TEST(WasiConfig, Smoke) { + WasiConfig config; + config.argv({"x"}); + config.inherit_argv(); + config.env({{"x", "y"}}); + config.inherit_env(); + EXPECT_FALSE(config.stdin_file("nonexistent")); + config.inherit_stdin(); + EXPECT_FALSE(config.stdout_file("path/to/nonexistent")); + config.inherit_stdout(); + EXPECT_FALSE(config.stderr_file("path/to/nonexistent")); + config.inherit_stderr(); + + WasiConfig config2; + if (config2.preopen_dir("nonexistent", "nonexistent", 0, 0)) { + Engine engine; + Store store(engine); + EXPECT_FALSE(store.context().set_wasi(std::move(config2))); + } +} + +TEST(ExternRef, Smoke) { + Engine engine; + Store store(engine); + ExternRef a(store, "foo"); + ExternRef b(store, 3); + EXPECT_STREQ(std::any_cast(a.data(store)), "foo"); + EXPECT_EQ(std::any_cast(b.data(store)), 3); + a.unroot(store); + a = b; +} + +TEST(Val, Smoke) { + Val val(1); + EXPECT_EQ(val.kind(), ValKind::I32); + EXPECT_EQ(val.i32(), 1); + + val = (int32_t)3; + EXPECT_EQ(val.kind(), ValKind::I32); + EXPECT_EQ(val.i32(), 3); + + val = (int64_t)4; + EXPECT_EQ(val.kind(), ValKind::I64); + EXPECT_EQ(val.i64(), 4); + + val = (float)5; + EXPECT_EQ(val.kind(), ValKind::F32); + EXPECT_EQ(val.f32(), 5); + + val = (double)6; + EXPECT_EQ(val.kind(), ValKind::F64); + EXPECT_EQ(val.f64(), 6); + + val = V128(); + EXPECT_EQ(val.kind(), ValKind::V128); + for (int i = 0; i < 16; i++) { + EXPECT_EQ(val.v128().v128[i], 0); + } + + Engine engine; + Store store(engine); + val = std::optional(std::nullopt); + EXPECT_EQ(val.kind(), ValKind::ExternRef); + EXPECT_EQ(val.externref(store), std::nullopt); + + val = std::optional(ExternRef(store, 5)); + EXPECT_EQ(val.kind(), ValKind::ExternRef); + EXPECT_EQ(std::any_cast(val.externref(store)->data(store)), 5); + + val = ExternRef(store, 5); + EXPECT_EQ(val.kind(), ValKind::ExternRef); + EXPECT_EQ(std::any_cast(val.externref(store)->data(store)), 5); + + val = std::optional(std::nullopt); + EXPECT_EQ(val.kind(), ValKind::FuncRef); + EXPECT_EQ(val.funcref(), std::nullopt); + + Func func( + store, FuncType({}, {}), + [](auto caller, auto params, auto results) -> auto{ + return std::monostate(); + }); + + val = std::optional(func); + EXPECT_EQ(val.kind(), ValKind::FuncRef); + + val = func; + EXPECT_EQ(val.kind(), ValKind::FuncRef); +} + +TEST(Global, Smoke) { + Engine engine; + Store store(engine); + Global::create(store, GlobalType(ValKind::I32, true), 3.0).err(); + unwrap(Global::create(store, GlobalType(ValKind::I32, true), 3)); + unwrap(Global::create(store, GlobalType(ValKind::I32, false), 3)); + + Global g = unwrap(Global::create(store, GlobalType(ValKind::I32, true), 4)); + EXPECT_EQ(g.get(store).i32(), 4); + unwrap(g.set(store, 10)); + EXPECT_EQ(g.get(store).i32(), 10); + g.set(store, 10.23).err(); + EXPECT_EQ(g.get(store).i32(), 10); + + EXPECT_EQ(g.type(store)->content().kind(), ValKind::I32); + EXPECT_TRUE(g.type(store)->is_mutable()); +} + +TEST(Table, Smoke) { + Engine engine; + Store store(engine); + Table::create(store, TableType(ValKind::FuncRef, 1), 3.0).err(); + + Val null = std::optional(); + Table t = unwrap(Table::create(store, TableType(ValKind::FuncRef, 1), null)); + EXPECT_FALSE(t.get(store, 1)); + EXPECT_TRUE(t.get(store, 0)); + Val val = *t.get(store, 0); + EXPECT_EQ(val.kind(), ValKind::FuncRef); + EXPECT_FALSE(val.funcref()); + EXPECT_EQ(unwrap(t.grow(store, 4, null)), 1); + unwrap(t.set(store, 3, null)); + t.set(store, 3, 3).err(); + EXPECT_EQ(t.size(store), 5); + EXPECT_EQ(t.type(store)->element().kind(), ValKind::FuncRef); +} + +TEST(Memory, Smoke) { + Engine engine; + Store store(engine); + Memory m = unwrap(Memory::create(store, MemoryType(1))); + EXPECT_EQ(m.size(store), 1); + EXPECT_EQ(unwrap(m.grow(store, 1)), 1); + EXPECT_EQ(m.data(store).size(), 2 << 16); + EXPECT_EQ(m.type(store)->min(), 1); +} + +TEST(Instance, Smoke) { + Engine engine; + Store store(engine); + Memory m = unwrap(Memory::create(store, MemoryType(1))); + Global g = unwrap(Global::create(store, GlobalType(ValKind::I32, false), 1)); + Table t = unwrap(Table::create(store, TableType(ValKind::FuncRef, 1), + std::optional())); + Func f( + store, FuncType({}, {}), + [](auto caller, auto params, auto results) -> auto{ + return std::monostate(); + }); + + Module mod = + unwrap(Module::compile(engine, "(module" + "(import \"\" \"\" (func))" + "(import \"\" \"\" (global i32))" + "(import \"\" \"\" (table 1 funcref))" + "(import \"\" \"\" (memory 1))" + + "(func (export \"f\"))" + "(global (export \"g\") i32 (i32.const 0))" + "(export \"m\" (memory 0))" + "(export \"t\" (table 0))" + ")")); + Instance::create(store, mod, {}).err(); + Instance i = unwrap(Instance::create(store, mod, {f, g, t, m})); + EXPECT_FALSE(i.get(store, "not-present")); + f = std::get(*i.get(store, "f")); + m = std::get(*i.get(store, "m")); + t = std::get
(*i.get(store, "t")); + g = std::get(*i.get(store, "g")); + + EXPECT_TRUE(i.get(store, 0)); + EXPECT_TRUE(i.get(store, 1)); + EXPECT_TRUE(i.get(store, 2)); + EXPECT_TRUE(i.get(store, 3)); + EXPECT_FALSE(i.get(store, 4)); + auto [name, func] = *i.get(store, 0); + EXPECT_EQ(name, "f"); +} + +TEST(Linker, Smoke) { + Engine engine; + Linker linker(engine); + Store store(engine); + linker.allow_shadowing(false); + Global g = unwrap(Global::create(store, GlobalType(ValKind::I32, false), 1)); + unwrap(linker.define(store, "a", "g", g)); + unwrap(linker.define_wasi()); + unwrap(linker.func_new( + "a", "f", FuncType({}, {}), + [](auto caller, auto params, auto results) -> auto{ + return std::monostate(); + })); + unwrap(linker.func_wrap("a", "f2", []() {})); + unwrap(linker.func_wrap("a", "f3", [](Caller arg) {})); + unwrap(linker.func_wrap("a", "f4", [](Caller arg, int32_t a) {})); + Module mod = unwrap(Module::compile(engine, "(module)")); + Instance i = unwrap(Instance::create(store, mod, {})); + unwrap(linker.define_instance(store, "x", i)); + unwrap(linker.instantiate(store, mod)); + unwrap(linker.module(store, "y", mod)); + EXPECT_TRUE(linker.get(store, "a", "g")); + unwrap(linker.get_default(store, "g")); + EXPECT_TRUE(linker.get(store, "a", "f")); + EXPECT_TRUE(std::holds_alternative(*linker.get(store, "a", "f"))); +} + +TEST(Linker, CallableMove) { + Engine engine; + Linker linker(engine); + Store store(engine); + linker.allow_shadowing(false); + + struct CallableFunc { + CallableFunc() = default; + CallableFunc(const CallableFunc&) = delete; + CallableFunc(CallableFunc&&) = default; + + Result operator()(Caller caller, Span params, Span results) { + return std::monostate(); + } + }; + + CallableFunc cf; + unwrap(linker.func_new("a", "f", FuncType({}, {}), std::move(cf))); +} + +TEST(Linker, CallableCopy) { + Engine engine; + Linker linker(engine); + Store store(engine); + linker.allow_shadowing(false); + + struct CallableFunc { + CallableFunc() = default; + CallableFunc(const CallableFunc&) = default; + CallableFunc(CallableFunc&&) = default; + + Result operator()(Caller caller, Span params, Span results) { + return std::monostate(); + } + }; + + CallableFunc cf; + unwrap(linker.func_new("a", "f", FuncType({}, {}), cf)); +} + +TEST(Caller, Smoke) { + Engine engine; + Store store(engine); + Func f( + store, FuncType({}, {}), + [](auto caller, auto params, auto results) -> auto{ + EXPECT_FALSE(caller.get_export("foo")); + return std::monostate(); + }); + unwrap(f.call(store, {})); + + Module m = unwrap(Module::compile(engine, "(module " + "(import \"\" \"\" (func))" + "(memory (export \"m\") 1)" + "(func (export \"f\") call 0)" + ")")); + Func f2( + store, FuncType({}, {}), + [](auto caller, auto params, auto results) -> auto{ + EXPECT_FALSE(caller.get_export("foo")); + EXPECT_TRUE(caller.get_export("m")); + EXPECT_TRUE(caller.get_export("f")); + Memory m = std::get(*caller.get_export("m")); + EXPECT_EQ(m.type(caller)->min(), 1); + return std::monostate(); + }); + Instance i = unwrap(Instance::create(store, m, {f2})); + f = std::get(*i.get(store, "f")); + unwrap(f.call(store, {})); +} + +TEST(Func, Smoke) { + Engine engine; + Store store(engine); + Func f( + store, FuncType({}, {}), + [](auto caller, auto params, auto results) -> auto{ + return std::monostate(); + }); + unwrap(f.call(store, {})); + + Func f2( + store, FuncType({}, {}), + [](auto caller, auto params, auto results) -> auto{ + return Trap("message"); + }); + EXPECT_EQ(f2.call(store, {}).err().message(), "message"); +} + +TEST(Data, Smoke) { + + Engine engine; + Store store(engine); + store.context().set_data(10); + Func f0(store, FuncType({}, {}), + [](auto caller, auto params, + auto results) -> Result { + auto data = std::any_cast(caller.context().get_data()); + if (data != 10) { + return Trap("message"); + } + return std::monostate(); + }); + unwrap(f0.call(store, {})); + + store.context().set_data(std::make_pair(10, -3)); + Func f1(store, FuncType({}, {}), + [](auto caller, auto params, + auto results) -> Result { + auto data = + std::any_cast>(caller.context().get_data()); + if (data.first != 10 || data.second != -3) { + return Trap("message"); + } + return std::monostate(); + }); + unwrap(f1.call(store, {})); + + store.context().set_data(std::string("hello world")); + Func f2(store, FuncType({}, {}), + [](auto caller, auto params, + auto results) -> Result { + auto data = std::any_cast(caller.context().get_data()); + if (data != "hello world") { + return Trap("message"); + } + return std::monostate(); + }); + unwrap(f2.call(store, {})); + + struct test_object { + test_object() : v(nullptr) {} + test_object(int i) : v(new int(i)) {} + test_object(const test_object &other) + : v((other.v) ? new int(*other.v) : nullptr) {} + test_object(test_object &&other) : v(other.v) { other.v = nullptr; } + ~test_object() { + if (v) { + delete v; + v = nullptr; + } + } + int *v; + }; + + test_object data(7); + store.context().set_data(&data); // by pointer + Func f3(store, FuncType({}, {}), + [](auto caller, auto params, + auto results) -> Result { + auto data = + std::any_cast(caller.context().get_data()); + if (*data->v != 7) { + return Trap("message"); + } + return std::monostate(); + }); + unwrap(f3.call(store, {})); + EXPECT_EQ(*data.v, 7); + + store.context().set_data(data); // by copy + Func f4(store, FuncType({}, {}), + [](auto caller, auto params, + auto results) -> Result { + auto data = + std::any_cast(caller.context().get_data()); + if (*data.v != 7) { + return Trap("message"); + } + return std::monostate(); + }); + unwrap(f4.call(store, {})); + EXPECT_EQ(*data.v, 7); + + store.context().set_data(std::move(data)); // by move + Func f5(store, FuncType({}, {}), + [](auto caller, auto params, + auto results) -> Result { + auto data = + std::any_cast(caller.context().get_data()); + if (*data.v != 7) { + return Trap("message"); + } + return std::monostate(); + }); + unwrap(f5.call(store, {})); + EXPECT_EQ(data.v, nullptr); +} diff --git a/crates/c-api/tests/types.cc b/crates/c-api/tests/types.cc new file mode 100644 index 000000000000..fbe448e90bd8 --- /dev/null +++ b/crates/c-api/tests/types.cc @@ -0,0 +1,138 @@ +#include +#include + +using namespace wasmtime; + +template T unwrap(Result result) { + if (result) { + return result.ok(); + } + std::cerr << "error: " << result.err().message() << "\n"; + std::abort(); +} + +TEST(ValType, Smoke) { + EXPECT_EQ(ValType(ValKind::I32)->kind(), ValKind::I32); + EXPECT_EQ(ValType(ValKind::I64)->kind(), ValKind::I64); + EXPECT_EQ(ValType(ValKind::F32)->kind(), ValKind::F32); + EXPECT_EQ(ValType(ValKind::F64)->kind(), ValKind::F64); + EXPECT_EQ(ValType(ValKind::V128)->kind(), ValKind::V128); + EXPECT_EQ(ValType(ValKind::FuncRef)->kind(), ValKind::FuncRef); + EXPECT_EQ(ValType(ValKind::ExternRef)->kind(), ValKind::ExternRef); + + ValType t(ValKind::I32); + t = ValKind::I64; + ValType t2(ValKind::F32); + t = t2; + ValType t3(t2); + + ValType t4(**t); + ValType::Ref r(t4); +} + +TEST(MemoryType, Smoke) { + MemoryType t(1); + + EXPECT_EQ(t->min(), 1); + EXPECT_EQ(t->max(), std::nullopt); + MemoryType t2 = t; + t2 = t; +} + +TEST(TableType, Smoke) { + TableType t(ValKind::FuncRef, 1); + + EXPECT_EQ(t->min(), 1); + EXPECT_EQ(t->max(), std::nullopt); + EXPECT_EQ(t->element().kind(), ValKind::FuncRef); + + TableType t2 = t; + t2 = t; +} + +TEST(GlobalType, Smoke) { + GlobalType t(ValKind::FuncRef, true); + + EXPECT_EQ(t->content().kind(), ValKind::FuncRef); + EXPECT_TRUE(t->is_mutable()); + + GlobalType t2 = t; + t2 = t; +} + +TEST(FuncType, Smoke) { + FuncType t({}, {}); + EXPECT_EQ(t->params().size(), 0); + EXPECT_EQ(t->results().size(), 0); + + auto other = t; + other = t; + + FuncType t2({ValKind::I32}, {ValKind::I64}); + EXPECT_EQ(t2->params().size(), 1); + for (auto ty : t2->params()) { + EXPECT_EQ(ty.kind(), ValKind::I32); + } + EXPECT_EQ(t2->results().size(), 1); + for (auto ty : t2->results()) { + EXPECT_EQ(ty.kind(), ValKind::I64); + } +} + +TEST(ModuleType, Smoke) { + Engine engine; + Module module = unwrap(Module::compile(engine, "(module)")); + EXPECT_EQ(module.imports().size(), 0); + EXPECT_EQ(module.exports().size(), 0); + + module = + unwrap(Module::compile(engine, "(module" + "(import \"a\" \"b\" (func))" + "(global (export \"x\") i32 (i32.const 0))" + ")")); + + auto imports = module.imports(); + EXPECT_EQ(imports.size(), 1); + auto i = *imports.begin(); + EXPECT_EQ(i.module(), "a"); + EXPECT_EQ(i.name(), "b"); + auto import_ty = std::get(ExternType::from_import(i)); + EXPECT_EQ(import_ty.params().size(), 0); + EXPECT_EQ(import_ty.results().size(), 0); + + for (auto &imp : imports) { + } + + auto exports = module.exports(); + EXPECT_EQ(exports.size(), 1); + auto e = *exports.begin(); + EXPECT_EQ(e.name(), "x"); + auto export_ty = std::get(ExternType::from_export(e)); + EXPECT_EQ(export_ty.content().kind(), ValKind::I32); + EXPECT_FALSE(export_ty.is_mutable()); + + for (auto &exp : exports) { + } + + auto other_imports = module.imports(); + other_imports = std::move(imports); + ImportType::List last_imports(std::move(other_imports)); + + auto other_exports = module.exports(); + other_exports = std::move(exports); + ExportType::List last_exports(std::move(other_exports)); +} + +TEST(MemoryType, SixtyFour) { + MemoryType t(1); + EXPECT_FALSE(t->is_64()); + t = MemoryType::New64(1); + EXPECT_TRUE(t->is_64()); + EXPECT_EQ(t->min(), 1); + EXPECT_EQ(t->max(), std::nullopt); + + t = MemoryType::New64(0x100000000, 0x100000001); + EXPECT_TRUE(t->is_64()); + EXPECT_EQ(t->min(), 0x100000000); + EXPECT_EQ(t->max(), 0x100000001); +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index fd9e4b119fca..c8078ec4219b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.10) project(wasmtime-examples) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../crates/c-api ${CMAKE_CURRENT_BINARY_DIR}/wasmtime) @@ -55,13 +55,20 @@ create_rust_wasm(component wasm32-unknown-unknown) create_target(anyref anyref.c) create_target(async async.cpp) create_target(externref externref.c) +create_target(externref-cpp externref.cc) create_target(fib-debug fib-debug/main.c) create_target(fuel fuel.c) +create_target(fuel-cpp fuel.cc) create_target(gcd gcd.c) +create_target(gcd-cpp gcd.cc) create_target(hello hello.c) +create_target(hello-cpp hello.cc) create_target(interrupt interrupt.c) +create_target(interrupt-cpp interrupt.cc) create_target(linking linking.c) +create_target(linking-cpp linking.cc) create_target(memory memory.c) +create_target(memory-cpp memory.cc) create_target(multi multi.c) create_target(multimemory multimemory.c) create_target(serialize serialize.c) diff --git a/examples/externref.cc b/examples/externref.cc new file mode 100644 index 000000000000..c11e8c921ee4 --- /dev/null +++ b/examples/externref.cc @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +using namespace wasmtime; + +std::string readFile(const char *name) { + std::ifstream watFile; + watFile.open(name); + std::stringstream strStream; + strStream << watFile.rdbuf(); + return strStream.str(); +} + +int main() { + std::cout << "Initializing...\n"; + Engine engine; + Store store(engine); + + std::cout << "Compiling module...\n"; + auto wat = readFile("examples/externref.wat"); + Module module = Module::compile(engine, wat).unwrap(); + std::cout << "Instantiating module...\n"; + Instance instance = Instance::create(store, module, {}).unwrap(); + + ExternRef externref(store, std::string("Hello, world!")); + std::any &data = externref.data(store); + std::cout << "externref data: " << std::any_cast(data) << "\n"; + + std::cout << "Touching `externref` table..\n"; + Table table = std::get
(*instance.get(store, "table")); + table.set(store, 3, externref).unwrap(); + ExternRef val = *table.get(store, 3)->externref(store); + std::cout << "externref data: " << std::any_cast(val.data(store)) + << "\n"; + + std::cout << "Touching `externref` global..\n"; + Global global = std::get(*instance.get(store, "global")); + global.set(store, externref).unwrap(); + val = *global.get(store).externref(store); + std::cout << "externref data: " << std::any_cast(val.data(store)) + << "\n"; + + std::cout << "Calling `externref` func..\n"; + Func func = std::get(*instance.get(store, "func")); + auto results = func.call(store, {externref}).unwrap(); + val = *results[0].externref(store); + std::cout << "externref data: " << std::any_cast(val.data(store)) + << "\n"; + + std::cout << "Running a gc..\n"; + store.context().gc(); +} diff --git a/examples/fuel.cc b/examples/fuel.cc new file mode 100644 index 000000000000..8ce579cbfc7a --- /dev/null +++ b/examples/fuel.cc @@ -0,0 +1,63 @@ +/* +Example of instantiating of the WebAssembly module and invoking its exported +function. + +You can compile and run this example on Linux with: + + cargo build --release -p wasmtime-c-api + c++ examples/fuel.cc -std=c++20 \ + -I crates/c-api/include \ + -I crates/c-api/wasm-c-api/include \ + target/release/libwasmtime.a \ + -lpthread -ldl -lm \ + -o fuel + ./fuel + +Note that on Windows and macOS the command will be similar, but you'll need +to tweak the `-lpthread` and such annotations. +*/ + +#include +#include +#include +#include + +using namespace wasmtime; + +std::string readFile(const char *name) { + std::ifstream watFile; + watFile.open(name); + std::stringstream strStream; + strStream << watFile.rdbuf(); + return strStream.str(); +} + +const size_t kStoreFuel = 10000; + +int main() { + Config config; + config.consume_fuel(true); + Engine engine(std::move(config)); + Store store(engine); + store.context().set_fuel(kStoreFuel).unwrap(); + + auto wat = readFile("examples/fuel.wat"); + Module module = Module::compile(engine, wat).unwrap(); + Instance instance = Instance::create(store, module, {}).unwrap(); + Func fib = std::get(*instance.get(store, "fibonacci")); + + // Call it repeatedly until it fails + for (int32_t n = 1;; n++) { + auto result = fib.call(store, {n}); + if (!result) { + std::cout << "Exhausted fuel computing fib(" << n << ")\n"; + break; + } + uint64_t consumed = kStoreFuel - store.context().get_fuel().unwrap(); + auto fib_result = std::move(result).unwrap()[0].i32(); + + std::cout << "fib(" << n << ") = " << fib_result << " [consumed " + << consumed << " fuel]\n"; + store.context().set_fuel(kStoreFuel).unwrap(); + } +} diff --git a/examples/gcd.cc b/examples/gcd.cc new file mode 100644 index 000000000000..6b4151c78386 --- /dev/null +++ b/examples/gcd.cc @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +using namespace wasmtime; + +std::string readFile(const char *name) { + std::ifstream watFile; + watFile.open(name); + std::stringstream strStream; + strStream << watFile.rdbuf(); + return strStream.str(); +} + +int main() { + // Load our WebAssembly (parsed WAT in our case), and then load it into a + // `Module` which is attached to a `Store`. After we've got that we + // can instantiate it. + Engine engine; + Store store(engine); + auto module = Module::compile(engine, readFile("examples/gcd.wat")).unwrap(); + auto instance = Instance::create(store, module, {}).unwrap(); + + // Invoke `gcd` export + auto gcd = std::get(*instance.get(store, "gcd")); + auto results = gcd.call(store, {6, 27}).unwrap(); + + std::cout << "gcd(6, 27) = " << results[0].i32() << "\n"; +} diff --git a/examples/hello.cc b/examples/hello.cc new file mode 100644 index 000000000000..2362ba7856ef --- /dev/null +++ b/examples/hello.cc @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +using namespace wasmtime; + +std::string readFile(const char *name) { + std::ifstream watFile; + watFile.open(name); + std::stringstream strStream; + strStream << watFile.rdbuf(); + return strStream.str(); +} + +int main() { + // First the wasm module needs to be compiled. This is done with a global + // "compilation environment" within an `Engine`. Note that engines can be + // further configured through `Config` if desired instead of using the + // default like this is here. + std::cout << "Compiling module\n"; + Engine engine; + auto module = + Module::compile(engine, readFile("examples/hello.wat")).unwrap(); + + // After a module is compiled we create a `Store` which will contain + // instantiated modules and other items like host functions. A Store + // contains an arbitrary piece of host information, and we use `MyState` + // here. + std::cout << "Initializing...\n"; + Store store(engine); + + // Our wasm module we'll be instantiating requires one imported function. + // the function takes no parameters and returns no results. We create a host + // implementation of that function here. + std::cout << "Creating callback...\n"; + Func host_func = + Func::wrap(store, []() { std::cout << "Calling back...\n"; }); + + // Once we've got that all set up we can then move to the instantiation + // phase, pairing together a compiled module as well as a set of imports. + // Note that this is where the wasm `start` function, if any, would run. + std::cout << "Instantiating module...\n"; + auto instance = Instance::create(store, module, {host_func}).unwrap(); + + // Next we poke around a bit to extract the `run` function from the module. + std::cout << "Extracting export...\n"; + auto run = std::get(*instance.get(store, "run")); + + // And last but not least we can call it! + std::cout << "Calling export...\n"; + run.call(store, {}).unwrap(); + + std::cout << "Done\n"; +} diff --git a/examples/interrupt.cc b/examples/interrupt.cc new file mode 100644 index 000000000000..d3a96538e1a1 --- /dev/null +++ b/examples/interrupt.cc @@ -0,0 +1,66 @@ +/* +Example of instantiating of the WebAssembly module and invoking its exported +function. + +You can compile and run this example on Linux with: + + cargo build --release -p wasmtime-c-api + c++ examples/interrupt.cc -std=c++20 \ + -I crates/c-api/include \ + -I crates/c-api/wasm-c-api/include \ + target/release/libwasmtime.a \ + -lpthread -ldl -lm \ + -o interrupt + ./interrupt + +Note that on Windows and macOS the command will be similar, but you'll need +to tweak the `-lpthread` and such annotations as well as the name of the +`libwasmtime.a` file on Windows. +*/ + +#include +#include +#include +#include +#include +#include + +using namespace wasmtime; + +std::string readFile(const char *name) { + std::ifstream watFile; + watFile.open(name); + std::stringstream strStream; + strStream << watFile.rdbuf(); + return strStream.str(); +} + +int main() { + // Enable interruptible code via `Config` and then create an interrupt + // handle which we'll use later to interrupt running code. + Config config; + config.epoch_interruption(true); + Engine engine(std::move(config)); + Store store(engine); + store.context().set_epoch_deadline(1); + + // Compile and instantiate a small example with an infinite loop. + auto wat = readFile("examples/interrupt.wat"); + Module module = Module::compile(engine, wat).unwrap(); + Instance instance = Instance::create(store, module, {}).unwrap(); + Func run = std::get(*instance.get(store, "run")); + + // Spin up a thread to send us an interrupt in a second + std::thread t([engine{std::move(engine)}]() { + std::this_thread::sleep_for(std::chrono::seconds(1)); + std::cout << "Interrupting!\n"; + engine.increment_epoch(); + }); + + std::cout << "Entering infinite loop ...\n"; + auto err = run.call(store, {}).err(); + auto &trap = std::get(err.data); + + std::cout << "trap: " << trap.message() << "\n"; + t.join(); +} diff --git a/examples/linking.cc b/examples/linking.cc new file mode 100644 index 000000000000..70674bf8baa9 --- /dev/null +++ b/examples/linking.cc @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +using namespace wasmtime; + +template T unwrap(Result result) { + if (result) { + return result.ok(); + } + std::cerr << "error: " << result.err().message() << "\n"; + std::abort(); +} + +std::string readFile(const char *name) { + std::ifstream watFile; + watFile.open(name); + std::stringstream strStream; + strStream << watFile.rdbuf(); + return strStream.str(); +} + +int main() { + Engine engine; + Store store(engine); + + // Read our input `*.wat` files into `std::string`s + std::string linking1_wat = readFile("examples/linking1.wat"); + std::string linking2_wat = readFile("examples/linking2.wat"); + + // Compile our two modules + Module linking1_module = Module::compile(engine, linking1_wat).unwrap(); + Module linking2_module = Module::compile(engine, linking2_wat).unwrap(); + + // Configure WASI and store it within our `wasmtime_store_t` + WasiConfig wasi; + wasi.inherit_argv(); + wasi.inherit_env(); + wasi.inherit_stdin(); + wasi.inherit_stdout(); + wasi.inherit_stderr(); + store.context().set_wasi(std::move(wasi)).unwrap(); + + // Create our linker which will be linking our modules together, and then add + // our WASI instance to it. + Linker linker(engine); + linker.define_wasi().unwrap(); + + // Instantiate our first module which only uses WASI, then register that + // instance with the linker since the next linking will use it. + Instance linking2 = linker.instantiate(store, linking2_module).unwrap(); + linker.define_instance(store, "linking2", linking2).unwrap(); + + // And with that we can perform the final link and the execute the module. + Instance linking1 = linker.instantiate(store, linking1_module).unwrap(); + Func f = std::get(*linking1.get(store, "run")); + f.call(store, {}).unwrap(); +} diff --git a/examples/memory.cc b/examples/memory.cc new file mode 100644 index 000000000000..d2802a74a6ec --- /dev/null +++ b/examples/memory.cc @@ -0,0 +1,79 @@ +#undef NDEBUG + +#include +#include +#include +#include + +using namespace wasmtime; + +std::string readFile(const char *name) { + std::ifstream watFile; + watFile.open(name); + std::stringstream strStream; + strStream << watFile.rdbuf(); + return strStream.str(); +} + +int main() { + // Create our `store` context and then compile a module and create an + // instance from the compiled module all in one go. + Engine engine; + Module module = + Module::compile(engine, readFile("examples/memory.wat")).unwrap(); + Store store(engine); + Instance instance = Instance::create(store, module, {}).unwrap(); + + // load_fn up our exports from the instance + auto memory = std::get(*instance.get(store, "memory")); + auto size = std::get(*instance.get(store, "size")); + auto load_fn = std::get(*instance.get(store, "load")); + auto store_fn = std::get(*instance.get(store, "store")); + + std::cout << "Checking memory...\n"; + assert(memory.size(store) == 2); + auto data = memory.data(store); + assert(data.size() == 0x20000); + assert(data[0] == 0); + assert(data[0x1000] == 1); + assert(data[0x1003] == 4); + + assert(size.call(store, {}).unwrap()[0].i32() == 2); + assert(load_fn.call(store, {0}).unwrap()[0].i32() == 0); + assert(load_fn.call(store, {0x1000}).unwrap()[0].i32() == 1); + assert(load_fn.call(store, {0x1003}).unwrap()[0].i32() == 4); + assert(load_fn.call(store, {0x1ffff}).unwrap()[0].i32() == 0); + load_fn.call(store, {0x20000}).err(); // out of bounds trap + + std::cout << "Mutating memory...\n"; + memory.data(store)[0x1003] = 5; + + store_fn.call(store, {0x1002, 6}).unwrap(); + store_fn.call(store, {0x20000, 0}).err(); // out of bounds trap + + assert(memory.data(store)[0x1002] == 6); + assert(memory.data(store)[0x1003] == 5); + assert(load_fn.call(store, {0x1002}).unwrap()[0].i32() == 6); + assert(load_fn.call(store, {0x1003}).unwrap()[0].i32() == 5); + + // Grow memory. + std::cout << "Growing memory...\n"; + memory.grow(store, 1).unwrap(); + assert(memory.size(store) == 3); + assert(memory.data(store).size() == 0x30000); + + assert(load_fn.call(store, {0x20000}).unwrap()[0].i32() == 0); + store_fn.call(store, {0x20000, 0}).unwrap(); + load_fn.call(store, {0x30000}).err(); + store_fn.call(store, {0x30000, 0}).err(); + + memory.grow(store, 1).err(); + memory.grow(store, 0).ok(); + + std::cout << "Creating stand-alone memory...\n"; + MemoryType ty(5, 5); + Memory memory2 = Memory::create(store, ty).unwrap(); + assert(memory2.size(store) == 5); + memory2.grow(store, 1).err(); + memory2.grow(store, 0).ok(); +}