use crate::component::func::{bad_type_info, desc, LiftContext, LowerContext};
use crate::component::matching::InstanceType;
use crate::component::{ComponentType, Lift, Lower};
use crate::prelude::*;
use crate::runtime::vm::component::{ComponentInstance, InstanceFlags, ResourceTables};
use crate::runtime::vm::{SendSyncPtr, VMFuncRef, ValRaw};
use crate::store::{StoreId, StoreOpaque};
use crate::{AsContextMut, StoreContextMut, Trap};
use anyhow::{bail, ensure, Result};
use core::any::TypeId;
use core::fmt;
use core::marker;
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use core::sync::atomic::{AtomicU64, Ordering::Relaxed};
use wasmtime_environ::component::{
CanonicalAbiInfo, ComponentTypes, DefinedResourceIndex, InterfaceType, ResourceIndex,
TypeResourceTableIndex,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ResourceType {
kind: ResourceTypeKind,
}
impl ResourceType {
pub fn host<T: 'static>() -> ResourceType {
ResourceType {
kind: ResourceTypeKind::Host(TypeId::of::<T>()),
}
}
pub(crate) fn guest(
store: StoreId,
instance: &ComponentInstance,
id: DefinedResourceIndex,
) -> ResourceType {
ResourceType {
kind: ResourceTypeKind::Guest {
store,
instance: instance as *const _ as usize,
id,
},
}
}
pub(crate) fn uninstantiated(types: &ComponentTypes, index: ResourceIndex) -> ResourceType {
ResourceType {
kind: ResourceTypeKind::Uninstantiated {
component: types as *const _ as usize,
index,
},
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum ResourceTypeKind {
Host(TypeId),
Guest {
store: StoreId,
instance: usize,
id: DefinedResourceIndex,
},
Uninstantiated {
component: usize,
index: ResourceIndex,
},
}
pub struct Resource<T> {
rep: u32,
_marker: marker::PhantomData<fn() -> T>,
state: AtomicResourceState,
}
struct AtomicResourceState(AtomicU64);
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum ResourceState {
Borrow,
NotInTable,
Taken,
Index(HostResourceIndex),
}
impl AtomicResourceState {
const BORROW: Self = Self(AtomicU64::new(ResourceState::BORROW));
const NOT_IN_TABLE: Self = Self(AtomicU64::new(ResourceState::NOT_IN_TABLE));
fn get(&self) -> ResourceState {
ResourceState::decode(self.0.load(Relaxed))
}
fn swap(&self, state: ResourceState) -> ResourceState {
ResourceState::decode(self.0.swap(state.encode(), Relaxed))
}
}
impl ResourceState {
const BORROW: u64 = u64::MAX;
const NOT_IN_TABLE: u64 = u64::MAX - 1;
const TAKEN: u64 = u64::MAX - 2;
fn decode(bits: u64) -> ResourceState {
match bits {
Self::BORROW => Self::Borrow,
Self::NOT_IN_TABLE => Self::NotInTable,
Self::TAKEN => Self::Taken,
other => Self::Index(HostResourceIndex(other)),
}
}
fn encode(&self) -> u64 {
match self {
Self::Borrow => Self::BORROW,
Self::NotInTable => Self::NOT_IN_TABLE,
Self::Taken => Self::TAKEN,
Self::Index(index) => index.0,
}
}
}
pub struct HostResourceTables<'a> {
tables: ResourceTables<'a>,
host_resource_data: &'a mut HostResourceData,
}
#[derive(Default)]
pub struct HostResourceData {
cur_generation: u32,
table_slot_metadata: Vec<TableSlot>,
}
#[derive(Copy, Clone)]
struct TableSlot {
generation: u32,
flags: Option<InstanceFlags>,
dtor: Option<SendSyncPtr<VMFuncRef>>,
}
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
#[repr(transparent)]
pub struct HostResourceIndex(u64);
impl HostResourceIndex {
fn new(idx: u32, gen: u32) -> HostResourceIndex {
HostResourceIndex(u64::from(idx) | (u64::from(gen) << 32))
}
fn index(&self) -> u32 {
u32::try_from(self.0 & 0xffffffff).unwrap()
}
fn gen(&self) -> u32 {
u32::try_from(self.0 >> 32).unwrap()
}
}
impl<'a> HostResourceTables<'a> {
pub fn new_host(store: &'a mut StoreOpaque) -> HostResourceTables<'_> {
let (calls, host_table, host_resource_data) = store.component_resource_state();
HostResourceTables::from_parts(
ResourceTables {
host_table: Some(host_table),
calls,
tables: None,
},
host_resource_data,
)
}
pub fn from_parts(
tables: ResourceTables<'a>,
host_resource_data: &'a mut HostResourceData,
) -> Self {
HostResourceTables {
tables,
host_resource_data,
}
}
pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> {
let (idx, _) = self.validate_host_index(idx, true)?;
self.tables.resource_lift_own(None, idx)
}
pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> {
let (idx, _) = self.validate_host_index(idx, false)?;
self.tables.resource_lift_borrow(None, idx)
}
pub fn host_resource_lower_own(
&mut self,
rep: u32,
dtor: Option<NonNull<VMFuncRef>>,
flags: Option<InstanceFlags>,
) -> Result<HostResourceIndex> {
let idx = self.tables.resource_lower_own(None, rep)?;
Ok(self.new_host_index(idx, dtor, flags))
}
pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result<HostResourceIndex> {
let idx = self.tables.resource_lower_borrow(None, rep)?;
Ok(self.new_host_index(idx, None, None))
}
fn validate_host_index(
&mut self,
idx: HostResourceIndex,
is_removal: bool,
) -> Result<(u32, Option<TableSlot>)> {
let actual = usize::try_from(idx.index())
.ok()
.and_then(|i| self.host_resource_data.table_slot_metadata.get(i).copied());
if let Some(actual) = actual {
if actual.generation != idx.gen() {
bail!("host-owned resource is being used with the wrong type");
}
}
if is_removal {
self.host_resource_data.cur_generation += 1;
}
Ok((idx.index(), actual))
}
fn new_host_index(
&mut self,
idx: u32,
dtor: Option<NonNull<VMFuncRef>>,
flags: Option<InstanceFlags>,
) -> HostResourceIndex {
let list = &mut self.host_resource_data.table_slot_metadata;
let info = TableSlot {
generation: self.host_resource_data.cur_generation,
flags,
dtor: dtor.map(SendSyncPtr::new),
};
match list.get_mut(idx as usize) {
Some(slot) => *slot = info,
None => {
if list.is_empty() {
assert_eq!(idx, 1);
list.push(TableSlot {
generation: 0,
flags: None,
dtor: None,
});
}
assert_eq!(idx as usize, list.len());
list.push(info);
}
}
HostResourceIndex::new(idx, info.generation)
}
fn host_resource_drop(&mut self, idx: HostResourceIndex) -> Result<Option<(u32, TableSlot)>> {
let (idx, slot) = self.validate_host_index(idx, true)?;
match self.tables.resource_drop(None, idx)? {
Some(rep) => Ok(Some((rep, slot.unwrap()))),
None => Ok(None),
}
}
pub fn guest_resource_lower_own(
&mut self,
rep: u32,
ty: TypeResourceTableIndex,
) -> Result<u32> {
self.tables.resource_lower_own(Some(ty), rep)
}
pub fn guest_resource_lower_borrow(
&mut self,
rep: u32,
ty: TypeResourceTableIndex,
) -> Result<u32> {
self.tables.resource_lower_borrow(Some(ty), rep)
}
pub fn guest_resource_lift_own(&mut self, idx: u32, ty: TypeResourceTableIndex) -> Result<u32> {
self.tables.resource_lift_own(Some(ty), idx)
}
pub fn guest_resource_lift_borrow(
&mut self,
idx: u32,
ty: TypeResourceTableIndex,
) -> Result<u32> {
self.tables.resource_lift_borrow(Some(ty), idx)
}
#[inline]
pub fn enter_call(&mut self) {
self.tables.enter_call()
}
#[inline]
pub fn exit_call(&mut self) -> Result<()> {
self.tables.exit_call()
}
}
impl<T> Resource<T>
where
T: 'static,
{
pub fn new_own(rep: u32) -> Resource<T> {
Resource {
state: AtomicResourceState::NOT_IN_TABLE,
rep,
_marker: marker::PhantomData,
}
}
pub fn new_borrow(rep: u32) -> Resource<T> {
Resource {
state: AtomicResourceState::BORROW,
rep,
_marker: marker::PhantomData,
}
}
pub fn rep(&self) -> u32 {
self.rep
}
pub fn owned(&self) -> bool {
match self.state.get() {
ResourceState::Borrow => false,
ResourceState::Taken | ResourceState::NotInTable | ResourceState::Index(_) => true,
}
}
fn lower_to_index<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result<u32> {
match ty {
InterfaceType::Own(t) => {
let rep = match self.state.get() {
ResourceState::Borrow => {
bail!("cannot lower a `borrow` resource into an `own`")
}
ResourceState::NotInTable => {
let prev = self.state.swap(ResourceState::Taken);
assert_eq!(prev, ResourceState::NotInTable);
self.rep
}
ResourceState::Taken => bail!("host resource already consumed"),
ResourceState::Index(idx) => cx.host_resource_lift_own(idx)?,
};
cx.guest_resource_lower_own(t, rep)
}
InterfaceType::Borrow(t) => {
let rep = match self.state.get() {
ResourceState::Borrow => self.rep,
ResourceState::Taken => bail!("host resource already consumed"),
ResourceState::NotInTable => {
let idx = cx.host_resource_lower_own(self.rep, None, None)?;
let prev = self.state.swap(ResourceState::Index(idx));
assert_eq!(prev, ResourceState::NotInTable);
cx.host_resource_lift_borrow(idx)?
}
ResourceState::Index(idx) => cx.host_resource_lift_borrow(idx)?,
};
cx.guest_resource_lower_borrow(t, rep)
}
_ => bad_type_info(),
}
}
fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> {
let (state, rep) = match ty {
InterfaceType::Own(t) => {
debug_assert!(cx.resource_type(t) == ResourceType::host::<T>());
let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?;
assert!(dtor.is_some());
assert!(flags.is_none());
(AtomicResourceState::NOT_IN_TABLE, rep)
}
InterfaceType::Borrow(t) => {
debug_assert!(cx.resource_type(t) == ResourceType::host::<T>());
let rep = cx.guest_resource_lift_borrow(t, index)?;
(AtomicResourceState::BORROW, rep)
}
_ => bad_type_info(),
};
Ok(Resource {
state,
rep,
_marker: marker::PhantomData,
})
}
pub fn try_from_resource_any(
resource: ResourceAny,
mut store: impl AsContextMut,
) -> Result<Self> {
let store = store.as_context_mut();
let mut tables = HostResourceTables::new_host(store.0);
let ResourceAny { idx, ty, owned } = resource;
ensure!(ty == ResourceType::host::<T>(), "resource type mismatch");
let (state, rep) = if owned {
let rep = tables.host_resource_lift_own(idx)?;
(AtomicResourceState::NOT_IN_TABLE, rep)
} else {
let rep = tables.host_resource_lift_borrow(idx)?;
let res = tables.host_resource_drop(idx)?;
assert!(res.is_none());
(AtomicResourceState::BORROW, rep)
};
Ok(Resource {
state,
rep,
_marker: marker::PhantomData,
})
}
pub fn try_into_resource_any(self, store: impl AsContextMut) -> Result<ResourceAny> {
ResourceAny::try_from_resource(self, store)
}
}
unsafe impl<T: 'static> ComponentType for Resource<T> {
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
type Lower = <u32 as ComponentType>::Lower;
fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> {
let resource = match ty {
InterfaceType::Own(t) | InterfaceType::Borrow(t) => *t,
other => bail!("expected `own` or `borrow`, found `{}`", desc(other)),
};
match types.resource_type(resource).kind {
ResourceTypeKind::Host(id) if TypeId::of::<T>() == id => {}
_ => bail!("resource type mismatch"),
}
Ok(())
}
}
unsafe impl<T: 'static> Lower for Resource<T> {
fn lower<U>(
&self,
cx: &mut LowerContext<'_, U>,
ty: InterfaceType,
dst: &mut MaybeUninit<Self::Lower>,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.lower(cx, InterfaceType::U32, dst)
}
fn store<U>(
&self,
cx: &mut LowerContext<'_, U>,
ty: InterfaceType,
offset: usize,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.store(cx, InterfaceType::U32, offset)
}
}
unsafe impl<T: 'static> Lift for Resource<T> {
fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
let index = u32::lift(cx, InterfaceType::U32, src)?;
Resource::lift_from_index(cx, ty, index)
}
fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
let index = u32::load(cx, InterfaceType::U32, bytes)?;
Resource::lift_from_index(cx, ty, index)
}
}
impl<T> fmt::Debug for Resource<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = match self.state.get() {
ResourceState::Borrow => "borrow",
ResourceState::NotInTable => "own (not in table)",
ResourceState::Taken => "taken",
ResourceState::Index(_) => "own",
};
f.debug_struct("Resource")
.field("rep", &self.rep)
.field("state", &state)
.finish()
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct ResourceAny {
idx: HostResourceIndex,
ty: ResourceType,
owned: bool,
}
impl ResourceAny {
pub fn try_from_resource<T: 'static>(
resource: Resource<T>,
mut store: impl AsContextMut,
) -> Result<Self> {
let Resource { rep, state, .. } = resource;
let store = store.as_context_mut();
let mut tables = HostResourceTables::new_host(store.0);
let (idx, owned) = match state.get() {
ResourceState::Borrow => (tables.host_resource_lower_borrow(rep)?, false),
ResourceState::NotInTable => {
let idx = tables.host_resource_lower_own(rep, None, None)?;
(idx, true)
}
ResourceState::Taken => bail!("host resource already consumed"),
ResourceState::Index(idx) => (idx, true),
};
Ok(Self {
idx,
ty: ResourceType::host::<T>(),
owned,
})
}
pub fn try_into_resource<T: 'static>(self, store: impl AsContextMut) -> Result<Resource<T>> {
Resource::try_from_resource_any(self, store)
}
pub fn ty(&self) -> ResourceType {
self.ty
}
pub fn owned(&self) -> bool {
self.owned
}
pub fn resource_drop(self, mut store: impl AsContextMut) -> Result<()> {
let mut store = store.as_context_mut();
assert!(
!store.0.async_support(),
"must use `resource_drop_async` when async support is enabled on the config"
);
self.resource_drop_impl(&mut store.as_context_mut())
}
#[cfg(feature = "async")]
pub async fn resource_drop_async<T>(self, mut store: impl AsContextMut<Data = T>) -> Result<()>
where
T: Send,
{
let mut store = store.as_context_mut();
assert!(
store.0.async_support(),
"cannot use `resource_drop_async` without enabling async support in the config"
);
store
.on_fiber(|store| self.resource_drop_impl(store))
.await?
}
fn resource_drop_impl<T>(self, store: &mut StoreContextMut<'_, T>) -> Result<()> {
let pair = HostResourceTables::new_host(store.0).host_resource_drop(self.idx)?;
let (rep, slot) = match (pair, self.owned) {
(Some(pair), true) => pair,
(None, false) => return Ok(()),
_ => unreachable!(),
};
if let Some(flags) = slot.flags {
unsafe {
if !flags.may_enter() {
bail!(Trap::CannotEnterComponent);
}
}
}
let dtor = match slot.dtor {
Some(dtor) => dtor.as_non_null(),
None => return Ok(()),
};
let mut args = [ValRaw::u32(rep)];
unsafe { crate::Func::call_unchecked_raw(store, dtor, args.as_mut_ptr(), args.len()) }
}
fn lower_to_index<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result<u32> {
match ty {
InterfaceType::Own(t) => {
if cx.resource_type(t) != self.ty {
bail!("mismatched resource types");
}
let rep = cx.host_resource_lift_own(self.idx)?;
cx.guest_resource_lower_own(t, rep)
}
InterfaceType::Borrow(t) => {
if cx.resource_type(t) != self.ty {
bail!("mismatched resource types");
}
let rep = cx.host_resource_lift_borrow(self.idx)?;
cx.guest_resource_lower_borrow(t, rep)
}
_ => bad_type_info(),
}
}
fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> {
match ty {
InterfaceType::Own(t) => {
let ty = cx.resource_type(t);
let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?;
let idx = cx.host_resource_lower_own(rep, dtor, flags)?;
Ok(ResourceAny {
idx,
ty,
owned: true,
})
}
InterfaceType::Borrow(t) => {
let ty = cx.resource_type(t);
let rep = cx.guest_resource_lift_borrow(t, index)?;
let idx = cx.host_resource_lower_borrow(rep)?;
Ok(ResourceAny {
idx,
ty,
owned: false,
})
}
_ => bad_type_info(),
}
}
}
unsafe impl ComponentType for ResourceAny {
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
type Lower = <u32 as ComponentType>::Lower;
fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> {
match ty {
InterfaceType::Own(_) | InterfaceType::Borrow(_) => Ok(()),
other => bail!("expected `own` or `borrow`, found `{}`", desc(other)),
}
}
}
unsafe impl Lower for ResourceAny {
fn lower<T>(
&self,
cx: &mut LowerContext<'_, T>,
ty: InterfaceType,
dst: &mut MaybeUninit<Self::Lower>,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.lower(cx, InterfaceType::U32, dst)
}
fn store<T>(
&self,
cx: &mut LowerContext<'_, T>,
ty: InterfaceType,
offset: usize,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.store(cx, InterfaceType::U32, offset)
}
}
unsafe impl Lift for ResourceAny {
fn lift(cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
let index = u32::lift(cx, InterfaceType::U32, src)?;
ResourceAny::lift_from_index(cx, ty, index)
}
fn load(cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
let index = u32::load(cx, InterfaceType::U32, bytes)?;
ResourceAny::lift_from_index(cx, ty, index)
}
}