1 use super::{TypedResource, TypedResourceIndex};
2 use alloc::vec::Vec;
3 use anyhow::{Result, bail};
4 use core::mem;
5 use wasmtime_environ::component::{TypeFutureTableIndex, TypeStreamTableIndex};
6 
7 /// The maximum handle value is specified in
8 /// <https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md>
9 /// currently and keeps the upper bits free for use in the component and ABI.
10 const MAX_HANDLE: u32 = 1 << 28;
11 
12 /// Represents the state of a stream or future handle from the perspective of a
13 /// given component instance.
14 #[derive(Debug, Eq, PartialEq)]
15 pub enum TransmitLocalState {
16     /// The write end of the stream or future.
17     Write {
18         /// Whether the component instance has been notified that the stream or
19         /// future is "done" (i.e. the other end has dropped, or, in the case of
20         /// a future, a value has been transmitted).
21         done: bool,
22     },
23     /// The read end of the stream or future.
24     Read {
25         /// Whether the component instance has been notified that the stream or
26         /// future is "done" (i.e. the other end has dropped, or, in the case of
27         /// a future, a value has been transmitted).
28         done: bool,
29     },
30     /// A read or write is in progress.
31     Busy,
32 }
33 
34 /// Return value from [`HandleTable::remove_resource`].
35 pub enum RemovedResource {
36     /// An `own` resource was removed with the specified `rep`
37     Own { rep: u32 },
38     /// A `borrow` resource was removed originally created within `scope`.
39     Borrow { scope: usize },
40 }
41 
42 /// Different kinds of waitables returned by [`HandleTable::waitable_rep`].
43 pub enum Waitable {
44     Subtask { is_host: bool },
45     Future,
46     Stream,
47 }
48 
49 #[derive(Debug)]
50 enum Slot {
51     Free {
52         next: u32,
53     },
54 
55     /// Represents an owned resource handle with the listed representation.
56     ///
57     /// The `lend_count` tracks how many times this has been lent out as a
58     /// `borrow` and if nonzero this can't be removed.
59     ResourceOwn {
60         resource: TypedResource,
61         lend_count: u32,
62     },
63 
64     /// Represents a borrowed resource handle connected to the `scope`
65     /// provided.
66     ///
67     /// The `rep` is listed and dropping this borrow will decrement the borrow
68     /// count of the `scope`.
69     ResourceBorrow {
70         resource: TypedResource,
71         scope: usize,
72     },
73 
74     /// Represents a host task handle.
75     HostTask {
76         rep: u32,
77     },
78 
79     /// Represents a guest task handle.
80     GuestTask {
81         rep: u32,
82     },
83 
84     /// Represents a guest thread handle.
85     GuestThread {
86         rep: u32,
87     },
88 
89     /// Represents a stream handle.
90     Stream {
91         ty: TypeStreamTableIndex,
92         rep: u32,
93         state: TransmitLocalState,
94     },
95 
96     /// Represents a future handle.
97     Future {
98         ty: TypeFutureTableIndex,
99         rep: u32,
100         state: TransmitLocalState,
101     },
102 
103     /// Represents a waitable-set handle.
104     WaitableSet {
105         rep: u32,
106     },
107 
108     /// Represents an error-context handle.
109     ErrorContext {
110         rep: u32,
111     },
112 }
113 
114 pub struct HandleTable {
115     next: u32,
116     slots: Vec<Slot>,
117 }
118 
119 impl Default for HandleTable {
120     fn default() -> Self {
121         Self {
122             next: 0,
123             slots: Vec::new(),
124         }
125     }
126 }
127 
128 impl HandleTable {
129     /// Returns whether or not this table is empty.
130     pub fn is_empty(&self) -> bool {
131         self.slots
132             .iter()
133             .all(|slot| matches!(slot, Slot::Free { .. }))
134     }
135 
136     fn insert(&mut self, slot: Slot) -> Result<u32> {
137         let next = self.next as usize;
138         if next == self.slots.len() {
139             self.slots.push(Slot::Free {
140                 next: self.next.checked_add(1).unwrap(),
141             });
142         }
143         let ret = self.next;
144         self.next = match mem::replace(&mut self.slots[next], slot) {
145             Slot::Free { next } => next,
146             _ => unreachable!(),
147         };
148         // The component model reserves index 0 as never allocatable so add one
149         // to the table index to start the numbering at 1 instead. Also note
150         // that the component model places an upper-limit per-table on the
151         // maximum allowed index.
152         let ret = ret + 1;
153         if ret >= MAX_HANDLE {
154             bail!("cannot allocate another handle: index overflow");
155         }
156 
157         Ok(ret)
158     }
159 
160     fn remove(&mut self, idx: u32) -> Result<()> {
161         let to_fill = Slot::Free { next: self.next };
162         let slot = self.get_mut(idx)?;
163         *slot = to_fill;
164         self.next = idx - 1;
165         Ok(())
166     }
167 
168     fn handle_index_to_table_index(&self, idx: u32) -> Option<usize> {
169         // NB: `idx` is decremented by one to account for the `+1` above during
170         // allocation.
171         let idx = idx.checked_sub(1)?;
172         usize::try_from(idx).ok()
173     }
174 
175     fn get_mut(&mut self, idx: u32) -> Result<&mut Slot> {
176         let slot = self
177             .handle_index_to_table_index(idx)
178             .and_then(|i| self.slots.get_mut(i));
179         match slot {
180             None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"),
181             Some(slot) => Ok(slot),
182         }
183     }
184 
185     /// Inserts a new `own` resource into this table whose type/rep are
186     /// specified by `resource`.
187     pub fn resource_own_insert(&mut self, resource: TypedResource) -> Result<u32> {
188         self.insert(Slot::ResourceOwn {
189             resource,
190             lend_count: 0,
191         })
192     }
193 
194     /// Inserts a new `borrow` resource into this table whose type/rep are
195     /// specified by `resource`. The `scope` specified is used by
196     /// `CallContexts` to manage lending information.
197     pub fn resource_borrow_insert(&mut self, resource: TypedResource, scope: usize) -> Result<u32> {
198         self.insert(Slot::ResourceBorrow { resource, scope })
199     }
200 
201     /// Returns the internal "rep" of the resource specified by `idx`.
202     ///
203     /// Returns an error if `idx` is out-of-bounds or doesn't point to a
204     /// resource of the appropriate type.
205     pub fn resource_rep(&mut self, idx: TypedResourceIndex) -> Result<u32> {
206         match self.get_mut(idx.raw_index())? {
207             Slot::ResourceOwn { resource, .. } | Slot::ResourceBorrow { resource, .. } => {
208                 resource.rep(&idx)
209             }
210             _ => bail!("index is not a resource"),
211         }
212     }
213 
214     /// Accesses the "rep" of the resource pointed to by `idx` as part of a
215     /// lending operation.
216     ///
217     /// This will increase `lend_count` for owned resources and must be paired
218     /// with a `resource_undo_lend` below later on (managed by `CallContexts`).
219     ///
220     /// Upon success returns the "rep" plus whether the borrow came from an
221     /// `own` handle.
222     pub fn resource_lend(&mut self, idx: TypedResourceIndex) -> Result<(u32, bool)> {
223         match self.get_mut(idx.raw_index())? {
224             Slot::ResourceOwn {
225                 resource,
226                 lend_count,
227             } => {
228                 let rep = resource.rep(&idx)?;
229                 *lend_count = lend_count.checked_add(1).unwrap();
230                 Ok((rep, true))
231             }
232             Slot::ResourceBorrow { resource, .. } => Ok((resource.rep(&idx)?, false)),
233             _ => bail!("index {} is not a resource", idx.raw_index()),
234         }
235     }
236 
237     /// For `own` resources that were borrowed in `resource_lend`, undoes the
238     /// lending operation.
239     pub fn resource_undo_lend(&mut self, idx: TypedResourceIndex) -> Result<()> {
240         match self.get_mut(idx.raw_index())? {
241             Slot::ResourceOwn { lend_count, .. } => {
242                 *lend_count -= 1;
243                 Ok(())
244             }
245             _ => bail!("index {} is not an own resource", idx.raw_index()),
246         }
247     }
248 
249     /// Removes the resource specified by `idx` from the table.
250     ///
251     /// This can fail if `idx` doesn't point to a resource, points to a
252     /// borrowed resource, or points to a resource of the wrong type.
253     pub fn remove_resource(&mut self, idx: TypedResourceIndex) -> Result<RemovedResource> {
254         let ret = match self.get_mut(idx.raw_index())? {
255             Slot::ResourceOwn {
256                 resource,
257                 lend_count,
258             } => {
259                 if *lend_count != 0 {
260                     bail!("cannot remove owned resource while borrowed")
261                 }
262                 RemovedResource::Own {
263                     rep: resource.rep(&idx)?,
264                 }
265             }
266             Slot::ResourceBorrow { resource, scope } => {
267                 // Ensure the drop is done with the right type
268                 resource.rep(&idx)?;
269                 RemovedResource::Borrow { scope: *scope }
270             }
271             _ => bail!("index {} is not a resource", idx.raw_index()),
272         };
273         self.remove(idx.raw_index())?;
274         Ok(ret)
275     }
276 
277     /// Inserts a readable-end stream of type `ty` and with the specified `rep`
278     /// into this table.
279     ///
280     /// Returns the table-local index of the stream.
281     pub fn stream_insert_read(&mut self, ty: TypeStreamTableIndex, rep: u32) -> Result<u32> {
282         self.insert(Slot::Stream {
283             rep,
284             ty,
285             state: TransmitLocalState::Read { done: false },
286         })
287     }
288 
289     /// Inserts a writable-end stream of type `ty` and with the specified `rep`
290     /// into this table.
291     ///
292     /// Returns the table-local index of the stream.
293     pub fn stream_insert_write(&mut self, ty: TypeStreamTableIndex, rep: u32) -> Result<u32> {
294         self.insert(Slot::Stream {
295             rep,
296             ty,
297             state: TransmitLocalState::Write { done: false },
298         })
299     }
300 
301     /// Returns the `rep` and `state` associated with the stream pointed to by
302     /// `idx`.
303     ///
304     /// Returns an error if `idx` is out-of-bounds or doesn't point to a stream
305     /// of type `ty`.
306     pub fn stream_rep(
307         &mut self,
308         expected_ty: TypeStreamTableIndex,
309         idx: u32,
310     ) -> Result<(u32, &mut TransmitLocalState)> {
311         match self.get_mut(idx)? {
312             Slot::Stream { rep, ty, state } => {
313                 if *ty != expected_ty {
314                     bail!("handle is a stream of a different type");
315                 }
316                 Ok((*rep, state))
317             }
318             _ => bail!("handle is not a stream"),
319         }
320     }
321 
322     /// Removes the stream handle from `idx`, returning its `rep`.
323     ///
324     /// The stream must have the type `ty` and additionally be in a state
325     /// suitable for removal.
326     ///
327     /// Returns the `rep` for the stream along with whether the stream was
328     /// "done" or the writable end was witnessed as being done.
329     pub fn stream_remove_readable(
330         &mut self,
331         expected_ty: TypeStreamTableIndex,
332         idx: u32,
333     ) -> Result<(u32, bool)> {
334         let ret = match self.get_mut(idx)? {
335             Slot::Stream { rep, ty, state } => {
336                 if *ty != expected_ty {
337                     bail!("handle is a stream of a different type");
338                 }
339                 let is_done = match state {
340                     TransmitLocalState::Read { done } => *done,
341                     TransmitLocalState::Write { .. } => {
342                         bail!("handle is not a readable end of a stream")
343                     }
344                     TransmitLocalState::Busy => bail!("cannot remove busy stream"),
345                 };
346                 (*rep, is_done)
347             }
348             _ => bail!("handle is not a stream"),
349         };
350         self.remove(idx)?;
351         Ok(ret)
352     }
353 
354     /// Removes the writable stream handle from `idx`, returning its `rep`.
355     pub fn stream_remove_writable(
356         &mut self,
357         expected_ty: TypeStreamTableIndex,
358         idx: u32,
359     ) -> Result<u32> {
360         let ret = match self.get_mut(idx)? {
361             Slot::Stream { rep, ty, state } => {
362                 if *ty != expected_ty {
363                     bail!("handle is a stream of a different type");
364                 }
365                 match state {
366                     TransmitLocalState::Write { .. } => {}
367                     TransmitLocalState::Read { .. } => {
368                         bail!("passed read end to `stream.drop-writable`")
369                     }
370                     TransmitLocalState::Busy => bail!("cannot drop busy stream"),
371                 }
372                 *rep
373             }
374             _ => bail!("handle is not a stream"),
375         };
376         self.remove(idx)?;
377         Ok(ret)
378     }
379 
380     /// Inserts a readable-end future of type `ty` and with the specified `rep`
381     /// into this table.
382     ///
383     /// Returns the table-local index of the future.
384     pub fn future_insert_read(&mut self, ty: TypeFutureTableIndex, rep: u32) -> Result<u32> {
385         self.insert(Slot::Future {
386             rep,
387             ty,
388             state: TransmitLocalState::Read { done: false },
389         })
390     }
391 
392     /// Inserts a writable-end future of type `ty` and with the specified `rep`
393     /// into this table.
394     ///
395     /// Returns the table-local index of the future.
396     pub fn future_insert_write(&mut self, ty: TypeFutureTableIndex, rep: u32) -> Result<u32> {
397         self.insert(Slot::Future {
398             rep,
399             ty,
400             state: TransmitLocalState::Write { done: false },
401         })
402     }
403 
404     /// Returns the `rep` and `state` associated with the future pointed to by
405     /// `idx`.
406     ///
407     /// Returns an error if `idx` is out-of-bounds or doesn't point to a future
408     /// of type `ty`.
409     pub fn future_rep(
410         &mut self,
411         expected_ty: TypeFutureTableIndex,
412         idx: u32,
413     ) -> Result<(u32, &mut TransmitLocalState)> {
414         match self.get_mut(idx)? {
415             Slot::Future { rep, ty, state } => {
416                 if *ty != expected_ty {
417                     bail!("handle is a future of a different type");
418                 }
419                 Ok((*rep, state))
420             }
421             _ => bail!("handle is not a future"),
422         }
423     }
424 
425     /// Removes the future handle from `idx`, returning its `rep`.
426     ///
427     /// The future must have the type `ty` and additionally be in a state
428     /// suitable for removal.
429     ///
430     /// Returns the `rep` for the future along with whether the future was
431     /// "done" or the writable end was witnessed as being done.
432     pub fn future_remove_readable(
433         &mut self,
434         expected_ty: TypeFutureTableIndex,
435         idx: u32,
436     ) -> Result<(u32, bool)> {
437         let ret = match self.get_mut(idx)? {
438             Slot::Future { rep, ty, state } => {
439                 if *ty != expected_ty {
440                     bail!("handle is a future of a different type");
441                 }
442                 let is_done = match state {
443                     TransmitLocalState::Read { done } => *done,
444                     TransmitLocalState::Write { .. } => {
445                         bail!("handle is not a readable end of a future")
446                     }
447                     TransmitLocalState::Busy => bail!("cannot remove busy future"),
448                 };
449                 (*rep, is_done)
450             }
451             _ => bail!("handle is not a future"),
452         };
453         self.remove(idx)?;
454         Ok(ret)
455     }
456 
457     /// Removes the writable future handle from `idx`, returning its `rep`.
458     pub fn future_remove_writable(
459         &mut self,
460         expected_ty: TypeFutureTableIndex,
461         idx: u32,
462     ) -> Result<u32> {
463         let ret = match self.get_mut(idx)? {
464             Slot::Future { rep, ty, state } => {
465                 if *ty != expected_ty {
466                     bail!("handle is a future of a different type");
467                 }
468                 match state {
469                     TransmitLocalState::Write { .. } => {}
470                     TransmitLocalState::Read { .. } => {
471                         bail!("passed read end to `future.drop-writable`")
472                     }
473                     TransmitLocalState::Busy => bail!("cannot drop busy future"),
474                 }
475                 *rep
476             }
477             _ => bail!("handle is not a future"),
478         };
479         self.remove(idx)?;
480         Ok(ret)
481     }
482 
483     /// Inserts the error-context `rep` into this table, returning the index it
484     /// now resides at.
485     pub fn error_context_insert(&mut self, rep: u32) -> Result<u32> {
486         self.insert(Slot::ErrorContext { rep })
487     }
488 
489     /// Returns the `rep` of an error-context pointed to by `idx`.
490     pub fn error_context_rep(&mut self, idx: u32) -> Result<u32> {
491         match self.get_mut(idx)? {
492             Slot::ErrorContext { rep } => Ok(*rep),
493             _ => bail!("handle is not an error-context"),
494         }
495     }
496 
497     /// Drops the error-context pointed to by `idx`.
498     ///
499     /// Returns the internal `rep`.
500     pub fn error_context_drop(&mut self, idx: u32) -> Result<u32> {
501         let rep = match self.get_mut(idx)? {
502             Slot::ErrorContext { rep } => *rep,
503             _ => bail!("handle is not an error-context"),
504         };
505         self.remove(idx)?;
506         Ok(rep)
507     }
508 
509     /// Inserts `rep` as a guest subtask into this table.
510     pub fn subtask_insert_guest(&mut self, rep: u32) -> Result<u32> {
511         self.insert(Slot::GuestTask { rep })
512     }
513 
514     /// Inserts `rep` as a host subtask into this table.
515     pub fn subtask_insert_host(&mut self, rep: u32) -> Result<u32> {
516         self.insert(Slot::HostTask { rep })
517     }
518 
519     /// Returns the `rep` of the subtask at `idx` as well as if it's a host
520     /// task or not.
521     pub fn subtask_rep(&mut self, idx: u32) -> Result<(u32, bool)> {
522         match self.get_mut(idx)? {
523             Slot::GuestTask { rep } => Ok((*rep, false)),
524             Slot::HostTask { rep } => Ok((*rep, true)),
525             _ => bail!("handle is not a subtask"),
526         }
527     }
528 
529     /// Removes the subtask set at `idx`, returning its `rep`.
530     pub fn subtask_remove(&mut self, idx: u32) -> Result<(u32, bool)> {
531         let ret = match self.get_mut(idx)? {
532             Slot::GuestTask { rep } => (*rep, false),
533             Slot::HostTask { rep } => (*rep, true),
534             _ => bail!("handle is not a subtask"),
535         };
536         self.remove(idx)?;
537         Ok(ret)
538     }
539 
540     /// Inserts `rep` as a waitable set into this table.
541     pub fn waitable_set_insert(&mut self, rep: u32) -> Result<u32> {
542         self.insert(Slot::WaitableSet { rep })
543     }
544 
545     /// Returns the `rep` of an waitable-set pointed to by `idx`.
546     pub fn waitable_set_rep(&mut self, idx: u32) -> Result<u32> {
547         match self.get_mut(idx)? {
548             Slot::WaitableSet { rep, .. } => Ok(*rep),
549             _ => bail!("handle is not an waitable-set"),
550         }
551     }
552 
553     /// Removes the waitable set at `idx`, returning its `rep`.
554     pub fn waitable_set_remove(&mut self, idx: u32) -> Result<u32> {
555         let ret = match self.get_mut(idx)? {
556             Slot::WaitableSet { rep } => *rep,
557             _ => bail!("handle is not a waitable-set"),
558         };
559         self.remove(idx)?;
560         Ok(ret)
561     }
562 
563     /// Returns the `rep` for the waitable specified by `idx` along with what
564     /// kind of waitable it is.
565     pub fn waitable_rep(&mut self, idx: u32) -> Result<(u32, Waitable)> {
566         match self.get_mut(idx)? {
567             Slot::GuestTask { rep } => Ok((*rep, Waitable::Subtask { is_host: false })),
568             Slot::HostTask { rep } => Ok((*rep, Waitable::Subtask { is_host: true })),
569             Slot::Future { rep, .. } => Ok((*rep, Waitable::Future)),
570             Slot::Stream { rep, .. } => Ok((*rep, Waitable::Stream)),
571             _ => bail!("handle is not a waitable"),
572         }
573     }
574 
575     /// Inserts the guest thread `rep` into this table, returning the index it
576     /// now resides at.
577     pub fn guest_thread_insert(&mut self, rep: u32) -> Result<u32> {
578         self.insert(Slot::GuestThread { rep })
579     }
580 
581     /// Returns the `rep` of a guest thread pointed to by `idx`.
582     pub fn guest_thread_rep(&mut self, idx: u32) -> Result<u32> {
583         match self.get_mut(idx)? {
584             Slot::GuestThread { rep } => Ok(*rep),
585             _ => bail!("handle is not a guest thread"),
586         }
587     }
588 
589     /// Removes the guest thread pointed to by `idx`.
590     ///
591     /// Returns the internal `rep`.
592     pub fn guest_thread_remove(&mut self, idx: u32) -> Result<u32> {
593         let rep = match self.get_mut(idx)? {
594             Slot::GuestThread { rep } => *rep,
595             _ => bail!("handle is not a guest thread"),
596         };
597         self.remove(idx)?;
598         Ok(rep)
599     }
600 }
601