mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 23:10:33 +02:00
Rewrites most of DisArm64.cpp. Previously missing: test-and-branch, exceptions other than brk, hints and barriers, adc/sbc, conditional compare, extr, literal loads, exclusives, unscaled and FP register offset loads, ldpsw, SIMD structure loads/stores, scalar pairwise, fixed-point conversions, fccmp, most two-reg misc ops, modified immediates, smov/umov/dup from GPR, and the scalar shift forms. Fixed: sp printed as x31, unsigned load offsets sign-extended, ldrsb and friends shown as stores, adrp ignoring the page, double precision 1-source ops shown as single, fcmpe shown as fcmp, smulh/umulh, the bitmask decode for 64-bit elements, and fpcr shown as fpsr. Common aliases are used now (cset, mul, tst, cmn, lsl/asr/ubfx, sxtw...). TestArm64Disasm runs two examples of every emitter function through it. The expected strings were verified with Tools/arm64_disasm_check.py, which compares all 833 instruction words against llvm-mc. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
149 lines
4.8 KiB
Python
149 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
# Cross-checks our ARM64 disassembler (Core/Util/DisArm64.cpp) against LLVM's.
|
|
#
|
|
# Runs the Arm64Disasm unit test with ARM64_DISASM_DUMP set, which makes it print every instruction
|
|
# word along with our disassembly, then disassembles the same words with llvm-mc and compares the two
|
|
# after normalizing away the syntax differences: our compact vector syntax ("fadd.32 q1, q2, q3" for
|
|
# "fadd v1.4s, v2.4s, v3.4s"), absolute branch targets, and a handful of aliases we print differently.
|
|
# What's left over is printed, so an empty report means the two agree on mnemonic, registers and
|
|
# immediates for every instruction in the test.
|
|
#
|
|
# Usage: python Tools/arm64_disasm_check.py [path/to/PPSSPPUnitTest] [--llvm-mc=llvm-mc]
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
|
|
def run_test(exe):
|
|
env = dict(os.environ, ARM64_DISASM_DUMP='1')
|
|
out = subprocess.run([exe, 'Arm64Disasm'], env=env, capture_output=True, text=True).stdout
|
|
rows = []
|
|
for line in out.splitlines():
|
|
if line.startswith('DIS|'):
|
|
_, ln, addr, words, ours, expected, code = line.split('|', 6)
|
|
rows.append((int(ln), int(addr, 16), words.split(), ours.split('; '), code))
|
|
return rows
|
|
|
|
|
|
def llvm_dis(llvm_mc, word):
|
|
w = int(word, 16)
|
|
inp = '0x%02x,0x%02x,0x%02x,0x%02x' % (w & 0xff, (w >> 8) & 0xff, (w >> 16) & 0xff, w >> 24)
|
|
out = subprocess.run([llvm_mc, '--disassemble', '-triple=aarch64', '-mattr=+crc,+v8.2a,+fullfp16'],
|
|
input=inp, capture_output=True, text=True).stdout
|
|
lines = [l.strip() for l in out.splitlines() if l.strip() and not l.strip().startswith('.text')]
|
|
return lines[0].split('//')[0].strip() if lines else 'INVALID'
|
|
|
|
|
|
PCREL = {'b', 'bl', 'cbz', 'cbnz', 'tbz', 'tbnz', 'adr', 'adrp', 'ldr', 'ldrsw', 'prfm'}
|
|
CONDS = {'cs': 'hs', 'cc': 'lo'}
|
|
ALIASES = {'lslv': 'lsl', 'lsrv': 'lsr', 'asrv': 'asr', 'rorv': 'ror', 'not': 'mvn', 'ins': 'mov', 'umov': 'mov'}
|
|
|
|
|
|
def tokens(text):
|
|
text = text.lower()
|
|
# Arrangements and lane sizes: v1.4s, q2.s[3], { v1.b }[15].
|
|
text = re.sub(r'\b([vqdshb]\d+)\.\d*[bhsdq]\b', r'\1', text)
|
|
text = re.sub(r'\}\.[bhsd]\b', '}', text)
|
|
# Our branch targets are 12 bare hex digits.
|
|
toks = re.findall(r'\b[0-9a-f]{12}\b|[a-z][a-z0-9_.]*|-?0x[0-9a-f]+|-?\d+\.\d+|-?\d+', text)
|
|
if not toks:
|
|
return toks
|
|
# Element sizes on our mnemonics: "fadd.32", "xtn.32.64".
|
|
toks[0] = re.sub(r'(\.\d+)+$', '', toks[0])
|
|
out = [toks[0]]
|
|
for t in toks[1:]:
|
|
if re.fullmatch(r'[0-9a-f]{12}', t):
|
|
t = int(t, 16)
|
|
elif re.fullmatch(r'[vqdshb]\d+', t):
|
|
t = 'v' + t[1:]
|
|
elif re.fullmatch(r'-?0x[0-9a-f]+', t):
|
|
t = int(t, 16)
|
|
elif re.fullmatch(r'-?\d+\.\d+', t):
|
|
t = float(t)
|
|
elif re.fullmatch(r'-?\d+', t):
|
|
t = int(t)
|
|
else:
|
|
t = CONDS.get(t, t)
|
|
out.append(t)
|
|
return out
|
|
|
|
|
|
def signed(value, bits):
|
|
value &= (1 << bits) - 1
|
|
return value - (1 << bits) if value >> (bits - 1) else value
|
|
|
|
|
|
def norm_ours(text):
|
|
t = tokens(text)
|
|
if not t:
|
|
return t
|
|
t[0] = ALIASES.get(t[0], t[0])
|
|
m = t[0]
|
|
if m in ('sxtl', 'uxtl', 'sxtl2', 'uxtl2'):
|
|
t = [('s' if m[0] == 's' else 'u') + 'shll' + m[4:]] + t[1:] + [0]
|
|
elif m in ('movz', 'movn') and len(t) == 4:
|
|
bits = 64 if t[1].startswith('x') else 32
|
|
value = t[2] << t[3]
|
|
if m == 'movn':
|
|
value = ~value
|
|
t = ['mov', t[1], signed(value, bits)]
|
|
elif m == 'movk' and len(t) == 4:
|
|
t = ['movk', t[1], t[2]] + (['lsl', t[3]] if t[3] else [])
|
|
elif m == 'mov' and len(t) == 5 and t[3] in ('lsl', 'lsr', 'asr', 'ror'):
|
|
t = ['orr', t[1], 'xzr' if t[1].startswith('x') else 'wzr'] + t[2:]
|
|
return t
|
|
|
|
|
|
def norm_llvm(text, addr):
|
|
t = tokens(text)
|
|
if not t:
|
|
return t
|
|
if t[0] == 'umov':
|
|
t[0] = 'mov'
|
|
m = t[0]
|
|
# PC-relative operands come out as offsets; ours are absolute.
|
|
if (m in PCREL or m.startswith('b.')) and isinstance(t[-1], int) and re.search(r'#-?\d+\s*$', text):
|
|
if m == 'adrp':
|
|
t[-1] = (addr & ~0xfff) + t[-1]
|
|
elif m in ('ldr', 'ldrsw', 'prfm') and '[' in text:
|
|
pass
|
|
else:
|
|
t[-1] = addr + t[-1]
|
|
return t
|
|
|
|
|
|
def main():
|
|
exe = 'build/PPSSPPUnitTest'
|
|
llvm_mc = 'llvm-mc'
|
|
for arg in sys.argv[1:]:
|
|
if arg.startswith('--llvm-mc='):
|
|
llvm_mc = arg.split('=', 1)[1]
|
|
else:
|
|
exe = arg
|
|
rows = run_test(exe)
|
|
if not rows:
|
|
print('No output from the test - is %s built with the Arm64Disasm test?' % exe)
|
|
return 1
|
|
words = [w for r in rows for w in r[2]]
|
|
with ThreadPoolExecutor(16) as ex:
|
|
ref = dict(zip(words, ex.map(lambda w: llvm_dis(llvm_mc, w), words)))
|
|
mismatches = 0
|
|
count = 0
|
|
for line, addr, ws, ours, code in rows:
|
|
for i, w in enumerate(ws):
|
|
count += 1
|
|
ours_text = ours[i] if i < len(ours) else ''
|
|
theirs = ref[w]
|
|
if norm_ours(ours_text) != norm_llvm(theirs, addr + i * 4):
|
|
mismatches += 1
|
|
print('line %d: %s %-40s llvm: %s (%s)' % (line, w, ours_text, theirs, code))
|
|
print('%d of %d instructions differ from llvm-mc' % (mismatches, count))
|
|
return 1 if mismatches else 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|