]>
Commit | Line | Data |
---|---|---|
4e2f5f3a DB |
1 | #!/usr/bin/python3 |
2 | # | |
3 | # SPDX-License-Identifier: GPL-2.0-or-later | |
4 | # | |
5 | # A script to generate a CSV file showing the x86_64 ABI | |
6 | # compatibility levels for each CPU model. | |
7 | # | |
8 | ||
9 | from qemu import qmp | |
10 | import sys | |
11 | ||
12 | if len(sys.argv) != 1: | |
13 | print("syntax: %s QMP-SOCK\n\n" % __file__ + | |
14 | "Where QMP-SOCK points to a QEMU process such as\n\n" + | |
15 | " # qemu-system-x86_64 -qmp unix:/tmp/qmp,server,nowait " + | |
16 | "-display none -accel kvm", file=sys.stderr) | |
17 | sys.exit(1) | |
18 | ||
19 | # Mandatory CPUID features for each microarch ABI level | |
20 | levels = [ | |
21 | [ # x86-64 baseline | |
22 | "cmov", | |
23 | "cx8", | |
24 | "fpu", | |
25 | "fxsr", | |
26 | "mmx", | |
27 | "syscall", | |
28 | "sse", | |
29 | "sse2", | |
30 | ], | |
31 | [ # x86-64-v2 | |
32 | "cx16", | |
33 | "lahf-lm", | |
34 | "popcnt", | |
35 | "pni", | |
36 | "sse4.1", | |
37 | "sse4.2", | |
38 | "ssse3", | |
39 | ], | |
40 | [ # x86-64-v3 | |
41 | "avx", | |
42 | "avx2", | |
43 | "bmi1", | |
44 | "bmi2", | |
45 | "f16c", | |
46 | "fma", | |
47 | "abm", | |
48 | "movbe", | |
49 | ], | |
50 | [ # x86-64-v4 | |
51 | "avx512f", | |
52 | "avx512bw", | |
53 | "avx512cd", | |
54 | "avx512dq", | |
55 | "avx512vl", | |
56 | ], | |
57 | ] | |
58 | ||
59 | # Assumes externally launched process such as | |
60 | # | |
61 | # qemu-system-x86_64 -qmp unix:/tmp/qmp,server,nowait -display none -accel kvm | |
62 | # | |
63 | # Note different results will be obtained with TCG, as | |
64 | # TCG masks out certain features otherwise present in | |
65 | # the CPU model definitions, as does KVM. | |
66 | ||
67 | ||
68 | sock = sys.argv[1] | |
69 | cmd = sys.argv[2] | |
70 | shell = qmp.QEMUMonitorProtocol(sock) | |
71 | shell.connect() | |
72 | ||
73 | models = shell.cmd("query-cpu-definitions") | |
74 | ||
75 | # These QMP props don't correspond to CPUID fatures | |
76 | # so ignore them | |
77 | skip = [ | |
78 | "family", | |
79 | "min-level", | |
80 | "min-xlevel", | |
81 | "vendor", | |
82 | "model", | |
83 | "model-id", | |
84 | "stepping", | |
85 | ] | |
86 | ||
87 | names = [] | |
88 | ||
89 | for model in models["return"]: | |
90 | if "alias-of" in model: | |
91 | continue | |
92 | names.append(model["name"]) | |
93 | ||
94 | models = {} | |
95 | ||
96 | for name in sorted(names): | |
97 | cpu = shell.cmd("query-cpu-model-expansion", | |
98 | { "type": "static", | |
99 | "model": { "name": name }}) | |
100 | ||
101 | got = {} | |
102 | for (feature, present) in cpu["return"]["model"]["props"].items(): | |
103 | if present and feature not in skip: | |
104 | got[feature] = True | |
105 | ||
106 | if name in ["host", "max", "base"]: | |
107 | continue | |
108 | ||
109 | models[name] = { | |
110 | # Dict of all present features in this CPU model | |
111 | "features": got, | |
112 | ||
113 | # Whether each x86-64 ABI level is satisfied | |
114 | "levels": [False, False, False, False], | |
115 | ||
116 | # Number of extra CPUID features compared to the x86-64 ABI level | |
117 | "distance":[-1, -1, -1, -1], | |
118 | ||
119 | # CPUID features present in model, but not in ABI level | |
120 | "delta":[[], [], [], []], | |
121 | ||
122 | # CPUID features in ABI level but not present in model | |
123 | "missing": [[], [], [], []], | |
124 | } | |
125 | ||
126 | ||
127 | # Calculate whether the CPU models satisfy each ABI level | |
128 | for name in models.keys(): | |
129 | for level in range(len(levels)): | |
130 | got = set(models[name]["features"]) | |
131 | want = set(levels[level]) | |
132 | missing = want - got | |
133 | match = True | |
134 | if len(missing) > 0: | |
135 | match = False | |
136 | models[name]["levels"][level] = match | |
137 | models[name]["missing"][level] = missing | |
138 | ||
139 | # Cache list of CPU models satisfying each ABI level | |
140 | abi_models = [ | |
141 | [], | |
142 | [], | |
143 | [], | |
144 | [], | |
145 | ] | |
146 | ||
147 | for name in models.keys(): | |
148 | for level in range(len(levels)): | |
149 | if models[name]["levels"][level]: | |
150 | abi_models[level].append(name) | |
151 | ||
152 | ||
153 | for level in range(len(abi_models)): | |
154 | # Find the union of features in all CPU models satisfying this ABI | |
155 | allfeatures = {} | |
156 | for name in abi_models[level]: | |
157 | for feat in models[name]["features"]: | |
158 | allfeatures[feat] = True | |
159 | ||
160 | # Find the intersection of features in all CPU models satisfying this ABI | |
161 | commonfeatures = [] | |
162 | for feat in allfeatures: | |
163 | present = True | |
164 | for name in models.keys(): | |
165 | if not models[name]["levels"][level]: | |
166 | continue | |
167 | if feat not in models[name]["features"]: | |
168 | present = False | |
169 | if present: | |
170 | commonfeatures.append(feat) | |
171 | ||
172 | # Determine how many extra features are present compared to the lowest | |
173 | # common denominator | |
174 | for name in models.keys(): | |
175 | if not models[name]["levels"][level]: | |
176 | continue | |
177 | ||
178 | delta = set(models[name]["features"].keys()) - set(commonfeatures) | |
179 | models[name]["distance"][level] = len(delta) | |
180 | models[name]["delta"][level] = delta | |
181 | ||
182 | def print_uarch_abi_csv(): | |
183 | print("# Automatically generated from '%s'" % __file__) | |
184 | print("Model,baseline,v2,v3,v4") | |
185 | for name in models.keys(): | |
186 | print(name, end="") | |
187 | for level in range(len(levels)): | |
188 | if models[name]["levels"][level]: | |
189 | print(",✅", end="") | |
190 | else: | |
191 | print(",", end="") | |
192 | print() | |
193 | ||
194 | print_uarch_abi_csv() |