use super::{Scope, TypedResource, TypedResourceIndex};
use crate::prelude::TryVec;
use crate::{Result, bail};
use core::mem;
use wasmtime_environ::component::{TypeFutureTableIndex, TypeStreamTableIndex};
const MAX_HANDLE: u32 = 1 << 28;
pub struct HandleCounter {
live: usize,
limit: usize,
}
impl HandleCounter {
pub fn new(limit: usize) -> HandleCounter {
HandleCounter { live: 0, limit }
}
pub fn limit(&self) -> usize {
self.limit
}
pub fn set_limit(&mut self, limit: usize) {
self.limit = limit;
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum TransmitLocalState {
Write {
done: bool,
},
Read {
done: bool,
},
Busy,
}
pub enum RemovedResource {
Own { rep: u32 },
Borrow { scope: Scope },
}
pub enum Waitable {
Subtask { is_host: bool },
Future,
Stream,
}
#[derive(Debug)]
enum Slot {
Free {
next: u32,
},
ResourceOwn {
resource: TypedResource,
lend_count: u32,
},
ResourceBorrow {
resource: TypedResource,
scope: Scope,
},
HostTask {
rep: u32,
},
GuestTask {
rep: u32,
},
#[cfg(feature = "component-model-async")]
GuestThread {
rep: u32,
},
Stream {
ty: TypeStreamTableIndex,
rep: u32,
state: TransmitLocalState,
},
Future {
ty: TypeFutureTableIndex,
rep: u32,
state: TransmitLocalState,
},
WaitableSet {
rep: u32,
},
ErrorContext {
rep: u32,
},
}
pub struct HandleTable {
next: u32,
slots: TryVec<Slot>,
}
impl Default for HandleTable {
fn default() -> Self {
Self {
next: 0,
slots: TryVec::new(),
}
}
}
impl HandleTable {
pub fn is_empty(&self) -> bool {
self.slots
.iter()
.all(|slot| matches!(slot, Slot::Free { .. }))
}
fn insert(&mut self, counter: &mut HandleCounter, slot: Slot) -> Result<u32> {
let next = self.next;
if next + 1 >= MAX_HANDLE {
bail!("cannot allocate another handle: index overflow");
}
if counter.live >= counter.limit {
bail!("cannot allocate another handle: store handle limit exceeded");
}
if next as usize == self.slots.len() {
self.slots.push(Slot::Free {
next: next.checked_add(1).unwrap(),
})?;
}
self.next = match mem::replace(&mut self.slots[next as usize], slot) {
Slot::Free { next } => next,
_ => unreachable!(),
};
counter.live += 1;
Ok(next + 1)
}
fn remove(&mut self, counter: &mut HandleCounter, idx: u32) -> Result<()> {
let to_fill = Slot::Free { next: self.next };
let slot = self.get_mut(idx)?;
*slot = to_fill;
self.next = idx - 1;
counter.live -= 1;
Ok(())
}
fn handle_index_to_table_index(&self, idx: u32) -> Option<usize> {
let idx = idx.checked_sub(1)?;
usize::try_from(idx).ok()
}
fn get_mut(&mut self, idx: u32) -> Result<&mut Slot> {
let slot = self
.handle_index_to_table_index(idx)
.and_then(|i| self.slots.get_mut(i));
match slot {
None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"),
Some(slot) => Ok(slot),
}
}
pub fn resource_own_insert(
&mut self,
counter: &mut HandleCounter,
resource: TypedResource,
) -> Result<u32> {
self.insert(
counter,
Slot::ResourceOwn {
resource,
lend_count: 0,
},
)
}
pub fn resource_borrow_insert(
&mut self,
counter: &mut HandleCounter,
resource: TypedResource,
scope: Scope,
) -> Result<u32> {
self.insert(counter, Slot::ResourceBorrow { resource, scope })
}
pub fn resource_rep(&mut self, idx: TypedResourceIndex) -> Result<u32> {
match self.get_mut(idx.raw_index())? {
Slot::ResourceOwn { resource, .. } | Slot::ResourceBorrow { resource, .. } => {
resource.rep(&idx)
}
_ => bail!("index is not a resource"),
}
}
pub fn resource_lend(
&mut self,
counter: &mut HandleCounter,
idx: TypedResourceIndex,
) -> Result<(u32, bool)> {
match self.get_mut(idx.raw_index())? {
Slot::ResourceOwn {
resource,
lend_count,
} => {
let rep = resource.rep(&idx)?;
let Some(new_lend_count) = lend_count.checked_add(1) else {
bail!("cannot lend resource: too many active lends")
};
if counter.live >= counter.limit {
bail!("cannot lend resource: store handle limit exceeded");
}
counter.live += 1;
*lend_count = new_lend_count;
Ok((rep, true))
}
Slot::ResourceBorrow { resource, .. } => Ok((resource.rep(&idx)?, false)),
_ => bail!("index {} is not a resource", idx.raw_index()),
}
}
pub fn resource_undo_lend(
&mut self,
counter: &mut HandleCounter,
idx: TypedResourceIndex,
) -> Result<()> {
match self.get_mut(idx.raw_index())? {
Slot::ResourceOwn { lend_count, .. } => {
*lend_count -= 1;
counter.live -= 1;
Ok(())
}
_ => bail!("index {} is not an own resource", idx.raw_index()),
}
}
pub fn remove_resource(
&mut self,
counter: &mut HandleCounter,
idx: TypedResourceIndex,
) -> Result<RemovedResource> {
let ret = match self.get_mut(idx.raw_index())? {
Slot::ResourceOwn {
resource,
lend_count,
} => {
if *lend_count != 0 {
bail!("cannot remove owned resource while borrowed")
}
RemovedResource::Own {
rep: resource.rep(&idx)?,
}
}
Slot::ResourceBorrow { resource, scope } => {
resource.rep(&idx)?;
RemovedResource::Borrow { scope: *scope }
}
_ => bail!("index {} is not a resource", idx.raw_index()),
};
self.remove(counter, idx.raw_index())?;
Ok(ret)
}
pub fn stream_insert_read(
&mut self,
counter: &mut HandleCounter,
ty: TypeStreamTableIndex,
rep: u32,
) -> Result<u32> {
self.insert(
counter,
Slot::Stream {
rep,
ty,
state: TransmitLocalState::Read { done: false },
},
)
}
pub fn stream_insert_write(
&mut self,
counter: &mut HandleCounter,
ty: TypeStreamTableIndex,
rep: u32,
) -> Result<u32> {
self.insert(
counter,
Slot::Stream {
rep,
ty,
state: TransmitLocalState::Write { done: false },
},
)
}
pub fn stream_rep(
&mut self,
expected_ty: TypeStreamTableIndex,
idx: u32,
) -> Result<(u32, &mut TransmitLocalState)> {
match self.get_mut(idx)? {
Slot::Stream { rep, ty, state } => {
if *ty != expected_ty {
bail!("handle is a stream of a different type");
}
Ok((*rep, state))
}
_ => bail!("handle is not a stream"),
}
}
pub fn stream_remove_readable(
&mut self,
counter: &mut HandleCounter,
expected_ty: TypeStreamTableIndex,
idx: u32,
) -> Result<(u32, bool)> {
let ret = match self.get_mut(idx)? {
Slot::Stream { rep, ty, state } => {
if *ty != expected_ty {
bail!("handle is a stream of a different type");
}
let is_done = match state {
TransmitLocalState::Read { done } => *done,
TransmitLocalState::Write { .. } => {
bail!("handle is not a readable end of a stream")
}
TransmitLocalState::Busy => bail!("cannot remove busy stream"),
};
(*rep, is_done)
}
_ => bail!("handle is not a stream"),
};
self.remove(counter, idx)?;
Ok(ret)
}
pub fn stream_remove_writable(
&mut self,
counter: &mut HandleCounter,
expected_ty: TypeStreamTableIndex,
idx: u32,
) -> Result<u32> {
let ret = match self.get_mut(idx)? {
Slot::Stream { rep, ty, state } => {
if *ty != expected_ty {
bail!("handle is a stream of a different type");
}
match state {
TransmitLocalState::Write { .. } => {}
TransmitLocalState::Read { .. } => {
bail!("passed read end to `stream.drop-writable`")
}
TransmitLocalState::Busy => bail!("cannot drop busy stream"),
}
*rep
}
_ => bail!("handle is not a stream"),
};
self.remove(counter, idx)?;
Ok(ret)
}
pub fn future_insert_read(
&mut self,
counter: &mut HandleCounter,
ty: TypeFutureTableIndex,
rep: u32,
) -> Result<u32> {
self.insert(
counter,
Slot::Future {
rep,
ty,
state: TransmitLocalState::Read { done: false },
},
)
}
pub fn future_insert_write(
&mut self,
counter: &mut HandleCounter,
ty: TypeFutureTableIndex,
rep: u32,
) -> Result<u32> {
self.insert(
counter,
Slot::Future {
rep,
ty,
state: TransmitLocalState::Write { done: false },
},
)
}
pub fn future_rep(
&mut self,
expected_ty: TypeFutureTableIndex,
idx: u32,
) -> Result<(u32, &mut TransmitLocalState)> {
match self.get_mut(idx)? {
Slot::Future { rep, ty, state } => {
if *ty != expected_ty {
bail!("handle is a future of a different type");
}
Ok((*rep, state))
}
_ => bail!("handle is not a future"),
}
}
pub fn future_remove_readable(
&mut self,
counter: &mut HandleCounter,
expected_ty: TypeFutureTableIndex,
idx: u32,
) -> Result<(u32, bool)> {
let ret = match self.get_mut(idx)? {
Slot::Future { rep, ty, state } => {
if *ty != expected_ty {
bail!("handle is a future of a different type");
}
let is_done = match state {
TransmitLocalState::Read { done } => *done,
TransmitLocalState::Write { .. } => {
bail!("handle is not a readable end of a future")
}
TransmitLocalState::Busy => bail!("cannot remove busy future"),
};
(*rep, is_done)
}
_ => bail!("handle is not a future"),
};
self.remove(counter, idx)?;
Ok(ret)
}
pub fn future_remove_writable(
&mut self,
counter: &mut HandleCounter,
expected_ty: TypeFutureTableIndex,
idx: u32,
) -> Result<(u32, bool)> {
let ret = match self.get_mut(idx)? {
Slot::Future { rep, ty, state } => {
if *ty != expected_ty {
bail!("handle is a future of a different type");
}
let is_done = match state {
TransmitLocalState::Write { done } => *done,
TransmitLocalState::Read { .. } => {
bail!("passed read end to `future.drop-writable`")
}
TransmitLocalState::Busy => bail!("cannot drop busy future"),
};
(*rep, is_done)
}
_ => bail!("handle is not a future"),
};
self.remove(counter, idx)?;
Ok(ret)
}
pub fn error_context_insert(&mut self, counter: &mut HandleCounter, rep: u32) -> Result<u32> {
self.insert(counter, Slot::ErrorContext { rep })
}
pub fn error_context_rep(&mut self, idx: u32) -> Result<u32> {
match self.get_mut(idx)? {
Slot::ErrorContext { rep } => Ok(*rep),
_ => bail!("handle is not an error-context"),
}
}
pub fn error_context_drop(&mut self, counter: &mut HandleCounter, idx: u32) -> Result<u32> {
let rep = match self.get_mut(idx)? {
Slot::ErrorContext { rep } => *rep,
_ => bail!("handle is not an error-context"),
};
self.remove(counter, idx)?;
Ok(rep)
}
pub fn subtask_insert_guest(&mut self, counter: &mut HandleCounter, rep: u32) -> Result<u32> {
self.insert(counter, Slot::GuestTask { rep })
}
pub fn subtask_insert_host(&mut self, counter: &mut HandleCounter, rep: u32) -> Result<u32> {
self.insert(counter, Slot::HostTask { rep })
}
pub fn subtask_rep(&mut self, idx: u32) -> Result<(u32, bool)> {
match self.get_mut(idx)? {
Slot::GuestTask { rep } => Ok((*rep, false)),
Slot::HostTask { rep } => Ok((*rep, true)),
_ => bail!("handle is not a subtask"),
}
}
pub fn subtask_remove(&mut self, counter: &mut HandleCounter, idx: u32) -> Result<(u32, bool)> {
let ret = match self.get_mut(idx)? {
Slot::GuestTask { rep } => (*rep, false),
Slot::HostTask { rep } => (*rep, true),
_ => bail!("handle is not a subtask"),
};
self.remove(counter, idx)?;
Ok(ret)
}
pub fn waitable_set_insert(&mut self, counter: &mut HandleCounter, rep: u32) -> Result<u32> {
self.insert(counter, Slot::WaitableSet { rep })
}
pub fn waitable_set_rep(&mut self, idx: u32) -> Result<u32> {
match self.get_mut(idx)? {
Slot::WaitableSet { rep, .. } => Ok(*rep),
_ => bail!("handle is not an waitable-set"),
}
}
pub fn waitable_set_remove(&mut self, counter: &mut HandleCounter, idx: u32) -> Result<u32> {
let ret = match self.get_mut(idx)? {
Slot::WaitableSet { rep } => *rep,
_ => bail!("handle is not a waitable-set"),
};
self.remove(counter, idx)?;
Ok(ret)
}
pub fn waitable_rep(&mut self, idx: u32) -> Result<(u32, Waitable)> {
match self.get_mut(idx)? {
Slot::GuestTask { rep } => Ok((*rep, Waitable::Subtask { is_host: false })),
Slot::HostTask { rep } => Ok((*rep, Waitable::Subtask { is_host: true })),
Slot::Future { rep, .. } => Ok((*rep, Waitable::Future)),
Slot::Stream { rep, .. } => Ok((*rep, Waitable::Stream)),
_ => bail!("handle is not a waitable"),
}
}
}
#[derive(Default)]
#[cfg(feature = "component-model-async")]
pub struct ThreadHandleTable(HandleTable);
#[cfg(feature = "component-model-async")]
impl ThreadHandleTable {
pub fn guest_thread_insert(&mut self, counter: &mut HandleCounter, rep: u32) -> Result<u32> {
self.0.insert(counter, Slot::GuestThread { rep })
}
pub fn guest_thread_rep(&mut self, idx: u32) -> Result<u32> {
match self.0.get_mut(idx)? {
Slot::GuestThread { rep } => Ok(*rep),
_ => bail!("handle is not a guest thread"),
}
}
pub fn guest_thread_remove(&mut self, counter: &mut HandleCounter, idx: u32) -> Result<u32> {
let rep = match self.0.get_mut(idx)? {
Slot::GuestThread { rep } => *rep,
_ => bail!("handle is not a guest thread"),
};
self.0.remove(counter, idx)?;
Ok(rep)
}
}