In this blog I will cover various methods of runtime feature detection on CPUs implementing ARMv8-A architecture. These methods include using HWCAP on Linux and Android, using NDK on Android and using /proc/cpuinfo. I will also provide sample code to detect the new optional features introduced in the ARMv8-A architecture. Before we dig deep in to the different methods, let us understand more about ARMv8-A CPU features.
/proc/cpuinfo
The ARMv8-A architecture has made many ARMv7-A optional features mandatory, including advanced SIMD (also called NEON). This applies to both the ARMv8-A execution states namely, AArch32 (32-bit execution state, backward compatible with ARMv7-A) and AArch64 (64-bit execution state).
The ARMv8-A architecture introduces a new set of optional instructions including AES. These instructions were not available in ARMv7-A architecture. These optional instructions are grouped into various categories, as listed below.
User-space programs can detect features supported by an ARMv8-A CPU at runtime, using many mechanisms including /proc/cpuinfo, HWCAP and the Android NDK CPU feature API. I will describe them in detail below.
Parsing /proc/cpuinfo is a popular way to detect CPU features. However I strongly recommend not to use /proc/cpuinfo on ARMv8-A for cpu feature detection, as this is not a portable way of detecting CPU features. Indeed, /proc/cpuinfo reflects the characteristics of the kernel rather than the application which is being executed. This means that /proc/cpuinfo is the same for both 32-bit and 64-bit processes running on an ARMv8-A 64-bit kernel. The ARMv8-A 64-bit kernel's /proc/cpuinfo output is quite different from that of a ARMv7-A 32-bit kernel. For example, ARMv8-A 64-bit kernel uses 'asimd' for advanced SIMD support, while ARMv7-A 32-bit kernel uses 'neon'. Thus, NEON detection code that looks for the "neon" string in /proc/cpuinfo will not work on ARMv8-A 64-bit kernel. Applications using /proc/cpuinfo should migrate to either using HWCAP or the NDK API, as they are maintained and controlled interfaces unlike /proc/cpuinfo.
HWCAP can be used on ARMv8-A processors to detect CPU features at runtime.
First, let me give you a brief overview of HWCAP. HWCAP uses the auxiliary vector feature provided by the Linux kernel. The Linux kernel's ELF binary loader uses the auxiliary vector to pass certain OS and architecture specific information to user space programs. Each entry in the vector consists of two items: the first identifies the type of entry, the second provides the value for that type. Processes can access these auxiliary vectors through the getauxval() API call.
getauxval()
getauxval() is a library function available to user space programs to retrieve a value from the auxiliary vector. This function is supported by both bionic (Android's libc library) and glibc (GNU libc library). The prototype of this function is unsigned long getauxval(unsigned long type); Given the argument type, getauxval() returns the corresponding value.
unsigned long getauxval(unsigned long type);
type
<sys/auxv.h> defines various vector types. Amongst them, AT_HWCAP and AT_HWCAP2 are of our interest. These auxiliary vector types specify processor capabilities. For these types, getauxval() returns a bit-mask with different bits indicating various processor capabilities.
<sys/auxv.h>
AT_HWCAP
AT_HWCAP2
Let us look at how HWCAP can be used on ARMv8-A. In ARMv8-A, the values returned by AT_HWCAP and AT_HWCAP2 depend on the execution state. For AArch32 (32-bit processes), AT_HWCAP provides flags specific to ARMv7 and prior architectures, NEON for example.AT_HWCAP2 provides ARMv8-A related flags like AES, CRC. In case of AArch64, AT_HWCAP provides ARMv8-A related flags like AES and AT_HWCAP2 bit-space is not used.
One of the main benefits of using HWCAP over other mechanisms like /proc/cpuinfo is portability. Existing ARMv7-A programs that use HWCAP to detect features like NEON will run as is on ARMv8-A, without any change. Since the getauxval() is supported in Linux (through glibc) and Android (through bionic), the same code can run on both Android and Linux.
glibc
bionic
The sample code below shows how to detect CPU features using AT_HWCAP in the AArch32 state.
#include <stdio.h> #include <sys/auxv.h> #include <asm/hwcap.h> int main() { long hwcaps2 = getauxval(AT_HWCAP2); if(hwcaps2 & HWCAP2_AES){ printf("AES instructions are available\n"); } if(hwcaps2 & HWCAP2_CRC32){ printf("CRC32 instructions are available\n"); } if(hwcaps2 & HWCAP2_PMULL){ printf("PMULL/PMULL2 instructions that operate on 64-bit data are available\n"); } if(hwcaps2 & HWCAP2_SHA1){ printf("SHA1 instructions are available\n"); } if(hwcaps2 & HWCAP2_SHA2){ printf("SHA2 instructions are available\n"); } return 0; }
The code below shows how to detect ARMv8-A CPU features in AArch64 process using HWCAP
#include <stdio.h> #include <sys/auxv.h> #include <asm/hwcap.h> int main() { long hwcaps= getauxval(AT_HWCAP); if(hwcaps & HWCAP_AES){ printf("AES instructions are available\n"); } if(hwcaps & HWCAP_CRC32){ printf("CRC32 instructions are available\n"); } if(hwcaps & HWCAP_PMULL){ printf("PMULL/PMULL2 instructions that operate on 64-bit data are available\n"); } if(hwcaps & HWCAP_SHA1){ printf("SHA1 instructions are available\n"); } if(hwcaps & HWCAP_SHA2){ printf("SHA2 instructions are available\n"); } return 0; }
The Android NDK provides an API to detect the CPU architecture family and the supported features at run time.
There are two main functions, android_getCpuFamily() and android_getCpuFeatures().
android_getCpuFamily()
android_getCpuFeatures()
cpu-features.h
The latest NDK release (version 10b, September 2014) supports ARMv8-A CPU features detection only for the AArch64 mode. However, the NDK project in AOSP supports both the AArch32 and the AArch64 CPU feature flags. The AArch32 feature flags were added to the AOSP in the change list 106360. The NDK uses HWCAP internally to detect the CPU features.
#include <stdio.h> #include "cpu-features.h" int main() { AndroidCpuFamily family; family = android_getCpuFamily(); if(family == ANDROID_CPU_FAMILY_ARM){ printf("CPU family is ANDROID_CPU_FAMILY_ARM \n"); } else if(family == ANDROID_CPU_FAMILY_ARM64){ printf("CPU family is ANDROID_CPU_FAMILY_ARM64 \n"); } else { printf("CPU family is %d \n", family); } return 0; }
#include <stdio.h> #include "cpu-features.h" void printArm64Features(){ uint64_t features; features = android_getCpuFeatures(); if(features & ANDROID_CPU_ARM64_FEATURE_AES){ printf("AES instructions are available\n"); } if(features & ANDROID_CPU_ARM64_FEATURE_PMULL){ printf("PMULL instructions, that operate on 64-bit data, are available\n"); } if(features & ANDROID_CPU_ARM64_FEATURE_SHA1){ printf("SHA1 instructions are available\n"); } if(features & ANDROID_CPU_ARM64_FEATURE_SHA2){ printf("SHA2 instructions are available\n"); } if(features & ANDROID_CPU_ARM64_FEATURE_CRC32){ printf("CRC32 instructions are available\n"); } } void printArmFeatures(){ uint64_t features; features = android_getCpuFeatures(); if(features & ANDROID_CPU_ARM_FEATURE_AES){ printf("AES instructions are available\n"); } if(features & ANDROID_CPU_ARM_FEATURE_PMULL){ printf("PMULL instructions, that operate on 64-bit data, are available\n"); } if(features & ANDROID_CPU_ARM_FEATURE_SHA1){ printf("SHA1 instructions are available\n"); } if(features & ANDROID_CPU_ARM_FEATURE_SHA2){ printf("SHA2 instructions are available\n"); } if(features & ANDROID_CPU_ARM_FEATURE_CRC32){ printf("CRC32 instructions are available\n"); } } int main(){ AndroidCpuFamily family; family = android_getCpuFamily(); if(family == ANDROID_CPU_FAMILY_ARM){ printArmFeatures(); } if(family == ANDROID_CPU_FAMILY_ARM64){ printArm64Features(); } return 0; }
The ARMv8-A architecture makes certain ARMv7-A features mandatory and introduces a new set of optional features. The popular way of detecting the features at runtime by parsing /proc/cpuinfo is not portable to ARMv8-A and existing code will not work without tricky changes. Instead, application programmers can easily use HWCAP on Linux and the NDK on Android. For detecting ARMv8-A optional features in the AArch32 mode, programmers should use HWCAP on Android as the latest NDK does not have support for it yet.
Thanks, well written guide.