Skip to main content

wasmtime/runtime/vm/
cow.rs

1//! Copy-on-write initialization support: creation of backing images for
2//! modules, and logic to support mapping these backing images into memory.
3
4use super::sys::DecommitBehavior;
5use crate::Engine;
6use crate::prelude::*;
7use crate::runtime::vm::sys::vm::{self, MemoryImageSource, PageMap, reset_with_pagemap};
8use crate::runtime::vm::{
9    HostAlignedByteCount, MmapOffset, ModuleMemoryImageSource, host_page_size,
10};
11use alloc::sync::Arc;
12use core::fmt;
13use core::ops::Range;
14use wasmtime_environ::prelude::TryPrimaryMap;
15use wasmtime_environ::{DefinedMemoryIndex, MemoryInitialization, MemoryTunables, Module};
16
17/// Backing images for memories in a module.
18///
19/// This is meant to be built once, when a module is first loaded/constructed,
20/// and then used many times for instantiation.
21pub struct ModuleMemoryImages {
22    memories: TryPrimaryMap<DefinedMemoryIndex, Option<Arc<MemoryImage>>>,
23}
24
25impl ModuleMemoryImages {
26    /// Get the MemoryImage for a given memory.
27    pub fn get_memory_image(&self, defined_index: DefinedMemoryIndex) -> Option<&Arc<MemoryImage>> {
28        self.memories[defined_index].as_ref()
29    }
30}
31
32/// One backing image for one memory.
33pub struct MemoryImage {
34    /// The platform-specific source of this image.
35    ///
36    /// This might be a mapped `*.cwasm` file or on Unix it could also be a
37    /// `Memfd` as an anonymous file in memory on Linux. In either case this is
38    /// used as the backing-source for the CoW image.
39    source: MemoryImageSource,
40
41    /// Length of image, in bytes.
42    ///
43    /// Note that initial memory size may be larger; leading and trailing zeroes
44    /// are truncated (handled by backing fd).
45    ///
46    /// Must be a multiple of the system page size.
47    len: HostAlignedByteCount,
48
49    /// Image starts this many bytes into `source`.
50    ///
51    /// This is 0 for anonymous-backed memfd files and is the offset of the
52    /// data section in a `*.cwasm` file for `*.cwasm`-backed images.
53    ///
54    /// Must be a multiple of the system page size.
55    ///
56    /// ## Notes
57    ///
58    /// This currently isn't a `HostAlignedByteCount` because that's a usize and
59    /// this, being a file offset, is a u64.
60    source_offset: u64,
61
62    /// Image starts this many bytes into heap space.
63    ///
64    /// Must be a multiple of the system page size.
65    linear_memory_offset: HostAlignedByteCount,
66
67    /// The original source of data that this image is derived from.
68    module_source: Arc<dyn ModuleMemoryImageSource>,
69
70    /// The offset, within `module_source.wasm_data()`, that this image starts
71    /// at.
72    module_source_offset: usize,
73}
74
75impl MemoryImage {
76    fn new(
77        engine: &Engine,
78        page_size: u32,
79        linear_memory_offset: HostAlignedByteCount,
80        module_source: &Arc<impl ModuleMemoryImageSource>,
81        data_range: Range<usize>,
82    ) -> Result<Option<MemoryImage>> {
83        let assert_page_aligned = |val: usize| {
84            assert_eq!(val % (page_size as usize), 0);
85        };
86        // Sanity-check that various parameters are page-aligned.
87        let len =
88            HostAlignedByteCount::new(data_range.len()).expect("memory image data is page-aligned");
89
90        // If a backing `mmap` is present then `data` should be a sub-slice of
91        // the `mmap`. The sanity-checks here double-check that. Additionally
92        // compilation should have ensured that the `data` section is
93        // page-aligned within `mmap`, so that's also all double-checked here.
94        //
95        // Finally if the `mmap` itself comes from a backing file on disk, such
96        // as a `*.cwasm` file, then that's a valid source of data for the
97        // memory image so we simply return referencing that.
98        //
99        // Note that this path is platform-agnostic in the sense of all
100        // platforms we support support memory mapping copy-on-write data from
101        // files, but for now this is still a Linux-specific region of Wasmtime.
102        // Some work will be needed to get this file compiling for macOS and
103        // Windows.
104        let data = &module_source.wasm_data()[data_range.clone()];
105        if !engine.config().force_memory_init_memfd {
106            if let Some(mmap) = module_source.mmap() {
107                let start = mmap.as_ptr() as usize;
108                let end = start + mmap.len();
109                let data_start = data.as_ptr() as usize;
110                let data_end = data_start + data.len();
111                assert!(start <= data_start && data_end <= end);
112                assert_page_aligned(start);
113                assert_page_aligned(data_start);
114                assert_page_aligned(data_end);
115
116                #[cfg(feature = "std")]
117                if let Some(file) = mmap.original_file() {
118                    if let Some(source) = MemoryImageSource::from_file(file) {
119                        return Ok(Some(MemoryImage {
120                            source,
121                            source_offset: u64::try_from(data_start - start).unwrap(),
122                            linear_memory_offset,
123                            len,
124                            module_source: module_source.clone(),
125                            module_source_offset: data_range.start,
126                        }));
127                    }
128                }
129            }
130        }
131
132        // If `mmap` doesn't come from a file then platform-specific mechanisms
133        // may be used to place the data in a form that's amenable to an mmap.
134        if let Some(source) = MemoryImageSource::from_data(data)? {
135            return Ok(Some(MemoryImage {
136                source,
137                source_offset: 0,
138                linear_memory_offset,
139                len,
140                module_source: module_source.clone(),
141                module_source_offset: data_range.start,
142            }));
143        }
144
145        Ok(None)
146    }
147
148    unsafe fn map_at(&self, mmap_base: &MmapOffset) -> Result<()> {
149        unsafe {
150            mmap_base.map_image_at(
151                &self.source,
152                self.source_offset,
153                self.linear_memory_offset,
154                self.len,
155            )
156        }
157    }
158
159    unsafe fn remap_as_zeros_at(&self, base: *mut u8) -> Result<()> {
160        unsafe {
161            self.source.remap_as_zeros_at(
162                base.add(self.linear_memory_offset.byte_count()),
163                self.len.byte_count(),
164            )?;
165        }
166        Ok(())
167    }
168}
169
170impl ModuleMemoryImages {
171    /// Create a new `ModuleMemoryImages` for the given module. This can be
172    /// passed in as part of a `InstanceAllocationRequest` to speed up
173    /// instantiation and execution by using copy-on-write-backed memories.
174    pub fn new(
175        engine: &Engine,
176        module: &Module,
177        source: &Arc<impl ModuleMemoryImageSource>,
178    ) -> Result<Option<ModuleMemoryImages>> {
179        let map = match &module.memory_initialization {
180            MemoryInitialization::Static { map } => map,
181            _ => return Ok(None),
182        };
183        let mut memories = TryPrimaryMap::with_capacity(map.len())?;
184        let page_size = crate::runtime::vm::host_page_size();
185        let page_size = u32::try_from(page_size).unwrap();
186        for (memory_index, init) in map {
187            // mmap-based-initialization only works for defined memories with a
188            // known starting point of all zeros, so bail out if the mmeory is
189            // imported.
190            let defined_memory = match module.defined_memory_index(memory_index) {
191                Some(idx) => idx,
192                None => return Ok(None),
193            };
194
195            // If there's no initialization for this memory known then we don't
196            // need an image for the memory so push `None` and move on.
197            let (offset, runtime_index) = match init {
198                Some(init) => init,
199                None => {
200                    memories.push(None)?;
201                    continue;
202                }
203            };
204
205            let data_range = &module.runtime_data[*runtime_index];
206            let data_range = usize::try_from(data_range.start).unwrap()
207                ..usize::try_from(data_range.end).unwrap();
208
209            if module.memories[memory_index]
210                .minimum_byte_size()
211                .map_or(false, |mem_initial_len| {
212                    *offset + u64::try_from(data_range.len()).unwrap() > mem_initial_len
213                })
214            {
215                // The image is rounded up to multiples of the host OS page
216                // size. But if Wasm is using a custom page size, the Wasm page
217                // size might be smaller than the host OS page size, and that
218                // rounding might have made the image larger than the Wasm
219                // memory's initial length. This is *probably* okay, since the
220                // rounding would have just introduced new runs of zeroes in the
221                // image, but out of an abundance of caution we don't generate
222                // CoW images in this scenario.
223                return Ok(None);
224            }
225
226            let offset_usize = match usize::try_from(*offset) {
227                Ok(offset) => offset,
228                Err(_) => return Ok(None),
229            };
230            let offset = HostAlignedByteCount::new(offset_usize)
231                .expect("memory init offset is a multiple of the host page size");
232
233            // If this creation fails then we fail creating
234            // `ModuleMemoryImages` since this memory couldn't be represented.
235            let image = match MemoryImage::new(engine, page_size, offset, source, data_range)? {
236                Some(image) => image,
237                None => return Ok(None),
238            };
239
240            let idx = memories.push(Some(try_new::<Arc<_>>(image)?))?;
241            assert_eq!(idx, defined_memory);
242        }
243
244        Ok(Some(ModuleMemoryImages { memories }))
245    }
246}
247
248/// Slot management of a copy-on-write image which can be reused for the pooling
249/// allocator.
250///
251/// This data structure manages a slot of linear memory, primarily in the
252/// pooling allocator, which optionally has a contiguous memory image in the
253/// middle of it. Pictorially this data structure manages a virtual memory
254/// region that looks like:
255///
256/// ```text
257///   +--------------------+-------------------+--------------+--------------+
258///   |   anonymous        |      optional     |   anonymous  |    PROT_NONE |
259///   |     zero           |       memory      |     zero     |     memory   |
260///   |    memory          |       image       |    memory    |              |
261///   +--------------------+-------------------+--------------+--------------+
262///   |                     <------+---------->
263///   |<-----+------------>         \
264///   |      \                   image.len
265///   |       \
266///   |  image.linear_memory_offset
267///   |
268///   \
269///  self.base is this virtual address
270///
271///    <------------------+------------------------------------------------>
272///                        \
273///                      static_size
274///
275///    <------------------+---------------------------------->
276///                        \
277///                      accessible
278/// ```
279///
280/// When a `MemoryImageSlot` is created it's told what the `static_size` and
281/// `accessible` limits are. Initially there is assumed to be no image in linear
282/// memory.
283///
284/// When `MemoryImageSlot::instantiate` is called then the method will perform
285/// a "synchronization" to take the image from its prior state to the new state
286/// for the image specified. The first instantiation for example will mmap the
287/// heap image into place. Upon reuse of a slot nothing happens except possibly
288/// shrinking `self.accessible`. When a new image is used then the old image is
289/// mapped to anonymous zero memory and then the new image is mapped in place.
290///
291/// A `MemoryImageSlot` is either `dirty` or it isn't. When a `MemoryImageSlot`
292/// is dirty then it is assumed that any memory beneath `self.accessible` could
293/// have any value. Instantiation cannot happen into a `dirty` slot, however, so
294/// the `MemoryImageSlot::clear_and_remain_ready` returns this memory back to
295/// its original state to mark `dirty = false`. This is done by resetting all
296/// anonymous memory back to zero and the image itself back to its initial
297/// contents.
298///
299/// On Linux this is achieved with the `madvise(MADV_DONTNEED)` syscall. This
300/// syscall will release the physical pages back to the OS but retain the
301/// original mappings, effectively resetting everything back to its initial
302/// state. Non-linux platforms will replace all memory below `self.accessible`
303/// with a fresh zero'd mmap, meaning that reuse is effectively not supported.
304pub struct MemoryImageSlot {
305    /// The mmap and offset within it that contains the linear memory for this
306    /// slot.
307    base: MmapOffset,
308
309    /// The maximum static memory size which `self.accessible` can grow to.
310    static_size: usize,
311
312    /// An optional image that is currently being used in this linear memory.
313    ///
314    /// This can be `None` in which case memory is originally all zeros. When
315    /// `Some` the image describes where it's located within the image.
316    image: Option<Arc<MemoryImage>>,
317
318    /// The size of the heap that is readable and writable.
319    ///
320    /// Note that this may extend beyond the actual linear memory heap size in
321    /// the case of dynamic memories in use. Memory accesses to memory below
322    /// `self.accessible` may still page fault as pages are lazily brought in
323    /// but the faults will always be resolved by the kernel.
324    ///
325    /// Also note that this is always page-aligned.
326    accessible: HostAlignedByteCount,
327
328    /// Whether this slot may have "dirty" pages (pages written by an
329    /// instantiation). Set by `instantiate()` and cleared by
330    /// `clear_and_remain_ready()`, and used in assertions to ensure
331    /// those methods are called properly.
332    ///
333    /// Invariant: if !dirty, then this memory slot contains a clean
334    /// CoW mapping of `image`, if `Some(..)`, and anonymous-zero
335    /// memory beyond the image up to `static_size`. The addresses
336    /// from offset 0 to `self.accessible` are R+W and set to zero or the
337    /// initial image content, as appropriate. Everything between
338    /// `self.accessible` and `self.static_size` is inaccessible.
339    dirty: bool,
340}
341
342impl fmt::Debug for MemoryImageSlot {
343    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344        f.debug_struct("MemoryImageSlot")
345            .field("base", &self.base)
346            .field("static_size", &self.static_size)
347            .field("accessible", &self.accessible)
348            .field("dirty", &self.dirty)
349            .finish_non_exhaustive()
350    }
351}
352
353impl MemoryImageSlot {
354    /// Create a new MemoryImageSlot. Assumes that there is an anonymous
355    /// mmap backing in the given range to start.
356    ///
357    /// The `accessible` parameter describes how much of linear memory is
358    /// already mapped as R/W with all zero-bytes. The `static_size` value is
359    /// the maximum size of this image which `accessible` cannot grow beyond,
360    /// and all memory from `accessible` from `static_size` should be mapped as
361    /// `PROT_NONE` backed by zero-bytes.
362    pub(crate) fn create(
363        base: MmapOffset,
364        accessible: HostAlignedByteCount,
365        static_size: usize,
366    ) -> Self {
367        MemoryImageSlot {
368            base,
369            static_size,
370            accessible,
371            image: None,
372            dirty: false,
373        }
374    }
375
376    pub(crate) fn set_heap_limit(&mut self, size_bytes: usize) -> Result<()> {
377        let size_bytes_aligned = HostAlignedByteCount::new_rounded_up(size_bytes)?;
378        assert!(size_bytes <= self.static_size);
379        assert!(size_bytes_aligned.byte_count() <= self.static_size);
380
381        // If the heap limit already addresses accessible bytes then no syscalls
382        // are necessary since the data is already mapped into the process and
383        // waiting to go.
384        //
385        // This is used for "dynamic" memories where memory is not always
386        // decommitted during recycling (but it's still always reset).
387        if size_bytes_aligned <= self.accessible {
388            return Ok(());
389        }
390
391        // Otherwise use `mprotect` to make the new pages read/write.
392        self.set_protection(self.accessible..size_bytes_aligned, true)?;
393        self.accessible = size_bytes_aligned;
394
395        Ok(())
396    }
397
398    /// Prepares this slot for the instantiation of a new instance with the
399    /// provided linear memory image.
400    ///
401    /// The `initial_size_bytes` parameter indicates the required initial size
402    /// of the heap for the instance. The `maybe_image` is an optional initial
403    /// image for linear memory to contains. The `style` is the way compiled
404    /// code will be accessing this memory.
405    ///
406    /// The purpose of this method is to take a previously pristine slot
407    /// (`!self.dirty`) and transform its prior state into state necessary for
408    /// the given parameters. This could include, for example:
409    ///
410    /// * More memory may be made read/write if `initial_size_bytes` is larger
411    ///   than `self.accessible`.
412    /// * For `MemoryStyle::Static` linear memory may be made `PROT_NONE` if
413    ///   `self.accessible` is larger than `initial_size_bytes`.
414    /// * If no image was previously in place or if the wrong image was
415    ///   previously in place then `mmap` may be used to setup the initial
416    ///   image.
417    pub(crate) fn instantiate(
418        &mut self,
419        initial_size_bytes: usize,
420        maybe_image: Option<&Arc<MemoryImage>>,
421        ty: &wasmtime_environ::Memory,
422        memory_tunables: &MemoryTunables<'_>,
423    ) -> Result<()> {
424        assert!(!self.dirty);
425        assert!(
426            initial_size_bytes <= self.static_size,
427            "initial_size_bytes <= self.static_size failed: \
428             initial_size_bytes={initial_size_bytes}, self.static_size={}",
429            self.static_size
430        );
431        let initial_size_bytes_page_aligned =
432            HostAlignedByteCount::new_rounded_up(initial_size_bytes)?;
433
434        // First order of business is to blow away the previous linear memory
435        // image if it doesn't match the image specified here. If one is
436        // detected then it's reset with anonymous memory which means that all
437        // of memory up to `self.accessible` will now be read/write and zero.
438        //
439        // Note that this intentionally a "small mmap" which only covers the
440        // extent of the prior initialization image in order to preserve
441        // resident memory that might come before or after the image.
442        let images_equal = match (self.image.as_ref(), maybe_image) {
443            (Some(a), Some(b)) if Arc::ptr_eq(a, b) => true,
444            (None, None) => true,
445            _ => false,
446        };
447        if !images_equal {
448            self.remove_image()?;
449        }
450
451        // The next order of business is to ensure that `self.accessible` is
452        // appropriate. First up is to grow the read/write portion of memory if
453        // it's not large enough to accommodate `initial_size_bytes`.
454        if self.accessible < initial_size_bytes_page_aligned {
455            self.set_protection(self.accessible..initial_size_bytes_page_aligned, true)?;
456            self.accessible = initial_size_bytes_page_aligned;
457        }
458
459        // If (1) the accessible region is not in its initial state, and (2) the
460        // memory relies on virtual memory at all (i.e. has offset guard
461        // pages), then we need to reset memory protections. Put another way,
462        // the only time it is safe to not reset protections is when we are
463        // using dynamic memory without any guard pages.
464        let host_page_size_log2 = u8::try_from(host_page_size().ilog2()).unwrap();
465        if initial_size_bytes_page_aligned < self.accessible
466            && (memory_tunables.guard_size() > 0
467                || ty.can_use_virtual_memory(memory_tunables.tunables(), host_page_size_log2))
468        {
469            self.set_protection(initial_size_bytes_page_aligned..self.accessible, false)?;
470            self.accessible = initial_size_bytes_page_aligned;
471        }
472
473        // Now that memory is sized appropriately the final operation is to
474        // place the new image into linear memory. Note that this operation is
475        // skipped if `self.image` matches `maybe_image`.
476        assert!(initial_size_bytes <= self.accessible.byte_count());
477        assert!(initial_size_bytes_page_aligned <= self.accessible);
478        if !images_equal {
479            if let Some(image) = maybe_image.as_ref() {
480                assert!(
481                    image
482                        .linear_memory_offset
483                        .checked_add(image.len)
484                        .unwrap()
485                        .byte_count()
486                        <= initial_size_bytes
487                );
488                if !image.len.is_zero() {
489                    unsafe {
490                        image.map_at(&self.base)?;
491                    }
492                }
493            }
494            self.image = maybe_image.cloned();
495        }
496
497        // Flag ourselves as `dirty` which means that the next operation on this
498        // slot is required to be `clear_and_remain_ready`.
499        self.dirty = true;
500
501        Ok(())
502    }
503
504    pub(crate) fn remove_image(&mut self) -> Result<()> {
505        if let Some(image) = &self.image {
506            unsafe {
507                image.remap_as_zeros_at(self.base.as_mut_ptr())?;
508            }
509            self.image = None;
510        }
511        Ok(())
512    }
513
514    /// Resets this linear memory slot back to a "pristine state".
515    ///
516    /// This will reset the memory back to its original contents on Linux or
517    /// reset the contents back to zero on other platforms. The `keep_resident`
518    /// argument is the maximum amount of memory to keep resident in this
519    /// process's memory on Linux. Up to that much memory will be `memset` to
520    /// zero where the rest of it will be reset or released with `madvise`.
521    ///
522    /// Returns the number of bytes still resident in memory after this function
523    /// has returned.
524    #[allow(dead_code, reason = "only used in some cfgs")]
525    pub(crate) fn clear_and_remain_ready(
526        &mut self,
527        pagemap: Option<&PageMap>,
528        keep_resident: HostAlignedByteCount,
529        decommit: impl FnMut(*mut u8, usize),
530    ) -> Result<usize> {
531        assert!(self.dirty);
532
533        let bytes_resident =
534            unsafe { self.reset_all_memory_contents(pagemap, keep_resident, decommit)? };
535
536        self.dirty = false;
537        Ok(bytes_resident)
538    }
539
540    #[allow(dead_code, reason = "only used in some cfgs")]
541    unsafe fn reset_all_memory_contents(
542        &mut self,
543        pagemap: Option<&PageMap>,
544        keep_resident: HostAlignedByteCount,
545        decommit: impl FnMut(*mut u8, usize),
546    ) -> Result<usize> {
547        match vm::decommit_behavior() {
548            DecommitBehavior::Zero => {
549                // If we're not on Linux then there's no generic platform way to
550                // reset memory back to its original state, so instead reset memory
551                // back to entirely zeros with an anonymous backing.
552                //
553                // Additionally the previous image, if any, is dropped here
554                // since it's no longer applicable to this mapping.
555                self.reset_with_anon_memory()?;
556                Ok(0)
557            }
558            DecommitBehavior::RestoreOriginalMapping => {
559                let bytes_resident =
560                    unsafe { self.reset_with_original_mapping(pagemap, keep_resident, decommit) };
561                Ok(bytes_resident)
562            }
563        }
564    }
565
566    #[allow(dead_code, reason = "only used in some cfgs")]
567    unsafe fn reset_with_original_mapping(
568        &mut self,
569        pagemap: Option<&PageMap>,
570        keep_resident: HostAlignedByteCount,
571        decommit: impl FnMut(*mut u8, usize),
572    ) -> usize {
573        assert_eq!(
574            vm::decommit_behavior(),
575            DecommitBehavior::RestoreOriginalMapping
576        );
577
578        unsafe {
579            return match &self.image {
580                // If there's a backing image then manually resetting a region
581                // is a bit trickier than without an image, so delegate to the
582                // helper function below.
583                Some(image) => reset_with_pagemap(
584                    pagemap,
585                    self.base.as_mut_ptr(),
586                    self.accessible,
587                    keep_resident,
588                    |region| manually_reset_region(self.base.as_mut_ptr().addr(), image, region),
589                    decommit,
590                ),
591
592                // If there's no memory image for this slot then pages are always
593                // manually reset back to zero or given to `decommit`.
594                None => reset_with_pagemap(
595                    pagemap,
596                    self.base.as_mut_ptr(),
597                    self.accessible,
598                    keep_resident,
599                    |region| region.fill(0),
600                    decommit,
601                ),
602            };
603        }
604
605        /// Manually resets `region` back to its original contents as specified
606        /// in `image`.
607        ///
608        /// This assumes that the original mmap starts at `base_addr` and
609        /// `region` is a subslice within the original mmap.
610        ///
611        /// # Panics
612        ///
613        /// Panics if `base_addr` is not the right index due to the various
614        /// indexing calculations below.
615        fn manually_reset_region(base_addr: usize, image: &MemoryImage, mut region: &mut [u8]) {
616            let image_start = image.linear_memory_offset.byte_count();
617            let image_end = image_start + image.len.byte_count();
618            let mut region_start = region.as_ptr().addr() - base_addr;
619            let region_end = region_start + region.len();
620            let image_bytes = image.module_source.wasm_data();
621            let image_bytes = &image_bytes[image.module_source_offset..][..image.len.byte_count()];
622
623            // 1. Zero out the part before the image (if any).
624            if let Some(len_before_image) = image_start.checked_sub(region_start) {
625                let len = len_before_image.min(region.len());
626                let (a, b) = region.split_at_mut(len);
627                a.fill(0);
628                region = b;
629                region_start += len;
630
631                if region.is_empty() {
632                    return;
633                }
634            }
635
636            debug_assert_eq!(region_end - region_start, region.len());
637            debug_assert!(region_start >= image_start);
638
639            // 2. Copy the original bytes from the image for the part that
640            //    overlaps with the image.
641            if let Some(len_in_image) = image_end.checked_sub(region_start) {
642                let len = len_in_image.min(region.len());
643                let (a, b) = region.split_at_mut(len);
644                a.copy_from_slice(&image_bytes[region_start - image_start..][..len]);
645                region = b;
646                region_start += len;
647
648                if region.is_empty() {
649                    return;
650                }
651            }
652
653            debug_assert_eq!(region_end - region_start, region.len());
654            debug_assert!(region_start >= image_end);
655
656            // 3. Zero out the part after the image.
657            region.fill(0);
658        }
659    }
660
661    fn set_protection(&self, range: Range<HostAlignedByteCount>, readwrite: bool) -> Result<()> {
662        let len = range
663            .end
664            .checked_sub(range.start)
665            .expect("range.start <= range.end");
666        assert!(range.end.byte_count() <= self.static_size);
667        if len.is_zero() {
668            return Ok(());
669        }
670
671        // TODO: use Mmap to change memory permissions instead of these free
672        // functions.
673        unsafe {
674            let start = self.base.as_mut_ptr().add(range.start.byte_count());
675            if readwrite {
676                vm::expose_existing_mapping(start, len.byte_count())?;
677            } else {
678                vm::hide_existing_mapping(start, len.byte_count())?;
679            }
680        }
681
682        Ok(())
683    }
684
685    pub(crate) fn has_image(&self) -> bool {
686        self.image.is_some()
687    }
688
689    #[allow(dead_code, reason = "only used in some cfgs")]
690    pub(crate) fn is_dirty(&self) -> bool {
691        self.dirty
692    }
693
694    /// Map anonymous zeroed memory across the whole slot,
695    /// inaccessible. Used both during instantiate and during drop.
696    pub(crate) fn reset_with_anon_memory(&mut self) -> Result<()> {
697        if self.static_size == 0 {
698            assert!(self.image.is_none());
699            assert_eq!(self.accessible, 0);
700            return Ok(());
701        }
702
703        unsafe {
704            vm::erase_existing_mapping(self.base.as_mut_ptr(), self.static_size)?;
705        }
706
707        self.image = None;
708        self.accessible = HostAlignedByteCount::ZERO;
709
710        Ok(())
711    }
712}
713
714#[cfg(all(test, target_os = "linux", not(miri)))]
715mod test {
716    use super::*;
717    use crate::runtime::vm::mmap::{AlignedLength, Mmap};
718    use crate::runtime::vm::sys::vm::decommit_pages;
719    use crate::runtime::vm::{HostAlignedByteCount, MmapVec, host_page_size};
720    use std::sync::Arc;
721    use wasmtime_environ::{IndexType, Limits, Memory, MemoryKind, Tunables};
722
723    fn create_memfd_with_data(offset: usize, data: &[u8]) -> Result<MemoryImage> {
724        // offset must be a multiple of the page size.
725        let linear_memory_offset =
726            HostAlignedByteCount::new(offset).expect("offset is page-aligned");
727        // The image length is rounded up to the nearest page size
728        let image_len = HostAlignedByteCount::new_rounded_up(data.len()).unwrap();
729
730        let mut source = TestDataSource {
731            data: vec![0; image_len.byte_count()],
732        };
733        source.data[..data.len()].copy_from_slice(data);
734
735        return Ok(MemoryImage {
736            source: MemoryImageSource::from_data(data)?.unwrap(),
737            len: image_len,
738            source_offset: 0,
739            linear_memory_offset,
740            module_source: Arc::new(source),
741            module_source_offset: 0,
742        });
743
744        struct TestDataSource {
745            data: Vec<u8>,
746        }
747
748        impl ModuleMemoryImageSource for TestDataSource {
749            fn wasm_data(&self) -> &[u8] {
750                &self.data
751            }
752            fn mmap(&self) -> Option<&MmapVec> {
753                None
754            }
755        }
756    }
757
758    fn dummy_memory() -> Memory {
759        Memory {
760            idx_type: IndexType::I32,
761            limits: Limits { min: 0, max: None },
762            shared: false,
763            page_size_log2: Memory::DEFAULT_PAGE_SIZE_LOG2,
764        }
765    }
766
767    fn mmap_4mib_inaccessible() -> Arc<Mmap<AlignedLength>> {
768        let four_mib = HostAlignedByteCount::new(4 << 20).expect("4 MiB is page aligned");
769        Arc::new(Mmap::accessible_reserved(HostAlignedByteCount::ZERO, four_mib).unwrap())
770    }
771
772    /// Presents a part of an mmap as a mutable slice within a callback.
773    ///
774    /// The callback ensures that the reference no longer lives after the
775    /// function is done.
776    ///
777    /// # Safety
778    ///
779    /// The caller must ensure that during this function call, the only way this
780    /// region of memory is not accessed by (read from or written to) is via the
781    /// reference. Making the callback `'static` goes some way towards ensuring
782    /// that, but it's still possible to squirrel away a reference into global
783    /// state. So don't do that.
784    unsafe fn with_slice_mut(
785        mmap: &Arc<Mmap<AlignedLength>>,
786        range: Range<usize>,
787        f: impl FnOnce(&mut [u8]) + 'static,
788    ) {
789        let ptr = mmap.as_ptr().cast_mut();
790        let slice = unsafe {
791            core::slice::from_raw_parts_mut(ptr.add(range.start), range.end - range.start)
792        };
793        f(slice);
794    }
795
796    #[test]
797    fn instantiate_no_image() {
798        let ty = dummy_memory();
799        let tunables = Tunables {
800            memory_reservation: 4 << 30,
801            ..Tunables::default_miri()
802        };
803        // 4 MiB mmap'd area, not accessible
804        let mmap = mmap_4mib_inaccessible();
805        // Create a MemoryImageSlot on top of it
806        let mut memfd =
807            MemoryImageSlot::create(mmap.zero_offset(), HostAlignedByteCount::ZERO, 4 << 20);
808        assert!(!memfd.is_dirty());
809        // instantiate with 64 KiB initial size
810        memfd
811            .instantiate(
812                64 << 10,
813                None,
814                &ty,
815                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
816            )
817            .unwrap();
818        assert!(memfd.is_dirty());
819
820        // We should be able to access this 64 KiB (try both ends) and
821        // it should consist of zeroes.
822        unsafe {
823            with_slice_mut(&mmap, 0..65536, |slice| {
824                assert_eq!(0, slice[0]);
825                assert_eq!(0, slice[65535]);
826                slice[1024] = 42;
827                assert_eq!(42, slice[1024]);
828            });
829        }
830
831        // grow the heap
832        memfd.set_heap_limit(128 << 10).unwrap();
833        let slice = unsafe { mmap.slice(0..1 << 20) };
834        assert_eq!(42, slice[1024]);
835        assert_eq!(0, slice[131071]);
836        // instantiate again; we should see zeroes, even as the
837        // reuse-anon-mmap-opt kicks in
838        memfd
839            .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe {
840                decommit_pages(ptr, len).unwrap()
841            })
842            .unwrap();
843        assert!(!memfd.is_dirty());
844        memfd
845            .instantiate(
846                64 << 10,
847                None,
848                &ty,
849                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
850            )
851            .unwrap();
852        let slice = unsafe { mmap.slice(0..65536) };
853        assert_eq!(0, slice[1024]);
854    }
855
856    #[test]
857    fn instantiate_image() {
858        let page_size = host_page_size();
859        let ty = dummy_memory();
860        let tunables = Tunables {
861            memory_reservation: 4 << 30,
862            ..Tunables::default_miri()
863        };
864        // 4 MiB mmap'd area, not accessible
865        let mmap = mmap_4mib_inaccessible();
866        // Create a MemoryImageSlot on top of it
867        let mut memfd =
868            MemoryImageSlot::create(mmap.zero_offset(), HostAlignedByteCount::ZERO, 4 << 20);
869        // Create an image with some data.
870        let image = Arc::new(create_memfd_with_data(page_size, &[1, 2, 3, 4]).unwrap());
871        // Instantiate with this image
872        memfd
873            .instantiate(
874                64 << 10,
875                Some(&image),
876                &ty,
877                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
878            )
879            .unwrap();
880        assert!(memfd.has_image());
881
882        unsafe {
883            with_slice_mut(&mmap, 0..65536, move |slice| {
884                assert_eq!(&[1, 2, 3, 4], &slice[page_size..][..4]);
885                slice[page_size] = 5;
886            });
887        }
888
889        // Clear and re-instantiate same image
890        memfd
891            .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe {
892                decommit_pages(ptr, len).unwrap()
893            })
894            .unwrap();
895        memfd
896            .instantiate(
897                64 << 10,
898                Some(&image),
899                &ty,
900                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
901            )
902            .unwrap();
903        let slice = unsafe { mmap.slice(0..65536) };
904        assert_eq!(&[1, 2, 3, 4], &slice[page_size..][..4]);
905
906        // Clear and re-instantiate no image
907        memfd
908            .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe {
909                decommit_pages(ptr, len).unwrap()
910            })
911            .unwrap();
912        memfd
913            .instantiate(
914                64 << 10,
915                None,
916                &ty,
917                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
918            )
919            .unwrap();
920        assert!(!memfd.has_image());
921        let slice = unsafe { mmap.slice(0..65536) };
922        assert_eq!(&[0, 0, 0, 0], &slice[page_size..][..4]);
923
924        // Clear and re-instantiate image again
925        memfd
926            .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe {
927                decommit_pages(ptr, len).unwrap()
928            })
929            .unwrap();
930        memfd
931            .instantiate(
932                64 << 10,
933                Some(&image),
934                &ty,
935                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
936            )
937            .unwrap();
938        let slice = unsafe { mmap.slice(0..65536) };
939        assert_eq!(&[1, 2, 3, 4], &slice[page_size..][..4]);
940
941        // Create another image with different data.
942        let image2 = Arc::new(create_memfd_with_data(page_size, &[10, 11, 12, 13]).unwrap());
943        memfd
944            .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe {
945                decommit_pages(ptr, len).unwrap()
946            })
947            .unwrap();
948        memfd
949            .instantiate(
950                128 << 10,
951                Some(&image2),
952                &ty,
953                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
954            )
955            .unwrap();
956        let slice = unsafe { mmap.slice(0..65536) };
957        assert_eq!(&[10, 11, 12, 13], &slice[page_size..][..4]);
958
959        // Instantiate the original image again; we should notice it's
960        // a different image and not reuse the mappings.
961        memfd
962            .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe {
963                decommit_pages(ptr, len).unwrap()
964            })
965            .unwrap();
966        memfd
967            .instantiate(
968                64 << 10,
969                Some(&image),
970                &ty,
971                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
972            )
973            .unwrap();
974        let slice = unsafe { mmap.slice(0..65536) };
975        assert_eq!(&[1, 2, 3, 4], &slice[page_size..][..4]);
976    }
977
978    #[test]
979    #[cfg(target_os = "linux")]
980    fn memset_instead_of_madvise() {
981        let page_size = host_page_size();
982        let ty = dummy_memory();
983        let tunables = Tunables {
984            memory_reservation: 100 << 16,
985            ..Tunables::default_miri()
986        };
987        let mmap = mmap_4mib_inaccessible();
988        let mut memfd =
989            MemoryImageSlot::create(mmap.zero_offset(), HostAlignedByteCount::ZERO, 4 << 20);
990
991        // Test basics with the image
992        for image_off in [0, page_size, page_size * 2] {
993            let image = Arc::new(create_memfd_with_data(image_off, &[1, 2, 3, 4]).unwrap());
994            for amt_to_memset in [0, page_size, page_size * 10, 1 << 20, 10 << 20] {
995                let amt_to_memset = HostAlignedByteCount::new(amt_to_memset).unwrap();
996                memfd
997                    .instantiate(
998                        64 << 10,
999                        Some(&image),
1000                        &ty,
1001                        &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
1002                    )
1003                    .unwrap();
1004                assert!(memfd.has_image());
1005
1006                unsafe {
1007                    with_slice_mut(&mmap, 0..64 << 10, move |slice| {
1008                        if image_off > 0 {
1009                            assert_eq!(slice[image_off - 1], 0);
1010                        }
1011                        assert_eq!(slice[image_off + 5], 0);
1012                        assert_eq!(&[1, 2, 3, 4], &slice[image_off..][..4]);
1013                        slice[image_off] = 5;
1014                        assert_eq!(&[5, 2, 3, 4], &slice[image_off..][..4]);
1015                    })
1016                };
1017
1018                memfd
1019                    .clear_and_remain_ready(None, amt_to_memset, |ptr, len| unsafe {
1020                        decommit_pages(ptr, len).unwrap()
1021                    })
1022                    .unwrap();
1023            }
1024        }
1025
1026        // Test without an image
1027        for amt_to_memset in [0, page_size, page_size * 10, 1 << 20, 10 << 20] {
1028            let amt_to_memset = HostAlignedByteCount::new(amt_to_memset).unwrap();
1029            memfd
1030                .instantiate(
1031                    64 << 10,
1032                    None,
1033                    &ty,
1034                    &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
1035                )
1036                .unwrap();
1037
1038            unsafe {
1039                with_slice_mut(&mmap, 0..64 << 10, |slice| {
1040                    for chunk in slice.chunks_mut(1024) {
1041                        assert_eq!(chunk[0], 0);
1042                        chunk[0] = 5;
1043                    }
1044                });
1045            }
1046            memfd
1047                .clear_and_remain_ready(None, amt_to_memset, |ptr, len| unsafe {
1048                    decommit_pages(ptr, len).unwrap()
1049                })
1050                .unwrap();
1051        }
1052    }
1053
1054    #[test]
1055    #[cfg(target_os = "linux")]
1056    fn dynamic() {
1057        let page_size = host_page_size();
1058        let ty = dummy_memory();
1059        let tunables = Tunables {
1060            memory_reservation: 0,
1061            memory_reservation_for_growth: 200,
1062            ..Tunables::default_miri()
1063        };
1064
1065        let mmap = mmap_4mib_inaccessible();
1066        let mut memfd =
1067            MemoryImageSlot::create(mmap.zero_offset(), HostAlignedByteCount::ZERO, 4 << 20);
1068        let image = Arc::new(create_memfd_with_data(page_size, &[1, 2, 3, 4]).unwrap());
1069        let initial = 64 << 10;
1070
1071        // Instantiate the image and test that memory remains accessible after
1072        // it's cleared.
1073        memfd
1074            .instantiate(
1075                initial,
1076                Some(&image),
1077                &ty,
1078                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
1079            )
1080            .unwrap();
1081        assert!(memfd.has_image());
1082
1083        unsafe {
1084            with_slice_mut(&mmap, 0..(64 << 10) + page_size, move |slice| {
1085                assert_eq!(&[1, 2, 3, 4], &slice[page_size..][..4]);
1086                slice[page_size] = 5;
1087                assert_eq!(&[5, 2, 3, 4], &slice[page_size..][..4]);
1088            });
1089        }
1090
1091        memfd
1092            .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe {
1093                decommit_pages(ptr, len).unwrap()
1094            })
1095            .unwrap();
1096        let slice = unsafe { mmap.slice(0..(64 << 10) + page_size) };
1097        assert_eq!(&[1, 2, 3, 4], &slice[page_size..][..4]);
1098
1099        // Re-instantiate make sure it preserves memory. Grow a bit and set data
1100        // beyond the initial size.
1101        memfd
1102            .instantiate(
1103                initial,
1104                Some(&image),
1105                &ty,
1106                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
1107            )
1108            .unwrap();
1109        assert_eq!(&[1, 2, 3, 4], &slice[page_size..][..4]);
1110
1111        memfd.set_heap_limit(initial * 2).unwrap();
1112
1113        unsafe {
1114            with_slice_mut(&mmap, 0..(64 << 10) + page_size, move |slice| {
1115                assert_eq!(&[0, 0], &slice[initial..initial + 2]);
1116                slice[initial] = 100;
1117                assert_eq!(&[100, 0], &slice[initial..initial + 2]);
1118            });
1119        }
1120
1121        memfd
1122            .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe {
1123                decommit_pages(ptr, len).unwrap()
1124            })
1125            .unwrap();
1126
1127        // Test that memory is still accessible, but it's been reset
1128        assert_eq!(&[0, 0], &slice[initial..initial + 2]);
1129
1130        // Instantiate again, and again memory beyond the initial size should
1131        // still be accessible. Grow into it again and make sure it works.
1132        memfd
1133            .instantiate(
1134                initial,
1135                Some(&image),
1136                &ty,
1137                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
1138            )
1139            .unwrap();
1140        assert_eq!(&[0, 0], &slice[initial..initial + 2]);
1141        memfd.set_heap_limit(initial * 2).unwrap();
1142
1143        unsafe {
1144            with_slice_mut(&mmap, 0..(64 << 10) + page_size, move |slice| {
1145                assert_eq!(&[0, 0], &slice[initial..initial + 2]);
1146                slice[initial] = 100;
1147                assert_eq!(&[100, 0], &slice[initial..initial + 2]);
1148            });
1149        }
1150
1151        memfd
1152            .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe {
1153                decommit_pages(ptr, len).unwrap()
1154            })
1155            .unwrap();
1156
1157        // Reset the image to none and double-check everything is back to zero
1158        memfd
1159            .instantiate(
1160                64 << 10,
1161                None,
1162                &ty,
1163                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
1164            )
1165            .unwrap();
1166        assert!(!memfd.has_image());
1167        assert_eq!(&[0, 0, 0, 0], &slice[page_size..][..4]);
1168        assert_eq!(&[0, 0], &slice[initial..initial + 2]);
1169    }
1170
1171    #[test]
1172    fn reset_with_pagemap() {
1173        let page_size = host_page_size();
1174        let ty = dummy_memory();
1175        let tunables = Tunables {
1176            memory_reservation: 100 << 16,
1177            ..Tunables::default_miri()
1178        };
1179        let mmap = mmap_4mib_inaccessible();
1180        let mmap_len = page_size * 9;
1181        let mut memfd =
1182            MemoryImageSlot::create(mmap.zero_offset(), HostAlignedByteCount::ZERO, mmap_len);
1183        let pagemap = PageMap::new();
1184        let pagemap = pagemap.as_ref();
1185
1186        let mut data = vec![0; 3 * page_size];
1187        for (i, chunk) in data.chunks_mut(page_size).enumerate() {
1188            for slot in chunk {
1189                *slot = u8::try_from(i + 1).unwrap();
1190            }
1191        }
1192        let image = Arc::new(create_memfd_with_data(3 * page_size, &data).unwrap());
1193
1194        memfd
1195            .instantiate(
1196                mmap_len,
1197                Some(&image),
1198                &ty,
1199                &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
1200            )
1201            .unwrap();
1202
1203        let keep_resident = HostAlignedByteCount::new(mmap_len).unwrap();
1204        let assert_pristine_after_reset = |memfd: &mut MemoryImageSlot| unsafe {
1205            // Wipe the image, keeping some bytes resident.
1206            memfd
1207                .clear_and_remain_ready(pagemap, keep_resident, |ptr, len| {
1208                    decommit_pages(ptr, len).unwrap()
1209                })
1210                .unwrap();
1211
1212            // Double check that the contents of memory are as expected after
1213            // reset.
1214            with_slice_mut(&mmap, 0..mmap_len, move |slice| {
1215                for (i, chunk) in slice.chunks(page_size).enumerate() {
1216                    let expected = match i {
1217                        0..3 => 0,
1218                        3..6 => u8::try_from(i).unwrap() - 2,
1219                        6..9 => 0,
1220                        _ => unreachable!(),
1221                    };
1222                    for slot in chunk {
1223                        assert_eq!(*slot, expected);
1224                    }
1225                }
1226            });
1227
1228            // Re-instantiate, but then wipe the image entirely by keeping
1229            // nothing resident.
1230            memfd
1231                .instantiate(
1232                    mmap_len,
1233                    Some(&image),
1234                    &ty,
1235                    &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
1236                )
1237                .unwrap();
1238            memfd
1239                .clear_and_remain_ready(pagemap, HostAlignedByteCount::ZERO, |ptr, len| {
1240                    decommit_pages(ptr, len).unwrap()
1241                })
1242                .unwrap();
1243
1244            // Next re-instantiate a final time to get used for the next test.
1245            memfd
1246                .instantiate(
1247                    mmap_len,
1248                    Some(&image),
1249                    &ty,
1250                    &MemoryTunables::new(&tunables, MemoryKind::LinearMemory),
1251                )
1252                .unwrap();
1253        };
1254
1255        let write_page = |_memfd: &mut MemoryImageSlot, page: usize| unsafe {
1256            with_slice_mut(
1257                &mmap,
1258                page * page_size..(page + 1) * page_size,
1259                move |slice| slice.fill(0xff),
1260            );
1261        };
1262
1263        // Test various combinations of dirty pages and regions. For example
1264        // test a dirty region of memory entirely in the zero-initialized zone
1265        // before/after the image and also test when the dirty region straddles
1266        // just the start of the image, just the end of the image, both ends,
1267        // and is entirely contained in just the image.
1268        assert_pristine_after_reset(&mut memfd);
1269
1270        for i in 0..9 {
1271            write_page(&mut memfd, i);
1272            assert_pristine_after_reset(&mut memfd);
1273        }
1274        write_page(&mut memfd, 0);
1275        write_page(&mut memfd, 1);
1276        assert_pristine_after_reset(&mut memfd);
1277        write_page(&mut memfd, 1);
1278        assert_pristine_after_reset(&mut memfd);
1279        write_page(&mut memfd, 2);
1280        write_page(&mut memfd, 3);
1281        assert_pristine_after_reset(&mut memfd);
1282        write_page(&mut memfd, 3);
1283        write_page(&mut memfd, 4);
1284        write_page(&mut memfd, 5);
1285        assert_pristine_after_reset(&mut memfd);
1286        write_page(&mut memfd, 0);
1287        write_page(&mut memfd, 1);
1288        write_page(&mut memfd, 2);
1289        assert_pristine_after_reset(&mut memfd);
1290        write_page(&mut memfd, 0);
1291        write_page(&mut memfd, 3);
1292        write_page(&mut memfd, 6);
1293        assert_pristine_after_reset(&mut memfd);
1294        write_page(&mut memfd, 2);
1295        write_page(&mut memfd, 3);
1296        write_page(&mut memfd, 4);
1297        write_page(&mut memfd, 5);
1298        write_page(&mut memfd, 6);
1299        assert_pristine_after_reset(&mut memfd);
1300        write_page(&mut memfd, 4);
1301        write_page(&mut memfd, 5);
1302        write_page(&mut memfd, 6);
1303        write_page(&mut memfd, 7);
1304        assert_pristine_after_reset(&mut memfd);
1305        write_page(&mut memfd, 4);
1306        write_page(&mut memfd, 5);
1307        write_page(&mut memfd, 8);
1308        assert_pristine_after_reset(&mut memfd);
1309    }
1310}