| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * 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. |
| */ |
| |
| #include "instruction_set_features_arm.h" |
| |
| #if defined(ART_TARGET_ANDROID) && defined(__arm__) |
| #include <asm/hwcap.h> |
| #include <sys/auxv.h> |
| #endif |
| |
| #include "signal.h" |
| |
| #include <fstream> |
| |
| #include <android-base/logging.h> |
| #include <android-base/stringprintf.h> |
| #include <android-base/strings.h> |
| |
| #include "base/array_ref.h" |
| |
| #include <cpu_features_macros.h> |
| |
| #ifdef CPU_FEATURES_ARCH_ARM |
| // This header can only be included on ARM targets, |
| // as determined by cpu_features own define. |
| #include <cpuinfo_arm.h> |
| #endif |
| |
| |
| #if defined(__arm__) |
| extern "C" bool artCheckForArmSdivInstruction(); |
| extern "C" bool artCheckForArmv8AInstructions(); |
| #endif |
| |
| namespace art HIDDEN { |
| |
| using android::base::StringPrintf; |
| |
| ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromVariant( |
| const std::string& variant, std::string* error_msg) { |
| static const char* arm_variants_with_armv8a[] = { |
| "cortex-a32", |
| "cortex-a35", |
| "cortex-a53", |
| "cortex-a53.a57", |
| "cortex-a53.a72", |
| "cortex-a55", |
| "cortex-a57", |
| "cortex-a72", |
| "cortex-a73", |
| "cortex-a75", |
| "cortex-a76", |
| "exynos-m1", |
| "kryo", |
| "kryo385", |
| "kryo785", |
| }; |
| bool has_armv8a = FindVariantInArray(arm_variants_with_armv8a, |
| arraysize(arm_variants_with_armv8a), |
| variant); |
| |
| // Look for variants that have divide support. |
| static const char* arm_variants_with_div[] = { |
| "cortex-a7", |
| "cortex-a12", |
| "cortex-a15", |
| "cortex-a17", |
| "krait", |
| }; |
| bool has_div = has_armv8a || FindVariantInArray(arm_variants_with_div, |
| arraysize(arm_variants_with_div), |
| variant); |
| |
| // Look for variants that have LPAE support. |
| static const char* arm_variants_with_lpae[] = { |
| "cortex-a7", |
| "cortex-a12", |
| "cortex-a15", |
| "cortex-a17", |
| "krait", |
| }; |
| bool has_atomic_ldrd_strd = has_armv8a || FindVariantInArray(arm_variants_with_lpae, |
| arraysize(arm_variants_with_lpae), |
| variant); |
| |
| if (has_armv8a == false && has_div == false && has_atomic_ldrd_strd == false) { |
| static const char* arm_variants_with_default_features[] = { |
| "cortex-a5", |
| "cortex-a8", |
| "cortex-a9", |
| "cortex-a9-mp", |
| "default", |
| "generic" |
| }; |
| if (!FindVariantInArray(arm_variants_with_default_features, |
| arraysize(arm_variants_with_default_features), |
| variant)) { |
| std::ostringstream os; |
| os << "Unexpected CPU variant for Arm: " << variant << ".\n" |
| << "Known variants with armv8a support: " |
| << android::base::Join(ArrayRef<const char* const>(arm_variants_with_armv8a), ", ") |
| << ".\n" |
| << "Known variants with divide support: " |
| << android::base::Join(ArrayRef<const char* const>(arm_variants_with_div), ", ") << ".\n" |
| << "Known variants with LPAE support: " |
| << android::base::Join(ArrayRef<const char* const>(arm_variants_with_lpae), ", ") << ".\n" |
| << "Other known variants: " |
| << android::base::Join(ArrayRef<const char* const>(arm_variants_with_default_features), |
| ", "); |
| *error_msg = os.str(); |
| return nullptr; |
| } else { |
| // Warn if we use the default features. |
| LOG(WARNING) << "Using default instruction set features for ARM CPU variant (" << variant |
| << ") using conservative defaults"; |
| } |
| } |
| return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, |
| has_atomic_ldrd_strd, |
| has_armv8a)); |
| } |
| |
| ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromBitmap(uint32_t bitmap) { |
| bool has_div = (bitmap & kDivBitfield) != 0; |
| bool has_atomic_ldrd_strd = (bitmap & kAtomicLdrdStrdBitfield) != 0; |
| bool has_armv8a = (bitmap & kARMv8A) != 0; |
| return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, |
| has_atomic_ldrd_strd, |
| has_armv8a)); |
| } |
| |
| ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromCppDefines() { |
| // Note: This will not work for now since we still build the 32-bit as __ARCH_ARM_7A__. |
| #if defined(__ARM_ARCH_8A__) |
| const bool has_armv8a = true; |
| #else |
| const bool has_armv8a = false; |
| #endif |
| #if defined (__ARM_ARCH_8A__) || defined(__ARM_ARCH_EXT_IDIV__) |
| const bool has_div = true; |
| #else |
| const bool has_div = false; |
| #endif |
| #if defined (__ARM_ARCH_8A__) || defined(__ARM_FEATURE_LPAE) |
| const bool has_atomic_ldrd_strd = true; |
| #else |
| const bool has_atomic_ldrd_strd = false; |
| #endif |
| return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, |
| has_atomic_ldrd_strd, |
| has_armv8a)); |
| } |
| |
| ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromCpuInfo() { |
| // Look in /proc/cpuinfo for features we need. Only use this when we can guarantee that |
| // the kernel puts the appropriate feature flags in here. Sometimes it doesn't. |
| bool has_atomic_ldrd_strd = false; |
| bool has_div = false; |
| bool has_armv8a = false; |
| |
| std::ifstream in("/proc/cpuinfo"); |
| if (!in.fail()) { |
| while (!in.eof()) { |
| std::string line; |
| std::getline(in, line); |
| if (!in.eof()) { |
| LOG(INFO) << "cpuinfo line: " << line; |
| if (line.find("Features") != std::string::npos) { |
| LOG(INFO) << "found features"; |
| if (line.find("idivt") != std::string::npos) { |
| // We always expect both ARM and Thumb divide instructions to be available or not |
| // available. |
| CHECK_NE(line.find("idiva"), std::string::npos); |
| has_div = true; |
| } |
| if (line.find("lpae") != std::string::npos) { |
| has_atomic_ldrd_strd = true; |
| } |
| } |
| if (line.find("architecture") != std::string::npos |
| && line.find(": 8") != std::string::npos) { |
| LOG(INFO) << "found architecture ARMv8"; |
| // Android is only run on A cores, so ARMv8 implies ARMv8-A. |
| has_armv8a = true; |
| // ARMv8 CPUs have LPAE and div support. |
| has_div = true; |
| has_atomic_ldrd_strd = true; |
| } |
| } |
| } |
| in.close(); |
| } else { |
| LOG(ERROR) << "Failed to open /proc/cpuinfo"; |
| } |
| return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, |
| has_atomic_ldrd_strd, |
| has_armv8a)); |
| } |
| |
| ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromHwcap() { |
| bool has_div = false; |
| bool has_atomic_ldrd_strd = false; |
| bool has_armv8a = false; |
| |
| #if defined(ART_TARGET_ANDROID) && defined(__arm__) |
| uint64_t hwcaps = getauxval(AT_HWCAP); |
| LOG(INFO) << "hwcaps=" << hwcaps; |
| if ((hwcaps & HWCAP_IDIVT) != 0) { |
| // We always expect both ARM and Thumb divide instructions to be available or not |
| // available. |
| CHECK_NE(hwcaps & HWCAP_IDIVA, 0U); |
| has_div = true; |
| } |
| if ((hwcaps & HWCAP_LPAE) != 0) { |
| has_atomic_ldrd_strd = true; |
| } |
| // TODO: Fix this once FPMISC makes it upstream. |
| // For now we detect if we run on an ARMv8 CPU by looking for CRC32 and SHA1 |
| // (only available on ARMv8 CPUs). |
| if ((hwcaps & HWCAP2_CRC32) != 0 && (hwcaps & HWCAP2_SHA1) != 0) { |
| has_armv8a = true; |
| } |
| #endif |
| |
| return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, |
| has_atomic_ldrd_strd, |
| has_armv8a)); |
| } |
| |
| // A signal handler called by a fault for an illegal instruction. We record the fact in r0 |
| // and then increment the PC in the signal context to return to the next instruction. We know the |
| // instruction is 4 bytes long. |
| static void bad_instr_handle([[maybe_unused]] int signo, |
| [[maybe_unused]] siginfo_t* si, |
| void* data) { |
| #if defined(__arm__) |
| ucontext_t* uc = reinterpret_cast<ucontext_t*>(data); |
| mcontext_t* mc = &uc->uc_mcontext; |
| mc->arm_r0 = 0; // Set R0 to #0 to signal error. |
| mc->arm_pc += 4; // Skip offending instruction. |
| #else |
| UNUSED(data); |
| #endif |
| } |
| |
| ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromAssembly() { |
| // See if have a sdiv instruction. Register a signal handler and try to execute an sdiv |
| // instruction. If we get a SIGILL then it's not supported. |
| struct sigaction sa, osa; |
| sa.sa_flags = SA_ONSTACK | SA_RESTART | SA_SIGINFO; |
| sa.sa_sigaction = bad_instr_handle; |
| sigemptyset(&sa.sa_mask); |
| sigaction(SIGILL, &sa, &osa); |
| |
| bool has_div = false; |
| bool has_armv8a = false; |
| #if defined(__arm__) |
| if (artCheckForArmSdivInstruction()) { |
| has_div = true; |
| } |
| if (artCheckForArmv8AInstructions()) { |
| has_armv8a = true; |
| } |
| #endif |
| |
| // Restore the signal handler. |
| sigaction(SIGILL, &osa, nullptr); |
| |
| // Use compile time features to "detect" LPAE support. |
| // TODO: write an assembly LPAE support test. |
| #if defined (__ARM_ARCH_8A__) || defined(__ARM_FEATURE_LPAE) |
| const bool has_atomic_ldrd_strd = true; |
| #else |
| const bool has_atomic_ldrd_strd = false; |
| #endif |
| return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, |
| has_atomic_ldrd_strd, |
| has_armv8a)); |
| } |
| |
| ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromCpuFeatures() { |
| #ifdef CPU_FEATURES_ARCH_ARM |
| auto info = cpu_features::GetArmInfo(); |
| auto features = info.features; |
| return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(features.idiva, |
| features.lpae, |
| info.architecture == 8)); |
| #else |
| UNIMPLEMENTED(WARNING); |
| return FromCppDefines(); |
| #endif |
| } |
| |
| bool ArmInstructionSetFeatures::Equals(const InstructionSetFeatures* other) const { |
| if (InstructionSet::kArm != other->GetInstructionSet()) { |
| return false; |
| } |
| const ArmInstructionSetFeatures* other_as_arm = other->AsArmInstructionSetFeatures(); |
| return has_div_ == other_as_arm->has_div_ |
| && has_atomic_ldrd_strd_ == other_as_arm->has_atomic_ldrd_strd_ |
| && has_armv8a_ == other_as_arm->has_armv8a_; |
| } |
| |
| bool ArmInstructionSetFeatures::HasAtLeast(const InstructionSetFeatures* other) const { |
| if (InstructionSet::kArm != other->GetInstructionSet()) { |
| return false; |
| } |
| const ArmInstructionSetFeatures* other_as_arm = other->AsArmInstructionSetFeatures(); |
| return (has_div_ || !other_as_arm->has_div_) |
| && (has_atomic_ldrd_strd_ || !other_as_arm->has_atomic_ldrd_strd_) |
| && (has_armv8a_ || !other_as_arm->has_armv8a_); |
| } |
| |
| uint32_t ArmInstructionSetFeatures::AsBitmap() const { |
| return (has_div_ ? kDivBitfield : 0) |
| | (has_atomic_ldrd_strd_ ? kAtomicLdrdStrdBitfield : 0) |
| | (has_armv8a_ ? kARMv8A : 0); |
| } |
| |
| std::string ArmInstructionSetFeatures::GetFeatureString() const { |
| std::string result; |
| if (has_div_) { |
| result += "div"; |
| } else { |
| result += "-div"; |
| } |
| if (has_atomic_ldrd_strd_) { |
| result += ",atomic_ldrd_strd"; |
| } else { |
| result += ",-atomic_ldrd_strd"; |
| } |
| if (has_armv8a_) { |
| result += ",armv8a"; |
| } else { |
| result += ",-armv8a"; |
| } |
| return result; |
| } |
| |
| std::unique_ptr<const InstructionSetFeatures> |
| ArmInstructionSetFeatures::AddFeaturesFromSplitString( |
| const std::vector<std::string>& features, std::string* error_msg) const { |
| bool has_atomic_ldrd_strd = has_atomic_ldrd_strd_; |
| bool has_div = has_div_; |
| bool has_armv8a = has_armv8a_; |
| for (const std::string& feature : features) { |
| DCHECK_EQ(android::base::Trim(feature), feature) |
| << "Feature name is not trimmed: '" << feature << "'"; |
| if (feature == "div") { |
| has_div = true; |
| } else if (feature == "-div") { |
| has_div = false; |
| } else if (feature == "atomic_ldrd_strd") { |
| has_atomic_ldrd_strd = true; |
| } else if (feature == "-atomic_ldrd_strd") { |
| has_atomic_ldrd_strd = false; |
| } else if (feature == "armv8a") { |
| has_armv8a = true; |
| } else if (feature == "-armv8a") { |
| has_armv8a = false; |
| } else { |
| *error_msg = StringPrintf("Unknown instruction set feature: '%s'", feature.c_str()); |
| return nullptr; |
| } |
| } |
| return std::unique_ptr<const InstructionSetFeatures>( |
| new ArmInstructionSetFeatures(has_div, has_atomic_ldrd_strd, has_armv8a)); |
| } |
| |
| } // namespace art |