Skip to main content

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::component::{ComponentInstance, CurrentScope, HandleTable, ResourceTables};
10use crate::runtime::vm::{UncaughtException, VMFuncRef};
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(
183                &mut StoreContextMut(store),
184                &realloc_ty,
185                realloc,
186                params,
187                UncaughtException::Trap,
188            )?
189        };
190
191        if result % old_align != 0 {
192            bail!("realloc return: result not aligned");
193        }
194        let result = usize::try_from(result)?;
195
196        if self
197            .as_slice_mut()
198            .get_mut(result..)
199            .and_then(|s| s.get_mut(..new_size))
200            .is_none()
201        {
202            bail!("realloc return: beyond end of memory")
203        }
204
205        // Note that this restoration isn't part of a `Drop` guard which works
206        // because once a component traps it's locked-down and inaccessible, so
207        // it's ok if this isn't restored.
208        #[cfg(feature = "component-model-async")]
209        {
210            *self.store.0.vm_store_context_mut().component_context_mut() = orig_context;
211        }
212
213        Ok(result)
214    }
215
216    /// Returns a fixed mutable slice of memory `N` bytes large starting at
217    /// offset `N`, panicking on out-of-bounds.
218    ///
219    /// It should be previously verified that `offset` is in-bounds via
220    /// bounds-checks.
221    ///
222    /// # Panics
223    ///
224    /// This will panic if memory has not been configured for this lowering
225    /// (e.g. it wasn't present during the specification of canonical options).
226    pub fn get<const N: usize>(&mut self, offset: usize) -> &mut [u8; N] {
227        // FIXME: this bounds check shouldn't actually be necessary, all
228        // callers of `ComponentType::store` have already performed a bounds
229        // check so we're guaranteed that `offset..offset+N` is in-bounds. That
230        // being said we at least should do bounds checks in debug mode and
231        // it's not clear to me how to easily structure this so that it's
232        // "statically obvious" the bounds check isn't necessary.
233        //
234        // For now I figure we can leave in this bounds check and if it becomes
235        // an issue we can optimize further later, probably with judicious use
236        // of `unsafe`.
237        self.as_slice_mut()[offset..].first_chunk_mut().unwrap()
238    }
239
240    /// Lowers an `own` resource into the guest, converting the `rep` specified
241    /// into a guest-local index.
242    ///
243    /// The `ty` provided is which table to put this into.
244    pub fn guest_resource_lower_own(
245        &mut self,
246        ty: TypeResourceTableIndex,
247        rep: u32,
248    ) -> Result<u32> {
249        self.resource_tables()?.guest_resource_lower_own(rep, ty)
250    }
251
252    /// Lowers a `borrow` resource into the guest, converting the `rep` to a
253    /// guest-local index in the `ty` table specified.
254    pub fn guest_resource_lower_borrow(
255        &mut self,
256        ty: TypeResourceTableIndex,
257        rep: u32,
258    ) -> Result<u32> {
259        // Implement `lower_borrow`'s special case here where if a borrow is
260        // inserted into a table owned by the instance which implemented the
261        // original resource then no borrow tracking is employed and instead the
262        // `rep` is returned "raw".
263        //
264        // This check is performed by comparing the owning instance of `ty`
265        // against the owning instance of the resource that `ty` is working
266        // with.
267        if self.instance().resource_owned_by_own_instance(ty) {
268            return Ok(rep);
269        }
270        self.resource_tables()?.guest_resource_lower_borrow(rep, ty)
271    }
272
273    /// Lifts a host-owned `own` resource at the `idx` specified into the
274    /// representation of that resource.
275    pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> {
276        self.resource_tables()?.host_resource_lift_own(idx)
277    }
278
279    /// Lifts a host-owned `borrow` resource at the `idx` specified into the
280    /// representation of that resource.
281    pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> {
282        self.resource_tables()?.host_resource_lift_borrow(idx)
283    }
284
285    /// Lowers a resource into the host-owned table, returning the index it was
286    /// inserted at.
287    ///
288    /// Note that this is a special case for `Resource<T>`. Most of the time a
289    /// host value shouldn't be lowered with a lowering context.
290    pub fn host_resource_lower_own(
291        &mut self,
292        rep: u32,
293        dtor: Option<NonNull<VMFuncRef>>,
294        instance: Option<RuntimeInstance>,
295    ) -> Result<HostResourceIndex> {
296        self.resource_tables()?
297            .host_resource_lower_own(rep, dtor, instance)
298    }
299
300    /// Returns the underlying resource type for the `ty` table specified.
301    pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType {
302        self.instance_type().resource_type(ty)
303    }
304
305    /// Returns the instance type information corresponding to the instance that
306    /// this context is lowering into.
307    pub fn instance_type(&self) -> InstanceType<'_> {
308        InstanceType::new(self.instance())
309    }
310
311    fn resource_tables(&mut self) -> Result<HostResourceTables<'_>> {
312        let (tables, data) = self
313            .store
314            .0
315            .component_resource_tables_and_host_resource_data(Some(self.instance))?;
316        Ok(HostResourceTables::from_parts(tables, data))
317    }
318
319    /// See [`HostResourceTables::validate_scope_exit`].
320    #[inline]
321    pub fn validate_scope_exit(&mut self) -> Result<()> {
322        self.resource_tables()?.validate_scope_exit()
323    }
324}
325
326/// Contextual information used when lifting a type from a component into the
327/// host.
328///
329/// This structure is the analogue of `LowerContext` except used during lifting
330/// operations (or loading from memory).
331#[doc(hidden)]
332pub struct LiftContext<'a> {
333    store_id: StoreId,
334    current_scope: Option<CurrentScope>,
335    /// Like lowering, lifting always has options configured.
336    options: OptionsIndex,
337
338    /// Instance type information, like with lowering.
339    pub types: &'a Arc<ComponentTypes>,
340
341    memory: &'a [u8],
342
343    instance: Pin<&'a mut ComponentInstance>,
344    instance_handle: Instance,
345
346    host_table: &'a mut HandleTable,
347    host_resource_data: &'a mut HostResourceData,
348
349    task_state: &'a mut ComponentTaskState,
350
351    /// Remaining fuel for this hostcall/lift operation.
352    ///
353    /// This is decremented for strings/lists, for example, to cap the size of
354    /// data the host allocates on behalf of the guest.
355    hostcall_fuel: usize,
356}
357
358#[doc(hidden)]
359impl<'a> LiftContext<'a> {
360    /// Creates a new lifting context given the provided context.
361    #[inline]
362    pub fn new(
363        store: &'a mut StoreOpaque,
364        options: OptionsIndex,
365        instance_handle: Instance,
366    ) -> Result<LiftContext<'a>> {
367        let store_id = store.id();
368        let hostcall_fuel = store.hostcall_fuel();
369        let current_scope = store.current_scope()?;
370        // From `&mut StoreOpaque` provided the goal here is to project out
371        // three different disjoint fields owned by the store: memory,
372        // `CallContexts`, and `HandleTable`. There's no native API for that
373        // so it's hacked around a bit. This unsafe pointer cast could be fixed
374        // with more methods in more places, but it doesn't seem worth doing it
375        // at this time.
376        let memory =
377            instance_handle.options_memory(unsafe { &*(store as *const StoreOpaque) }, options);
378        let (task_state, host_table, host_resource_data, instance) =
379            store.lift_context_parts(instance_handle);
380        let (component, instance) = instance.component_and_self();
381
382        Ok(LiftContext {
383            store_id,
384            current_scope,
385            memory,
386            options,
387            types: component.types(),
388            instance,
389            instance_handle,
390            task_state,
391            host_table,
392            host_resource_data,
393            hostcall_fuel,
394        })
395    }
396
397    /// Returns the canonical options that are being used during lifting.
398    pub fn options(&self) -> &CanonicalOptions {
399        &self.instance.component().env_component().options[self.options]
400    }
401
402    /// Returns the `OptionsIndex` being used during lifting.
403    pub fn options_index(&self) -> OptionsIndex {
404        self.options
405    }
406
407    /// Returns the entire contents of linear memory for this set of lifting
408    /// options.
409    ///
410    /// # Panics
411    ///
412    /// This will panic if memory has not been configured for this lifting
413    /// operation.
414    pub fn memory(&self) -> &'a [u8] {
415        self.memory
416    }
417
418    /// Returns an identifier for the store from which this `LiftContext` was
419    /// created.
420    pub fn store_id(&self) -> StoreId {
421        self.store_id
422    }
423
424    /// Returns the component instance that is being lifted from.
425    pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> {
426        self.instance.as_mut()
427    }
428    /// Returns the component instance that is being lifted from.
429    pub fn instance_handle(&self) -> Instance {
430        self.instance_handle
431    }
432
433    #[cfg(feature = "component-model-async")]
434    pub(crate) fn concurrent_state_and_instance_mut(
435        &mut self,
436    ) -> (&mut ConcurrentState, Pin<&mut ComponentInstance>) {
437        (
438            self.task_state.concurrent_state_mut(),
439            self.instance.as_mut(),
440        )
441    }
442
443    /// Lifts an `own` resource from the guest at the `idx` specified into its
444    /// representation.
445    ///
446    /// Additionally returns a destructor/instance flags to go along with the
447    /// representation so the host knows how to destroy this resource.
448    pub fn guest_resource_lift_own(
449        &mut self,
450        ty: TypeResourceTableIndex,
451        idx: u32,
452    ) -> Result<(u32, Option<NonNull<VMFuncRef>>, Option<RuntimeInstance>)> {
453        let idx = self.resource_tables().guest_resource_lift_own(idx, ty)?;
454        let (dtor, instance) = self.instance.dtor_and_instance(ty);
455        Ok((idx, dtor, instance))
456    }
457
458    /// Lifts a `borrow` resource from the guest at the `idx` specified.
459    pub fn guest_resource_lift_borrow(
460        &mut self,
461        ty: TypeResourceTableIndex,
462        idx: u32,
463    ) -> Result<u32> {
464        self.resource_tables().guest_resource_lift_borrow(idx, ty)
465    }
466
467    /// Lowers a resource into the host-owned table, returning the index it was
468    /// inserted at.
469    pub fn host_resource_lower_own(
470        &mut self,
471        rep: u32,
472        dtor: Option<NonNull<VMFuncRef>>,
473        instance: Option<RuntimeInstance>,
474    ) -> Result<HostResourceIndex> {
475        self.resource_tables()
476            .host_resource_lower_own(rep, dtor, instance)
477    }
478
479    /// Lowers a resource into the host-owned table, returning the index it was
480    /// inserted at.
481    pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result<HostResourceIndex> {
482        self.resource_tables().host_resource_lower_borrow(rep)
483    }
484
485    /// Returns the underlying type of the resource table specified by `ty`.
486    pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType {
487        self.instance_type().resource_type(ty)
488    }
489
490    /// Returns instance type information for the component instance that is
491    /// being lifted from.
492    pub fn instance_type(&self) -> InstanceType<'_> {
493        InstanceType::new(&self.instance)
494    }
495
496    fn resource_tables(&mut self) -> HostResourceTables<'_> {
497        HostResourceTables::from_parts(
498            ResourceTables {
499                host_table: self.host_table,
500                task_state: self.task_state,
501                guest: Some(self.instance.as_mut().instance_states()),
502                current_scope: self.current_scope,
503            },
504            self.host_resource_data,
505        )
506    }
507
508    /// See [`HostResourceTables::validate_scope_exit`].
509    #[inline]
510    pub fn validate_scope_exit(&mut self) -> Result<()> {
511        self.resource_tables().validate_scope_exit()
512    }
513
514    /// Consumes `amt` units of fuel, typically a number of bytes, from this
515    /// context.
516    ///
517    /// Returns an error if the fuel is exhausted which will cause a trap in the
518    /// guest. Note that this is distinct from Wasm's fuel, this is just for
519    /// keeping track of data flowing from the guest to the host.
520    pub fn consume_fuel(&mut self, amt: usize) -> Result<()> {
521        match self.hostcall_fuel.checked_sub(amt) {
522            Some(new) => self.hostcall_fuel = new,
523            None => bail!(HostcallFuelExhausted),
524        }
525        Ok(())
526    }
527
528    /// Same as [`Self::consume_fuel`], but safely multiplies `len` and `size`
529    /// together before calling that.
530    pub fn consume_fuel_array(&mut self, len: usize, size: usize) -> Result<()> {
531        match len.checked_mul(size) {
532            Some(bytes) => self.consume_fuel(bytes),
533            None => bail!(HostcallFuelExhausted),
534        }
535    }
536}
537
538#[derive(Debug)]
539struct HostcallFuelExhausted;
540
541impl fmt::Display for HostcallFuelExhausted {
542    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
543        write!(
544            f,
545            "too much data is being copied between the host and the guest: \
546             fuel allocated for hostcalls has been exhausted"
547        )
548    }
549}
550
551impl core::error::Error for HostcallFuelExhausted {}