text: // Copyright 2017 Google Inc. 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.
  
  #include "highwayhash/arch_specific.h"
  
  #include <stdint.h>
  
  #if HH_ARCH_X64 && !HH_MSC_VERSION
  #include <cpuid.h>
  #endif
  
  #if HH_ARCH_PPC
  #if __GLIBC__
  #include <sys/platform/ppc.h>  // __ppc_get_timebase_freq
  #elif __FreeBSD__
  #include <sys/types.h>
  #include <sys/sysctl.h>
  #endif
  #endif
  
  #include <string.h>  // memcpy
  #include <string>
  
  namespace highwayhash {
  
  const char* TargetName(const TargetBits target_bit) {
    switch (target_bit) {
      case HH_TARGET_Portable:
        return "Portable";
      case HH_TARGET_SSE41:
        return "SSE41";
      case HH_TARGET_AVX2:
        return "AVX2";
      case HH_TARGET_VSX:
        return "VSX";
      case HH_TARGET_NEON:
        return "NEON";
      default:
        return nullptr;  // zero, multiple, or unknown bits
    }
  }
  
  #if HH_ARCH_X64
  
  namespace {
  
  std::string BrandString() {
    char brand_string[49];
    uint32_t abcd[4];
  
    // Check if brand string is supported (it is on all reasonable Intel/AMD)
    Cpuid(0x80000000U, 0, abcd);
    if (abcd[0] < 0x80000004U) {
      return std::string();
    }
  
    for (int i = 0; i < 3; ++i) {
      Cpuid(0x80000002U + i, 0, abcd);
      memcpy(brand_string + i * 16, &abcd, sizeof(abcd));
    }
    brand_string[48] = 0;
    return brand_string;
  }
  
  }  // namespace
  
  void Cpuid(const uint32_t level, const uint32_t count,
             uint32_t* HH_RESTRICT abcd) {
  #if HH_MSC_VERSION
    int regs[4];
    __cpuidex(regs, level, count);
    for (int i = 0; i < 4; ++i) {
      abcd[i] = regs[i];
    }
  #else
    uint32_t a, b, c, d;
    __cpuid_count(level, count, a, b, c, d);
    abcd[0] = a;
    abcd[1] = b;
    abcd[2] = c;
    abcd[3] = d;
  #endif
  }
  
  uint32_t ApicId() {
    uint32_t abcd[4];
    Cpuid(1, 0, abcd);
    return abcd[1] >> 24;  // ebx
  }
  
  #endif  // HH_ARCH_X64
  
  namespace {
  
  double DetectNominalClockRate() {
  #if HH_ARCH_X64
    const std::string& brand_string = BrandString();
    // Brand strings include the maximum configured frequency. These prefixes are
    // defined by Intel CPUID documentation.
    const char* prefixes[3] = {"MHz", "GHz", "THz"};
    const double multipliers[3] = {1E6, 1E9, 1E12};
    for (size_t i = 0; i < 3; ++i) {
      const size_t pos_prefix = brand_string.find(prefixes[i]);
      if (pos_prefix != std::string::npos) {
        const size_t pos_space = brand_string.rfind(' ', pos_prefix - 1);
        if (pos_space != std::string::npos) {
          const std::string digits =
              brand_string.substr(pos_space + 1, pos_prefix - pos_space - 1);
          return std::stod(digits) * multipliers[i];
        }
      }
    }
  #elif HH_ARCH_PPC
    double freq = -1;
  #if __linux__
    char line[200];
    char* s;
    char* value;
  
    FILE* f = fopen("/proc/cpuinfo", "r");
    if (f != nullptr) {
      while (fgets(line, sizeof(line), f) != nullptr) {
        // NOTE: the ':' is the only character we can rely on
        if (!(value = strchr(line, ':'))) continue;
        // terminate the valuename
        *value++ = '\0';
        // skip any leading spaces
        while (*value == ' ') value++;
        if ((s = strchr(value, '\n'))) *s = '\0';
  
        if (!strncasecmp(line, "clock", strlen("clock")) &&
            sscanf(value, "%lf", &freq) == 1) {
          freq *= 1E6;
          break;
        }
      }
      fclose(f);
      return freq;
    }
   #elif __FreeBSD__
    size_t length = sizeof(freq);
    sysctlbyname("dev.cpu.0.freq"), &freq, &length, NULL, 0);
    freq *= 1E6;
    return freq;
  #endif
  #endif
  
    return 0.0;
  }
  
  }  // namespace
  
  double NominalClockRate() {
    // Thread-safe caching - this is called several times.
    static const double cycles_per_second = DetectNominalClockRate();
    return cycles_per_second;
  }
  
  double InvariantTicksPerSecond() {
  #if HH_ARCH_PPC
  #if __GLIBC__  
    static const double cycles_per_second = __ppc_get_timebase_freq();
  #elif __FreeBSD__
    static double cycles_per_second = 0;
    size_t length = sizeof(cycles_per_second);
    sysctlbyname("kern.timecounter.tc.timebase.frequency", &cycles_per_second, &length, NULL, 0);
  #endif
    return cycles_per_second;
  #else
    return NominalClockRate();
  #endif
  }
  
  }  // namespace highwayhash
  