Files
UniXNU/scripts/check-priming-cache-host.py

179 lines
12 KiB
Python

#!/usr/bin/env python3
"""Qualify real device-priming cache hits while all compilation is forbidden.
Actual Turnip/IR3 plus the mandatory software transport; no shader execution,
physical GPU, or deadline claim. Preserve exact source/driver/cache identities.
"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
ROOT = Path(__file__).resolve().parents[1]
KIT = ROOT / 'drivers/AdrenoMetal'
OUT = ROOT / 'build/priming-cache'
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output', type=Path, required=True)
parser.add_argument('--baseline', type=Path, help='Optional exact preserved pre-fix executable for a before/after refusal control')
parser.add_argument('--cross-build-ios', action='store_true', help='Link/sign the same instrument for iOS; never run or stage hardware')
args = parser.parse_args()
work = args.output.resolve()
def require(value, reason):
if not value:
raise SystemExit(reason)
def sha(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
require(not work.exists(), 'Preserve the existing controls; choose a fresh output directory')
require(shutil.disk_usage(ROOT).free > 5 * 1024**3 + 256 * 1024**2, 'Preserve 5 GiB and bounded allowance')
OUT.mkdir(exist_ok=True)
work.mkdir(parents=True)
loader, program = OUT / 'libOfflinePrimingCacheControl.dylib', OUT / 'priming_cache_host'
bridge = ROOT / 'build/adreno-metal-bridge/host/libadreno_metal_bridge.a'
driver = ROOT / 'build/mesa-tu/src/freedreno/vulkan/libvulkan_freedreno.dylib'
peer = ROOT / 'build/turnip-darwin-host/libhostiokit.dylib'
flags = ['xcrun', 'clang++', '-O1', '-std=c++17', '-Wall', '-Wextra', '-Werror']
subprocess.run([*flags, '-dynamiclib', '-I', ROOT / '.cache/mesa-work/include', KIT / 'bridge/priming_cache_loader.cpp', '-o', loader], check=True, timeout=60)
subprocess.run([*flags, KIT / 'bridge/priming_cache_host.cpp', bridge, '-Wl,-dead_strip', '-Wl,-S',
'-framework', 'Foundation', '-framework', 'CoreFoundation', '-framework', 'IOKit', '-o', program], check=True, timeout=90)
ios_link = None
if args.cross_build_ios:
import plistlib
import struct
target = OUT / 'ios'
target.mkdir(exist_ok=True)
sdk = subprocess.check_output(['xcrun', '--sdk', 'iphoneos', '--show-sdk-path'], text=True, timeout=10).strip()
ios_bridge = ROOT / 'build/adreno-metal-bridge/ios/libadreno_metal_bridge.a'
peer_dir = ROOT / 'build/turnip-ios-compiler-peer'
ios_peer = peer_dir / 'libOfflineTurnipCompilerPeer.dylib'
peer_build = json.loads((peer_dir / 'build-report.json').read_text())
require(sha(ios_peer) == peer_build['outputs'][ios_peer.name]['sha256'], 'Exact own signed software peer')
for name, digest in peer_build['sources'].items():
require(sha(ROOT / name) == digest, 'Exact software peer source: ' + name)
policy = peer_dir / 'compiler-entitlements.plist'
require(plistlib.loads(policy.read_bytes()) == {'com.apple.private.security.no-container': True,
'com.apple.private.bindfs-allow': True}, 'Exact own offline permissions; no no-sandbox')
ios_flags = [*flags, '-target', 'arm64-apple-ios17.0', '-isysroot', sdk, '-fstack-protector-strong']
ios_loader, worker = target / loader.name, target / 'compiler-worker'
subprocess.run([*ios_flags, '-dynamiclib', '-install_name', '@rpath/' + loader.name, '-I', ROOT / '.cache/mesa-work/include',
KIT / 'bridge/priming_cache_loader.cpp', '-o', ios_loader], check=True, timeout=60)
subprocess.run([*ios_flags, KIT / 'bridge/priming_cache_host.cpp', ios_bridge, '-Wl,-dead_strip', '-Wl,-S',
'-Wl,-needed_library,' + str(ios_peer), '-Wl,-rpath,/System/Library/Extensions/OfflineTurnipCompiler',
'-framework', 'Foundation', '-framework', 'CoreFoundation', '-framework', 'IOKit', '-o', worker], check=True, timeout=120)
outputs = {}
for path in [ios_loader, worker]:
command = ['codesign', '--force', '--sign', '-', '--pagesize', '4096', '--identifier', 'org.blu-sharky.offline-priming-cache.' + path.stem]
if path == worker:
command += ['--entitlements', str(policy)]
subprocess.run([*command, str(path)], check=True, timeout=30)
subprocess.run(['codesign', '--verify', '--strict', str(path)], check=True, capture_output=True, timeout=30)
detail = subprocess.run(['codesign', '--display', '--verbose=4', str(path)], capture_output=True, text=True, check=True, timeout=30).stderr
cdhash = re.search(r'^CDHash=([a-f0-9]{40})$', detail, re.M)
raw = path.read_bytes()
require(struct.unpack_from('<3I', raw) == (0xfeedfacf, 0x100000c, 0) and cdhash and re.search(r'^Page size=4096$', detail, re.M),
'Actual own ordinary ARM64 4K CodeDirectory')
cursor, builds, ids = 32, [], []
for _ in range(struct.unpack_from('<I', raw, 16)[0]):
command, size = struct.unpack_from('<2I', raw, cursor)
require(size >= 8 and cursor + size <= len(raw), 'Bounded actual Mach-O')
if command == 0x32:
builds.append(struct.unpack_from('<2I', raw, cursor + 8))
if command == 0x1b:
ids.append(raw[cursor + 8:cursor + 24].hex())
cursor += size
require(builds == [(2, 0x110000)] and len(ids) == 1, 'Actual iOS17 build/UUID')
outputs[path.name] = dict(sha256=sha(path), bytes=len(raw), cdhash=cdhash[1], macho_uuid=ids[0], platform=2, cpu_subtype=0, signature_page_bytes=4096)
ios_link = dict(evidence='cross-link/signature verification only, not iOS execution', outputs=outputs,
bridge_sha256=sha(ios_bridge), peer_sha256=sha(ios_peer), peer_report_sha256=sha(peer_dir / 'build-report.json'))
env = {k: v for k, v in os.environ.items() if not k.startswith(('IR3_', 'FD_MESA_', 'NIR_', 'TU_', 'HOSTSIM_', 'MESA_', 'NMB_TEST_'))}
env.update(DYLD_INSERT_LIBRARIES=str(peer), NMB_TEST_PRIMING_CACHE_LOADER=str(loader))
cache = work / 'control.cache'
pattern = re.compile(r'^PASS: device priming (\S+) pid=(\d+) compute=(\d+)/(\d+)/(\d+)/(\d+)/(\d+) graphics=(\d+)/(\d+)/(\d+)/(\d+)/(\d+) uuid_mutations=(\d+) submissions=(\d+) live=0; software peer, no shader execution$', re.M)
fields = ['pid', 'compute_calls', 'compute_success', 'compute_required', 'compute_valid', 'compute_hits',
'graphics_calls', 'graphics_success', 'graphics_required', 'graphics_valid', 'graphics_hits', 'uuid_mutations', 'submissions']
cases = {}
def case(label, mode, directory):
run = subprocess.run([str(program), str(driver), str(directory), mode], env=env, capture_output=True, text=True, timeout=60)
log = work / (label + '.log')
log.write_text(run.stdout + run.stderr)
row = pattern.search(run.stdout)
require(run.returncode == 0 and row and row[1] == mode, 'Actual priming control failed: ' + label)
data = dict(zip(fields, map(int, row.groups()[1:])))
require(data['submissions'] == 1 and data['compute_calls'] == 1, 'Full priming still submits its barriers; observed non-generated compute shape')
if mode in ('cold-miss', 'uuid-miss'):
require(data['compute_success'] == 0 and data['compute_required'] == 1 and data['graphics_calls'] == 0,
'Actual cold/invalid priming fails instead of silently compiling')
if mode == 'uuid-miss':
require(data['uuid_mutations'] > 0, 'Actual driver cache UUID refusal control')
else:
require(data['compute_success'] == data['compute_valid'] == data['graphics_calls'] == data['graphics_success'] == data['graphics_valid'] == 1,
'Both actual helper creations and their feedback are valid')
if mode == 'consume':
require(data['compute_hits'] == data['graphics_hits'] == 1 and data['compute_required'] == data['graphics_required'] == 0,
'Both helpers truly hit with compilation forbidden')
data['log_sha256'] = sha(log)
cases[label] = data
print(row[0], flush=True)
case('cold-miss', 'cold-miss', cache)
case('produce', 'produce', cache)
records = [p for p in cache.rglob('*') if p.is_file()]
require(len(records) == 1 and records[0].stat().st_size < 16 * 1024**2, 'Exactly one bounded native priming record')
record = records[0]
original = record.read_bytes()
require(original.startswith(b'M2VPPLC\0') and hashlib.sha256(original[:-32]).digest() == original[-32:], 'Actual driver record and checksum')
case('consume', 'consume', cache)
case('uuid-miss', 'uuid-miss', cache)
require(record.read_bytes() == original, 'Warm and UUID controls preserve producer record bytes')
for label in ['truncated-miss', 'checksum-miss']:
directory = work / (label + '.cache')
shutil.copytree(cache, directory)
bad = directory / record.relative_to(cache)
if label == 'truncated-miss':
bad.write_bytes(original[:20])
else:
data = bytearray(original);data[-33] ^= 1;bad.write_bytes(data)
case(label, 'cold-miss', directory)
require(len({v['pid'] for v in cases.values()}) == len(cases), 'Independent cold processes for all controls')
baseline_evidence = None
if args.baseline:
baseline = args.baseline.resolve()
baseline_report = ROOT / 'build/ui-shader-coverage/turnip-graphics/committed-qualified/checkpoint.json'
old = json.loads(baseline_report.read_text())
require(baseline.is_file() and sha(baseline) == old['process']['sha256'], 'Exact previously qualified executable with uncached priming')
old_env = dict(env, NMB_TEST_GRAPHICS_CACHE_LOADER=str(loader), NMB_TEST_FORCE_PRIME_STRICT='1')
run = subprocess.run([str(baseline), str(driver), str(work / 'control'), 'consume'], env=old_env, capture_output=True, text=True, timeout=60)
log = work / 'baseline.log'
log.write_text(run.stdout + run.stderr)
require(run.returncode != 0 and 'device: Vulkan(VkResultCode(1000297000))' in log.read_text(),
'Old executable must still fail during device open, using this exact populated cache directory')
require(record.read_bytes() == original, 'The old refusal also preserves all producer record bytes')
baseline_evidence = dict(process_sha256=sha(baseline), log_sha256=sha(log), exit_code=run.returncode)
patches = KIT / 'bridge/patches'
report = dict(evidence='real host Turnip compiler, mandatory software peer; no shader execution', checker_sha256=sha(Path(__file__)),
sources_sha256={str(p.relative_to(ROOT)): sha(p) for p in [KIT / 'bridge/priming_cache_host.cpp', KIT / 'bridge/priming_cache_loader.cpp', KIT / 'bridge/graphics_cache_loader.cpp']},
runtime_base_revision=subprocess.check_output(['git', '-C', str(ROOT / '.cache/gpu-reference/metal2vulkan'), 'rev-parse', 'HEAD'], text=True).strip(),
runtime_patches_sha256={p.name: sha(p) for p in sorted(patches.glob('*.patch'))}, host_bridge_sha256=sha(bridge),
driver_sha256=sha(driver), peer_sha256=sha(peer), ios_cross_link=ios_link, process_sha256=sha(program), loader_sha256=sha(loader),
cache_record_path=str(record.relative_to(cache)), cache_record_sha256=sha(record), cache_record_bytes=len(original),
driver_cache_uuid=original[20:36].hex(), cases=cases, baseline=baseline_evidence,
exit_guard_priming_preserved=True, generated_command_priming_qualified=False, ios_execution=False, shader_execution=False,
physical_device_tested=False, active_native_adreno_4k=False, complete_ui_variant_coverage=False, hardware_ready=False, stable_120_fps_qualified=False)
(work / 'checkpoint.json').write_text(json.dumps(report, indent=2) + '\n')
(OUT / 'checkpoint.json').write_text(json.dumps(report, indent=2) + '\n')
print('PASS: both priming helpers hit with compilation forbidden; invalid data refuses and leaves no live software objects')
if baseline_evidence:
print('PASS: preserved pre-fix executable still fails in device open with the same populated cache')