/*
 * 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 <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 {

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",
  };
  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)) {
      *error_msg = StringPrintf("Attempt to use unsupported ARM variant: %s", variant.c_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(int signo ATTRIBUTE_UNUSED,
                            siginfo_t* si ATTRIBUTE_UNUSED,
                            void* data) {
#if defined(__arm__)
  struct ucontext *uc = (struct ucontext *)data;
  struct sigcontext *sc = &uc->uc_mcontext;
  sc->arm_r0 = 0;     // Set R0 to #0 to signal error.
  sc->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
