diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 75d2d327988d6..e5098f8c13ad1 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -77,6 +77,7 @@ def _build_recipe_repository_impl(ctxt): print("Fetching external dependencies...") result = ctxt.execute( ["./repositories.sh"] + recipes, + timeout = 3600, quiet = False, ) print(result.stdout) diff --git a/bazel/target_recipes.bzl b/bazel/target_recipes.bzl index f27dd1a829e5c..43cde181ada45 100644 --- a/bazel/target_recipes.bzl +++ b/bazel/target_recipes.bzl @@ -5,5 +5,6 @@ TARGET_RECIPES = { "tcmalloc_and_profiler": "gperftools", "tcmalloc_debug": "gperftools", "luajit": "luajit", + "v8": "v8", "wavm_with_llvm": "wavm", } diff --git a/ci/build_container/build_recipes/v8.sh b/ci/build_container/build_recipes/v8.sh new file mode 100755 index 0000000000000..52380a8c4d3cb --- /dev/null +++ b/ci/build_container/build_recipes/v8.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +set -e + +# Get wasm-c-api. + +COMMIT=111a3e4a0962fae4da2428b8680f7dfbc8deef47 # Mon May 13 11:10:04 2019 +0200 +SHA256=4eb700586902d0f6ebdcbc0147f5674df95743cc831495191b7df4cb32fb3ef0 + +curl https://github.com/WebAssembly/wasm-c-api/archive/"$COMMIT".tar.gz -sLo wasm-c-api-"$COMMIT".tar.gz \ + && echo "$SHA256" wasm-c-api-"$COMMIT".tar.gz | sha256sum --check +tar xf wasm-c-api-"$COMMIT".tar.gz +cd wasm-c-api-"$COMMIT" + +# Build v8 inside v8 subdirectory to match wasm-c-api's Makefile. + +mkdir v8 +cd v8 + +# Get depot_tools. + +git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git +export PATH="$PATH:$PWD/depot_tools" + +# Get v8. + +VERSION=7.4.288.28 # match wasm-c-api (branch-heads/7.4) + +fetch v8 +cd v8 +git checkout "$VERSION" +gclient sync + +# Patch v8 for wasm-c-api. + +patch -p1 < ../../patch/0001-BUILD.gn-add-wasm-v8-lowlevel.patch +cp -p ../../src/wasm-v8-lowlevel.cc src/wasm-v8-lowlevel.cc +cp -p ../../src/wasm-v8-lowlevel.hh include/wasm-v8-lowlevel.hh + +# Build v8 static library. + +tools/dev/v8gen.py x64.release -- v8_monolithic=true v8_use_external_startup_data=false v8_enable_i18n_support=false v8_enable_gdbjit=false use_custom_libcxx=false +ninja -v -C out.gn/x64.release v8_monolith + +# Install v8. + +mkdir -p "$THIRDPARTY_BUILD/include/v8/libplatform" +cp -p include/v8*.h "$THIRDPARTY_BUILD/include/v8/" +cp -p include/libplatform/*.h "$THIRDPARTY_BUILD/include/v8/libplatform/" +cp -p out.gn/x64.release/obj/libv8_monolith.a "$THIRDPARTY_BUILD/lib/" + +cd ../.. + +# Patch wasm-c-api. + +# 1. Disable DEBUG (alloc/free accounting), since it seems to be broken +# in optimized builds and/or when using sanitizers. +# 2. Disable hardcoded sanitizers. + +cat <<\EOF | patch -p1 +--- a/Makefile ++++ b/Makefile +@@ -7,10 +7,10 @@ V8_VERSION = branch-heads/7.4 + V8_ARCH = x64 + V8_MODE = release + +-WASM_FLAGS = -DDEBUG # -DDEBUG_LOG +-C_FLAGS = ${WASM_FLAGS} -Wall -Werror -ggdb -O -fsanitize=address ++WASM_FLAGS = ++C_FLAGS = ${WASM_FLAGS} -Wall -Werror -ggdb -O + CC_FLAGS = ${C_FLAGS} +-LD_FLAGS = -fsanitize-memory-track-origins -fsanitize-memory-use-after-dtor ++LD_FLAGS = + + C_COMP = clang +EOF + +# 3. Enable "wasm_bulk_memory" required to load WASM modules with DataCount +# section, even when DataCount = 1. +# 4. Force full GC when destroying VMs. + +cat <<\EOF | patch -p1 +--- a/src/wasm-v8.cc ++++ b/src/wasm-v8.cc +@@ -296,7 +296,7 @@ auto Engine::make(own&& config) -> own { + v8::internal::FLAG_experimental_wasm_bigint = true; + v8::internal::FLAG_experimental_wasm_mv = true; + // v8::internal::FLAG_experimental_wasm_anyref = true; +- // v8::internal::FLAG_experimental_wasm_bulk_memory = true; ++ v8::internal::FLAG_experimental_wasm_bulk_memory = true; + // v8::V8::SetFlagsFromCommandLine(&argc, const_cast(argv), false); + auto engine = new(std::nothrow) EngineImpl; + if (!engine) return own(); +@@ -349,7 +349,7 @@ public: + } + + ~StoreImpl() { +-#ifdef DEBUG ++#if 1 //def DEBUG + isolate_->RequestGarbageCollectionForTesting( + v8::Isolate::kFullGarbageCollection); + #endif +EOF + +# Build wasm-c-api. + +# TODO(PiotrSikora): respect CC/CXX/CFLAGS/CXXFLAGS/LDFLAGS upstream. + +make wasm + +# Install wasm-c-api. + +mkdir -p "$THIRDPARTY_BUILD/include/wasm-c-api" +cp -p include/wasm.hh "$THIRDPARTY_BUILD/include/wasm-c-api/" +cp -p src/wasm-bin.hh "$THIRDPARTY_BUILD/include/wasm-c-api/" +ar -r -c -s "$THIRDPARTY_BUILD/lib/libwasm.a" out/wasm-bin.o out/wasm-v8.o diff --git a/ci/prebuilt/BUILD b/ci/prebuilt/BUILD index 54d15bdc33f55..fd9842869fb38 100644 --- a/ci/prebuilt/BUILD +++ b/ci/prebuilt/BUILD @@ -25,6 +25,23 @@ cc_library( strip_include_prefix = "thirdparty_build/include", ) +cc_library( + name = "v8", + srcs = select({ + "@envoy//bazel:windows_x86_64": ["WINDOWS_IS_NOT_SUPPORTED_YET"], + "//conditions:default": [ + # Order matters! + "thirdparty_build/lib/libwasm.a", + "thirdparty_build/lib/libv8_monolith.a", + ], + }), + hdrs = [ + "thirdparty_build/include/wasm-c-api/wasm.hh", + "thirdparty_build/include/wasm-c-api/wasm-bin.hh", + ], + includes = ["thirdparty_build/include"], +) + cc_library( name = "wavm_with_llvm", srcs = select({ diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index 6c5af58703a58..93ca37bfe6061 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -42,6 +42,7 @@ envoy_cc_library( "//source/common/http:message_lib", "//source/common/http:utility_lib", "//source/common/tracing:http_tracer_lib", + "//source/extensions/common/wasm/v8:v8_lib", "//source/extensions/common/wasm/wavm:wavm_lib", ], ) diff --git a/source/extensions/common/wasm/v8/BUILD b/source/extensions/common/wasm/v8/BUILD new file mode 100644 index 0000000000000..322a37ad6452c --- /dev/null +++ b/source/extensions/common/wasm/v8/BUILD @@ -0,0 +1,28 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "v8_lib", + srcs = ["v8.cc"], + hdrs = ["v8.h"], + external_deps = [ + "v8", + ], + deps = [ + "//include/envoy/server:wasm_interface", + "//include/envoy/thread_local:thread_local_interface", + "//source/common/common:assert_lib", + "//source/common/common:c_smart_ptr_lib", + "//source/common/protobuf", + "//source/extensions/common/wasm:wasm_hdr", + "//source/extensions/common/wasm:well_known_names", + "@envoy_api//envoy/config/wasm/v2:wasm_cc", + ], +) diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc new file mode 100644 index 0000000000000..ac28d7a685394 --- /dev/null +++ b/source/extensions/common/wasm/v8/v8.cc @@ -0,0 +1,620 @@ +#include "extensions/common/wasm/v8/v8.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "envoy/common/exception.h" +#include "envoy/server/wasm.h" + +#include "common/common/assert.h" +#include "common/common/logger.h" + +#include "extensions/common/wasm/wasm.h" +#include "extensions/common/wasm/well_known_names.h" + +#include "absl/strings/match.h" +#include "absl/types/span.h" +#include "absl/utility/utility.h" +#include "wasm-c-api/wasm.hh" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +extern thread_local Envoy::Extensions::Common::Wasm::Context* current_context_; + +namespace V8 { + +wasm::Engine* engine() { + static const auto engine = wasm::Engine::make(); + return engine.get(); +} + +class V8 : public WasmVm { +public: + V8() = default; + + // Extensions::Common::Wasm::WasmVm + absl::string_view vm() override { return WasmVmNames::get().v8; } + + bool load(const std::string& code, bool allow_precompiled) override; + absl::string_view getUserSection(absl::string_view name) override; + void link(absl::string_view debug_name, bool needs_emscripten) override; + + // We don't care about this. + void makeModule(absl::string_view) override {} + + // v8 is currently not clonable. + bool clonable() override { return false; } + std::unique_ptr clone() override { return nullptr; } + + void start(Context* context) override; + absl::string_view getMemory(uint32_t pointer, uint32_t size) override; + bool getMemoryOffset(void* host_pointer, uint32_t* vm_pointer) override; + bool setMemory(uint32_t pointer, uint32_t size, void* data) override; + +#define _REGISTER_HOST_GLOBAL(_type) \ + std::unique_ptr> makeGlobal(absl::string_view moduleName, absl::string_view name, \ + _type initialValue) override { \ + return registerHostGlobalImpl(moduleName, name, initialValue); \ + }; + _REGISTER_HOST_GLOBAL(uint32_t); + _REGISTER_HOST_GLOBAL(double); +#undef _REGISTER_HOST_GLOBAL + +#define _REGISTER_HOST_FUNCTION(_type) \ + void registerCallback(absl::string_view moduleName, absl::string_view functionName, _type f) \ + override { \ + registerHostFunctionImpl(moduleName, functionName, f); \ + }; + _REGISTER_HOST_FUNCTION(WasmCallback0Void); + _REGISTER_HOST_FUNCTION(WasmCallback1Void); + _REGISTER_HOST_FUNCTION(WasmCallback2Void); + _REGISTER_HOST_FUNCTION(WasmCallback3Void); + _REGISTER_HOST_FUNCTION(WasmCallback4Void); + _REGISTER_HOST_FUNCTION(WasmCallback5Void); + _REGISTER_HOST_FUNCTION(WasmCallback0Int); + _REGISTER_HOST_FUNCTION(WasmCallback1Int); + _REGISTER_HOST_FUNCTION(WasmCallback2Int); + _REGISTER_HOST_FUNCTION(WasmCallback3Int); + _REGISTER_HOST_FUNCTION(WasmCallback4Int); + _REGISTER_HOST_FUNCTION(WasmCallback5Int); + _REGISTER_HOST_FUNCTION(WasmCallback6Int); + _REGISTER_HOST_FUNCTION(WasmCallback7Int); + _REGISTER_HOST_FUNCTION(WasmCallback8Int); + _REGISTER_HOST_FUNCTION(WasmCallback9Int); + _REGISTER_HOST_FUNCTION(WasmCallback_Zjl); + _REGISTER_HOST_FUNCTION(WasmCallback_Zjm); + _REGISTER_HOST_FUNCTION(WasmCallback_mj); + _REGISTER_HOST_FUNCTION(WasmCallback_mjj); +#undef _REGISTER_HOST_FUNCTION + +#define _GET_MODULE_FUNCTION(_type) \ + void getFunction(absl::string_view functionName, _type* f) override { \ + getModuleFunctionImpl(functionName, f); \ + }; + _GET_MODULE_FUNCTION(WasmCall0Void); + _GET_MODULE_FUNCTION(WasmCall1Void); + _GET_MODULE_FUNCTION(WasmCall2Void); + _GET_MODULE_FUNCTION(WasmCall3Void); + _GET_MODULE_FUNCTION(WasmCall4Void); + _GET_MODULE_FUNCTION(WasmCall5Void); + _GET_MODULE_FUNCTION(WasmCall8Void); + _GET_MODULE_FUNCTION(WasmCall1Int); + _GET_MODULE_FUNCTION(WasmCall3Int); +#undef _GET_MODULE_FUNCTION + +private: + void callModuleFunction(Context* context, absl::string_view functionName, const wasm::Val args[], + wasm::Val results[]); + + template + std::unique_ptr> registerHostGlobalImpl(absl::string_view moduleName, + absl::string_view name, T initialValue); + template + void registerHostFunctionImpl(absl::string_view moduleName, absl::string_view functionName, + void (*function)(void*, Args...)); + template + void registerHostFunctionImpl(absl::string_view moduleName, absl::string_view functionName, + R (*function)(void*, Args...)); + template + void getModuleFunctionImpl(absl::string_view functionName, + std::function* function); + template + void getModuleFunctionImpl(absl::string_view functionName, + std::function* function); + + wasm::vec source_ = wasm::vec::invalid(); + wasm::own store_; + wasm::own module_; + wasm::own instance_; + wasm::own memory_; + wasm::own table_; + + absl::flat_hash_map> host_globals_; + absl::flat_hash_map> host_functions_; + absl::flat_hash_map> module_functions_; + bool module_needs_emscripten_{}; +}; + +template struct V8ProxyForGlobal : Global { + V8ProxyForGlobal(wasm::Global* value) : global_(value) {} + + T get() override { return global_->get().get(); }; + void set(const T& value) override { global_->set(wasm::Val::make(static_cast(value))); }; + + wasm::Global* global_; +}; + +// Helper functions. + +static const char* printValKind(wasm::ValKind 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::ANYREF: + return "anyref"; + case wasm::FUNCREF: + return "funcref"; + } +} + +static std::string printValTypes(const wasm::vec& 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) { + s.append(" "); + } + s.append(printValKind(types[i]->kind())); + } + return s; +} + +static bool equalValTypes(const wasm::vec& left, + const wasm::vec& 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; +} + +static uint32_t parseVarint(const byte_t*& pos, const byte_t* end) { + uint32_t n = 0; + uint32_t shift = 0; + byte_t b; + do { + if (pos + 1 > end) { + throw WasmException("Failed to parse corrupted WASM module"); + } + b = *pos++; + n += (b & 0x7f) << shift; + shift += 7; + } while ((b & 0x80) != 0); + return n; +} + +// Template magic. + +template constexpr auto convertArgToValKind(); +template <> constexpr auto convertArgToValKind() { return wasm::I32; }; +template <> constexpr auto convertArgToValKind() { return wasm::I32; }; +template <> constexpr auto convertArgToValKind() { return wasm::I64; }; +template <> constexpr auto convertArgToValKind() { return wasm::I64; }; +template <> constexpr auto convertArgToValKind() { return wasm::F32; }; +template <> constexpr auto convertArgToValKind() { return wasm::F64; }; + +template +constexpr auto convertArgsTupleToValTypesImpl(absl::index_sequence) { + return wasm::vec::make( + wasm::ValType::make(convertArgToValKind::type>())...); +} + +template constexpr auto convertArgsTupleToValTypes() { + return convertArgsTupleToValTypesImpl(absl::make_index_sequence::value>()); +} + +template +constexpr T convertValTypesToArgsTupleImpl(const U& arr, absl::index_sequence) { + return std::make_tuple((arr[I].template get::type>())...); +} + +template constexpr T convertValTypesToArgsTuple(const U& arr) { + return convertValTypesToArgsTupleImpl(arr, + absl::make_index_sequence::value>()); +} + +// V8 implementation. + +bool V8::load(const std::string& code, bool /* allow_precompiled */) { + ENVOY_LOG(trace, "[wasm] load()"); + store_ = wasm::Store::make(engine()); + RELEASE_ASSERT(store_ != nullptr, ""); + + source_ = wasm::vec::make_uninitialized(code.size()); + ::memcpy(source_.get(), code.data(), code.size()); + + module_ = wasm::Module::make(store_.get(), source_); + return module_ != nullptr; +} + +absl::string_view V8::getUserSection(absl::string_view name) { + ENVOY_LOG(trace, "[wasm] getUserSection(\"{}\")", name); + ASSERT(source_.get() != nullptr); + + const byte_t* end = source_.get() + source_.size(); + const byte_t* pos = source_.get() + 8; // skip header + while (pos < end) { + if (pos + 1 > end) { + throw WasmException("Failed to parse corrupted WASM module"); + } + auto type = *pos++; + auto rest = parseVarint(pos, end); + if (pos + rest > end) { + throw WasmException("Failed to parse corrupted WASM module"); + } + if (type == 0 /* custom section */) { + auto start = pos; + auto len = parseVarint(pos, end); + if (pos + len > end) { + throw WasmException("Failed to parse corrupted WASM module"); + } + pos += len; + rest -= (pos - start); + if (len == name.size() && ::memcmp(pos - len, name.data(), len) == 0) { + ENVOY_LOG(trace, "[wasm] getUserSection(\"{}\") found, size: {}", name, rest); + return absl::string_view(pos, rest); + } + } + pos += rest; + } + return ""; +} + +void V8::link(absl::string_view debug_name, bool needs_emscripten) { + ENVOY_LOG(trace, "[wasm] link(\"{}\"), emscripten: {}", debug_name, needs_emscripten); + ASSERT(module_ != nullptr); + + const auto import_types = module_.get()->imports(); + std::vector imports; + + for (size_t i = 0; i < import_types.size(); i++) { + absl::string_view module(import_types[i]->module().get(), import_types[i]->module().size()); + absl::string_view name(import_types[i]->name().get(), import_types[i]->name().size()); + auto import_type = import_types[i]->type(); + + switch (import_type->kind()) { + + case wasm::EXTERN_FUNC: { + ENVOY_LOG(trace, "[wasm] link(), export host func: {}.{} ({} -> {})", module, name, + printValTypes(import_type->func()->params()), + printValTypes(import_type->func()->results())); + + const wasm::Func* func = nullptr; + auto it = host_functions_.find(absl::StrCat(module, ".", name)); + if (it != host_functions_.end()) { + func = it->second.get(); + } else { + it = host_functions_.find(absl::StrCat("envoy", ".", name)); + if (it != host_functions_.end()) { + func = it->second.get(); + } + } + if (func) { + if (equalValTypes(import_type->func()->params(), func->type()->params()) && + equalValTypes(import_type->func()->results(), func->type()->results())) { + imports.push_back(func); + } else { + throw WasmException(fmt::format( + "Failed to load WASM module due to an import type mismatch: {}.{}, " + "want: {} -> {}, but host exports: {} -> {}", + module, name, printValTypes(import_type->func()->params()), + printValTypes(import_type->func()->results()), printValTypes(func->type()->params()), + printValTypes(func->type()->results()))); + } + } else { + throw WasmException( + fmt::format("Failed to load WASM module due to a missing import: {}.{}", module, name)); + } + } break; + + case wasm::EXTERN_GLOBAL: { + ENVOY_LOG(trace, "[wasm] link(), export host global: {}.{} ({})", module, name, + printValKind(import_type->global()->content()->kind())); + + const wasm::Global* global = nullptr; + auto it = host_globals_.find(absl::StrCat(module, ".", name)); + if (it != host_globals_.end()) { + global = it->second.get(); + } else { + it = host_globals_.find(absl::StrCat("envoy", ".", name)); + if (it != host_globals_.end()) { + global = it->second.get(); + } + } + if (global) { + imports.push_back(global); + } else { + throw WasmException( + fmt::format("Failed to load WASM module due to a missing import: {}.{}", module, name)); + } + } break; + + case wasm::EXTERN_MEMORY: { + ENVOY_LOG(trace, "[wasm] link(), export host memory: {}.{} (min: {} max: {})", module, name, + import_type->memory()->limits().min, import_type->memory()->limits().max); + + ASSERT(memory_ == nullptr); + auto type = wasm::MemoryType::make(import_type->memory()->limits()); + memory_ = wasm::Memory::make(store_.get(), type.get()); + imports.push_back(memory_.get()); + } break; + + case wasm::EXTERN_TABLE: { + ENVOY_LOG(trace, "[wasm] link(), export host table: {}.{} (min: {} max: {})", module, name, + import_type->table()->limits().min, import_type->table()->limits().max); + + ASSERT(table_ == nullptr); + auto type = + wasm::TableType::make(wasm::ValType::make(import_type->table()->element()->kind()), + import_type->table()->limits()); + table_ = wasm::Table::make(store_.get(), type.get()); + imports.push_back(table_.get()); + } break; + } + } + + ASSERT(import_types.size() == imports.size()); + + instance_ = wasm::Instance::make(store_.get(), module_.get(), imports.data()); + RELEASE_ASSERT(instance_ != nullptr, ""); + module_needs_emscripten_ = needs_emscripten; + + 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++) { + absl::string_view name(export_types[i]->name().get(), export_types[i]->name().size()); + auto export_type = export_types[i]->type(); + auto export_item = exports[i]; + ASSERT(export_type->kind() == export_item->kind()); + + switch (export_type->kind()) { + + case wasm::EXTERN_FUNC: { + ENVOY_LOG(trace, "[wasm] link(), import module func: {} ({} -> {})", name, + printValTypes(export_type->func()->params()), + printValTypes(export_type->func()->results())); + + ASSERT(export_item->func() != nullptr); + module_functions_.emplace(name, export_item->func()->copy()); + } break; + + case wasm::EXTERN_GLOBAL: { + // TODO(PiotrSikora): add support when/if needed. + ENVOY_LOG(trace, "[wasm] link(), import module global: {} ({}) --- IGNORED", name, + printValKind(export_type->global()->content()->kind())); + } break; + + case wasm::EXTERN_MEMORY: { + ENVOY_LOG(trace, "[wasm] link(), import module memory: {} (min: {} max: {})", name, + export_type->memory()->limits().min, export_type->memory()->limits().max); + + ASSERT(export_item->memory() != nullptr); + ASSERT(memory_ == nullptr); + memory_ = exports[i]->memory()->copy(); + } break; + + case wasm::EXTERN_TABLE: { + // TODO(PiotrSikora): add support when/if needed. + ENVOY_LOG(trace, "[wasm] link(), import module table: {} (min: {} max: {}) --- IGNORED", name, + export_type->table()->limits().min, export_type->table()->limits().max); + } break; + } + } +} + +void V8::start(Context* context) { + ENVOY_LOG(trace, "[wasm] start()"); + + if (module_needs_emscripten_) { + const wasm::Val args[] = {wasm::Val::make(static_cast(64 * 64 * 1024 /* 4MB */)), + wasm::Val::make(static_cast(128 * 64 * 1024 /* 8MB */))}; + callModuleFunction(context, "establishStackSpace", args, nullptr); + callModuleFunction(context, "globalCtors", nullptr, nullptr); + + for (const auto& kv : module_functions_) { + if (absl::StartsWith(kv.first, "__GLOBAL__")) { + const wasm::Func* func = kv.second.get(); + auto trap = func->call(nullptr, nullptr); + if (trap) { + throw WasmVmException( + fmt::format("Function: {} failed: {}", kv.first, + absl::string_view(trap->message().get(), trap->message().size()))); + } + } + } + } + + callModuleFunction(context, "__post_instantiate", nullptr, nullptr); +} + +void V8::callModuleFunction(Context* context, absl::string_view functionName, + const wasm::Val args[], wasm::Val results[]) { + ENVOY_LOG(trace, "[wasm] callModuleFunction(\"{}\")", functionName); + current_context_ = context; + + auto it = module_functions_.find(functionName); + if (it != module_functions_.end()) { + const wasm::Func* func = it->second.get(); + auto trap = func->call(args, results); + if (trap) { + throw WasmVmException( + fmt::format("Function: {} failed: {}", functionName, + absl::string_view(trap->message().get(), trap->message().size()))); + } + } +} + +absl::string_view V8::getMemory(uint32_t pointer, uint32_t size) { + ENVOY_LOG(trace, "[wasm] getMemory({}, {})", pointer, size); + ASSERT(memory_ != nullptr); + RELEASE_ASSERT(pointer + size <= memory_->data_size(), ""); + return absl::string_view(memory_->data() + pointer, size); +} + +bool V8::getMemoryOffset(void* host_pointer, uint32_t* vm_pointer) { + ENVOY_LOG(trace, "[wasm] getMemoryOffset({})", host_pointer); + ASSERT(memory_ != nullptr); + RELEASE_ASSERT(static_cast(host_pointer) >= memory_->data(), ""); + RELEASE_ASSERT(static_cast(host_pointer) <= memory_->data() + memory_->data_size(), ""); + *vm_pointer = static_cast(host_pointer) - memory_->data(); + return true; +} + +bool V8::setMemory(uint32_t pointer, uint32_t size, void* data) { + ENVOY_LOG(trace, "[wasm] setMemory({}, {})", pointer, size); + ASSERT(memory_ != nullptr); + RELEASE_ASSERT(pointer + size <= memory_->data_size(), ""); + ::memcpy(memory_->data() + pointer, data, size); + return true; +} + +template +std::unique_ptr> V8::registerHostGlobalImpl(absl::string_view moduleName, + absl::string_view name, T initialValue) { + ENVOY_LOG(trace, "[wasm] registerHostGlobal(\"{}.{}\", {})", moduleName, name, initialValue); + auto value = wasm::Val::make(initialValue); + auto type = wasm::GlobalType::make(wasm::ValType::make(value.kind()), wasm::CONST); + auto global = wasm::Global::make(store_.get(), type.get(), value); + auto proxy = std::make_unique>(global.get()); + host_globals_.emplace(absl::StrCat(moduleName, ".", name), std::move(global)); + return proxy; +} + +template +void V8::registerHostFunctionImpl(absl::string_view moduleName, absl::string_view functionName, + void (*function)(void*, Args...)) { + ENVOY_LOG(trace, "[wasm] registerHostFunction(\"{}.{}\")", moduleName, functionName); + auto type = wasm::FuncType::make(convertArgsTupleToValTypes>(), + convertArgsTupleToValTypes>()); + auto func = wasm::Func::make( + store_.get(), type.get(), + [](void* data, const wasm::Val params[], wasm::Val[]) -> wasm::own { + auto args_tuple = convertValTypesToArgsTuple>(params); + auto args = std::tuple_cat(std::make_tuple(current_context_), args_tuple); + auto function = reinterpret_cast(data); + absl::apply(function, args); + return nullptr; + }, + reinterpret_cast(function)); + host_functions_.emplace(absl::StrCat(moduleName, ".", functionName), std::move(func)); +} + +template +void V8::registerHostFunctionImpl(absl::string_view moduleName, absl::string_view functionName, + R (*function)(void*, Args...)) { + ENVOY_LOG(trace, "[wasm] registerHostFunction(\"{}.{}\")", moduleName, functionName); + auto type = wasm::FuncType::make(convertArgsTupleToValTypes>(), + convertArgsTupleToValTypes>()); + auto func = wasm::Func::make( + store_.get(), type.get(), + [](void* data, const wasm::Val params[], wasm::Val results[]) -> wasm::own { + auto args_tuple = convertValTypesToArgsTuple>(params); + auto args = std::tuple_cat(std::make_tuple(current_context_), args_tuple); + auto function = reinterpret_cast(data); + R rvalue = absl::apply(function, args); + results[0] = wasm::Val::make(rvalue); + return nullptr; + }, + reinterpret_cast(function)); + host_functions_.emplace(absl::StrCat(moduleName, ".", functionName), std::move(func)); +} + +template +void V8::getModuleFunctionImpl(absl::string_view functionName, + std::function* function) { + ENVOY_LOG(trace, "[wasm] getModuleFunction(\"{}\")", functionName); + auto it = module_functions_.find(functionName); + if (it == module_functions_.end()) { + *function = nullptr; + return; + } + const wasm::Func* func = it->second.get(); + if (!equalValTypes(func->type()->params(), convertArgsTupleToValTypes>()) || + !equalValTypes(func->type()->results(), convertArgsTupleToValTypes>())) { + throw WasmVmException(fmt::format("Bad function signature for: {}", functionName)); + } + *function = [func, functionName](Context* context, Args... args) -> void { + ENVOY_LOG(trace, "[wasm] callModuleFunction(\"{}\")", functionName); + current_context_ = context; + wasm::Val params[] = {wasm::Val::make(args)...}; + auto trap = func->call(params, nullptr); + if (trap) { + throw WasmVmException( + fmt::format("Function: {} failed: {}", functionName, + absl::string_view(trap->message().get(), trap->message().size()))); + } + }; +} + +template +void V8::getModuleFunctionImpl(absl::string_view functionName, + std::function* function) { + ENVOY_LOG(trace, "[wasm] getModuleFunction(\"{}\")", functionName); + auto it = module_functions_.find(functionName); + if (it == module_functions_.end()) { + *function = nullptr; + return; + } + const wasm::Func* func = it->second.get(); + if (!equalValTypes(func->type()->params(), convertArgsTupleToValTypes>()) || + !equalValTypes(func->type()->results(), convertArgsTupleToValTypes>())) { + throw WasmVmException(fmt::format("Bad function signature for: {}", functionName)); + } + *function = [func, functionName](Context* context, Args... args) -> R { + ENVOY_LOG(trace, "[wasm] callModuleFunction(\"{}\")", functionName); + current_context_ = context; + wasm::Val params[] = {wasm::Val::make(args)...}; + wasm::Val results[1]; + auto trap = func->call(params, results); + if (trap) { + throw WasmVmException( + fmt::format("Function: {} failed: {}", functionName, + absl::string_view(trap->message().get(), trap->message().size()))); + } + R rvalue = results[0].get(); + return rvalue; + }; +} + +std::unique_ptr createVm() { return std::make_unique(); } + +} // namespace V8 +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/wasm/v8/v8.h b/source/extensions/common/wasm/v8/v8.h new file mode 100644 index 0000000000000..7a22f6dbcbe4c --- /dev/null +++ b/source/extensions/common/wasm/v8/v8.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "extensions/common/wasm/wasm.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { +namespace V8 { + +std::unique_ptr createVm(); + +} // namespace V8 +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index 4951a5f87005f..eef75b03b30fe 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -26,6 +26,7 @@ #include "common/http/utility.h" #include "common/tracing/http_tracer_impl.h" +#include "extensions/common/wasm/v8/v8.h" #include "extensions/common/wasm/wavm/wavm.h" #include "extensions/common/wasm/well_known_names.h" @@ -1437,7 +1438,7 @@ void Wasm::getFunctions() { #undef _GET_PROXY if (!malloc_ || !free_) { - throw WasmException("WAVM missing malloc/free"); + throw WasmException("WASM missing malloc/free"); } } @@ -1762,8 +1763,10 @@ void GrpcStreamClientHandler::onRemoteClose(Grpc::Status::GrpcStatus status, } std::unique_ptr createWasmVm(absl::string_view wasm_vm) { - if (wasm_vm == WasmVmNames::get().Wavm) { - return Wavm::createWavm(); + if (wasm_vm == WasmVmNames::get().v8) { + return V8::createVm(); + } else if (wasm_vm == WasmVmNames::get().Wavm) { + return Wavm::createVm(); } else { return nullptr; } diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index f0def1ab0326f..6d84282b3097b 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -732,7 +732,7 @@ inline Context::Context(Wasm* wasm) : wasm_(wasm), id_(wasm->allocContextId()) { inline void* Wasm::allocMemory(uint32_t size, uint32_t* address) { uint32_t a = malloc_(generalContext(), size); *address = a; - // Note: this can thorw a WAVM exception. + // Note: this can thorw a WASM exception. return const_cast(reinterpret_cast(wasm_vm_->getMemory(a, size).data())); } diff --git a/source/extensions/common/wasm/wavm/wavm.cc b/source/extensions/common/wasm/wavm/wavm.cc index 2f5be75349d1b..9c7fe111e0846 100644 --- a/source/extensions/common/wasm/wavm/wavm.cc +++ b/source/extensions/common/wasm/wavm/wavm.cc @@ -469,7 +469,7 @@ absl::string_view Wavm::getUserSection(absl::string_view name) { return {}; } -std::unique_ptr createWavm() { return std::make_unique(); } +std::unique_ptr createVm() { return std::make_unique(); } } // namespace Wavm diff --git a/source/extensions/common/wasm/wavm/wavm.h b/source/extensions/common/wasm/wavm/wavm.h index f818e3afd712f..4f9b7da0e66c2 100644 --- a/source/extensions/common/wasm/wavm/wavm.h +++ b/source/extensions/common/wasm/wavm/wavm.h @@ -21,7 +21,7 @@ namespace Common { namespace Wasm { namespace Wavm { -std::unique_ptr createWavm(); +std::unique_ptr createVm(); } // namespace Wavm } // namespace Wasm diff --git a/source/extensions/common/wasm/well_known_names.h b/source/extensions/common/wasm/well_known_names.h index 592f2e51699b1..f44de97ac75b1 100644 --- a/source/extensions/common/wasm/well_known_names.h +++ b/source/extensions/common/wasm/well_known_names.h @@ -13,6 +13,8 @@ namespace Wasm { */ class WasmVmValues { public: + // V8 (https://v8.dev) WASM VM. + const std::string v8 = "envoy.wasm.vm.v8"; // WAVM (https://github.com/WAVM/WAVM) Wasm VM. const std::string Wavm = "envoy.wasm.vm.wavm"; }; diff --git a/test/extensions/access_loggers/wasm/config_test.cc b/test/extensions/access_loggers/wasm/config_test.cc index bf4a7a48c6fb4..925bd1009427b 100644 --- a/test/extensions/access_loggers/wasm/config_test.cc +++ b/test/extensions/access_loggers/wasm/config_test.cc @@ -40,7 +40,11 @@ class TestFactoryContext : public NiceMock {}; + +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmAccessLogConfigTest, testing::Values("wavm", "v8")); + +TEST_P(WasmAccessLogConfigTest, CreateWasmFromEmpty) { auto factory = Registry::FactoryRegistry::getFactory( AccessLogNames::get().Wasm); @@ -58,14 +62,14 @@ TEST(WasmAccessLogConfigTest, CreateWasmFromEmpty) { Common::Wasm::WasmVmException, "No WASM VM Id or vm_config specified"); } -TEST(WasmAccessLogConfigTest, CreateWasmFromWASM) { +TEST_P(WasmAccessLogConfigTest, CreateWasmFromWASM) { auto factory = Registry::FactoryRegistry::getFactory( AccessLogNames::get().Wasm); ASSERT_NE(factory, nullptr); envoy::config::accesslog::v2::WasmAccessLog config; - config.mutable_vm_config()->set_vm("envoy.wasm.vm.wavm"); + config.mutable_vm_config()->set_vm(absl::StrCat("envoy.wasm.vm.", GetParam())); config.mutable_vm_config()->mutable_code()->set_filename(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/access_loggers/wasm/test_data/logging.wasm")); diff --git a/test/extensions/filters/http/wasm/config_test.cc b/test/extensions/filters/http/wasm/config_test.cc index 4d92322f2dec2..ca8c663a1db24 100644 --- a/test/extensions/filters/http/wasm/config_test.cc +++ b/test/extensions/filters/http/wasm/config_test.cc @@ -21,7 +21,7 @@ namespace Extensions { namespace HttpFilters { namespace Wasm { -class WasmFilterConfigTest : public TestBase { +class WasmFilterConfigTest : public TestBaseWithParam { protected: WasmFilterConfigTest() : api_(Api::createApiForTest(stats_store_)) { ON_CALL(context_, api()).WillByDefault(ReturnRef(*api_)); @@ -33,17 +33,20 @@ class WasmFilterConfigTest : public TestBase { Api::ApiPtr api_; }; -TEST_F(WasmFilterConfigTest, JsonLoadFromFileWASM) { - const std::string json = TestEnvironment::substitute(R"EOF( +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFilterConfigTest, testing::Values("wavm", "v8")); + +TEST_P(WasmFilterConfigTest, JsonLoadFromFileWASM) { + const std::string json = TestEnvironment::substitute(absl::StrCat(R"EOF( { "vm_config": { - "vm": "envoy.wasm.vm.wavm", + "vm": "envoy.wasm.vm.)EOF", + GetParam(), R"EOF(", "code": { "filename": "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm" }, "allow_precompiled": true }} - )EOF"); + )EOF")); envoy::config::filter::http::wasm::v2::Wasm proto_config; MessageUtil::loadFromJson(json, proto_config); @@ -55,12 +58,13 @@ TEST_F(WasmFilterConfigTest, JsonLoadFromFileWASM) { cb(filter_callback); } -TEST_F(WasmFilterConfigTest, YamlLoadFromFileWASM) { - const std::string yaml = TestEnvironment::substitute(R"EOF( +TEST_P(WasmFilterConfigTest, YamlLoadFromFileWASM) { + const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( vm_config: - vm: "envoy.wasm.vm.wavm" + vm: "envoy.wasm.vm.)EOF", + GetParam(), R"EOF(" code: { filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm" } - )EOF"); + )EOF")); envoy::config::filter::http::wasm::v2::Wasm proto_config; MessageUtil::loadFromYaml(yaml, proto_config); @@ -72,13 +76,17 @@ TEST_F(WasmFilterConfigTest, YamlLoadFromFileWASM) { cb(filter_callback); } -TEST_F(WasmFilterConfigTest, YamlLoadInlineWASM) { +TEST_P(WasmFilterConfigTest, YamlLoadInlineWASM) { const std::string code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm")); EXPECT_FALSE(code.empty()); - const std::string yaml = - absl::StrCat("vm_config:\n vm: \"envoy.wasm.vm.wavm\"\n", " code: { inline_bytes: \"", - Base64::encode(code.data(), code.size()), "\" }\n"); + const std::string yaml = absl::StrCat(R"EOF( + vm_config: + vm: "envoy.wasm.vm.)EOF", + GetParam(), R"EOF(" + code: { inline_bytes: ")EOF", + Base64::encode(code.data(), code.size()), R"EOF(" } + )EOF"); envoy::config::filter::http::wasm::v2::Wasm proto_config; MessageUtil::loadFromYaml(yaml, proto_config); @@ -90,12 +98,13 @@ TEST_F(WasmFilterConfigTest, YamlLoadInlineWASM) { cb(filter_callback); } -TEST_F(WasmFilterConfigTest, YamlLoadInlineBadCode) { - const std::string yaml = R"EOF( +TEST_P(WasmFilterConfigTest, YamlLoadInlineBadCode) { + const std::string yaml = absl::StrCat(R"EOF( vm_config: - vm: "envoy.wasm.vm.wavm" + vm: "envoy.wasm.vm.)EOF", + GetParam(), R"EOF(" code: { inline_string: "bad code" } - )EOF"; + )EOF"); envoy::config::filter::http::wasm::v2::Wasm proto_config; MessageUtil::loadFromYaml(yaml, proto_config); diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index e31e0fc85ac11..20d011ffb171a 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -54,14 +54,14 @@ class TestFilter : public Envoy::Extensions::Common::Wasm::Context { MOCK_METHOD2(scriptLog, void(spdlog::level::level_enum level, absl::string_view message)); }; -class WasmHttpFilterTest : public TestBase { +class WasmHttpFilterTest : public TestBaseWithParam { public: WasmHttpFilterTest() {} ~WasmHttpFilterTest() {} void setupConfig(const std::string& code) { envoy::config::filter::http::wasm::v2::Wasm proto_config; - proto_config.mutable_vm_config()->set_vm("envoy.wasm.vm.wavm"); + proto_config.mutable_vm_config()->set_vm(absl::StrCat("envoy.wasm.vm.", GetParam())); proto_config.mutable_vm_config()->mutable_code()->set_inline_bytes(code); Api::ApiPtr api = Api::createApiForTest(stats_store_); scope_ = Stats::ScopeSharedPtr(stats_store_.createScope("wasm.")); @@ -93,14 +93,16 @@ class WasmHttpFilterTest : public TestBase { NiceMock local_info_; }; +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmHttpFilterTest, testing::Values("wavm", "v8")); + // Bad code in initial config. -TEST_F(WasmHttpFilterTest, BadCode) { +TEST_P(WasmHttpFilterTest, BadCode) { EXPECT_THROW_WITH_MESSAGE(setupConfig("bad code"), Common::Wasm::WasmException, "Failed to initialize WASM code from "); } // Script touching headers only, request that is headers only. -TEST_F(WasmHttpFilterTest, HeadersOnlyRequestHeadersOnly) { +TEST_P(WasmHttpFilterTest, HeadersOnlyRequestHeadersOnly) { setupConfig(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm"))); setupFilter(); @@ -117,7 +119,7 @@ TEST_F(WasmHttpFilterTest, HeadersOnlyRequestHeadersOnly) { } // Script touching headers only, request that is headers only. -TEST_F(WasmHttpFilterTest, HeadersOnlyRequestHeadersAndBody) { +TEST_P(WasmHttpFilterTest, HeadersOnlyRequestHeadersAndBody) { setupConfig(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm"))); setupFilter(); @@ -136,7 +138,7 @@ TEST_F(WasmHttpFilterTest, HeadersOnlyRequestHeadersAndBody) { } // Script testing AccessLog::Instance::log. -TEST_F(WasmHttpFilterTest, AccessLog) { +TEST_P(WasmHttpFilterTest, AccessLog) { setupConfig(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm"))); setupFilter(); @@ -158,7 +160,7 @@ TEST_F(WasmHttpFilterTest, AccessLog) { filter_->log(&request_headers, nullptr, nullptr, log_stream_info); } -TEST_F(WasmHttpFilterTest, AsyncCall) { +TEST_P(WasmHttpFilterTest, AsyncCall) { setupConfig(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/async_call_cpp.wasm"))); setupFilter(); @@ -198,7 +200,7 @@ TEST_F(WasmHttpFilterTest, AsyncCall) { } } -TEST_F(WasmHttpFilterTest, GrpcCall) { +TEST_P(WasmHttpFilterTest, GrpcCall) { setupConfig(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/grpc_call_cpp.wasm"))); setupFilter(); @@ -252,7 +254,7 @@ TEST_F(WasmHttpFilterTest, GrpcCall) { } } -TEST_F(WasmHttpFilterTest, Metadata) { +TEST_P(WasmHttpFilterTest, Metadata) { setupConfig(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/metadata_cpp.wasm"))); setupFilter(); diff --git a/test/extensions/wasm/config_test.cc b/test/extensions/wasm/config_test.cc index 6a10d224daa43..ad5398864fa67 100644 --- a/test/extensions/wasm/config_test.cc +++ b/test/extensions/wasm/config_test.cc @@ -21,12 +21,16 @@ namespace Envoy { namespace Extensions { namespace Wasm { -TEST(WasmFactoryTest, CreateWasmFromWASM) { +class WasmFactoryTest : public TestBaseWithParam {}; + +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFactoryTest, testing::Values("wavm", "v8")); + +TEST_P(WasmFactoryTest, CreateWasmFromWASM) { auto factory = Registry::FactoryRegistry::getFactory("envoy.wasm"); ASSERT_NE(factory, nullptr); envoy::config::wasm::v2::WasmConfig config; - config.mutable_vm_config()->set_vm("envoy.wasm.vm.wavm"); + config.mutable_vm_config()->set_vm(absl::StrCat("envoy.wasm.vm.", GetParam())); config.mutable_vm_config()->mutable_code()->set_filename(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/wasm/test_data/logging_cpp.wasm")); config.set_singleton(true); @@ -42,12 +46,12 @@ TEST(WasmFactoryTest, CreateWasmFromWASM) { auto wasm = factory->createWasm(config, context); EXPECT_NE(wasm, nullptr); } -TEST(WasmFactoryTest, CreateWasmFromPrecompiledWASM) { +TEST_P(WasmFactoryTest, CreateWasmFromPrecompiledWASM) { auto factory = Registry::FactoryRegistry::getFactory("envoy.wasm"); ASSERT_NE(factory, nullptr); envoy::config::wasm::v2::WasmConfig config; - config.mutable_vm_config()->set_vm("envoy.wasm.vm.wavm"); + config.mutable_vm_config()->set_vm(absl::StrCat("envoy.wasm.vm.", GetParam())); config.mutable_vm_config()->mutable_code()->set_filename(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/wasm/test_data/logging_cpp.wasm")); config.mutable_vm_config()->set_allow_precompiled(true); @@ -65,12 +69,12 @@ TEST(WasmFactoryTest, CreateWasmFromPrecompiledWASM) { EXPECT_NE(wasm, nullptr); } -TEST(WasmFactoryTest, CreateWasmFromWASMPerThread) { +TEST_P(WasmFactoryTest, CreateWasmFromWASMPerThread) { auto factory = Registry::FactoryRegistry::getFactory("envoy.wasm"); ASSERT_NE(factory, nullptr); envoy::config::wasm::v2::WasmConfig config; - config.mutable_vm_config()->set_vm("envoy.wasm.vm.wavm"); + config.mutable_vm_config()->set_vm(absl::StrCat("envoy.wasm.vm.", GetParam())); config.mutable_vm_config()->mutable_code()->set_filename(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/wasm/test_data/logging_cpp.wasm")); config.set_id("test_id"); @@ -87,12 +91,12 @@ TEST(WasmFactoryTest, CreateWasmFromWASMPerThread) { EXPECT_EQ(wasm, nullptr); } -TEST(WasmFactoryTest, MissingImport) { +TEST_P(WasmFactoryTest, MissingImport) { auto factory = Registry::FactoryRegistry::getFactory("envoy.wasm"); ASSERT_NE(factory, nullptr); envoy::config::wasm::v2::WasmConfig config; - config.mutable_vm_config()->set_vm("envoy.wasm.vm.wavm"); + config.mutable_vm_config()->set_vm(absl::StrCat("envoy.wasm.vm.", GetParam())); config.mutable_vm_config()->mutable_code()->set_filename(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/wasm/test_data/missing_cpp.wasm")); config.set_singleton(true); diff --git a/test/extensions/wasm/wasm_test.cc b/test/extensions/wasm/wasm_test.cc index 73ec11fa5e964..3e9496b49be8d 100644 --- a/test/extensions/wasm/wasm_test.cc +++ b/test/extensions/wasm/wasm_test.cc @@ -33,11 +33,17 @@ class TestContext : public Extensions::Common::Wasm::Context { MOCK_METHOD1(setTickPeriodMilliseconds, void(uint32_t tick_period_milliseconds)); }; -class WasmTestCppRust : public TestBaseWithParam {}; +class WasmTest : public TestBaseWithParam {}; -INSTANTIATE_TEST_SUITE_P(SourceLanguages, WasmTestCppRust, testing::Values("cpp", "rust")); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmTest, testing::Values("wavm", "v8")); -TEST_P(WasmTestCppRust, Logging) { +class WasmTestMatrix : public TestBaseWithParam> {}; + +INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmTestMatrix, + testing::Combine(testing::Values("wavm", "v8"), + testing::Values("cpp", "rust"))); + +TEST_P(WasmTestMatrix, Logging) { Stats::IsolatedStoreImpl stats_store; Api::ApiPtr api = Api::createApiForTest(stats_store); Upstream::MockClusterManager cluster_manager; @@ -45,11 +51,12 @@ TEST_P(WasmTestCppRust, Logging) { auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); NiceMock local_info; auto wasm = std::make_unique( - "envoy.wasm.vm.wavm", "", "", cluster_manager, dispatcher, *scope, local_info, scope); + absl::StrCat("envoy.wasm.vm.", std::get<0>(GetParam())), "", "", cluster_manager, dispatcher, + *scope, local_info, scope); EXPECT_NE(wasm, nullptr); - const auto code = - TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(absl::StrCat( - "{{ test_rundir }}/test/extensions/wasm/test_data/logging_", GetParam(), ".wasm"))); + const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + absl::StrCat("{{ test_rundir }}/test/extensions/wasm/test_data/logging_", + std::get<1>(GetParam()), ".wasm"))); EXPECT_FALSE(code.empty()); auto context = std::make_unique(wasm.get()); @@ -67,7 +74,7 @@ TEST_P(WasmTestCppRust, Logging) { wasm->tickHandler(); } -TEST(WasmTest, BadSignature) { +TEST_P(WasmTest, BadSignature) { Stats::IsolatedStoreImpl stats_store; Api::ApiPtr api = Api::createApiForTest(stats_store); Upstream::MockClusterManager cluster_manager; @@ -75,7 +82,8 @@ TEST(WasmTest, BadSignature) { auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); NiceMock local_info; auto wasm = std::make_shared( - "envoy.wasm.vm.wavm", "", "", cluster_manager, dispatcher, *scope, local_info, scope); + absl::StrCat("envoy.wasm.vm.", GetParam()), "", "", cluster_manager, dispatcher, *scope, + local_info, scope); EXPECT_NE(wasm, nullptr); const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/wasm/test_data/bad_signature_cpp.wasm")); @@ -86,7 +94,8 @@ TEST(WasmTest, BadSignature) { "Bad function signature for: _proxy_onConfigure"); } -TEST(WasmTest, Segv) { +// TODO(PiotrSikora): catch llvm_trap in v8. +TEST(WasmTestWavmOnly, Segv) { Stats::IsolatedStoreImpl stats_store; Api::ApiPtr api = Api::createApiForTest(stats_store); Upstream::MockClusterManager cluster_manager; @@ -107,7 +116,7 @@ TEST(WasmTest, Segv) { "emscripten llvm_trap"); } -TEST(WasmTest, DivByZero) { +TEST_P(WasmTest, DivByZero) { Stats::IsolatedStoreImpl stats_store; Api::ApiPtr api = Api::createApiForTest(stats_store); Upstream::MockClusterManager cluster_manager; @@ -115,7 +124,8 @@ TEST(WasmTest, DivByZero) { auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); NiceMock local_info; auto wasm = std::make_shared( - "envoy.wasm.vm.wavm", "", "", cluster_manager, dispatcher, *scope, local_info, scope); + absl::StrCat("envoy.wasm.vm.", GetParam()), "", "", cluster_manager, dispatcher, *scope, + local_info, scope); EXPECT_NE(wasm, nullptr); const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/wasm/test_data/segv_cpp.wasm")); @@ -130,7 +140,7 @@ TEST(WasmTest, DivByZero) { wasm->generalContext()->onLog(); } -TEST(WasmTest, EmscriptenVersion) { +TEST_P(WasmTest, EmscriptenVersion) { Stats::IsolatedStoreImpl stats_store; Api::ApiPtr api = Api::createApiForTest(stats_store); Upstream::MockClusterManager cluster_manager; @@ -138,7 +148,8 @@ TEST(WasmTest, EmscriptenVersion) { auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); NiceMock local_info; auto wasm = std::make_shared( - "envoy.wasm.vm.wavm", "", "", cluster_manager, dispatcher, *scope, local_info, scope); + absl::StrCat("envoy.wasm.vm.", GetParam()), "", "", cluster_manager, dispatcher, *scope, + local_info, scope); EXPECT_NE(wasm, nullptr); const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/wasm/test_data/segv_cpp.wasm")); @@ -153,7 +164,7 @@ TEST(WasmTest, EmscriptenVersion) { EXPECT_EQ(abi_minor, 1); } -TEST(WasmTest, IntrinsicGlobals) { +TEST_P(WasmTest, IntrinsicGlobals) { Stats::IsolatedStoreImpl stats_store; Api::ApiPtr api = Api::createApiForTest(stats_store); Upstream::MockClusterManager cluster_manager; @@ -161,7 +172,8 @@ TEST(WasmTest, IntrinsicGlobals) { auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); NiceMock local_info; auto wasm = std::make_shared( - "envoy.wasm.vm.wavm", "", "", cluster_manager, dispatcher, *scope, local_info, scope); + absl::StrCat("envoy.wasm.vm.", GetParam()), "", "", cluster_manager, dispatcher, *scope, + local_info, scope); EXPECT_NE(wasm, nullptr); const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/wasm/test_data/emscripten_cpp.wasm")); @@ -180,7 +192,7 @@ TEST(WasmTest, IntrinsicGlobals) { // module is not required with the trap mode is set to "allow". Note: future WASM standards will // change this behavior by providing non-trapping instructions, but in the mean time we support the // default Emscripten behavior. -TEST(WasmTest, Asm2Wasm) { +TEST_P(WasmTest, Asm2Wasm) { Stats::IsolatedStoreImpl stats_store; Api::ApiPtr api = Api::createApiForTest(stats_store); Upstream::MockClusterManager cluster_manager; @@ -188,7 +200,8 @@ TEST(WasmTest, Asm2Wasm) { auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); NiceMock local_info; auto wasm = std::make_shared( - "envoy.wasm.vm.wavm", "", "", cluster_manager, dispatcher, *scope, local_info, scope); + absl::StrCat("envoy.wasm.vm.", GetParam()), "", "", cluster_manager, dispatcher, *scope, + local_info, scope); EXPECT_NE(wasm, nullptr); const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/wasm/test_data/asm2wasm_cpp.wasm")); @@ -200,7 +213,7 @@ TEST(WasmTest, Asm2Wasm) { wasm->start(); } -TEST(WasmTest, Stats) { +TEST_P(WasmTest, Stats) { Stats::IsolatedStoreImpl stats_store; Api::ApiPtr api = Api::createApiForTest(stats_store); Upstream::MockClusterManager cluster_manager; @@ -208,7 +221,8 @@ TEST(WasmTest, Stats) { auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); NiceMock local_info; auto wasm = std::make_unique( - "envoy.wasm.vm.wavm", "", "", cluster_manager, dispatcher, *scope, local_info, scope); + absl::StrCat("envoy.wasm.vm.", GetParam()), "", "", cluster_manager, dispatcher, *scope, + local_info, scope); EXPECT_NE(wasm, nullptr); const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/wasm/test_data/stats_cpp.wasm")); @@ -229,7 +243,7 @@ TEST(WasmTest, Stats) { wasm->start(); } -TEST(WasmTest, StatsHigherLevel) { +TEST_P(WasmTest, StatsHigherLevel) { Stats::IsolatedStoreImpl stats_store; Api::ApiPtr api = Api::createApiForTest(stats_store); Upstream::MockClusterManager cluster_manager; @@ -237,7 +251,8 @@ TEST(WasmTest, StatsHigherLevel) { auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); NiceMock local_info; auto wasm = std::make_unique( - "envoy.wasm.vm.wavm", "", "", cluster_manager, dispatcher, *scope, local_info, scope); + absl::StrCat("envoy.wasm.vm.", GetParam()), "", "", cluster_manager, dispatcher, *scope, + local_info, scope); EXPECT_NE(wasm, nullptr); const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/wasm/test_data/stats_cpp.wasm")); @@ -262,7 +277,7 @@ TEST(WasmTest, StatsHigherLevel) { wasm->tickHandler(); } -TEST(WasmTest, StatsHighLevel) { +TEST_P(WasmTest, StatsHighLevel) { Stats::IsolatedStoreImpl stats_store; Api::ApiPtr api = Api::createApiForTest(stats_store); Upstream::MockClusterManager cluster_manager; @@ -270,7 +285,8 @@ TEST(WasmTest, StatsHighLevel) { auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); NiceMock local_info; auto wasm = std::make_unique( - "envoy.wasm.vm.wavm", "", "", cluster_manager, dispatcher, *scope, local_info, scope); + absl::StrCat("envoy.wasm.vm.", GetParam()), "", "", cluster_manager, dispatcher, *scope, + local_info, scope); EXPECT_NE(wasm, nullptr); const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/wasm/test_data/stats_cpp.wasm"));