From b76eae42ec5fc8f7875f27e3b81fe2db5d411d73 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 8 May 2019 18:56:08 +0000 Subject: [PATCH 01/17] build: update ninja to version 1.8.2. Signed-off-by: Piotr Sikora --- ci/build_container/build_container_ubuntu.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ci/build_container/build_container_ubuntu.sh b/ci/build_container/build_container_ubuntu.sh index a997ac82a54ba..98dd0730f30d2 100755 --- a/ci/build_container/build_container_ubuntu.sh +++ b/ci/build_container/build_container_ubuntu.sh @@ -6,11 +6,15 @@ set -e apt-get update export DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget software-properties-common make cmake git python python-pip python3 python3-pip \ - unzip bc libtool ninja-build automake zip time golang gdb strace wireshark tshark tcpdump + unzip bc libtool automake zip time golang gdb strace wireshark tshark tcpdump # Install cmake 3.12. curl -sLO https://cmake.org/files/v3.12/cmake-3.12.3-Linux-x86_64.tar.gz echo "0210f500c71af0ee7e8c42da76954298144d5f72f725ea381ae5db7b766b000e cmake-3.12.3-Linux-x86_64.tar.gz" | sha256sum --check tar -zxf cmake-3.12.3-Linux-x86_64.tar.gz -C /usr --strip-components=1 +# Install ninja 1.8.2. +curl -sLO https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-linux.zip +echo "d2fea9ff33b3ef353161ed906f260d565ca55b8ca0568fa07b1d2cab90a84a07 ninja-linux.zip" | sha256sum --check +unzip ninja-linux.zip && mv ninja /usr/bin # clang 7. curl http://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-7 main" From 5f7bf108a93e962bf21dce7bbdfd9294d747cc71 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 8 May 2019 18:58:02 +0000 Subject: [PATCH 02/17] wasm: add v8 runtime. Signed-off-by: Piotr Sikora --- bazel/repositories.bzl | 1 + bazel/target_recipes.bzl | 1 + ci/build_container/build_recipes/v8.sh | 84 +++ ci/prebuilt/BUILD | 17 + source/extensions/common/wasm/BUILD | 1 + source/extensions/common/wasm/v8/BUILD | 28 + source/extensions/common/wasm/v8/v8.cc | 590 ++++++++++++++++++ source/extensions/common/wasm/v8/v8.h | 30 + source/extensions/common/wasm/wasm.cc | 15 +- source/extensions/common/wasm/wasm.h | 2 +- source/extensions/common/wasm/wavm/wavm.cc | 2 +- source/extensions/common/wasm/wavm/wavm.h | 2 +- .../extensions/common/wasm/well_known_names.h | 2 + .../access_loggers/wasm/config_test.cc | 10 +- .../filters/http/wasm/config_test.cc | 43 +- .../filters/http/wasm/wasm_filter_test.cc | 20 +- test/extensions/wasm/config_test.cc | 20 +- test/extensions/wasm/wasm_test.cc | 62 +- 18 files changed, 862 insertions(+), 68 deletions(-) create mode 100755 ci/build_container/build_recipes/v8.sh create mode 100644 source/extensions/common/wasm/v8/BUILD create mode 100644 source/extensions/common/wasm/v8/v8.cc create mode 100644 source/extensions/common/wasm/v8/v8.h 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..0c3d520d7b9c4 --- /dev/null +++ b/ci/build_container/build_recipes/v8.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +set -e + +# Get wasm-c-api. + +COMMIT=0705a10690d42ff312102392a7f6eeda56985182 # Tue Apr 16 14:05:11 2019 +0200 +SHA256=dc4ea492e4a45ea8aa482091585710a44719bc60a60b958fe5aa4e22fa8887ba + +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.26 # 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. + +cat <&& 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(); +EOF + +# Build wasm-c-api. + +# TODO(PiotrSikora): respect CC/CXX/CFLAGS/CXXFLAGS/LDFLAGS upstream. + +# Disable sanitizers. +sed -i"" -E "s/ -fsanitize[^\n]+//" Makefile + +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 8fba80bad2d45..fd35c4e6dfce2 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..e61967267775e --- /dev/null +++ b/source/extensions/common/wasm/v8/v8.cc @@ -0,0 +1,590 @@ +#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-bin.hh" +#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, 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_{}; +}; + +// Helper functions. + +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"; + } +} + +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; +} + +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; +} + +// Template magic. + +template +wasm::vec convertArgsTupleToValTypesImpl(absl::index_sequence) { + return wasm::vec::make(wasm::ValType::make( + wasm::Val::make(static_cast::type>(0)).kind())...); +} + +template ::value>> +wasm::vec convertArgsTupleToValTypes() { + return convertArgsTupleToValTypesImpl(Indices{}); +} + +template +T convertValRefsVectorToTupleImpl(const std::vector& vec, + absl::index_sequence) { + return std::make_tuple((vec[I]->get::type>())...); +} + +template ::value>> +T convertValRefsVectorToTuple(const std::vector& vec) { + // TODO(PiotrSikora): assert that vec.size() == std::tuple_size::value. + return convertValRefsVectorToTupleImpl(vec, Indices{}); +} + +// 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) { + // TODO(PiotrSikora): make sure that we don't overread. + auto type = *pos++; + auto rest = wasm::bin::u32(pos); + if (type == 0 /* custom section */) { + auto start = pos; + auto len = wasm::bin::u32(pos); + 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()->func()->params()) && + equalValTypes(import_type->func()->results(), func->type()->func()->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()->func()->params()), + printValTypes(func->type()->func()->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_) { + wasm::Val args[] = {wasm::Val::make(static_cast(64 * 64 * 1024)), + wasm::Val::make(static_cast(128 * 64 * 1024))}; + 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); +} + +void V8::callModuleFunction(Context* context, absl::string_view functionName, 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*) { + ENVOY_LOG(trace, "[wasm] getMemoryOffset({})", host_pointer); + // TODO(PiotrSikora): add support when/if needed. + return false; +} + +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] registerHostGlobabl(\"{}.{}\", {})", 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); + host_globals_.emplace(absl::StrCat(moduleName, ".", name), std::move(global)); + return nullptr; +} + +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 { + // TODO(PiotrSikora): avoid vector, convert to array-to-tuple. + std::vector args_vector; + for (size_t i = 0; i < sizeof...(Args); i++) { + args_vector.push_back(¶ms[i]); + } + auto args_tuple = convertValRefsVectorToTuple>(args_vector); + 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 { + // TODO(PiotrSikora): avoid vector, convert to array-to-tuple. + std::vector args_vector; + for (size_t i = 0; i < sizeof...(Args); i++) { + args_vector.push_back(¶ms[i]); + } + auto args_tuple = convertValRefsVectorToTuple>(args_vector); + 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(); + // TODO(PiotrSikora): make sure that type of each argument is correct. + if (func->param_arity() != sizeof...(Args) || func->result_arity() != 0) { + 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(); + // TODO(PiotrSikora): make sure that type of each argument is correct. + if (func->param_arity() != sizeof...(Args) || func->result_arity() != 1) { + 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..0fdabb4527170 --- /dev/null +++ b/source/extensions/common/wasm/v8/v8.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#include "envoy/common/exception.h" +#include "envoy/config/wasm/v2/wasm.pb.validate.h" +#include "envoy/server/wasm.h" +#include "envoy/thread_local/thread_local.h" + +#include "common/common/assert.h" +#include "common/common/c_smart_ptr.h" +#include "common/common/logger.h" + +#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..d1a0378ee6004 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"); } } @@ -1474,8 +1475,9 @@ bool Wasm::initialize(const std::string& code, absl::string_view name, bool allo general_context_ = createContext(); wasm_vm_->start(general_context_.get()); if (is_emscripten_) { - ASSERT(std::isnan(emscripten_NaN_->get())); - ASSERT(std::isinf(emscripten_Infinity_->get())); + // TODO(PiotrSikora): re-enable those checks after v8 starts returning globals. + // ASSERT(std::isnan(emscripten_NaN_->get())); + // ASSERT(std::isinf(emscripten_Infinity_->get())); } code_ = code; allow_precompiled_ = allow_precompiled; @@ -1762,9 +1764,12 @@ 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 { + UNREFERENCED_PARAMETER(wasm_vm); 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 0fb1f903942c5..5010d613de966 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..af21ae6838263 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,8 @@ TEST(WasmTest, EmscriptenVersion) { EXPECT_EQ(abi_minor, 1); } -TEST(WasmTest, IntrinsicGlobals) { +// TODO(PiotrSikora): fix "nan" being emitted as "0.00000" in v8. +TEST(WasmTestWavmOnly, IntrinsicGlobals) { Stats::IsolatedStoreImpl stats_store; Api::ApiPtr api = Api::createApiForTest(stats_store); Upstream::MockClusterManager cluster_manager; @@ -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")); From a3f153ad9e4cf02c25593c2ec55e72ede9a6ccd8 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Sat, 11 May 2019 00:23:01 +0000 Subject: [PATCH 03/17] review: address Yuchen's comments. Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/v8/v8.cc | 7 ++++--- source/extensions/common/wasm/wasm.cc | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index e61967267775e..c0e3c86b1587b 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -147,7 +147,7 @@ class V8 : public WasmVm { // Helper functions. -const char* printValKind(wasm::ValKind kind) { +static const char* printValKind(wasm::ValKind kind) { switch (kind) { case wasm::I32: return "i32"; @@ -164,7 +164,7 @@ const char* printValKind(wasm::ValKind kind) { } } -std::string printValTypes(const wasm::vec& types) { +static std::string printValTypes(const wasm::vec& types) { if (types.size() == 0) { return "void"; } @@ -180,7 +180,8 @@ std::string printValTypes(const wasm::vec& types) { return s; } -bool equalValTypes(const wasm::vec& left, const wasm::vec& right) { +static bool equalValTypes(const wasm::vec& left, + const wasm::vec& right) { if (left.size() != right.size()) { return false; } diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index d1a0378ee6004..3881c7c68142d 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -1769,7 +1769,6 @@ std::unique_ptr createWasmVm(absl::string_view wasm_vm) { } else if (wasm_vm == WasmVmNames::get().Wavm) { return Wavm::createVm(); } else { - UNREFERENCED_PARAMETER(wasm_vm); return nullptr; } } From 2f8a050f455afc8c881cd56756afa8764611212d Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Tue, 14 May 2019 00:47:43 +0000 Subject: [PATCH 04/17] build: update wasm-c-api to latest. Signed-off-by: Piotr Sikora --- ci/build_container/build_recipes/v8.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/build_container/build_recipes/v8.sh b/ci/build_container/build_recipes/v8.sh index 0c3d520d7b9c4..9f93e1ba720e7 100755 --- a/ci/build_container/build_recipes/v8.sh +++ b/ci/build_container/build_recipes/v8.sh @@ -4,8 +4,8 @@ set -e # Get wasm-c-api. -COMMIT=0705a10690d42ff312102392a7f6eeda56985182 # Tue Apr 16 14:05:11 2019 +0200 -SHA256=dc4ea492e4a45ea8aa482091585710a44719bc60a60b958fe5aa4e22fa8887ba +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 @@ -24,7 +24,7 @@ export PATH="$PATH:$PWD/depot_tools" # Get v8. -VERSION=7.4.288.26 # match wasm-c-api (branch-heads/7.4) +VERSION=7.4.288.28 # match wasm-c-api (branch-heads/7.4) fetch v8 cd v8 From ff8c565892d12e29b0a845282e649db41b418a24 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Fri, 10 May 2019 23:30:18 +0000 Subject: [PATCH 05/17] v8: disable DEBUG, since it seems to be broken. Signed-off-by: Piotr Sikora --- ci/build_container/build_recipes/v8.sh | 40 +++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/ci/build_container/build_recipes/v8.sh b/ci/build_container/build_recipes/v8.sh index 9f93e1ba720e7..fe13d5e6889ff 100755 --- a/ci/build_container/build_recipes/v8.sh +++ b/ci/build_container/build_recipes/v8.sh @@ -53,7 +53,33 @@ cd ../.. # Patch wasm-c-api. -cat <&& config) -> own { @@ -65,15 +91,21 @@ cat <(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. -# Disable sanitizers. -sed -i"" -E "s/ -fsanitize[^\n]+//" Makefile - make wasm # Install wasm-c-api. From 4bcf3ea328a6be3765ad5aae0be34ecc25d33588 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Tue, 14 May 2019 01:42:26 +0000 Subject: [PATCH 06/17] v8: fix typo. Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/v8/v8.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index c0e3c86b1587b..b581258719719 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -468,7 +468,7 @@ bool V8::setMemory(uint32_t pointer, uint32_t size, void* data) { template std::unique_ptr> V8::registerHostGlobalImpl(absl::string_view moduleName, absl::string_view name, T initialValue) { - ENVOY_LOG(trace, "[wasm] registerHostGlobabl(\"{}.{}\", {})", moduleName, name, 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); From 3eea9ffad1128dbc909723a63ee5ba5d037cef9d Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 15 May 2019 00:46:23 +0000 Subject: [PATCH 07/17] v8: more template magic. Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/v8/v8.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index b581258719719..f92a647a03288 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -197,13 +197,13 @@ static bool equalValTypes(const wasm::vec& left, template wasm::vec convertArgsTupleToValTypesImpl(absl::index_sequence) { + // TODO(PiotrSikora): use partial template instantiation to convert types to enums. return wasm::vec::make(wasm::ValType::make( wasm::Val::make(static_cast::type>(0)).kind())...); } -template ::value>> -wasm::vec convertArgsTupleToValTypes() { - return convertArgsTupleToValTypesImpl(Indices{}); +template wasm::vec convertArgsTupleToValTypes() { + return convertArgsTupleToValTypesImpl(absl::make_index_sequence::value>{}); } template @@ -212,10 +212,10 @@ T convertValRefsVectorToTupleImpl(const std::vector& vec, return std::make_tuple((vec[I]->get::type>())...); } -template ::value>> -T convertValRefsVectorToTuple(const std::vector& vec) { +template T convertValRefsVectorToTuple(const std::vector& vec) { // TODO(PiotrSikora): assert that vec.size() == std::tuple_size::value. - return convertValRefsVectorToTupleImpl(vec, Indices{}); + return convertValRefsVectorToTupleImpl(vec, + absl::make_index_sequence::value>{}); } // V8 implementation. From d32353f2dc1b388f8ca43d3f2b67ebef94b5ed50 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 15 May 2019 00:46:42 +0000 Subject: [PATCH 08/17] v8: implement getMemoryOffset(). Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/v8/v8.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index f92a647a03288..4c741cfb5bc88 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -451,10 +451,13 @@ absl::string_view V8::getMemory(uint32_t pointer, uint32_t size) { return absl::string_view(memory_->data() + pointer, size); } -bool V8::getMemoryOffset(void* host_pointer, uint32_t*) { +bool V8::getMemoryOffset(void* host_pointer, uint32_t* vm_pointer) { ENVOY_LOG(trace, "[wasm] getMemoryOffset({})", host_pointer); - // TODO(PiotrSikora): add support when/if needed. - return false; + 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) { From 18e4dee1be1e13cc6a682b9656962204a995bbc1 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 15 May 2019 00:47:02 +0000 Subject: [PATCH 09/17] v8: remove unnecessary includes. Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/v8/v8.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/source/extensions/common/wasm/v8/v8.h b/source/extensions/common/wasm/v8/v8.h index 0fdabb4527170..7a22f6dbcbe4c 100644 --- a/source/extensions/common/wasm/v8/v8.h +++ b/source/extensions/common/wasm/v8/v8.h @@ -1,17 +1,6 @@ #pragma once #include -#include -#include - -#include "envoy/common/exception.h" -#include "envoy/config/wasm/v2/wasm.pb.validate.h" -#include "envoy/server/wasm.h" -#include "envoy/thread_local/thread_local.h" - -#include "common/common/assert.h" -#include "common/common/c_smart_ptr.h" -#include "common/common/logger.h" #include "extensions/common/wasm/wasm.h" From 0027504c5ad99c74818bfcc325066b52d6582a9c Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 15 May 2019 03:34:56 +0000 Subject: [PATCH 10/17] v8: even more template magic. Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/v8/v8.cc | 28 ++++++++------------------ 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index 4c741cfb5bc88..f9b9917bca662 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -206,16 +206,14 @@ template wasm::vec convertArgsTupleToValTypes() { return convertArgsTupleToValTypesImpl(absl::make_index_sequence::value>{}); } -template -T convertValRefsVectorToTupleImpl(const std::vector& vec, - absl::index_sequence) { - return std::make_tuple((vec[I]->get::type>())...); +template +constexpr T convertValTypesToArgsTupleImpl(const U& arr, absl::index_sequence) { + return std::make_tuple((arr[I].template get::type>())...); } -template T convertValRefsVectorToTuple(const std::vector& vec) { - // TODO(PiotrSikora): assert that vec.size() == std::tuple_size::value. - return convertValRefsVectorToTupleImpl(vec, - absl::make_index_sequence::value>{}); +template constexpr T convertValTypesToArgsTuple(const U& arr) { + return convertValTypesToArgsTupleImpl(arr, + absl::make_index_sequence::value>{}); } // V8 implementation. @@ -488,12 +486,7 @@ void V8::registerHostFunctionImpl(absl::string_view moduleName, absl::string_vie auto func = wasm::Func::make( store_.get(), type.get(), [](void* data, const wasm::Val params[], wasm::Val[]) -> wasm::own { - // TODO(PiotrSikora): avoid vector, convert to array-to-tuple. - std::vector args_vector; - for (size_t i = 0; i < sizeof...(Args); i++) { - args_vector.push_back(¶ms[i]); - } - auto args_tuple = convertValRefsVectorToTuple>(args_vector); + 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); @@ -512,12 +505,7 @@ void V8::registerHostFunctionImpl(absl::string_view moduleName, absl::string_vie auto func = wasm::Func::make( store_.get(), type.get(), [](void* data, const wasm::Val params[], wasm::Val results[]) -> wasm::own { - // TODO(PiotrSikora): avoid vector, convert to array-to-tuple. - std::vector args_vector; - for (size_t i = 0; i < sizeof...(Args); i++) { - args_vector.push_back(¶ms[i]); - } - auto args_tuple = convertValRefsVectorToTuple>(args_vector); + 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); From 0bef0e7752ff0c44361c8b50b38c0a7c641d88ac Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 15 May 2019 11:20:25 +0000 Subject: [PATCH 11/17] v8: even more template magic. Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/v8/v8.cc | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index f9b9917bca662..9c07733a424f8 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -195,15 +195,22 @@ static bool equalValTypes(const wasm::vec& left, // 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 -wasm::vec convertArgsTupleToValTypesImpl(absl::index_sequence) { - // TODO(PiotrSikora): use partial template instantiation to convert types to enums. - return wasm::vec::make(wasm::ValType::make( - wasm::Val::make(static_cast::type>(0)).kind())...); +constexpr auto convertArgsTupleToValTypesImpl(absl::index_sequence) { + return wasm::vec::make( + wasm::ValType::make(convertArgToValKind::type>())...); } -template wasm::vec convertArgsTupleToValTypes() { - return convertArgsTupleToValTypesImpl(absl::make_index_sequence::value>{}); +template constexpr auto convertArgsTupleToValTypes() { + return convertArgsTupleToValTypesImpl(absl::make_index_sequence::value>()); } template @@ -213,7 +220,7 @@ constexpr T convertValTypesToArgsTupleImpl(const U& arr, absl::index_sequence constexpr T convertValTypesToArgsTuple(const U& arr) { return convertValTypesToArgsTupleImpl(arr, - absl::make_index_sequence::value>{}); + absl::make_index_sequence::value>()); } // V8 implementation. From e20e042e1265342b982f304551480d7213179b25 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 15 May 2019 12:30:54 +0000 Subject: [PATCH 12/17] v8: verify type of each argument in module functions. Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/v8/v8.cc | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index 9c07733a424f8..fb85719646d0b 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -292,17 +292,16 @@ void V8::link(absl::string_view debug_name, bool needs_emscripten) { } } if (func) { - if (equalValTypes(import_type->func()->params(), func->type()->func()->params()) && - equalValTypes(import_type->func()->results(), func->type()->func()->results())) { + 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()->func()->params()), - printValTypes(func->type()->func()->results()))); + 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( @@ -533,8 +532,8 @@ void V8::getModuleFunctionImpl(absl::string_view functionName, return; } const wasm::Func* func = it->second.get(); - // TODO(PiotrSikora): make sure that type of each argument is correct. - if (func->param_arity() != sizeof...(Args) || func->result_arity() != 0) { + 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 { @@ -560,8 +559,8 @@ void V8::getModuleFunctionImpl(absl::string_view functionName, return; } const wasm::Func* func = it->second.get(); - // TODO(PiotrSikora): make sure that type of each argument is correct. - if (func->param_arity() != sizeof...(Args) || func->result_arity() != 1) { + 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 { From 51de647877252808a0c391e937420f9ba5168a37 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 15 May 2019 12:33:11 +0000 Subject: [PATCH 13/17] v8: add more reasons for disabled DEBUG. Signed-off-by: Piotr Sikora --- ci/build_container/build_recipes/v8.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/build_container/build_recipes/v8.sh b/ci/build_container/build_recipes/v8.sh index fe13d5e6889ff..52380a8c4d3cb 100755 --- a/ci/build_container/build_recipes/v8.sh +++ b/ci/build_container/build_recipes/v8.sh @@ -54,7 +54,7 @@ cd ../.. # Patch wasm-c-api. # 1. Disable DEBUG (alloc/free accounting), since it seems to be broken -# in optimized builds. +# in optimized builds and/or when using sanitizers. # 2. Disable hardcoded sanitizers. cat <<\EOF | patch -p1 From 998a80c738f1b29a803188559c07043780fea30f Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 15 May 2019 23:37:07 +0000 Subject: [PATCH 14/17] v8: guard reads when parsing binary WASM module. Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/v8/v8.cc | 30 ++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index fb85719646d0b..52ce69284cc5a 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -21,7 +21,6 @@ #include "absl/strings/match.h" #include "absl/types/span.h" #include "absl/utility/utility.h" -#include "wasm-c-api/wasm-bin.hh" #include "wasm-c-api/wasm.hh" namespace Envoy { @@ -193,6 +192,21 @@ static bool equalValTypes(const wasm::vec& left, 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(); @@ -244,12 +258,20 @@ absl::string_view V8::getUserSection(absl::string_view name) { const byte_t* end = source_.get() + source_.size(); const byte_t* pos = source_.get() + 8; // skip header while (pos < end) { - // TODO(PiotrSikora): make sure that we don't overread. + if (pos + 1 > end) { + throw WasmException("Failed to parse corrupted WASM module"); + } auto type = *pos++; - auto rest = wasm::bin::u32(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 = wasm::bin::u32(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) { From 09306b8f7a1c486ac2123c05a85d26a1e2f918ce Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Thu, 16 May 2019 00:03:46 +0000 Subject: [PATCH 15/17] v8: mark callModuleFunction() args as const. Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/v8/v8.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index 52ce69284cc5a..8030479d9e486 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -112,7 +112,7 @@ class V8 : public WasmVm { #undef _GET_MODULE_FUNCTION private: - void callModuleFunction(Context* context, absl::string_view functionName, wasm::Val args[], + void callModuleFunction(Context* context, absl::string_view functionName, const wasm::Val args[], wasm::Val results[]); template @@ -432,8 +432,8 @@ void V8::start(Context* context) { ENVOY_LOG(trace, "[wasm] start()"); if (module_needs_emscripten_) { - wasm::Val args[] = {wasm::Val::make(static_cast(64 * 64 * 1024)), - wasm::Val::make(static_cast(128 * 64 * 1024))}; + 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); @@ -450,11 +450,11 @@ void V8::start(Context* context) { } } - callModuleFunction(context, "__post_instantiate", {}, nullptr); + callModuleFunction(context, "__post_instantiate", nullptr, nullptr); } -void V8::callModuleFunction(Context* context, absl::string_view functionName, wasm::Val args[], - wasm::Val results[]) { +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; From 20ee24d21aef8c69f90a55b3e3be40a1f634a7b0 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Thu, 16 May 2019 00:15:38 +0000 Subject: [PATCH 16/17] v8: re-enable NaN test, since it was fixed upstream. Signed-off-by: Piotr Sikora --- test/extensions/wasm/wasm_test.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/extensions/wasm/wasm_test.cc b/test/extensions/wasm/wasm_test.cc index af21ae6838263..3e9496b49be8d 100644 --- a/test/extensions/wasm/wasm_test.cc +++ b/test/extensions/wasm/wasm_test.cc @@ -164,8 +164,7 @@ TEST_P(WasmTest, EmscriptenVersion) { EXPECT_EQ(abi_minor, 1); } -// TODO(PiotrSikora): fix "nan" being emitted as "0.00000" in v8. -TEST(WasmTestWavmOnly, IntrinsicGlobals) { +TEST_P(WasmTest, IntrinsicGlobals) { Stats::IsolatedStoreImpl stats_store; Api::ApiPtr api = Api::createApiForTest(stats_store); Upstream::MockClusterManager cluster_manager; @@ -173,7 +172,8 @@ TEST(WasmTestWavmOnly, 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")); From 36a7064c42859d457c94b77d0b8b1e84271b773e Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Thu, 16 May 2019 01:21:36 +0000 Subject: [PATCH 17/17] v8: add proxy for globals. Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/v8/v8.cc | 12 +++++++++++- source/extensions/common/wasm/wasm.cc | 5 ++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index 8030479d9e486..ac28d7a685394 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -144,6 +144,15 @@ class V8 : public WasmVm { 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) { @@ -501,8 +510,9 @@ std::unique_ptr> V8::registerHostGlobalImpl(absl::string_view moduleNa 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 nullptr; + return proxy; } template diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index 3881c7c68142d..eef75b03b30fe 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -1475,9 +1475,8 @@ bool Wasm::initialize(const std::string& code, absl::string_view name, bool allo general_context_ = createContext(); wasm_vm_->start(general_context_.get()); if (is_emscripten_) { - // TODO(PiotrSikora): re-enable those checks after v8 starts returning globals. - // ASSERT(std::isnan(emscripten_NaN_->get())); - // ASSERT(std::isinf(emscripten_Infinity_->get())); + ASSERT(std::isnan(emscripten_NaN_->get())); + ASSERT(std::isinf(emscripten_Infinity_->get())); } code_ = code; allow_precompiled_ = allow_precompiled;