1 use super::{TypedResource, TypedResourceIndex}; 2 use alloc::vec::Vec; 3 use anyhow::{Result, bail}; 4 use core::mem; 5 use wasmtime_environ::component::{TypeFutureTableIndex, TypeStreamTableIndex}; 6 7 /// The maximum handle value is specified in 8 /// <https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md> 9 /// currently and keeps the upper bits free for use in the component and ABI. 10 const MAX_HANDLE: u32 = 1 << 28; 11 12 /// Represents the state of a stream or future handle from the perspective of a 13 /// given component instance. 14 #[derive(Debug, Eq, PartialEq)] 15 pub enum TransmitLocalState { 16 /// The write end of the stream or future. 17 Write { 18 /// Whether the component instance has been notified that the stream or 19 /// future is "done" (i.e. the other end has dropped, or, in the case of 20 /// a future, a value has been transmitted). 21 done: bool, 22 }, 23 /// The read end of the stream or future. 24 Read { 25 /// Whether the component instance has been notified that the stream or 26 /// future is "done" (i.e. the other end has dropped, or, in the case of 27 /// a future, a value has been transmitted). 28 done: bool, 29 }, 30 /// A read or write is in progress. 31 Busy, 32 } 33 34 /// Return value from [`HandleTable::remove_resource`]. 35 pub enum RemovedResource { 36 /// An `own` resource was removed with the specified `rep` 37 Own { rep: u32 }, 38 /// A `borrow` resource was removed originally created within `scope`. 39 Borrow { scope: usize }, 40 } 41 42 /// Different kinds of waitables returned by [`HandleTable::waitable_rep`]. 43 pub enum Waitable { 44 Subtask { is_host: bool }, 45 Future, 46 Stream, 47 } 48 49 enum Slot { 50 Free { 51 next: u32, 52 }, 53 54 /// Represents an owned resource handle with the listed representation. 55 /// 56 /// The `lend_count` tracks how many times this has been lent out as a 57 /// `borrow` and if nonzero this can't be removed. 58 ResourceOwn { 59 resource: TypedResource, 60 lend_count: u32, 61 }, 62 63 /// Represents a borrowed resource handle connected to the `scope` 64 /// provided. 65 /// 66 /// The `rep` is listed and dropping this borrow will decrement the borrow 67 /// count of the `scope`. 68 ResourceBorrow { 69 resource: TypedResource, 70 scope: usize, 71 }, 72 73 /// Represents a host task handle. 74 HostTask { 75 rep: u32, 76 }, 77 78 /// Represents a guest task handle. 79 GuestTask { 80 rep: u32, 81 }, 82 83 /// Represents a stream handle. 84 Stream { 85 ty: TypeStreamTableIndex, 86 rep: u32, 87 state: TransmitLocalState, 88 }, 89 90 /// Represents a future handle. 91 Future { 92 ty: TypeFutureTableIndex, 93 rep: u32, 94 state: TransmitLocalState, 95 }, 96 97 /// Represents a waitable-set handle. 98 WaitableSet { 99 rep: u32, 100 }, 101 102 /// Represents an error-context handle. 103 ErrorContext { 104 rep: u32, 105 }, 106 } 107 108 pub struct HandleTable { 109 next: u32, 110 slots: Vec<Slot>, 111 } 112 113 impl Default for HandleTable { 114 fn default() -> Self { 115 Self { 116 next: 0, 117 slots: Vec::new(), 118 } 119 } 120 } 121 122 impl HandleTable { 123 /// Returns whether or not this table is empty. 124 pub fn is_empty(&self) -> bool { 125 self.slots 126 .iter() 127 .all(|slot| matches!(slot, Slot::Free { .. })) 128 } 129 130 fn insert(&mut self, slot: Slot) -> Result<u32> { 131 let next = self.next as usize; 132 if next == self.slots.len() { 133 self.slots.push(Slot::Free { 134 next: self.next.checked_add(1).unwrap(), 135 }); 136 } 137 let ret = self.next; 138 self.next = match mem::replace(&mut self.slots[next], slot) { 139 Slot::Free { next } => next, 140 _ => unreachable!(), 141 }; 142 // The component model reserves index 0 as never allocatable so add one 143 // to the table index to start the numbering at 1 instead. Also note 144 // that the component model places an upper-limit per-table on the 145 // maximum allowed index. 146 let ret = ret + 1; 147 if ret >= MAX_HANDLE { 148 bail!("cannot allocate another handle: index overflow"); 149 } 150 151 Ok(ret) 152 } 153 154 fn remove(&mut self, idx: u32) -> Result<()> { 155 let to_fill = Slot::Free { next: self.next }; 156 let slot = self.get_mut(idx)?; 157 *slot = to_fill; 158 self.next = idx - 1; 159 Ok(()) 160 } 161 162 fn handle_index_to_table_index(&self, idx: u32) -> Option<usize> { 163 // NB: `idx` is decremented by one to account for the `+1` above during 164 // allocation. 165 let idx = idx.checked_sub(1)?; 166 usize::try_from(idx).ok() 167 } 168 169 fn get_mut(&mut self, idx: u32) -> Result<&mut Slot> { 170 let slot = self 171 .handle_index_to_table_index(idx) 172 .and_then(|i| self.slots.get_mut(i)); 173 match slot { 174 None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"), 175 Some(slot) => Ok(slot), 176 } 177 } 178 179 /// Inserts a new `own` resource into this table whose type/rep are 180 /// specified by `resource`. 181 pub fn resource_own_insert(&mut self, resource: TypedResource) -> Result<u32> { 182 self.insert(Slot::ResourceOwn { 183 resource, 184 lend_count: 0, 185 }) 186 } 187 188 /// Inserts a new `borrow` resource into this table whose type/rep are 189 /// specified by `resource`. The `scope` specified is used by 190 /// `CallContexts` to manage lending information. 191 pub fn resource_borrow_insert(&mut self, resource: TypedResource, scope: usize) -> Result<u32> { 192 self.insert(Slot::ResourceBorrow { resource, scope }) 193 } 194 195 /// Returns the internal "rep" of the resource specified by `idx`. 196 /// 197 /// Returns an error if `idx` is out-of-bounds or doesn't point to a 198 /// resource of the appropriate type. 199 pub fn resource_rep(&mut self, idx: TypedResourceIndex) -> Result<u32> { 200 match self.get_mut(idx.raw_index())? { 201 Slot::ResourceOwn { resource, .. } | Slot::ResourceBorrow { resource, .. } => { 202 resource.rep(&idx) 203 } 204 _ => bail!("index is not a resource"), 205 } 206 } 207 208 /// Accesses the "rep" of the resource pointed to by `idx` as part of a 209 /// lending operation. 210 /// 211 /// This will increase `lend_count` for owned resources and must be paired 212 /// with a `resource_undo_lend` below later on (managed by `CallContexts`). 213 /// 214 /// Upon success returns the "rep" plus whether the borrow came from an 215 /// `own` handle. 216 pub fn resource_lend(&mut self, idx: TypedResourceIndex) -> Result<(u32, bool)> { 217 match self.get_mut(idx.raw_index())? { 218 Slot::ResourceOwn { 219 resource, 220 lend_count, 221 } => { 222 let rep = resource.rep(&idx)?; 223 *lend_count = lend_count.checked_add(1).unwrap(); 224 Ok((rep, true)) 225 } 226 Slot::ResourceBorrow { resource, .. } => Ok((resource.rep(&idx)?, false)), 227 _ => bail!("index {} is not a resource", idx.raw_index()), 228 } 229 } 230 231 /// For `own` resources that were borrowed in `resource_lend`, undoes the 232 /// lending operation. 233 pub fn resource_undo_lend(&mut self, idx: TypedResourceIndex) -> Result<()> { 234 match self.get_mut(idx.raw_index())? { 235 Slot::ResourceOwn { lend_count, .. } => { 236 *lend_count -= 1; 237 Ok(()) 238 } 239 _ => bail!("index {} is not an own resource", idx.raw_index()), 240 } 241 } 242 243 /// Removes the resource specified by `idx` from the table. 244 /// 245 /// This can fail if `idx` doesn't point to a resource, points to a 246 /// borrowed resource, or points to a resource of the wrong type. 247 pub fn remove_resource(&mut self, idx: TypedResourceIndex) -> Result<RemovedResource> { 248 let ret = match self.get_mut(idx.raw_index())? { 249 Slot::ResourceOwn { 250 resource, 251 lend_count, 252 } => { 253 if *lend_count != 0 { 254 bail!("cannot remove owned resource while borrowed") 255 } 256 RemovedResource::Own { 257 rep: resource.rep(&idx)?, 258 } 259 } 260 Slot::ResourceBorrow { resource, scope } => { 261 // Ensure the drop is done with the right type 262 resource.rep(&idx)?; 263 RemovedResource::Borrow { scope: *scope } 264 } 265 _ => bail!("index {} is not a resource", idx.raw_index()), 266 }; 267 self.remove(idx.raw_index())?; 268 Ok(ret) 269 } 270 271 /// Inserts a readable-end stream of type `ty` and with the specified `rep` 272 /// into this table. 273 /// 274 /// Returns the table-local index of the stream. 275 pub fn stream_insert_read(&mut self, ty: TypeStreamTableIndex, rep: u32) -> Result<u32> { 276 self.insert(Slot::Stream { 277 rep, 278 ty, 279 state: TransmitLocalState::Read { done: false }, 280 }) 281 } 282 283 /// Inserts a writable-end stream of type `ty` and with the specified `rep` 284 /// into this table. 285 /// 286 /// Returns the table-local index of the stream. 287 pub fn stream_insert_write(&mut self, ty: TypeStreamTableIndex, rep: u32) -> Result<u32> { 288 self.insert(Slot::Stream { 289 rep, 290 ty, 291 state: TransmitLocalState::Write { done: false }, 292 }) 293 } 294 295 /// Returns the `rep` and `state` associated with the stream pointed to by 296 /// `idx`. 297 /// 298 /// Returns an error if `idx` is out-of-bounds or doesn't point to a stream 299 /// of type `ty`. 300 pub fn stream_rep( 301 &mut self, 302 expected_ty: TypeStreamTableIndex, 303 idx: u32, 304 ) -> Result<(u32, &mut TransmitLocalState)> { 305 match self.get_mut(idx)? { 306 Slot::Stream { rep, ty, state } => { 307 if *ty != expected_ty { 308 bail!("handle is a stream of a different type"); 309 } 310 Ok((*rep, state)) 311 } 312 _ => bail!("handle is not a stream"), 313 } 314 } 315 316 /// Removes the stream handle from `idx`, returning its `rep`. 317 /// 318 /// The stream must have the type `ty` and additionally be in a state 319 /// suitable for removal. 320 /// 321 /// Returns the `rep` for the stream along with whether the stream was 322 /// "done" or the writable end was witnessed as being done. 323 pub fn stream_remove_readable( 324 &mut self, 325 expected_ty: TypeStreamTableIndex, 326 idx: u32, 327 ) -> Result<(u32, bool)> { 328 let ret = match self.get_mut(idx)? { 329 Slot::Stream { rep, ty, state } => { 330 if *ty != expected_ty { 331 bail!("handle is a stream of a different type"); 332 } 333 let is_done = match state { 334 TransmitLocalState::Read { done } => *done, 335 TransmitLocalState::Write { .. } => { 336 bail!("handle is not a readable end of a stream") 337 } 338 TransmitLocalState::Busy => bail!("cannot remove busy stream"), 339 }; 340 (*rep, is_done) 341 } 342 _ => bail!("handle is not a stream"), 343 }; 344 self.remove(idx)?; 345 Ok(ret) 346 } 347 348 /// Removes the writable stream handle from `idx`, returning its `rep`. 349 pub fn stream_remove_writable( 350 &mut self, 351 expected_ty: TypeStreamTableIndex, 352 idx: u32, 353 ) -> Result<u32> { 354 let ret = match self.get_mut(idx)? { 355 Slot::Stream { rep, ty, state } => { 356 if *ty != expected_ty { 357 bail!("handle is a stream of a different type"); 358 } 359 match state { 360 TransmitLocalState::Write { .. } => {} 361 TransmitLocalState::Read { .. } => { 362 bail!("passed read end to `stream.drop-writable`") 363 } 364 TransmitLocalState::Busy => bail!("cannot drop busy stream"), 365 } 366 *rep 367 } 368 _ => bail!("handle is not a stream"), 369 }; 370 self.remove(idx)?; 371 Ok(ret) 372 } 373 374 /// Inserts a readable-end future of type `ty` and with the specified `rep` 375 /// into this table. 376 /// 377 /// Returns the table-local index of the future. 378 pub fn future_insert_read(&mut self, ty: TypeFutureTableIndex, rep: u32) -> Result<u32> { 379 self.insert(Slot::Future { 380 rep, 381 ty, 382 state: TransmitLocalState::Read { done: false }, 383 }) 384 } 385 386 /// Inserts a writable-end future of type `ty` and with the specified `rep` 387 /// into this table. 388 /// 389 /// Returns the table-local index of the future. 390 pub fn future_insert_write(&mut self, ty: TypeFutureTableIndex, rep: u32) -> Result<u32> { 391 self.insert(Slot::Future { 392 rep, 393 ty, 394 state: TransmitLocalState::Write { done: false }, 395 }) 396 } 397 398 /// Returns the `rep` and `state` associated with the future pointed to by 399 /// `idx`. 400 /// 401 /// Returns an error if `idx` is out-of-bounds or doesn't point to a future 402 /// of type `ty`. 403 pub fn future_rep( 404 &mut self, 405 expected_ty: TypeFutureTableIndex, 406 idx: u32, 407 ) -> Result<(u32, &mut TransmitLocalState)> { 408 match self.get_mut(idx)? { 409 Slot::Future { rep, ty, state } => { 410 if *ty != expected_ty { 411 bail!("handle is a future of a different type"); 412 } 413 Ok((*rep, state)) 414 } 415 _ => bail!("handle is not a future"), 416 } 417 } 418 419 /// Removes the future handle from `idx`, returning its `rep`. 420 /// 421 /// The future must have the type `ty` and additionally be in a state 422 /// suitable for removal. 423 /// 424 /// Returns the `rep` for the future along with whether the future was 425 /// "done" or the writable end was witnessed as being done. 426 pub fn future_remove_readable( 427 &mut self, 428 expected_ty: TypeFutureTableIndex, 429 idx: u32, 430 ) -> Result<(u32, bool)> { 431 let ret = match self.get_mut(idx)? { 432 Slot::Future { rep, ty, state } => { 433 if *ty != expected_ty { 434 bail!("handle is a future of a different type"); 435 } 436 let is_done = match state { 437 TransmitLocalState::Read { done } => *done, 438 TransmitLocalState::Write { .. } => { 439 bail!("handle is not a readable end of a future") 440 } 441 TransmitLocalState::Busy => bail!("cannot remove busy future"), 442 }; 443 (*rep, is_done) 444 } 445 _ => bail!("handle is not a future"), 446 }; 447 self.remove(idx)?; 448 Ok(ret) 449 } 450 451 /// Removes the writable future handle from `idx`, returning its `rep`. 452 pub fn future_remove_writable( 453 &mut self, 454 expected_ty: TypeFutureTableIndex, 455 idx: u32, 456 ) -> Result<u32> { 457 let ret = match self.get_mut(idx)? { 458 Slot::Future { rep, ty, state } => { 459 if *ty != expected_ty { 460 bail!("handle is a future of a different type"); 461 } 462 match state { 463 TransmitLocalState::Write { .. } => {} 464 TransmitLocalState::Read { .. } => { 465 bail!("passed read end to `future.drop-writable`") 466 } 467 TransmitLocalState::Busy => bail!("cannot drop busy future"), 468 } 469 *rep 470 } 471 _ => bail!("handle is not a future"), 472 }; 473 self.remove(idx)?; 474 Ok(ret) 475 } 476 477 /// Inserts the error-context `rep` into this table, returning the index it 478 /// now resides at. 479 pub fn error_context_insert(&mut self, rep: u32) -> Result<u32> { 480 self.insert(Slot::ErrorContext { rep }) 481 } 482 483 /// Returns the `rep` of an error-context pointed to by `idx`. 484 pub fn error_context_rep(&mut self, idx: u32) -> Result<u32> { 485 match self.get_mut(idx)? { 486 Slot::ErrorContext { rep } => Ok(*rep), 487 _ => bail!("handle is not an error-context"), 488 } 489 } 490 491 /// Drops the error-context pointed to by `idx`. 492 /// 493 /// Returns the internal `rep`. 494 pub fn error_context_drop(&mut self, idx: u32) -> Result<u32> { 495 let rep = match self.get_mut(idx)? { 496 Slot::ErrorContext { rep } => *rep, 497 _ => bail!("handle is not an error-context"), 498 }; 499 self.remove(idx)?; 500 Ok(rep) 501 } 502 503 /// Inserts `rep` as a guest subtask into this table. 504 pub fn subtask_insert_guest(&mut self, rep: u32) -> Result<u32> { 505 self.insert(Slot::GuestTask { rep }) 506 } 507 508 /// Inserts `rep` as a host subtask into this table. 509 pub fn subtask_insert_host(&mut self, rep: u32) -> Result<u32> { 510 self.insert(Slot::HostTask { rep }) 511 } 512 513 /// Returns the `rep` of the subtask at `idx` as well as if it's a host 514 /// task or not. 515 pub fn subtask_rep(&mut self, idx: u32) -> Result<(u32, bool)> { 516 match self.get_mut(idx)? { 517 Slot::GuestTask { rep } => Ok((*rep, false)), 518 Slot::HostTask { rep } => Ok((*rep, true)), 519 _ => bail!("handle is not a subtask"), 520 } 521 } 522 523 /// Removes the subtask set at `idx`, returning its `rep`. 524 pub fn subtask_remove(&mut self, idx: u32) -> Result<(u32, bool)> { 525 let ret = match self.get_mut(idx)? { 526 Slot::GuestTask { rep } => (*rep, false), 527 Slot::HostTask { rep } => (*rep, true), 528 _ => bail!("handle is not a subtask"), 529 }; 530 self.remove(idx)?; 531 Ok(ret) 532 } 533 534 /// Inserts `rep` as a waitable set into this table. 535 pub fn waitable_set_insert(&mut self, rep: u32) -> Result<u32> { 536 self.insert(Slot::WaitableSet { rep }) 537 } 538 539 /// Returns the `rep` of an waitable-set pointed to by `idx`. 540 pub fn waitable_set_rep(&mut self, idx: u32) -> Result<u32> { 541 match self.get_mut(idx)? { 542 Slot::WaitableSet { rep, .. } => Ok(*rep), 543 _ => bail!("handle is not an waitable-set"), 544 } 545 } 546 547 /// Removes the waitable set at `idx`, returning its `rep`. 548 pub fn waitable_set_remove(&mut self, idx: u32) -> Result<u32> { 549 let ret = match self.get_mut(idx)? { 550 Slot::WaitableSet { rep } => *rep, 551 _ => bail!("handle is not a waitable-set"), 552 }; 553 self.remove(idx)?; 554 Ok(ret) 555 } 556 557 /// Returns the `rep` for the waitable specified by `idx` along with what 558 /// kind of waitable it is. 559 pub fn waitable_rep(&mut self, idx: u32) -> Result<(u32, Waitable)> { 560 match self.get_mut(idx)? { 561 Slot::GuestTask { rep } => Ok((*rep, Waitable::Subtask { is_host: false })), 562 Slot::HostTask { rep } => Ok((*rep, Waitable::Subtask { is_host: true })), 563 Slot::Future { rep, .. } => Ok((*rep, Waitable::Future)), 564 Slot::Stream { rep, .. } => Ok((*rep, Waitable::Stream)), 565 _ => bail!("handle is not a waitable"), 566 } 567 } 568 } 569