use anyhow::{bail, Result};
use std::mem;
use wasmtime_environ::component::TypeResourceTableIndex;
use wasmtime_environ::PrimaryMap;
const MAX_RESOURCE_HANDLE: u32 = 1 << 30;
pub struct ResourceTables<'a> {
pub tables: Option<&'a mut PrimaryMap<TypeResourceTableIndex, ResourceTable>>,
pub host_table: Option<&'a mut ResourceTable>,
pub calls: &'a mut CallContexts,
}
#[derive(Default)]
pub struct ResourceTable {
next: u32,
slots: Vec<Slot>,
}
enum Slot {
Free { next: u32 },
Own { rep: u32, lend_count: u32 },
Borrow { rep: u32, scope: usize },
}
#[derive(Default)]
pub struct CallContexts {
scopes: Vec<CallContext>,
}
#[derive(Default)]
struct CallContext {
lenders: Vec<Lender>,
borrow_count: u32,
}
#[derive(Copy, Clone)]
struct Lender {
ty: Option<TypeResourceTableIndex>,
idx: u32,
}
impl ResourceTables<'_> {
fn table(&mut self, ty: Option<TypeResourceTableIndex>) -> &mut ResourceTable {
match ty {
None => self.host_table.as_mut().unwrap(),
Some(idx) => &mut self.tables.as_mut().unwrap()[idx],
}
}
pub fn resource_new(&mut self, ty: Option<TypeResourceTableIndex>, rep: u32) -> Result<u32> {
self.table(ty).insert(Slot::Own { rep, lend_count: 0 })
}
pub fn resource_rep(&mut self, ty: Option<TypeResourceTableIndex>, idx: u32) -> Result<u32> {
self.table(ty).rep(idx)
}
pub fn resource_drop(
&mut self,
ty: Option<TypeResourceTableIndex>,
idx: u32,
) -> Result<Option<u32>> {
match self.table(ty).remove(idx)? {
Slot::Own { rep, lend_count: 0 } => Ok(Some(rep)),
Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"),
Slot::Borrow { scope, .. } => {
self.calls.scopes[scope].borrow_count -= 1;
Ok(None)
}
Slot::Free { .. } => unreachable!(),
}
}
pub fn resource_lower_own(
&mut self,
ty: Option<TypeResourceTableIndex>,
rep: u32,
) -> Result<u32> {
self.table(ty).insert(Slot::Own { rep, lend_count: 0 })
}
pub fn resource_lift_own(
&mut self,
ty: Option<TypeResourceTableIndex>,
idx: u32,
) -> Result<u32> {
match self.table(ty).remove(idx)? {
Slot::Own { rep, lend_count: 0 } => Ok(rep),
Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"),
Slot::Borrow { .. } => bail!("cannot lift own resource from a borrow"),
Slot::Free { .. } => unreachable!(),
}
}
pub fn resource_lift_borrow(
&mut self,
ty: Option<TypeResourceTableIndex>,
idx: u32,
) -> Result<u32> {
match self.table(ty).get_mut(idx)? {
Slot::Own { rep, lend_count } => {
*lend_count = lend_count.checked_add(1).unwrap();
let rep = *rep;
let scope = self.calls.scopes.last_mut().unwrap();
scope.lenders.push(Lender { ty, idx });
Ok(rep)
}
Slot::Borrow { rep, .. } => Ok(*rep),
Slot::Free { .. } => unreachable!(),
}
}
pub fn resource_lower_borrow(
&mut self,
ty: Option<TypeResourceTableIndex>,
rep: u32,
) -> Result<u32> {
let scope = self.calls.scopes.len() - 1;
let borrow_count = &mut self.calls.scopes.last_mut().unwrap().borrow_count;
*borrow_count = borrow_count.checked_add(1).unwrap();
self.table(ty).insert(Slot::Borrow { rep, scope })
}
#[inline]
pub fn enter_call(&mut self) {
self.calls.scopes.push(CallContext::default());
}
#[inline]
pub fn exit_call(&mut self) -> Result<()> {
let cx = self.calls.scopes.pop().unwrap();
if cx.borrow_count > 0 {
bail!("borrow handles still remain at the end of the call")
}
for lender in cx.lenders.iter() {
match self.table(lender.ty).get_mut(lender.idx).unwrap() {
Slot::Own { lend_count, .. } => {
*lend_count -= 1;
}
_ => unreachable!(),
}
}
Ok(())
}
}
impl ResourceTable {
fn insert(&mut self, new: Slot) -> Result<u32> {
let next = self.next as usize;
if next == self.slots.len() {
self.slots.push(Slot::Free {
next: self.next.checked_add(1).unwrap(),
});
}
let ret = self.next;
self.next = match mem::replace(&mut self.slots[next], new) {
Slot::Free { next } => next,
_ => unreachable!(),
};
let ret = ret + 1;
if ret >= MAX_RESOURCE_HANDLE {
bail!("cannot allocate another handle: index overflow");
}
Ok(ret)
}
fn handle_index_to_table_index(&self, idx: u32) -> Option<usize> {
let idx = idx.checked_sub(1)?;
usize::try_from(idx).ok()
}
fn rep(&self, idx: u32) -> Result<u32> {
let slot = self
.handle_index_to_table_index(idx)
.and_then(|i| self.slots.get(i));
match slot {
None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"),
Some(Slot::Own { rep, .. } | Slot::Borrow { rep, .. }) => Ok(*rep),
}
}
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(other) => Ok(other),
}
}
fn remove(&mut self, idx: u32) -> Result<Slot> {
let to_fill = Slot::Free { next: self.next };
let ret = mem::replace(self.get_mut(idx)?, to_fill);
self.next = idx - 1;
Ok(ret)
}
}