166 lines
11 KiB
Python
166 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""Check actual AIR/IR3 parameter images through native A740 direct PM4 encoding."""
|
|
import ctypes as C
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
import xml.etree.ElementTree as ET
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / 'kits/XnuPrelink'))
|
|
from native_prelink import macho
|
|
OUT = ROOT / 'build/adreno-constant-commands'
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
KIT = ROOT / 'kits/AdrenoPM4'
|
|
MESA = ROOT / '.cache/mesa-work'
|
|
REV = '85fda3b36405bcaea8bffc64aa4f2651dc90770d'
|
|
|
|
|
|
def run(cmd, **kw):
|
|
return subprocess.check_output([str(v) for v in cmd], timeout=30, **kw)
|
|
|
|
|
|
def sha(raw):
|
|
return hashlib.sha256(raw).hexdigest()
|
|
|
|
|
|
if run(['git', '-C', MESA, 'rev-parse', 'HEAD'], text=True).strip() != REV:
|
|
raise SystemExit('Pinned Mesa revision differs')
|
|
paths = ['src/gallium/drivers/freedreno/a6xx/fd6_const.cc',
|
|
'src/gallium/drivers/freedreno/a6xx/fd6_emit.h',
|
|
'src/gallium/drivers/freedreno/ir3/ir3_const.h',
|
|
'src/freedreno/registers/adreno/adreno_pm4.xml',
|
|
'src/freedreno/common/freedreno_pm4.h']
|
|
documents = {p: run(['git', '-C', MESA, 'show', REV + ':' + p]) for p in paths}
|
|
manifest = json.loads((ROOT / 'scripts/data/adreno-constant-inputs.json').read_text())
|
|
if manifest['mesa_revision'] != REV or manifest['files'] != {p: sha(raw) for p, raw in documents.items()}:
|
|
raise SystemExit('Exact reference source hashes differ')
|
|
for p in paths[3:]:
|
|
if (MESA / p).read_bytes() != documents[p]:
|
|
raise SystemExit('Actual encoder/XML reference working source differs')
|
|
xml = ET.fromstring(documents[paths[3]])
|
|
ns = {'r': xml.tag.split('}')[0].lstrip('{')}
|
|
domain = xml.find("r:domain[@name='CP_LOAD_STATE6']", ns)
|
|
fields = {v.attrib['name']: (int(v.attrib['low']), int(v.attrib['high']))
|
|
for v in domain.find("r:reg32[@name='0']", ns)}
|
|
enums = {v.attrib['name']: int(v.attrib['value'], 0) for enum in xml.findall('.//r:enum', ns) for v in enum.findall('r:value', ns) if 'value' in v.attrib}
|
|
# Build the unit oracle against the actual Mesa-generated XML/header functions.
|
|
test = OUT / 'test-constants'
|
|
run(['xcrun', 'clang', '-std=c11', '-O2', '-Wall', '-Wextra', '-Werror',
|
|
'-fsanitize=address,undefined', '-DMESA_REFERENCE',
|
|
'-I', ROOT / 'build/mesa-ir3/src/freedreno/registers/adreno',
|
|
'-I', MESA / 'src/freedreno/common', KIT / 'AdrenoPM4.c', KIT / 'AdrenoConstants.c',
|
|
KIT / 'test_constants.c', '-o', test])
|
|
unit = run([test], text=True).strip()
|
|
if not unit.startswith('PASS: 9202 direct upload cases,'):
|
|
raise SystemExit('Native unit inventory changed')
|
|
print(unit)
|
|
objects = []
|
|
native_imports = {}
|
|
for name in ('AdrenoPM4', 'AdrenoConstants'):
|
|
obj = OUT / (name + '.ios.o')
|
|
run(['xcrun', 'clang', '-target', 'arm64-apple-ios17.0', '-std=c11', '-O2', '-Wall', '-Wextra', '-Werror',
|
|
'-Wno-incompatible-sysroot', '-ffreestanding', '-fstack-protector-strong', '-c', KIT / (name + '.c'), '-o', obj])
|
|
parsed = macho(obj.read_bytes())
|
|
version = next(at for cmd, at, _ in parsed['commands'] if cmd == 0x32)
|
|
if parsed['kind'] != 1 or struct.unpack_from('<II', obj.read_bytes(), version+8) != (2, 0x110000):
|
|
raise SystemExit('Ordinary ARM64/iOS17 object required')
|
|
imports = run(['xcrun', 'nm', '-u', obj], text=True).split()
|
|
native_imports[name] = imports
|
|
checked = [v for v in imports if v not in ('___stack_chk_fail', '___stack_chk_guard')]
|
|
if checked != ([] if name == 'AdrenoPM4' else ['_AdrenoPM4Type7']):
|
|
raise SystemExit('Unexpected runtime import: ' + repr(imports))
|
|
objects.append(obj)
|
|
archive = OUT / 'libAdrenoConstants.ios.a'
|
|
archive.unlink(missing_ok=True)
|
|
run(['xcrun', 'ar', 'rcs', archive, *objects], env=dict(os.environ, ZERO_AR_DATE='1'))
|
|
cpp = OUT / 'client.cpp'
|
|
cpp.write_text('#include "AdrenoConstants.h"\nextern "C" AdrenoConstantResult client(const AdrenoConstantConfig *c,const AdrenoConstantSpan *s,const unsigned char *b,unsigned *o) {return AdrenoA740Constants(c,s,1,b,16,o,8);}\n')
|
|
cpp_obj = OUT / 'client.ios.o'
|
|
run(['xcrun', 'clang++', '-target', 'arm64-apple-ios17.0', '-std=c++17', '-O2', '-Wall', '-Wextra', '-Werror',
|
|
'-Wno-incompatible-sysroot', '-ffreestanding', '-fno-exceptions', '-fno-rtti', '-I', KIT, '-c', cpp, '-o', cpp_obj])
|
|
if run(['xcrun', 'nm', '-u', cpp_obj], text=True).split() != ['_AdrenoA740Constants']:
|
|
raise SystemExit('Native C++ linkage differs')
|
|
shared = OUT / 'Constants.dylib'
|
|
run(['xcrun', 'clang', '-std=c11', '-O2', '-Wall', '-Wextra', '-Werror', '-dynamiclib',
|
|
KIT / 'AdrenoPM4.c', KIT / 'AdrenoConstants.c', ROOT / 'kits/AirNir/ConstantUploads.c', '-o', shared])
|
|
|
|
class Read(C.Structure): _fields_ = [('binding', C.c_uint32), ('offset', C.c_uint32), ('bytes', C.c_uint32)]
|
|
class Range(C.Structure): _fields_ = [('binding', C.c_uint32), ('source_begin', C.c_uint32), ('source_end', C.c_uint32), ('destination_begin', C.c_uint32)]
|
|
class Binding(C.Structure): _fields_ = [('data', C.c_void_p), ('bytes', C.c_size_t), ('minimum_bytes', C.c_size_t)]
|
|
class Layout(C.Structure):
|
|
_fields_ = [('constant_bytes', C.c_uint32), ('upload_alignment', C.c_uint32), ('ubo_begin', C.c_uint32), ('ubo_bytes', C.c_uint32),
|
|
('ranges', C.POINTER(Range)), ('range_count', C.c_size_t), ('reads', C.POINTER(Read)), ('read_count', C.c_size_t),
|
|
('immediate_begin', C.c_uint32), ('immediate_words', C.c_uint32), ('immediates', C.POINTER(C.c_uint32))]
|
|
class Result(C.Structure): _fields_ = [('status', C.c_uint32), ('a', C.c_uint32), ('b', C.c_uint32)]
|
|
class Config(C.Structure): _fields_ = [('chip', C.c_uint64), ('stage', C.c_uint32), ('bytes', C.c_uint32)]
|
|
class Span(C.Structure): _fields_ = [('source', C.c_uint32), ('destination', C.c_uint32), ('bytes', C.c_uint32)]
|
|
lib = C.CDLL(str(shared))
|
|
pack = lib.air_pack_constants
|
|
pack.argtypes = [C.POINTER(Layout), C.POINTER(Binding), C.c_void_p, C.c_size_t]; pack.restype = Result
|
|
emit = lib.AdrenoA740Constants
|
|
emit.argtypes = [C.POINTER(Config), C.POINTER(Span), C.c_size_t, C.c_void_p, C.c_size_t, C.POINTER(C.c_uint32), C.c_size_t]; emit.restype = Result
|
|
previous = json.loads((ROOT / 'build/air-nir/constant-uploads/report.json').read_text())['original_layouts']
|
|
results = []
|
|
for old in previous:
|
|
library, index = old['library'], old['index']
|
|
prefix = ROOT / ('build/air-nir/resources' if library == 'QuartzCore' and index != 111 else 'build/air-nir') / f'{library}-{index}'
|
|
if sha(prefix.with_suffix('.ir3.json').read_bytes()) != old['abi_sha256'] or sha(prefix.with_suffix('.bin').read_bytes()) != old['binary_sha256']:
|
|
raise SystemExit('Actual compiler/layout identity differs')
|
|
abi = json.loads(prefix.with_suffix('.ir3.json').read_text())
|
|
interface = json.loads(prefix.with_suffix('.interface.json').read_text())
|
|
if abi['chip_id'] != '0x43050a01' or abi['stage'] != 'vertex' or abi['load_shader_consts_via_preamble']:
|
|
raise SystemExit('Unsupported actual variant')
|
|
reads = (Read * len(abi['source_reads']))(*(Read(r['binding'], r['offset'], r['bytes']) for r in abi['source_reads']))
|
|
ranges = (Range * len(abi['ubo_ranges']))(*(Range(r['binding'], r['source_begin'], r['source_end'], r['destination_begin']) for r in abi['ubo_ranges']))
|
|
bindings = (Binding * 16)(); backing = []
|
|
for item in interface['inputs']:
|
|
if item['kind'] == 'air.buffer':
|
|
size, slot = item['minimum_bytes'], item['index']
|
|
data = (C.c_uint8 * size)(*(i*7+slot*29 & 255 for i in range(size)))
|
|
backing.append(data); bindings[slot] = Binding(C.addressof(data), size, size)
|
|
imm = abi['immediates']; values = (C.c_uint32 * len(imm['values']))(*imm['values'])
|
|
ubo = next(v for v in abi['allocations'] if v['name'] == 'ubo_ranges')
|
|
layout = Layout(abi['constant_bytes'], abi['upload_alignment_bytes'], ubo['begin'], ubo['bytes'], ranges, len(ranges), reads, len(reads), imm['destination_begin'], len(values), values)
|
|
image = (C.c_uint8 * abi['constant_bytes'])(*([0x6b] * abi['constant_bytes']))
|
|
packed = pack(C.byref(layout), bindings, image, len(image))
|
|
if packed.status or packed.a != old['written_bytes'] or packed.b != old['padded_bytes']:
|
|
raise SystemExit('Actual prepared parameters differ')
|
|
intervals = [(r['destination_begin'], r['source_end'] - r['source_begin']) for r in abi['ubo_ranges']]
|
|
if imm['padded_words']: intervals.append((imm['destination_begin'], imm['padded_words']*4))
|
|
spans = (Span * len(intervals))(*(Span(offset, offset, size) for offset, size in intervals))
|
|
cfg = Config(0x43050a01, 0, len(image)); output = (C.c_uint32 * 2304)(*([0x6b6b6b6b] * 2304))
|
|
r = emit(C.byref(cfg), spans, len(spans), image, len(image), output, len(output))
|
|
if r.status or r.b != len(intervals): raise SystemExit('Actual parameter intervals refused')
|
|
fallback = (C.c_uint32 * 2304)(*([0x6b6b6b6b] * 2304))
|
|
cfg.chip = 0xffff43050a01
|
|
alternate = emit(C.byref(cfg), spans, len(spans), image, len(image), fallback, len(fallback))
|
|
if alternate.status or alternate.a != r.a or alternate.b != r.b or bytes(fallback) != bytes(output):
|
|
raise SystemExit('Observed complete chip identity changed actual command encoding')
|
|
encoded = bytes(output)[:r.a*4]; restored = bytearray(b'\x6b' * len(image)); at = 0
|
|
for offset, size in intervals:
|
|
header, state, low, high = struct.unpack_from('<4I', encoded, at)
|
|
opcode, payload = header >> 16 & 127, header & 0x3fff
|
|
if header >> 28 != 7 or opcode != enums['CP_LOAD_STATE6_GEOM'] or payload != 3 + size//4 or low or high:
|
|
raise SystemExit('Unexpected command/opcode/address')
|
|
for field, expected in [('DST_OFF', offset//16), ('STATE_TYPE', enums['ST6_CONSTANTS']), ('STATE_SRC', enums['SS6_DIRECT']), ('STATE_BLOCK', enums['SB6_VS_SHADER']), ('NUM_UNIT', size//16)]:
|
|
lo, hi = fields[field]
|
|
if (state >> lo) & ((1 << (hi-lo+1))-1) != expected: raise SystemExit('XML field mismatch: '+field)
|
|
restored[offset:offset+size] = encoded[at+16:at+16+size]; at += (1+payload)*4
|
|
if restored != bytes(image) or at != len(encoded) or bytes(output)[at:] != b'\x6b'*(len(output)*4-at):
|
|
raise SystemExit('Payload, undeclared allocation or output tail differs')
|
|
(OUT / f'{library}-{index}.pm4').write_bytes(encoded)
|
|
results.append(dict(library=library, index=index, abi_sha256=old['abi_sha256'], binary_sha256=old['binary_sha256'],
|
|
command_sha256=sha(encoded), command_words=r.a, packets=r.b, transferred_bytes=packed.a,
|
|
only_explicit_intervals=True, indirect_gpu_addresses=False, full_chip_identity_profiles=2, gpu_execution=False))
|
|
report = dict(mesa_revision=REV, source_documents=manifest['files'], unit=unit, layouts=results,
|
|
source_sha256={str(p.relative_to(ROOT)): sha(p.read_bytes()) for p in (KIT/'AdrenoPM4.c', KIT/'AdrenoConstants.c', KIT/'AdrenoConstants.h', ROOT/'kits/AirNir/ConstantUploads.c')},
|
|
native_imports=native_imports, native_objects={p.name: sha(p.read_bytes()) for p in objects}, native_archive_sha256=sha(archive.read_bytes()),
|
|
native_cpp_sha256=sha(cpp_obj.read_bytes()), ordinary_arm64_ios17=True,
|
|
actual_submission=False, gpu_execution=False, metal_device=False)
|
|
(OUT / 'report.json').write_text(json.dumps(report, indent=2)+'\n')
|
|
print('PASS: 14 actual IR3 parameter images through PM4/XML decode, no unowned intervals uploaded; native ARM64/iOS17 C/C++ archive')
|