using System.Buffers; using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using PerfectNumbers.Core; using ILGPU; using ILGPU.Runtime; namespace PerfectNumbers.Core.Gpu; /// /// Scans Mersenne divisors on the GPU for prime exponents p >= 31 using per-divisor Montgomery data. /// public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDivisorByDivisorTester { private const int RemainderSlotCount = 4; private int _gpuBatchSize = GpuConstants.ScanBatchSize; // EvenPerfectBitScanner configures the GPU tester once before scanning and never mutates the configuration afterwards, // so the synchronization fields from the previous implementation remain commented out here. // private readonly object _sync = new(); private ulong _divisorLimit; // private bool _isConfigured; private readonly ConcurrentDictionary, ArrayView, ArrayView>> _kernelCache = new(); private readonly ConcurrentDictionary, ArrayView>> _kernelExponentCache = new(); private readonly ConcurrentDictionary, ArrayView, byte>> _remainderDeltaKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary, ArrayView, byte, byte>> _remainderScanKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary, ArrayView, ArrayView, ArrayView, byte, ArrayView>> _candidateMaskKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary, ArrayView, ArrayView, ArrayView>> _candidateCompactionKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary, ArrayView>> _hitDetectionKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary, ArrayView>> _hitConversionKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary, ArrayView, ArrayView, GpuUInt128, GpuUInt128>> _candidateGenerationKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary> _resourcePools = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentBag _acceleratorPool = new(); private readonly ConcurrentBag _sessionPool = new(); private Action, ArrayView, ArrayView> GetKernel(Accelerator accelerator) => _kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView, ArrayView>(DivisorByDivisorKernels.CheckKernel)); private Action, ArrayView> GetKernelByPrimeExponent(Accelerator accelerator) => _kernelExponentCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView>(DivisorByDivisorKernels.ComputeMontgomeryExponentKernel)); private Action, ArrayView, byte> GetRemainderDeltaKernel(Accelerator accelerator) => _remainderDeltaKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView, byte>(DivisorByDivisorKernels.ComputeRemainderDeltasKernel)); private Action, ArrayView, byte, byte> GetRemainderScanKernel(Accelerator accelerator) => _remainderScanKernelCache.GetOrAdd( accelerator, acc => acc.LoadStreamKernel, ArrayView, byte, byte>(DivisorByDivisorKernels.AccumulateRemaindersKernel)); private Action, ArrayView, ArrayView, ArrayView, byte, ArrayView> GetCandidateMaskKernel(Accelerator accelerator) => _candidateMaskKernelCache.GetOrAdd( accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, byte, ArrayView>(DivisorByDivisorKernels.EvaluateCandidateMaskKernel)); private Action, ArrayView, ArrayView, ArrayView> GetCandidateCompactionKernel(Accelerator accelerator) => _candidateCompactionKernelCache.GetOrAdd( accelerator, acc => acc.LoadStreamKernel, ArrayView, ArrayView, ArrayView>(DivisorByDivisorKernels.CompactCandidateMaskKernel)); private Action, ArrayView> GetHitDetectionKernel(Accelerator accelerator) => _hitDetectionKernelCache.GetOrAdd( accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView>(DivisorByDivisorKernels.PublishFirstHitKernel)); private Action, ArrayView> GetResultConversionKernel(Accelerator accelerator) => _hitConversionKernelCache.GetOrAdd( accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView>(DivisorByDivisorKernels.ConvertMontgomeryResultsToHitsKernel)); private Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> GetCandidateGenerationKernel(Accelerator accelerator) => _candidateGenerationKernelCache.GetOrAdd( accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, GpuUInt128, GpuUInt128>(DivisorByDivisorKernels.GenerateCandidatesAndGapsKernel)); public int GpuBatchSize { get => _gpuBatchSize; set => _gpuBatchSize = value < 1 ? 1 : value; } int IMersenneNumberDivisorByDivisorTester.BatchSize { get => GpuBatchSize; set => GpuBatchSize = value; } public void ConfigureFromMaxPrime(ulong maxPrime) { // EvenPerfectBitScanner configures the GPU tester once before scanning and never mutates the configuration afterwards, // so synchronization and runtime configuration guards are unnecessary here. // lock (_sync) // { // _divisorLimit = ComputeDivisorLimitFromMaxPrimeGpu(maxPrime); // _isConfigured = true; // } _divisorLimit = ComputeDivisorLimitFromMaxPrimeGpu(maxPrime); // _isConfigured = true; } public bool IsPrime(ulong prime, out bool divisorsExhausted) { ulong allowedMax; int batchCapacity; // EvenPerfectBitScanner only calls into this tester after configuring it once, so we can read the cached values without locking. // lock (_sync) // { // if (!_isConfigured) // { // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); // } // allowedMax = ComputeAllowedMaxDivisorGpu(prime, _divisorLimit); // batchCapacity = _gpuBatchSize; // } allowedMax = ComputeAllowedMaxDivisorGpu(prime, _divisorLimit); batchCapacity = _gpuBatchSize; // Production scans never shrink the divisor window below three, so this guard stays commented out. // if (allowedMax < 3UL) // { // divisorsExhausted = true; // return true; // } bool composite; bool coveredRange; ulong processedCount; ulong lastProcessed; var gpuLease = RentAccelerator(); var accelerator = gpuLease.Accelerator; Monitor.Enter(gpuLease.ExecutionLock); var kernel = GetKernel(accelerator); var resources = RentBatchResources(accelerator, batchCapacity); composite = CheckDivisors( prime, allowedMax, accelerator, kernel, resources.DivisorsBuffer, resources.ExponentBuffer, resources.HitsBuffer, resources.HitIndexBuffer, resources.CandidateBuffer, resources.NextStartBuffer, resources.DivisorDeltaBuffer, resources.RemainderDeltaBuffer, resources.RemainderBuffer, resources.FilterCountBuffer, resources.RemainderDeltaKernel, resources.RemainderScanKernel, resources.CandidateMaskKernel, resources.CandidateCompactionKernel, resources.CandidateGenerationKernel, resources.HitDetectionKernel, resources.Divisors, resources.Exponents, resources.DivisorData, resources.CycleCandidates, resources.CycleLengths, resources.CycleCapacity, out lastProcessed, out coveredRange, out processedCount); ReturnBatchResources(accelerator, resources); Monitor.Exit(gpuLease.ExecutionLock); ReturnAccelerator(gpuLease); if (composite) { divisorsExhausted = true; return false; } divisorsExhausted = coveredRange; return true; } public void PrepareCandidates(in ReadOnlySpan primes, Span allowedMaxValues) { // The scanner always supplies matching spans, so the previous validation remains commented out. // if (allowedMaxValues.Length < primes.Length) // { // throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues)); // } ulong divisorLimit; // EvenPerfectBitScanner configures the tester exactly once before preparing candidates. // lock (_sync) // { // if (!_isConfigured) // { // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); // } // divisorLimit = _divisorLimit; // } divisorLimit = _divisorLimit; for (int index = 0; index < primes.Length; index++) { allowedMaxValues[index] = ComputeAllowedMaxDivisorGpu(primes[index], divisorLimit); } } private static bool CheckDivisors( ulong prime, ulong allowedMax, Accelerator accelerator, Action, ArrayView, ArrayView> kernel, MemoryBuffer1D divisorsBuffer, MemoryBuffer1D exponentBuffer, MemoryBuffer1D hitsBuffer, MemoryBuffer1D hitIndexBuffer, MemoryBuffer1D candidateBuffer, MemoryBuffer1D nextStartBuffer, MemoryBuffer1D divisorDeltaBuffer, MemoryBuffer1D remainderDeltaBuffer, MemoryBuffer1D remainderBuffer, MemoryBuffer1D filterCountBuffer, Action, ArrayView, byte> remainderDeltaKernel, Action, ArrayView, byte, byte> remainderScanKernel, Action, ArrayView, ArrayView, ArrayView, byte, ArrayView> candidateMaskKernel, Action, ArrayView, ArrayView, ArrayView> candidateCompactionKernel, Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> candidateGenerationKernel, Action, ArrayView> hitDetectionKernel, in ulong[] divisors, ulong[] exponents, MontgomeryDivisorData[] divisorData, ulong[] cycleCandidates, ulong[] cycleLengths, int cycleCapacity, out ulong lastProcessed, out bool coveredRange, out ulong processedCount) { int batchCapacity = (int)divisorsBuffer.Length; bool composite = false; bool processedAll = false; processedCount = 0UL; lastProcessed = 0UL; cycleCapacity = Math.Max(1, cycleCapacity); int chunkCapacity = Math.Min(batchCapacity, cycleCapacity); int maxThreadsPerGroup = (int)accelerator.MaxNumThreadsPerGroup; bool clampThreads = maxThreadsPerGroup > 0; chunkCapacity = clampThreads && chunkCapacity > maxThreadsPerGroup ? maxThreadsPerGroup : chunkCapacity; chunkCapacity = chunkCapacity <= 0 ? cycleCapacity : chunkCapacity; UInt128 twoP128 = (UInt128)prime << 1; UInt128 allowedMax128 = allowedMax; UInt128 firstDivisor128 = twoP128 + UInt128.One; bool invalidStride = twoP128 == UInt128.Zero; bool outOfRange = firstDivisor128 > allowedMax128; UInt128 numerator = allowedMax128 - UInt128.One; UInt128 maxK128 = invalidStride || outOfRange ? UInt128.Zero : numerator / twoP128; bool hasCandidates = !invalidStride && !outOfRange && maxK128 != UInt128.Zero; if (!hasCandidates) { coveredRange = true; return false; } ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128; ulong currentK = 1UL; byte step10 = (byte)(twoP128 % 10UL); byte step8 = (byte)(twoP128 % 8UL); byte step5 = (byte)(twoP128 % 5UL); byte step3 = (byte)(twoP128 % 3UL); UInt128 currentDivisor128 = firstDivisor128; byte remainder10 = (byte)((ulong)(currentDivisor128 % 10UL)); byte remainder8 = (byte)((ulong)(currentDivisor128 & 7UL)); byte remainder5 = (byte)((ulong)(currentDivisor128 % 5UL)); byte remainder3 = (byte)((ulong)(currentDivisor128 % 3UL)); bool lastIsSeven = (prime & 3UL) == 3UL; byte lastIsSevenFlag = lastIsSeven ? (byte)1 : (byte)0; DivisorCycleCache cycleCache = DivisorCycleCache.Shared; int remainderStride = (int)divisorDeltaBuffer.Length; remainderStride = remainderStride <= 0 ? chunkCapacity : remainderStride; ArrayPool ulongPool = ThreadStaticPools.UlongPool; ulong[] filteredDivisors = ulongPool.Rent(chunkCapacity); var viewCache = new BufferSliceCache(divisorDeltaBuffer, remainderDeltaBuffer, remainderBuffer, hitsBuffer, remainderStride); Span candidateStorage = cycleCandidates.AsSpan(); Span filteredStorage = filteredDivisors.AsSpan(); Span cycleLengthStorage = cycleLengths.AsSpan(); Span divisorDataStorage = divisorData.AsSpan(); Span divisorStorage = divisors.AsSpan(); Span exponentStorage = exponents.AsSpan(); while (currentK <= maxK && !composite) { int chunkCount = Math.Min(chunkCapacity, batchCapacity); ulong remainingK = maxK - currentK + 1UL; chunkCount = (ulong)chunkCount > remainingK ? (int)remainingK : chunkCount; bool hasChunk = chunkCount > 0; int effectiveCount = hasChunk ? chunkCount : 0; if (!hasChunk) { processedAll = true; break; } Span candidateSpan = candidateStorage[..effectiveCount]; viewCache.Ensure(effectiveCount); ArrayView1D gapView = viewCache.DivisorDelta; ArrayView1D deltaView = viewCache.RemainderDelta; ArrayView1D remainder10View = viewCache.Remainder10; ArrayView1D remainder8View = viewCache.Remainder8; ArrayView1D remainder5View = viewCache.Remainder5; ArrayView1D remainder3View = viewCache.Remainder3; ArrayView1D maskView = viewCache.Hits; ArrayView1D filteredCountView = filterCountBuffer.View; UInt128 localDivisor = currentDivisor128; ArrayView1D candidateView = candidateBuffer.View.SubView(0, effectiveCount); ArrayView1D nextStartView = nextStartBuffer.View.SubView(0, 1); GpuUInt128 gpuStartValue = new GpuUInt128(localDivisor); GpuUInt128 gpuStride = new GpuUInt128(twoP128); GpuUInt128 nextDivisorGpu = GenerateCandidatesOnGpu( effectiveCount, gpuStartValue, gpuStride, candidateView, gapView, nextStartView, candidateGenerationKernel); if (effectiveCount > 0) { candidateView.CopyToCPU(ref MemoryMarshal.GetReference(candidateSpan), effectiveCount); processedCount += (ulong)effectiveCount; lastProcessed = candidateSpan[effectiveCount - 1]; currentDivisor128 = (UInt128)nextDivisorGpu; } ComputeRemaindersOnGpu(effectiveCount, remainder10, 10, gapView, deltaView, remainder10View, remainderDeltaKernel, remainderScanKernel); ComputeRemaindersOnGpu(effectiveCount, remainder8, 8, gapView, deltaView, remainder8View, remainderDeltaKernel, remainderScanKernel); ComputeRemaindersOnGpu(effectiveCount, remainder5, 5, gapView, deltaView, remainder5View, remainderDeltaKernel, remainderScanKernel); ComputeRemaindersOnGpu(effectiveCount, remainder3, 3, gapView, deltaView, remainder3View, remainderDeltaKernel, remainderScanKernel); BuildCandidateMaskOnGpu(effectiveCount, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView, candidateMaskKernel); byte lastRemainder10 = effectiveCount > 0 ? FetchLastRemainder(remainder10View, effectiveCount) : (byte)0; byte lastRemainder8 = effectiveCount > 0 ? FetchLastRemainder(remainder8View, effectiveCount) : (byte)0; byte lastRemainder5 = effectiveCount > 0 ? FetchLastRemainder(remainder5View, effectiveCount) : (byte)0; byte lastRemainder3 = effectiveCount > 0 ? FetchLastRemainder(remainder3View, effectiveCount) : (byte)0; remainder10 = AddModGpu(lastRemainder10, step10, 10); remainder8 = AddModGpu(lastRemainder8, step8, 8); remainder5 = AddModGpu(lastRemainder5, step5, 5); remainder3 = AddModGpu(lastRemainder3, step3, 3); int filteredCount = CompactCandidatesOnGpu( effectiveCount, maskView, candidateView, candidateView, filteredCountView, candidateCompactionKernel); if (filteredCount > 0) { Span filteredSlice = filteredStorage[..filteredCount]; ArrayView1D filteredView = candidateView.SubView(0, filteredCount); // The compaction kernel rewrites the accepted divisors into the front of the candidate buffer, // so copying the compacted prefix is enough to build the CPU span for cycle checks. filteredView.CopyToCPU(ref MemoryMarshal.GetReference(filteredSlice), filteredCount); } currentK += (ulong)effectiveCount; processedAll = currentK > maxK; if (filteredCount == 0) { continue; } Span filteredDivisorsSpan = filteredStorage[..filteredCount]; Span cycleSpan = cycleLengthStorage[..filteredCount]; cycleCache.GetCycleLengths(filteredDivisorsSpan, cycleSpan); int admissibleCount = 0; for (int i = 0; i < filteredCount; i++) { int add = cycleSpan[i] == prime ? 1 : 0; filteredDivisorsSpan[admissibleCount] = filteredDivisorsSpan[i]; admissibleCount += add; } if (admissibleCount == 0) { continue; } Span divisorDataSpan = divisorDataStorage[..admissibleCount]; Span divisorSpan = divisorStorage[..admissibleCount]; Span exponentSpan = exponentStorage[..admissibleCount]; ArrayView1D divisorView = divisorsBuffer.View.SubView(0, admissibleCount); ArrayView1D exponentView = exponentBuffer.View.SubView(0, admissibleCount); ArrayView1D hitsView = hitsBuffer.View.SubView(0, admissibleCount); ArrayView1D hitIndexView = hitIndexBuffer.View.SubView(0, 1); for (int i = 0; i < admissibleCount; i++) { ulong divisorValue = filteredDivisorsSpan[i]; divisorSpan[i] = divisorValue; exponentSpan[i] = prime; divisorDataSpan[i] = MontgomeryDivisorData.FromModulus(divisorValue); } divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), admissibleCount); exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), admissibleCount); kernel(admissibleCount, divisorView, exponentView, hitsView); int hitIndex = FindFirstHitOnGpu(admissibleCount, hitsView, hitIndexView, hitDetectionKernel); bool hitFound = hitIndex >= 0; composite = hitFound; int lastIndex = admissibleCount - 1; lastProcessed = hitFound ? divisorSpan[hitIndex] : divisorSpan[lastIndex]; } ulongPool.Return(filteredDivisors, clearArray: false); coveredRange = composite || processedAll || (currentDivisor128 > allowedMax128); return composite; } private ref struct BufferSliceCache { private readonly MemoryBuffer1D _divisorDeltaBuffer; private readonly MemoryBuffer1D _remainderDeltaBuffer; private readonly MemoryBuffer1D _remainderBuffer; private readonly MemoryBuffer1D _hitsBuffer; private readonly int _remainderStride; private int _length; private ArrayView1D _divisorDelta; private ArrayView1D _remainderDelta; private ArrayView1D _remainder10; private ArrayView1D _remainder8; private ArrayView1D _remainder5; private ArrayView1D _remainder3; private ArrayView1D _hits; internal BufferSliceCache( MemoryBuffer1D divisorDeltaBuffer, MemoryBuffer1D remainderDeltaBuffer, MemoryBuffer1D remainderBuffer, MemoryBuffer1D hitsBuffer, int remainderStride) { _divisorDeltaBuffer = divisorDeltaBuffer; _remainderDeltaBuffer = remainderDeltaBuffer; _remainderBuffer = remainderBuffer; _hitsBuffer = hitsBuffer; _remainderStride = remainderStride; _length = -1; _divisorDelta = default; _remainderDelta = default; _remainder10 = default; _remainder8 = default; _remainder5 = default; _remainder3 = default; _hits = default; } internal void Ensure(int length) { if (_length == length) { return; } _length = length; ArrayView1D remainderBaseView = _remainderBuffer.View; _divisorDelta = _divisorDeltaBuffer.View.SubView(0, length); _remainderDelta = _remainderDeltaBuffer.View.SubView(0, length); _remainder10 = remainderBaseView.SubView(0, length); _remainder8 = remainderBaseView.SubView(_remainderStride, length); _remainder5 = remainderBaseView.SubView(_remainderStride * 2, length); _remainder3 = remainderBaseView.SubView(_remainderStride * 3, length); _hits = _hitsBuffer.View.SubView(0, length); } internal ArrayView1D DivisorDelta => _divisorDelta; internal ArrayView1D RemainderDelta => _remainderDelta; internal ArrayView1D Remainder10 => _remainder10; internal ArrayView1D Remainder8 => _remainder8; internal ArrayView1D Remainder5 => _remainder5; internal ArrayView1D Remainder3 => _remainder3; internal ArrayView1D Hits => _hits; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static GpuUInt128 GenerateCandidatesOnGpu( int count, GpuUInt128 startValue, GpuUInt128 stride, ArrayView1D candidateView, ArrayView1D gapView, ArrayView1D nextStartView, Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> kernel) { // EvenPerfectBitScanner always launches this helper with a positive count, // so the guard stays commented out to keep the hot path branch-free. // if (count <= 0) // { // return GpuUInt128.Zero; // } kernel(count, candidateView, gapView, nextStartView, startValue, stride); GpuUInt128 nextStart = default; nextStartView.SubView(0, 1).CopyToCPU(ref nextStart, 1); return nextStart; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ComputeRemaindersOnGpu( int count, byte baseRemainder, byte modulus, ArrayView1D gapView, ArrayView1D deltaView, ArrayView1D remainderView, Action, ArrayView, byte> deltaKernel, Action, ArrayView, byte, byte> scanKernel) { // EvenPerfectBitScanner always supplies a positive count here, // so the guard stays commented out to keep the hot path branch-free. // if (count <= 0) // { // return; // } deltaKernel(count, gapView, deltaView, modulus); SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic(count); Index1D gridDim = new Index1D(1); Index1D groupDim = new Index1D(count); KernelConfig kernelConfig = new KernelConfig(gridDim, groupDim, sharedMemoryConfig); scanKernel(kernelConfig, new Index1D(count), deltaView, remainderView, baseRemainder, modulus); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void BuildCandidateMaskOnGpu( int count, ArrayView1D remainder10View, ArrayView1D remainder8View, ArrayView1D remainder5View, ArrayView1D remainder3View, byte lastIsSevenFlag, ArrayView1D maskView, Action, ArrayView, ArrayView, ArrayView, byte, ArrayView> maskKernel) { // EvenPerfectBitScanner always supplies a positive count here, // so the guard stays commented out to keep the hot path branch-free. // if (count <= 0) // { // return; // } maskKernel(count, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView); } private static int CompactCandidatesOnGpu( int count, ArrayView1D maskView, ArrayView1D sourceView, ArrayView1D destinationView, ArrayView1D countView, Action, ArrayView, ArrayView, ArrayView> compactionKernel) { // EvenPerfectBitScanner always compacts with a positive count, // so the guard stays commented out to keep the hot path branch-free. // if (count <= 0) // { // return 0; // } SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic(count * 3); Index1D gridDim = new Index1D(1); Index1D groupDim = new Index1D(count); KernelConfig kernelConfig = new KernelConfig(gridDim, groupDim, sharedMemoryConfig); compactionKernel(kernelConfig, new Index1D(count), maskView, sourceView, destinationView, countView); int filteredCount = 0; countView.SubView(0, 1).CopyToCPU(ref filteredCount, 1); return filteredCount; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int FindFirstHitOnGpu( int count, ArrayView1D hitsView, ArrayView1D hitIndexView, Action, ArrayView> hitKernel) { // EvenPerfectBitScanner only calls this helper with positive counts, // so the guard stays commented out to keep the hot path branch-free. // if (count <= 0) // { // return -1; // } int sentinel = int.MaxValue; hitIndexView.SubView(0, 1).CopyFromCPU(ref sentinel, 1); hitKernel(count, hitsView, hitIndexView); int firstHit = sentinel; hitIndexView.SubView(0, 1).CopyToCPU(ref firstHit, 1); return firstHit >= count ? -1 : firstHit; } private static byte FetchLastRemainder(ArrayView1D remainderView, int count) { // Callers guard against zero counts before invoking this helper to keep the hot path branch-free. byte value = 0; remainderView.SubView(count - 1, 1).CopyToCPU(ref value, 1); return value; } private static byte AddModGpu(byte value, byte delta, byte modulus) { int result = value + delta; int firstWrap = result >= modulus ? 1 : 0; result -= firstWrap * modulus; int secondWrap = result >= modulus ? 1 : 0; result -= secondWrap * modulus; return (byte)result; } private static ulong ComputeDivisorLimitFromMaxPrimeGpu(ulong maxPrime) { // The --mersenne=bydivisor flow in EvenPerfectBitScanner only calls ConfigureFromMaxPrime with primes greater than 1, // so the guard below never trips in production runs. // if (maxPrime <= 1UL) // { // return 0UL; // } if (maxPrime - 1UL >= 64UL) { return ulong.MaxValue; } return (1UL << (int)(maxPrime - 1UL)) - 1UL; } private static ulong ComputeAllowedMaxDivisorGpu(ulong prime, ulong divisorLimit) { // Production --mersenne=bydivisor runs only pass prime exponents, so the guard below never executes outside tests. // if (prime <= 1UL) // { // return 0UL; // } if (prime - 1UL >= 64UL) { return divisorLimit; } ulong computedLimit = (1UL << (int)(prime - 1UL)) - 1UL; return computedLimit < divisorLimit ? computedLimit : divisorLimit; } public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession { private readonly MersenneNumberDivisorByDivisorGpuTester _owner; private readonly GpuContextPool.GpuContextLease _lease; private readonly Accelerator _accelerator; private Action, ArrayView> _kernel = null!; private Action, ArrayView> _hitConversionKernel = null!; private MemoryBuffer1D _exponentsBuffer = null!; private MemoryBuffer1D _resultsBuffer = null!; private MemoryBuffer1D _hitBuffer = null!; private ulong[] _hostBuffer = null!; private int _capacity; private bool _disposed; internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner) { _owner = owner; _lease = GpuContextPool.RentPreferred(preferCpu: false); _accelerator = _lease.Accelerator; _capacity = Math.Max(1, owner._gpuBatchSize); } internal void Reset() { _disposed = false; } private void EnsureExecutionResourcesLocked() { _kernel = _owner.GetKernelByPrimeExponent(_accelerator); _hitConversionKernel = _owner.GetResultConversionKernel(_accelerator); _exponentsBuffer = _accelerator.Allocate1D(_capacity); _resultsBuffer = _accelerator.Allocate1D(_capacity); _hitBuffer = _accelerator.Allocate1D(_capacity); _hostBuffer = ThreadStaticPools.UlongPool.Rent(_capacity); } public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan primes, Span hits) { if (_disposed) { throw new ObjectDisposedException(nameof(DivisorScanSession)); } int length = primes.Length; // EvenPerfectBitScanner always supplies at least one exponent per divisor check, so the previous zero-length guard stays commented out. // if (length == 0) // { // return; // } // The GPU divisor sessions only materialize odd moduli greater than one (q = 2kp + 1), // so the defensive modulus guard stays commented out to keep the hot path branch-free. // if (divisorData.Modulus <= 1UL || (divisorData.Modulus & 1UL) == 0UL) // { // hits.Clear(); // return; // } Monitor.Enter(_lease.ExecutionLock); if (_kernel == null || _hitConversionKernel == null) { EnsureExecutionResourcesLocked(); } var kernel = _kernel!; var hitConversionKernel = _hitConversionKernel!; ArrayView1D exponentsView = _exponentsBuffer.View; ArrayView1D resultsView = _resultsBuffer.View; ArrayView1D hitView = _hitBuffer.View; Span hostSpan = _hostBuffer.AsSpan(0, _capacity); int offset = 0; while (offset < length) { int batchSize = Math.Min(_capacity, length - offset); ReadOnlySpan batch = primes.Slice(offset, batchSize); batch.CopyTo(hostSpan); ArrayView1D exponentSlice = exponentsView.SubView(0, batchSize); exponentSlice.CopyFromCPU(ref MemoryMarshal.GetReference(hostSpan), batchSize); ArrayView1D resultSlice = resultsView.SubView(0, batchSize); kernel(batchSize, divisorData, exponentSlice, resultSlice); ArrayView1D hitSliceView = hitView.SubView(0, batchSize); hitConversionKernel(batchSize, resultSlice, hitSliceView); Span hitSlice = hits.Slice(offset, batchSize); hitSliceView.CopyToCPU(ref MemoryMarshal.GetReference(hitSlice), batchSize); offset += batchSize; } Monitor.Exit(_lease.ExecutionLock); } public void Dispose() { if (_disposed) { return; } _disposed = true; _owner._sessionPool.Add(this); } } public ulong DivisorLimit { get { // The EvenPerfectBitScanner driver configures the GPU tester before exposing it to callers, so the previous synchronization guard remains commented out. // lock (_sync) // { // if (!_isConfigured) // { // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); // } // return _divisorLimit; // } return _divisorLimit; } } private GpuContextPool.GpuContextLease RentAccelerator() { if (_acceleratorPool.TryTake(out var lease)) { return lease; } return GpuContextPool.RentPreferred(preferCpu: false); } private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) => _acceleratorPool.Add(lease); private BatchResources RentBatchResources(Accelerator accelerator, int capacity) { var bag = _resourcePools.GetOrAdd(accelerator, static _ => []); if (bag.TryTake(out BatchResources? resources)) { return resources; } return new BatchResources(this, accelerator, capacity); } private void ReturnBatchResources(Accelerator accelerator, BatchResources resources) => _resourcePools.GetOrAdd(accelerator, static _ => []).Add(resources); private sealed class BatchResources : IDisposable { internal BatchResources(MersenneNumberDivisorByDivisorGpuTester owner, Accelerator accelerator, int capacity) { int cycleCapacity = DivisorCycleCache.Shared.PreferredBatchSize; if (cycleCapacity <= 0) { cycleCapacity = 1; } int actualCapacity = Math.Max(capacity, cycleCapacity); DivisorsBuffer = accelerator.Allocate1D(actualCapacity); ExponentBuffer = accelerator.Allocate1D(actualCapacity); HitsBuffer = accelerator.Allocate1D(actualCapacity); HitIndexBuffer = accelerator.Allocate1D(1); CandidateBuffer = accelerator.Allocate1D(actualCapacity); NextStartBuffer = accelerator.Allocate1D(1); DivisorDeltaBuffer = accelerator.Allocate1D(actualCapacity); RemainderDeltaBuffer = accelerator.Allocate1D(actualCapacity); RemainderBuffer = accelerator.Allocate1D(actualCapacity * RemainderSlotCount); FilterCountBuffer = accelerator.Allocate1D(1); ArrayPool ulongPool = ThreadStaticPools.UlongPool; Divisors = ulongPool.Rent(actualCapacity); Exponents = ulongPool.Rent(actualCapacity); DivisorData = ThreadStaticPools.MontgomeryDivisorDataPool.Rent(actualCapacity); CycleCandidates = ulongPool.Rent(cycleCapacity); CycleLengths = ulongPool.Rent(cycleCapacity); RemainderDeltaKernel = owner.GetRemainderDeltaKernel(accelerator); RemainderScanKernel = owner.GetRemainderScanKernel(accelerator); CandidateMaskKernel = owner.GetCandidateMaskKernel(accelerator); CandidateCompactionKernel = owner.GetCandidateCompactionKernel(accelerator); CandidateGenerationKernel = owner.GetCandidateGenerationKernel(accelerator); HitDetectionKernel = owner.GetHitDetectionKernel(accelerator); Capacity = actualCapacity; CycleCapacity = cycleCapacity; } internal readonly MemoryBuffer1D DivisorsBuffer; internal readonly MemoryBuffer1D ExponentBuffer; internal readonly MemoryBuffer1D HitsBuffer; internal readonly MemoryBuffer1D HitIndexBuffer; internal readonly MemoryBuffer1D CandidateBuffer; internal readonly MemoryBuffer1D NextStartBuffer; internal readonly MemoryBuffer1D DivisorDeltaBuffer; internal readonly MemoryBuffer1D RemainderDeltaBuffer; internal readonly MemoryBuffer1D RemainderBuffer; internal readonly MemoryBuffer1D FilterCountBuffer; internal readonly ulong[] Divisors; internal readonly ulong[] Exponents; internal readonly MontgomeryDivisorData[] DivisorData; internal readonly ulong[] CycleCandidates; internal readonly ulong[] CycleLengths; internal readonly Action, ArrayView, byte> RemainderDeltaKernel; internal readonly Action, ArrayView, byte, byte> RemainderScanKernel; internal readonly Action, ArrayView, ArrayView, ArrayView, byte, ArrayView> CandidateMaskKernel; internal readonly Action, ArrayView, ArrayView, ArrayView> CandidateCompactionKernel; internal readonly Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> CandidateGenerationKernel; internal readonly Action, ArrayView> HitDetectionKernel; internal readonly int Capacity; internal readonly int CycleCapacity; public void Dispose() { DivisorsBuffer.Dispose(); ExponentBuffer.Dispose(); HitsBuffer.Dispose(); HitIndexBuffer.Dispose(); CandidateBuffer.Dispose(); NextStartBuffer.Dispose(); DivisorDeltaBuffer.Dispose(); RemainderDeltaBuffer.Dispose(); RemainderBuffer.Dispose(); FilterCountBuffer.Dispose(); ArrayPool ulongPool = ThreadStaticPools.UlongPool; ArrayPool divisorPool = ThreadStaticPools.MontgomeryDivisorDataPool; ulongPool.Return(Divisors, clearArray: false); ulongPool.Return(Exponents, clearArray: false); divisorPool.Return(DivisorData, clearArray: false); ulongPool.Return(CycleCandidates, clearArray: false); ulongPool.Return(CycleLengths, clearArray: false); } } private sealed class AcceleratorReferenceComparer : IEqualityComparer { public static AcceleratorReferenceComparer Instance { get; } = new(); public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y); public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj); } public ulong GetAllowedMaxDivisor(ulong prime) { // The configuration is immutable after setup, so the cached limit can be read without locking. // lock (_sync) // { // if (!_isConfigured) // { // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); // } // return ComputeAllowedMaxDivisorGpu(prime, _divisorLimit); // } return ComputeAllowedMaxDivisorGpu(prime, _divisorLimit); } public DivisorScanSession CreateDivisorSession() { // The tester is configured once at startup and the session pool relies on thread-safe collections, so the previous synchronization guard stays commented out. // lock (_sync) // { // if (!_isConfigured) // { // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); // } // if (_sessionPool.TryTake(out DivisorScanSession? session)) // { // session.Reset(); // return session; // } // return new DivisorScanSession(this); // } if (_sessionPool.TryTake(out DivisorScanSession? session)) { session.Reset(); return session; } return new DivisorScanSession(this); } IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession(); }