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