From 35b3b0812f872700df9675e54ca6d7341fc10b3a Mon Sep 17 00:00:00 2001 From: Rachel Green Date: Fri, 1 Aug 2025 15:38:26 +0000 Subject: [PATCH 1/7] Support creating warmed VM in the proxy wasm V8 VM API. Signed-off-by: Rachel Green --- include/proxy-wasm/null_vm.h | 2 + include/proxy-wasm/wasm_vm.h | 5 + include/proxy-wasm/wasm_vm.h.orig | 376 +++++++++++++++ src/v8/v8.cc | 16 +- src/v8/v8.cc.orig | 746 ++++++++++++++++++++++++++++++ 5 files changed, 1144 insertions(+), 1 deletion(-) create mode 100644 include/proxy-wasm/wasm_vm.h.orig create mode 100644 src/v8/v8.cc.orig diff --git a/include/proxy-wasm/null_vm.h b/include/proxy-wasm/null_vm.h index a0a3798ff..703266df3 100644 --- a/include/proxy-wasm/null_vm.h +++ b/include/proxy-wasm/null_vm.h @@ -63,6 +63,8 @@ struct NullVm : public WasmVm { void terminate() override {} bool usesWasmByteOrder() override { return false; } + void warm() override {} + std::string plugin_name_; std::unique_ptr plugin_; }; diff --git a/include/proxy-wasm/wasm_vm.h b/include/proxy-wasm/wasm_vm.h index db54ebd86..c14624ac0 100644 --- a/include/proxy-wasm/wasm_vm.h +++ b/include/proxy-wasm/wasm_vm.h @@ -308,6 +308,11 @@ class WasmVm { */ virtual bool usesWasmByteOrder() = 0; + /** + * Warm the VM such as engine and runtime. + */ + virtual void warm() = 0; + bool isFailed() { return failed_ != FailState::Ok; } void fail(FailState fail_state, std::string_view message) { integration()->error(message); diff --git a/include/proxy-wasm/wasm_vm.h.orig b/include/proxy-wasm/wasm_vm.h.orig new file mode 100644 index 000000000..db54ebd86 --- /dev/null +++ b/include/proxy-wasm/wasm_vm.h.orig @@ -0,0 +1,376 @@ +// Copyright 2016-2019 Envoy Project Authors +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/proxy-wasm/sdk.h" +#include "include/proxy-wasm/word.h" + +namespace proxy_wasm { + +class ContextBase; + +// These are templates and its helper for constructing signatures of functions calling into Wasm +// VMs. +// - WasmCallInFuncTypeHelper is a helper for WasmFuncType and shouldn't be used anywhere else than +// WasmFuncType definition. +// - WasmCallInFuncType takes 4 template parameter which are number of argument, return type, +// context type and param type respectively, resolve to a function type. +// For example `WasmFuncType<3, void, Context*, Word>` resolves to `void(Context*, Word, Word, +// Word)` +template +struct WasmCallInFuncTypeHelper {}; + +template +struct WasmCallInFuncTypeHelper { + // NOLINTNEXTLINE(readability-identifier-naming) + using type = typename WasmCallInFuncTypeHelper::type; +}; + +template +struct WasmCallInFuncTypeHelper<0, ReturnType, ContextType, ParamType, + ReturnType(ContextType, Args...)> { + using type = ReturnType(ContextType, Args...); // NOLINT(readability-identifier-naming) +}; + +template +using WasmCallInFuncType = + typename WasmCallInFuncTypeHelper::type; + +// Calls into the WASM VM. +// 1st arg is always a pointer to Context (Context*). +template +using WasmCallVoid = std::function>; +template +using WasmCallWord = std::function>; + +#define FOR_ALL_WASM_VM_EXPORTS(_f) \ + _f(proxy_wasm::WasmCallVoid<0>) _f(proxy_wasm::WasmCallVoid<1>) _f(proxy_wasm::WasmCallVoid<2>) \ + _f(proxy_wasm::WasmCallVoid<3>) _f(proxy_wasm::WasmCallVoid<5>) \ + _f(proxy_wasm::WasmCallWord<1>) _f(proxy_wasm::WasmCallWord<2>) \ + _f(proxy_wasm::WasmCallWord<3>) + +// These are templates and its helper for constructing signatures of functions callbacks from Wasm +// VMs. +// - WasmCallbackFuncTypeHelper is a helper for WasmFuncType and shouldn't be used anywhere else +// than WasmFuncType definition. +// - WasmCallbackFuncType takes 3 template parameter which are number of argument, return type, and +// param type respectively, resolve to a function type. +// For example `WasmFuncType<3, Word>` resolves to `void(Word, Word, Word)` +template +struct WasmCallbackFuncTypeHelper {}; + +template +struct WasmCallbackFuncTypeHelper { + // NOLINTNEXTLINE(readability-identifier-naming) + using type = typename WasmCallbackFuncTypeHelper::type; +}; + +template +struct WasmCallbackFuncTypeHelper<0, ReturnType, ParamType, ReturnType(Args...)> { + using type = ReturnType(Args...); // NOLINT(readability-identifier-naming) +}; + +template +using WasmCallbackFuncType = typename WasmCallbackFuncTypeHelper::type; + +// Calls out of the WASM VM. +template using WasmCallbackVoid = WasmCallbackFuncType *; +template using WasmCallbackWord = WasmCallbackFuncType *; + +// Using the standard g++/clang mangling algorithm: +// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-builtin +// Extended with W = Word +// Z = void, j = uint32_t, l = int64_t, m = uint64_t +using WasmCallback_WWl = Word (*)(Word, int64_t); +using WasmCallback_WWlWW = Word (*)(Word, int64_t, Word, Word); +using WasmCallback_WWm = Word (*)(Word, uint64_t); +using WasmCallback_WWmW = Word (*)(Word, uint64_t, Word); +using WasmCallback_WWWWWWllWW = Word (*)(Word, Word, Word, Word, Word, int64_t, int64_t, Word, + Word); +using WasmCallback_dd = double (*)(double); + +#define FOR_ALL_WASM_VM_IMPORTS(_f) \ + _f(proxy_wasm::WasmCallbackVoid<0>) _f(proxy_wasm::WasmCallbackVoid<1>) \ + _f(proxy_wasm::WasmCallbackVoid<2>) _f(proxy_wasm::WasmCallbackVoid<3>) \ + _f(proxy_wasm::WasmCallbackVoid<4>) _f(proxy_wasm::WasmCallbackWord<0>) \ + _f(proxy_wasm::WasmCallbackWord<1>) _f(proxy_wasm::WasmCallbackWord<2>) \ + _f(proxy_wasm::WasmCallbackWord<3>) _f(proxy_wasm::WasmCallbackWord<4>) \ + _f(proxy_wasm::WasmCallbackWord<5>) _f(proxy_wasm::WasmCallbackWord<6>) \ + _f(proxy_wasm::WasmCallbackWord<7>) _f(proxy_wasm::WasmCallbackWord<8>) \ + _f(proxy_wasm::WasmCallbackWord<9>) \ + _f(proxy_wasm::WasmCallbackWord<10>) \ + _f(proxy_wasm::WasmCallbackWord<12>) \ + _f(proxy_wasm::WasmCallback_WWl) \ + _f(proxy_wasm::WasmCallback_WWlWW) \ + _f(proxy_wasm::WasmCallback_WWm) \ + _f(proxy_wasm::WasmCallback_WWmW) \ + _f(proxy_wasm::WasmCallback_WWWWWWllWW) \ + _f(proxy_wasm::WasmCallback_dd) + +enum class Cloneable { + NotCloneable, // VMs can not be cloned and should be created from scratch. + CompiledBytecode, // VMs can be cloned with compiled bytecode. + InstantiatedModule // VMs can be cloned from an instantiated module. +}; + +enum class AbiVersion { ProxyWasm_0_1_0, ProxyWasm_0_2_0, ProxyWasm_0_2_1, Unknown }; + +class NullPlugin; + +// Integrator specific WasmVm operations. +struct WasmVmIntegration { + virtual ~WasmVmIntegration() {} + virtual WasmVmIntegration *clone() = 0; + virtual proxy_wasm::LogLevel getLogLevel() = 0; + virtual void error(std::string_view message) = 0; + virtual void trace(std::string_view message) = 0; + // Get a NullVm implementation of a function. + // @param function_name is the name of the function with the implementation specific prefix. + // @param returns_word is true if the function returns a Word and false if it returns void. + // @param number_of_arguments is the number of Word arguments to the function. + // @param plugin is the Null VM plugin on which the function will be called. + // @param ptr_to_function_return is the location to write the function e.g. of type + // WasmCallWord<3>. + // @return true if the function was found. ptr_to_function_return could still be set to nullptr + // (of the correct type) if the function has no implementation. Returning true will prevent a + // "Missing getFunction" error. + virtual bool getNullVmFunction(std::string_view function_name, bool returns_word, + int number_of_arguments, NullPlugin *plugin, + void *ptr_to_function_return) = 0; +}; + +enum class FailState : int { + Ok = 0, + UnableToCreateVm = 1, + UnableToCloneVm = 2, + MissingFunction = 3, + UnableToInitializeCode = 4, + StartFailed = 5, + ConfigureFailed = 6, + RuntimeError = 7, +}; + +// Wasm VM instance. Provides the low level WASM interface. +class WasmVm { +public: + virtual ~WasmVm() = default; + /** + * Identify the Wasm engine. + * @return the name of the underlying Wasm engine. + */ + virtual std::string_view getEngineName() = 0; + + /** + * Whether or not the VM implementation supports cloning. Cloning is VM system dependent. + * When a VM is configured a single VM is instantiated to check that the .wasm file is valid and + * to do VM system specific initialization. Then, if cloning is supported, we clone that VM for + * each worker, potentially copying and sharing the initialized data structures for efficiency. + * Otherwise we create an new VM from scratch for each worker. + * @return one of enum Cloneable with the VMs cloneability. + */ + virtual Cloneable cloneable() = 0; + + /** + * Make a worker/thread-specific copy if supported by the underlying VM system (see cloneable() + * above). If not supported, the caller will need to create a new VM from scratch. If supported, + * the clone may share compiled code and other read-only data with the source VM. + * @return a clone of 'this' (e.g. for a different worker/thread). + */ + virtual std::unique_ptr clone() = 0; + + /** + * Load the WASM code from a file. Return true on success. Once the module is loaded it can be + * queried, e.g. to see which version of emscripten support is required. After loading, the + * appropriate ABI callbacks can be registered and then the module can be link()ed (see below). + * @param bytecode the Wasm bytecode or registered NullVm plugin name. + * @param precompiled (optional) the precompiled Wasm module. + * @param function_names (optional) an index-to-name mapping for the functions. + * @return whether or not the load was successful. + */ + virtual bool load(std::string_view bytecode, std::string_view precompiled, + const std::unordered_map &function_names) = 0; + + /** + * Link the WASM code to the host-provided functions, e.g. the ABI. Prior to linking, the module + * should be loaded and the ABI callbacks registered (see above). Linking should be done once + * after load(). + * @param debug_name user-provided name for use in log and error messages. + * @return whether or not the link was successful. + */ + virtual bool link(std::string_view debug_name) = 0; + + /** + * Get size of the currently allocated memory in the VM. + * @return the size of memory in bytes. + */ + virtual uint64_t getMemorySize() = 0; + + /** + * Convert a block of memory in the VM to a std::string_view. + * @param pointer the offset into VM memory of the requested VM memory block. + * @param size the size of the requested VM memory block. + * @return if std::nullopt then the pointer/size pair were invalid, otherwise returns + * a host std::string_view pointing to the pointer/size pair in VM memory. + */ + virtual std::optional getMemory(uint64_t pointer, uint64_t size) = 0; + + /** + * Set a block of memory in the VM, returns true on success, false if the pointer/size is invalid. + * @param pointer the offset into VM memory describing the start of a region of VM memory. + * @param size the size of the region of VM memory. + * @return whether or not the pointer/size pair was a valid VM memory block. + */ + virtual bool setMemory(uint64_t pointer, uint64_t size, const void *data) = 0; + + /** + * Get a VM native Word (e.g. sizeof(void*) or sizeof(size_t)) from VM memory, returns true on + * success, false if the pointer is invalid. WASM-32 VMs have 32-bit native words and WASM-64 VMs + * (not yet supported) will have 64-bit words as does the Null VM (compiled into a 64-bit proxy). + * This function can be used to chase pointers in VM memory. + * @param pointer the offset into VM memory describing the start of VM native word size block. + * @param data a pointer to a Word whose contents will be filled from the VM native word at + * 'pointer'. + * @return whether or not the pointer was to a valid VM memory block of VM native word size. + */ + virtual bool getWord(uint64_t pointer, Word *data) = 0; + + /** + * Set a Word in the VM, returns true on success, false if the pointer is invalid. + * See getWord above for details. This function can be used (for example) to set indirect pointer + * return values (e.g. proxy_getHeaderHapValue(... const char** value_ptr, size_t* value_size). + * @param pointer the offset into VM memory describing the start of VM native word size block. + * @param data a Word whose contents will be written in VM native word size at 'pointer'. + * @return whether or not the pointer was to a valid VM memory block of VM native word size. + */ + virtual bool setWord(uint64_t pointer, Word data) = 0; + + /** + * @return the Word size in this VM. + */ + virtual size_t getWordSize() = 0; + + /** + * Get the name of the custom section that contains precompiled module. + * @return the name of the custom section that contains precompiled module. + */ + virtual std::string_view getPrecompiledSectionName() = 0; + + /** + * Get typed function exported by the WASM module. + */ +#define _GET_FUNCTION(_T) virtual void getFunction(std::string_view function_name, _T *f) = 0; + FOR_ALL_WASM_VM_EXPORTS(_GET_FUNCTION) +#undef _GET_FUNCTION + + /** + * Register typed callbacks exported by the host environment. + */ +#define _REGISTER_CALLBACK(_T) \ + virtual void registerCallback(std::string_view moduleName, std::string_view function_name, _T f, \ + typename ConvertFunctionTypeWordToUint32<_T>::type) = 0; + FOR_ALL_WASM_VM_IMPORTS(_REGISTER_CALLBACK) +#undef _REGISTER_CALLBACK + + /** + * Terminate execution of this WasmVM. It shouldn't be used after being terminated. + */ + virtual void terminate() = 0; + + /** + * Byte order flag (host or wasm). + * @return 'false' for a null VM and 'true' for a wasm VM. + */ + virtual bool usesWasmByteOrder() = 0; + + bool isFailed() { return failed_ != FailState::Ok; } + void fail(FailState fail_state, std::string_view message) { + integration()->error(message); + failed_ = fail_state; + for (auto &callback : fail_callbacks_) { + callback(fail_state); + } + } + void addFailCallback(std::function fail_callback) { + fail_callbacks_.push_back(fail_callback); + } + + bool isHostFunctionAllowed(const std::string &name) { + return !restricted_callback_ || allowed_hostcalls_.find(name) != allowed_hostcalls_.end(); + } + + void setRestrictedCallback(bool restricted, + std::unordered_set allowed_hostcalls = {}) { + restricted_callback_ = restricted; + allowed_hostcalls_ = std::move(allowed_hostcalls); + } + + // Integrator operations. + std::unique_ptr &integration() { return integration_; } + bool cmpLogLevel(proxy_wasm::LogLevel level) { return integration_->getLogLevel() <= level; } + +protected: + std::unique_ptr integration_; + FailState failed_ = FailState::Ok; + std::vector> fail_callbacks_; + +private: + bool restricted_callback_{false}; + std::unordered_set allowed_hostcalls_{}; +}; + +// Thread local state set during a call into a WASM VM so that calls coming out of the +// VM can be attributed correctly to calling Filter. We use thread_local instead of ThreadLocal +// because this state is live only during the calls and does not need to be initialized consistently +// over all workers as with ThreadLocal data. +extern thread_local ContextBase *current_context_; + +// Requested effective context set by code within the VM to request that the calls coming out of the +// VM be attributed to another filter, for example if a control plane gRPC comes back to the +// RootContext which effects some set of waiting filters. +extern thread_local uint32_t effective_context_id_; + +// Helper to save and restore thread local VM call context information to support reentrant calls. +// NB: this happens for example when a call from the VM invokes a handler which needs to _malloc +// memory in the VM. +struct SaveRestoreContext { + explicit SaveRestoreContext(ContextBase *context) { + saved_context = current_context_; + saved_effective_context_id_ = effective_context_id_; + current_context_ = context; + effective_context_id_ = 0; // No effective context id. + } + ~SaveRestoreContext() { + current_context_ = saved_context; + effective_context_id_ = saved_effective_context_id_; + } + ContextBase *saved_context; + uint32_t saved_effective_context_id_; +}; + +} // namespace proxy_wasm diff --git a/src/v8/v8.cc b/src/v8/v8.cc index bc5b82850..f51cdfffa 100644 --- a/src/v8/v8.cc +++ b/src/v8/v8.cc @@ -103,6 +103,8 @@ class V8 : public WasmVm { void terminate() override; bool usesWasmByteOrder() override { return true; } + void warm() override; + private: wasm::own trap(std::string message); @@ -124,6 +126,9 @@ class V8 : public WasmVm { void getModuleFunctionImpl(std::string_view function_name, std::function *function); + // Initialize the V8 engine and store if necessary. + void initStore(); + wasm::own store_; wasm::own module_; wasm::own> shared_module_; @@ -260,9 +265,16 @@ template constexpr T convertValTypesToArgsTuple(const U // V8 implementation. +void V8::initStore() { + if (store_ != nullptr) { + return; + } + store_ = wasm::Store::make(engine()); +} + bool V8::load(std::string_view bytecode, std::string_view precompiled, const std::unordered_map &function_names) { - store_ = wasm::Store::make(engine()); + initStore(); if (store_ == nullptr) { return false; } @@ -708,6 +720,8 @@ void V8::terminate() { isolate->TerminateExecution(); } +void V8::warm() { initStore(); } + std::string V8::getFailMessage(std::string_view function_name, wasm::own trap) { auto message = "Function: " + std::string(function_name) + " failed: "; message += std::string(trap->message().get(), trap->message().size()); diff --git a/src/v8/v8.cc.orig b/src/v8/v8.cc.orig new file mode 100644 index 000000000..bc5b82850 --- /dev/null +++ b/src/v8/v8.cc.orig @@ -0,0 +1,746 @@ +// Copyright 2016-2019 Envoy Project Authors +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "include/proxy-wasm/v8.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/proxy-wasm/limits.h" + +#include "include/v8-version.h" +#include "include/v8.h" +#include "src/flags/flags.h" +#include "src/wasm/c-api.h" +#include "wasm-api/wasm.hh" + +namespace proxy_wasm { +namespace v8 { + +wasm::Engine *engine() { + static std::once_flag init; + static wasm::own engine; + + std::call_once(init, []() { + ::v8::internal::v8_flags.liftoff = false; + ::v8::internal::v8_flags.wasm_max_mem_pages = + PROXY_WASM_HOST_MAX_WASM_MEMORY_SIZE_BYTES / PROXY_WASM_HOST_WASM_MEMORY_PAGE_SIZE_BYTES; + ::v8::V8::EnableWebAssemblyTrapHandler(true); + engine = wasm::Engine::make(); + }); + + return engine.get(); +} + +struct FuncData { + FuncData(std::string name) : name_(std::move(name)) {} + + std::string name_; + wasm::own callback_; + void *raw_func_{}; + WasmVm *vm_{}; +}; + +using FuncDataPtr = std::unique_ptr; + +class V8 : public WasmVm { +public: + V8() = default; + + // WasmVm + std::string_view getEngineName() override { return "v8"; } + + bool load(std::string_view bytecode, std::string_view precompiled, + const std::unordered_map &function_names) override; + std::string_view getPrecompiledSectionName() override; + bool link(std::string_view debug_name) override; + + Cloneable cloneable() override { return Cloneable::CompiledBytecode; } + std::unique_ptr clone() override; + + uint64_t getMemorySize() override; + std::optional getMemory(uint64_t pointer, uint64_t size) override; + bool setMemory(uint64_t pointer, uint64_t size, const void *data) override; + bool getWord(uint64_t pointer, Word *word) override; + bool setWord(uint64_t pointer, Word word) override; + size_t getWordSize() override { return sizeof(uint32_t); }; + +#define _REGISTER_HOST_FUNCTION(T) \ + void registerCallback(std::string_view module_name, std::string_view function_name, T, \ + typename ConvertFunctionTypeWordToUint32::type f) override { \ + registerHostFunctionImpl(module_name, function_name, f); \ + }; + FOR_ALL_WASM_VM_IMPORTS(_REGISTER_HOST_FUNCTION) +#undef _REGISTER_HOST_FUNCTION + +#define _GET_MODULE_FUNCTION(T) \ + void getFunction(std::string_view function_name, T *f) override { \ + getModuleFunctionImpl(function_name, f); \ + }; + FOR_ALL_WASM_VM_EXPORTS(_GET_MODULE_FUNCTION) +#undef _GET_MODULE_FUNCTION + + void terminate() override; + bool usesWasmByteOrder() override { return true; } + +private: + wasm::own trap(std::string message); + + std::string getFailMessage(std::string_view function_name, wasm::own trap); + + template + void registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, + void (*function)(Args...)); + + template + void registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, + R (*function)(Args...)); + + template + void getModuleFunctionImpl(std::string_view function_name, + std::function *function); + + template + void getModuleFunctionImpl(std::string_view function_name, + std::function *function); + + wasm::own store_; + wasm::own module_; + wasm::own> shared_module_; + wasm::own instance_; + wasm::own memory_; + wasm::own table_; + + std::unordered_map host_functions_; + std::unordered_map> module_functions_; + std::unordered_map function_names_index_; +}; + +// Helper functions. + +static std::string printValue(const wasm::Val &value) { + switch (value.kind()) { + case wasm::ValKind::I32: + return std::to_string(value.get()); + case wasm::ValKind::I64: + return std::to_string(value.get()); + case wasm::ValKind::F32: + return std::to_string(value.get()); + case wasm::ValKind::F64: + return std::to_string(value.get()); + default: + return "unknown"; + } +} + +static std::string printValues(const wasm::vec &values, size_t size) { + if (size == 0) { + return ""; + } + + std::string s; + for (size_t i = 0; i < size; i++) { + if (i != 0U) { + s.append(", "); + } + s.append(printValue(values[i])); + } + return s; +} + +static const char *printValKind(wasm::ValKind kind) { + switch (kind) { + case wasm::ValKind::I32: + return "i32"; + case wasm::ValKind::I64: + return "i64"; + case wasm::ValKind::F32: + return "f32"; + case wasm::ValKind::F64: + return "f64"; + case wasm::ValKind::EXTERNREF: + return "externref"; + case wasm::ValKind::FUNCREF: + return "funcref"; + default: + return "unknown"; + } +} + +static std::string printValTypes(const wasm::ownvec &types) { + if (types.size() == 0) { + return "void"; + } + + std::string s; + s.reserve(types.size() * 8 /* max size + " " */ - 1); + for (size_t i = 0; i < types.size(); i++) { + if (i != 0U) { + s.append(" "); + } + s.append(printValKind(types[i]->kind())); + } + return s; +} + +static bool equalValTypes(const wasm::ownvec &left, + const wasm::ownvec &right) { + if (left.size() != right.size()) { + return false; + } + for (size_t i = 0; i < left.size(); i++) { + if (left[i]->kind() != right[i]->kind()) { + return false; + } + } + return true; +} + +// Template magic. + +template struct ConvertWordType { + using type = T; // NOLINT(readability-identifier-naming) +}; +template <> struct ConvertWordType { + using type = uint32_t; // NOLINT(readability-identifier-naming) +}; + +template wasm::Val makeVal(T t) { return wasm::Val::make(t); } +template <> wasm::Val makeVal(Word t) { return wasm::Val::make(static_cast(t.u64_)); } + +template constexpr auto convertArgToValKind(); +template <> constexpr auto convertArgToValKind() { return wasm::ValKind::I32; }; +template <> constexpr auto convertArgToValKind() { return wasm::ValKind::I32; }; +template <> constexpr auto convertArgToValKind() { return wasm::ValKind::I64; }; +template <> constexpr auto convertArgToValKind() { return wasm::ValKind::I64; }; +template <> constexpr auto convertArgToValKind() { return wasm::ValKind::F64; }; + +template +constexpr auto convertArgsTupleToValTypesImpl(std::index_sequence /*comptime*/) { + return wasm::ownvec::make( + wasm::ValType::make(convertArgToValKind::type>())...); +} + +template constexpr auto convertArgsTupleToValTypes() { + return convertArgsTupleToValTypesImpl(std::make_index_sequence::value>()); +} + +template +constexpr T convertValTypesToArgsTupleImpl(const U &arr, std::index_sequence /*comptime*/) { + return std::make_tuple( + (arr[I] + .template get< + typename ConvertWordType::type>::type>())...); +} + +template constexpr T convertValTypesToArgsTuple(const U &arr) { + return convertValTypesToArgsTupleImpl(arr, + std::make_index_sequence::value>()); +} + +// V8 implementation. + +bool V8::load(std::string_view bytecode, std::string_view precompiled, + const std::unordered_map &function_names) { + store_ = wasm::Store::make(engine()); + if (store_ == nullptr) { + return false; + } + + if (!precompiled.empty()) { + auto vec = wasm::vec::make_uninitialized(precompiled.size()); + ::memcpy(vec.get(), precompiled.data(), precompiled.size()); + module_ = wasm::Module::deserialize(store_.get(), vec); + if (module_ == nullptr) { + return false; + } + + } else { + auto vec = wasm::vec::make_uninitialized(bytecode.size()); + ::memcpy(vec.get(), bytecode.data(), bytecode.size()); + module_ = wasm::Module::make(store_.get(), vec); + if (module_ == nullptr) { + return false; + } + } + + shared_module_ = module_->share(); + if (shared_module_ == nullptr) { + return false; + } + + function_names_index_ = function_names; + + return true; +} + +std::unique_ptr V8::clone() { + assert(shared_module_ != nullptr); + + auto clone = std::make_unique(); + if (clone == nullptr) { + return nullptr; + } + + clone->store_ = wasm::Store::make(engine()); + if (clone->store_ == nullptr) { + return nullptr; + } + + clone->module_ = wasm::Module::obtain(clone->store_.get(), shared_module_.get()); + if (clone->module_ == nullptr) { + return nullptr; + } + + auto *integration_clone = integration()->clone(); + if (integration_clone == nullptr) { + return nullptr; + } + clone->integration().reset(integration_clone); + + clone->function_names_index_ = function_names_index_; + + return clone; +} + +#if defined(__linux__) && defined(__x86_64__) +#define WEE8_PLATFORM "linux_x86_64" +#else +#define WEE8_PLATFORM "" +#endif + +std::string_view V8::getPrecompiledSectionName() { + static const auto name = + sizeof(WEE8_PLATFORM) - 1 > 0 + ? ("precompiled_wee8_v" + std::to_string(V8_MAJOR_VERSION) + "." + + std::to_string(V8_MINOR_VERSION) + "." + std::to_string(V8_BUILD_NUMBER) + "." + + std::to_string(V8_PATCH_LEVEL) + "_" + WEE8_PLATFORM) + : ""; + return name; +} + +bool V8::link(std::string_view /*debug_name*/) { + assert(module_ != nullptr); + + const auto import_types = module_.get()->imports(); + wasm::vec imports = + wasm::vec::make_uninitialized(import_types.size()); + + for (size_t i = 0; i < import_types.size(); i++) { + std::string_view module(import_types[i]->module().get(), import_types[i]->module().size()); + std::string_view name(import_types[i]->name().get(), import_types[i]->name().size()); + const auto *import_type = import_types[i]->type(); + + switch (import_type->kind()) { + + case wasm::ExternKind::FUNC: { + auto it = host_functions_.find(std::string(module) + "." + std::string(name)); + if (it == host_functions_.end()) { + fail(FailState::UnableToInitializeCode, + std::string("Failed to load Wasm module due to a missing import: ") + + std::string(module) + "." + std::string(name)); + return false; + } + auto *func = it->second->callback_.get(); + if (!equalValTypes(import_type->func()->params(), func->type()->params()) || + !equalValTypes(import_type->func()->results(), func->type()->results())) { + fail(FailState::UnableToInitializeCode, + std::string("Failed to load Wasm module due to an import type mismatch: ") + + std::string(module) + "." + std::string(name) + + ", want: " + printValTypes(import_type->func()->params()) + " -> " + + printValTypes(import_type->func()->results()) + + ", but host exports: " + printValTypes(func->type()->params()) + " -> " + + printValTypes(func->type()->results())); + return false; + } + imports[i] = func; + } break; + + case wasm::ExternKind::GLOBAL: { + // TODO(PiotrSikora): add support when/if needed. + fail(FailState::UnableToInitializeCode, + "Failed to load Wasm module due to a missing import: " + std::string(module) + "." + + std::string(name)); + return false; + } break; + + case wasm::ExternKind::MEMORY: { + assert(memory_ == nullptr); + auto type = wasm::MemoryType::make(import_type->memory()->limits()); + if (type == nullptr) { + return false; + } + memory_ = wasm::Memory::make(store_.get(), type.get()); + if (memory_ == nullptr) { + return false; + } + imports[i] = memory_.get(); + } break; + + case wasm::ExternKind::TABLE: { + assert(table_ == nullptr); + auto type = + wasm::TableType::make(wasm::ValType::make(import_type->table()->element()->kind()), + import_type->table()->limits()); + if (type == nullptr) { + return false; + } + table_ = wasm::Table::make(store_.get(), type.get()); + if (table_ == nullptr) { + return false; + } + imports[i] = table_.get(); + } break; + } + } + + instance_ = wasm::Instance::make(store_.get(), module_.get(), imports); + if (instance_ == nullptr) { + fail(FailState::UnableToInitializeCode, "Failed to create new Wasm instance"); + return false; + } + + const auto export_types = module_.get()->exports(); + const auto exports = instance_.get()->exports(); + assert(export_types.size() == exports.size()); + + for (size_t i = 0; i < export_types.size(); i++) { + std::string_view name(export_types[i]->name().get(), export_types[i]->name().size()); + const auto *export_type = export_types[i]->type(); + auto *export_item = exports[i].get(); + assert(export_type->kind() == export_item->kind()); + + switch (export_type->kind()) { + + case wasm::ExternKind::FUNC: { + assert(export_item->func() != nullptr); + module_functions_.insert_or_assign(std::string(name), export_item->func()->copy()); + } break; + + case wasm::ExternKind::GLOBAL: { + // TODO(PiotrSikora): add support when/if needed. + } break; + + case wasm::ExternKind::MEMORY: { + assert(export_item->memory() != nullptr); + assert(memory_ == nullptr); + memory_ = exports[i]->memory()->copy(); + if (memory_ == nullptr) { + return false; + } + } break; + + case wasm::ExternKind::TABLE: { + // TODO(PiotrSikora): add support when/if needed. + } break; + } + } + + return true; +} + +uint64_t V8::getMemorySize() { return memory_->data_size(); } + +std::optional V8::getMemory(uint64_t pointer, uint64_t size) { + assert(memory_ != nullptr); + // Make sure we're operating in a wasm32 memory space. + if (pointer > UINT32_MAX || size > UINT32_MAX || pointer + size > UINT32_MAX) { + return std::nullopt; + } + if (pointer + size > memory_->data_size()) { + return std::nullopt; + } + return std::string_view(memory_->data() + pointer, size); +} + +bool V8::setMemory(uint64_t pointer, uint64_t size, const void *data) { + assert(memory_ != nullptr); + // Make sure we're operating in a wasm32 memory space. + if (pointer > UINT32_MAX || size > UINT32_MAX || pointer + size > UINT32_MAX) { + return false; + } + if (pointer + size > memory_->data_size()) { + return false; + } + ::memcpy(memory_->data() + pointer, data, size); + return true; +} + +bool V8::getWord(uint64_t pointer, Word *word) { + constexpr auto size = sizeof(uint32_t); + // Make sure we're operating in a wasm32 memory space. + if (pointer > UINT32_MAX || pointer + size > UINT32_MAX) { + return false; + } + if (pointer + size > memory_->data_size()) { + return false; + } + uint32_t word32; + ::memcpy(&word32, memory_->data() + pointer, size); + word->u64_ = wasmtoh(word32, true); + return true; +} + +bool V8::setWord(uint64_t pointer, Word word) { + constexpr auto size = sizeof(uint32_t); + // Make sure we're operating in a wasm32 memory space. + if (pointer > UINT32_MAX || pointer + size > UINT32_MAX) { + return false; + } + if (pointer + size > memory_->data_size()) { + return false; + } + uint32_t word32 = htowasm(word.u32(), true); + ::memcpy(memory_->data() + pointer, &word32, size); + return true; +} + +wasm::own V8::trap(std::string message) { + return wasm::Trap::make(store_.get(), wasm::Message::make(std::move(message))); +} + +template +void V8::registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, + void (*function)(Args...)) { + auto data = + std::make_unique(std::string(module_name) + "." + std::string(function_name)); + auto type = wasm::FuncType::make(convertArgsTupleToValTypes>(), + convertArgsTupleToValTypes>()); + auto func = wasm::Func::make( + store_.get(), type.get(), + [](void *data, const wasm::vec ¶ms, + wasm::vec & /*results*/) -> wasm::own { + auto *func_data = reinterpret_cast(data); + const bool log = func_data->vm_->cmpLogLevel(LogLevel::trace); + if (log) { + func_data->vm_->integration()->trace("[vm->host] " + func_data->name_ + "(" + + printValues(params, sizeof...(Args)) + ")"); + } + if (!func_data->vm_->isHostFunctionAllowed(func_data->name_)) { + return dynamic_cast(func_data->vm_)->trap("restricted_callback"); + } + auto args = convertValTypesToArgsTuple>(params); + auto function = reinterpret_cast(func_data->raw_func_); + std::apply(function, args); + if (log) { + func_data->vm_->integration()->trace("[vm<-host] " + func_data->name_ + " return: void"); + } + return nullptr; + }, + data.get()); + + data->vm_ = this; + data->callback_ = std::move(func); + data->raw_func_ = reinterpret_cast(function); + host_functions_.insert_or_assign(std::string(module_name) + "." + std::string(function_name), + std::move(data)); +} + +template +void V8::registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, + R (*function)(Args...)) { + auto data = + std::make_unique(std::string(module_name) + "." + std::string(function_name)); + auto type = wasm::FuncType::make(convertArgsTupleToValTypes>(), + convertArgsTupleToValTypes>()); + auto func = wasm::Func::make( + store_.get(), type.get(), + [](void *data, const wasm::vec ¶ms, + wasm::vec &results) -> wasm::own { + auto *func_data = reinterpret_cast(data); + const bool log = func_data->vm_->cmpLogLevel(LogLevel::trace); + if (log) { + func_data->vm_->integration()->trace("[vm->host] " + func_data->name_ + "(" + + printValues(params, sizeof...(Args)) + ")"); + } + if (!func_data->vm_->isHostFunctionAllowed(func_data->name_)) { + return dynamic_cast(func_data->vm_)->trap("restricted_callback"); + } + auto args = convertValTypesToArgsTuple>(params); + auto function = reinterpret_cast(func_data->raw_func_); + R rvalue = std::apply(function, args); + results[0] = makeVal(rvalue); + if (log) { + func_data->vm_->integration()->trace("[vm<-host] " + func_data->name_ + + " return: " + std::to_string(rvalue)); + } + return nullptr; + }, + data.get()); + + data->vm_ = this; + data->callback_ = std::move(func); + data->raw_func_ = reinterpret_cast(function); + host_functions_.insert_or_assign(std::string(module_name) + "." + std::string(function_name), + std::move(data)); +} + +template +void V8::getModuleFunctionImpl(std::string_view function_name, + std::function *function) { + auto it = module_functions_.find(std::string(function_name)); + if (it == module_functions_.end()) { + *function = nullptr; + return; + } + const wasm::Func *func = it->second.get(); + auto arg_valtypes = convertArgsTupleToValTypes>(); + auto result_valtypes = convertArgsTupleToValTypes>(); + if (!equalValTypes(func->type()->params(), arg_valtypes) || + !equalValTypes(func->type()->results(), result_valtypes)) { + fail(FailState::UnableToInitializeCode, + "Bad function signature for: " + std::string(function_name) + + ", want: " + printValTypes(arg_valtypes) + " -> " + printValTypes(result_valtypes) + + ", but the module exports: " + printValTypes(func->type()->params()) + " -> " + + printValTypes(func->type()->results())); + *function = nullptr; + return; + } + *function = [func, function_name, this](ContextBase *context, Args... args) -> void { + const bool log = cmpLogLevel(LogLevel::trace); + SaveRestoreContext saved_context(context); + wasm::own trap = nullptr; + wasm::vec results = wasm::vec::make_uninitialized(); + + // Workaround for MSVC++ not supporting zero-sized arrays. + if constexpr (sizeof...(args) > 0) { + wasm::vec params = wasm::vec::make(makeVal(args)...); + if (log) { + integration()->trace("[host->vm] " + std::string(function_name) + "(" + + printValues(params, sizeof...(Args)) + ")"); + } + trap = func->call(params, results); + } else { + if (log) { + integration()->trace("[host->vm] " + std::string(function_name) + "()"); + } + trap = func->call(wasm::vec::make_uninitialized(), results); + } + + if (trap) { + fail(FailState::RuntimeError, getFailMessage(std::string(function_name), std::move(trap))); + return; + } + if (log) { + integration()->trace("[host<-vm] " + std::string(function_name) + " return: void"); + } + }; +} + +template +void V8::getModuleFunctionImpl(std::string_view function_name, + std::function *function) { + auto it = module_functions_.find(std::string(function_name)); + if (it == module_functions_.end()) { + *function = nullptr; + return; + } + const wasm::Func *func = it->second.get(); + auto arg_valtypes = convertArgsTupleToValTypes>(); + auto result_valtypes = convertArgsTupleToValTypes>(); + if (!equalValTypes(func->type()->params(), arg_valtypes) || + !equalValTypes(func->type()->results(), result_valtypes)) { + fail(FailState::UnableToInitializeCode, + "Bad function signature for: " + std::string(function_name) + + ", want: " + printValTypes(arg_valtypes) + " -> " + printValTypes(result_valtypes) + + ", but the module exports: " + printValTypes(func->type()->params()) + " -> " + + printValTypes(func->type()->results())); + *function = nullptr; + return; + } + *function = [func, function_name, this](ContextBase *context, Args... args) -> R { + const bool log = cmpLogLevel(LogLevel::trace); + SaveRestoreContext saved_context(context); + wasm::vec results = wasm::vec::make_uninitialized(1); + wasm::own trap = nullptr; + + // Workaround for MSVC++ not supporting zero-sized arrays. + if constexpr (sizeof...(args) > 0) { + wasm::vec params = wasm::vec::make(makeVal(args)...); + if (log) { + integration()->trace("[host->vm] " + std::string(function_name) + "(" + + printValues(params, sizeof...(Args)) + ")"); + } + trap = func->call(params, results); + } else { + if (log) { + integration()->trace("[host->vm] " + std::string(function_name) + "()"); + } + trap = func->call(wasm::vec::make_uninitialized(), results); + } + + if (trap) { + fail(FailState::RuntimeError, getFailMessage(std::string(function_name), std::move(trap))); + return R{}; + } + R rvalue = results[0].get::type>(); + if (log) { + integration()->trace("[host<-vm] " + std::string(function_name) + + " return: " + std::to_string(rvalue)); + } + return rvalue; + }; +} + +void V8::terminate() { + auto *store_impl = reinterpret_cast(store_.get()); + auto *isolate = store_impl->isolate(); + isolate->TerminateExecution(); +} + +std::string V8::getFailMessage(std::string_view function_name, wasm::own trap) { + auto message = "Function: " + std::string(function_name) + " failed: "; + message += std::string(trap->message().get(), trap->message().size()); + + if (function_names_index_.empty()) { + return message; + } + + auto trace = trap->trace(); + message += "\nProxy-Wasm plugin in-VM backtrace:"; + for (size_t i = 0; i < trace.size(); ++i) { + auto *frame = trace[i].get(); + std::ostringstream oss; + oss << std::setw(3) << std::setfill(' ') << std::to_string(i); + message += "\n" + oss.str() + ": "; + + std::stringstream address; + address << std::hex << frame->module_offset(); + message += " 0x" + address.str() + " - "; + + auto func_index = frame->func_index(); + auto it = function_names_index_.find(func_index); + if (it != function_names_index_.end()) { + message += it->second; + } else { + message += "unknown(function index:" + std::to_string(func_index) + ")"; + } + } + return message; +} + +} // namespace v8 + +std::unique_ptr createV8Vm() { return std::make_unique(); } + +} // namespace proxy_wasm From 158d7bf3a3129ac9076c6ff5f99d10230ad9a3b8 Mon Sep 17 00:00:00 2001 From: Rachel Green Date: Fri, 1 Aug 2025 16:03:23 +0000 Subject: [PATCH 2/7] Removed unnecessary files. Signed-off-by: Rachel Green --- include/proxy-wasm/wasm_vm.h.orig | 376 --------------- src/v8/v8.cc.orig | 746 ------------------------------ 2 files changed, 1122 deletions(-) delete mode 100644 include/proxy-wasm/wasm_vm.h.orig delete mode 100644 src/v8/v8.cc.orig diff --git a/include/proxy-wasm/wasm_vm.h.orig b/include/proxy-wasm/wasm_vm.h.orig deleted file mode 100644 index db54ebd86..000000000 --- a/include/proxy-wasm/wasm_vm.h.orig +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright 2016-2019 Envoy Project Authors -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "include/proxy-wasm/sdk.h" -#include "include/proxy-wasm/word.h" - -namespace proxy_wasm { - -class ContextBase; - -// These are templates and its helper for constructing signatures of functions calling into Wasm -// VMs. -// - WasmCallInFuncTypeHelper is a helper for WasmFuncType and shouldn't be used anywhere else than -// WasmFuncType definition. -// - WasmCallInFuncType takes 4 template parameter which are number of argument, return type, -// context type and param type respectively, resolve to a function type. -// For example `WasmFuncType<3, void, Context*, Word>` resolves to `void(Context*, Word, Word, -// Word)` -template -struct WasmCallInFuncTypeHelper {}; - -template -struct WasmCallInFuncTypeHelper { - // NOLINTNEXTLINE(readability-identifier-naming) - using type = typename WasmCallInFuncTypeHelper::type; -}; - -template -struct WasmCallInFuncTypeHelper<0, ReturnType, ContextType, ParamType, - ReturnType(ContextType, Args...)> { - using type = ReturnType(ContextType, Args...); // NOLINT(readability-identifier-naming) -}; - -template -using WasmCallInFuncType = - typename WasmCallInFuncTypeHelper::type; - -// Calls into the WASM VM. -// 1st arg is always a pointer to Context (Context*). -template -using WasmCallVoid = std::function>; -template -using WasmCallWord = std::function>; - -#define FOR_ALL_WASM_VM_EXPORTS(_f) \ - _f(proxy_wasm::WasmCallVoid<0>) _f(proxy_wasm::WasmCallVoid<1>) _f(proxy_wasm::WasmCallVoid<2>) \ - _f(proxy_wasm::WasmCallVoid<3>) _f(proxy_wasm::WasmCallVoid<5>) \ - _f(proxy_wasm::WasmCallWord<1>) _f(proxy_wasm::WasmCallWord<2>) \ - _f(proxy_wasm::WasmCallWord<3>) - -// These are templates and its helper for constructing signatures of functions callbacks from Wasm -// VMs. -// - WasmCallbackFuncTypeHelper is a helper for WasmFuncType and shouldn't be used anywhere else -// than WasmFuncType definition. -// - WasmCallbackFuncType takes 3 template parameter which are number of argument, return type, and -// param type respectively, resolve to a function type. -// For example `WasmFuncType<3, Word>` resolves to `void(Word, Word, Word)` -template -struct WasmCallbackFuncTypeHelper {}; - -template -struct WasmCallbackFuncTypeHelper { - // NOLINTNEXTLINE(readability-identifier-naming) - using type = typename WasmCallbackFuncTypeHelper::type; -}; - -template -struct WasmCallbackFuncTypeHelper<0, ReturnType, ParamType, ReturnType(Args...)> { - using type = ReturnType(Args...); // NOLINT(readability-identifier-naming) -}; - -template -using WasmCallbackFuncType = typename WasmCallbackFuncTypeHelper::type; - -// Calls out of the WASM VM. -template using WasmCallbackVoid = WasmCallbackFuncType *; -template using WasmCallbackWord = WasmCallbackFuncType *; - -// Using the standard g++/clang mangling algorithm: -// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-builtin -// Extended with W = Word -// Z = void, j = uint32_t, l = int64_t, m = uint64_t -using WasmCallback_WWl = Word (*)(Word, int64_t); -using WasmCallback_WWlWW = Word (*)(Word, int64_t, Word, Word); -using WasmCallback_WWm = Word (*)(Word, uint64_t); -using WasmCallback_WWmW = Word (*)(Word, uint64_t, Word); -using WasmCallback_WWWWWWllWW = Word (*)(Word, Word, Word, Word, Word, int64_t, int64_t, Word, - Word); -using WasmCallback_dd = double (*)(double); - -#define FOR_ALL_WASM_VM_IMPORTS(_f) \ - _f(proxy_wasm::WasmCallbackVoid<0>) _f(proxy_wasm::WasmCallbackVoid<1>) \ - _f(proxy_wasm::WasmCallbackVoid<2>) _f(proxy_wasm::WasmCallbackVoid<3>) \ - _f(proxy_wasm::WasmCallbackVoid<4>) _f(proxy_wasm::WasmCallbackWord<0>) \ - _f(proxy_wasm::WasmCallbackWord<1>) _f(proxy_wasm::WasmCallbackWord<2>) \ - _f(proxy_wasm::WasmCallbackWord<3>) _f(proxy_wasm::WasmCallbackWord<4>) \ - _f(proxy_wasm::WasmCallbackWord<5>) _f(proxy_wasm::WasmCallbackWord<6>) \ - _f(proxy_wasm::WasmCallbackWord<7>) _f(proxy_wasm::WasmCallbackWord<8>) \ - _f(proxy_wasm::WasmCallbackWord<9>) \ - _f(proxy_wasm::WasmCallbackWord<10>) \ - _f(proxy_wasm::WasmCallbackWord<12>) \ - _f(proxy_wasm::WasmCallback_WWl) \ - _f(proxy_wasm::WasmCallback_WWlWW) \ - _f(proxy_wasm::WasmCallback_WWm) \ - _f(proxy_wasm::WasmCallback_WWmW) \ - _f(proxy_wasm::WasmCallback_WWWWWWllWW) \ - _f(proxy_wasm::WasmCallback_dd) - -enum class Cloneable { - NotCloneable, // VMs can not be cloned and should be created from scratch. - CompiledBytecode, // VMs can be cloned with compiled bytecode. - InstantiatedModule // VMs can be cloned from an instantiated module. -}; - -enum class AbiVersion { ProxyWasm_0_1_0, ProxyWasm_0_2_0, ProxyWasm_0_2_1, Unknown }; - -class NullPlugin; - -// Integrator specific WasmVm operations. -struct WasmVmIntegration { - virtual ~WasmVmIntegration() {} - virtual WasmVmIntegration *clone() = 0; - virtual proxy_wasm::LogLevel getLogLevel() = 0; - virtual void error(std::string_view message) = 0; - virtual void trace(std::string_view message) = 0; - // Get a NullVm implementation of a function. - // @param function_name is the name of the function with the implementation specific prefix. - // @param returns_word is true if the function returns a Word and false if it returns void. - // @param number_of_arguments is the number of Word arguments to the function. - // @param plugin is the Null VM plugin on which the function will be called. - // @param ptr_to_function_return is the location to write the function e.g. of type - // WasmCallWord<3>. - // @return true if the function was found. ptr_to_function_return could still be set to nullptr - // (of the correct type) if the function has no implementation. Returning true will prevent a - // "Missing getFunction" error. - virtual bool getNullVmFunction(std::string_view function_name, bool returns_word, - int number_of_arguments, NullPlugin *plugin, - void *ptr_to_function_return) = 0; -}; - -enum class FailState : int { - Ok = 0, - UnableToCreateVm = 1, - UnableToCloneVm = 2, - MissingFunction = 3, - UnableToInitializeCode = 4, - StartFailed = 5, - ConfigureFailed = 6, - RuntimeError = 7, -}; - -// Wasm VM instance. Provides the low level WASM interface. -class WasmVm { -public: - virtual ~WasmVm() = default; - /** - * Identify the Wasm engine. - * @return the name of the underlying Wasm engine. - */ - virtual std::string_view getEngineName() = 0; - - /** - * Whether or not the VM implementation supports cloning. Cloning is VM system dependent. - * When a VM is configured a single VM is instantiated to check that the .wasm file is valid and - * to do VM system specific initialization. Then, if cloning is supported, we clone that VM for - * each worker, potentially copying and sharing the initialized data structures for efficiency. - * Otherwise we create an new VM from scratch for each worker. - * @return one of enum Cloneable with the VMs cloneability. - */ - virtual Cloneable cloneable() = 0; - - /** - * Make a worker/thread-specific copy if supported by the underlying VM system (see cloneable() - * above). If not supported, the caller will need to create a new VM from scratch. If supported, - * the clone may share compiled code and other read-only data with the source VM. - * @return a clone of 'this' (e.g. for a different worker/thread). - */ - virtual std::unique_ptr clone() = 0; - - /** - * Load the WASM code from a file. Return true on success. Once the module is loaded it can be - * queried, e.g. to see which version of emscripten support is required. After loading, the - * appropriate ABI callbacks can be registered and then the module can be link()ed (see below). - * @param bytecode the Wasm bytecode or registered NullVm plugin name. - * @param precompiled (optional) the precompiled Wasm module. - * @param function_names (optional) an index-to-name mapping for the functions. - * @return whether or not the load was successful. - */ - virtual bool load(std::string_view bytecode, std::string_view precompiled, - const std::unordered_map &function_names) = 0; - - /** - * Link the WASM code to the host-provided functions, e.g. the ABI. Prior to linking, the module - * should be loaded and the ABI callbacks registered (see above). Linking should be done once - * after load(). - * @param debug_name user-provided name for use in log and error messages. - * @return whether or not the link was successful. - */ - virtual bool link(std::string_view debug_name) = 0; - - /** - * Get size of the currently allocated memory in the VM. - * @return the size of memory in bytes. - */ - virtual uint64_t getMemorySize() = 0; - - /** - * Convert a block of memory in the VM to a std::string_view. - * @param pointer the offset into VM memory of the requested VM memory block. - * @param size the size of the requested VM memory block. - * @return if std::nullopt then the pointer/size pair were invalid, otherwise returns - * a host std::string_view pointing to the pointer/size pair in VM memory. - */ - virtual std::optional getMemory(uint64_t pointer, uint64_t size) = 0; - - /** - * Set a block of memory in the VM, returns true on success, false if the pointer/size is invalid. - * @param pointer the offset into VM memory describing the start of a region of VM memory. - * @param size the size of the region of VM memory. - * @return whether or not the pointer/size pair was a valid VM memory block. - */ - virtual bool setMemory(uint64_t pointer, uint64_t size, const void *data) = 0; - - /** - * Get a VM native Word (e.g. sizeof(void*) or sizeof(size_t)) from VM memory, returns true on - * success, false if the pointer is invalid. WASM-32 VMs have 32-bit native words and WASM-64 VMs - * (not yet supported) will have 64-bit words as does the Null VM (compiled into a 64-bit proxy). - * This function can be used to chase pointers in VM memory. - * @param pointer the offset into VM memory describing the start of VM native word size block. - * @param data a pointer to a Word whose contents will be filled from the VM native word at - * 'pointer'. - * @return whether or not the pointer was to a valid VM memory block of VM native word size. - */ - virtual bool getWord(uint64_t pointer, Word *data) = 0; - - /** - * Set a Word in the VM, returns true on success, false if the pointer is invalid. - * See getWord above for details. This function can be used (for example) to set indirect pointer - * return values (e.g. proxy_getHeaderHapValue(... const char** value_ptr, size_t* value_size). - * @param pointer the offset into VM memory describing the start of VM native word size block. - * @param data a Word whose contents will be written in VM native word size at 'pointer'. - * @return whether or not the pointer was to a valid VM memory block of VM native word size. - */ - virtual bool setWord(uint64_t pointer, Word data) = 0; - - /** - * @return the Word size in this VM. - */ - virtual size_t getWordSize() = 0; - - /** - * Get the name of the custom section that contains precompiled module. - * @return the name of the custom section that contains precompiled module. - */ - virtual std::string_view getPrecompiledSectionName() = 0; - - /** - * Get typed function exported by the WASM module. - */ -#define _GET_FUNCTION(_T) virtual void getFunction(std::string_view function_name, _T *f) = 0; - FOR_ALL_WASM_VM_EXPORTS(_GET_FUNCTION) -#undef _GET_FUNCTION - - /** - * Register typed callbacks exported by the host environment. - */ -#define _REGISTER_CALLBACK(_T) \ - virtual void registerCallback(std::string_view moduleName, std::string_view function_name, _T f, \ - typename ConvertFunctionTypeWordToUint32<_T>::type) = 0; - FOR_ALL_WASM_VM_IMPORTS(_REGISTER_CALLBACK) -#undef _REGISTER_CALLBACK - - /** - * Terminate execution of this WasmVM. It shouldn't be used after being terminated. - */ - virtual void terminate() = 0; - - /** - * Byte order flag (host or wasm). - * @return 'false' for a null VM and 'true' for a wasm VM. - */ - virtual bool usesWasmByteOrder() = 0; - - bool isFailed() { return failed_ != FailState::Ok; } - void fail(FailState fail_state, std::string_view message) { - integration()->error(message); - failed_ = fail_state; - for (auto &callback : fail_callbacks_) { - callback(fail_state); - } - } - void addFailCallback(std::function fail_callback) { - fail_callbacks_.push_back(fail_callback); - } - - bool isHostFunctionAllowed(const std::string &name) { - return !restricted_callback_ || allowed_hostcalls_.find(name) != allowed_hostcalls_.end(); - } - - void setRestrictedCallback(bool restricted, - std::unordered_set allowed_hostcalls = {}) { - restricted_callback_ = restricted; - allowed_hostcalls_ = std::move(allowed_hostcalls); - } - - // Integrator operations. - std::unique_ptr &integration() { return integration_; } - bool cmpLogLevel(proxy_wasm::LogLevel level) { return integration_->getLogLevel() <= level; } - -protected: - std::unique_ptr integration_; - FailState failed_ = FailState::Ok; - std::vector> fail_callbacks_; - -private: - bool restricted_callback_{false}; - std::unordered_set allowed_hostcalls_{}; -}; - -// Thread local state set during a call into a WASM VM so that calls coming out of the -// VM can be attributed correctly to calling Filter. We use thread_local instead of ThreadLocal -// because this state is live only during the calls and does not need to be initialized consistently -// over all workers as with ThreadLocal data. -extern thread_local ContextBase *current_context_; - -// Requested effective context set by code within the VM to request that the calls coming out of the -// VM be attributed to another filter, for example if a control plane gRPC comes back to the -// RootContext which effects some set of waiting filters. -extern thread_local uint32_t effective_context_id_; - -// Helper to save and restore thread local VM call context information to support reentrant calls. -// NB: this happens for example when a call from the VM invokes a handler which needs to _malloc -// memory in the VM. -struct SaveRestoreContext { - explicit SaveRestoreContext(ContextBase *context) { - saved_context = current_context_; - saved_effective_context_id_ = effective_context_id_; - current_context_ = context; - effective_context_id_ = 0; // No effective context id. - } - ~SaveRestoreContext() { - current_context_ = saved_context; - effective_context_id_ = saved_effective_context_id_; - } - ContextBase *saved_context; - uint32_t saved_effective_context_id_; -}; - -} // namespace proxy_wasm diff --git a/src/v8/v8.cc.orig b/src/v8/v8.cc.orig deleted file mode 100644 index bc5b82850..000000000 --- a/src/v8/v8.cc.orig +++ /dev/null @@ -1,746 +0,0 @@ -// Copyright 2016-2019 Envoy Project Authors -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "include/proxy-wasm/v8.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "include/proxy-wasm/limits.h" - -#include "include/v8-version.h" -#include "include/v8.h" -#include "src/flags/flags.h" -#include "src/wasm/c-api.h" -#include "wasm-api/wasm.hh" - -namespace proxy_wasm { -namespace v8 { - -wasm::Engine *engine() { - static std::once_flag init; - static wasm::own engine; - - std::call_once(init, []() { - ::v8::internal::v8_flags.liftoff = false; - ::v8::internal::v8_flags.wasm_max_mem_pages = - PROXY_WASM_HOST_MAX_WASM_MEMORY_SIZE_BYTES / PROXY_WASM_HOST_WASM_MEMORY_PAGE_SIZE_BYTES; - ::v8::V8::EnableWebAssemblyTrapHandler(true); - engine = wasm::Engine::make(); - }); - - return engine.get(); -} - -struct FuncData { - FuncData(std::string name) : name_(std::move(name)) {} - - std::string name_; - wasm::own callback_; - void *raw_func_{}; - WasmVm *vm_{}; -}; - -using FuncDataPtr = std::unique_ptr; - -class V8 : public WasmVm { -public: - V8() = default; - - // WasmVm - std::string_view getEngineName() override { return "v8"; } - - bool load(std::string_view bytecode, std::string_view precompiled, - const std::unordered_map &function_names) override; - std::string_view getPrecompiledSectionName() override; - bool link(std::string_view debug_name) override; - - Cloneable cloneable() override { return Cloneable::CompiledBytecode; } - std::unique_ptr clone() override; - - uint64_t getMemorySize() override; - std::optional getMemory(uint64_t pointer, uint64_t size) override; - bool setMemory(uint64_t pointer, uint64_t size, const void *data) override; - bool getWord(uint64_t pointer, Word *word) override; - bool setWord(uint64_t pointer, Word word) override; - size_t getWordSize() override { return sizeof(uint32_t); }; - -#define _REGISTER_HOST_FUNCTION(T) \ - void registerCallback(std::string_view module_name, std::string_view function_name, T, \ - typename ConvertFunctionTypeWordToUint32::type f) override { \ - registerHostFunctionImpl(module_name, function_name, f); \ - }; - FOR_ALL_WASM_VM_IMPORTS(_REGISTER_HOST_FUNCTION) -#undef _REGISTER_HOST_FUNCTION - -#define _GET_MODULE_FUNCTION(T) \ - void getFunction(std::string_view function_name, T *f) override { \ - getModuleFunctionImpl(function_name, f); \ - }; - FOR_ALL_WASM_VM_EXPORTS(_GET_MODULE_FUNCTION) -#undef _GET_MODULE_FUNCTION - - void terminate() override; - bool usesWasmByteOrder() override { return true; } - -private: - wasm::own trap(std::string message); - - std::string getFailMessage(std::string_view function_name, wasm::own trap); - - template - void registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, - void (*function)(Args...)); - - template - void registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, - R (*function)(Args...)); - - template - void getModuleFunctionImpl(std::string_view function_name, - std::function *function); - - template - void getModuleFunctionImpl(std::string_view function_name, - std::function *function); - - wasm::own store_; - wasm::own module_; - wasm::own> shared_module_; - wasm::own instance_; - wasm::own memory_; - wasm::own table_; - - std::unordered_map host_functions_; - std::unordered_map> module_functions_; - std::unordered_map function_names_index_; -}; - -// Helper functions. - -static std::string printValue(const wasm::Val &value) { - switch (value.kind()) { - case wasm::ValKind::I32: - return std::to_string(value.get()); - case wasm::ValKind::I64: - return std::to_string(value.get()); - case wasm::ValKind::F32: - return std::to_string(value.get()); - case wasm::ValKind::F64: - return std::to_string(value.get()); - default: - return "unknown"; - } -} - -static std::string printValues(const wasm::vec &values, size_t size) { - if (size == 0) { - return ""; - } - - std::string s; - for (size_t i = 0; i < size; i++) { - if (i != 0U) { - s.append(", "); - } - s.append(printValue(values[i])); - } - return s; -} - -static const char *printValKind(wasm::ValKind kind) { - switch (kind) { - case wasm::ValKind::I32: - return "i32"; - case wasm::ValKind::I64: - return "i64"; - case wasm::ValKind::F32: - return "f32"; - case wasm::ValKind::F64: - return "f64"; - case wasm::ValKind::EXTERNREF: - return "externref"; - case wasm::ValKind::FUNCREF: - return "funcref"; - default: - return "unknown"; - } -} - -static std::string printValTypes(const wasm::ownvec &types) { - if (types.size() == 0) { - return "void"; - } - - std::string s; - s.reserve(types.size() * 8 /* max size + " " */ - 1); - for (size_t i = 0; i < types.size(); i++) { - if (i != 0U) { - s.append(" "); - } - s.append(printValKind(types[i]->kind())); - } - return s; -} - -static bool equalValTypes(const wasm::ownvec &left, - const wasm::ownvec &right) { - if (left.size() != right.size()) { - return false; - } - for (size_t i = 0; i < left.size(); i++) { - if (left[i]->kind() != right[i]->kind()) { - return false; - } - } - return true; -} - -// Template magic. - -template struct ConvertWordType { - using type = T; // NOLINT(readability-identifier-naming) -}; -template <> struct ConvertWordType { - using type = uint32_t; // NOLINT(readability-identifier-naming) -}; - -template wasm::Val makeVal(T t) { return wasm::Val::make(t); } -template <> wasm::Val makeVal(Word t) { return wasm::Val::make(static_cast(t.u64_)); } - -template constexpr auto convertArgToValKind(); -template <> constexpr auto convertArgToValKind() { return wasm::ValKind::I32; }; -template <> constexpr auto convertArgToValKind() { return wasm::ValKind::I32; }; -template <> constexpr auto convertArgToValKind() { return wasm::ValKind::I64; }; -template <> constexpr auto convertArgToValKind() { return wasm::ValKind::I64; }; -template <> constexpr auto convertArgToValKind() { return wasm::ValKind::F64; }; - -template -constexpr auto convertArgsTupleToValTypesImpl(std::index_sequence /*comptime*/) { - return wasm::ownvec::make( - wasm::ValType::make(convertArgToValKind::type>())...); -} - -template constexpr auto convertArgsTupleToValTypes() { - return convertArgsTupleToValTypesImpl(std::make_index_sequence::value>()); -} - -template -constexpr T convertValTypesToArgsTupleImpl(const U &arr, std::index_sequence /*comptime*/) { - return std::make_tuple( - (arr[I] - .template get< - typename ConvertWordType::type>::type>())...); -} - -template constexpr T convertValTypesToArgsTuple(const U &arr) { - return convertValTypesToArgsTupleImpl(arr, - std::make_index_sequence::value>()); -} - -// V8 implementation. - -bool V8::load(std::string_view bytecode, std::string_view precompiled, - const std::unordered_map &function_names) { - store_ = wasm::Store::make(engine()); - if (store_ == nullptr) { - return false; - } - - if (!precompiled.empty()) { - auto vec = wasm::vec::make_uninitialized(precompiled.size()); - ::memcpy(vec.get(), precompiled.data(), precompiled.size()); - module_ = wasm::Module::deserialize(store_.get(), vec); - if (module_ == nullptr) { - return false; - } - - } else { - auto vec = wasm::vec::make_uninitialized(bytecode.size()); - ::memcpy(vec.get(), bytecode.data(), bytecode.size()); - module_ = wasm::Module::make(store_.get(), vec); - if (module_ == nullptr) { - return false; - } - } - - shared_module_ = module_->share(); - if (shared_module_ == nullptr) { - return false; - } - - function_names_index_ = function_names; - - return true; -} - -std::unique_ptr V8::clone() { - assert(shared_module_ != nullptr); - - auto clone = std::make_unique(); - if (clone == nullptr) { - return nullptr; - } - - clone->store_ = wasm::Store::make(engine()); - if (clone->store_ == nullptr) { - return nullptr; - } - - clone->module_ = wasm::Module::obtain(clone->store_.get(), shared_module_.get()); - if (clone->module_ == nullptr) { - return nullptr; - } - - auto *integration_clone = integration()->clone(); - if (integration_clone == nullptr) { - return nullptr; - } - clone->integration().reset(integration_clone); - - clone->function_names_index_ = function_names_index_; - - return clone; -} - -#if defined(__linux__) && defined(__x86_64__) -#define WEE8_PLATFORM "linux_x86_64" -#else -#define WEE8_PLATFORM "" -#endif - -std::string_view V8::getPrecompiledSectionName() { - static const auto name = - sizeof(WEE8_PLATFORM) - 1 > 0 - ? ("precompiled_wee8_v" + std::to_string(V8_MAJOR_VERSION) + "." + - std::to_string(V8_MINOR_VERSION) + "." + std::to_string(V8_BUILD_NUMBER) + "." + - std::to_string(V8_PATCH_LEVEL) + "_" + WEE8_PLATFORM) - : ""; - return name; -} - -bool V8::link(std::string_view /*debug_name*/) { - assert(module_ != nullptr); - - const auto import_types = module_.get()->imports(); - wasm::vec imports = - wasm::vec::make_uninitialized(import_types.size()); - - for (size_t i = 0; i < import_types.size(); i++) { - std::string_view module(import_types[i]->module().get(), import_types[i]->module().size()); - std::string_view name(import_types[i]->name().get(), import_types[i]->name().size()); - const auto *import_type = import_types[i]->type(); - - switch (import_type->kind()) { - - case wasm::ExternKind::FUNC: { - auto it = host_functions_.find(std::string(module) + "." + std::string(name)); - if (it == host_functions_.end()) { - fail(FailState::UnableToInitializeCode, - std::string("Failed to load Wasm module due to a missing import: ") + - std::string(module) + "." + std::string(name)); - return false; - } - auto *func = it->second->callback_.get(); - if (!equalValTypes(import_type->func()->params(), func->type()->params()) || - !equalValTypes(import_type->func()->results(), func->type()->results())) { - fail(FailState::UnableToInitializeCode, - std::string("Failed to load Wasm module due to an import type mismatch: ") + - std::string(module) + "." + std::string(name) + - ", want: " + printValTypes(import_type->func()->params()) + " -> " + - printValTypes(import_type->func()->results()) + - ", but host exports: " + printValTypes(func->type()->params()) + " -> " + - printValTypes(func->type()->results())); - return false; - } - imports[i] = func; - } break; - - case wasm::ExternKind::GLOBAL: { - // TODO(PiotrSikora): add support when/if needed. - fail(FailState::UnableToInitializeCode, - "Failed to load Wasm module due to a missing import: " + std::string(module) + "." + - std::string(name)); - return false; - } break; - - case wasm::ExternKind::MEMORY: { - assert(memory_ == nullptr); - auto type = wasm::MemoryType::make(import_type->memory()->limits()); - if (type == nullptr) { - return false; - } - memory_ = wasm::Memory::make(store_.get(), type.get()); - if (memory_ == nullptr) { - return false; - } - imports[i] = memory_.get(); - } break; - - case wasm::ExternKind::TABLE: { - assert(table_ == nullptr); - auto type = - wasm::TableType::make(wasm::ValType::make(import_type->table()->element()->kind()), - import_type->table()->limits()); - if (type == nullptr) { - return false; - } - table_ = wasm::Table::make(store_.get(), type.get()); - if (table_ == nullptr) { - return false; - } - imports[i] = table_.get(); - } break; - } - } - - instance_ = wasm::Instance::make(store_.get(), module_.get(), imports); - if (instance_ == nullptr) { - fail(FailState::UnableToInitializeCode, "Failed to create new Wasm instance"); - return false; - } - - const auto export_types = module_.get()->exports(); - const auto exports = instance_.get()->exports(); - assert(export_types.size() == exports.size()); - - for (size_t i = 0; i < export_types.size(); i++) { - std::string_view name(export_types[i]->name().get(), export_types[i]->name().size()); - const auto *export_type = export_types[i]->type(); - auto *export_item = exports[i].get(); - assert(export_type->kind() == export_item->kind()); - - switch (export_type->kind()) { - - case wasm::ExternKind::FUNC: { - assert(export_item->func() != nullptr); - module_functions_.insert_or_assign(std::string(name), export_item->func()->copy()); - } break; - - case wasm::ExternKind::GLOBAL: { - // TODO(PiotrSikora): add support when/if needed. - } break; - - case wasm::ExternKind::MEMORY: { - assert(export_item->memory() != nullptr); - assert(memory_ == nullptr); - memory_ = exports[i]->memory()->copy(); - if (memory_ == nullptr) { - return false; - } - } break; - - case wasm::ExternKind::TABLE: { - // TODO(PiotrSikora): add support when/if needed. - } break; - } - } - - return true; -} - -uint64_t V8::getMemorySize() { return memory_->data_size(); } - -std::optional V8::getMemory(uint64_t pointer, uint64_t size) { - assert(memory_ != nullptr); - // Make sure we're operating in a wasm32 memory space. - if (pointer > UINT32_MAX || size > UINT32_MAX || pointer + size > UINT32_MAX) { - return std::nullopt; - } - if (pointer + size > memory_->data_size()) { - return std::nullopt; - } - return std::string_view(memory_->data() + pointer, size); -} - -bool V8::setMemory(uint64_t pointer, uint64_t size, const void *data) { - assert(memory_ != nullptr); - // Make sure we're operating in a wasm32 memory space. - if (pointer > UINT32_MAX || size > UINT32_MAX || pointer + size > UINT32_MAX) { - return false; - } - if (pointer + size > memory_->data_size()) { - return false; - } - ::memcpy(memory_->data() + pointer, data, size); - return true; -} - -bool V8::getWord(uint64_t pointer, Word *word) { - constexpr auto size = sizeof(uint32_t); - // Make sure we're operating in a wasm32 memory space. - if (pointer > UINT32_MAX || pointer + size > UINT32_MAX) { - return false; - } - if (pointer + size > memory_->data_size()) { - return false; - } - uint32_t word32; - ::memcpy(&word32, memory_->data() + pointer, size); - word->u64_ = wasmtoh(word32, true); - return true; -} - -bool V8::setWord(uint64_t pointer, Word word) { - constexpr auto size = sizeof(uint32_t); - // Make sure we're operating in a wasm32 memory space. - if (pointer > UINT32_MAX || pointer + size > UINT32_MAX) { - return false; - } - if (pointer + size > memory_->data_size()) { - return false; - } - uint32_t word32 = htowasm(word.u32(), true); - ::memcpy(memory_->data() + pointer, &word32, size); - return true; -} - -wasm::own V8::trap(std::string message) { - return wasm::Trap::make(store_.get(), wasm::Message::make(std::move(message))); -} - -template -void V8::registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, - void (*function)(Args...)) { - auto data = - std::make_unique(std::string(module_name) + "." + std::string(function_name)); - auto type = wasm::FuncType::make(convertArgsTupleToValTypes>(), - convertArgsTupleToValTypes>()); - auto func = wasm::Func::make( - store_.get(), type.get(), - [](void *data, const wasm::vec ¶ms, - wasm::vec & /*results*/) -> wasm::own { - auto *func_data = reinterpret_cast(data); - const bool log = func_data->vm_->cmpLogLevel(LogLevel::trace); - if (log) { - func_data->vm_->integration()->trace("[vm->host] " + func_data->name_ + "(" + - printValues(params, sizeof...(Args)) + ")"); - } - if (!func_data->vm_->isHostFunctionAllowed(func_data->name_)) { - return dynamic_cast(func_data->vm_)->trap("restricted_callback"); - } - auto args = convertValTypesToArgsTuple>(params); - auto function = reinterpret_cast(func_data->raw_func_); - std::apply(function, args); - if (log) { - func_data->vm_->integration()->trace("[vm<-host] " + func_data->name_ + " return: void"); - } - return nullptr; - }, - data.get()); - - data->vm_ = this; - data->callback_ = std::move(func); - data->raw_func_ = reinterpret_cast(function); - host_functions_.insert_or_assign(std::string(module_name) + "." + std::string(function_name), - std::move(data)); -} - -template -void V8::registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, - R (*function)(Args...)) { - auto data = - std::make_unique(std::string(module_name) + "." + std::string(function_name)); - auto type = wasm::FuncType::make(convertArgsTupleToValTypes>(), - convertArgsTupleToValTypes>()); - auto func = wasm::Func::make( - store_.get(), type.get(), - [](void *data, const wasm::vec ¶ms, - wasm::vec &results) -> wasm::own { - auto *func_data = reinterpret_cast(data); - const bool log = func_data->vm_->cmpLogLevel(LogLevel::trace); - if (log) { - func_data->vm_->integration()->trace("[vm->host] " + func_data->name_ + "(" + - printValues(params, sizeof...(Args)) + ")"); - } - if (!func_data->vm_->isHostFunctionAllowed(func_data->name_)) { - return dynamic_cast(func_data->vm_)->trap("restricted_callback"); - } - auto args = convertValTypesToArgsTuple>(params); - auto function = reinterpret_cast(func_data->raw_func_); - R rvalue = std::apply(function, args); - results[0] = makeVal(rvalue); - if (log) { - func_data->vm_->integration()->trace("[vm<-host] " + func_data->name_ + - " return: " + std::to_string(rvalue)); - } - return nullptr; - }, - data.get()); - - data->vm_ = this; - data->callback_ = std::move(func); - data->raw_func_ = reinterpret_cast(function); - host_functions_.insert_or_assign(std::string(module_name) + "." + std::string(function_name), - std::move(data)); -} - -template -void V8::getModuleFunctionImpl(std::string_view function_name, - std::function *function) { - auto it = module_functions_.find(std::string(function_name)); - if (it == module_functions_.end()) { - *function = nullptr; - return; - } - const wasm::Func *func = it->second.get(); - auto arg_valtypes = convertArgsTupleToValTypes>(); - auto result_valtypes = convertArgsTupleToValTypes>(); - if (!equalValTypes(func->type()->params(), arg_valtypes) || - !equalValTypes(func->type()->results(), result_valtypes)) { - fail(FailState::UnableToInitializeCode, - "Bad function signature for: " + std::string(function_name) + - ", want: " + printValTypes(arg_valtypes) + " -> " + printValTypes(result_valtypes) + - ", but the module exports: " + printValTypes(func->type()->params()) + " -> " + - printValTypes(func->type()->results())); - *function = nullptr; - return; - } - *function = [func, function_name, this](ContextBase *context, Args... args) -> void { - const bool log = cmpLogLevel(LogLevel::trace); - SaveRestoreContext saved_context(context); - wasm::own trap = nullptr; - wasm::vec results = wasm::vec::make_uninitialized(); - - // Workaround for MSVC++ not supporting zero-sized arrays. - if constexpr (sizeof...(args) > 0) { - wasm::vec params = wasm::vec::make(makeVal(args)...); - if (log) { - integration()->trace("[host->vm] " + std::string(function_name) + "(" + - printValues(params, sizeof...(Args)) + ")"); - } - trap = func->call(params, results); - } else { - if (log) { - integration()->trace("[host->vm] " + std::string(function_name) + "()"); - } - trap = func->call(wasm::vec::make_uninitialized(), results); - } - - if (trap) { - fail(FailState::RuntimeError, getFailMessage(std::string(function_name), std::move(trap))); - return; - } - if (log) { - integration()->trace("[host<-vm] " + std::string(function_name) + " return: void"); - } - }; -} - -template -void V8::getModuleFunctionImpl(std::string_view function_name, - std::function *function) { - auto it = module_functions_.find(std::string(function_name)); - if (it == module_functions_.end()) { - *function = nullptr; - return; - } - const wasm::Func *func = it->second.get(); - auto arg_valtypes = convertArgsTupleToValTypes>(); - auto result_valtypes = convertArgsTupleToValTypes>(); - if (!equalValTypes(func->type()->params(), arg_valtypes) || - !equalValTypes(func->type()->results(), result_valtypes)) { - fail(FailState::UnableToInitializeCode, - "Bad function signature for: " + std::string(function_name) + - ", want: " + printValTypes(arg_valtypes) + " -> " + printValTypes(result_valtypes) + - ", but the module exports: " + printValTypes(func->type()->params()) + " -> " + - printValTypes(func->type()->results())); - *function = nullptr; - return; - } - *function = [func, function_name, this](ContextBase *context, Args... args) -> R { - const bool log = cmpLogLevel(LogLevel::trace); - SaveRestoreContext saved_context(context); - wasm::vec results = wasm::vec::make_uninitialized(1); - wasm::own trap = nullptr; - - // Workaround for MSVC++ not supporting zero-sized arrays. - if constexpr (sizeof...(args) > 0) { - wasm::vec params = wasm::vec::make(makeVal(args)...); - if (log) { - integration()->trace("[host->vm] " + std::string(function_name) + "(" + - printValues(params, sizeof...(Args)) + ")"); - } - trap = func->call(params, results); - } else { - if (log) { - integration()->trace("[host->vm] " + std::string(function_name) + "()"); - } - trap = func->call(wasm::vec::make_uninitialized(), results); - } - - if (trap) { - fail(FailState::RuntimeError, getFailMessage(std::string(function_name), std::move(trap))); - return R{}; - } - R rvalue = results[0].get::type>(); - if (log) { - integration()->trace("[host<-vm] " + std::string(function_name) + - " return: " + std::to_string(rvalue)); - } - return rvalue; - }; -} - -void V8::terminate() { - auto *store_impl = reinterpret_cast(store_.get()); - auto *isolate = store_impl->isolate(); - isolate->TerminateExecution(); -} - -std::string V8::getFailMessage(std::string_view function_name, wasm::own trap) { - auto message = "Function: " + std::string(function_name) + " failed: "; - message += std::string(trap->message().get(), trap->message().size()); - - if (function_names_index_.empty()) { - return message; - } - - auto trace = trap->trace(); - message += "\nProxy-Wasm plugin in-VM backtrace:"; - for (size_t i = 0; i < trace.size(); ++i) { - auto *frame = trace[i].get(); - std::ostringstream oss; - oss << std::setw(3) << std::setfill(' ') << std::to_string(i); - message += "\n" + oss.str() + ": "; - - std::stringstream address; - address << std::hex << frame->module_offset(); - message += " 0x" + address.str() + " - "; - - auto func_index = frame->func_index(); - auto it = function_names_index_.find(func_index); - if (it != function_names_index_.end()) { - message += it->second; - } else { - message += "unknown(function index:" + std::to_string(func_index) + ")"; - } - } - return message; -} - -} // namespace v8 - -std::unique_ptr createV8Vm() { return std::make_unique(); } - -} // namespace proxy_wasm From 008c46de74f9011c35bd98dbd2bf975ca90c43c3 Mon Sep 17 00:00:00 2001 From: Rachel Green Date: Mon, 4 Aug 2025 23:19:07 +0000 Subject: [PATCH 3/7] Added no-op implementations of warm() to other subclasses of WasmVm Signed-off-by: Rachel Green --- src/wamr/wamr.cc | 2 ++ src/wasmedge/wasmedge.cc | 3 +++ src/wasmtime/wasmtime.cc | 3 +++ 3 files changed, 8 insertions(+) diff --git a/src/wamr/wamr.cc b/src/wamr/wamr.cc index 482a59bf1..e403908c0 100644 --- a/src/wamr/wamr.cc +++ b/src/wamr/wamr.cc @@ -89,6 +89,8 @@ class Wamr : public WasmVm { void terminate() override {} bool usesWasmByteOrder() override { return true; } + + void warm() override {} private: template diff --git a/src/wasmedge/wasmedge.cc b/src/wasmedge/wasmedge.cc index 596af0c9e..03562ec67 100644 --- a/src/wasmedge/wasmedge.cc +++ b/src/wasmedge/wasmedge.cc @@ -264,6 +264,7 @@ class WasmEdge : public WasmVm { }; FOR_ALL_WASM_VM_EXPORTS(_GET_MODULE_FUNCTION) #undef _GET_MODULE_FUNCTION + private: template void registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, @@ -284,6 +285,8 @@ class WasmEdge : public WasmVm { void terminate() override {} bool usesWasmByteOrder() override { return true; } + void warm() override {} + WasmEdgeLoaderPtr loader_; WasmEdgeValidatorPtr validator_; WasmEdgeExecutorPtr executor_; diff --git a/src/wasmtime/wasmtime.cc b/src/wasmtime/wasmtime.cc index c4a7646f0..8acf9fb69 100644 --- a/src/wasmtime/wasmtime.cc +++ b/src/wasmtime/wasmtime.cc @@ -80,6 +80,7 @@ class Wasmtime : public WasmVm { }; FOR_ALL_WASM_VM_EXPORTS(_GET_MODULE_FUNCTION) #undef _GET_MODULE_FUNCTION + private: template void registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, @@ -100,6 +101,8 @@ class Wasmtime : public WasmVm { void terminate() override {} bool usesWasmByteOrder() override { return true; } + void warm() override {} + WasmStorePtr store_; WasmModulePtr module_; WasmSharedModulePtr shared_module_; From 068b288610a4e5e220c9871b562b4fd000764992 Mon Sep 17 00:00:00 2001 From: Rachel Green Date: Tue, 5 Aug 2025 15:31:50 +0000 Subject: [PATCH 4/7] Applied clang-tidy changes. Signed-off-by: Rachel Green --- src/wamr/wamr.cc | 2 +- src/wasmtime/wasmtime.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wamr/wamr.cc b/src/wamr/wamr.cc index e403908c0..6f7a3fcb4 100644 --- a/src/wamr/wamr.cc +++ b/src/wamr/wamr.cc @@ -89,7 +89,7 @@ class Wamr : public WasmVm { void terminate() override {} bool usesWasmByteOrder() override { return true; } - + void warm() override {} private: diff --git a/src/wasmtime/wasmtime.cc b/src/wasmtime/wasmtime.cc index 8acf9fb69..1f9c5891e 100644 --- a/src/wasmtime/wasmtime.cc +++ b/src/wasmtime/wasmtime.cc @@ -80,7 +80,7 @@ class Wasmtime : public WasmVm { }; FOR_ALL_WASM_VM_EXPORTS(_GET_MODULE_FUNCTION) #undef _GET_MODULE_FUNCTION - + private: template void registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, From 3a7c1bcfeb22dffee41aba13c2825073de8105b9 Mon Sep 17 00:00:00 2001 From: Rachel Green Date: Tue, 5 Aug 2025 17:40:13 +0000 Subject: [PATCH 5/7] Feat: Add 'Plugin Crash' to messages logged when plugins crash to improve error observability. Signed-off-by: Rachel Green --- src/v8/v8.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/v8/v8.cc b/src/v8/v8.cc index f51cdfffa..ad2c5af39 100644 --- a/src/v8/v8.cc +++ b/src/v8/v8.cc @@ -108,7 +108,7 @@ class V8 : public WasmVm { private: wasm::own trap(std::string message); - std::string getFailMessage(std::string_view function_name, wasm::own trap); + std::string getPluginFailMessage(std::string_view function_name, wasm::own trap); template void registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, @@ -650,7 +650,8 @@ void V8::getModuleFunctionImpl(std::string_view function_name, } if (trap) { - fail(FailState::RuntimeError, getFailMessage(std::string(function_name), std::move(trap))); + fail(FailState::RuntimeError, + getPluginFailMessage(std::string(function_name), std::move(trap))); return; } if (log) { @@ -702,7 +703,8 @@ void V8::getModuleFunctionImpl(std::string_view function_name, } if (trap) { - fail(FailState::RuntimeError, getFailMessage(std::string(function_name), std::move(trap))); + fail(FailState::RuntimeError, + getPluginFailMessage(std::string(function_name), std::move(trap))); return R{}; } R rvalue = results[0].get::type>(); @@ -722,8 +724,8 @@ void V8::terminate() { void V8::warm() { initStore(); } -std::string V8::getFailMessage(std::string_view function_name, wasm::own trap) { - auto message = "Function: " + std::string(function_name) + " failed: "; +std::string V8::getPluginFailMessage(std::string_view function_name, wasm::own trap) { + auto message = "Plugin Crash: Function: " + std::string(function_name) + " failed: "; message += std::string(trap->message().get(), trap->message().size()); if (function_names_index_.empty()) { From fb145cb8c2f48d7277af1d4ac05af53464e9ea18 Mon Sep 17 00:00:00 2001 From: Rachel Green Date: Tue, 12 Aug 2025 17:36:14 +0000 Subject: [PATCH 6/7] Add all init*Engine() static functions, TestVm.Init test, and all warm() implementations. Signed-off-by: Rachel Green --- include/proxy-wasm/v8.h | 1 + include/proxy-wasm/wamr.h | 1 + include/proxy-wasm/wasmtime.h | 1 + src/v8/v8.cc | 2 ++ src/wamr/wamr.cc | 13 +++++++++++- src/wasmedge/wasmedge.cc | 16 +++++++++++--- src/wasmtime/wasmtime.cc | 15 ++++++++++++- test/wasm_vm_test.cc | 40 +++++++++++++++++++++++++++++++++++ 8 files changed, 84 insertions(+), 5 deletions(-) diff --git a/include/proxy-wasm/v8.h b/include/proxy-wasm/v8.h index 73c91b956..7d2aaa9d8 100644 --- a/include/proxy-wasm/v8.h +++ b/include/proxy-wasm/v8.h @@ -21,6 +21,7 @@ namespace proxy_wasm { +bool initv8Engine(); std::unique_ptr createV8Vm(); } // namespace proxy_wasm diff --git a/include/proxy-wasm/wamr.h b/include/proxy-wasm/wamr.h index 98ff72e31..32a05e94a 100644 --- a/include/proxy-wasm/wamr.h +++ b/include/proxy-wasm/wamr.h @@ -21,6 +21,7 @@ namespace proxy_wasm { +bool initWamrEngine(); std::unique_ptr createWamrVm(); } // namespace proxy_wasm diff --git a/include/proxy-wasm/wasmtime.h b/include/proxy-wasm/wasmtime.h index e3fe4b48c..11c4ae7a3 100644 --- a/include/proxy-wasm/wasmtime.h +++ b/include/proxy-wasm/wasmtime.h @@ -18,6 +18,7 @@ namespace proxy_wasm { +bool initWasmtimeEngine(); std::unique_ptr createWasmtimeVm(); } // namespace proxy_wasm diff --git a/src/v8/v8.cc b/src/v8/v8.cc index ad2c5af39..f7db6cefc 100644 --- a/src/v8/v8.cc +++ b/src/v8/v8.cc @@ -757,6 +757,8 @@ std::string V8::getPluginFailMessage(std::string_view function_name, wasm::own createV8Vm() { return std::make_unique(); } } // namespace proxy_wasm diff --git a/src/wamr/wamr.cc b/src/wamr/wamr.cc index 6f7a3fcb4..b2431580e 100644 --- a/src/wamr/wamr.cc +++ b/src/wamr/wamr.cc @@ -121,9 +121,16 @@ class Wamr : public WasmVm { std::unordered_map module_functions_; }; +void Wamr::initStore() { + if (store_ != nullptr) { + return; + } + store_ = wasm_store_new(engine()); +} + bool Wamr::load(std::string_view bytecode, std::string_view precompiled, const std::unordered_map & /*function_names*/) { - store_ = wasm_store_new(engine()); + initStore(); if (store_ == nullptr) { return false; } @@ -699,8 +706,12 @@ void Wamr::getModuleFunctionImpl(std::string_view function_name, }; }; +void Wamr::warm() { initStore(); } + } // namespace wamr +bool initWamrEngine() { return wamr::engine() != nullptr; } + std::unique_ptr createWamrVm() { return std::make_unique(); } } // namespace proxy_wasm diff --git a/src/wasmedge/wasmedge.cc b/src/wasmedge/wasmedge.cc index 03562ec67..212f9dadf 100644 --- a/src/wasmedge/wasmedge.cc +++ b/src/wasmedge/wasmedge.cc @@ -317,13 +317,19 @@ bool WasmEdge::load(std::string_view bytecode, std::string_view /*precompiled*/, return true; } +void WasmEdge::initStore() { + if (store_ != nullptr) { + return; + } + store_ = WasmEdge_StoreCreate(); +} + bool WasmEdge::link(std::string_view /*debug_name*/) { assert(ast_module_ != nullptr); // Create store and register imports. - if (store_ == nullptr) { - store_ = WasmEdge_StoreCreate(); - } + initStore(); + if (store_ == nullptr) { return false; } @@ -612,8 +618,12 @@ void WasmEdge::getModuleFunctionImpl(std::string_view function_name, }; } +void WasmEdge::warm() { initStore(); } + } // namespace WasmEdge +bool initWasmEdgeEngine() { return wasmedge::engine() != nullptr; } + std::unique_ptr createWasmEdgeVm() { return std::make_unique(); } } // namespace proxy_wasm diff --git a/src/wasmtime/wasmtime.cc b/src/wasmtime/wasmtime.cc index 1f9c5891e..4a6d0c843 100644 --- a/src/wasmtime/wasmtime.cc +++ b/src/wasmtime/wasmtime.cc @@ -103,6 +103,9 @@ class Wasmtime : public WasmVm { void warm() override {} + // Initialize the V8 engine and store if necessary. + void initStore(); + WasmStorePtr store_; WasmModulePtr module_; WasmSharedModulePtr shared_module_; @@ -114,9 +117,15 @@ class Wasmtime : public WasmVm { std::unordered_map module_functions_; }; +void Wasmtime::initStore() { + if (store_ != nullptr) { + return; + } + store_ = wasm_store_new(engine()); + bool Wasmtime::load(std::string_view bytecode, std::string_view /*precompiled*/, const std::unordered_map & /*function_names*/) { - store_ = wasm_store_new(engine()); + initStore(); if (store_ == nullptr) { return false; } @@ -696,8 +705,12 @@ void Wasmtime::getModuleFunctionImpl(std::string_view function_name, }; }; +void Wasmtime::warm() { initStore(); } + } // namespace wasmtime +bool initWasmtimeEngine() { return wasmtime::engine() != nullptr; } + std::unique_ptr createWasmtimeVm() { return std::make_unique(); } } // namespace proxy_wasm diff --git a/test/wasm_vm_test.cc b/test/wasm_vm_test.cc index 4d084cc75..5c7fa09af 100644 --- a/test/wasm_vm_test.cc +++ b/test/wasm_vm_test.cc @@ -30,6 +30,46 @@ INSTANTIATE_TEST_SUITE_P(WasmEngines, TestVm, testing::ValuesIn(getWasmEngines() return info.param; }); +TEST_P(TestVm, Init) { + std::chrono::time_point time2; + + auto time1 = std::chrono::steady_clock::now(); + if (engine_ == "v8") { +#if defined(PROXY_WASM_HOST_ENGINE_V8) + EXPECT_TRUE(proxy_wasm::initV8Engine()); + time2 = std::chrono::steady_clock::now(); + EXPECT_TRUE(proxy_wasm::initV8Engine()); +#endif + } else if (engine_ == "wamr") { +#if defined(PROXY_WASM_HOST_ENGINE_WAMR) + EXPECT_TRUE(proxy_wasm::initWamrEngine()); + time2 = std::chrono::steady_clock::now(); + EXPECT_TRUE(proxy_wasm::initWamrEngine()); +#endif + } else if (engine_ == "wasmtime") { +#if defined(PROXY_WASM_HOST_ENGINE_WASMTIME) + EXPECT_TRUE(proxy_wasm::initWasmtimeEngine()); + time2 = std::chrono::steady_clock::now(); + EXPECT_TRUE(proxy_wasm::initWasmtimeEngine()); +#endif + } else { + return; + } + auto time3 = std::chrono::steady_clock::now(); + + auto cold = std::chrono::duration_cast(time2 - time1).count(); + auto warm = std::chrono::duration_cast(time3 - time2).count(); + + std::cout << "\"cold\" engine time: " << cold << "ns" << std::endl; + std::cout << "\"warm\" engine time: " << warm << "ns" << std::endl; + + // Verify that getting a "warm" engine takes less than 10us. + EXPECT_LE(warm, 10000); + + // Verify that getting a "warm" engine takes at least 50x less time than getting a "cold" one. + EXPECT_LE(warm * 50, cold); +} + TEST_P(TestVm, Basic) { if (engine_ == "wasmedge") { EXPECT_EQ(vm_->cloneable(), proxy_wasm::Cloneable::NotCloneable); From 56bf0a85824d264aee6061ddfbb04e6245bbf0e1 Mon Sep 17 00:00:00 2001 From: Rachel Green Date: Tue, 12 Aug 2025 17:44:21 +0000 Subject: [PATCH 7/7] Applied clang-format Signed-off-by: Rachel Green --- src/wasmedge/wasmedge.cc | 2 +- src/wasmtime/wasmtime.cc | 999 ++++++++++++++++++++------------------- 2 files changed, 502 insertions(+), 499 deletions(-) diff --git a/src/wasmedge/wasmedge.cc b/src/wasmedge/wasmedge.cc index 212f9dadf..c2e295911 100644 --- a/src/wasmedge/wasmedge.cc +++ b/src/wasmedge/wasmedge.cc @@ -329,7 +329,7 @@ bool WasmEdge::link(std::string_view /*debug_name*/) { // Create store and register imports. initStore(); - + if (store_ == nullptr) { return false; } diff --git a/src/wasmtime/wasmtime.cc b/src/wasmtime/wasmtime.cc index 4a6d0c843..5e98fc19e 100644 --- a/src/wasmtime/wasmtime.cc +++ b/src/wasmtime/wasmtime.cc @@ -123,589 +123,592 @@ void Wasmtime::initStore() { } store_ = wasm_store_new(engine()); -bool Wasmtime::load(std::string_view bytecode, std::string_view /*precompiled*/, - const std::unordered_map & /*function_names*/) { - initStore(); - if (store_ == nullptr) { - return false; - } - - WasmByteVec vec; - wasm_byte_vec_new(vec.get(), bytecode.size(), bytecode.data()); - - module_ = wasm_module_new(store_.get(), vec.get()); - if (module_ == nullptr) { - return false; - } + bool Wasmtime::load(std::string_view bytecode, std::string_view /*precompiled*/, + const std::unordered_map & /*function_names*/) { + initStore(); + if (store_ == nullptr) { + return false; + } - shared_module_ = wasm_module_share(module_.get()); - if (shared_module_ == nullptr) { - return false; - } + WasmByteVec vec; + wasm_byte_vec_new(vec.get(), bytecode.size(), bytecode.data()); - return true; -} + module_ = wasm_module_new(store_.get(), vec.get()); + if (module_ == nullptr) { + return false; + } -std::unique_ptr Wasmtime::clone() { - assert(shared_module_ != nullptr); + shared_module_ = wasm_module_share(module_.get()); + if (shared_module_ == nullptr) { + return false; + } - auto clone = std::make_unique(); - if (clone == nullptr) { - return nullptr; + return true; } - clone->store_ = wasm_store_new(engine()); - if (clone->store_ == nullptr) { - return nullptr; - } + std::unique_ptr Wasmtime::clone() { + assert(shared_module_ != nullptr); - clone->module_ = wasm_module_obtain(clone->store_.get(), shared_module_.get()); - if (clone->module_ == nullptr) { - return nullptr; - } + auto clone = std::make_unique(); + if (clone == nullptr) { + return nullptr; + } - auto *integration_clone = integration()->clone(); - if (integration_clone == nullptr) { - return nullptr; - } - clone->integration().reset(integration_clone); + clone->store_ = wasm_store_new(engine()); + if (clone->store_ == nullptr) { + return nullptr; + } - return clone; -} + clone->module_ = wasm_module_obtain(clone->store_.get(), shared_module_.get()); + if (clone->module_ == nullptr) { + return nullptr; + } -static bool equalValTypes(const wasm_valtype_vec_t *left, const wasm_valtype_vec_t *right) { - if (left->size != right->size) { - return false; + auto *integration_clone = integration()->clone(); + if (integration_clone == nullptr) { + return nullptr; + } + clone->integration().reset(integration_clone); + + return clone; } - for (size_t i = 0; i < left->size; i++) { - if (wasm_valtype_kind(left->data[i]) != wasm_valtype_kind(right->data[i])) { + static bool equalValTypes(const wasm_valtype_vec_t *left, const wasm_valtype_vec_t *right) { + if (left->size != right->size) { return false; } - } - return true; -} + for (size_t i = 0; i < left->size; i++) { + if (wasm_valtype_kind(left->data[i]) != wasm_valtype_kind(right->data[i])) { + return false; + } + } -static std::string printValue(const wasm_val_t &value) { - switch (value.kind) { - case WASM_I32: - return std::to_string(value.of.i32); - case WASM_I64: - return std::to_string(value.of.i64); - case WASM_F32: - return std::to_string(value.of.f32); - case WASM_F64: - return std::to_string(value.of.f64); - default: - return "unknown"; + return true; } -} -static std::string printValues(const wasm_val_vec_t *values) { - if (values->size == 0) { - return ""; + static std::string printValue(const wasm_val_t &value) { + switch (value.kind) { + case WASM_I32: + return std::to_string(value.of.i32); + case WASM_I64: + return std::to_string(value.of.i64); + case WASM_F32: + return std::to_string(value.of.f32); + case WASM_F64: + return std::to_string(value.of.f64); + default: + return "unknown"; + } } - std::string s; - for (size_t i = 0; i < values->size; i++) { - if (i != 0U) { - s.append(", "); + static std::string printValues(const wasm_val_vec_t *values) { + if (values->size == 0) { + return ""; } - s.append(printValue(values->data[i])); - } - return s; -} -static const char *printValKind(wasm_valkind_t kind) { - switch (kind) { - case WASM_I32: - return "i32"; - case WASM_I64: - return "i64"; - case WASM_F32: - return "f32"; - case WASM_F64: - return "f64"; - case WASM_EXTERNREF: - return "externref"; - case WASM_FUNCREF: - return "funcref"; - default: - return "unknown"; + std::string s; + for (size_t i = 0; i < values->size; i++) { + if (i != 0U) { + s.append(", "); + } + s.append(printValue(values->data[i])); + } + return s; } -} -static std::string printValTypes(const wasm_valtype_vec_t *types) { - if (types->size == 0) { - return "void"; + static const char *printValKind(wasm_valkind_t kind) { + switch (kind) { + case WASM_I32: + return "i32"; + case WASM_I64: + return "i64"; + case WASM_F32: + return "f32"; + case WASM_F64: + return "f64"; + case WASM_EXTERNREF: + return "externref"; + case WASM_FUNCREF: + return "funcref"; + default: + return "unknown"; + } } - std::string s; - s.reserve(types->size * 8 /* max size + " " */ - 1); - for (size_t i = 0; i < types->size; i++) { - if (i != 0U) { - s.append(" "); + static std::string printValTypes(const wasm_valtype_vec_t *types) { + if (types->size == 0) { + return "void"; } - s.append(printValKind(wasm_valtype_kind(types->data[i]))); + + std::string s; + s.reserve(types->size * 8 /* max size + " " */ - 1); + for (size_t i = 0; i < types->size; i++) { + if (i != 0U) { + s.append(" "); + } + s.append(printValKind(wasm_valtype_kind(types->data[i]))); + } + return s; } - return s; -} -bool Wasmtime::link(std::string_view /*debug_name*/) { - assert(module_ != nullptr); - - WasmImporttypeVec import_types; - wasm_module_imports(module_.get(), import_types.get()); - - std::vector imports; - for (size_t i = 0; i < import_types.get()->size; i++) { - const wasm_name_t *module_name_ptr = wasm_importtype_module(import_types.get()->data[i]); - const wasm_name_t *name_ptr = wasm_importtype_name(import_types.get()->data[i]); - const wasm_externtype_t *extern_type = wasm_importtype_type(import_types.get()->data[i]); - - std::string_view module_name(module_name_ptr->data, module_name_ptr->size); - std::string_view name(name_ptr->data, name_ptr->size); - assert(name_ptr->size > 0); - switch (wasm_externtype_kind(extern_type)) { - case WASM_EXTERN_FUNC: { - auto it = host_functions_.find(std::string(module_name) + "." + std::string(name)); - if (it == host_functions_.end()) { + bool Wasmtime::link(std::string_view /*debug_name*/) { + assert(module_ != nullptr); + + WasmImporttypeVec import_types; + wasm_module_imports(module_.get(), import_types.get()); + + std::vector imports; + for (size_t i = 0; i < import_types.get()->size; i++) { + const wasm_name_t *module_name_ptr = wasm_importtype_module(import_types.get()->data[i]); + const wasm_name_t *name_ptr = wasm_importtype_name(import_types.get()->data[i]); + const wasm_externtype_t *extern_type = wasm_importtype_type(import_types.get()->data[i]); + + std::string_view module_name(module_name_ptr->data, module_name_ptr->size); + std::string_view name(name_ptr->data, name_ptr->size); + assert(name_ptr->size > 0); + switch (wasm_externtype_kind(extern_type)) { + case WASM_EXTERN_FUNC: { + auto it = host_functions_.find(std::string(module_name) + "." + std::string(name)); + if (it == host_functions_.end()) { + fail(FailState::UnableToInitializeCode, + std::string("Failed to load Wasm module due to a missing import: ") + + std::string(module_name) + "." + std::string(name)); + return false; + } + + auto *func = it->second->callback_.get(); + const wasm_functype_t *exp_type = wasm_externtype_as_functype_const(extern_type); + WasmFunctypePtr actual_type = wasm_func_type(it->second->callback_.get()); + if (!equalValTypes(wasm_functype_params(exp_type), + wasm_functype_params(actual_type.get())) || + !equalValTypes(wasm_functype_results(exp_type), + wasm_functype_results(actual_type.get()))) { + fail(FailState::UnableToInitializeCode, + std::string( + "Failed to load Wasm module due to an import type mismatch for function ") + + std::string(module_name) + "." + std::string(name) + + ", want: " + printValTypes(wasm_functype_params(exp_type)) + " -> " + + printValTypes(wasm_functype_results(exp_type)) + + ", but host exports: " + printValTypes(wasm_functype_params(actual_type.get())) + + " -> " + printValTypes(wasm_functype_results(actual_type.get()))); + return false; + } + imports.push_back(wasm_func_as_extern(func)); + } break; + case WASM_EXTERN_GLOBAL: { + // TODO(mathetake): add support when/if needed. fail(FailState::UnableToInitializeCode, - std::string("Failed to load Wasm module due to a missing import: ") + - std::string(module_name) + "." + std::string(name)); + "Failed to load Wasm module due to a missing import: " + std::string(module_name) + + "." + std::string(name)); return false; + } break; + case WASM_EXTERN_MEMORY: { + assert(memory_ == nullptr); + const wasm_memorytype_t *memory_type = + wasm_externtype_as_memorytype_const(extern_type); // owned by `extern_type` + if (memory_type == nullptr) { + return false; + } + memory_ = wasm_memory_new(store_.get(), memory_type); + if (memory_ == nullptr) { + return false; + } + imports.push_back(wasm_memory_as_extern(memory_.get())); + } break; + case WASM_EXTERN_TABLE: { + assert(table_ == nullptr); + const wasm_tabletype_t *table_type = + wasm_externtype_as_tabletype_const(extern_type); // owned by `extern_type` + if (table_type == nullptr) { + return false; + } + table_ = wasm_table_new(store_.get(), table_type, nullptr); + if (table_ == nullptr) { + return false; + } + imports.push_back(wasm_table_as_extern(table_.get())); + } break; } + } - auto *func = it->second->callback_.get(); - const wasm_functype_t *exp_type = wasm_externtype_as_functype_const(extern_type); - WasmFunctypePtr actual_type = wasm_func_type(it->second->callback_.get()); - if (!equalValTypes(wasm_functype_params(exp_type), wasm_functype_params(actual_type.get())) || - !equalValTypes(wasm_functype_results(exp_type), - wasm_functype_results(actual_type.get()))) { - fail( - FailState::UnableToInitializeCode, - std::string("Failed to load Wasm module due to an import type mismatch for function ") + - std::string(module_name) + "." + std::string(name) + - ", want: " + printValTypes(wasm_functype_params(exp_type)) + " -> " + - printValTypes(wasm_functype_results(exp_type)) + - ", but host exports: " + printValTypes(wasm_functype_params(actual_type.get())) + - " -> " + printValTypes(wasm_functype_results(actual_type.get()))); - return false; - } - imports.push_back(wasm_func_as_extern(func)); - } break; - case WASM_EXTERN_GLOBAL: { - // TODO(mathetake): add support when/if needed. - fail(FailState::UnableToInitializeCode, - "Failed to load Wasm module due to a missing import: " + std::string(module_name) + "." + - std::string(name)); + if (import_types.get()->size != imports.size()) { return false; - } break; - case WASM_EXTERN_MEMORY: { - assert(memory_ == nullptr); - const wasm_memorytype_t *memory_type = - wasm_externtype_as_memorytype_const(extern_type); // owned by `extern_type` - if (memory_type == nullptr) { - return false; - } - memory_ = wasm_memory_new(store_.get(), memory_type); - if (memory_ == nullptr) { - return false; - } - imports.push_back(wasm_memory_as_extern(memory_.get())); - } break; - case WASM_EXTERN_TABLE: { - assert(table_ == nullptr); - const wasm_tabletype_t *table_type = - wasm_externtype_as_tabletype_const(extern_type); // owned by `extern_type` - if (table_type == nullptr) { - return false; - } - table_ = wasm_table_new(store_.get(), table_type, nullptr); - if (table_ == nullptr) { - return false; + } + + wasm_extern_vec_t imports_vec = {imports.size(), imports.data()}; + instance_ = wasm_instance_new(store_.get(), module_.get(), &imports_vec, nullptr); + if (instance_ == nullptr) { + fail(FailState::UnableToInitializeCode, "Failed to create new Wasm instance"); + return false; + } + + WasmExportTypeVec export_types; + wasm_module_exports(module_.get(), export_types.get()); + + WasmExternVec exports; + wasm_instance_exports(instance_.get(), exports.get()); + + for (size_t i = 0; i < export_types.get()->size; i++) { + const wasm_externtype_t *exp_extern_type = wasm_exporttype_type(export_types.get()->data[i]); + wasm_extern_t *actual_extern = exports.get()->data[i]; + + wasm_externkind_t kind = wasm_extern_kind(actual_extern); + assert(kind == wasm_externtype_kind(exp_extern_type)); + switch (kind) { + case WASM_EXTERN_FUNC: { + WasmFuncPtr func = wasm_func_copy(wasm_extern_as_func(actual_extern)); + const wasm_name_t *name_ptr = wasm_exporttype_name(export_types.get()->data[i]); + module_functions_.insert_or_assign(std::string(name_ptr->data, name_ptr->size), + std::move(func)); + } break; + case WASM_EXTERN_GLOBAL: { + // TODO(mathetake): add support when/if needed. + } break; + case WASM_EXTERN_MEMORY: { + assert(memory_ == nullptr); + memory_ = wasm_memory_copy(wasm_extern_as_memory(actual_extern)); + if (memory_ == nullptr) { + return false; + } + } break; + case WASM_EXTERN_TABLE: { + // TODO(mathetake): add support when/if needed. + } break; } - imports.push_back(wasm_table_as_extern(table_.get())); - } break; } + return true; } - if (import_types.get()->size != imports.size()) { - return false; - } + uint64_t Wasmtime::getMemorySize() { return wasm_memory_data_size(memory_.get()); } - wasm_extern_vec_t imports_vec = {imports.size(), imports.data()}; - instance_ = wasm_instance_new(store_.get(), module_.get(), &imports_vec, nullptr); - if (instance_ == nullptr) { - fail(FailState::UnableToInitializeCode, "Failed to create new Wasm instance"); - return false; + std::optional Wasmtime::getMemory(uint64_t pointer, uint64_t size) { + assert(memory_ != nullptr); + if (pointer + size > wasm_memory_data_size(memory_.get())) { + return std::nullopt; + } + return std::string_view(wasm_memory_data(memory_.get()) + pointer, size); } - WasmExportTypeVec export_types; - wasm_module_exports(module_.get(), export_types.get()); + bool Wasmtime::setMemory(uint64_t pointer, uint64_t size, const void *data) { + assert(memory_ != nullptr); + if (pointer + size > wasm_memory_data_size(memory_.get())) { + return false; + } + ::memcpy(wasm_memory_data(memory_.get()) + pointer, data, size); + return true; + } - WasmExternVec exports; - wasm_instance_exports(instance_.get(), exports.get()); + bool Wasmtime::getWord(uint64_t pointer, Word *word) { + assert(memory_ != nullptr); + constexpr auto size = sizeof(uint32_t); + if (pointer + size > wasm_memory_data_size(memory_.get())) { + return false; + } - for (size_t i = 0; i < export_types.get()->size; i++) { - const wasm_externtype_t *exp_extern_type = wasm_exporttype_type(export_types.get()->data[i]); - wasm_extern_t *actual_extern = exports.get()->data[i]; + uint32_t word32; + ::memcpy(&word32, wasm_memory_data(memory_.get()) + pointer, size); + word->u64_ = wasmtoh(word32, true); + return true; + } - wasm_externkind_t kind = wasm_extern_kind(actual_extern); - assert(kind == wasm_externtype_kind(exp_extern_type)); - switch (kind) { - case WASM_EXTERN_FUNC: { - WasmFuncPtr func = wasm_func_copy(wasm_extern_as_func(actual_extern)); - const wasm_name_t *name_ptr = wasm_exporttype_name(export_types.get()->data[i]); - module_functions_.insert_or_assign(std::string(name_ptr->data, name_ptr->size), - std::move(func)); - } break; - case WASM_EXTERN_GLOBAL: { - // TODO(mathetake): add support when/if needed. - } break; - case WASM_EXTERN_MEMORY: { - assert(memory_ == nullptr); - memory_ = wasm_memory_copy(wasm_extern_as_memory(actual_extern)); - if (memory_ == nullptr) { - return false; - } - } break; - case WASM_EXTERN_TABLE: { - // TODO(mathetake): add support when/if needed. - } break; + bool Wasmtime::setWord(uint64_t pointer, Word word) { + constexpr auto size = sizeof(uint32_t); + if (pointer + size > wasm_memory_data_size(memory_.get())) { + return false; } + uint32_t word32 = htowasm(word.u32(), true); + ::memcpy(wasm_memory_data(memory_.get()) + pointer, &word32, size); + return true; } - return true; -} -uint64_t Wasmtime::getMemorySize() { return wasm_memory_data_size(memory_.get()); } - -std::optional Wasmtime::getMemory(uint64_t pointer, uint64_t size) { - assert(memory_ != nullptr); - if (pointer + size > wasm_memory_data_size(memory_.get())) { - return std::nullopt; + template void assignVal(T t, wasm_val_t & val); + template <> void assignVal(Word t, wasm_val_t & val) { + val.kind = WASM_I32; + val.of.i32 = static_cast(t.u64_); } - return std::string_view(wasm_memory_data(memory_.get()) + pointer, size); -} - -bool Wasmtime::setMemory(uint64_t pointer, uint64_t size, const void *data) { - assert(memory_ != nullptr); - if (pointer + size > wasm_memory_data_size(memory_.get())) { - return false; + template <> void assignVal(uint32_t t, wasm_val_t &val) { + val.kind = WASM_I32; + val.of.i32 = static_cast(t); + } + template <> void assignVal(uint64_t t, wasm_val_t &val) { + val.kind = WASM_I64; + val.of.i64 = static_cast(t); + } + template <> void assignVal(double t, wasm_val_t &val) { + val.kind = WASM_F64; + val.of.f64 = t; } - ::memcpy(wasm_memory_data(memory_.get()) + pointer, data, size); - return true; -} -bool Wasmtime::getWord(uint64_t pointer, Word *word) { - assert(memory_ != nullptr); - constexpr auto size = sizeof(uint32_t); - if (pointer + size > wasm_memory_data_size(memory_.get())) { - return false; + template wasm_val_t makeVal(T t) { + wasm_val_t val{}; + assignVal(t, val); + return val; } - uint32_t word32; - ::memcpy(&word32, wasm_memory_data(memory_.get()) + pointer, size); - word->u64_ = wasmtoh(word32, true); - return true; -} + template struct ConvertWordType { + using type = T; // NOLINT(readability-identifier-naming) + }; + template <> struct ConvertWordType { + using type = uint32_t; // NOLINT(readability-identifier-naming) + }; + + template auto convertArgToValTypePtr(); + template <> auto convertArgToValTypePtr() { return wasm_valtype_new_i32(); }; + template <> auto convertArgToValTypePtr() { return wasm_valtype_new_i32(); }; + template <> auto convertArgToValTypePtr() { return wasm_valtype_new_i64(); }; + template <> auto convertArgToValTypePtr() { return wasm_valtype_new_i64(); }; + template <> auto convertArgToValTypePtr() { return wasm_valtype_new_f64(); }; -bool Wasmtime::setWord(uint64_t pointer, Word word) { - constexpr auto size = sizeof(uint32_t); - if (pointer + size > wasm_memory_data_size(memory_.get())) { - return false; + template T convertValueTypeToArg(wasm_val_t val); + template <> uint32_t convertValueTypeToArg(wasm_val_t val) { + return static_cast(val.of.i32); } - uint32_t word32 = htowasm(word.u32(), true); - ::memcpy(wasm_memory_data(memory_.get()) + pointer, &word32, size); - return true; -} + template <> Word convertValueTypeToArg(wasm_val_t val) { return val.of.i32; } + template <> int64_t convertValueTypeToArg(wasm_val_t val) { return val.of.i64; } + template <> uint64_t convertValueTypeToArg(wasm_val_t val) { + return static_cast(val.of.i64); + } + template <> double convertValueTypeToArg(wasm_val_t val) { return val.of.f64; } -template void assignVal(T t, wasm_val_t &val); -template <> void assignVal(Word t, wasm_val_t &val) { - val.kind = WASM_I32; - val.of.i32 = static_cast(t.u64_); -} -template <> void assignVal(uint32_t t, wasm_val_t &val) { - val.kind = WASM_I32; - val.of.i32 = static_cast(t); -} -template <> void assignVal(uint64_t t, wasm_val_t &val) { - val.kind = WASM_I64; - val.of.i64 = static_cast(t); -} -template <> void assignVal(double t, wasm_val_t &val) { - val.kind = WASM_F64; - val.of.f64 = t; -} + template + constexpr T convertValTypesToArgsTuple(const U &vec, std::index_sequence /*comptime*/) { + return std::make_tuple( + convertValueTypeToArg>::type>( + vec->data[I])...); + } -template wasm_val_t makeVal(T t) { - wasm_val_t val{}; - assignVal(t, val); - return val; -} + template + void convertArgsTupleToValTypesImpl(wasm_valtype_vec_t * types, + std::index_sequence /*comptime*/) { + auto size = std::tuple_size::value; + auto ps = std::array::value>{ + convertArgToValTypePtr::type>()...}; + wasm_valtype_vec_new(types, size, ps.data()); + } -template struct ConvertWordType { - using type = T; // NOLINT(readability-identifier-naming) -}; -template <> struct ConvertWordType { - using type = uint32_t; // NOLINT(readability-identifier-naming) -}; + template ::value>> + void convertArgsTupleToValTypes(wasm_valtype_vec_t * types) { + convertArgsTupleToValTypesImpl(types, Is()); + } -template auto convertArgToValTypePtr(); -template <> auto convertArgToValTypePtr() { return wasm_valtype_new_i32(); }; -template <> auto convertArgToValTypePtr() { return wasm_valtype_new_i32(); }; -template <> auto convertArgToValTypePtr() { return wasm_valtype_new_i64(); }; -template <> auto convertArgToValTypePtr() { return wasm_valtype_new_i64(); }; -template <> auto convertArgToValTypePtr() { return wasm_valtype_new_f64(); }; + template WasmFunctypePtr newWasmNewFuncType() { + WasmValtypeVec params; + WasmValtypeVec results; + convertArgsTupleToValTypes(params.get()); + convertArgsTupleToValTypes>(results.get()); + return wasm_functype_new(params.get(), results.get()); + } -template T convertValueTypeToArg(wasm_val_t val); -template <> uint32_t convertValueTypeToArg(wasm_val_t val) { - return static_cast(val.of.i32); -} -template <> Word convertValueTypeToArg(wasm_val_t val) { return val.of.i32; } -template <> int64_t convertValueTypeToArg(wasm_val_t val) { return val.of.i64; } -template <> uint64_t convertValueTypeToArg(wasm_val_t val) { - return static_cast(val.of.i64); -} -template <> double convertValueTypeToArg(wasm_val_t val) { return val.of.f64; } + template WasmFunctypePtr newWasmNewFuncType() { + WasmValtypeVec params; + WasmValtypeVec results; + convertArgsTupleToValTypes(params.get()); + convertArgsTupleToValTypes>(results.get()); + return wasm_functype_new(params.get(), results.get()); + } -template -constexpr T convertValTypesToArgsTuple(const U &vec, std::index_sequence /*comptime*/) { - return std::make_tuple( - convertValueTypeToArg>::type>( - vec->data[I])...); -} + template + void Wasmtime::registerHostFunctionImpl( + std::string_view module_name, std::string_view function_name, void (*function)(Args...)) { + auto data = + std::make_unique(std::string(module_name) + "." + std::string(function_name)); + + WasmFunctypePtr type = newWasmNewFuncType>(); + WasmFuncPtr func = wasm_func_new_with_env( + store_.get(), type.get(), + [](void *data, const wasm_val_vec_t *params, + wasm_val_vec_t * /*results*/) -> wasm_trap_t * { + auto *func_data = reinterpret_cast(data); + const bool log = func_data->vm_->cmpLogLevel(LogLevel::trace); + if (log) { + func_data->vm_->integration()->trace("[vm->host] " + func_data->name_ + "(" + + printValues(params) + ")"); + } + auto args = convertValTypesToArgsTuple>( + params, std::make_index_sequence{}); + auto fn = reinterpret_cast(func_data->raw_func_); + std::apply(fn, args); + if (log) { + func_data->vm_->integration()->trace("[vm<-host] " + func_data->name_ + + " return: void"); + } + return nullptr; + }, + data.get(), nullptr); + + data->vm_ = this; + data->callback_ = std::move(func); + data->raw_func_ = reinterpret_cast(function); + host_functions_.insert_or_assign(std::string(module_name) + "." + std::string(function_name), + std::move(data)); + }; -template -void convertArgsTupleToValTypesImpl(wasm_valtype_vec_t *types, - std::index_sequence /*comptime*/) { - auto size = std::tuple_size::value; - auto ps = std::array::value>{ - convertArgToValTypePtr::type>()...}; - wasm_valtype_vec_new(types, size, ps.data()); -} + template + void Wasmtime::registerHostFunctionImpl(std::string_view module_name, + std::string_view function_name, R(*function)(Args...)) { + auto data = + std::make_unique(std::string(module_name) + "." + std::string(function_name)); + WasmFunctypePtr type = newWasmNewFuncType>(); + WasmFuncPtr func = wasm_func_new_with_env( + store_.get(), type.get(), + [](void *data, const wasm_val_vec_t *params, wasm_val_vec_t *results) -> wasm_trap_t * { + auto *func_data = reinterpret_cast(data); + const bool log = func_data->vm_->cmpLogLevel(LogLevel::trace); + if (log) { + func_data->vm_->integration()->trace("[vm->host] " + func_data->name_ + "(" + + printValues(params) + ")"); + } + auto args = convertValTypesToArgsTuple>( + params, std::make_index_sequence{}); + auto fn = reinterpret_cast(func_data->raw_func_); + R res = std::apply(fn, args); + assignVal(res, results->data[0]); + if (log) { + func_data->vm_->integration()->trace("[vm<-host] " + func_data->name_ + + " return: " + std::to_string(res)); + } + return nullptr; + }, + data.get(), nullptr); + + data->vm_ = this; + data->callback_ = std::move(func); + data->raw_func_ = reinterpret_cast(function); + host_functions_.insert_or_assign(std::string(module_name) + "." + std::string(function_name), + std::move(data)); + }; -template ::value>> -void convertArgsTupleToValTypes(wasm_valtype_vec_t *types) { - convertArgsTupleToValTypesImpl(types, Is()); -} + template + void Wasmtime::getModuleFunctionImpl(std::string_view function_name, + std::function * function) { -template WasmFunctypePtr newWasmNewFuncType() { - WasmValtypeVec params; - WasmValtypeVec results; - convertArgsTupleToValTypes(params.get()); - convertArgsTupleToValTypes>(results.get()); - return wasm_functype_new(params.get(), results.get()); -} + auto it = module_functions_.find(std::string(function_name)); + if (it == module_functions_.end()) { + *function = nullptr; + return; + } -template WasmFunctypePtr newWasmNewFuncType() { - WasmValtypeVec params; - WasmValtypeVec results; - convertArgsTupleToValTypes(params.get()); - convertArgsTupleToValTypes>(results.get()); - return wasm_functype_new(params.get(), results.get()); -} + WasmValtypeVec exp_args; + WasmValtypeVec exp_returns; + convertArgsTupleToValTypes>(exp_args.get()); + convertArgsTupleToValTypes>(exp_returns.get()); + wasm_func_t *func = it->second.get(); + WasmFunctypePtr func_type = wasm_func_type(func); -template -void Wasmtime::registerHostFunctionImpl(std::string_view module_name, - std::string_view function_name, void (*function)(Args...)) { - auto data = - std::make_unique(std::string(module_name) + "." + std::string(function_name)); - - WasmFunctypePtr type = newWasmNewFuncType>(); - WasmFuncPtr func = wasm_func_new_with_env( - store_.get(), type.get(), - [](void *data, const wasm_val_vec_t *params, wasm_val_vec_t * /*results*/) -> wasm_trap_t * { - auto *func_data = reinterpret_cast(data); - const bool log = func_data->vm_->cmpLogLevel(LogLevel::trace); - if (log) { - func_data->vm_->integration()->trace("[vm->host] " + func_data->name_ + "(" + - printValues(params) + ")"); - } - auto args = convertValTypesToArgsTuple>( - params, std::make_index_sequence{}); - auto fn = reinterpret_cast(func_data->raw_func_); - std::apply(fn, args); - if (log) { - func_data->vm_->integration()->trace("[vm<-host] " + func_data->name_ + " return: void"); - } - return nullptr; - }, - data.get(), nullptr); - - data->vm_ = this; - data->callback_ = std::move(func); - data->raw_func_ = reinterpret_cast(function); - host_functions_.insert_or_assign(std::string(module_name) + "." + std::string(function_name), - std::move(data)); -}; + if (!equalValTypes(wasm_functype_params(func_type.get()), exp_args.get()) || + !equalValTypes(wasm_functype_results(func_type.get()), exp_returns.get())) { + fail(FailState::UnableToInitializeCode, + "Bad function signature for: " + std::string(function_name) + ", want: " + + printValTypes(exp_args.get()) + " -> " + printValTypes(exp_returns.get()) + + ", but the module exports: " + printValTypes(wasm_functype_params(func_type.get())) + + " -> " + printValTypes(wasm_functype_results(func_type.get()))); + return; + } -template -void Wasmtime::registerHostFunctionImpl(std::string_view module_name, - std::string_view function_name, R (*function)(Args...)) { - auto data = - std::make_unique(std::string(module_name) + "." + std::string(function_name)); - WasmFunctypePtr type = newWasmNewFuncType>(); - WasmFuncPtr func = wasm_func_new_with_env( - store_.get(), type.get(), - [](void *data, const wasm_val_vec_t *params, wasm_val_vec_t *results) -> wasm_trap_t * { - auto *func_data = reinterpret_cast(data); - const bool log = func_data->vm_->cmpLogLevel(LogLevel::trace); + *function = [func, function_name, this](ContextBase *context, Args... args) -> void { + const bool log = cmpLogLevel(LogLevel::trace); + SaveRestoreContext saved_context(context); + wasm_val_vec_t results = WASM_EMPTY_VEC; + WasmTrapPtr trap; + + // Workaround for MSVC++ not supporting zero-sized arrays. + if constexpr (sizeof...(args) > 0) { + wasm_val_t params_arr[] = {makeVal(args)...}; + wasm_val_vec_t params = WASM_ARRAY_VEC(params_arr); if (log) { - func_data->vm_->integration()->trace("[vm->host] " + func_data->name_ + "(" + - printValues(params) + ")"); + integration()->trace("[host->vm] " + std::string(function_name) + "(" + + printValues(¶ms) + ")"); } - auto args = convertValTypesToArgsTuple>( - params, std::make_index_sequence{}); - auto fn = reinterpret_cast(func_data->raw_func_); - R res = std::apply(fn, args); - assignVal(res, results->data[0]); + trap.reset(wasm_func_call(func, ¶ms, &results)); + } else { + wasm_val_vec_t params = WASM_EMPTY_VEC; if (log) { - func_data->vm_->integration()->trace("[vm<-host] " + func_data->name_ + - " return: " + std::to_string(res)); + integration()->trace("[host->vm] " + std::string(function_name) + "()"); } - return nullptr; - }, - data.get(), nullptr); - - data->vm_ = this; - data->callback_ = std::move(func); - data->raw_func_ = reinterpret_cast(function); - host_functions_.insert_or_assign(std::string(module_name) + "." + std::string(function_name), - std::move(data)); -}; - -template -void Wasmtime::getModuleFunctionImpl(std::string_view function_name, - std::function *function) { - - auto it = module_functions_.find(std::string(function_name)); - if (it == module_functions_.end()) { - *function = nullptr; - return; - } - - WasmValtypeVec exp_args; - WasmValtypeVec exp_returns; - convertArgsTupleToValTypes>(exp_args.get()); - convertArgsTupleToValTypes>(exp_returns.get()); - wasm_func_t *func = it->second.get(); - WasmFunctypePtr func_type = wasm_func_type(func); - - if (!equalValTypes(wasm_functype_params(func_type.get()), exp_args.get()) || - !equalValTypes(wasm_functype_results(func_type.get()), exp_returns.get())) { - fail(FailState::UnableToInitializeCode, - "Bad function signature for: " + std::string(function_name) + ", want: " + - printValTypes(exp_args.get()) + " -> " + printValTypes(exp_returns.get()) + - ", but the module exports: " + printValTypes(wasm_functype_params(func_type.get())) + - " -> " + printValTypes(wasm_functype_results(func_type.get()))); - return; - } - - *function = [func, function_name, this](ContextBase *context, Args... args) -> void { - const bool log = cmpLogLevel(LogLevel::trace); - SaveRestoreContext saved_context(context); - wasm_val_vec_t results = WASM_EMPTY_VEC; - WasmTrapPtr trap; + trap.reset(wasm_func_call(func, ¶ms, &results)); + } - // Workaround for MSVC++ not supporting zero-sized arrays. - if constexpr (sizeof...(args) > 0) { - wasm_val_t params_arr[] = {makeVal(args)...}; - wasm_val_vec_t params = WASM_ARRAY_VEC(params_arr); - if (log) { - integration()->trace("[host->vm] " + std::string(function_name) + "(" + - printValues(¶ms) + ")"); + if (trap) { + WasmByteVec error_message; + wasm_trap_message(trap.get(), error_message.get()); + std::string message(error_message.get()->data); // NULL-terminated + fail(FailState::RuntimeError, + "Function: " + std::string(function_name) + " failed: " + message); + return; } - trap.reset(wasm_func_call(func, ¶ms, &results)); - } else { - wasm_val_vec_t params = WASM_EMPTY_VEC; if (log) { - integration()->trace("[host->vm] " + std::string(function_name) + "()"); + integration()->trace("[host<-vm] " + std::string(function_name) + " return: void"); } - trap.reset(wasm_func_call(func, ¶ms, &results)); - } + }; + }; - if (trap) { - WasmByteVec error_message; - wasm_trap_message(trap.get(), error_message.get()); - std::string message(error_message.get()->data); // NULL-terminated - fail(FailState::RuntimeError, - "Function: " + std::string(function_name) + " failed: " + message); + template + void Wasmtime::getModuleFunctionImpl(std::string_view function_name, + std::function * function) { + auto it = module_functions_.find(std::string(function_name)); + if (it == module_functions_.end()) { + *function = nullptr; return; } - if (log) { - integration()->trace("[host<-vm] " + std::string(function_name) + " return: void"); + WasmValtypeVec exp_args; + WasmValtypeVec exp_returns; + convertArgsTupleToValTypes>(exp_args.get()); + convertArgsTupleToValTypes>(exp_returns.get()); + wasm_func_t *func = it->second.get(); + WasmFunctypePtr func_type = wasm_func_type(func); + if (!equalValTypes(wasm_functype_params(func_type.get()), exp_args.get()) || + !equalValTypes(wasm_functype_results(func_type.get()), exp_returns.get())) { + fail(FailState::UnableToInitializeCode, + "Bad function signature for: " + std::string(function_name) + ", want: " + + printValTypes(exp_args.get()) + " -> " + printValTypes(exp_returns.get()) + + ", but the module exports: " + printValTypes(wasm_functype_params(func_type.get())) + + " -> " + printValTypes(wasm_functype_results(func_type.get()))); + return; } - }; -}; -template -void Wasmtime::getModuleFunctionImpl(std::string_view function_name, - std::function *function) { - auto it = module_functions_.find(std::string(function_name)); - if (it == module_functions_.end()) { - *function = nullptr; - return; - } - WasmValtypeVec exp_args; - WasmValtypeVec exp_returns; - convertArgsTupleToValTypes>(exp_args.get()); - convertArgsTupleToValTypes>(exp_returns.get()); - wasm_func_t *func = it->second.get(); - WasmFunctypePtr func_type = wasm_func_type(func); - if (!equalValTypes(wasm_functype_params(func_type.get()), exp_args.get()) || - !equalValTypes(wasm_functype_results(func_type.get()), exp_returns.get())) { - fail(FailState::UnableToInitializeCode, - "Bad function signature for: " + std::string(function_name) + ", want: " + - printValTypes(exp_args.get()) + " -> " + printValTypes(exp_returns.get()) + - ", but the module exports: " + printValTypes(wasm_functype_params(func_type.get())) + - " -> " + printValTypes(wasm_functype_results(func_type.get()))); - return; - } + *function = [func, function_name, this](ContextBase *context, Args... args) -> R { + const bool log = cmpLogLevel(LogLevel::trace); + SaveRestoreContext saved_context(context); + wasm_val_t results_arr[1]; + wasm_val_vec_t results = WASM_ARRAY_VEC(results_arr); + WasmTrapPtr trap; + + // Workaround for MSVC++ not supporting zero-sized arrays. + if constexpr (sizeof...(args) > 0) { + wasm_val_t params_arr[] = {makeVal(args)...}; + wasm_val_vec_t params = WASM_ARRAY_VEC(params_arr); + if (log) { + integration()->trace("[host->vm] " + std::string(function_name) + "(" + + printValues(¶ms) + ")"); + } + trap.reset(wasm_func_call(func, ¶ms, &results)); + } else { + wasm_val_vec_t params = WASM_EMPTY_VEC; + if (log) { + integration()->trace("[host->vm] " + std::string(function_name) + "()"); + } + trap.reset(wasm_func_call(func, ¶ms, &results)); + } - *function = [func, function_name, this](ContextBase *context, Args... args) -> R { - const bool log = cmpLogLevel(LogLevel::trace); - SaveRestoreContext saved_context(context); - wasm_val_t results_arr[1]; - wasm_val_vec_t results = WASM_ARRAY_VEC(results_arr); - WasmTrapPtr trap; - - // Workaround for MSVC++ not supporting zero-sized arrays. - if constexpr (sizeof...(args) > 0) { - wasm_val_t params_arr[] = {makeVal(args)...}; - wasm_val_vec_t params = WASM_ARRAY_VEC(params_arr); - if (log) { - integration()->trace("[host->vm] " + std::string(function_name) + "(" + - printValues(¶ms) + ")"); + if (trap) { + WasmByteVec error_message; + wasm_trap_message(trap.get(), error_message.get()); + std::string message(error_message.get()->data); // NULL-terminated + fail(FailState::RuntimeError, + "Function: " + std::string(function_name) + " failed: " + message); + return R{}; } - trap.reset(wasm_func_call(func, ¶ms, &results)); - } else { - wasm_val_vec_t params = WASM_EMPTY_VEC; + R ret = convertValueTypeToArg(results.data[0]); if (log) { - integration()->trace("[host->vm] " + std::string(function_name) + "()"); + integration()->trace("[host<-vm] " + std::string(function_name) + + " return: " + std::to_string(ret)); } - trap.reset(wasm_func_call(func, ¶ms, &results)); - } - - if (trap) { - WasmByteVec error_message; - wasm_trap_message(trap.get(), error_message.get()); - std::string message(error_message.get()->data); // NULL-terminated - fail(FailState::RuntimeError, - "Function: " + std::string(function_name) + " failed: " + message); - return R{}; - } - R ret = convertValueTypeToArg(results.data[0]); - if (log) { - integration()->trace("[host<-vm] " + std::string(function_name) + - " return: " + std::to_string(ret)); - } - return ret; + return ret; + }; }; -}; -void Wasmtime::warm() { initStore(); } + void Wasmtime::warm() { initStore(); } } // namespace wasmtime