2
0

cpu-x86-uarch-abi.py 4.8 KB

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