wasmtime/runtime/component/func/options.rs
1use crate::component::concurrent::ConcurrentState;
2use crate::component::matching::InstanceType;
3use crate::component::resources::{HostResourceData, HostResourceIndex, HostResourceTables};
4use crate::component::{Instance, ResourceType};
5use crate::prelude::*;
6use crate::runtime::vm::component::{
7 CallContexts, ComponentInstance, HandleTable, InstanceFlags, ResourceTables,
8};
9use crate::runtime::vm::{VMFuncRef, VMMemoryDefinition};
10use crate::store::{StoreId, StoreOpaque};
11use crate::{FuncType, StoreContextMut};
12use alloc::sync::Arc;
13use core::pin::Pin;
14use core::ptr::NonNull;
15use wasmtime_environ::component::{
16 CanonicalOptions, CanonicalOptionsDataModel, ComponentTypes, OptionsIndex, StringEncoding,
17 TypeResourceTableIndex,
18};
19
20/// Runtime representation of canonical ABI options in the component model.
21///
22/// This structure packages up the runtime representation of each option from
23/// memories to reallocs to string encodings. Note that this is a "standalone"
24/// structure which has raw pointers internally. This allows it to be created
25/// out of thin air for a host function import, for example. The `store_id`
26/// field, however, is what is used to pair this set of options with a store
27/// reference to actually use the pointers.
28#[derive(Copy, Clone)]
29pub struct Options {
30 /// The store from which this options originated.
31 store_id: StoreId,
32
33 /// An optional pointer for the memory that this set of options is referring
34 /// to. This option is not required to be specified in the canonical ABI
35 /// hence the `Option`.
36 ///
37 /// Note that this pointer cannot be safely dereferenced unless a store,
38 /// verified with `self.store_id`, has the appropriate borrow available.
39 memory: Option<NonNull<VMMemoryDefinition>>,
40
41 /// Similar to `memory` but corresponds to the `canonical_abi_realloc`
42 /// function.
43 ///
44 /// Safely using this pointer has the same restrictions as `memory` above.
45 realloc: Option<NonNull<VMFuncRef>>,
46
47 /// The encoding used for strings, if found.
48 ///
49 /// This defaults to utf-8 but can be changed if necessary.
50 string_encoding: StringEncoding,
51
52 /// Whether or not this the async option was set when lowering.
53 async_: bool,
54
55 #[cfg(feature = "component-model-async")]
56 callback: Option<NonNull<VMFuncRef>>,
57}
58
59// The `Options` structure stores raw pointers but they're never used unless a
60// `Store` is available so this should be threadsafe and largely inherit the
61// thread-safety story of `Store<T>` itself.
62unsafe impl Send for Options {}
63unsafe impl Sync for Options {}
64
65impl Options {
66 // FIXME(#4311): prevent a ctor where the memory is memory64
67
68 /// Creates a new [`Options`] from the given [`OptionsIndex`] belonging to
69 /// the specified [`Instance`]
70 ///
71 /// # Panics
72 ///
73 /// Panics if `instance` is not owned by `store` or if `index` is not valid
74 /// for `instance`'s component.
75 pub fn new_index(store: &StoreOpaque, instance: Instance, index: OptionsIndex) -> Options {
76 let instance = instance.id().get(store);
77 let CanonicalOptions {
78 string_encoding,
79 async_,
80 callback,
81 ref data_model,
82 ..
83 } = instance.component().env_component().options[index];
84 let (memory, realloc) = match data_model {
85 CanonicalOptionsDataModel::Gc { .. } => (None, None),
86 CanonicalOptionsDataModel::LinearMemory(o) => (o.memory, o.realloc),
87 };
88 let memory = memory.map(|i| NonNull::new(instance.runtime_memory(i)).unwrap());
89 let realloc = realloc.map(|i| instance.runtime_realloc(i));
90 let callback = callback.map(|i| instance.runtime_callback(i));
91 let _ = callback;
92
93 Options {
94 store_id: store.id(),
95 memory,
96 realloc,
97 string_encoding,
98 async_,
99 #[cfg(feature = "component-model-async")]
100 callback,
101 }
102 }
103
104 fn realloc<'a, T>(
105 &self,
106 store: &'a mut StoreContextMut<'_, T>,
107 realloc_ty: &FuncType,
108 old: usize,
109 old_size: usize,
110 old_align: u32,
111 new_size: usize,
112 ) -> Result<(&'a mut [u8], usize)> {
113 self.store_id.assert_belongs_to(store.0.id());
114
115 let realloc = self.realloc.unwrap();
116
117 let params = (
118 u32::try_from(old)?,
119 u32::try_from(old_size)?,
120 old_align,
121 u32::try_from(new_size)?,
122 );
123
124 type ReallocFunc = crate::TypedFunc<(u32, u32, u32, u32), u32>;
125
126 // Invoke the wasm malloc function using its raw and statically known
127 // signature.
128 let result = unsafe { ReallocFunc::call_raw(store, realloc_ty, realloc, params)? };
129
130 if result % old_align != 0 {
131 bail!("realloc return: result not aligned");
132 }
133 let result = usize::try_from(result)?;
134
135 let memory = self.memory_mut(store.0);
136
137 let result_slice = match memory.get_mut(result..).and_then(|s| s.get_mut(..new_size)) {
138 Some(end) => end,
139 None => bail!("realloc return: beyond end of memory"),
140 };
141
142 Ok((result_slice, result))
143 }
144
145 /// Asserts that this function has an associated memory attached to it and
146 /// then returns the slice of memory tied to the lifetime of the provided
147 /// store.
148 pub fn memory<'a>(&self, store: &'a StoreOpaque) -> &'a [u8] {
149 self.store_id.assert_belongs_to(store.id());
150
151 // The unsafety here is intended to be encapsulated by the two
152 // preceding assertions. Namely we assert that the `store` is the same
153 // as the original store of this `Options`, meaning that we safely have
154 // either a shared reference or a mutable reference (as below) which
155 // means it's safe to view the memory (aka it's not a different store
156 // where our original store is on some other thread or something like
157 // that).
158 //
159 // Additionally the memory itself is asserted to be present as memory
160 // is an optional configuration in canonical ABI options.
161 unsafe {
162 let memory = self.memory.unwrap().as_ref();
163 core::slice::from_raw_parts(memory.base.as_ptr(), memory.current_length())
164 }
165 }
166
167 /// Same as above, just `_mut`
168 pub fn memory_mut<'a>(&self, store: &'a mut StoreOpaque) -> &'a mut [u8] {
169 self.store_id.assert_belongs_to(store.id());
170
171 // See comments in `memory` about the unsafety
172 unsafe {
173 let memory = self.memory.unwrap().as_ref();
174 core::slice::from_raw_parts_mut(memory.base.as_ptr(), memory.current_length())
175 }
176 }
177
178 /// Returns the underlying encoding used for strings in this
179 /// lifting/lowering.
180 pub fn string_encoding(&self) -> StringEncoding {
181 self.string_encoding
182 }
183
184 /// Returns the id of the store that this `Options` is connected to.
185 pub fn store_id(&self) -> StoreId {
186 self.store_id
187 }
188
189 /// Returns whether this lifting or lowering uses the async ABI.
190 pub fn async_(&self) -> bool {
191 self.async_
192 }
193
194 #[cfg(feature = "component-model-async")]
195 pub(crate) fn callback(&self) -> Option<NonNull<VMFuncRef>> {
196 self.callback
197 }
198
199 #[cfg(feature = "component-model-async")]
200 pub(crate) fn memory_raw(&self) -> Option<NonNull<VMMemoryDefinition>> {
201 self.memory
202 }
203}
204
205/// A helper structure which is a "package" of the context used during lowering
206/// values into a component (or storing them into memory).
207///
208/// This type is used by the `Lower` trait extensively and contains any
209/// contextual information necessary related to the context in which the
210/// lowering is happening.
211#[doc(hidden)]
212pub struct LowerContext<'a, T: 'static> {
213 /// Lowering may involve invoking memory allocation functions so part of the
214 /// context here is carrying access to the entire store that wasm is
215 /// executing within. This store serves as proof-of-ability to actually
216 /// execute wasm safely.
217 pub store: StoreContextMut<'a, T>,
218
219 /// Lowering always happens into a function that's been `canon lift`'d or
220 /// `canon lower`'d, both of which specify a set of options for the
221 /// canonical ABI. For example details like string encoding are contained
222 /// here along with which memory pointers are relative to or what the memory
223 /// allocation function is.
224 pub options: &'a Options,
225
226 /// Lowering happens within the context of a component instance and this
227 /// field stores the type information of that component instance. This is
228 /// used for type lookups and general type queries during the
229 /// lifting/lowering process.
230 pub types: &'a ComponentTypes,
231
232 /// Index of the component instance that's being lowered into.
233 instance: Instance,
234
235 /// Whether to allow `options.realloc` to be used when lowering.
236 allow_realloc: bool,
237}
238
239#[doc(hidden)]
240impl<'a, T: 'static> LowerContext<'a, T> {
241 /// Creates a new lowering context from the specified parameters.
242 pub fn new(
243 store: StoreContextMut<'a, T>,
244 options: &'a Options,
245 types: &'a ComponentTypes,
246 instance: Instance,
247 ) -> LowerContext<'a, T> {
248 #[cfg(all(debug_assertions, feature = "component-model-async"))]
249 if store.engine().config().async_support {
250 // Assert that we're running on a fiber, which is necessary in
251 // case we call the guest's realloc function.
252 store.0.with_blocking(|_, _| {});
253 }
254 LowerContext {
255 store,
256 options,
257 types,
258 instance,
259 allow_realloc: true,
260 }
261 }
262
263 /// Like `new`, except disallows use of `options.realloc`.
264 ///
265 /// The returned object will panic if its `realloc` method is called.
266 ///
267 /// This is meant for use when lowering "flat" values (i.e. values which
268 /// require no allocations) into already-allocated memory or into stack
269 /// slots, in which case the lowering may safely be done outside of a fiber
270 /// since there is no need to make any guest calls.
271 #[cfg(feature = "component-model-async")]
272 pub(crate) fn new_without_realloc(
273 store: StoreContextMut<'a, T>,
274 options: &'a Options,
275 types: &'a ComponentTypes,
276 instance: Instance,
277 ) -> LowerContext<'a, T> {
278 LowerContext {
279 store,
280 options,
281 types,
282 instance,
283 allow_realloc: false,
284 }
285 }
286
287 /// Returns the `&ComponentInstance` that's being lowered into.
288 pub fn instance(&self) -> &ComponentInstance {
289 self.instance.id().get(self.store.0)
290 }
291
292 /// Returns the `&mut ComponentInstance` that's being lowered into.
293 pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> {
294 self.instance.id().get_mut(self.store.0)
295 }
296
297 /// Returns a view into memory as a mutable slice of bytes.
298 ///
299 /// # Panics
300 ///
301 /// This will panic if memory has not been configured for this lowering
302 /// (e.g. it wasn't present during the specification of canonical options).
303 pub fn as_slice_mut(&mut self) -> &mut [u8] {
304 self.options.memory_mut(self.store.0)
305 }
306
307 /// Invokes the memory allocation function (which is style after `realloc`)
308 /// with the specified parameters.
309 ///
310 /// # Panics
311 ///
312 /// This will panic if realloc hasn't been configured for this lowering via
313 /// its canonical options.
314 pub fn realloc(
315 &mut self,
316 old: usize,
317 old_size: usize,
318 old_align: u32,
319 new_size: usize,
320 ) -> Result<usize> {
321 assert!(self.allow_realloc);
322
323 let realloc_func_ty = Arc::clone(self.instance().component().realloc_func_ty());
324 self.options
325 .realloc(
326 &mut self.store,
327 &realloc_func_ty,
328 old,
329 old_size,
330 old_align,
331 new_size,
332 )
333 .map(|(_, ptr)| ptr)
334 }
335
336 /// Returns a fixed mutable slice of memory `N` bytes large starting at
337 /// offset `N`, panicking on out-of-bounds.
338 ///
339 /// It should be previously verified that `offset` is in-bounds via
340 /// bounds-checks.
341 ///
342 /// # Panics
343 ///
344 /// This will panic if memory has not been configured for this lowering
345 /// (e.g. it wasn't present during the specification of canonical options).
346 pub fn get<const N: usize>(&mut self, offset: usize) -> &mut [u8; N] {
347 // FIXME: this bounds check shouldn't actually be necessary, all
348 // callers of `ComponentType::store` have already performed a bounds
349 // check so we're guaranteed that `offset..offset+N` is in-bounds. That
350 // being said we at least should do bounds checks in debug mode and
351 // it's not clear to me how to easily structure this so that it's
352 // "statically obvious" the bounds check isn't necessary.
353 //
354 // For now I figure we can leave in this bounds check and if it becomes
355 // an issue we can optimize further later, probably with judicious use
356 // of `unsafe`.
357 self.as_slice_mut()[offset..].first_chunk_mut().unwrap()
358 }
359
360 /// Lowers an `own` resource into the guest, converting the `rep` specified
361 /// into a guest-local index.
362 ///
363 /// The `ty` provided is which table to put this into.
364 pub fn guest_resource_lower_own(
365 &mut self,
366 ty: TypeResourceTableIndex,
367 rep: u32,
368 ) -> Result<u32> {
369 self.resource_tables().guest_resource_lower_own(rep, ty)
370 }
371
372 /// Lowers a `borrow` resource into the guest, converting the `rep` to a
373 /// guest-local index in the `ty` table specified.
374 pub fn guest_resource_lower_borrow(
375 &mut self,
376 ty: TypeResourceTableIndex,
377 rep: u32,
378 ) -> Result<u32> {
379 // Implement `lower_borrow`'s special case here where if a borrow is
380 // inserted into a table owned by the instance which implemented the
381 // original resource then no borrow tracking is employed and instead the
382 // `rep` is returned "raw".
383 //
384 // This check is performed by comparing the owning instance of `ty`
385 // against the owning instance of the resource that `ty` is working
386 // with.
387 if self.instance().resource_owned_by_own_instance(ty) {
388 return Ok(rep);
389 }
390 self.resource_tables().guest_resource_lower_borrow(rep, ty)
391 }
392
393 /// Lifts a host-owned `own` resource at the `idx` specified into the
394 /// representation of that resource.
395 pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> {
396 self.resource_tables().host_resource_lift_own(idx)
397 }
398
399 /// Lifts a host-owned `borrow` resource at the `idx` specified into the
400 /// representation of that resource.
401 pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> {
402 self.resource_tables().host_resource_lift_borrow(idx)
403 }
404
405 /// Lowers a resource into the host-owned table, returning the index it was
406 /// inserted at.
407 ///
408 /// Note that this is a special case for `Resource<T>`. Most of the time a
409 /// host value shouldn't be lowered with a lowering context.
410 pub fn host_resource_lower_own(
411 &mut self,
412 rep: u32,
413 dtor: Option<NonNull<VMFuncRef>>,
414 flags: Option<InstanceFlags>,
415 ) -> Result<HostResourceIndex> {
416 self.resource_tables()
417 .host_resource_lower_own(rep, dtor, flags)
418 }
419
420 /// Returns the underlying resource type for the `ty` table specified.
421 pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType {
422 self.instance_type().resource_type(ty)
423 }
424
425 /// Returns the instance type information corresponding to the instance that
426 /// this context is lowering into.
427 pub fn instance_type(&self) -> InstanceType<'_> {
428 InstanceType::new(self.instance())
429 }
430
431 fn resource_tables(&mut self) -> HostResourceTables<'_> {
432 let (calls, host_table, host_resource_data, instance) = self
433 .store
434 .0
435 .component_resource_state_with_instance(self.instance);
436 HostResourceTables::from_parts(
437 ResourceTables {
438 host_table: Some(host_table),
439 calls,
440 guest: Some(instance.guest_tables()),
441 },
442 host_resource_data,
443 )
444 }
445
446 /// See [`HostResourceTables::enter_call`].
447 #[inline]
448 pub fn enter_call(&mut self) {
449 self.resource_tables().enter_call()
450 }
451
452 /// See [`HostResourceTables::exit_call`].
453 #[inline]
454 pub fn exit_call(&mut self) -> Result<()> {
455 self.resource_tables().exit_call()
456 }
457}
458
459/// Contextual information used when lifting a type from a component into the
460/// host.
461///
462/// This structure is the analogue of `LowerContext` except used during lifting
463/// operations (or loading from memory).
464#[doc(hidden)]
465pub struct LiftContext<'a> {
466 /// Like lowering, lifting always has options configured.
467 pub options: &'a Options,
468
469 /// Instance type information, like with lowering.
470 pub types: &'a Arc<ComponentTypes>,
471
472 memory: Option<&'a [u8]>,
473
474 instance: Pin<&'a mut ComponentInstance>,
475 instance_handle: Instance,
476
477 host_table: &'a mut HandleTable,
478 host_resource_data: &'a mut HostResourceData,
479
480 calls: &'a mut CallContexts,
481
482 #[cfg_attr(
483 not(feature = "component-model-async"),
484 allow(unused, reason = "easier to not #[cfg] away")
485 )]
486 concurrent_state: &'a mut ConcurrentState,
487}
488
489#[doc(hidden)]
490impl<'a> LiftContext<'a> {
491 /// Creates a new lifting context given the provided context.
492 #[inline]
493 pub fn new(
494 store: &'a mut StoreOpaque,
495 options: &'a Options,
496 instance_handle: Instance,
497 ) -> LiftContext<'a> {
498 // From `&mut StoreOpaque` provided the goal here is to project out
499 // three different disjoint fields owned by the store: memory,
500 // `CallContexts`, and `HandleTable`. There's no native API for that
501 // so it's hacked around a bit. This unsafe pointer cast could be fixed
502 // with more methods in more places, but it doesn't seem worth doing it
503 // at this time.
504 let memory = options
505 .memory
506 .map(|_| options.memory(unsafe { &*(store as *const StoreOpaque) }));
507 let (calls, host_table, host_resource_data, instance, concurrent_state) =
508 store.component_resource_state_with_instance_and_concurrent_state(instance_handle);
509 let (component, instance) = instance.component_and_self();
510
511 LiftContext {
512 memory,
513 options,
514 types: component.types(),
515 instance,
516 instance_handle,
517 calls,
518 host_table,
519 host_resource_data,
520 concurrent_state,
521 }
522 }
523
524 /// Returns the entire contents of linear memory for this set of lifting
525 /// options.
526 ///
527 /// # Panics
528 ///
529 /// This will panic if memory has not been configured for this lifting
530 /// operation.
531 pub fn memory(&self) -> &'a [u8] {
532 self.memory.unwrap()
533 }
534
535 /// Returns an identifier for the store from which this `LiftContext` was
536 /// created.
537 pub fn store_id(&self) -> StoreId {
538 self.options.store_id
539 }
540
541 /// Returns the component instance that is being lifted from.
542 pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> {
543 self.instance.as_mut()
544 }
545 /// Returns the component instance that is being lifted from.
546 pub fn instance_handle(&self) -> Instance {
547 self.instance_handle
548 }
549
550 #[cfg(feature = "component-model-async")]
551 pub(crate) fn concurrent_state_mut(&mut self) -> &mut ConcurrentState {
552 self.concurrent_state
553 }
554
555 /// Lifts an `own` resource from the guest at the `idx` specified into its
556 /// representation.
557 ///
558 /// Additionally returns a destructor/instance flags to go along with the
559 /// representation so the host knows how to destroy this resource.
560 pub fn guest_resource_lift_own(
561 &mut self,
562 ty: TypeResourceTableIndex,
563 idx: u32,
564 ) -> Result<(u32, Option<NonNull<VMFuncRef>>, Option<InstanceFlags>)> {
565 let idx = self.resource_tables().guest_resource_lift_own(idx, ty)?;
566 let (dtor, flags) = self.instance.dtor_and_flags(ty);
567 Ok((idx, dtor, flags))
568 }
569
570 /// Lifts a `borrow` resource from the guest at the `idx` specified.
571 pub fn guest_resource_lift_borrow(
572 &mut self,
573 ty: TypeResourceTableIndex,
574 idx: u32,
575 ) -> Result<u32> {
576 self.resource_tables().guest_resource_lift_borrow(idx, ty)
577 }
578
579 /// Lowers a resource into the host-owned table, returning the index it was
580 /// inserted at.
581 pub fn host_resource_lower_own(
582 &mut self,
583 rep: u32,
584 dtor: Option<NonNull<VMFuncRef>>,
585 flags: Option<InstanceFlags>,
586 ) -> Result<HostResourceIndex> {
587 self.resource_tables()
588 .host_resource_lower_own(rep, dtor, flags)
589 }
590
591 /// Lowers a resource into the host-owned table, returning the index it was
592 /// inserted at.
593 pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result<HostResourceIndex> {
594 self.resource_tables().host_resource_lower_borrow(rep)
595 }
596
597 /// Returns the underlying type of the resource table specified by `ty`.
598 pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType {
599 self.instance_type().resource_type(ty)
600 }
601
602 /// Returns instance type information for the component instance that is
603 /// being lifted from.
604 pub fn instance_type(&self) -> InstanceType<'_> {
605 InstanceType::new(&self.instance)
606 }
607
608 fn resource_tables(&mut self) -> HostResourceTables<'_> {
609 HostResourceTables::from_parts(
610 ResourceTables {
611 host_table: Some(self.host_table),
612 calls: self.calls,
613 // Note that the unsafety here should be valid given the contract of
614 // `LiftContext::new`.
615 guest: Some(self.instance.as_mut().guest_tables()),
616 },
617 self.host_resource_data,
618 )
619 }
620
621 /// See [`HostResourceTables::enter_call`].
622 #[inline]
623 pub fn enter_call(&mut self) {
624 self.resource_tables().enter_call()
625 }
626
627 /// See [`HostResourceTables::exit_call`].
628 #[inline]
629 pub fn exit_call(&mut self) -> Result<()> {
630 self.resource_tables().exit_call()
631 }
632}