mmtk/util/heap/layout/
vm_layout.rs

1//! The module defines virutal memory layout parameters.
2
3use std::ptr::addr_of;
4use std::sync::atomic::AtomicBool;
5
6use atomic::Ordering;
7
8use super::heap_parameters::*;
9use crate::util::constants::*;
10use crate::util::Address;
11
12use crate::util::conversions::{chunk_align_down, chunk_align_up};
13
14/// log_2 of the coarsest unit of address space allocation.
15pub const LOG_BYTES_IN_CHUNK: usize = 22;
16/// Coarsest unit of address space allocation.
17pub const BYTES_IN_CHUNK: usize = 1 << LOG_BYTES_IN_CHUNK;
18/// Mask for chunk size.
19pub const CHUNK_MASK: usize = (1 << LOG_BYTES_IN_CHUNK) - 1;
20/// Coarsest unit of address space allocation, in pages
21pub const PAGES_IN_CHUNK: usize = 1 << (LOG_BYTES_IN_CHUNK - LOG_BYTES_IN_PAGE as usize);
22
23/// Runtime-initialized virtual memory constants
24#[derive(Clone, Debug)]
25pub struct VMLayout {
26    /// log_2 of the addressable heap virtual space.
27    pub log_address_space: usize,
28    /// Lowest virtual address used by the virtual machine. Should be chunk aligned.
29    pub heap_start: Address,
30    /// Highest virtual address used by the virtual machine. Should be chunk aligned.
31    pub heap_end: Address,
32    /// An upper bound on the extent of any space in the
33    /// current memory layout
34    pub log_space_extent: usize,
35    /// Should mmtk enable contiguous spaces and virtual memory for all spaces?
36    /// For normal 64-bit config, this should be set to true. Each space should own a contiguous piece of virtual memory.
37    /// For 32-bit or 64-bit compressed heap, we don't have enough virtual memory, so this should be set to false.
38    pub force_use_contiguous_spaces: bool,
39}
40
41impl VMLayout {
42    cfg_if::cfg_if! {
43        if #[cfg(target_pointer_width = "32")] {
44            /// The maximum virtual memory address space that can be used on the target.
45            pub const LOG_ARCH_ADDRESS_SPACE: usize = 32;
46        } else if #[cfg(all(target_arch = "aarch64", target_os = "linux"))] {
47            /// The maximum virtual memory address space that can be used on the target.
48            /// Linux on AArch64 (with the default `CONFIG_ARM64_VA_BITS=48`) gives user space the
49            /// full 48-bit range (up to 0xffff_ffff_ffff).  Addresses returned by `malloc` (malloc mark sweep)
50            /// can be anywhere in that range, and side metadata must cover them.
51            pub const LOG_ARCH_ADDRESS_SPACE: usize = 48;
52        } else {
53            /// The maximum virtual memory address space that can be used on the target.
54            /// x86_64 Linux and AArch64 macOS limit user space to 47 bits.
55            pub const LOG_ARCH_ADDRESS_SPACE: usize = 47;
56        }
57    }
58
59    /// An upper bound on the extent of any space in the
60    /// current memory layout
61    pub const fn max_space_extent(&self) -> usize {
62        1 << self.log_space_extent
63    }
64    /// Lowest virtual address available for MMTk to manage.
65    pub const fn available_start(&self) -> Address {
66        self.heap_start
67    }
68    /// Highest virtual address available for MMTk to manage.
69    pub const fn available_end(&self) -> Address {
70        self.heap_end
71    }
72    /// Size of the address space available to the MMTk heap.
73    pub const fn available_bytes(&self) -> usize {
74        self.available_end().get_extent(self.available_start())
75    }
76    /// Maximum number of chunks we need to track.  Only used in 32-bit layout.
77    pub const fn max_chunks(&self) -> usize {
78        1 << self.log_max_chunks()
79    }
80    /// log_2 of the maximum number of chunks we need to track.  Only used in 32-bit layout.
81    pub const fn log_max_chunks(&self) -> usize {
82        Self::LOG_ARCH_ADDRESS_SPACE - LOG_BYTES_IN_CHUNK
83    }
84    /// Number of bits to shift a space index into/out of a virtual address.
85    /// In a 32-bit model, use a dummy value so that the compiler doesn't barf.
86    pub(crate) fn space_shift_64(&self) -> usize {
87        self.log_space_extent
88    }
89    /// Bitwise mask to isolate a space index in a virtual address.
90    /// We can't express this constant in a 32-bit environment, hence the
91    /// conditional definition.
92    pub(crate) fn space_mask_64(&self) -> usize {
93        ((1 << LOG_MAX_SPACES) - 1) << self.space_shift_64()
94    }
95    /// Size of each space in the 64-bit memory layout
96    /// We can't express this constant in a 32-bit environment, hence the
97    /// conditional definition.
98    /// FIXME: When Compiling for 32 bits this expression makes no sense
99    pub(crate) fn space_size_64(&self) -> usize {
100        self.max_space_extent()
101    }
102    /// log_2 of the number of pages in a 64-bit space
103    pub(crate) fn log_pages_in_space64(&self) -> usize {
104        self.log_space_extent - LOG_BYTES_IN_PAGE as usize
105    }
106    /// The number of pages in a 64-bit space
107    pub(crate) fn pages_in_space64(&self) -> usize {
108        1 << self.log_pages_in_space64()
109    }
110
111    /// This mask extracts a few bits from address, and use it as index to the space map table.
112    /// When masked with this constant, the index is 1 to 16. If we mask any arbitrary address with this mask, we will get 0 to 31 (32 entries).
113    pub(crate) fn address_mask(&self) -> usize {
114        0x1f << self.log_space_extent
115    }
116
117    const fn validate(&self) {
118        assert!(self.heap_start.is_aligned_to(BYTES_IN_CHUNK));
119        assert!(self.heap_end.is_aligned_to(BYTES_IN_CHUNK));
120        assert!(self.heap_start.as_usize() < self.heap_end.as_usize());
121        assert!(self.log_address_space <= Self::LOG_ARCH_ADDRESS_SPACE);
122        assert!(self.log_space_extent <= self.log_address_space);
123        if self.force_use_contiguous_spaces {
124            assert!(self.log_space_extent <= (self.log_address_space - LOG_MAX_SPACES));
125            assert!(self.heap_start.is_aligned_to(self.max_space_extent()));
126        }
127    }
128
129    pub(crate) fn validate_address_space(&self) {
130        let log_mappable_bytes = crate::mmtk::MMAPPER.log_mappable_bytes();
131        assert!(
132            self.log_address_space <= log_mappable_bytes as usize,
133            "log_address_space is {log_address_space}, but \
134            the MMAPPER can only handle up to {log_mappable_bytes} bits",
135            log_address_space = self.log_address_space,
136        );
137    }
138}
139
140impl VMLayout {
141    /// Normal 32-bit configuration
142    pub const fn new_32bit() -> Self {
143        let layout32 = Self {
144            log_address_space: 32,
145            heap_start: chunk_align_down(unsafe { Address::from_usize(0x8000_0000) }),
146            heap_end: chunk_align_up(unsafe { Address::from_usize(0xd000_0000) }),
147            log_space_extent: 31,
148            force_use_contiguous_spaces: false,
149        };
150        layout32.validate();
151        layout32
152    }
153    /// Normal 64-bit configuration
154    #[cfg(target_pointer_width = "64")]
155    pub const fn new_64bit() -> Self {
156        let layout64 = Self {
157            log_address_space: 47,
158            heap_start: chunk_align_down(unsafe {
159                Address::from_usize(0x0000_0200_0000_0000usize)
160            }),
161            heap_end: chunk_align_up(unsafe { Address::from_usize(0x0000_2200_0000_0000usize) }),
162            log_space_extent: 41,
163            force_use_contiguous_spaces: true,
164        };
165        layout64.validate();
166        layout64
167    }
168
169    /// Custom VM layout constants. VM bindings may use this function for compressed or 39-bit heap support.
170    /// This function must be called before MMTk::new()
171    pub(crate) fn set_custom_vm_layout(constants: VMLayout) {
172        if cfg!(debug_assertions) {
173            assert!(
174                !VM_LAYOUT_FETCHED.load(Ordering::SeqCst),
175                "vm_layout is already been used before setup"
176            );
177        }
178        constants.validate();
179        unsafe {
180            VM_LAYOUT = constants;
181        }
182    }
183}
184
185// Implement default so bindings can selectively change some parameters while using default for others.
186impl std::default::Default for VMLayout {
187    #[cfg(target_pointer_width = "32")]
188    fn default() -> Self {
189        Self::new_32bit()
190    }
191
192    #[cfg(target_pointer_width = "64")]
193    fn default() -> Self {
194        Self::new_64bit()
195    }
196}
197
198#[cfg(target_pointer_width = "32")]
199static mut VM_LAYOUT: VMLayout = VMLayout::new_32bit();
200#[cfg(target_pointer_width = "64")]
201static mut VM_LAYOUT: VMLayout = VMLayout::new_64bit();
202
203static VM_LAYOUT_FETCHED: AtomicBool = AtomicBool::new(false);
204
205/// Get the current virtual memory layout in use.
206/// If the binding would like to set a custom virtual memory layout ([`crate::mmtk::MMTKBuilder::set_vm_layout`]), they should not
207/// call this function before they set a custom layout.
208pub fn vm_layout() -> &'static VMLayout {
209    if cfg!(debug_assertions) {
210        VM_LAYOUT_FETCHED.store(true, Ordering::SeqCst);
211    }
212    unsafe { &*addr_of!(VM_LAYOUT) }
213}