Skip to content

Commit 0c3a69f

Browse files
authored
Always use mmap in cranelift-jit (#13180)
* Always use mmap in cranelift-jit This was previously not done with the theory that using the regular heap allocator could be faster. We now have an arena memory provider and using mmap is necessary anyway when SELinux is enabled. I don't think the benefits outweight the code complexity cost. * Use memmap2 on Windows too in cranelift-jit
1 parent 54929c1 commit 0c3a69f

9 files changed

Lines changed: 38 additions & 269 deletions

File tree

cranelift/filetests/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ cranelift-frontend = { workspace = true }
1919
cranelift-interpreter = { workspace = true }
2020
cranelift-native = { workspace = true }
2121
cranelift-reader = { workspace = true }
22-
cranelift-jit = { workspace = true, features = ["selinux-fix", "wasmtime-unwinder"] }
22+
cranelift-jit = { workspace = true, features = ["wasmtime-unwinder"] }
2323
cranelift-module = { workspace = true }
2424
cranelift-control = { workspace = true }
2525
wasmtime-unwinder = { workspace = true, features = ["cranelift"] }

cranelift/jit/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ anyhow = { workspace = true }
2424
region = "3.0.2"
2525
libc = { workspace = true }
2626
target-lexicon = { workspace = true }
27-
memmap2 = { version = "0.2.1", optional = true }
27+
memmap2 = "0.2.1"
2828
log = { workspace = true }
2929
wasmtime-jit-icache-coherence = { workspace = true }
3030

@@ -37,7 +37,6 @@ features = [
3737
]
3838

3939
[features]
40-
selinux-fix = ['memmap2']
4140
default = []
4241

4342
wasmtime-unwinder = ["dep:wasmtime-unwinder"]

cranelift/jit/src/memory/system.rs

Lines changed: 6 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
1-
use cranelift_module::{ModuleError, ModuleResult};
2-
3-
#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]
4-
use memmap2::MmapMut;
5-
6-
#[cfg(not(any(feature = "selinux-fix", windows)))]
7-
use std::alloc;
81
use std::io;
92
use std::mem;
103
use std::ptr;
114

5+
use cranelift_module::{ModuleError, ModuleResult};
6+
use memmap2::MmapMut;
7+
128
use super::{BranchProtection, JITMemoryKind, JITMemoryProvider};
139

1410
/// A simple struct consisting of a pointer and length.
1511
struct PtrLen {
16-
#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]
1712
map: Option<MmapMut>,
18-
1913
ptr: *mut u8,
2014
len: usize,
2115
}
@@ -24,17 +18,14 @@ impl PtrLen {
2418
/// Create a new empty `PtrLen`.
2519
fn new() -> Self {
2620
Self {
27-
#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]
2821
map: None,
29-
3022
ptr: ptr::null_mut(),
3123
len: 0,
3224
}
3325
}
3426

3527
/// Create a new `PtrLen` pointing to at least `size` bytes of memory,
3628
/// suitably sized and aligned for memory protection.
37-
#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]
3829
fn with_size(size: usize) -> io::Result<Self> {
3930
let alloc_size = region::page::ceil(size as *const ()) as usize;
4031
MmapMut::map_anon(alloc_size).map(|mut mmap| {
@@ -47,70 +38,8 @@ impl PtrLen {
4738
}
4839
})
4940
}
50-
51-
#[cfg(all(not(target_os = "windows"), not(feature = "selinux-fix")))]
52-
fn with_size(size: usize) -> io::Result<Self> {
53-
assert_ne!(size, 0);
54-
let page_size = region::page::size();
55-
let alloc_size = region::page::ceil(size as *const ()) as usize;
56-
let layout = alloc::Layout::from_size_align(alloc_size, page_size).unwrap();
57-
// Safety: We assert that the size is non-zero above.
58-
let ptr = unsafe { alloc::alloc(layout) };
59-
60-
if !ptr.is_null() {
61-
Ok(Self {
62-
ptr,
63-
len: alloc_size,
64-
})
65-
} else {
66-
Err(io::Error::from(io::ErrorKind::OutOfMemory))
67-
}
68-
}
69-
70-
#[cfg(target_os = "windows")]
71-
fn with_size(size: usize) -> io::Result<Self> {
72-
use windows_sys::Win32::System::Memory::{
73-
MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE, VirtualAlloc,
74-
};
75-
76-
// VirtualAlloc always rounds up to the next multiple of the page size
77-
let ptr = unsafe {
78-
VirtualAlloc(
79-
ptr::null_mut(),
80-
size,
81-
MEM_COMMIT | MEM_RESERVE,
82-
PAGE_READWRITE,
83-
)
84-
};
85-
if !ptr.is_null() {
86-
Ok(Self {
87-
ptr: ptr as *mut u8,
88-
len: region::page::ceil(size as *const ()) as usize,
89-
})
90-
} else {
91-
Err(io::Error::last_os_error())
92-
}
93-
}
9441
}
9542

96-
// `MMapMut` from `cfg(feature = "selinux-fix")` already deallocates properly.
97-
#[cfg(all(not(target_os = "windows"), not(feature = "selinux-fix")))]
98-
impl Drop for PtrLen {
99-
fn drop(&mut self) {
100-
if !self.ptr.is_null() {
101-
let page_size = region::page::size();
102-
let layout = alloc::Layout::from_size_align(self.len, page_size).unwrap();
103-
unsafe {
104-
region::protect(self.ptr, self.len, region::Protection::READ_WRITE)
105-
.expect("unable to unprotect memory");
106-
alloc::dealloc(self.ptr, layout)
107-
}
108-
}
109-
}
110-
}
111-
112-
// TODO: add a `Drop` impl for `cfg(target_os = "windows")`
113-
11443
/// JIT memory manager. This manages pages of suitably aligned and
11544
/// accessible memory. Memory will be leaked by default to have
11645
/// function pointers remain valid for the remainder of the
@@ -201,13 +130,9 @@ impl Memory {
201130

202131
/// Iterates non protected memory allocations that are of not zero bytes in size.
203132
fn non_protected_allocations_iter(&self) -> impl Iterator<Item = &PtrLen> {
204-
let iter = self.allocations[self.already_protected..].iter();
205-
206-
#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]
207-
return iter.filter(|&PtrLen { map, len, .. }| *len != 0 && map.is_some());
208-
209-
#[cfg(any(target_os = "windows", not(feature = "selinux-fix")))]
210-
return iter.filter(|&PtrLen { len, .. }| *len != 0);
133+
self.allocations[self.already_protected..]
134+
.iter()
135+
.filter(|&PtrLen { map, len, .. }| *len != 0 && map.is_some())
211136
}
212137

213138
/// Frees all allocated memory regions that would be leaked otherwise.

crates/wasmtime/src/runtime/func.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,10 @@ impl Func {
10641064
self.vm_func_ref(store).as_ptr().cast()
10651065
}
10661066

1067+
pub(crate) fn to_val_raw(&self, store: &mut StoreOpaque) -> ValRaw {
1068+
ValRaw::funcref(self.to_raw_(store))
1069+
}
1070+
10671071
/// Invokes this function with the `params` given, returning the results
10681072
/// asynchronously.
10691073
///

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

Lines changed: 11 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::runtime::vm::vmcontext::{
1313
VMTableDefinition, VMTableImport, VMTagDefinition, VMTagImport,
1414
};
1515
use crate::runtime::vm::{
16-
GcStore, HostResult, Imports, ModuleRuntimeInfo, SendSyncPtr, VMGcRef, VMGlobalKind, VMStore,
16+
GcStore, HostResult, Imports, ModuleRuntimeInfo, SendSyncPtr, VMGlobalKind, VMStore,
1717
VMStoreRawPtr, VmPtr, VmSafe, WasmFault, catch_unwind_and_record_trap,
1818
};
1919
use crate::store::{
@@ -133,7 +133,7 @@ pub struct Instance {
133133
//
134134
// TODO(#12621): This should be a `TrySecondaryMap<PassiveElemIndex, _>`
135135
// but that type is currently footgun-y / isn't actually OOM-safe yet.
136-
passive_elements: TryVec<PassiveElementSegment>,
136+
passive_elements: TryVec<Option<(NeedsGcRooting, TryVec<ValRaw>)>>,
137137

138138
/// Stores the dropped passive data segments in this instantiation by index.
139139
/// If the index is present in the set, the segment has been dropped.
@@ -225,8 +225,8 @@ impl Instance {
225225
gc_roots: &mut crate::vm::GcRootsList,
226226
) {
227227
for segment in self.passive_elements_mut().iter_mut() {
228-
if segment.needs_gc_rooting() == NeedsGcRooting::Yes {
229-
for e in segment.elements() {
228+
if let Some((wasmtime_environ::NeedsGcRooting::Yes, elems)) = segment {
229+
for e in elems {
230230
let Some(root) = e.as_vmgc_ref_ptr() else {
231231
continue;
232232
};
@@ -912,12 +912,16 @@ impl Instance {
912912
return &[];
913913
};
914914

915-
self.passive_elements[passive.index()].elements()
915+
let Some((_, seg)) = &self.passive_elements[passive.index()] else {
916+
return &[];
917+
};
918+
919+
&**seg
916920
}
917921

918922
pub(crate) fn passive_elements_mut(
919923
self: Pin<&mut Self>,
920-
) -> Pin<&mut TryVec<PassiveElementSegment>> {
924+
) -> Pin<&mut TryVec<Option<(NeedsGcRooting, TryVec<ValRaw>)>>> {
921925
// SAFETY: Not moving data out of `self`.
922926
Pin::new(&mut unsafe { self.get_unchecked_mut() }.passive_elements)
923927
}
@@ -992,7 +996,6 @@ impl Instance {
992996
/// Drop an element.
993997
pub(crate) fn elem_drop(
994998
self: Pin<&mut Self>,
995-
gc_store: Option<&mut GcStore>,
996999
elem_index: ElemIndex,
9971000
) -> Result<(), OutOfMemory> {
9981001
// https://webassembly.github.io/reference-types/core/exec/instructions.html#exec-elem-drop
@@ -1007,7 +1010,7 @@ impl Instance {
10071010
return Ok(());
10081011
};
10091012

1010-
self.passive_elements_mut()[passive_index.index()].clear(gc_store);
1013+
self.passive_elements_mut()[passive_index.index()] = None;
10111014
Ok(())
10121015
}
10131016

@@ -1919,77 +1922,3 @@ impl<T: InstanceLayout> Drop for OwnedInstance<T> {
19191922
}
19201923
}
19211924
}
1922-
1923-
#[derive(Debug)]
1924-
pub(crate) struct PassiveElementSegment {
1925-
needs_gc_rooting: NeedsGcRooting,
1926-
elements: TryVec<ValRaw>,
1927-
}
1928-
1929-
impl PassiveElementSegment {
1930-
/// Create a new passive element segment with the given capacity.
1931-
pub(crate) fn new(
1932-
needs_gc_rooting: NeedsGcRooting,
1933-
capacity: usize,
1934-
) -> Result<Self, OutOfMemory> {
1935-
Ok(Self {
1936-
needs_gc_rooting,
1937-
elements: TryVec::with_capacity(capacity)?,
1938-
})
1939-
}
1940-
1941-
/// Push a value onto this passive element segment.
1942-
///
1943-
/// NB: Does not type check the value, relies on callers to ensure the value
1944-
/// is of the correct type (generally, due to validation).
1945-
pub(crate) fn push(&mut self, store: &mut StoreOpaque, val: Val) -> Result<()> {
1946-
let val = {
1947-
let mut store = AutoAssertNoGc::new(store);
1948-
val.to_raw_(&mut store)?
1949-
};
1950-
let val = self.clone_gc_ref(store, val);
1951-
self.elements.push(val)?;
1952-
Ok(())
1953-
}
1954-
1955-
fn clone_gc_ref(&mut self, store: &mut StoreOpaque, val: ValRaw) -> ValRaw {
1956-
if let NeedsGcRooting::Yes = self.needs_gc_rooting {
1957-
let gc_ref = val.get_anyref();
1958-
if let Some(gc_ref) = VMGcRef::from_raw_u32(gc_ref) {
1959-
if let Some(gc_store) = store.optional_gc_store_mut() {
1960-
return ValRaw::anyref(gc_store.clone_gc_ref(&gc_ref).as_raw_u32());
1961-
}
1962-
}
1963-
}
1964-
val
1965-
}
1966-
1967-
/// Clear this segment's elements.
1968-
pub(crate) fn clear(&mut self, mut gc_store: Option<&mut GcStore>) {
1969-
for val in mem::take(&mut self.elements) {
1970-
self.drop_gc_ref(&mut gc_store, val);
1971-
}
1972-
}
1973-
1974-
fn drop_gc_ref(&mut self, gc_store: &mut Option<&mut GcStore>, val: ValRaw) {
1975-
if let NeedsGcRooting::Yes = self.needs_gc_rooting {
1976-
let gc_ref = val.get_anyref();
1977-
if let Some(gc_ref) = VMGcRef::from_raw_u32(gc_ref) {
1978-
if let Some(gc_store) = gc_store.as_deref_mut() {
1979-
let _ = gc_store.drop_gc_ref(gc_ref);
1980-
}
1981-
}
1982-
}
1983-
}
1984-
1985-
/// Whether this segment needs GC rooting and tracing.
1986-
#[cfg(feature = "gc")]
1987-
pub(crate) fn needs_gc_rooting(&self) -> NeedsGcRooting {
1988-
self.needs_gc_rooting
1989-
}
1990-
1991-
/// The elements of this segment.
1992-
pub(crate) fn elements(&self) -> &[ValRaw] {
1993-
&self.elements
1994-
}
1995-
}

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ use crate::runtime::vm::memory::Memory;
66
use crate::runtime::vm::mpk::ProtectionKey;
77
use crate::runtime::vm::table::Table;
88
use crate::runtime::vm::{CompiledModuleId, ModuleRuntimeInfo};
9-
use crate::store::{Asyncness, InstanceId, StoreOpaque, StoreResourceLimiter};
10-
use crate::vm::instance::PassiveElementSegment;
9+
use crate::store::{Asyncness, AutoAssertNoGc, InstanceId, StoreOpaque, StoreResourceLimiter};
1110
use crate::{OpaqueRootScope, Val};
1211
use core::future::Future;
1312
use core::pin::Pin;
@@ -860,34 +859,40 @@ async fn initialize_passive_elements(
860859
for (idx, segment) in &module.passive_elements {
861860
match segment {
862861
TableSegmentElements::Functions(func_indices) => {
863-
let mut segment =
864-
PassiveElementSegment::new(NeedsGcRooting::No, func_indices.len())?;
862+
let mut vals = TryVec::with_capacity(func_indices.len())?;
865863
for func_idx in func_indices {
866864
let (instance, registry) =
867865
store.instance_and_module_registry_mut(context.instance);
868866
// SAFETY: `store_id` is for the store that owns this instance.
869867
let func = unsafe { instance.get_exported_func(registry, store_id, *func_idx) };
870-
segment.push(store, func.into())?;
868+
vals.push(func.to_val_raw(store))?;
871869
}
872870
let instance = store.instance_mut(context.instance);
873871
debug_assert_eq!(instance.passive_elements.len(), idx.index());
874-
instance.passive_elements_mut().push(segment)?;
872+
instance
873+
.passive_elements_mut()
874+
.push(Some((NeedsGcRooting::No, vals)))?;
875875
}
876876
TableSegmentElements::Expressions {
877877
needs_gc_rooting,
878878
exprs,
879879
} => {
880-
let mut segment = PassiveElementSegment::new(*needs_gc_rooting, exprs.len())?;
880+
let mut vals = TryVec::with_capacity(exprs.len())?;
881881
for expr in exprs {
882882
let mut store = OpaqueRootScope::new(&mut *store);
883+
883884
let val = const_evaluator
884885
.eval(&mut store, limiter.as_deref_mut(), context, expr)
885886
.await?;
886-
segment.push(&mut store, *val)?;
887+
888+
let mut store = AutoAssertNoGc::new(&mut store);
889+
vals.push(val.to_raw_(&mut store)?)?;
887890
}
888891
let instance = store.instance_mut(context.instance);
889892
debug_assert_eq!(instance.passive_elements.len(), idx.index());
890-
instance.passive_elements_mut().push(segment)?;
893+
instance
894+
.passive_elements_mut()
895+
.push(Some((*needs_gc_rooting, vals)))?;
891896
}
892897
}
893898
}

crates/wasmtime/src/runtime/vm/libcalls.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,7 @@ fn table_init(
528528
// Implementation of `elem.drop`.
529529
fn elem_drop(store: &mut dyn VMStore, instance: InstanceId, elem_index: u32) -> Result<()> {
530530
let elem_index = ElemIndex::from_u32(elem_index);
531-
let (gc_store, instance) = store.optional_gc_store_and_instance_mut(instance);
532-
instance.elem_drop(gc_store, elem_index)?;
531+
store.instance_mut(instance).elem_drop(elem_index)?;
533532
Ok(())
534533
}
535534

0 commit comments

Comments
 (0)