diff --git a/BUILD b/BUILD index 69e04d811..5095d8cc4 100644 --- a/BUILD +++ b/BUILD @@ -72,6 +72,7 @@ cc_library( "//bazel:crypto_system": [], "//conditions:default": ["@boringssl//:crypto"], }), + alwayslink = 1, ) cc_library( diff --git a/src/exports.cc b/src/exports.cc index aac5e0f2a..c203946b8 100644 --- a/src/exports.cc +++ b/src/exports.cc @@ -21,6 +21,9 @@ namespace proxy_wasm { +thread_local ContextBase *current_context_; +thread_local uint32_t effective_context_id_ = 0; + // Any currently executing Wasm call context. ContextBase *contextOrEffectiveContext() { if (effective_context_id_ == 0) { diff --git a/src/wasm.cc b/src/wasm.cc index c7bcfcdd8..3c0473e87 100644 --- a/src/wasm.cc +++ b/src/wasm.cc @@ -35,9 +35,6 @@ namespace proxy_wasm { -thread_local ContextBase *current_context_; -thread_local uint32_t effective_context_id_ = 0; - namespace { // Map from Wasm Key to the local Wasm instance. diff --git a/test/BUILD b/test/BUILD index ced9dbd37..3bdd69f24 100644 --- a/test/BUILD +++ b/test/BUILD @@ -54,7 +54,6 @@ cc_test( name = "runtime_test", srcs = ["runtime_test.cc"], data = [ - "//test/test_data:abi_export.wasm", "//test/test_data:callback.wasm", "//test/test_data:clock.wasm", "//test/test_data:infinite_loop.wasm", @@ -133,6 +132,21 @@ cc_test( ], ) +cc_test( + name = "wasm_vm_test", + srcs = ["wasm_vm_test.cc"], + data = [ + "//test/test_data:abi_export.wasm", + ], + linkstatic = 1, + deps = [ + ":utility_lib", + "//:lib", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "utility_lib", testonly = True, diff --git a/test/runtime_test.cc b/test/runtime_test.cc index 3b7effcab..ccce3df69 100644 --- a/test/runtime_test.cc +++ b/test/runtime_test.cc @@ -14,13 +14,9 @@ #include "gtest/gtest.h" -#include -#include #include -#include #include #include -#include #include "include/proxy-wasm/context.h" #include "include/proxy-wasm/wasm.h" @@ -35,137 +31,26 @@ INSTANTIATE_TEST_SUITE_P(WasmEngines, TestVm, testing::ValuesIn(getWasmEngines() return info.param; }); -TEST_P(TestVm, Basic) { - if (engine_ == "wamr") { - EXPECT_EQ(vm_->cloneable(), proxy_wasm::Cloneable::NotCloneable); - } else if (engine_ == "wasmtime" || engine_ == "v8") { - EXPECT_EQ(vm_->cloneable(), proxy_wasm::Cloneable::CompiledBytecode); - } else if (engine_ == "wavm") { - EXPECT_EQ(vm_->cloneable(), proxy_wasm::Cloneable::InstantiatedModule); - } else { - FAIL(); - } - EXPECT_EQ(vm_->getEngineName(), engine_); -} - -TEST_P(TestVm, Memory) { - auto source = readTestWasmFile("abi_export.wasm"); - ASSERT_TRUE(vm_->load(source, {}, {})); - ASSERT_TRUE(vm_->link("")); - - Word word; - ASSERT_TRUE(vm_->setWord(0x2000, Word(100))); - ASSERT_TRUE(vm_->getWord(0x2000, &word)); - ASSERT_EQ(100, word.u64_); - - uint32_t data[2] = {htowasm(static_cast(-1)), htowasm(200)}; - ASSERT_TRUE(vm_->setMemory(0x200, sizeof(int32_t) * 2, static_cast(data))); - ASSERT_TRUE(vm_->getWord(0x200, &word)); - ASSERT_EQ(-1, static_cast(word.u64_)); - ASSERT_TRUE(vm_->getWord(0x204, &word)); - ASSERT_EQ(200, static_cast(word.u64_)); -} - -TEST_P(TestVm, Clone) { - if (vm_->cloneable() == proxy_wasm::Cloneable::NotCloneable) { - return; - } - auto source = readTestWasmFile("abi_export.wasm"); - ASSERT_TRUE(vm_->load(source, {}, {})); - ASSERT_TRUE(vm_->link("")); - const auto address = 0x2000; - Word word; - { - auto clone = vm_->clone(); - ASSERT_TRUE(clone != nullptr); - ASSERT_NE(vm_, clone); - if (clone->cloneable() != proxy_wasm::Cloneable::InstantiatedModule) { - ASSERT_TRUE(clone->link("")); - } - - ASSERT_TRUE(clone->setWord(address, Word(100))); - ASSERT_TRUE(clone->getWord(address, &word)); - ASSERT_EQ(100, word.u64_); - } - - // check memory arrays are not overrapped - ASSERT_TRUE(vm_->getWord(address, &word)); - ASSERT_NE(100, word.u64_); -} - -#if defined(__linux__) && defined(__x86_64__) - -TEST_P(TestVm, CloneUntilOutOfMemory) { - if (vm_->cloneable() == proxy_wasm::Cloneable::NotCloneable) { - return; - } - if (engine_ == "wavm") { - // TODO(PiotrSikora): Figure out why this fails on the CI. - return; - } - - auto source = readTestWasmFile("abi_export.wasm"); - ASSERT_TRUE(vm_->load(source, {}, {})); - ASSERT_TRUE(vm_->link("")); - - size_t max_clones = 100000; -#if defined(__has_feature) -#if __has_feature(address_sanitizer) - max_clones = 1000; -#endif -#endif - - std::vector> clones; - for (size_t i = 0; i < max_clones; i++) { - auto clone = vm_->clone(); - if (clone == nullptr) { - break; - } - if (clone->cloneable() != proxy_wasm::Cloneable::InstantiatedModule) { - if (!clone->link("")) { - break; - } - } - // Prevent clone from droping out of scope and freeing memory. - clones.push_back(std::move(clone)); - } - - size_t min_clones = 1000; -#if defined(__has_feature) -#if __has_feature(thread_sanitizer) - min_clones = 100; -#endif -#endif - EXPECT_GE(clones.size(), min_clones); -} - -#endif - -class TestCounterContext : public TestContext { -public: - TestCounterContext(WasmBase *wasm) : TestContext(wasm) {} - - void increment() { counter++; } - size_t getCount() { return counter; } - -private: - size_t counter = 0; -}; +TEST_P(TestVm, BadSignature) { + auto source = readTestWasmFile("clock.wasm"); + ASSERT_FALSE(source.empty()); + auto wasm = TestWasm(std::move(vm_)); + ASSERT_TRUE(wasm.load(source, false)); + ASSERT_TRUE(wasm.initialize()); -class TestCounterWasm : public TestWasm { -public: - TestCounterWasm(std::unique_ptr wasm_vm) : TestWasm(std::move(wasm_vm)) {} + WasmCallVoid<0> non_existent; + wasm.wasm_vm()->getFunction("non_existent", &non_existent); + EXPECT_TRUE(non_existent == nullptr); - ContextBase *createVmContext() override { return new TestCounterContext(this); }; -}; + WasmCallWord<2> bad_signature_run; + wasm.wasm_vm()->getFunction("run", &bad_signature_run); + EXPECT_TRUE(bad_signature_run == nullptr); -void callback() { - auto *context = dynamic_cast(contextOrEffectiveContext()); - context->increment(); + WasmCallVoid<0> run; + wasm.wasm_vm()->getFunction("run", &run); + ASSERT_TRUE(run != nullptr); } -Word callback2(Word val) { return val + 100; } - TEST_P(TestVm, StraceLogLevel) { if (engine_ == "wavm") { // TODO(mathetake): strace is yet to be implemented for WAVM. @@ -196,58 +81,6 @@ TEST_P(TestVm, StraceLogLevel) { EXPECT_TRUE(host->isTraceLogged("[host<-vm] run return: void")); } -TEST_P(TestVm, BadExportFunction) { - auto source = readTestWasmFile("clock.wasm"); - ASSERT_FALSE(source.empty()); - auto wasm = TestWasm(std::move(vm_)); - ASSERT_TRUE(wasm.load(source, false)); - ASSERT_TRUE(wasm.initialize()); - - WasmCallVoid<0> non_existent; - wasm.wasm_vm()->getFunction("non_existent", &non_existent); - EXPECT_TRUE(non_existent == nullptr); - - WasmCallWord<2> bad_signature_run; - wasm.wasm_vm()->getFunction("run", &bad_signature_run); - EXPECT_TRUE(bad_signature_run == nullptr); - - WasmCallVoid<0> run; - wasm.wasm_vm()->getFunction("run", &run); - ASSERT_TRUE(run != nullptr); -} - -TEST_P(TestVm, Callback) { - auto source = readTestWasmFile("callback.wasm"); - ASSERT_FALSE(source.empty()); - auto wasm = TestCounterWasm(std::move(vm_)); - ASSERT_TRUE(wasm.load(source, false)); - - wasm.wasm_vm()->registerCallback( - "env", "callback", &callback, - &ConvertFunctionWordToUint32::convertFunctionWordToUint32); - - wasm.wasm_vm()->registerCallback( - "env", "callback2", &callback2, - &ConvertFunctionWordToUint32::convertFunctionWordToUint32); - - ASSERT_TRUE(wasm.initialize()); - - WasmCallVoid<0> run; - wasm.wasm_vm()->getFunction("run", &run); - ASSERT_TRUE(run != nullptr); - for (auto i = 0; i < 5; i++) { - run(wasm.vm_context()); - } - auto *context = dynamic_cast(wasm.vm_context()); - EXPECT_EQ(context->getCount(), 5); - - WasmCallWord<1> run2; - wasm.wasm_vm()->getFunction("run2", &run2); - ASSERT_TRUE(run2 != nullptr); - Word res = run2(wasm.vm_context(), Word{0}); - EXPECT_EQ(res.u32(), 100100); // 10000 (global) + 100 (in callback) -} - TEST_P(TestVm, TerminateExecution) { // TODO(chaoqin-li1123): implement execution termination for other runtime. if (engine_ != "v8") { @@ -329,5 +162,62 @@ TEST_P(TestVm, Trap2) { } } +class TestCounterContext : public TestContext { +public: + TestCounterContext(WasmBase *wasm) : TestContext(wasm) {} + + void increment() { counter++; } + size_t getCount() { return counter; } + +private: + size_t counter = 0; +}; + +class TestCounterWasm : public TestWasm { +public: + TestCounterWasm(std::unique_ptr wasm_vm) : TestWasm(std::move(wasm_vm)) {} + + ContextBase *createVmContext() override { return new TestCounterContext(this); }; +}; + +void callback() { + auto *context = dynamic_cast(contextOrEffectiveContext()); + context->increment(); +} + +Word callback2(Word val) { return val + 100; } + +TEST_P(TestVm, Callback) { + auto source = readTestWasmFile("callback.wasm"); + ASSERT_FALSE(source.empty()); + auto wasm = TestCounterWasm(std::move(vm_)); + ASSERT_TRUE(wasm.load(source, false)); + + wasm.wasm_vm()->registerCallback( + "env", "callback", &callback, + &ConvertFunctionWordToUint32::convertFunctionWordToUint32); + + wasm.wasm_vm()->registerCallback( + "env", "callback2", &callback2, + &ConvertFunctionWordToUint32::convertFunctionWordToUint32); + + ASSERT_TRUE(wasm.initialize()); + + WasmCallVoid<0> run; + wasm.wasm_vm()->getFunction("run", &run); + ASSERT_TRUE(run != nullptr); + for (auto i = 0; i < 5; i++) { + run(wasm.vm_context()); + } + auto *context = dynamic_cast(wasm.vm_context()); + EXPECT_EQ(context->getCount(), 5); + + WasmCallWord<1> run2; + wasm.wasm_vm()->getFunction("run2", &run2); + ASSERT_TRUE(run2 != nullptr); + Word res = run2(wasm.vm_context(), Word{0}); + EXPECT_EQ(res.u32(), 100100); // 10000 (global) + 100 (in callback) +} + } // namespace } // namespace proxy_wasm diff --git a/test/wasm_vm_test.cc b/test/wasm_vm_test.cc new file mode 100644 index 000000000..d29437c9b --- /dev/null +++ b/test/wasm_vm_test.cc @@ -0,0 +1,140 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gtest/gtest.h" + +#include +#include +#include + +#include "include/proxy-wasm/wasm_vm.h" + +#include "test/utility.h" + +namespace proxy_wasm { +namespace { + +INSTANTIATE_TEST_SUITE_P(WasmEngines, TestVm, testing::ValuesIn(getWasmEngines()), + [](const testing::TestParamInfo &info) { + return info.param; + }); + +TEST_P(TestVm, Basic) { + if (engine_ == "wamr") { + EXPECT_EQ(vm_->cloneable(), proxy_wasm::Cloneable::NotCloneable); + } else if (engine_ == "wasmtime" || engine_ == "v8") { + EXPECT_EQ(vm_->cloneable(), proxy_wasm::Cloneable::CompiledBytecode); + } else if (engine_ == "wavm") { + EXPECT_EQ(vm_->cloneable(), proxy_wasm::Cloneable::InstantiatedModule); + } else { + FAIL(); + } + EXPECT_EQ(vm_->getEngineName(), engine_); +} + +TEST_P(TestVm, Memory) { + auto source = readTestWasmFile("abi_export.wasm"); + ASSERT_TRUE(vm_->load(source, {}, {})); + ASSERT_TRUE(vm_->link("")); + + Word word; + ASSERT_TRUE(vm_->setWord(0x2000, Word(100))); + ASSERT_TRUE(vm_->getWord(0x2000, &word)); + ASSERT_EQ(100, word.u64_); + + uint32_t data[2] = {htowasm(static_cast(-1)), htowasm(200)}; + ASSERT_TRUE(vm_->setMemory(0x200, sizeof(int32_t) * 2, static_cast(data))); + ASSERT_TRUE(vm_->getWord(0x200, &word)); + ASSERT_EQ(-1, static_cast(word.u64_)); + ASSERT_TRUE(vm_->getWord(0x204, &word)); + ASSERT_EQ(200, static_cast(word.u64_)); +} + +TEST_P(TestVm, Clone) { + if (vm_->cloneable() == proxy_wasm::Cloneable::NotCloneable) { + return; + } + auto source = readTestWasmFile("abi_export.wasm"); + ASSERT_TRUE(vm_->load(source, {}, {})); + ASSERT_TRUE(vm_->link("")); + const auto address = 0x2000; + Word word; + { + auto clone = vm_->clone(); + ASSERT_TRUE(clone != nullptr); + ASSERT_NE(vm_, clone); + if (clone->cloneable() != proxy_wasm::Cloneable::InstantiatedModule) { + ASSERT_TRUE(clone->link("")); + } + + ASSERT_TRUE(clone->setWord(address, Word(100))); + ASSERT_TRUE(clone->getWord(address, &word)); + ASSERT_EQ(100, word.u64_); + } + + // check memory arrays are not overrapped + ASSERT_TRUE(vm_->getWord(address, &word)); + ASSERT_NE(100, word.u64_); +} + +#if defined(__linux__) && defined(__x86_64__) + +TEST_P(TestVm, CloneUntilOutOfMemory) { + if (vm_->cloneable() == proxy_wasm::Cloneable::NotCloneable) { + return; + } + if (engine_ == "wavm") { + // TODO(PiotrSikora): Figure out why this fails on the CI. + return; + } + + auto source = readTestWasmFile("abi_export.wasm"); + ASSERT_TRUE(vm_->load(source, {}, {})); + ASSERT_TRUE(vm_->link("")); + + size_t max_clones = 100000; +#if defined(__has_feature) +#if __has_feature(address_sanitizer) + max_clones = 1000; +#endif +#endif + + std::vector> clones; + for (size_t i = 0; i < max_clones; i++) { + auto clone = vm_->clone(); + if (clone == nullptr) { + break; + } + if (clone->cloneable() != proxy_wasm::Cloneable::InstantiatedModule) { + if (!clone->link("")) { + break; + } + } + // Prevent clone from droping out of scope and freeing memory. + clones.push_back(std::move(clone)); + } + + size_t min_clones = 1000; +#if defined(__has_feature) +#if __has_feature(thread_sanitizer) + min_clones = 100; +#endif +#endif + EXPECT_GE(clones.size(), min_clones); +} + +#endif + +} // namespace +} // namespace proxy_wasm