From e286330255757b0090375f5a5a989549c8f8fe7b Mon Sep 17 00:00:00 2001 From: Josh Purcell Date: Wed, 28 Oct 2026 13:57:57 -0500 Subject: [PATCH 1/2] Use one alias region per runtime-data array With copy-on-write memory initialization disabled (or when a module's data segments can't be made into a static image), the module startup function initializes every data segment, loading each segment's base and length out of the `VMContext`. Each of those loads got an alias region keyed by its own `VMContext` offset, so a module with more than ~32k data segments needs more distinct `MemFlags` than a Cranelift function can hold and compilation panics with `MemFlagsSetOverflow`. Modules compiled by Go routinely have tens of thousands of data segments. Key the region on the start of the `runtime_data_bases` and `runtime_data_lengths` arrays instead, giving each array a single region. This is only ever coarser than before. On `main` this is covered by #14253, which bounds the number of alias regions in general. Claude-Session: https://claude.ai/code/session_01EHmbaDcapmSnyJtjfAGMtR --- crates/cranelift/src/alias_region.rs | 39 ++++++++++++++++++---------- tests/all/module.rs | 21 +++++++++++++++ 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/crates/cranelift/src/alias_region.rs b/crates/cranelift/src/alias_region.rs index 873f34b7a21b..af575dfbbae9 100644 --- a/crates/cranelift/src/alias_region.rs +++ b/crates/cranelift/src/alias_region.rs @@ -1244,6 +1244,12 @@ impl AliasRegions> { } } + // The runtime data arrays get one alias region each, keyed by the array's + // start, rather than one region per element: module startup initializes + // every data segment in one function, and a region per element gives a + // module with more than ~32k data segments more distinct `MemFlags` than + // a function can hold (`MemFlagsSetOverflow`). + /// Load the base of the given runtime data out of the `*mut VMContext`. pub fn vmctx_runtime_data_base( &mut self, @@ -1251,12 +1257,13 @@ impl AliasRegions> { vmctx: ir::Value, runtime_data: RuntimeDataIndex, ) -> ir::Value { - self.vmctx_load( - cursor, + let region = self.vmctx_region(cursor.func, self.offsets.vmctx_runtime_data_bases_begin()); + let offset = self.offsets.vmctx_runtime_data_base(runtime_data); + cursor.ins().load( self.pointer_type, - ir::MemFlagsData::trusted(), + ir::MemFlagsData::trusted().with_alias_region(Some(region)), vmctx, - self.offsets.vmctx_runtime_data_base(runtime_data), + i32::try_from(offset).unwrap(), ) } @@ -1267,12 +1274,14 @@ impl AliasRegions> { vmctx: ir::Value, runtime_data: RuntimeDataIndex, ) -> ir::Value { - self.vmctx_load( - cursor, + let region = + self.vmctx_region(cursor.func, self.offsets.vmctx_runtime_data_lengths_begin()); + let offset = self.offsets.vmctx_runtime_data_length(runtime_data); + cursor.ins().load( ir::types::I32, - ir::MemFlagsData::trusted(), + ir::MemFlagsData::trusted().with_alias_region(Some(region)), vmctx, - self.offsets.vmctx_runtime_data_length(runtime_data), + i32::try_from(offset).unwrap(), ) } @@ -1284,13 +1293,15 @@ impl AliasRegions> { runtime_data: RuntimeDataIndex, new_length: ir::Value, ) { - self.vmctx_store( - cursor, - ir::MemFlagsData::trusted(), - vmctx, - self.offsets.vmctx_runtime_data_length(runtime_data), + let region = + self.vmctx_region(cursor.func, self.offsets.vmctx_runtime_data_lengths_begin()); + let offset = self.offsets.vmctx_runtime_data_length(runtime_data); + cursor.ins().store( + ir::MemFlagsData::trusted().with_alias_region(Some(region)), new_length, - ) + vmctx, + i32::try_from(offset).unwrap(), + ); } } diff --git a/tests/all/module.rs b/tests/all/module.rs index 6a4daff1a78b..b0d937a9fad1 100644 --- a/tests/all/module.rs +++ b/tests/all/module.rs @@ -265,6 +265,27 @@ fn large_add_chain_no_stack_overflow() -> Result<()> { Ok(()) } +#[test] +#[cfg_attr(miri, ignore)] +fn many_data_segments_without_cow() -> Result<()> { + // Without CoW initialization every data segment is initialized by the + // module startup function, which once gave each segment's base and + // length its own alias region and overflowed the function's `MemFlags` + // past ~32k segments. + let mut config = Config::new(); + config.memory_init_cow(false); + let engine = Engine::new(&config)?; + let mut wat = String::from("(module (memory 1)\n"); + for i in 0..40_000 { + wat.push_str(&format!("(data (i32.const {}) \"x\")\n", i % 65536)); + } + wat.push_str(")"); + let module = Module::new(&engine, &wat)?; + let mut store = Store::new(&engine, ()); + Instance::new(&mut store, &module, &[])?; + Ok(()) +} + #[test] fn compile_a_component() -> Result<()> { let engine = Engine::default(); From 0cd6b74fd6017b469fbeb15f96869b1c6ad27542 Mon Sep 17 00:00:00 2001 From: Josh Purcell Date: Wed, 28 Oct 2026 15:05:08 -0500 Subject: [PATCH 2/2] Remove the now-unused `vmctx_store` helper Its only caller was `store_vmctx_runtime_data_length`, which now picks its alias region from the start of the runtime-data lengths array. Claude-Session: https://claude.ai/code/session_01EHmbaDcapmSnyJtjfAGMtR --- crates/cranelift/src/alias_region.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/crates/cranelift/src/alias_region.rs b/crates/cranelift/src/alias_region.rs index af575dfbbae9..04ceea02d730 100644 --- a/crates/cranelift/src/alias_region.rs +++ b/crates/cranelift/src/alias_region.rs @@ -808,23 +808,6 @@ where ) } - fn vmctx_store( - &mut self, - cursor: &mut FuncCursor<'_>, - base_flags: ir::MemFlagsData, - vmctx: ir::Value, - offset: u32, - val: ir::Value, - ) { - let region = self.vmctx_region(cursor.func, offset); - cursor.ins().store( - base_flags.with_alias_region(Some(region)), - val, - vmctx, - i32::try_from(offset).unwrap(), - ); - } - /// Load the `VMContext::magic` field. pub fn vmctx_magic(&mut self, cursor: &mut FuncCursor<'_>, vmctx: ir::Value) -> ir::Value { self.vmctx_load(