159 lines
6 KiB
Rust
159 lines
6 KiB
Rust
//! Counters the native boot window reads once per frame (the "Ignition Field"). The loader's
|
|
//! threads only ever do a relaxed atomic add or store here: no lock, no allocation (so nothing
|
|
//! of the compiler zone is kept in a static), and the window reads them at its own pace.
|
|
//!
|
|
//! An arm of the field is a crate: the first `ARMS - 1` crates that get a function installed each
|
|
//! open an arm of their own (in the order the program first needs them), the rest share the last
|
|
//! one.
|
|
use std::sync::atomic::{AtomicU32, AtomicU64, AtomicU8, Ordering::Relaxed};
|
|
|
|
pub const ARMS: usize = 16;
|
|
/// The arm every crate past the first `ARMS - 1` shares.
|
|
pub const REST: usize = ARMS - 1;
|
|
const CRATES: usize = 1024;
|
|
const NAME_WORDS: usize = 3;
|
|
pub const NAME_BYTES: usize = NAME_WORDS * 8;
|
|
const UNSET: u8 = 0xff;
|
|
/// O3 installs the window can still see when it falls behind (older ones are skipped).
|
|
pub const O3_RING: usize = 1024;
|
|
|
|
#[allow(clippy::declare_interior_mutable_const)]
|
|
const ZERO32: AtomicU32 = AtomicU32::new(0);
|
|
#[allow(clippy::declare_interior_mutable_const)]
|
|
const ZERO64: AtomicU64 = AtomicU64::new(0);
|
|
#[allow(clippy::declare_interior_mutable_const)]
|
|
const UNSET8: AtomicU8 = AtomicU8::new(UNSET);
|
|
#[allow(clippy::declare_interior_mutable_const)]
|
|
const NAME: [AtomicU64; NAME_WORDS] = [ZERO64; NAME_WORDS];
|
|
|
|
/// Functions installed per arm (compiled this run, or linked from the cache).
|
|
static FNS: [AtomicU32; ARMS] = [ZERO32; ARMS];
|
|
static ARM_OF_CRATE: [AtomicU8; CRATES] = [UNSET8; CRATES];
|
|
static ARMS_OPEN: AtomicU32 = AtomicU32::new(0);
|
|
static ARM_NAME: [[AtomicU64; NAME_WORDS]; ARMS] = [NAME; ARMS];
|
|
|
|
pub static FILES_PARSED: AtomicU32 = AtomicU32::new(0);
|
|
pub static LINES_PARSED: AtomicU64 = AtomicU64::new(0);
|
|
/// ms since process start (clock::ms_since_process_start) of main()'s first call; 0 before it
|
|
pub static MAIN_AT_MS: AtomicU64 = AtomicU64::new(0);
|
|
|
|
pub static O3_INSTALLED: AtomicU32 = AtomicU32::new(0);
|
|
pub static O3_SUPERSEDED: AtomicU32 = AtomicU32::new(0);
|
|
static O3_ARMS: [AtomicU8; O3_RING] = [UNSET8; O3_RING];
|
|
/// The function of each ring entry's install, to pick the star it ignites (hash of its slot).
|
|
static O3_KEYS: [AtomicU32; O3_RING] = [ZERO32; O3_RING];
|
|
|
|
/// The first settle (the compiles' state dropped, the heap trimmed): its end in ms since process
|
|
/// start + 1 (0: not yet), and the resident set (MB) around it.
|
|
pub static SETTLE_END_MS: AtomicU64 = AtomicU64::new(0);
|
|
pub static SETTLE_RSS_BEFORE_MB: AtomicU32 = AtomicU32::new(0);
|
|
pub static SETTLE_RSS_AFTER_MB: AtomicU32 = AtomicU32::new(0);
|
|
|
|
/// The arm of crate `krate`; `name` is asked only when the crate opens an arm.
|
|
pub fn arm_of(krate: u32, name: impl FnOnce() -> [u8; NAME_BYTES]) -> usize {
|
|
let Some(slot) = ARM_OF_CRATE.get(krate as usize) else {
|
|
return REST;
|
|
};
|
|
let arm = slot.load(Relaxed);
|
|
if arm != UNSET {
|
|
return arm as usize;
|
|
}
|
|
let next = ARMS_OPEN.fetch_add(1, Relaxed) as usize;
|
|
let arm = next.min(REST);
|
|
if arm < REST {
|
|
let bytes = name();
|
|
for (word, chunk) in ARM_NAME[arm].iter().zip(bytes.chunks(8)) {
|
|
word.store(u64::from_le_bytes(chunk.try_into().unwrap()), Relaxed);
|
|
}
|
|
}
|
|
// two threads can race for a crate's first function: the later store wins, the other
|
|
// thread's arm number stays unused
|
|
slot.store(arm as u8, Relaxed);
|
|
arm
|
|
}
|
|
|
|
/// A crate name cut to the name field (zero padded).
|
|
pub fn name_bytes(name: &str) -> [u8; NAME_BYTES] {
|
|
let mut out = [0; NAME_BYTES];
|
|
let n = name.len().min(NAME_BYTES);
|
|
out[..n].copy_from_slice(&name.as_bytes()[..n]);
|
|
out
|
|
}
|
|
|
|
pub fn fn_installed(arm: usize) {
|
|
FNS[arm.min(REST)].fetch_add(1, Relaxed);
|
|
}
|
|
|
|
pub fn fns(arm: usize) -> u32 {
|
|
FNS[arm.min(REST)].load(Relaxed)
|
|
}
|
|
|
|
/// The arms opened so far (REST included once it is in use).
|
|
pub fn arms_open() -> usize {
|
|
(ARMS_OPEN.load(Relaxed) as usize).min(ARMS)
|
|
}
|
|
|
|
pub fn arm_name(arm: usize) -> [u8; NAME_BYTES] {
|
|
let mut out = [0; NAME_BYTES];
|
|
if arm < REST {
|
|
for (chunk, word) in out.chunks_mut(8).zip(&ARM_NAME[arm]) {
|
|
chunk.copy_from_slice(&word.load(Relaxed).to_le_bytes());
|
|
}
|
|
}
|
|
out
|
|
}
|
|
|
|
pub fn o3_installed(arm: usize, key: u32) {
|
|
let n = O3_INSTALLED.fetch_add(1, Relaxed) as usize % O3_RING;
|
|
O3_KEYS[n].store(key, Relaxed);
|
|
O3_ARMS[n].store(arm.min(REST) as u8, Relaxed);
|
|
}
|
|
|
|
/// The `index`-th O3 install overall (arm, key), while it is within the last `O3_RING`.
|
|
pub fn o3_install(index: u32) -> (usize, u32) {
|
|
let n = index as usize % O3_RING;
|
|
(O3_ARMS[n].load(Relaxed) as usize % ARMS, O3_KEYS[n].load(Relaxed))
|
|
}
|
|
|
|
pub fn settle_done(rss_before: u64, rss_after: u64) {
|
|
if SETTLE_END_MS.load(Relaxed) != 0 {
|
|
return;
|
|
}
|
|
SETTLE_RSS_BEFORE_MB.store((rss_before >> 20) as u32, Relaxed);
|
|
SETTLE_RSS_AFTER_MB.store((rss_after >> 20) as u32, Relaxed);
|
|
SETTLE_END_MS.store(crate::clock::ms_since_process_start() as u64 + 1, Relaxed);
|
|
}
|
|
|
|
pub fn main_called() {
|
|
MAIN_AT_MS.store(crate::clock::ms_since_process_start() as u64 + 1, Relaxed);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
// one test: the counters are process-wide statics
|
|
#[test]
|
|
fn arms_open_in_order_and_the_rest_share_one() {
|
|
for krate in 0..40u32 {
|
|
let arm = arm_of(900 + krate, || name_bytes(&format!("crate_{krate}")));
|
|
assert_eq!(arm, (krate as usize).min(REST));
|
|
// the same crate keeps its arm
|
|
assert_eq!(arm_of(900 + krate, || unreachable!()), arm);
|
|
}
|
|
let name = arm_name(3);
|
|
assert!(name.starts_with(b"crate_3\0"));
|
|
fn_installed(3);
|
|
fn_installed(3);
|
|
fn_installed(REST + 5);
|
|
assert_eq!(fns(3), 2);
|
|
assert_eq!(fns(REST), 1);
|
|
// a crate past the table is the rest arm and does not open one
|
|
assert_eq!(arm_of(100_000, || unreachable!()), REST);
|
|
let long = name_bytes("a_crate_name_longer_than_the_field");
|
|
assert_eq!(&long[..], b"a_crate_name_longer_than");
|
|
o3_installed(2, 77);
|
|
let n = O3_INSTALLED.load(Relaxed) - 1;
|
|
assert_eq!(o3_install(n), (2, 77));
|
|
}
|
|
}
|