-
Notifications
You must be signed in to change notification settings - Fork 58
/
Copy path_func.py
277 lines (224 loc) · 8.73 KB
/
_func.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
from contextlib import contextmanager
from ctypes import POINTER, byref, CFUNCTYPE, c_void_p, cast
import ctypes
from wasmtime import Store, FuncType, Val, Trap, WasmtimeError
from . import _ffi as ffi
from ._extern import wrap_extern
from typing import Callable, Optional, Generic, TypeVar, List, Union, Tuple, cast as cast_type, Sequence, Any
from ._exportable import AsExtern
from ._store import Storelike
T = TypeVar('T')
FUNCTIONS: "Slab[Tuple]"
LAST_EXCEPTION: Optional[Exception] = None
class Func:
_func: ffi.wasmtime_func_t
def __init__(self, store: Storelike, ty: FuncType, func: Callable, access_caller: bool = False):
"""
Creates a new func in `store` with the given `ty` which calls the closure
given
The `func` is called with the parameters natively and they'll have native
Python values rather than being wrapped in `Val`. If `access_caller` is
set to `True` then the first argument given to `func` is an instance of
type `Caller` below.
"""
if not isinstance(store, Store):
raise TypeError("expected a Store")
if not isinstance(ty, FuncType):
raise TypeError("expected a FuncType")
idx = FUNCTIONS.allocate((func, ty.results, access_caller))
_func = ffi.wasmtime_func_t()
ffi.wasmtime_func_new(
store._context(),
ty.ptr(),
trampoline,
idx,
finalize,
byref(_func))
self._func = _func
@classmethod
def _from_raw(cls, func: ffi.wasmtime_func_t) -> "Func":
ty: "Func" = cls.__new__(cls)
ty._func = func
return ty
def type(self, store: Storelike) -> FuncType:
"""
Gets the type of this func as a `FuncType`
"""
ptr = ffi.wasmtime_func_type(store._context(), byref(self._func))
return FuncType._from_ptr(ptr, None)
def __call__(self, store: Storelike, *params: Any) -> Any:
"""
Calls this function with the given parameters
Parameters can either be a `Val` or a native python value which can be
converted to a `Val` of the corresponding correct type
Returns `None` if this func has 0 return types
Returns a single value if the func has 1 return type
Returns a list if the func has more than 1 return type
Note that you can also use the `__call__` method and invoke a `Func` as
if it were a function directly.
"""
ty = self.type(store)
param_tys = ty.params
if len(params) > len(param_tys):
raise WasmtimeError("too many parameters provided: given %s, expected %s" %
(len(params), len(param_tys)))
if len(params) < len(param_tys):
raise WasmtimeError("too few parameters provided: given %s, expected %s" %
(len(params), len(param_tys)))
params_ptr = (ffi.wasmtime_val_t * len(params))()
params_set = 0
try:
for val in params:
params_ptr[params_set] = Val._convert_to_raw(store, param_tys[params_set], val)
params_set += 1
result_tys = ty.results
results_ptr = (ffi.wasmtime_val_t * len(result_tys))()
with enter_wasm(store) as trap:
error = ffi.wasmtime_func_call(
store._context(),
byref(self._func),
params_ptr,
len(params),
results_ptr,
len(result_tys),
trap)
if error:
raise WasmtimeError._from_ptr(error)
finally:
for i in range(0, params_set):
ffi.wasmtime_val_unroot(store._context(), byref(params_ptr[i]))
results = []
for i in range(0, len(result_tys)):
results.append(Val._from_raw(store, results_ptr[i]).value)
if len(results) == 0:
return None
elif len(results) == 1:
return results[0]
else:
return results
def _as_extern(self) -> ffi.wasmtime_extern_t:
union = ffi.wasmtime_extern_union(func=self._func)
return ffi.wasmtime_extern_t(ffi.WASMTIME_EXTERN_FUNC, union)
class Caller:
__ptr: "Optional[ctypes._Pointer[ffi.wasmtime_caller_t]]"
__context: "Optional[ctypes._Pointer[ffi.wasmtime_context_t]]"
def __init__(self, ptr: "ctypes._Pointer[ffi.wasmtime_caller_t]"):
self.__ptr = ptr
self.__context = ffi.wasmtime_caller_context(ptr)
def __getitem__(self, name: str) -> AsExtern:
"""
Looks up an export with `name` on the calling module.
If `name` isn't defined on the calling module, or if the caller has gone
away for some reason, then this will raise a `KeyError`. For more
information about when this could fail see the `get` method which
returns `None` on failure.
"""
ret = self.get(name)
if ret is None:
raise KeyError("failed to find export {}".format(name))
return ret
def get(self, name: str) -> Optional[AsExtern]:
"""
Looks up an export with `name` on the calling module.
May return `None` if the export isn't found, if it's not a memory (for
now), or if the caller has gone away and this `Caller` object has
persisted too long.
"""
# First convert to a raw name so we can typecheck our argument
name_bytes = name.encode('utf-8')
name_buf = ffi.create_string_buffer(name_bytes)
# Next see if we've been invalidated
if self.__ptr is None:
return None
# And if we're not invalidated we can perform the actual lookup
item = ffi.wasmtime_extern_t()
ok = ffi.wasmtime_caller_export_get(self.__ptr, name_buf, len(name_bytes), byref(item))
if ok:
return wrap_extern(item)
else:
return None
def _context(self) -> "ctypes._Pointer[ffi.wasmtime_context_t]":
if self.__context is None:
raise ValueError("caller is no longer valid")
return self.__context
def _invalidate(self) -> None:
self.__ptr = None
self.__context = None
def extract_val(val: Val) -> Any:
a = val.value
if a is not None:
return a
return val
@ffi.wasmtime_func_callback_t
def trampoline(idx, caller, params, nparams, results, nresults): # type: ignore
caller = Caller(caller)
try:
func, result_tys, access_caller = FUNCTIONS.get(idx or 0)
pyparams = []
if access_caller:
pyparams.append(caller)
for i in range(0, nparams):
pyparams.append(Val._from_raw(caller, params[i], owned=False).value)
pyresults = func(*pyparams)
if nresults == 0:
if pyresults is not None:
raise WasmtimeError(
"callback produced results when it shouldn't")
elif nresults == 1:
results[0] = Val._convert_to_raw(caller, result_tys[0], pyresults)
else:
if len(pyresults) != nresults:
raise WasmtimeError("callback produced wrong number of results")
for i, result in enumerate(pyresults):
results[i] = Val._convert_to_raw(caller, result_tys[i], result)
return 0
except Exception as e:
global LAST_EXCEPTION
LAST_EXCEPTION = e
trap = Trap("python exception")._consume()
return cast(trap, c_void_p).value
finally:
caller._invalidate()
@CFUNCTYPE(None, c_void_p)
def finalize(idx): # type: ignore
FUNCTIONS.deallocate(idx or 0)
class Slab(Generic[T]):
list: List[Union[int, T]]
next: int
def __init__(self) -> None:
self.list = []
self.next = 0
def allocate(self, val: T) -> int:
idx = self.next
if len(self.list) == idx:
self.list.append(0)
self.next += 1
else:
self.next = cast_type(int, self.list[idx])
self.list[idx] = val
return idx
def get(self, idx: int) -> T:
return cast_type(T, self.list[idx])
def deallocate(self, idx: int) -> None:
self.list[idx] = self.next
self.next = idx
FUNCTIONS = Slab()
@contextmanager
def enter_wasm(store: Storelike): # type: ignore
try:
trap = POINTER(ffi.wasm_trap_t)()
yield byref(trap)
if trap:
trap_obj = Trap._from_ptr(trap)
maybe_raise_last_exn()
raise trap_obj
except WasmtimeError:
maybe_raise_last_exn()
raise
def maybe_raise_last_exn() -> None:
global LAST_EXCEPTION
if LAST_EXCEPTION is None:
return
exn = LAST_EXCEPTION
LAST_EXCEPTION = None
raise exn