"""Shared parts of the differential tests: Unicorn reference, operand generators, build and run of harness.c. Adapted from LAS Recomp tools/test_lift.py and tools/test_simd.py @ da35b83 (MIT, Copyright (c) 2026 Peiikai). """ import os import shutil import struct import subprocess import sys from pathlib import Path import capstone import unicorn as uc from unicorn import arm64_const as A ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / 'recomp')) BUF, BUF_SIZE = 0x7200000000, 0x40000 UC_INVALID = (uc.UC_ERR_INSN_INVALID, uc.UC_ERR_EXCEPTION) # Record layouts of struct In / struct Out in harness.c. IN_FMT = '> sz): out |= rand_lane(r, sz) << ((8 << sz) * i) else: out = r.getrandbits(128) if r.random() < 0.15: # upper half zero, like most 64-bit results out &= (1 << 64) - 1 return out def rand_x(r): k = r.random() if k < 0.3: return r.choice([0, 1, 2, 0xFFFFFFFFFFFFFFFF, 0x7FFFFFFFFFFFFFFF, 0x8000000000000000, 0xFFFFFFFF, 0x7FFFFFFF, 0x80000000, 0x100000000, 0xFFFFFFFF80000000, 0x8000000000000001, 0xFFFFFF, 0x1000001, 0x20000000000001, 0xFFFFFFFFFF000001, 63, 64, 31, 32, 0xFF, 0xFFFF]) if k < 0.45: return rand_f32(r) | (r.getrandbits(32) << 32 if r.random() < 0.3 else 0) if k < 0.6: return rand_f64(r) if k < 0.75: return r.getrandbits(r.randint(1, 64)) if k < 0.8: return (-r.getrandbits(r.randint(1, 63))) & 0xFFFFFFFFFFFFFFFF return r.getrandbits(64) def rand_fpcr(r): k = r.random() if k < 0.55: return 0 v = 0 if r.random() < 0.4: v |= 1 << 25 # DN if r.random() < 0.4: v |= 1 << 24 # FZ if r.random() < 0.4: v |= r.randrange(4) << 22 # RMode if r.random() < 0.15: v |= 1 << 26 # AHP return v class Emu: """Unicorn as a Cortex-A57, the Switch's CPU, unless another model is given (LSE atomics need UC_CPU_ARM64_MAX). Images are (base, bytes) pairs mapped at their guest address.""" def __init__(self, images, model=A.UC_CPU_ARM64_A57): mu = uc.Uc(uc.UC_ARCH_ARM64, uc.UC_MODE_ARM) mu.ctl_set_cpu_model(model) mu.mem_map(BUF, BUF_SIZE) for base, data in images: mu.mem_map(base, (len(data) + 0xFFFF) & ~0xFFFF) mu.mem_write(base, bytes(data)) mu.reg_write(A.UC_ARM64_REG_CPACR_EL1, 0x300000) # enable FP/SIMD self.mu = mu self.xregs = [A.UC_ARM64_REG_X0 + i for i in range(29)] + [A.UC_ARM64_REG_X29, A.UC_ARM64_REG_X30] self.qregs = [A.UC_ARM64_REG_Q0 + i for i in range(32)] self.out_regs = self.xregs + [A.UC_ARM64_REG_SP, A.UC_ARM64_REG_NZCV, A.UC_ARM64_REG_PC] + self.qregs def run(self, pc, fpcr, x, sp, nzcv, v, count=1): """Run count instructions from pc. Returns (x[31], sp, nzcv, pc, v[32]).""" mu = self.mu mu.reg_write_batch(list(zip(self.xregs, x[:31])) + list(zip(self.qregs, v)) + [(A.UC_ARM64_REG_SP, sp), (A.UC_ARM64_REG_NZCV, nzcv), (A.UC_ARM64_REG_FPCR, fpcr)]) mu.emu_start(pc, pc + 4 * count + 4, count=count) o = mu.reg_read_batch(self.out_regs) return list(o[:31]), o[31], o[32] & 0xF0000000, o[33], list(o[34:]) def compiler(): cc = os.environ.get('CC') if cc: return cc if os.name == 'nt' and (MSYS_BIN / 'clang.exe').exists(): return str(MSYS_BIN / 'clang.exe') return shutil.which('clang') or 'clang' def build_harness(work, bodies, jobs, cflags): """Compile one C function per test body plus harness.c into work/harness[.exe].""" cc = compiler() env = dict(os.environ) if os.name == 'nt' and MSYS_BIN.exists(): env['PATH'] = str(MSYS_BIN) + os.pathsep + env.get('PATH', '') flags = ['-O1', '-fno-strict-aliasing', '-Wno-unused-label', '-I', str(ROOT / 'runtime')] + cflags.split() n = max(1, min(jobs, len(bodies) // 200 + 1)) per = (len(bodies) + n - 1) // n srcs = [] for j in range(n): path = work / f'tests_{j}.c' with open(path, 'w') as f: f.write('#include "cpu.h"\n') for k in range(j * per, min(len(bodies), (j + 1) * per)): f.write(f'void t{k}(Cpu *c) {{ {bodies[k]} }}\n') srcs.append(path) with open(work / 'table.c', 'w') as f: f.write('#include "cpu.h"\n') f.write(''.join(f'void t{k}(Cpu *c);\n' for k in range(len(bodies)))) f.write('GuestFn tests[] = {' + ','.join(f't{k}' for k in range(len(bodies))) + '};\n') f.write(f'int ntests = {len(bodies)};\n') # fma.c: a --cflags=-march=x86-64-v2 run must exercise the software fma the v2 build links, not the C runtime's. srcs += [work / 'table.c', ROOT / 'tests' / 'diff' / 'harness.c', ROOT / 'runtime' / 'fma.c'] objs, procs = [], [] for s in srcs: o = work / (s.stem + '.o') objs.append(o) procs.append(subprocess.Popen([cc] + flags + ['-c', str(s), '-o', str(o)], env=env)) if any(p.wait() for p in procs): raise SystemExit('compile failed') exe = work / ('harness.exe' if os.name == 'nt' else 'harness') subprocess.check_call([cc, '-fuse-ld=lld', '-o', str(exe)] + [str(o) for o in objs], env=env) return exe def write_cases(path, cases, stubs=False): """cases: (k, fpcr, x[31], sp, nzcv, v[32], memchk). stubs: calls go to harness.c's call stub (rt_stub).""" with open(path, 'wb') as f: for k, fpcr, x, sp, nzcv, v, memchk in cases: f.write(struct.pack(IN_FMT, k, fpcr, *(list(x[:31]) + [0]), sp, nzcv)) for q in v: f.write(q.to_bytes(16, 'little')) f.write(struct.pack(IN_TAIL, int(memchk), int(stubs))) def run_harness(exe, work, buf0, cases, images, stubs=False): """Run the lifted tests. Returns (records, stderr tail or None). images: (base, path) pairs.""" with open(work / 'buf.bin', 'wb') as f: f.write(buf0) write_cases(work / 'cases.bin', cases, stubs) out = work / 'out.bin' args = [str(exe), str(work / 'buf.bin'), str(work / 'cases.bin'), str(out)] for base, path in images: args += [str(path), f'{base:x}'] p = subprocess.run(args, capture_output=True) err = None if p.returncode == 0 else f'exit {p.returncode:#x}: {p.stderr.decode(errors="replace")[-2000:]}' data = out.read_bytes() if out.exists() else b'' recs = [] for i in range(len(data) // OUT_SIZE): rec = data[i * OUT_SIZE:(i + 1) * OUT_SIZE] x = struct.unpack_from('<31Q', rec, 0) sp, nzcv, pc, mem = struct.unpack_from('<4Q', rec, 256) v = [int.from_bytes(rec[288 + 16 * j:304 + 16 * j], 'little') for j in range(32)] recs.append((x, sp, nzcv, pc, mem, v)) return recs, err def diff_state(got, want, check_pc, check_mem, check_nzcv=True): """got: harness record; want: (x, sp, nzcv, pc, v, mem). Returns a list of mismatches.""" hx, hsp, hnz, hpc, hmem, hv = got ex, esp, enz, epc, ev, emem = want d = [f'x{j}: got {hx[j]:x} want {ex[j]:x}' for j in range(31) if hx[j] != ex[j]] if hsp != esp: d.append(f'sp: got {hsp:x} want {esp:x}') if check_nzcv and hnz != enz: d.append(f'nzcv: got {hnz:x} want {enz:x}') if check_pc and hpc != epc: d.append(f'pc: got {hpc:x} want {epc:x}') if check_mem and hmem != emem: d.append('memory differs') d += [f'v{j}: got {hv[j]:032x} want {ev[j]:032x}' for j in range(32) if hv[j] != ev[j]] return d