#include #include #include #include #include #include #include #include #include #include namespace { constexpr uint64_t kJavaMultiplier = 0x5DEECE66DULL; constexpr uint64_t kJavaAddend = 0xBULL; constexpr uint64_t kJavaMask = (1ULL << 48) - 1; constexpr uint64_t kSlimeXor = 987234911ULL; constexpr int32_t kTileW = 16; constexpr int32_t kTileH = 16; constexpr int32_t kMaskFastPathMaxN = 49; struct Args { int64_t startSeed = 0; int64_t endSeed = 1'000'000; int32_t n = 3; int32_t required = -1; int32_t radius = 100; int64_t seedBatch = 0; int32_t maxResults = 32; bool allLocations = false; }; struct Hit { int64_t seed; int32_t x; int32_t z; int32_t count; }; void checkCuda(cudaError_t error, const char* expr, const char* file, int line) { if (error == cudaSuccess) { return; } std::cerr << file << ':' << line << ": CUDA call failed: " << expr << ": " << cudaGetErrorString(error) << '\n'; std::exit(1); } #define CUDA_CHECK(expr) checkCuda((expr), #expr, __FILE__, __LINE__) [[noreturn]] void usage(int exitCode = 1) { std::cerr << "Usage: slime_cuda [args...]\n" << " -s First world seed, inclusive (default: 0)\n" << " -e Last world seed, exclusive (default: 1000000)\n" << " -n N for the NxN window (default: 3)\n" << " -d Required slime chunks in the window (default: N*N)\n" << " -r Search start positions from -r..r on both axes (default: 100)\n" << " --batch Seeds per kernel launch (default: max(total/100, 1000000))\n" << " --max-results Print at most n hits, while still counting all hits (default: 32)\n" << " --all-locations Count every matching location, not just the first per seed\n" << " --help Show this help\n"; std::exit(exitCode); } const char* requireValue(int argc, char** argv, int& i) { if (i + 1 >= argc) { usage(); } return argv[++i]; } Args parseArgs(int argc, char** argv) { Args args; for (int i = 1; i < argc; ++i) { const std::string arg = argv[i]; if (arg == "--help") { usage(0); } else if (arg == "-s") { args.startSeed = std::stoll(requireValue(argc, argv, i)); } else if (arg == "-e") { args.endSeed = std::stoll(requireValue(argc, argv, i)); } else if (arg == "-n") { args.n = std::stoi(requireValue(argc, argv, i)); } else if (arg == "-d") { args.required = std::stoi(requireValue(argc, argv, i)); } else if (arg == "-r") { args.radius = std::stoi(requireValue(argc, argv, i)); } else if (arg == "--batch") { args.seedBatch = std::stoll(requireValue(argc, argv, i)); } else if (arg == "--max-results") { args.maxResults = std::stoi(requireValue(argc, argv, i)); } else if (arg == "--all-locations") { args.allLocations = true; } else { usage(); } } const int64_t cells = int64_t(args.n) * args.n; if (args.required < 0) { args.required = static_cast(cells); } if (args.startSeed >= args.endSeed || args.n <= 0 || args.radius < 0 || args.seedBatch < 0 || args.maxResults < 0 || cells > INT32_MAX || args.required <= 0 || args.required > cells) { usage(); } return args; } __host__ __device__ __forceinline__ uint64_t javaIntAsLongBits(uint32_t value) { return static_cast(static_cast(static_cast(value))); } __host__ __device__ __forceinline__ uint64_t coordXTerm(int32_t chunkX) { const uint32_t x = static_cast(chunkX); return javaIntAsLongBits(x * x * 4987142u) + javaIntAsLongBits(x * 5947611u); } __host__ __device__ __forceinline__ uint64_t coordZTerm(int32_t chunkZ) { const uint32_t z = static_cast(chunkZ); return javaIntAsLongBits(z * z) * 4392871ULL + javaIntAsLongBits(z * 389711u); } __host__ __device__ __forceinline__ uint64_t coordTerm(int32_t chunkX, int32_t chunkZ) { return coordXTerm(chunkX) + coordZTerm(chunkZ); } __device__ __forceinline__ bool isSlimeFromTerm(uint64_t worldSeedBits, uint64_t term) { uint64_t randomSeed = ((worldSeedBits + term) ^ kSlimeXor ^ kJavaMultiplier) & kJavaMask; for (;;) { randomSeed = (randomSeed * kJavaMultiplier + kJavaAddend) & kJavaMask; const uint32_t bits = static_cast(randomSeed >> 17); const uint32_t value = bits % 10u; if (bits - value + 9u <= 0x7FFFFFFFu) { return value == 0; } } } __device__ __forceinline__ bool isNextInt10ZeroFast(uint32_t bits) { const uint32_t product = bits * 0xCCCCCCCDu; const uint32_t rotated = (product >> 1) | (product << 31); return rotated <= 429496729u; } __device__ __forceinline__ bool isSlimeFromSplitTerms(uint64_t worldSeedBits, uint64_t xTerm, uint64_t zTerm) { uint64_t randomSeed = ((worldSeedBits + xTerm + zTerm) ^ kSlimeXor ^ kJavaMultiplier) & kJavaMask; randomSeed = (randomSeed * kJavaMultiplier + kJavaAddend) & kJavaMask; return isNextInt10ZeroFast(static_cast(randomSeed >> 17)); } __device__ __forceinline__ bool isSlimeChunkInline(uint64_t worldSeedBits, int32_t chunkX, int32_t chunkZ) { return isSlimeFromSplitTerms(worldSeedBits, coordXTerm(chunkX), coordZTerm(chunkZ)); } std::vector makeCoordTerms(int32_t radius, int32_t n) { const int32_t window = radius * 2 + 1; const int32_t chunkWidth = window + n - 1; std::vector terms(size_t(chunkWidth) * chunkWidth); for (int32_t z = 0; z < chunkWidth; ++z) { for (int32_t x = 0; x < chunkWidth; ++x) { terms[size_t(z) * chunkWidth + x] = coordTerm(x - radius, z - radius); } } return terms; } __device__ void recordHit( int32_t onePerSeed, int32_t* seedFound, int64_t seedOffset, unsigned long long* totalHits, Hit* hits, int32_t maxHits, int64_t seed, int32_t x, int32_t z, int32_t count ) { if (onePerSeed != 0) { if (atomicCAS(seedFound + seedOffset, 0, 1) != 0) { return; } } const unsigned long long index = atomicAdd(totalHits, 1ULL); if (index < static_cast(maxHits)) { hits[index] = {seed, x, z, count}; } } __global__ void searchTilesKernel( int64_t startSeed, int64_t seedCount, const uint64_t* terms, int32_t chunkWidth, int32_t window, int32_t n, int32_t required, int32_t radius, int32_t tilesPerSeed, int32_t tilesX, int32_t onePerSeed, int32_t* seedFound, unsigned long long* totalHits, Hit* hits, int32_t maxHits ) { extern __shared__ uint8_t tile[]; __shared__ unsigned long long horizontalRows[kTileH + kMaskFastPathMaxN - 1]; const int64_t linearBlock = blockIdx.x; const int64_t seedOffset = linearBlock / tilesPerSeed; if (seedOffset >= seedCount) { return; } if (onePerSeed != 0 && seedFound[seedOffset] != 0) { return; } const int32_t tileId = static_cast(linearBlock - seedOffset * tilesPerSeed); const int32_t tileBaseX = (tileId % tilesX) * kTileW; const int32_t tileBaseZ = (tileId / tilesX) * kTileH; const int32_t actualW = (window - tileBaseX) < kTileW ? (window - tileBaseX) : kTileW; const int32_t actualH = (window - tileBaseZ) < kTileH ? (window - tileBaseZ) : kTileH; const int32_t sharedW = actualW + n - 1; const int32_t sharedH = actualH + n - 1; const int32_t sharedCells = sharedW * sharedH; const int64_t seed = startSeed + seedOffset; const uint64_t seedBits = static_cast(seed); const int32_t localThread = threadIdx.y * blockDim.x + threadIdx.x; const int32_t threadCount = blockDim.x * blockDim.y; for (int32_t index = localThread; index < sharedCells; index += threadCount) { const int32_t localX = index % sharedW; const int32_t localZ = index / sharedW; const size_t termIndex = size_t(tileBaseZ + localZ) * chunkWidth + tileBaseX + localX; tile[index] = isSlimeFromTerm(seedBits, terms[termIndex]) ? 1 : 0; } __syncthreads(); if (required == n * n && n <= kMaskFastPathMaxN) { for (int32_t row = localThread; row < sharedH; row += threadCount) { unsigned long long rowMask = 0; for (int32_t x = 0; x < sharedW; ++x) { rowMask |= static_cast(tile[row * sharedW + x]) << x; } unsigned long long mask = rowMask; for (int32_t shift = 1; shift < n; ++shift) { mask &= rowMask >> shift; } horizontalRows[row] = mask; } __syncthreads(); const int32_t localX = threadIdx.x; const int32_t localZ = threadIdx.y; if (localX < actualW && localZ < actualH) { unsigned long long mask = ~0ULL; for (int32_t dz = 0; dz < n; ++dz) { mask &= horizontalRows[localZ + dz]; } if (((mask >> localX) & 1ULL) != 0) { recordHit( onePerSeed, seedFound, seedOffset, totalHits, hits, maxHits, seed, tileBaseX + localX - radius, tileBaseZ + localZ - radius, n * n ); } } return; } const int32_t localX = threadIdx.x; const int32_t localZ = threadIdx.y; if (localX >= actualW || localZ >= actualH) { return; } if (onePerSeed != 0 && seedFound[seedOffset] != 0) { return; } int32_t count = 0; int32_t remaining = n * n; for (int32_t dz = 0; dz < n; ++dz) { const int32_t rowBase = (localZ + dz) * sharedW + localX; for (int32_t dx = 0; dx < n; ++dx) { count += tile[rowBase + dx]; --remaining; if (count + remaining < required) { return; } } } if (count >= required) { recordHit( onePerSeed, seedFound, seedOffset, totalHits, hits, maxHits, seed, tileBaseX + localX - radius, tileBaseZ + localZ - radius, count ); } } __global__ void searchSeedsFullKernel( int64_t startSeed, int64_t seedCount, int32_t window, int32_t n, int32_t radius, unsigned long long* totalHits, Hit* hits, int32_t maxHits ) { const int64_t seedOffset = int64_t(blockIdx.x) * blockDim.x + threadIdx.x; if (seedOffset >= seedCount) { return; } const int64_t seed = startSeed + seedOffset; const uint64_t seedBits = static_cast(seed); for (int32_t x = 0; x < window; ++x) { const int32_t chunkX = x - radius; const uint64_t firstXTerm = coordXTerm(chunkX); for (int32_t z = 0; z < window; ++z) { const int32_t chunkZ = z - radius; if (!isSlimeFromSplitTerms(seedBits, firstXTerm, coordZTerm(chunkZ))) { continue; } bool found = true; for (int32_t dx = 0; dx < n && found; ++dx) { const uint64_t xTerm = dx == 0 ? firstXTerm : coordXTerm(chunkX + dx); const int32_t firstDz = dx == 0 ? 1 : 0; for (int32_t dz = firstDz; dz < n; ++dz) { if (!isSlimeFromSplitTerms(seedBits, xTerm, coordZTerm(chunkZ + dz))) { found = false; break; } } } if (found) { const unsigned long long index = atomicAdd(totalHits, 1ULL); if (index < static_cast(maxHits)) { hits[index] = {seed, chunkX, chunkZ, n * n}; } return; } } } } void printHits(const std::vector& hits) { for (const Hit& hit : hits) { std::cout << "hit seed=" << hit.seed << " x=" << hit.x << " z=" << hit.z << " count=" << hit.count << '\n'; } } } // namespace int main(int argc, char** argv) { try { const Args args = parseArgs(argc, argv); const int32_t window = args.radius * 2 + 1; const int32_t chunkWidth = window + args.n - 1; const int32_t tilesX = (window + kTileW - 1) / kTileW; const int32_t tilesZ = (window + kTileH - 1) / kTileH; const int32_t tilesPerSeed = tilesX * tilesZ; const size_t dynamicSharedBytes = size_t(kTileW + args.n - 1) * (kTileH + args.n - 1); const size_t staticSharedBytes = size_t(kTileH + kMaskFastPathMaxN - 1) * sizeof(unsigned long long); const bool useSeedKernel = !args.allLocations && args.required == args.n * args.n; const int32_t seedThreadsPerBlock = 64; int device = 0; cudaDeviceProp prop{}; CUDA_CHECK(cudaGetDevice(&device)); CUDA_CHECK(cudaGetDeviceProperties(&prop, device)); if (!useSeedKernel && dynamicSharedBytes + staticSharedBytes > static_cast(prop.sharedMemPerBlock)) { throw std::runtime_error("requested N needs more shared memory per block than this GPU allows"); } uint64_t* deviceTerms = nullptr; if (!useSeedKernel) { std::vector hostTerms = makeCoordTerms(args.radius, args.n); CUDA_CHECK(cudaMalloc(&deviceTerms, hostTerms.size() * sizeof(uint64_t))); CUDA_CHECK(cudaMemcpy(deviceTerms, hostTerms.data(), hostTerms.size() * sizeof(uint64_t), cudaMemcpyHostToDevice)); } const int64_t totalSeeds = args.endSeed - args.startSeed; const int64_t requestedBatch = args.seedBatch > 0 ? args.seedBatch : std::max(totalSeeds / 100, int64_t(1'000'000)); const int64_t maxSeedsPerLaunch = useSeedKernel ? int64_t(INT_MAX) * seedThreadsPerBlock : std::max(1, INT_MAX / std::max(1, tilesPerSeed)); const int64_t maxBatchSeeds = std::min({requestedBatch, maxSeedsPerLaunch, totalSeeds}); int32_t* deviceSeedFound = nullptr; if (!args.allLocations && !useSeedKernel) { CUDA_CHECK(cudaMalloc(&deviceSeedFound, size_t(maxBatchSeeds) * sizeof(int32_t))); } unsigned long long* deviceHitCount = nullptr; CUDA_CHECK(cudaMalloc(&deviceHitCount, sizeof(unsigned long long))); Hit* deviceHits = nullptr; if (args.maxResults > 0) { CUDA_CHECK(cudaMalloc(&deviceHits, size_t(args.maxResults) * sizeof(Hit))); } std::cout << "CUDA search on " << prop.name << ": seeds [" << args.startSeed << ", " << args.endSeed << "), N=" << args.n << ", required=" << args.required << ", radius=" << args.radius << ", tile=" << kTileW << "x" << kTileH << ", mode=" << (useSeedKernel ? "seed-early-exit" : "tile-shared") << (args.allLocations ? ", all locations\n" : ", first location per seed\n"); const auto started = std::chrono::steady_clock::now(); uint64_t totalHits = 0; int32_t printed = 0; for (int64_t seed = args.startSeed; seed < args.endSeed;) { const int64_t remainingSeeds = args.endSeed - seed; const int64_t batchSeeds = std::min(maxBatchSeeds, remainingSeeds); const int32_t remainingPrints = std::max(0, args.maxResults - printed); CUDA_CHECK(cudaMemset(deviceHitCount, 0, sizeof(unsigned long long))); if (!args.allLocations && !useSeedKernel) { CUDA_CHECK(cudaMemset(deviceSeedFound, 0, size_t(batchSeeds) * sizeof(int32_t))); } if (useSeedKernel) { const int64_t blocks = (batchSeeds + seedThreadsPerBlock - 1) / seedThreadsPerBlock; searchSeedsFullKernel<<(blocks), seedThreadsPerBlock>>>( seed, batchSeeds, window, args.n, args.radius, deviceHitCount, deviceHits, remainingPrints ); } else { const int64_t blocks = batchSeeds * tilesPerSeed; searchTilesKernel<<(blocks), dim3(kTileW, kTileH), dynamicSharedBytes>>>( seed, batchSeeds, deviceTerms, chunkWidth, window, args.n, args.required, args.radius, tilesPerSeed, tilesX, args.allLocations ? 0 : 1, deviceSeedFound, deviceHitCount, deviceHits, remainingPrints ); } CUDA_CHECK(cudaGetLastError()); CUDA_CHECK(cudaDeviceSynchronize()); unsigned long long batchHits = 0; CUDA_CHECK(cudaMemcpy(&batchHits, deviceHitCount, sizeof(batchHits), cudaMemcpyDeviceToHost)); totalHits += batchHits; const int32_t copyHits = static_cast( std::min(batchHits, static_cast(remainingPrints)) ); if (copyHits > 0) { std::vector hostHits(copyHits); CUDA_CHECK(cudaMemcpy(hostHits.data(), deviceHits, size_t(copyHits) * sizeof(Hit), cudaMemcpyDeviceToHost)); printHits(hostHits); printed += copyHits; } seed += batchSeeds; std::cerr << "\rchecked " << (seed - args.startSeed) << "/" << totalSeeds << " seeds" << std::flush; } std::cerr << '\n'; const auto finished = std::chrono::steady_clock::now(); const std::chrono::duration elapsed = finished - started; std::cout << "checked " << totalSeeds << " seeds, hits=" << totalHits << ", elapsed=" << elapsed.count() << "s\n"; CUDA_CHECK(cudaFree(deviceHits)); CUDA_CHECK(cudaFree(deviceHitCount)); CUDA_CHECK(cudaFree(deviceSeedFound)); if (deviceTerms != nullptr) { CUDA_CHECK(cudaFree(deviceTerms)); } return 0; } catch (const std::exception& ex) { std::cerr << "error: " << ex.what() << '\n'; return 1; } }