Code cleanup.

Last minute code clean up to fix some comments and rename `address_space_size`
to `memory_reservation_size` to better describe what the option is doing.
This commit is contained in:
Peter Huene
2021-02-26 18:41:33 -08:00
parent a481e11e63
commit 505437e353
6 changed files with 57 additions and 53 deletions

View File

@@ -60,7 +60,7 @@ pub struct ModuleLimits {
/// The maximum number of imported tables for a module (default is 0).
pub imported_tables: u32,
/// The maximum number of imported memories for a module (default is 0).
/// The maximum number of imported linear memories for a module (default is 0).
pub imported_memories: u32,
/// The maximum number of imported globals for a module (default is 0).
@@ -75,7 +75,7 @@ pub struct ModuleLimits {
/// The maximum number of defined tables for a module (default is 1).
pub tables: u32,
/// The maximum number of defined memories for a module (default is 1).
/// The maximum number of defined linear memories for a module (default is 1).
pub memories: u32,
/// The maximum number of defined globals for a module (default is 10).
@@ -90,7 +90,7 @@ pub struct ModuleLimits {
/// the maximum will be `table_elements` for the purpose of any `table.grow` instruction.
pub table_elements: u32,
/// The maximum number of pages for any memory defined in a module (default is 160).
/// The maximum number of pages for any linear memory defined in a module (default is 160).
///
/// The default of 160 means at most 10 MiB of host memory may be committed for each instance.
///
@@ -100,7 +100,7 @@ pub struct ModuleLimits {
/// If a memory's maximum page limit is unbounded or greater than this value,
/// the maximum will be `memory_pages` for the purpose of any `memory.grow` instruction.
///
/// This value cannot exceed any address space limits placed on instances.
/// This value cannot exceed any memory reservation size limits placed on instances.
pub memory_pages: u32,
}
@@ -234,14 +234,14 @@ pub struct InstanceLimits {
/// The maximum number of concurrent instances supported (default is 1000).
pub count: u32,
/// The maximum reserved host address space size to use for each instance in bytes.
/// The maximum size, in bytes, of host address space to reserve for each linear memory of an instance.
///
/// Note: this value has important performance ramifications.
///
/// On 64-bit platforms, the default for this value will be 6 GiB. A value of less than 4 GiB will
/// force runtime bounds checking for memory accesses and thus will negatively impact performance.
/// Any value above 4 GiB will start eliding bounds checks provided the `offset` of the memory access is
/// less than (`address_space_size` - 4 GiB). A value of 8 GiB will completely elide *all* bounds
/// less than (`memory_reservation_size` - 4 GiB). A value of 8 GiB will completely elide *all* bounds
/// checks; consequently, 8 GiB will be the maximum supported value. The default of 6 GiB reserves
/// less host address space for each instance, but a memory access with an offet above 2 GiB will incur
/// runtime bounds checks.
@@ -251,7 +251,7 @@ pub struct InstanceLimits {
/// for all memory accesses. For better runtime performance, a 64-bit host is recommended.
///
/// This value will be rounded up by the WebAssembly page size (64 KiB).
pub address_space_size: u64,
pub memory_reservation_size: u64,
}
impl Default for InstanceLimits {
@@ -260,9 +260,9 @@ impl Default for InstanceLimits {
Self {
count: 1000,
#[cfg(target_pointer_width = "32")]
address_space_size: 0xA00000,
memory_reservation_size: 0xA00000,
#[cfg(target_pointer_width = "64")]
address_space_size: 0x180000000,
memory_reservation_size: 0x180000000,
}
}
}
@@ -611,8 +611,8 @@ struct MemoryPool {
impl MemoryPool {
fn new(module_limits: &ModuleLimits, instance_limits: &InstanceLimits) -> Result<Self, String> {
let memory_size = usize::try_from(instance_limits.address_space_size)
.map_err(|_| "address space size exceeds addressable memory".to_string())?;
let memory_size = usize::try_from(instance_limits.memory_reservation_size)
.map_err(|_| "memory reservation size exceeds addressable memory".to_string())?;
debug_assert!(
memory_size % region::page::size() == 0,
@@ -627,7 +627,7 @@ impl MemoryPool {
.checked_mul(max_memories)
.and_then(|c| c.checked_mul(max_instances))
.ok_or_else(|| {
"total size of instance address space exceeds addressable memory".to_string()
"total size of memory reservation exceeds addressable memory".to_string()
})?;
Ok(Self {
@@ -877,15 +877,16 @@ impl PoolingInstanceAllocator {
return Err("the instance count limit cannot be zero".into());
}
// Round the instance address space size to the nearest Wasm page size
instance_limits.address_space_size = u64::try_from(round_up_to_pow2(
usize::try_from(instance_limits.address_space_size).unwrap(),
// Round the memory reservation size to the nearest Wasm page size
instance_limits.memory_reservation_size = u64::try_from(round_up_to_pow2(
usize::try_from(instance_limits.memory_reservation_size).unwrap(),
WASM_PAGE_SIZE as usize,
))
.unwrap();
// Cap the instance address space size to 8 GiB (maximum 4 GiB address space + 4 GiB of guard region)
instance_limits.address_space_size = min(instance_limits.address_space_size, 0x200000000);
// Cap the memory reservation size to 8 GiB (maximum 4 GiB accessible + 4 GiB of guard region)
instance_limits.memory_reservation_size =
min(instance_limits.memory_reservation_size, 0x200000000);
// The maximum module memory page count cannot exceed 65536 pages
if module_limits.memory_pages > 0x10000 {
@@ -895,13 +896,14 @@ impl PoolingInstanceAllocator {
));
}
// The maximum module memory page count cannot exceed the instance address space size
if (module_limits.memory_pages * WASM_PAGE_SIZE) as u64 > instance_limits.address_space_size
// The maximum module memory page count cannot exceed the memory reservation size
if (module_limits.memory_pages * WASM_PAGE_SIZE) as u64
> instance_limits.memory_reservation_size
{
return Err(format!(
"module memory page limit of {} pages exeeds the instance address space size limit of {} bytes",
"module memory page limit of {} pages exeeds the memory reservation size limit of {} bytes",
module_limits.memory_pages,
instance_limits.address_space_size
instance_limits.memory_reservation_size
));
}
@@ -940,15 +942,15 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
}
fn adjust_tunables(&self, tunables: &mut Tunables) {
let address_space_size = self.instance_limits.address_space_size;
let memory_reservation_size = self.instance_limits.memory_reservation_size;
// For address spaces larger than 4 GiB, use a guard region to elide
if address_space_size >= 0x100000000 {
// For reservation sizes larger than 4 GiB, use a guard region to elide bounds checks
if memory_reservation_size >= 0x100000000 {
tunables.static_memory_bound = 0x10000; // in Wasm pages
tunables.static_memory_offset_guard_size = address_space_size - 0x100000000;
tunables.static_memory_offset_guard_size = memory_reservation_size - 0x100000000;
} else {
tunables.static_memory_bound =
u32::try_from(address_space_size).unwrap() / WASM_PAGE_SIZE;
u32::try_from(memory_reservation_size).unwrap() / WASM_PAGE_SIZE;
tunables.static_memory_offset_guard_size = 0;
}
@@ -1349,7 +1351,7 @@ mod test {
};
let instance_limits = InstanceLimits {
count: 3,
address_space_size: 4096,
memory_reservation_size: 4096,
};
let instances = InstancePool::new(&module_limits, &instance_limits)?;
@@ -1471,7 +1473,7 @@ mod test {
},
&InstanceLimits {
count: 5,
address_space_size: WASM_PAGE_SIZE as u64,
memory_reservation_size: WASM_PAGE_SIZE as u64,
},
)?;
@@ -1517,7 +1519,7 @@ mod test {
},
&InstanceLimits {
count: 7,
address_space_size: WASM_PAGE_SIZE as u64,
memory_reservation_size: WASM_PAGE_SIZE as u64,
},
)?;
@@ -1551,7 +1553,7 @@ mod test {
let pool = StackPool::new(
&InstanceLimits {
count: 10,
address_space_size: 0,
memory_reservation_size: 0,
},
1,
)?;
@@ -1638,7 +1640,7 @@ mod test {
},
InstanceLimits {
count: 1,
address_space_size: 1,
memory_reservation_size: 1,
},
4096
)
@@ -1648,7 +1650,7 @@ mod test {
}
#[test]
fn test_pooling_allocator_with_address_space_exeeded() {
fn test_pooling_allocator_with_reservation_size_exeeded() {
assert_eq!(
PoolingInstanceAllocator::new(
PoolingAllocationStrategy::Random,
@@ -1658,12 +1660,12 @@ mod test {
},
InstanceLimits {
count: 1,
address_space_size: 1,
memory_reservation_size: 1,
},
4096,
)
.expect_err("expected a failure constructing instance allocator"),
"module memory page limit of 2 pages exeeds the instance address space size limit of 65536 bytes"
"module memory page limit of 2 pages exeeds the memory reservation size limit of 65536 bytes"
);
}
@@ -1686,7 +1688,7 @@ mod test {
},
InstanceLimits {
count: 1,
address_space_size: 1,
memory_reservation_size: 1,
},
4096,
)?;

View File

@@ -1,12 +1,12 @@
//! Implements user-mode page fault handling with the `userfaultfd` ("uffd") system call on Linux.
//! Implements user space page fault handling with the `userfaultfd` ("uffd") system call on Linux.
//!
//! Handling page faults for memory accesses in regions relating to WebAssembly instances
//! enables the implementation of guard pages in user space rather than kernel space.
//! enables the implementation of protecting guard pages in user space rather than kernel space.
//!
//! This reduces the number of system calls and kernel locks needed to provide correct
//! WebAssembly memory semantics.
//!
//! Additionally, linear memories and WebAssembly tables can be lazy-initialized upon access.
//! Additionally, linear memories can be lazy-initialized upon access.
//!
//! This feature requires a Linux kernel 4.11 or newer to use.
@@ -49,7 +49,7 @@ pub fn create_memory_map(_accessible_size: usize, mapping_size: usize) -> Result
// Allocate a single read-write region at once
// As writable pages need to count towards commit charge, use MAP_NORESERVE to override.
// This implies that the kernel is configured to allow overcommit or else
// this allocation will almost certainly fail without a plethora of physical memory to back the alloction.
// this allocation will almost certainly fail without a plethora of physical memory to back the allocation.
// The consequence of not reserving is that our process may segfault on any write to a memory
// page that cannot be backed (i.e. out of memory conditions).
@@ -170,8 +170,8 @@ impl AddressLocator {
}
}
// This is super-duper unsafe as it is used from the handler thread
// to access instance data without any locking primitives.
/// This is super-duper unsafe as it is used from the handler thread
/// to access instance data without any locking primitives.
///
/// It is assumed that the thread that owns the instance being accessed is
/// currently suspended waiting on a fault to be handled.
@@ -182,9 +182,9 @@ impl AddressLocator {
///
/// If the assumption holds true, accessing the instance data from the handler thread
/// should, in theory, be safe.
unsafe fn get_instance(&self, index: usize) -> &mut Instance {
unsafe fn get_instance(&self, index: usize) -> &Instance {
debug_assert!(index < self.max_instances);
&mut *((self.instances_start + (index * self.instance_size)) as *mut Instance)
&*((self.instances_start + (index * self.instance_size)) as *const Instance)
}
unsafe fn get_location(&self, addr: usize) -> Option<AddressLocation> {
@@ -475,6 +475,8 @@ fn handler_thread(
}
}
log::trace!("fault handler thread has successfully terminated");
Ok(())
}
@@ -591,7 +593,7 @@ mod test {
};
let instance_limits = InstanceLimits {
count: 3,
address_space_size: (WASM_PAGE_SIZE * 10) as u64,
memory_reservation_size: (WASM_PAGE_SIZE * 10) as u64,
};
let instances =