use std::fmt::{self, Display};
use std::sync::{Arc, RwLock};
use std::thread::ThreadId;
use foldhash::{HashMap, HashMapExt};
use nonempty::NonEmpty;
use rand::prelude::*;
use rand::rng;
use crate::fake::HardwareBuilder;
use crate::pal::{AbstractProcessor, Platform, ProcessorFacade};
use crate::{EfficiencyClass, MemoryRegionId, ProcessorId, RelativeSpeed};
#[derive(Clone, Debug)]
pub(crate) struct FakeProcessor {
id: ProcessorId,
memory_region_id: MemoryRegionId,
efficiency_class: EfficiencyClass,
relative_speed: RelativeSpeed,
model: Option<Arc<str>>,
}
impl Display for FakeProcessor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"FakeProcessor({} in region {}, {:?}, speed {}, model {:?})",
self.id, self.memory_region_id, self.efficiency_class, self.relative_speed, self.model
)
}
}
impl FakeProcessor {
pub(crate) fn new(
id: ProcessorId,
memory_region_id: MemoryRegionId,
efficiency_class: EfficiencyClass,
relative_speed: RelativeSpeed,
model: Option<Arc<str>>,
) -> Self {
Self {
id,
memory_region_id,
efficiency_class,
relative_speed,
model,
}
}
}
impl AbstractProcessor for FakeProcessor {
fn id(&self) -> ProcessorId {
self.id
}
fn memory_region_id(&self) -> MemoryRegionId {
self.memory_region_id
}
fn efficiency_class(&self) -> EfficiencyClass {
self.efficiency_class
}
fn relative_speed(&self) -> RelativeSpeed {
self.relative_speed
}
fn model(&self) -> Option<&str> {
self.model.as_deref()
}
}
#[derive(Clone, Debug, Default)]
struct FakeThreadState {
allowed_processors: Option<NonEmpty<ProcessorId>>,
}
#[derive(Debug)]
pub(crate) struct FakePlatform {
processors: Vec<FakeProcessor>,
max_processor_id: ProcessorId,
max_memory_region_id: MemoryRegionId,
max_processor_time: f64,
thread_states: RwLock<HashMap<ThreadId, FakeThreadState>>,
#[cfg(test)]
processor_id_override: RwLock<Option<ProcessorId>>,
}
impl FakePlatform {
pub(crate) fn from_builder(builder: &HardwareBuilder) -> Self {
let configured_processors = builder.build_processors();
assert!(
!configured_processors.is_empty(),
"at least one processor must be configured"
);
let processors: Vec<FakeProcessor> = configured_processors
.iter()
.map(|p| {
FakeProcessor::new(
p.id,
p.memory_region_id,
p.efficiency_class,
p.relative_speed,
p.model.clone(),
)
})
.collect();
let max_processor_id = processors.iter().map(|p| p.id).max().unwrap_or(0);
let max_memory_region_id = processors
.iter()
.map(|p| p.memory_region_id)
.max()
.unwrap_or(0);
#[expect(
clippy::cast_precision_loss,
reason = "processor count is small enough for precise f64 representation"
)]
let max_processor_time = builder
.build_max_processor_time()
.unwrap_or(processors.len() as f64);
Self {
processors,
max_processor_id,
max_memory_region_id,
max_processor_time,
thread_states: RwLock::new(HashMap::new()),
#[cfg(test)]
processor_id_override: RwLock::new(None),
}
}
fn thread_processor_id(&self, thread_id: ThreadId) -> ProcessorId {
let allowed = {
let states = self
.thread_states
.read()
.expect("thread state lock should never be poisoned");
states
.get(&thread_id)
.and_then(|s| s.allowed_processors.clone())
};
match allowed {
Some(processors) => {
*processors
.iter()
.choose(&mut rng())
.expect("allowed processors is non-empty")
}
None => {
self.processors
.iter()
.choose(&mut rng())
.expect("at least one processor was configured")
.id
}
}
}
#[cfg(test)]
pub(crate) fn set_processor_id_override(&self, id: Option<ProcessorId>) {
*self
.processor_id_override
.write()
.expect("processor_id_override lock should never be poisoned") = id;
}
}
impl Platform for FakePlatform {
fn get_all_processors(&self) -> NonEmpty<ProcessorFacade> {
let facades: Vec<ProcessorFacade> = self
.processors
.iter()
.map(|p| ProcessorFacade::Fake(p.clone()))
.collect();
NonEmpty::from_vec(facades).expect("at least one processor was configured")
}
fn pin_current_thread_to<P>(&self, processors: &NonEmpty<P>)
where
P: AsRef<ProcessorFacade>,
{
let thread_id = std::thread::current().id();
let processor_ids: Vec<ProcessorId> = processors.iter().map(|p| p.as_ref().id()).collect();
let processor_ids = NonEmpty::from_vec(processor_ids)
.expect("processors is non-empty so the resulting vec is also non-empty");
let mut states = self
.thread_states
.write()
.expect("thread state lock should never be poisoned");
let state = states.entry(thread_id).or_default();
state.allowed_processors = Some(processor_ids);
}
fn current_processor_id(&self) -> ProcessorId {
#[cfg(test)]
if let Some(id) = *self.processor_id_override.read().unwrap() {
return id;
}
let thread_id = std::thread::current().id();
self.thread_processor_id(thread_id)
}
fn current_thread_processors(&self) -> NonEmpty<ProcessorId> {
let thread_id = std::thread::current().id();
let states = self
.thread_states
.read()
.expect("thread state lock should never be poisoned");
match states
.get(&thread_id)
.and_then(|s| s.allowed_processors.clone())
{
Some(processors) => processors,
None => {
let all_ids: Vec<ProcessorId> = self.processors.iter().map(|p| p.id).collect();
NonEmpty::from_vec(all_ids).expect("at least one processor was configured")
}
}
}
fn max_processor_id(&self) -> ProcessorId {
self.max_processor_id
}
fn max_memory_region_id(&self) -> MemoryRegionId {
self.max_memory_region_id
}
fn max_processor_time(&self) -> f64 {
self.max_processor_time
}
fn active_processor_count(&self) -> usize {
self.processors.len()
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
#[allow(clippy::indexing_slicing, reason = "test code, panics are acceptable")]
mod tests {
use new_zealand::nz;
use super::*;
use crate::fake::ProcessorBuilder;
#[test]
fn basic_hardware_creation() {
let builder = HardwareBuilder::from_counts(nz!(4), nz!(1));
let backend = FakePlatform::from_builder(&builder);
assert_eq!(backend.processors.len(), 4);
assert_eq!(backend.max_processor_id, 3);
assert_eq!(backend.max_memory_region_id, 0);
assert_eq!(backend.active_processor_count(), 4);
}
#[test]
fn custom_memory_regions() {
let builder = HardwareBuilder::from_counts(nz!(4), nz!(2));
let backend = FakePlatform::from_builder(&builder);
assert_eq!(backend.processors[0].memory_region_id, 0);
assert_eq!(backend.processors[1].memory_region_id, 1);
assert_eq!(backend.processors[2].memory_region_id, 0);
assert_eq!(backend.processors[3].memory_region_id, 1);
assert_eq!(backend.max_memory_region_id, 1);
}
#[test]
fn custom_processor_configuration() {
let builder = HardwareBuilder::new()
.processor(
ProcessorBuilder::new()
.id(0)
.memory_region(0)
.efficiency_class(EfficiencyClass::Performance),
)
.processor(
ProcessorBuilder::new()
.id(1)
.memory_region(1)
.efficiency_class(EfficiencyClass::Efficiency),
);
let backend = FakePlatform::from_builder(&builder);
assert_eq!(backend.processors.len(), 2);
assert_eq!(
backend.processors[0].efficiency_class,
EfficiencyClass::Performance
);
assert_eq!(
backend.processors[1].efficiency_class,
EfficiencyClass::Efficiency
);
assert_eq!(backend.max_memory_region_id, 1);
}
#[test]
fn max_processor_time_configuration() {
let builder = HardwareBuilder::from_counts(nz!(4), nz!(1)).max_processor_time(2.5);
let backend = FakePlatform::from_builder(&builder);
assert!((backend.max_processor_time - 2.5).abs() < f64::EPSILON);
}
#[test]
fn current_processor_id_is_from_valid_set() {
let builder = HardwareBuilder::from_counts(nz!(4), nz!(1));
let backend = FakePlatform::from_builder(&builder);
let id = backend.current_processor_id();
assert!(id < 4);
}
#[test]
fn get_all_processors_returns_all() {
let builder = HardwareBuilder::from_counts(nz!(4), nz!(1));
let backend = FakePlatform::from_builder(&builder);
let processors = backend.get_all_processors();
assert_eq!(processors.len(), 4);
}
#[test]
fn pin_current_thread_to_restricts_processor_selection() {
let builder = HardwareBuilder::from_counts(nz!(4), nz!(1));
let backend = FakePlatform::from_builder(&builder);
let processor = backend.get_all_processors().head;
let processor_id = processor.id();
let processors = NonEmpty::singleton(processor);
backend.pin_current_thread_to(&processors);
let id = backend.current_processor_id();
assert_eq!(id, processor_id);
}
#[test]
fn current_thread_processors_returns_all_when_not_pinned() {
let builder = HardwareBuilder::from_counts(nz!(4), nz!(1));
let backend = FakePlatform::from_builder(&builder);
let processors = backend.current_thread_processors();
assert_eq!(processors.len(), 4);
}
#[test]
fn current_thread_processors_returns_pinned_set() {
let builder = HardwareBuilder::from_counts(nz!(4), nz!(1));
let backend = FakePlatform::from_builder(&builder);
let all = backend.get_all_processors();
let mut iter = all.iter();
let p0 = iter.next().unwrap().clone();
let p1 = iter.next().unwrap().clone();
let p0_id = p0.id();
let p1_id = p1.id();
let pinned = NonEmpty::from((p0, vec![p1]));
backend.pin_current_thread_to(&pinned);
let thread_processors = backend.current_thread_processors();
assert_eq!(thread_processors.len(), 2);
assert!(thread_processors.iter().any(|&id| id == p0_id));
assert!(thread_processors.iter().any(|&id| id == p1_id));
}
#[test]
fn thread_processor_id_selects_from_pinned_set() {
let builder = HardwareBuilder::from_counts(nz!(8), nz!(1));
let backend = FakePlatform::from_builder(&builder);
let all = backend.get_all_processors();
let processors_vec: Vec<_> = all.iter().cloned().collect();
let p2 = processors_vec[2].clone();
let p3 = processors_vec[3].clone();
let p2_id = p2.id();
let p3_id = p3.id();
let pinned = NonEmpty::from((p2, vec![p3]));
backend.pin_current_thread_to(&pinned);
let id = backend.current_processor_id();
assert!(id == p2_id || id == p3_id);
}
#[test]
fn fake_processor_display_includes_all_fields() {
let processor = FakeProcessor::new(
5,
2,
EfficiencyClass::Efficiency,
RelativeSpeed::from_raw(3600),
Some(Arc::from("Test Model")),
);
let display = format!("{processor}");
assert!(display.contains('5'));
assert!(display.contains('2'));
assert!(display.contains("Efficiency"));
assert!(display.contains("3600"));
assert!(display.contains("Test Model"));
}
#[test]
fn fake_processor_efficiency_class_returns_configured_value() {
let perf = FakeProcessor::new(
0,
0,
EfficiencyClass::Performance,
RelativeSpeed::from_raw(1),
None,
);
let eff = FakeProcessor::new(
1,
0,
EfficiencyClass::Efficiency,
RelativeSpeed::from_raw(1),
None,
);
assert_eq!(perf.efficiency_class(), EfficiencyClass::Performance);
assert_eq!(eff.efficiency_class(), EfficiencyClass::Efficiency);
}
#[test]
fn fake_processor_id_returns_configured_value() {
let processor = FakeProcessor::new(
42,
0,
EfficiencyClass::Performance,
RelativeSpeed::from_raw(1),
None,
);
assert_eq!(processor.id(), 42);
}
#[test]
fn fake_processor_memory_region_id_returns_configured_value() {
let processor = FakeProcessor::new(
0,
7,
EfficiencyClass::Performance,
RelativeSpeed::from_raw(1),
None,
);
assert_eq!(processor.memory_region_id(), 7);
}
#[test]
fn fake_processor_relative_speed_returns_configured_value() {
let processor = FakeProcessor::new(
0,
0,
EfficiencyClass::Performance,
RelativeSpeed::from_raw(2400),
None,
);
assert_eq!(processor.relative_speed().as_u64(), 2400);
}
#[test]
fn fake_processor_model_returns_configured_value() {
let with_model = FakeProcessor::new(
0,
0,
EfficiencyClass::Performance,
RelativeSpeed::from_raw(1),
Some(Arc::from("Fake Model")),
);
let without_model = FakeProcessor::new(
1,
0,
EfficiencyClass::Performance,
RelativeSpeed::from_raw(1),
None,
);
assert_eq!(with_model.model(), Some("Fake Model"));
assert_eq!(without_model.model(), None);
}
#[test]
fn relative_speed_flows_through_builder() {
let builder = HardwareBuilder::new()
.processor(ProcessorBuilder::new().id(0).relative_speed(nz!(1800)))
.processor(ProcessorBuilder::new().id(1));
let backend = FakePlatform::from_builder(&builder);
assert_eq!(backend.processors[0].relative_speed().as_u64(), 1800);
assert_eq!(
backend.processors[1].relative_speed(),
RelativeSpeed::from_raw(1)
);
}
#[test]
fn model_flows_through_builder() {
let builder = HardwareBuilder::new()
.processor(ProcessorBuilder::new().id(0).model("Configured Model"))
.processor(ProcessorBuilder::new().id(1));
let backend = FakePlatform::from_builder(&builder);
assert_eq!(backend.processors[0].model(), Some("Configured Model"));
assert_eq!(backend.processors[1].model(), None);
}
}