| #!/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.aqmp.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["return"]: |
| 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["return"]["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() |