2021-06-07 15:58:42 +02:00
|
|
|
#!/usr/bin/python3
|
|
|
|
#
|
|
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#
|
|
|
|
# A script to generate a CSV file showing the x86_64 ABI
|
|
|
|
# compatibility levels for each CPU model.
|
|
|
|
#
|
|
|
|
|
2022-03-30 19:28:10 +02:00
|
|
|
from qemu.qmp.legacy import QEMUMonitorProtocol
|
2021-06-07 15:58:42 +02:00
|
|
|
import sys
|
|
|
|
|
2022-01-11 00:28:57 +01:00
|
|
|
if len(sys.argv) != 2:
|
2021-06-07 15:58:42 +02:00
|
|
|
print("syntax: %s QMP-SOCK\n\n" % __file__ +
|
|
|
|
"Where QMP-SOCK points to a QEMU process such as\n\n" +
|
|
|
|
" # qemu-system-x86_64 -qmp unix:/tmp/qmp,server,nowait " +
|
|
|
|
"-display none -accel kvm", file=sys.stderr)
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
# Mandatory CPUID features for each microarch ABI level
|
|
|
|
levels = [
|
|
|
|
[ # x86-64 baseline
|
|
|
|
"cmov",
|
|
|
|
"cx8",
|
|
|
|
"fpu",
|
|
|
|
"fxsr",
|
|
|
|
"mmx",
|
|
|
|
"syscall",
|
|
|
|
"sse",
|
|
|
|
"sse2",
|
|
|
|
],
|
|
|
|
[ # x86-64-v2
|
|
|
|
"cx16",
|
|
|
|
"lahf-lm",
|
|
|
|
"popcnt",
|
|
|
|
"pni",
|
|
|
|
"sse4.1",
|
|
|
|
"sse4.2",
|
|
|
|
"ssse3",
|
|
|
|
],
|
|
|
|
[ # x86-64-v3
|
|
|
|
"avx",
|
|
|
|
"avx2",
|
|
|
|
"bmi1",
|
|
|
|
"bmi2",
|
|
|
|
"f16c",
|
|
|
|
"fma",
|
|
|
|
"abm",
|
|
|
|
"movbe",
|
|
|
|
],
|
|
|
|
[ # x86-64-v4
|
|
|
|
"avx512f",
|
|
|
|
"avx512bw",
|
|
|
|
"avx512cd",
|
|
|
|
"avx512dq",
|
|
|
|
"avx512vl",
|
|
|
|
],
|
|
|
|
]
|
|
|
|
|
|
|
|
# Assumes externally launched process such as
|
|
|
|
#
|
|
|
|
# qemu-system-x86_64 -qmp unix:/tmp/qmp,server,nowait -display none -accel kvm
|
|
|
|
#
|
|
|
|
# Note different results will be obtained with TCG, as
|
|
|
|
# TCG masks out certain features otherwise present in
|
|
|
|
# the CPU model definitions, as does KVM.
|
|
|
|
|
|
|
|
|
|
|
|
sock = sys.argv[1]
|
2022-01-11 00:28:58 +01:00
|
|
|
shell = QEMUMonitorProtocol(sock)
|
2021-06-07 15:58:42 +02:00
|
|
|
shell.connect()
|
|
|
|
|
2023-10-06 17:41:15 +02:00
|
|
|
models = shell.cmd("query-cpu-definitions")
|
2021-06-07 15:58:42 +02:00
|
|
|
|
|
|
|
# These QMP props don't correspond to CPUID fatures
|
|
|
|
# so ignore them
|
|
|
|
skip = [
|
|
|
|
"family",
|
|
|
|
"min-level",
|
|
|
|
"min-xlevel",
|
|
|
|
"vendor",
|
|
|
|
"model",
|
|
|
|
"model-id",
|
|
|
|
"stepping",
|
|
|
|
]
|
|
|
|
|
|
|
|
names = []
|
|
|
|
|
2023-10-06 17:41:13 +02:00
|
|
|
for model in models:
|
2021-06-07 15:58:42 +02:00
|
|
|
if "alias-of" in model:
|
|
|
|
continue
|
|
|
|
names.append(model["name"])
|
|
|
|
|
|
|
|
models = {}
|
|
|
|
|
|
|
|
for name in sorted(names):
|
2023-10-06 17:41:15 +02:00
|
|
|
cpu = shell.cmd("query-cpu-model-expansion",
|
|
|
|
{ "type": "static",
|
|
|
|
"model": { "name": name }})
|
2021-06-07 15:58:42 +02:00
|
|
|
|
|
|
|
got = {}
|
2023-10-06 17:41:13 +02:00
|
|
|
for (feature, present) in cpu["model"]["props"].items():
|
2021-06-07 15:58:42 +02:00
|
|
|
if present and feature not in skip:
|
|
|
|
got[feature] = True
|
|
|
|
|
|
|
|
if name in ["host", "max", "base"]:
|
|
|
|
continue
|
|
|
|
|
|
|
|
models[name] = {
|
|
|
|
# Dict of all present features in this CPU model
|
|
|
|
"features": got,
|
|
|
|
|
|
|
|
# Whether each x86-64 ABI level is satisfied
|
|
|
|
"levels": [False, False, False, False],
|
|
|
|
|
|
|
|
# Number of extra CPUID features compared to the x86-64 ABI level
|
|
|
|
"distance":[-1, -1, -1, -1],
|
|
|
|
|
|
|
|
# CPUID features present in model, but not in ABI level
|
|
|
|
"delta":[[], [], [], []],
|
|
|
|
|
|
|
|
# CPUID features in ABI level but not present in model
|
|
|
|
"missing": [[], [], [], []],
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Calculate whether the CPU models satisfy each ABI level
|
|
|
|
for name in models.keys():
|
|
|
|
for level in range(len(levels)):
|
|
|
|
got = set(models[name]["features"])
|
|
|
|
want = set(levels[level])
|
|
|
|
missing = want - got
|
|
|
|
match = True
|
|
|
|
if len(missing) > 0:
|
|
|
|
match = False
|
|
|
|
models[name]["levels"][level] = match
|
|
|
|
models[name]["missing"][level] = missing
|
|
|
|
|
|
|
|
# Cache list of CPU models satisfying each ABI level
|
|
|
|
abi_models = [
|
|
|
|
[],
|
|
|
|
[],
|
|
|
|
[],
|
|
|
|
[],
|
|
|
|
]
|
|
|
|
|
|
|
|
for name in models.keys():
|
|
|
|
for level in range(len(levels)):
|
|
|
|
if models[name]["levels"][level]:
|
|
|
|
abi_models[level].append(name)
|
|
|
|
|
|
|
|
|
|
|
|
for level in range(len(abi_models)):
|
|
|
|
# Find the union of features in all CPU models satisfying this ABI
|
|
|
|
allfeatures = {}
|
|
|
|
for name in abi_models[level]:
|
|
|
|
for feat in models[name]["features"]:
|
|
|
|
allfeatures[feat] = True
|
|
|
|
|
|
|
|
# Find the intersection of features in all CPU models satisfying this ABI
|
|
|
|
commonfeatures = []
|
|
|
|
for feat in allfeatures:
|
|
|
|
present = True
|
|
|
|
for name in models.keys():
|
|
|
|
if not models[name]["levels"][level]:
|
|
|
|
continue
|
|
|
|
if feat not in models[name]["features"]:
|
|
|
|
present = False
|
|
|
|
if present:
|
|
|
|
commonfeatures.append(feat)
|
|
|
|
|
|
|
|
# Determine how many extra features are present compared to the lowest
|
|
|
|
# common denominator
|
|
|
|
for name in models.keys():
|
|
|
|
if not models[name]["levels"][level]:
|
|
|
|
continue
|
|
|
|
|
|
|
|
delta = set(models[name]["features"].keys()) - set(commonfeatures)
|
|
|
|
models[name]["distance"][level] = len(delta)
|
|
|
|
models[name]["delta"][level] = delta
|
|
|
|
|
|
|
|
def print_uarch_abi_csv():
|
|
|
|
print("# Automatically generated from '%s'" % __file__)
|
|
|
|
print("Model,baseline,v2,v3,v4")
|
|
|
|
for name in models.keys():
|
|
|
|
print(name, end="")
|
|
|
|
for level in range(len(levels)):
|
|
|
|
if models[name]["levels"][level]:
|
|
|
|
print(",✅", end="")
|
|
|
|
else:
|
|
|
|
print(",", end="")
|
|
|
|
print()
|
|
|
|
|
|
|
|
print_uarch_abi_csv()
|