Skip to content

Commit 87ee47b

Browse files
committed
Merge remote-tracking branch 'origin/master' into ccr-9f3b8075-74u47l-spr-check
# Conflicts: # .claude/blackboard.md
2 parents 6b993c0 + 21948f8 commit 87ee47b

5 files changed

Lines changed: 128 additions & 0 deletions

File tree

‎.claude/blackboard.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
- **Measured here (Cascade Lake host, which lacks BF16/FP16 so nothing was run):** `cargo check` at `sapphirerapids` with `-Dwarnings` passes for `approx,serde,rayon` and `+hpc-extras`; the rustc call for ndarray carries `-Ctarget-cpu=native`, then `-Ctarget-cpu=sapphirerapids` (the later one wins), then `-Dwarnings`; the codegen witness passes (1, 1 and 6 `vpternlog`).
66
- **Open:** the global `RUSTFLAGS` crypto cfgs (dalek, poly1305) are dropped by this job's `env -u RUSTFLAGS`, as before; unchanged here.
77

8+
## 2026-10-13 — build-time warning for target features `cpu_guard` cannot check
9+
10+
- **Change:** new `build.rs` + `src/cpu_guard_drift.rs` (shared by `build.rs` and the tests via `include!`). On x86_64 the build script reads `CARGO_CFG_TARGET_FEATURE` and the guard's `"name" =>` rows and prints `cargo:warning=` for every enabled feature without a row. Without a row the guard never compares that feature with the running CPU, so the binary passes the guard and can still SIGILL; a toolchain bump that lets LLVM enable a newer feature is the usual cause.
11+
- **Exclusions:** the x86_64 baseline (`sse`, `sse2`, `x87`) and non-CPU target features. `crt-static` (static C runtime, on by default for musl) was the one non-CPU feature in the default x86_64 sets; measured `rustc --print cfg` for gnu, musl, msvc and darwin (codex P2).
12+
- **Tests:** the parser reads exactly the 54 guard rows; a missing row is reported (`avx10.1`); table + baseline report nothing; musl's default set reports nothing while a real missing row beside `crt-static` still does (red before the exclusion).
13+
- **Limits:** cargo hides build-script warnings from git dependencies, so a consumer pulling ndarray by git URL sees nothing; path dependencies (the sibling layout) do show it. Stable rustc hides unstable features (amx, avx10), which are therefore never enabled or reported.
14+
815
## 2026-10-12 — `mask_gather_chain_u32` withdrawn: rejected design, moved to backup
916

1017
- **Removed:** `mask_gather_chain_u32`, `mask_gather_chain_u32_under`, `ChainHop`, `ChainTest`, their tests and the parity case 0xD02/0xD03 (shipped in #358). The exact state is kept on branch `backup/session-011BMJ-2026-10-12`.

‎Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ categories = ["data-structures", "science"]
2828

2929
include = [
3030
"/src/**/*.rs",
31+
"/build.rs",
3132
"LICENSE-MIT",
3233
"LICENSE-APACHE",
3334
"RELEASES.md",

‎build.rs‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Warns when a build enables x86_64 target features that `cpu_guard` has no
2+
// row for. Those features are compiled in but never compared with the running
3+
// CPU, so the guard would pass a binary that can still die with SIGILL.
4+
//
5+
// Limits: on stable rustc, `CARGO_CFG_TARGET_FEATURE` omits unstable features
6+
// (avx10.x, amx-*), exactly as `cfg!` in the guard does, so they cannot be
7+
// reported here. Cargo also hides warnings from git and registry dependencies,
8+
// so this fires when building ndarray itself, not inside a consumer.
9+
10+
include!("src/cpu_guard_drift.rs");
11+
12+
fn main() {
13+
println!("cargo:rerun-if-changed=build.rs");
14+
println!("cargo:rerun-if-changed=src/cpu_guard.rs");
15+
println!("cargo:rerun-if-changed=src/cpu_guard_drift.rs");
16+
17+
if std::env::var("CARGO_CFG_TARGET_ARCH").as_deref() != Ok("x86_64") {
18+
return;
19+
}
20+
let enabled = std::env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default();
21+
let guard_src = match std::fs::read_to_string("src/cpu_guard.rs") {
22+
Ok(src) => src,
23+
Err(e) => {
24+
println!("cargo:warning=cpu_guard drift check skipped: cannot read src/cpu_guard.rs: {e}");
25+
return;
26+
}
27+
};
28+
let table = guard_table_names(&guard_src);
29+
let unchecked = unchecked_features(&enabled, &table);
30+
if !unchecked.is_empty() {
31+
println!(
32+
"cargo:warning=cpu_guard has no row for enabled target features: {} \
33+
(the guard cannot detect a CPU that lacks them; add rows in src/cpu_guard.rs)",
34+
unchecked.join(", ")
35+
);
36+
}
37+
}

‎src/cpu_guard.rs‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,50 @@ static GUARD_BEFORE_MAIN: extern "C" fn() = guard_before_main;
640640
mod tests {
641641
use super::*;
642642

643+
mod drift {
644+
include!("cpu_guard_drift.rs");
645+
646+
const GUARD_SRC: &str = include_str!("cpu_guard.rs");
647+
648+
/// The build script's parser must read exactly the names the guard
649+
/// checks, or its warning reports the wrong set.
650+
#[cfg(target_arch = "x86_64")]
651+
#[test]
652+
fn parser_reads_exactly_the_guard_table() {
653+
let parsed = guard_table_names(GUARD_SRC);
654+
let rows: Vec<&str> = super::FEATURES.iter().map(|r| r.0).collect();
655+
assert_eq!(parsed.len(), 54);
656+
assert_eq!(parsed, rows);
657+
}
658+
659+
/// Can fire: a feature the table lacks is reported, and only that one.
660+
#[test]
661+
fn a_feature_without_a_row_is_reported() {
662+
let table = guard_table_names(GUARD_SRC);
663+
let enabled = "sse,sse2,x87,avx2,avx512f,avx10.1,fma";
664+
assert_eq!(unchecked_features(enabled, &table), ["avx10.1"]);
665+
}
666+
667+
/// Can stay silent: every row plus the x86_64 baseline reports nothing.
668+
#[test]
669+
fn the_table_and_the_baseline_report_nothing() {
670+
let table = guard_table_names(GUARD_SRC);
671+
let enabled = format!("sse,sse2,x87,{}", table.join(","));
672+
assert!(unchecked_features(&enabled, &table).is_empty());
673+
}
674+
675+
/// The default x86_64 musl target enables `crt-static`, which is C
676+
/// runtime linkage rather than a CPU feature, so it must not be
677+
/// reported; a feature without a row beside it still is.
678+
#[test]
679+
fn crt_static_is_not_a_cpu_feature() {
680+
let table = guard_table_names(GUARD_SRC);
681+
let musl_default = "crt-static,fxsr,sse,sse2";
682+
assert!(unchecked_features(musl_default, &table).is_empty());
683+
assert_eq!(unchecked_features("crt-static,avx10.1", &table), ["avx10.1"]);
684+
}
685+
}
686+
643687
/// The exit-or-warn decision must see a test binary as an executable;
644688
/// if this were false, every mismatching executable would only warn.
645689
#[cfg(not(miri))]

‎src/cpu_guard_drift.rs‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Shared by `build.rs` and the tests in `cpu_guard.rs` (both `include!` it);
2+
// not a module of the crate.
3+
//
4+
// Which target features a build enables that `cpu_guard` cannot check. The
5+
// guard asks `cfg!(target_feature = ...)` row by row, so a feature that is
6+
// compiled in but has no row is never compared with the running CPU: the
7+
// binary passes the guard and can still die with SIGILL. That happens when a
8+
// toolchain bump lets LLVM enable a feature the table predates.
9+
10+
/// Features every x86_64 CPU has; the guard does not need rows for them.
11+
const X86_64_BASELINE: &[&str] = &["sse", "sse2", "x87"];
12+
13+
/// Target features that do not describe the CPU: `crt-static` selects static
14+
/// C-runtime linkage and is on by default for musl targets.
15+
const NOT_CPU_FEATURES: &[&str] = &["crt-static"];
16+
17+
/// The feature names in the x86_64 table of `cpu_guard.rs` (`"name" =>` rows).
18+
fn guard_table_names(cpu_guard_src: &str) -> Vec<&str> {
19+
cpu_guard_src
20+
.lines()
21+
.filter_map(|line| {
22+
let rest = line.trim_start().strip_prefix('"')?;
23+
let (name, tail) = rest.split_once('"')?;
24+
tail.trim_start().starts_with("=>").then_some(name)
25+
})
26+
.collect()
27+
}
28+
29+
/// The enabled features (`CARGO_CFG_TARGET_FEATURE`, comma-separated) that
30+
/// are neither in the guard's table, nor x86_64 baseline, nor a non-CPU
31+
/// feature, in input order.
32+
fn unchecked_features<'a>(enabled: &'a str, table: &[&str]) -> Vec<&'a str> {
33+
enabled
34+
.split(',')
35+
.filter(|f| {
36+
!f.is_empty() && !table.contains(f) && !X86_64_BASELINE.contains(f) && !NOT_CPU_FEATURES.contains(f)
37+
})
38+
.collect()
39+
}

0 commit comments

Comments
 (0)