Skip to content

Commit 99efc20

Browse files
authored
Add Engine/Component::deserialize_raw (#10321)
* Add Engine/Component::deserialize_raw For targets without virtual memory support, the only mechanism for load code previously was to copy bytes from a provided slice into a separate, owned, allocated buffer containing a copy of the serialized module contents. This is expensive for constrained targets and prohibits embedded runtimes like doing things like running code directly out of memory-mapped devices such as NOR flash. The tradeoff for directly using the externally owned memory is that the caller must ensure that the code memory will not be written to for the lifetime of the CodeMemory. Loading code from externally owned memory is supported for all configurations but is expected to fail to deserialize on platforms that suport virtual memory and are attempting to load modules or components that require that the memory be made executable (native code rather than pulley). #10245 * Fix tests for s390x Previously, there was a bad assumption that pulley32/64 were the only targets but there's also the "be" variants. Use the `pulley_host` helper to better get the right pulley target for the host.
1 parent 716ebce commit 99efc20

File tree

7 files changed

+224
-8
lines changed

7 files changed

+224
-8
lines changed

crates/wasmtime/src/engine.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::runtime::type_registry::TypeRegistry;
77
use crate::runtime::vm::GcRuntime;
88
use crate::Config;
99
use alloc::sync::Arc;
10+
use core::ptr::NonNull;
1011
#[cfg(target_has_atomic = "64")]
1112
use core::sync::atomic::{AtomicU64, Ordering};
1213
#[cfg(any(feature = "cranelift", feature = "winch"))]
@@ -783,6 +784,29 @@ impl Engine {
783784
)
784785
}
785786

787+
/// Loads a `CodeMemory` from the specified memory region without copying
788+
///
789+
/// The `expected` marker here is whether the bytes are expected to be
790+
/// a precompiled module or a component. The `memory` provided is expected
791+
/// to be a serialized module (.cwasm) generated by `[Module::serialize]`
792+
/// or [`Engine::precompile_module] or their `Component` counterparts
793+
/// [`Component::serialize`] or `[Engine::precompile_component]`.
794+
///
795+
/// The memory provided is guaranteed to only be immutably by the runtime.
796+
///
797+
/// # Safety
798+
///
799+
/// As there is no copy here, the runtime will be making direct readonly use
800+
/// of the provided memory. As such, outside writes to this memory region
801+
/// will result in undefined and likely very undesirable behavior.
802+
pub(crate) unsafe fn load_code_raw(
803+
&self,
804+
memory: NonNull<[u8]>,
805+
expected: ObjectKind,
806+
) -> Result<Arc<crate::CodeMemory>> {
807+
self.load_code(crate::runtime::vm::MmapVec::from_raw(memory)?, expected)
808+
}
809+
786810
/// Like `load_code_bytes`, but creates a mmap from a file on disk.
787811
#[cfg(feature = "std")]
788812
pub(crate) fn load_code_file(

crates/wasmtime/src/runtime/code_memory.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,11 +324,17 @@ impl CodeMemory {
324324
// we aren't able to make it readonly, but this is just a
325325
// defense-in-depth measure and isn't required for correctness.
326326
#[cfg(has_virtual_memory)]
327-
self.mmap.make_readonly(0..self.mmap.len())?;
327+
if self.mmap.supports_virtual_memory() {
328+
self.mmap.make_readonly(0..self.mmap.len())?;
329+
}
328330

329331
// Switch the executable portion from readonly to read/execute.
330332
if self.needs_executable {
331333
if !self.custom_publish()? {
334+
if !self.mmap.supports_virtual_memory() {
335+
bail!("this target requires virtual memory to be enabled");
336+
}
337+
332338
#[cfg(has_virtual_memory)]
333339
{
334340
let text = self.text();
@@ -349,8 +355,6 @@ impl CodeMemory {
349355
// Flush any in-flight instructions from the pipeline
350356
icache_coherence::pipeline_flush_mt().expect("Failed pipeline flush");
351357
}
352-
#[cfg(not(has_virtual_memory))]
353-
bail!("this target requires virtual memory to be enabled");
354358
}
355359
}
356360

@@ -395,6 +399,10 @@ impl CodeMemory {
395399
return Ok(());
396400
}
397401

402+
if self.mmap.is_always_readonly() {
403+
bail!("Unable to apply relocations to readonly MmapVec");
404+
}
405+
398406
for (offset, libcall) in self.relocations.iter() {
399407
let offset = self.text.start + offset;
400408
let libcall = match libcall {
@@ -413,6 +421,7 @@ impl CodeMemory {
413421
#[cfg(not(target_arch = "x86_64"))]
414422
obj::LibCall::X86Pshufb => unreachable!(),
415423
};
424+
416425
self.mmap
417426
.as_mut_slice()
418427
.as_mut_ptr()

crates/wasmtime/src/runtime/component/component.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,23 @@ impl Component {
214214
Component::from_parts(engine, code, None)
215215
}
216216

217+
/// Same as [`Module::deserialize_raw`], but for components.
218+
///
219+
/// See [`Component::deserialize`] for additional information; this method
220+
/// works identically except that it will not create a copy of the provided
221+
/// memory but will use it directly.
222+
///
223+
/// # Unsafety
224+
///
225+
/// All of the safety notes from [`Component::deserialize`] apply here as well
226+
/// with the additional constraint that the code memory provide by `memory`
227+
/// lives for as long as the module and is nevery externally modified for
228+
/// the lifetime of the deserialized module.
229+
pub unsafe fn deserialize_raw(engine: &Engine, memory: NonNull<[u8]>) -> Result<Component> {
230+
let code = engine.load_code_raw(memory, ObjectKind::Component)?;
231+
Component::from_parts(engine, code, None)
232+
}
233+
217234
/// Same as [`Module::deserialize_file`], but for components.
218235
///
219236
/// Note that the file referenced here must contain contents previously

crates/wasmtime/src/runtime/module.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,24 @@ impl Module {
402402
Module::from_parts(engine, code, None)
403403
}
404404

405+
/// In-place deserialization of an in-memory compiled module previously
406+
/// created with [`Module::serialize`] or [`Engine::precompile_module`].
407+
///
408+
/// See [`Self::deserialize`] for additional information; this method
409+
/// works identically except that it will not create a copy of the provided
410+
/// memory but will use it directly.
411+
///
412+
/// # Unsafety
413+
///
414+
/// All of the safety notes from [`Self::deserialize`] apply here as well
415+
/// with the additional constraint that the code memory provide by `memory`
416+
/// lives for as long as the module and is nevery externally modified for
417+
/// the lifetime of the deserialized module.
418+
pub unsafe fn deserialize_raw(engine: &Engine, memory: NonNull<[u8]>) -> Result<Module> {
419+
let code = engine.load_code_raw(memory, ObjectKind::Module)?;
420+
Module::from_parts(engine, code, None)
421+
}
422+
405423
/// Same as [`deserialize`], except that the contents of `path` are read to
406424
/// deserialize into a [`Module`].
407425
///

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

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
use crate::prelude::*;
2-
#[cfg(not(has_virtual_memory))]
32
use crate::runtime::vm::send_sync_ptr::SendSyncPtr;
43
#[cfg(has_virtual_memory)]
54
use crate::runtime::vm::{mmap::UnalignedLength, Mmap};
65
#[cfg(not(has_virtual_memory))]
76
use alloc::alloc::Layout;
87
use alloc::sync::Arc;
98
use core::ops::{Deref, Range};
10-
#[cfg(not(has_virtual_memory))]
119
use core::ptr::NonNull;
1210
#[cfg(feature = "std")]
1311
use std::fs::File;
@@ -40,6 +38,8 @@ pub enum MmapVec {
4038
layout: Layout,
4139
},
4240
#[doc(hidden)]
41+
ExternallyOwned { memory: SendSyncPtr<[u8]> },
42+
#[doc(hidden)]
4343
#[cfg(has_virtual_memory)]
4444
Mmap {
4545
mmap: Mmap<UnalignedLength>,
@@ -74,6 +74,11 @@ impl MmapVec {
7474
MmapVec::Alloc { base, layout }
7575
}
7676

77+
fn new_externally_owned(memory: NonNull<[u8]>) -> MmapVec {
78+
let memory = SendSyncPtr::new(memory);
79+
MmapVec::ExternallyOwned { memory }
80+
}
81+
7782
/// Creates a new zero-initialized `MmapVec` with the given `size`
7883
/// and `alignment`.
7984
///
@@ -101,6 +106,25 @@ impl MmapVec {
101106
MmapVec::from_slice_with_alignment(slice, 1)
102107
}
103108

109+
/// Creates a new `MmapVec` from an existing memory region
110+
///
111+
/// This method avoids the copy performed by [`Self::from_slice`] by
112+
/// directly using the memory region provided. This must be done with
113+
/// extreme care, however, as any concurrent modification of the provided
114+
/// memory will cause undefined and likely very, very bad things to
115+
/// happen.
116+
///
117+
/// The memory provided is guaranteed to not be mutated by the runtime.
118+
///
119+
/// # Safety
120+
///
121+
/// As there is no copy here, the runtime will be making direct readonly use
122+
/// of the provided memory. As such, outside writes to this memory region
123+
/// will result in undefined and likely very undesirable behavior.
124+
pub unsafe fn from_raw(memory: NonNull<[u8]>) -> Result<MmapVec> {
125+
Ok(MmapVec::new_externally_owned(memory))
126+
}
127+
104128
/// Creates a new `MmapVec` from the contents of an existing
105129
/// `slice`, with a minimum alignment.
106130
///
@@ -110,7 +134,7 @@ impl MmapVec {
110134
///
111135
/// A new `MmapVec` is allocated to hold the contents of `slice` and then
112136
/// `slice` is copied into the new mmap. It's recommended to avoid this
113-
/// method if possible to avoid the need to copy data around. pub
137+
/// method if possible to avoid the need to copy data around.
114138
pub fn from_slice_with_alignment(slice: &[u8], align: usize) -> Result<MmapVec> {
115139
let mut result = MmapVec::with_capacity_and_alignment(slice.len(), align)?;
116140
// SAFETY: The mmap hasn't been made readonly yet so this should be
@@ -121,6 +145,37 @@ impl MmapVec {
121145
Ok(result)
122146
}
123147

148+
/// Return `true` if the `MmapVec` suport virtual memory operations
149+
///
150+
/// In some cases, such as when using externally owned memory, the underlying
151+
/// platform may support virtual memory but it still may not be legal
152+
/// to perform virtual memory operations on this memory.
153+
pub fn supports_virtual_memory(&self) -> bool {
154+
match self {
155+
#[cfg(has_virtual_memory)]
156+
MmapVec::Mmap { .. } => true,
157+
MmapVec::ExternallyOwned { .. } => false,
158+
#[cfg(not(has_virtual_memory))]
159+
MmapVec::Alloc { .. } => false,
160+
}
161+
}
162+
163+
/// Return true if this `MmapVec` is always readonly
164+
///
165+
/// Attempting to get access to mutate readonly memory via
166+
/// [`MmapVec::as_mut`] will result in a panic. Note that this method
167+
/// does not change with runtime changes to portions of the code memory
168+
/// via `MmapVec::make_readonly` for platforms with virtual memory.
169+
pub fn is_always_readonly(&self) -> bool {
170+
match self {
171+
#[cfg(has_virtual_memory)]
172+
MmapVec::Mmap { .. } => false,
173+
MmapVec::ExternallyOwned { .. } => true,
174+
#[cfg(not(has_virtual_memory))]
175+
MmapVec::Alloc { .. } => false,
176+
}
177+
}
178+
124179
/// Creates a new `MmapVec` which is the given `File` mmap'd into memory.
125180
///
126181
/// This function will determine the file's size and map the full contents
@@ -148,6 +203,9 @@ impl MmapVec {
148203
) -> Result<()> {
149204
let (mmap, len) = match self {
150205
MmapVec::Mmap { mmap, len } => (mmap, *len),
206+
MmapVec::ExternallyOwned { .. } => {
207+
bail!("Unable to make externally owned memory executable");
208+
}
151209
};
152210
assert!(range.start <= range.end);
153211
assert!(range.end <= len);
@@ -159,6 +217,9 @@ impl MmapVec {
159217
pub unsafe fn make_readonly(&self, range: Range<usize>) -> Result<()> {
160218
let (mmap, len) = match self {
161219
MmapVec::Mmap { mmap, len } => (mmap, *len),
220+
MmapVec::ExternallyOwned { .. } => {
221+
bail!("Unable to make externally owned memory readonly");
222+
}
162223
};
163224
assert!(range.start <= range.end);
164225
assert!(range.end <= len);
@@ -171,6 +232,7 @@ impl MmapVec {
171232
match self {
172233
#[cfg(not(has_virtual_memory))]
173234
MmapVec::Alloc { .. } => None,
235+
MmapVec::ExternallyOwned { .. } => None,
174236
#[cfg(has_virtual_memory)]
175237
MmapVec::Mmap { mmap, .. } => mmap.original_file(),
176238
}
@@ -189,13 +251,21 @@ impl MmapVec {
189251
/// # Unsafety
190252
///
191253
/// This method is only safe if `make_readonly` hasn't been called yet to
192-
/// ensure that the memory is indeed writable
254+
/// ensure that the memory is indeed writable. For a MmapVec created from
255+
/// a raw pointer using this memory as mutable is only safe if there are
256+
/// no outside reads or writes to the memory region.
257+
///
258+
/// Externally owned code is implicitly considered to be readonly and this
259+
/// code will panic if called on externally owned memory.
193260
pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
194261
match self {
195262
#[cfg(not(has_virtual_memory))]
196263
MmapVec::Alloc { base, layout } => {
197264
core::slice::from_raw_parts_mut(base.as_mut(), layout.size())
198265
}
266+
MmapVec::ExternallyOwned { .. } => {
267+
panic!("Mutating externally owned memory is prohibited");
268+
}
199269
#[cfg(has_virtual_memory)]
200270
MmapVec::Mmap { mmap, len } => mmap.slice_mut(0..*len),
201271
}
@@ -212,6 +282,7 @@ impl Deref for MmapVec {
212282
MmapVec::Alloc { base, layout } => unsafe {
213283
core::slice::from_raw_parts(base.as_ptr(), layout.size())
214284
},
285+
MmapVec::ExternallyOwned { memory } => unsafe { memory.as_ref() },
215286
#[cfg(has_virtual_memory)]
216287
MmapVec::Mmap { mmap, len } => {
217288
// SAFETY: all bytes for this mmap, which is owned by
@@ -229,6 +300,9 @@ impl Drop for MmapVec {
229300
MmapVec::Alloc { base, layout, .. } => unsafe {
230301
alloc::alloc::dealloc(base.as_mut(), layout.clone());
231302
},
303+
MmapVec::ExternallyOwned { .. } => {
304+
// Memory is allocated externally, nothing to do
305+
}
232306
#[cfg(has_virtual_memory)]
233307
MmapVec::Mmap { .. } => {
234308
// Drop impl on the `mmap` takes care of this case.

examples/min-platform/embedding/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ extern crate alloc;
55

66
use alloc::string::ToString;
77
use anyhow::Result;
8+
use core::ptr;
89
use wasmtime::{Engine, Instance, Linker, Module, Store};
910

1011
mod allocator;
@@ -94,7 +95,11 @@ fn simple_host_fn(module: &[u8]) -> Result<()> {
9495
}
9596

9697
fn deserialize(engine: &Engine, module: &[u8]) -> Result<Option<Module>> {
97-
match unsafe { Module::deserialize(engine, module) } {
98+
// NOTE: deserialize_raw avoids creating a copy of the module code. See the
99+
// safety notes before using in your embedding.
100+
let memory_ptr = ptr::slice_from_raw_parts(module.as_ptr(), module.len());
101+
let module_memory = ptr::NonNull::new(memory_ptr.cast_mut()).unwrap();
102+
match unsafe { Module::deserialize_raw(engine, module_memory) } {
98103
Ok(module) => Ok(Some(module)),
99104
Err(e) => {
100105
// Currently if custom signals/virtual memory are disabled then this

0 commit comments

Comments
 (0)