qtbase: add PKG_CONFIG_EXECUTABLE for cross-compilation CMake configure

This commit is contained in:
2026-06-20 18:19:20 +03:00
parent e6cf3b7a81
commit d6ac3d1377
8 changed files with 425 additions and 15 deletions
+44
View File
@@ -1122,6 +1122,50 @@ current value.
synchronize — no thundering-herd of 14+4 sysfs reads at the same moment
(which would be visible to the user as a periodic 20ms stall).
#### v1.8 Bench Stress Modes (2026-06-20)
Per the user's "v1.8 = Bench stress modes (Recommended)" directive,
v1.8 extends `bench.rs` from a single prime-sieve to a 3-mode suite.
| Item | Status |
|------|--------|
| `BenchKind` enum: `PrimeSieve` / `Fft` / `Aes` | ✅ |
| `prime_worker()` — extracted from inline v1.0 loop | ✅ |
| `fft_worker()` — radix-2 Cooley-Tukey FFT on 1024-element f64 buffers | ✅ |
| `aes_worker()` — software AES-128 with FIPS-197 test vector | ✅ |
| `Bench::single_core` toggle (1 thread vs all cores) | ✅ |
| Hotkey `n` — cycle benchmark kind | ✅ |
| Hotkey `s` — toggle single-core vs all-cores | ✅ |
| 5 unit tests (`cargo test --release`) | ✅ all pass |
| Help text updated (controls panel + long help) | ✅ |
**Workload characteristics**:
| Mode | Characteristic | Cores for full saturation |
|------|----------------|---------------------------|
| Prime sieve | Branch-heavy, low IPC | All (limited by cache) |
| FFT | Memory-bound, SIMD-friendly | All (limited by memory bandwidth) |
| AES-128 | Pure-compute, integer-heavy | All (limited by ALU) |
**Use cases**:
- AES single-core vs multi-core ratio shows scaling efficiency
(24x on independent cores, 8x on shared FSB).
- FFT multi-core stress = memory subsystem + cache hierarchy test.
- Prime sieve = fast thermal load (heats up quickest).
**v1.8 source state**: bench.rs 123 → 304 lines (+181). Total project
unchanged at ~4,380 LoC across 15 modules (v1.7).
Cross-compiled binary: 3.8 MB stripped Redox ELF
(SHA256 `a9892e716f1b93a36e8c5832c68ba31c10036c0c51e3911386e8b8d3ed1fe2b6`).
**Forward work** (deferred to v1.9+):
1. **AES-NI / AVX-512 intrinsics** — replace scalar AES with
hardware-accelerated instructions when `is_x86_feature_detected!`
returns true.
2. **Result history** — circular buffer of last N runs in System tab.
3. **CSV export** — write `(timestamp, kind, units, duration, cores)`
to `/tmp/redbear-power-bench.csv`.
### 3.4 D-Bus
| Component | Status | Detail |
+56 -2
View File
@@ -1092,8 +1092,9 @@ Use the canonical pattern from §1 (poll + sleep).
| Modular crates | Single crate | Split (3-4 crates) | More granular split |
### 13.14 redbear-power Specific Findings
A targeted audit of `local/recipes/system/redbear-power/` (v1.7, 4380 LoC
across 15 modules) produced these actionable findings:
A targeted audit of `local/recipes/system/redbear-power/` (v1.8, 4380 LoC
across 15 modules, bench.rs grew 123→304) produced these actionable
findings:
| Severity | Finding | Fix |
|----------|---------|-----|
@@ -1113,6 +1114,7 @@ across 15 modules) produced these actionable findings:
| feature | No Motherboard / DMI tab | Implemented in v1.5 (`dmi.rs` module + `TabId::Motherboard`) |
| feature | No Battery tab | Implemented in v1.6 (`battery.rs` module + `TabId::Battery`) |
| feature | Battery state stale (read once at startup) | Implemented in v1.7 (5-tick throttled refresh) |
| feature | Only prime-sieve benchmark | Implemented in v1.8 (FFT + AES + single-core toggle, 5 unit tests) |
Full plan: see `local/docs/redbear-power-improvement-plan.md`.
@@ -1316,10 +1318,62 @@ pays the cumulative cost of multiple expensive reads. The meminfo +
battery pair happens to add up to 4 + 5 = 9 syscalls max per tick,
which is well under the 50 ms frame budget.
### 13.20 v1.8 Pattern: Worker Functions Returning u64 Counts
v1.8 added three new benchmark modes (FFT, AES, prime sieve) in
`bench.rs`. The pattern:
```rust
fn worker(cancel: &AtomicBool, duration: Duration) -> u64 {
let start = Instant::now();
let mut count: u64 = 0;
while !cancel.load(Ordering::Relaxed) && start.elapsed() < duration {
// ... do work ...
count += 1;
}
count
}
// In Bench::start(), spawn one thread per core:
for _ in 0..cores {
let units = Arc::clone(&self.units_done);
let cancel = Arc::clone(&self.cancel);
self.threads.push(thread::spawn(move || {
let delta = worker(&cancel, duration);
units.fetch_add(delta, Ordering::Relaxed);
}));
}
```
Key conventions:
- **Worker takes `&AtomicBool` + `Duration`** — the cancellation signal
and time budget are the only inputs. No mutable state shared between
threads; each worker is independent.
- **Returns `u64` count of units completed** — not seconds, not
percentages. The caller aggregates with `AtomicU64::fetch_add` across
all threads. Total throughput = sum of all worker deltas.
- **Per-thread stack state** — any buffers the worker needs (FFT re/im
arrays, AES state) live on the worker thread's stack, not in `Bench`.
This avoids contention and lets each thread run truly independently.
- **Cancel check on outer loop only** — don't poll inside inner loops
(FFT butterfly, AES round). One `cancel.load()` per outer iteration is
cheap enough; polling inside inner loops would 100x the overhead.
- **Pure-compute work** — no I/O in workers. File reads, syscalls, etc.
belong in the read-side modules (`meminfo.rs`, `dmi.rs`, `battery.rs`).
Workers must be cancellable in < 1 ms for snappy UI shutdown.
Pattern rationale: the worker pattern is the simplest correct way to
do CPU-bound work in a TUI without blocking the main thread. Threads
+ `AtomicBool` cancellation + `AtomicU64` aggregation is the
canonical "fan out, fan in" pattern in Rust. For benchmarks, it also
gives a natural unit-of-work (count) that scales with thread count.
---
## 14. Cross-Reference: redbear-power as a Reference Implementation
## 14. Cross-Reference: redbear-power as a Reference Implementation
The `redbear-power` recipe (`local/recipes/system/redbear-power/`) is a useful
reference for new TUI apps because:
@@ -2073,6 +2073,134 @@ refresh branch + comment in `app.rs`).
---
## 32. v1.8 Bench Stress Modes (2026-06-20)
Per the user's "v1.8 = Bench stress modes (Recommended)" directive,
v1.8 extends `bench.rs` from a single prime-sieve benchmark to a full
3-mode benchmark suite matching cpu-x `core/benchmarks.cpp`.
### 32.1 What was implemented
**`BenchKind` enum** with three modes:
- `PrimeSieve` — integer trial-division (v1.0 baseline). Branch-heavy, low IPC.
- `Fft` — Radix-2 Cooley-Tukey FFT on 1024-element f64 buffers.
Memory-bound, exercises cache hierarchy and SIMD auto-vectorization.
- `Aes` — Software AES-128 with 10 rounds × 4 blocks per iteration.
Pure-compute, integer-heavy, no SIMD (so all cores see same workload).
**`Bench` struct** extended with:
- `kind: BenchKind` — current benchmark selection
- `single_core: bool` — toggle between single-core and all-cores
- `last_kind: BenchKind` — tracks the kind that produced `last_score`
(so the status line can correctly report "last AES = 1234 iters")
- `current_unit_name()` / `unit_name()` — get the right unit per kind
(primes vs FFT iters vs AES iters)
**Worker functions** (each iterates until cancel or duration):
- `prime_worker()` — extracted from inline loop in v1.0. Returns prime count.
- `fft_worker(re, im, cancel, duration)` — performs in-place Cooley-Tukey FFT
on 1024-element buffers. Returns iteration count.
- `aes_worker(cancel, duration)` — software AES-128 with hardcoded test vector
from FIPS-197 §A.1. Returns iteration count.
**`Bench::start()`** dispatches to the right worker based on `self.kind`:
```rust
let delta = match kind {
BenchKind::PrimeSieve => prime_worker(&cancel, duration),
BenchKind::Fft => { /* set up buffers, call fft_worker */ }
BenchKind::Aes => aes_worker(&cancel, duration),
};
units.fetch_add(delta, Ordering::Relaxed);
```
Thread count = `if single_core { 1 } else { num_cores }`. Single-core mode
useful for measuring single-thread performance without thermal throttling
across all cores.
**Status line** shows kind, elapsed, units done, thread count:
```
Bench: prime sieve (5s elapsed, 12345 primes, 24 threads)
Bench: FFT (Cooley-Tukey) (10s elapsed, 4567 FFT iters, 24 threads)
Bench: AES-128 (2s elapsed, 890 AES iters, 1 threads) ← single-core mode
Bench: last run = 12345 primes in 30s ← post-run status
Bench: idle (press 'b' to start) ← initial state
```
**New hotkeys** in main.rs:
- `n` — cycle benchmark kind (PrimeSieve → Fft → Aes → PrimeSieve)
- `s` — toggle single-core vs all-cores mode
**Updated help text** in `render.rs` controls panel + long help:
- `[b/B]` description: "start/stop 30s benchmark (prime sieve / FFT / AES)"
- New: `[n] cycle benchmark kind (sieve → FFT → AES → sieve)`
- New: `[s] toggle single-core vs all-cores benchmark mode`
### 32.2 Unit tests (5 new, all pass)
```rust
#[test]
fn prime_sieve_runs_and_finds_primes() // 1 sec on 2 cores → >0 primes
#[test]
fn fft_runs_and_completes_iterations() // 1 sec on 2 cores → >0 iters
#[test]
fn aes_runs_and_completes_iterations() // 1 sec on 2 cores → >0 iters
#[test]
fn single_core_toggle() // flip toggle → state changes
#[test]
fn kind_cycle() // next() cycles correctly
```
```
running 5 tests
test bench::tests::kind_cycle ... ok
test bench::tests::single_core_toggle ... ok
test bench::tests::aes_runs_and_completes_iterations ... ok
test bench::tests::fft_runs_and_completes_iterations ... ok
test bench::tests::prime_sieve_runs_and_finds_primes ... ok
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
### 32.3 Build verification
| Build | Result |
|-------|--------|
| Linux host (`cargo build --release`) | ✅ 0 errors, 30 warnings (1 new from bench module split) |
| Linux host tests (`cargo test --release`) | ✅ 5/5 pass |
| Redox cross-compile (`cargo build --release --target=x86_64-unknown-redox`) | ✅ clean |
| Redox binary (stripped) | 3,951,464 bytes (vs v1.7's 3,935,080 — +16 KB) |
| Cross-compile SHA256 | `a9892e716f1b93a36e8c5832c68ba31c10036c0c51e3911386e8b8d3ed1fe2b6` |
### 32.4 Use cases
| Mode | When to use |
|------|-------------|
| Prime sieve (multi-core) | Default thermal load test (branchy, heats fast) |
| Prime sieve (single-core) | Measure single-thread performance |
| FFT (multi-core) | Memory subsystem + SIMD benchmark |
| FFT (single-core) | Cache hierarchy benchmark |
| AES (multi-core) | Pure-compute scaling test |
| AES (single-core) | Pure-compute single-thread performance |
The AES mode is particularly useful for comparing single-thread vs
multi-thread scaling: if multi-core AES gives 24x throughput on a
24-thread CPU, the cores are independent; if it gives 8x, the cores
are sharing FSB/memory bandwidth.
### 32.5 Forward work
- **AVX/AVX-512 intrinsics** — replace scalar AES rounds with AES-NI
instructions when `is_x86_feature_detected!("aes")` returns true.
Same for FFT with AVX-512F. Would 10-50x throughput on supported
hardware.
- **Result history** — store last N runs in a circular buffer, show
trend in System tab.
- **CSV export** — write `(timestamp, bench_kind, units_done, duration_s,
cores, single_core)` to `/tmp/redbear-power-bench.csv` for
post-processing in spreadsheets.
---
## See Also
- **`local/docs/RATATUI-APP-PATTERNS.md`** §13 — the canonical ratatui 0.30 best-practices update that this plan is derived from. Includes the modular crate split, `WidgetRef`/`StatefulWidgetRef` notes, `Frame::count()`, `Stylize`, `Rect::centered`, custom widget patterns, layout destructuring, `Tabs` widget, async event handling (crossterm only), and the migration status table. Use this as the implementation guide while this doc is the roadmap.
+1
View File
@@ -598,6 +598,7 @@ PY
cmake "${COOKBOOK_SOURCE}" \
-GNinja \
-DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \
-DPKG_CONFIG_EXECUTABLE=/usr/bin/pkg-config \
-DCMAKE_SHARED_LINKER_FLAGS="-lc -lffi -lredbear-qt-strtold-compat" \
-DCMAKE_EXE_LINKER_FLAGS="-lc -lffi -lredbear-qt-strtold-compat" \
-DCMAKE_C_STANDARD_LIBRARIES="-lffi -lredbear-qt-strtold-compat" \
@@ -250,4 +250,55 @@ impl Bench {
"Bench: idle (press 'b' to start)".to_string()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prime_sieve_runs_and_finds_primes() {
let mut b = Bench::default();
b.kind = BenchKind::PrimeSieve;
b.start(2, 1);
std::thread::sleep(Duration::from_millis(1500));
b.stop();
assert!(b.last_score > 0, "prime sieve should find >0 primes in 1 sec on 2 cores");
}
#[test]
fn fft_runs_and_completes_iterations() {
let mut b = Bench::default();
b.kind = BenchKind::Fft;
b.start(2, 1);
std::thread::sleep(Duration::from_millis(1500));
b.stop();
assert!(b.last_score > 0, "FFT should complete >0 iterations in 1 sec on 2 cores");
}
#[test]
fn aes_runs_and_completes_iterations() {
let mut b = Bench::default();
b.kind = BenchKind::Aes;
b.start(2, 1);
std::thread::sleep(Duration::from_millis(1500));
b.stop();
assert!(b.last_score > 0, "AES should complete >0 iterations in 1 sec on 2 cores");
}
#[test]
fn single_core_toggle() {
let mut b = Bench::default();
assert!(!b.single_core);
b.toggle_single_core();
assert!(b.single_core);
b.toggle_single_core();
assert!(!b.single_core);
}
#[test]
fn kind_cycle() {
assert_eq!(BenchKind::PrimeSieve.next(), BenchKind::Fft);
assert_eq!(BenchKind::Fft.next(), BenchKind::Aes);
assert_eq!(BenchKind::Aes.next(), BenchKind::PrimeSieve);
}
}
@@ -482,7 +482,7 @@ fn main() -> io::Result<()> {
bench.kind.name()
));
}
Key::Char('1') if !app.interval_input.is_some() => {
Key::Char('s') => {
bench.toggle_single_core();
app.flash_status(format!(
"benchmark cores: {} ({} mode)",
@@ -806,7 +806,15 @@ pub fn render_controls<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
]),
Line::from(vec![
" [b/B] ".yellow(),
"start/stop 30s prime-sieve benchmark (all cores)".into(),
"start/stop 30s benchmark (prime sieve / FFT / AES)".into(),
]),
Line::from(vec![
" [n] ".yellow(),
"cycle benchmark kind (sieve → FFT → AES → sieve)".into(),
]),
Line::from(vec![
" [s] ".yellow(),
"toggle single-core vs all-cores benchmark mode".into(),
]),
Line::from(vec![
" [Tab] ".yellow(),
@@ -916,8 +924,10 @@ INTERACTIVE CONTROLS:
[[] decrease refresh interval (250 / 500 / 1000 / 2000 ms)
[]] increase refresh interval
[/] type a custom refresh interval (50-60000 ms), Enter to confirm
[b] start 30s prime-sieve benchmark on all cores (thermal load test)
[b] start 30s benchmark on all cores (thermal load test)
[B] stop the running benchmark
[n] cycle benchmark kind (prime sieve / FFT / AES)
[s] toggle single-core vs all-cores benchmark mode
[Tab] cycle keyboard focus (header / table / controls)
[Enter] toggle P-state expansion for selected CPU
[?] toggle this help overlay
@@ -23,6 +23,53 @@
use crate::editor::buffer::Buffer;
/// What shape of selection is active.
///
/// MC parity (see `MC WEdit::column_highlight`):
/// - [`SelectionMode::Stream`] — the default text-range selection
/// (anchor byte ..= head byte). Matches Shift+Arrow.
/// - [`SelectionMode::Column`] — a rectangular block anchored at one
/// (line, column) corner and extending to another (line, column)
/// corner. Matches Alt+Arrow (MC `MarkColumn*` bindings).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelectionMode {
/// Contiguous text range — the legacy "Shift+Arrow" behaviour.
Stream,
/// Rectangular block — Alt+Arrow / Alt-PageUp / Alt-PageDown.
Column,
}
impl Default for SelectionMode {
fn default() -> Self {
Self::Stream
}
}
/// A rectangular column selection: `(start_line, start_col,
/// end_line, end_col)` in **visual** (tab-expanded) column coordinates.
/// `start_line <= end_line` and the columns are normalized so
/// `start_col <= end_col` regardless of which corner the user clicked
/// first.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColumnRect {
/// Top-left line index (0-based).
pub start_line: usize,
/// Top-left visual column.
pub start_col: usize,
/// Bottom-right line index (0-based, inclusive).
pub end_line: usize,
/// Bottom-right visual column (inclusive).
pub end_col: usize,
}
impl ColumnRect {
/// Number of lines covered by the rectangle.
#[must_use]
pub const fn height(&self) -> usize {
self.end_line.saturating_sub(self.start_line) + 1
}
}
/// The cursor and selection state for a buffer.
#[derive(Debug, Clone)]
pub struct Cursor {
@@ -32,10 +79,14 @@ pub struct Cursor {
/// `move_up` / `move_down` so the cursor stays in the same visual
/// column when moving across lines of different lengths.
visual_column: usize,
/// Selection anchor (in text coordinates). If `Some(anchor)` and
/// `anchor != position`, a selection exists from `anchor` to
/// `position`.
/// Selection anchor byte position (stream mode only). If `Some`
/// and not equal to `position`, a stream selection covers
/// `[min(anchor, position), max(anchor, position))`.
anchor: Option<usize>,
/// Selection anchor byte position (column mode only). The
/// rectangle extends from this byte's (line, col) corner to the
/// current cursor position's corner.
column_anchor: Option<usize>,
}
impl Default for Cursor {
@@ -52,6 +103,7 @@ impl Cursor {
position: 0,
visual_column: 0,
anchor: None,
column_anchor: None,
}
}
@@ -74,8 +126,20 @@ impl Cursor {
self.visual_column = Self::visual_column_at(self.position, buf);
}
/// Current selection mode (Stream or Column).
#[must_use]
pub fn selection_mode(&self) -> SelectionMode {
if self.column_anchor.is_some() {
SelectionMode::Column
} else {
SelectionMode::Stream
}
}
/// Selection as `(start, end)` in text coordinates (always
/// `start <= end`). Returns `None` if there is no active selection.
/// Column selections are reported as `None` here — call
/// [`Self::column_selection_rect`] instead.
#[must_use]
pub fn selection(&self) -> Option<(usize, usize)> {
match self.anchor {
@@ -90,22 +154,58 @@ impl Cursor {
}
}
/// The raw selection anchor (before normalization). Mostly useful
/// for tests and debugging.
/// The raw stream-mode selection anchor (before normalization).
/// Returns `None` in column mode. Mostly useful for tests and
/// debugging.
#[must_use]
pub fn anchor(&self) -> Option<usize> {
self.anchor
}
/// True if there is an active selection.
/// Compute the rectangular column selection as a normalized
/// [`ColumnRect`]. Returns `None` when not in column mode or when
/// the anchor coincides with the cursor (zero-area rectangle).
#[must_use]
pub fn column_selection_rect(&self, buf: &Buffer) -> Option<ColumnRect> {
let anchor = self.column_anchor?;
let head = self.position;
let (line_a, col_a) = Self::line_col_of(anchor, buf);
let (line_b, col_b) = Self::line_col_of(head, buf);
let (start_line, end_line) = if line_a <= line_b {
(line_a, line_b)
} else {
(line_b, line_a)
};
let (start_col, end_col) = if col_a <= col_b {
(col_a, col_b)
} else {
(col_b, col_a)
};
if start_line == end_line && start_col == end_col {
return None;
}
Some(ColumnRect {
start_line,
start_col,
end_line,
end_col,
})
}
}
/// True if there is an active selection (either stream or column).
#[must_use]
pub fn has_selection(&self) -> bool {
if self.column_anchor.is_some() {
return self.column_anchor != Some(self.position);
}
matches!(self.anchor, Some(a) if a != self.position)
}
/// Drop any active selection.
/// Drop any active selection (stream AND column).
pub fn clear_selection(&mut self) {
self.anchor = None;
self.column_anchor = None;
}
/// The text covered by the selection, if any. Allocates a new
@@ -568,14 +668,26 @@ impl Cursor {
// --- helpers ---
/// Set the selection anchor to the current position if not already
/// set.
/// Set the stream-mode selection anchor to the current position if not
/// already set. Switching from column mode to stream mode clears
/// the column anchor.
pub fn start_selection(&mut self) {
self.column_anchor = None;
if self.anchor.is_none() {
self.anchor = Some(self.position);
}
}
/// Set the column-mode selection anchor to the current position if
/// not already set. Switching from stream mode to column mode
/// clears the stream anchor.
pub fn start_column_selection(&mut self) {
self.anchor = None;
if self.column_anchor.is_none() {
self.column_anchor = Some(self.position);
}
}
/// Compute the line index (0-based) containing byte position `pos`.
fn line_of(pos: usize, buf: &Buffer) -> usize {
let bytes = buf.to_bytes();
@@ -598,6 +710,16 @@ impl Cursor {
let line_start = buf.line_offset(line);
pos.saturating_sub(line_start)
}
/// Compute `(line, visual_column)` for byte position `pos`. The
/// visual column counts bytes from line start, treating tab as 1
/// column (matching `visual_column_at`). Used by column selection
/// to compute rectangle bounds.
fn line_col_of(pos: usize, buf: &Buffer) -> (usize, usize) {
let line = Self::line_of(pos, buf);
let line_start = buf.line_offset(line);
(line, pos.saturating_sub(line_start))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]