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

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 

12 

13LinkerInstanceParent = Union['Linker', 'LinkerInstance'] 

14 

15ResourceDtor = Callable[[StoreContext, int], None] 

16RESOURCE_DTORS: Slab[ResourceDtor] = Slab() 

17 

18UserFunc = Callable[..., Optional[Any]] 

19FUNCTIONS: Slab[UserFunc] = Slab() 

20 

21 

22class Linker(Managed["ctypes._Pointer[ffi.wasmtime_component_linker_t]"]): 

23 engine: Engine 

24 locked: bool 

25 

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 

34 

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) 

38 

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) 

47 

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") 

51 

52 def root(self) -> "LinkerInstance": 

53 """ 

54 Returns the root instance used to defined new items in this linker. 

55 

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". 

59 

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) 

66 

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) 

75 

76 def instantiate(self, store: Storelike, component: Component) -> Instance: 

77 """ 

78 Instantiates the given component using this linker within the provided 

79 store. 

80 

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) 

93 

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) 

105 

106 

107class LinkerInstance(Managed["ctypes._Pointer[ffi.wasmtime_component_linker_instance_t]"]): 

108 parent: Union[LinkerInstanceParent, None] 

109 locked: bool 

110 

111 def __init__(self) -> None: 

112 raise WasmtimeError("Cannot directly construct a `LinkerInstance`") 

113 

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 

121 

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") 

125 

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 

137 

138 

139 def add_instance(self, name: str) -> "LinkerInstance": 

140 """ 

141 Adds a new instance to this linker instance under the given name. 

142 

143 Returns a new `LinkerInstance` which can be used to define further 

144 items. 

145 

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". 

149 

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) 

164 

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) 

178 

179 def add_resource(self, name: str, ty: ResourceType, dtor: ResourceDtor) -> None: 

180 """ 

181 Defines a the resource type `ty` under `name` 

182 

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) 

198 

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) 

212 

213 

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) 

219 

220 

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 

226 

227 

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) 

240 

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) 

246 

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) 

252 

253 

254@CFUNCTYPE(None, c_void_p) 

255def func_finalize(idx): # type: ignore 

256 if FUNCTIONS: 

257 FUNCTIONS.deallocate(idx or 0) 

258 return None