wasmtime/runtime/component/func/options.rs
1use crate::StoreContextMut;
2#[cfg(feature = "component-model-async")]
3use crate::component::concurrent::ConcurrentState;
4use crate::component::matching::InstanceType;
5use crate::component::resources::{HostResourceData, HostResourceIndex, HostResourceTables};
6use crate::component::store::ComponentTaskState;
7use crate::component::{Instance, ResourceType, RuntimeInstance};
8use crate::prelude::*;
9use crate::runtime::vm::VMFuncRef;
10use crate::runtime::vm::component::{ComponentInstance, HandleTable, ResourceTables};
11use crate::store::{StoreId, StoreOpaque};
12use alloc::sync::Arc;
13use core::fmt;
14use core::pin::Pin;
15use core::ptr::NonNull;
16use wasmtime_environ::component::{
17 CanonicalOptions, CanonicalOptionsDataModel, ComponentTypes, OptionsIndex,
18 TypeResourceTableIndex,
19};
20
21/// A helper structure which is a "package" of the context used during lowering
22/// values into a component (or storing them into memory).
23///
24/// This type is used by the `Lower` trait extensively and contains any
25/// contextual information necessary related to the context in which the
26/// lowering is happening.
27#[doc(hidden)]
28pub struct LowerContext<'a, T: 'static> {
29 /// Lowering may involve invoking memory allocation functions so part of the
30 /// context here is carrying access to the entire store that wasm is
31 /// executing within. This store serves as proof-of-ability to actually
32 /// execute wasm safely.
33 pub store: StoreContextMut<'a, T>,
34
35 /// Lowering always happens into a function that's been `canon lift`'d or
36 /// `canon lower`'d, both of which specify a set of options for the
37 /// canonical ABI. For example details like string encoding are contained
38 /// here along with which memory pointers are relative to or what the memory
39 /// allocation function is.
40 options: OptionsIndex,
41
42 /// Lowering happens within the context of a component instance and this
43 /// field stores the type information of that component instance. This is
44 /// used for type lookups and general type queries during the
45 /// lifting/lowering process.
46 pub types: &'a ComponentTypes,
47
48 /// Index of the component instance that's being lowered into.
49 instance: Instance,
50
51 /// Whether to allow `options.realloc` to be used when lowering.
52 allow_realloc: bool,
53}
54
55#[doc(hidden)]
56impl<'a, T: 'static> LowerContext<'a, T> {
57 /// Creates a new lowering context from the specified parameters.
58 pub fn new(
59 store: StoreContextMut<'a, T>,
60 options: OptionsIndex,
61 instance: Instance,
62 ) -> LowerContext<'a, T> {
63 // Debug-assert that if we can't block that blocking is indeed allowed.
64 // This'll catch when this is accidentally created outside of a fiber
65 // when we need to be on a fiber.
66 if cfg!(debug_assertions) && !store.0.can_block() {
67 store.0.validate_sync_call().unwrap();
68 }
69 let (component, store) = instance.component_and_store_mut(store.0);
70 LowerContext {
71 store: StoreContextMut(store),
72 options,
73 types: component.types(),
74 instance,
75 allow_realloc: true,
76 }
77 }
78
79 /// Like `new`, except disallows use of `options.realloc`.
80 ///
81 /// The returned object will panic if its `realloc` method is called.
82 ///
83 /// This is meant for use when lowering "flat" values (i.e. values which
84 /// require no allocations) into already-allocated memory or into stack
85 /// slots, in which case the lowering may safely be done outside of a fiber
86 /// since there is no need to make any guest calls.
87 #[cfg(feature = "component-model-async")]
88 pub(crate) fn new_without_realloc(
89 store: StoreContextMut<'a, T>,
90 options: OptionsIndex,
91 instance: Instance,
92 ) -> LowerContext<'a, T> {
93 let (component, store) = instance.component_and_store_mut(store.0);
94 LowerContext {
95 store: StoreContextMut(store),
96 options,
97 types: component.types(),
98 instance,
99 allow_realloc: false,
100 }
101 }
102
103 /// Returns the `Instance` that's being lowered into.
104 pub fn instance_handle(&self) -> Instance {
105 self.instance
106 }
107
108 /// Returns the `&ComponentInstance` that's being lowered into.
109 pub fn instance(&self) -> &ComponentInstance {
110 self.instance.id().get(self.store.0)
111 }
112
113 /// Returns the `&mut ComponentInstance` that's being lowered into.
114 pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> {
115 self.instance.id().get_mut(self.store.0)
116 }
117
118 /// Returns the canonical options that are being used during lifting.
119 pub fn options(&self) -> &CanonicalOptions {
120 &self.instance().component().env_component().options[self.options]
121 }
122
123 /// Returns a view into memory as a mutable slice of bytes.
124 ///
125 /// # Panics
126 ///
127 /// This will panic if memory has not been configured for this lowering
128 /// (e.g. it wasn't present during the specification of canonical options).
129 pub fn as_slice_mut(&mut self) -> &mut [u8] {
130 self.instance.options_memory_mut(self.store.0, self.options)
131 }
132
133 /// Invokes the memory allocation function (which is style after `realloc`)
134 /// with the specified parameters.
135 ///
136 /// # Panics
137 ///
138 /// This will panic if realloc hasn't been configured for this lowering via
139 /// its canonical options.
140 pub fn realloc(
141 &mut self,
142 old: usize,
143 old_size: usize,
144 old_align: u32,
145 new_size: usize,
146 ) -> Result<usize> {
147 assert!(self.allow_realloc);
148
149 // All calls to `realloc` options in the canonical ABI zero out the
150 // `context.{get,set}` slots for the duration of the call. This sort of
151 // fakes a "fresh thread" for each call, but this is the only observable
152 // state so nothing else needs adjusting. Note though that the original
153 // values are preserved still to get restored after this call.
154 #[cfg(feature = "component-model-async")]
155 let orig_context = core::mem::replace(
156 self.store.0.vm_store_context_mut().component_context_mut(),
157 Default::default(),
158 );
159
160 let (component, store) = self.instance.component_and_store_mut(self.store.0);
161 let instance = self.instance.id().get(store);
162 let options = &component.env_component().options[self.options];
163 let realloc_ty = component.realloc_func_ty();
164 let realloc = match options.data_model {
165 CanonicalOptionsDataModel::Gc {} => unreachable!(),
166 CanonicalOptionsDataModel::LinearMemory(m) => m.realloc.unwrap(),
167 };
168 let realloc = instance.runtime_realloc(realloc);
169
170 let params = (
171 u32::try_from(old)?,
172 u32::try_from(old_size)?,
173 old_align,
174 u32::try_from(new_size)?,
175 );
176
177 type ReallocFunc = crate::TypedFunc<(u32, u32, u32, u32), u32>;
178
179 // Invoke the wasm malloc function using its raw and statically known
180 // signature.
181 let result = unsafe {
182 ReallocFunc::call_raw(&mut StoreContextMut(store), &realloc_ty, realloc, params)?
183 };
184
185 if result % old_align != 0 {
186 bail!("realloc return: result not aligned");
187 }
188 let result = usize::try_from(result)?;
189
190 if self
191 .as_slice_mut()
192 .get_mut(result..)
193 .and_then(|s| s.get_mut(..new_size))
194 .is_none()
195 {
196 bail!("realloc return: beyond end of memory")
197 }
198
199 // Note that this restoration isn't part of a `Drop` guard which works
200 // because once a component traps it's locked-down and inaccessible, so
201 // it's ok if this isn't restored.
202 #[cfg(feature = "component-model-async")]
203 {
204 *self.store.0.vm_store_context_mut().component_context_mut() = orig_context;
205 }
206
207 Ok(result)
208 }
209
210 /// Returns a fixed mutable slice of memory `N` bytes large starting at
211 /// offset `N`, panicking on out-of-bounds.
212 ///
213 /// It should be previously verified that `offset` is in-bounds via
214 /// bounds-checks.
215 ///
216 /// # Panics
217 ///
218 /// This will panic if memory has not been configured for this lowering
219 /// (e.g. it wasn't present during the specification of canonical options).
220 pub fn get<const N: usize>(&mut self, offset: usize) -> &mut [u8; N] {
221 // FIXME: this bounds check shouldn't actually be necessary, all
222 // callers of `ComponentType::store` have already performed a bounds
223 // check so we're guaranteed that `offset..offset+N` is in-bounds. That
224 // being said we at least should do bounds checks in debug mode and
225 // it's not clear to me how to easily structure this so that it's
226 // "statically obvious" the bounds check isn't necessary.
227 //
228 // For now I figure we can leave in this bounds check and if it becomes
229 // an issue we can optimize further later, probably with judicious use
230 // of `unsafe`.
231 self.as_slice_mut()[offset..].first_chunk_mut().unwrap()
232 }
233
234 /// Lowers an `own` resource into the guest, converting the `rep` specified
235 /// into a guest-local index.
236 ///
237 /// The `ty` provided is which table to put this into.
238 pub fn guest_resource_lower_own(
239 &mut self,
240 ty: TypeResourceTableIndex,
241 rep: u32,
242 ) -> Result<u32> {
243 self.resource_tables()?.guest_resource_lower_own(rep, ty)
244 }
245
246 /// Lowers a `borrow` resource into the guest, converting the `rep` to a
247 /// guest-local index in the `ty` table specified.
248 pub fn guest_resource_lower_borrow(
249 &mut self,
250 ty: TypeResourceTableIndex,
251 rep: u32,
252 ) -> Result<u32> {
253 // Implement `lower_borrow`'s special case here where if a borrow is
254 // inserted into a table owned by the instance which implemented the
255 // original resource then no borrow tracking is employed and instead the
256 // `rep` is returned "raw".
257 //
258 // This check is performed by comparing the owning instance of `ty`
259 // against the owning instance of the resource that `ty` is working
260 // with.
261 if self.instance().resource_owned_by_own_instance(ty) {
262 return Ok(rep);
263 }
264 self.resource_tables()?.guest_resource_lower_borrow(rep, ty)
265 }
266
267 /// Lifts a host-owned `own` resource at the `idx` specified into the
268 /// representation of that resource.
269 pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> {
270 self.resource_tables()?.host_resource_lift_own(idx)
271 }
272
273 /// Lifts a host-owned `borrow` resource at the `idx` specified into the
274 /// representation of that resource.
275 pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> {
276 self.resource_tables()?.host_resource_lift_borrow(idx)
277 }
278
279 /// Lowers a resource into the host-owned table, returning the index it was
280 /// inserted at.
281 ///
282 /// Note that this is a special case for `Resource<T>`. Most of the time a
283 /// host value shouldn't be lowered with a lowering context.
284 pub fn host_resource_lower_own(
285 &mut self,
286 rep: u32,
287 dtor: Option<NonNull<VMFuncRef>>,
288 instance: Option<RuntimeInstance>,
289 ) -> Result<HostResourceIndex> {
290 self.resource_tables()?
291 .host_resource_lower_own(rep, dtor, instance)
292 }
293
294 /// Returns the underlying resource type for the `ty` table specified.
295 pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType {
296 self.instance_type().resource_type(ty)
297 }
298
299 /// Returns the instance type information corresponding to the instance that
300 /// this context is lowering into.
301 pub fn instance_type(&self) -> InstanceType<'_> {
302 InstanceType::new(self.instance())
303 }
304
305 fn resource_tables(&mut self) -> Result<HostResourceTables<'_>> {
306 let (tables, data) = self
307 .store
308 .0
309 .component_resource_tables_and_host_resource_data(Some(self.instance))?;
310 Ok(HostResourceTables::from_parts(tables, data))
311 }
312
313 /// See [`HostResourceTables::validate_scope_exit`].
314 #[inline]
315 pub fn validate_scope_exit(&mut self) -> Result<()> {
316 self.resource_tables()?.validate_scope_exit()
317 }
318}
319
320/// Contextual information used when lifting a type from a component into the
321/// host.
322///
323/// This structure is the analogue of `LowerContext` except used during lifting
324/// operations (or loading from memory).
325#[doc(hidden)]
326pub struct LiftContext<'a> {
327 store_id: StoreId,
328 current_scope_id: Option<u32>,
329 /// Like lowering, lifting always has options configured.
330 options: OptionsIndex,
331
332 /// Instance type information, like with lowering.
333 pub types: &'a Arc<ComponentTypes>,
334
335 memory: &'a [u8],
336
337 instance: Pin<&'a mut ComponentInstance>,
338 instance_handle: Instance,
339
340 host_table: &'a mut HandleTable,
341 host_resource_data: &'a mut HostResourceData,
342
343 task_state: &'a mut ComponentTaskState,
344
345 /// Remaining fuel for this hostcall/lift operation.
346 ///
347 /// This is decremented for strings/lists, for example, to cap the size of
348 /// data the host allocates on behalf of the guest.
349 hostcall_fuel: usize,
350}
351
352#[doc(hidden)]
353impl<'a> LiftContext<'a> {
354 /// Creates a new lifting context given the provided context.
355 #[inline]
356 pub fn new(
357 store: &'a mut StoreOpaque,
358 options: OptionsIndex,
359 instance_handle: Instance,
360 ) -> Result<LiftContext<'a>> {
361 let store_id = store.id();
362 let hostcall_fuel = store.hostcall_fuel();
363 let current_scope_id = store.current_scope_id()?;
364 // From `&mut StoreOpaque` provided the goal here is to project out
365 // three different disjoint fields owned by the store: memory,
366 // `CallContexts`, and `HandleTable`. There's no native API for that
367 // so it's hacked around a bit. This unsafe pointer cast could be fixed
368 // with more methods in more places, but it doesn't seem worth doing it
369 // at this time.
370 let memory =
371 instance_handle.options_memory(unsafe { &*(store as *const StoreOpaque) }, options);
372 let (task_state, host_table, host_resource_data, instance) =
373 store.lift_context_parts(instance_handle);
374 let (component, instance) = instance.component_and_self();
375
376 Ok(LiftContext {
377 store_id,
378 current_scope_id,
379 memory,
380 options,
381 types: component.types(),
382 instance,
383 instance_handle,
384 task_state,
385 host_table,
386 host_resource_data,
387 hostcall_fuel,
388 })
389 }
390
391 /// Returns the canonical options that are being used during lifting.
392 pub fn options(&self) -> &CanonicalOptions {
393 &self.instance.component().env_component().options[self.options]
394 }
395
396 /// Returns the `OptionsIndex` being used during lifting.
397 pub fn options_index(&self) -> OptionsIndex {
398 self.options
399 }
400
401 /// Returns the entire contents of linear memory for this set of lifting
402 /// options.
403 ///
404 /// # Panics
405 ///
406 /// This will panic if memory has not been configured for this lifting
407 /// operation.
408 pub fn memory(&self) -> &'a [u8] {
409 self.memory
410 }
411
412 /// Returns an identifier for the store from which this `LiftContext` was
413 /// created.
414 pub fn store_id(&self) -> StoreId {
415 self.store_id
416 }
417
418 /// Returns the component instance that is being lifted from.
419 pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> {
420 self.instance.as_mut()
421 }
422 /// Returns the component instance that is being lifted from.
423 pub fn instance_handle(&self) -> Instance {
424 self.instance_handle
425 }
426
427 #[cfg(feature = "component-model-async")]
428 pub(crate) fn concurrent_state_and_instance_mut(
429 &mut self,
430 ) -> (&mut ConcurrentState, Pin<&mut ComponentInstance>) {
431 (
432 self.task_state.concurrent_state_mut(),
433 self.instance.as_mut(),
434 )
435 }
436
437 /// Lifts an `own` resource from the guest at the `idx` specified into its
438 /// representation.
439 ///
440 /// Additionally returns a destructor/instance flags to go along with the
441 /// representation so the host knows how to destroy this resource.
442 pub fn guest_resource_lift_own(
443 &mut self,
444 ty: TypeResourceTableIndex,
445 idx: u32,
446 ) -> Result<(u32, Option<NonNull<VMFuncRef>>, Option<RuntimeInstance>)> {
447 let idx = self.resource_tables().guest_resource_lift_own(idx, ty)?;
448 let (dtor, instance) = self.instance.dtor_and_instance(ty);
449 Ok((idx, dtor, instance))
450 }
451
452 /// Lifts a `borrow` resource from the guest at the `idx` specified.
453 pub fn guest_resource_lift_borrow(
454 &mut self,
455 ty: TypeResourceTableIndex,
456 idx: u32,
457 ) -> Result<u32> {
458 self.resource_tables().guest_resource_lift_borrow(idx, ty)
459 }
460
461 /// Lowers a resource into the host-owned table, returning the index it was
462 /// inserted at.
463 pub fn host_resource_lower_own(
464 &mut self,
465 rep: u32,
466 dtor: Option<NonNull<VMFuncRef>>,
467 instance: Option<RuntimeInstance>,
468 ) -> Result<HostResourceIndex> {
469 self.resource_tables()
470 .host_resource_lower_own(rep, dtor, instance)
471 }
472
473 /// Lowers a resource into the host-owned table, returning the index it was
474 /// inserted at.
475 pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result<HostResourceIndex> {
476 self.resource_tables().host_resource_lower_borrow(rep)
477 }
478
479 /// Returns the underlying type of the resource table specified by `ty`.
480 pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType {
481 self.instance_type().resource_type(ty)
482 }
483
484 /// Returns instance type information for the component instance that is
485 /// being lifted from.
486 pub fn instance_type(&self) -> InstanceType<'_> {
487 InstanceType::new(&self.instance)
488 }
489
490 fn resource_tables(&mut self) -> HostResourceTables<'_> {
491 HostResourceTables::from_parts(
492 ResourceTables {
493 host_table: self.host_table,
494 task_state: self.task_state,
495 guest: Some(self.instance.as_mut().instance_states()),
496 current_scope_id: self.current_scope_id,
497 },
498 self.host_resource_data,
499 )
500 }
501
502 /// See [`HostResourceTables::validate_scope_exit`].
503 #[inline]
504 pub fn validate_scope_exit(&mut self) -> Result<()> {
505 self.resource_tables().validate_scope_exit()
506 }
507
508 /// Consumes `amt` units of fuel, typically a number of bytes, from this
509 /// context.
510 ///
511 /// Returns an error if the fuel is exhausted which will cause a trap in the
512 /// guest. Note that this is distinct from Wasm's fuel, this is just for
513 /// keeping track of data flowing from the guest to the host.
514 pub fn consume_fuel(&mut self, amt: usize) -> Result<()> {
515 match self.hostcall_fuel.checked_sub(amt) {
516 Some(new) => self.hostcall_fuel = new,
517 None => bail!(HostcallFuelExhausted),
518 }
519 Ok(())
520 }
521
522 /// Same as [`Self::consume_fuel`], but safely multiplies `len` and `size`
523 /// together before calling that.
524 pub fn consume_fuel_array(&mut self, len: usize, size: usize) -> Result<()> {
525 match len.checked_mul(size) {
526 Some(bytes) => self.consume_fuel(bytes),
527 None => bail!(HostcallFuelExhausted),
528 }
529 }
530}
531
532#[derive(Debug)]
533struct HostcallFuelExhausted;
534
535impl fmt::Display for HostcallFuelExhausted {
536 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537 write!(
538 f,
539 "too much data is being copied between the host and the guest: \
540 fuel allocated for hostcalls has been exhausted"
541 )
542 }
543}
544
545impl core::error::Error for HostcallFuelExhausted {}