#!/usr/bin/env python3 """GPU integration checks for the MicroCUDA runtime; no games or NGX evaluation. usage: check_runtime.py LIBRARY PACK_DIR KNOWN_PTX NATIVE_DIR KNOWN_PTX is the K enc0 module text (a module in the pack with a native dltss_pwin_enc0_layer in NATIVE_DIR). Pack-state failures (switch mismatch, tampered object) run in child processes: the pack is read once. """ import ctypes as c import json import os from pathlib import Path import shutil import struct import subprocess import sys import tempfile ptr, u64, size = c.c_void_p, c.c_uint64, c.c_size_t def api(lib, name, args): f = getattr(lib, name) f.argtypes = args f.restype = c.c_int return f def child(library, ptx): """Prints the cuModuleLoadData result for ptx under the environment it was started with.""" lib = c.CDLL(library) assert api(lib, 'cuInit', [c.c_uint])(0) == 0 context = ptr() assert api(lib, 'cuCtxCreate_v2', [c.POINTER(ptr), c.c_uint, c.c_int])(c.byref(context), 0, 0) == 0 module = ptr() print(api(lib, 'cuModuleLoadData', [c.POINTER(ptr), ptr])(c.byref(module), c.create_string_buffer(Path(ptx).read_bytes()))) def run_child(library, ptx, **env): out = subprocess.run([sys.executable, __file__, '--child', library, ptx], env={**os.environ, **env}, capture_output=True, text=True, check=True) return int(out.stdout.split()[-1]) def main(): if sys.argv[1] == '--child': return child(*sys.argv[2:]) library, pack, ptx, native = sys.argv[1:] os.environ['D4R_MICROCUDA_PACK'] = pack os.environ['D4R_MICROCUDA_NATIVE_DIR'] = native switches = next(l[9:].strip() for l in open(Path(pack) / 'microcuda-pack.txt') if l.startswith('switches')) for key in [k for k in os.environ if k.startswith('D4R_ZLUDA_')]: del os.environ[key] for item in filter(None, switches.split(',')): k, v = item.split('=', 1) os.environ[k] = v checks = [] def expect(label, result, code=0): assert result == code, (label, result, code) checks.append(label) expect('switch-set mismatch refuses the pack', run_child(library, ptx, D4R_ZLUDA_WMMA='0'), 200) with tempfile.TemporaryDirectory(prefix='d4r-microcuda-check-') as directory: shutil.copytree(pack, directory, dirs_exist_ok=True) for co in (Path(directory) / 'modules').glob('*.co'): b = bytearray(co.read_bytes()) b[-1] ^= 1 co.write_bytes(b) expect('tampered pack object rejected', run_child(library, ptx, D4R_MICROCUDA_PACK=directory), 200) expect('missing pack', run_child(library, ptx, D4R_MICROCUDA_PACK='/nonexistent-microcuda'), 500) lib = c.CDLL(library) A = lambda name, args: api(lib, name, args) init = A('cuInit', [c.c_uint]) load = A('cuModuleLoadData', [c.POINTER(ptr), ptr]) unload = A('cuModuleUnload', [ptr]) get = A('cuModuleGetFunction', [c.POINTER(ptr), ptr, c.c_char_p]) launch = A('cuLaunchKernel', [ptr] + [c.c_uint] * 7 + [ptr, c.POINTER(ptr), c.POINTER(ptr)]) text = c.create_string_buffer(Path(ptx).read_bytes()) module, function = ptr(), ptr() expect('module before init', load(c.byref(module), text), 3) expect('invalid init flags', init(1), 1) expect('init', init(0)) proc = A('cuGetProcAddress_v2', [c.c_char_p, c.POINTER(ptr), c.c_int, u64, c.POINTER(c.c_int)]) address, status = ptr(), c.c_int() expect('getProc modern alias', proc(b'cuMemAlloc', c.byref(address), 12080, 0, c.byref(status))) assert address.value == c.cast(lib.cuMemAlloc_v2, ptr).value and status.value == 0 expect('getProc array API', proc(b'cuArrayCreate', c.byref(address), 12080, 0, c.byref(status))) expect('getProc unknown', proc(b'cuGraphLaunch', c.byref(address), 12080, 0, c.byref(status)), 500) assert address.value is None and status.value == 1 expect('getProc perthread flag rejected', proc(b'cuLaunchKernel', c.byref(address), 12080, 2, c.byref(status)), 801) expect('getProc future version rejected', proc(b'cuLaunchKernel', c.byref(address), 13000, 0, c.byref(status)), 801) expect('getProc null output', proc(b'cuInit', None, 12080, 0, None), 1) device = c.c_int() expect('device', A('cuDeviceGet', [c.POINTER(c.c_int), c.c_int])(c.byref(device), 0)) context = ptr() expect('context', A('cuCtxCreate_v2', [c.POINTER(ptr), c.c_uint, c.c_int])(c.byref(context), 0, device.value)) current = ptr() expect('get current', A('cuCtxGetCurrent', [c.POINTER(ptr)])(c.byref(current))) assert current.value == context.value expect('unknown PTX', load(c.byref(module), c.create_string_buffer(b'.version 8.7\n.target sm_89\n.address_size 64\n')), 200) bad = bytearray(Path(ptx).read_bytes()) bad[0] ^= 1 expect('changed PTX', load(c.byref(module), c.create_string_buffer(bytes(bad))), 200) expect('known PTX', load(c.byref(module), text)) expect('unknown function', get(c.byref(function), module, b'unknown'), 500) assert function.value is None expect('native function', get(c.byref(function), module, b'dltss_pwin_enc0_layer')) another = ptr() expect('cached lookup', get(c.byref(another), module, b'dltss_pwin_enc0_layer')) assert another.value == function.value os.environ['D4R_MICROCUDA_NATIVE_DIR'] = '/nonexistent-microcuda' plain_module, plain = ptr(), ptr() expect('second module instance', load(c.byref(plain_module), text)) expect('pack function without native override', get(c.byref(plain), plain_module, b'dltss_pwin_enc0_layer')) assert plain.value != function.value expect('second module unload', unload(plain_module)) os.environ['D4R_MICROCUDA_NATIVE_DIR'] = native args = c.create_string_buffer(176) n = size(176) extra = (ptr * 5)(3, c.addressof(args), 2, c.addressof(n), None) expect('HIP token rejected as CUDA token', launch(function, 1, 1, 1, 32, 1, 1, 0, None, None, extra), 801) unterminated = (ptr * 8)(1, c.addressof(args), 2, c.addressof(n), 1, c.addressof(args), 2, c.addressof(n)) expect('unterminated packed args', launch(function, 1, 1, 1, 32, 1, 1, 0, None, None, unterminated), 1) expect('null function', launch(None, 1, 1, 1, 32, 1, 1, 0, None, None, extra), 400) allocate = A('cuMemAlloc_v2', [c.POINTER(u64), size]) d2h = A('cuMemcpyDtoH_v2', [ptr, u64, size]) source = c.create_string_buffer(bytes(range(256))) result = c.create_string_buffer(256) device_mem = u64() expect('allocation', allocate(c.byref(device_mem), 256)) expect('zero-filled allocation readback', d2h(result, device_mem, 256)) assert result.raw == bytes(256) stream = ptr() expect('stream create', A('cuStreamCreate', [c.POINTER(ptr), c.c_uint])(c.byref(stream), 1)) expect('async H2D', A('cuMemcpyHtoDAsync_v2', [u64, ptr, size, ptr])(device_mem, source, 256, stream)) event = ptr() expect('event create', A('cuEventCreate', [c.POINTER(ptr), c.c_uint])(c.byref(event), 0)) expect('event record', A('cuEventRecord', [ptr, ptr])(event, stream)) expect('event wait', A('cuEventSynchronize', [ptr])(event)) expect('event query ready', A('cuEventQuery', [ptr])(event)) expect('D2H', d2h(result, device_mem, 256)) assert result.raw == source.raw[:256] expect('event destroy', A('cuEventDestroy_v2', [ptr])(event)) expect('stream destroy', A('cuStreamDestroy_v2', [ptr])(stream)) expect('free', A('cuMemFree_v2', [u64])(device_mem)) # arrays: CUDA_ARRAY_DESCRIPTOR {size_t Width, Height; int Format; unsigned NumChannels} array_create = A('cuArrayCreate_v2', [c.POINTER(ptr), ptr]) array_desc = A('cuArrayGetDescriptor_v2', [ptr, ptr]) array_destroy = A('cuArrayDestroy', [ptr]) memcpy2d = A('cuMemcpy2D_v2', [ptr]) unorm = ptr() expect('UNORM8x4 array', array_create(c.byref(unorm), c.create_string_buffer(struct.pack(' array -> host packed = ptr() expect('packed 1010102 array', array_create(c.byref(packed), c.create_string_buffer(struct.pack('