1 use crate::AsContextMut; 2 use crate::component::func::{LiftContext, LowerContext, bad_type_info, desc}; 3 use crate::component::matching::InstanceType; 4 use crate::component::resources::{HostResourceIndex, HostResourceTables}; 5 use crate::component::{ComponentType, Lift, Lower, ResourceAny, ResourceType}; 6 use crate::prelude::*; 7 use core::fmt; 8 use core::marker; 9 use core::mem::MaybeUninit; 10 use core::sync::atomic::{AtomicU32, Ordering::Relaxed}; 11 use wasmtime_environ::component::{CanonicalAbiInfo, InterfaceType}; 12 13 /// Internal type in Wasmtime used to represent host-defined resources that are 14 /// not tracked within the store. 15 pub struct HostResource<T: HostResourceType<D>, D> { 16 /// The host-defined 32-bit representation of this resource. 17 rep: u32, 18 19 /// Metadata, if necessary, tracking the type of this resource. 20 ty: D, 21 22 /// Internal state about borrows and such. 23 state: AtomicResourceState, 24 25 _marker: marker::PhantomData<fn() -> T>, 26 } 27 28 // FIXME(rust-lang/rust#110338) the `D` type parameter here should be an 29 // associated type. In a first attempt at doing that the `wasmtime-wasi-io` 30 // crate failed to compile with obscure errors. At least this is an internal 31 // trait for now... 32 pub trait HostResourceType<D> { 33 /// Tests whether `ty` matches this resource type. typecheck(ty: ResourceType) -> Option<D>34 fn typecheck(ty: ResourceType) -> Option<D>; 35 /// Converts `Data` to a `ResourceType`. resource_type(data: D) -> ResourceType36 fn resource_type(data: D) -> ResourceType; 37 } 38 39 /// Internal dynamic state tracking for this resource. This can be one of 40 /// four different states: 41 /// 42 /// * `BORROW` / `u64::MAX` - this indicates that this is a borrowed 43 /// resource. The `rep` doesn't live in the host table and this `Resource` 44 /// instance is transiently available. It's the host's responsibility to 45 /// discard this resource when the borrow duration has finished. 46 /// 47 /// * `NOT_IN_TABLE` / `u64::MAX - 1` - this indicates that this is an owned 48 /// resource not present in any store's table. This resource is not lent 49 /// out. It can be passed as an `(own $t)` directly into a guest's table 50 /// or it can be passed as a borrow to a guest which will insert it into 51 /// a host store's table for dynamic borrow tracking. 52 /// 53 /// * `TAKEN` / `u64::MAX - 2` - while the `rep` is available the resource 54 /// has been dynamically moved into a guest and cannot be moved in again. 55 /// This is used for example to prevent the same resource from being 56 /// passed twice to a guest. 57 /// 58 /// * All other values - any other value indicates that the value is an 59 /// index into a store's table of host resources. It's guaranteed that the 60 /// table entry represents a host resource and the resource may have 61 /// borrow tracking associated with it. The low 32-bits of the value are 62 /// the table index and the upper 32-bits are the generation. 63 /// 64 /// Note that this is two `AtomicU32` fields but it's not intended to actually 65 /// be used in conjunction with threads as generally a `Store<T>` lives on one 66 /// thread at a time. The pair of `AtomicU32` here is used to ensure that this 67 /// type is `Send + Sync` when captured as a reference to make async 68 /// programming more ergonomic. 69 /// 70 /// Also note that two `AtomicU32` here are used instead of `AtomicU64` to be 71 /// more portable to platforms without 64-bit atomics. 72 struct AtomicResourceState { 73 index: AtomicU32, 74 generation: AtomicU32, 75 } 76 77 #[derive(Debug, PartialEq, Eq, Copy, Clone)] 78 enum ResourceState { 79 Borrow, 80 NotInTable, 81 Taken, 82 Index(HostResourceIndex), 83 } 84 85 impl AtomicResourceState { 86 const BORROW: Self = AtomicResourceState::new(ResourceState::Borrow); 87 const NOT_IN_TABLE: Self = AtomicResourceState::new(ResourceState::NotInTable); 88 new(state: ResourceState) -> AtomicResourceState89 const fn new(state: ResourceState) -> AtomicResourceState { 90 let (index, generation) = state.encode(); 91 Self { 92 index: AtomicU32::new(index), 93 generation: AtomicU32::new(generation), 94 } 95 } 96 get(&self) -> ResourceState97 fn get(&self) -> ResourceState { 98 ResourceState::decode(self.index.load(Relaxed), self.generation.load(Relaxed)) 99 } 100 swap(&self, state: ResourceState) -> ResourceState101 fn swap(&self, state: ResourceState) -> ResourceState { 102 let (index, generation) = state.encode(); 103 let index_prev = self.index.load(Relaxed); 104 self.index.store(index, Relaxed); 105 let generation_prev = self.generation.load(Relaxed); 106 self.generation.store(generation, Relaxed); 107 ResourceState::decode(index_prev, generation_prev) 108 } 109 } 110 111 impl ResourceState { 112 // See comments on `state` above for info about these values. 113 const BORROW: u32 = u32::MAX; 114 const NOT_IN_TABLE: u32 = u32::MAX - 1; 115 const TAKEN: u32 = u32::MAX - 2; 116 decode(idx: u32, generation: u32) -> ResourceState117 fn decode(idx: u32, generation: u32) -> ResourceState { 118 match generation { 119 Self::BORROW => Self::Borrow, 120 Self::NOT_IN_TABLE => Self::NotInTable, 121 Self::TAKEN => Self::Taken, 122 _ => Self::Index(HostResourceIndex::new(idx, generation)), 123 } 124 } 125 encode(&self) -> (u32, u32)126 const fn encode(&self) -> (u32, u32) { 127 match self { 128 Self::Borrow => (0, Self::BORROW), 129 Self::NotInTable => (0, Self::NOT_IN_TABLE), 130 Self::Taken => (0, Self::TAKEN), 131 Self::Index(index) => (index.index(), index.generation()), 132 } 133 } 134 } 135 136 impl<T, D> HostResource<T, D> 137 where 138 T: HostResourceType<D>, 139 D: PartialEq + Send + Sync + Copy + 'static, 140 { new_own(rep: u32, ty: D) -> Self141 pub fn new_own(rep: u32, ty: D) -> Self { 142 HostResource { 143 state: AtomicResourceState::NOT_IN_TABLE, 144 rep, 145 ty, 146 _marker: marker::PhantomData, 147 } 148 } 149 new_borrow(rep: u32, ty: D) -> Self150 pub fn new_borrow(rep: u32, ty: D) -> Self { 151 HostResource { 152 state: AtomicResourceState::BORROW, 153 rep, 154 ty, 155 _marker: marker::PhantomData, 156 } 157 } 158 rep(&self) -> u32159 pub fn rep(&self) -> u32 { 160 self.rep 161 } 162 ty(&self) -> D163 pub fn ty(&self) -> D { 164 self.ty 165 } 166 owned(&self) -> bool167 pub fn owned(&self) -> bool { 168 match self.state.get() { 169 ResourceState::Borrow => false, 170 ResourceState::Taken | ResourceState::NotInTable | ResourceState::Index(_) => true, 171 } 172 } 173 lower_to_index<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result<u32>174 fn lower_to_index<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result<u32> { 175 match ty { 176 InterfaceType::Own(t) => { 177 match T::typecheck(cx.resource_type(t)) { 178 Some(t) if t == self.ty => {} 179 _ => bail!("resource type mismatch"), 180 } 181 let rep = match self.state.get() { 182 // If this is a borrow resource then this is a dynamic 183 // error on behalf of the embedder. 184 ResourceState::Borrow => { 185 bail!("cannot lower a `borrow` resource into an `own`") 186 } 187 188 // If this resource does not yet live in a table then we're 189 // dynamically transferring ownership to wasm. Record that 190 // it's no longer present and then pass through the 191 // representation. 192 ResourceState::NotInTable => { 193 let prev = self.state.swap(ResourceState::Taken); 194 assert_eq!(prev, ResourceState::NotInTable); 195 self.rep 196 } 197 198 // This resource has already been moved into wasm so this is 199 // a dynamic error on behalf of the embedder. 200 ResourceState::Taken => bail!("host resource already consumed"), 201 202 // If this resource lives in a host table then try to take 203 // it out of the table, which may fail, and on success we 204 // can move the rep into the guest table. 205 ResourceState::Index(idx) => cx.host_resource_lift_own(idx)?, 206 }; 207 cx.guest_resource_lower_own(t, rep) 208 } 209 InterfaceType::Borrow(t) => { 210 match T::typecheck(cx.resource_type(t)) { 211 Some(t) if t == self.ty => {} 212 _ => bail!("resource type mismatch"), 213 } 214 let rep = match self.state.get() { 215 // If this is already a borrowed resource, nothing else to 216 // do and the rep is passed through. 217 ResourceState::Borrow => self.rep, 218 219 // If this resource is already gone, that's a dynamic error 220 // for the embedder. 221 ResourceState::Taken => bail!("host resource already consumed"), 222 223 // If this resource is not currently in a table then it 224 // needs to move into a table to participate in state 225 // related to borrow tracking. Execute the 226 // `host_resource_lower_own` operation here and update our 227 // state. 228 // 229 // Afterwards this is the same as the `idx` case below. 230 // 231 // Note that flags/dtor are passed as `None` here since 232 // `Resource<T>` doesn't offer destruction support. 233 ResourceState::NotInTable => { 234 let idx = cx.host_resource_lower_own(self.rep, None, None)?; 235 let prev = self.state.swap(ResourceState::Index(idx)); 236 assert_eq!(prev, ResourceState::NotInTable); 237 cx.host_resource_lift_borrow(idx)? 238 } 239 240 // If this resource lives in a table then it needs to come 241 // out of the table with borrow-tracking employed. 242 ResourceState::Index(idx) => cx.host_resource_lift_borrow(idx)?, 243 }; 244 cx.guest_resource_lower_borrow(t, rep) 245 } 246 _ => bad_type_info(), 247 } 248 } 249 lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self>250 fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> { 251 let (state, rep, ty) = match ty { 252 // Ownership is being transferred from a guest to the host, so move 253 // it from the guest table into a new `Resource`. Note that this 254 // isn't immediately inserted into the host table and that's left 255 // for the future if it's necessary to take a borrow from this owned 256 // resource. 257 InterfaceType::Own(t) => { 258 let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?; 259 assert!(dtor.is_some()); 260 assert!(flags.is_none()); 261 (AtomicResourceState::NOT_IN_TABLE, rep, t) 262 } 263 264 // The borrow here is lifted from the guest, but note the lack of 265 // `host_resource_lower_borrow` as it's intentional. Lowering 266 // a borrow has a special case in the canonical ABI where if the 267 // receiving module is the owner of the resource then it directly 268 // receives the `rep` and no other dynamic tracking is employed. 269 // This effectively mirrors that even though the canonical ABI 270 // isn't really all that applicable in host context here. 271 InterfaceType::Borrow(t) => { 272 let rep = cx.guest_resource_lift_borrow(t, index)?; 273 (AtomicResourceState::BORROW, rep, t) 274 } 275 _ => bad_type_info(), 276 }; 277 let ty = T::typecheck(cx.resource_type(ty)).unwrap(); 278 Ok(HostResource { 279 state, 280 rep, 281 ty, 282 _marker: marker::PhantomData, 283 }) 284 } 285 try_into_resource_any(self, mut store: impl AsContextMut) -> Result<ResourceAny>286 pub fn try_into_resource_any(self, mut store: impl AsContextMut) -> Result<ResourceAny> { 287 let HostResource { 288 rep, 289 state, 290 ty, 291 _marker: _, 292 } = self; 293 let store = store.as_context_mut(); 294 295 let mut tables = HostResourceTables::new_host(store.0); 296 let (idx, owned) = match state.get() { 297 ResourceState::Borrow => (tables.host_resource_lower_borrow(rep)?, false), 298 ResourceState::NotInTable => { 299 let idx = tables.host_resource_lower_own(rep, None, None)?; 300 (idx, true) 301 } 302 ResourceState::Taken => bail!("host resource already consumed"), 303 ResourceState::Index(idx) => (idx, true), 304 }; 305 Ok(ResourceAny::new(idx, T::resource_type(ty), owned)) 306 } 307 } 308 309 unsafe impl<T, D> ComponentType for HostResource<T, D> 310 where 311 T: HostResourceType<D>, 312 D: PartialEq + Send + Sync + Copy + 'static, 313 { 314 const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4; 315 316 type Lower = <u32 as ComponentType>::Lower; 317 typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()>318 fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> { 319 let resource = match ty { 320 InterfaceType::Own(t) | InterfaceType::Borrow(t) => *t, 321 other => bail!("expected `own` or `borrow`, found `{}`", desc(other)), 322 }; 323 if T::typecheck(types.resource_type(resource)).is_none() { 324 bail!("resource type mismatch"); 325 } 326 327 Ok(()) 328 } 329 } 330 331 unsafe impl<T, D> Lower for HostResource<T, D> 332 where 333 T: HostResourceType<D>, 334 D: PartialEq + Send + Sync + Copy + 'static, 335 { linear_lower_to_flat<U>( &self, cx: &mut LowerContext<'_, U>, ty: InterfaceType, dst: &mut MaybeUninit<Self::Lower>, ) -> Result<()>336 fn linear_lower_to_flat<U>( 337 &self, 338 cx: &mut LowerContext<'_, U>, 339 ty: InterfaceType, 340 dst: &mut MaybeUninit<Self::Lower>, 341 ) -> Result<()> { 342 self.lower_to_index(cx, ty)? 343 .linear_lower_to_flat(cx, InterfaceType::U32, dst) 344 } 345 linear_lower_to_memory<U>( &self, cx: &mut LowerContext<'_, U>, ty: InterfaceType, offset: usize, ) -> Result<()>346 fn linear_lower_to_memory<U>( 347 &self, 348 cx: &mut LowerContext<'_, U>, 349 ty: InterfaceType, 350 offset: usize, 351 ) -> Result<()> { 352 self.lower_to_index(cx, ty)? 353 .linear_lower_to_memory(cx, InterfaceType::U32, offset) 354 } 355 } 356 357 unsafe impl<T, D> Lift for HostResource<T, D> 358 where 359 T: HostResourceType<D>, 360 D: PartialEq + Send + Sync + Copy + 'static, 361 { linear_lift_from_flat( cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower, ) -> Result<Self>362 fn linear_lift_from_flat( 363 cx: &mut LiftContext<'_>, 364 ty: InterfaceType, 365 src: &Self::Lower, 366 ) -> Result<Self> { 367 let index = u32::linear_lift_from_flat(cx, InterfaceType::U32, src)?; 368 HostResource::lift_from_index(cx, ty, index) 369 } 370 linear_lift_from_memory( cx: &mut LiftContext<'_>, ty: InterfaceType, bytes: &[u8], ) -> Result<Self>371 fn linear_lift_from_memory( 372 cx: &mut LiftContext<'_>, 373 ty: InterfaceType, 374 bytes: &[u8], 375 ) -> Result<Self> { 376 let index = u32::linear_lift_from_memory(cx, InterfaceType::U32, bytes)?; 377 HostResource::lift_from_index(cx, ty, index) 378 } 379 } 380 381 impl<T, D> fmt::Debug for HostResource<T, D> 382 where 383 T: HostResourceType<D>, 384 D: PartialEq + Send + Sync + Copy + 'static, 385 { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 387 let state = match self.state.get() { 388 ResourceState::Borrow => "borrow", 389 ResourceState::NotInTable => "own (not in table)", 390 ResourceState::Taken => "taken", 391 ResourceState::Index(_) => "own", 392 }; 393 f.debug_struct("Resource") 394 .field("rep", &self.rep) 395 .field("state", &state) 396 .finish() 397 } 398 } 399