const-gen.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import os
  4. import re
  5. import subprocess
  6. import sys
  7. from collections import defaultdict
  8. from collections import namedtuple
  9. Name = namedtuple('Name', 'name desc')
  10. Device = namedtuple('Device', 'name bus alias desc')
  11. Architecture = namedtuple('Architecture', 'name items default')
  12. TARGETS = [
  13. Name("alpha", "Alpha"),
  14. Name("arm", "ARM (aarch32)"),
  15. Name("aarch64", "ARM64 (aarch64)"),
  16. Name("avr", "AVR"),
  17. Name("cris", "CRIS"),
  18. Name("hppa", "HPPA"),
  19. Name("i386", "i386 (x86)"),
  20. Name("loongarch64", "LoongArch64"),
  21. Name("m68k", "m68k"),
  22. Name("microblaze", "Microblaze"),
  23. Name("microblazeel", "Microblaze (Little Endian)"),
  24. Name("mips", "MIPS"),
  25. Name("mipsel", "MIPS (Little Endian)"),
  26. Name("mips64", "MIPS64"),
  27. Name("mips64el", "MIPS64 (Little Endian)"),
  28. Name("nios2", "NIOS2"),
  29. Name("or1k", "OpenRISC"),
  30. Name("ppc", "PowerPC"),
  31. Name("ppc64", "PowerPC64"),
  32. Name("riscv32", "RISC-V32"),
  33. Name("riscv64", "RISC-V64"),
  34. Name("rx", "RX"),
  35. Name("s390x", "S390x (zSeries)"),
  36. Name("sh4", "SH4"),
  37. Name("sh4eb", "SH4 (Big Endian)"),
  38. Name("sparc", "SPARC"),
  39. Name("sparc64", "SPARC64"),
  40. Name("tricore", "TriCore"),
  41. Name("x86_64", "x86_64"),
  42. Name("xtensa", "Xtensa"),
  43. Name("xtensaeb", "Xtensa (Big Endian)")
  44. ]
  45. DEFAULTS = {
  46. "aarch64": "virt",
  47. "arm": "virt",
  48. "avr": "mega",
  49. "i386": "q35",
  50. "rx": "gdbsim-r5f562n7",
  51. "tricore": "tricore_testboard",
  52. "x86_64": "q35"
  53. }
  54. AUDIO_SCREAMER = Device('screamer', 'macio', '', 'Screamer (Mac99 only)')
  55. AUDIO_PCSPK = Device('pcspk', 'macio', '', 'PC Speaker')
  56. DISPLAY_TCX = Device('tcx', 'none', '', 'Sun TCX')
  57. DISPLAY_CG3 = Device('cg3', 'none', '', 'Sun cgthree')
  58. NETWORK_LANCE = Device('lance', 'none', '', 'Lance (Am7990)')
  59. ADD_DEVICES = {
  60. "ppc": {
  61. "Sound devices": set([
  62. AUDIO_SCREAMER
  63. ])
  64. },
  65. "ppc64": {
  66. "Sound devices": set([
  67. AUDIO_SCREAMER
  68. ])
  69. },
  70. "sparc": {
  71. "Display devices": set([
  72. DISPLAY_TCX,
  73. DISPLAY_CG3
  74. ]),
  75. "Network devices": set([
  76. NETWORK_LANCE
  77. ])
  78. },
  79. "i386": {
  80. "Sound devices": set([
  81. AUDIO_PCSPK
  82. ])
  83. },
  84. "x86_64": {
  85. "Sound devices": set([
  86. AUDIO_PCSPK
  87. ])
  88. },
  89. }
  90. HEADER = '''//
  91. // Copyright © 2022 osy. All rights reserved.
  92. //
  93. // Licensed under the Apache License, Version 2.0 (the "License");
  94. // you may not use this file except in compliance with the License.
  95. // You may obtain a copy of the License at
  96. //
  97. // http://www.apache.org/licenses/LICENSE-2.0
  98. //
  99. // Unless required by applicable law or agreed to in writing, software
  100. // distributed under the License is distributed on an "AS IS" BASIS,
  101. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  102. // See the License for the specific language governing permissions and
  103. // limitations under the License.
  104. //
  105. // !! THIS FILE IS GENERATED FROM const-gen.py, DO NOT MODIFY MANUALLY !!
  106. import Foundation
  107. '''
  108. def parseListing(listing):
  109. output = listing.splitlines()[1:]
  110. result = set()
  111. for line in output:
  112. idx = line.find(' ')
  113. if idx < 0:
  114. break
  115. name = line[0:idx]
  116. description = line[idx:].strip()
  117. result.add(Name(name, '{} ({})'.format(description, name)))
  118. return result
  119. def parseDeviceListing(defaults, listing):
  120. output = listing.splitlines()
  121. group = ''
  122. result = defaultdict(set, defaults)
  123. for line in output:
  124. if not line:
  125. continue
  126. if not line.startswith('name '):
  127. group = line.rstrip(':')
  128. continue
  129. search = re.search('^name "(?P<name>[^"]*)"(?:, bus (?P<bus>[^\s]+))?(?:, alias "(?P<alias>[^"]+)")?(?:, desc "(?P<desc>[^"]+)")?$', line)
  130. name = search.group('name')
  131. desc = search.group('desc')
  132. if not desc:
  133. desc = name
  134. else:
  135. desc = '{} ({})'.format(desc, name)
  136. item = Device(name, search.group('bus'), search.group('alias'), desc)
  137. result[group].add(item)
  138. return result
  139. def parseCpu(listing):
  140. def parseMips(line):
  141. search = re.search('^(?P<arch>\S+)\s+\'(?P<name>.+)\'.*', line)
  142. return Name(search.group('name'), search.group('name'))
  143. def parseSingle(line):
  144. name = line.strip()
  145. return Name(name, name)
  146. def parseSparc(line):
  147. search = re.search('^(?P<arch>\S+)\s+(?P<name>.+)\s+IU\s+(?P<iu>\S+)\s+FPU\s+(?P<fpu>\S+)\s+MMU\s+(?P<mmu>\S+)\s+NWINS\s+(?P<nwins>\d+).*$', line)
  148. return Name(search.group('name'), search.group('name'))
  149. def parseStandard(line):
  150. search = re.search('^(?P<arch>\S+)\s+(?P<name>\S+)\s+(?P<desc>.*)?$', line)
  151. name = search.group('name')
  152. desc = search.group('desc').strip()
  153. desc = ' '.join(desc.split())
  154. if not desc or desc.startswith('(alias'):
  155. desc = name
  156. else:
  157. desc = '{} ({})'.format(desc, name)
  158. return Name(name, desc)
  159. def parseSparcFlags(line):
  160. if line.startswith('Default CPU feature flags') or line.startswith('Available CPU feature flags'):
  161. flags = line.split(':')[1].strip()
  162. return [Name(flag, flag) for flag in flags.split(' ')]
  163. elif line.startswith('Numerical features'):
  164. return []
  165. else:
  166. return None
  167. def parseS390Flags(line):
  168. if line.endswith(':'):
  169. return []
  170. else:
  171. flag = line.split(' ')[0]
  172. return [Name(flag, flag)]
  173. def parseX86Flags(line):
  174. flags = []
  175. for flag in line.split(' '):
  176. if flag:
  177. flags.append(Name(flag, flag))
  178. return flags
  179. output = enumerate(listing.splitlines())
  180. cpus = [Name('default', 'Default')]
  181. flags = []
  182. if next(output, None) == None:
  183. return (cpus, flags)
  184. for (index, line) in output:
  185. if not line:
  186. break
  187. if len(line.strip().split(' ')) == 1:
  188. cpu = parseSingle(line)
  189. elif line.startswith('Sparc'):
  190. cpu = parseSparc(line)
  191. elif line.startswith('MIPS'):
  192. cpu = parseMips(line)
  193. elif parseSparcFlags(line) != None:
  194. flags += parseSparcFlags(line)
  195. continue
  196. else:
  197. cpu = parseStandard(line)
  198. if cpu.name != 'default':
  199. cpus.append(cpu)
  200. header = next(output, None)
  201. if header == None:
  202. return (cpus, flags)
  203. for (index, line) in output:
  204. if header[1] == 'Recognized CPUID flags:':
  205. flags += parseX86Flags(line)
  206. elif header[1] == 'Recognized feature flags:':
  207. flags += parseS390Flags(line)
  208. flags = set(flags) # de-duplicate
  209. return (cpus, flags)
  210. def sortItems(items):
  211. return sorted(items, key=lambda item: item.desc if item.desc else item.name)
  212. def getMachines(target, qemu_path):
  213. output = subprocess.check_output([qemu_path, '-machine', 'help']).decode('utf-8')
  214. return parseListing(output)
  215. def getDefaultMachine(target, machines):
  216. if target in DEFAULTS:
  217. return DEFAULTS[target]
  218. for machine in machines:
  219. if "default" in machine.desc:
  220. return machine.name
  221. return machines[0].name
  222. def getDevices(target, qemu_path):
  223. output = subprocess.check_output([qemu_path, '-device', 'help']).decode('utf-8')
  224. devices = parseDeviceListing(ADD_DEVICES[target.name] if target.name in ADD_DEVICES else {}, output)
  225. return devices
  226. def getCpus(target, qemu_path):
  227. output = subprocess.check_output([qemu_path, '-cpu', 'help']).decode('utf-8')
  228. return parseCpu(output)
  229. def sanitizeName(name):
  230. sanitized = re.sub('[^0-9a-zA-Z]+', '_', name)
  231. if len(sanitized) == 0:
  232. sanitized = '_empty'
  233. if sanitized[0].isdigit():
  234. sanitized = '_' + sanitized
  235. if sanitized in ['default']:
  236. sanitized = '`' + sanitized + '`'
  237. return sanitized
  238. def generateEmptyEnum(name):
  239. output = f'typealias {name} = AnyQEMUConstant\n'
  240. output += f'\n'
  241. return output
  242. def generateEnum(name, values, prettyValues, baseName='QEMUConstant', defaultValue=None):
  243. if len(values) == 0:
  244. return generateEmptyEnum(name)
  245. output = f'enum {name}: String, CaseIterable, {baseName} {{\n'
  246. for value in values:
  247. sanitized = sanitizeName(value)
  248. if sanitized == value:
  249. output += f' case {value}\n'
  250. else:
  251. output += f' case {sanitized} = "{value}"\n'
  252. output += '\n'
  253. if defaultValue:
  254. sanitized = sanitizeName(defaultValue)
  255. output += f' static var `default`: {name} {{\n'
  256. output += f' .{sanitized}\n'
  257. output += f' }}\n'
  258. output += f'\n'
  259. output += f' var prettyValue: String {{\n'
  260. output += f' switch self {{\n'
  261. for value, valuePretty in zip(values, prettyValues):
  262. sanitized = sanitizeName(value)
  263. if value in ['default']:
  264. output += f' case .{sanitized}: return NSLocalizedString("{valuePretty}", comment: "QEMUConstantGenerated")\n'
  265. else:
  266. output += f' case .{sanitized}: return "{valuePretty}"\n'
  267. output += f' }}\n'
  268. output += f' }}\n'
  269. output += f'}}\n'
  270. output += f'\n'
  271. return output
  272. def generateArchitectureAtlas(architectures, types):
  273. output = f'extension QEMUArchitecture {{\n'
  274. for k, v in types.items():
  275. output += f' var {v}: any {k}.Type {{\n'
  276. output += f' switch self {{\n'
  277. for a in architectures:
  278. a = sanitizeName(a)
  279. output += f' case .{a}: return {k}_{a}.self\n'
  280. output += f' }}\n'
  281. output += f' }}\n'
  282. output += f'\n'
  283. output += f'}}\n'
  284. output += f'\n'
  285. return output
  286. def generateEnumForeachArchitecture(name, targetItems, defaults={}):
  287. output = ''
  288. for target in targetItems:
  289. arch = target.name
  290. className = name + '_' + arch
  291. sortedItems = sortItems(target.items)
  292. values = [item.name for item in sortedItems]
  293. prettyValues = [item.desc for item in sortedItems]
  294. default = defaults[arch] if arch in defaults else None
  295. output += generateEnum(className, values, prettyValues, name, default)
  296. return output
  297. def generate(targets, cpus, cpuFlags, machines, displayDevices, networkDevices, soundDevices, serialDevices):
  298. targetKeys = [item.name for item in targets]
  299. output = HEADER
  300. output += generateEnum('QEMUArchitecture', targetKeys, [item.desc for item in targets])
  301. output += generateEnumForeachArchitecture('QEMUCPU', cpus)
  302. output += generateEnumForeachArchitecture('QEMUCPUFlag', cpuFlags)
  303. output += generateEnumForeachArchitecture('QEMUTarget', machines, {machine.name: machine.default for machine in machines})
  304. output += generateEnumForeachArchitecture('QEMUDisplayDevice', displayDevices)
  305. output += generateEnumForeachArchitecture('QEMUNetworkDevice', networkDevices)
  306. output += generateEnumForeachArchitecture('QEMUSoundDevice', soundDevices)
  307. output += generateEnumForeachArchitecture('QEMUSerialDevice', serialDevices)
  308. output += generateArchitectureAtlas(targetKeys, {
  309. 'QEMUCPU': 'cpuType',
  310. 'QEMUCPUFlag': 'cpuFlagType',
  311. 'QEMUTarget': 'targetType',
  312. 'QEMUDisplayDevice': 'displayDeviceType',
  313. 'QEMUNetworkDevice': 'networkDeviceType',
  314. 'QEMUSoundDevice': 'soundDeviceType',
  315. 'QEMUSerialDevice': 'serialDeviceType',
  316. })
  317. return output
  318. def transformDisplayCards(displayCards):
  319. def transform(item):
  320. if item.name.endswith('-gl') or '-gl-' in item.name:
  321. item = Device(item.name, item.bus, item.alias, item.desc + ' (GPU Supported)')
  322. return item
  323. return set(map(transform, displayCards))
  324. def main(argv):
  325. base = argv[1]
  326. allMachines = []
  327. allCpus = []
  328. allCpuFlags = []
  329. allDisplayCards = []
  330. allSoundCards = []
  331. allNetworkCards = []
  332. allSerialCards = []
  333. # parse outputs
  334. for target in TARGETS:
  335. path = '{}/{}-softmmu/qemu-system-{}'.format(base, target.name, target.name)
  336. if not os.path.exists(path):
  337. path = '{}/qemu-system-{}'.format(base, target.name)
  338. if not os.path.exists(path):
  339. raise "Invalid path."
  340. machines = sortItems(getMachines(target, path))
  341. default = getDefaultMachine(target.name, machines)
  342. allMachines.append(Architecture(target.name, machines, default))
  343. devices = getDevices(target, path)
  344. displayCards = transformDisplayCards(devices["Display devices"])
  345. allDisplayCards.append(Architecture(target.name, displayCards, None))
  346. allNetworkCards.append(Architecture(target.name, devices["Network devices"], None))
  347. nonHdaDevices = [device for device in devices["Sound devices"] if device.bus != 'HDA']
  348. allSoundCards.append(Architecture(target.name, nonHdaDevices, None))
  349. serialDevices = [device for device in devices["Input devices"] if 'serial' in device.name]
  350. allSerialCards.append(Architecture(target.name, serialDevices, None))
  351. cpus, flags = getCpus(target, path)
  352. allCpus.append(Architecture(target.name, cpus, 0))
  353. allCpuFlags.append(Architecture(target.name, flags, 0))
  354. # generate constants
  355. print(generate(TARGETS, allCpus, allCpuFlags, allMachines, allDisplayCards, allNetworkCards, allSoundCards, allSerialCards))
  356. if __name__ == "__main__":
  357. main(sys.argv)