Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 84 additions & 52 deletions source/extensions/common/wasm/wasm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,22 @@ void replaceHeader(Http::HeaderMap* map, absl::string_view key, absl::string_vie
map->addCopy(lower_key, std::string(value));
}

const uint8_t* decodeVarint(const uint8_t* pos, const uint8_t* end, uint32_t* out) {
uint32_t ret = 0;
int shift = 0;
while (pos < end && (*pos & 0x80)) {
ret |= (*pos & 0x7f) << shift;
shift += 7;
pos++;
}
if (pos < end) {
ret |= *pos << shift;
pos++;
}
*out = ret;
return pos;
}

} // namespace

void Context::setTickPeriod(std::chrono::milliseconds tick_period) {
Expand Down Expand Up @@ -933,66 +949,68 @@ Wasm::Wasm(absl::string_view vm, absl::string_view id, absl::string_view initial
initial_configuration_(initial_configuration) {
wasm_vm_ = Common::Wasm::createWasmVm(vm);
id_ = std::string(id);
if (wasm_vm_) {
}

void Wasm::registerFunctions() {
#define _REGISTER(_fn) registerCallback(wasm_vm_.get(), #_fn, &_fn##Handler);
if (is_emscripten_) {
_REGISTER(getTotalMemory);
_REGISTER(_emscripten_get_heap_size);
_REGISTER(_llvm_trap);
}
#undef _REGISTER

// Calls with the "_proxy_" prefix.
// Calls with the "_proxy_" prefix.
#define _REGISTER_PROXY(_fn) registerCallback(wasm_vm_.get(), "_proxy_" #_fn, &_fn##Handler);
_REGISTER_PROXY(log);

_REGISTER_PROXY(getRequestStreamInfoProtocol);
_REGISTER_PROXY(getResponseStreamInfoProtocol);

_REGISTER_PROXY(getRequestMetadata);
_REGISTER_PROXY(setRequestMetadata);
_REGISTER_PROXY(getRequestMetadataPairs);
_REGISTER_PROXY(getResponseMetadata);
_REGISTER_PROXY(setResponseMetadata);
_REGISTER_PROXY(getResponseMetadataPairs);

_REGISTER_PROXY(continueRequest);
_REGISTER_PROXY(continueResponse);

_REGISTER_PROXY(getSharedData);
_REGISTER_PROXY(setSharedData);

_REGISTER_PROXY(getRequestHeader);
_REGISTER_PROXY(addRequestHeader);
_REGISTER_PROXY(replaceRequestHeader);
_REGISTER_PROXY(removeRequestHeader);
_REGISTER_PROXY(getRequestHeaderPairs);

_REGISTER_PROXY(getRequestTrailer);
_REGISTER_PROXY(addRequestTrailer);
_REGISTER_PROXY(replaceRequestTrailer);
_REGISTER_PROXY(removeRequestTrailer);
_REGISTER_PROXY(getRequestTrailerPairs);

_REGISTER_PROXY(getResponseHeader);
_REGISTER_PROXY(addResponseHeader);
_REGISTER_PROXY(replaceResponseHeader);
_REGISTER_PROXY(removeResponseHeader);
_REGISTER_PROXY(getResponseHeaderPairs);

_REGISTER_PROXY(getResponseTrailer);
_REGISTER_PROXY(addResponseTrailer);
_REGISTER_PROXY(replaceResponseTrailer);
_REGISTER_PROXY(removeResponseTrailer);
_REGISTER_PROXY(getResponseTrailerPairs);

_REGISTER_PROXY(getRequestBodyBufferBytes);
_REGISTER_PROXY(getResponseBodyBufferBytes);

_REGISTER_PROXY(httpCall);

_REGISTER_PROXY(setTickPeriodMilliseconds);
_REGISTER_PROXY(log);

_REGISTER_PROXY(getRequestStreamInfoProtocol);
_REGISTER_PROXY(getResponseStreamInfoProtocol);

_REGISTER_PROXY(getRequestMetadata);
_REGISTER_PROXY(setRequestMetadata);
_REGISTER_PROXY(getRequestMetadataPairs);
_REGISTER_PROXY(getResponseMetadata);
_REGISTER_PROXY(setResponseMetadata);
_REGISTER_PROXY(getResponseMetadataPairs);

_REGISTER_PROXY(continueRequest);
_REGISTER_PROXY(continueResponse);

_REGISTER_PROXY(getSharedData);
_REGISTER_PROXY(setSharedData);

_REGISTER_PROXY(getRequestHeader);
_REGISTER_PROXY(addRequestHeader);
_REGISTER_PROXY(replaceRequestHeader);
_REGISTER_PROXY(removeRequestHeader);
_REGISTER_PROXY(getRequestHeaderPairs);

_REGISTER_PROXY(getRequestTrailer);
_REGISTER_PROXY(addRequestTrailer);
_REGISTER_PROXY(replaceRequestTrailer);
_REGISTER_PROXY(removeRequestTrailer);
_REGISTER_PROXY(getRequestTrailerPairs);

_REGISTER_PROXY(getResponseHeader);
_REGISTER_PROXY(addResponseHeader);
_REGISTER_PROXY(replaceResponseHeader);
_REGISTER_PROXY(removeResponseHeader);
_REGISTER_PROXY(getResponseHeaderPairs);

_REGISTER_PROXY(getResponseTrailer);
_REGISTER_PROXY(addResponseTrailer);
_REGISTER_PROXY(replaceResponseTrailer);
_REGISTER_PROXY(removeResponseTrailer);
_REGISTER_PROXY(getResponseTrailerPairs);

_REGISTER_PROXY(getRequestBodyBufferBytes);
_REGISTER_PROXY(getResponseBodyBufferBytes);

_REGISTER_PROXY(httpCall);

_REGISTER_PROXY(setTickPeriodMilliseconds);
#undef _REGISTER_PROXY
}
}

void Wasm::getFunctions() {
Expand Down Expand Up @@ -1028,9 +1046,23 @@ Wasm::Wasm(const Wasm& wasm)
bool Wasm::initialize(const std::string& code, absl::string_view name, bool allow_precompiled) {
if (!wasm_vm_)
return false;
auto ok = wasm_vm_->initialize(code, name, allow_precompiled);
auto ok = wasm_vm_->load(code, allow_precompiled);
if (!ok)
return false;
auto metadata = wasm_vm_->getUserSection("emscripten_metadata");
if (!metadata.empty()) {
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_major_version_);
start = decodeVarint(start, end, &emscripten_minor_version_);
start = decodeVarint(start, end, &emscripten_abi_major_version_);
start = decodeVarint(start, end, &emscripten_abi_minor_version_);
start = decodeVarint(start, end, &emscripten_memory_size_);
decodeVarint(start, end, &emscripten_table_size_);
}
registerFunctions();
wasm_vm_->link(name, is_emscripten_);
general_context_ = createContext();
wasm_vm_->start(general_context_.get());
code_ = code;
Expand Down
35 changes: 31 additions & 4 deletions source/extensions/common/wasm/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,24 @@ class Wasm : public Envoy::Server::Wasm,
general_context_ = std::move(context);
}

bool getEmscriptenVersion(uint32_t* emscripten_major_version, uint32_t* emscripten_minor_version,
uint32_t* emscripten_abi_major_version,
uint32_t* emscripten_abi_minor_version) {
if (!is_emscripten_) {
return false;
}
*emscripten_major_version = emscripten_major_version_;
*emscripten_minor_version = emscripten_minor_version_;
*emscripten_abi_major_version = emscripten_abi_major_version_;
*emscripten_abi_minor_version = emscripten_abi_minor_version_;
return true;
}

private:
friend class Context;

void getFunctions();
void registerFunctions(); // Register functions called out from WASM.
void getFunctions(); // Get functions call into WASM.

Upstream::ClusterManager& cluster_manager_;
Event::Dispatcher& dispatcher_;
Expand Down Expand Up @@ -338,6 +352,14 @@ class Wasm : public Envoy::Server::Wasm,
std::string code_;
std::string initial_configuration_;
bool allow_precompiled_ = false;

bool is_emscripten_ = false;
uint32_t emscripten_major_version_ = 0;
uint32_t emscripten_minor_version_ = 0;
uint32_t emscripten_abi_major_version_ = 0;
uint32_t emscripten_abi_minor_version_ = 0;
uint32_t emscripten_memory_size_ = 0;
uint32_t emscripten_table_size_ = 0;
};

inline WasmVm* Context::wasmVm() const { return wasm_->wasmVm(); }
Expand Down Expand Up @@ -368,10 +390,11 @@ class WasmVm : public Logger::Loggable<Logger::Id::wasm> {
virtual std::unique_ptr<WasmVm> clone() PURE;

// Load the WASM code from a file. Return true on success.
virtual bool initialize(const std::string& code, absl::string_view id,
bool allow_precompiled) PURE;
virtual bool load(const std::string& code, bool allow_precompiled) PURE;
// Link to registered function.
virtual void link(absl::string_view debug_name, bool needs_emscripten) PURE;

// Call the 'start' function or main() if there is no start funcition.
// Call the 'start' function and initialize globals.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I believe main() is still called in start(), isn't it?

virtual void start(Context*) PURE;

// Allocate a block of memory in the VM and return the pointer to use as a call arguments.
Expand All @@ -381,6 +404,10 @@ class WasmVm : public Logger::Loggable<Logger::Id::wasm> {
// Set a block of memory in the VM, returns true on success, false if the pointer/size is invalid.
virtual bool setMemory(uint32_t pointer, uint32_t size, void* data) PURE;

// Get the contents of the user section with the given name or "" if it does not exist and
// optionally a presence indicator.
virtual absl::string_view getUserSection(absl::string_view name, bool* present = nullptr) PURE;

// Convenience functions.

// Allocate a null-terminated string in the VM and return the pointer to use as a call arguments.
Expand Down
45 changes: 28 additions & 17 deletions source/extensions/common/wasm/wavm/wavm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -175,20 +175,21 @@ template <typename F> EnvoyHandlerBase* MakeEnvoyHandler(F handler) {
return new EnvoyHandler<F>(handler);
}

class Wavm : public WasmVm {
public:
struct Wavm : public WasmVm {
Wavm() = default;
~Wavm() override;

// WasmVm
absl::string_view vm() override { return WasmVmNames::get().Wavm; }
bool clonable() override { return true; };
std::unique_ptr<WasmVm> clone() override;
bool initialize(const std::string& code, absl::string_view name, bool allow_precompiled) override;
bool load(const std::string& code, bool allow_precompiled) override;
void link(absl::string_view debug_name, bool needs_emscripten) override;
void start(Context* context) override;
void* allocMemory(uint32_t size, uint32_t* pointer) override;
absl::string_view getMemory(uint32_t pointer, uint32_t size) override;
bool setMemory(uint32_t pointer, uint32_t size, void* data) override;
absl::string_view getUserSection(absl::string_view name, bool* present) override;

WAVM::Runtime::Memory* memory() { return memory_; }
WAVM::Runtime::Context* context() { return context_; }
Expand All @@ -200,6 +201,7 @@ class Wavm : public WasmVm {

bool hasInstantiatedModule_ = false;
IR::Module irModule_;
WAVM::Runtime::ModuleRef module_ = nullptr;
WAVM::Runtime::GCPointer<WAVM::Runtime::ModuleInstance> moduleInstance_;
WAVM::Runtime::Memory* memory_;
Emscripten::Instance* emscriptenInstance_ = nullptr;
Expand Down Expand Up @@ -239,15 +241,14 @@ std::unique_ptr<WasmVm> Wavm::clone() {
return wavm;
}

bool Wavm::initialize(const std::string& code, absl::string_view name, bool allow_precompiled) {
bool Wavm::load(const std::string& code, bool allow_precompiled) {
ASSERT(!hasInstantiatedModule_);
hasInstantiatedModule_ = true;
compartment_ = WAVM::Runtime::createCompartment();
context_ = WAVM::Runtime::createContext(compartment_);
if (!loadModule(code, irModule_)) {
return false;
}
WAVM::Runtime::ModuleRef module = nullptr;
// todo check percompiled section is permitted
const UserSection* precompiledObjectSection = nullptr;
if (allow_precompiled) {
Expand All @@ -259,32 +260,27 @@ bool Wavm::initialize(const std::string& code, absl::string_view name, bool allo
}
}
if (!precompiledObjectSection) {
module = WAVM::Runtime::compileModule(irModule_);
module_ = WAVM::Runtime::compileModule(irModule_);
} else {
module = WAVM::Runtime::loadPrecompiledModule(irModule_, precompiledObjectSection->data);
module_ = WAVM::Runtime::loadPrecompiledModule(irModule_, precompiledObjectSection->data);
}
return true;
}

void Wavm::link(absl::string_view name, bool needs_emscripten) {
RootResolver rootResolver(compartment_);
envoyModuleInstance_ = Intrinsics::instantiateModule(compartment_, envoy_module_, "envoy");
rootResolver.moduleNameToInstanceMap().set("envoy", envoyModuleInstance_);
// Auto-detect if WASM module needs Emscripten.
bool needs_emscripten = false;
for (const auto& func : irModule_.functions.imports) {
if (func.exportName == "_emscripten_memcpy_big" && func.moduleName == "env") {
needs_emscripten = true;
break;
}
}
if (needs_emscripten) {
emscriptenInstance_ = Emscripten::instantiate(compartment_, irModule_);
rootResolver.moduleNameToInstanceMap().set("env", emscriptenInstance_->env);
rootResolver.moduleNameToInstanceMap().set("asm2wasm", emscriptenInstance_->asm2wasm);
rootResolver.moduleNameToInstanceMap().set("global", emscriptenInstance_->global);
}
WAVM::Runtime::LinkResult linkResult = linkModule(irModule_, rootResolver);
moduleInstance_ = instantiateModule(compartment_, module, std::move(linkResult.resolvedImports),
moduleInstance_ = instantiateModule(compartment_, module_, std::move(linkResult.resolvedImports),
std::string(name));
memory_ = getDefaultMemory(moduleInstance_);
return true;
}

void Wavm::start(Context* context) {
Expand Down Expand Up @@ -334,6 +330,21 @@ bool Wavm::setMemory(uint32_t pointer, uint32_t size, void* data) {
}
}

absl::string_view Wavm::getUserSection(absl::string_view name, bool* present) {
for (auto& section : irModule_.userSections) {
if (section.name == name) {
if (present) {
*present = true;
}
return {reinterpret_cast<char*>(section.data.data()), section.data.size()};
}
}
if (present) {
*present = false;
}
return {};
}

std::unique_ptr<WasmVm> createWavm() { return std::make_unique<Wavm>(); }

} // namespace Wavm
Expand Down
2 changes: 1 addition & 1 deletion test/extensions/wasm/config_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ TEST(WasmFactoryTest, CreateWasmFromWAT) {
envoy::config::wasm::v2::WasmConfig config;
config.mutable_vm_config()->set_vm("envoy.wasm.vm.wavm");
config.mutable_vm_config()->mutable_code()->set_filename(
TestEnvironment::substitute("{{ test_rundir }}/test/extensions/wasm/test_data/logging.wat"));
TestEnvironment::substitute("{{ test_rundir }}/test/extensions/wasm/test_data/wat.wat"));
config.set_singleton(true);
Upstream::MockClusterManager cluster_manager;
Event::MockDispatcher dispatcher;
Expand Down
16 changes: 16 additions & 0 deletions test/extensions/wasm/test_data/wat.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
(module
(type $0 (func (param i32 i32 i32)))
(type $1 (func))
(import "env" "_proxy_log" (func $_proxy_log (param i32 i32 i32)))
(export "memory" (memory $2))
(export "main" (func $main))
(memory $2 17)
(data $2 (i32.const 1048576) "Hello, world!")

(func $main (type $1)
i32.const 1
i32.const 1048576
i32.const 13
call $_proxy_log
)
)
Loading