/* Copyright (c) 2018 tevador This file is part of RandomX. RandomX is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. RandomX is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RandomX. If not, see. */ /* Original code from Argon2 reference source code package used under CC0 Licence * https://github.com/P-H-C/phc-winner-argon2 * Copyright 2015 * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves */ #include #include #include #include #include #include #include "common.hpp" #include "dataset.hpp" #include "virtual_memory.hpp" #include "superscalar.hpp" #include "blake2_generator.hpp" #include "reciprocal.h" #include "blake2/endian.h" #include "argon2.h" #include "argon2_core.h" #if defined(__SSE2__) #include #define PREFETCHNTA(x) _mm_prefetch((const char *)(x), _MM_HINT_NTA) #else #define PREFETCH(memory) #endif randomx_dataset::~randomx_dataset() { } static_assert(RANDOMX_ARGON_MEMORY % (RANDOMX_ARGON_LANES * ARGON2_SYNC_POINTS) == 0, "RANDOMX_ARGON_MEMORY - invalid value"); void randomx_cache::initialize(const void *seed, size_t seedSize) { uint32_t memory_blocks, segment_length; argon2_instance_t instance; argon2_context context; context.out = nullptr; context.outlen = 0; context.pwd = CONST_CAST(uint8_t *)seed; context.pwdlen = (uint32_t)seedSize; context.salt = CONST_CAST(uint8_t *)RANDOMX_ARGON_SALT; context.saltlen = (uint32_t)randomx::ArgonSaltSize; context.secret = NULL; context.secretlen = 0; context.ad = NULL; context.adlen = 0; context.t_cost = RANDOMX_ARGON_ITERATIONS; context.m_cost = RANDOMX_ARGON_MEMORY; context.lanes = RANDOMX_ARGON_LANES; context.threads = 1; context.allocate_cbk = NULL; context.free_cbk = NULL; context.flags = ARGON2_DEFAULT_FLAGS; context.version = ARGON2_VERSION_NUMBER; /* 2. Align memory size */ /* Minimum memory_blocks = 8L blocks, where L is the number of lanes */ memory_blocks = context.m_cost; segment_length = memory_blocks / (context.lanes * ARGON2_SYNC_POINTS); instance.version = context.version; instance.memory = NULL; instance.passes = context.t_cost; instance.memory_blocks = memory_blocks; instance.segment_length = segment_length; instance.lane_length = segment_length * ARGON2_SYNC_POINTS; instance.lanes = context.lanes; instance.threads = context.threads; instance.type = Argon2_d; instance.memory = (block*)memory; if (instance.threads > instance.lanes) { instance.threads = instance.lanes; } /* 3. Initialization: Hashing inputs, allocating memory, filling first * blocks */ argon_initialize(&instance, &context); fill_memory_blocks(&instance); reciprocalCache.clear(); randomx::Blake2Generator gen(seed, seedSize, 1000); //TODO for (int i = 0; i < RANDOMX_CACHE_ACCESSES; ++i) { randomx::generateSuperscalar(programs[i], gen); for (unsigned j = 0; j < programs[i].getSize(); ++j) { auto& instr = programs[i](j); if (instr.opcode == randomx::SuperscalarInstructionType::IMUL_RCP) { auto rcp = randomx_reciprocal(instr.getImm32()); instr.setImm32(reciprocalCache.size()); reciprocalCache.push_back(rcp); } } } } namespace randomx { template bool Dataset::allocate() { memory = (uint8_t*)Allocator::allocMemory(RANDOMX_DATASET_SIZE); return true; } template Dataset::~Dataset() { Allocator::freeMemory(memory, RANDOMX_DATASET_SIZE); } template bool Cache::allocate() { memory = (uint8_t*)Allocator::allocMemory(RANDOMX_ARGON_MEMORY * ARGON2_BLOCK_SIZE); return true; } template Cache::~Cache() { Allocator::freeMemory(memory, RANDOMX_ARGON_MEMORY * ARGON2_BLOCK_SIZE); } template DatasetInitFunc Cache::getInitFunc() { return &initDataset; } template DatasetInitFunc CacheWithJit::getInitFunc() { return jit.getDatasetInitFunc(); } template void CacheWithJit::initialize(const void *seed, size_t seedSize) { randomx_cache::initialize(seed, seedSize); jit.generateSuperscalarHash(programs, reciprocalCache); jit.generateDatasetInitCode(); } template class Dataset>; template class Dataset; template class Cache>; template class Cache; template class CacheWithJit>; template class CacheWithJit; constexpr uint64_t superscalarMul0 = 6364136223846793005ULL; constexpr uint64_t superscalarAdd1 = 9298410992540426748ULL; constexpr uint64_t superscalarAdd2 = 12065312585734608966ULL; constexpr uint64_t superscalarAdd3 = 9306329213124610396ULL; constexpr uint64_t superscalarAdd4 = 5281919268842080866ULL; constexpr uint64_t superscalarAdd5 = 10536153434571861004ULL; constexpr uint64_t superscalarAdd6 = 3398623926847679864ULL; constexpr uint64_t superscalarAdd7 = 9549104520008361294ULL; static inline uint8_t* getMixBlock(uint64_t registerValue, uint8_t *memory) { constexpr uint32_t mask = (RANDOMX_ARGON_MEMORY * ArgonBlockSize / CacheLineSize - 1); return memory + (registerValue & mask) * CacheLineSize; } void initDatasetBlock(randomx_cache* cache, uint8_t* out, uint64_t blockNumber) { int_reg_t rl[8]; uint8_t* mixBlock; uint64_t registerValue = blockNumber; rl[0] = (blockNumber + 1) * superscalarMul0; rl[1] = rl[0] ^ superscalarAdd1; rl[2] = rl[0] ^ superscalarAdd2; rl[3] = rl[0] ^ superscalarAdd3; rl[4] = rl[0] ^ superscalarAdd4; rl[5] = rl[0] ^ superscalarAdd5; rl[6] = rl[0] ^ superscalarAdd6; rl[7] = rl[0] ^ superscalarAdd7; for (unsigned i = 0; i < RANDOMX_CACHE_ACCESSES; ++i) { mixBlock = getMixBlock(registerValue, cache->memory); SuperscalarProgram& prog = cache->programs[i]; executeSuperscalar(rl, prog, &cache->reciprocalCache); for (unsigned q = 0; q < 8; ++q) rl[q] ^= load64(mixBlock + 8 * q); registerValue = rl[prog.getAddressRegister()]; } memcpy(out, &rl, CacheLineSize); } void initDataset(randomx_cache* cache, uint8_t* dataset, uint32_t startBlock, uint32_t endBlock) { for (uint32_t blockNumber = startBlock; blockNumber < endBlock; ++blockNumber, dataset += CacheLineSize) initDatasetBlock(cache, dataset, blockNumber); } /*void datasetAlloc(dataset_t& ds, bool largePages) { if (std::numeric_limits::max() < RANDOMX_DATASET_SIZE) throw std::runtime_error("Platform doesn't support enough memory for the dataset"); if (largePages) { ds.dataset.memory = (uint8_t*)allocLargePagesMemory(ds.dataset.size); } else { ds.dataset.memory = (uint8_t*)_mm_malloc(ds.dataset.size, 64); if (ds.dataset.memory == nullptr) { throw std::runtime_error("Dataset memory allocation failed. >4 GiB of free virtual memory is needed."); } } } void datasetInitCache(const void* seed, dataset_t& ds, bool largePages) { ds.cache.memory = allocCache(ds.cache.size, largePages); argonFill(ds.cache, seed, SeedSize); }*/ }