Add ARM v8.7 FP8 ISA feature detection and reporting to cpuinfo. (#413)

Expose ARM v8.7 / Armv9.6-A FP8 instructions across public API structs, Linux HWCAP2 / /proc/cpuinfo parsing, Apple sysctls, Windows processor feature checks, and isa_info:
- Base FP8 (cpuinfo_has_arm_fp8 / FEAT_FP8 / HWCAP2_F8CVT / F8E4M3 / F8E5M2)
- FP8 4-way Dot Product (cpuinfo_has_arm_f8dot / FEAT_FP8DOT4 / HWCAP2_F8DP4)
- FP8 8-way Matrix Multiply-Accumulate into FP32 (cpuinfo_has_arm_f8mm / FEAT_F8F32MM / HWCAP_F8MM8)

Fixes https://github.com/pytorch/cpuinfo/issues/412
diff --git a/include/cpuinfo-mock.h b/include/cpuinfo-mock.h
index 7bb6d1e..24b9768 100644
--- a/include/cpuinfo-mock.h
+++ b/include/cpuinfo-mock.h
@@ -57,7 +57,7 @@
 ssize_t CPUINFO_ABI cpuinfo_mock_read(int fd, void* buffer, size_t capacity);
 
 #if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64
-void CPUINFO_ABI cpuinfo_set_hwcap(uint32_t hwcap);
+void CPUINFO_ABI cpuinfo_set_hwcap(uint64_t hwcap);
 #endif
 #if CPUINFO_ARCH_ARM
 void CPUINFO_ABI cpuinfo_set_hwcap2(uint64_t hwcap2);
diff --git a/include/cpuinfo.h b/include/cpuinfo.h
index bee3c23..ed05330 100644
--- a/include/cpuinfo.h
+++ b/include/cpuinfo.h
@@ -1771,6 +1771,9 @@
 	bool sme_bi32i32;
 	bool sme_b16b16;
 	bool sme_f16f16;
+	bool fp8;
+	bool f8dot;
+	bool f8mm;
 	uint32_t svelen;
 	uint32_t smelen;
 #endif
@@ -2081,6 +2084,30 @@
 #endif
 }
 
+static inline bool cpuinfo_has_arm_fp8(void) {
+#if CPUINFO_ARCH_ARM64
+	return cpuinfo_isa.fp8;
+#else
+	return false;
+#endif
+}
+
+static inline bool cpuinfo_has_arm_f8dot(void) {
+#if CPUINFO_ARCH_ARM64
+	return cpuinfo_isa.f8dot;
+#else
+	return false;
+#endif
+}
+
+static inline bool cpuinfo_has_arm_f8mm(void) {
+#if CPUINFO_ARCH_ARM64
+	return cpuinfo_isa.f8mm;
+#else
+	return false;
+#endif
+}
+
 static inline bool cpuinfo_has_arm_aes(void) {
 #if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64
 	return cpuinfo_isa.aes;
diff --git a/src/arm/linux/aarch32-isa.c b/src/arm/linux/aarch32-isa.c
index 5a9f2cc..5ed5fc6 100644
--- a/src/arm/linux/aarch32-isa.c
+++ b/src/arm/linux/aarch32-isa.c
@@ -23,7 +23,7 @@
 #endif
 
 void cpuinfo_arm_linux_decode_isa_from_proc_cpuinfo(
-	uint32_t features,
+	uint64_t features,
 	uint64_t features2,
 	uint32_t midr,
 	uint32_t architecture_version,
diff --git a/src/arm/linux/aarch64-isa.c b/src/arm/linux/aarch64-isa.c
index 8e83d51..bd09a9b 100644
--- a/src/arm/linux/aarch64-isa.c
+++ b/src/arm/linux/aarch64-isa.c
@@ -6,7 +6,7 @@
 #include <sys/prctl.h>
 
 void cpuinfo_arm64_linux_decode_isa_from_proc_cpuinfo(
-	uint32_t features,
+	uint64_t features,
 	uint64_t features2,
 	uint32_t midr,
 	const struct cpuinfo_arm_chipset chipset[restrict static 1],
@@ -174,6 +174,15 @@
 	if (features & CPUINFO_ARM_LINUX_FEATURE_ASIMDFHM) {
 		isa->fhm = true;
 	}
+	if (features2 & CPUINFO_ARM_LINUX_FEATURE2_FP8) {
+		isa->fp8 = true;
+	}
+	if (features2 & CPUINFO_ARM_LINUX_FEATURE2_F8DOT) {
+		isa->f8dot = true;
+	}
+	if (features & CPUINFO_ARM_LINUX_FEATURE_F8MM) {
+		isa->f8mm = true;
+	}
 
 #ifndef PR_SVE_GET_VL
 #define PR_SVE_GET_VL 51
diff --git a/src/arm/linux/api.h b/src/arm/linux/api.h
index 43bbbc3..733a9dd 100644
--- a/src/arm/linux/api.h
+++ b/src/arm/linux/api.h
@@ -119,6 +119,7 @@
 #define CPUINFO_ARM_LINUX_FEATURE_SB UINT32_C(0x20000000)
 #define CPUINFO_ARM_LINUX_FEATURE_PACA UINT32_C(0x40000000)
 #define CPUINFO_ARM_LINUX_FEATURE_PACG UINT32_C(0x80000000)
+#define CPUINFO_ARM_LINUX_FEATURE_F8MM UINT64_C(0x0000000800000000)
 
 #define CPUINFO_ARM_LINUX_FEATURE2_DCPODP UINT32_C(0x00000001)
 #define CPUINFO_ARM_LINUX_FEATURE2_SVE2 UINT32_C(0x00000002)
@@ -145,6 +146,8 @@
 #define CPUINFO_ARM_LINUX_FEATURE2_SME_BI32I32 UINT64_C(0x0000010000000000)
 #define CPUINFO_ARM_LINUX_FEATURE2_SME_B16B16 UINT64_C(0x0000020000000000)
 #define CPUINFO_ARM_LINUX_FEATURE2_SME_F16F16 UINT64_C(0x0000040000000000)
+#define CPUINFO_ARM_LINUX_FEATURE2_FP8 UINT64_C(0x0008000000000000)
+#define CPUINFO_ARM_LINUX_FEATURE2_F8DOT UINT64_C(0x0020000000000000)
 #endif
 
 #define CPUINFO_ARM_LINUX_VALID_ARCHITECTURE UINT32_C(0x00010000)
@@ -179,7 +182,7 @@
 	uint32_t architecture_flags;
 	struct cpuinfo_arm_linux_proc_cpuinfo_cache proc_cpuinfo_cache;
 #endif
-	uint32_t features;
+	uint64_t features;
 	uint64_t features2;
 	/**
 	 * Main ID Register value.
@@ -302,14 +305,14 @@
 
 #if CPUINFO_ARCH_ARM
 CPUINFO_INTERNAL bool cpuinfo_arm_linux_hwcap_from_getauxval(
-	uint32_t hwcap[restrict static 1],
+	uint64_t hwcap[restrict static 1],
 	uint64_t hwcap2[restrict static 1]);
 CPUINFO_INTERNAL bool cpuinfo_arm_linux_hwcap_from_procfs(
-	uint32_t hwcap[restrict static 1],
+	uint64_t hwcap[restrict static 1],
 	uint64_t hwcap2[restrict static 1]);
 
 CPUINFO_INTERNAL void cpuinfo_arm_linux_decode_isa_from_proc_cpuinfo(
-	uint32_t features,
+	uint64_t features,
 	uint64_t features2,
 	uint32_t midr,
 	uint32_t architecture_version,
@@ -318,11 +321,11 @@
 	struct cpuinfo_arm_isa isa[restrict static 1]);
 #elif CPUINFO_ARCH_ARM64
 CPUINFO_INTERNAL void cpuinfo_arm_linux_hwcap_from_getauxval(
-	uint32_t hwcap[restrict static 1],
+	uint64_t hwcap[restrict static 1],
 	uint64_t hwcap2[restrict static 1]);
 
 CPUINFO_INTERNAL void cpuinfo_arm64_linux_decode_isa_from_proc_cpuinfo(
-	uint32_t features,
+	uint64_t features,
 	uint64_t features2,
 	uint32_t midr,
 	const struct cpuinfo_arm_chipset chipset[restrict static 1],
diff --git a/src/arm/linux/cpuinfo.c b/src/arm/linux/cpuinfo.c
index 081d9aa..c627dc8 100644
--- a/src/arm/linux/cpuinfo.c
+++ b/src/arm/linux/cpuinfo.c
@@ -237,6 +237,12 @@
 				} else if (memcmp(feature_start, "lrcpc", feature_length) == 0) {
 #if CPUINFO_ARCH_ARM64
 					processor->features |= CPUINFO_ARM_LINUX_FEATURE_LRCPC;
+				} else if (memcmp(feature_start, "f8cvt", feature_length) == 0) {
+					processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_FP8;
+				} else if (memcmp(feature_start, "f8dp4", feature_length) == 0) {
+					processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_F8DOT;
+				} else if (memcmp(feature_start, "f8mm8", feature_length) == 0) {
+					processor->features |= CPUINFO_ARM_LINUX_FEATURE_F8MM;
 #endif
 #if CPUINFO_ARCH_ARM
 				} else if (memcmp(feature_start, "thumb", feature_length) == 0) {
diff --git a/src/arm/linux/hwcap.c b/src/arm/linux/hwcap.c
index 7f7b4df..0ffc635 100644
--- a/src/arm/linux/hwcap.c
+++ b/src/arm/linux/hwcap.c
@@ -26,8 +26,8 @@
 #endif
 
 #if CPUINFO_MOCK
-static uint32_t mock_hwcap = 0;
-void cpuinfo_set_hwcap(uint32_t hwcap) {
+static uint64_t mock_hwcap = 0;
+void cpuinfo_set_hwcap(uint64_t hwcap) {
 	mock_hwcap = hwcap;
 }
 
@@ -40,7 +40,7 @@
 #if CPUINFO_ARCH_ARM
 typedef unsigned long (*getauxval_function_t)(unsigned long);
 
-bool cpuinfo_arm_linux_hwcap_from_getauxval(uint32_t hwcap[restrict static 1], uint64_t hwcap2[restrict static 1]) {
+bool cpuinfo_arm_linux_hwcap_from_getauxval(uint64_t hwcap[restrict static 1], uint64_t hwcap2[restrict static 1]) {
 #if CPUINFO_MOCK
 	*hwcap = mock_hwcap;
 	*hwcap2 = mock_hwcap2;
@@ -63,8 +63,8 @@
 		goto cleanup;
 	}
 
-	*hwcap = getauxval(AT_HWCAP);
-	*hwcap2 = getauxval(AT_HWCAP2);
+	*hwcap = (uint64_t)getauxval(AT_HWCAP);
+	*hwcap2 = (uint64_t)getauxval(AT_HWCAP2);
 
 cleanup:
 	if (libc != NULL) {
@@ -74,8 +74,8 @@
 	return getauxval != NULL;
 #elif defined(__GLIBC__) && defined(__GLIBC_MINOR__) && (__GLIBC__ > 2 || __GLIBC__ == 2 && __GLIBC_MINOR__ >= 16)
 	/* GNU/Linux: getauxval is supported since glibc-2.16 */
-	*hwcap = getauxval(AT_HWCAP);
-	*hwcap2 = getauxval(AT_HWCAP2);
+	*hwcap = (uint64_t)getauxval(AT_HWCAP);
+	*hwcap2 = (uint64_t)getauxval(AT_HWCAP2);
 	return true;
 #else
 	return false;
@@ -83,7 +83,7 @@
 }
 
 #ifdef __ANDROID__
-bool cpuinfo_arm_linux_hwcap_from_procfs(uint32_t hwcap[restrict static 1], uint64_t hwcap2[restrict static 1]) {
+bool cpuinfo_arm_linux_hwcap_from_procfs(uint64_t hwcap[restrict static 1], uint64_t hwcap2[restrict static 1]) {
 #if CPUINFO_MOCK
 	*hwcap = mock_hwcap;
 	*hwcap2 = mock_hwcap2;
@@ -110,7 +110,7 @@
 			if (bytes_read == sizeof(elf_auxv)) {
 				switch (elf_auxv.a_type) {
 					case AT_HWCAP:
-						hwcaps[0] = (uint32_t)elf_auxv.a_un.a_val;
+						hwcaps[0] = elf_auxv.a_un.a_val;
 						break;
 					case AT_HWCAP2:
 						hwcaps[1] = (uint64_t)elf_auxv.a_un.a_val;
@@ -141,12 +141,12 @@
 }
 #endif /* __ANDROID__ */
 #elif CPUINFO_ARCH_ARM64
-void cpuinfo_arm_linux_hwcap_from_getauxval(uint32_t hwcap[restrict static 1], uint64_t hwcap2[restrict static 1]) {
+void cpuinfo_arm_linux_hwcap_from_getauxval(uint64_t hwcap[restrict static 1], uint64_t hwcap2[restrict static 1]) {
 #if CPUINFO_MOCK
 	*hwcap = mock_hwcap;
 	*hwcap2 = mock_hwcap2;
 #else
-	*hwcap = (uint32_t)getauxval(AT_HWCAP);
+	*hwcap = (uint64_t)getauxval(AT_HWCAP);
 	*hwcap2 = (uint64_t)getauxval(AT_HWCAP2);
 	return;
 #endif
diff --git a/src/arm/linux/init.c b/src/arm/linux/init.c
index 534230d..48c9f4a 100644
--- a/src/arm/linux/init.c
+++ b/src/arm/linux/init.c
@@ -335,7 +335,7 @@
 #endif
 
 #if CPUINFO_ARCH_ARM
-	uint32_t isa_features = 0;
+	uint64_t isa_features = 0;
 	uint64_t isa_features2 = 0;
 #ifdef __ANDROID__
 	/*
@@ -388,7 +388,7 @@
 		&chipset,
 		&cpuinfo_isa);
 #elif CPUINFO_ARCH_ARM64
-	uint32_t isa_features = 0;
+	uint64_t isa_features = 0;
 	uint64_t isa_features2 = 0;
 	/* getauxval is always available on ARM64 Android */
 	cpuinfo_arm_linux_hwcap_from_getauxval(&isa_features, &isa_features2);
diff --git a/src/arm/mach/init.c b/src/arm/mach/init.c
index 11de4ae..ca89023 100644
--- a/src/arm/mach/init.c
+++ b/src/arm/mach/init.c
@@ -491,6 +491,9 @@
 	cpuinfo_isa.sme_bi32i32 = get_sys_info_by_name("hw.optional.arm.SME_BI32I32") != 0;
 	cpuinfo_isa.sme_b16b16 = get_sys_info_by_name("hw.optional.arm.FEAT_SME_B16B16") != 0;
 	cpuinfo_isa.sme_f16f16 = get_sys_info_by_name("hw.optional.arm.FEAT_SME_F16F16") != 0;
+	cpuinfo_isa.fp8 = get_sys_info_by_name("hw.optional.arm.FEAT_FP8") != 0;
+	cpuinfo_isa.f8dot = get_sys_info_by_name("hw.optional.arm.FEAT_FP8DOT4") != 0;
+	cpuinfo_isa.f8mm = get_sys_info_by_name("hw.optional.arm.FEAT_F8F32MM") != 0;
 
 	cpuinfo_isa.smelen = get_sys_info_by_name("hw.optional.arm.sme_max_svl_b");
 
diff --git a/src/arm/windows/init.c b/src/arm/windows/init.c
index a07fbe4..528ae32 100644
--- a/src/arm/windows/init.c
+++ b/src/arm/windows/init.c
@@ -235,6 +235,15 @@
 	// - sme_bi32i32

 

 	cpuinfo_isa.bf16 = IsProcessorFeaturePresent(PF_ARM_V86_BF16_INSTRUCTIONS_AVAILABLE) != 0;

+#if defined(PF_ARM_V87_FP8_INSTRUCTIONS_AVAILABLE)

+	cpuinfo_isa.fp8 = IsProcessorFeaturePresent(PF_ARM_V87_FP8_INSTRUCTIONS_AVAILABLE) != 0;

+#endif

+#if defined(PF_ARM_V87_FP8_DOT4_INSTRUCTIONS_AVAILABLE)

+	cpuinfo_isa.f8dot = IsProcessorFeaturePresent(PF_ARM_V87_FP8_DOT4_INSTRUCTIONS_AVAILABLE) != 0;

+#endif

+#if defined(PF_ARM_V87_FP8_F32MM_INSTRUCTIONS_AVAILABLE)

+	cpuinfo_isa.f8mm = IsProcessorFeaturePresent(PF_ARM_V87_FP8_F32MM_INSTRUCTIONS_AVAILABLE) != 0;

+#endif

 

 	// TODO: This is not available in the Windows SDK yet , so conservatively go with the lowest value (128 bits)

 	// https://developer.arm.com/documentation/101427/0102/Register-descriptions/Scalable-vector-extensions--SVE--registers/ZCR-EL1--SVE-Control-Register--EL1

diff --git a/test/mock/pixel-8.cc b/test/mock/pixel-8.cc
index a0235b4..b04a98b 100644
--- a/test/mock/pixel-8.cc
+++ b/test/mock/pixel-8.cc
@@ -582,6 +582,18 @@
 	ASSERT_TRUE(cpuinfo_has_arm_crc32());
 }
 
+TEST(ISA, fp8) {
+	ASSERT_FALSE(cpuinfo_has_arm_fp8());
+}
+
+TEST(ISA, f8dot) {
+	ASSERT_FALSE(cpuinfo_has_arm_f8dot());
+}
+
+TEST(ISA, f8mm) {
+	ASSERT_FALSE(cpuinfo_has_arm_f8mm());
+}
+
 TEST(L1I, count) {
 	ASSERT_EQ(9, cpuinfo_get_l1i_caches_count());
 }
diff --git a/tools/isa-info.c b/tools/isa-info.c
index 7ec3eec..0cc2146 100644
--- a/tools/isa-info.c
+++ b/tools/isa-info.c
@@ -170,6 +170,9 @@
 	printf("\tARM v8.2 Int8 matrix multiplication: %s\n", cpuinfo_has_arm_i8mm() ? "yes" : "no");
 	printf("\tARM v8.3 JS conversion: %s\n", cpuinfo_has_arm_jscvt() ? "yes" : "no");
 	printf("\tARM v8.3 complex: %s\n", cpuinfo_has_arm_fcma() ? "yes" : "no");
+	printf("\tARM v8.7 FP8: %s\n", cpuinfo_has_arm_fp8() ? "yes" : "no");
+	printf("\tARM v8.7 FP8 dot product: %s\n", cpuinfo_has_arm_f8dot() ? "yes" : "no");
+	printf("\tARM v8.7 FP8 matrix multiplication: %s\n", cpuinfo_has_arm_f8mm() ? "yes" : "no");
 
 	printf("SIMD extensions:\n");
 	printf("\tARM SVE: %s\n", cpuinfo_has_arm_sve() ? "yes" : "no");