/*
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.
*/
#include
#include
#include
#include
#include
#include
#include
#include "vm_interpreted.hpp"
#include "dataset.hpp"
#include "intrin_portable.h"
#include "reciprocal.h"
namespace randomx {
static int_reg_t Zero = 0;
template
void InterpretedVm::setDataset(randomx_dataset* dataset) {
datasetPtr = dataset;
mem.memory = dataset->memory;
}
template
void InterpretedVm::run(void* seed) {
VmBase::generateProgram(seed);
randomx_vm::initialize();
execute();
}
template
void InterpretedVm::executeBytecode(int_reg_t(&r)[RegistersCount], __m128d (&f)[RegisterCountFlt], __m128d (&e)[RegisterCountFlt], __m128d (&a)[RegisterCountFlt]) {
for (int pc = 0; pc < RANDOMX_PROGRAM_SIZE; ++pc) {
executeBytecode(pc, r, f, e, a);
}
}
template
FORCE_INLINE void* InterpretedVm::getScratchpadAddress(InstructionByteCode& ibc) {
uint32_t addr = (*ibc.isrc + ibc.imm) & ibc.memMask;
return scratchpad + addr;
}
template
FORCE_INLINE __m128d InterpretedVm::maskRegisterExponentMantissa(__m128d x) {
constexpr uint64_t mantissaMask64 = (1ULL << 52) - 1;
const __m128d mantissaMask = _mm_castsi128_pd(_mm_set_epi64x(mantissaMask64, mantissaMask64));
const __m128d exponentMask = _mm_load_pd((const double*)&config.eMask);
x = _mm_and_pd(x, mantissaMask);
x = _mm_or_pd(x, exponentMask);
return x;
}
template
void InterpretedVm::executeBytecode(int& pc, int_reg_t(&r)[RegistersCount], __m128d (&f)[RegisterCountFlt], __m128d (&e)[RegisterCountFlt], __m128d (&a)[RegisterCountFlt]) {
auto& ibc = byteCode[pc];
switch (ibc.type)
{
case InstructionType::IADD_RS: {
*ibc.idst += (*ibc.isrc << ibc.shift) + ibc.imm;
} break;
case InstructionType::IADD_M: {
*ibc.idst += load64(getScratchpadAddress(ibc));
} break;
case InstructionType::ISUB_R: {
*ibc.idst -= *ibc.isrc;
} break;
case InstructionType::ISUB_M: {
*ibc.idst -= load64(getScratchpadAddress(ibc));
} break;
case InstructionType::IMUL_R: { //also handles IMUL_RCP
*ibc.idst *= *ibc.isrc;
} break;
case InstructionType::IMUL_M: {
*ibc.idst *= load64(getScratchpadAddress(ibc));
} break;
case InstructionType::IMULH_R: {
*ibc.idst = mulh(*ibc.idst, *ibc.isrc);
} break;
case InstructionType::IMULH_M: {
*ibc.idst = mulh(*ibc.idst, load64(getScratchpadAddress(ibc)));
} break;
case InstructionType::ISMULH_R: {
*ibc.idst = smulh(unsigned64ToSigned2sCompl(*ibc.idst), unsigned64ToSigned2sCompl(*ibc.isrc));
} break;
case InstructionType::ISMULH_M: {
*ibc.idst = smulh(unsigned64ToSigned2sCompl(*ibc.idst), unsigned64ToSigned2sCompl(load64(getScratchpadAddress(ibc))));
} break;
case InstructionType::INEG_R: {
*ibc.idst = ~(*ibc.idst) + 1; //two's complement negative
} break;
case InstructionType::IXOR_R: {
*ibc.idst ^= *ibc.isrc;
} break;
case InstructionType::IXOR_M: {
*ibc.idst ^= load64(getScratchpadAddress(ibc));
} break;
case InstructionType::IROR_R: {
*ibc.idst = rotr(*ibc.idst, *ibc.isrc & 63);
} break;
case InstructionType::IROL_R: {
*ibc.idst = rotl(*ibc.idst, *ibc.isrc & 63);
} break;
case InstructionType::ISWAP_R: {
int_reg_t temp = *ibc.isrc;
*ibc.isrc = *ibc.idst;
*ibc.idst = temp;
} break;
case InstructionType::FSWAP_R: {
*ibc.fdst = _mm_shuffle_pd(*ibc.fdst, *ibc.fdst, 1);
} break;
case InstructionType::FADD_R: {
*ibc.fdst = _mm_add_pd(*ibc.fdst, *ibc.fsrc);
} break;
case InstructionType::FADD_M: {
__m128d fsrc = load_cvt_i32x2(getScratchpadAddress(ibc));
*ibc.fdst = _mm_add_pd(*ibc.fdst, fsrc);
} break;
case InstructionType::FSUB_R: {
*ibc.fdst = _mm_sub_pd(*ibc.fdst, *ibc.fsrc);
} break;
case InstructionType::FSUB_M: {
__m128d fsrc = load_cvt_i32x2(getScratchpadAddress(ibc));
*ibc.fdst = _mm_sub_pd(*ibc.fdst, fsrc);
} break;
case InstructionType::FSCAL_R: {
const __m128d mask = _mm_castsi128_pd(_mm_set1_epi64x(0x81F0000000000000));
*ibc.fdst = _mm_xor_pd(*ibc.fdst, mask);
} break;
case InstructionType::FMUL_R: {
*ibc.fdst = _mm_mul_pd(*ibc.fdst, *ibc.fsrc);
} break;
case InstructionType::FDIV_M: {
__m128d fsrc = maskRegisterExponentMantissa(load_cvt_i32x2(getScratchpadAddress(ibc)));
*ibc.fdst = _mm_div_pd(*ibc.fdst, fsrc);
} break;
case InstructionType::FSQRT_R: {
*ibc.fdst = _mm_sqrt_pd(*ibc.fdst);
} break;
case InstructionType::CBRANCH: {
*ibc.isrc += ibc.imm;
if ((*ibc.isrc & ibc.memMask) == 0) {
pc = ibc.target;
}
} break;
case InstructionType::CFROUND: {
setRoundMode(rotr(*ibc.isrc, ibc.imm) % 4);
} break;
case InstructionType::ISTORE: {
store64(scratchpad + ((*ibc.idst + ibc.imm) & ibc.memMask), *ibc.isrc);
} break;
case InstructionType::NOP: {
//nothing
} break;
default:
UNREACHABLE;
}
}
template
void InterpretedVm::execute() {
int_reg_t r[RegistersCount] = { 0 };
__m128d f[RegisterCountFlt];
__m128d e[RegisterCountFlt];
__m128d a[RegisterCountFlt];
for(unsigned i = 0; i < RegisterCountFlt; ++i)
a[i] = _mm_load_pd(®.a[i].lo);
precompileProgram(r, f, e, a);
uint32_t spAddr0 = mem.mx;
uint32_t spAddr1 = mem.ma;
for(unsigned ic = 0; ic < RANDOMX_PROGRAM_ITERATIONS; ++ic) {
uint64_t spMix = r[config.readReg0] ^ r[config.readReg1];
spAddr0 ^= spMix;
spAddr0 &= ScratchpadL3Mask64;
spAddr1 ^= spMix >> 32;
spAddr1 &= ScratchpadL3Mask64;
for (unsigned i = 0; i < RegistersCount; ++i)
r[i] ^= load64(scratchpad + spAddr0 + 8 * i);
for (unsigned i = 0; i < RegisterCountFlt; ++i)
f[i] = load_cvt_i32x2(scratchpad + spAddr1 + 8 * i);
for (unsigned i = 0; i < RegisterCountFlt; ++i)
e[i] = maskRegisterExponentMantissa(load_cvt_i32x2(scratchpad + spAddr1 + 8 * (RegisterCountFlt + i)));
executeBytecode(r, f, e, a);
mem.mx ^= r[config.readReg2] ^ r[config.readReg3];
mem.mx &= CacheLineAlignMask;
datasetRead(datasetOffset + mem.ma, r);
std::swap(mem.mx, mem.ma);
for (unsigned i = 0; i < RegistersCount; ++i)
store64(scratchpad + spAddr1 + 8 * i, r[i]);
for (unsigned i = 0; i < RegisterCountFlt; ++i)
f[i] = _mm_xor_pd(f[i], e[i]);
for (unsigned i = 0; i < RegisterCountFlt; ++i)
_mm_store_pd((double*)(scratchpad + spAddr0 + 16 * i), f[i]);
spAddr0 = 0;
spAddr1 = 0;
}
for (unsigned i = 0; i < RegistersCount; ++i)
store64(®.r[i], r[i]);
for (unsigned i = 0; i < RegisterCountFlt; ++i)
_mm_store_pd(®.f[i].lo, f[i]);
for (unsigned i = 0; i < RegisterCountFlt; ++i)
_mm_store_pd(®.e[i].lo, e[i]);
}
static int getConditionRegister(int(®isterUsage)[RegistersCount]) {
int min = INT_MAX;
int minIndex;
for (unsigned i = 0; i < RegistersCount; ++i) {
if (registerUsage[i] < min) {
min = registerUsage[i];
minIndex = i;
}
}
return minIndex;
}
template
void InterpretedVm::datasetRead(uint32_t address, int_reg_t(&r)[RegistersCount]) {
uint64_t* datasetLine = (uint64_t*)(mem.memory + address);
for (int i = 0; i < RegistersCount; ++i)
r[i] ^= datasetLine[i];
}
#include "instruction_weights.hpp"
template
void InterpretedVm::precompileProgram(int_reg_t(&r)[RegistersCount], __m128d (&f)[RegisterCountFlt], __m128d (&e)[RegisterCountFlt], __m128d (&a)[RegisterCountFlt]) {
int registerUsage[RegistersCount];
for (unsigned i = 0; i < RegistersCount; ++i) {
registerUsage[i] = -1;
}
for (unsigned i = 0; i < RANDOMX_PROGRAM_SIZE; ++i) {
auto& instr = program(i);
auto& ibc = byteCode[i];
switch (instr.opcode) {
CASE_REP(IADD_RS) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::IADD_RS;
ibc.idst = &r[dst];
if (dst != RegisterNeedsDisplacement) {
ibc.isrc = &r[src];
ibc.shift = instr.getModShift();
ibc.imm = 0;
}
else {
ibc.isrc = &r[src];
ibc.shift = instr.getModShift();
ibc.imm = signExtend2sCompl(instr.getImm32());
}
registerUsage[dst] = i;
} break;
CASE_REP(IADD_M) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::IADD_M;
ibc.idst = &r[dst];
ibc.imm = signExtend2sCompl(instr.getImm32());
if (src != dst) {
ibc.isrc = &r[src];
ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
}
else {
ibc.isrc = &Zero;
ibc.memMask = ScratchpadL3Mask;
}
registerUsage[dst] = i;
} break;
CASE_REP(ISUB_R) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::ISUB_R;
ibc.idst = &r[dst];
if (src != dst) {
ibc.isrc = &r[src];
}
else {
ibc.imm = signExtend2sCompl(instr.getImm32());
ibc.isrc = &ibc.imm;
}
registerUsage[dst] = i;
} break;
CASE_REP(ISUB_M) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::ISUB_M;
ibc.idst = &r[dst];
ibc.imm = signExtend2sCompl(instr.getImm32());
if (src != dst) {
ibc.isrc = &r[src];
ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
}
else {
ibc.isrc = &Zero;
ibc.memMask = ScratchpadL3Mask;
}
registerUsage[dst] = i;
} break;
CASE_REP(IMUL_R) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::IMUL_R;
ibc.idst = &r[dst];
if (src != dst) {
ibc.isrc = &r[src];
}
else {
ibc.imm = signExtend2sCompl(instr.getImm32());
ibc.isrc = &ibc.imm;
}
registerUsage[dst] = i;
} break;
CASE_REP(IMUL_M) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::IMUL_M;
ibc.idst = &r[dst];
ibc.imm = signExtend2sCompl(instr.getImm32());
if (src != dst) {
ibc.isrc = &r[src];
ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
}
else {
ibc.isrc = &Zero;
ibc.memMask = ScratchpadL3Mask;
}
registerUsage[dst] = i;
} break;
CASE_REP(IMULH_R) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::IMULH_R;
ibc.idst = &r[dst];
ibc.isrc = &r[src];
registerUsage[dst] = i;
} break;
CASE_REP(IMULH_M) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::IMULH_M;
ibc.idst = &r[dst];
ibc.imm = signExtend2sCompl(instr.getImm32());
if (src != dst) {
ibc.isrc = &r[src];
ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
}
else {
ibc.isrc = &Zero;
ibc.memMask = ScratchpadL3Mask;
}
registerUsage[dst] = i;
} break;
CASE_REP(ISMULH_R) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::ISMULH_R;
ibc.idst = &r[dst];
ibc.isrc = &r[src];
registerUsage[dst] = i;
} break;
CASE_REP(ISMULH_M) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::ISMULH_M;
ibc.idst = &r[dst];
ibc.imm = signExtend2sCompl(instr.getImm32());
if (src != dst) {
ibc.isrc = &r[src];
ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
}
else {
ibc.isrc = &Zero;
ibc.memMask = ScratchpadL3Mask;
}
registerUsage[dst] = i;
} break;
CASE_REP(IMUL_RCP) {
uint32_t divisor = instr.getImm32();
if (divisor != 0) {
auto dst = instr.dst % RegistersCount;
ibc.type = InstructionType::IMUL_R;
ibc.idst = &r[dst];
ibc.imm = randomx_reciprocal(divisor);
ibc.isrc = &ibc.imm;
registerUsage[dst] = i;
}
else {
ibc.type = InstructionType::NOP;
}
} break;
CASE_REP(INEG_R) {
auto dst = instr.dst % RegistersCount;
ibc.type = InstructionType::INEG_R;
ibc.idst = &r[dst];
registerUsage[dst] = i;
} break;
CASE_REP(IXOR_R) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::IXOR_R;
ibc.idst = &r[dst];
if (src != dst) {
ibc.isrc = &r[src];
}
else {
ibc.imm = signExtend2sCompl(instr.getImm32());
ibc.isrc = &ibc.imm;
}
registerUsage[dst] = i;
} break;
CASE_REP(IXOR_M) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::IXOR_M;
ibc.idst = &r[dst];
ibc.imm = signExtend2sCompl(instr.getImm32());
if (src != dst) {
ibc.isrc = &r[src];
ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
}
else {
ibc.isrc = &Zero;
ibc.memMask = ScratchpadL3Mask;
}
registerUsage[dst] = i;
} break;
CASE_REP(IROR_R) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::IROR_R;
ibc.idst = &r[dst];
if (src != dst) {
ibc.isrc = &r[src];
}
else {
ibc.imm = instr.getImm32();
ibc.isrc = &ibc.imm;
}
registerUsage[dst] = i;
} break;
CASE_REP(IROL_R) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::IROL_R;
ibc.idst = &r[dst];
if (src != dst) {
ibc.isrc = &r[src];
}
else {
ibc.imm = instr.getImm32();
ibc.isrc = &ibc.imm;
}
registerUsage[dst] = i;
} break;
CASE_REP(ISWAP_R) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
if (src != dst) {
ibc.idst = &r[dst];
ibc.isrc = &r[src];
ibc.type = InstructionType::ISWAP_R;
registerUsage[dst] = i;
registerUsage[src] = i;
}
else {
ibc.type = InstructionType::NOP;
}
} break;
CASE_REP(FSWAP_R) {
auto dst = instr.dst % RegistersCount;
ibc.type = InstructionType::FSWAP_R;
if (dst < RegisterCountFlt)
ibc.fdst = &f[dst];
else
ibc.fdst = &e[dst - RegisterCountFlt];
} break;
CASE_REP(FADD_R) {
auto dst = instr.dst % RegisterCountFlt;
auto src = instr.src % RegisterCountFlt;
ibc.type = InstructionType::FADD_R;
ibc.fdst = &f[dst];
ibc.fsrc = &a[src];
} break;
CASE_REP(FADD_M) {
auto dst = instr.dst % RegisterCountFlt;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::FADD_M;
ibc.fdst = &f[dst];
ibc.isrc = &r[src];
ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
ibc.imm = signExtend2sCompl(instr.getImm32());
} break;
CASE_REP(FSUB_R) {
auto dst = instr.dst % RegisterCountFlt;
auto src = instr.src % RegisterCountFlt;
ibc.type = InstructionType::FSUB_R;
ibc.fdst = &f[dst];
ibc.fsrc = &a[src];
} break;
CASE_REP(FSUB_M) {
auto dst = instr.dst % RegisterCountFlt;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::FSUB_M;
ibc.fdst = &f[dst];
ibc.isrc = &r[src];
ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
ibc.imm = signExtend2sCompl(instr.getImm32());
} break;
CASE_REP(FSCAL_R) {
auto dst = instr.dst % RegisterCountFlt;
ibc.fdst = &f[dst];
ibc.type = InstructionType::FSCAL_R;
} break;
CASE_REP(FMUL_R) {
auto dst = instr.dst % RegisterCountFlt;
auto src = instr.src % RegisterCountFlt;
ibc.type = InstructionType::FMUL_R;
ibc.fdst = &e[dst];
ibc.fsrc = &a[src];
} break;
CASE_REP(FDIV_M) {
auto dst = instr.dst % RegisterCountFlt;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::FDIV_M;
ibc.fdst = &e[dst];
ibc.isrc = &r[src];
ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
ibc.imm = signExtend2sCompl(instr.getImm32());
} break;
CASE_REP(FSQRT_R) {
auto dst = instr.dst % RegisterCountFlt;
ibc.type = InstructionType::FSQRT_R;
ibc.fdst = &e[dst];
} break;
CASE_REP(CBRANCH) {
ibc.type = InstructionType::CBRANCH;
//jump condition
int reg = getConditionRegister(registerUsage);
ibc.isrc = &r[reg];
ibc.target = registerUsage[reg];
int shift = instr.getModCond();
const uint64_t conditionMask = ConditionMask << instr.getModCond();
ibc.imm = signExtend2sCompl(instr.getImm32()) | (1ULL << shift);
ibc.memMask = ConditionMask << shift;
for (unsigned j = 0; j < RegistersCount; ++j) { //mark all registers as used
registerUsage[j] = i;
}
} break;
CASE_REP(CFROUND) {
auto src = instr.src % RegistersCount;
ibc.isrc = &r[src];
ibc.type = InstructionType::CFROUND;
ibc.imm = instr.getImm32() & 63;
} break;
CASE_REP(ISTORE) {
auto dst = instr.dst % RegistersCount;
auto src = instr.src % RegistersCount;
ibc.type = InstructionType::ISTORE;
ibc.idst = &r[dst];
ibc.isrc = &r[src];
ibc.imm = signExtend2sCompl(instr.getImm32());
if (instr.getModCond() < StoreL3Condition)
ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
else
ibc.memMask = ScratchpadL3Mask;
} break;
CASE_REP(NOP) {
ibc.type = InstructionType::NOP;
} break;
default:
UNREACHABLE;
}
}
}
template class InterpretedVm, false>;
template class InterpretedVm, true>;
template class InterpretedVm;
template class InterpretedVm;
}