refactor(scripts): improve get_native_properties.sh

Improve `get_native_properties.sh` with a refreshed implementation.
The new version covers all Makefile ARCH variants and keeps the
script interface stable while improving portability and CPU feature
detection across supported platforms. Drop the asset file name to
avoid coupling outputs to a specific artifact naming scheme.

Refs: https://github.com/ppigazzini/get-native-properties (includes a
testing framework)

closes https://github.com/official-stockfish/Stockfish/pull/6552

No functional change.
This commit is contained in:
ppigazzini
2026-02-04 17:58:55 +01:00
committed by Joost VandeVondele
parent 7f0b5d10ee
commit 3ee16a1d8e
+338 -139
View File
@@ -1,163 +1,362 @@
#!/bin/sh #!/bin/sh
# #
# Returns properties of the native system. # Returns the best architecture supported by the CPU (as expected by src/Makefile ARCH=).
# best architecture as supported by the CPU #
# filename of the best binary uploaded as an artifact during CI # Output format:
# "<true_arch>\n"
# #
# Check if all the given flags are present in the CPU flags list # ---------------------------
check_flags() { # Helpers (POSIX)
for flag; do # ---------------------------
printf '%s\n' "$flags" | grep -q -w "$flag" || return 1
done # Test hooks (optional env overrides)
# GP_UNAME_S: override `uname -s`
# GP_UNAME_M: override `uname -m`
# GP_CPUINFO: path to a cpuinfo-like fixture file (defaults to /proc/cpuinfo)
# GP_BITS: override getconf LONG_BIT result (32/64)
# GP_SYSCTL_FEATURES: override sysctl feature strings on Darwin x86_64
cpuinfo_path=${GP_CPUINFO:-/proc/cpuinfo}
# Normalize to a single-line, space-separated string.
normalize_ws() {
printf '%s\n' "$*" | tr '\n\t' ' ' | tr -s ' '
} }
# Set the CPU flags list die() {
# remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1 printf '%s\n' "$*" >&2
exit 1
}
# Populate $flags from /proc/cpuinfo when available,
# removing underscores and dots to reduce naming variations.
get_flags() { get_flags() {
flags=$(awk '/^flags[ \t]*:|^Features[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, ""); line=$0} END{print line}' /proc/cpuinfo) if [ -r "$cpuinfo_path" ]; then
flags=$(
awk '
/^flags[ \t]*:|^Features[ \t]*:/ {
if (!found) {
gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, "");
line=$0
found=1
}
}
END { print line }
' "$cpuinfo_path" 2>/dev/null
)
else
flags=''
fi
flags=$(printf '%s\n' "$flags" | tr '[:upper:]' '[:lower:]')
flags=$(normalize_ws "$flags")
} }
# Check for gcc march "znver1" or "znver2" https://en.wikichip.org/wiki/amd/cpuid # Populate $flags from sysctl on Darwin x86_64.
check_znver_1_2() { get_sysctl_flags() {
vendor_id=$(awk '/^vendor_id/{print $3; exit}' /proc/cpuinfo) if [ -n "${GP_SYSCTL_FEATURES:-}" ]; then
cpu_family=$(awk '/^cpu family/{print $4; exit}' /proc/cpuinfo) flags=$(printf '%s\n' "$GP_SYSCTL_FEATURES")
[ "$vendor_id" = "AuthenticAMD" ] && [ "$cpu_family" = "23" ] && znver_1_2=true else
flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features 2>/dev/null)
fi
flags=$(printf '%s\n' "$flags" | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '._')
flags=$(normalize_ws "$flags")
} }
# Set the file CPU loongarch64 architecture # Best-effort bitness for fallback arch selection.
get_bits() {
if [ -n "${GP_BITS:-}" ]; then
bits=$GP_BITS
else
bits=$(getconf LONG_BIT 2>/dev/null)
fi
case $bits in
32|64) : ;;
*) bits=64 ;;
esac
}
# Extract ARM architecture level (5/6/7/8/...) from /proc/cpuinfo when present.
get_arm_arch_level() {
[ -r "$cpuinfo_path" ] || return 1
awk '
/^CPU architecture[ \t]*:/{
s=$0
sub(/^[^:]*:[ \t]*/, "", s)
if (match(s, /[0-9]+/)) { print substr(s, RSTART, RLENGTH); exit }
}
/^Processor[ \t]*:/{
s=$0
sub(/^[^:]*:[ \t]*/, "", s)
if (match(s, /ARMv[0-9]+/)) { print substr(s, RSTART+4, RLENGTH-4); exit }
}
' "$cpuinfo_path" 2>/dev/null
}
# Best-effort ARM architecture level (5/6/7/8/...) with a minimal fallback.
# Prefer /proc/cpuinfo when available; fall back to uname -m only when it encodes it.
get_arm_level() {
arm_level=$(get_arm_arch_level || :)
if [ -n "$arm_level" ]; then
printf '%s\n' "$arm_level"
return 0
fi
case ${1:-} in
armv5*) printf '5\n' ;;
armv6*) printf '6\n' ;;
armv7*) printf '7\n' ;;
armv8l) printf '8\n' ;;
*) return 1 ;;
esac
}
# Whole-token membership check.
has_flag() {
case " $flags " in
*" $1 "*) return 0 ;;
*) return 1 ;;
esac
}
match_flags() {
for f; do
has_flag "$f" || return 1
done
return 0
}
match_any_flags() {
for f; do
has_flag "$f" && return 0
done
return 1
}
# SSE3 is often exposed as "pni" in /proc/cpuinfo.
match_sse3() {
match_any_flags sse3 pni
}
# AMD Zen1/2 exclusion logic (used for bmi2 tier).
# https://web.archive.org/web/20250821132355/https://en.wikichip.org/wiki/amd/cpuid
is_znver_1_2() (
[ -r "$cpuinfo_path" ] || exit 1
vendor_id=$(awk '/^vendor_id/{print $3; exit}' "$cpuinfo_path" 2>/dev/null)
cpu_family=$(awk '/^cpu family/{print $4; exit}' "$cpuinfo_path" 2>/dev/null)
[ "$vendor_id" = "AuthenticAMD" ] && [ "$cpu_family" = "23" ]
)
match_not_znver12_and_flags() {
is_znver_1_2 && return 1
match_flags "$@"
}
match_sse3_popcnt() {
has_flag popcnt || return 1
match_sse3
}
match_true() { return 0; }
# Generic selector: reads lines like "arch|predicate|arg1 arg2 ..."
# First match wins; blank lines and lines starting with '#' are ignored.
select_arch_from_table() {
while IFS='|' read -r arch pred args; do
[ -z "$arch" ] && continue
case $arch in \#*) continue ;; esac
if [ -n "$args" ]; then
# Intentional splitting of args into words for the predicate.
# shellcheck disable=SC2086
$pred $args && { printf '%s\n' "$arch"; return 0; }
else
$pred && { printf '%s\n' "$arch"; return 0; }
fi
done
return 1
}
# ---------------------------
# Arch selection (table-driven)
# ---------------------------
set_arch_loongarch64() { set_arch_loongarch64() {
if check_flags 'lasx'; then true_arch=$(
true_arch='loongarch64-lasx' select_arch_from_table <<'EOF'
elif check_flags 'lsx'; then loongarch64-lasx|match_flags|lasx
true_arch='loongarch64-lsx' loongarch64-lsx|match_flags|lsx
else loongarch64|match_true|
true_arch='loongarch64' EOF
fi )
} }
# Set the file CPU x86_64 architecture
set_arch_x86_64() { set_arch_x86_64() {
if check_flags 'avx512f' 'avx512cd' 'avx512vl' 'avx512dq' 'avx512bw' 'avx512ifma' 'avx512vbmi' 'avx512vbmi2' 'avx512vpopcntdq' 'avx512bitalg' 'avx512vnni' 'vpclmulqdq' 'gfni' 'vaes'; then true_arch=$(
true_arch='x86-64-avx512icl' select_arch_from_table <<'EOF'
elif check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then # Strongest -> weakest (first match wins)
true_arch='x86-64-vnni512' x86-64-avx512icl|match_flags|avx512f avx512cd avx512vl avx512dq avx512bw avx512ifma avx512vbmi avx512vbmi2 avx512vpopcntdq avx512bitalg avx512vnni vpclmulqdq gfni vaes
elif check_flags 'avx512f' 'avx512bw'; then x86-64-vnni512|match_flags|avx512vnni avx512dq avx512f avx512bw avx512vl
true_arch='x86-64-avx512' x86-64-avx512|match_flags|avx512f avx512bw
elif check_flags 'avxvnni'; then x86-64-avxvnni|match_flags|avxvnni
true_arch='x86-64-avxvnni' x86-64-bmi2|match_not_znver12_and_flags|bmi2
elif [ -z "${znver_1_2+1}" ] && check_flags 'bmi2'; then x86-64-avx2|match_flags|avx2
true_arch='x86-64-bmi2' x86-64-sse41-popcnt|match_flags|sse41 popcnt
elif check_flags 'avx2'; then x86-64-ssse3|match_flags|ssse3
true_arch='x86-64-avx2' x86-64-sse3-popcnt|match_sse3_popcnt|
elif check_flags 'sse41' && check_flags 'popcnt'; then x86-64|match_true|
true_arch='x86-64-sse41-popcnt' EOF
else )
true_arch='x86-64'
fi
} }
set_arch_x86_32() {
true_arch=$(
select_arch_from_table <<'EOF'
x86-32-sse41-popcnt|match_flags|sse41 popcnt
x86-32-sse2|match_flags|sse2
x86-32|match_true|
EOF
)
}
# PPC64 needs a little parsing to distinguish vsx vs altivec.
set_arch_ppc_64() { set_arch_ppc_64() {
if grep -q -w "altivec" /proc/cpuinfo; then if [ -r "$cpuinfo_path" ] && grep -q "altivec" "$cpuinfo_path" 2>/dev/null; then
power=$(grep -oP -m 1 'cpu\t+: POWER\K\d+' /proc/cpuinfo) # Typical: "cpu : POWER8E" (extract the number after POWER)
if [ "0$power" -gt 7 ]; then power=$(
# VSX started with POWER8 awk -F: '/^cpu[ \t]*:/{print $2; exit}' "$cpuinfo_path" 2>/dev/null \
true_arch='ppc-64-vsx' | sed -n 's/.*[Pp][Oo][Ww][Ee][Rr][^0-9]*\([0-9][0-9]*\).*/\1/p'
else )
true_arch='ppc-64-altivec' if [ -z "$power" ]; then
fi power=$(
else awk -F: '/^cpu[ \t]*:/{print $2; exit}' "$cpuinfo_path" 2>/dev/null \
true_arch='ppc-64' | sed -n 's/.*\([0-9][0-9]*\).*/\1/p'
fi )
fi
case $power in
''|*[!0-9]*)
true_arch='ppc-64-altivec'
;;
*)
if [ "$power" -gt 7 ] 2>/dev/null; then
true_arch='ppc-64-vsx'
else
true_arch='ppc-64-altivec'
fi
;;
esac
else
true_arch='ppc-64'
fi
} }
# Check the system type # ---------------------------
uname_s=$(uname -s) # OS / machine dispatch
uname_m=$(uname -m) # ---------------------------
uname_s=$(uname -s 2>/dev/null)
uname_m=$(uname -m 2>/dev/null)
uname_s=${GP_UNAME_S:-$uname_s}
uname_m=${GP_UNAME_M:-$uname_m}
case $uname_s in case $uname_s in
'Darwin') # Mac OSX system Darwin)
case $uname_m in case $uname_m in
'arm64') arm64)
true_arch='apple-silicon' true_arch='apple-silicon'
file_arch='m1-apple-silicon' ;;
;; x86_64)
'x86_64') get_sysctl_flags
flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.') set_arch_x86_64
set_arch_x86_64 ;;
if [ "$true_arch" = 'x86-64-avx512' ]; then *)
file_arch='x86-64-bmi2' get_bits
fi if [ "$bits" = "32" ]; then
;; true_arch='general-32'
esac else
file_os='macos' true_arch='general-64'
file_ext='tar' fi
;; ;;
'Linux') # Linux system esac
get_flags ;;
case $uname_m in
'x86_64') Linux)
file_os='ubuntu' get_flags
check_znver_1_2 case $uname_m in
set_arch_x86_64 x86_64)
;; set_arch_x86_64
'i686') ;;
file_os='ubuntu' i?86)
true_arch='x86-32' set_arch_x86_32
;; ;;
'ppc64'*) ppc64*)
file_os='ubuntu' set_arch_ppc_64
set_arch_ppc_64 ;;
;; aarch64|arm64)
'aarch64') true_arch='armv8'
file_os='android' if match_flags asimddp; then
true_arch='armv8' true_arch='armv8-dotprod'
if check_flags 'asimddp'; then fi
true_arch="$true_arch-dotprod" ;;
fi armv5*|armv6*|armv7*|armv8l|arm*)
;; arm_level=$(get_arm_level "$uname_m" || :)
'armv7'*) case $arm_level in
file_os='android' 5|6)
true_arch='armv7' true_arch='general-32'
if check_flags 'neon'; then ;;
true_arch="$true_arch-neon" 7|8)
fi true_arch='armv7'
;; if match_flags neon; then
'loongarch64'*) true_arch='armv7-neon'
file_os='linux' fi
set_arch_loongarch64 ;;
;; *)
*) # Unsupported machine type, exit with error true_arch='general-32'
printf 'Unsupported machine type: %s\n' "$uname_m" if match_flags neon; then
exit 1 true_arch='armv7-neon'
;; fi
esac ;;
file_ext='tar' esac
;; ;;
'MINGW'*'ARM64'*) # Windows ARM64 system with POSIX compatibility layer loongarch64*)
# TODO: older chips might be armv8, but we have no good way to detect, /proc/cpuinfo shows x86 info set_arch_loongarch64
file_os='windows' ;;
true_arch='armv8-dotprod' riscv64)
file_ext='zip' true_arch='riscv64'
;; ;;
'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows x86_64system with POSIX compatibility layer e2k*)
get_flags true_arch='e2k'
check_znver_1_2 ;;
set_arch_x86_64 ppc|ppc32|powerpc)
file_os='windows' true_arch='ppc-32'
file_ext='zip' ;;
;; *)
*) # Don't hard-fail: fall back to general-* so ARCH=native still builds
# Unknown system type, exit with error get_bits
printf 'Unsupported system type: %s\n' "$uname_s" if [ "$bits" = "32" ]; then
exit 1 true_arch='general-32'
;; else
true_arch='general-64'
fi
;;
esac
;;
MINGW*ARM64*)
# Windows ARM64 (MSYS2/MinGW)
# Can't reliably detect ARM CPU features here
true_arch='armv8-dotprod'
;;
CYGWIN*|MINGW*|MSYS*)
# Windows x86_64 (MSYS2/Cygwin/MinGW)
get_flags
set_arch_x86_64
;;
*)
die "Unsupported system type: $uname_s"
;;
esac esac
if [ -z "$file_arch" ]; then printf '%s\n' "$true_arch"
file_arch=$true_arch
fi
file_name="stockfish-$file_os-$file_arch.$file_ext"
printf '%s %s\n' "$true_arch" "$file_name"