wiggle: factor BorrowChecker concrete implementation to live in engines

The BorrowChecker methods get inlined as part of the GuestMemory trait.

The BorrowChecker implementation moves out to the engines. Unfortunately
this does mean having a copy in `test-helpers` along with another in
`wasmtime-wiggle`. The `wasmtime-wiggle` copy will move into `wasmtime`
itself in a subsequent PR.

https://github.com/bytecodealliance/wasmtime/issues/1917
This commit is contained in:
Pat Hickey
2020-06-29 11:55:22 -07:00
parent c10c79b7ae
commit 05201b514d
7 changed files with 343 additions and 69 deletions

View File

@@ -85,7 +85,7 @@ macro_rules! primitives {
start: offset, start: offset,
len: size, len: size,
}; };
if ptr.borrow_checker().is_borrowed(region) { if ptr.mem().is_borrowed(region) {
return Err(GuestError::PtrBorrowed(region)); return Err(GuestError::PtrBorrowed(region));
} }
Ok(unsafe { *host_ptr.cast::<Self>() }) Ok(unsafe { *host_ptr.cast::<Self>() })
@@ -104,7 +104,7 @@ macro_rules! primitives {
start: offset, start: offset,
len: size, len: size,
}; };
if ptr.borrow_checker().is_borrowed(region) { if ptr.mem().is_borrowed(region) {
return Err(GuestError::PtrBorrowed(region)); return Err(GuestError::PtrBorrowed(region));
} }
unsafe { unsafe {

View File

@@ -11,15 +11,12 @@ pub use wiggle_macro::from_witx;
#[cfg(feature = "wiggle_metadata")] #[cfg(feature = "wiggle_metadata")]
pub use witx; pub use witx;
mod borrow;
mod error; mod error;
mod guest_type; mod guest_type;
mod region; mod region;
pub extern crate tracing; pub extern crate tracing;
pub use borrow::BorrowChecker;
use borrow::BorrowHandle;
pub use error::GuestError; pub use error::GuestError;
pub use guest_type::{GuestErrorType, GuestType, GuestTypeTransparent}; pub use guest_type::{GuestErrorType, GuestType, GuestTypeTransparent};
pub use region::Region; pub use region::Region;
@@ -55,24 +52,27 @@ pub use region::Region;
/// must be "somehow nonzero in length" to allow users of `GuestMemory` and /// must be "somehow nonzero in length" to allow users of `GuestMemory` and
/// `GuestPtr` to safely read and write interior data. /// `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 /// # Using References
/// ///
/// See the safety guarantees of [`BorrowChecker`], which asserts that exactly
/// one `BorrowChecker` may be constructed for each WebAssembly memory.
///
/// The [`GuestMemory::as_slice`] or [`GuestPtr::as_str`] will return smart /// The [`GuestMemory::as_slice`] or [`GuestPtr::as_str`] will return smart
/// pointers [`GuestSlice`] and [`GuestStr`]. These types, which implement /// pointers [`GuestSlice`] and [`GuestStr`]. These types, which implement
/// [`std::ops::Deref`] and [`std::ops::DerefMut`], provide mutable references /// [`std::ops::Deref`] and [`std::ops::DerefMut`], provide mutable references
/// into the memory region given by a `GuestMemory`. /// into the memory region given by a `GuestMemory`.
/// ///
/// These smart pointers are dynamically borrow-checked by the `BorrowChecker` /// These smart pointers are dynamically borrow-checked by the borrow checker
/// given by [`GuestMemory::borrow_checker()`]. While a `GuestSlice` /// methods on this trait. While a `GuestSlice` or a `GuestStr` are live, the
/// or a `GuestStr` are live, the [`BorrowChecker::has_outstanding_borrows()`] /// [`GuestMemory::has_outstanding_borrows()`] method will always return
/// method will always return `true`. If you need to re-enter the guest or /// `true`. If you need to re-enter the guest or otherwise read or write to
/// otherwise read or write to the contents of a WebAssembly memory, all /// the contents of a WebAssembly memory, all `GuestSlice`s and `GuestStr`s
/// `GuestSlice`s and `GuestStr`s for the memory must be dropped, at which /// for the memory must be dropped, at which point
/// point `BorrowChecker::has_outstanding_borrows()` will return `false`. /// `GuestMemory::has_outstanding_borrows()` will return `false`.
pub unsafe trait GuestMemory { pub unsafe trait GuestMemory {
/// Returns the base allocation of this guest memory, located in host /// Returns the base allocation of this guest memory, located in host
/// memory. /// memory.
@@ -86,11 +86,6 @@ pub unsafe trait GuestMemory {
/// [`GuestMemory`] documentation. /// [`GuestMemory`] documentation.
fn base(&self) -> (*mut u8, u32); 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 /// Validates a guest-relative pointer given various attributes, and returns
/// the corresponding host pointer. /// the corresponding host pointer.
/// ///
@@ -152,16 +147,53 @@ pub unsafe trait GuestMemory {
{ {
GuestPtr::new(self, offset) 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 { unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a T {
fn base(&self) -> (*mut u8, u32) { fn base(&self) -> (*mut u8, u32) {
T::base(self) T::base(self)
} }
fn borrow_checker(&self) -> &BorrowChecker { fn has_outstanding_borrows(&self) -> bool {
T::borrow_checker(self) 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) { fn base(&self) -> (*mut u8, u32) {
T::base(self) T::base(self)
} }
fn borrow_checker(&self) -> &BorrowChecker { fn has_outstanding_borrows(&self) -> bool {
T::borrow_checker(self) 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) { fn base(&self) -> (*mut u8, u32) {
T::base(self) T::base(self)
} }
fn borrow_checker(&self) -> &BorrowChecker { fn has_outstanding_borrows(&self) -> bool {
T::borrow_checker(self) 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) { fn base(&self) -> (*mut u8, u32) {
T::base(self) T::base(self)
} }
fn borrow_checker(&self) -> &BorrowChecker { fn has_outstanding_borrows(&self) -> bool {
T::borrow_checker(self) 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) { fn base(&self) -> (*mut u8, u32) {
T::base(self) T::base(self)
} }
fn borrow_checker(&self) -> &BorrowChecker { fn has_outstanding_borrows(&self) -> bool {
T::borrow_checker(self) 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 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. /// Casts this `GuestPtr` type to a different type.
/// ///
/// This is a safe method which is useful for simply reinterpreting the 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 /// 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` /// 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 /// 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 /// This function will return a `GuestSlice` into host memory if all checks
/// succeed (valid utf-8, valid pointers, memory is not borrowed, etc). If /// succeed (valid utf-8, valid pointers, memory is not borrowed, etc). If
@@ -426,7 +489,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
self.mem self.mem
.validate_size_align(self.pointer.0, T::guest_align(), len)? as *mut T; .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, start: self.pointer.0,
len, len,
})?; })?;
@@ -442,7 +505,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
Ok(GuestSlice { Ok(GuestSlice {
ptr, ptr,
bc: self.mem.borrow_checker(), mem: self.mem,
borrow, borrow,
}) })
} }
@@ -509,7 +572,7 @@ impl<'a> GuestPtr<'a, str> {
/// bounds checks and utf-8 checks. The resulting `GuestStr` can be used /// 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 /// 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 /// 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 /// This function will return `GuestStr` into host memory if all checks
/// succeed (valid utf-8, valid pointers, etc). If any checks fail then /// succeed (valid utf-8, valid pointers, etc). If any checks fail then
@@ -519,7 +582,7 @@ impl<'a> GuestPtr<'a, str> {
.mem .mem
.validate_size_align(self.pointer.0, 1, self.pointer.1)?; .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, start: self.pointer.0,
len: self.pointer.1, len: self.pointer.1,
})?; })?;
@@ -531,7 +594,7 @@ impl<'a> GuestPtr<'a, str> {
match str::from_utf8_mut(ptr) { match str::from_utf8_mut(ptr) {
Ok(ptr) => Ok(GuestStr { Ok(ptr) => Ok(GuestStr {
ptr, ptr,
bc: self.mem.borrow_checker(), mem: self.mem,
borrow, borrow,
}), }),
Err(e) => Err(GuestError::InvalidUtf8(e)), Err(e) => Err(GuestError::InvalidUtf8(e)),
@@ -566,7 +629,7 @@ impl<T: ?Sized + Pointee> fmt::Debug for GuestPtr<'_, T> {
/// [`std::ops::DerefMut`]. /// [`std::ops::DerefMut`].
pub struct GuestSlice<'a, T> { pub struct GuestSlice<'a, T> {
ptr: &'a mut [T], ptr: &'a mut [T],
bc: &'a BorrowChecker, mem: &'a dyn GuestMemory,
borrow: BorrowHandle, borrow: BorrowHandle,
} }
@@ -585,7 +648,7 @@ impl<'a, T> std::ops::DerefMut for GuestSlice<'a, T> {
impl<'a, T> Drop for GuestSlice<'a, T> { impl<'a, T> Drop for GuestSlice<'a, T> {
fn drop(&mut self) { 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`]. /// [`std::ops::DerefMut`].
pub struct GuestStr<'a> { pub struct GuestStr<'a> {
ptr: &'a mut str, ptr: &'a mut str,
bc: &'a BorrowChecker, mem: &'a dyn GuestMemory,
borrow: BorrowHandle, borrow: BorrowHandle,
} }
@@ -613,7 +676,7 @@ impl<'a> std::ops::DerefMut for GuestStr<'a> {
impl<'a> Drop for GuestStr<'a> { impl<'a> Drop for GuestStr<'a> {
fn drop(&mut self) { fn drop(&mut self) {
self.bc.unborrow(self.borrow) self.mem.unborrow(self.borrow)
} }
} }

View File

@@ -1,10 +1,6 @@
use crate::error::GuestError;
use crate::region::Region;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use wiggle::{BorrowHandle, GuestError, Region};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct BorrowHandle(usize);
pub struct BorrowChecker { pub struct BorrowChecker {
bc: RefCell<InnerBorrowChecker>, bc: RefCell<InnerBorrowChecker>,
@@ -16,10 +12,6 @@ impl BorrowChecker {
/// `GuestSlice` and `GuestStr` structs, which implement `std::ops::Deref` and /// `GuestSlice` and `GuestStr` structs, which implement `std::ops::Deref` and
/// `std::ops::DerefMut`. It also enforces that `GuestPtr::read` and `GuestPtr::write` do not /// `std::ops::DerefMut`. It also enforces that `GuestPtr::read` and `GuestPtr::write` do not
/// access memory with an outstanding borrow. /// 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 unsafe fn new() -> Self {
BorrowChecker { BorrowChecker {
bc: RefCell::new(InnerBorrowChecker::new()), bc: RefCell::new(InnerBorrowChecker::new()),

View File

@@ -1,7 +1,10 @@
use proptest::prelude::*; use proptest::prelude::*;
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::marker; use std::marker;
use wiggle::{BorrowChecker, GuestMemory}; use wiggle::{BorrowHandle, GuestMemory, Region};
mod borrow;
use borrow::BorrowChecker;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MemAreas(Vec<MemArea>); pub struct MemAreas(Vec<MemArea>);
@@ -119,8 +122,17 @@ unsafe impl GuestMemory for HostMemory {
((*ptr).as_mut_ptr(), (*ptr).len() as u32) ((*ptr).as_mut_ptr(), (*ptr).len() as u32)
} }
} }
fn borrow_checker(&self) -> &BorrowChecker { fn has_outstanding_borrows(&self) -> bool {
&self.bc 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)
} }
} }

View File

@@ -212,13 +212,7 @@ fn generate_func(
#handle_early_error #handle_early_error
} }
}; };
// Wiggle does not expose any methods for functions to re-enter the WebAssembly let mem = #runtime::WasmtimeGuestMemory::new(mem);
// 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 );
#target_module::#name_ident( #target_module::#name_ident(
&mut my_cx.borrow_mut(), &mut my_cx.borrow_mut(),
&mem, &mem,

View 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 unsafe 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");
}
}

View File

@@ -1,6 +1,10 @@
pub use wasmtime_wiggle_macro::*; pub use wasmtime_wiggle_macro::*;
pub use wiggle::*; pub use wiggle::*;
mod borrow;
use borrow::BorrowChecker;
/// Lightweight `wasmtime::Memory` wrapper so we can implement the /// Lightweight `wasmtime::Memory` wrapper so we can implement the
/// `wiggle::GuestMemory` trait on it. /// `wiggle::GuestMemory` trait on it.
pub struct WasmtimeGuestMemory { pub struct WasmtimeGuestMemory {
@@ -9,8 +13,20 @@ pub struct WasmtimeGuestMemory {
} }
impl WasmtimeGuestMemory { impl WasmtimeGuestMemory {
pub fn new(mem: wasmtime::Memory, bc: BorrowChecker) -> Self { pub fn new(mem: wasmtime::Memory) -> Self {
Self { mem, bc } 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: unsafe { BorrowChecker::new() },
}
} }
} }
@@ -18,7 +34,16 @@ unsafe impl GuestMemory for WasmtimeGuestMemory {
fn base(&self) -> (*mut u8, u32) { fn base(&self) -> (*mut u8, u32) {
(self.mem.data_ptr(), self.mem.data_size() as _) (self.mem.data_ptr(), self.mem.data_size() as _)
} }
fn borrow_checker(&self) -> &BorrowChecker { fn has_outstanding_borrows(&self) -> bool {
&self.bc 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)
} }
} }