|  | #!/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. | 
|  | # | 
|  |  | 
|  | from qemu.qmp.legacy import QEMUMonitorProtocol | 
|  | import sys | 
|  |  | 
|  | if len(sys.argv) != 2: | 
|  | 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] | 
|  | shell = QEMUMonitorProtocol(sock) | 
|  | shell.connect() | 
|  |  | 
|  | models = shell.cmd("query-cpu-definitions") | 
|  |  | 
|  | # These QMP props don't correspond to CPUID fatures | 
|  | # so ignore them | 
|  | skip = [ | 
|  | "family", | 
|  | "min-level", | 
|  | "min-xlevel", | 
|  | "vendor", | 
|  | "model", | 
|  | "model-id", | 
|  | "stepping", | 
|  | ] | 
|  |  | 
|  | names = [] | 
|  |  | 
|  | for model in models: | 
|  | if "alias-of" in model: | 
|  | continue | 
|  | names.append(model["name"]) | 
|  |  | 
|  | models = {} | 
|  |  | 
|  | for name in sorted(names): | 
|  | cpu = shell.cmd("query-cpu-model-expansion", | 
|  | { "type": "static", | 
|  | "model": { "name": name }}) | 
|  |  | 
|  | got = {} | 
|  | for (feature, present) in cpu["model"]["props"].items(): | 
|  | 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() |