Skip to content

Commit ab76f64

Browse files
authored
Make vm::Instance::get_defined_memory safe (#11211)
This is a small step forward to making `vm::Instance` safe internally. Notably the `get_defined_memory` helper now returns a safe mutable reference instead of a raw pointer. This is then additionally coupled with the canonicalization of always working with memories as "instance plus defined memory index" instead of "instance plus memory index". This enables removing some unsafe `VMContext` to `Instance` conversion as well. This change, however, required updating libcalls to special-case when an imported memory is operated on to load the vmcontext/index in the libcall itself. This change notably does not update the `memory_init` libcall just yet due to the fact that the `DataIndex` is relative to the owning instance even if the memory is owned by a different instance (e.g. it's imported). Otherwise this chips away at some of the `unsafe` related to memory/table management in `vm::Instance`.
1 parent 8a23cc7 commit ab76f64

File tree

11 files changed

+210
-150
lines changed

11 files changed

+210
-150
lines changed

crates/cranelift/src/func_environ.rs

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -282,21 +282,10 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
282282
}
283283

284284
#[cfg(feature = "threads")]
285-
fn get_memory_atomic_wait(
286-
&mut self,
287-
func: &mut Function,
288-
memory_index: MemoryIndex,
289-
ty: ir::Type,
290-
) -> (ir::FuncRef, usize) {
285+
fn get_memory_atomic_wait(&mut self, func: &mut Function, ty: ir::Type) -> ir::FuncRef {
291286
match ty {
292-
I32 => (
293-
self.builtin_functions.memory_atomic_wait32(func),
294-
memory_index.index(),
295-
),
296-
I64 => (
297-
self.builtin_functions.memory_atomic_wait64(func),
298-
memory_index.index(),
299-
),
287+
I32 => self.builtin_functions.memory_atomic_wait32(func),
288+
I64 => self.builtin_functions.memory_atomic_wait64(func),
300289
x => panic!("get_memory_atomic_wait unsupported type: {x:?}"),
301290
}
302291
}
@@ -2744,6 +2733,45 @@ impl FuncEnvironment<'_> {
27442733
Ok(())
27452734
}
27462735

2736+
/// Returns two `ir::Value`s, the first of which is the vmctx for the memory
2737+
/// `index` and the second of which is the `DefinedMemoryIndex` for `index`.
2738+
///
2739+
/// Handles internally whether `index` is an imported memory or not.
2740+
fn memory_vmctx_and_defined_index(
2741+
&mut self,
2742+
pos: &mut FuncCursor,
2743+
index: MemoryIndex,
2744+
) -> (ir::Value, ir::Value) {
2745+
let cur_vmctx = self.vmctx_val(pos);
2746+
match self.module.defined_memory_index(index) {
2747+
// This is a defined memory, so the vmctx is our own and the defined
2748+
// index is `index` here.
2749+
Some(index) => (cur_vmctx, pos.ins().iconst(I32, i64::from(index.as_u32()))),
2750+
2751+
// This is an imported memory, so load the vmctx/defined index from
2752+
// the import definition itself.
2753+
None => {
2754+
let vmimport = self.offsets.vmctx_vmmemory_import(index);
2755+
2756+
let vmctx = pos.ins().load(
2757+
self.isa.pointer_type(),
2758+
ir::MemFlags::trusted(),
2759+
cur_vmctx,
2760+
i32::try_from(vmimport + u32::from(self.offsets.vmmemory_import_vmctx()))
2761+
.unwrap(),
2762+
);
2763+
let index = pos.ins().load(
2764+
ir::types::I32,
2765+
ir::MemFlags::trusted(),
2766+
cur_vmctx,
2767+
i32::try_from(vmimport + u32::from(self.offsets.vmmemory_import_index()))
2768+
.unwrap(),
2769+
);
2770+
(vmctx, index)
2771+
}
2772+
}
2773+
}
2774+
27472775
pub fn translate_memory_grow(
27482776
&mut self,
27492777
builder: &mut FunctionBuilder<'_>,
@@ -2753,14 +2781,15 @@ impl FuncEnvironment<'_> {
27532781
) -> WasmResult<ir::Value> {
27542782
let mut pos = builder.cursor();
27552783
let memory_grow = self.builtin_functions.memory_grow(&mut pos.func);
2756-
let index_arg = index.index();
27572784

2758-
let memory_index = pos.ins().iconst(I32, index_arg as i64);
2759-
let vmctx = self.vmctx_val(&mut pos);
2785+
let (memory_vmctx, defined_memory_index) =
2786+
self.memory_vmctx_and_defined_index(&mut pos, index);
27602787

27612788
let index_type = self.memory(index).idx_type;
27622789
let val = self.cast_index_to_i64(&mut pos, val, index_type);
2763-
let call_inst = pos.ins().call(memory_grow, &[vmctx, val, memory_index]);
2790+
let call_inst = pos
2791+
.ins()
2792+
.call(memory_grow, &[memory_vmctx, val, defined_memory_index]);
27642793
let result = *pos.func.dfg.inst_results(call_inst).first().unwrap();
27652794
let single_byte_pages = match self.memory(index).page_size_log2 {
27662795
16 => false,
@@ -2910,12 +2939,13 @@ impl FuncEnvironment<'_> {
29102939
let memory_fill = self.builtin_functions.memory_fill(&mut pos.func);
29112940
let dst = self.cast_index_to_i64(&mut pos, dst, self.memory(memory_index).idx_type);
29122941
let len = self.cast_index_to_i64(&mut pos, len, self.memory(memory_index).idx_type);
2913-
let memory_index_arg = pos.ins().iconst(I32, i64::from(memory_index.as_u32()));
2914-
2915-
let vmctx = self.vmctx_val(&mut pos);
2942+
let (memory_vmctx, defined_memory_index) =
2943+
self.memory_vmctx_and_defined_index(&mut pos, memory_index);
29162944

2917-
pos.ins()
2918-
.call(memory_fill, &[vmctx, memory_index_arg, dst, val, len]);
2945+
pos.ins().call(
2946+
memory_fill,
2947+
&[memory_vmctx, defined_memory_index, dst, val, len],
2948+
);
29192949

29202950
Ok(())
29212951
}
@@ -3056,16 +3086,14 @@ impl FuncEnvironment<'_> {
30563086
let mut pos = builder.cursor();
30573087
let addr = self.cast_index_to_i64(&mut pos, addr, self.memory(memory_index).idx_type);
30583088
let implied_ty = pos.func.dfg.value_type(expected);
3059-
let (wait_func, memory_index) =
3060-
self.get_memory_atomic_wait(&mut pos.func, memory_index, implied_ty);
3061-
3062-
let memory_index_arg = pos.ins().iconst(I32, memory_index as i64);
3089+
let wait_func = self.get_memory_atomic_wait(&mut pos.func, implied_ty);
30633090

3064-
let vmctx = self.vmctx_val(&mut pos);
3091+
let (memory_vmctx, defined_memory_index) =
3092+
self.memory_vmctx_and_defined_index(&mut pos, memory_index);
30653093

30663094
let call_inst = pos.ins().call(
30673095
wait_func,
3068-
&[vmctx, memory_index_arg, addr, expected, timeout],
3096+
&[memory_vmctx, defined_memory_index, addr, expected, timeout],
30693097
);
30703098
let ret = pos.func.dfg.inst_results(call_inst)[0];
30713099
Ok(builder.ins().ireduce(ir::types::I32, ret))
@@ -3093,11 +3121,12 @@ impl FuncEnvironment<'_> {
30933121
let addr = self.cast_index_to_i64(&mut pos, addr, self.memory(memory_index).idx_type);
30943122
let atomic_notify = self.builtin_functions.memory_atomic_notify(&mut pos.func);
30953123

3096-
let memory_index_arg = pos.ins().iconst(I32, memory_index.index() as i64);
3097-
let vmctx = self.vmctx_val(&mut pos);
3098-
let call_inst = pos
3099-
.ins()
3100-
.call(atomic_notify, &[vmctx, memory_index_arg, addr, count]);
3124+
let (memory_vmctx, defined_memory_index) =
3125+
self.memory_vmctx_and_defined_index(&mut pos, memory_index);
3126+
let call_inst = pos.ins().call(
3127+
atomic_notify,
3128+
&[memory_vmctx, defined_memory_index, addr, count],
3129+
);
31013130
let ret = pos.func.dfg.inst_results(call_inst)[0];
31023131
Ok(builder.ins().ireduce(ir::types::I32, ret))
31033132
}

crates/environ/src/vmoffsets.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,18 @@ impl<P: PtrSize> VMOffsets<P> {
747747
0 * self.pointer_size()
748748
}
749749

750+
/// The offset of the `vmctx` field.
751+
#[inline]
752+
pub fn vmmemory_import_vmctx(&self) -> u8 {
753+
1 * self.pointer_size()
754+
}
755+
756+
/// The offset of the `index` field.
757+
#[inline]
758+
pub fn vmmemory_import_index(&self) -> u8 {
759+
2 * self.pointer_size()
760+
}
761+
750762
/// Return the size of `VMMemoryImport`.
751763
#[inline]
752764
pub fn size_of_vmmemory_import(&self) -> u8 {

crates/wasmtime/src/runtime/memory.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,9 @@ impl Memory {
603603
/// ```
604604
pub fn grow(&self, mut store: impl AsContextMut, delta: u64) -> Result<u64> {
605605
let store = store.as_context_mut().0;
606-
let mem = self.wasmtime_memory(store);
606+
// FIXME(#11179) shouldn't use a raw pointer to work around the borrow
607+
// checker here.
608+
let mem: *mut _ = self.wasmtime_memory(store);
607609
unsafe {
608610
match (*mem).grow(delta, Some(store))? {
609611
Some(size) => {
@@ -638,7 +640,10 @@ impl Memory {
638640
store.on_fiber(|store| self.grow(store, delta)).await?
639641
}
640642

641-
fn wasmtime_memory(&self, store: &mut StoreOpaque) -> *mut crate::runtime::vm::Memory {
643+
fn wasmtime_memory<'a>(
644+
&self,
645+
store: &'a mut StoreOpaque,
646+
) -> &'a mut crate::runtime::vm::Memory {
642647
self.instance.get_mut(store).get_defined_memory(self.index)
643648
}
644649

@@ -1040,15 +1045,11 @@ impl SharedMemory {
10401045
)
10411046
)]
10421047
crate::runtime::vm::Instance::from_vmctx(wasmtime_export.vmctx, |handle| {
1043-
let memory_index = handle.env_module().memory_index(wasmtime_export.index);
1044-
let page_size = handle.memory_page_size(memory_index);
1045-
debug_assert!(page_size.is_power_of_two());
1046-
let page_size_log2 = u8::try_from(page_size.ilog2()).unwrap();
1048+
let module = handle.env_module();
1049+
let memory_index = module.memory_index(wasmtime_export.index);
1050+
let page_size_log2 = module.memories[memory_index].page_size_log2;
10471051

1048-
let memory = handle
1049-
.get_defined_memory(wasmtime_export.index)
1050-
.as_mut()
1051-
.unwrap();
1052+
let memory = handle.get_defined_memory(wasmtime_export.index);
10521053
match memory.as_shared_memory() {
10531054
Some(mem) => Self {
10541055
vm: mem.clone(),

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

Lines changed: 4 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -423,22 +423,6 @@ impl Instance {
423423
}
424424
}
425425

426-
/// Get a locally defined or imported memory.
427-
#[cfg(feature = "threads")]
428-
pub(crate) fn get_runtime_memory(self: Pin<&mut Self>, index: MemoryIndex) -> &mut Memory {
429-
if let Some(defined_index) = self.env_module().defined_memory_index(index) {
430-
unsafe { &mut *self.get_defined_memory(defined_index) }
431-
} else {
432-
let import = self.imported_memory(index);
433-
unsafe {
434-
let ptr = Instance::from_vmctx(import.vmctx.as_non_null(), |i| {
435-
i.get_defined_memory(import.index)
436-
});
437-
&mut *ptr
438-
}
439-
}
440-
}
441-
442426
/// Return the indexed `VMMemoryDefinition`, loaded from vmctx memory
443427
/// already.
444428
pub fn memory(&self, index: DefinedMemoryIndex) -> VMMemoryDefinition {
@@ -698,36 +682,12 @@ impl Instance {
698682
index
699683
}
700684

701-
/// Get the given memory's page size, in bytes.
702-
pub(crate) fn memory_page_size(&self, index: MemoryIndex) -> usize {
703-
usize::try_from(self.env_module().memories[index].page_size()).unwrap()
704-
}
705-
706685
/// Grow memory by the specified amount of pages.
707686
///
708687
/// Returns `None` if memory can't be grown by the specified amount
709688
/// of pages. Returns `Some` with the old size in bytes if growth was
710689
/// successful.
711690
pub(crate) fn memory_grow(
712-
self: Pin<&mut Self>,
713-
store: &mut dyn VMStore,
714-
index: MemoryIndex,
715-
delta: u64,
716-
) -> Result<Option<usize>, Error> {
717-
match self.env_module().defined_memory_index(index) {
718-
Some(idx) => self.defined_memory_grow(store, idx, delta),
719-
None => {
720-
let import = self.imported_memory(index);
721-
unsafe {
722-
Instance::from_vmctx(import.vmctx.as_non_null(), |i| {
723-
i.defined_memory_grow(store, import.index, delta)
724-
})
725-
}
726-
}
727-
}
728-
}
729-
730-
fn defined_memory_grow(
731691
mut self: Pin<&mut Self>,
732692
store: &mut dyn VMStore,
733693
idx: DefinedMemoryIndex,
@@ -1064,10 +1024,8 @@ impl Instance {
10641024
}
10651025

10661026
/// Get a locally-defined memory.
1067-
pub fn get_defined_memory(self: Pin<&mut Self>, index: DefinedMemoryIndex) -> *mut Memory {
1068-
// SAFETY: the `unsafe` here is projecting from `*mut (A, B)` to
1069-
// `*mut A`, which should be a safe operation to do.
1070-
unsafe { &raw mut (*self.memories_mut().get_raw_mut(index).unwrap()).1 }
1027+
pub fn get_defined_memory(self: Pin<&mut Self>, index: DefinedMemoryIndex) -> &mut Memory {
1028+
&mut self.memories_mut()[index].1
10711029
}
10721030

10731031
/// Do a `memory.copy`
@@ -1126,11 +1084,12 @@ impl Instance {
11261084
/// Returns a `Trap` error if the memory range is out of bounds.
11271085
pub(crate) fn memory_fill(
11281086
self: Pin<&mut Self>,
1129-
memory_index: MemoryIndex,
1087+
memory_index: DefinedMemoryIndex,
11301088
dst: u64,
11311089
val: u8,
11321090
len: u64,
11331091
) -> Result<(), Trap> {
1092+
let memory_index = self.env_module().memory_index(memory_index);
11341093
let memory = self.get_memory(memory_index);
11351094
let dst = self.validate_inbounds(memory.current_length(), dst, len)?;
11361095
let len = usize::try_from(len).unwrap();

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ use core::pin::Pin;
6969
use core::ptr::NonNull;
7070
#[cfg(feature = "threads")]
7171
use core::time::Duration;
72-
use wasmtime_environ::{DataIndex, ElemIndex, FuncIndex, MemoryIndex, TableIndex, Trap};
72+
use wasmtime_environ::{
73+
DataIndex, DefinedMemoryIndex, ElemIndex, FuncIndex, MemoryIndex, TableIndex, Trap,
74+
};
7375
#[cfg(feature = "wmemcheck")]
7476
use wasmtime_wmemcheck::AccessError::{
7577
DoubleMalloc, InvalidFree, InvalidRead, InvalidWrite, OutOfBounds,
@@ -168,13 +170,14 @@ fn memory_grow(
168170
delta: u64,
169171
memory_index: u32,
170172
) -> Result<Option<AllocationSize>, TrapReason> {
171-
let memory_index = MemoryIndex::from_u32(memory_index);
173+
let memory_index = DefinedMemoryIndex::from_u32(memory_index);
174+
let module = instance.env_module();
175+
let page_size_log2 = module.memories[module.memory_index(memory_index)].page_size_log2;
176+
172177
let result = instance
173178
.as_mut()
174179
.memory_grow(store, memory_index, delta)?
175-
.map(|size_in_bytes| {
176-
AllocationSize(size_in_bytes / instance.memory_page_size(memory_index))
177-
});
180+
.map(|size_in_bytes| AllocationSize(size_in_bytes >> page_size_log2));
178181

179182
Ok(result)
180183
}
@@ -437,7 +440,7 @@ fn memory_fill(
437440
val: u32,
438441
len: u64,
439442
) -> Result<(), Trap> {
440-
let memory_index = MemoryIndex::from_u32(memory_index);
443+
let memory_index = DefinedMemoryIndex::from_u32(memory_index);
441444
#[expect(clippy::cast_possible_truncation, reason = "known to truncate here")]
442445
instance.memory_fill(memory_index, dst, val as u8, len)
443446
}
@@ -1056,9 +1059,9 @@ fn memory_atomic_notify(
10561059
addr_index: u64,
10571060
count: u32,
10581061
) -> Result<u32, Trap> {
1059-
let memory = MemoryIndex::from_u32(memory_index);
1062+
let memory = DefinedMemoryIndex::from_u32(memory_index);
10601063
instance
1061-
.get_runtime_memory(memory)
1064+
.get_defined_memory(memory)
10621065
.atomic_notify(addr_index, count)
10631066
}
10641067

@@ -1073,9 +1076,9 @@ fn memory_atomic_wait32(
10731076
timeout: u64,
10741077
) -> Result<u32, Trap> {
10751078
let timeout = (timeout as i64 >= 0).then(|| Duration::from_nanos(timeout));
1076-
let memory = MemoryIndex::from_u32(memory_index);
1079+
let memory = DefinedMemoryIndex::from_u32(memory_index);
10771080
Ok(instance
1078-
.get_runtime_memory(memory)
1081+
.get_defined_memory(memory)
10791082
.atomic_wait32(addr_index, expected, timeout)? as u32)
10801083
}
10811084

@@ -1090,9 +1093,9 @@ fn memory_atomic_wait64(
10901093
timeout: u64,
10911094
) -> Result<u32, Trap> {
10921095
let timeout = (timeout as i64 >= 0).then(|| Duration::from_nanos(timeout));
1093-
let memory = MemoryIndex::from_u32(memory_index);
1096+
let memory = DefinedMemoryIndex::from_u32(memory_index);
10941097
Ok(instance
1095-
.get_runtime_memory(memory)
1098+
.get_defined_memory(memory)
10961099
.atomic_wait64(addr_index, expected, timeout)? as u32)
10971100
}
10981101

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,14 @@ mod test_vmmemory_import {
234234
offset_of!(VMMemoryImport, from),
235235
usize::from(offsets.vmmemory_import_from())
236236
);
237+
assert_eq!(
238+
offset_of!(VMMemoryImport, vmctx),
239+
usize::from(offsets.vmmemory_import_vmctx())
240+
);
241+
assert_eq!(
242+
offset_of!(VMMemoryImport, index),
243+
usize::from(offsets.vmmemory_import_index())
244+
);
237245
}
238246
}
239247

0 commit comments

Comments
 (0)