Coverage for wasmtime/component/_linker.py: 98%

148 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2026-05-27 18:55 +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 add_wasi_http(self) -> None: 

77 """ 

78 Adds the WASI HTTP API definitions to this linker. 

79 

80 This adds `wasi:http/types` and `wasi:http/outgoing-handler`. 

81 Requires WASIp2 to be added first via `add_wasip2()`. 

82 """ 

83 self._assert_not_locked() 

84 err = ffi.wasmtime_component_linker_add_wasi_http(self.ptr()) 

85 if err: 

86 raise WasmtimeError._from_ptr(err) 

87 

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

89 """ 

90 Instantiates the given component using this linker within the provided 

91 store. 

92 

93 Returns the instantiated `Instance` on success. 

94 """ 

95 self._assert_not_locked() 

96 instance = ffi.wasmtime_component_instance_t() 

97 err = ffi.wasmtime_component_linker_instantiate( 

98 self.ptr(), 

99 store._context(), 

100 component.ptr(), 

101 byref(instance)) 

102 if err: 

103 raise WasmtimeError._from_ptr(err) 

104 return Instance._from_raw(instance) 

105 

106 def define_unknown_imports_as_traps(self, component: Component) -> None: 

107 """ 

108 Configures this linker to define any unknown imports of `component` as 

109 traps which will error when invoked. 

110 """ 

111 self._assert_not_locked() 

112 err = ffi.wasmtime_component_linker_define_unknown_imports_as_traps( 

113 self.ptr(), 

114 component.ptr()) 

115 if err: 

116 raise WasmtimeError._from_ptr(err) 

117 

118 

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

120 parent: Union[LinkerInstanceParent, None] 

121 locked: bool 

122 

123 def __init__(self) -> None: 

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

125 

126 def _delete(self, ptr: "ctypes._Pointer[ffi.wasmtime_component_linker_instance_t]") -> None: 

127 self._assert_not_locked() 

128 ffi.wasmtime_component_linker_instance_delete(ptr) 

129 assert(self.parent is not None) 

130 assert(self.parent.locked) 

131 self.parent.locked = False 

132 self.parent = None 

133 

134 def _assert_not_locked(self) -> None: 

135 if self.locked: 

136 raise WasmtimeError("cannot use linker instance while it's in use by other instances") 

137 

138 @classmethod 

139 def _from_ptr(cls, ptr: "ctypes._Pointer[ffi.wasmtime_component_linker_instance_t]", parent: LinkerInstanceParent) -> "LinkerInstance": 

140 if not isinstance(ptr, POINTER(ffi.wasmtime_component_linker_instance_t)): 

141 raise TypeError("wrong pointer type") 

142 ty: "LinkerInstance" = cls.__new__(cls) 

143 ty._set_ptr(ptr) 

144 ty.parent = parent 

145 ty.locked = False 

146 assert(not parent.locked) 

147 parent.locked = True 

148 return ty 

149 

150 

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

152 """ 

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

154 

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

156 items. 

157 

158 While the returned instance is alive this linker instance cannot be 

159 used for other operations. Until the returned object is destroyed this 

160 linker instance is considered "locked". 

161 

162 It's recommended to bind the return value in a `with` block to 

163 automatically dispose of it when it's done. 

164 """ 

165 self._assert_not_locked() 

166 name_bytes = name.encode('utf-8') 

167 name_buf = create_string_buffer(name_bytes) 

168 ptr = POINTER(ffi.wasmtime_component_linker_instance_t)() 

169 err = ffi.wasmtime_component_linker_instance_add_instance(self.ptr(), 

170 name_buf, 

171 len(name_bytes), 

172 byref(ptr)) 

173 if err: 

174 raise WasmtimeError._from_ptr(err) 

175 return LinkerInstance._from_ptr(ptr, self) 

176 

177 def add_module(self, name: str, module: Module) -> None: 

178 """ 

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

180 """ 

181 self._assert_not_locked() 

182 name_bytes = name.encode('utf-8') 

183 name_buf = create_string_buffer(name_bytes) 

184 err = ffi.wasmtime_component_linker_instance_add_module(self.ptr(), 

185 name_buf, 

186 len(name_bytes), 

187 module.ptr()) 

188 if err: 

189 raise WasmtimeError._from_ptr(err) 

190 

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

192 """ 

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

194 

195 When a value of this resource is destroyed then `dtor` is invoked. 

196 """ 

197 self._assert_not_locked() 

198 name_bytes = name.encode('utf-8') 

199 name_buf = create_string_buffer(name_bytes) 

200 idx = RESOURCE_DTORS.allocate(dtor) 

201 err = ffi.wasmtime_component_linker_instance_add_resource(self.ptr(), 

202 name_buf, 

203 len(name_bytes), 

204 ty.ptr(), 

205 resource_dtor_impl, 

206 idx, 

207 resource_dtor_finalize) 

208 if err: 

209 raise WasmtimeError._from_ptr(err) 

210 

211 def add_func(self, name: str, func: UserFunc) -> None: 

212 self._assert_not_locked() 

213 name_bytes = name.encode('utf-8') 

214 name_buf = create_string_buffer(name_bytes) 

215 idx = FUNCTIONS.allocate(func) 

216 err = ffi.wasmtime_component_linker_instance_add_func(self.ptr(), 

217 name_buf, 

218 len(name_bytes), 

219 func_impl, 

220 idx, 

221 func_finalize) 

222 if err: 

223 raise WasmtimeError._from_ptr(err) 

224 

225 

226@ffi.wasmtime_component_resource_destructor_t 

227def resource_dtor_impl(idx, context, rep): # type: ignore 

228 def run(store: StoreContext) -> None: 

229 return RESOURCE_DTORS.get(idx or 0)(store, rep) 

230 return catch_exceptions(context, run) 

231 

232 

233@CFUNCTYPE(None, c_void_p) 

234def resource_dtor_finalize(idx): # type: ignore 

235 if RESOURCE_DTORS: 

236 RESOURCE_DTORS.deallocate(idx or 0) 

237 return None 

238 

239 

240@ffi.wasmtime_component_func_callback_t 

241def func_impl(idx, context, ty_raw, args, nargs, results, nresults): # type: ignore 

242 def run(store: StoreContext) -> None: 

243 with FuncType._from_ptr(ty_raw, owner=store) as fty: 

244 func = FUNCTIONS.get(idx or 0) 

245 param_tys = fty.params 

246 result_ty = fty.result 

247 assert(len(param_tys) == nargs) 

248 if result_ty is None: 

249 assert(nresults == 0) 

250 else: 

251 assert(nresults == 1) 

252 

253 pyargs = [] 

254 for (_, ty), i in zip(param_tys, range(nargs)): 

255 val = ty.convert_from_c(args[i]) 

256 pyargs.append(val) 

257 result = func(store, *pyargs) 

258 

259 if result_ty is None: 

260 assert(result is None) 

261 else: 

262 result_ty.convert_to_c(store, result, results) 

263 return catch_exceptions(context, run) 

264 

265 

266@CFUNCTYPE(None, c_void_p) 

267def func_finalize(idx): # type: ignore 

268 if FUNCTIONS: 

269 FUNCTIONS.deallocate(idx or 0) 

270 return None