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