From a2bcdb658fcffffdfc975b940b78a20bbb267c8b Mon Sep 17 00:00:00 2001 From: Syrus Date: Thu, 13 Dec 2018 12:49:30 -0800 Subject: [PATCH 1/4] Refactored libcalls --- src/webassembly/instance.rs | 52 ++++++------------- .../{math_intrinsics.rs => libcalls.rs} | 7 +++ src/webassembly/mod.rs | 2 +- 3 files changed, 24 insertions(+), 37 deletions(-) rename src/webassembly/{math_intrinsics.rs => libcalls.rs} (78%) diff --git a/src/webassembly/instance.rs b/src/webassembly/instance.rs index c517a339a..efa13bbfb 100644 --- a/src/webassembly/instance.rs +++ b/src/webassembly/instance.rs @@ -26,7 +26,7 @@ use std::{fmt, mem, slice}; use super::super::common::slice::{BoundedSlice, UncheckedSlice}; use super::errors::ErrorKind; use super::import_object::{ImportObject, ImportValue}; -use super::math_intrinsics; +use super::libcalls; use super::memory::LinearMemory; use super::module::{Export, ImportableExportable, Module}; use super::relocation::{Reloc, RelocSink, RelocationType}; @@ -311,35 +311,21 @@ impl Instance { RelocationType::GrowMemory => { grow_memory as isize }, - RelocationType::LibCall(LibCall::CeilF32) => { - math_intrinsics::ceilf32 as isize - }, - RelocationType::LibCall(LibCall::FloorF32) => { - math_intrinsics::floorf32 as isize - }, - RelocationType::LibCall(LibCall::TruncF32) => { - math_intrinsics::truncf32 as isize - }, - RelocationType::LibCall(LibCall::NearestF32) => { - math_intrinsics::nearbyintf32 as isize - }, - RelocationType::LibCall(LibCall::CeilF64) => { - math_intrinsics::ceilf64 as isize - }, - RelocationType::LibCall(LibCall::FloorF64) => { - math_intrinsics::floorf64 as isize - }, - RelocationType::LibCall(LibCall::TruncF64) => { - math_intrinsics::truncf64 as isize - }, - RelocationType::LibCall(LibCall::NearestF64) => { - math_intrinsics::nearbyintf64 as isize - }, - RelocationType::LibCall(LibCall::Probestack) => { - __rust_probestack as isize - }, - RelocationType::LibCall(call) => { - panic!("Unexpected libcall {}", call); + RelocationType::LibCall(libcall) => { + match libcall { + LibCall::CeilF32 => libcalls::ceilf32 as isize, + LibCall::FloorF32 => libcalls::floorf32 as isize, + LibCall::TruncF32 => libcalls::truncf32 as isize, + LibCall::NearestF32 => libcalls::nearbyintf32 as isize, + LibCall::CeilF64 => libcalls::ceilf64 as isize, + LibCall::FloorF64 => libcalls::floorf64 as isize, + LibCall::TruncF64 => libcalls::truncf64 as isize, + LibCall::NearestF64 => libcalls::nearbyintf64 as isize, + LibCall::Probestack => libcalls::__rust_probestack as isize, + _ => { + panic!("Unexpected libcall {}", libcall); + } + } }, RelocationType::Intrinsic(ref name) => { panic!("Unexpected intrinsic {}", name); @@ -690,9 +676,3 @@ extern "C" fn current_memory(memory_index: u32, instance: &mut Instance) -> u32 let memory = &instance.memories[memory_index as usize]; memory.current_pages() as u32 } - -/// A declaration for the stack probe function in Rust's standard library, for -/// catching callstack overflow. -extern "C" { - pub fn __rust_probestack(); -} diff --git a/src/webassembly/math_intrinsics.rs b/src/webassembly/libcalls.rs similarity index 78% rename from src/webassembly/math_intrinsics.rs rename to src/webassembly/libcalls.rs index 0e946af3d..793d68005 100644 --- a/src/webassembly/math_intrinsics.rs +++ b/src/webassembly/libcalls.rs @@ -39,3 +39,10 @@ pub extern "C" fn truncf64(x: f64) -> f64 { pub extern "C" fn nearbyintf64(x: f64) -> f64 { x.round() } + + +/// A declaration for the stack probe function in Rust's standard library, for +/// catching callstack overflow. +extern "C" { + pub fn __rust_probestack(); +} diff --git a/src/webassembly/mod.rs b/src/webassembly/mod.rs index a481b557f..4e8c92b88 100644 --- a/src/webassembly/mod.rs +++ b/src/webassembly/mod.rs @@ -1,7 +1,7 @@ pub mod errors; pub mod import_object; pub mod instance; -pub mod math_intrinsics; +pub mod libcalls; pub mod memory; pub mod module; pub mod relocation; From fd5554c3bd0cbf5cb50ecf3d43dbf8961eaf0b5d Mon Sep 17 00:00:00 2001 From: Syrus Date: Fri, 14 Dec 2018 17:32:35 -0800 Subject: [PATCH 2/4] Refactored memory usage to use impl-abstract mmap --- Cargo.lock | 13 +---- Cargo.toml | 3 +- src/common/mmap/common.rs | 18 ++++++ src/common/mmap/mod.rs | 13 +++++ src/common/mmap/unix.rs | 96 +++++++++++++++++++++++++++++++ src/common/mmap/windows.rs | 89 ++++++++++++++++++++++++++++ src/common/mod.rs | 1 + src/lib.rs | 3 +- src/webassembly/memory.rs | 115 ++++++++++++++++++++----------------- 9 files changed, 285 insertions(+), 66 deletions(-) create mode 100644 src/common/mmap/common.rs create mode 100644 src/common/mmap/mod.rs create mode 100644 src/common/mmap/unix.rs create mode 100644 src/common/mmap/windows.rs diff --git a/Cargo.lock b/Cargo.lock index 723934f91..0960fe670 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,15 +373,6 @@ dependencies = [ "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "memmap" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "memoffset" version = "0.2.1" @@ -876,11 +867,11 @@ dependencies = [ "cranelift-native 0.23.0", "cranelift-wasm 0.23.0", "docopt 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "indicatif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.44 (git+https://github.com/rust-lang/libc)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "region 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -891,6 +882,7 @@ dependencies = [ "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "wasmparser 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -960,7 +952,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" "checksum mach 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9" "checksum memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4b3629fe9fdbff6daa6c33b90f7c08355c1aca05a3d01fa8063b822fcf185f3b" -"checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" "checksum nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "921f61dc817b379d0834e45d5ec45beaacfae97082090a49c2cf30dcbc30206f" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" diff --git a/Cargo.toml b/Cargo.toml index 053f71d4f..9481b45c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,11 +33,12 @@ serde = "1.0.55" serde_derive = "1.0.55" tempdir = "0.3.7" error-chain = "0.12.0" +errno = "0.2.4" structopt = "0.2.11" wabt = "0.7.1" wasmparser = "0.20.0" +winapi = "0.3.6" region = "0.3.0" -memmap = "0.6.2" # spin = "0.4.10" log = "0.4.5" target-lexicon = "0.2.0" diff --git a/src/common/mmap/common.rs b/src/common/mmap/common.rs new file mode 100644 index 000000000..c47b509ea --- /dev/null +++ b/src/common/mmap/common.rs @@ -0,0 +1,18 @@ + +/// Round `size` up to the nearest multiple of `page_size`. +pub fn round_up_to_page_size(size: usize, page_size: usize) -> usize { + (size + (page_size - 1)) & !(page_size - 1) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_round_up_to_page_size() { + assert_eq!(round_up_to_page_size(0, 4096), 0); + assert_eq!(round_up_to_page_size(1, 4096), 4096); + assert_eq!(round_up_to_page_size(4096, 4096), 4096); + assert_eq!(round_up_to_page_size(4097, 4096), 8192); + } +} diff --git a/src/common/mmap/mod.rs b/src/common/mmap/mod.rs new file mode 100644 index 000000000..b72a09b45 --- /dev/null +++ b/src/common/mmap/mod.rs @@ -0,0 +1,13 @@ +//! A cross-platform Rust API for memory mapped buffers. +// TODO: Refactor this into it's own lib +mod common; + +#[cfg(windows)] +mod windows; +#[cfg(windows)] +pub use self::windows::Mmap; + +#[cfg(unix)] +mod unix; +#[cfg(unix)] +pub use self::unix::Mmap; diff --git a/src/common/mmap/unix.rs b/src/common/mmap/unix.rs new file mode 100644 index 000000000..cc60063f4 --- /dev/null +++ b/src/common/mmap/unix.rs @@ -0,0 +1,96 @@ +//! Low-level abstraction for allocating and managing zero-filled pages +//! of memory. + +use errno; +use libc; +use region; +use std::ptr; +use std::slice; +use std::string::String; + +use super::common::round_up_to_page_size; + +/// A simple struct consisting of a page-aligned pointer to page-aligned +/// and initially-zeroed memory and a length. +#[derive(Debug)] +pub struct Mmap { + ptr: *mut u8, + len: usize, +} + +impl Mmap { + /// Construct a new empty instance of `Mmap`. + pub fn new() -> Self { + Self { + ptr: ptr::null_mut(), + len: 0, + } + } + + /// Create a new `Mmap` pointing to at least `size` bytes of memory, + /// suitably sized and aligned for memory protection. + #[cfg(not(target_os = "windows"))] + pub fn with_size(size: usize) -> Result { + // Mmap may return EINVAL if the size is zero, so just + // special-case that. + if size == 0 { + return Ok(Self::new()); + } + + let page_size = region::page::size(); + let alloc_size = round_up_to_page_size(size, page_size); + let ptr = unsafe { + libc::mmap( + ptr::null_mut(), + alloc_size, + // libc::PROT_READ | libc::PROT_WRITE, + libc::PROT_NONE, + libc::MAP_PRIVATE | libc::MAP_ANON, + -1, + 0, + ) + }; + if ptr as isize == -1isize { + Err(errno::errno().to_string()) + } else { + Ok(Self { + ptr: ptr as *mut u8, + len: alloc_size, + }) + } + } + + /// Return the allocated memory as a slice of u8. + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.ptr, self.len) } + } + + /// Return the allocated memory as a mutable slice of u8. + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.ptr, self.len) } + } + + /// Return the allocated memory as a pointer to u8. + pub fn as_ptr(&self) -> *const u8 { + self.ptr + } + + /// Return the allocated memory as a mutable pointer to u8. + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.ptr + } + + /// Return the lengthof the allocated memory. + pub fn len(&self) -> usize { + self.len + } +} + +impl Drop for Mmap { + fn drop(&mut self) { + if !self.ptr.is_null() { + let r = unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) }; + assert_eq!(r, 0, "munmap failed: {}", errno::errno()); + } + } +} diff --git a/src/common/mmap/windows.rs b/src/common/mmap/windows.rs new file mode 100644 index 000000000..f782e536b --- /dev/null +++ b/src/common/mmap/windows.rs @@ -0,0 +1,89 @@ +//! Low-level abstraction for allocating and managing zero-filled pages +//! of memory. + +use errno; +use region; +use std::ptr; +use std::slice; +use std::string::String; +use winapi::um::memoryapi::{VirtualAlloc, VirtualFree}; +use winapi::um::winnt::{MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE, MEM_RELEASE}; + +use super::common::round_up_to_page_size; + + +/// A simple struct consisting of a page-aligned pointer to page-aligned +/// and initially-zeroed memory and a length. +#[derive(Debug)] +pub struct Mmap { + ptr: *mut u8, + len: usize, +} + +impl Mmap { + /// Construct a new empty instance of `Mmap`. + pub fn new() -> Self { + Self { + ptr: ptr::null_mut(), + len: 0, + } + } + + /// Create a new `Mmap` pointing to at least `size` bytes of memory, + /// suitably sized and aligned for memory protection. + pub fn with_size(size: usize) -> Result { + let page_size = region::page::size(); + + // VirtualAlloc always rounds up to the next multiple of the page size + let ptr = unsafe { + VirtualAlloc( + ptr::null_mut(), + size, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE, + ) + }; + if !ptr.is_null() { + Ok(Self { + ptr: ptr as *mut u8, + len: round_up_to_page_size(size, page_size), + }) + } else { + Err(errno::errno().to_string()) + } + } + + /// Return the allocated memory as a slice of u8. + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.ptr, self.len) } + } + + /// Return the allocated memory as a mutable slice of u8. + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.ptr, self.len) } + } + + /// Return the allocated memory as a pointer to u8. + pub fn as_ptr(&self) -> *const u8 { + self.ptr + } + + /// Return the allocated memory as a mutable pointer to u8. + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.ptr + } + + /// Return the lengthof the allocated memory. + pub fn len(&self) -> usize { + self.len + } +} + +impl Drop for Mmap { + fn drop(&mut self) { + if !self.ptr.is_null() { + let r = unsafe { VirtualFree(self.ptr, self.len, MEM_RELEASE) }; + assert_eq!(r, 0); + } + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 665ee9bb0..2143bbf4e 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,2 +1,3 @@ pub mod slice; pub mod stdio; +pub mod mmap; diff --git a/src/lib.rs b/src/lib.rs index 8ae6bfdf5..db5d72d02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ extern crate cranelift_entity; extern crate cranelift_native; extern crate cranelift_wasm; extern crate libc; -extern crate memmap; extern crate region; extern crate structopt; extern crate wabt; @@ -17,6 +16,8 @@ pub extern crate nix; // re-exported for usage in macros extern crate rayon; extern crate indicatif; extern crate console; +#[cfg(windows)] +extern crate winapi; #[macro_use] mod macros; diff --git a/src/webassembly/memory.rs b/src/webassembly/memory.rs index 394628fe8..4275088da 100644 --- a/src/webassembly/memory.rs +++ b/src/webassembly/memory.rs @@ -3,17 +3,21 @@ //! webassembly::Instance. //! A memory created by Rust or in WebAssembly code will be accessible and //! mutable from both Rust and WebAssembly. -use nix::libc::{c_void, mprotect, PROT_READ, PROT_WRITE}; -use nix::sys::mman::{mmap, MapFlags, ProtFlags}; use std::ops::{Deref, DerefMut}; use std::slice; +use region; + +use crate::common::mmap::Mmap; /// A linear memory instance. -// #[derive(Debug)] pub struct LinearMemory { - base: *mut c_void, // The size will always be `LinearMemory::DEFAULT_SIZE` - current: u32, // current number of wasm pages + // The mmap allocation + mmap: Mmap, + + // current number of wasm pages + current: u32, + // The maximum size the WebAssembly Memory is allowed to grow // to, in units of WebAssembly pages. When present, the maximum // parameter acts as a hint to the engine to reserve memory up @@ -21,6 +25,8 @@ pub struct LinearMemory { // request. In general, most WebAssembly modules shouldn't need // to set a maximum. maximum: Option, + + offset_guard_size: usize, } /// It holds the raw bytes of memory accessed by a WebAssembly Instance @@ -42,46 +48,38 @@ impl LinearMemory { "Instantiate LinearMemory(initial={:?}, maximum={:?})", initial, maximum ); + + let offset_guard_size = LinearMemory::DEFAULT_SIZE as usize; + let mut mmap = Mmap::with_size(offset_guard_size).expect("Can't create mmap"); - // TODO: Investigate if memory is zeroed out - let base = unsafe { - mmap( - 0 as _, - LinearMemory::DEFAULT_SIZE, - ProtFlags::PROT_NONE, - MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE, - -1, - 0, - ).unwrap() - }; + let base = mmap.as_mut_ptr(); - if initial > 0 { - assert_eq!( - unsafe { - mprotect( - base, - initial as usize * Self::PAGE_SIZE as usize, - // Self::DEFAULT_HEAP_SIZE, - PROT_READ | PROT_WRITE, - ) - }, - 0 - ); + if initial != 0 { + unsafe { + region::protect( + base as _, + initial as usize * Self::PAGE_SIZE as usize, + region::Protection::Read | region::Protection::Write, + // region::Protection::None, + ) + } + .expect("unable to make memory inaccessible"); } debug!("LinearMemory instantiated"); debug!(" - usable: {:#x}..{:#x}", base as usize, (base as usize) + LinearMemory::DEFAULT_HEAP_SIZE); debug!(" - guard: {:#x}..{:#x}", (base as usize) + LinearMemory::DEFAULT_HEAP_SIZE, (base as usize) + LinearMemory::DEFAULT_SIZE); Self { - base, + mmap, current: initial, + offset_guard_size, maximum, } } /// Returns an base address of this linear memory. - pub fn base_addr(&mut self) -> *mut u8 { - self.base as _ + pub fn base(&mut self) -> *mut u8 { + self.mmap.as_mut_ptr() as _ } /// Returns a number of allocated wasm pages. @@ -130,32 +128,43 @@ impl LinearMemory { let new_bytes = (new_pages * Self::PAGE_SIZE) as usize; unsafe { - assert_eq!( - mprotect( - self.base.add(prev_bytes), - new_bytes - prev_bytes, - PROT_READ | PROT_WRITE, - ), - 0 - ); + region::protect( + self.mmap.as_mut_ptr().add(prev_bytes) as _, + new_bytes - prev_bytes, + region::Protection::Read | region::Protection::Write, + // region::Protection::None, + ) } + .expect("unable to make memory inaccessible"); + // if new_bytes > self.mmap.len() - self.offset_guard_size { + // // If we have no maximum, this is a "dynamic" heap, and it's allowed to move. + // assert!(self.maximum.is_none()); + // let guard_bytes = self.offset_guard_size; + // let request_bytes = new_bytes.checked_add(guard_bytes)?; + + // let mut new_mmap = Mmap::with_size(request_bytes).ok()?; + + // // Make the offset-guard pages inaccessible. + // unsafe { + // region::protect( + // new_mmap.as_ptr().add(new_bytes), + // guard_bytes, + // region::Protection::Read | region::Protection::Write, + // // region::Protection::None, + // ) + // } + // .expect("unable to make memory inaccessible"); + + // let copy_len = self.mmap.len() - self.offset_guard_size; + // new_mmap.as_mut_slice()[..copy_len].copy_from_slice(&self.mmap.as_slice()[..copy_len]); + + // self.mmap = new_mmap; + // } self.current = new_pages; Some(prev_pages as i32) } - - pub fn carve_slice(&self, offset: u32, size: u32) -> Option<&[u8]> { - let start = offset as usize; - let end = start + size as usize; - let slice: &[u8] = &*self; - - if end <= self.current_size() as usize { - Some(&slice[start..end]) - } else { - None - } - } } // Not comparing based on memory content. That would be inefficient. @@ -168,12 +177,12 @@ impl PartialEq for LinearMemory { impl Deref for LinearMemory { type Target = [u8]; fn deref(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.base as _, self.current as usize * Self::PAGE_SIZE as usize) } + unsafe { slice::from_raw_parts(self.mmap.as_ptr() as _, self.current as usize * Self::PAGE_SIZE as usize) } } } impl DerefMut for LinearMemory { fn deref_mut(&mut self) -> &mut [u8] { - unsafe { slice::from_raw_parts_mut(self.base as _, self.current as usize * Self::PAGE_SIZE as usize) } + unsafe { slice::from_raw_parts_mut(self.mmap.as_mut_ptr() as _, self.current as usize * Self::PAGE_SIZE as usize) } } } From 7e78d3c661acc8d44cc7180f2fbeff32caf92747 Mon Sep 17 00:00:00 2001 From: Syrus Date: Fri, 14 Dec 2018 17:37:44 -0800 Subject: [PATCH 3/4] Improved max size code --- src/webassembly/memory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webassembly/memory.rs b/src/webassembly/memory.rs index 4275088da..d166c15ff 100644 --- a/src/webassembly/memory.rs +++ b/src/webassembly/memory.rs @@ -93,7 +93,7 @@ impl LinearMemory { /// Returns the maximum number of wasm pages allowed. pub fn maximum_size(&self) -> u32 { - self.maximum.unwrap_or(65536) + self.maximum.unwrap_or(Self::MAX_PAGES) } /// Grow memory by the specified amount of pages. From 1057131bc7e70ebcab49c9a1680ed85a2610364d Mon Sep 17 00:00:00 2001 From: Syrus Date: Fri, 14 Dec 2018 19:57:00 -0800 Subject: [PATCH 4/4] Added description of offset_guard_size --- src/webassembly/memory.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/webassembly/memory.rs b/src/webassembly/memory.rs index d166c15ff..b8fc6f4a6 100644 --- a/src/webassembly/memory.rs +++ b/src/webassembly/memory.rs @@ -26,6 +26,8 @@ pub struct LinearMemory { // to set a maximum. maximum: Option, + // The size of the extra guard pages after the end. + // Is used to optimize loads and stores with constant offsets. offset_guard_size: usize, }