using System.Buffers.Binary; using System.Diagnostics; using System.Security.Cryptography; using System.Text; namespace XFESystem.DriverOracle; internal sealed unsafe class HostExecution { public bool ContextCreated { get; private set; } public bool KernelSubmitted { get; private set; } public bool SynchronizationPassed { get; private set; } public bool ResultAndGuardsPassed { get; private set; } public bool CleanupPassed { get; private set; } public bool ContextDestroyed { get; private set; } public bool OriginalContextRestored { get; private set; } public bool ActualHostExecutionPassed { get; private set; } public long DeviceAllocationBytes { get; private set; } public string? Error { get; private set; } public string? CubinSha256 { get; private set; } public int CubinBytes { get; private set; } public string? CubinElfFlags { get; private set; } public uint? CubinSm { get; private set; } public uint Seed { get; private set; } public object? Verification { get; private set; } public double ExecutionMilliseconds { get; private set; } public List Allocations { get; } = []; public uint GridBlocks => (VectorCase.Count + VectorCase.BlockThreads - 1) / VectorCase.BlockThreads; public int ThreadsPerBlock => VectorCase.BlockThreads; public string JitTarget => "CU_JIT_TARGET=120 / sm_120"; public string Kernel => "XfeVectorAdd: unsigned 32-bit addition modulo 2^32"; public void Run(CudaDriver driver, CudaDevice device, byte[] ptx, string output) { nint context = 0, prior = 0, module = 0, link = 0; var owned = new List(); bool moduleOwned = false, linkOwned = false; byte[] info = new byte[8192], errors = new byte[8192]; var timer = Stopwatch.StartNew(); try { driver.Check("cuCtxGetCurrent(before)", driver.Api("cuCtxGetCurrent")(out prior)); if (prior != 0) throw new InvalidOperationException("The dedicated oracle process unexpectedly already owns a CUDA context."); driver.Check("cuCtxCreate_v2", driver.Api("cuCtxCreate_v2")(out context, 4 /* CU_CTX_SCHED_BLOCKING_SYNC */, device.Handle)); ContextCreated = true; if (context == 0) throw new InvalidOperationException("CUDA returned a null owned context."); byte[] terminated = new byte[ptx.Length + 1]; ptx.CopyTo(terminated, 0); byte[] cubin; fixed (byte* infoPointer = info, errorPointer = errors, ptxPointer = terminated) { // Values of these CUDA ABI enums are fixed by the public Driver // API: INFO_LOG_BUFFER=3, INFO_SIZE=4, ERROR_BUFFER=5, // ERROR_SIZE=6, TARGET=9, CU_TARGET_COMPUTE_120=120. int* options = stackalloc int[5] { 3, 4, 5, 6, 9 }; nint* values = stackalloc nint[5] { (nint)infoPointer, info.Length, (nint)errorPointer, errors.Length, 120 }; driver.Check("cuLinkCreate_v2", driver.Api("cuLinkCreate_v2")(5, options, values, out link)); linkOwned = true; if (link == 0) throw new InvalidOperationException("CUDA returned a null linker state."); driver.Check("cuLinkAddData_v2(PTX)", driver.Api("cuLinkAddData_v2")(link, 1 /* CU_JIT_INPUT_PTX */, ptxPointer, (nuint)terminated.Length, "XfeVectorAdd.ptx", 0, null, null)); driver.Check("cuLinkComplete", driver.Api("cuLinkComplete")(link, out nint image, out nuint length)); if (image == 0 || length < 64 || length > 2 * 1024 * 1024) throw new InvalidOperationException("The generated CUDA image exceeds its bounded ELF geometry."); cubin = new ReadOnlySpan((void*)image, (int)length).ToArray(); } CubinBytes = cubin.Length; CubinSha256 = Program.Hash(cubin); if (!cubin.AsSpan(0, 7).SequenceEqual(new byte[] { 0x7F, (byte)'E', (byte)'L', (byte)'F', 2, 1, 1 }) || BinaryPrimitives.ReadUInt16LittleEndian(cubin.AsSpan(18)) != 190 /* EM_CUDA */) throw new InvalidOperationException("The real Driver JIT did not return a little-endian CUDA ELF64 image."); uint flags = BinaryPrimitives.ReadUInt32LittleEndian(cubin.AsSpan(48)); // Keep ELF flags raw: CUDA ELF ABI revisions use different bit // layouts. The actual loaded function's Driver API attribute below // is the architecture gate, rather than a guessed mask. CubinElfFlags = "0x" + flags.ToString("x8"); Program.Save(output, "XfeVectorAdd-sm120.cubin", cubin); driver.Check("cuLinkDestroy", driver.Api("cuLinkDestroy")(link)); linkOwned = false; fixed (byte* image = cubin) driver.Check("cuModuleLoadData", driver.Api("cuModuleLoadData")(out module, image)); moduleOwned = true; driver.Check("cuModuleGetFunction", driver.Api("cuModuleGetFunction")(out nint function, module, "XfeVectorAdd")); if (module == 0 || function == 0) throw new InvalidOperationException("CUDA returned a null module/function."); driver.Check("cuFuncGetAttribute(BINARY_VERSION)", driver.Api("cuFuncGetAttribute")(out int binaryVersion, 6 /* CU_FUNC_ATTRIBUTE_BINARY_VERSION */, function)); CubinSm = checked((uint)binaryVersion); if (CubinSm != 120) throw new InvalidOperationException("The real loaded function is not SM120."); Seed = BinaryPrimitives.ReadUInt32LittleEndian(RandomNumberGenerator.GetBytes(4)); var vector = new VectorCase(Seed); ulong a = Allocate(vector.A.Length), b = Allocate(vector.B.Length), guarded = Allocate(vector.Initial.Length); DeviceAllocationBytes = vector.DeviceBytes; Upload(a, vector.A); Upload(b, vector.B); Upload(guarded, vector.Initial); ulong target = checked(guarded + VectorCase.GuardWords * sizeof(uint)); uint count = VectorCase.Count; void** parameters = stackalloc void*[4] { &a, &b, &target, &count }; driver.Check("cuLaunchKernel", driver.Api("cuLaunchKernel")(function, GridBlocks, 1, 1, VectorCase.BlockThreads, 1, 1, 0, 0, parameters, null)); KernelSubmitted = true; driver.Check("cuCtxSynchronize", () => driver.Api("cuCtxSynchronize")()); SynchronizationPassed = true; uint[] actual = new uint[vector.Initial.Length]; fixed (uint* actualPointer = actual) driver.Check("cuMemcpyDtoH_v2", driver.Api("cuMemcpyDtoH_v2")(actualPointer, guarded, checked((nuint)actual.Length * sizeof(uint)))); Verification = vector.Verify(actual); ResultAndGuardsPassed = true; ulong Allocate(int words) { nuint bytes = checked((nuint)words * sizeof(uint)); if ((ulong)bytes + sizes.Values.Aggregate(0UL, (total, value) => checked(total + value)) >= (ulong)VectorCase.MaximumDeviceBytes) throw new InvalidOperationException("The owned allocation budget would be exceeded."); driver.Check("cuMemAlloc_v2", driver.Api("cuMemAlloc_v2")(out ulong address, bytes)); if (address == 0) throw new InvalidOperationException("CUDA returned a null device allocation."); owned.Add(address); sizes.Add(address, (ulong)bytes); Allocations.Add(new { Address = "0x" + address.ToString("x"), Bytes = (ulong)bytes }); return address; } void Upload(ulong address, uint[] data) { fixed (uint* pointer = data) driver.Check("cuMemcpyHtoD_v2", driver.Api("cuMemcpyHtoD_v2")(address, pointer, checked((nuint)data.Length * sizeof(uint)))); } } catch (Exception error) { Error = error.ToString(); } finally { bool cleanup = true; // These are this context's allocations only. Never reset a device, // primary context, host graphics driver, or somebody else's memory. for (int index = owned.Count - 1; index >= 0; index--) { ulong address = owned[index]; cleanup &= driver.Cleanup("cuMemFree_v2", () => driver.Api("cuMemFree_v2")(address)); } if (moduleOwned) cleanup &= driver.Cleanup("cuModuleUnload", () => driver.Api("cuModuleUnload")(module)); if (linkOwned) cleanup &= driver.Cleanup("cuLinkDestroy(cleanup)", () => driver.Api("cuLinkDestroy")(link)); if (ContextCreated) { ContextDestroyed = driver.Cleanup("cuCtxDestroy_v2", () => driver.Api("cuCtxDestroy_v2")(context)); cleanup &= ContextDestroyed; nint current = -1; bool read = driver.Cleanup("cuCtxGetCurrent(after)", () => driver.Api("cuCtxGetCurrent")(out current)); OriginalContextRestored = read && current == prior; cleanup &= OriginalContextRestored; } CleanupPassed = cleanup; ActualHostExecutionPassed = ContextCreated && KernelSubmitted && SynchronizationPassed && ResultAndGuardsPassed && ContextDestroyed && OriginalContextRestored && cleanup && Error is null; ExecutionMilliseconds = timer.Elapsed.TotalMilliseconds; Program.Save(output, "JitInfo.log", Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(info).TrimEnd('\0'))); Program.Save(output, "JitError.log", Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(errors).TrimEnd('\0'))); } } private readonly Dictionary sizes = []; }