using System; using System.Collections.Concurrent; using ILGPU; using ILGPU.Runtime; using static PerfectNumbers.Core.Gpu.GpuContextPool; namespace PerfectNumbers.Core.Gpu; public sealed class GpuKernelLease { private static readonly ConcurrentQueue Pool = new(); private Action? _releaseLimiter; private GpuContextLease? _gpu; private KernelContainer? _kernels; private AcceleratorStream? _stream; private GpuKernelLease() { } internal static GpuKernelLease Rent(GpuPrimeWorkLimiter.Lease limiter, GpuContextLease gpu, KernelContainer kernels) { if (!Pool.TryDequeue(out var lease)) { lease = new GpuKernelLease(); } lease._releaseLimiter = limiter.Dispose; lease._gpu = gpu; lease._kernels = kernels; lease._stream = null; return lease; } public Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released"); public AcceleratorStream Stream { get { if (_stream is { } stream) { return stream; } // It feels that we can combine these into 1 return statement, but that increases the max stack frame size to 3 from 2 stream = Accelerator.CreateStream(); _stream = stream; return stream; } } public Action, ArrayView> OrderKernel { get { var accel = Accelerator; // avoid capturing 'this' in lambda return KernelContainer.InitOnce(ref _kernels!.Order, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView>(OrderKernels.OrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate, ArrayView>>(); }); } } public Action, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> Pow2ModKernel { get { var accel = Accelerator; return KernelContainer.InitOnce(ref _kernels!.Pow2Mod, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>(Pow2ModKernels.Pow2ModKernelScan); var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>>(); }); } } public Action, ArrayView1D> IncrementalKernel { get { var accel = Accelerator; return KernelContainer.InitOnce(ref _kernels!.Incremental, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalKernels.IncrementalKernelScan); var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate, ArrayView1D>>(); }); } } public Action, ArrayView1D> IncrementalOrderKernel { get { var accel = Accelerator; return KernelContainer.InitOnce(ref _kernels!.IncrementalOrder, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalKernels.IncrementalOrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate, ArrayView1D>>(); }); } } public Action, ArrayView1D> Pow2ModOrderKernel { get { var accel = Accelerator; return KernelContainer.InitOnce(ref _kernels!.Pow2ModOrder, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(Pow2ModKernels.Pow2ModOrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate, ArrayView1D>>(); }); } } public Action, ArrayView1D, int, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> SmallPrimeFactorKernel { get { var accel = Accelerator; return KernelContainer.InitOnce(ref _kernels!.SmallPrimeFactor, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D, int, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>(SmallPrimeFactorKernels.SmallPrimeFactorKernelScan); var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate, ArrayView1D, int, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>>(); }); } } public void Dispose() { _stream?.Dispose(); _stream = null; _gpu?.Dispose(); _gpu = null; _releaseLimiter?.Invoke(); _releaseLimiter = null; _kernels = null; Pool.Enqueue(this); } }