#![expect(clippy::indexing_slicing)]
use alloc::{borrow::ToOwned as _, vec};
use core::{
ffi::CStr,
mem::MaybeUninit,
ptr::{self, NonNull},
};
use darwin_kperf_sys::{
kperf::{KPC_CLASS_CONFIGURABLE_MASK, KPC_MAX_COUNTERS, kpc_config_t},
kperfdata::{kpep_db, kpep_event},
};
use super::{Sampler, error::SamplerError, thread::ThreadSampler};
use crate::{
KPerf, KPerfData,
event::{Cpu, Event, EventInfo as _, ResolvedEvent},
sampler::error::{try_kpc, try_kpep},
utils::DropGuard,
};
fn resolve_array<const N: usize>(
cpu: Cpu,
events: [Event; N],
) -> Result<[ResolvedEvent; N], SamplerError> {
let mut resolved = [MaybeUninit::uninit(); N];
for (index, event) in events.into_iter().enumerate() {
let event = event.on(cpu).ok_or(SamplerError::EventUnavailable(event))?;
resolved[index].write(event);
}
Ok(resolved.map(|value| unsafe { value.assume_init() }))
}
pub(crate) fn ll_configure<const N: usize>(
sampler: &Sampler,
events: [Event; N],
) -> Result<ThreadSampler<'_, N>, SamplerError> {
let kpc_vt = sampler.kperf.vtable();
let kpep_vt = sampler.kperfdata.vtable();
let mut force_ctrs = 0;
try_kpc(
unsafe { (kpc_vt.kpc_force_all_ctrs_get)(&raw mut force_ctrs) },
|_| SamplerError::MissingPrivileges,
)?;
let mut config = ptr::null_mut();
try_kpep(unsafe { (kpep_vt.kpep_config_create)(sampler.db.as_ptr(), &raw mut config) })?;
let Some(config) = NonNull::new(config) else {
return Err(SamplerError::UnexpectedNullPointer);
};
let config = DropGuard::new(config, |config| {
unsafe { (kpep_vt.kpep_config_free)(config.as_ptr()) }
});
try_kpep(unsafe { (kpep_vt.kpep_config_force_counters)(config.as_ptr()) })?;
let mut event_pointers = [ptr::null_mut(); N];
let resolved_events = resolve_array(sampler.cpu, events)?;
for (index, event) in resolved_events.iter().enumerate() {
try_kpep(unsafe {
(kpep_vt.kpep_db_event)(
sampler.db.as_ptr(),
event.c_name().as_ptr(),
&raw mut event_pointers[index],
)
})?;
}
for event_pointer in &mut event_pointers {
try_kpep(unsafe {
(kpep_vt.kpep_config_add_event)(config.as_ptr(), event_pointer, 0, ptr::null_mut())
})?;
}
let mut classes = 0_u32;
let mut reg_count = 0_usize;
let mut regs = [0 as kpc_config_t; KPC_MAX_COUNTERS];
let mut counter_map = [0_usize; N];
try_kpep(unsafe { (kpep_vt.kpep_config_kpc_classes)(config.as_ptr(), &raw mut classes) })?;
try_kpep(unsafe { (kpep_vt.kpep_config_kpc_count)(config.as_ptr(), &raw mut reg_count) })?;
try_kpep(unsafe {
(kpep_vt.kpep_config_kpc_map)(
config.as_ptr(),
counter_map.as_mut_ptr(),
size_of_val(&counter_map),
)
})?;
try_kpep(unsafe {
(kpep_vt.kpep_config_kpc)(config.as_ptr(), regs.as_mut_ptr(), size_of_val(®s))
})?;
if (classes & KPC_CLASS_CONFIGURABLE_MASK) != 0 && reg_count > 0 {
try_kpc(
unsafe { (kpc_vt.kpc_set_config)(classes, regs.as_mut_ptr()) },
SamplerError::FailedToSetKpcConfig,
)?;
}
let config = DropGuard::dismiss(config);
Ok(ThreadSampler::new(sampler, config, classes, counter_map))
}
pub(crate) fn ll_init() -> Result<Sampler, SamplerError> {
let kperf = KPerf::new()?;
let kperfdata = KPerfData::new()?;
let kpc_vt = kperf.vtable();
let kpep_vt = kperfdata.vtable();
let mut db = ptr::null_mut();
try_kpep(unsafe { (kpep_vt.kpep_db_create)(ptr::null(), &raw mut db) })?;
let Some(db) = NonNull::new(db) else {
return Err(SamplerError::UnexpectedNullPointer);
};
let db = DropGuard::new(db, |db| {
unsafe { (kpep_vt.kpep_db_free)(db.as_ptr()) }
});
let name = unsafe { db.as_ref().name };
let name = unsafe { CStr::from_ptr(name) };
let name = name.to_str().map_err(|_err| SamplerError::InvalidCpuName)?;
let cpu = Cpu::from_db_name(name).ok_or_else(|| SamplerError::UnknownCpu(name.to_owned()))?;
let mut saved_force_all = 0;
try_kpc(
unsafe { (kpc_vt.kpc_force_all_ctrs_get)(&raw mut saved_force_all) },
|_| SamplerError::MissingPrivileges,
)?;
try_kpc(
unsafe { (kpc_vt.kpc_force_all_ctrs_set)(1) },
SamplerError::FailedToForceAllCounters,
)?;
let clear_force = DropGuard::new((), |()| {
let _result = unsafe { (kpc_vt.kpc_force_all_ctrs_set)(saved_force_all) };
});
if cfg!(any(debug_assertions, feature = "runtime-assertions")) {
verify_event_stride(kpep_vt, db.as_ptr())?;
}
DropGuard::dismiss(clear_force);
let db = DropGuard::dismiss(db);
Ok(Sampler {
kperf,
kperfdata,
db,
cpu,
saved_force_all,
})
}
pub(crate) fn ll_drop(sampler: &Sampler) {
let kpc_vt = sampler.kperf.vtable();
let kpep_vt = sampler.kperfdata.vtable();
let _result = unsafe { (kpc_vt.kpc_force_all_ctrs_set)(sampler.saved_force_all) };
unsafe {
(kpep_vt.kpep_db_free)(sampler.db.as_ptr());
}
}
fn verify_event_stride(
vt: &darwin_kperf_sys::kperfdata::VTable,
db: *mut kpep_db,
) -> Result<(), SamplerError> {
let mut count: usize = 0;
try_kpep(unsafe { (vt.kpep_db_events_count)(db, &raw mut count) })?;
if count < 2 {
return Ok(());
}
let mut buf = vec![ptr::null_mut::<kpep_event>(); count];
try_kpep(unsafe {
(vt.kpep_db_events)(db, buf.as_mut_ptr(), count * size_of::<*mut kpep_event>())
})?;
let mut addrs: alloc::vec::Vec<usize> = buf.iter().map(|ptr| ptr.addr()).collect();
addrs.sort_unstable();
let expected = size_of::<kpep_event>();
let min_stride = addrs
.windows(2)
.map(|pair| pair[1] - pair[0])
.filter(|&delta| delta > 0)
.min();
if let Some(stride) = min_stride
&& stride != expected
{
return Err(SamplerError::EventLayoutMismatch {
expected,
actual: stride,
});
}
Ok(())
}