1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597
use super::sft::*;
use crate::util::metadata::side_metadata::SideMetadataSpec;
use crate::util::Address;
use std::sync::atomic::Ordering;
/// SFTMap manages the SFT table, and mapping between addresses with indices in the table. The trait allows
/// us to have multiple implementations of the SFT table.
pub trait SFTMap {
/// Check if the address has an SFT entry in the map (including an empty SFT entry). This is mostly a bound check
/// to make sure that we won't have an index-out-of-bound error. For the sake of performance, the implementation
/// of other methods in this trait (such as get_unchecked(), update() and clear()) does not need to do this check implicitly.
/// Instead, they assume the address has a valid entry in the SFT. If an address could be arbitary, they should call this
/// method as a pre-check before they call those methods in the trait. We also provide a method `get_checked()` which includes
/// this check, and will return an empty SFT if the address is out of bound.
fn has_sft_entry(&self, addr: Address) -> bool;
/// Get the side metadata spec this SFT map uses.
fn get_side_metadata(&self) -> Option<&SideMetadataSpec>;
/// Get SFT for the address. The address must have a valid SFT entry in the table (e.g. from an object reference, or from an address
/// that is known to be in our spaces). Otherwise, use `get_checked()`.
///
/// # Safety
/// The address must have a valid SFT entry in the map. Usually we know this if the address is from an object reference, or from our space address range.
/// Otherwise, the caller should check with `has_sft_entry()` before calling this method, or use `get_checked()`.
unsafe fn get_unchecked(&self, address: Address) -> &dyn SFT;
/// Get SFT for the address. The address can be arbitrary. For out-of-bound access, an empty SFT will be returned.
/// We only provide the checked version for `get()`, as it may be used to query arbitrary objects and addresses. Other methods like `update/clear/etc` are
/// mostly used inside MMTk, and in most cases, we know that they are within our space address range.
fn get_checked(&self, address: Address) -> &dyn SFT;
/// Set SFT for the address range. The address must have a valid SFT entry in the table.
///
/// # Safety
/// The address must have a valid SFT entry in the map. Usually we know this if the address is from an object reference, or from our space address range.
/// Otherwise, the caller should check with `has_sft_entry()` before calling this method.
unsafe fn update(&self, space: SFTRawPointer, start: Address, bytes: usize);
/// Notify the SFT map for space creation. `DenseChunkMap` needs to create an entry for the space.
fn notify_space_creation(&mut self, _space: SFTRawPointer) {}
/// Eagerly initialize the SFT table. For most implementations, it could be the same as update().
/// However, we need this as a seprate method for SFTDenseChunkMap, as it needs to map side metadata first
/// before setting the table.
///
/// # Safety
/// The address must have a valid SFT entry in the map. Usually we know this if the address is from an object reference, or from our space address range.
/// Otherwise, the caller should check with `has_sft_entry()` before calling this method.
unsafe fn eager_initialize(
&mut self,
space: *const (dyn SFT + Sync + 'static),
start: Address,
bytes: usize,
) {
self.update(space, start, bytes);
}
/// Clear SFT for the address. The address must have a valid SFT entry in the table.
///
/// # Safety
/// The address must have a valid SFT entry in the map. Usually we know this if the address is from an object reference, or from our space address range.
/// Otherwise, the caller should check with `has_sft_entry()` before calling this method.
unsafe fn clear(&self, address: Address);
}
pub(crate) fn create_sft_map() -> Box<dyn SFTMap> {
cfg_if::cfg_if! {
if #[cfg(target_pointer_width = "64")] {
// For 64bits, we generally want to use the space map, which requires using contiguous space and no off-heap memory.
// If the requirements do not meet, we have to choose a different SFT map implementation.
use crate::util::heap::layout::vm_layout::vm_layout;
if !vm_layout().force_use_contiguous_spaces {
// This is usually the case for compressed pointer. Use the 32bits implementation.
Box::new(sparse_chunk_map::SFTSparseChunkMap::new())
} else if cfg!(any(feature = "malloc_mark_sweep", feature = "vm_space")) {
// We have off-heap memory (malloc'd objects, or VM space). We have to use a chunk-based map.
Box::new(dense_chunk_map::SFTDenseChunkMap::new())
} else {
// We can use space map.
Box::new(space_map::SFTSpaceMap::new())
}
} else if #[cfg(target_pointer_width = "32")] {
// Use sparse chunk map. As we have limited virtual address range on 32 bits,
// it is okay to have a sparse chunk map which maps every chunk into an index in the array.
Box::new(sparse_chunk_map::SFTSparseChunkMap::new())
} else {
compile_err!("Cannot figure out which SFT map to use.");
}
}
}
/// The raw pointer for SFT. We expect a space to provide this to SFT map.
pub(crate) type SFTRawPointer = *const (dyn SFT + Sync + 'static);
/// We store raw pointer as a double word using atomics.
/// We use portable_atomic. It provides non locking atomic operations where possible,
/// and use a locking operation as the fallback.
/// Rust only provides AtomicU128 for some platforms, and do not provide the type
/// on x86_64-linux, as some earlier x86_64 CPUs do not have 128 bits atomic instructions.
/// The crate portable_atomic works around the problem with a runtime detection to
/// see if 128 bits atomic instructions are available.
#[cfg(target_pointer_width = "64")]
type AtomicDoubleWord = portable_atomic::AtomicU128;
#[cfg(target_pointer_width = "64")]
type DoubleWord = u128;
#[cfg(target_pointer_width = "32")]
type AtomicDoubleWord = portable_atomic::AtomicU64;
#[cfg(target_pointer_width = "32")]
type DoubleWord = u64;
/// The type we store SFT raw pointer as. It basically just double word sized atomic integer.
/// This type provides an abstraction so we can access SFT easily.
#[repr(transparent)]
pub(crate) struct SFTRefStorage(AtomicDoubleWord);
impl SFTRefStorage {
/// A check at boot time to ensure `SFTRefStorage` is correct.
pub fn pre_use_check() {
// If we do not have lock free operations, warn the users.
if !AtomicDoubleWord::is_lock_free() {
warn!(
"SFT access word is not lock free on this platform. This will slow down SFT map."
);
}
// Our storage type needs to be the same width as the dyn pointer type.
assert_eq!(
std::mem::size_of::<AtomicDoubleWord>(),
std::mem::size_of::<SFTRawPointer>()
);
}
pub fn new(sft: SFTRawPointer) -> Self {
let val: DoubleWord = unsafe { std::mem::transmute(sft) };
Self(AtomicDoubleWord::new(val))
}
// Load with the acquire ordering.
pub fn load(&self) -> &dyn SFT {
let val = self.0.load(Ordering::Acquire);
unsafe { std::mem::transmute(val) }
}
// Store a raw SFT pointer with the release ordering.
pub fn store(&self, sft: SFTRawPointer) {
let val: DoubleWord = unsafe { std::mem::transmute(sft) };
self.0.store(val, Ordering::Release)
}
}
impl std::default::Default for SFTRefStorage {
fn default() -> Self {
Self::new(&EMPTY_SPACE_SFT as SFTRawPointer)
}
}
#[allow(dead_code)]
#[cfg(target_pointer_width = "64")] // This impl only works for 64 bits: 1. the mask is designed for our 64bit heap range, 2. on 64bits, all our spaces are contiguous.
mod space_map {
use super::*;
use crate::util::heap::layout::vm_layout::vm_layout;
/// Space map is a small table, and it has one entry for each MMTk space.
pub struct SFTSpaceMap {
sft: Vec<SFTRefStorage>,
space_address_start: Address,
space_address_end: Address,
}
unsafe impl Sync for SFTSpaceMap {}
impl SFTMap for SFTSpaceMap {
fn has_sft_entry(&self, addr: Address) -> bool {
// An arbitrary address from Address::ZERO to Address::MAX will be cyclically mapped to an index between 0 and 31
// Only addresses between the virtual address range we use have valid entries.
addr >= self.space_address_start && addr < self.space_address_end
}
fn get_side_metadata(&self) -> Option<&SideMetadataSpec> {
None
}
fn get_checked(&self, address: Address) -> &dyn SFT {
// We should be able to map the entire address range to indices in the table.
debug_assert!(Self::addr_to_index(address) < self.sft.len());
unsafe { self.get_unchecked(address) }
}
unsafe fn get_unchecked(&self, address: Address) -> &dyn SFT {
let cell = unsafe { self.sft.get_unchecked(Self::addr_to_index(address)) };
cell.load()
}
unsafe fn update(
&self,
space: *const (dyn SFT + Sync + 'static),
start: Address,
bytes: usize,
) {
let index = Self::addr_to_index(start);
if cfg!(debug_assertions) {
// Make sure we only update from empty to a valid space, or overwrite the space
let old = self.sft[index].load();
assert!((*old).name() == EMPTY_SFT_NAME || (*old).name() == (*space).name());
// Make sure the range is in the space
let space_start = Self::index_to_space_start(index);
assert!(start >= space_start);
assert!(
start + bytes <= space_start + vm_layout().max_space_extent(),
"The range of {} + {} bytes does not fall into the space range {} and {}, \
and it is probably outside the address range we use.",
start,
bytes,
space_start,
space_start + vm_layout().max_space_extent()
);
}
self.sft.get_unchecked(index).store(space);
}
unsafe fn clear(&self, addr: Address) {
let index = Self::addr_to_index(addr);
self.sft.get_unchecked(index).store(&EMPTY_SPACE_SFT as _);
}
}
impl SFTSpaceMap {
/// Create a new space map.
#[allow(clippy::assertions_on_constants)] // We assert to make sure the constants
pub fn new() -> Self {
use crate::util::heap::layout::heap_parameters::MAX_SPACES;
let table_size = Self::addr_to_index(Address::MAX) + 1;
debug_assert!(table_size >= MAX_SPACES);
Self {
sft: std::iter::repeat_with(SFTRefStorage::default)
.take(table_size)
.collect(),
space_address_start: Self::index_to_space_range(1).0, // the start of the first space
space_address_end: Self::index_to_space_range(MAX_SPACES - 1).1, // the end of the last space
}
}
fn addr_to_index(addr: Address) -> usize {
addr.and(vm_layout().address_mask()) >> vm_layout().log_space_extent
}
fn index_to_space_start(i: usize) -> Address {
let (start, _) = Self::index_to_space_range(i);
start
}
fn index_to_space_range(i: usize) -> (Address, Address) {
if i == 0 {
panic!("Invalid index: there is no space for index 0")
} else {
let start = Address::ZERO.add(i << vm_layout().log_space_extent);
let extent = 1 << vm_layout().log_space_extent;
(start, start.add(extent))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::heap::layout::heap_parameters::MAX_SPACES;
use crate::util::heap::layout::vm_layout::vm_layout;
// If the test `test_address_arithmetic()` fails, it is possible due to change of our heap range, max space extent, or max number of spaces.
// We need to update the code and the constants for the address arithemtic.
#[test]
fn test_address_arithmetic() {
// Before 1st space
assert_eq!(SFTSpaceMap::addr_to_index(Address::ZERO), 0);
assert_eq!(SFTSpaceMap::addr_to_index(vm_layout().heap_start - 1), 0);
let assert_for_index = |i: usize| {
let (start, end) = SFTSpaceMap::index_to_space_range(i);
println!("Space: Index#{} = [{}, {})", i, start, end);
assert_eq!(SFTSpaceMap::addr_to_index(start), i);
assert_eq!(SFTSpaceMap::addr_to_index(end - 1), i);
};
// Index 1 to 16 (MAX_SPACES)
for i in 1..=MAX_SPACES {
assert_for_index(i);
}
// assert space end
let (_, last_space_end) = SFTSpaceMap::index_to_space_range(MAX_SPACES);
println!("Space end = {}", last_space_end);
println!("Heap end = {}", vm_layout().heap_end);
assert_eq!(last_space_end, vm_layout().heap_end);
// after last space
assert_eq!(SFTSpaceMap::addr_to_index(last_space_end), 17);
assert_eq!(SFTSpaceMap::addr_to_index(Address::MAX), 31);
}
}
}
#[allow(dead_code)]
mod dense_chunk_map {
use super::*;
use crate::util::conversions;
use crate::util::heap::layout::vm_layout::BYTES_IN_CHUNK;
use crate::util::metadata::side_metadata::spec_defs::SFT_DENSE_CHUNK_MAP_INDEX;
use crate::util::metadata::side_metadata::*;
use std::collections::HashMap;
use std::sync::atomic::Ordering;
/// SFTDenseChunkMap is a small table. It has one entry for each space in the table, and use
/// side metadata to record the index for each chunk. This works for both 32 bits and 64 bits.
/// However, its performance is expected to be suboptimal, compared to the sparse chunk map on
/// 32 bits, and the space map on 64 bits. So usually we do not use this implementation. However,
/// it provides some flexibility so we can set SFT at chunk basis for 64bits for decent performance.
/// For example, when we use library malloc for mark sweep, we have no control of where the
/// library malloc may allocate into, so we cannot use the space map. And using a sparse chunk map
/// will be costly in terms of memory. In this case, the dense chunk map is a good solution.
pub struct SFTDenseChunkMap {
/// The dense table, one entry per space. We use side metadata to store the space index for each chunk.
/// 0 is EMPTY_SPACE_SFT.
sft: Vec<SFTRefStorage>,
/// A map from space name (assuming they are unique) to their index. We use this to know whether we have
/// pushed &dyn SFT for a space, and to know its index.
index_map: HashMap<String, usize>,
}
unsafe impl Sync for SFTDenseChunkMap {}
impl SFTMap for SFTDenseChunkMap {
fn has_sft_entry(&self, addr: Address) -> bool {
if SFT_DENSE_CHUNK_MAP_INDEX.is_mapped(addr) {
let index = Self::addr_to_index(addr);
index < self.sft.len() as u8
} else {
// We haven't mapped side metadata for the chunk, so we do not have an SFT entry for the address.
false
}
}
fn get_side_metadata(&self) -> Option<&SideMetadataSpec> {
Some(&crate::util::metadata::side_metadata::spec_defs::SFT_DENSE_CHUNK_MAP_INDEX)
}
fn get_checked(&self, address: Address) -> &dyn SFT {
if self.has_sft_entry(address) {
unsafe { self.get_unchecked(address) }
} else {
&EMPTY_SPACE_SFT
}
}
unsafe fn get_unchecked(&self, address: Address) -> &dyn SFT {
let cell = self
.sft
.get_unchecked(Self::addr_to_index(address) as usize);
cell.load()
}
fn notify_space_creation(&mut self, space: SFTRawPointer) {
// Insert the space into the SFT table, and the SFT map.
let space_name = unsafe { &*space }.name().to_string();
// We shouldn't have this space in our map yet. Otherwise, this method is called multiple times for the same space.
assert!(!self.index_map.contains_key(&space_name));
// Index for the space
let index = self.sft.len();
// Insert to hashmap and vec
self.sft.push(SFTRefStorage::new(space));
self.index_map.insert(space_name, index);
}
unsafe fn eager_initialize(&mut self, space: SFTRawPointer, start: Address, bytes: usize) {
let context = SideMetadataContext {
global: vec![SFT_DENSE_CHUNK_MAP_INDEX],
local: vec![],
};
context
.try_map_metadata_space(start, bytes, "SFTDenseChunkMap")
.unwrap_or_else(|e| {
panic!("failed to mmap metadata memory: {e}");
});
self.update(space, start, bytes);
}
unsafe fn update(
&self,
space: *const (dyn SFT + Sync + 'static),
start: Address,
bytes: usize,
) {
let index: u8 = *self.index_map.get((*space).name()).unwrap() as u8;
// Iterate through the chunks and record the space index in the side metadata.
let first_chunk = conversions::chunk_align_down(start);
let last_chunk = conversions::chunk_align_up(start + bytes);
let mut chunk = first_chunk;
debug!(
"update {} (chunk {}) to {} (chunk {})",
start,
first_chunk,
start + bytes,
last_chunk
);
while chunk < last_chunk {
trace!("Update {} to index {}", chunk, index);
SFT_DENSE_CHUNK_MAP_INDEX.store_atomic::<u8>(chunk, index, Ordering::SeqCst);
chunk += BYTES_IN_CHUNK;
}
debug!("update done");
}
unsafe fn clear(&self, address: Address) {
SFT_DENSE_CHUNK_MAP_INDEX.store_atomic::<u8>(
address,
Self::EMPTY_SFT_INDEX,
Ordering::SeqCst,
);
}
}
impl SFTDenseChunkMap {
/// Empty space is at index 0
const EMPTY_SFT_INDEX: u8 = 0;
pub fn new() -> Self {
Self {
// Empty space is at index 0
sft: vec![SFTRefStorage::default()],
index_map: HashMap::new(),
}
}
pub fn addr_to_index(addr: Address) -> u8 {
SFT_DENSE_CHUNK_MAP_INDEX.load_atomic::<u8>(addr, Ordering::Relaxed)
}
}
}
#[allow(dead_code)]
mod sparse_chunk_map {
use super::*;
use crate::util::conversions;
use crate::util::conversions::*;
use crate::util::heap::layout::vm_layout::vm_layout;
use crate::util::heap::layout::vm_layout::BYTES_IN_CHUNK;
/// The chunk map is a sparse table. It has one entry for each chunk in the address space we may use.
pub struct SFTSparseChunkMap {
sft: Vec<SFTRefStorage>,
}
unsafe impl Sync for SFTSparseChunkMap {}
impl SFTMap for SFTSparseChunkMap {
fn has_sft_entry(&self, addr: Address) -> bool {
addr.chunk_index() < vm_layout().max_chunks()
}
fn get_side_metadata(&self) -> Option<&SideMetadataSpec> {
None
}
fn get_checked(&self, address: Address) -> &dyn SFT {
if self.has_sft_entry(address) {
unsafe { self.get_unchecked(address) }
} else {
&EMPTY_SPACE_SFT
}
}
unsafe fn get_unchecked(&self, address: Address) -> &dyn SFT {
let cell = self.sft.get_unchecked(address.chunk_index());
cell.load()
}
/// Update SFT map for the given address range.
/// It should be used when we acquire new memory and use it as part of a space. For example, the cases include:
/// 1. when a space grows, 2. when initializing a contiguous space, 3. when ensure_mapped() is called on a space.
unsafe fn update(
&self,
space: *const (dyn SFT + Sync + 'static),
start: Address,
bytes: usize,
) {
if DEBUG_SFT {
self.log_update(&*space, start, bytes);
}
let first = start.chunk_index();
let last = conversions::chunk_align_up(start + bytes).chunk_index();
for chunk in first..last {
self.set(chunk, &*space);
}
if DEBUG_SFT {
self.trace_sft_map();
}
}
// TODO: We should clear a SFT entry when a space releases a chunk.
#[allow(dead_code)]
unsafe fn clear(&self, chunk_start: Address) {
if DEBUG_SFT {
debug!(
"Clear SFT for chunk {} (was {})",
chunk_start,
self.get_checked(chunk_start).name()
);
}
assert!(chunk_start.is_aligned_to(BYTES_IN_CHUNK));
let chunk_idx = chunk_start.chunk_index();
self.set(chunk_idx, &EMPTY_SPACE_SFT);
}
}
impl SFTSparseChunkMap {
pub fn new() -> Self {
SFTSparseChunkMap {
sft: std::iter::repeat_with(SFTRefStorage::default)
.take(vm_layout().max_chunks())
.collect(),
}
}
fn log_update(&self, space: &(dyn SFT + Sync + 'static), start: Address, bytes: usize) {
debug!("Update SFT for Chunk {} as {}", start, space.name(),);
let first = start.chunk_index();
let start_chunk = chunk_index_to_address(first);
debug!(
"Update SFT for {} bytes of Chunk {} #{}",
bytes, start_chunk, first
);
}
fn trace_sft_map(&self) {
trace!("{}", self.print_sft_map());
}
// This can be used during debugging to print SFT map.
fn print_sft_map(&self) -> String {
// print the entire SFT map
let mut res = String::new();
const SPACE_PER_LINE: usize = 10;
for i in (0..self.sft.len()).step_by(SPACE_PER_LINE) {
let max = if i + SPACE_PER_LINE > self.sft.len() {
self.sft.len()
} else {
i + SPACE_PER_LINE
};
let chunks: Vec<usize> = (i..max).collect();
let space_names: Vec<&str> =
chunks.iter().map(|&x| self.sft[x].load().name()).collect();
res.push_str(&format!(
"{}: {}",
chunk_index_to_address(i),
space_names.join(",")
));
res.push('\n');
}
res
}
fn set(&self, chunk: usize, sft: &(dyn SFT + Sync + 'static)) {
/*
* This is safe (only) because a) this is only called during the
* allocation and deallocation of chunks, which happens under a global
* lock, and b) it only transitions from empty to valid and valid to
* empty, so if there were a race to view the contents, in the one case
* it would either see the new (valid) space or an empty space (both of
* which are reasonable), and in the other case it would either see the
* old (valid) space or an empty space, both of which are valid.
*/
// It is okay to set empty to valid, or set valid to empty. It is wrong if we overwrite a valid value with another valid value.
if cfg!(debug_assertions) {
let old = self.sft[chunk].load().name();
let new = sft.name();
// Allow overwriting the same SFT pointer. E.g., if we have set SFT map for a space, then ensure_mapped() is called on the same,
// in which case, we still set SFT map again.
debug_assert!(
old == EMPTY_SFT_NAME || new == EMPTY_SFT_NAME || old == new,
"attempt to overwrite a non-empty chunk {} ({}) in SFT map (from {} to {})",
chunk,
crate::util::conversions::chunk_index_to_address(chunk),
old,
new
);
}
unsafe { self.sft.get_unchecked(chunk).store(sft) };
}
}
}