Coverage for wasmtime/component/_linker.py: 98%
143 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-12-01 19:40 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2025-12-01 19:40 +0000
1import ctypes
2from ctypes import POINTER, byref, create_string_buffer, CFUNCTYPE, c_void_p
3from .. import Managed, Engine, WasmtimeError, Module, _ffi as ffi, Storelike, StoreContext
4from .._config import setter_property
5from ._instance import Instance
6from ._component import Component
7from ._resource_type import ResourceType
8from typing import Union, Tuple, Callable, Optional, List, Any
9from .._slab import Slab
10from ._enter import catch_exceptions
11from ._types import ValType, FuncType
13LinkerInstanceParent = Union['Linker', 'LinkerInstance']
15ResourceDtor = Callable[[StoreContext, int], None]
16RESOURCE_DTORS: Slab[ResourceDtor] = Slab()
18UserFunc = Callable[..., Optional[Any]]
19FUNCTIONS: Slab[UserFunc] = Slab()
22class Linker(Managed["ctypes._Pointer[ffi.wasmtime_component_linker_t]"]):
23 engine: Engine
24 locked: bool
26 def __init__(self, engine: Engine):
27 """
28 Creates a new linker ready to instantiate modules within the store
29 provided.
30 """
31 self._set_ptr(ffi.wasmtime_component_linker_new(engine.ptr()))
32 self.engine = engine
33 self.locked = False
35 def _delete(self, ptr: "ctypes._Pointer[ffi.wasmtime_component_linker_t]") -> None:
36 self._assert_not_locked()
37 ffi.wasmtime_component_linker_delete(ptr)
39 @setter_property
40 def allow_shadowing(self, allow: bool) -> None:
41 """
42 Configures whether definitions are allowed to shadow one another within
43 this linker
44 """
45 self._assert_not_locked()
46 ffi.wasmtime_component_linker_allow_shadowing(self.ptr(), allow)
48 def _assert_not_locked(self) -> None:
49 if self.locked:
50 raise WasmtimeError("cannot use linker while it's in use by other instances")
52 def root(self) -> "LinkerInstance":
53 """
54 Returns the root instance used to defined new items in this linker.
56 While this root instance is alive this linker cannot be used for any
57 other operations. Until the returned object is destroyed this linker is
58 considered "locked".
60 It's recommended to bind the return value in a `with` block to
61 automatically dispose of it when it's done.
62 """
63 self._assert_not_locked()
64 ptr = ffi.wasmtime_component_linker_root(self.ptr())
65 return LinkerInstance._from_ptr(ptr, self)
67 def add_wasip2(self) -> None:
68 """
69 Adds the WASIp2 API definitions, from Wasmtime, in this linker.
70 """
71 self._assert_not_locked()
72 err = ffi.wasmtime_component_linker_add_wasip2(self.ptr())
73 if err:
74 raise WasmtimeError._from_ptr(err)
76 def instantiate(self, store: Storelike, component: Component) -> Instance:
77 """
78 Instantiates the given component using this linker within the provided
79 store.
81 Returns the instantiated `Instance` on success.
82 """
83 self._assert_not_locked()
84 instance = ffi.wasmtime_component_instance_t()
85 err = ffi.wasmtime_component_linker_instantiate(
86 self.ptr(),
87 store._context(),
88 component.ptr(),
89 byref(instance))
90 if err:
91 raise WasmtimeError._from_ptr(err)
92 return Instance._from_raw(instance)
94 def define_unknown_imports_as_traps(self, component: Component) -> None:
95 """
96 Configures this linker to define any unknown imports of `component` as
97 traps which will error when invoked.
98 """
99 self._assert_not_locked()
100 err = ffi.wasmtime_component_linker_define_unknown_imports_as_traps(
101 self.ptr(),
102 component.ptr())
103 if err:
104 raise WasmtimeError._from_ptr(err)
107class LinkerInstance(Managed["ctypes._Pointer[ffi.wasmtime_component_linker_instance_t]"]):
108 parent: Union[LinkerInstanceParent, None]
109 locked: bool
111 def __init__(self) -> None:
112 raise WasmtimeError("Cannot directly construct a `LinkerInstance`")
114 def _delete(self, ptr: "ctypes._Pointer[ffi.wasmtime_component_linker_instance_t]") -> None:
115 self._assert_not_locked()
116 ffi.wasmtime_component_linker_instance_delete(ptr)
117 assert(self.parent is not None)
118 assert(self.parent.locked)
119 self.parent.locked = False
120 self.parent = None
122 def _assert_not_locked(self) -> None:
123 if self.locked:
124 raise WasmtimeError("cannot use linker instance while it's in use by other instances")
126 @classmethod
127 def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasmtime_component_linker_instance_t]", parent: LinkerInstanceParent) -> "LinkerInstance":
128 if not isinstance(ptr, POINTER(ffi.wasmtime_component_linker_instance_t)):
129 raise TypeError("wrong pointer type")
130 ty: "LinkerInstance" = cls.__new__(cls)
131 ty._set_ptr(ptr)
132 ty.parent = parent
133 ty.locked = False
134 assert(not parent.locked)
135 parent.locked = True
136 return ty
139 def add_instance(self, name: str) -> "LinkerInstance":
140 """
141 Adds a new instance to this linker instance under the given name.
143 Returns a new `LinkerInstance` which can be used to define further
144 items.
146 While the returned instance is alive this linker instance cannot be
147 used for other operations. Until the returned object is destroyed this
148 linker instance is considered "locked".
150 It's recommended to bind the return value in a `with` block to
151 automatically dispose of it when it's done.
152 """
153 self._assert_not_locked()
154 name_bytes = name.encode('utf-8')
155 name_buf = create_string_buffer(name_bytes)
156 ptr = POINTER(ffi.wasmtime_component_linker_instance_t)()
157 err = ffi.wasmtime_component_linker_instance_add_instance(self.ptr(),
158 name_buf,
159 len(name_bytes),
160 byref(ptr))
161 if err:
162 raise WasmtimeError._from_ptr(err)
163 return LinkerInstance._from_ptr(ptr, self)
165 def add_module(self, name: str, module: Module) -> None:
166 """
167 Adds a new module to this linker instance under the given name.
168 """
169 self._assert_not_locked()
170 name_bytes = name.encode('utf-8')
171 name_buf = create_string_buffer(name_bytes)
172 err = ffi.wasmtime_component_linker_instance_add_module(self.ptr(),
173 name_buf,
174 len(name_bytes),
175 module.ptr())
176 if err:
177 raise WasmtimeError._from_ptr(err)
179 def add_resource(self, name: str, ty: ResourceType, dtor: ResourceDtor) -> None:
180 """
181 Defines a the resource type `ty` under `name`
183 When a value of this resource is destroyed then `dtor` is invoked.
184 """
185 self._assert_not_locked()
186 name_bytes = name.encode('utf-8')
187 name_buf = create_string_buffer(name_bytes)
188 idx = RESOURCE_DTORS.allocate(dtor)
189 err = ffi.wasmtime_component_linker_instance_add_resource(self.ptr(),
190 name_buf,
191 len(name_bytes),
192 ty.ptr(),
193 resource_dtor_impl,
194 idx,
195 resource_dtor_finalize)
196 if err:
197 raise WasmtimeError._from_ptr(err)
199 def add_func(self, name: str, func: UserFunc) -> None:
200 self._assert_not_locked()
201 name_bytes = name.encode('utf-8')
202 name_buf = create_string_buffer(name_bytes)
203 idx = FUNCTIONS.allocate(func)
204 err = ffi.wasmtime_component_linker_instance_add_func(self.ptr(),
205 name_buf,
206 len(name_bytes),
207 func_impl,
208 idx,
209 func_finalize)
210 if err:
211 raise WasmtimeError._from_ptr(err)
214@ffi.wasmtime_component_resource_destructor_t
215def resource_dtor_impl(idx, context, rep): # type: ignore
216 def run(store: StoreContext) -> None:
217 return RESOURCE_DTORS.get(idx or 0)(store, rep)
218 return catch_exceptions(context, run)
221@CFUNCTYPE(None, c_void_p)
222def resource_dtor_finalize(idx): # type: ignore
223 if RESOURCE_DTORS:
224 RESOURCE_DTORS.deallocate(idx or 0)
225 return None
228@ffi.wasmtime_component_func_callback_t
229def func_impl(idx, context, ty_raw, args, nargs, results, nresults): # type: ignore
230 def run(store: StoreContext) -> None:
231 with FuncType._from_ptr(ty_raw, owner=store) as fty:
232 func = FUNCTIONS.get(idx or 0)
233 param_tys = fty.params
234 result_ty = fty.result
235 assert(len(param_tys) == nargs)
236 if result_ty is None:
237 assert(nresults == 0)
238 else:
239 assert(nresults == 1)
241 pyargs = []
242 for (_, ty), i in zip(param_tys, range(nargs)):
243 val = ty.convert_from_c(args[i])
244 pyargs.append(val)
245 result = func(store, *pyargs)
247 if result_ty is None:
248 assert(result is None)
249 else:
250 result_ty.convert_to_c(store, result, results)
251 return catch_exceptions(context, run)
254@CFUNCTYPE(None, c_void_p)
255def func_finalize(idx): # type: ignore
256 if FUNCTIONS:
257 FUNCTIONS.deallocate(idx or 0)
258 return None