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:
Pat Hickey
2020-06-30 13:08:29 -07:00
committed by GitHub
7 changed files with 345 additions and 71 deletions

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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()),
}

View File

@@ -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)
}
}

View File

@@ -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,

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 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 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)
}
}