Merge pull request #167 from mlemanczyk/codex/analyze-if-conditions-in-evenperfectbitscanner

Document unreachable guards in GPU by-divisor pipeline
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-10-13 20:42:48 +02:00
commit 305a489d6c
7 files changed
+292 -255

No files matched your search

@@ -3,6 +3,7 @@ using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Threading;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
@@ -10,14 +11,11 @@ namespace PerfectNumbers.Core.Cpu;
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
{ {
private readonly object _sync = new();
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new(); private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
private ulong _divisorLimit; private ulong _divisorLimit;
private ulong _lastStatusDivisor; private int _lastStatusDivisor;
private bool _isConfigured; private bool _isConfigured;
private int _batchSize = 1_024; private int _batchSize = 1_024;
private ulong _configuredMaxPrime;
private ByDivisorDeltasDevice _deltasDevice = ByDivisorDeltasDevice.Cpu; private ByDivisorDeltasDevice _deltasDevice = ByDivisorDeltasDevice.Cpu;
private ByDivisorMontgomeryDevice _montgomeryDevice = ByDivisorMontgomeryDevice.Cpu; private ByDivisorMontgomeryDevice _montgomeryDevice = ByDivisorMontgomeryDevice.Cpu;
@@ -34,79 +32,60 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
public ByDivisorDeltasDevice DeltasDevice public ByDivisorDeltasDevice DeltasDevice
{ {
get => _deltasDevice; get => _deltasDevice;
set set => _deltasDevice = value;
{
lock (_sync)
{
_deltasDevice = value;
}
}
} }
public ByDivisorMontgomeryDevice MontgomeryDevice public ByDivisorMontgomeryDevice MontgomeryDevice
{ {
get => _montgomeryDevice; get => _montgomeryDevice;
set set => _montgomeryDevice = value;
{
lock (_sync)
{
_montgomeryDevice = value;
}
}
} }
public void ConfigureFromMaxPrime(ulong maxPrime) public void ConfigureFromMaxPrime(ulong maxPrime)
{ {
lock (_sync) _divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
{ _lastStatusDivisor = 0;
_configuredMaxPrime = maxPrime; _isConfigured = true;
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
_lastStatusDivisor = 0UL;
_isConfigured = true;
}
} }
public ulong DivisorLimit public ulong DivisorLimit
{ {
get get
{ {
lock (_sync) // The by-divisor driver configures the tester during startup, so exposing the divisor limit never
{ // re-enters this method before initialization completes.
if (!_isConfigured) // if (!_isConfigured)
{ // {
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
} // }
return _divisorLimit; return _divisorLimit;
}
} }
} }
public ulong GetAllowedMaxDivisor(ulong prime) public ulong GetAllowedMaxDivisor(ulong prime)
{ {
lock (_sync) // The CLI wires the tester through ConfigureFromMaxPrime before invoking query helpers, so the guard
{ // would never trigger in production runs.
if (!_isConfigured) // if (!_isConfigured)
{ // {
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
} // }
return ComputeAllowedMaxDivisor(prime, _divisorLimit); return ComputeAllowedMaxDivisor(prime, _divisorLimit);
}
} }
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null) public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
{ {
ulong allowedMax; // The tester is configured once during startup, so runtime IsPrime calls always observe a ready
lock (_sync) // configuration and the legacy guard can remain commented out.
{ // if (!_isConfigured)
if (!_isConfigured) // {
{ // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); // }
}
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit); ulong divisorLimit = _divisorLimit;
} ulong allowedMax = ComputeAllowedMaxDivisor(prime, divisorLimit);
if (allowedMax < 3UL) if (allowedMax < 3UL)
{ {
@@ -128,10 +107,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
if (processedCount > 0UL) if (processedCount > 0UL)
{ {
lock (_sync) UpdateStatusUnsafe(processedCount);
{
UpdateStatusUnsafe(processedCount);
}
} }
if (composite) if (composite)
@@ -146,22 +122,21 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues) public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
{ {
if (allowedMaxValues.Length < primes.Length) // EvenPerfectBitScanner always provides equally sized spans here, so the historical guard never triggers.
{ // if (allowedMaxValues.Length < primes.Length)
throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues)); // {
} // throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues));
// }
ulong divisorLimit; // PrepareCandidates is scheduled only after ConfigureFromMaxPrime succeeds, so the tester state is
// stable here and the defensive guard is no longer needed.
// if (!_isConfigured)
// {
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
// }
lock (_sync) // Configuration is immutable after startup, so reading the cached limit does not require locking.
{ ulong divisorLimit = _divisorLimit;
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
divisorLimit = _divisorLimit;
}
for (int index = 0; index < primes.Length; index++) for (int index = 0; index < primes.Length; index++)
{ {
@@ -171,21 +146,20 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
{ {
lock (_sync) // The session pool is populated only after configuration, so consumers never request a session before
// ConfigureFromMaxPrime completes.
// if (!_isConfigured)
// {
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
// }
if (_sessionPool.TryTake(out DivisorScanSession? session))
{ {
if (!_isConfigured) session.Reset();
{ return session;
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);
} }
return new DivisorScanSession(this);
} }
private void ReturnSession(DivisorScanSession session) private void ReturnSession(DivisorScanSession session)
@@ -236,11 +210,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
} }
UInt128 step = (UInt128)prime << 1; UInt128 step = (UInt128)prime << 1;
if (step == UInt128.Zero) // EvenPerfectBitScanner only supplies primes greater than one here, so the shifted step never becomes zero.
{ // if (step == UInt128.Zero)
processedAll = true; // {
return false; // processedAll = true;
} // return false;
// }
UInt128 limit = allowedMax; UInt128 limit = allowedMax;
UInt128 divisor = step + UInt128.One; UInt128 divisor = step + UInt128.One;
@@ -323,6 +298,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
if (divisorCycle == 0UL) if (divisorCycle == 0UL)
{ {
// Keep this guard active to surface mismatches between generated candidates and the snapshot cache.
Console.WriteLine($"Divisor cycle was not calculated for {prime}"); Console.WriteLine($"Divisor cycle was not calculated for {prime}");
} }
} }
@@ -369,11 +345,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
GpuUInt128 step128 = new GpuUInt128(prime); GpuUInt128 step128 = new GpuUInt128(prime);
step128.ShiftLeft(1); step128.ShiftLeft(1);
if (step128.IsZero) // Primes above one yield a non-zero 2p value even after widening to GpuUInt128, so the defensive
{ // zero guard remains commented out to document the invariant without branching.
processedAll = true; // if (step128.IsZero)
return false; // {
} // processedAll = true;
// return false;
// }
GpuUInt128 limit128 = new GpuUInt128(allowedMax); GpuUInt128 limit128 = new GpuUInt128(allowedMax);
GpuUInt128 divisor128 = step128; GpuUInt128 divisor128 = step128;
@@ -449,10 +427,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
int chunkCount = remainingCandidates > (ulong)batchCapacity int chunkCount = remainingCandidates > (ulong)batchCapacity
? batchCapacity ? batchCapacity
: (int)remainingCandidates; : (int)remainingCandidates;
if (chunkCount <= 0) // batchCapacity and remainingCandidates are strictly positive in this path, so the zero-count
{ // shortcut never executes on production scans.
break; // if (chunkCount <= 0)
} // {
// break;
// }
Span<ulong> candidateSpan = candidateBuffer.AsSpan(0, chunkCount); Span<ulong> candidateSpan = candidateBuffer.AsSpan(0, chunkCount);
Span<byte> maskSpan = maskBuffer.AsSpan(0, chunkCount); Span<byte> maskSpan = maskBuffer.AsSpan(0, chunkCount);
@@ -603,17 +583,34 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return; return;
} }
ulong interval = PerfectNumberConstants.ConsoleInterval; int interval = PerfectNumberConstants.ConsoleInterval;
if (interval == 0UL) // PerfectNumberConstants.ConsoleInterval is a positive constant, so this guard never triggers on EvenPerfectBitScanner.
// if (interval <= 0)
// {
// Volatile.Write(ref _lastStatusDivisor, 0);
// return;
// }
int processedDelta = (int)(processedCount % (uint)interval);
if (processedDelta == 0)
{ {
_lastStatusDivisor = 0UL;
return; return;
} }
ulong total = _lastStatusDivisor + processedCount; while (true)
// TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI {
// status benchmarks so we avoid `%` in this hot loop while still wrapping progress correctly. int previous = Volatile.Read(ref _lastStatusDivisor);
_lastStatusDivisor = total % interval; int total = previous + processedDelta;
if (total >= interval)
{
total -= interval;
}
if (previous == Interlocked.CompareExchange(ref _lastStatusDivisor, total, previous))
{
return;
}
}
} }
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime) private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
@@ -696,11 +693,14 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
} }
ulong modulus = effectiveDivisorData.Modulus; ulong modulus = effectiveDivisorData.Modulus;
if (modulus <= 1UL || (modulus & 1UL) == 0UL) // EvenPerfectBitScanner only schedules odd divisors greater than one, and the fallback
{ // above rebuilds Montgomery data when the modulus mismatches, so this guard never
hits.Clear(); // triggers for production scans.
return; // if (modulus <= 1UL || (modulus & 1UL) == 0UL)
} // {
// hits.Clear();
// return;
// }
if (divisorCycle == 0UL) if (divisorCycle == 0UL)
{ {
@@ -803,10 +803,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
private void EnsureCapacity(int requiredLength) private void EnsureCapacity(int requiredLength)
{ {
if (requiredLength <= 0) // Prime batches are never empty on the by-divisor scanner path, so the zero-length guard stays commented
{ // out to remove the redundant branch.
return; // if (requiredLength <= 0)
} // {
// return;
// }
// Sessions release pooled buffers between scans; a single null check covers every array because buffers are rented and returned together. // Sessions release pooled buffers between scans; a single null check covers every array because buffers are rented and returned together.
if (requiredLength <= _bufferCapacity && _primeDeltas is not null) if (requiredLength <= _bufferCapacity && _primeDeltas is not null)
@@ -817,7 +819,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
int newCapacity = _bufferCapacity; int newCapacity = _bufferCapacity;
if (newCapacity == 0) if (newCapacity == 0)
{ {
// Fresh sessions start without buffers; seed the first rent with a single-slot array. // EvenPerfectBitScanner hits this branch on the first batch of each session, so keep the seed allocation.
newCapacity = 1; newCapacity = 1;
} }
@@ -873,11 +875,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
private static void ComputeCycleRemainders(ulong cycleLength, ReadOnlySpan<ulong> primes, ReadOnlySpan<ulong> deltas, Span<ulong> remainders) private static void ComputeCycleRemainders(ulong cycleLength, ReadOnlySpan<ulong> primes, ReadOnlySpan<ulong> deltas, Span<ulong> remainders)
{ {
if (cycleLength == 0UL || primes.Length == 0) // Divisor cycles are validated before reaching the GPU helper, and prime batches are non-empty, so
{ // the defensive zero checks remain commented out here.
remainders.Clear(); // if (cycleLength == 0UL || primes.Length == 0)
return; // {
} // remainders.Clear();
// return;
// }
ulong remainder = primes[0] % cycleLength; ulong remainder = primes[0] % cycleLength;
remainders[0] = remainder; remainders[0] = remainder;
@@ -893,6 +897,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
{ {
if (modulus == 0UL) if (modulus == 0UL)
{ {
// A zero modulus indicates a missing cycle entry; preserve the fallback so snapshot regressions remain visible.
return 0UL; return 0UL;
} }
@@ -19,7 +19,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private const int RemainderSlotCount = 4; private const int RemainderSlotCount = 4;
private int _gpuBatchSize = GpuConstants.ScanBatchSize; private int _gpuBatchSize = GpuConstants.ScanBatchSize;
private readonly object _sync = new();
private ulong _divisorLimit; private ulong _divisorLimit;
private bool _isConfigured; private bool _isConfigured;
@@ -64,34 +63,30 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
public void ConfigureFromMaxPrime(ulong maxPrime) public void ConfigureFromMaxPrime(ulong maxPrime)
{ {
lock (_sync) // Configuration happens once during startup, so no synchronization is required to cache the divisor limit.
{ _divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime); _isConfigured = true;
_isConfigured = true;
}
} }
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null) public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
{ {
ulong allowedMax; // The CLI always configures the tester during startup, so the historical guard is commented out to avoid branching.
int batchCapacity; // if (!_isConfigured)
// {
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
// }
lock (_sync) // Configuration is immutable, so the cached values can be read without locking.
{ ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
if (!_isConfigured) int batchCapacity = _gpuBatchSize;
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit); // The GPU path always receives an allowed maximum well above the minimal divisor, so the legacy
batchCapacity = _gpuBatchSize; // guard stays commented out to document the invariant without branching.
} // if (allowedMax < 3UL)
// {
if (allowedMax < 3UL) // divisorsExhausted = true;
{ // return true;
divisorsExhausted = true; // }
return true;
}
bool composite; bool composite;
bool coveredRange; bool coveredRange;
@@ -155,18 +150,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues)); throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues));
} }
ulong divisorLimit; // The GPU pipeline calls ConfigureFromMaxPrime before preparing candidates, so the legacy guard
// remains commented out to document the invariant without branching.
lock (_sync) // if (!_isConfigured)
{ // {
if (!_isConfigured) // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
{ // }
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
divisorLimit = _divisorLimit;
}
ulong divisorLimit = _divisorLimit;
for (int index = 0; index < primes.Length; index++) for (int index = 0; index < primes.Length; index++)
{ {
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit); allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
@@ -222,7 +213,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ArrayView1D<ulong, Stride1D.Dense> exponentView; ArrayView1D<ulong, Stride1D.Dense> exponentView;
ArrayView1D<byte, Stride1D.Dense> hitsView; ArrayView1D<byte, Stride1D.Dense> hitsView;
cycleCapacity = Math.Max(1, cycleCapacity); // Batch resources expose a positive cycle capacity, so skip the redundant clamp here.
// cycleCapacity = Math.Max(1, cycleCapacity);
int chunkCapacity = Math.Min(batchCapacity, cycleCapacity); int chunkCapacity = Math.Min(batchCapacity, cycleCapacity);
int maxThreadsPerGroup = (int)accelerator.MaxNumThreadsPerGroup; int maxThreadsPerGroup = (int)accelerator.MaxNumThreadsPerGroup;
@@ -231,20 +223,22 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
chunkCapacity = Math.Min(chunkCapacity, maxThreadsPerGroup); chunkCapacity = Math.Min(chunkCapacity, maxThreadsPerGroup);
} }
if (chunkCapacity <= 0) // cycleCapacity already bounds chunkCapacity above zero, so the fallback would never execute.
{ // if (chunkCapacity <= 0)
chunkCapacity = cycleCapacity; // {
} // chunkCapacity = cycleCapacity;
// }
ulong[] filteredDivisors = ArrayPool<ulong>.Shared.Rent(chunkCapacity); ulong[] filteredDivisors = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
ulong[] divisorGaps = ArrayPool<ulong>.Shared.Rent(chunkCapacity); ulong[] divisorGaps = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
UInt128 twoP128 = (UInt128)prime << 1; UInt128 twoP128 = (UInt128)prime << 1;
if (twoP128 == UInt128.Zero) // Primes above one yield a non-zero 2p value, so the legacy zero guard remains commented out.
{ // if (twoP128 == UInt128.Zero)
coveredRange = true; // {
return false; // coveredRange = true;
} // return false;
// }
UInt128 allowedMax128 = allowedMax; UInt128 allowedMax128 = allowedMax;
UInt128 firstDivisor128 = twoP128 + UInt128.One; UInt128 firstDivisor128 = twoP128 + UInt128.One;
@@ -255,11 +249,13 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
UInt128 maxK128 = (allowedMax128 - UInt128.One) / twoP128; UInt128 maxK128 = (allowedMax128 - UInt128.One) / twoP128;
if (maxK128 == UInt128.Zero) // Once the first divisor falls within the allowed range, at least one multiplier k is admissible,
{ // so keep the defensive zero check commented out.
coveredRange = true; // if (maxK128 == UInt128.Zero)
return false; // {
} // coveredRange = true;
// return false;
// }
ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128; ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128;
ulong currentK = 1UL; ulong currentK = 1UL;
@@ -301,10 +297,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
chunkCount = (int)remainingK; chunkCount = (int)remainingK;
} }
if (chunkCount <= 0) // chunkCapacity and the remaining multiplier range are strictly positive here, so the zero
{ // chunk fallback never triggers on production scans.
break; // if (chunkCount <= 0)
} // {
// break;
// }
Span<ulong> candidateSpan = cycleCandidates.AsSpan(0, chunkCount); Span<ulong> candidateSpan = cycleCandidates.AsSpan(0, chunkCount);
Span<ulong> gapSpan = divisorGaps.AsSpan(0, chunkCount); Span<ulong> gapSpan = divisorGaps.AsSpan(0, chunkCount);
@@ -465,10 +463,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> deltaKernel, Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> deltaKernel,
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> scanKernel) Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> scanKernel)
{ {
if (count <= 0) // GPU batches always contain at least one divisor gap, so the empty-count guard remains commented
{ // to eliminate redundant branching.
return; // if (count <= 0)
} // {
// return;
// }
deltaKernel(count, gapView, deltaView, modulus); deltaKernel(count, gapView, deltaView, modulus);
SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic<int>(count); SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic<int>(count);
@@ -491,11 +491,13 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
Span<byte> destination, Span<byte> destination,
Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> maskKernel) Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> maskKernel)
{ {
if (count <= 0) // Candidate batches always provide at least one entry, so the zero-count guard stays commented out
{ // to avoid clearing spans unnecessarily.
destination.Clear(); // if (count <= 0)
return; // {
} // destination.Clear();
// return;
// }
maskKernel(count, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView); maskKernel(count, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView);
maskView.CopyToCPU(ref MemoryMarshal.GetReference(destination), count); maskView.CopyToCPU(ref MemoryMarshal.GetReference(destination), count);
@@ -503,10 +505,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private static byte FetchLastRemainder(ArrayView1D<byte, Stride1D.Dense> remainderView, int count) private static byte FetchLastRemainder(ArrayView1D<byte, Stride1D.Dense> remainderView, int count)
{ {
if (count <= 0) // Kernels always process at least one remainder in this path, so the empty-span guard remains
{ // commented to document the invariant.
return 0; // if (count <= 0)
} // {
// return 0;
// }
byte value = 0; byte value = 0;
remainderView.SubView(count - 1, 1).CopyToCPU(ref value, 1); remainderView.SubView(count - 1, 1).CopyToCPU(ref value, 1);
@@ -519,10 +523,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
if (result >= modulus) if (result >= modulus)
{ {
result -= modulus; result -= modulus;
if (result >= modulus) // The step deltas remain below the modulus, so a single subtraction suffices on this path.
{ // if (result >= modulus)
result %= modulus; // {
} // result %= modulus;
// }
} }
return (byte)result; return (byte)result;
@@ -933,34 +938,32 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
public ulong GetAllowedMaxDivisor(ulong prime) public ulong GetAllowedMaxDivisor(ulong prime)
{ {
lock (_sync) // The CLI configures the GPU tester once at startup, so callers never request divisor limits before
{ // initialization completes.
if (!_isConfigured) // if (!_isConfigured)
{ // {
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
} // }
return ComputeAllowedMaxDivisor(prime, _divisorLimit); return ComputeAllowedMaxDivisor(prime, _divisorLimit);
}
} }
public DivisorScanSession CreateDivisorSession() public DivisorScanSession CreateDivisorSession()
{ {
lock (_sync) // Divisor sessions are handed out only after configuration, so this guard would never trigger on the
// production path.
// if (!_isConfigured)
// {
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
// }
if (_sessionPool.TryTake(out DivisorScanSession? session))
{ {
if (!_isConfigured) session.Reset();
{ return session;
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);
} }
return new DivisorScanSession(this);
} }
IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession(); IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession();
@@ -68,12 +68,17 @@ internal sealed class MersenneNumberDivisorCandidateGpuEvaluator : IDisposable
} }
int count = candidates.Length; int count = candidates.Length;
if (count == 0) // Candidate batches always carry at least one divisor on the by-divisor path, so the empty-count
{ // guard remains commented out to eliminate the branch.
return; // if (count == 0)
} // {
// return;
// }
EnsureCapacity(count); if (count > _capacity)
{
EnsureCapacity(count);
}
Span<ulong> hostCandidateSpan = _hostCandidates.AsSpan(0, count); Span<ulong> hostCandidateSpan = _hostCandidates.AsSpan(0, count);
Span<byte> hostMaskSpan = _hostMask.AsSpan(0, count); Span<byte> hostMaskSpan = _hostMask.AsSpan(0, count);
@@ -115,7 +120,12 @@ internal sealed class MersenneNumberDivisorCandidateGpuEvaluator : IDisposable
return; return;
} }
int newCapacity = _capacity == 0 ? 1 : _capacity; int newCapacity = _capacity;
// The evaluator always starts with a positive capacity, so the zero-capacity fallback remains commented.
// if (newCapacity == 0)
// {
// newCapacity = 1;
// }
while (newCapacity < required) while (newCapacity < required)
{ {
newCapacity <<= 1; newCapacity <<= 1;
@@ -188,10 +198,11 @@ internal sealed class MersenneNumberDivisorCandidateGpuEvaluator : IDisposable
private static int ComputeRemainder(byte remainder, byte step, int index, int modulus) private static int ComputeRemainder(byte remainder, byte step, int index, int modulus)
{ {
if (modulus == 0) // Modulus lookups only cover {10, 8, 5, 3, 7, 11}, so the zero-modulus guard stays commented out.
{ // if (modulus == 0)
return 0; // {
} // return 0;
// }
long value = remainder; long value = remainder;
value += (long)step * index; value += (long)step * index;
@@ -54,10 +54,12 @@ internal sealed class MersenneNumberDivisorMontgomeryGpuBuilder : IDisposable
} }
int length = moduli.Length; int length = moduli.Length;
if (length == 0) // Montgomery batches always include at least one modulus on the by-divisor path, so the empty-length
{ // guard stays commented out to avoid branching.
return; // if (length == 0)
} // {
// return;
// }
EnsureCapacity(length); EnsureCapacity(length);
@@ -83,7 +85,12 @@ internal sealed class MersenneNumberDivisorMontgomeryGpuBuilder : IDisposable
return; return;
} }
int newCapacity = _capacity == 0 ? 1 : _capacity; int newCapacity = _capacity;
// The builder always starts with a positive capacity, so the zero-capacity fallback remains commented.
// if (newCapacity == 0)
// {
// newCapacity = 1;
// }
while (newCapacity < required) while (newCapacity < required)
{ {
newCapacity <<= 1; newCapacity <<= 1;
@@ -52,7 +52,7 @@ internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable
{ {
if (_disposed) if (_disposed)
{ {
return; throw new InvalidOperationException("MersenneNumberDivisorRemainderGpuStepper was disposed more than once.");
} }
_disposed = true; _disposed = true;
@@ -71,15 +71,18 @@ internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable
throw new ObjectDisposedException(nameof(MersenneNumberDivisorRemainderGpuStepper)); throw new ObjectDisposedException(nameof(MersenneNumberDivisorRemainderGpuStepper));
} }
if (advanceCount <= 0 || remainders.Length == 0) // GPU remainder batches always advance by a positive chunk count and operate on fixed-size tables, so
{ // the defensive early-return remains commented out.
return; // if (advanceCount <= 0 || remainders.Length == 0)
} // {
// return;
// }
if (remainders.Length != _length) // Production code always calls the stepper with a fixed-length table, so the historical length guard stays commented.
{ // if (remainders.Length != _length)
throw new ArgumentException("Remainder buffer length must match the configured length."); // {
} // throw new ArgumentException("Remainder buffer length must match the configured length.");
// }
Span<byte> hostRemainderSpan = _hostRemainders.AsSpan(0, _length); Span<byte> hostRemainderSpan = _hostRemainders.AsSpan(0, _length);
remainders.CopyTo(hostRemainderSpan); remainders.CopyTo(hostRemainderSpan);
@@ -99,17 +102,20 @@ internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable
private static void AdvanceRemaindersKernel(Index1D index, int advanceCount, ArrayView<byte> remainders, ArrayView<byte> steps, ArrayView<byte> results) private static void AdvanceRemaindersKernel(Index1D index, int advanceCount, ArrayView<byte> remainders, ArrayView<byte> steps, ArrayView<byte> results)
{ {
int idx = index; int idx = index;
if (idx >= remainders.Length) // Kernel launches match the remainder table length, so the bounds guard stays commented to avoid redundant branching.
{ // if (idx >= remainders.Length)
return; // {
} // return;
// }
int modulus = GetModulus(idx); int modulus = GetModulus(idx);
if (modulus == 0) // The modulus selector only emits positive bases (10, 8, 5, 3, 7, 11) for the configured indices, so the zero-modulus
{ // fallback remains commented out.
results[idx] = remainders[idx]; // if (modulus == 0)
return; // {
} // results[idx] = remainders[idx];
// return;
// }
int remainder = remainders[idx] % modulus; int remainder = remainders[idx] % modulus;
int step = steps[idx] % modulus; int step = steps[idx] % modulus;
@@ -33,7 +33,7 @@ internal sealed class MersenneNumberDivisorResidueGpuEvaluator : IDisposable
{ {
if (_disposed) if (_disposed)
{ {
return; throw new InvalidOperationException("MersenneNumberDivisorResidueGpuEvaluator was disposed more than once.");
} }
_disposed = true; _disposed = true;
@@ -51,10 +51,12 @@ internal sealed class MersenneNumberDivisorResidueGpuEvaluator : IDisposable
} }
int length = exponents.Length; int length = exponents.Length;
if (length == 0) // Residue batches always include at least one exponent when scheduled from the by-divisor scanner, so
{ // the empty-length guard remains commented out to avoid branching.
return; // if (length == 0)
} // {
// return;
// }
EnsureCapacity(length); EnsureCapacity(length);
@@ -79,10 +81,11 @@ internal sealed class MersenneNumberDivisorResidueGpuEvaluator : IDisposable
} }
int newCapacity = _capacity; int newCapacity = _capacity;
if (newCapacity == 0) // The evaluator always starts with a positive capacity, so the zero-capacity fallback stays commented.
{ // if (newCapacity == 0)
newCapacity = 1; // {
} // newCapacity = 1;
// }
while (newCapacity < required) while (newCapacity < required)
{ {
@@ -180,11 +180,13 @@ public static class MersenneNumberDivisorByDivisorTester
return; return;
} }
if (maxPrime <= 1UL) // EvenPerfectBitScanner removes candidates <= 1 before reaching this point,
{ // so this guard never triggers during production runs.
Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor."); // if (maxPrime <= 1UL)
return; // {
} // Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor.");
// return;
// }
tester.ConfigureFromMaxPrime(maxPrime); tester.ConfigureFromMaxPrime(maxPrime);