use crate::component::func::{bad_type_info, desc, LiftContext, LowerContext};
use crate::component::matching::InstanceType;
use crate::component::{ComponentType, Lift, Lower};
use crate::store::{StoreId, StoreOpaque};
use crate::{AsContextMut, StoreContextMut, Trap};
use anyhow::{bail, Result};
use std::any::TypeId;
use std::fmt;
use std::marker;
use std::mem::MaybeUninit;
use std::sync::atomic::{AtomicU32, Ordering::Relaxed};
use wasmtime_environ::component::{CanonicalAbiInfo, DefinedResourceIndex, InterfaceType};
use wasmtime_runtime::component::{ComponentInstance, InstanceFlags, ResourceTables};
use wasmtime_runtime::{SendSyncPtr, VMFuncRef, ValRaw};
#[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,
},
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum ResourceTypeKind {
Host(TypeId),
Guest {
store: StoreId,
instance: usize,
id: DefinedResourceIndex,
},
}
pub struct Resource<T> {
rep: u32,
_marker: marker::PhantomData<fn() -> T>,
state: AtomicU32,
}
const BORROW: u32 = u32::MAX;
const NOT_IN_TABLE: u32 = u32::MAX - 1;
const TAKEN: u32 = u32::MAX - 2;
fn host_resource_tables(store: &mut StoreOpaque) -> ResourceTables<'_> {
let (calls, host_table) = store.component_calls_and_host_table();
ResourceTables {
calls,
host_table: Some(host_table),
tables: None,
}
}
impl<T> Resource<T>
where
T: 'static,
{
pub fn new_own(rep: u32) -> Resource<T> {
Resource {
state: AtomicU32::new(NOT_IN_TABLE),
rep,
_marker: marker::PhantomData,
}
}
pub fn new_borrow(rep: u32) -> Resource<T> {
Resource {
state: AtomicU32::new(BORROW),
rep,
_marker: marker::PhantomData,
}
}
pub fn rep(&self) -> u32 {
self.rep
}
pub fn owned(&self) -> bool {
match self.state.load(Relaxed) {
BORROW => false,
_ => 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.load(Relaxed) {
BORROW => {
bail!("cannot lower a `borrow` resource into an `own`")
}
NOT_IN_TABLE => {
let prev = self.state.swap(TAKEN, Relaxed);
assert_eq!(prev, NOT_IN_TABLE);
self.rep
}
TAKEN => bail!("host resource already consumed"),
idx => cx.host_resource_lift_own(idx)?,
};
Ok(cx.guest_resource_lower_own(t, rep))
}
InterfaceType::Borrow(t) => {
let rep = match self.state.load(Relaxed) {
BORROW => self.rep,
TAKEN => bail!("host resource already consumed"),
NOT_IN_TABLE => {
let idx = cx.host_resource_lower_own(self.rep);
let prev = self.state.swap(idx, Relaxed);
assert_eq!(prev, NOT_IN_TABLE);
cx.host_resource_lift_borrow(idx)?
}
idx => cx.host_resource_lift_borrow(idx)?,
};
Ok(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());
(AtomicU32::new(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)?;
(AtomicU32::new(BORROW), rep)
}
_ => bad_type_info(),
};
Ok(Resource {
state,
rep,
_marker: marker::PhantomData,
})
}
}
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 {
f.debug_struct("Resource").field("rep", &self.rep).finish()
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct ResourceAny {
idx: u32,
ty: ResourceType,
own_state: Option<OwnState>,
}
#[derive(Copy, Clone)]
struct OwnState {
store: StoreId,
flags: Option<InstanceFlags>,
dtor: Option<SendSyncPtr<VMFuncRef>>,
}
impl ResourceAny {
pub fn ty(&self) -> ResourceType {
self.ty
}
pub fn owned(&self) -> bool {
self.own_state.is_some()
}
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")]
#[cfg_attr(nightlydoc, doc(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 rep = host_resource_tables(store.0).resource_drop(None, self.idx)?;
let (rep, state) = match (rep, &self.own_state) {
(Some(rep), Some(state)) => (rep, state),
(None, None) => return Ok(()),
_ => unreachable!(),
};
assert_eq!(
store.0.id(),
state.store,
"wrong store used to destroy resource"
);
if let Some(flags) = state.flags {
unsafe {
if !flags.may_enter() {
bail!(Trap::CannotEnterComponent);
}
}
}
let dtor = match state.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)?;
Ok(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)?;
Ok(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);
Ok(ResourceAny {
idx,
ty,
own_state: Some(OwnState {
dtor: dtor.map(SendSyncPtr::new),
flags,
store: cx.store_id(),
}),
})
}
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,
own_state: None,
})
}
_ => 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)
}
}
impl fmt::Debug for OwnState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OwnState")
.field("store", &self.store)
.finish()
}
}
impl PartialEq for OwnState {
fn eq(&self, _other: &OwnState) -> bool {
true
}
}
impl Eq for OwnState {}