diff --git a/bazel/external/wee8.genrule_cmd b/bazel/external/wee8.genrule_cmd index 323402b493927..9103dfb4d9fbc 100644 --- a/bazel/external/wee8.genrule_cmd +++ b/bazel/external/wee8.genrule_cmd @@ -72,7 +72,11 @@ WEE8_BUILD_ARGS+=" v8_use_external_startup_data=false" WEE8_BUILD_ARGS+=" v8_enable_shared_ro_heap=false" # Build wee8. -third_party/depot_tools/gn gen out/wee8 --args="$$WEE8_BUILD_ARGS" +if [[ `uname` == "Darwin" ]]; then + buildtools/mac/gn gen out/wee8 --args="$$WEE8_BUILD_ARGS" +else + buildtools/linux64/gn gen out/wee8 --args="$$WEE8_BUILD_ARGS" +fi third_party/depot_tools/ninja -C out/wee8 wee8 # Move compiled library to the expected destinations. diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index dbc0f83910be8..9333c679421f6 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -25,6 +25,14 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "wasm_vm_base", + hdrs = ["wasm_vm_base.h"], + deps = [ + "//source/common/stats:stats_lib", + ], +) + envoy_cc_library( name = "wasm_vm_lib", srcs = ["wasm_vm.cc"], diff --git a/source/extensions/common/wasm/null/BUILD b/source/extensions/common/wasm/null/BUILD index eed8e62d2e49b..6f93952bc9cfc 100644 --- a/source/extensions/common/wasm/null/BUILD +++ b/source/extensions/common/wasm/null/BUILD @@ -26,6 +26,7 @@ envoy_cc_library( "//external:abseil_node_hash_map", "//include/envoy/registry", "//source/common/common:assert_lib", + "//source/extensions/common/wasm:wasm_vm_base", "//source/extensions/common/wasm:wasm_vm_interface", "//source/extensions/common/wasm:well_known_names", ], diff --git a/source/extensions/common/wasm/null/null.cc b/source/extensions/common/wasm/null/null.cc index 185dde60780ab..af2ba77d1dc56 100644 --- a/source/extensions/common/wasm/null/null.cc +++ b/source/extensions/common/wasm/null/null.cc @@ -18,7 +18,7 @@ namespace Common { namespace Wasm { namespace Null { -WasmVmPtr createVm() { return std::make_unique(); } +WasmVmPtr createVm(const Stats::ScopeSharedPtr& scope) { return std::make_unique(scope); } } // namespace Null } // namespace Wasm diff --git a/source/extensions/common/wasm/null/null.h b/source/extensions/common/wasm/null/null.h index 7d88fb356923e..285b13373fbc8 100644 --- a/source/extensions/common/wasm/null/null.h +++ b/source/extensions/common/wasm/null/null.h @@ -11,7 +11,7 @@ namespace Common { namespace Wasm { namespace Null { -WasmVmPtr createVm(); +WasmVmPtr createVm(const Stats::ScopeSharedPtr& scope); } // namespace Null } // namespace Wasm diff --git a/source/extensions/common/wasm/null/null_vm.h b/source/extensions/common/wasm/null/null_vm.h index d23e332bcb77d..82da033019723 100644 --- a/source/extensions/common/wasm/null/null_vm.h +++ b/source/extensions/common/wasm/null/null_vm.h @@ -9,6 +9,7 @@ #include "common/common/assert.h" #include "extensions/common/wasm/null/null_vm_plugin.h" +#include "extensions/common/wasm/wasm_vm_base.h" #include "extensions/common/wasm/well_known_names.h" namespace Envoy { @@ -20,13 +21,14 @@ namespace Null { // The NullVm wraps a C++ WASM plugin which has been compiled with the WASM API // and linked directly into the Envoy process. This is useful for development // in that it permits the debugger to set breakpoints in both Envoy and the plugin. -struct NullVm : public WasmVm { - NullVm() = default; - NullVm(const NullVm& other) : plugin_name_(other.plugin_name_) {} +struct NullVm : public WasmVmBase { + NullVm(const Stats::ScopeSharedPtr& scope) : WasmVmBase(scope, WasmRuntimeNames::get().Null) {} + NullVm(const NullVm& other) + : WasmVmBase(other.scope_, WasmRuntimeNames::get().Null), plugin_name_(other.plugin_name_) {} // WasmVm absl::string_view runtime() override { return WasmRuntimeNames::get().Null; } - bool cloneable() override { return true; }; + Cloneable cloneable() override { return Cloneable::InstantiatedModule; }; WasmVmPtr clone() override; bool load(const std::string& code, bool allow_precompiled) override; void link(absl::string_view debug_name) override; diff --git a/source/extensions/common/wasm/v8/BUILD b/source/extensions/common/wasm/v8/BUILD index fb5a473026402..04d0954d1b2cd 100644 --- a/source/extensions/common/wasm/v8/BUILD +++ b/source/extensions/common/wasm/v8/BUILD @@ -17,6 +17,7 @@ envoy_cc_library( ], deps = [ "//source/common/common:assert_lib", + "//source/extensions/common/wasm:wasm_vm_base", "//source/extensions/common/wasm:wasm_vm_interface", "//source/extensions/common/wasm:well_known_names", ], diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index 44e4a46f1989d..bd158874ba731 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -6,6 +6,7 @@ #include "common/common/assert.h" +#include "extensions/common/wasm/wasm_vm_base.h" #include "extensions/common/wasm/well_known_names.h" #include "absl/container/flat_hash_map.h" @@ -33,9 +34,9 @@ struct FuncData { using FuncDataPtr = std::unique_ptr; -class V8 : public WasmVm { +class V8 : public WasmVmBase { public: - V8() = default; + V8(const Stats::ScopeSharedPtr& scope) : WasmVmBase(scope, WasmRuntimeNames::get().V8) {} // Extensions::Common::Wasm::WasmVm absl::string_view runtime() override { return WasmRuntimeNames::get().V8; } @@ -44,9 +45,8 @@ class V8 : public WasmVm { absl::string_view getCustomSection(absl::string_view name) override; void link(absl::string_view debug_name) override; - // V8 is currently not cloneable. - bool cloneable() override { return false; } - WasmVmPtr clone() override { return nullptr; } + Cloneable cloneable() override { return Cloneable::CompiledBytecode; } + WasmVmPtr clone() override; uint64_t getMemorySize() override; absl::optional getMemory(uint64_t pointer, uint64_t size) override; @@ -89,6 +89,7 @@ class V8 : public WasmVm { wasm::vec source_ = wasm::vec::invalid(); wasm::own store_; wasm::own module_; + wasm::own> shared_module_; wasm::own instance_; wasm::own memory_; wasm::own table_; @@ -241,15 +242,30 @@ template constexpr T convertValTypesToArgsTuple(const U bool V8::load(const std::string& code, bool /* allow_precompiled */) { ENVOY_LOG(trace, "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_); + if (module_) { + shared_module_ = module_->share(); + } + return module_ != nullptr; } +WasmVmPtr V8::clone() { + ENVOY_LOG(trace, "clone()"); + ASSERT(shared_module_ != nullptr); + + auto clone = std::make_unique(scope_); + clone->store_ = wasm::Store::make(engine()); + + clone->module_ = wasm::Module::obtain(clone->store_.get(), shared_module_.get()); + + return clone; +} + absl::string_view V8::getCustomSection(absl::string_view name) { ENVOY_LOG(trace, "getCustomSection(\"{}\")", name); ASSERT(source_.get() != nullptr); @@ -356,7 +372,6 @@ void V8::link(absl::string_view debug_name) { ASSERT(import_types.size() == imports.size()); instance_ = wasm::Instance::make(store_.get(), module_.get(), imports.data()); - RELEASE_ASSERT(instance_ != nullptr, ""); const auto export_types = module_.get()->exports(); const auto exports = instance_.get()->exports(); @@ -562,7 +577,7 @@ void V8::getModuleFunctionImpl(absl::string_view function_name, }; } -WasmVmPtr createVm() { return std::make_unique(); } +WasmVmPtr createVm(const Stats::ScopeSharedPtr& scope) { return std::make_unique(scope); } } // namespace V8 } // namespace Wasm diff --git a/source/extensions/common/wasm/v8/v8.h b/source/extensions/common/wasm/v8/v8.h index 3650f190a73c8..a7288f0004a59 100644 --- a/source/extensions/common/wasm/v8/v8.h +++ b/source/extensions/common/wasm/v8/v8.h @@ -10,7 +10,7 @@ namespace Common { namespace Wasm { namespace V8 { -WasmVmPtr createVm(); +WasmVmPtr createVm(const Stats::ScopeSharedPtr& scope); } // namespace V8 } // namespace Wasm diff --git a/source/extensions/common/wasm/wasm_vm.cc b/source/extensions/common/wasm/wasm_vm.cc index 34f2745331440..9299eceba2d10 100644 --- a/source/extensions/common/wasm/wasm_vm.cc +++ b/source/extensions/common/wasm/wasm_vm.cc @@ -14,13 +14,13 @@ namespace Wasm { thread_local Envoy::Extensions::Common::Wasm::Context* current_context_ = nullptr; thread_local uint32_t effective_context_id_ = 0; -WasmVmPtr createWasmVm(absl::string_view runtime) { +WasmVmPtr createWasmVm(absl::string_view runtime, const Stats::ScopeSharedPtr& scope) { if (runtime.empty()) { throw WasmVmException("Failed to create WASM VM with unspecified runtime."); } else if (runtime == WasmRuntimeNames::get().Null) { - return Null::createVm(); + return Null::createVm(scope); } else if (runtime == WasmRuntimeNames::get().V8) { - return V8::createVm(); + return V8::createVm(scope); } else { throw WasmVmException(fmt::format( "Failed to create WASM VM using {} runtime. Envoy was compiled without support for it.", diff --git a/source/extensions/common/wasm/wasm_vm.h b/source/extensions/common/wasm/wasm_vm.h index 7cf58ed561550..95b43aee47512 100644 --- a/source/extensions/common/wasm/wasm_vm.h +++ b/source/extensions/common/wasm/wasm_vm.h @@ -3,6 +3,7 @@ #include #include "envoy/common/exception.h" +#include "envoy/stats/scope.h" #include "common/common/logger.h" @@ -98,8 +99,7 @@ template using WasmCallWord = std::function) _f(WasmCallVoid<1>) _f(WasmCallVoid<2>) _f(WasmCallVoid<3>) \ - _f(WasmCallVoid<4>) _f(WasmCallVoid<5>) _f(WasmCallVoid<8>) _f(WasmCallWord<0>) \ - _f(WasmCallWord<1>) _f(WasmCallWord<2>) _f(WasmCallWord<3>) + _f(WasmCallVoid<5>) _f(WasmCallWord<1>) _f(WasmCallWord<2>) _f(WasmCallWord<3>) // Calls out of the WASM VM. // 1st arg is always a pointer to raw_context (void*). @@ -111,6 +111,7 @@ template using WasmCallbackWord = WasmFuncType* // Extended with W = Word // Z = void, j = uint32_t, l = int64_t, m = uint64_t using WasmCallback_WWl = Word (*)(void*, Word, int64_t); +using WasmCallback_WWlWW = Word (*)(void*, Word, int64_t, Word, Word); using WasmCallback_WWm = Word (*)(void*, Word, uint64_t); using WasmCallback_dd = double (*)(void*, double); @@ -119,8 +120,15 @@ using WasmCallback_dd = double (*)(void*, double); _f(WasmCallbackVoid<4>) _f(WasmCallbackWord<0>) _f(WasmCallbackWord<1>) \ _f(WasmCallbackWord<2>) _f(WasmCallbackWord<3>) _f(WasmCallbackWord<4>) \ _f(WasmCallbackWord<5>) _f(WasmCallbackWord<6>) _f(WasmCallbackWord<7>) \ - _f(WasmCallbackWord<8>) _f(WasmCallbackWord<9>) _f(WasmCallback_WWl) \ - _f(WasmCallback_WWm) _f(WasmCallback_dd) + _f(WasmCallbackWord<8>) _f(WasmCallbackWord<9>) _f(WasmCallbackWord<10>) \ + _f(WasmCallback_WWl) _f(WasmCallback_WWlWW) _f(WasmCallback_WWm) \ + _f(WasmCallback_dd) + +enum class Cloneable { + NotCloneable, // VMs can not be cloned and should be created from scratch. + CompiledBytecode, // VMs can be cloned with compiled bytecode. + InstantiatedModule // VMs can be cloned from an instantiated module. +}; // Wasm VM instance. Provides the low level WASM interface. class WasmVm : public Logger::Loggable { @@ -141,9 +149,9 @@ class WasmVm : public Logger::Loggable { * compilation. Then, if cloning is supported, we clone that VM for each worker, potentially * copying and sharing the initialized data structures for efficiency. Otherwise we create an new * VM from scratch for each worker. - * @return true if the VM is cloneable. + * @return one of enum Cloneable with the VMs cloneability. */ - virtual bool cloneable() PURE; + virtual Cloneable cloneable() PURE; /** * Make a worker/thread-specific copy if supported by the underlying VM system (see cloneable() @@ -287,7 +295,7 @@ struct SaveRestoreContext { }; // Create a new low-level WASM VM using runtime of the given type (e.g. "envoy.wasm.runtime.wavm"). -WasmVmPtr createWasmVm(absl::string_view runtime); +WasmVmPtr createWasmVm(absl::string_view runtime, const Stats::ScopeSharedPtr& scope); } // namespace Wasm } // namespace Common diff --git a/source/extensions/common/wasm/wasm_vm_base.h b/source/extensions/common/wasm/wasm_vm_base.h new file mode 100644 index 0000000000000..a709534cba521 --- /dev/null +++ b/source/extensions/common/wasm/wasm_vm_base.h @@ -0,0 +1,55 @@ +#pragma once + +#include "envoy/stats/scope.h" +#include "envoy/stats/stats.h" +#include "envoy/stats/stats_macros.h" + +#include "extensions/common/wasm/wasm_vm.h" + +#include "absl/strings/str_cat.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +/** + * Wasm host stats. + */ +#define ALL_VM_STATS(COUNTER, GAUGE) \ + COUNTER(created) \ + COUNTER(cloned) \ + GAUGE(active, NeverImport) + +struct VmStats { + ALL_VM_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) +}; + +// Wasm VM base instance. Provides common behavior (e.g. Stats). +class WasmVmBase : public WasmVm { +public: + WasmVmBase(const Stats::ScopeSharedPtr& scope, absl::string_view runtime) + : scope_(scope), runtime_prefix_(absl::StrCat("wasm_vm.", runtime, ".")), + runtime_(std::string(runtime)), + stats_(VmStats{ALL_VM_STATS(POOL_COUNTER_PREFIX(*scope_, runtime_prefix_), + POOL_GAUGE_PREFIX(*scope_, runtime_prefix_))}) { + stats_.created_.inc(); + stats_.active_.inc(); + ENVOY_LOG(debug, "WasmVm created {} now active", runtime_, stats_.active_.value()); + } + virtual ~WasmVmBase() { + stats_.active_.dec(); + ENVOY_LOG(debug, "~WasmVm {} {} remaining active", runtime_, stats_.active_.value()); + } + +protected: + const Stats::ScopeSharedPtr scope_; + const std::string runtime_prefix_; + const std::string runtime_; // The runtime e.g. "v8". + VmStats stats_; +}; + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/common/wasm/wasm_vm_test.cc b/test/extensions/common/wasm/wasm_vm_test.cc index 1584406c89097..87085a97475ef 100644 --- a/test/extensions/common/wasm/wasm_vm_test.cc +++ b/test/extensions/common/wasm/wasm_vm_test.cc @@ -1,5 +1,7 @@ #include "envoy/registry/registry.h" +#include "common/stats/isolated_store_impl.h" + #include "extensions/common/wasm/null/null_vm_plugin.h" #include "extensions/common/wasm/wasm_vm.h" @@ -35,7 +37,7 @@ class PluginFactory : public Null::NullVmPluginFactory { }; TestNullVmPlugin* test_null_vm_plugin_ = nullptr; -REGISTER_FACTORY(PluginFactory, Null::NullVmPluginFactory); +Envoy::Registry::RegisterFactory register_; std::unique_ptr PluginFactory::create() const { auto result = std::make_unique(); @@ -43,29 +45,38 @@ std::unique_ptr PluginFactory::create() const { return result; } -TEST(BadVmTest, NoRuntime) { - EXPECT_THROW_WITH_MESSAGE(createWasmVm(""), WasmVmException, +class BaseVmTest : public testing::Test { +public: + BaseVmTest() : scope_(Stats::ScopeSharedPtr(stats_store.createScope("wasm."))) {} + +protected: + Stats::IsolatedStoreImpl stats_store; + Stats::ScopeSharedPtr scope_; +}; + +TEST_F(BaseVmTest, NoRuntime) { + EXPECT_THROW_WITH_MESSAGE(createWasmVm("", scope_), WasmVmException, "Failed to create WASM VM with unspecified runtime."); } -TEST(BadVmTest, BadRuntime) { - EXPECT_THROW_WITH_MESSAGE(createWasmVm("envoy.wasm.runtime.invalid"), WasmVmException, +TEST_F(BaseVmTest, BadRuntime) { + EXPECT_THROW_WITH_MESSAGE(createWasmVm("envoy.wasm.runtime.invalid", scope_), WasmVmException, "Failed to create WASM VM using envoy.wasm.runtime.invalid runtime. " "Envoy was compiled without support for it."); } -TEST(NullVmTest, NullVmStartup) { - auto wasm_vm = createWasmVm("envoy.wasm.runtime.null"); +TEST_F(BaseVmTest, NullVmStartup) { + auto wasm_vm = createWasmVm("envoy.wasm.runtime.null", scope_); EXPECT_TRUE(wasm_vm != nullptr); EXPECT_TRUE(wasm_vm->runtime() == "envoy.wasm.runtime.null"); - EXPECT_TRUE(wasm_vm->cloneable()); + EXPECT_TRUE(wasm_vm->cloneable() == Cloneable::InstantiatedModule); auto wasm_vm_clone = wasm_vm->clone(); EXPECT_TRUE(wasm_vm_clone != nullptr); EXPECT_TRUE(wasm_vm->getCustomSection("user").empty()); } -TEST(NullVmTest, NullVmMemory) { - auto wasm_vm = createWasmVm("envoy.wasm.runtime.null"); +TEST_F(BaseVmTest, NullVmMemory) { + auto wasm_vm = createWasmVm("envoy.wasm.runtime.null", scope_); EXPECT_EQ(wasm_vm->getMemorySize(), std::numeric_limits::max()); std::string d = "data"; auto m = wasm_vm->getMemory(reinterpret_cast(d.data()), d.size()).value(); @@ -114,26 +125,23 @@ Word bad_pong2(void*, Word) { return 2; } // pong() with wrong argument type. double bad_pong3(void*, double) { return 3; } -class WasmVmTest : public testing::Test { +class WasmVmTest : public BaseVmTest { public: void SetUp() override { g_host_functions = new MockHostFunctions(); } void TearDown() override { delete g_host_functions; } }; TEST_F(WasmVmTest, V8BadCode) { - auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8"); + auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8", scope_); ASSERT_TRUE(wasm_vm != nullptr); EXPECT_FALSE(wasm_vm->load("bad code", false)); } TEST_F(WasmVmTest, V8Code) { - auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8"); + auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8", scope_); ASSERT_TRUE(wasm_vm != nullptr); - EXPECT_TRUE(wasm_vm->runtime() == "envoy.wasm.runtime.v8"); - EXPECT_FALSE(wasm_vm->cloneable()); - EXPECT_TRUE(wasm_vm->clone() == nullptr); auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_rust.wasm")); @@ -141,10 +149,13 @@ TEST_F(WasmVmTest, V8Code) { EXPECT_THAT(wasm_vm->getCustomSection("producers"), HasSubstr("rustc")); EXPECT_TRUE(wasm_vm->getCustomSection("emscripten_metadata").empty()); + + EXPECT_TRUE(wasm_vm->cloneable() == Cloneable::CompiledBytecode); + EXPECT_TRUE(wasm_vm->clone() != nullptr); } TEST_F(WasmVmTest, V8BadHostFunctions) { - auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8"); + auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8", scope_); ASSERT_TRUE(wasm_vm != nullptr); auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( @@ -172,7 +183,7 @@ TEST_F(WasmVmTest, V8BadHostFunctions) { } TEST_F(WasmVmTest, V8BadModuleFunctions) { - auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8"); + auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8", scope_); ASSERT_TRUE(wasm_vm != nullptr); auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( @@ -200,7 +211,7 @@ TEST_F(WasmVmTest, V8BadModuleFunctions) { } TEST_F(WasmVmTest, V8FunctionCalls) { - auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8"); + auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8", scope_); ASSERT_TRUE(wasm_vm != nullptr); auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( @@ -238,7 +249,7 @@ TEST_F(WasmVmTest, V8FunctionCalls) { } TEST_F(WasmVmTest, V8Memory) { - auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8"); + auto wasm_vm = createWasmVm("envoy.wasm.runtime.v8", scope_); ASSERT_TRUE(wasm_vm != nullptr); auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( diff --git a/tools/spelling_dictionary.txt b/tools/spelling_dictionary.txt index 522f9769c677b..30ee87a18959b 100644 --- a/tools/spelling_dictionary.txt +++ b/tools/spelling_dictionary.txt @@ -393,6 +393,7 @@ builtin builtins bulkstrings bursty +bytecode callee callsite callsites @@ -418,6 +419,7 @@ ciphersuites circllhist CITT cloneable +cloneability cmd cmsghdr codebase