Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,11 @@ rustflags = ["-Dwarnings"]

[target.aarch64-unknown-toyos]
rustflags = ["-Dwarnings"]

# `tests/guest/osbench`'s Linux build, static so a bench runs it without a
# toolchain, linked by the toolchain's own LLD so no C toolchain is needed, and
# with the frame pointers the ToyOS targets keep, so both builds run the same
# code.
[target.x86_64-unknown-linux-musl]
linker = "rust-lld"
rustflags = ["-Dwarnings", "-Cforce-frame-pointers=yes"]
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ members = [
"system/vdagent/bridge",
"tests/guest/kernelprobe",
"tests/guest/metalprobe",
"tests/guest/osbench",
"tests/guest/pciholder",
"tests/guest/test-runner",
"tests/metal",
Expand Down
1 change: 1 addition & 0 deletions build/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3111,6 +3111,7 @@ mod tests {
"images/diag.toml",
"images/console.toml",
"tests/images/acpicase.toml",
"tests/images/benchcase.toml",
"tests/images/clipboardcase.toml",
"tests/images/drivescase.toml",
"tests/images/hostedclangcase.toml",
Expand Down
10 changes: 5 additions & 5 deletions build/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -802,9 +802,9 @@ fn run_control(root: &Path, control: &Control) -> Result<String, String> {
/// end, is a test that fills the host's disk one run at a time.
///
/// Clippy needs none of the ToyOS toolchain — the kernel and the bootloader
/// lint against every architecture's bare targets ([`crate::clippy::BARE_TARGETS`]),
/// which any rustup installs, and userland carries no clippy shape
/// (`build/clippy.rs`).
/// lint against every architecture's bare targets, and osbench's Linux half
/// against its Linux one ([`crate::clippy::TARGETS`]), which any rustup
/// installs; no other userland carries a clippy shape (`build/clippy.rs`).
///
/// In a job that carries the cache ([`cicache::carried`]) the restored entry is
/// read before any step. A developer's tree keeps the dates its edits gave it.
Expand Down Expand Up @@ -870,10 +870,10 @@ fn host(root: &Path, scope: Result<(Scope, String), String>) -> Vec<Step> {
}),
licences(),
]);
steps.push(step("clippy and the bare targets", || {
steps.push(step("clippy and its targets", || {
for args in [
vec!["component", "add", "clippy"],
[&["target", "add"][..], &crate::clippy::BARE_TARGETS].concat(),
[&["target", "add"][..], &crate::clippy::TARGETS].concat(),
] {
let status = Command::new("rustup").args(&args).status().map_err(|e| e.to_string())?;
if !status.success() {
Expand Down
17 changes: 13 additions & 4 deletions build/clippy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ const SHAPES: &[Shape] = &[
before: &["-p", "toyos", "--lib", "--features", "fd-table"],
after: &["$ADOPTED", "-D", "warnings"],
},
// osbench's Linux half, which no image builds: a check, so nothing links.
Shape {
before: &["-p", "osbench", "--target", OSBENCH_LINUX],
after: &["$ADOPTED", "-D", "warnings"],
},
// Its own target directory: cargo keeps one check of a unit, so a unit
// linted under two sets of lints is checked again by each, every run, and
// so is every crate depending on it.
Expand All @@ -224,10 +229,14 @@ const SHAPES: &[Shape] = &[
},
];

/// The bare targets the kernel and bootloader shapes lint against, which
/// `rustup target add` installs: every architecture's.
pub const BARE_TARGETS: [&str; 4] =
[Arch::X86_64.kernel(), Arch::X86_64.loader(), Arch::Aarch64.kernel(), Arch::Aarch64.loader()];
/// The Linux target `tests/guest/osbench` is built for beside ToyOS.
const OSBENCH_LINUX: &str = "x86_64-unknown-linux-musl";

/// The targets the shapes lint against, which `rustup target add` installs:
/// every architecture's bare targets for the kernel and bootloader, and
/// osbench's Linux one.
pub const TARGETS: [&str; 5] =
[Arch::X86_64.kernel(), Arch::X86_64.loader(), Arch::Aarch64.kernel(), Arch::Aarch64.loader(), OSBENCH_LINUX];

impl Shape {
/// The command as a reader writes it, its tokens unexpanded.
Expand Down
19 changes: 19 additions & 0 deletions issues/osbench-builds-for-no-aarch64-toyos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
status: open
kind: defect
opened: 2026-10-22
---

# osbench builds for no AArch64 ToyOS

`tests/guest/osbench` names the CPU a thread runs on, and the cores CPUs
share, to stage and check its placement benchmarks. Linux answers both itself
(`sched_getcpu`, sysfs topology). ToyOS has no call for either, so osbench
reads them with x86-64's CPUID (`src/arch/x86_64.rs`). AArch64 offers no EL0
equivalent: `MPIDR_EL1` is not readable at EL0. So `src/arch/mod.rs` refuses
`aarch64-unknown-toyos` with a `compile_error!`, and no AArch64 machine can be
compared.

**Exit**: osbench builds and runs its placement benchmarks on an AArch64 ToyOS
guest, either from a ToyOS call that names a thread's CPU and its core, or from
an EL0-readable register the kernel sets per CPU.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
status: open
kind: tooling
opened: 2026-10-21
---

# osbench times CPUs leaving idle, not their settled clock

`tests/guest/osbench` runs each benchmark straight after the last one, and
its clock gauge, `cpu.chunk`, is a 2 ms single-threaded burst at the start
of each group. So both read a CPU on its way out of idle, and how fast it
gets there is each system's idle and frequency policy rather than the
workload.

Measured on the T14 (osbench at 951c7d1, Linux with intel_pstate powersave
and EPP balance_performance, ToyOS with HWP at EPP 0x80, idling in `hlt`): the
same chunk took 10.5 µs on Linux in every group, and 7.9 µs, then 3.3 µs,
across ToyOS's groups. Under all-CPU load the two run one clock: 2.389 M and
2.371 M chunks/s machine-wide. Dividing a result by its group's `cpu.chunk`
is therefore no normalisation: the memory bandwidths, equal raw, come out a
quarter apart.

**Exit**: each benchmark runs after a steady-state warm-up of the CPUs it
uses and prints the chunk time measured beside it, and a T14 run on each
system shows `cpu.chunk` agreeing with the all-CPU rate per CPU within the
run-to-run spread.
17 changes: 17 additions & 0 deletions tests/guest/osbench/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "osbench"
description = "Scheduler and system benchmarks run unchanged on ToyOS and on Linux, so the two can be compared on one machine: one result line per measured distribution."
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"

[target.'cfg(target_os = "toyos")'.dependencies]
toyos-abi = { path = "../../../sdk/abi" }
toyos = { path = "../../../sdk/toyos" }

[target.'cfg(target_os = "linux")'.dependencies]
libc = "0.2"

# The upstream compiler that builds the Linux side knows no ToyOS target.
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("toyos"))'] }
13 changes: 13 additions & 0 deletions tests/guest/osbench/src/arch/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! What ToyOS's half of `sys` names CPUs by: the architecture's own reading of
//! the CPU a thread runs on and of the cores CPUs share.

#[cfg(target_arch = "x86_64")]
mod x86_64;
#[cfg(target_arch = "x86_64")]
pub use x86_64::{core_of, cpu_model, current_cpu, threads_per_core};

#[cfg(not(target_arch = "x86_64"))]
compile_error!(
"osbench names a ToyOS CPU by its x86-64 APIC ID, and has no AArch64 equivalent: \
issues/osbench-builds-for-no-aarch64-toyos.md"
);
64 changes: 64 additions & 0 deletions tests/guest/osbench/src/arch/x86_64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! CPU identity and topology as x86-64's CPUID reads them, which answers the
//! same under any OS.

use core::arch::x86_64::__cpuid_count;
use std::sync::OnceLock;

struct Topology {
/// CPUID leaf 0BH enumerates topology (SDM Vol. 2A: a subleaf 0 whose
/// EBX[15:0] is 0 enumerates nothing).
x2apic: bool,
/// Right shift from an APIC ID to its core's ID.
smt_shift: u32,
}

fn topology() -> &'static Topology {
static TOPOLOGY: OnceLock<Topology> = OnceLock::new();
TOPOLOGY.get_or_init(|| {
let max = __cpuid_count(0, 0).eax;
let leaf_b = __cpuid_count(0xb, 0);
if max >= 0xb && leaf_b.ebx & 0xffff != 0 {
// EAX[4:0] at the SMT level: the shift to the next level's ID.
return Topology { x2apic: true, smt_shift: leaf_b.eax & 0x1f };
}
// AMD without leaf 0BH: CPUID 8000_001EH EBX[15:8] is threads per
// core minus one (APM Vol. 3), where 8000_0001H ECX[22] says the
// topology extensions exist.
let max_ext = __cpuid_count(0x8000_0000, 0).eax;
let extensions = max_ext >= 0x8000_001e && __cpuid_count(0x8000_0001, 0).ecx & (1 << 22) != 0;
let threads = if extensions { ((__cpuid_count(0x8000_001e, 0).ebx >> 8) & 0xff) + 1 } else { 1 };
Topology { x2apic: false, smt_shift: threads.next_power_of_two().trailing_zeros() }
})
}

/// The APIC ID of the CPU that ran this: leaf 0BH's EDX, or leaf 01H's
/// EBX[31:24] where leaf 0BH enumerates nothing. Stale as soon as the
/// scheduler moves the thread.
pub fn current_cpu() -> u32 {
if topology().x2apic {
__cpuid_count(0xb, 0).edx
} else {
__cpuid_count(1, 0).ebx >> 24
}
}

/// The core an APIC ID belongs to: two CPUs of one core are SMT siblings.
pub fn core_of(apic: u32) -> u32 {
apic >> topology().smt_shift
}

pub fn threads_per_core() -> u32 {
1 << topology().smt_shift
}

/// The processor brand string, CPUID 8000_0002H to 8000_0004H.
pub fn cpu_model() -> String {
let mut bytes = Vec::with_capacity(48);
for leaf in 0x8000_0002u32..=0x8000_0004 {
let r = __cpuid_count(leaf, 0);
for reg in [r.eax, r.ebx, r.ecx, r.edx] {
bytes.extend_from_slice(&reg.to_le_bytes());
}
}
String::from_utf8_lossy(&bytes).trim_matches(char::from(0)).trim().to_string()
}
Loading