diff --git a/ndk/docs/CHANGES.TXT b/ndk/docs/CHANGES.TXT index e36a82f50..f3d5cfb42 100644 --- a/ndk/docs/CHANGES.TXT +++ b/ndk/docs/CHANGES.TXT @@ -28,6 +28,14 @@ IMPORTANT CHANGES: More details about ABIs is now available in docs/CPU-ARCH-ABIS.TXT +- Added a new sample static library, named "cpufeatures" to detect + CPU Features at runtime. For now, this can be used to detect the + availability of ARM Advanced SIMD (a.k.a. NEON) instruction support + at runtime in order to provide optimized code paths for specific + operations. + + See docs/CPU-FEATURES.TXT for details. + - GCC 4.4.0 is now used by default by the NDK. It generates better code than GCC 4.2.1, which was used in previous releases. However, the compiler's C++ frontend is also a lot more pedantic regarding certain template constructs diff --git a/ndk/docs/CPU-FEATURES.TXT b/ndk/docs/CPU-FEATURES.TXT new file mode 100644 index 000000000..f69807c8a --- /dev/null +++ b/ndk/docs/CPU-FEATURES.TXT @@ -0,0 +1,92 @@ +Android NDK CPU Features detection library: +------------------------------------------- + +This NDK provides a small library named "cpufeatures" that can be used at +runtime to detect the target device's CPU family and the optional features +it supports. + +Usage: +------ + +The library is available from sources/cpufeatures. It provides an Android.mk +build script that can be used to build it as a static library. + +To use it, you must: + + * add 'cpufeatures' to your APP_MODULES list in your Application.mk + + * include 'sources/cpufeatures/Android.mk' at the start or end of your + Android.mk file. + + * add 'sources/cpufeatures' to your LOCAL_C_INCLUDES definition. + + * add 'cpufeatures' to your LOCAL_STATIC_LIBRARIES definition when building + your final shared library. + + your source code can then #include to compile against it. + +Here is a simple example: + +/apps//Application.mk: + APP_PROJECT_PATH := + APP_MODULES := cpufeatures + +/jni/Android.mk: + LOCAL_PATH := $(call my-dir) + + include $(CLEAR_VARS) + LOCAL_MODULE := + LOCAL_C_INCLUDES += sources/cpufeatures + LOCAL_SRC_FILES := + LOCAL_STATIC_LIBRARIES += cpufeatures + include $(BUILD_SHARED_LIBRARY) + + include sources/cpufeature/Android.mk + + +Features: +--------- + +Two functions are provided for now: + + AndroidCpuFamily android_getCpuFamily(); + +Returns the target device's CPU Family as an enum. For now, the only +supported family is ANDROID_CPU_FAMILY_ARM. + + + uint64_t android_getCpuFeatures(); + +Returns the set of optional features supported by the device's CPU. +The result is a set of bit-flags, each corresponding to one CPU +Family-specific optional feature. + +Currently, only the following flags are defined, for the ARM CPU Family: + + ANDROID_CPU_ARM_FEATURE_ARMv7 + Indicates that the device's CPU supports the ARMv7-A instruction + set as supported by the "armeabi-v7a" abi (see CPU-ARCH-ABIS.TXT). + This corresponds to Thumb-2 and VFPv3-D16 instructions. + + ANDROID_CPU_ARM_FEATURE_VFPv3 + Indicates that the device's CPU supports the VFPv3 hardware FPU + instruction set extension. Due to the definition of 'armeabi-v7a', + this will always be the case if ANDROID_CPU_ARM_FEATURE_ARMv7 is + returned. + + Note that this corresponds to the minimum profile VFPv3-D16 that + only provides 16 hardware FP registers. + + ANDROID_CPU_ARM_FEATURE_NEON + Indicates that the device's CPU supports the ARM Advanced SIMD + (a.k.a. NEON) vector instruction set extension. Note that ARM + mandates that such CPUs also implement VFPv3-D32, which provides + 32 hardware FP registers (shared with the NEON unit). + + +Important Note: +--------------- + +The cpufeatures library will be updated to support more CPU families and +optional features in the future. It is designed to work as-is on all +official Android platform versions. diff --git a/ndk/sources/cpufeatures/Android.mk b/ndk/sources/cpufeatures/Android.mk new file mode 100644 index 000000000..49d479a88 --- /dev/null +++ b/ndk/sources/cpufeatures/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH := $(call my-dir) + +# If targetting ARM CPUs, build the ARMv7 detection +# function. It will only be called by the main +# library if we detect ARMv7 through a system property. +# +include $(CLEAR_VARS) +LOCAL_MODULE := cpufeatures +LOCAL_SRC_FILES := cpu-features.c +include $(BUILD_STATIC_LIBRARY) diff --git a/ndk/sources/cpufeatures/cpu-features.c b/ndk/sources/cpufeatures/cpu-features.c new file mode 100644 index 000000000..d16563575 --- /dev/null +++ b/ndk/sources/cpufeatures/cpu-features.c @@ -0,0 +1,219 @@ +#include +#include +#include +#include "cpu-features.h" +#include +#include +#include +#include + +static pthread_once_t g_once; +static AndroidCpuFamily g_cpuFamily; +static uint64_t g_cpuFeatures; + +static const int android_cpufeatures_debug = 0; + +#define D(...) \ + do { \ + if (android_cpufeatures_debug) { \ + printf(__VA_ARGS__); fflush(stdout); \ + } \ + } while (0) + +/* Read the content of /proc/cpuinfo into a user-provided buffer. + * Return the length of the data, or -1 on error. Does *not* + * zero-terminate the content. Will not read more + * than 'buffsize' bytes. + */ +static int +read_file(const char* pathname, char* buffer, size_t buffsize) +{ + int fd, len; + + fd = open(pathname, O_RDONLY); + if (fd < 0) + return -1; + + do { + len = read(fd, buffer, buffsize); + } while (len < 0 && errno == EINTR); + + close(fd); + + return len; +} + +/* Extract the content of a the first occurence of a given field in + * the content of /proc/cpuinfo and return it as a heap-allocated + * string that must be freed by the caller. + * + * Return NULL if not found + */ +static char* +extract_cpuinfo_field(char* buffer, int buflen, const char* field) +{ + int fieldlen = strlen(field); + char* bufend = buffer + buflen; + char* result = NULL; + int len, ignore; + const char *p, *q; + + /* Look for first field occurence, and ensures it starts the line. + */ + p = buffer; + bufend = buffer + buflen; + for (;;) { + p = memmem(p, bufend-p, field, fieldlen); + if (p == NULL) + goto EXIT; + + if (p == buffer || p[-1] == '\n') + break; + + p += fieldlen; + } + + /* Skip to the first column followed by a space */ + p += fieldlen; + p = memchr(p, ':', bufend-p); + if (p == NULL || p[1] != ' ') + goto EXIT; + + /* Find the end of the line */ + p += 2; + q = memchr(p, '\n', bufend-p); + if (q == NULL) + q = bufend; + + /* Copy the line into a heap-allocated buffer */ + len = q-p; + result = malloc(len+1); + if (result == NULL) + goto EXIT; + + memcpy(result, p, len); + result[len] = '\0'; + +EXIT: + return result; +} + + +/* Checks that a space-separated list of items contains one given 'item'. + * Returns 1 if found, 0 otherwise. + */ +static int +has_list_item(const char* list, const char* item) +{ + const char* p = list; + int itemlen = strlen(item); + + if (list == NULL) + return 0; + + while (*p) { + const char* q; + + /* skip spaces */ + while (*p == ' ' || *p == '\t') + p++; + + /* find end of current list item */ + q = p; + while (*q && *q != ' ' && *q != '\t') + q++; + + if (itemlen == q-p && !memcmp(p, item, itemlen)) + return 1; + + /* skip to next item */ + p = q; + } + return 0; +} + + +static void +android_cpuInit(void) +{ + g_cpuFamily = ANDROID_CPU_FAMILY_UNKNOWN; + g_cpuFeatures = 0; + +#ifdef __ARM_ARCH__ + { + char cpuinfo[4096]; + int cpuinfo_len = read_file("/proc/cpuinfo", cpuinfo, sizeof cpuinfo); + char* features = NULL; + char* architecture = NULL; + + g_cpuFamily = ANDROID_CPU_FAMILY_ARM; + + D("cpuinfo_len is (%d):\n%.*s\n", cpuinfo_len, cpuinfo_len, cpuinfo); + + if (cpuinfo_len < 0) /* should not happen */ { + return; + } + + /* Extract architecture from the "CPU Architecture" field, + * Which can be something like 5JTE, 7, or something else. + * We cannot rely on the 'Processor' field here. + */ + { + char* cpuArch = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "CPU architecture"); + + if (cpuArch != NULL) { + char* end; + long archNumber; + + D("found cpuArch = '%s'\n", cpuArch); + + /* read the initial decimal number, ignore the rest */ + archNumber = strtol(cpuArch, &end, 10); + + /* Here we assume that ARMv8 will be upwards compatible with v7 + * in the future. Unfortunately, there is no 'Features' field to + * indicate that Thumb-2 is supported. + */ + if (end > cpuArch && archNumber >= 7) { + g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_ARMv7; + } + free(cpuArch); + } + } + + /* Extract the list of CPU features from 'Features' field */ + { + char* cpuFeatures = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "Features"); + + if (cpuFeatures != NULL) { + + D("found cpuFeatures = '%s'\n", cpuFeatures); + + if (has_list_item(cpuFeatures, "vfpv3")) + g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv3; + + if (has_list_item(cpuFeatures, "neon")) + g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_NEON; + + free(cpuFeatures); + } + } + } +#endif /* __ARM_ARCH__ */ +} + + +AndroidCpuFamily +android_getCpuFamily(void) +{ + pthread_once(&g_once, android_cpuInit); + return g_cpuFamily; +} + + +uint64_t +android_getCpuFeatures(void) +{ + pthread_once(&g_once, android_cpuInit); + return g_cpuFeatures; +} diff --git a/ndk/sources/cpufeatures/cpu-features.h b/ndk/sources/cpufeatures/cpu-features.h new file mode 100644 index 000000000..d5fa87694 --- /dev/null +++ b/ndk/sources/cpufeatures/cpu-features.h @@ -0,0 +1,26 @@ +#ifndef CPU_FEATURES_H +#define CPU_FEATURES_H + +#include + +typedef enum { + ANDROID_CPU_FAMILY_UNKNOWN = 0, + ANDROID_CPU_FAMILY_ARM, + ANDROID_CPU_FAMILY_X86, + + ANDROID_CPU_FAMILY_MAX /* do not remove */ + +} AndroidCpuFamily; + +/* Return family of the device's CPU */ +extern AndroidCpuFamily android_getCpuFamily(void); + +enum { + ANDROID_CPU_ARM_FEATURE_ARMv7 = (1 << 0), + ANDROID_CPU_ARM_FEATURE_VFPv3 = (1 << 1), + ANDROID_CPU_ARM_FEATURE_NEON = (1 << 2), +}; + +extern uint64_t android_getCpuFeatures(void); + +#endif /* CPU_FEATURES_H */