use crate::AsContextMut;
use crate::component::func::{LiftContext, LowerContext, bad_type_info, desc};
use crate::component::matching::InstanceType;
use crate::component::resources::{HostResourceIndex, HostResourceTables};
use crate::component::{ComponentType, Lift, Lower, ResourceAny, ResourceType};
use crate::prelude::*;
use core::fmt;
use core::marker;
use core::mem::MaybeUninit;
use core::sync::atomic::{AtomicU32, Ordering::Relaxed};
use wasmtime_environ::component::{CanonicalAbiInfo, InterfaceType};
pub struct HostResource<T: HostResourceType<D>, D> {
rep: u32,
ty: D,
state: AtomicResourceState,
_marker: marker::PhantomData<fn() -> T>,
}
pub trait HostResourceType<D> {
fn typecheck(ty: ResourceType) -> Option<D>;
fn resource_type(data: D) -> ResourceType;
}
struct AtomicResourceState {
index: AtomicU32,
generation: AtomicU32,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum ResourceState {
Borrow,
NotInTable,
Taken,
Index(HostResourceIndex),
}
impl AtomicResourceState {
const BORROW: Self = AtomicResourceState::new(ResourceState::Borrow);
const NOT_IN_TABLE: Self = AtomicResourceState::new(ResourceState::NotInTable);
const fn new(state: ResourceState) -> AtomicResourceState {
let (index, generation) = state.encode();
Self {
index: AtomicU32::new(index),
generation: AtomicU32::new(generation),
}
}
fn get(&self) -> ResourceState {
ResourceState::decode(self.index.load(Relaxed), self.generation.load(Relaxed))
}
fn swap(&self, state: ResourceState) -> ResourceState {
let (index, generation) = state.encode();
let index_prev = self.index.load(Relaxed);
self.index.store(index, Relaxed);
let generation_prev = self.generation.load(Relaxed);
self.generation.store(generation, Relaxed);
ResourceState::decode(index_prev, generation_prev)
}
}
impl ResourceState {
const BORROW: u32 = u32::MAX;
const NOT_IN_TABLE: u32 = u32::MAX - 1;
const TAKEN: u32 = u32::MAX - 2;
fn decode(idx: u32, generation: u32) -> ResourceState {
match generation {
Self::BORROW => Self::Borrow,
Self::NOT_IN_TABLE => Self::NotInTable,
Self::TAKEN => Self::Taken,
_ => Self::Index(HostResourceIndex::new(idx, generation)),
}
}
const fn encode(&self) -> (u32, u32) {
match self {
Self::Borrow => (0, Self::BORROW),
Self::NotInTable => (0, Self::NOT_IN_TABLE),
Self::Taken => (0, Self::TAKEN),
Self::Index(index) => (index.index(), index.generation()),
}
}
}
impl<T, D> HostResource<T, D>
where
T: HostResourceType<D>,
D: PartialEq + Send + Sync + Copy + 'static,
{
pub fn new_own(rep: u32, ty: D) -> Self {
HostResource {
state: AtomicResourceState::NOT_IN_TABLE,
rep,
ty,
_marker: marker::PhantomData,
}
}
pub fn new_borrow(rep: u32, ty: D) -> Self {
HostResource {
state: AtomicResourceState::BORROW,
rep,
ty,
_marker: marker::PhantomData,
}
}
pub fn rep(&self) -> u32 {
self.rep
}
pub fn ty(&self) -> D {
self.ty
}
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) => {
match T::typecheck(cx.resource_type(t)) {
Some(t) if t == self.ty => {}
_ => bail!("resource type mismatch"),
}
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) => {
match T::typecheck(cx.resource_type(t)) {
Some(t) if t == self.ty => {}
_ => bail!("resource type mismatch"),
}
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, ty) = match ty {
InterfaceType::Own(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, t)
}
InterfaceType::Borrow(t) => {
let rep = cx.guest_resource_lift_borrow(t, index)?;
(AtomicResourceState::BORROW, rep, t)
}
_ => bad_type_info(),
};
let ty = T::typecheck(cx.resource_type(ty)).unwrap();
Ok(HostResource {
state,
rep,
ty,
_marker: marker::PhantomData,
})
}
pub fn try_into_resource_any(self, mut store: impl AsContextMut) -> Result<ResourceAny> {
let HostResource {
rep,
state,
ty,
_marker: _,
} = self;
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(ResourceAny::new(idx, T::resource_type(ty), owned))
}
}
unsafe impl<T, D> ComponentType for HostResource<T, D>
where
T: HostResourceType<D>,
D: PartialEq + Send + Sync + Copy + 'static,
{
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)),
};
if T::typecheck(types.resource_type(resource)).is_none() {
bail!("resource type mismatch");
}
Ok(())
}
}
unsafe impl<T, D> Lower for HostResource<T, D>
where
T: HostResourceType<D>,
D: PartialEq + Send + Sync + Copy + 'static,
{
fn linear_lower_to_flat<U>(
&self,
cx: &mut LowerContext<'_, U>,
ty: InterfaceType,
dst: &mut MaybeUninit<Self::Lower>,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.linear_lower_to_flat(cx, InterfaceType::U32, dst)
}
fn linear_lower_to_memory<U>(
&self,
cx: &mut LowerContext<'_, U>,
ty: InterfaceType,
offset: usize,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.linear_lower_to_memory(cx, InterfaceType::U32, offset)
}
}
unsafe impl<T, D> Lift for HostResource<T, D>
where
T: HostResourceType<D>,
D: PartialEq + Send + Sync + Copy + 'static,
{
fn linear_lift_from_flat(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
src: &Self::Lower,
) -> Result<Self> {
let index = u32::linear_lift_from_flat(cx, InterfaceType::U32, src)?;
HostResource::lift_from_index(cx, ty, index)
}
fn linear_lift_from_memory(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
bytes: &[u8],
) -> Result<Self> {
let index = u32::linear_lift_from_memory(cx, InterfaceType::U32, bytes)?;
HostResource::lift_from_index(cx, ty, index)
}
}
impl<T, D> fmt::Debug for HostResource<T, D>
where
T: HostResourceType<D>,
D: PartialEq + Send + Sync + Copy + 'static,
{
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()
}
}