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:
@@ -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 [`GuestMemory::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,10 +12,6 @@ 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 {
|
||||
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>);
|
||||
@@ -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 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");
|
||||
}
|
||||
}
|
||||
@@ -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: unsafe { 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