diff --git a/tests/test_config.py b/tests/test_config.py index e1dea137..80cd33ce 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -17,7 +17,16 @@ def test_smoke(self): config.wasm_multi_memory = True config.wasm_memory64 = True config.wasm_exceptions = True - config.cranelift_debug_verifier = True + config.wasm_component_model = True + config.wasm_component_model_map = True + config.wasm_function_references = True + config.wasm_wide_arithmetic = True + config.wasm_custom_page_sizes = True + config.wasm_gc = True + config.wasm_stack_switching = True + config.wasm_relaxed_simd = True + config.wasm_relaxed_simd_deterministic = True + config.strategy = "cranelift" config.strategy = "auto" config.cache = True @@ -32,11 +41,44 @@ def test_smoke(self): with self.assertRaises(WasmtimeError): config.cranelift_opt_level = "nonexistent-level" config.profiler = "none" + config.profiler = "jitdump" + config.profiler = "vtune" + config.profiler = "perfmap" with self.assertRaises(WasmtimeError): config.profiler = "nonexistent-profiler" + + config.cranelift_debug_verifier = True + config.cranelift_nan_canonicalization = True + config.cranelift_flag_enable("preserve_frame_pointers") + config.cranelift_flag_set("opt_level", "speed") + config.consume_fuel = True - config.wasm_relaxed_simd = True - config.wasm_relaxed_simd_deterministic = True + config.max_wasm_stack = 1024 * 1024 + config.gc_support = True + config.native_unwind_info = True + config.macos_use_mach_ports = True + config.signals_based_traps = True + + config.memory_may_move = True + config.memory_reservation = 1 << 20 + config.memory_guard_size = 1 << 16 + config.memory_reservation_for_growth = 1 << 20 + config.memory_init_cow = True + + def test_target(self): + config = Config() + # Setting the host target should always succeed + import platform + host_triple = None + if platform.machine() == "x86_64" and platform.system() == "Linux": + host_triple = "x86_64-unknown-linux-gnu" + elif platform.machine() == "aarch64" and platform.system() == "Linux": + host_triple = "aarch64-unknown-linux-gnu" + if host_triple is not None: + config.target = host_triple + # An invalid target should raise + with self.assertRaises(WasmtimeError): + config.target = "not-a-real-target" with closing(config) as config: pass diff --git a/tests/test_linker.py b/tests/test_linker.py index 7b914d44..9550b5de 100644 --- a/tests/test_linker.py +++ b/tests/test_linker.py @@ -159,3 +159,63 @@ def call(): assert(called['hits'] == 1) linker.instantiate(Store(engine), module) assert(called['hits'] == 2) + + def test_define_unknown_imports_as_traps(self): + engine = Engine() + linker = Linker(engine) + module = Module(engine, """ + (module + (import "env" "missing" (func)) + ) + """) + linker.define_unknown_imports_as_traps(module) + store = Store(engine) + instance = linker.instantiate(store, module) + self.assertIsNotNone(instance) + + with self.assertRaises(TypeError): + linker.define_unknown_imports_as_traps("not a module") # type: ignore + + def test_define_unknown_imports_as_default_values(self): + engine = Engine() + linker = Linker(engine) + module = Module(engine, """ + (module + (import "env" "missing" (func (result i32))) + ) + """) + store = Store(engine) + linker.define_unknown_imports_as_default_values(store, module) + instance = linker.instantiate(store, module) + self.assertIsNotNone(instance) + + with self.assertRaises(TypeError): + linker.define_unknown_imports_as_default_values(store, "not a module") # type: ignore + + def test_instantiate_pre(self): + engine = Engine() + linker = Linker(engine) + module = Module(engine, """ + (module + (func (export "f") (result i32) + i32.const 42 + ) + ) + """) + pre = linker.instantiate_pre(module) + self.assertIsNotNone(pre) + + store1 = Store(engine) + instance1 = pre.instantiate(store1) + f1 = instance1.exports(store1)["f"] + assert isinstance(f1, Func) + self.assertEqual(f1(store1), 42) + + store2 = Store(engine) + instance2 = pre.instantiate(store2) + f2 = instance2.exports(store2)["f"] + assert isinstance(f2, Func) + self.assertEqual(f2(store2), 42) + + with self.assertRaises(TypeError): + linker.instantiate_pre("not a module") # type: ignore diff --git a/tests/test_memory.py b/tests/test_memory.py index bc00d311..c6f4256b 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -104,3 +104,23 @@ def test_slices(self): self.assertEqual(memory.write(store, ba, -ba_size), ba_size) out = memory.read(store, -ba_size) self.assertEqual(ba, out) + + def test_page_size_default(self): + store = Store() + ty = MemoryType(Limits(1, None)) + self.assertEqual(ty.page_size, 65536) + self.assertEqual(ty.page_size_log2, 16) + memory = Memory(store, ty) + self.assertEqual(memory.page_size(store), 65536) + self.assertEqual(memory.page_size_log2(store), 16) + + def test_page_size_custom(self): + config = Config() + config.wasm_custom_page_sizes = True + store = Store(Engine(config)) + ty = MemoryType(Limits(1, None), page_size_log2=0) + self.assertEqual(ty.page_size, 1) + self.assertEqual(ty.page_size_log2, 0) + memory = Memory(store, ty) + self.assertEqual(memory.page_size(store), 1) + self.assertEqual(memory.page_size_log2(store), 0) diff --git a/tests/test_tag.py b/tests/test_tag.py new file mode 100644 index 00000000..de719f1c --- /dev/null +++ b/tests/test_tag.py @@ -0,0 +1,79 @@ +import unittest +from wasmtime import * + + +class TestTag(unittest.TestCase): + def test_new(self): + store = Store() + functype = FuncType([ValType.i32()], []) + tagtype = TagType(functype) + tag = Tag(store, tagtype) + self.assertIsNotNone(tag) + + def test_type(self): + functype = FuncType([ValType.i32()], []) + tagtype = TagType(functype) + + self.assertEqual(tagtype.functype.params, [ValType.i32()]) + self.assertEqual(tagtype.functype.results, []) + + config = Config() + config.wasm_exceptions = True + engine = Engine(config) + + module = Module(engine, """ + (module + (tag (export "mytag") (param i32)) + ) + """) + ty = module.exports[0].type + assert isinstance(ty, TagType) + self.assertEqual(ty.functype.params, [ValType.i32()]) + self.assertEqual(ty.functype.results, []) + + store = Store(engine) + tag = Tag(store, tagtype) + retrieved = tag.type(store) + self.assertIsInstance(retrieved, TagType) + + def test_eq(self): + store = Store() + functype = FuncType([ValType.i32()], []) + tagtype = TagType(functype) + tag1 = Tag(store, tagtype) + tag2 = Tag(store, tagtype) + self.assertFalse(tag1.eq(store, tag2)) + self.assertTrue(tag1.eq(store, tag1)) + + def test_wrong_type(self): + store = Store() + with self.assertRaises(TypeError): + Tag(store, "not a tagtype") # type: ignore + + def test_tag_import(self): + config = Config() + config.wasm_exceptions = True + engine = Engine(config) + + module = Module(engine, """ + (module (import "" "" (tag))) + """) + store = Store(engine) + with self.assertRaises(WasmtimeError): + Instance(store, module, []) + Instance(store, module, [Tag(store, TagType(FuncType([], [])))]) + + def test_tag_export(self): + config = Config() + config.wasm_exceptions = True + engine = Engine(config) + + module = Module(engine, """ + (module (tag (export ""))) + """) + store = Store(engine) + i = Instance(store, module, []) + tag = i.exports(store)[''] + assert isinstance(tag, Tag) + self.assertEqual(tag.type(store).functype.params, []) + self.assertEqual(tag.type(store).functype.results, []) diff --git a/wasmtime/__init__.py b/wasmtime/__init__.py index fc918ee4..d568631a 100644 --- a/wasmtime/__init__.py +++ b/wasmtime/__init__.py @@ -19,7 +19,7 @@ from ._engine import Engine from ._store import Store, Storelike, StoreContext from ._types import FuncType, GlobalType, MemoryType, TableType -from ._types import ValType, Limits, ImportType, ExportType +from ._types import ValType, Limits, ImportType, ExportType, TagType from ._wat2wasm import wat2wasm from ._module import Module from ._value import Val @@ -31,6 +31,8 @@ from ._instance import Instance from ._wasi import WasiConfig, FilePerms, DirPerms from ._linker import Linker +from ._tag import Tag +from ._instance_pre import InstancePre from ._sharedmemory import SharedMemory __all__ = [ @@ -65,4 +67,7 @@ 'Linker', 'WasmtimeError', 'StoreContext', + 'TagType', + 'Tag', + 'InstancePre', ] diff --git a/wasmtime/_config.py b/wasmtime/_config.py index cc51cb4d..9f77c6ee 100644 --- a/wasmtime/_config.py +++ b/wasmtime/_config.py @@ -159,6 +159,25 @@ def wasm_relaxed_simd_deterministic(self, enable: bool) -> None: raise TypeError('expected a bool') ffi.wasmtime_config_wasm_relaxed_simd_deterministic_set(self.ptr(), enable) + @setter_property + def wasm_component_model(self, enable: bool) -> None: + """ + Configures whether the WebAssembly component model proposal is enabled. + """ + if not isinstance(enable, bool): + raise TypeError("expected a bool") + ffi.wasmtime_config_wasm_component_model_set(self.ptr(), enable) + + @setter_property + def wasm_component_model_map(self, enable: bool) -> None: + """ + Configures whether the WebAssembly component model map types proposal + is enabled. + """ + if not isinstance(enable, bool): + raise TypeError("expected a bool") + ffi.wasmtime_config_wasm_component_model_map_set(self.ptr(), enable) + @setter_property def wasm_exceptions(self, enable: bool) -> None: """ @@ -171,6 +190,62 @@ def wasm_exceptions(self, enable: bool) -> None: raise TypeError('expected a bool') ffi.wasmtime_config_wasm_exceptions_set(self.ptr(), enable) + @setter_property + def wasm_function_references(self, enable: bool) -> None: + """ + Configures whether the wasm [typed function references proposal] is + enabled. + + [typed function references proposal]: https://github.com/WebAssembly/function-references + """ + if not isinstance(enable, bool): + raise TypeError('expected a bool') + ffi.wasmtime_config_wasm_function_references_set(self.ptr(), enable) + + @setter_property + def wasm_gc(self, enable: bool) -> None: + """ + Configures whether the wasm [GC proposal] is enabled. + + [GC proposal]: https://github.com/WebAssembly/gc + """ + if not isinstance(enable, bool): + raise TypeError('expected a bool') + ffi.wasmtime_config_wasm_gc_set(self.ptr(), enable) + + @setter_property + def wasm_wide_arithmetic(self, enable: bool) -> None: + """ + Configures whether the wasm [wide arithmetic proposal] is enabled. + + [wide arithmetic proposal]: https://github.com/WebAssembly/wide-arithmetic + """ + if not isinstance(enable, bool): + raise TypeError('expected a bool') + ffi.wasmtime_config_wasm_wide_arithmetic_set(self.ptr(), enable) + + @setter_property + def wasm_custom_page_sizes(self, enable: bool) -> None: + """ + Configures whether the wasm [custom-page-sizes proposal] is enabled. + + [custom-page-sizes proposal]: https://github.com/WebAssembly/custom-page-sizes + """ + if not isinstance(enable, bool): + raise TypeError('expected a bool') + ffi.wasmtime_config_wasm_custom_page_sizes_set(self.ptr(), enable) + + @setter_property + def wasm_stack_switching(self, enable: bool) -> None: + """ + Configures whether the wasm [stack switching proposal] is enabled. + + [stack switching proposal]: https://github.com/WebAssembly/stack-switching + """ + if not isinstance(enable, bool): + raise TypeError('expected a bool') + ffi.wasmtime_config_wasm_stack_switching_set(self.ptr(), enable) + @setter_property def strategy(self, strategy: str) -> None: """ @@ -208,10 +283,24 @@ def cranelift_opt_level(self, opt_level: str) -> None: @setter_property def profiler(self, profiler: str) -> None: + """ + Configures the profiling strategy used for JIT code. + + Acceptable values for `profiler` are: + + * `"none"` + * `"jitdump"` + * `"vtune"` + * `"perfmap"` + """ if profiler == "none": ffi.wasmtime_config_profiler_set(self.ptr(), 0) elif profiler == "jitdump": ffi.wasmtime_config_profiler_set(self.ptr(), 1) + elif profiler == "vtune": + ffi.wasmtime_config_profiler_set(self.ptr(), 2) + elif profiler == "perfmap": + ffi.wasmtime_config_profiler_set(self.ptr(), 3) else: raise WasmtimeError("unknown profiler: " + str(profiler)) @@ -289,3 +378,191 @@ def shared_memory(self, enable: bool) -> None: if not isinstance(enable, bool): raise TypeError('expected a bool') ffi.wasmtime_config_shared_memory_set(self.ptr(), enable) + + @setter_property + def max_wasm_stack(self, size: int) -> None: + """ + Configures the maximum stack size, in bytes, that JIT code can use. + + This defaults to 2MB. Configuring this can help if you hit stack + overflow or want to limit wasm stack usage. + + Note that if this limit is set too high then the OS's stack guards may + be hit which will result in an uncaught segfault. This limit can only + be set to a size that's smaller than the actual OS stack, and that's not + something able to be dynamically determined, so it's the responsibility + of embedders to uphold this invariant. + """ + if not isinstance(size, int): + raise TypeError('expected an int') + ffi.wasmtime_config_max_wasm_stack_set(self.ptr(), size) + + @setter_property + def gc_support(self, enable: bool) -> None: + """ + Enables or disables GC support in Wasmtime entirely. + + This defaults to `True`. + """ + if not isinstance(enable, bool): + raise TypeError('expected a bool') + ffi.wasmtime_config_gc_support_set(self.ptr(), enable) + + @setter_property + def cranelift_nan_canonicalization(self, enable: bool) -> None: + """ + Configures whether Cranelift should perform a NaN-canonicalization pass. + + This replaces NaNs with a single canonical value for fully deterministic + WebAssembly execution. Not required by the spec; disabled by default. + """ + if not isinstance(enable, bool): + raise TypeError('expected a bool') + ffi.wasmtime_config_cranelift_nan_canonicalization_set(self.ptr(), enable) + + @setter_property + def memory_may_move(self, enable: bool) -> None: + """ + Configures whether `memory_reservation` is the maximal size of linear + memory (disabling movement) or whether linear memories may be moved to + a new location when they need to grow. + """ + if not isinstance(enable, bool): + raise TypeError('expected a bool') + ffi.wasmtime_config_memory_may_move_set(self.ptr(), enable) + + @setter_property + def memory_reservation(self, size: int) -> None: + """ + Configures the initial memory reservation size, in bytes, for linear + memories. + + For more information see the Rust documentation at + https://bytecodealliance.github.io/wasmtime/api/wasmtime/struct.Config.html#method.memory_reservation + """ + if not isinstance(size, int): + raise TypeError('expected an int') + ffi.wasmtime_config_memory_reservation_set(self.ptr(), size) + + @setter_property + def memory_guard_size(self, size: int) -> None: + """ + Configures the guard region size, in bytes, for linear memory. + + For more information see the Rust documentation at + https://bytecodealliance.github.io/wasmtime/api/wasmtime/struct.Config.html#method.memory_guard_size + """ + if not isinstance(size, int): + raise TypeError('expected an int') + ffi.wasmtime_config_memory_guard_size_set(self.ptr(), size) + + @setter_property + def memory_reservation_for_growth(self, size: int) -> None: + """ + Configures the size, in bytes, of extra virtual memory reserved for + memories to grow into after being relocated. + + For more information see the Rust documentation at + https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.memory_reservation_for_growth + """ + if not isinstance(size, int): + raise TypeError('expected an int') + ffi.wasmtime_config_memory_reservation_for_growth_set(self.ptr(), size) + + @setter_property + def native_unwind_info(self, enable: bool) -> None: + """ + Configures whether to generate native unwind information (e.g. + `.eh_frame` on Linux). + + This defaults to `True`. + """ + if not isinstance(enable, bool): + raise TypeError('expected a bool') + ffi.wasmtime_config_native_unwind_info_set(self.ptr(), enable) + + @setter_property + def target(self, triple: str) -> None: + """ + Configures the target triple that this configuration will produce + machine code for. + + Defaults to the native host. Setting this also disables automatic + inference of native CPU features. + + Raises a `WasmtimeError` if the target triple is not recognized. + + Note that if this is set to something other than the host then an + `Engine` created won't be able to run generated code, but it can still + be used to compile code. + """ + if not isinstance(triple, str): + raise TypeError('expected a str') + error = ffi.wasmtime_config_target_set(self.ptr(), + ctypes.c_char_p(triple.encode('utf-8'))) + if error: + raise WasmtimeError._from_ptr(error) + + def cranelift_flag_enable(self, flag: str) -> None: + """ + Enables a target-specific flag in Cranelift. + + This can be used to enable CPU features such as SSE4.2 on x86_64 + hosts. Available flags can be explored with `wasmtime settings`. + """ + if not isinstance(flag, str): + raise TypeError('expected a str') + ffi.wasmtime_config_cranelift_flag_enable(self.ptr(), + ctypes.c_char_p(flag.encode('utf-8'))) + + def cranelift_flag_set(self, key: str, value: str) -> None: + """ + Sets a target-specific flag in Cranelift to the specified value. + + This can be used to configure CPU features such as SSE4.2 on x86_64 + hosts. Available flags can be explored with `wasmtime settings`. + """ + if not isinstance(key, str): + raise TypeError('expected a str for key') + if not isinstance(value, str): + raise TypeError('expected a str for value') + ffi.wasmtime_config_cranelift_flag_set(self.ptr(), + ctypes.c_char_p(key.encode('utf-8')), + ctypes.c_char_p(value.encode('utf-8'))) + + @setter_property + def macos_use_mach_ports(self, enable: bool) -> None: + """ + Configures whether Mach ports are used for exception handling on macOS + instead of traditional Unix signal handling. + + This defaults to `True` on macOS. + """ + if not isinstance(enable, bool): + raise TypeError('expected a bool') + ffi.wasmtime_config_macos_use_mach_ports_set(self.ptr(), enable) + + @setter_property + def signals_based_traps(self, enable: bool) -> None: + """ + Configures whether signals-based trap handlers are enabled (e.g. + `SIGILL` and `SIGSEGV` on Unix platforms). + + This defaults to `True`. + """ + if not isinstance(enable, bool): + raise TypeError('expected a bool') + ffi.wasmtime_config_signals_based_traps_set(self.ptr(), enable) + + @setter_property + def memory_init_cow(self, enable: bool) -> None: + """ + Configures whether copy-on-write memory-mapped data is used to + initialize linear memory. + + This can significantly improve instantiation performance. Defaults + to `True`. + """ + if not isinstance(enable, bool): + raise TypeError('expected a bool') + ffi.wasmtime_config_memory_init_cow_set(self.ptr(), enable) diff --git a/wasmtime/_exportable.py b/wasmtime/_exportable.py index b6854d06..f145b3c3 100644 --- a/wasmtime/_exportable.py +++ b/wasmtime/_exportable.py @@ -6,5 +6,6 @@ from ._memory import Memory from ._sharedmemory import SharedMemory from ._table import Table + from ._tag import Tag -AsExtern = typing.Union["Func", "Table", "Memory", "SharedMemory", "Global"] +AsExtern = typing.Union["Func", "Table", "Memory", "SharedMemory", "Global", "Tag"] diff --git a/wasmtime/_extern.py b/wasmtime/_extern.py index 16f4d362..1fe9684a 100644 --- a/wasmtime/_extern.py +++ b/wasmtime/_extern.py @@ -5,7 +5,7 @@ def wrap_extern(ptr: ffi.wasmtime_extern_t) -> AsExtern: - from wasmtime import Func, Table, Global, Memory, SharedMemory + from wasmtime import Func, Table, Global, Memory, SharedMemory, Tag if ptr.kind == ffi.WASMTIME_EXTERN_FUNC.value: return Func._from_raw(ptr.of.func) @@ -17,11 +17,13 @@ def wrap_extern(ptr: ffi.wasmtime_extern_t) -> AsExtern: return Memory._from_raw(ptr.of.memory) if ptr.kind == ffi.WASMTIME_EXTERN_SHAREDMEMORY.value: return SharedMemory._from_ptr(ptr.of.sharedmemory) + if ptr.kind == ffi.WASMTIME_EXTERN_TAG.value: + return Tag._from_raw(ptr.of.tag) raise WasmtimeError("unknown extern") def get_extern_ptr(item: AsExtern) -> ffi.wasmtime_extern_t: - from wasmtime import Func, Table, Global, Memory, SharedMemory + from wasmtime import Func, Table, Global, Memory, SharedMemory, Tag if isinstance(item, Func): return item._as_extern() @@ -31,10 +33,12 @@ def get_extern_ptr(item: AsExtern) -> ffi.wasmtime_extern_t: return item._as_extern() elif isinstance(item, SharedMemory): return item._as_extern() + elif isinstance(item, Tag): + return item._as_extern() elif isinstance(item, Table): return item._as_extern() else: - raise TypeError("expected a Func, Global, Memory, or Table") + raise TypeError("expected a Func, Global, Memory, SharedMemory, Tag, or Table") class Extern(Managed["ctypes._Pointer[ffi.wasm_extern_t]"]): diff --git a/wasmtime/_ffi.py b/wasmtime/_ffi.py index 2f277b38..c24a7477 100644 --- a/wasmtime/_ffi.py +++ b/wasmtime/_ffi.py @@ -61,6 +61,7 @@ WASMTIME_EXTERN_TABLE = c_uint8(2) WASMTIME_EXTERN_MEMORY = c_uint8(3) WASMTIME_EXTERN_SHAREDMEMORY = c_uint8(4) +WASMTIME_EXTERN_TAG = c_uint8(5) WASMTIME_FUNCREF_NULL = (1 << 64) - 1 diff --git a/wasmtime/_instance_pre.py b/wasmtime/_instance_pre.py new file mode 100644 index 00000000..44dd25f6 --- /dev/null +++ b/wasmtime/_instance_pre.py @@ -0,0 +1,55 @@ +import ctypes +from . import _ffi as ffi +from ._managed import Managed +from wasmtime import WasmtimeError +from ._store import Storelike +from ._module import Module +from ._instance import Instance +from ._func import enter_wasm + + +class InstancePre(Managed["ctypes._Pointer[ffi.wasmtime_instance_pre_t]"]): + """ + A pre-instantiated module with all imports already satisfied. + + `InstancePre` allows you to skip the import-resolution step on every call + to `instantiate`, which can improve performance when creating many + instances of the same module. + + Created via `Linker.instantiate_pre`. + """ + + def _delete(self, ptr: "ctypes._Pointer[ffi.wasmtime_instance_pre_t]") -> None: + ffi.wasmtime_instance_pre_delete(ptr) + + def instantiate(self, store: Storelike) -> Instance: + """ + Instantiates the pre-linked module in the given store. + + Raises a `WasmtimeError` on error or a `wasmtime.Trap` if a trap occurs + during instantiation. + """ + instance = ffi.wasmtime_instance_t() + with enter_wasm(store) as trap: + error = ffi.wasmtime_instance_pre_instantiate( + self.ptr(), store._context(), ctypes.byref(instance), trap) + if error: + raise WasmtimeError._from_ptr(error) + return Instance._from_raw(instance) + + @property + def module(self) -> Module: + """ + Returns the `Module` that this `InstancePre` was + created from. + """ + ptr = ffi.wasmtime_instance_pre_module(self.ptr()) + return Module._from_ptr(ptr) + + @classmethod + def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasmtime_instance_pre_t]") -> 'InstancePre': + if not isinstance(ptr, ctypes.POINTER(ffi.wasmtime_instance_pre_t)): + raise TypeError("wrong pointer type") + pre: InstancePre = cls.__new__(cls) + pre._set_ptr(ptr) + return pre diff --git a/wasmtime/_linker.py b/wasmtime/_linker.py index cfb4c5ea..e2ee5853 100644 --- a/wasmtime/_linker.py +++ b/wasmtime/_linker.py @@ -9,6 +9,7 @@ from ._store import Storelike from ._func import enter_wasm, trampoline, FUNCTIONS, finalize from typing import Callable +from ._instance_pre import InstancePre class Linker(Managed["ctypes._Pointer[ffi.wasmtime_linker_t]"]): @@ -205,3 +206,55 @@ def get(self, store: Storelike, module: str, name: str) -> AsExtern: if ok: return wrap_extern(item) raise WasmtimeError("item not defined in linker") + + def define_unknown_imports_as_traps(self, module: Module) -> None: + """ + Defines all currently-unknown imports of `module` as functions that + unconditionally trap when called. + + This is useful when you want to instantiate a module whose imports + aren't fully known yet and are willing to trap on any call to an + unresolved import. + """ + if not isinstance(module, Module): + raise TypeError("expected a `Module`") + error = ffi.wasmtime_linker_define_unknown_imports_as_traps( + self.ptr(), module.ptr()) + if error: + raise WasmtimeError._from_ptr(error) + + def define_unknown_imports_as_default_values(self, store: Storelike, module: Module) -> None: + """ + Defines all currently-unknown imports of `module` as default values + (zero for numerics, null for references, etc.). + + This is similar to `define_unknown_imports_as_traps` but instead + of trapping the imported functions return appropriate zero/default + values. + """ + if not isinstance(module, Module): + raise TypeError("expected a `Module`") + error = ffi.wasmtime_linker_define_unknown_imports_as_default_values( + self.ptr(), store._context(), module.ptr()) + if error: + raise WasmtimeError._from_ptr(error) + + def instantiate_pre(self, module: Module) -> InstancePre: + """ + Pre-instantiates `module` by resolving all of its imports from the + definitions in this linker. + + Returns an `InstancePre` that can be used to cheaply + create multiple instances of the same module without repeating import + resolution. + + Raises `WasmtimeError` if any imports are unresolvable. + """ + if not isinstance(module, Module): + raise TypeError("expected a `Module`") + ptr = ctypes.POINTER(ffi.wasmtime_instance_pre_t)() + error = ffi.wasmtime_linker_instantiate_pre( + self.ptr(), module.ptr(), ctypes.byref(ptr)) + if error: + raise WasmtimeError._from_ptr(error) + return InstancePre._from_ptr(ptr) diff --git a/wasmtime/_memory.py b/wasmtime/_memory.py index 3f9073af..d2a3c7bc 100644 --- a/wasmtime/_memory.py +++ b/wasmtime/_memory.py @@ -141,6 +141,23 @@ def data_len(self, store: Storelike) -> int: return ffi.wasmtime_memory_data_size(store._context(), ctypes.byref(self._memory)) + def page_size(self, store: Storelike) -> int: + """ + Returns the page size, in bytes, for this memory. + + WebAssembly memories are made up of a whole number of pages, so the + byte size is always a multiple of the page size. Defaults to 65536 + (64 KiB). The custom-page-sizes proposal allows opting into a page + size of 1. + """ + return ffi.wasmtime_memory_page_size(store._context(), ctypes.byref(self._memory)) + + def page_size_log2(self, store: Storelike) -> int: + """ + Returns the log2 of this memory's page size, in bytes. + """ + return int(ffi.wasmtime_memory_page_size_log2(store._context(), ctypes.byref(self._memory))) + def _as_extern(self) -> ffi.wasmtime_extern_t: union = ffi.wasmtime_extern_union(memory=self._memory) return ffi.wasmtime_extern_t(ffi.WASMTIME_EXTERN_MEMORY, union) diff --git a/wasmtime/_tag.py b/wasmtime/_tag.py new file mode 100644 index 00000000..69cd1ff5 --- /dev/null +++ b/wasmtime/_tag.py @@ -0,0 +1,56 @@ +import ctypes +from . import _ffi as ffi +from wasmtime import WasmtimeError +from typing import TYPE_CHECKING +from ._store import Storelike +from ._types import TagType + + +class Tag: + """ + Represents a WebAssembly tag, used to identify exception types. + + Tags are associated with a store and describe the payload signature + of exceptions that can be thrown and caught. + """ + + _tag: ffi.wasmtime_tag_t + + def __init__(self, store: Storelike, ty: TagType) -> None: + """ + Creates a new host-defined tag with the given tag type. + + Raises `WasmtimeError` if the tag cannot be created. + """ + if not isinstance(ty, TagType): + raise TypeError("expected a TagType") + tag = ffi.wasmtime_tag_t() + error = ffi.wasmtime_tag_new(store._context(), ty.ptr(), ctypes.byref(tag)) + if error: + raise WasmtimeError._from_ptr(error) + self._tag = tag + + @classmethod + def _from_raw(cls, tag: ffi.wasmtime_tag_t) -> "Tag": + obj = cls.__new__(cls) + obj._tag = tag + return obj + + def type(self, store: Storelike) -> TagType: + """ + Returns the type of this tag. + """ + ptr = ffi.wasmtime_tag_type(store._context(), ctypes.byref(self._tag)) + return TagType._from_ptr(ptr) + + def eq(self, store: Storelike, other: "Tag") -> bool: + """ + Tests whether two tags are identical (same definition). + """ + return bool(ffi.wasmtime_tag_eq(store._context(), + ctypes.byref(self._tag), + ctypes.byref(other._tag))) + + def _as_extern(self) -> ffi.wasmtime_extern_t: + union = ffi.wasmtime_extern_union(tag=self._tag) + return ffi.wasmtime_extern_t(ffi.WASMTIME_EXTERN_TAG, union) diff --git a/wasmtime/_trap.py b/wasmtime/_trap.py index 741bf8e6..41ac88f2 100644 --- a/wasmtime/_trap.py +++ b/wasmtime/_trap.py @@ -29,6 +29,76 @@ class TrapCode(Enum): UNREACHABLE = 9 # Execution has potentially run too long and may be interrupted. INTERRUPT = 10 + # Execution has run out of the configured fuel amount. + OUT_OF_FUEL = 11 + # Atomic wait on non-shared memory. + ATOMIC_WAIT_NON_SHARED_MEMORY = 12 + # Call to a null reference. + NULL_REFERENCE = 13 + # Attempt to access beyond the bounds of an array. + ARRAY_OUT_OF_BOUNDS = 14 + # Attempted an allocation that was too large to succeed. + ALLOCATION_TOO_LARGE = 15 + # Attempted to cast a reference to a type that it is not an instance of. + CAST_FAILURE = 16 + # A component tried to call another component in violation of the reentrance rules. + CANNOT_ENTER_COMPONENT = 17 + # Async-lifted export failed to produce a result before returning STATUS_DONE. + NO_ASYNC_RESULT = 18 + # Suspending to a tag for which there is no active handler. + UNHANDLED_TAG = 19 + # Attempt to resume a continuation twice. + CONTINUATION_ALREADY_CONSUMED = 20 + # A Pulley opcode was executed that was disabled at compile time. + DISABLED_OPCODE = 21 + # Async event loop deadlocked. + ASYNC_DEADLOCK = 22 + # A component tried to call an import when it was not allowed to. + CANNOT_LEAVE_COMPONENT = 23 + # A synchronous task attempted a potentially blocking call before returning. + CANNOT_BLOCK_SYNC_TASK = 24 + # A component tried to lift a char with an invalid bit pattern. + INVALID_CHAR = 25 + # Debug assertion: string encoding not finished. + DEBUG_ASSERT_STRING_ENCODING_FINISHED = 26 + # Debug assertion: equal code units. + DEBUG_ASSERT_EQUAL_CODE_UNITS = 27 + # Debug assertion: pointer aligned. + DEBUG_ASSERT_POINTER_ALIGNED = 28 + # Debug assertion: upper bits unset. + DEBUG_ASSERT_UPPER_BITS_UNSET = 29 + # A component tried to lift or lower a string past the end of its memory. + STRING_OUT_OF_BOUNDS = 30 + # A component tried to lift or lower a list past the end of its memory. + LIST_OUT_OF_BOUNDS = 31 + # A component used an invalid discriminant when lowering a variant value. + INVALID_DISCRIMINANT = 32 + # A component passed an unaligned pointer when lifting or lowering a value. + UNALIGNED_POINTER = 33 + # task.cancel invoked in an invalid way. + TASK_CANCEL_NOT_CANCELLED = 34 + # task.cancel or task.return called too many times. + TASK_CANCEL_OR_RETURN_TWICE = 35 + # subtask.cancel invoked after the subtask already finished. + SUBTASK_CANCEL_AFTER_TERMINAL = 36 + # task.return invoked with an invalid type. + TASK_RETURN_INVALID = 37 + # waitable-set.drop invoked on a waitable set with waiters. + WAITABLE_SET_DROP_HAS_WAITERS = 38 + # subtask.drop invoked on a subtask that hasn't resolved yet. + SUBTASK_DROP_NOT_RESOLVED = 39 + # thread.new-indirect invoked with a function that has an invalid type. + THREAD_NEW_INDIRECT_INVALID_TYPE = 40 + # thread.new-indirect invoked with an uninitialized function reference. + THREAD_NEW_INDIRECT_UNINITIALIZED = 41 + # Backpressure-related intrinsics overflowed the built-in counter. + BACKPRESSURE_OVERFLOW = 42 + # Invalid code returned from the callback of an async-lifted function. + UNSUPPORTED_CALLBACK_CODE = 43 + # Cannot resume a thread which is not suspended. + CANNOT_RESUME_THREAD = 44 + # Cannot issue a read/write on a future/stream while there is a pending operation. + CONCURRENT_FUTURE_STREAM_OP = 45 class Trap(Exception, Managed["ctypes._Pointer[ffi.wasm_trap_t]"]): diff --git a/wasmtime/_types.py b/wasmtime/_types.py index 018e1b49..07b6150a 100644 --- a/wasmtime/_types.py +++ b/wasmtime/_types.py @@ -346,6 +346,22 @@ def is_shared(self) -> bool: """ return ffi.wasmtime_memorytype_isshared(self.ptr()) + @property + def page_size(self) -> int: + """ + Returns the page size, in bytes, of this memory type. + + Defaults to 65536 (64 KiB). The custom-page-sizes proposal allows + opting into a page size of 1. + """ + return ffi.wasmtime_memorytype_page_size(self.ptr()) + + @property + def page_size_log2(self) -> int: + """ + Returns the log2 of this memory type's page size, in bytes. + """ + return int(ffi.wasmtime_memorytype_page_size_log2(self.ptr())) def _as_extern(self) -> "ctypes._Pointer[ffi.wasm_externtype_t]": return ffi.wasm_memorytype_as_externtype_const(self.ptr()) @@ -366,6 +382,9 @@ def wrap_externtype(ptr: "ctypes._Pointer[ffi.wasm_externtype_t]", owner: Option val = ffi.wasm_externtype_as_memorytype(ptr) if val: return MemoryType._from_ptr(val, owner) + val = ffi.wasm_externtype_as_tagtype(ptr) + if val: + return TagType._from_ptr(val, owner) raise WasmtimeError("unknown extern type") @@ -448,4 +467,49 @@ def type(self) -> "AsExternType": return wrap_externtype(ptr, self._owner or self) -AsExternType = Union[FuncType, TableType, MemoryType, GlobalType] +class TagType(Managed["ctypes._Pointer[ffi.wasm_tagtype_t]"]): + """ + Represents the type of a WebAssembly tag (used in exception handling and + stack-switching). + + A `TagType` wraps a function type that describes the payload of exceptions + of this tag or types for stack switching. + """ + _owner: Optional[Any] + + def __init__(self, functype: FuncType) -> None: + if not isinstance(functype, FuncType): + raise TypeError("expected a FuncType") + ptr = ffi.wasm_tagtype_new(functype._consume()) + if not ptr: + raise WasmtimeError("failed to allocate TagType") + self._set_ptr(ptr) + self._owner = None + + def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_tagtype_t]") -> None: + if self._owner is None: + ffi.wasm_tagtype_delete(ptr) + + @classmethod + def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasm_tagtype_t]", owner: Optional[Any] = None) -> "TagType": + if not isinstance(ptr, POINTER(ffi.wasm_tagtype_t)): + raise TypeError("wrong pointer type") + ty: "TagType" = cls.__new__(cls) + ty._set_ptr(ptr) + ty._owner = owner + return ty + + def ptr(self) -> "ctypes._Pointer[ffi.wasm_tagtype_t]": + return super().ptr() + + @property + def functype(self) -> FuncType: + """Returns the function type that describes the tag's payload.""" + ptr = ffi.wasm_tagtype_functype(self.ptr()) + return FuncType._from_ptr(ptr, self) + + def _as_extern(self) -> "ctypes._Pointer[ffi.wasm_externtype_t]": + return ffi.wasm_tagtype_as_externtype_const(self.ptr()) + + +AsExternType = Union[FuncType, TableType, MemoryType, GlobalType, TagType]