• Docs >
  • Program Listing for File cpu_info.hpp
Shortcuts

Program Listing for File cpu_info.hpp

Return to documentation for file (include/ripple/arch/cpu_info.hpp)

#ifndef RIPPLE_ARCH_CPU_INFO_HPP
#define RIPPLE_ARCH_CPU_INFO_HPP

#include "cache.hpp"
#include "cpu_utils.hpp"
#include "proc_info.hpp"
#include <ripple/utility/bitops.hpp>
#include <algorithm>
#include <vector>

namespace ripple {

static constexpr uint32_t max_cache_subleaves = 16;

struct CpuInfo {
 private:
  /*==--- [aliases] --------------------------------------------------------==*/

  // clang-format off
  using CacheContainer    = std::vector<Cache>;
  using ProcInfoContainer = std::vector<ProcInfo>;
  // clang-format on

 public:
  /*==--- [construction] ---------------------------------------------------==*/

  /* Constructor, initialises that max supported values for cpuid. */
  CpuInfo() noexcept {
    // Determine the max supported function:
    regs_.eax = CpuidFunction::MaxFunction;
    cpuid();
    max_func_ = regs_.eax;

    // Determine the max suported extended function:
    regs_.eax = CpuidFunction::MaxExtFunction;
    regs_.ecx = 0;
    cpuid();
    max_ext_func_ = regs_.eax;

    set_max_cores();
    create_cache_info();
    create_proc_info();
  }

  /*==--- [interface] ------------------------------------------------------==*/

  auto available_cores() const noexcept -> uint32_t {
    return num_packages_ * cores_per_package_;
  }

  auto packages() const noexcept -> uint32_t {
    return num_packages_;
  }

  auto cores_per_package() const noexcept -> uint32_t {
    return cores_per_package_;
  }

  auto cache_info() noexcept -> CacheContainer& {
    return caches_;
  }

  auto processor_info() noexcept -> ProcInfoContainer& {
    return proc_info_;
  }

 private:
  enum TopoLevel {
    Invalid = 0x0,
    Thread  = 0x1,
    Core    = 0x2
  };

  enum CpuidFunction : uint32_t {
    MaxFunction       = 0x00000000,
    MaxExtFunction    = 0x80000000,
    CoreAndCacheInfo  = 0x00000004,
    ProcessorTopology = 0x0000000B
  };

  struct Regs {
    using Reg = uint32_t;

    Reg eax = 0;
    Reg ebx = 0;
    Reg ecx = 0;
    Reg edx = 0;
  };

  static constexpr uint32_t max_cache_subleaves = 16;

  Regs     regs_;
  uint32_t max_func_          = 0;
  uint32_t max_ext_func_      = 0;
  uint32_t max_cores_         = 0;
  uint32_t num_packages_      = 0;
  uint32_t cores_per_package_ = 0;
  uint32_t threads_per_core_  = 0;

  CacheContainer    caches_;
  ProcInfoContainer proc_info_;

  /*==--- [methods] --------------------------------------------------------==*/

  auto create_cache_info() noexcept -> void {
    // CPU does not support cache information, none can be returned.
    if (max_func_ < CpuidFunction::CoreAndCacheInfo) {
      return;
    }

    uint32_t subleaf = 0;
    while (subleaf < max_cache_subleaves) {
      auto& cache = caches_.emplace_back();
      regs_.eax   = CpuidFunction::CoreAndCacheInfo;
      regs_.ecx   = subleaf;
      cpuid();

      cache.type = static_cast<Cache::Type>(bits(regs_.eax, 0, 4));
      // CPUID reference says that once cpuid returns (0 - Null), then the end
      // of the cache information has been reached.
      if (cache.type == Cache::Type::Null) {
        caches_.pop_back();
        break;
      }

      cache.level          = bits(regs_.eax, 5, 7);
      cache.linesize       = bits(regs_.ebx, 0, 11) + 1;
      cache.partitions     = bits(regs_.ebx, 12, 21) + 1;
      cache.assosciativity = bits(regs_.ebx, 22, 31) + 1;
      cache.shared_by      = bits(regs_.eax, 14, 25) + 1;
      cache.sets           = bits(regs_.ecx, 0, 31) + 1;

      // Determine the mask for the cache, which can be used to
      // determine which processors share the cache.
      uint32_t temp = cache.shared_by - 1, mask_width = 0;
      for (; temp; mask_width++) {
        temp >>= 1;
      }
      cache.mask = (-1) << mask_width;
      subleaf++;
    }

    // Sort caches from lowest level to highest level,
    // and by type for same level.
    std::sort(
      caches_.begin(),
      caches_.end(),
      [](const Cache& a, const Cache& b) -> bool {
        return a.level == b.level ? a.type <= b.type : a.level < b.level;
      });
  }

  auto create_proc_info() noexcept -> void {
    for (uint32_t i = 0; i < max_cores_; ++i) {
      create_proc_info_for_proc(i);
    }

    // Find max index for package, core and thred from all procsessors.
    for (auto& info : proc_info_) {
      if (info.is_invalid()) {
        continue;
      }
      if (info.package > num_packages_) {
        num_packages_ = info.package;
      }
      if (info.core > cores_per_package_) {
        cores_per_package_ = info.core;
      }
      if (info.thread > threads_per_core_) {
        threads_per_core_ = info.thread;
      }
    }
    num_packages_ += 1;
    cores_per_package_ += 1;
    threads_per_core_ += 1;
  }

  auto create_proc_info_for_proc(uint32_t proc_index) noexcept -> void {
    if (proc_index > max_cores_) {
      return;
    }
    set_affinity(proc_index);
    auto& info = proc_info_.emplace_back();

    uint32_t apic = 0, apic_width = 0, level_type = 0, mask = 0,
             select_mask = 0, topo_level = 0, prev_mask_width = 0;

    bool next_level_invalid = false;
    while (!next_level_invalid) {
      regs_.eax = CpuidFunction::ProcessorTopology;
      regs_.ecx = topo_level++;
      cpuid();
      next_level_invalid = regs_.eax == 0 && regs_.ebx == 0;

      apic_width = bits(regs_.eax, 0, 4);
      level_type = bits(regs_.ecx, 8, 15);
      apic       = bits(regs_.edx, 0, 31);
      mask       = bitmask(apic_width);
      if (level_type == TopoLevel::Thread) {
        select_mask     = mask;
        info.thread     = apic & select_mask;
        prev_mask_width = apic_width;
        continue;
      }
      if (level_type == TopoLevel::Core) {
        select_mask = mask ^ select_mask;
        info.core   = (apic & select_mask) >> prev_mask_width;

        // For the package:
        select_mask  = (-1) << apic_width;
        info.package = (apic & select_mask) >> apic_width;
      }
    }
  }

  auto set_max_cores() noexcept -> void {
    if (max_func_ >= CpuidFunction::CoreAndCacheInfo) {
      regs_.eax = CpuidFunction::CoreAndCacheInfo;
      regs_.ecx = 0;
      cpuid();
      max_cores_ = bits(regs_.eax, 26, 31) + 1;
    }
  }

  inline auto cpuid() noexcept -> void {
    asm("cpuid"
        : "=a"(regs_.eax), "=b"(regs_.ebx), "=c"(regs_.ecx), "=d"(regs_.edx)
        : "0"(regs_.eax), "2"(regs_.ecx));
  }
};

} // namespace ripple

#endif // RIPPLE_ARCH_CPU_INFO_HPP

Docs

Access comprehensive developer documentation for Ripple

View Docs

Tutorials

Get tutorials to help with understand all features

View Tutorials

Examples

Find examples to help get started

View Examples