├── .gitignore ├── .gitmodules ├── src ├── cpu │ ├── mod.rs │ ├── microarchitecture.rs │ └── detect.rs ├── lib.rs ├── schema │ ├── cpuid.rs │ ├── mod.rs │ └── microarchitecture.rs └── cpuid.rs ├── bin ├── Cargo.toml └── src │ └── bin │ └── archspec.rs ├── .github ├── depandabot.yml └── workflows │ ├── lint-pr.yml │ ├── release-plz.yml │ └── rust-compile.yml ├── examples └── basic.rs ├── Cargo.toml ├── CHANGELOG.md ├── LICENSE-MIT ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | rls*.log 5 | .idea/ 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "json"] 2 | path = json 3 | url = https://github.com/archspec/archspec-json.git 4 | -------------------------------------------------------------------------------- /src/cpu/mod.rs: -------------------------------------------------------------------------------- 1 | mod detect; 2 | mod microarchitecture; 3 | 4 | pub use detect::host; 5 | pub use microarchitecture::{Microarchitecture, UnsupportedMicroarchitecture}; 6 | -------------------------------------------------------------------------------- /bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "archspec-bin" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | archspec = { path = ".." } 9 | clap = { version = "4.5.4", features = ["derive"] } 10 | -------------------------------------------------------------------------------- /.github/depandabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | # Check for updates every Monday 6 | schedule: 7 | interval: "weekly" 8 | - package-ecosystem: "gitsubmodule" 9 | directory: "json" 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/lint-pr.yml: -------------------------------------------------------------------------------- 1 | name: "Lint PR" 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | permissions: 11 | pull-requests: read 12 | 13 | jobs: 14 | main: 15 | name: Validate PR title 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: amannn/action-semantic-pull-request@v5 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), archspec::cpu::UnsupportedMicroarchitecture> { 2 | let architecture = archspec::cpu::host()?; 3 | 4 | println!("Current CPU architecture:"); 5 | println!("- Name: {}", architecture.name()); 6 | println!("- Vendor: {}", architecture.vendor()); 7 | println!("- Generation: {}", architecture.generation()); 8 | println!("- Family Name: {}", architecture.family().name()); 9 | println!("- Features: {:?}", architecture.all_features()); 10 | 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The Rust port of the archspec Python library. 2 | //! 3 | //! Both this and the corresponding [Python library](https://github.com/archspec/archspec) 4 | //! are data-driven from a [JSON document](https://github.com/archspec/archspec-json) which 5 | //! determines what architecture the current machine is a superset of, as well as provides 6 | //! ways to compare architecture capabilities. 7 | //! 8 | //! Built documentation for the Python library can be found at 9 | //! [archspec.readthedocs.org](https://archspec.readthedocs.org) for additional context. 10 | 11 | pub mod cpu; 12 | mod cpuid; 13 | pub mod schema; 14 | -------------------------------------------------------------------------------- /bin/src/bin/archspec.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | 3 | /// Simple program to greet a person 4 | #[derive(Parser, Debug)] 5 | #[command(version, about = "archspec command line interface", long_about = None)] 6 | struct Args { 7 | #[command(subcommand)] 8 | command: Command, 9 | } 10 | 11 | #[derive(Subcommand, Clone, Debug)] 12 | enum Command { 13 | /// archspec command line interface for CPU 14 | Cpu, 15 | } 16 | 17 | fn main() { 18 | let args = Args::parse(); 19 | match args.command { 20 | Command::Cpu => detect_cpu(), 21 | } 22 | } 23 | 24 | fn detect_cpu() { 25 | match archspec::cpu::host() { 26 | Ok(arch) => println!("{}", arch.name()), 27 | Err(_err) => eprintln!("Error: unsupported micro architecture"), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/release-plz.yml: -------------------------------------------------------------------------------- 1 | name: Release-plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | release-plz: 14 | name: Release-plz 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | submodules: true 22 | token: ${{ secrets.RELEASE_PLZ_TOKEN }} 23 | - name: Install Rust toolchain 24 | uses: dtolnay/rust-toolchain@stable 25 | - name: Run release-plz 26 | uses: MarcoIeni/release-plz-action@v0.5 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} 29 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 30 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ "bin"] 3 | default-members = [ "bin"] 4 | 5 | [package] 6 | name = "archspec" 7 | version = "0.1.3" 8 | authors = ["Bas Zalmstra ", "Lars Viklund "] 9 | description = "Provides standardized human-readable labels for aspects and capabilities of a system" 10 | license = "MIT OR Apache-2.0" 11 | edition = "2018" 12 | resolver = "2" 13 | include = ["src/*", "json/cpu/*.json"] 14 | readme = "README.md" 15 | repository = "https://github.com/prefix-dev/archspec-rs" 16 | 17 | [dependencies] 18 | serde = { version = "1.0", features = ["derive"] } 19 | serde_json = "1.0" 20 | itertools = "0.13" 21 | cfg-if = "1" 22 | 23 | [target.'cfg(not(target_os = "windows"))'.dependencies] 24 | libc = "0.2" 25 | 26 | [target.'cfg(target_os = "macos")'.dependencies] 27 | sysctl = "0.5" 28 | 29 | [dev-dependencies] 30 | rstest = "0.19" 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.1.3](https://github.com/prefix-dev/archspec-rs/compare/v0.1.2...v0.1.3) - 2024-03-30 10 | 11 | ### Fixed 12 | - use access token for releaze-plz 13 | 14 | ### Other 15 | - update readme ([#6](https://github.com/prefix-dev/archspec-rs/pull/6)) 16 | 17 | ## [0.1.2](https://github.com/prefix-dev/archspec-rs/compare/v0.1.1...v0.1.2) - 2024-03-30 18 | 19 | ### Fixed 20 | - expose microarchitecture fields ([#4](https://github.com/prefix-dev/archspec-rs/pull/4)) 21 | 22 | ## [0.1.1](https://github.com/prefix-dev/archspec-rs/compare/v0.1.0...v0.1.1) - 2024-03-29 23 | 24 | ### Added 25 | - *(tests)* target tests 26 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2013-2020 Lawrence Livermore National Security, LLC and other 2 | Spack and Archspec Project Developers. See the top-level COPYRIGHT file 3 | for details. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/schema/cpuid.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use std::sync::OnceLock; 3 | 4 | #[derive(Debug, Deserialize)] 5 | pub struct CpuIdSchema { 6 | pub vendor: CpuIdProperty, 7 | pub highest_extension_support: CpuIdProperty, 8 | pub flags: Vec, 9 | #[serde(rename = "extension-flags")] 10 | pub extension_flags: Vec, 11 | } 12 | 13 | impl CpuIdSchema { 14 | pub fn schema() -> &'static CpuIdSchema { 15 | static SCHEMA: OnceLock = OnceLock::new(); 16 | SCHEMA.get_or_init(|| { 17 | serde_json::from_str(include_str!(concat!( 18 | env!("CARGO_MANIFEST_DIR"), 19 | "/json/cpu/cpuid.json" 20 | ))) 21 | .expect("Failed to load cpuid.json") 22 | }) 23 | } 24 | } 25 | 26 | #[derive(Debug, Deserialize)] 27 | pub struct CpuIdProperty { 28 | pub description: String, 29 | pub input: CpuIdInput, 30 | } 31 | 32 | #[derive(Debug, Deserialize)] 33 | pub struct CpuIdFlags { 34 | pub description: String, 35 | pub input: CpuIdInput, 36 | pub bits: Vec, 37 | } 38 | 39 | #[derive(Debug, Deserialize)] 40 | pub struct CpuIdBits { 41 | pub name: String, 42 | pub register: CpuRegister, 43 | pub bit: u8, 44 | } 45 | 46 | #[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)] 47 | #[serde(rename_all = "lowercase")] 48 | pub enum CpuRegister { 49 | Eax, 50 | Ebx, 51 | Ecx, 52 | Edx, 53 | } 54 | 55 | #[derive(Debug, Deserialize)] 56 | pub struct CpuIdInput { 57 | pub eax: u32, 58 | pub ecx: u32, 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 | banner 7 | 8 | 9 |

10 | 11 |

12 | 13 | [![Crates.io][crates-badge]][crates-url] 14 | ![License][license-badge] 15 | [![Build Status][build-badge]][build] 16 | [![Project Chat][chat-badge]][chat-url] 17 | [![docs][docs-badge]][docs-url] 18 | 19 | [license-badge]: https://img.shields.io/crates/l/archspec?style=flat-square 20 | [build-badge]: https://img.shields.io/github/actions/workflow/status/prefix-dev/archspec-rs/rust-compile.yml?style=flat-square&branch=main 21 | [build]: https://github.com/prefix-dev/archspec-rs 22 | [chat-badge]: https://img.shields.io/discord/1082332781146800168.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2&style=flat-square 23 | [chat-url]: https://discord.gg/kKV8ZxyzY4 24 | [docs-badge]: https://img.shields.io/badge/docs-main-yellow.svg?style=flat-square 25 | [docs-url]: https://docs.rs/archspec 26 | [crates-badge]: https://img.shields.io/crates/v/archspec.svg?style=flat-square 27 | [crates-url]: https://crates.io/crates/archspec 28 | 29 |

30 | 31 | # archspec-rs 32 | 33 | An implementation of [archspec](https://github.com/archspec/archspec) in Rust. 34 | 35 | Archspec aims at providing a standard set of human-understandable labels for various aspects of a system architecture like CPU, network fabrics, etc. and APIs to detect, query and compare them. 36 | 37 | The original archspec project grew out of [Spack](https://spack.io/) and is currently under active development. 38 | At present, it supports APIs to detect and model compatibility relationships among different CPU microarchitectures. 39 | 40 | Developed with ❤️ at [prefix.dev](https://prefix.dev). 41 | 42 | ## License 43 | 44 | Licensed under either of 45 | 46 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 47 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 48 | 49 | at your option. 50 | 51 | ### Contribution 52 | 53 | Unless you explicitly state otherwise, any contribution intentionally submitted 54 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 55 | additional terms or conditions. 56 | -------------------------------------------------------------------------------- /.github/workflows/rust-compile.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - "main" 5 | pull_request: 6 | 7 | name: Rust 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | env: 14 | RUST_LOG: info 15 | RUST_BACKTRACE: 1 16 | RUSTFLAGS: "-D warnings" 17 | CARGO_TERM_COLOR: always 18 | 19 | jobs: 20 | check-rustdoc-links: 21 | name: Check intra-doc links 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | with: 26 | submodules: true 27 | - uses: actions-rust-lang/setup-rust-toolchain@v1 28 | - run: cargo rustdoc --all-features -- -D warnings -W unreachable-pub 29 | 30 | format: 31 | name: Format 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | with: 36 | submodules: true 37 | - uses: actions-rust-lang/setup-rust-toolchain@v1 38 | with: 39 | components: clippy, rustfmt 40 | - name: Run rustfmt 41 | uses: actions-rust-lang/rustfmt@v1 42 | 43 | build: 44 | name: ${{ matrix.name }} 45 | runs-on: ${{ matrix.os }} 46 | needs: [ format ] 47 | strategy: 48 | fail-fast: false 49 | matrix: 50 | include: 51 | - { name: "Linux-x86_64", target: x86_64-unknown-linux-musl, os: ubuntu-20.04 } 52 | - { name: "Linux-aarch64", target: aarch64-unknown-linux-musl, os: ubuntu-latest } 53 | - { name: "Linux-powerpc64le", target: powerpc64le-unknown-linux-gnu, os: ubuntu-latest } 54 | - { name: "Linux-powerpc64", target: powerpc64-unknown-linux-gnu, os: ubuntu-latest } 55 | - { name: "Linux-riscv64gc", target: riscv64gc-unknown-linux-gnu, os: ubuntu-latest } 56 | - { name: "Linux-s390x", target: s390x-unknown-linux-gnu, os: ubuntu-latest } 57 | 58 | - { name: "macOS", target: x86_64-apple-darwin, os: macOS-latest } 59 | - { name: "macOS-arm", target: aarch64-apple-darwin, os: macOS-14 } 60 | 61 | - { name: "Windows-x86_64", target: x86_64-pc-windows-msvc, os: windows-latest } 62 | steps: 63 | - name: Checkout source code 64 | uses: actions/checkout@v4 65 | with: 66 | submodules: true 67 | 68 | - uses: taiki-e/setup-cross-toolchain-action@v1 69 | with: 70 | target: ${{ matrix.target }} 71 | 72 | - uses: Swatinem/rust-cache@v2 73 | 74 | - name: Run clippy 75 | run: cargo clippy --all 76 | 77 | - name: "Install cargo nextest" 78 | uses: taiki-e/install-action@v2 79 | with: 80 | tool: cargo-nextest 81 | 82 | - name: "Cargo nextest" 83 | run: | 84 | cargo nextest run --no-capture --status-level skip --no-fail-fast --final-status-level slow --all 85 | -------------------------------------------------------------------------------- /src/schema/mod.rs: -------------------------------------------------------------------------------- 1 | //! Types and functions to manipulate the contents microarchitecture data file. 2 | //! 3 | //! These are encoding the rules of the corresponding schema as Rust data types 4 | //! with the help of `serde` deserialization. 5 | 6 | use serde::de; 7 | use serde::{Deserialize, Deserializer}; 8 | use std::marker::PhantomData; 9 | 10 | mod cpuid; 11 | mod microarchitecture; 12 | 13 | pub use cpuid::*; 14 | pub use microarchitecture::*; 15 | 16 | /// Deserialization helper to map {null, string, [string]} to a sequence of strings. 17 | fn zero_one_many_string<'de, D>(deserializer: D) -> Result, D::Error> 18 | where 19 | D: Deserializer<'de>, 20 | { 21 | struct Vtor; 22 | 23 | impl<'de> de::Visitor<'de> for Vtor { 24 | type Value = Vec; 25 | 26 | fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { 27 | fmt.write_str("a null or a loose element or a sequence") 28 | } 29 | 30 | fn visit_unit(self) -> Result 31 | where 32 | E: de::Error, 33 | { 34 | Ok(vec![]) 35 | } 36 | 37 | fn visit_str(self, v: &str) -> Result 38 | where 39 | E: de::Error, 40 | { 41 | Ok(vec![v.to_string()]) 42 | } 43 | 44 | fn visit_seq(self, mut access: A) -> Result 45 | where 46 | A: de::SeqAccess<'de>, 47 | { 48 | let mut v = Vec::with_capacity(access.size_hint().unwrap_or(0)); 49 | while let Some(a) = access.next_element()? { 50 | v.push(a); 51 | } 52 | 53 | Ok(v) 54 | } 55 | } 56 | 57 | deserializer.deserialize_any(Vtor) 58 | } 59 | 60 | /// Deserialization helper to map from a single object or a sequence of objects to a sequence. 61 | #[allow(dead_code)] 62 | fn one_many_object<'de, D, T>(deserializer: D) -> Result, D::Error> 63 | where 64 | D: Deserializer<'de>, 65 | T: Deserialize<'de>, 66 | { 67 | struct Vtor { 68 | marker: PhantomData Vec>, 69 | } 70 | 71 | impl Vtor { 72 | fn new() -> Self { 73 | Vtor { 74 | marker: PhantomData, 75 | } 76 | } 77 | } 78 | 79 | impl<'de, T> de::Visitor<'de> for Vtor 80 | where 81 | T: Deserialize<'de>, 82 | { 83 | type Value = Vec; 84 | 85 | fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { 86 | fmt.write_str("a loose element or a sequence") 87 | } 88 | 89 | fn visit_map(self, access: A) -> Result 90 | where 91 | A: de::MapAccess<'de>, 92 | { 93 | let obj: T = Deserialize::deserialize(de::value::MapAccessDeserializer::new(access))?; 94 | Ok(vec![obj]) 95 | } 96 | 97 | fn visit_seq(self, mut access: A) -> Result 98 | where 99 | A: de::SeqAccess<'de>, 100 | { 101 | let mut v = Vec::with_capacity(access.size_hint().unwrap_or(0)); 102 | while let Some(a) = access.next_element()? { 103 | v.push(a); 104 | } 105 | 106 | Ok(v) 107 | } 108 | } 109 | 110 | deserializer.deserialize_any(Vtor::new()) 111 | } 112 | 113 | #[cfg(test)] 114 | mod tests { 115 | use crate::schema::cpuid::CpuIdSchema; 116 | use crate::schema::microarchitecture::MicroarchitecturesSchema; 117 | 118 | #[test] 119 | #[ignore] 120 | fn show_microarchitecture_json() { 121 | println!("{:#?}", MicroarchitecturesSchema::schema()); 122 | } 123 | 124 | #[test] 125 | #[ignore] 126 | fn show_cpuid_json() { 127 | println!("{:#?}", CpuIdSchema::schema()); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/schema/microarchitecture.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use std::collections::HashMap; 3 | use std::sync::OnceLock; 4 | 5 | /// Schema for microarchitecture definitions and feature aliases. 6 | #[derive(Debug, Deserialize)] 7 | pub struct MicroarchitecturesSchema { 8 | pub microarchitectures: HashMap, 9 | pub feature_aliases: HashMap, 10 | pub conversions: Conversions, 11 | } 12 | 13 | impl MicroarchitecturesSchema { 14 | pub fn schema() -> &'static MicroarchitecturesSchema { 15 | static SCHEMA: OnceLock = OnceLock::new(); 16 | SCHEMA.get_or_init(|| { 17 | serde_json::from_str(include_str!(concat!( 18 | env!("CARGO_MANIFEST_DIR"), 19 | "/json/cpu/microarchitectures.json" 20 | ))) 21 | .expect("Failed to load microarchitectures.json") 22 | }) 23 | } 24 | } 25 | 26 | /// Defines the attributes and requirements of a microarchitecture. 27 | #[derive(Debug, Deserialize)] 28 | pub struct Microarchitecture { 29 | /// A list of the immediate microarchitectures that this one is considered 30 | /// to be derived from. 31 | #[serde(deserialize_with = "super::zero_one_many_string")] 32 | pub from: Vec, 33 | 34 | /// Human-readable vendor name. 35 | pub vendor: String, 36 | 37 | /// The CPU features that are required to exist on the system for it to be 38 | /// compatible with this microarchitecture. 39 | pub features: Vec, 40 | 41 | /// Optional information on how to tell different compilers how to optimize 42 | /// for this microarchitecture. 43 | pub compilers: Option>, 44 | 45 | /// Generation of the microarchitecture, if relevant. 46 | pub generation: Option, 47 | } 48 | 49 | /// Compiler optimization for a particular compiler, either one for all flavours 50 | /// of the compiler or several indicating how to do it for particular version ranges. 51 | #[derive(Debug, Clone, Deserialize)] 52 | #[serde(untagged)] 53 | pub enum CompilerSet { 54 | /// Multiple entries (Compiler change options across versions). 55 | Several(Vec), 56 | 57 | /// Single entry (Compiler didn't change options across versions). 58 | Single(Compiler), 59 | } 60 | 61 | /// Indicates how to tell a particular compiler flavour how to optimize 62 | /// for an microarchitecture. 63 | #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 64 | pub struct Compiler { 65 | /// Indicates the versions of the compiler this applies to. 66 | pub versions: String, 67 | 68 | /// Command line argument to pass to the compiler to optimize for this architecture. 69 | /// May contain `{name}` placeholders. 70 | pub flags: String, 71 | 72 | /// Architecture name, for use in the optimization flags. 73 | pub name: Option, 74 | } 75 | 76 | /// Synthesised feature aliases derived from existing features or families. 77 | #[derive(Debug, Clone, Deserialize)] 78 | pub struct FeatureAlias { 79 | /// The reason for why this alias is defined. 80 | pub reason: Option, 81 | 82 | /// The alias is valid if any of the items are a feature of the target. 83 | pub any_of: Option>, 84 | 85 | /// The alias is valid if the family of the target is in this list. 86 | pub families: Option>, 87 | } 88 | 89 | /// Conversions that map some platform specific value to canonical values. 90 | #[derive(Debug, Deserialize)] 91 | pub struct Conversions { 92 | pub description: String, 93 | 94 | /// Maps from ARM vendor hex-values to actual vendor names. 95 | pub arm_vendors: HashMap, 96 | 97 | /// Maps from macOS feature flags to the expected feature names. 98 | pub darwin_flags: HashMap, 99 | } 100 | -------------------------------------------------------------------------------- /src/cpuid.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use crate::schema::{CpuIdSchema, CpuRegister}; 4 | use itertools::chain; 5 | use std::collections::HashSet; 6 | use std::ffi::CStr; 7 | 8 | pub(crate) trait CpuIdProvider { 9 | fn cpuid(&self, leaf: u32, sub_leaf: u32) -> CpuIdRegisters; 10 | } 11 | 12 | #[derive(Debug, Clone)] 13 | pub(crate) struct CpuIdRegisters { 14 | /// EAX register. 15 | pub eax: u32, 16 | /// EBX register. 17 | pub ebx: u32, 18 | /// ECX register. 19 | pub ecx: u32, 20 | /// EDX register. 21 | pub edx: u32, 22 | } 23 | 24 | #[cfg(target_arch = "x86_64")] 25 | impl From for CpuIdRegisters { 26 | fn from(value: std::arch::x86_64::CpuidResult) -> Self { 27 | Self { 28 | eax: value.eax, 29 | ebx: value.ebx, 30 | ecx: value.ecx, 31 | edx: value.edx, 32 | } 33 | } 34 | } 35 | 36 | #[cfg(target_arch = "x86")] 37 | impl From for CpuIdRegisters { 38 | fn from(value: std::arch::x86::CpuidResult) -> Self { 39 | Self { 40 | eax: value.eax, 41 | ebx: value.ebx, 42 | ecx: value.ecx, 43 | edx: value.edx, 44 | } 45 | } 46 | } 47 | 48 | /// Default implementation of the `CpuidProvider` trait. This implementation uses the 49 | /// [`__cpuid_count`] intrinsic to read actual CPUID information. 50 | /// 51 | /// This implementation is only available on x86 and x86_64 architectures. 52 | #[derive(Default)] 53 | pub(crate) struct MachineCpuIdProvider {} 54 | 55 | impl CpuIdProvider for MachineCpuIdProvider { 56 | fn cpuid(&self, leaf: u32, sub_leaf: u32) -> CpuIdRegisters { 57 | cfg_if::cfg_if! { 58 | if #[cfg(target_arch = "x86_64")] { 59 | unsafe { std::arch::x86_64::__cpuid_count(leaf, sub_leaf).into() } 60 | } else if #[cfg(target_arch = "x86")] { 61 | unsafe { std::arch::x86::__cpuid_count(leaf, sub_leaf).into() } 62 | } else { 63 | unimplemented!("Unsupported architecture for CPUID instruction ({leaf} {sub_leaf})") 64 | } 65 | } 66 | } 67 | } 68 | 69 | #[derive(Debug)] 70 | pub(crate) struct CpuId { 71 | /// The vendor name of the CPU 72 | pub vendor: String, 73 | 74 | /// Optional brand name of the CPU 75 | pub brand: Option, 76 | 77 | /// The supported features of the CPU. 78 | pub features: HashSet, 79 | } 80 | 81 | impl CpuId { 82 | pub fn detect(provider: &P) -> Self { 83 | let schema = CpuIdSchema::schema(); 84 | 85 | // Read the vendor information 86 | let registers = provider.cpuid(schema.vendor.input.eax, schema.vendor.input.ecx); 87 | let highest_basic_support = registers.eax; 88 | let vendor_bytes: Vec<_> = chain!( 89 | registers.ebx.to_le_bytes(), 90 | registers.edx.to_le_bytes(), 91 | registers.ecx.to_le_bytes() 92 | ) 93 | .collect(); 94 | let vendor = String::from_utf8_lossy(&vendor_bytes).into_owned(); 95 | 96 | // Read the highest_extension_support 97 | let registers = provider.cpuid( 98 | schema.highest_extension_support.input.eax, 99 | schema.highest_extension_support.input.ecx, 100 | ); 101 | let highest_extension_support = registers.eax; 102 | 103 | // Read feature flags 104 | let mut features = HashSet::new(); 105 | let supported_flags = schema 106 | .flags 107 | .iter() 108 | .filter(|flags| flags.input.eax <= highest_basic_support); 109 | let supported_extensions = schema 110 | .extension_flags 111 | .iter() 112 | .filter(|flags| flags.input.eax <= highest_extension_support); 113 | for flags in supported_flags.chain(supported_extensions) { 114 | let registers = provider.cpuid(flags.input.eax, flags.input.ecx); 115 | for bits in &flags.bits { 116 | let register = match bits.register { 117 | CpuRegister::Eax => registers.eax, 118 | CpuRegister::Ebx => registers.ebx, 119 | CpuRegister::Ecx => registers.ecx, 120 | CpuRegister::Edx => registers.edx, 121 | }; 122 | if register & (1 << bits.bit) != 0 { 123 | features.insert(bits.name.clone()); 124 | } 125 | } 126 | } 127 | 128 | // Read brand name if supported. 129 | let brand = if highest_extension_support >= 0x80000004 { 130 | let registers = ( 131 | provider.cpuid(0x80000002, 0), 132 | provider.cpuid(0x80000003, 0), 133 | provider.cpuid(0x80000004, 0), 134 | ); 135 | 136 | let vendor_bytes: Vec<_> = chain!( 137 | registers.0.eax.to_le_bytes(), 138 | registers.0.ebx.to_le_bytes(), 139 | registers.0.ecx.to_le_bytes(), 140 | registers.0.edx.to_le_bytes(), 141 | registers.1.eax.to_le_bytes(), 142 | registers.1.ebx.to_le_bytes(), 143 | registers.1.ecx.to_le_bytes(), 144 | registers.1.edx.to_le_bytes(), 145 | registers.2.eax.to_le_bytes(), 146 | registers.2.ebx.to_le_bytes(), 147 | registers.2.ecx.to_le_bytes(), 148 | registers.2.edx.to_le_bytes(), 149 | ) 150 | .collect(); 151 | let brand_string = match CStr::from_bytes_until_nul(&vendor_bytes) { 152 | Ok(cstr) => cstr.to_string_lossy(), 153 | Err(_) => String::from_utf8_lossy(&vendor_bytes), 154 | }; 155 | Some(brand_string.trim().to_string()) 156 | } else { 157 | None 158 | }; 159 | 160 | Self { 161 | vendor, 162 | features, 163 | brand, 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/cpu/microarchitecture.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::detect::target_architecture_uname; 2 | use crate::schema::{Compiler, CompilerSet}; 3 | use itertools::Itertools; 4 | use std::collections::{HashMap, HashSet}; 5 | use std::fmt::{Debug, Formatter}; 6 | use std::iter; 7 | use std::sync::{Arc, OnceLock}; 8 | 9 | pub struct Microarchitecture { 10 | pub(crate) name: String, 11 | pub(crate) parents: Vec>, 12 | pub(crate) vendor: String, 13 | pub(crate) features: HashSet, 14 | pub(crate) compilers: HashMap>, 15 | pub(crate) generation: usize, 16 | 17 | // Not used in comparison 18 | pub(crate) ancestors: OnceLock>>, 19 | } 20 | 21 | impl PartialEq for Microarchitecture { 22 | fn eq(&self, other: &Self) -> bool { 23 | self.name == other.name 24 | && self.vendor == other.vendor 25 | && self.features == other.features 26 | && self.parents == other.parents 27 | && self.compilers == other.compilers 28 | && self.generation == other.generation 29 | } 30 | } 31 | 32 | impl Eq for Microarchitecture {} 33 | 34 | impl Debug for Microarchitecture { 35 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 36 | f.debug_struct("Microarchitecture") 37 | .field("name", &self.name) 38 | .field( 39 | "ancestors", 40 | &self 41 | .ancestors() 42 | .iter() 43 | .map(|arch| arch.name.as_str()) 44 | .collect_vec(), 45 | ) 46 | .field("vendor", &self.vendor) 47 | .field("features", &self.all_features()) 48 | .field("compilers", &self.compilers) 49 | .field("generation", &self.generation) 50 | .finish() 51 | } 52 | } 53 | 54 | impl Microarchitecture { 55 | pub(crate) fn new( 56 | name: String, 57 | parents: Vec>, 58 | vendor: String, 59 | features: HashSet, 60 | compilers: HashMap>, 61 | ) -> Self { 62 | Microarchitecture::new_generation(name, parents, vendor, features, compilers, 0) 63 | } 64 | 65 | pub(crate) fn new_generation( 66 | name: String, 67 | parents: Vec>, 68 | vendor: String, 69 | features: HashSet, 70 | compilers: HashMap>, 71 | generation: usize, 72 | ) -> Self { 73 | Microarchitecture { 74 | name, 75 | parents, 76 | vendor, 77 | features, 78 | compilers, 79 | generation, 80 | ancestors: OnceLock::new(), 81 | } 82 | } 83 | 84 | /// Constructs a new generic micro architecture 85 | pub fn generic(name: &str) -> Microarchitecture { 86 | Microarchitecture::new( 87 | name.to_string(), 88 | vec![], 89 | "generic".to_string(), 90 | HashSet::new(), 91 | HashMap::new(), 92 | ) 93 | } 94 | 95 | /// Returns the name of the micro architecture. 96 | pub fn name(&self) -> &str { 97 | &self.name 98 | } 99 | 100 | /// Returns the vendor of the micro architecture 101 | pub fn vendor(&self) -> &str { 102 | &self.vendor 103 | } 104 | 105 | /// Returns the generation of the micro architecture. 106 | /// Examples of architectures with a generation are: 107 | /// - x86_64 = 0 108 | /// - x86_64_v3 = 3 109 | /// - power7 = 7 110 | /// - power10 = 10 111 | pub fn generation(&self) -> usize { 112 | self.generation 113 | } 114 | 115 | /// Returns all the known micro architectures. 116 | pub fn known_targets() -> &'static HashMap> { 117 | static KNOWN_TARGETS: std::sync::OnceLock>> = 118 | std::sync::OnceLock::new(); 119 | KNOWN_TARGETS.get_or_init(known_microarchitectures) 120 | } 121 | 122 | /// Returns all the ancestors of this micro architecture. 123 | pub fn ancestors(&self) -> &[Arc] { 124 | self.ancestors.get_or_init(|| { 125 | let mut v = self.parents.clone(); 126 | for parent in &self.parents { 127 | let new_ancestors = parent 128 | .ancestors() 129 | .iter() 130 | .filter(|a| !v.contains(a)) 131 | .cloned() 132 | .collect_vec(); 133 | v.extend(new_ancestors); 134 | } 135 | v 136 | }) 137 | } 138 | 139 | /// Returns true if the given micro architecture is an ancestor of this micro architecture. 140 | pub fn decendent_of(&self, parent: &Microarchitecture) -> bool { 141 | for p in self.parents.iter() { 142 | if p.as_ref() == parent || p.decendent_of(parent) { 143 | return true; 144 | } 145 | } 146 | false 147 | } 148 | 149 | /// Returns true if this micro architecture is a strict superset of the other. 150 | /// 151 | /// If a micro architecture is a strict superset of another, it means that it has all the 152 | /// features of the other micro architecture, and more. 153 | pub fn is_strict_superset(&self, other: &Microarchitecture) -> bool { 154 | self.is_superset(other) && self.name != other.name 155 | } 156 | 157 | /// Returns true if this micro architecture is a superset of the other. 158 | /// 159 | /// This means that the current micro architecture has at least all the features of the other 160 | /// micro architecture. 161 | fn is_superset(&self, other: &Microarchitecture) -> bool { 162 | let a = self.node_set(); 163 | let b = other.node_set(); 164 | a.is_superset(&b) 165 | } 166 | 167 | /// Returns the names of all the ancestors, including the current micro architecture name. 168 | /// 169 | /// This effectively returns all the nodes in the graph of micro architectures that are 170 | /// reachable from the current node. This is useful for comparing two micro architectures. 171 | /// 172 | /// See also [`Self::is_strict_superset`]. 173 | fn node_set(&self) -> HashSet<&str> { 174 | iter::once(self.name.as_str()) 175 | .chain(self.ancestors().iter().map(|a| a.name.as_str())) 176 | .collect() 177 | } 178 | 179 | /// Returns the architecture root, the first parent architecture that does not have a 180 | /// defined parent. 181 | /// 182 | /// It is assumed that all architectures have a single root. 183 | pub fn family(&self) -> &Self { 184 | match self.parents.first() { 185 | Some(parent) => parent.family(), 186 | None => self, 187 | } 188 | } 189 | 190 | /// Returns all features supported by this architecture. 191 | pub fn all_features(&self) -> HashSet { 192 | let mut features = self.features.clone(); 193 | for parent in &self.parents { 194 | features.extend(parent.all_features()); 195 | } 196 | features 197 | } 198 | } 199 | 200 | #[derive(Debug)] 201 | pub struct UnsupportedMicroarchitecture; 202 | 203 | fn known_microarchitectures() -> HashMap> { 204 | let mut known_targets: HashMap> = HashMap::new(); 205 | let schema = crate::schema::MicroarchitecturesSchema::schema(); 206 | 207 | fn fill_target_from_map( 208 | name: &str, 209 | schema: &crate::schema::MicroarchitecturesSchema, 210 | targets: &mut HashMap>, 211 | ) { 212 | let data = &schema.microarchitectures; 213 | let values = &data[name]; 214 | let parent_names = &values.from; 215 | for parent in parent_names { 216 | if !targets.contains_key(parent) { 217 | fill_target_from_map(parent, schema, targets); 218 | } 219 | } 220 | let parents = parent_names 221 | .iter() 222 | .map(|parent| targets[parent].clone()) 223 | .collect::>>(); 224 | 225 | let vendor = values.vendor.clone(); 226 | let features: HashSet = values.features.iter().cloned().collect(); 227 | let compilers: HashMap> = values 228 | .compilers 229 | .as_ref() 230 | .map(|compilers| { 231 | compilers 232 | .iter() 233 | .map(|(vendor, set)| { 234 | ( 235 | vendor.clone(), 236 | // normalize to a sequence of compiler definitions 237 | match set { 238 | CompilerSet::Several(cs) => cs.clone(), 239 | CompilerSet::Single(c) => vec![c.clone()], 240 | }, 241 | ) 242 | }) 243 | .collect() 244 | }) 245 | .unwrap_or_default(); 246 | let generation = values.generation.unwrap_or(0); 247 | 248 | targets.insert( 249 | name.to_string(), 250 | Arc::new(Microarchitecture::new_generation( 251 | name.to_string(), 252 | parents, 253 | vendor, 254 | features, 255 | compilers, 256 | generation, 257 | )), 258 | ); 259 | } 260 | 261 | for name in schema.microarchitectures.keys() { 262 | if !known_targets.contains_key(name) { 263 | fill_target_from_map(name, schema, &mut known_targets); 264 | } 265 | } 266 | 267 | if let Ok(host_platform) = target_architecture_uname() { 268 | known_targets 269 | .entry(host_platform.to_string()) 270 | .or_insert_with(|| Arc::new(Microarchitecture::generic(&host_platform))); 271 | } 272 | 273 | known_targets 274 | } 275 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/cpu/detect.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use super::microarchitecture::{Microarchitecture, UnsupportedMicroarchitecture}; 4 | use crate::cpuid::{CpuId, CpuIdProvider, MachineCpuIdProvider}; 5 | use itertools::Itertools; 6 | use std::{ 7 | cmp::Ordering, 8 | collections::{HashMap, HashSet}, 9 | io::{BufRead, BufReader, Cursor}, 10 | sync::Arc, 11 | }; 12 | 13 | /// Returns the architecture as defined by the compiler. 14 | const fn target_architecture_compiler() -> &'static str { 15 | // HACK: Cannot compare strings in const context, but we can compare bytes. 16 | match std::env::consts::ARCH.as_bytes() { 17 | b"powerpc64" if cfg!(target_endian = "little") => "ppc64le", 18 | b"powerpc64" => "ppc64", 19 | _ => std::env::consts::ARCH, 20 | } 21 | } 22 | 23 | /// Returns the architecture of the host machine by querying uname. 24 | #[cfg(not(target_os = "windows"))] 25 | pub(crate) fn target_architecture_uname() -> std::io::Result { 26 | use std::ffi::CStr; 27 | use std::mem::MaybeUninit; 28 | 29 | let mut utsname = MaybeUninit::zeroed(); 30 | let r = unsafe { libc::uname(utsname.as_mut_ptr()) }; 31 | if r != 0 { 32 | return Err(std::io::Error::last_os_error()); 33 | } 34 | 35 | let utsname = unsafe { utsname.assume_init() }; 36 | let machine = unsafe { CStr::from_ptr(utsname.machine.as_ptr()) }; 37 | 38 | Ok(machine.to_string_lossy().into_owned()) 39 | } 40 | 41 | #[cfg(target_os = "windows")] 42 | pub(crate) fn target_architecture_uname() -> std::io::Result { 43 | Ok(target_architecture_compiler().to_string()) 44 | } 45 | 46 | pub(crate) struct ProcCpuInfo { 47 | cpu_info: HashMap, 48 | } 49 | 50 | impl ProcCpuInfo { 51 | pub fn from_str(contents: &str) -> Self { 52 | Self::from_reader(Cursor::new(contents.as_bytes())) 53 | } 54 | 55 | pub fn from_reader(reader: impl BufRead) -> Self { 56 | let mut cpu_info = std::collections::HashMap::new(); 57 | for line in reader.lines() { 58 | let Ok(line) = line else { 59 | continue; 60 | }; 61 | let Some((key, value)) = line.split_once(':') else { 62 | // If there is no seperator and info was already populated, break because we are on a 63 | // blank line seperating CPUs. 64 | if !cpu_info.is_empty() { 65 | break; 66 | } 67 | continue; 68 | }; 69 | cpu_info.insert(key.trim().to_string(), value.trim().to_string()); 70 | } 71 | Self { cpu_info } 72 | } 73 | 74 | /// Read the contents from /proc/cpuinfo and parse it into a `ProcCpuInfo` struct. 75 | pub fn from_proc_info() -> std::io::Result { 76 | let file = std::fs::File::open("/proc/cpuinfo")?; 77 | Ok(Self::from_reader(BufReader::new(file))) 78 | } 79 | 80 | pub fn get(&self, key: &str) -> Option<&str> { 81 | self.cpu_info.get(key).map(String::as_str) 82 | } 83 | } 84 | 85 | /// Returns the micro architecture of a Windows machine with the specified properties. 86 | pub(crate) fn detect_windows( 87 | arch: &str, 88 | cpuid: &C, 89 | ) -> Result { 90 | match arch { 91 | "x86_64" | "x86" => { 92 | let cpuid = CpuId::detect(cpuid); 93 | Ok(Microarchitecture { 94 | name: String::new(), 95 | parents: vec![], 96 | vendor: cpuid.vendor.clone(), 97 | features: cpuid.features.clone(), 98 | compilers: Default::default(), 99 | generation: 0, 100 | ancestors: Default::default(), 101 | }) 102 | } 103 | target_arch @ ("ppc64" | "ppc64le" | "aarch64" | "riscv64") => { 104 | Ok(Microarchitecture::generic(target_arch)) 105 | } 106 | _ => Err(UnsupportedMicroarchitecture), 107 | } 108 | } 109 | 110 | fn detect_linux(arch: &str, cpu_info: &ProcCpuInfo) -> Microarchitecture { 111 | match arch { 112 | "x86_64" => Microarchitecture { 113 | vendor: cpu_info.get("vendor_id").unwrap_or("generic").to_string(), 114 | features: cpu_info 115 | .get("flags") 116 | .unwrap_or_default() 117 | .split_ascii_whitespace() 118 | .map(|s| s.to_string()) 119 | .collect(), 120 | ..Microarchitecture::generic("") 121 | }, 122 | "aarch64" => { 123 | let vendor = if let Some(implementer) = cpu_info.get("CPU implementer") { 124 | // Mapping numeric codes to vendor (ARM). This list is a merge from 125 | // different sources: 126 | // 127 | // https://github.com/karelzak/util-linux/blob/master/sys-utils/lscpu-arm.c 128 | // https://developer.arm.com/docs/ddi0487/latest/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile 129 | // https://github.com/gcc-mirror/gcc/blob/master/gcc/config/aarch64/aarch64-cores.def 130 | // https://patchwork.kernel.org/patch/10524949/ 131 | crate::schema::MicroarchitecturesSchema::schema() 132 | .conversions 133 | .arm_vendors 134 | .get(implementer) 135 | .cloned() 136 | .unwrap_or_else(|| "generic".to_string()) 137 | } else { 138 | String::from("generic") 139 | }; 140 | 141 | Microarchitecture { 142 | vendor, 143 | features: cpu_info 144 | .get("Features") 145 | .unwrap_or_default() 146 | .split_ascii_whitespace() 147 | .map(|s| s.to_string()) 148 | .collect(), 149 | ..Microarchitecture::generic("") 150 | } 151 | } 152 | "ppc64" | "ppc64le" => { 153 | let cpu = cpu_info.get("cpu").unwrap_or_default(); 154 | let generation = cpu 155 | .strip_prefix("POWER") 156 | .map(|rest| { 157 | rest.split_once(|c: char| !c.is_ascii_digit()) 158 | .map_or(rest, |(digits, _)| digits) 159 | }) 160 | .and_then(|gen| gen.parse().ok()) 161 | .unwrap_or(0); 162 | Microarchitecture { 163 | generation, 164 | ..Microarchitecture::generic("") 165 | } 166 | } 167 | "riscv64" => { 168 | let uarch = match cpu_info.get("uarch") { 169 | Some("sifive,u74-mc") => "u74mc", 170 | Some(uarch) => uarch, 171 | None => "riscv64", 172 | }; 173 | Microarchitecture::generic(uarch) 174 | } 175 | _ => Microarchitecture::generic(arch), 176 | } 177 | } 178 | 179 | pub(crate) trait SysCtlProvider { 180 | fn sysctl(&self, name: &str) -> std::io::Result; 181 | } 182 | 183 | #[derive(Default)] 184 | pub(crate) struct MachineSysCtlProvider {} 185 | 186 | impl SysCtlProvider for MachineSysCtlProvider { 187 | fn sysctl(&self, name: &str) -> std::io::Result { 188 | cfg_if::cfg_if! { 189 | if #[cfg(target_os = "macos")] { 190 | use sysctl::Sysctl; 191 | sysctl::Ctl::new(name) 192 | .and_then(|ctl| ctl.value()) 193 | .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) 194 | .map(|v| v.to_string()) 195 | } else { 196 | unimplemented!("Sysctl is not implemented for this platform, requesting {name}") 197 | } 198 | } 199 | } 200 | } 201 | 202 | fn detect_macos(arch: &str, sysctl: &S) -> Microarchitecture { 203 | match arch { 204 | "x86_64" => { 205 | let cpu_features = sysctl 206 | .sysctl("machdep.cpu.features") 207 | .unwrap_or_default() 208 | .to_lowercase(); 209 | let cpu_leaf7_features = sysctl 210 | .sysctl("machdep.cpu.leaf7_features") 211 | .unwrap_or_default() 212 | .to_lowercase(); 213 | let vendor = sysctl.sysctl("machdep.cpu.vendor").unwrap_or_default(); 214 | 215 | let mut features = cpu_features 216 | .split_whitespace() 217 | .chain(cpu_leaf7_features.split_whitespace()) 218 | .map(|s| s.to_string()) 219 | .collect::>(); 220 | 221 | // Flags detected on Darwin turned to their linux counterpart. 222 | for (darwin_flag, linux_flag) in crate::schema::MicroarchitecturesSchema::schema() 223 | .conversions 224 | .darwin_flags 225 | .iter() 226 | { 227 | if features.contains(darwin_flag) { 228 | features.extend(linux_flag.split_whitespace().map(|s| s.to_string())) 229 | } 230 | } 231 | 232 | Microarchitecture { 233 | vendor, 234 | features, 235 | ..Microarchitecture::generic("") 236 | } 237 | } 238 | _ => { 239 | let model = match sysctl 240 | .sysctl("machdep.cpu.brand_string") 241 | .map(|v| v.to_string().to_lowercase()) 242 | .ok() 243 | { 244 | Some(model) if model.contains("m2") => String::from("m2"), 245 | Some(model) if model.contains("m1") => String::from("m1"), 246 | Some(model) if model.contains("apple") => String::from("m1"), 247 | _ => String::from("unknown"), 248 | }; 249 | 250 | Microarchitecture { 251 | vendor: String::from("Apple"), 252 | ..Microarchitecture::generic(&model) 253 | } 254 | } 255 | } 256 | } 257 | 258 | fn compare_microarchitectures(a: &Microarchitecture, b: &Microarchitecture) -> Ordering { 259 | let ancestors_a = a.ancestors().len(); 260 | let ancestors_b = b.ancestors().len(); 261 | 262 | let features_a = a.features.len(); 263 | let features_b = b.features.len(); 264 | 265 | ancestors_a 266 | .cmp(&ancestors_b) 267 | .then(features_a.cmp(&features_b)) 268 | } 269 | 270 | struct TargetDetector { 271 | target_os: Option, 272 | target_arch: Option, 273 | cpu_info: Option, 274 | cpuid_provider: C, 275 | sysctl_provider: S, 276 | } 277 | 278 | impl TargetDetector { 279 | pub fn new() -> Self { 280 | Self { 281 | target_os: None, 282 | target_arch: None, 283 | cpu_info: None, 284 | cpuid_provider: MachineCpuIdProvider::default(), 285 | sysctl_provider: MachineSysCtlProvider::default(), 286 | } 287 | } 288 | } 289 | 290 | impl TargetDetector { 291 | pub fn with_sysctl_provider( 292 | self, 293 | sysctl_provider: O, 294 | ) -> TargetDetector { 295 | TargetDetector { 296 | target_os: self.target_os, 297 | target_arch: self.target_arch, 298 | cpu_info: self.cpu_info, 299 | cpuid_provider: self.cpuid_provider, 300 | sysctl_provider, 301 | } 302 | } 303 | 304 | pub fn with_cpuid_provider(self, cpuid_provider: O) -> TargetDetector { 305 | TargetDetector { 306 | target_os: self.target_os, 307 | target_arch: self.target_arch, 308 | cpu_info: self.cpu_info, 309 | cpuid_provider, 310 | sysctl_provider: self.sysctl_provider, 311 | } 312 | } 313 | 314 | pub fn with_target_os(self, target_os: &str) -> Self { 315 | Self { 316 | target_os: Some(target_os.to_string()), 317 | ..self 318 | } 319 | } 320 | 321 | pub fn with_target_arch(self, target_arch: &str) -> Self { 322 | Self { 323 | target_arch: Some(target_arch.to_string()), 324 | ..self 325 | } 326 | } 327 | 328 | pub fn with_proc_cpu_info(self, proc_cpu_info: ProcCpuInfo) -> Self { 329 | Self { 330 | cpu_info: Some(proc_cpu_info), 331 | ..self 332 | } 333 | } 334 | 335 | pub fn detect(self) -> Result, UnsupportedMicroarchitecture> { 336 | let os = self.target_os.as_deref().unwrap_or(std::env::consts::OS); 337 | 338 | // Determine the architecture of the machine based on the operating system. 339 | let target_arch_uname; 340 | let target_arch = match (os, &self.target_arch) { 341 | ("linux", None) => { 342 | target_arch_uname = 343 | target_architecture_uname().map_err(|_| UnsupportedMicroarchitecture)?; 344 | &target_arch_uname 345 | } 346 | ("macos", _) => { 347 | // On macOS, it might happen that we are on an M1 but running in Rosetta. In that 348 | // case uname will return "x86_64" so we need to fix that. 349 | if self 350 | .sysctl_provider 351 | .sysctl("machdep.cpu.brand_string") 352 | .unwrap_or_default() 353 | .contains("Apple") 354 | { 355 | "aarch64" 356 | } else { 357 | "x86_64" 358 | } 359 | } 360 | (_, Some(arch)) => arch.as_str(), 361 | (_, None) => target_architecture_compiler(), 362 | }; 363 | 364 | // Detect the architecture based on the operating system. 365 | let detected_arch = match os { 366 | "linux" => { 367 | if let Some(cpu_info) = self.cpu_info.or_else(|| ProcCpuInfo::from_proc_info().ok()) 368 | { 369 | detect_linux(target_arch, &cpu_info) 370 | } else { 371 | Microarchitecture::generic(target_arch) 372 | } 373 | } 374 | "macos" => detect_macos(target_arch, &self.sysctl_provider), 375 | "windows" => detect_windows(target_arch, &self.cpuid_provider)?, 376 | _ => { 377 | return Err(UnsupportedMicroarchitecture); 378 | } 379 | }; 380 | 381 | // Determine compatible targets based on the architecture. 382 | let compatible_targets = match target_arch { 383 | "aarch64" => compatible_microarchitectures_for_aarch64(&detected_arch, os == "macos"), 384 | "ppc64" | "ppc64le" => { 385 | compatible_microarchitectures_for_ppc64(&detected_arch, target_arch == "ppc64le") 386 | } 387 | "riscv64" => compatible_microarchitectures_for_riscv64(&detected_arch), 388 | "x86_64" | "x86" => compatible_microarchitectures_for_x86_64(&detected_arch), 389 | _ => vec![Microarchitecture::known_targets() 390 | .get(target_arch) 391 | .ok_or(UnsupportedMicroarchitecture)? 392 | .clone()], 393 | }; 394 | 395 | // Find the best generic candidates 396 | let Some(best_generic_candidate) = compatible_targets 397 | .iter() 398 | .filter(|target| target.vendor == "generic") 399 | .sorted_by(|a, b| compare_microarchitectures(a, b)) 400 | .last() 401 | else { 402 | // If there is no matching generic candidate then 403 | return Err(UnsupportedMicroarchitecture); 404 | }; 405 | 406 | // Filter the candidates to be descendant of the best generic candidate. This is to avoid that 407 | // the lack of a niche feature that can be disabled from e.g. BIOS prevents detection of a 408 | // reasonably performant architecture 409 | let best_candidates = compatible_targets 410 | .iter() 411 | .filter(|target| target.is_strict_superset(best_generic_candidate)) 412 | .collect_vec(); 413 | 414 | // Resort the matching candidates and fall back to the best generic candidate if there is no 415 | // matching non-generic candidate. 416 | Ok(best_candidates 417 | .into_iter() 418 | .sorted_by(|a, b| compare_microarchitectures(a, b)) 419 | .last() 420 | .unwrap_or(best_generic_candidate) 421 | .clone()) 422 | } 423 | } 424 | 425 | /// Detects the host micro-architecture and returns it. 426 | pub fn host() -> Result, UnsupportedMicroarchitecture> { 427 | TargetDetector::new().detect() 428 | } 429 | 430 | #[allow(unused)] 431 | fn compatible_microarchitectures_for_aarch64( 432 | detected_info: &Microarchitecture, 433 | is_macos: bool, 434 | ) -> Vec> { 435 | let targets = Microarchitecture::known_targets(); 436 | 437 | // Get the root micro-architecture for aarch64. 438 | let Some(arch_root) = targets.get("aarch64") else { 439 | return vec![]; 440 | }; 441 | 442 | // On macOS it seems impossible to get all the CPU features with sysctl info, but for 443 | // ARM we can get the exact model 444 | let macos_model = if is_macos { 445 | match targets.get(&detected_info.name) { 446 | None => return vec![], 447 | model => model, 448 | } 449 | } else { 450 | None 451 | }; 452 | 453 | // Find all targets that are decendants of the root architecture and are compatibile with the 454 | // detected micro-architecture. 455 | targets 456 | .values() 457 | .filter(|target| { 458 | // At the moment, it's not clear how to detect compatibility with a specific version of 459 | // the architecture. 460 | if target.vendor == "generic" && target.name != "aarch64" { 461 | return false; 462 | } 463 | 464 | // Must share the same architecture family and vendor. 465 | if arch_root.as_ref() != target.family() 466 | || !(target.vendor == "generic" || target.vendor == detected_info.vendor) 467 | { 468 | return false; 469 | } 470 | 471 | if let Some(macos_model) = macos_model { 472 | target.as_ref() == macos_model.as_ref() || macos_model.decendent_of(target) 473 | } else { 474 | target.features.is_subset(&detected_info.features) 475 | } 476 | }) 477 | .cloned() 478 | .collect() 479 | } 480 | 481 | #[allow(unused)] 482 | fn compatible_microarchitectures_for_ppc64( 483 | detected_info: &Microarchitecture, 484 | little_endian: bool, 485 | ) -> Vec> { 486 | let targets = Microarchitecture::known_targets(); 487 | 488 | let root_arch = if little_endian { "ppc64le" } else { "ppc64" }; 489 | 490 | // Get the root micro-architecture. 491 | let Some(arch_root) = targets.get(root_arch) else { 492 | return vec![]; 493 | }; 494 | 495 | // Find all targets that are decendants of the root architecture and are compatibile with the 496 | // detected micro-architecture. 497 | targets 498 | .values() 499 | .filter(|target| { 500 | (target.as_ref() == arch_root.as_ref() || target.decendent_of(arch_root)) 501 | && target.generation <= detected_info.generation 502 | }) 503 | .cloned() 504 | .collect() 505 | } 506 | 507 | #[allow(unused)] 508 | fn compatible_microarchitectures_for_x86_64( 509 | detected_info: &Microarchitecture, 510 | ) -> Vec> { 511 | let targets = Microarchitecture::known_targets(); 512 | 513 | // Get the root micro-architecture for x86_64. 514 | let Some(arch_root) = targets.get("x86_64") else { 515 | return vec![]; 516 | }; 517 | 518 | // Find all targets that are decendants of the root architecture and are compatibile with the 519 | // detected micro-architecture. 520 | targets 521 | .values() 522 | .filter(|target| { 523 | (target.as_ref() == arch_root.as_ref() || target.decendent_of(arch_root)) 524 | && (target.vendor == detected_info.vendor || target.vendor == "generic") 525 | && target.features.is_subset(&detected_info.features) 526 | }) 527 | .cloned() 528 | .collect() 529 | } 530 | 531 | #[allow(unused)] 532 | fn compatible_microarchitectures_for_riscv64( 533 | detected_info: &Microarchitecture, 534 | ) -> Vec> { 535 | let targets = Microarchitecture::known_targets(); 536 | 537 | // Get the root micro-architecture for riscv64. 538 | let Some(arch_root) = targets.get("riscv64") else { 539 | return vec![]; 540 | }; 541 | 542 | // Find all targets that are descendants of the root architecture and are compatible with the 543 | // detected micro-architecture. 544 | targets 545 | .values() 546 | .filter(|target| { 547 | (target.as_ref() == arch_root.as_ref() || target.decendent_of(arch_root)) 548 | && (target.name == detected_info.name || target.vendor == "generic") 549 | }) 550 | .cloned() 551 | .collect() 552 | } 553 | 554 | #[cfg(test)] 555 | mod tests { 556 | use crate::cpu::detect::{ProcCpuInfo, SysCtlProvider}; 557 | use crate::cpu::Microarchitecture; 558 | use crate::cpuid::{CpuIdProvider, CpuIdRegisters}; 559 | use itertools::Itertools; 560 | use rstest::rstest; 561 | use std::collections::HashMap; 562 | use std::path::PathBuf; 563 | 564 | #[test] 565 | fn check_host() { 566 | let host = super::host(); 567 | eprintln!("{:#?}", &host); 568 | host.expect("host() should return something"); 569 | } 570 | 571 | #[rstest] 572 | fn test_expected_target(#[files("json/tests/targets/*")] path: PathBuf) { 573 | // Determine the type of target test. 574 | let filename = path.file_name().unwrap().to_string_lossy(); 575 | let (platform, _operating_system, target) = filename.split('-').collect_tuple().unwrap(); 576 | 577 | let expected_target = Microarchitecture::known_targets() 578 | .get(target) 579 | .expect("missing target"); 580 | 581 | let architecture_family = match platform { 582 | "darwin" => "x86_64", 583 | "windows" => "x86_64", 584 | _ => expected_target.family().name.as_str(), 585 | }; 586 | 587 | // Read the contents of the file. 588 | let contents = std::fs::read_to_string(&path).unwrap(); 589 | 590 | let detector = super::TargetDetector::new().with_target_arch(architecture_family); 591 | let detected_target = match platform { 592 | "linux" | "bgq" => detector 593 | .with_target_os("linux") 594 | .with_proc_cpu_info(ProcCpuInfo::from_str(&contents)) 595 | .detect(), 596 | "darwin" => detector 597 | .with_target_os("macos") 598 | .with_sysctl_provider(MemorySysCtlProvider::from_str(&contents)) 599 | .detect(), 600 | "windows" => detector 601 | .with_target_os("windows") 602 | .with_cpuid_provider(MockCpuIdProvider::from_str(&contents)) 603 | .detect(), 604 | _ => panic!("Unsupported platform: {}", platform), 605 | }; 606 | 607 | let detected_target = detected_target.expect("Failed to detect target"); 608 | assert_eq!(detected_target.as_ref(), expected_target.as_ref()); 609 | } 610 | 611 | struct MemorySysCtlProvider { 612 | contents: HashMap, 613 | } 614 | 615 | impl MemorySysCtlProvider { 616 | pub fn from_str(data: &str) -> Self { 617 | let mut contents = HashMap::new(); 618 | for line in data.lines() { 619 | let (key, value) = line.split_once(':').unwrap(); 620 | contents.insert(key.trim().to_string(), value.trim().to_string()); 621 | } 622 | Self { contents } 623 | } 624 | } 625 | 626 | impl SysCtlProvider for MemorySysCtlProvider { 627 | fn sysctl(&self, name: &str) -> std::io::Result { 628 | self.contents 629 | .get(name) 630 | .cloned() 631 | .ok_or_else(|| std::io::Error::from(std::io::ErrorKind::NotFound)) 632 | } 633 | } 634 | 635 | struct MockCpuIdProvider { 636 | contents: HashMap<(u32, u32), CpuIdRegisters>, 637 | } 638 | 639 | impl MockCpuIdProvider { 640 | pub fn from_str(data: &str) -> Self { 641 | let mut contents = HashMap::new(); 642 | for line in data.lines() { 643 | let (leaf, subleaf, eax, ebx, ecx, edx) = line 644 | .split(", ") 645 | .map(|d| d.parse().unwrap()) 646 | .collect_tuple() 647 | .unwrap(); 648 | contents.insert((leaf, subleaf), CpuIdRegisters { eax, ebx, ecx, edx }); 649 | } 650 | Self { contents } 651 | } 652 | } 653 | 654 | impl CpuIdProvider for MockCpuIdProvider { 655 | fn cpuid(&self, leaf: u32, subleaf: u32) -> CpuIdRegisters { 656 | self.contents.get(&(leaf, subleaf)).cloned().unwrap() 657 | } 658 | } 659 | } 660 | --------------------------------------------------------------------------------