1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
//! `cpuid` 内部函数
#![allow(clippy::module_name_repetitions)]

use crate::arch::asm;
#[cfg(test)]
use stdarch_test::assert_instr;

/// `cpuid` 指令的结果。
#[allow(clippy::missing_inline_in_public_items)]
// ^^ CpuidResult 的 Debug 派生的暗示不是 #[inline],这没关系。
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[stable(feature = "simd_x86", since = "1.27.0")]
pub struct CpuidResult {
    /// EAX 寄存器。
    #[stable(feature = "simd_x86", since = "1.27.0")]
    pub eax: u32,
    /// EBX 寄存器。
    #[stable(feature = "simd_x86", since = "1.27.0")]
    pub ebx: u32,
    /// ECX 寄存器。
    #[stable(feature = "simd_x86", since = "1.27.0")]
    pub ecx: u32,
    /// EDX 寄存器。
    #[stable(feature = "simd_x86", since = "1.27.0")]
    pub edx: u32,
}

/// 返回给定 `leaf` (`EAX`) 和 `sub_leaf` (`ECX`) 的 `cpuid` 指令的结果。
///
/// [`__get_cpuid_max(0)`](fn.__get_cpuid_max.html) 的第一个元组参数返回支持的最高叶子值。
/// 对于叶子包含子叶,第二个元组参数返回支持的最高子叶值。
///
/// [CPUID 维基百科][wiki_cpuid] 页面包含如何使用 `EAX` 和 `ECX` 寄存器查询哪些信息,以及如何解释 `EAX`,`EBX`,`ECX` 和 `EDX` 返回的结果。
///
///
/// 引用的是:
/// - [英特尔 64 位和 IA-32 架构软件开发人员手册第 2 卷:
///   指令集引用,AZ][intel64_ref]。
/// - [AMD64 Architecture Programmer's Manual, Volume 3: General-Purpose and System Instructions][amd64_ref].
///
/// [wiki_cpuid]: https://en.wikipedia.org/wiki/CPUID
/// [intel64_ref]: https://cdrdv2-public.intel.com/671110/325383-sdm-vol-2abcd.pdf
/// [amd64_ref]: http://support.amd.com/TechDocs/24594.pdf
///
///
///
///
///
///
///
#[inline]
#[cfg_attr(test, assert_instr(cpuid))]
#[stable(feature = "simd_x86", since = "1.27.0")]
pub unsafe fn __cpuid_count(leaf: u32, sub_leaf: u32) -> CpuidResult {
    let eax;
    let ebx;
    let ecx;
    let edx;

    // LLVM 有时会保留 `ebx` 供其内部使用,因此我们需要为它使用临时寄存器。
    //
    #[cfg(target_arch = "x86")]
    {
        asm!(
            "mov {0}, ebx",
            "cpuid",
            "xchg {0}, ebx",
            out(reg) ebx,
            inout("eax") leaf => eax,
            inout("ecx") sub_leaf => ecx,
            out("edx") edx,
            options(nostack, preserves_flags),
        );
    }
    #[cfg(target_arch = "x86_64")]
    {
        asm!(
            "mov {0:r}, rbx",
            "cpuid",
            "xchg {0:r}, rbx",
            out(reg) ebx,
            inout("eax") leaf => eax,
            inout("ecx") sub_leaf => ecx,
            out("edx") edx,
            options(nostack, preserves_flags),
        );
    }
    CpuidResult { eax, ebx, ecx, edx }
}

/// 请参见 [`__cpuid_count`](fn.__cpuid_count.html)。
#[inline]
#[cfg_attr(test, assert_instr(cpuid))]
#[stable(feature = "simd_x86", since = "1.27.0")]
pub unsafe fn __cpuid(leaf: u32) -> CpuidResult {
    __cpuid_count(leaf, 0)
}

/// 主机是否支持 `cpuid` 指令?
#[inline]
pub fn has_cpuid() -> bool {
    #[cfg(target_env = "sgx")]
    {
        false
    }
    #[cfg(all(not(target_env = "sgx"), target_arch = "x86_64"))]
    {
        true
    }
    #[cfg(all(not(target_env = "sgx"), target_arch = "x86"))]
    {
        // 对 i586 和 i686 Rust 的优化目标是启用了 SSE 并支持 cpuid 的目标:
        //
        #[cfg(target_feature = "sse")]
        {
            true
        }

        // 如果未启用 SSE,请检测 cpuid 是否可用:
        #[cfg(not(target_feature = "sse"))]
        unsafe {
            // 在 `x86` 上,`cpuid` 指令并非始终可用。
            // 这遵循以下指示的方法:
            // http://wiki.osdev.org/CPUID#Checking_CPUID_availability
            // https://software.intel.com/en-us/articles/using-cpuid-to-detect-the-presence-of-sse-41-and-sse-42-instruction-sets/
            // 它通过检查 EFLAGS 寄存器的第 21 位是否可修改来检测 `cpuid` 是否可用。
            //
            // 如果是,则 `cpuid` 可用。
            let result: u32;
            asm!(
                // 读取 eflags 并保存它的副本
                "pushfd",
                "pop {result}",
                "mov {result}, {saved_flags}",
                // 翻转标志的第 21 位
                "xor $0x200000, {result}",
                // 加载修改后的标志并将其读回。
                // 只有在 cpuid 可用时才能修改位 21。
                "push {result}",
                "popfd",
                "pushfd",
                "pop {result}",
                // 使用 xor 找出第 21 位是否发生了变化
                "xor {saved_flags}, {result}",
                result = out(reg) result,
                saved_flags = out(reg) _,
                options(nomem, att_syntax),
            );
            // popfd (A) 和 pushfd (B) 之间存在竞争,由于中断,调试器逐步通过 asm 等原因,可能已修改了 21st 之后的其他位。
            //
            //
            // 因此,明确检查第 21 位是否被修改。
            //
            // 如果结果为零,则未修改 cpuid 位。
            // 如果结果为 `0x200000` (non-zero),则说明 cpuid 已正确修改,并且 CPU 支持 cpuid 指令:
            //
            //
            //
            //
            (result & 0x200000) != 0
        }
    }
}

/// 返回最高支持的 `leaf` (`EAX`) 和子叶 (`ECX`) `cpuid` 值。
///
/// 如果支持 `cpuid`,并且 `leaf` 为零,则第一个元组参数包含 `cpuid` 支持的最高 `leaf` 值。
/// 对于包含子叶的 `leaf',第二个元组参数包含支持的最高子叶值。
///
/// 另请参见 [`__cpuid`](fn.__cpuid.html) 和 [`__cpuid_count`](fn.__cpuid_count.html)。
///
///
///
///
#[inline]
#[stable(feature = "simd_x86", since = "1.27.0")]
pub unsafe fn __get_cpuid_max(leaf: u32) -> (u32, u32) {
    let CpuidResult { eax, ebx, .. } = __cpuid(leaf);
    (eax, ebx)
}

#[cfg(test)]
mod tests {
    use crate::core_arch::x86::*;

    #[test]
    fn test_always_has_cpuid() {
        // 所有当前测试过的目标都有说明
        // FIXME: 将没有 `cpuid` 的目标添加到 CI
        assert!(cpuid::has_cpuid());
    }

    #[test]
    fn test_has_cpuid_idempotent() {
        assert_eq!(cpuid::has_cpuid(), cpuid::has_cpuid());
    }
}