Skip to content

Commit

Permalink
wasm: make V8-based runtime cloneable. (envoyproxy#314)
Browse files Browse the repository at this point in the history
Fixes envoyproxy#161.

Signed-off-by: Piotr Sikora <piotrsikora@google.com>
  • Loading branch information
PiotrSikora authored and jplevyak committed Nov 19, 2019
1 parent eb32240 commit eccd25f
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 64 deletions.
2 changes: 1 addition & 1 deletion source/extensions/common/wasm/null/null_vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct NullVm : public WasmVmBase {

// 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;
Expand Down
25 changes: 22 additions & 3 deletions source/extensions/common/wasm/v8/v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ class V8 : public WasmVmBase {
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<absl::string_view> getMemory(uint64_t pointer, uint64_t size) override;
Expand Down Expand Up @@ -92,6 +91,7 @@ class V8 : public WasmVmBase {
wasm::vec<byte_t> source_ = wasm::vec<byte_t>::invalid();
wasm::own<wasm::Store> store_;
wasm::own<wasm::Module> module_;
wasm::own<wasm::Shared<wasm::Module>> shared_module_;
wasm::own<wasm::Instance> instance_;
wasm::own<wasm::Memory> memory_;
wasm::own<wasm::Table> table_;
Expand Down Expand Up @@ -250,9 +250,28 @@ bool V8::load(const std::string& code, bool /* allow_precompiled */) {
::memcpy(source_.get(), code.data(), code.size());

module_ = wasm::Module::make(store_.get(), source_);
if (module_) {
shared_module_ = module_->share();
RELEASE_ASSERT(shared_module_ != nullptr, "");
}

return module_ != nullptr;
}

WasmVmPtr V8::clone() {
ENVOY_LOG(trace, "clone()");
ASSERT(shared_module_ != nullptr);

auto clone = std::make_unique<V8>(scope_);
clone->store_ = wasm::Store::make(engine());
RELEASE_ASSERT(clone->store_ != nullptr, "");

clone->module_ = wasm::Module::obtain(clone->store_.get(), shared_module_.get());
RELEASE_ASSERT(clone->module_ != nullptr, "");

return clone;
}

absl::string_view V8::getCustomSection(absl::string_view name) {
ENVOY_LOG(trace, "getCustomSection(\"{}\")", name);
ASSERT(source_.get() != nullptr);
Expand Down
114 changes: 60 additions & 54 deletions source/extensions/common/wasm/wasm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,24 +112,20 @@ void Wasm::registerCallbacks() {
"env", #_fn, &Exports::_fn, \
&ConvertFunctionWordToUint32<decltype(Exports::_fn), \
Exports::_fn>::convertFunctionWordToUint32)
if (is_emscripten_) {
_REGISTER(pthread_equal);
}
_REGISTER(pthread_equal);
#undef _REGISTER

#define _REGISTER_WASI(_fn) \
wasm_vm_->registerCallback( \
"wasi_unstable", #_fn, &Exports::wasi_unstable_##_fn, \
&ConvertFunctionWordToUint32<decltype(Exports::wasi_unstable_##_fn), \
Exports::wasi_unstable_##_fn>::convertFunctionWordToUint32)
if (is_emscripten_) {
_REGISTER_WASI(fd_write);
_REGISTER_WASI(fd_seek);
_REGISTER_WASI(fd_close);
_REGISTER_WASI(environ_get);
_REGISTER_WASI(environ_sizes_get);
_REGISTER_WASI(proc_exit);
}
_REGISTER_WASI(fd_write);
_REGISTER_WASI(fd_seek);
_REGISTER_WASI(fd_close);
_REGISTER_WASI(environ_get);
_REGISTER_WASI(environ_sizes_get);
_REGISTER_WASI(proc_exit);
#undef _REGISTER_WASI

// Calls with the "proxy_" prefix.
Expand Down Expand Up @@ -241,19 +237,17 @@ void Wasm::getFunctions() {

Wasm::Wasm(const Wasm& wasm, Event::Dispatcher& dispatcher)
: std::enable_shared_from_this<Wasm>(wasm), vm_id_(wasm.vm_id_),
vm_id_with_hash_(wasm.vm_id_with_hash_), scope_(wasm.scope_),
cluster_manager_(wasm.cluster_manager_), dispatcher_(dispatcher),
vm_id_with_hash_(wasm.vm_id_with_hash_), started_from_(wasm.wasm_vm()->cloneable()),
scope_(wasm.scope_), cluster_manager_(wasm.cluster_manager_), dispatcher_(dispatcher),
time_source_(dispatcher.timeSource()), wasm_stats_(wasm.wasm_stats_),
stat_name_set_(wasm.stat_name_set_) {
if (wasm.wasm_vm()->cloneable()) {
if (started_from_ != Cloneable::NotCloneable) {
wasm_vm_ = wasm.wasm_vm()->clone();
vm_context_ = std::make_shared<Context>(this);
getFunctions();
} else {
wasm_vm_ = Common::Wasm::createWasmVm(wasm.wasm_vm()->runtime(), scope_);
if (!initialize(wasm.code(), wasm.allow_precompiled())) {
throw WasmException("Failed to initialize WASM code");
}
}
if (!initialize(wasm.code(), wasm.allow_precompiled())) {
throw WasmException("Failed to load WASM code");
}
active_wasm_++;
wasm_stats_.active_.set(active_wasm_);
Expand All @@ -277,50 +271,62 @@ bool Wasm::initialize(const std::string& code, bool allow_precompiled) {
return false;
}

// Construct a unique identifier for the VM based on the provided vm_id and a hash of the
// code.
vm_id_with_hash_ = vm_id_ + ":" + base64Sha256(code);
if (started_from_ == Cloneable::NotCloneable) {
// Construct a unique identifier for the VM based on the provided vm_id and a hash of the
// code.
vm_id_with_hash_ = vm_id_ + ":" + base64Sha256(code);

auto ok = wasm_vm_->load(code, allow_precompiled);
if (!ok) {
return false;
}
auto metadata = wasm_vm_->getCustomSection("emscripten_metadata");
if (!metadata.empty()) {
// See https://github.com/emscripten-core/emscripten/blob/incoming/tools/shared.py#L3059
is_emscripten_ = true;
auto start = reinterpret_cast<const uint8_t*>(metadata.data());
auto end = reinterpret_cast<const uint8_t*>(metadata.data() + metadata.size());
start = decodeVarint(start, end, &emscripten_metadata_major_version_);
start = decodeVarint(start, end, &emscripten_metadata_minor_version_);
start = decodeVarint(start, end, &emscripten_abi_major_version_);
start = decodeVarint(start, end, &emscripten_abi_minor_version_);
uint32_t temp;
if (emscripten_metadata_major_version_ > 0 || emscripten_metadata_minor_version_ > 1) {
// metadata 0.2 - added: wasm_backend.
start = decodeVarint(start, end, &temp);
auto ok = wasm_vm_->load(code, allow_precompiled);
if (!ok) {
return false;
}
start = decodeVarint(start, end, &temp);
start = decodeVarint(start, end, &temp);
if (emscripten_metadata_major_version_ > 0 || emscripten_metadata_minor_version_ > 0) {
// metadata 0.1 - added: global_base, dynamic_base, dynamictop_ptr and tempdouble_ptr.
start = decodeVarint(start, end, &temp);
auto metadata = wasm_vm_->getCustomSection("emscripten_metadata");
if (!metadata.empty()) {
// See https://github.com/emscripten-core/emscripten/blob/incoming/tools/shared.py#L3059
is_emscripten_ = true;
auto start = reinterpret_cast<const uint8_t*>(metadata.data());
auto end = reinterpret_cast<const uint8_t*>(metadata.data() + metadata.size());
start = decodeVarint(start, end, &emscripten_metadata_major_version_);
start = decodeVarint(start, end, &emscripten_metadata_minor_version_);
start = decodeVarint(start, end, &emscripten_abi_major_version_);
start = decodeVarint(start, end, &emscripten_abi_minor_version_);
uint32_t temp;
if (emscripten_metadata_major_version_ > 0 || emscripten_metadata_minor_version_ > 1) {
// metadata 0.2 - added: wasm_backend.
start = decodeVarint(start, end, &temp);
}
start = decodeVarint(start, end, &temp);
start = decodeVarint(start, end, &temp);
decodeVarint(start, end, &temp);
if (emscripten_metadata_major_version_ > 0 || emscripten_metadata_minor_version_ > 2) {
// metadata 0.3 - added: standalone_wasm.
start = decodeVarint(start, end, &emscripten_standalone_wasm_);
if (emscripten_metadata_major_version_ > 0 || emscripten_metadata_minor_version_ > 0) {
// metadata 0.1 - added: global_base, dynamic_base, dynamictop_ptr and tempdouble_ptr.
start = decodeVarint(start, end, &temp);
start = decodeVarint(start, end, &temp);
start = decodeVarint(start, end, &temp);
decodeVarint(start, end, &temp);
if (emscripten_metadata_major_version_ > 0 || emscripten_metadata_minor_version_ > 2) {
// metadata 0.3 - added: standalone_wasm.
start = decodeVarint(start, end, &emscripten_standalone_wasm_);
}
}
}

code_ = code;
allow_precompiled_ = allow_precompiled;
}

if (started_from_ != Cloneable::InstantiatedModule) {
registerCallbacks();
wasm_vm_->link(vm_id_);
}
registerCallbacks();
wasm_vm_->link(vm_id_);

vm_context_ = std::make_shared<Context>(this);
getFunctions();
startVm(vm_context_.get());
code_ = code;
allow_precompiled_ = allow_precompiled;

if (started_from_ != Cloneable::InstantiatedModule) {
// Base VM was already started, so don't try to start cloned VMs again.
startVm(vm_context_.get());
}

return true;
}

Expand Down
1 change: 1 addition & 0 deletions source/extensions/common/wasm/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ class Wasm : public Logger::Loggable<Logger::Id::wasm>, public std::enable_share
std::string vm_id_; // User-provided vm_id.
std::string vm_id_with_hash_; // vm_id + hash of code.
std::unique_ptr<WasmVm> wasm_vm_;
Cloneable started_from_{Cloneable::NotCloneable};
Stats::ScopeSharedPtr scope_;

Upstream::ClusterManager& cluster_manager_;
Expand Down
4 changes: 3 additions & 1 deletion source/extensions/common/wasm/wasm_vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ using WasmCallback_dd = double (*)(void*, double);
_f(WasmCallback_WWl) _f(WasmCallback_WWlWW) _f(WasmCallback_WWm) \
_f(WasmCallback_dd)

enum class Cloneable { NotCloneable, CompiledBytecode, InstantiatedModule };

// Wasm VM instance. Provides the low level WASM interface.
class WasmVm : public Logger::Loggable<Logger::Id::wasm> {
public:
Expand All @@ -145,7 +147,7 @@ class WasmVm : public Logger::Loggable<Logger::Id::wasm> {
* VM from scratch for each worker.
* @return true if the VM is cloneable.
*/
virtual bool cloneable() PURE;
virtual Cloneable cloneable() PURE;

/**
* Make a worker/thread-specific copy if supported by the underlying VM system (see cloneable()
Expand Down
2 changes: 1 addition & 1 deletion source/extensions/common/wasm/wavm/wavm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ struct Wavm : public WasmVmBase {

// WasmVm
absl::string_view runtime() override { return WasmRuntimeNames::get().Wavm; }
bool cloneable() override { return true; };
Cloneable cloneable() override { return Cloneable::InstantiatedModule; };
std::unique_ptr<WasmVm> clone() override;
bool load(const std::string& code, bool allow_precompiled) override;
void link(absl::string_view debug_name) override;
Expand Down
8 changes: 4 additions & 4 deletions test/extensions/common/wasm/wasm_vm_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ 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());
Expand Down Expand Up @@ -141,17 +141,17 @@ TEST_F(WasmVmTest, V8BadCode) {
TEST_F(WasmVmTest, V8Code) {
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"));
EXPECT_TRUE(wasm_vm->load(code, false));

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) {
Expand Down

0 comments on commit eccd25f

Please sign in to comment.