blob: d7466ccc9bf95e9e626e78d3fc9ea1b479fe5991 [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file contains implementations of SbCPUFeaturesGet() on all supported
// architectures defined in Starboard header cpu_features.h on linux platform.
// We use the most applicable and precise approach to get CPU features on each
// architecture. To be specific,
//
// On Arm/Arm64, we read CPU version information from /proc/cpuinfo. We get
// feature flags by parsing HWCAP bitmasks retrieved from function getauxval()
// or /proc/self/auxv, or by reading the flags from /proc/cpuinfo.
//
// On X86/X86_64, we read CPU version strings from /proc/cpuinfo. We get other
// CPU version information and feature flags by CPUID instruction, which gives
// more precise and complete information than reading from /proc/cpuinfo.
#include "starboard/cpu_features.h"
#include <dlfcn.h> // dlsym, dlclose, dlopen
#include <linux/auxvec.h> // AT_HWCAP
#include <stdio.h> // fopen, fclose
#include <string.h>
#include <strings.h> // strcasecmp
#include <sys/auxv.h> // getauxval()
#include <unistd.h> // sysconf()
#include <memory>
#include <cstdlib>
#if defined(ANDROID) && (SB_IS(ARCH_ARM) || SB_IS(ARCH_ARM64))
#include <asm/hwcap.h>
#endif
#include "starboard/common/log.h"
#include "starboard/shared/starboard/cpu_features.h"
namespace {
#if SB_IS(ARCH_ARM) || SB_IS(ARCH_ARM64)
// Android hwcap.h defines these flags conditionally, depending on target arch
#if SB_IS(32_BIT) || defined(ANDROID)
// See <arch/arm/include/uapi/asm/hwcap.h> kernel header.
#define HWCAP_VFP (1 << 6)
#define HWCAP_IWMMXT (1 << 9)
#define HWCAP_NEON (1 << 12)
#define HWCAP_VFPv3 (1 << 13)
#define HWCAP_VFPv3D16 (1 << 14) /* also set for VFPv4-D16 */
#define HWCAP_VFPv4 (1 << 16)
#define HWCAP_IDIVA (1 << 17)
#define HWCAP_IDIVT (1 << 18)
#define HWCAP_VFPD32 (1 << 19) /* set if VFP has 32 regs (not 16) */
#define HWCAP_IDIV (HWCAP_IDIVA | HWCAP_IDIVT)
#define HWCAP2_AES (1 << 0)
#define HWCAP2_PMULL (1 << 1)
#define HWCAP2_SHA1 (1 << 2)
#define HWCAP2_SHA2 (1 << 3)
#define HWCAP2_CRC32 (1 << 4)
// Preset hwcap for Armv8
#define HWCAP_SET_FOR_ARMV8 \
(HWCAP_VFP | HWCAP_NEON | HWCAP_VFPv3 | HWCAP_VFPv4 | HWCAP_IDIV)
#elif SB_IS(64_BIT)
// See <arch/arm64/include/uapi/asm/hwcap.h> kernel header
#define HWCAP_FP (1 << 0)
#define HWCAP_ASIMD (1 << 1)
#define HWCAP_AES (1 << 3)
#define HWCAP_PMULL (1 << 4)
#define HWCAP_SHA1 (1 << 5)
#define HWCAP_SHA2 (1 << 6)
#define HWCAP_CRC32 (1 << 7)
#endif // SB_IS(32_BIT)
#elif SB_IS(ARCH_X86) || SB_IS(ARCH_X64)
// x86_xgetbv returns the value of an Intel Extended Control Register (XCR).
// Currently only XCR0 is defined by Intel so |xcr| should always be zero.
uint64_t x86_xgetbv(uint32_t xcr) {
uint32_t eax, edx;
__asm__ volatile("xgetbv" : "=a"(eax), "=d"(edx) : "c"(xcr));
return (static_cast<uint64_t>(edx) << 32) | eax;
}
#if SB_IS(32_BIT)
void x86_cpuid(int cpuid_info[4], int info_type) {
__asm__ volatile(
"mov %%ebx, %%edi\n"
"cpuid\n"
"xchg %%edi, %%ebx\n"
: "=a"(cpuid_info[0]), "=D"(cpuid_info[1]), "=c"(cpuid_info[2]),
"=d"(cpuid_info[3])
: "a"(info_type), "c"(0));
}
#elif SB_IS(64_BIT)
void x86_cpuid(int cpuid_info[4], int info_type) {
__asm__ volatile("cpuid\n"
: "=a"(cpuid_info[0]), "=b"(cpuid_info[1]),
"=c"(cpuid_info[2]), "=d"(cpuid_info[3])
: "a"(info_type), "c"(0));
}
#endif // SB_IS(32_BIT)
#endif // SB_IS(ARCH_ARM) || SB_IS(ARCH_ARM64)
using starboard::shared::SetX86FeaturesInvalid;
using starboard::shared::SetArmFeaturesInvalid;
using starboard::shared::SetGeneralFeaturesInvalid;
// Class that holds the information in system file /proc/cpuinfo.
class ProcCpuInfo {
// Raw data of the file /proc/cpuinfo
std::unique_ptr<char[]> file_data_;
// Size of the raw data
size_t file_data_size_;
public:
explicit ProcCpuInfo(const char* file_path = "/proc/cpuinfo") {
file_data_size_ = 0;
// Get the size of the cpuinfo file by reading it until the end. This is
// required because files under /proc do not always return a valid size
// when using fseek(0, SEEK_END) + ftell(). Nor can the be mmap()-ed.
FILE* file = fopen(file_path, "r");
if (file != nullptr) {
for (;;) {
char file_buffer[256];
size_t data_size = fread(file_buffer, 1, sizeof(file_buffer), file);
if (data_size == 0) {
break;
}
file_data_size_ += data_size;
}
fclose(file);
}
// Read the contents of the cpuinfo file.
file_data_ = std::unique_ptr<char[]>(new char[file_data_size_ + 1]);
char* file_data_ptr = file_data_.get();
memset(file_data_ptr, 0, file_data_size_ + 1);
file = fopen(file_path, "r");
if (file != nullptr) {
for (size_t offset = 0; offset < file_data_size_;) {
size_t data_size =
fread(file_data_ptr + offset, 1, file_data_size_ - offset, file);
if (data_size == 0) {
break;
}
offset += data_size;
}
fclose(file);
}
// Zero-terminate the data.
file_data_ptr[file_data_size_] = '\0';
}
~ProcCpuInfo() { file_data_.reset(); }
// Extract the string feature data named by |feature| and store in
// |out_feature| whose size is |out_feature_size|.
bool ExtractStringFeature(const char* feature,
char* out_feature,
size_t out_feature_size) {
if (feature == nullptr) {
return false;
}
// Look for first feature occurrence, and ensure it starts the line.
size_t feature_name_size = strlen(feature);
const char* feature_ptr = nullptr;
char* file_data_ptr = file_data_.get();
const char* line_start_ptr = file_data_.get();
while (line_start_ptr < file_data_ptr + file_data_size_) {
// Find the end of the line.
const char* line_end_ptr = strchr(line_start_ptr, '\n');
if (line_end_ptr == nullptr) {
line_end_ptr = file_data_ptr + file_data_size_;
}
char line_buffer[line_end_ptr - line_start_ptr +
1]; // NOLINT(runtime/arrays)
memset(line_buffer, 0, sizeof(line_buffer));
memcpy(line_buffer, line_start_ptr, line_end_ptr - line_start_ptr);
line_buffer[line_end_ptr - line_start_ptr] = '\0';
// Find the colon
const char* colon_ptr = strchr(line_buffer, ':');
if (colon_ptr == nullptr || !isspace(colon_ptr[1])) {
line_start_ptr = line_end_ptr + 1;
continue;
}
line_buffer[colon_ptr - line_buffer] = '\0';
// Trim trailing white space of the line before colon
const char* feature_end_ptr = colon_ptr - 1;
while (feature_end_ptr >= line_buffer &&
isspace(line_buffer[feature_end_ptr - line_buffer])) {
line_buffer[feature_end_ptr - line_buffer] = '\0';
feature_end_ptr--;
}
// Out of boundary
if (feature_end_ptr < line_buffer) {
line_start_ptr = line_end_ptr + 1;
continue;
}
// Trim leading white space of the line
const char* feature_start_ptr = line_buffer;
while (feature_start_ptr <= feature_end_ptr &&
isspace(line_buffer[feature_start_ptr - line_buffer])) {
line_buffer[feature_start_ptr - line_buffer] = '\0';
feature_start_ptr++;
}
// There is no need to check feature_start_ptr out of boundary, because if
// feature_start_ptr > feature_end_ptr, it means the line before colon is
// all white space, and feature_end_ptr will be out of boundary already.
if (strcmp(feature_start_ptr, feature) != 0) {
line_start_ptr = line_end_ptr + 1;
continue;
}
feature_ptr = colon_ptr + 2 - line_buffer + line_start_ptr;
break;
}
if (feature_ptr == nullptr) {
return false;
}
// Find the end of the line.
const char* line_end_ptr = strchr(feature_ptr, '\n');
if (line_end_ptr == nullptr) {
line_end_ptr = file_data_ptr + file_data_size_;
}
// Get the size of the feature data
int feature_size = line_end_ptr - feature_ptr;
if (out_feature_size < feature_size + 1) {
SB_LOG(WARNING) << "CPU Feature " << feature << " is truncated.";
feature_size = out_feature_size - 1;
}
memcpy(out_feature, feature_ptr, feature_size);
out_feature[feature_size] = '\0';
return true;
}
// Extract a integer feature field identified by |feature| from /proc/cpuinfo
int ExtractIntegerFeature(const char* feature) {
int feature_data = -1;
// Allocate 128 bytes for an integer field.
char feature_buffer[128] = {0};
if (!ExtractStringFeature(
feature, feature_buffer,
sizeof(feature_buffer) / sizeof(feature_buffer[0]))) {
return feature_data;
}
char* end;
feature_data = static_cast<int>(strtol(feature_buffer, &end, 0));
if (end == feature_buffer) {
feature_data = -1;
}
return feature_data;
}
};
// Check if getauxval() is supported
bool IsGetauxvalSupported() {
// TODO: figure out which linking flags are needed to use
// dl* functions. Currently the linker complains symbols
// like "__dlopen" undefined, even though "-ldl" and "-lc"
// are added to linker flags.
// dlerror();
// void* libc_handle = dlopen("libc.so", RTLD_NOW);
// if (!libc_handle) {
// printf("Could not dlopen() C library: %s\n", dlerror());
// return false;
// }
// typedef unsigned long getauxval_func_t(unsigned long);
// getauxval_func_t* func = (getauxval_func_t*)
// dlsym(libc_handle, "getauxval");
// if (!func) {
// printf("Could not find getauxval() in C library\n");
// return false;
// }
// dlclose(libc_handle);
return true;
}
// Get hwcap bitmask by getauxval() or by reading /proc/self/auxv
uint32_t ReadElfHwcaps(uint32_t hwcap_type) {
uint32_t hwcap = 0;
if (IsGetauxvalSupported()) {
hwcap = static_cast<uint32_t>(getauxval(hwcap_type));
} else {
// Read the ELF HWCAP flags by parsing /proc/self/auxv.
FILE* file_ptr = fopen("/proc/self/auxv", "r");
if (file_ptr == nullptr) {
return hwcap;
}
struct {
uint32_t tag;
uint32_t value;
} entry;
for (;;) {
size_t n = fread(&entry, sizeof(entry), 1, file_ptr);
if (n == 0 || (entry.tag == 0 && entry.value == 0)) {
break;
}
if (entry.tag == hwcap_type) {
hwcap = entry.value;
break;
}
}
fclose(file_ptr);
}
return hwcap;
}
#if SB_IS(ARCH_ARM) || SB_IS(ARCH_ARM64)
// Checks if a space-separated list of items |list|, in the form of a string,
// contains one given item |item|.
bool HasItemInList(const char* list, const char* flag) {
ssize_t flag_length = strlen(flag);
const char* list_ptr = list;
if (list_ptr == nullptr) {
return false;
}
while (*list_ptr != '\0') {
// Skip whitespace.
while (isspace(*list_ptr))
++list_ptr;
// Find end of current list flag.
const char* end_ptr = list_ptr;
while (*end_ptr != '\0' && !isspace(*end_ptr))
++end_ptr;
if (flag_length == end_ptr - list_ptr &&
memcmp(list_ptr, flag, flag_length) == 0) {
return true;
}
// Continue to the next flag.
list_ptr = end_ptr;
}
return false;
}
// Construct hwcap bitmask by the feature flags in /proc/cpuinfo
uint32_t ConstructHwcapFromCPUInfo(ProcCpuInfo* cpu_info,
int16_t architecture_generation,
uint32_t hwcap_type) {
if (hwcap_type == AT_HWCAP && architecture_generation >= 8) {
// This is a 32-bit ARM binary running on a 64-bit ARM64 kernel.
// The 'Features' line only lists the optional features that the
// device's CPU supports, compared to its reference architecture
// which are of no use for this process.
SB_LOG(INFO) << "Faking 32-bit ARM HWCaps on ARMv"
<< architecture_generation;
return HWCAP_SET_FOR_ARMV8;
}
uint32_t hwcap_value = 0;
// Allocate 1024 bytes for "Features", which is a list of flags.
char flags_buffer[1024] = {0};
if (!cpu_info->ExtractStringFeature(
"Features", flags_buffer,
sizeof(flags_buffer) / sizeof(flags_buffer[0]))) {
return hwcap_value;
}
if (hwcap_type == AT_HWCAP) {
hwcap_value |= HasItemInList(flags_buffer, "vfp") ? HWCAP_VFP : 0;
hwcap_value |= HasItemInList(flags_buffer, "vfpv3") ? HWCAP_VFPv3 : 0;
hwcap_value |= HasItemInList(flags_buffer, "vfpv3d16") ? HWCAP_VFPv3D16 : 0;
hwcap_value |= HasItemInList(flags_buffer, "vfpv4") ? HWCAP_VFPv4 : 0;
hwcap_value |= HasItemInList(flags_buffer, "neon") ? HWCAP_NEON : 0;
hwcap_value |= HasItemInList(flags_buffer, "idiva") ? HWCAP_IDIVA : 0;
hwcap_value |= HasItemInList(flags_buffer, "idivt") ? HWCAP_IDIVT : 0;
hwcap_value |=
HasItemInList(flags_buffer, "idiv") ? (HWCAP_IDIVA | HWCAP_IDIVT) : 0;
hwcap_value |= HasItemInList(flags_buffer, "iwmmxt") ? HWCAP_IWMMXT : 0;
} else if (hwcap_type == AT_HWCAP2) {
hwcap_value |= HasItemInList(flags_buffer, "aes") ? HWCAP2_AES : 0;
hwcap_value |= HasItemInList(flags_buffer, "pmull") ? HWCAP2_PMULL : 0;
hwcap_value |= HasItemInList(flags_buffer, "sha1") ? HWCAP2_SHA1 : 0;
hwcap_value |= HasItemInList(flags_buffer, "sha2") ? HWCAP2_SHA2 : 0;
hwcap_value |= HasItemInList(flags_buffer, "crc32") ? HWCAP2_CRC32 : 0;
}
return hwcap_value;
}
bool SbCPUFeaturesGet_ARM(SbCPUFeatures* features) {
memset(features, 0, sizeof(*features));
#if SB_IS(32_BIT)
features->architecture = kSbCPUFeaturesArchitectureArm;
#elif SB_IS(64_BIT)
features->architecture = kSbCPUFeaturesArchitectureArm64;
#else
#error "Your platform must be either 32-bit or 64-bit."
#endif
// Set the default value of the features to be invalid, then fill them in
// if appropriate.
SetGeneralFeaturesInvalid(features);
SetArmFeaturesInvalid(features);
SetX86FeaturesInvalid(features);
ProcCpuInfo cpu_info;
// Extract CPU implementor, variant, revision and part information, which
// are all integers.
features->arm.implementer = cpu_info.ExtractIntegerFeature("CPU implementer");
features->arm.variant = cpu_info.ExtractIntegerFeature("CPU variant");
features->arm.revision = cpu_info.ExtractIntegerFeature("CPU revision");
features->arm.part = cpu_info.ExtractIntegerFeature("CPU part");
// Extract CPU architecture generation from the "CPU Architecture" field.
// Allocate 128 bytes for "CPU architecture", which is an integer field.
char architecture_buffer[128] = {0};
if (cpu_info.ExtractStringFeature(
"CPU architecture", architecture_buffer,
sizeof(architecture_buffer) / sizeof(architecture_buffer[0]))) {
char* end;
features->arm.architecture_generation =
static_cast<int>(strtol(architecture_buffer, &end, 10));
if (end == architecture_buffer) {
// Kernels older than 3.18 report "CPU architecture: AArch64" on ARMv8.
if (strcasecmp(architecture_buffer, "AArch64") == 0) {
features->arm.architecture_generation = 8;
} else {
features->arm.architecture_generation = -1;
}
}
// Unfortunately, it seems that certain ARMv6-based CPUs
// report an incorrect architecture number of 7!
//
// See http://code.google.com/p/android/issues/detail?id=10812
//
// We try to correct this by looking at the 'elf_platform'
// feature reported by the 'Processor' field or 'model name'
// field in Linux v3.8, which is of the form of "(v7l)" for an
// ARMv7-based CPU, and "(v6l)" for an ARMv6-one. For example,
// the Raspberry Pi is one popular ARMv6 device that reports
// architecture 7. The 'Processor' or 'model name' fields
// also contain processor brand information. "model name" is used in
// Linux 3.8 and later (3.7
// and later for arm64) and is shown once per CPU. "Processor" is used in
// earlier versions and is shown only once at the top of /proc/cpuinfo
// regardless of the number CPUs.
// Allocate 256 bytes for "Processor"/"model name" field.
static char brand_buffer[256] = {0};
if (!cpu_info.ExtractStringFeature(
"Processor", brand_buffer,
sizeof(brand_buffer) / sizeof(brand_buffer[0]))) {
if (cpu_info.ExtractStringFeature(
"model name", brand_buffer,
sizeof(brand_buffer) / sizeof(brand_buffer[0]))) {
features->brand = brand_buffer;
if (features->arm.architecture_generation == 7 &&
HasItemInList(features->brand, "(v6l)")) {
features->arm.architecture_generation = 6;
}
}
}
}
#if SB_IS(32_BIT)
// Get hwcap bitmask and extract the CPU feature flags from it.
features->hwcap = ReadElfHwcaps(AT_HWCAP);
if (features->hwcap == 0) {
features->hwcap = ConstructHwcapFromCPUInfo(
&cpu_info, features->arm.architecture_generation, AT_HWCAP);
}
features->arm.has_idiva = (features->hwcap & HWCAP_IDIVA) != 0;
features->arm.has_neon = (features->hwcap & HWCAP_NEON) != 0;
features->arm.has_vfp = (features->hwcap & HWCAP_VFP) != 0;
features->arm.has_vfp3 = (features->hwcap & (HWCAP_VFPv3 | HWCAP_VFPv3D16 |
HWCAP_VFPv4 | HWCAP_NEON)) != 0;
features->arm.has_vfp3_d32 =
(features->arm.has_vfp3 && ((features->hwcap & HWCAP_VFPv3D16) == 0 ||
(features->hwcap & HWCAP_VFPD32) != 0));
features->arm.has_vfp3_d32 =
features->arm.has_vfp3_d32 || features->arm.has_neon;
// Some old kernels will report vfp not vfpv3. Here we make an attempt
// to detect vfpv3 by checking for vfp *and* neon, since neon is only
// available on architectures with vfpv3. Checking neon on its own is
// not enough as it is possible to have neon without vfp.
if (features->arm.has_vfp && features->arm.has_neon) {
features->arm.has_vfp3 = true;
}
// VFPv3 implies ARMv7, see ARM DDI 0406B, page A1-6.
if (features->arm.architecture_generation < 7 && features->arm.has_vfp3) {
features->arm.architecture_generation = 7;
}
// ARMv7 implies Thumb2.
if (features->arm.architecture_generation >= 7) {
features->arm.has_thumb2 = true;
}
// The earliest architecture with Thumb2 is ARMv6T2.
if (features->arm.has_thumb2 && features->arm.architecture_generation < 6) {
features->arm.architecture_generation = 6;
}
// We don't support any FPUs other than VFP.
features->has_fpu = features->arm.has_vfp;
// The following flags are always supported by ARMv8, as mandated by the ARM
// Architecture Reference Manual.
if (features->arm.architecture_generation >= 8) {
features->arm.has_idiva = true;
features->arm.has_neon = true;
features->arm.has_thumb2 = true;
features->arm.has_vfp = true;
features->arm.has_vfp3 = true;
}
// Read hwcaps2 bitmask and extract the CPU feature flags from it.
features->hwcap2 = ReadElfHwcaps(AT_HWCAP2);
if (features->hwcap2 == 0) {
features->hwcap2 = ConstructHwcapFromCPUInfo(
&cpu_info, features->arm.architecture_generation, AT_HWCAP2);
}
features->arm.has_aes = (features->hwcap2 & HWCAP2_AES) != 0;
features->arm.has_pmull = (features->hwcap2 & HWCAP2_PMULL) != 0;
features->arm.has_sha1 = (features->hwcap2 & HWCAP2_SHA1) != 0;
features->arm.has_sha2 = (features->hwcap2 & HWCAP2_SHA2) != 0;
features->arm.has_crc32 = (features->hwcap2 & HWCAP2_CRC32) != 0;
#elif SB_IS(64_BIT)
// Read hwcaps bitmask and extract the CPU feature flags from it.
features->hwcap = ReadElfHwcaps(AT_HWCAP);
features->arm.has_aes = (features->hwcap & HWCAP_AES) != 0;
features->arm.has_pmull = (features->hwcap & HWCAP_PMULL) != 0;
features->arm.has_sha1 = (features->hwcap & HWCAP_SHA1) != 0;
features->arm.has_sha2 = (features->hwcap & HWCAP_SHA2) != 0;
features->arm.has_crc32 = (features->hwcap & HWCAP_CRC32) != 0;
#endif // SB_IS(32_BIT)
// Get L1 ICACHE and DCACHE line size.
features->icache_line_size = sysconf(_SC_LEVEL1_ICACHE_LINESIZE);
features->dcache_line_size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
return true;
}
#elif SB_IS(ARCH_X86) || SB_IS(ARCH_X64)
bool SbCPUFeaturesGet_X86(SbCPUFeatures* features) {
memset(features, 0, sizeof(*features));
#if SB_IS(32_BIT)
features->architecture = kSbCPUFeaturesArchitectureX86;
#elif SB_IS(64_BIT)
features->architecture = kSbCPUFeaturesArchitectureX86_64;
#else
#error "Your platform must be either 32-bit or 64-bit."
#endif
// Set the default value of the features to be invalid, then fill them in
// if appropriate.
SetGeneralFeaturesInvalid(features);
SetArmFeaturesInvalid(features);
SetX86FeaturesInvalid(features);
ProcCpuInfo cpu_info;
// Extract brand and vendor information.
// Allocate 256 bytes for "model name" field.
static char brand_buffer[256] = {0};
if (cpu_info.ExtractStringFeature(
"model name", brand_buffer,
sizeof(brand_buffer) / sizeof(brand_buffer[0]))) {
features->brand = brand_buffer;
}
// Allocate 128 bytes for "vendor_id" field.
static char vendor_buffer[128] = {0};
if (cpu_info.ExtractStringFeature(
"vendor_id", vendor_buffer,
sizeof(vendor_buffer) / sizeof(vendor_buffer[0]))) {
features->x86.vendor = vendor_buffer;
}
// Get L1 ICACHE and DCACHE line size.
features->icache_line_size = sysconf(_SC_LEVEL1_ICACHE_LINESIZE);
features->dcache_line_size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
// Hwcap bitmasks are not applicable to x86/x86_64. They'll remain 0.
// Use cpuid instruction to get feature flags.
int cpuid_info[4] = {0};
// x86_cpuid with an InfoType argument of 0 returns the number of
// valid Ids in cpuid_info[0].
x86_cpuid(cpuid_info, 0);
unsigned int num_ids = cpuid_info[0];
// Interpret CPU feature information of CPUID level 1. EAX is returned
// in cpuid_info[0], EBX in cpuid_info[1], ECX in cpuid_info[2],
// EDX in cpuid_info[3].
if (num_ids > 0) {
x86_cpuid(cpuid_info, 1);
features->x86.signature = cpuid_info[0];
features->x86.stepping = cpuid_info[0] & 0xF;
features->x86.model =
((cpuid_info[0] >> 4) & 0xF) + ((cpuid_info[0] >> 12) & 0xF0);
features->x86.family = (cpuid_info[0] >> 8) & 0xF;
features->x86.type = (cpuid_info[0] >> 12) & 0x3;
features->x86.ext_model = (cpuid_info[0] >> 16) & 0xF;
features->x86.ext_family = (cpuid_info[0] >> 20) & 0xFF;
features->has_fpu = (cpuid_info[3] & (1 << 0)) != 0;
features->x86.has_cmov = (cpuid_info[3] & (1 << 15)) != 0;
features->x86.has_mmx = (cpuid_info[3] & (1 << 23)) != 0;
features->x86.has_sse = (cpuid_info[3] & (1 << 25)) != 0;
features->x86.has_sse2 = (cpuid_info[3] & (1 < 26)) != 0;
features->x86.has_tsc = (cpuid_info[3] & (1 << 4)) != 0;
features->x86.has_sse3 = (cpuid_info[2] & (1 << 0)) != 0;
features->x86.has_pclmulqdq = (cpuid_info[2] & (1 << 1)) != 0;
features->x86.has_ssse3 = (cpuid_info[2] & (1 << 9)) != 0;
features->x86.has_sse41 = (cpuid_info[2] & (1 << 19)) != 0;
features->x86.has_sse42 = (cpuid_info[2] & (1 << 20)) != 0;
features->x86.has_movbe = (cpuid_info[2] & (1 << 22)) != 0;
features->x86.has_popcnt = (cpuid_info[2] & (1 << 23)) != 0;
features->x86.has_osxsave = (cpuid_info[2] & (1 << 27)) != 0;
features->x86.has_f16c = (cpuid_info[2] & (1 << 29)) != 0;
features->x86.has_fma3 = (cpuid_info[2] & (1 << 12)) != 0;
features->x86.has_aesni = (cpuid_info[2] & (1 << 25)) != 0;
// AVX instructions will generate an illegal instruction exception unless
// a) they are supported by the CPU,
// b) XSAVE is supported by the CPU and
// c) XSAVE is enabled by the kernel.
// See http://software.intel.com/en-us/blogs/2011/04/14/is-avx-enabled
//
// In addition, we have observed some crashes with the xgetbv instruction
// even after following Intel's example code. (See crbug.com/375968.)
// Because of that, we also test the XSAVE bit because its description in
// the CPUID documentation suggests that it signals xgetbv support.
features->x86.has_avx =
(cpuid_info[2] & (1 << 28)) != 0 &&
(cpuid_info[2] & (1 << 26)) != 0 /* XSAVE */ &&
(cpuid_info[2] & (1 << 27)) != 0 /* OSXSAVE */ &&
(x86_xgetbv(0) & 6) == 6 /* XSAVE enabled by kernel */;
}
// Interpret CPU feature information of CPUID level 7.
if (num_ids >= 7) {
x86_cpuid(cpuid_info, 7);
features->x86.has_avx2 =
features->x86.has_avx && (cpuid_info[1] & (1 << 5)) != 0;
features->x86.has_avx512f =
features->x86.has_avx && (cpuid_info[1] & (1 << 16)) != 0;
features->x86.has_avx512dq =
features->x86.has_avx && (cpuid_info[1] & (1 << 17)) != 0;
features->x86.has_avx512ifma =
features->x86.has_avx && (cpuid_info[1] & (1 << 21)) != 0;
features->x86.has_avx512pf =
features->x86.has_avx && (cpuid_info[1] & (1 << 26)) != 0;
features->x86.has_avx512er =
features->x86.has_avx && (cpuid_info[1] & (1 << 27)) != 0;
features->x86.has_avx512cd =
features->x86.has_avx && (cpuid_info[1] & (1 << 28)) != 0;
features->x86.has_avx512bw =
features->x86.has_avx && (cpuid_info[1] & (1 << 30)) != 0;
features->x86.has_avx512vl =
features->x86.has_avx && (cpuid_info[1] & (1 << 31)) != 0;
features->x86.has_bmi1 = (cpuid_info[1] & (1 << 3)) != 0;
features->x86.has_bmi2 = (cpuid_info[1] & (1 << 8)) != 0;
}
// Query extended IDs.
x86_cpuid(cpuid_info, 0x80000000);
unsigned int num_ext_ids = cpuid_info[0];
// Interpret extended CPU feature information.
if (num_ext_ids > 0x80000000) {
// Interpret CPU feature information of CPUID level 0x80000001.
x86_cpuid(cpuid_info, 0x80000001);
features->x86.has_lzcnt = (cpuid_info[2] & (1 << 5)) != 0;
// SAHF must be probed in long mode.
features->x86.has_sahf = (cpuid_info[2] & (1 << 0)) != 0;
}
return true;
}
#endif
} // namespace
// TODO: Only ARM/ARM64 and X86/X86_64 are currently implemented and tested
bool SbCPUFeaturesGet(SbCPUFeatures* features) {
#if SB_IS(ARCH_ARM) || SB_IS(ARCH_ARM64)
return SbCPUFeaturesGet_ARM(features);
#elif SB_IS(ARCH_X86) || SB_IS(ARCH_X64)
return SbCPUFeaturesGet_X86(features);
#else
SB_NOTIMPLEMENTED();
memset(features, 0, sizeof(*features));
features->architecture = kSbCPUFeaturesArchitectureUnknown;
SetGeneralFeaturesInvalid(features);
SetArmFeaturesInvalid(features);
SetX86FeaturesInvalid(features);
return false;
#endif
}