Linux: ask the dynamic loader

With glibc 2.33 or later (ldd --version shows the version), the dynamic loader reports which x86-64 levels it would load optimized libraries for:

$ /lib64/ld-linux-x86-64.so.2 --help | grep -A4 'glibc-hwcaps directories'
Subdirectories of glibc-hwcaps directories, in priority order:
  x86-64-v4
  x86-64-v3
  x86-64-v2 (supported, searched)

The highest level marked supported is the machine’s level: the output above is from an x86-64-v2 machine. If no level is marked, the machine is at the x86-64-v1 baseline.

Linux: run the x86-64-level script

This site’s x86-64-level script works where glibc is older, and says what is missing for the next level. It reads /proc/cpuinfo and needs only a POSIX shell. Download it, read it, then run it:

curl -fsSLO https://x86-64.net/x86-64-level
less x86-64-level
sh x86-64-level -v
x86-64-v2
missing for x86-64-v3: avx avx2 bmi1 bmi2 f16c fma abm movbe xsave

-q prints only the number, for use in scripts. The names in the output are Linux’s flag names; the level pages map them to features.

Source of x86-64-level
#!/bin/sh
# x86-64-level: print the x86-64 microarchitecture level (x86-64-v1 to
# x86-64-v4) of this machine's processor, as Linux reports it.
#
# Documentation: https://x86-64.net/check/
# SPDX-License-Identifier: MIT
#
# Usage: x86-64-level [-q] [-v]
#   -q  print only the level number (1 to 4)
#   -v  also list the features missing for the next level
#
# X86_64_LEVEL_CPUINFO names a file to read instead of /proc/cpuinfo.
set -eu

# Linux flag names of the features each level adds, following the x86-64
# psABI. OSFXSR and OSXSAVE have no flags; Linux clears the AVX family
# flags when the OS has not enabled XSAVE, so the lists below cover them.
LEVEL1="cmov cx8 fpu fxsr mmx syscall sse sse2"
LEVEL2="cx16 lahf_lm popcnt pni sse4_1 sse4_2 ssse3"
LEVEL3="avx avx2 bmi1 bmi2 f16c fma abm movbe xsave"
LEVEL4="avx512f avx512bw avx512cd avx512dq avx512vl"

quiet=0
verbose=0
for arg in "$@"; do
	case $arg in
	-q) quiet=1 ;;
	-v) verbose=1 ;;
	-h | --help)
		sed -n '2,13s/^# \{0,1\}//p' "$0"
		exit 0
		;;
	*)
		echo "x86-64-level: unknown option: $arg" >&2
		exit 2
		;;
	esac
done

cpuinfo=${X86_64_LEVEL_CPUINFO:-/proc/cpuinfo}
if [ ! -r "$cpuinfo" ]; then
	echo "x86-64-level: cannot read $cpuinfo (this script needs Linux)" >&2
	exit 1
fi

# All CPUs report the same flags; the first "flags" line is enough.
flags=$(sed -n 's/^flags[[:space:]]*:[[:space:]]*//p' "$cpuinfo" | head -n 1)
if [ -z "$flags" ]; then
	echo "x86-64-level: no x86 flags line in $cpuinfo" >&2
	exit 1
fi

missing() {
	for flag in $1; do
		case " $flags " in
		*" $flag "*) ;;
		*) printf ' %s' "$flag" ;;
		esac
	done
}

level=0
lack=
for n in 1 2 3 4; do
	eval "wanted=\$LEVEL$n"
	lack=$(missing "$wanted")
	[ -n "$lack" ] && break
	level=$n
done

if [ "$level" -eq 0 ]; then
	echo "x86-64-level: not even the x86-64 baseline; missing:$lack" >&2
	exit 1
fi

if [ "$quiet" -eq 1 ]; then
	echo "$level"
else
	echo "x86-64-v$level"
fi
if [ "$verbose" -eq 1 ] && [ "$level" -lt 4 ]; then
	echo "missing for x86-64-v$((level + 1)):$lack"
fi

Linux: read the flags yourself

Every feature has a flag in /proc/cpuinfo, listed on the level pages. For example, to see which of the x86-64-v3 flags a processor has:

grep -m1 -o -w -E 'avx|avx2|bmi1|bmi2|f16c|fma|abm|movbe|xsave' /proc/cpuinfo | sort -u

A few flags are named differently from the feature: SSE3 is pni, LZCNT is abm, CMPXCHG16B is cx16 and LAHF/SAHF is lahf_lm.

Containers and virtual machines

A container shares the kernel of the machine it runs on, so every method on this page reports that machine’s processor, from inside the container as well as outside it.

A virtual machine is different: the guest sees the processor model its hypervisor presents, which often hides newer features. Checking inside the VM gives the level that software in the VM can use, which may be lower than the host’s. Levels in virtual machines explains how to change the model in QEMU, libvirt, Proxmox, VMware and Hyper-V.

Windows

Microsoft’s Coreinfo lists the processor’s features. Run coreinfo64 -f and compare the lines marked * with the level tables: it reports every feature the levels need, including LAHF-SAHF, CX16, MOVBE, F16C and the AVX-512 subsets.

PowerShell 7.4 or later can also ask .NET, which reports what both the processor and Windows support. .NET has no properties for MOVBE, F16C, CMPXCHG16B or LAHF/SAHF, so the result is a close approximation. Save this as level.ps1 and run pwsh ./level.ps1:

using namespace System.Runtime.Intrinsics.X86
# PowerShell 7.4 or later. .NET does not report MOVBE, F16C,
# CMPXCHG16B or LAHF/SAHF, so this is a close approximation.
$v2 = [Sse3]::IsSupported -and [Ssse3]::IsSupported -and
      [Sse41]::IsSupported -and [Sse42]::IsSupported -and [Popcnt]::IsSupported
$v3 = $v2 -and [Avx2]::IsSupported -and [Bmi1]::IsSupported -and
      [Bmi2]::IsSupported -and [Fma]::IsSupported -and [Lzcnt]::IsSupported
$v4 = $v3 -and [Avx512F]::IsSupported -and [Avx512BW]::IsSupported -and
      [Avx512CD]::IsSupported -and [Avx512DQ]::IsSupported -and [Avx512F+VL]::IsSupported
if ($v4) { 'x86-64-v4' } elseif ($v3) { 'x86-64-v3' } elseif ($v2) { 'x86-64-v2' } else { 'x86-64-v1' }

macOS

On a Mac with an Intel processor, the kernel lists the processor’s features:

sysctl -n machdep.cpu.features machdep.cpu.leaf7_features machdep.cpu.extfeatures

Compare the names with the level tables. macOS spells a few of them its own way, for example AVX1.0 for AVX and SSE4.1 for SSE4.1.

From C and C++

GCC 12 and Clang 18 accept the level names in __builtin_cpu_supports, which checks the running processor and operating system:

#include <stdio.h>

int main(void)
{
	/* GCC 12 or later, Clang 18 or later */
	if (__builtin_cpu_supports("x86-64-v4"))
		puts("x86-64-v4");
	else if (__builtin_cpu_supports("x86-64-v3"))
		puts("x86-64-v3");
	else if (__builtin_cpu_supports("x86-64-v2"))
		puts("x86-64-v2");
	else
		puts("x86-64-v1");
	return 0;
}

This is also how a program built for the baseline can choose a faster code path at run time.

Sources

  1. glibc 2.33 release notes (glibc-hwcaps)
  2. GCC 12 release notes
  3. LLVM change D158811: __builtin_cpu_supports x86-64 levels
  4. Coreinfo (Microsoft Sysinternals)
  5. System.Runtime.Intrinsics.X86 namespace (.NET)