Skip to content

Commit 698028c

Browse files
authored
Add a configuration knob for PAGEMAP_SCAN (#11433)
* Add a configuration knob for `PAGEMAP_SCAN` This commit adds `PoolingAlloationConfig::pagemap_scan` and additionally adds `-Opooling-pagemap-scan` to configure on the CLI. This is the same tri-state configuration option as `MpkEnable` so that enum was renamed to just `Enabled` and repurposed for both options. This then additionally turns the option off-by-default instead of the previous on-by-default-if-able-to to enable more slowly rolling out this feature. * Fix broken test
1 parent 8d4e923 commit 698028c

File tree

16 files changed

+142
-50
lines changed

16 files changed

+142
-50
lines changed

benches/call.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ fn engines() -> Vec<(Engine, IsAsync)> {
5151

5252
let mut pool = PoolingAllocationConfig::default();
5353
if std::env::var("WASMTIME_TEST_FORCE_MPK").is_ok() {
54-
pool.memory_protection_keys(MpkEnabled::Enable);
54+
pool.memory_protection_keys(Enabled::Yes);
5555
}
5656

5757
vec![

crates/cli-flags/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ wasmtime_option_group! {
9797
/// optimize the size of memory slots.
9898
#[serde(default)]
9999
#[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
100-
pub pooling_memory_protection_keys: Option<wasmtime::MpkEnabled>,
100+
pub pooling_memory_protection_keys: Option<wasmtime::Enabled>,
101101

102102
/// Sets an upper limit on how many memory protection keys (MPK) Wasmtime
103103
/// will use. (default: 16)
@@ -193,6 +193,12 @@ wasmtime_option_group! {
193193

194194
/// DEPRECATED: Use `-Cmemory-reservation-for-growth=N` instead.
195195
pub dynamic_memory_reserved_for_growth: Option<u64>,
196+
197+
/// Whether or not `PAGEMAP_SCAN` ioctls are used to reset linear
198+
/// memory.
199+
#[serde(default)]
200+
#[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
201+
pub pooling_pagemap_scan: Option<wasmtime::Enabled>,
196202
}
197203

198204
enum Optimize {
@@ -933,6 +939,9 @@ impl CommonOptions {
933939
max => cfg.total_gc_heaps(max),
934940
_ => err,
935941
}
942+
if let Some(enabled) = self.opts.pooling_pagemap_scan {
943+
cfg.pagemap_scan(enabled);
944+
}
936945
config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling(cfg));
937946
}
938947
},

crates/cli-flags/src/opt.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -538,22 +538,22 @@ impl WasmtimeOptionValue for wasmtime::Collector {
538538
}
539539
}
540540

541-
impl WasmtimeOptionValue for wasmtime::MpkEnabled {
541+
impl WasmtimeOptionValue for wasmtime::Enabled {
542542
const VAL_HELP: &'static str = "[=y|n|auto]";
543543
fn parse(val: Option<&str>) -> Result<Self> {
544544
match val {
545-
None | Some("y") | Some("yes") | Some("true") => Ok(wasmtime::MpkEnabled::Enable),
546-
Some("n") | Some("no") | Some("false") => Ok(wasmtime::MpkEnabled::Disable),
547-
Some("auto") => Ok(wasmtime::MpkEnabled::Auto),
548-
Some(s) => bail!("unknown mpk flag `{s}`, only yes,no,auto,<nothing> accepted"),
545+
None | Some("y") | Some("yes") | Some("true") => Ok(wasmtime::Enabled::Yes),
546+
Some("n") | Some("no") | Some("false") => Ok(wasmtime::Enabled::No),
547+
Some("auto") => Ok(wasmtime::Enabled::Auto),
548+
Some(s) => bail!("unknown flag `{s}`, only yes,no,auto,<nothing> accepted"),
549549
}
550550
}
551551

552552
fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
553553
match *self {
554-
wasmtime::MpkEnabled::Enable => f.write_str("y"),
555-
wasmtime::MpkEnabled::Disable => f.write_str("n"),
556-
wasmtime::MpkEnabled::Auto => f.write_str("auto"),
554+
wasmtime::Enabled::Yes => f.write_str("y"),
555+
wasmtime::Enabled::No => f.write_str("n"),
556+
wasmtime::Enabled::Auto => f.write_str("auto"),
557557
}
558558
}
559559
}

crates/fuzzing/src/generators/config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::oracles::{StoreLimits, Timeout};
55
use anyhow::Result;
66
use arbitrary::{Arbitrary, Unstructured};
77
use std::time::Duration;
8-
use wasmtime::{Engine, Module, MpkEnabled, Store};
8+
use wasmtime::{Enabled, Engine, Module, Store};
99
use wasmtime_test_util::wast::{WastConfig, WastTest, limits};
1010

1111
/// Configuration for `wasmtime::Config` and generated modules for a session of
@@ -75,7 +75,7 @@ impl Config {
7575
pooling.total_memories = config.max_memories as u32;
7676
pooling.max_memory_size = 10 << 16;
7777
pooling.max_memories_per_module = config.max_memories as u32;
78-
if pooling.memory_protection_keys == MpkEnabled::Auto
78+
if pooling.memory_protection_keys == Enabled::Auto
7979
&& pooling.max_memory_protection_keys > 1
8080
{
8181
pooling.total_memories =

crates/fuzzing/src/generators/pooling_config.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Generate instance limits for the pooling allocation strategy.
22
33
use arbitrary::{Arbitrary, Unstructured};
4-
use wasmtime::MpkEnabled;
4+
use wasmtime::Enabled;
55

66
/// Configuration for `wasmtime::PoolingAllocationStrategy`.
77
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
@@ -32,8 +32,10 @@ pub struct PoolingAllocationConfig {
3232

3333
pub async_stack_keep_resident: usize,
3434

35-
pub memory_protection_keys: MpkEnabled,
35+
pub memory_protection_keys: Enabled,
3636
pub max_memory_protection_keys: usize,
37+
38+
pub pagemap_scan: Enabled,
3739
}
3840

3941
impl PoolingAllocationConfig {
@@ -66,6 +68,8 @@ impl PoolingAllocationConfig {
6668

6769
cfg.opts.pooling_memory_protection_keys = Some(self.memory_protection_keys);
6870
cfg.opts.pooling_max_memory_protection_keys = Some(self.max_memory_protection_keys);
71+
72+
cfg.opts.pooling_pagemap_scan = Some(self.pagemap_scan);
6973
}
7074
}
7175

@@ -108,8 +112,10 @@ impl<'a> Arbitrary<'a> for PoolingAllocationConfig {
108112

109113
async_stack_keep_resident: u.int_in_range(0..=1 << 20)?,
110114

111-
memory_protection_keys: *u.choose(&[MpkEnabled::Auto, MpkEnabled::Disable])?,
115+
memory_protection_keys: *u.choose(&[Enabled::Auto, Enabled::No])?,
112116
max_memory_protection_keys: u.int_in_range(1..=20)?,
117+
118+
pagemap_scan: *u.choose(&[Enabled::Auto, Enabled::No])?,
113119
})
114120
}
115121
}

crates/wasmtime/src/config.rs

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2950,16 +2950,17 @@ pub enum WasmBacktraceDetails {
29502950
Environment,
29512951
}
29522952

2953-
/// Describe the tri-state configuration of memory protection keys (MPK).
2953+
/// Describe the tri-state configuration of keys such as MPK or PAGEMAP_SCAN.
29542954
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
2955-
pub enum MpkEnabled {
2956-
/// Use MPK if supported by the current system; fall back to guard regions
2957-
/// otherwise.
2955+
pub enum Enabled {
2956+
/// Enable this feature if it's detected on the host system, otherwise leave
2957+
/// it disabled.
29582958
Auto,
2959-
/// Use MPK or fail if not supported.
2960-
Enable,
2961-
/// Do not use MPK.
2962-
Disable,
2959+
/// Enable this feature and fail configuration if the feature is not
2960+
/// detected on the host system.
2961+
Yes,
2962+
/// Do not enable this feature, even if the host system supports it.
2963+
No,
29632964
}
29642965

29652966
/// Configuration options used with [`InstanceAllocationStrategy::Pooling`] to
@@ -3482,19 +3483,19 @@ impl PoolingAllocationConfig {
34823483
///
34833484
/// - `auto`: if MPK support is available the guard regions are removed; if
34843485
/// not, the guard regions remain
3485-
/// - `enable`: use MPK to eliminate guard regions; fail if MPK is not
3486+
/// - `yes`: use MPK to eliminate guard regions; fail if MPK is not
34863487
/// supported
3487-
/// - `disable`: never use MPK
3488+
/// - `no`: never use MPK
34883489
///
3489-
/// By default this value is `disabled`, but may become `auto` in future
3490+
/// By default this value is `no`, but may become `auto` in future
34903491
/// releases.
34913492
///
34923493
/// __WARNING__: this configuration options is still experimental--use at
34933494
/// your own risk! MPK uses kernel and CPU features to protect memory
34943495
/// regions; you may observe segmentation faults if anything is
34953496
/// misconfigured.
34963497
#[cfg(feature = "memory-protection-keys")]
3497-
pub fn memory_protection_keys(&mut self, enable: MpkEnabled) -> &mut Self {
3498+
pub fn memory_protection_keys(&mut self, enable: Enabled) -> &mut Self {
34983499
self.config.memory_protection_keys = enable;
34993500
self
35003501
}
@@ -3520,7 +3521,7 @@ impl PoolingAllocationConfig {
35203521
/// Check if memory protection keys (MPK) are available on the current host.
35213522
///
35223523
/// This is a convenience method for determining MPK availability using the
3523-
/// same method that [`MpkEnabled::Auto`] does. See
3524+
/// same method that [`Enabled::Auto`] does. See
35243525
/// [`PoolingAllocationConfig::memory_protection_keys`] for more
35253526
/// information.
35263527
#[cfg(feature = "memory-protection-keys")]
@@ -3541,6 +3542,45 @@ impl PoolingAllocationConfig {
35413542
self.config.limits.total_gc_heaps = count;
35423543
self
35433544
}
3545+
3546+
/// Configures whether the Linux-specific [`PAGEMAP_SCAN` ioctl][ioctl] is
3547+
/// used to help reset linear memory.
3548+
///
3549+
/// When [`Self::linear_memory_keep_resident`] or
3550+
/// [`Self::table_keep_resident`] options are configured to nonzero values
3551+
/// the default behavior is to `memset` the lowest addresses of a table or
3552+
/// memory back to their original contents. With the `PAGEMAP_SCAN` ioctl on
3553+
/// Linux this can be done to more intelligently scan for resident pages in
3554+
/// the region and only reset those pages back to their original contents
3555+
/// with `memset` rather than assuming the low addresses are all resident.
3556+
///
3557+
/// This ioctl has the potential to provide a number of performance benefits
3558+
/// in high-reuse and high concurrency scenarios. Notably this enables
3559+
/// Wasmtime to scan the entire region of WebAssembly linear memory and
3560+
/// manually reset memory back to its original contents, up to
3561+
/// [`Self::linear_memory_keep_resident`] bytes, possibly skipping an
3562+
/// `madvise` entirely. This can be more efficient by avoiding removing
3563+
/// pages from the address space entirely and additionally ensuring that
3564+
/// future use of the linear memory doesn't incur page faults as the pages
3565+
/// remain resident.
3566+
///
3567+
/// At this time this configuration option is still being evaluated as to
3568+
/// how appropriate it is for all use cases. It currently defaults to
3569+
/// `no` or disabled but may change to `auto`, enable if supported, in the
3570+
/// future. This option is only supported on Linux and requires a kernel
3571+
/// version of 6.7 or higher.
3572+
///
3573+
/// [ioctl]: https://www.man7.org/linux/man-pages/man2/PAGEMAP_SCAN.2const.html
3574+
pub fn pagemap_scan(&mut self, enable: Enabled) -> &mut Self {
3575+
self.config.pagemap_scan = enable;
3576+
self
3577+
}
3578+
3579+
/// Tests whether [`Self::pagemap_scan`] is available or not on the host
3580+
/// system.
3581+
pub fn is_pagemap_scan_available() -> bool {
3582+
crate::runtime::vm::PoolingInstanceAllocatorConfig::is_pagemap_scan_available()
3583+
}
35443584
}
35453585

35463586
#[cfg(feature = "std")]

crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use self::table_pool::TablePool;
4646
use super::{
4747
InstanceAllocationRequest, InstanceAllocatorImpl, MemoryAllocationIndex, TableAllocationIndex,
4848
};
49-
use crate::MpkEnabled;
49+
use crate::Enabled;
5050
use crate::prelude::*;
5151
use crate::runtime::vm::{
5252
CompiledModuleId, Memory, Table,
@@ -222,9 +222,11 @@ pub struct PoolingInstanceAllocatorConfig {
222222
/// Same as `linear_memory_keep_resident` but for tables.
223223
pub table_keep_resident: usize,
224224
/// Whether to enable memory protection keys.
225-
pub memory_protection_keys: MpkEnabled,
225+
pub memory_protection_keys: Enabled,
226226
/// How many memory protection keys to allocate.
227227
pub max_memory_protection_keys: usize,
228+
/// Whether to enable PAGEMAP_SCAN on Linux.
229+
pub pagemap_scan: Enabled,
228230
}
229231

230232
impl Default for PoolingInstanceAllocatorConfig {
@@ -239,12 +241,19 @@ impl Default for PoolingInstanceAllocatorConfig {
239241
async_stack_keep_resident: 0,
240242
linear_memory_keep_resident: 0,
241243
table_keep_resident: 0,
242-
memory_protection_keys: MpkEnabled::Disable,
244+
memory_protection_keys: Enabled::No,
243245
max_memory_protection_keys: 16,
246+
pagemap_scan: Enabled::No,
244247
}
245248
}
246249
}
247250

251+
impl PoolingInstanceAllocatorConfig {
252+
pub fn is_pagemap_scan_available() -> bool {
253+
PageMap::new().is_some()
254+
}
255+
}
256+
248257
/// An error returned when the pooling allocator cannot allocate a table,
249258
/// memory, etc... because the maximum number of concurrent allocations for that
250259
/// entity has been reached.
@@ -353,7 +362,16 @@ impl PoolingInstanceAllocator {
353362
gc_heaps: GcHeapPool::new(config)?,
354363
#[cfg(feature = "async")]
355364
stacks: StackPool::new(config)?,
356-
pagemap: PageMap::new(),
365+
pagemap: match config.pagemap_scan {
366+
Enabled::Auto => PageMap::new(),
367+
Enabled::Yes => Some(PageMap::new().ok_or_else(|| {
368+
anyhow!(
369+
"required to enable PAGEMAP_SCAN but this system \
370+
does not support it"
371+
)
372+
})?),
373+
Enabled::No => None,
374+
},
357375
})
358376
}
359377

crates/wasmtime/src/runtime/vm/instance/allocator/pooling/memory_pool.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ use crate::runtime::vm::{
6060
MemoryImageSlot, Mmap, MmapOffset, PoolingInstanceAllocatorConfig, mmap::AlignedLength,
6161
};
6262
use crate::{
63-
MpkEnabled,
63+
Enabled,
6464
runtime::vm::mpk::{self, ProtectionKey, ProtectionMask},
6565
vm::HostAlignedByteCount,
6666
};
@@ -151,21 +151,21 @@ impl MemoryPool {
151151
);
152152
}
153153
let pkeys = match config.memory_protection_keys {
154-
MpkEnabled::Auto => {
154+
Enabled::Auto => {
155155
if mpk::is_supported() {
156156
mpk::keys(config.max_memory_protection_keys)
157157
} else {
158158
&[]
159159
}
160160
}
161-
MpkEnabled::Enable => {
161+
Enabled::Yes => {
162162
if mpk::is_supported() {
163163
mpk::keys(config.max_memory_protection_keys)
164164
} else {
165165
bail!("mpk is disabled on this system")
166166
}
167167
}
168-
MpkEnabled::Disable => &[],
168+
Enabled::No => &[],
169169
};
170170

171171
// This is a tricky bit of global state: when creating a memory pool
@@ -840,7 +840,7 @@ mod tests {
840840

841841
// Force the use of MPK.
842842
let config = PoolingInstanceAllocatorConfig {
843-
memory_protection_keys: MpkEnabled::Enable,
843+
memory_protection_keys: Enabled::Yes,
844844
..PoolingInstanceAllocatorConfig::default()
845845
};
846846
let pool = MemoryPool::new(&config, &Tunables::default_host()).unwrap();

crates/wasmtime/tests/engine_across_forks.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ mod unix {
5151
fn pooling_allocator_reset() -> Result<()> {
5252
let mut pooling = PoolingAllocationConfig::new();
5353
pooling.linear_memory_keep_resident(4096);
54+
pooling.pagemap_scan(Enabled::Auto);
5455
let mut config = Config::new();
5556
config.allocation_strategy(pooling);
5657
config.macos_use_mach_ports(false);

examples/mpk.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ fn main() -> Result<()> {
4343
let args = Args::parse();
4444
info!("{args:?}");
4545

46-
let without_mpk = probe_engine_size(&args, MpkEnabled::Disable)?;
46+
let without_mpk = probe_engine_size(&args, Enabled::No)?;
4747
println!("without MPK:\t{}", without_mpk.to_string());
4848

4949
if PoolingAllocationConfig::are_memory_protection_keys_available() {
50-
let with_mpk = probe_engine_size(&args, MpkEnabled::Enable)?;
50+
let with_mpk = probe_engine_size(&args, Enabled::Yes)?;
5151
println!("with MPK:\t{}", with_mpk.to_string());
5252
println!(
5353
"\t\t{}x more slots per reserved memory",
@@ -90,7 +90,7 @@ fn parse_byte_size(value: &str) -> Result<u64> {
9090

9191
/// Find the engine with the largest number of memories we can create on this
9292
/// machine.
93-
fn probe_engine_size(args: &Args, mpk: MpkEnabled) -> Result<Pool> {
93+
fn probe_engine_size(args: &Args, mpk: Enabled) -> Result<Pool> {
9494
let mut search = ExponentialSearch::new();
9595
let mut mapped_bytes = 0;
9696
while !search.done() {
@@ -182,7 +182,7 @@ impl ExponentialSearch {
182182
}
183183

184184
/// Build a pool-allocated engine with `num_memories` slots.
185-
fn build_engine(args: &Args, num_memories: u32, enable_mpk: MpkEnabled) -> Result<usize> {
185+
fn build_engine(args: &Args, num_memories: u32, enable_mpk: Enabled) -> Result<usize> {
186186
// Configure the memory pool.
187187
let mut pool = PoolingAllocationConfig::default();
188188
let max_memory_size =

0 commit comments

Comments
 (0)