1 use crate::StoreContextMut;
2 #[cfg(feature = "component-model-async")]
3 use crate::component::concurrent::ConcurrentState;
4 use crate::component::matching::InstanceType;
5 use crate::component::resources::{HostResourceData, HostResourceIndex, HostResourceTables};
6 use crate::component::store::ComponentTaskState;
7 use crate::component::{Instance, ResourceType, RuntimeInstance};
8 use crate::prelude::*;
9 use crate::runtime::vm::VMFuncRef;
10 use crate::runtime::vm::component::{ComponentInstance, HandleTable, ResourceTables};
11 use crate::store::{StoreId, StoreOpaque};
12 use alloc::sync::Arc;
13 use core::fmt;
14 use core::pin::Pin;
15 use core::ptr::NonNull;
16 use 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)]
28 pub 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)]
56 impl<'a, T: 'static> LowerContext<'a, T> {
57     /// Creates a new lowering context from the specified parameters.
new( store: StoreContextMut<'a, T>, options: OptionsIndex, instance: Instance, ) -> LowerContext<'a, T>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")]
new_without_realloc( store: StoreContextMut<'a, T>, options: OptionsIndex, instance: Instance, ) -> LowerContext<'a, T>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 `&ComponentInstance` that's being lowered into.
instance(&self) -> &ComponentInstance104     pub fn instance(&self) -> &ComponentInstance {
105         self.instance.id().get(self.store.0)
106     }
107 
108     /// Returns the `&mut ComponentInstance` that's being lowered into.
instance_mut(&mut self) -> Pin<&mut ComponentInstance>109     pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> {
110         self.instance.id().get_mut(self.store.0)
111     }
112 
113     /// Returns the canonical options that are being used during lifting.
options(&self) -> &CanonicalOptions114     pub fn options(&self) -> &CanonicalOptions {
115         &self.instance().component().env_component().options[self.options]
116     }
117 
118     /// Returns a view into memory as a mutable slice of bytes.
119     ///
120     /// # Panics
121     ///
122     /// This will panic if memory has not been configured for this lowering
123     /// (e.g. it wasn't present during the specification of canonical options).
as_slice_mut(&mut self) -> &mut [u8]124     pub fn as_slice_mut(&mut self) -> &mut [u8] {
125         self.instance.options_memory_mut(self.store.0, self.options)
126     }
127 
128     /// Invokes the memory allocation function (which is style after `realloc`)
129     /// with the specified parameters.
130     ///
131     /// # Panics
132     ///
133     /// This will panic if realloc hasn't been configured for this lowering via
134     /// its canonical options.
realloc( &mut self, old: usize, old_size: usize, old_align: u32, new_size: usize, ) -> Result<usize>135     pub fn realloc(
136         &mut self,
137         old: usize,
138         old_size: usize,
139         old_align: u32,
140         new_size: usize,
141     ) -> Result<usize> {
142         assert!(self.allow_realloc);
143 
144         let (component, store) = self.instance.component_and_store_mut(self.store.0);
145         let instance = self.instance.id().get(store);
146         let options = &component.env_component().options[self.options];
147         let realloc_ty = component.realloc_func_ty();
148         let realloc = match options.data_model {
149             CanonicalOptionsDataModel::Gc {} => unreachable!(),
150             CanonicalOptionsDataModel::LinearMemory(m) => m.realloc.unwrap(),
151         };
152         let realloc = instance.runtime_realloc(realloc);
153 
154         let params = (
155             u32::try_from(old)?,
156             u32::try_from(old_size)?,
157             old_align,
158             u32::try_from(new_size)?,
159         );
160 
161         type ReallocFunc = crate::TypedFunc<(u32, u32, u32, u32), u32>;
162 
163         // Invoke the wasm malloc function using its raw and statically known
164         // signature.
165         let result = unsafe {
166             ReallocFunc::call_raw(&mut StoreContextMut(store), &realloc_ty, realloc, params)?
167         };
168 
169         if result % old_align != 0 {
170             bail!("realloc return: result not aligned");
171         }
172         let result = usize::try_from(result)?;
173 
174         if self
175             .as_slice_mut()
176             .get_mut(result..)
177             .and_then(|s| s.get_mut(..new_size))
178             .is_none()
179         {
180             bail!("realloc return: beyond end of memory")
181         }
182 
183         Ok(result)
184     }
185 
186     /// Returns a fixed mutable slice of memory `N` bytes large starting at
187     /// offset `N`, panicking on out-of-bounds.
188     ///
189     /// It should be previously verified that `offset` is in-bounds via
190     /// bounds-checks.
191     ///
192     /// # Panics
193     ///
194     /// This will panic if memory has not been configured for this lowering
195     /// (e.g. it wasn't present during the specification of canonical options).
get<const N: usize>(&mut self, offset: usize) -> &mut [u8; N]196     pub fn get<const N: usize>(&mut self, offset: usize) -> &mut [u8; N] {
197         // FIXME: this bounds check shouldn't actually be necessary, all
198         // callers of `ComponentType::store` have already performed a bounds
199         // check so we're guaranteed that `offset..offset+N` is in-bounds. That
200         // being said we at least should do bounds checks in debug mode and
201         // it's not clear to me how to easily structure this so that it's
202         // "statically obvious" the bounds check isn't necessary.
203         //
204         // For now I figure we can leave in this bounds check and if it becomes
205         // an issue we can optimize further later, probably with judicious use
206         // of `unsafe`.
207         self.as_slice_mut()[offset..].first_chunk_mut().unwrap()
208     }
209 
210     /// Lowers an `own` resource into the guest, converting the `rep` specified
211     /// into a guest-local index.
212     ///
213     /// The `ty` provided is which table to put this into.
guest_resource_lower_own( &mut self, ty: TypeResourceTableIndex, rep: u32, ) -> Result<u32>214     pub fn guest_resource_lower_own(
215         &mut self,
216         ty: TypeResourceTableIndex,
217         rep: u32,
218     ) -> Result<u32> {
219         self.resource_tables().guest_resource_lower_own(rep, ty)
220     }
221 
222     /// Lowers a `borrow` resource into the guest, converting the `rep` to a
223     /// guest-local index in the `ty` table specified.
guest_resource_lower_borrow( &mut self, ty: TypeResourceTableIndex, rep: u32, ) -> Result<u32>224     pub fn guest_resource_lower_borrow(
225         &mut self,
226         ty: TypeResourceTableIndex,
227         rep: u32,
228     ) -> Result<u32> {
229         // Implement `lower_borrow`'s special case here where if a borrow is
230         // inserted into a table owned by the instance which implemented the
231         // original resource then no borrow tracking is employed and instead the
232         // `rep` is returned "raw".
233         //
234         // This check is performed by comparing the owning instance of `ty`
235         // against the owning instance of the resource that `ty` is working
236         // with.
237         if self.instance().resource_owned_by_own_instance(ty) {
238             return Ok(rep);
239         }
240         self.resource_tables().guest_resource_lower_borrow(rep, ty)
241     }
242 
243     /// Lifts a host-owned `own` resource at the `idx` specified into the
244     /// representation of that resource.
host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32>245     pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> {
246         self.resource_tables().host_resource_lift_own(idx)
247     }
248 
249     /// Lifts a host-owned `borrow` resource at the `idx` specified into the
250     /// representation of that resource.
host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32>251     pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> {
252         self.resource_tables().host_resource_lift_borrow(idx)
253     }
254 
255     /// Lowers a resource into the host-owned table, returning the index it was
256     /// inserted at.
257     ///
258     /// Note that this is a special case for `Resource<T>`. Most of the time a
259     /// host value shouldn't be lowered with a lowering context.
host_resource_lower_own( &mut self, rep: u32, dtor: Option<NonNull<VMFuncRef>>, instance: Option<RuntimeInstance>, ) -> Result<HostResourceIndex>260     pub fn host_resource_lower_own(
261         &mut self,
262         rep: u32,
263         dtor: Option<NonNull<VMFuncRef>>,
264         instance: Option<RuntimeInstance>,
265     ) -> Result<HostResourceIndex> {
266         self.resource_tables()
267             .host_resource_lower_own(rep, dtor, instance)
268     }
269 
270     /// Returns the underlying resource type for the `ty` table specified.
resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType271     pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType {
272         self.instance_type().resource_type(ty)
273     }
274 
275     /// Returns the instance type information corresponding to the instance that
276     /// this context is lowering into.
instance_type(&self) -> InstanceType<'_>277     pub fn instance_type(&self) -> InstanceType<'_> {
278         InstanceType::new(self.instance())
279     }
280 
resource_tables(&mut self) -> HostResourceTables<'_>281     fn resource_tables(&mut self) -> HostResourceTables<'_> {
282         let (tables, data) = self
283             .store
284             .0
285             .component_resource_tables_and_host_resource_data(Some(self.instance));
286         HostResourceTables::from_parts(tables, data)
287     }
288 
289     /// See [`HostResourceTables::validate_scope_exit`].
290     #[inline]
validate_scope_exit(&mut self) -> Result<()>291     pub fn validate_scope_exit(&mut self) -> Result<()> {
292         self.resource_tables().validate_scope_exit()
293     }
294 }
295 
296 /// Contextual information used when lifting a type from a component into the
297 /// host.
298 ///
299 /// This structure is the analogue of `LowerContext` except used during lifting
300 /// operations (or loading from memory).
301 #[doc(hidden)]
302 pub struct LiftContext<'a> {
303     store_id: StoreId,
304     /// Like lowering, lifting always has options configured.
305     options: OptionsIndex,
306 
307     /// Instance type information, like with lowering.
308     pub types: &'a Arc<ComponentTypes>,
309 
310     memory: &'a [u8],
311 
312     instance: Pin<&'a mut ComponentInstance>,
313     instance_handle: Instance,
314 
315     host_table: &'a mut HandleTable,
316     host_resource_data: &'a mut HostResourceData,
317 
318     task_state: &'a mut ComponentTaskState,
319 
320     /// Remaining fuel for this hostcall/lift operation.
321     ///
322     /// This is decremented for strings/lists, for example, to cap the size of
323     /// data the host allocates on behalf of the guest.
324     hostcall_fuel: usize,
325 }
326 
327 #[doc(hidden)]
328 impl<'a> LiftContext<'a> {
329     /// Creates a new lifting context given the provided context.
330     #[inline]
new( store: &'a mut StoreOpaque, options: OptionsIndex, instance_handle: Instance, ) -> LiftContext<'a>331     pub fn new(
332         store: &'a mut StoreOpaque,
333         options: OptionsIndex,
334         instance_handle: Instance,
335     ) -> LiftContext<'a> {
336         let store_id = store.id();
337         let hostcall_fuel = store.hostcall_fuel();
338         // From `&mut StoreOpaque` provided the goal here is to project out
339         // three different disjoint fields owned by the store: memory,
340         // `CallContexts`, and `HandleTable`. There's no native API for that
341         // so it's hacked around a bit. This unsafe pointer cast could be fixed
342         // with more methods in more places, but it doesn't seem worth doing it
343         // at this time.
344         let memory =
345             instance_handle.options_memory(unsafe { &*(store as *const StoreOpaque) }, options);
346         let (task_state, host_table, host_resource_data, instance) =
347             store.lift_context_parts(instance_handle);
348         let (component, instance) = instance.component_and_self();
349 
350         LiftContext {
351             store_id,
352             memory,
353             options,
354             types: component.types(),
355             instance,
356             instance_handle,
357             task_state,
358             host_table,
359             host_resource_data,
360             hostcall_fuel,
361         }
362     }
363 
364     /// Returns the canonical options that are being used during lifting.
options(&self) -> &CanonicalOptions365     pub fn options(&self) -> &CanonicalOptions {
366         &self.instance.component().env_component().options[self.options]
367     }
368 
369     /// Returns the `OptionsIndex` being used during lifting.
options_index(&self) -> OptionsIndex370     pub fn options_index(&self) -> OptionsIndex {
371         self.options
372     }
373 
374     /// Returns the entire contents of linear memory for this set of lifting
375     /// options.
376     ///
377     /// # Panics
378     ///
379     /// This will panic if memory has not been configured for this lifting
380     /// operation.
memory(&self) -> &'a [u8]381     pub fn memory(&self) -> &'a [u8] {
382         self.memory
383     }
384 
385     /// Returns an identifier for the store from which this `LiftContext` was
386     /// created.
store_id(&self) -> StoreId387     pub fn store_id(&self) -> StoreId {
388         self.store_id
389     }
390 
391     /// Returns the component instance that is being lifted from.
instance_mut(&mut self) -> Pin<&mut ComponentInstance>392     pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> {
393         self.instance.as_mut()
394     }
395     /// Returns the component instance that is being lifted from.
instance_handle(&self) -> Instance396     pub fn instance_handle(&self) -> Instance {
397         self.instance_handle
398     }
399 
400     #[cfg(feature = "component-model-async")]
concurrent_state_mut(&mut self) -> &mut ConcurrentState401     pub(crate) fn concurrent_state_mut(&mut self) -> &mut ConcurrentState {
402         self.task_state.concurrent_state_mut()
403     }
404 
405     /// Lifts an `own` resource from the guest at the `idx` specified into its
406     /// representation.
407     ///
408     /// Additionally returns a destructor/instance flags to go along with the
409     /// representation so the host knows how to destroy this resource.
guest_resource_lift_own( &mut self, ty: TypeResourceTableIndex, idx: u32, ) -> Result<(u32, Option<NonNull<VMFuncRef>>, Option<RuntimeInstance>)>410     pub fn guest_resource_lift_own(
411         &mut self,
412         ty: TypeResourceTableIndex,
413         idx: u32,
414     ) -> Result<(u32, Option<NonNull<VMFuncRef>>, Option<RuntimeInstance>)> {
415         let idx = self.resource_tables().guest_resource_lift_own(idx, ty)?;
416         let (dtor, instance) = self.instance.dtor_and_instance(ty);
417         Ok((idx, dtor, instance))
418     }
419 
420     /// Lifts a `borrow` resource from the guest at the `idx` specified.
guest_resource_lift_borrow( &mut self, ty: TypeResourceTableIndex, idx: u32, ) -> Result<u32>421     pub fn guest_resource_lift_borrow(
422         &mut self,
423         ty: TypeResourceTableIndex,
424         idx: u32,
425     ) -> Result<u32> {
426         self.resource_tables().guest_resource_lift_borrow(idx, ty)
427     }
428 
429     /// Lowers a resource into the host-owned table, returning the index it was
430     /// inserted at.
host_resource_lower_own( &mut self, rep: u32, dtor: Option<NonNull<VMFuncRef>>, instance: Option<RuntimeInstance>, ) -> Result<HostResourceIndex>431     pub fn host_resource_lower_own(
432         &mut self,
433         rep: u32,
434         dtor: Option<NonNull<VMFuncRef>>,
435         instance: Option<RuntimeInstance>,
436     ) -> Result<HostResourceIndex> {
437         self.resource_tables()
438             .host_resource_lower_own(rep, dtor, instance)
439     }
440 
441     /// Lowers a resource into the host-owned table, returning the index it was
442     /// inserted at.
host_resource_lower_borrow(&mut self, rep: u32) -> Result<HostResourceIndex>443     pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result<HostResourceIndex> {
444         self.resource_tables().host_resource_lower_borrow(rep)
445     }
446 
447     /// Returns the underlying type of the resource table specified by `ty`.
resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType448     pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType {
449         self.instance_type().resource_type(ty)
450     }
451 
452     /// Returns instance type information for the component instance that is
453     /// being lifted from.
instance_type(&self) -> InstanceType<'_>454     pub fn instance_type(&self) -> InstanceType<'_> {
455         InstanceType::new(&self.instance)
456     }
457 
resource_tables(&mut self) -> HostResourceTables<'_>458     fn resource_tables(&mut self) -> HostResourceTables<'_> {
459         HostResourceTables::from_parts(
460             ResourceTables {
461                 host_table: self.host_table,
462                 task_state: self.task_state,
463                 guest: Some(self.instance.as_mut().instance_states()),
464             },
465             self.host_resource_data,
466         )
467     }
468 
469     /// See [`HostResourceTables::validate_scope_exit`].
470     #[inline]
validate_scope_exit(&mut self) -> Result<()>471     pub fn validate_scope_exit(&mut self) -> Result<()> {
472         self.resource_tables().validate_scope_exit()
473     }
474 
475     /// Consumes `amt` units of fuel, typically a number of bytes, from this
476     /// context.
477     ///
478     /// Returns an error if the fuel is exhausted which will cause a trap in the
479     /// guest. Note that this is distinct from Wasm's fuel, this is just for
480     /// keeping track of data flowing from the guest to the host.
consume_fuel(&mut self, amt: usize) -> Result<()>481     pub fn consume_fuel(&mut self, amt: usize) -> Result<()> {
482         match self.hostcall_fuel.checked_sub(amt) {
483             Some(new) => self.hostcall_fuel = new,
484             None => bail!(HostcallFuelExhausted),
485         }
486         Ok(())
487     }
488 
489     /// Same as [`Self::consume_fuel`], but safely multiplies `len` and `size`
490     /// together before calling that.
consume_fuel_array(&mut self, len: usize, size: usize) -> Result<()>491     pub fn consume_fuel_array(&mut self, len: usize, size: usize) -> Result<()> {
492         match len.checked_mul(size) {
493             Some(bytes) => self.consume_fuel(bytes),
494             None => bail!(HostcallFuelExhausted),
495         }
496     }
497 }
498 
499 #[derive(Debug)]
500 struct HostcallFuelExhausted;
501 
502 impl fmt::Display for HostcallFuelExhausted {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result503     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
504         write!(
505             f,
506             "too much data is being copied between the host and the guest: \
507              fuel allocated for hostcalls has been exhausted"
508         )
509     }
510 }
511 
512 impl core::error::Error for HostcallFuelExhausted {}
513