Skip to content

Commit 35b3b08

Browse files
committed
Support creating warmed VM in the proxy wasm V8 VM API.
Signed-off-by: Rachel Green <[email protected]>
1 parent a374efd commit 35b3b08

File tree

5 files changed

+1144
-1
lines changed

5 files changed

+1144
-1
lines changed

include/proxy-wasm/null_vm.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ struct NullVm : public WasmVm {
6363
void terminate() override {}
6464
bool usesWasmByteOrder() override { return false; }
6565

66+
void warm() override {}
67+
6668
std::string plugin_name_;
6769
std::unique_ptr<NullVmPlugin> plugin_;
6870
};

include/proxy-wasm/wasm_vm.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,11 @@ class WasmVm {
308308
*/
309309
virtual bool usesWasmByteOrder() = 0;
310310

311+
/**
312+
* Warm the VM such as engine and runtime.
313+
*/
314+
virtual void warm() = 0;
315+
311316
bool isFailed() { return failed_ != FailState::Ok; }
312317
void fail(FailState fail_state, std::string_view message) {
313318
integration()->error(message);

include/proxy-wasm/wasm_vm.h.orig

Lines changed: 376 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,376 @@
1+
// Copyright 2016-2019 Envoy Project Authors
2+
// Copyright 2020 Google LLC
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
#pragma once
17+
18+
#include <functional>
19+
#include <memory>
20+
#include <optional>
21+
#include <string>
22+
#include <string_view>
23+
#include <unordered_map>
24+
#include <unordered_set>
25+
#include <vector>
26+
27+
#include "include/proxy-wasm/sdk.h"
28+
#include "include/proxy-wasm/word.h"
29+
30+
namespace proxy_wasm {
31+
32+
class ContextBase;
33+
34+
// These are templates and its helper for constructing signatures of functions calling into Wasm
35+
// VMs.
36+
// - WasmCallInFuncTypeHelper is a helper for WasmFuncType and shouldn't be used anywhere else than
37+
// WasmFuncType definition.
38+
// - WasmCallInFuncType takes 4 template parameter which are number of argument, return type,
39+
// context type and param type respectively, resolve to a function type.
40+
// For example `WasmFuncType<3, void, Context*, Word>` resolves to `void(Context*, Word, Word,
41+
// Word)`
42+
template <size_t N, class ReturnType, class ContextType, class ParamType,
43+
class FuncBase = ReturnType(ContextType)>
44+
struct WasmCallInFuncTypeHelper {};
45+
46+
template <size_t N, class ReturnType, class ContextType, class ParamType, class... Args>
47+
struct WasmCallInFuncTypeHelper<N, ReturnType, ContextType, ParamType,
48+
ReturnType(ContextType, Args...)> {
49+
// NOLINTNEXTLINE(readability-identifier-naming)
50+
using type = typename WasmCallInFuncTypeHelper<N - 1, ReturnType, ContextType, ParamType,
51+
ReturnType(ContextType, Args..., ParamType)>::type;
52+
};
53+
54+
template <class ReturnType, class ContextType, class ParamType, class... Args>
55+
struct WasmCallInFuncTypeHelper<0, ReturnType, ContextType, ParamType,
56+
ReturnType(ContextType, Args...)> {
57+
using type = ReturnType(ContextType, Args...); // NOLINT(readability-identifier-naming)
58+
};
59+
60+
template <size_t N, class ReturnType, class ContextType, class ParamType>
61+
using WasmCallInFuncType =
62+
typename WasmCallInFuncTypeHelper<N, ReturnType, ContextType, ParamType>::type;
63+
64+
// Calls into the WASM VM.
65+
// 1st arg is always a pointer to Context (Context*).
66+
template <size_t N>
67+
using WasmCallVoid = std::function<WasmCallInFuncType<N, void, ContextBase *, Word>>;
68+
template <size_t N>
69+
using WasmCallWord = std::function<WasmCallInFuncType<N, Word, ContextBase *, Word>>;
70+
71+
#define FOR_ALL_WASM_VM_EXPORTS(_f) \
72+
_f(proxy_wasm::WasmCallVoid<0>) _f(proxy_wasm::WasmCallVoid<1>) _f(proxy_wasm::WasmCallVoid<2>) \
73+
_f(proxy_wasm::WasmCallVoid<3>) _f(proxy_wasm::WasmCallVoid<5>) \
74+
_f(proxy_wasm::WasmCallWord<1>) _f(proxy_wasm::WasmCallWord<2>) \
75+
_f(proxy_wasm::WasmCallWord<3>)
76+
77+
// These are templates and its helper for constructing signatures of functions callbacks from Wasm
78+
// VMs.
79+
// - WasmCallbackFuncTypeHelper is a helper for WasmFuncType and shouldn't be used anywhere else
80+
// than WasmFuncType definition.
81+
// - WasmCallbackFuncType takes 3 template parameter which are number of argument, return type, and
82+
// param type respectively, resolve to a function type.
83+
// For example `WasmFuncType<3, Word>` resolves to `void(Word, Word, Word)`
84+
template <size_t N, class ReturnType, class ParamType, class FuncBase = ReturnType()>
85+
struct WasmCallbackFuncTypeHelper {};
86+
87+
template <size_t N, class ReturnType, class ParamType, class... Args>
88+
struct WasmCallbackFuncTypeHelper<N, ReturnType, ParamType, ReturnType(Args...)> {
89+
// NOLINTNEXTLINE(readability-identifier-naming)
90+
using type = typename WasmCallbackFuncTypeHelper<N - 1, ReturnType, ParamType,
91+
ReturnType(Args..., ParamType)>::type;
92+
};
93+
94+
template <class ReturnType, class ParamType, class... Args>
95+
struct WasmCallbackFuncTypeHelper<0, ReturnType, ParamType, ReturnType(Args...)> {
96+
using type = ReturnType(Args...); // NOLINT(readability-identifier-naming)
97+
};
98+
99+
template <size_t N, class ReturnType, class ParamType>
100+
using WasmCallbackFuncType = typename WasmCallbackFuncTypeHelper<N, ReturnType, ParamType>::type;
101+
102+
// Calls out of the WASM VM.
103+
template <size_t N> using WasmCallbackVoid = WasmCallbackFuncType<N, void, Word> *;
104+
template <size_t N> using WasmCallbackWord = WasmCallbackFuncType<N, Word, Word> *;
105+
106+
// Using the standard g++/clang mangling algorithm:
107+
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-builtin
108+
// Extended with W = Word
109+
// Z = void, j = uint32_t, l = int64_t, m = uint64_t
110+
using WasmCallback_WWl = Word (*)(Word, int64_t);
111+
using WasmCallback_WWlWW = Word (*)(Word, int64_t, Word, Word);
112+
using WasmCallback_WWm = Word (*)(Word, uint64_t);
113+
using WasmCallback_WWmW = Word (*)(Word, uint64_t, Word);
114+
using WasmCallback_WWWWWWllWW = Word (*)(Word, Word, Word, Word, Word, int64_t, int64_t, Word,
115+
Word);
116+
using WasmCallback_dd = double (*)(double);
117+
118+
#define FOR_ALL_WASM_VM_IMPORTS(_f) \
119+
_f(proxy_wasm::WasmCallbackVoid<0>) _f(proxy_wasm::WasmCallbackVoid<1>) \
120+
_f(proxy_wasm::WasmCallbackVoid<2>) _f(proxy_wasm::WasmCallbackVoid<3>) \
121+
_f(proxy_wasm::WasmCallbackVoid<4>) _f(proxy_wasm::WasmCallbackWord<0>) \
122+
_f(proxy_wasm::WasmCallbackWord<1>) _f(proxy_wasm::WasmCallbackWord<2>) \
123+
_f(proxy_wasm::WasmCallbackWord<3>) _f(proxy_wasm::WasmCallbackWord<4>) \
124+
_f(proxy_wasm::WasmCallbackWord<5>) _f(proxy_wasm::WasmCallbackWord<6>) \
125+
_f(proxy_wasm::WasmCallbackWord<7>) _f(proxy_wasm::WasmCallbackWord<8>) \
126+
_f(proxy_wasm::WasmCallbackWord<9>) \
127+
_f(proxy_wasm::WasmCallbackWord<10>) \
128+
_f(proxy_wasm::WasmCallbackWord<12>) \
129+
_f(proxy_wasm::WasmCallback_WWl) \
130+
_f(proxy_wasm::WasmCallback_WWlWW) \
131+
_f(proxy_wasm::WasmCallback_WWm) \
132+
_f(proxy_wasm::WasmCallback_WWmW) \
133+
_f(proxy_wasm::WasmCallback_WWWWWWllWW) \
134+
_f(proxy_wasm::WasmCallback_dd)
135+
136+
enum class Cloneable {
137+
NotCloneable, // VMs can not be cloned and should be created from scratch.
138+
CompiledBytecode, // VMs can be cloned with compiled bytecode.
139+
InstantiatedModule // VMs can be cloned from an instantiated module.
140+
};
141+
142+
enum class AbiVersion { ProxyWasm_0_1_0, ProxyWasm_0_2_0, ProxyWasm_0_2_1, Unknown };
143+
144+
class NullPlugin;
145+
146+
// Integrator specific WasmVm operations.
147+
struct WasmVmIntegration {
148+
virtual ~WasmVmIntegration() {}
149+
virtual WasmVmIntegration *clone() = 0;
150+
virtual proxy_wasm::LogLevel getLogLevel() = 0;
151+
virtual void error(std::string_view message) = 0;
152+
virtual void trace(std::string_view message) = 0;
153+
// Get a NullVm implementation of a function.
154+
// @param function_name is the name of the function with the implementation specific prefix.
155+
// @param returns_word is true if the function returns a Word and false if it returns void.
156+
// @param number_of_arguments is the number of Word arguments to the function.
157+
// @param plugin is the Null VM plugin on which the function will be called.
158+
// @param ptr_to_function_return is the location to write the function e.g. of type
159+
// WasmCallWord<3>.
160+
// @return true if the function was found. ptr_to_function_return could still be set to nullptr
161+
// (of the correct type) if the function has no implementation. Returning true will prevent a
162+
// "Missing getFunction" error.
163+
virtual bool getNullVmFunction(std::string_view function_name, bool returns_word,
164+
int number_of_arguments, NullPlugin *plugin,
165+
void *ptr_to_function_return) = 0;
166+
};
167+
168+
enum class FailState : int {
169+
Ok = 0,
170+
UnableToCreateVm = 1,
171+
UnableToCloneVm = 2,
172+
MissingFunction = 3,
173+
UnableToInitializeCode = 4,
174+
StartFailed = 5,
175+
ConfigureFailed = 6,
176+
RuntimeError = 7,
177+
};
178+
179+
// Wasm VM instance. Provides the low level WASM interface.
180+
class WasmVm {
181+
public:
182+
virtual ~WasmVm() = default;
183+
/**
184+
* Identify the Wasm engine.
185+
* @return the name of the underlying Wasm engine.
186+
*/
187+
virtual std::string_view getEngineName() = 0;
188+
189+
/**
190+
* Whether or not the VM implementation supports cloning. Cloning is VM system dependent.
191+
* When a VM is configured a single VM is instantiated to check that the .wasm file is valid and
192+
* to do VM system specific initialization. Then, if cloning is supported, we clone that VM for
193+
* each worker, potentially copying and sharing the initialized data structures for efficiency.
194+
* Otherwise we create an new VM from scratch for each worker.
195+
* @return one of enum Cloneable with the VMs cloneability.
196+
*/
197+
virtual Cloneable cloneable() = 0;
198+
199+
/**
200+
* Make a worker/thread-specific copy if supported by the underlying VM system (see cloneable()
201+
* above). If not supported, the caller will need to create a new VM from scratch. If supported,
202+
* the clone may share compiled code and other read-only data with the source VM.
203+
* @return a clone of 'this' (e.g. for a different worker/thread).
204+
*/
205+
virtual std::unique_ptr<WasmVm> clone() = 0;
206+
207+
/**
208+
* Load the WASM code from a file. Return true on success. Once the module is loaded it can be
209+
* queried, e.g. to see which version of emscripten support is required. After loading, the
210+
* appropriate ABI callbacks can be registered and then the module can be link()ed (see below).
211+
* @param bytecode the Wasm bytecode or registered NullVm plugin name.
212+
* @param precompiled (optional) the precompiled Wasm module.
213+
* @param function_names (optional) an index-to-name mapping for the functions.
214+
* @return whether or not the load was successful.
215+
*/
216+
virtual bool load(std::string_view bytecode, std::string_view precompiled,
217+
const std::unordered_map<uint32_t, std::string> &function_names) = 0;
218+
219+
/**
220+
* Link the WASM code to the host-provided functions, e.g. the ABI. Prior to linking, the module
221+
* should be loaded and the ABI callbacks registered (see above). Linking should be done once
222+
* after load().
223+
* @param debug_name user-provided name for use in log and error messages.
224+
* @return whether or not the link was successful.
225+
*/
226+
virtual bool link(std::string_view debug_name) = 0;
227+
228+
/**
229+
* Get size of the currently allocated memory in the VM.
230+
* @return the size of memory in bytes.
231+
*/
232+
virtual uint64_t getMemorySize() = 0;
233+
234+
/**
235+
* Convert a block of memory in the VM to a std::string_view.
236+
* @param pointer the offset into VM memory of the requested VM memory block.
237+
* @param size the size of the requested VM memory block.
238+
* @return if std::nullopt then the pointer/size pair were invalid, otherwise returns
239+
* a host std::string_view pointing to the pointer/size pair in VM memory.
240+
*/
241+
virtual std::optional<std::string_view> getMemory(uint64_t pointer, uint64_t size) = 0;
242+
243+
/**
244+
* Set a block of memory in the VM, returns true on success, false if the pointer/size is invalid.
245+
* @param pointer the offset into VM memory describing the start of a region of VM memory.
246+
* @param size the size of the region of VM memory.
247+
* @return whether or not the pointer/size pair was a valid VM memory block.
248+
*/
249+
virtual bool setMemory(uint64_t pointer, uint64_t size, const void *data) = 0;
250+
251+
/**
252+
* Get a VM native Word (e.g. sizeof(void*) or sizeof(size_t)) from VM memory, returns true on
253+
* success, false if the pointer is invalid. WASM-32 VMs have 32-bit native words and WASM-64 VMs
254+
* (not yet supported) will have 64-bit words as does the Null VM (compiled into a 64-bit proxy).
255+
* This function can be used to chase pointers in VM memory.
256+
* @param pointer the offset into VM memory describing the start of VM native word size block.
257+
* @param data a pointer to a Word whose contents will be filled from the VM native word at
258+
* 'pointer'.
259+
* @return whether or not the pointer was to a valid VM memory block of VM native word size.
260+
*/
261+
virtual bool getWord(uint64_t pointer, Word *data) = 0;
262+
263+
/**
264+
* Set a Word in the VM, returns true on success, false if the pointer is invalid.
265+
* See getWord above for details. This function can be used (for example) to set indirect pointer
266+
* return values (e.g. proxy_getHeaderHapValue(... const char** value_ptr, size_t* value_size).
267+
* @param pointer the offset into VM memory describing the start of VM native word size block.
268+
* @param data a Word whose contents will be written in VM native word size at 'pointer'.
269+
* @return whether or not the pointer was to a valid VM memory block of VM native word size.
270+
*/
271+
virtual bool setWord(uint64_t pointer, Word data) = 0;
272+
273+
/**
274+
* @return the Word size in this VM.
275+
*/
276+
virtual size_t getWordSize() = 0;
277+
278+
/**
279+
* Get the name of the custom section that contains precompiled module.
280+
* @return the name of the custom section that contains precompiled module.
281+
*/
282+
virtual std::string_view getPrecompiledSectionName() = 0;
283+
284+
/**
285+
* Get typed function exported by the WASM module.
286+
*/
287+
#define _GET_FUNCTION(_T) virtual void getFunction(std::string_view function_name, _T *f) = 0;
288+
FOR_ALL_WASM_VM_EXPORTS(_GET_FUNCTION)
289+
#undef _GET_FUNCTION
290+
291+
/**
292+
* Register typed callbacks exported by the host environment.
293+
*/
294+
#define _REGISTER_CALLBACK(_T) \
295+
virtual void registerCallback(std::string_view moduleName, std::string_view function_name, _T f, \
296+
typename ConvertFunctionTypeWordToUint32<_T>::type) = 0;
297+
FOR_ALL_WASM_VM_IMPORTS(_REGISTER_CALLBACK)
298+
#undef _REGISTER_CALLBACK
299+
300+
/**
301+
* Terminate execution of this WasmVM. It shouldn't be used after being terminated.
302+
*/
303+
virtual void terminate() = 0;
304+
305+
/**
306+
* Byte order flag (host or wasm).
307+
* @return 'false' for a null VM and 'true' for a wasm VM.
308+
*/
309+
virtual bool usesWasmByteOrder() = 0;
310+
311+
bool isFailed() { return failed_ != FailState::Ok; }
312+
void fail(FailState fail_state, std::string_view message) {
313+
integration()->error(message);
314+
failed_ = fail_state;
315+
for (auto &callback : fail_callbacks_) {
316+
callback(fail_state);
317+
}
318+
}
319+
void addFailCallback(std::function<void(FailState)> fail_callback) {
320+
fail_callbacks_.push_back(fail_callback);
321+
}
322+
323+
bool isHostFunctionAllowed(const std::string &name) {
324+
return !restricted_callback_ || allowed_hostcalls_.find(name) != allowed_hostcalls_.end();
325+
}
326+
327+
void setRestrictedCallback(bool restricted,
328+
std::unordered_set<std::string> allowed_hostcalls = {}) {
329+
restricted_callback_ = restricted;
330+
allowed_hostcalls_ = std::move(allowed_hostcalls);
331+
}
332+
333+
// Integrator operations.
334+
std::unique_ptr<WasmVmIntegration> &integration() { return integration_; }
335+
bool cmpLogLevel(proxy_wasm::LogLevel level) { return integration_->getLogLevel() <= level; }
336+
337+
protected:
338+
std::unique_ptr<WasmVmIntegration> integration_;
339+
FailState failed_ = FailState::Ok;
340+
std::vector<std::function<void(FailState)>> fail_callbacks_;
341+
342+
private:
343+
bool restricted_callback_{false};
344+
std::unordered_set<std::string> allowed_hostcalls_{};
345+
};
346+
347+
// Thread local state set during a call into a WASM VM so that calls coming out of the
348+
// VM can be attributed correctly to calling Filter. We use thread_local instead of ThreadLocal
349+
// because this state is live only during the calls and does not need to be initialized consistently
350+
// over all workers as with ThreadLocal data.
351+
extern thread_local ContextBase *current_context_;
352+
353+
// Requested effective context set by code within the VM to request that the calls coming out of the
354+
// VM be attributed to another filter, for example if a control plane gRPC comes back to the
355+
// RootContext which effects some set of waiting filters.
356+
extern thread_local uint32_t effective_context_id_;
357+
358+
// Helper to save and restore thread local VM call context information to support reentrant calls.
359+
// NB: this happens for example when a call from the VM invokes a handler which needs to _malloc
360+
// memory in the VM.
361+
struct SaveRestoreContext {
362+
explicit SaveRestoreContext(ContextBase *context) {
363+
saved_context = current_context_;
364+
saved_effective_context_id_ = effective_context_id_;
365+
current_context_ = context;
366+
effective_context_id_ = 0; // No effective context id.
367+
}
368+
~SaveRestoreContext() {
369+
current_context_ = saved_context;
370+
effective_context_id_ = saved_effective_context_id_;
371+
}
372+
ContextBase *saved_context;
373+
uint32_t saved_effective_context_id_;
374+
};
375+
376+
} // namespace proxy_wasm

0 commit comments

Comments
 (0)