From aaa27689c32f3f10b9015aee082584b834251c4d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 15 Feb 2024 17:36:23 -0600 Subject: [PATCH] Enable explicit destruction of all resources (#209) * Enable explicit destruction of all resources This commit adds a suite of methods to most of the types in this package to enable explicit destruction of resources as necessary. Specifically most classes which own a pointer internally now sport some new methods: * `__enter__` and `__exit__` enable using the objects in `with` blocks. * `close` enables explicitly deallocating the object. All objects internally now validate that they have not been closed before operating, raising a `ValueError` if a closed object is operated on. * Try to appease Python 3.8 --- tests/test_config.py | 12 ++++ tests/test_engine.py | 2 +- tests/test_store.py | 9 +++ wasmtime/__init__.py | 1 + wasmtime/_config.py | 61 ++++++++-------- wasmtime/_engine.py | 23 +++--- wasmtime/_error.py | 30 ++++---- wasmtime/_extern.py | 10 +-- wasmtime/_func.py | 38 ++++++---- wasmtime/_globals.py | 10 +-- wasmtime/_instance.py | 7 +- wasmtime/_linker.py | 38 +++++----- wasmtime/_managed.py | 95 +++++++++++++++++++++++++ wasmtime/_memory.py | 12 ++-- wasmtime/_module.py | 41 +++++------ wasmtime/_store.py | 39 +++++----- wasmtime/_table.py | 12 ++-- wasmtime/_trap.py | 46 ++++++------ wasmtime/_types.py | 161 ++++++++++++++++++++---------------------- wasmtime/_wasi.py | 36 +++++----- 20 files changed, 396 insertions(+), 287 deletions(-) create mode 100644 wasmtime/_managed.py diff --git a/tests/test_config.py b/tests/test_config.py index 737613ed..2dc7219d 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,4 +1,5 @@ import unittest +from contextlib import closing from wasmtime import * @@ -34,3 +35,14 @@ def test_smoke(self): config.consume_fuel = True config.wasm_relaxed_simd = True config.wasm_relaxed_simd_deterministic = True + + with closing(config) as config: + pass + + config.close() + + with self.assertRaises(ValueError): + Engine(config) + + with self.assertRaises(ValueError): + config.cache = True diff --git a/tests/test_engine.py b/tests/test_engine.py index a4894ab4..b1956c4d 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -13,5 +13,5 @@ def test_errors(self): Engine(3) # type: ignore config = Config() Engine(config) - with self.assertRaises(WasmtimeError): + with self.assertRaises(ValueError): Engine(config) diff --git a/tests/test_store.py b/tests/test_store.py index 1ed88f2a..965fb221 100644 --- a/tests/test_store.py +++ b/tests/test_store.py @@ -8,6 +8,15 @@ def test_smoke(self): Store() Store(Engine()) + with Store() as store: + pass + + store = Store() + store.close() + store.close() + with self.assertRaises(ValueError): + store.set_epoch_deadline(1) + def test_errors(self): with self.assertRaises(TypeError): Store(3) # type: ignore diff --git a/wasmtime/__init__.py b/wasmtime/__init__.py index 77a9ccbe..c6e462b3 100644 --- a/wasmtime/__init__.py +++ b/wasmtime/__init__.py @@ -13,6 +13,7 @@ libraries are intended to be quite similar. """ +from ._managed import Managed from ._error import WasmtimeError, ExitTrap from ._config import Config from ._engine import Engine diff --git a/wasmtime/_config.py b/wasmtime/_config.py index dc4b9eaa..3f358e7a 100644 --- a/wasmtime/_config.py +++ b/wasmtime/_config.py @@ -1,7 +1,7 @@ from . import _ffi as ffi from ctypes import * import ctypes -from wasmtime import WasmtimeError +from wasmtime import WasmtimeError, Managed import typing @@ -13,7 +13,7 @@ def setter_property(fset: typing.Callable) -> property: return prop -class Config: +class Config(Managed["ctypes._Pointer[ffi.wasm_config_t]"]): """ Global configuration, used to create an `Engine`. @@ -21,10 +21,11 @@ class Config: code is compiled or generated. """ - _ptr: "ctypes._Pointer[ffi.wasm_config_t]" - def __init__(self) -> None: - self._ptr = ffi.wasm_config_new() + self._set_ptr(ffi.wasm_config_new()) + + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_config_t]") -> None: + ffi.wasm_config_delete(ptr) @setter_property def debug_info(self, enable: bool) -> None: @@ -35,7 +36,7 @@ def debug_info(self, enable: bool) -> None: if not isinstance(enable, bool): raise TypeError('expected a bool') - ffi.wasmtime_config_debug_info_set(self._ptr, enable) + ffi.wasmtime_config_debug_info_set(self.ptr(), enable) @setter_property def wasm_threads(self, enable: bool) -> None: @@ -47,7 +48,7 @@ def wasm_threads(self, enable: bool) -> None: if not isinstance(enable, bool): raise TypeError('expected a bool') - ffi.wasmtime_config_wasm_threads_set(self._ptr, enable) + ffi.wasmtime_config_wasm_threads_set(self.ptr(), enable) @setter_property def wasm_reference_types(self, enable: bool) -> None: @@ -59,7 +60,7 @@ def wasm_reference_types(self, enable: bool) -> None: if not isinstance(enable, bool): raise TypeError('expected a bool') - ffi.wasmtime_config_wasm_reference_types_set(self._ptr, enable) + ffi.wasmtime_config_wasm_reference_types_set(self.ptr(), enable) @setter_property def wasm_simd(self, enable: bool) -> None: @@ -71,7 +72,7 @@ def wasm_simd(self, enable: bool) -> None: if not isinstance(enable, bool): raise TypeError('expected a bool') - ffi.wasmtime_config_wasm_simd_set(self._ptr, enable) + ffi.wasmtime_config_wasm_simd_set(self.ptr(), enable) @setter_property def wasm_bulk_memory(self, enable: bool) -> None: @@ -83,7 +84,7 @@ def wasm_bulk_memory(self, enable: bool) -> None: if not isinstance(enable, bool): raise TypeError('expected a bool') - ffi.wasmtime_config_wasm_bulk_memory_set(self._ptr, enable) + ffi.wasmtime_config_wasm_bulk_memory_set(self.ptr(), enable) @setter_property def wasm_multi_value(self, enable: bool) -> None: @@ -95,7 +96,7 @@ def wasm_multi_value(self, enable: bool) -> None: if not isinstance(enable, bool): raise TypeError('expected a bool') - ffi.wasmtime_config_wasm_multi_value_set(self._ptr, enable) + ffi.wasmtime_config_wasm_multi_value_set(self.ptr(), enable) @setter_property def wasm_multi_memory(self, enable: bool) -> None: @@ -107,7 +108,7 @@ def wasm_multi_memory(self, enable: bool) -> None: if not isinstance(enable, bool): raise TypeError('expected a bool') - ffi.wasmtime_config_wasm_multi_memory_set(self._ptr, enable) + ffi.wasmtime_config_wasm_multi_memory_set(self.ptr(), enable) @setter_property def wasm_memory64(self, enable: bool) -> None: @@ -119,7 +120,7 @@ def wasm_memory64(self, enable: bool) -> None: if not isinstance(enable, bool): raise TypeError('expected a bool') - ffi.wasmtime_config_wasm_memory64_set(self._ptr, enable) + ffi.wasmtime_config_wasm_memory64_set(self.ptr(), enable) @setter_property def wasm_relaxed_simd(self, enable: bool) -> None: @@ -131,7 +132,7 @@ def wasm_relaxed_simd(self, enable: bool) -> None: if not isinstance(enable, bool): raise TypeError('expected a bool') - ffi.wasmtime_config_wasm_relaxed_simd_set(self._ptr, enable) + ffi.wasmtime_config_wasm_relaxed_simd_set(self.ptr(), enable) @setter_property def wasm_relaxed_simd_deterministic(self, enable: bool) -> None: @@ -145,7 +146,7 @@ def wasm_relaxed_simd_deterministic(self, enable: bool) -> None: if not isinstance(enable, bool): raise TypeError('expected a bool') - ffi.wasmtime_config_wasm_relaxed_simd_deterministic_set(self._ptr, enable) + ffi.wasmtime_config_wasm_relaxed_simd_deterministic_set(self.ptr(), enable) @setter_property def strategy(self, strategy: str) -> None: @@ -159,9 +160,9 @@ def strategy(self, strategy: str) -> None: """ if strategy == "auto": - ffi.wasmtime_config_strategy_set(self._ptr, 0) + ffi.wasmtime_config_strategy_set(self.ptr(), 0) elif strategy == "cranelift": - ffi.wasmtime_config_strategy_set(self._ptr, 1) + ffi.wasmtime_config_strategy_set(self.ptr(), 1) else: raise WasmtimeError("unknown strategy: " + str(strategy)) @@ -169,25 +170,25 @@ def strategy(self, strategy: str) -> None: def cranelift_debug_verifier(self, enable: bool) -> None: if not isinstance(enable, bool): raise TypeError('expected a bool') - ffi.wasmtime_config_cranelift_debug_verifier_set(self._ptr, enable) + ffi.wasmtime_config_cranelift_debug_verifier_set(self.ptr(), enable) @setter_property def cranelift_opt_level(self, opt_level: str) -> None: if opt_level == "none": - ffi.wasmtime_config_cranelift_opt_level_set(self._ptr, 0) + ffi.wasmtime_config_cranelift_opt_level_set(self.ptr(), 0) elif opt_level == "speed": - ffi.wasmtime_config_cranelift_opt_level_set(self._ptr, 1) + ffi.wasmtime_config_cranelift_opt_level_set(self.ptr(), 1) elif opt_level == "speed_and_size": - ffi.wasmtime_config_cranelift_opt_level_set(self._ptr, 2) + ffi.wasmtime_config_cranelift_opt_level_set(self.ptr(), 2) else: raise WasmtimeError("unknown opt level: " + str(opt_level)) @setter_property def profiler(self, profiler: str) -> None: if profiler == "none": - ffi.wasmtime_config_profiler_set(self._ptr, 0) + ffi.wasmtime_config_profiler_set(self.ptr(), 0) elif profiler == "jitdump": - ffi.wasmtime_config_profiler_set(self._ptr, 1) + ffi.wasmtime_config_profiler_set(self.ptr(), 1) else: raise WasmtimeError("unknown profiler: " + str(profiler)) @@ -207,9 +208,9 @@ def cache(self, enabled: typing.Union[bool, str]) -> None: if isinstance(enabled, bool): if not enabled: raise WasmtimeError("caching cannot be explicitly disabled") - error = ffi.wasmtime_config_cache_config_load(self._ptr, None) + error = ffi.wasmtime_config_cache_config_load(self.ptr(), None) elif isinstance(enabled, str): - error = ffi.wasmtime_config_cache_config_load(self._ptr, + error = ffi.wasmtime_config_cache_config_load(self.ptr(), c_char_p(enabled.encode('utf-8'))) else: raise TypeError("expected string or bool") @@ -227,7 +228,7 @@ def epoch_interruption(self, enabled: bool) -> None: val = 1 else: val = 0 - ffi.wasmtime_config_epoch_interruption_set(self._ptr, val) + ffi.wasmtime_config_epoch_interruption_set(self.ptr(), val) @setter_property def consume_fuel(self, instances: bool) -> None: @@ -240,7 +241,7 @@ def consume_fuel(self, instances: bool) -> None: """ if not isinstance(instances, bool): raise TypeError('expected an bool') - ffi.wasmtime_config_consume_fuel_set(self._ptr, instances) + ffi.wasmtime_config_consume_fuel_set(self.ptr(), instances) @setter_property def parallel_compilation(self, enable: bool) -> None: @@ -252,8 +253,4 @@ def parallel_compilation(self, enable: bool) -> None: """ if not isinstance(enable, bool): raise TypeError('expected a bool') - ffi.wasmtime_config_parallel_compilation_set(self._ptr, enable) - - def __del__(self) -> None: - if hasattr(self, '_ptr'): - ffi.wasm_config_delete(self._ptr) + ffi.wasmtime_config_parallel_compilation_set(self.ptr(), enable) diff --git a/wasmtime/_engine.py b/wasmtime/_engine.py index 18a6abb0..37e3d60f 100644 --- a/wasmtime/_engine.py +++ b/wasmtime/_engine.py @@ -1,27 +1,22 @@ from . import _ffi as ffi -from wasmtime import Config, WasmtimeError +from wasmtime import Config, WasmtimeError, Managed from typing import Optional import ctypes -class Engine: - _ptr: "ctypes._Pointer[ffi.wasm_engine_t]" +class Engine(Managed["ctypes._Pointer[ffi.wasm_engine_t]"]): def __init__(self, config: Optional[Config] = None): if config is None: - self._ptr = ffi.wasm_engine_new() + self._set_ptr(ffi.wasm_engine_new()) elif not isinstance(config, Config): raise TypeError("expected Config") - elif not hasattr(config, '_ptr'): - raise WasmtimeError("Config already used") else: - ptr = config._ptr - delattr(config, '_ptr') - self._ptr = ffi.wasm_engine_new_with_config(ptr) + ptr = config._consume() + self._set_ptr(ffi.wasm_engine_new_with_config(ptr)) - def increment_epoch(self) -> None: - ffi.wasmtime_engine_increment_epoch(self._ptr) + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_engine_t]") -> None: + ffi.wasm_engine_delete(ptr) - def __del__(self) -> None: - if hasattr(self, '_ptr'): - ffi.wasm_engine_delete(self._ptr) + def increment_epoch(self) -> None: + ffi.wasmtime_engine_increment_epoch(self.ptr()) diff --git a/wasmtime/_error.py b/wasmtime/_error.py index 9b209987..7152be5d 100644 --- a/wasmtime/_error.py +++ b/wasmtime/_error.py @@ -2,15 +2,17 @@ from . import _ffi as ffi import ctypes from typing import Optional +from wasmtime import Managed -class WasmtimeError(Exception): - _ptr: "Optional[ctypes._Pointer[ffi.wasmtime_error_t]]" - _message: Optional[str] +class WasmtimeError(Exception, Managed["ctypes._Pointer[ffi.wasmtime_error_t]"]): + __message: Optional[str] def __init__(self, message: str): - self._message = message - self._ptr = None + self.__message = message + + def _delete(self, ptr: "ctypes._Pointer[ffi.wasmtime_error_t]") -> None: + ffi.wasmtime_error_delete(ptr) @classmethod def _from_ptr(cls, ptr: "ctypes._Pointer") -> 'WasmtimeError': @@ -21,29 +23,25 @@ def _from_ptr(cls, ptr: "ctypes._Pointer") -> 'WasmtimeError': exit_code = c_int(0) if ffi.wasmtime_error_exit_status(ptr, byref(exit_code)): exit_trap: ExitTrap = ExitTrap.__new__(ExitTrap) - exit_trap._ptr = ptr - exit_trap._message = None + exit_trap._set_ptr(ptr) + exit_trap.__message = None exit_trap.code = exit_code.value return exit_trap err: WasmtimeError = cls.__new__(cls) - err._ptr = ptr - err._message = None + err._set_ptr(ptr) + err.__message = None return err def __str__(self) -> str: - if self._message: - return self._message + if self.__message: + return self.__message message_vec = ffi.wasm_byte_vec_t() - ffi.wasmtime_error_message(self._ptr, byref(message_vec)) + ffi.wasmtime_error_message(self.ptr(), byref(message_vec)) message = ffi.to_str(message_vec) ffi.wasm_byte_vec_delete(byref(message_vec)) return message - def __del__(self) -> None: - if hasattr(self, '_ptr') and self._ptr: - ffi.wasmtime_error_delete(self._ptr) - class ExitTrap(WasmtimeError): """ diff --git a/wasmtime/_extern.py b/wasmtime/_extern.py index 9c2d4656..95bccbe8 100644 --- a/wasmtime/_extern.py +++ b/wasmtime/_extern.py @@ -1,7 +1,7 @@ from . import _ffi as ffi import ctypes from ._exportable import AsExtern -from wasmtime import WasmtimeError +from wasmtime import WasmtimeError, Managed def wrap_extern(ptr: ffi.wasmtime_extern_t) -> AsExtern: @@ -41,9 +41,9 @@ def get_extern_ptr(item: AsExtern) -> ffi.wasmtime_extern_t: raise TypeError("expected a Func, Global, Memory, Table, Module, or Instance") -class Extern: +class Extern(Managed["ctypes._Pointer[ffi.wasm_extern_t]"]): def __init__(self, ptr: "ctypes._Pointer[ffi.wasm_extern_t]"): - self.ptr = ptr + self._set_ptr(ptr) - def __del__(self) -> None: - ffi.wasm_extern_delete(self.ptr) + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_extern_t]") -> None: + ffi.wasm_extern_delete(ptr) diff --git a/wasmtime/_func.py b/wasmtime/_func.py index d720bc5a..e69e8bb8 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -35,8 +35,8 @@ def __init__(self, store: Storelike, ty: FuncType, func: Callable, access_caller idx = FUNCTIONS.allocate((func, ty.results, access_caller)) _func = ffi.wasmtime_func_t() ffi.wasmtime_func_new( - store._context, - ty._ptr, + store._context(), + ty.ptr(), trampoline, idx, finalize, @@ -53,7 +53,7 @@ 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)) + ptr = ffi.wasmtime_func_type(store._context(), byref(self._func)) return FuncType._from_ptr(ptr, None) def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequence[IntoVal], None]: @@ -90,7 +90,7 @@ def __call__(self, store: Storelike, *params: IntoVal) -> Union[IntoVal, Sequenc with enter_wasm(store) as trap: error = ffi.wasmtime_func_call( - store._context, + store._context(), byref(self._func), params_ptr, len(params), @@ -116,10 +116,12 @@ def _as_extern(self) -> ffi.wasmtime_extern_t: class Caller: - _context: "ctypes._Pointer[ffi.wasmtime_context_t]" + __ptr: "ctypes._Pointer[ffi.wasmtime_caller_t] | None" + __context: "ctypes._Pointer[ffi.wasmtime_context_t] | None" - def __init__(self, ptr: "ctypes._Pointer"): - self._ptr = ptr + 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: """ @@ -150,17 +152,26 @@ def get(self, name: str) -> Optional[AsExtern]: name_buf = ffi.create_string_buffer(name_bytes) # Next see if we've been invalidated - if not hasattr(self, '_ptr'): + 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)) + 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) -> IntoVal: a = val.value @@ -176,7 +187,6 @@ def trampoline(idx, caller, params, nparams, results, nresults): func, result_tys, access_caller = FUNCTIONS.get(idx or 0) pyparams = [] if access_caller: - caller._context = ffi.wasmtime_caller_context(caller._ptr) pyparams.append(caller) for i in range(0, nparams): @@ -209,12 +219,10 @@ def trampoline(idx, caller, params, nparams, results, nresults): except Exception as e: global LAST_EXCEPTION LAST_EXCEPTION = e - trap = Trap("python exception") - ptr = trap._ptr - delattr(trap, '_ptr') - return cast(ptr, c_void_p).value + trap = Trap("python exception")._consume() + return cast(trap, c_void_p).value finally: - delattr(caller, '_ptr') + caller._invalidate() @CFUNCTYPE(None, c_void_p) diff --git a/wasmtime/_globals.py b/wasmtime/_globals.py index 11178803..f61c58dd 100644 --- a/wasmtime/_globals.py +++ b/wasmtime/_globals.py @@ -13,8 +13,8 @@ def __init__(self, store: Storelike, ty: GlobalType, val: IntoVal): val = Val._convert(ty.content, val) global_ = ffi.wasmtime_global_t() error = ffi.wasmtime_global_new( - store._context, - ty._ptr, + store._context(), + ty.ptr(), byref(val._unwrap_raw()), byref(global_)) if error: @@ -32,7 +32,7 @@ def type(self, store: Storelike) -> GlobalType: Gets the type of this global as a `GlobalType` """ - ptr = ffi.wasmtime_global_type(store._context, byref(self._global)) + ptr = ffi.wasmtime_global_type(store._context(), byref(self._global)) return GlobalType._from_ptr(ptr, None) def value(self, store: Storelike) -> IntoVal: @@ -42,7 +42,7 @@ def value(self, store: Storelike) -> IntoVal: Returns a native python type """ raw = ffi.wasmtime_val_t() - ffi.wasmtime_global_get(store._context, byref(self._global), byref(raw)) + ffi.wasmtime_global_get(store._context(), byref(self._global), byref(raw)) val = Val(raw) if val.value is not None: return val.value @@ -54,7 +54,7 @@ def set_value(self, store: Storelike, val: IntoVal) -> None: Sets the value of this global to a new value """ val = Val._convert(self.type(store).content, val) - error = ffi.wasmtime_global_set(store._context, byref(self._global), byref(val._unwrap_raw())) + error = ffi.wasmtime_global_set(store._context(), byref(self._global), byref(val._unwrap_raw())) if error: raise WasmtimeError._from_ptr(error) diff --git a/wasmtime/_instance.py b/wasmtime/_instance.py index 5354a2c9..c82d7aa5 100644 --- a/wasmtime/_instance.py +++ b/wasmtime/_instance.py @@ -30,11 +30,10 @@ def __init__(self, store: Storelike, module: Module, imports: Sequence[AsExtern] imports_ptr[i] = get_extern_ptr(val) instance = ffi.wasmtime_instance_t() - trap = POINTER(ffi.wasm_trap_t)() with enter_wasm(store) as trap: error = ffi.wasmtime_instance_new( - store._context, - module._ptr, + store._context(), + module.ptr(), imports_ptr, len(imports), byref(instance), @@ -80,7 +79,7 @@ def __init__(self, store: Storelike, instance: Instance): name_ptr = POINTER(ffi.c_char)() name_len = ffi.c_size_t(0) while ffi.wasmtime_instance_export_nth( - store._context, + store._context(), byref(instance._instance), i, byref(name_ptr), diff --git a/wasmtime/_linker.py b/wasmtime/_linker.py index 89585477..9ceacfd5 100644 --- a/wasmtime/_linker.py +++ b/wasmtime/_linker.py @@ -1,7 +1,8 @@ +import ctypes from ctypes import * from typing import Any from wasmtime import Instance, Engine, FuncType -from wasmtime import Module, WasmtimeError, Func +from wasmtime import Module, WasmtimeError, Func, Managed from . import _ffi as ffi from ._extern import get_extern_ptr, wrap_extern from ._config import setter_property @@ -11,7 +12,7 @@ from typing import Callable -class Linker: +class Linker(Managed["ctypes._Pointer[ffi.wasmtime_linker_t]"]): engine: Engine def __init__(self, engine: Engine): @@ -19,9 +20,12 @@ def __init__(self, engine: Engine): Creates a new linker ready to instantiate modules within the store provided. """ - self._ptr = ffi.wasmtime_linker_new(engine._ptr) + self._set_ptr(ffi.wasmtime_linker_new(engine.ptr())) self.engine = engine + def _delete(self, ptr: "ctypes._Pointer[ffi.wasmtime_linker_t]") -> None: + ffi.wasmtime_linker_delete(ptr) + @setter_property def allow_shadowing(self, allow: bool) -> None: """ @@ -30,7 +34,7 @@ def allow_shadowing(self, allow: bool) -> None: """ if not isinstance(allow, bool): raise TypeError("expected a boolean") - ffi.wasmtime_linker_allow_shadowing(self._ptr, allow) + ffi.wasmtime_linker_allow_shadowing(self.ptr(), allow) def define(self, store: Storelike, module: str, name: str, item: AsExtern) -> None: """ @@ -49,8 +53,8 @@ def define(self, store: Storelike, module: str, name: str, item: AsExtern) -> No name_bytes = name.encode('utf-8') name_buf = create_string_buffer(name_bytes) error = ffi.wasmtime_linker_define( - self._ptr, - store._context, + self.ptr(), + store._context(), module_buf, len(module_bytes), name_buf, @@ -76,12 +80,12 @@ def define_func(self, module: str, name: str, ty: FuncType, func: Callable[..., raise TypeError("expected a FuncType") idx = FUNCTIONS.allocate((func, ty.results, access_caller)) error = ffi.wasmtime_linker_define_func( - self._ptr, + self.ptr(), module_buf, len(module_bytes), name_buf, len(name_bytes), - ty._ptr, + ty.ptr(), trampoline, idx, finalize) @@ -103,8 +107,8 @@ def define_instance(self, store: Storelike, name: str, instance: Instance) -> No raise TypeError("expected an `Instance`") name_bytes = name.encode('utf8') name_buf = create_string_buffer(name_bytes) - error = ffi.wasmtime_linker_define_instance(self._ptr, - store._context, + error = ffi.wasmtime_linker_define_instance(self.ptr(), + store._context(), name_buf, len(name_bytes), byref(instance._instance)) @@ -122,7 +126,7 @@ def define_wasi(self) -> None: This function will raise an error if shadowing is disallowed and a name was previously defined. """ - error = ffi.wasmtime_linker_define_wasi(self._ptr) + error = ffi.wasmtime_linker_define_wasi(self.ptr()) if error: raise WasmtimeError._from_ptr(error) @@ -143,7 +147,7 @@ def define_module(self, store: Storelike, name: str, module: Module) -> None: raise TypeError("expected a `Module`") name_bytes = name.encode('utf-8') name_buf = create_string_buffer(name_bytes) - error = ffi.wasmtime_linker_module(self._ptr, store._context, name_buf, len(name_bytes), module._ptr) + error = ffi.wasmtime_linker_module(self.ptr(), store._context(), name_buf, len(name_bytes), module.ptr()) if error: raise WasmtimeError._from_ptr(error) @@ -162,7 +166,7 @@ def instantiate(self, store: Storelike, module: Module) -> Instance: instance = ffi.wasmtime_instance_t() with enter_wasm(store) as trap: error = ffi.wasmtime_linker_instantiate( - self._ptr, store._context, module._ptr, byref(instance), trap) + self.ptr(), store._context(), module.ptr(), byref(instance), trap) if error: raise WasmtimeError._from_ptr(error) return Instance._from_raw(instance) @@ -179,7 +183,7 @@ def get_default(self, store: Storelike, name: str) -> Func: name_bytes = name.encode('utf-8') name_buf = create_string_buffer(name_bytes) func = ffi.wasmtime_func_t() - error = ffi.wasmtime_linker_get_default(self._ptr, store._context, + error = ffi.wasmtime_linker_get_default(self.ptr(), store._context(), name_buf, len(name_bytes), byref(func)) if error: raise WasmtimeError._from_ptr(error) @@ -197,14 +201,10 @@ def get(self, store: Storelike, module: str, name: str) -> AsExtern: name_bytes = name.encode('utf-8') name_buf = create_string_buffer(name_bytes) item = ffi.wasmtime_extern_t() - ok = ffi.wasmtime_linker_get(self._ptr, store._context, + ok = ffi.wasmtime_linker_get(self.ptr(), store._context(), module_buf, len(module_bytes), name_buf, len(name_bytes), byref(item)) if ok: return wrap_extern(item) raise WasmtimeError("item not defined in linker") - - def __del__(self) -> None: - if hasattr(self, '_ptr'): - ffi.wasmtime_linker_delete(self._ptr) diff --git a/wasmtime/_managed.py b/wasmtime/_managed.py new file mode 100644 index 00000000..483101d1 --- /dev/null +++ b/wasmtime/_managed.py @@ -0,0 +1,95 @@ +from abc import abstractmethod +from typing import TypeVar, Generic, Any, Optional + +T = TypeVar('T') + +class Managed(Generic[T]): + """ + Abstract base class for types which contain an owned pointer in the C FFI + layer. + + Not exported directly from this package. + """ + __ptr: Optional[T] + + @abstractmethod + def _delete(self, ptr: T) -> None: + """ + Runs the FFI destructor for the `ptr` specified. + + Must be implemented by classes that inherit from this class. + """ + pass + + def _set_ptr(self, ptr: T) -> None: + if hasattr(self, '__ptr'): + raise ValueError('already initialized') + self.__ptr = ptr + + def close(self) -> None: + """ + Closes this object, or deallocates it. Further usage of this object + will raise a `ValueError`. + """ + + # Get the pointer's value but don't worry if it was never set. + try: + ptr = self.__ptr + except AttributeError: + return + + # If it wasn't previously deallocated then do so here, otherwise ignore + # this repeated call to `close` + if ptr is not None: + self._delete(ptr) + self.__ptr = None + + def ptr(self) -> T: + """ + Returns the underlying pointer for this FFI object, or a `ValueError` + if it's already been closed. + """ + + # Fail with a `ValueError` if the pointer was never set + try: + ptr = self.__ptr + except AttributeError: + raise ValueError('never initialized') + + # Check to see if this object is already deallocated, and if so raise + # a specific exception + if ptr is not None: + return ptr + else: + raise ValueError('already closed') + + def _consume(self) -> T: + """ + Internal method to take ownership of the internal pointer without + destroying it. + """ + ret = self.ptr() + self.__ptr = None + return ret + + def __enter__(self) -> Any: + """ + Entry part of the contextlib protocol to enable using this object with + `with`. + + Returns `self` to bind to use within a `with` block. + """ + return self + + def __exit__(self, *exc: Any) -> None: + """ + Exit part of the contextlib protocol to call `close`. + """ + self.close() + + def __del__(self) -> None: + """ + Automatic destruction of the internal FFI object if it's still alive by + this point. + """ + self.close() diff --git a/wasmtime/_memory.py b/wasmtime/_memory.py index c07ecbf9..7c8e03a0 100644 --- a/wasmtime/_memory.py +++ b/wasmtime/_memory.py @@ -15,7 +15,7 @@ def __init__(self, store: Storelike, ty: MemoryType): """ mem = ffi.wasmtime_memory_t() - error = ffi.wasmtime_memory_new(store._context, ty._ptr, byref(mem)) + error = ffi.wasmtime_memory_new(store._context(), ty.ptr(), byref(mem)) if error: raise WasmtimeError._from_ptr(error) self._memory = mem @@ -31,7 +31,7 @@ def type(self, store: Storelike) -> MemoryType: Gets the type of this memory as a `MemoryType` """ - ptr = ffi.wasmtime_memory_type(store._context, byref(self._memory)) + ptr = ffi.wasmtime_memory_type(store._context(), byref(self._memory)) return MemoryType._from_ptr(ptr, None) def grow(self, store: Storelike, delta: int) -> int: @@ -42,7 +42,7 @@ def grow(self, store: Storelike, delta: int) -> int: if delta < 0: raise WasmtimeError("cannot grow by negative amount") prev = ffi.c_uint64(0) - error = ffi.wasmtime_memory_grow(store._context, byref(self._memory), delta, byref(prev)) + error = ffi.wasmtime_memory_grow(store._context(), byref(self._memory), delta, byref(prev)) if error: raise WasmtimeError._from_ptr(error) return prev.value @@ -52,7 +52,7 @@ def size(self, store: Storelike) -> int: Returns the size, in WebAssembly pages, of this memory. """ - return ffi.wasmtime_memory_size(store._context, byref(self._memory)) + return ffi.wasmtime_memory_size(store._context(), byref(self._memory)) def data_ptr(self, store: Storelike) -> "ctypes._Pointer[c_ubyte]": """ @@ -61,7 +61,7 @@ def data_ptr(self, store: Storelike) -> "ctypes._Pointer[c_ubyte]": Remember that all accesses to wasm memory should be bounds-checked against the `data_len` method. """ - return ffi.wasmtime_memory_data(store._context, byref(self._memory)) + return ffi.wasmtime_memory_data(store._context(), byref(self._memory)) def get_buffer_ptr(self, store: Storelike, size: typing.Optional[int] = None, @@ -140,7 +140,7 @@ def data_len(self, store: Storelike) -> int: Returns the raw byte length of this memory. """ - return ffi.wasmtime_memory_data_size(store._context, byref(self._memory)) + return ffi.wasmtime_memory_data_size(store._context(), byref(self._memory)) def _as_extern(self) -> ffi.wasmtime_extern_t: union = ffi.wasmtime_extern_union(memory=self._memory) diff --git a/wasmtime/_module.py b/wasmtime/_module.py index ce8e5139..dbea124b 100644 --- a/wasmtime/_module.py +++ b/wasmtime/_module.py @@ -1,12 +1,13 @@ from . import _ffi as ffi from ctypes import * import ctypes -from wasmtime import Engine, wat2wasm, ImportType, ExportType, WasmtimeError +from wasmtime import Engine, wat2wasm, ImportType, ExportType, WasmtimeError, Managed import typing from os import PathLike -class Module: +class Module(Managed["ctypes._Pointer[ffi.wasmtime_module_t]"]): + @classmethod def from_file(cls, engine: Engine, path: typing.Union[str, bytes, PathLike]) -> "Module": """ @@ -19,6 +20,7 @@ def from_file(cls, engine: Engine, path: typing.Union[str, bytes, PathLike]) -> return cls(engine, contents) def __init__(self, engine: Engine, wasm: typing.Union[str, bytes]): + if not isinstance(engine, Engine): raise TypeError("expected an Engine") @@ -38,17 +40,20 @@ def __init__(self, engine: Engine, wasm: typing.Union[str, bytes]): # figure this out. binary = (c_uint8 * len(wasm)).from_buffer_copy(wasm) ptr = POINTER(ffi.wasmtime_module_t)() - error = ffi.wasmtime_module_new(engine._ptr, binary, len(wasm), byref(ptr)) + error = ffi.wasmtime_module_new(engine.ptr(), binary, len(wasm), byref(ptr)) if error: raise WasmtimeError._from_ptr(error) - self._ptr = ptr + self._set_ptr(ptr) + + def _delete(self, ptr: "ctypes._Pointer[ffi.wasmtime_module_t]") -> None: + ffi.wasmtime_module_delete(ptr) @classmethod def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasmtime_module_t]") -> "Module": - ty: "Module" = cls.__new__(cls) if not isinstance(ptr, POINTER(ffi.wasmtime_module_t)): raise TypeError("wrong pointer type") - ty._ptr = ptr + ty: "Module" = cls.__new__(cls) + ty._set_ptr(ptr) return ty @classmethod @@ -70,14 +75,14 @@ def deserialize(cls, engine: Engine, encoded: typing.Union[bytes, bytearray]) -> # TODO: can the copy be avoided here? I can't for the life of me # figure this out. error = ffi.wasmtime_module_deserialize( - engine._ptr, + engine.ptr(), (c_uint8 * len(encoded)).from_buffer_copy(encoded), len(encoded), byref(ptr)) if error: raise WasmtimeError._from_ptr(error) ret: "Module" = cls.__new__(cls) - ret._ptr = ptr + ret._set_ptr(ptr) return ret @classmethod @@ -92,13 +97,13 @@ def deserialize_file(cls, engine: Engine, path: str) -> 'Module': ptr = POINTER(ffi.wasmtime_module_t)() path_bytes = path.encode('utf-8') error = ffi.wasmtime_module_deserialize_file( - engine._ptr, + engine.ptr(), path_bytes, byref(ptr)) if error: raise WasmtimeError._from_ptr(error) ret: "Module" = cls.__new__(cls) - ret._ptr = ptr + ret._set_ptr(ptr) return ret @classmethod @@ -116,7 +121,7 @@ def validate(cls, engine: Engine, wasm: typing.Union[bytes, bytearray]) -> None: # TODO: can the copy be avoided here? I can't for the life of me # figure this out. buf = (c_uint8 * len(wasm)).from_buffer_copy(wasm) - error = ffi.wasmtime_module_validate(engine._ptr, buf, len(wasm)) + error = ffi.wasmtime_module_validate(engine.ptr(), buf, len(wasm)) if error: raise WasmtimeError._from_ptr(error) @@ -127,7 +132,7 @@ def validate(cls, engine: Engine, wasm: typing.Union[bytes, bytearray]) -> None: # Gets the type of this module as a `ModuleType` # """ -# ptr = ffi.wasmtime_module_type(self._ptr) +# ptr = ffi.wasmtime_module_type(self.ptr()) # return ModuleType._from_ptr(ptr, None) @property @@ -137,7 +142,7 @@ def imports(self) -> typing.List[ImportType]: """ imports = ImportTypeList() - ffi.wasmtime_module_imports(self._ptr, byref(imports.vec)) + ffi.wasmtime_module_imports(self.ptr(), byref(imports.vec)) ret = [] for i in range(0, imports.vec.size): ret.append(ImportType._from_ptr(imports.vec.data[i], imports)) @@ -150,7 +155,7 @@ def exports(self) -> typing.List[ExportType]: """ exports = ExportTypeList() - ffi.wasmtime_module_exports(self._ptr, byref(exports.vec)) + ffi.wasmtime_module_exports(self.ptr(), byref(exports.vec)) ret = [] for i in range(0, exports.vec.size): ret.append(ExportType._from_ptr(exports.vec.data[i], exports)) @@ -165,7 +170,7 @@ def serialize(self) -> bytearray: module. """ raw = ffi.wasm_byte_vec_t() - err = ffi.wasmtime_module_serialize(self._ptr, byref(raw)) + err = ffi.wasmtime_module_serialize(self.ptr(), byref(raw)) if err: raise WasmtimeError._from_ptr(err) ret = ffi.to_bytes(raw) @@ -173,13 +178,9 @@ def serialize(self) -> bytearray: return ret def _as_extern(self) -> ffi.wasmtime_extern_t: - union = ffi.wasmtime_extern_union(module=self._ptr) + union = ffi.wasmtime_extern_union(module=self.ptr()) return ffi.wasmtime_extern_t(ffi.WASMTIME_EXTERN_MODULE, union) - def __del__(self) -> None: - if hasattr(self, '_ptr'): - ffi.wasmtime_module_delete(self._ptr) - class ImportTypeList: def __init__(self) -> None: diff --git a/wasmtime/_store.py b/wasmtime/_store.py index 4f69d1ce..3092bfa1 100644 --- a/wasmtime/_store.py +++ b/wasmtime/_store.py @@ -1,7 +1,7 @@ from . import _ffi as ffi from ctypes import byref, c_uint64, cast, c_void_p, CFUNCTYPE import ctypes -from wasmtime import Engine, WasmtimeError +from wasmtime import Engine, WasmtimeError, Managed from . import _value as value import typing @@ -9,9 +9,8 @@ from ._wasi import WasiConfig -class Store: - _ptr: "ctypes._Pointer[ffi.wasmtime_store_t]" - _context: "ctypes._Pointer[ffi.wasmtime_context_t]" +class Store(Managed["ctypes._Pointer[ffi.wasmtime_store_t]"]): + __context: "ctypes._Pointer[ffi.wasmtime_context_t] | None" def __init__(self, engine: typing.Optional[Engine] = None, data: typing.Optional[typing.Any] = None): @@ -24,15 +23,24 @@ def __init__(self, engine: typing.Optional[Engine] = None, data: typing.Optional if data: data_id = value._intern(data) finalize = value._externref_finalizer - self._ptr = ffi.wasmtime_store_new(engine._ptr, data_id, finalize) - self._context = ffi.wasmtime_store_context(self._ptr) + self._set_ptr(ffi.wasmtime_store_new(engine.ptr(), data_id, finalize)) + self.__context = ffi.wasmtime_store_context(self.ptr()) self.engine = engine + def _delete(self, ptr: "ctypes._Pointer[ffi.wasmtime_store_t]") -> None: + ffi.wasmtime_store_delete(ptr) + self.__context = None + + def _context(self) -> "ctypes._Pointer[ffi.wasmtime_context_t]": + if self.__context is None: + raise ValueError('already closed') + return self.__context + def data(self) -> typing.Optional[typing.Any]: """ TODO """ - data = ffi.wasmtime_context_get_data(self._context) + data = ffi.wasmtime_context_get_data(self._context()) if data: return value._unintern(data) else: @@ -47,7 +55,7 @@ def gc(self) -> None: like more precise control over when unreferenced `externref` values are deallocated. """ - ffi.wasmtime_context_gc(self._context) + ffi.wasmtime_context_gc(self._context()) def set_fuel(self, fuel: int) -> None: """ @@ -61,7 +69,7 @@ def set_fuel(self, fuel: int) -> None: Raises a `WasmtimeError` if this store's configuration is not configured to consume fuel. """ - err = ffi.wasmtime_context_set_fuel(self._context, fuel) + err = ffi.wasmtime_context_set_fuel(self._context(), fuel) if err: raise WasmtimeError._from_ptr(err) @@ -75,7 +83,7 @@ def get_fuel(self) -> int: to consume fuel or if the store doesn't have enough fuel remaining. """ remaining = c_uint64(0) - err = ffi.wasmtime_context_get_fuel(self._context, byref(remaining)) + err = ffi.wasmtime_context_get_fuel(self._context(), byref(remaining)) if err: raise WasmtimeError._from_ptr(err) return remaining.value @@ -84,8 +92,7 @@ def set_wasi(self, wasi: "WasiConfig") -> None: """ TODO """ - error = ffi.wasmtime_context_set_wasi(self._context, wasi._ptr) - delattr(wasi, '_ptr') + error = ffi.wasmtime_context_set_wasi(self._context(), wasi._consume()) if error: raise WasmtimeError._from_ptr(error) @@ -94,7 +101,7 @@ def set_epoch_deadline(self, ticks_after_current: int) -> None: Configures the relative epoch deadline, after the current engine's epoch, after which WebAssembly code will trap. """ - ffi.wasmtime_context_set_epoch_deadline(self._context, ticks_after_current) + ffi.wasmtime_context_set_epoch_deadline(self._context(), ticks_after_current) def set_limits(self, memory_size: int = -1, @@ -127,11 +134,7 @@ def set_limits(self, If any limit is negative then the limit will not be set as a part of this invocation and it will be ignored. """ - ffi.wasmtime_store_limiter(self._ptr, memory_size, table_elements, instances, tables, memories) - - def __del__(self) -> None: - if hasattr(self, '_ptr'): - ffi.wasmtime_store_delete(self._ptr) + ffi.wasmtime_store_limiter(self.ptr(), memory_size, table_elements, instances, tables, memories) if typing.TYPE_CHECKING: diff --git a/wasmtime/_table.py b/wasmtime/_table.py index 0a63ad03..6a1620d7 100644 --- a/wasmtime/_table.py +++ b/wasmtime/_table.py @@ -16,7 +16,7 @@ def __init__(self, store: Store, ty: TableType, init: IntoVal): init_val = Val._convert(ty.element, init) table = ffi.wasmtime_table_t() - error = ffi.wasmtime_table_new(store._context, ty._ptr, byref(init_val._unwrap_raw()), byref(table)) + error = ffi.wasmtime_table_new(store._context(), ty.ptr(), byref(init_val._unwrap_raw()), byref(table)) if error: raise WasmtimeError._from_ptr(error) self._table = table @@ -32,14 +32,14 @@ def type(self, store: Storelike) -> TableType: Gets the type of this table as a `TableType` """ - ptr = ffi.wasmtime_table_type(store._context, byref(self._table)) + ptr = ffi.wasmtime_table_type(store._context(), byref(self._table)) return TableType._from_ptr(ptr, None) def size(self, store: Storelike) -> int: """ Gets the size, in elements, of this table """ - return ffi.wasmtime_table_size(store._context, byref(self._table)) + return ffi.wasmtime_table_size(store._context(), byref(self._table)) def grow(self, store: Storelike, amt: int, init: IntoVal) -> int: """ @@ -51,7 +51,7 @@ def grow(self, store: Storelike, amt: int, init: IntoVal) -> int: """ init_val = Val._convert(self.type(store).element, init) prev = c_uint32(0) - error = ffi.wasmtime_table_grow(store._context, byref(self._table), c_uint32(amt), byref(init_val._unwrap_raw()), byref(prev)) + error = ffi.wasmtime_table_grow(store._context(), byref(self._table), c_uint32(amt), byref(init_val._unwrap_raw()), byref(prev)) if error: raise WasmtimeError._from_ptr(error) return prev.value @@ -70,7 +70,7 @@ def get(self, store: Store, idx: int) -> Optional[Any]: Returns `None` if `idx` is out of bounds. """ raw = ffi.wasmtime_val_t() - ok = ffi.wasmtime_table_get(store._context, byref(self._table), idx, byref(raw)) + ok = ffi.wasmtime_table_get(store._context(), byref(self._table), idx, byref(raw)) if not ok: return None val = Val(raw) @@ -93,7 +93,7 @@ def set(self, store: Store, idx: int, val: IntoVal) -> None: Raises a `WasmtimeError` if `idx` is out of bounds. """ value = Val._convert(self.type(store).element, val) - error = ffi.wasmtime_table_set(store._context, byref(self._table), idx, byref(value._unwrap_raw())) + error = ffi.wasmtime_table_set(store._context(), byref(self._table), idx, byref(value._unwrap_raw())) if error: raise WasmtimeError._from_ptr(error) diff --git a/wasmtime/_trap.py b/wasmtime/_trap.py index 91d30df1..e8a29715 100644 --- a/wasmtime/_trap.py +++ b/wasmtime/_trap.py @@ -3,6 +3,7 @@ from ctypes import byref, POINTER import ctypes from typing import Optional, Any, List +from wasmtime import Managed class TrapCode(Enum): @@ -30,8 +31,7 @@ class TrapCode(Enum): INTERRUPT = 10 -class Trap(Exception): - _ptr: "ctypes._Pointer[ffi.wasm_trap_t]" +class Trap(Exception, Managed["ctypes._Pointer[ffi.wasm_trap_t]"]): def __init__(self, message: str): """ @@ -39,14 +39,17 @@ def __init__(self, message: str): """ vec = message.encode('utf-8') - self._ptr = ffi.wasmtime_trap_new(ffi.create_string_buffer(vec), len(vec)) + self._set_ptr(ffi.wasmtime_trap_new(ffi.create_string_buffer(vec), len(vec))) + + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_trap_t]") -> None: + ffi.wasm_trap_delete(ptr) @classmethod def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasm_trap_t]") -> "Trap": if not isinstance(ptr, POINTER(ffi.wasm_trap_t)): raise TypeError("wrong pointer type") trap: Trap = cls.__new__(cls) - trap._ptr = ptr + trap._set_ptr(ptr) return trap @property @@ -56,7 +59,7 @@ def message(self) -> str: """ message = ffi.wasm_byte_vec_t() - ffi.wasm_trap_message(self._ptr, byref(message)) + ffi.wasm_trap_message(self.ptr(), byref(message)) # subtract one to chop off the trailing nul byte message.size -= 1 ret = ffi.to_str(message) @@ -67,7 +70,7 @@ def message(self) -> str: @property def frames(self) -> List["Frame"]: frames = FrameList(self) - ffi.wasm_trap_trace(self._ptr, byref(frames.vec)) + ffi.wasm_trap_trace(self.ptr(), byref(frames.vec)) ret = [] for i in range(0, frames.vec.size): ret.append(Frame._from_ptr(frames.vec.data[i], frames)) @@ -83,38 +86,37 @@ def trap_code(self) -> Optional[TrapCode]: not have an associated code with them. """ code = ffi.wasmtime_trap_code_t() - if ffi.wasmtime_trap_code(self._ptr, byref(code)): + if ffi.wasmtime_trap_code(self.ptr(), byref(code)): return TrapCode(code.value) return None def __str__(self) -> str: return self.message - def __del__(self) -> None: - if hasattr(self, '_ptr'): - ffi.wasm_trap_delete(self._ptr) - -class Frame: - _ptr: "ctypes._Pointer[ffi.wasm_frame_t]" +class Frame(Managed["ctypes._Pointer[ffi.wasm_frame_t]"]): _owner: Optional[Any] @classmethod def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasm_frame_t]", owner: Optional[Any]) -> "Frame": - ty: "Frame" = cls.__new__(cls) if not isinstance(ptr, POINTER(ffi.wasm_frame_t)): raise TypeError("wrong pointer type") - ty._ptr = ptr + ty: "Frame" = cls.__new__(cls) + ty._set_ptr(ptr) ty._owner = owner return ty + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_frame_t]") -> None: + if self._owner is None: + ffi.wasm_frame_delete(ptr) + @property def func_index(self) -> int: """ Returns the function index this frame corresponds to in its wasm module """ - return ffi.wasm_frame_func_index(self._ptr) + return ffi.wasm_frame_func_index(self.ptr()) @property def func_name(self) -> Optional[str]: @@ -124,7 +126,7 @@ def func_name(self) -> Optional[str]: May return `None` if no name can be inferred """ - ptr = ffi.wasmtime_frame_func_name(self._ptr) + ptr = ffi.wasmtime_frame_func_name(self.ptr()) if ptr: return ffi.to_str(ptr.contents) else: @@ -138,7 +140,7 @@ def module_name(self) -> Optional[str]: May return `None` if no name can be inferred """ - ptr = ffi.wasmtime_frame_module_name(self._ptr) + ptr = ffi.wasmtime_frame_module_name(self.ptr()) if ptr: return ffi.to_str(ptr.contents) else: @@ -151,7 +153,7 @@ def module_offset(self) -> int: wasm source module. """ - return ffi.wasm_frame_module_offset(self._ptr) + return ffi.wasm_frame_module_offset(self.ptr()) @property def func_offset(self) -> int: @@ -160,11 +162,7 @@ def func_offset(self) -> int: wasm function. """ - return ffi.wasm_frame_func_offset(self._ptr) - - def __del__(self) -> None: - if self._owner is None: - ffi.wasm_frame_delete(self._ptr) + return ffi.wasm_frame_func_offset(self.ptr()) class FrameList: diff --git a/wasmtime/_types.py b/wasmtime/_types.py index cd81556e..6957d4bc 100644 --- a/wasmtime/_types.py +++ b/wasmtime/_types.py @@ -1,14 +1,19 @@ from . import _ffi as ffi -from wasmtime import WasmtimeError +from wasmtime import WasmtimeError, Managed import ctypes from ctypes import byref, POINTER from typing import Union, List, Optional, Any -class ValType: - _ptr: "ctypes._Pointer[ffi.wasm_valtype_t]" +class ValType(Managed["ctypes._Pointer[ffi.wasm_valtype_t]"]): _owner: Optional[Any] + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_valtype_t]") -> None: + # If this is owned by another object we don't free it since that object + # is responsible for freeing the backing memory. + if self._owner is None: + ffi.wasm_valtype_delete(ptr) + @classmethod def i32(cls) -> "ValType": ptr = ffi.wasm_valtype_new(ffi.WASM_I32) @@ -44,20 +49,18 @@ def __init__(self) -> None: @classmethod def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasm_valtype_t]", owner: Optional[Any]) -> "ValType": - ty: "ValType" = cls.__new__(cls) if not isinstance(ptr, POINTER(ffi.wasm_valtype_t)): raise TypeError("wrong pointer type") - ty._ptr = ptr + ty: "ValType" = cls.__new__(cls) + ty._set_ptr(ptr) ty._owner = owner return ty def __eq__(self, other: object) -> bool: if not isinstance(other, ValType): return False - assert(self._ptr is not None) - assert(other._ptr is not None) - kind1 = ffi.wasm_valtype_kind(self._ptr) - kind2 = ffi.wasm_valtype_kind(other._ptr) + kind1 = ffi.wasm_valtype_kind(self.ptr()) + kind2 = ffi.wasm_valtype_kind(other.ptr()) return kind1 == kind2 def __ne__(self, other: object) -> bool: @@ -67,8 +70,7 @@ def __repr__(self) -> str: return str(self) def __str__(self) -> str: - assert(self._ptr is not None) - kind = ffi.wasm_valtype_kind(self._ptr) + kind = ffi.wasm_valtype_kind(self.ptr()) if kind == ffi.WASM_I32.value: return 'i32' if kind == ffi.WASM_I64.value: @@ -83,14 +85,6 @@ def __str__(self) -> str: return 'funcref' return 'ValType(%d)' % kind.value - def __del__(self) -> None: - if not hasattr(self, '_owner') or not hasattr(self, '_ptr'): - return - # If this is owned by another object we don't free it since that object - # is responsible for freeing the backing memory. - if self._owner is None: - ffi.wasm_valtype_delete(self._ptr) - @classmethod def _from_list(cls, items: "ctypes._Pointer[ffi.wasm_valtype_vec_t]", owner: Optional[Any]) -> List["ValType"]: types = [] @@ -107,10 +101,10 @@ def take_owned_valtype(ty: ValType) -> "ctypes._Pointer[ffi.wasm_valtype_t]": # # Trying to expose this as an implementation detail by sneaking out # types and having some be "taken" feels pretty weird - return ffi.wasm_valtype_new(ffi.wasm_valtype_kind(ty._ptr)) + return ffi.wasm_valtype_new(ffi.wasm_valtype_kind(ty.ptr())) -class FuncType: +class FuncType(Managed["ctypes._Pointer[ffi.wasm_functype_t]"]): def __init__(self, params: List[ValType], results: List[ValType]): for param in params: if not isinstance(param, ValType): @@ -133,15 +127,19 @@ def __init__(self, params: List[ValType], results: List[ValType]): ptr = ffi.wasm_functype_new(byref(params_ffi), byref(results_ffi)) if not ptr: raise WasmtimeError("failed to allocate FuncType") - self._ptr = ptr + self._set_ptr(ptr) self._owner = None + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_functype_t]") -> None: + if self._owner is None: + ffi.wasm_functype_delete(ptr) + @classmethod def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasm_functype_t]", owner: Optional[Any]) -> "FuncType": - ty: "FuncType" = cls.__new__(cls) if not isinstance(ptr, POINTER(ffi.wasm_functype_t)): raise TypeError("wrong pointer type") - ty._ptr = ptr + ty: "FuncType" = cls.__new__(cls) + ty._set_ptr(ptr) ty._owner = owner return ty @@ -151,7 +149,7 @@ def params(self) -> List["ValType"]: Returns the list of parameter types for this function type """ - ptr = ffi.wasm_functype_params(self._ptr) + ptr = ffi.wasm_functype_params(self.ptr()) return ValType._from_list(ptr, self) @property @@ -160,18 +158,14 @@ def results(self) -> List["ValType"]: Returns the list of result types for this function type """ - ptr = ffi.wasm_functype_results(self._ptr) + ptr = ffi.wasm_functype_results(self.ptr()) return ValType._from_list(ptr, self) def _as_extern(self) -> "ctypes._Pointer[ffi.wasm_externtype_t]": - return ffi.wasm_functype_as_externtype_const(self._ptr) - - def __del__(self) -> None: - if hasattr(self, '_owner') and self._owner is None: - ffi.wasm_functype_delete(self._ptr) + return ffi.wasm_functype_as_externtype_const(self.ptr()) -class GlobalType: +class GlobalType(Managed["ctypes._Pointer[ffi.wasm_globaltype_t]"]): def __init__(self, valtype: ValType, mutable: bool): if mutable: mutability = ffi.WASM_VAR @@ -181,15 +175,19 @@ def __init__(self, valtype: ValType, mutable: bool): ptr = ffi.wasm_globaltype_new(type_ptr, mutability) if ptr == 0: raise WasmtimeError("failed to allocate GlobalType") - self._ptr = ptr + self._set_ptr(ptr) self._owner = None + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_globaltype_t]") -> None: + if self._owner is None: + ffi.wasm_globaltype_delete(ptr) + @classmethod def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasm_globaltype_t]", owner: Optional[Any]) -> "GlobalType": - ty: "GlobalType" = cls.__new__(cls) if not isinstance(ptr, POINTER(ffi.wasm_globaltype_t)): raise TypeError("wrong pointer type") - ty._ptr = ptr + ty: "GlobalType" = cls.__new__(cls) + ty._set_ptr(ptr) ty._owner = owner return ty @@ -199,7 +197,7 @@ def content(self) -> ValType: Returns the type this global contains """ - ptr = ffi.wasm_globaltype_content(self._ptr) + ptr = ffi.wasm_globaltype_content(self.ptr()) return ValType._from_ptr(ptr, self) @property @@ -207,15 +205,11 @@ def mutable(self) -> bool: """ Returns whether this global is mutable or not """ - val = ffi.wasm_globaltype_mutability(self._ptr) + val = ffi.wasm_globaltype_mutability(self.ptr()) return val == ffi.WASM_VAR.value def _as_extern(self) -> "ctypes._Pointer[ffi.wasm_externtype_t]": - return ffi.wasm_globaltype_as_externtype_const(self._ptr) - - def __del__(self) -> None: - if hasattr(self, '_owner') and self._owner is None: - ffi.wasm_globaltype_delete(self._ptr) + return ffi.wasm_globaltype_as_externtype_const(self.ptr()) class Limits: @@ -243,7 +237,7 @@ def _from_ffi(cls, val: 'ctypes._Pointer[ffi.wasm_limits_t]') -> "Limits": return Limits(min, max) -class TableType: +class TableType(Managed["ctypes._Pointer[ffi.wasm_tabletype_t]"]): def __init__(self, valtype: ValType, limits: Limits): if not isinstance(limits, Limits): raise TypeError("expected Limits") @@ -251,15 +245,19 @@ def __init__(self, valtype: ValType, limits: Limits): ptr = ffi.wasm_tabletype_new(type_ptr, byref(limits.__ffi__())) if not ptr: raise WasmtimeError("failed to allocate TableType") - self._ptr = ptr + self._set_ptr(ptr) self._owner = None + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_tabletype_t]") -> None: + if self._owner is None: + ffi.wasm_tabletype_delete(ptr) + @classmethod def _from_ptr(cls, ptr: 'ctypes._Pointer[ffi.wasm_tabletype_t]', owner: Optional[Any]) -> "TableType": ty: "TableType" = cls.__new__(cls) if not isinstance(ptr, POINTER(ffi.wasm_tabletype_t)): raise TypeError("wrong pointer type") - ty._ptr = ptr + ty._set_ptr(ptr) ty._owner = owner return ty @@ -268,7 +266,7 @@ def element(self) -> ValType: """ Returns the type of this table's elements """ - ptr = ffi.wasm_tabletype_element(self._ptr) + ptr = ffi.wasm_tabletype_element(self.ptr()) return ValType._from_ptr(ptr, self) @property @@ -276,18 +274,14 @@ def limits(self) -> Limits: """ Returns the limits on the size of thi stable """ - val = ffi.wasm_tabletype_limits(self._ptr) + val = ffi.wasm_tabletype_limits(self.ptr()) return Limits._from_ffi(val) def _as_extern(self) -> "ctypes._Pointer[ffi.wasm_externtype_t]": - return ffi.wasm_tabletype_as_externtype_const(self._ptr) - - def __del__(self) -> None: - if hasattr(self, '_owner') and self._owner is None: - ffi.wasm_tabletype_delete(self._ptr) + return ffi.wasm_tabletype_as_externtype_const(self.ptr()) -class MemoryType: +class MemoryType(Managed["ctypes._Pointer[ffi.wasm_memorytype_t]"]): def __init__(self, limits: Limits, is_64: bool = False): if not isinstance(limits, Limits): raise TypeError("expected Limits") @@ -305,15 +299,19 @@ def __init__(self, limits: Limits, is_64: bool = False): is_64) if not ptr: raise WasmtimeError("failed to allocate MemoryType") - self._ptr = ptr + self._set_ptr(ptr) self._owner = None + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_memorytype_t]") -> None: + if self._owner is None: + ffi.wasm_memorytype_delete(ptr) + @classmethod def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasm_memorytype_t]", owner: Optional[Any]) -> "MemoryType": - ty: "MemoryType" = cls.__new__(cls) if not isinstance(ptr, POINTER(ffi.wasm_memorytype_t)): raise TypeError("wrong pointer type") - ty._ptr = ptr + ty: "MemoryType" = cls.__new__(cls) + ty._set_ptr(ptr) ty._owner = owner return ty @@ -322,9 +320,9 @@ def limits(self) -> Limits: """ Returns the limits on the size of this table """ - minimum = ffi.wasmtime_memorytype_minimum(self._ptr) + minimum = ffi.wasmtime_memorytype_minimum(self.ptr()) maximum = ffi.c_uint64(0) - has_max = ffi.wasmtime_memorytype_maximum(self._ptr, byref(maximum)) + has_max = ffi.wasmtime_memorytype_maximum(self.ptr(), byref(maximum)) return Limits(minimum, maximum.value if has_max else None) @property @@ -332,14 +330,10 @@ def is_64(self) -> bool: """ Returns whether or not this is a 64-bit memory """ - return ffi.wasmtime_memorytype_is64(self._ptr) + return ffi.wasmtime_memorytype_is64(self.ptr()) def _as_extern(self) -> "ctypes._Pointer[ffi.wasm_externtype_t]": - return ffi.wasm_memorytype_as_externtype_const(self._ptr) - - def __del__(self) -> None: - if hasattr(self, '_owner') and self._owner is None: - ffi.wasm_memorytype_delete(self._ptr) + return ffi.wasm_memorytype_as_externtype_const(self.ptr()) def wrap_externtype(ptr: "ctypes._Pointer[ffi.wasm_externtype_t]", owner: Optional[Any]) -> "AsExternType": @@ -360,26 +354,29 @@ def wrap_externtype(ptr: "ctypes._Pointer[ffi.wasm_externtype_t]", owner: Option raise WasmtimeError("unknown extern type") -class ImportType: - _ptr: "ctypes._Pointer[ffi.wasm_importtype_t]" +class ImportType(Managed["ctypes._Pointer[ffi.wasm_importtype_t]"]): _owner: Optional[Any] @classmethod def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasm_importtype_t]", owner: Optional[Any]) -> "ImportType": - ty: "ImportType" = cls.__new__(cls) if not isinstance(ptr, POINTER(ffi.wasm_importtype_t)): raise TypeError("wrong pointer type") - ty._ptr = ptr + ty: "ImportType" = cls.__new__(cls) + ty._set_ptr(ptr) ty._owner = owner return ty + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_importtype_t]") -> None: + if self._owner is None: + ffi.wasm_importtype_delete(ptr) + @property def module(self) -> str: """ Returns the module this import type refers to """ - return ffi.to_str(ffi.wasm_importtype_module(self._ptr).contents) + return ffi.to_str(ffi.wasm_importtype_module(self.ptr()).contents) @property def name(self) -> Optional[str]: @@ -389,7 +386,7 @@ def name(self) -> Optional[str]: Note that `None` may be returned for the module linking proposal where the field name is optional. """ - ptr = ffi.wasm_importtype_name(self._ptr) + ptr = ffi.wasm_importtype_name(self.ptr()) if ptr: return ffi.to_str(ptr.contents) return None @@ -399,45 +396,41 @@ def type(self) -> "AsExternType": """ Returns the type that this import refers to """ - ptr = ffi.wasm_importtype_type(self._ptr) + ptr = ffi.wasm_importtype_type(self.ptr()) return wrap_externtype(ptr, self._owner or self) - def __del__(self) -> None: - if self._owner is None: - ffi.wasm_importtype_delete(self._ptr) - -class ExportType: +class ExportType(Managed["ctypes._Pointer[ffi.wasm_exporttype_t]"]): _ptr: "ctypes._Pointer[ffi.wasm_exporttype_t]" _owner: Optional[Any] @classmethod def _from_ptr(cls, ptr: 'ctypes._Pointer[ffi.wasm_exporttype_t]', owner: Optional[Any]) -> "ExportType": - ty: "ExportType" = cls.__new__(cls) if not isinstance(ptr, POINTER(ffi.wasm_exporttype_t)): raise TypeError("wrong pointer type") - ty._ptr = ptr + ty: "ExportType" = cls.__new__(cls) + ty._set_ptr(ptr) ty._owner = owner return ty + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_exporttype_t]") -> None: + if self._owner is None: + ffi.wasm_exporttype_delete(ptr) + @property def name(self) -> str: """ Returns the name in the modulethis export type refers to """ - return ffi.to_str(ffi.wasm_exporttype_name(self._ptr).contents) + return ffi.to_str(ffi.wasm_exporttype_name(self.ptr()).contents) @property def type(self) -> "AsExternType": """ Returns the type that this export refers to """ - ptr = ffi.wasm_exporttype_type(self._ptr) + ptr = ffi.wasm_exporttype_type(self.ptr()) return wrap_externtype(ptr, self._owner or self) - def __del__(self) -> None: - if self._owner is None: - ffi.wasm_exporttype_delete(self._ptr) - AsExternType = Union[FuncType, TableType, MemoryType, GlobalType] diff --git a/wasmtime/_wasi.py b/wasmtime/_wasi.py index 96b286d1..377d03a6 100644 --- a/wasmtime/_wasi.py +++ b/wasmtime/_wasi.py @@ -1,6 +1,6 @@ from ctypes import c_char, POINTER, cast, c_char_p, c_int import ctypes -from wasmtime import WasmtimeError +from wasmtime import WasmtimeError, Managed from . import _ffi as ffi from ._config import setter_property from typing import List, Iterable, Union @@ -17,9 +17,13 @@ def _encode_path(path: Union[str, bytes, PathLike]) -> bytes: return path2.encode('utf8') -class WasiConfig: +class WasiConfig(Managed["ctypes._Pointer[ffi.wasi_config_t]"]): + def __init__(self) -> None: - self._ptr = ffi.wasi_config_new() + self._set_ptr(ffi.wasi_config_new()) + + def _delete(self, ptr: "ctypes._Pointer[ffi.wasi_config_t]") -> None: + ffi.wasi_config_delete(ptr) @setter_property def argv(self, argv: List[str]) -> None: @@ -27,10 +31,10 @@ def argv(self, argv: List[str]) -> None: Explicitly configure the `argv` for this WASI configuration """ ptrs = to_char_array(argv) - ffi.wasi_config_set_argv(self._ptr, c_int(len(argv)), ptrs) + ffi.wasi_config_set_argv(self.ptr(), c_int(len(argv)), ptrs) def inherit_argv(self) -> None: - ffi.wasi_config_inherit_argv(self._ptr) + ffi.wasi_config_inherit_argv(self.ptr()) @setter_property def env(self, pairs: Iterable[Iterable]) -> None: @@ -48,7 +52,7 @@ def env(self, pairs: Iterable[Iterable]) -> None: values.append(value) name_ptrs = to_char_array(names) value_ptrs = to_char_array(values) - ffi.wasi_config_set_env(self._ptr, c_int( + ffi.wasi_config_set_env(self.ptr(), c_int( len(names)), name_ptrs, value_ptrs) def inherit_env(self) -> None: @@ -57,7 +61,7 @@ def inherit_env(self) -> None: in this own process's environment. All environment variables are inherited. """ - ffi.wasi_config_inherit_env(self._ptr) + ffi.wasi_config_inherit_env(self.ptr()) @setter_property def stdin_file(self, path: Union[str, bytes, PathLike]) -> None: @@ -72,7 +76,7 @@ def stdin_file(self, path: Union[str, bytes, PathLike]) -> None: """ res = ffi.wasi_config_set_stdin_file( - self._ptr, c_char_p(_encode_path(path))) + self.ptr(), c_char_p(_encode_path(path))) if not res: raise WasmtimeError("failed to set stdin file") @@ -83,7 +87,7 @@ def inherit_stdin(self) -> None: Reads of the stdin stream will read this process's stdin. """ - ffi.wasi_config_inherit_stdin(self._ptr) + ffi.wasi_config_inherit_stdin(self.ptr()) @setter_property def stdout_file(self, path: str) -> None: @@ -98,7 +102,7 @@ def stdout_file(self, path: str) -> None: cannot be opened for writing then `WasmtimeError` is raised. """ res = ffi.wasi_config_set_stdout_file( - self._ptr, c_char_p(_encode_path(path))) + self.ptr(), c_char_p(_encode_path(path))) if not res: raise WasmtimeError("failed to set stdout file") @@ -109,7 +113,7 @@ def inherit_stdout(self) -> None: Writes to stdout stream will write to this process's stdout. """ - ffi.wasi_config_inherit_stdout(self._ptr) + ffi.wasi_config_inherit_stdout(self.ptr()) @setter_property def stderr_file(self, path: str) -> None: @@ -124,7 +128,7 @@ def stderr_file(self, path: str) -> None: cannot be opened for writing then `WasmtimeError` is raised. """ res = ffi.wasi_config_set_stderr_file( - self._ptr, c_char_p(_encode_path(path))) + self.ptr(), c_char_p(_encode_path(path))) if not res: raise WasmtimeError("failed to set stderr file") @@ -135,16 +139,12 @@ def inherit_stderr(self) -> None: Writes to stderr stream will write to this process's stderr. """ - ffi.wasi_config_inherit_stderr(self._ptr) + ffi.wasi_config_inherit_stderr(self.ptr()) def preopen_dir(self, path: str, guest_path: str) -> None: path_ptr = c_char_p(path.encode('utf-8')) guest_path_ptr = c_char_p(guest_path.encode('utf-8')) - ffi.wasi_config_preopen_dir(self._ptr, path_ptr, guest_path_ptr) - - def __del__(self) -> None: - if hasattr(self, '_ptr'): - ffi.wasi_config_delete(self._ptr) + ffi.wasi_config_preopen_dir(self.ptr(), path_ptr, guest_path_ptr) def to_char_array(strings: List[str]) -> "ctypes._Pointer[ctypes._Pointer[c_char]]":