Merge pull request #1938 from bytecodealliance/pch/factor_borrowchecker_out_of_wiggle
wiggle: factor BorrowChecker concrete implementation to live in engines
This commit is contained in:
@@ -85,7 +85,7 @@ macro_rules! primitives {
|
||||
start: offset,
|
||||
len: size,
|
||||
};
|
||||
if ptr.borrow_checker().is_borrowed(region) {
|
||||
if ptr.mem().is_borrowed(region) {
|
||||
return Err(GuestError::PtrBorrowed(region));
|
||||
}
|
||||
Ok(unsafe { *host_ptr.cast::<Self>() })
|
||||
@@ -104,7 +104,7 @@ macro_rules! primitives {
|
||||
start: offset,
|
||||
len: size,
|
||||
};
|
||||
if ptr.borrow_checker().is_borrowed(region) {
|
||||
if ptr.mem().is_borrowed(region) {
|
||||
return Err(GuestError::PtrBorrowed(region));
|
||||
}
|
||||
unsafe {
|
||||
|
||||
@@ -11,15 +11,12 @@ pub use wiggle_macro::from_witx;
|
||||
#[cfg(feature = "wiggle_metadata")]
|
||||
pub use witx;
|
||||
|
||||
mod borrow;
|
||||
mod error;
|
||||
mod guest_type;
|
||||
mod region;
|
||||
|
||||
pub extern crate tracing;
|
||||
|
||||
pub use borrow::BorrowChecker;
|
||||
use borrow::BorrowHandle;
|
||||
pub use error::GuestError;
|
||||
pub use guest_type::{GuestErrorType, GuestType, GuestTypeTransparent};
|
||||
pub use region::Region;
|
||||
@@ -55,24 +52,27 @@ pub use region::Region;
|
||||
/// must be "somehow nonzero in length" to allow users of `GuestMemory` and
|
||||
/// `GuestPtr` to safely read and write interior data.
|
||||
///
|
||||
/// This type also provides methods for run-time borrow checking of references
|
||||
/// into the memory. The safety of this mechanism depends on there being
|
||||
/// exactly one associated tracking of borrows for a given WebAssembly memory.
|
||||
/// There must be no other reads or writes of WebAssembly the memory by either
|
||||
/// Rust or WebAssembly code while there are any outstanding borrows, as given
|
||||
/// by `GuestMemory::has_outstanding_borrows()`.
|
||||
///
|
||||
/// # Using References
|
||||
///
|
||||
/// See the safety guarantees of [`BorrowChecker`], which asserts that exactly
|
||||
/// one `BorrowChecker` may be constructed for each WebAssembly memory.
|
||||
///
|
||||
/// The [`GuestPtr::as_slice`] or [`GuestPtr::as_str`] will return smart
|
||||
/// pointers [`GuestSlice`] and [`GuestStr`]. These types, which implement
|
||||
/// [`std::ops::Deref`] and [`std::ops::DerefMut`], provide mutable references
|
||||
/// into the memory region given by a `GuestMemory`.
|
||||
///
|
||||
/// These smart pointers are dynamically borrow-checked by the `BorrowChecker`
|
||||
/// given by [`GuestMemory::borrow_checker()`]. While a `GuestSlice`
|
||||
/// or a `GuestStr` are live, the [`BorrowChecker::has_outstanding_borrows()`]
|
||||
/// method will always return `true`. If you need to re-enter the guest or
|
||||
/// otherwise read or write to the contents of a WebAssembly memory, all
|
||||
/// `GuestSlice`s and `GuestStr`s for the memory must be dropped, at which
|
||||
/// point `BorrowChecker::has_outstanding_borrows()` will return `false`.
|
||||
/// These smart pointers are dynamically borrow-checked by the borrow checker
|
||||
/// methods on this trait. While a `GuestSlice` or a `GuestStr` are live, the
|
||||
/// [`GuestMemory::has_outstanding_borrows()`] method will always return
|
||||
/// `true`. If you need to re-enter the guest or otherwise read or write to
|
||||
/// the contents of a WebAssembly memory, all `GuestSlice`s and `GuestStr`s
|
||||
/// for the memory must be dropped, at which point
|
||||
/// `GuestMemory::has_outstanding_borrows()` will return `false`.
|
||||
pub unsafe trait GuestMemory {
|
||||
/// Returns the base allocation of this guest memory, located in host
|
||||
/// memory.
|
||||
@@ -86,11 +86,6 @@ pub unsafe trait GuestMemory {
|
||||
/// [`GuestMemory`] documentation.
|
||||
fn base(&self) -> (*mut u8, u32);
|
||||
|
||||
/// Gives a reference to the [`BorrowChecker`] used to keep track of each
|
||||
/// outstanding borrow of the memory region. [`BorrowChecker::new`] safety
|
||||
/// rules require that exactly one checker exist for each memory region.
|
||||
fn borrow_checker(&self) -> &BorrowChecker;
|
||||
|
||||
/// Validates a guest-relative pointer given various attributes, and returns
|
||||
/// the corresponding host pointer.
|
||||
///
|
||||
@@ -152,16 +147,53 @@ pub unsafe trait GuestMemory {
|
||||
{
|
||||
GuestPtr::new(self, offset)
|
||||
}
|
||||
|
||||
/// Indicates whether any outstanding borrows are known to the
|
||||
/// `GuestMemory`. This function must be `false` in order for it to be
|
||||
/// safe to recursively call into a WebAssembly module, or to manipulate
|
||||
/// the WebAssembly memory by any other means.
|
||||
fn has_outstanding_borrows(&self) -> bool;
|
||||
/// Check if a region of linear memory is borrowed. This is called during
|
||||
/// any `GuestPtr::read` or `GuestPtr::write` operation to ensure that
|
||||
/// wiggle is not reading or writing a region of memory which Rust believes
|
||||
/// it has exclusive access to.
|
||||
fn is_borrowed(&self, r: Region) -> bool;
|
||||
/// Borrow a region of linear memory. This is used when constructing a
|
||||
/// `GuestSlice` or `GuestStr`. Those types will give Rust `&mut` access
|
||||
/// to the region of linear memory, therefore, the `GuestMemory` impl must
|
||||
/// guarantee that at most one `BorrowHandle` is issued to a given region,
|
||||
/// `GuestMemory::has_outstanding_borrows` is true for the duration of the
|
||||
/// borrow, and that `GuestMemory::is_borrowed` of any overlapping region
|
||||
/// is false for the duration of the borrow.
|
||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError>;
|
||||
/// Unborrow a previously borrowed region. As long as `GuestSlice` and
|
||||
/// `GuestStr` are implemented correctly, a `BorrowHandle` should only be
|
||||
/// unborrowed once.
|
||||
fn unborrow(&self, h: BorrowHandle);
|
||||
}
|
||||
|
||||
// Forwarding trait implementations to the original type
|
||||
/// A handle to a borrow on linear memory. It is produced by `borrow` and
|
||||
/// consumed by `unborrow`. Only the `GuestMemory` impl should ever construct
|
||||
/// a `BorrowHandle` or inspect its contents.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct BorrowHandle(pub usize);
|
||||
|
||||
// Forwarding trait implementations to the original type
|
||||
unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a T {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
T::base(self)
|
||||
}
|
||||
fn borrow_checker(&self) -> &BorrowChecker {
|
||||
T::borrow_checker(self)
|
||||
fn has_outstanding_borrows(&self) -> bool {
|
||||
T::has_outstanding_borrows(self)
|
||||
}
|
||||
fn is_borrowed(&self, r: Region) -> bool {
|
||||
T::is_borrowed(self, r)
|
||||
}
|
||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||
T::borrow(self, r)
|
||||
}
|
||||
fn unborrow(&self, h: BorrowHandle) {
|
||||
T::unborrow(self, h)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,8 +201,17 @@ unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a mut T {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
T::base(self)
|
||||
}
|
||||
fn borrow_checker(&self) -> &BorrowChecker {
|
||||
T::borrow_checker(self)
|
||||
fn has_outstanding_borrows(&self) -> bool {
|
||||
T::has_outstanding_borrows(self)
|
||||
}
|
||||
fn is_borrowed(&self, r: Region) -> bool {
|
||||
T::is_borrowed(self, r)
|
||||
}
|
||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||
T::borrow(self, r)
|
||||
}
|
||||
fn unborrow(&self, h: BorrowHandle) {
|
||||
T::unborrow(self, h)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,8 +219,17 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Box<T> {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
T::base(self)
|
||||
}
|
||||
fn borrow_checker(&self) -> &BorrowChecker {
|
||||
T::borrow_checker(self)
|
||||
fn has_outstanding_borrows(&self) -> bool {
|
||||
T::has_outstanding_borrows(self)
|
||||
}
|
||||
fn is_borrowed(&self, r: Region) -> bool {
|
||||
T::is_borrowed(self, r)
|
||||
}
|
||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||
T::borrow(self, r)
|
||||
}
|
||||
fn unborrow(&self, h: BorrowHandle) {
|
||||
T::unborrow(self, h)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,8 +237,17 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Rc<T> {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
T::base(self)
|
||||
}
|
||||
fn borrow_checker(&self) -> &BorrowChecker {
|
||||
T::borrow_checker(self)
|
||||
fn has_outstanding_borrows(&self) -> bool {
|
||||
T::has_outstanding_borrows(self)
|
||||
}
|
||||
fn is_borrowed(&self, r: Region) -> bool {
|
||||
T::is_borrowed(self, r)
|
||||
}
|
||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||
T::borrow(self, r)
|
||||
}
|
||||
fn unborrow(&self, h: BorrowHandle) {
|
||||
T::unborrow(self, h)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +255,17 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Arc<T> {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
T::base(self)
|
||||
}
|
||||
fn borrow_checker(&self) -> &BorrowChecker {
|
||||
T::borrow_checker(self)
|
||||
fn has_outstanding_borrows(&self) -> bool {
|
||||
T::has_outstanding_borrows(self)
|
||||
}
|
||||
fn is_borrowed(&self, r: Region) -> bool {
|
||||
T::is_borrowed(self, r)
|
||||
}
|
||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||
T::borrow(self, r)
|
||||
}
|
||||
fn unborrow(&self, h: BorrowHandle) {
|
||||
T::unborrow(self, h)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,11 +350,6 @@ impl<'a, T: ?Sized + Pointee> GuestPtr<'a, T> {
|
||||
self.mem
|
||||
}
|
||||
|
||||
/// Returns the borrow checker that this pointer uses
|
||||
pub fn borrow_checker(&self) -> &'a BorrowChecker {
|
||||
self.mem.borrow_checker()
|
||||
}
|
||||
|
||||
/// Casts this `GuestPtr` type to a different type.
|
||||
///
|
||||
/// This is a safe method which is useful for simply reinterpreting the type
|
||||
@@ -409,7 +472,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
|
||||
/// bounds checks and type validation. The `GuestSlice` is a smart pointer
|
||||
/// that can be used as a `&[T]` or a `&mut [T]` via the `Deref` and `DerefMut`
|
||||
/// traits. The region of memory backing the slice will be marked as borrowed
|
||||
/// by the [`BorrowChecker`] until the `GuestSlice` is dropped.
|
||||
/// by the [`GuestMemory`] until the `GuestSlice` is dropped.
|
||||
///
|
||||
/// This function will return a `GuestSlice` into host memory if all checks
|
||||
/// succeed (valid utf-8, valid pointers, memory is not borrowed, etc). If
|
||||
@@ -426,7 +489,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
|
||||
self.mem
|
||||
.validate_size_align(self.pointer.0, T::guest_align(), len)? as *mut T;
|
||||
|
||||
let borrow = self.mem.borrow_checker().borrow(Region {
|
||||
let borrow = self.mem.borrow(Region {
|
||||
start: self.pointer.0,
|
||||
len,
|
||||
})?;
|
||||
@@ -442,7 +505,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
|
||||
|
||||
Ok(GuestSlice {
|
||||
ptr,
|
||||
bc: self.mem.borrow_checker(),
|
||||
mem: self.mem,
|
||||
borrow,
|
||||
})
|
||||
}
|
||||
@@ -509,7 +572,7 @@ impl<'a> GuestPtr<'a, str> {
|
||||
/// bounds checks and utf-8 checks. The resulting `GuestStr` can be used
|
||||
/// as a `&str` or `&mut str` via the `Deref` and `DerefMut` traits. The
|
||||
/// region of memory backing the `str` will be marked as borrowed by the
|
||||
/// [`BorrowChecker`] until the `GuestStr` is dropped.
|
||||
/// [`GuestMemory`] until the `GuestStr` is dropped.
|
||||
///
|
||||
/// This function will return `GuestStr` into host memory if all checks
|
||||
/// succeed (valid utf-8, valid pointers, etc). If any checks fail then
|
||||
@@ -519,7 +582,7 @@ impl<'a> GuestPtr<'a, str> {
|
||||
.mem
|
||||
.validate_size_align(self.pointer.0, 1, self.pointer.1)?;
|
||||
|
||||
let borrow = self.mem.borrow_checker().borrow(Region {
|
||||
let borrow = self.mem.borrow(Region {
|
||||
start: self.pointer.0,
|
||||
len: self.pointer.1,
|
||||
})?;
|
||||
@@ -531,7 +594,7 @@ impl<'a> GuestPtr<'a, str> {
|
||||
match str::from_utf8_mut(ptr) {
|
||||
Ok(ptr) => Ok(GuestStr {
|
||||
ptr,
|
||||
bc: self.mem.borrow_checker(),
|
||||
mem: self.mem,
|
||||
borrow,
|
||||
}),
|
||||
Err(e) => Err(GuestError::InvalidUtf8(e)),
|
||||
@@ -566,7 +629,7 @@ impl<T: ?Sized + Pointee> fmt::Debug for GuestPtr<'_, T> {
|
||||
/// [`std::ops::DerefMut`].
|
||||
pub struct GuestSlice<'a, T> {
|
||||
ptr: &'a mut [T],
|
||||
bc: &'a BorrowChecker,
|
||||
mem: &'a dyn GuestMemory,
|
||||
borrow: BorrowHandle,
|
||||
}
|
||||
|
||||
@@ -585,7 +648,7 @@ impl<'a, T> std::ops::DerefMut for GuestSlice<'a, T> {
|
||||
|
||||
impl<'a, T> Drop for GuestSlice<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.bc.unborrow(self.borrow)
|
||||
self.mem.unborrow(self.borrow)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,7 +657,7 @@ impl<'a, T> Drop for GuestSlice<'a, T> {
|
||||
/// [`std::ops::DerefMut`].
|
||||
pub struct GuestStr<'a> {
|
||||
ptr: &'a mut str,
|
||||
bc: &'a BorrowChecker,
|
||||
mem: &'a dyn GuestMemory,
|
||||
borrow: BorrowHandle,
|
||||
}
|
||||
|
||||
@@ -613,7 +676,7 @@ impl<'a> std::ops::DerefMut for GuestStr<'a> {
|
||||
|
||||
impl<'a> Drop for GuestStr<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.bc.unborrow(self.borrow)
|
||||
self.mem.unborrow(self.borrow)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
use crate::error::GuestError;
|
||||
use crate::region::Region;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct BorrowHandle(usize);
|
||||
use wiggle::{BorrowHandle, GuestError, Region};
|
||||
|
||||
pub struct BorrowChecker {
|
||||
bc: RefCell<InnerBorrowChecker>,
|
||||
@@ -16,11 +12,7 @@ impl BorrowChecker {
|
||||
/// `GuestSlice` and `GuestStr` structs, which implement `std::ops::Deref` and
|
||||
/// `std::ops::DerefMut`. It also enforces that `GuestPtr::read` and `GuestPtr::write` do not
|
||||
/// access memory with an outstanding borrow.
|
||||
/// The safety of this mechanism depends on creating exactly one `BorrowChecker` per
|
||||
/// WebAssembly memory. There must be no other reads or writes of WebAssembly the memory by
|
||||
/// either Rust or WebAssembly code while there are any outstanding borrows, as given by
|
||||
/// `BorrowChecker::has_outstanding_borrows()`.
|
||||
pub unsafe fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
BorrowChecker {
|
||||
bc: RefCell::new(InnerBorrowChecker::new()),
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
use proptest::prelude::*;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::marker;
|
||||
use wiggle::{BorrowChecker, GuestMemory};
|
||||
use wiggle::{BorrowHandle, GuestMemory, Region};
|
||||
|
||||
mod borrow;
|
||||
use borrow::BorrowChecker;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemAreas(Vec<MemArea>);
|
||||
@@ -57,7 +60,7 @@ impl HostMemory {
|
||||
buffer: HostBuffer {
|
||||
cell: UnsafeCell::new([0; 4096]),
|
||||
},
|
||||
bc: unsafe { BorrowChecker::new() },
|
||||
bc: BorrowChecker::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +122,17 @@ unsafe impl GuestMemory for HostMemory {
|
||||
((*ptr).as_mut_ptr(), (*ptr).len() as u32)
|
||||
}
|
||||
}
|
||||
fn borrow_checker(&self) -> &BorrowChecker {
|
||||
&self.bc
|
||||
fn has_outstanding_borrows(&self) -> bool {
|
||||
self.bc.has_outstanding_borrows()
|
||||
}
|
||||
fn is_borrowed(&self, r: Region) -> bool {
|
||||
self.bc.is_borrowed(r)
|
||||
}
|
||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||
self.bc.borrow(r)
|
||||
}
|
||||
fn unborrow(&self, h: BorrowHandle) {
|
||||
self.bc.unborrow(h)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -212,13 +212,7 @@ fn generate_func(
|
||||
#handle_early_error
|
||||
}
|
||||
};
|
||||
// Wiggle does not expose any methods for functions to re-enter the WebAssembly
|
||||
// instance, or expose the memory via non-wiggle mechanisms. However, the
|
||||
// user-defined code may end up re-entering the instance, in which case this
|
||||
// is an incorrect implementation - we require exactly one BorrowChecker exist
|
||||
// per instance.
|
||||
let bc = #runtime::BorrowChecker::new();
|
||||
let mem = #runtime::WasmtimeGuestMemory::new( mem, bc );
|
||||
let mem = #runtime::WasmtimeGuestMemory::new(mem);
|
||||
#target_module::#name_ident(
|
||||
&mut my_cx.borrow_mut(),
|
||||
&mem,
|
||||
|
||||
188
crates/wiggle/wasmtime/src/borrow.rs
Normal file
188
crates/wiggle/wasmtime/src/borrow.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use wiggle::{BorrowHandle, GuestError, Region};
|
||||
|
||||
pub struct BorrowChecker {
|
||||
bc: RefCell<InnerBorrowChecker>,
|
||||
}
|
||||
|
||||
impl BorrowChecker {
|
||||
/// A `BorrowChecker` manages run-time validation of borrows from a `GuestMemory`. It keeps
|
||||
/// track of regions of guest memory which are possible to alias with Rust references (via the
|
||||
/// `GuestSlice` and `GuestStr` structs, which implement `std::ops::Deref` and
|
||||
/// `std::ops::DerefMut`. It also enforces that `GuestPtr::read` and `GuestPtr::write` do not
|
||||
/// access memory with an outstanding borrow.
|
||||
pub fn new() -> Self {
|
||||
BorrowChecker {
|
||||
bc: RefCell::new(InnerBorrowChecker::new()),
|
||||
}
|
||||
}
|
||||
/// Indicates whether any outstanding borrows are known to the `BorrowChecker`. This function
|
||||
/// must be `false` in order for it to be safe to recursively call into a WebAssembly module,
|
||||
/// or to manipulate the WebAssembly memory by any other means.
|
||||
pub fn has_outstanding_borrows(&self) -> bool {
|
||||
self.bc.borrow().has_outstanding_borrows()
|
||||
}
|
||||
|
||||
pub(crate) fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||
self.bc.borrow_mut().borrow(r)
|
||||
}
|
||||
pub(crate) fn unborrow(&self, h: BorrowHandle) {
|
||||
self.bc.borrow_mut().unborrow(h)
|
||||
}
|
||||
pub(crate) fn is_borrowed(&self, r: Region) -> bool {
|
||||
self.bc.borrow().is_borrowed(r)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// This is a pretty naive way to account for borrows. This datastructure
|
||||
/// could be made a lot more efficient with some effort.
|
||||
struct InnerBorrowChecker {
|
||||
/// Map from handle to region borrowed. A HashMap is probably not ideal
|
||||
/// for this but it works. It would be more efficient if we could
|
||||
/// check `is_borrowed` without an O(n) iteration, by organizing borrows
|
||||
/// by an ordering of Region.
|
||||
borrows: HashMap<BorrowHandle, Region>,
|
||||
/// Handle to give out for the next borrow. This is the bare minimum of
|
||||
/// bookkeeping of free handles, and in a pathological case we could run
|
||||
/// out, hence [`GuestError::BorrowCheckerOutOfHandles`]
|
||||
next_handle: BorrowHandle,
|
||||
}
|
||||
|
||||
impl InnerBorrowChecker {
|
||||
fn new() -> Self {
|
||||
InnerBorrowChecker {
|
||||
borrows: HashMap::new(),
|
||||
next_handle: BorrowHandle(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn has_outstanding_borrows(&self) -> bool {
|
||||
!self.borrows.is_empty()
|
||||
}
|
||||
|
||||
fn is_borrowed(&self, r: Region) -> bool {
|
||||
!self.borrows.values().all(|b| !b.overlaps(r))
|
||||
}
|
||||
|
||||
fn new_handle(&mut self) -> Result<BorrowHandle, GuestError> {
|
||||
// Reset handles to 0 if all handles have been returned.
|
||||
if self.borrows.is_empty() {
|
||||
self.next_handle = BorrowHandle(0);
|
||||
}
|
||||
let h = self.next_handle;
|
||||
// Get the next handle. Since we don't recycle handles until all of
|
||||
// them have been returned, there is a pathological case where a user
|
||||
// may make a Very Large (usize::MAX) number of valid borrows and
|
||||
// unborrows while always keeping at least one borrow outstanding, and
|
||||
// we will run out of borrow handles.
|
||||
self.next_handle = BorrowHandle(
|
||||
h.0.checked_add(1)
|
||||
.ok_or_else(|| GuestError::BorrowCheckerOutOfHandles)?,
|
||||
);
|
||||
Ok(h)
|
||||
}
|
||||
|
||||
fn borrow(&mut self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||
if self.is_borrowed(r) {
|
||||
return Err(GuestError::PtrBorrowed(r));
|
||||
}
|
||||
let h = self.new_handle()?;
|
||||
self.borrows.insert(h, r);
|
||||
Ok(h)
|
||||
}
|
||||
|
||||
fn unborrow(&mut self, h: BorrowHandle) {
|
||||
let _ = self.borrows.remove(&h);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn nonoverlapping() {
|
||||
let mut bs = InnerBorrowChecker::new();
|
||||
let r1 = Region::new(0, 10);
|
||||
let r2 = Region::new(10, 10);
|
||||
assert!(!r1.overlaps(r2));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
bs.borrow(r2).expect("can borrow r2");
|
||||
|
||||
let mut bs = InnerBorrowChecker::new();
|
||||
let r1 = Region::new(10, 10);
|
||||
let r2 = Region::new(0, 10);
|
||||
assert!(!r1.overlaps(r2));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
bs.borrow(r2).expect("can borrow r2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlapping() {
|
||||
let mut bs = InnerBorrowChecker::new();
|
||||
let r1 = Region::new(0, 10);
|
||||
let r2 = Region::new(9, 10);
|
||||
assert!(r1.overlaps(r2));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
||||
|
||||
let mut bs = InnerBorrowChecker::new();
|
||||
let r1 = Region::new(0, 10);
|
||||
let r2 = Region::new(2, 5);
|
||||
assert!(r1.overlaps(r2));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
||||
|
||||
let mut bs = InnerBorrowChecker::new();
|
||||
let r1 = Region::new(9, 10);
|
||||
let r2 = Region::new(0, 10);
|
||||
assert!(r1.overlaps(r2));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
||||
|
||||
let mut bs = InnerBorrowChecker::new();
|
||||
let r1 = Region::new(2, 5);
|
||||
let r2 = Region::new(0, 10);
|
||||
assert!(r1.overlaps(r2));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
||||
|
||||
let mut bs = InnerBorrowChecker::new();
|
||||
let r1 = Region::new(2, 5);
|
||||
let r2 = Region::new(10, 5);
|
||||
let r3 = Region::new(15, 5);
|
||||
let r4 = Region::new(0, 10);
|
||||
assert!(r1.overlaps(r4));
|
||||
bs.borrow(r1).expect("can borrow r1");
|
||||
bs.borrow(r2).expect("can borrow r2");
|
||||
bs.borrow(r3).expect("can borrow r3");
|
||||
assert!(bs.borrow(r4).is_err(), "cant borrow r4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unborrowing() {
|
||||
let mut bs = InnerBorrowChecker::new();
|
||||
let r1 = Region::new(0, 10);
|
||||
let r2 = Region::new(10, 10);
|
||||
assert!(!r1.overlaps(r2));
|
||||
assert_eq!(bs.has_outstanding_borrows(), false, "start with no borrows");
|
||||
let h1 = bs.borrow(r1).expect("can borrow r1");
|
||||
assert_eq!(bs.has_outstanding_borrows(), true, "h1 is outstanding");
|
||||
let h2 = bs.borrow(r2).expect("can borrow r2");
|
||||
|
||||
assert!(bs.borrow(r2).is_err(), "can't borrow r2 twice");
|
||||
bs.unborrow(h2);
|
||||
assert_eq!(
|
||||
bs.has_outstanding_borrows(),
|
||||
true,
|
||||
"h1 is still outstanding"
|
||||
);
|
||||
bs.unborrow(h1);
|
||||
assert_eq!(bs.has_outstanding_borrows(), false, "no remaining borrows");
|
||||
|
||||
let _h3 = bs
|
||||
.borrow(r2)
|
||||
.expect("can borrow r2 again now that its been unborrowed");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
pub use wasmtime_wiggle_macro::*;
|
||||
pub use wiggle::*;
|
||||
|
||||
mod borrow;
|
||||
|
||||
use borrow::BorrowChecker;
|
||||
|
||||
/// Lightweight `wasmtime::Memory` wrapper so we can implement the
|
||||
/// `wiggle::GuestMemory` trait on it.
|
||||
pub struct WasmtimeGuestMemory {
|
||||
@@ -9,8 +13,20 @@ pub struct WasmtimeGuestMemory {
|
||||
}
|
||||
|
||||
impl WasmtimeGuestMemory {
|
||||
pub fn new(mem: wasmtime::Memory, bc: BorrowChecker) -> Self {
|
||||
Self { mem, bc }
|
||||
pub fn new(mem: wasmtime::Memory) -> Self {
|
||||
Self {
|
||||
mem,
|
||||
// Wiggle does not expose any methods for functions to re-enter
|
||||
// the WebAssembly instance, or expose the memory via non-wiggle
|
||||
// mechanisms. However, the user-defined code may end up
|
||||
// re-entering the instance, in which case this is an incorrect
|
||||
// implementation - we require exactly one BorrowChecker exist per
|
||||
// instance.
|
||||
// This BorrowChecker construction is a holdover until it is
|
||||
// integrated fully with wasmtime:
|
||||
// https://github.com/bytecodealliance/wasmtime/issues/1917
|
||||
bc: BorrowChecker::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +34,16 @@ unsafe impl GuestMemory for WasmtimeGuestMemory {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
(self.mem.data_ptr(), self.mem.data_size() as _)
|
||||
}
|
||||
fn borrow_checker(&self) -> &BorrowChecker {
|
||||
&self.bc
|
||||
fn has_outstanding_borrows(&self) -> bool {
|
||||
self.bc.has_outstanding_borrows()
|
||||
}
|
||||
fn is_borrowed(&self, r: Region) -> bool {
|
||||
self.bc.is_borrowed(r)
|
||||
}
|
||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||
self.bc.borrow(r)
|
||||
}
|
||||
fn unborrow(&self, h: BorrowHandle) {
|
||||
self.bc.unborrow(h)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user