Преглед изворни кода

Regression tests (#73)

* instruction decode/execute separated into class BytecodeMachine
* added randomx-tests project
* removed the use of non-portable __COUNTER__ macro
* removed the use of unsupported FENV_ACCESS pragma
tevador пре 7 година
родитељ
комит
07293a9378

+ 1 - 0
CMakeLists.txt

@@ -31,6 +31,7 @@ cmake_minimum_required(VERSION 2.8.7)
 set (randomx_sources
 src/aes_hash.cpp
 src/argon2_ref.c
+src/bytecode_machine.cpp
 src/dataset.cpp
 src/soft_aes.cpp
 src/virtual_memory.cpp

+ 29 - 11
makefile

@@ -11,8 +11,8 @@ TESTDIR=src/tests
 OBJDIR=obj
 LDFLAGS=-lpthread
 RXA=$(BINDIR)/librandomx.a
-BINARIES=$(RXA) $(BINDIR)/benchmark $(BINDIR)/code-generator
-RXOBJS=$(addprefix $(OBJDIR)/,aes_hash.o argon2_ref.o dataset.o soft_aes.o virtual_memory.o vm_interpreted.o allocator.o assembly_generator_x86.o instruction.o randomx.o superscalar.o vm_compiled.o vm_interpreted_light.o argon2_core.o blake2_generator.o instructions_portable.o reciprocal.o virtual_machine.o vm_compiled_light.o blake2b.o)
+BINARIES=$(RXA) $(BINDIR)/randomx-benchmark $(BINDIR)/randomx-generator $(BINDIR)/randomx-tests
+RXOBJS=$(addprefix $(OBJDIR)/,aes_hash.o argon2_ref.o bytecode_machine.o dataset.o soft_aes.o virtual_memory.o vm_interpreted.o allocator.o assembly_generator_x86.o instruction.o randomx.o superscalar.o vm_compiled.o vm_interpreted_light.o argon2_core.o blake2_generator.o instructions_portable.o reciprocal.o virtual_machine.o vm_compiled_light.o blake2b.o)
 ifeq ($(PLATFORM),amd64)
     RXOBJS += $(addprefix $(OBJDIR)/,jit_compiler_x86_static.o jit_compiler_x86.o)
     CXXFLAGS += -maes
@@ -51,7 +51,7 @@ debug: $(BINARIES)
 profile: CXXFLAGS += -pg
 profile: CCFLAGS += -pg
 profile: LDFLAGS += -pg
-profile: $(BINDIR)/benchmark
+profile: $(BINDIR)/randomx-benchmark
 
 test: CXXFLAGS += -O0
 
@@ -64,7 +64,7 @@ $(BINDIR):
 $(OBJDIR)/benchmark.o: $(TESTDIR)/benchmark.cpp $(TESTDIR)/stopwatch.hpp \
  $(TESTDIR)/utility.hpp $(SRCDIR)/randomx.h $(SRCDIR)/blake2/endian.h
 	$(CXX) $(CXXFLAGS) -pthread -c $< -o $@
-$(BINDIR)/benchmark: $(OBJDIR)/benchmark.o $(RXA)
+$(BINDIR)/randomx-benchmark: $(OBJDIR)/benchmark.o $(RXA)
 	$(CXX) $(LDFLAGS) -pthread $< $(RXA) -o $@
 $(OBJDIR)/code-generator.o: $(TESTDIR)/code-generator.cpp $(TESTDIR)/utility.hpp \
  $(SRCDIR)/common.hpp $(SRCDIR)/blake2/endian.h \
@@ -74,13 +74,30 @@ $(OBJDIR)/code-generator.o: $(TESTDIR)/code-generator.cpp $(TESTDIR)/utility.hpp
  $(SRCDIR)/blake2_generator.hpp $(SRCDIR)/aes_hash.hpp \
  $(SRCDIR)/blake2/blake2.h $(SRCDIR)/program.hpp
 	$(CXX) $(CXXFLAGS) -c $< -o $@
-$(BINDIR)/code-generator: $(OBJDIR)/code-generator.o $(RXA)
+$(BINDIR)/randomx-generator: $(OBJDIR)/code-generator.o $(RXA)
+	$(CXX) $(LDFLAGS) $< $(RXA) -o $@
+$(OBJDIR)/tests.o: $(TESTDIR)/tests.cpp $(TESTDIR)/utility.hpp \
+ $(SRCDIR)/bytecode_machine.hpp $(SRCDIR)/common.hpp \
+ $(SRCDIR)/blake2/endian.h $(SRCDIR)/configuration.h \
+ $(SRCDIR)/randomx.h $(SRCDIR)/intrin_portable.h \
+ $(SRCDIR)/instruction.hpp $(SRCDIR)/program.hpp \
+ $(SRCDIR)/dataset.hpp $(SRCDIR)/superscalar_program.hpp \
+ $(SRCDIR)/allocator.hpp $(SRCDIR)/blake2/blake2.h \
+ $(SRCDIR)/blake2_generator.hpp $(SRCDIR)/superscalar.hpp \
+ $(SRCDIR)/reciprocal.h $(SRCDIR)/jit_compiler.hpp \
+ $(SRCDIR)/jit_compiler_x86.hpp
+	$(CXX) $(CXXFLAGS) -c $< -o $@
+$(BINDIR)/randomx-tests: $(OBJDIR)/tests.o $(RXA)
 	$(CXX) $(LDFLAGS) $< $(RXA) -o $@
 $(OBJDIR)/aes_hash.o: $(SRCDIR)/aes_hash.cpp $(SRCDIR)/soft_aes.h $(SRCDIR)/intrin_portable.h | $(OBJDIR)
 $(OBJDIR)/argon2_ref.o: $(SRCDIR)/argon2_ref.c $(SRCDIR)/argon2.h $(SRCDIR)/argon2_core.h \
  $(SRCDIR)/blake2/blamka-round-ref.h $(SRCDIR)/blake2/blake2.h \
  $(SRCDIR)/blake2/blake2-impl.h $(SRCDIR)/blake2/endian.h $(SRCDIR)/blake2/blake2-impl.h \
  $(SRCDIR)/blake2/blake2.h
+$(OBJDIR)/bytecode_machine.o: $(SRCDIR)/bytecode_machine.cpp $(SRCDIR)/bytecode_machine.hpp \
+ $(SRCDIR)/common.hpp $(SRCDIR)/blake2/endian.h $(SRCDIR)/configuration.h $(SRCDIR)/randomx.h \
+ $(SRCDIR)/intrin_portable.h $(SRCDIR)/instruction.hpp $(SRCDIR)/program.hpp \
+ $(SRCDIR)/reciprocal.h
 $(OBJDIR)/blake2b.o: $(SRCDIR)/blake2/blake2b.c $(SRCDIR)/blake2/blake2.h \
  $(SRCDIR)/blake2/blake2-impl.h $(SRCDIR)/blake2/endian.h
 	$(CC) $(CCFLAGS) -c $< -o $@
@@ -108,11 +125,11 @@ $(OBJDIR)/jit_compiler_x86_static.o: $(SRCDIR)/jit_compiler_x86_static.S $(SRCDI
 $(OBJDIR)/soft_aes.o: $(SRCDIR)/soft_aes.cpp $(SRCDIR)/soft_aes.h $(SRCDIR)/intrin_portable.h
 $(OBJDIR)/virtual_memory.o: $(SRCDIR)/virtual_memory.cpp $(SRCDIR)/virtual_memory.hpp
 $(OBJDIR)/vm_interpreted.o: $(SRCDIR)/vm_interpreted.cpp $(SRCDIR)/vm_interpreted.hpp \
- $(SRCDIR)/common.hpp $(SRCDIR)/blake2/endian.h $(SRCDIR)/configuration.h $(SRCDIR)/randomx.h \
- $(SRCDIR)/virtual_machine.hpp $(SRCDIR)/program.hpp $(SRCDIR)/instruction.hpp \
+ $(SRCDIR)/bytecode_machine.hpp $(SRCDIR)/common.hpp $(SRCDIR)/blake2/endian.h \
+ $(SRCDIR)/configuration.h $(SRCDIR)/randomx.h $(SRCDIR)/virtual_machine.hpp \
+ $(SRCDIR)/program.hpp $(SRCDIR)/instruction.hpp $(SRCDIR)/instruction_weights.hpp \
  $(SRCDIR)/intrin_portable.h $(SRCDIR)/allocator.hpp $(SRCDIR)/dataset.hpp \
- $(SRCDIR)/superscalar_program.hpp $(SRCDIR)/jit_compiler_x86.hpp $(SRCDIR)/reciprocal.h \
- $(SRCDIR)/instruction_weights.hpp
+ $(SRCDIR)/superscalar_program.hpp $(SRCDIR)/jit_compiler_x86.hpp $(SRCDIR)/reciprocal.h
 $(OBJDIR)/allocator.o: $(SRCDIR)/allocator.cpp $(SRCDIR)/allocator.hpp $(SRCDIR)/intrin_portable.h \
  $(SRCDIR)/virtual_memory.hpp $(SRCDIR)/common.hpp $(SRCDIR)/blake2/endian.h \
  $(SRCDIR)/configuration.h $(SRCDIR)/randomx.h
@@ -129,7 +146,7 @@ $(OBJDIR)/randomx.o: $(SRCDIR)/randomx.cpp $(SRCDIR)/randomx.h $(SRCDIR)/dataset
  $(SRCDIR)/instruction.hpp $(SRCDIR)/jit_compiler_x86.hpp $(SRCDIR)/allocator.hpp \
  $(SRCDIR)/vm_interpreted.hpp $(SRCDIR)/virtual_machine.hpp $(SRCDIR)/program.hpp \
  $(SRCDIR)/intrin_portable.h $(SRCDIR)/vm_interpreted_light.hpp $(SRCDIR)/vm_compiled.hpp \
- $(SRCDIR)/vm_compiled_light.hpp $(SRCDIR)/blake2/blake2.h
+ $(SRCDIR)/vm_compiled_light.hpp $(SRCDIR)/blake2/blake2.h $(SRCDIR)/bytecode_machine.hpp
 $(OBJDIR)/superscalar.o: $(SRCDIR)/superscalar.cpp $(SRCDIR)/configuration.h $(SRCDIR)/program.hpp \
  $(SRCDIR)/common.hpp $(SRCDIR)/blake2/endian.h $(SRCDIR)/randomx.h $(SRCDIR)/instruction.hpp \
  $(SRCDIR)/superscalar.hpp $(SRCDIR)/superscalar_program.hpp $(SRCDIR)/blake2_generator.hpp \
@@ -144,7 +161,8 @@ $(OBJDIR)/vm_interpreted_light.o: $(SRCDIR)/vm_interpreted_light.cpp \
  $(SRCDIR)/blake2/endian.h $(SRCDIR)/configuration.h $(SRCDIR)/randomx.h \
  $(SRCDIR)/virtual_machine.hpp $(SRCDIR)/program.hpp $(SRCDIR)/instruction.hpp \
  $(SRCDIR)/intrin_portable.h $(SRCDIR)/allocator.hpp $(SRCDIR)/dataset.hpp \
- $(SRCDIR)/superscalar_program.hpp $(SRCDIR)/jit_compiler_x86.hpp
+ $(SRCDIR)/superscalar_program.hpp $(SRCDIR)/jit_compiler_x86.hpp \
+ $(SRCDIR)/bytecode_machine.hpp
 $(OBJDIR)/argon2_core.o: $(SRCDIR)/argon2_core.c $(SRCDIR)/argon2_core.h $(SRCDIR)/argon2.h \
  $(SRCDIR)/blake2/blake2.h $(SRCDIR)/blake2/blake2-impl.h $(SRCDIR)/blake2/endian.h
 $(OBJDIR)/blake2_generator.o: $(SRCDIR)/blake2_generator.cpp $(SRCDIR)/blake2/blake2.h \

+ 11 - 0
randomx.sln

@@ -31,6 +31,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runtime-distr", "vcxproj\ru
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "randomx-dll", "vcxproj\randomx-dll.vcxproj", "{59560AD8-18E3-463E-A941-BBD808EC7C83}"
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "vcxproj\tests.vcxproj", "{41F3F4DF-8113-4029-9915-FDDC44C43D49}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|x64 = Debug|x64
@@ -143,6 +145,14 @@ Global
 		{59560AD8-18E3-463E-A941-BBD808EC7C83}.Release|x64.Build.0 = Release|x64
 		{59560AD8-18E3-463E-A941-BBD808EC7C83}.Release|x86.ActiveCfg = Release|Win32
 		{59560AD8-18E3-463E-A941-BBD808EC7C83}.Release|x86.Build.0 = Release|Win32
+		{41F3F4DF-8113-4029-9915-FDDC44C43D49}.Debug|x64.ActiveCfg = Debug|x64
+		{41F3F4DF-8113-4029-9915-FDDC44C43D49}.Debug|x64.Build.0 = Debug|x64
+		{41F3F4DF-8113-4029-9915-FDDC44C43D49}.Debug|x86.ActiveCfg = Debug|Win32
+		{41F3F4DF-8113-4029-9915-FDDC44C43D49}.Debug|x86.Build.0 = Debug|Win32
+		{41F3F4DF-8113-4029-9915-FDDC44C43D49}.Release|x64.ActiveCfg = Release|x64
+		{41F3F4DF-8113-4029-9915-FDDC44C43D49}.Release|x64.Build.0 = Release|x64
+		{41F3F4DF-8113-4029-9915-FDDC44C43D49}.Release|x86.ActiveCfg = Release|Win32
+		{41F3F4DF-8113-4029-9915-FDDC44C43D49}.Release|x86.Build.0 = Release|Win32
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -159,6 +169,7 @@ Global
 		{535F2111-FA81-4C76-A354-EDD2F9AA00E3} = {4A4A689F-86AF-41C0-A974-1080506D0923}
 		{F1FC7AC0-2773-4A57-AFA7-56BB07216AA2} = {4A4A689F-86AF-41C0-A974-1080506D0923}
 		{F207EC8C-C55F-46C0-8851-887A71574F54} = {4A4A689F-86AF-41C0-A974-1080506D0923}
+		{41F3F4DF-8113-4029-9915-FDDC44C43D49} = {4A4A689F-86AF-41C0-A974-1080506D0923}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {4EBC03DB-AE37-4141-8147-692F16E0ED02}

+ 482 - 0
src/bytecode_machine.cpp

@@ -0,0 +1,482 @@
+/*
+Copyright (c) 2019, tevador <tevador@gmail.com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+	* Redistributions of source code must retain the above copyright
+	  notice, this list of conditions and the following disclaimer.
+	* Redistributions in binary form must reproduce the above copyright
+	  notice, this list of conditions and the following disclaimer in the
+	  documentation and/or other materials provided with the distribution.
+	* Neither the name of the copyright holder nor the
+	  names of its contributors may be used to endorse or promote products
+	  derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "bytecode_machine.hpp"
+#include "reciprocal.h"
+
+namespace randomx {
+
+	const int_reg_t BytecodeMachine::zero = 0;
+
+#define INSTR_CASE(x) case InstructionType::x: \
+	exe_ ## x(ibc, pc, scratchpad, config); \
+	break;
+
+	void BytecodeMachine::executeInstruction(RANDOMX_EXE_ARGS) {
+		switch (ibc.type)
+		{
+			INSTR_CASE(IADD_RS)
+			INSTR_CASE(IADD_M)
+			INSTR_CASE(ISUB_R)
+			INSTR_CASE(ISUB_M)
+			INSTR_CASE(IMUL_R)
+			INSTR_CASE(IMUL_M)
+			INSTR_CASE(IMULH_R)
+			INSTR_CASE(IMULH_M)
+			INSTR_CASE(ISMULH_R)
+			INSTR_CASE(ISMULH_M)
+			INSTR_CASE(INEG_R)
+			INSTR_CASE(IXOR_R)
+			INSTR_CASE(IXOR_M)
+			INSTR_CASE(IROR_R)
+			INSTR_CASE(IROL_R)
+			INSTR_CASE(ISWAP_R)
+			INSTR_CASE(FSWAP_R)
+			INSTR_CASE(FADD_R)
+			INSTR_CASE(FADD_M)
+			INSTR_CASE(FSUB_R)
+			INSTR_CASE(FSUB_M)
+			INSTR_CASE(FSCAL_R)
+			INSTR_CASE(FMUL_R)
+			INSTR_CASE(FDIV_M)
+			INSTR_CASE(FSQRT_R)
+			INSTR_CASE(CBRANCH)
+			INSTR_CASE(CFROUND)
+			INSTR_CASE(ISTORE)
+
+		case InstructionType::NOP:
+			break;
+
+		case InstructionType::IMUL_RCP: //executed as IMUL_R
+		default:
+			UNREACHABLE;
+		}
+	}
+
+	void BytecodeMachine::compileInstruction(RANDOMX_GEN_ARGS) {
+		int opcode = instr.opcode;
+
+		if (opcode < ceil_IADD_RS) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::IADD_RS;
+			ibc.idst = &nreg->r[dst];
+			if (dst != RegisterNeedsDisplacement) {
+				ibc.isrc = &nreg->r[src];
+				ibc.shift = instr.getModShift();
+				ibc.imm = 0;
+			}
+			else {
+				ibc.isrc = &nreg->r[src];
+				ibc.shift = instr.getModShift();
+				ibc.imm = signExtend2sCompl(instr.getImm32());
+			}
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_IADD_M) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::IADD_M;
+			ibc.idst = &nreg->r[dst];
+			ibc.imm = signExtend2sCompl(instr.getImm32());
+			if (src != dst) {
+				ibc.isrc = &nreg->r[src];
+				ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
+			}
+			else {
+				ibc.isrc = &zero;
+				ibc.memMask = ScratchpadL3Mask;
+			}
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_ISUB_R) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::ISUB_R;
+			ibc.idst = &nreg->r[dst];
+			if (src != dst) {
+				ibc.isrc = &nreg->r[src];
+			}
+			else {
+				ibc.imm = signExtend2sCompl(instr.getImm32());
+				ibc.isrc = &ibc.imm;
+			}
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_ISUB_M) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::ISUB_M;
+			ibc.idst = &nreg->r[dst];
+			ibc.imm = signExtend2sCompl(instr.getImm32());
+			if (src != dst) {
+				ibc.isrc = &nreg->r[src];
+				ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
+			}
+			else {
+				ibc.isrc = &zero;
+				ibc.memMask = ScratchpadL3Mask;
+			}
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_IMUL_R) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::IMUL_R;
+			ibc.idst = &nreg->r[dst];
+			if (src != dst) {
+				ibc.isrc = &nreg->r[src];
+			}
+			else {
+				ibc.imm = signExtend2sCompl(instr.getImm32());
+				ibc.isrc = &ibc.imm;
+			}
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_IMUL_M) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::IMUL_M;
+			ibc.idst = &nreg->r[dst];
+			ibc.imm = signExtend2sCompl(instr.getImm32());
+			if (src != dst) {
+				ibc.isrc = &nreg->r[src];
+				ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
+			}
+			else {
+				ibc.isrc = &zero;
+				ibc.memMask = ScratchpadL3Mask;
+			}
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_IMULH_R) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::IMULH_R;
+			ibc.idst = &nreg->r[dst];
+			ibc.isrc = &nreg->r[src];
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_IMULH_M) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::IMULH_M;
+			ibc.idst = &nreg->r[dst];
+			ibc.imm = signExtend2sCompl(instr.getImm32());
+			if (src != dst) {
+				ibc.isrc = &nreg->r[src];
+				ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
+			}
+			else {
+				ibc.isrc = &zero;
+				ibc.memMask = ScratchpadL3Mask;
+			}
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_ISMULH_R) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::ISMULH_R;
+			ibc.idst = &nreg->r[dst];
+			ibc.isrc = &nreg->r[src];
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_ISMULH_M) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::ISMULH_M;
+			ibc.idst = &nreg->r[dst];
+			ibc.imm = signExtend2sCompl(instr.getImm32());
+			if (src != dst) {
+				ibc.isrc = &nreg->r[src];
+				ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
+			}
+			else {
+				ibc.isrc = &zero;
+				ibc.memMask = ScratchpadL3Mask;
+			}
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_IMUL_RCP) {
+			uint64_t divisor = instr.getImm32();
+			if (!isPowerOf2(divisor)) {
+				auto dst = instr.dst % RegistersCount;
+				ibc.type = InstructionType::IMUL_R;
+				ibc.idst = &nreg->r[dst];
+				ibc.imm = randomx_reciprocal(divisor);
+				ibc.isrc = &ibc.imm;
+				registerUsage[dst] = i;
+			}
+			else {
+				ibc.type = InstructionType::NOP;
+			}
+			return;
+		}
+
+		if (opcode < ceil_INEG_R) {
+			auto dst = instr.dst % RegistersCount;
+			ibc.type = InstructionType::INEG_R;
+			ibc.idst = &nreg->r[dst];
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_IXOR_R) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::IXOR_R;
+			ibc.idst = &nreg->r[dst];
+			if (src != dst) {
+				ibc.isrc = &nreg->r[src];
+			}
+			else {
+				ibc.imm = signExtend2sCompl(instr.getImm32());
+				ibc.isrc = &ibc.imm;
+			}
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_IXOR_M) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::IXOR_M;
+			ibc.idst = &nreg->r[dst];
+			ibc.imm = signExtend2sCompl(instr.getImm32());
+			if (src != dst) {
+				ibc.isrc = &nreg->r[src];
+				ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
+			}
+			else {
+				ibc.isrc = &zero;
+				ibc.memMask = ScratchpadL3Mask;
+			}
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_IROR_R) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::IROR_R;
+			ibc.idst = &nreg->r[dst];
+			if (src != dst) {
+				ibc.isrc = &nreg->r[src];
+			}
+			else {
+				ibc.imm = instr.getImm32();
+				ibc.isrc = &ibc.imm;
+			}
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_IROL_R) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::IROL_R;
+			ibc.idst = &nreg->r[dst];
+			if (src != dst) {
+				ibc.isrc = &nreg->r[src];
+			}
+			else {
+				ibc.imm = instr.getImm32();
+				ibc.isrc = &ibc.imm;
+			}
+			registerUsage[dst] = i;
+			return;
+		}
+
+		if (opcode < ceil_ISWAP_R) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			if (src != dst) {
+				ibc.idst = &nreg->r[dst];
+				ibc.isrc = &nreg->r[src];
+				ibc.type = InstructionType::ISWAP_R;
+				registerUsage[dst] = i;
+				registerUsage[src] = i;
+			}
+			else {
+				ibc.type = InstructionType::NOP;
+			}
+			return;
+		}
+
+		if (opcode < ceil_FSWAP_R) {
+			auto dst = instr.dst % RegistersCount;
+			ibc.type = InstructionType::FSWAP_R;
+			if (dst < RegisterCountFlt)
+				ibc.fdst = &nreg->f[dst];
+			else
+				ibc.fdst = &nreg->e[dst - RegisterCountFlt];
+			return;
+		}
+
+		if (opcode < ceil_FADD_R) {
+			auto dst = instr.dst % RegisterCountFlt;
+			auto src = instr.src % RegisterCountFlt;
+			ibc.type = InstructionType::FADD_R;
+			ibc.fdst = &nreg->f[dst];
+			ibc.fsrc = &nreg->a[src];
+			return;
+		}
+
+		if (opcode < ceil_FADD_M) {
+			auto dst = instr.dst % RegisterCountFlt;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::FADD_M;
+			ibc.fdst = &nreg->f[dst];
+			ibc.isrc = &nreg->r[src];
+			ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
+			ibc.imm = signExtend2sCompl(instr.getImm32());
+			return;
+		}
+
+		if (opcode < ceil_FSUB_R) {
+			auto dst = instr.dst % RegisterCountFlt;
+			auto src = instr.src % RegisterCountFlt;
+			ibc.type = InstructionType::FSUB_R;
+			ibc.fdst = &nreg->f[dst];
+			ibc.fsrc = &nreg->a[src];
+			return;
+		}
+
+		if (opcode < ceil_FSUB_M) {
+			auto dst = instr.dst % RegisterCountFlt;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::FSUB_M;
+			ibc.fdst = &nreg->f[dst];
+			ibc.isrc = &nreg->r[src];
+			ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
+			ibc.imm = signExtend2sCompl(instr.getImm32());
+			return;
+		}
+
+		if (opcode < ceil_FSCAL_R) {
+			auto dst = instr.dst % RegisterCountFlt;
+			ibc.fdst = &nreg->f[dst];
+			ibc.type = InstructionType::FSCAL_R;
+			return;
+		}
+
+		if (opcode < ceil_FMUL_R) {
+			auto dst = instr.dst % RegisterCountFlt;
+			auto src = instr.src % RegisterCountFlt;
+			ibc.type = InstructionType::FMUL_R;
+			ibc.fdst = &nreg->e[dst];
+			ibc.fsrc = &nreg->a[src];
+			return;
+		}
+
+		if (opcode < ceil_FDIV_M) {
+			auto dst = instr.dst % RegisterCountFlt;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::FDIV_M;
+			ibc.fdst = &nreg->e[dst];
+			ibc.isrc = &nreg->r[src];
+			ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
+			ibc.imm = signExtend2sCompl(instr.getImm32());
+			return;
+		}
+
+		if (opcode < ceil_FSQRT_R) {
+			auto dst = instr.dst % RegisterCountFlt;
+			ibc.type = InstructionType::FSQRT_R;
+			ibc.fdst = &nreg->e[dst];
+			return;
+		}
+
+		if (opcode < ceil_CBRANCH) {
+			ibc.type = InstructionType::CBRANCH;
+			//jump condition
+			int creg = instr.dst % RegistersCount;
+			ibc.idst = &nreg->r[creg];
+			ibc.target = registerUsage[creg];
+			int shift = instr.getModCond() + ConditionOffset;
+			ibc.imm = signExtend2sCompl(instr.getImm32()) | (1ULL << shift);
+			if (ConditionOffset > 0 || shift > 0) //clear the bit below the condition mask - this limits the number of successive jumps to 2
+				ibc.imm &= ~(1ULL << (shift - 1));
+			ibc.memMask = ConditionMask << shift;
+			//mark all registers as used
+			for (unsigned j = 0; j < RegistersCount; ++j) {
+				registerUsage[j] = i;
+			}
+			return;
+		}
+
+		if (opcode < ceil_CFROUND) {
+			auto src = instr.src % RegistersCount;
+			ibc.isrc = &nreg->r[src];
+			ibc.type = InstructionType::CFROUND;
+			ibc.imm = instr.getImm32() & 63;
+			return;
+		}
+
+		if (opcode < ceil_ISTORE) {
+			auto dst = instr.dst % RegistersCount;
+			auto src = instr.src % RegistersCount;
+			ibc.type = InstructionType::ISTORE;
+			ibc.idst = &nreg->r[dst];
+			ibc.isrc = &nreg->r[src];
+			ibc.imm = signExtend2sCompl(instr.getImm32());
+			if (instr.getModCond() < StoreL3Condition)
+				ibc.memMask = (instr.getModMem() ? ScratchpadL1Mask : ScratchpadL2Mask);
+			else
+				ibc.memMask = ScratchpadL3Mask;
+			return;
+		}
+
+		if (opcode < ceil_NOP) {
+			ibc.type = InstructionType::NOP;
+			return;
+		}
+
+		UNREACHABLE;
+	}
+}

+ 322 - 0
src/bytecode_machine.hpp

@@ -0,0 +1,322 @@
+/*
+Copyright (c) 2019, tevador <tevador@gmail.com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+	* Redistributions of source code must retain the above copyright
+	  notice, this list of conditions and the following disclaimer.
+	* Redistributions in binary form must reproduce the above copyright
+	  notice, this list of conditions and the following disclaimer in the
+	  documentation and/or other materials provided with the distribution.
+	* Neither the name of the copyright holder nor the
+	  names of its contributors may be used to endorse or promote products
+	  derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#pragma once
+
+#include "common.hpp"
+#include "intrin_portable.h"
+#include "instruction.hpp"
+#include "program.hpp"
+
+namespace randomx {
+
+	//register file in machine byte order
+	struct NativeRegisterFile {
+		int_reg_t r[RegistersCount] = { 0 };
+		rx_vec_f128 f[RegisterCountFlt];
+		rx_vec_f128 e[RegisterCountFlt];
+		rx_vec_f128 a[RegisterCountFlt];
+	};
+
+	struct InstructionByteCode {
+		union {
+			int_reg_t* idst;
+			rx_vec_f128* fdst;
+		};
+		union {
+			const int_reg_t* isrc;
+			const rx_vec_f128* fsrc;
+		};
+		union {
+			uint64_t imm;
+			int64_t simm;
+		};
+		InstructionType type;
+		union {
+			int16_t target;
+			uint16_t shift;
+		};
+		uint32_t memMask;
+	};
+
+#define OPCODE_CEIL_DECLARE(curr, prev) constexpr int ceil_ ## curr = ceil_ ## prev + RANDOMX_FREQ_ ## curr;
+	constexpr int ceil_NULL = 0;
+	OPCODE_CEIL_DECLARE(IADD_RS, NULL);
+	OPCODE_CEIL_DECLARE(IADD_M, IADD_RS);
+	OPCODE_CEIL_DECLARE(ISUB_R, IADD_M);
+	OPCODE_CEIL_DECLARE(ISUB_M, ISUB_R);
+	OPCODE_CEIL_DECLARE(IMUL_R, ISUB_M);
+	OPCODE_CEIL_DECLARE(IMUL_M, IMUL_R);
+	OPCODE_CEIL_DECLARE(IMULH_R, IMUL_M);
+	OPCODE_CEIL_DECLARE(IMULH_M, IMULH_R);
+	OPCODE_CEIL_DECLARE(ISMULH_R, IMULH_M);
+	OPCODE_CEIL_DECLARE(ISMULH_M, ISMULH_R);
+	OPCODE_CEIL_DECLARE(IMUL_RCP, ISMULH_M);
+	OPCODE_CEIL_DECLARE(INEG_R, IMUL_RCP);
+	OPCODE_CEIL_DECLARE(IXOR_R, INEG_R);
+	OPCODE_CEIL_DECLARE(IXOR_M, IXOR_R);
+	OPCODE_CEIL_DECLARE(IROR_R, IXOR_M);
+	OPCODE_CEIL_DECLARE(IROL_R, IROR_R);
+	OPCODE_CEIL_DECLARE(ISWAP_R, IROL_R);
+	OPCODE_CEIL_DECLARE(FSWAP_R, ISWAP_R);
+	OPCODE_CEIL_DECLARE(FADD_R, FSWAP_R);
+	OPCODE_CEIL_DECLARE(FADD_M, FADD_R);
+	OPCODE_CEIL_DECLARE(FSUB_R, FADD_M);
+	OPCODE_CEIL_DECLARE(FSUB_M, FSUB_R);
+	OPCODE_CEIL_DECLARE(FSCAL_R, FSUB_M);
+	OPCODE_CEIL_DECLARE(FMUL_R, FSCAL_R);
+	OPCODE_CEIL_DECLARE(FDIV_M, FMUL_R);
+	OPCODE_CEIL_DECLARE(FSQRT_R, FDIV_M);
+	OPCODE_CEIL_DECLARE(CBRANCH, FSQRT_R);
+	OPCODE_CEIL_DECLARE(CFROUND, CBRANCH);
+	OPCODE_CEIL_DECLARE(ISTORE, CFROUND);
+	OPCODE_CEIL_DECLARE(NOP, ISTORE);
+#undef OPCODE_CEIL_DECLARE
+
+#define RANDOMX_EXE_ARGS InstructionByteCode& ibc, int& pc, uint8_t* scratchpad, ProgramConfiguration& config
+#define RANDOMX_GEN_ARGS Instruction& instr, int i, InstructionByteCode& ibc
+
+	class BytecodeMachine;
+
+	typedef void(BytecodeMachine::*InstructionGenBytecode)(RANDOMX_GEN_ARGS);
+
+	class BytecodeMachine {
+	public:
+		void beginCompilation(NativeRegisterFile& regFile) {
+			for (unsigned i = 0; i < RegistersCount; ++i) {
+				registerUsage[i] = -1;
+			}
+			nreg = &regFile;
+		}
+
+		void compileProgram(Program& program, InstructionByteCode bytecode[RANDOMX_PROGRAM_SIZE], NativeRegisterFile& regFile) {
+			beginCompilation(regFile);
+			for (unsigned i = 0; i < RANDOMX_PROGRAM_SIZE; ++i) {
+				auto& instr = program(i);
+				auto& ibc = bytecode[i];
+				compileInstruction(instr, i, ibc);
+			}
+		}
+
+		static void executeBytecode(InstructionByteCode bytecode[RANDOMX_PROGRAM_SIZE], uint8_t* scratchpad, ProgramConfiguration& config) {
+			for (int pc = 0; pc < RANDOMX_PROGRAM_SIZE; ++pc) {
+				auto& ibc = bytecode[pc];
+				executeInstruction(ibc, pc, scratchpad, config);
+			}
+		}
+
+		void compileInstruction(RANDOMX_GEN_ARGS)
+#ifdef RANDOMX_GEN_TABLE
+		{
+			auto generator = genTable[instr.opcode];
+			(this->*generator)(instr, i, ibc);
+		}
+#else
+		;
+#endif
+
+		static void executeInstruction(RANDOMX_EXE_ARGS);
+
+		static void exe_IADD_RS(RANDOMX_EXE_ARGS) {
+			*ibc.idst += (*ibc.isrc << ibc.shift) + ibc.imm;
+		}
+
+		static void exe_IADD_M(RANDOMX_EXE_ARGS) {
+			*ibc.idst += load64(getScratchpadAddress(ibc, scratchpad));
+		}
+
+		static void exe_ISUB_R(RANDOMX_EXE_ARGS) {
+			*ibc.idst -= *ibc.isrc;
+		}
+
+		static void exe_ISUB_M(RANDOMX_EXE_ARGS) {
+			*ibc.idst -= load64(getScratchpadAddress(ibc, scratchpad));
+		}
+
+		static void exe_IMUL_R(RANDOMX_EXE_ARGS) {
+			*ibc.idst *= *ibc.isrc;
+		}
+
+		static void exe_IMUL_M(RANDOMX_EXE_ARGS) {
+			*ibc.idst *= load64(getScratchpadAddress(ibc, scratchpad));
+		}
+
+		static void exe_IMULH_R(RANDOMX_EXE_ARGS) {
+			*ibc.idst = mulh(*ibc.idst, *ibc.isrc);
+		}
+
+		static void exe_IMULH_M(RANDOMX_EXE_ARGS) {
+			*ibc.idst = mulh(*ibc.idst, load64(getScratchpadAddress(ibc, scratchpad)));
+		}
+
+		static void exe_ISMULH_R(RANDOMX_EXE_ARGS) {
+			*ibc.idst = smulh(unsigned64ToSigned2sCompl(*ibc.idst), unsigned64ToSigned2sCompl(*ibc.isrc));
+		}
+
+		static void exe_ISMULH_M(RANDOMX_EXE_ARGS) {
+			*ibc.idst = smulh(unsigned64ToSigned2sCompl(*ibc.idst), unsigned64ToSigned2sCompl(load64(getScratchpadAddress(ibc, scratchpad))));
+		}
+
+		static void exe_INEG_R(RANDOMX_EXE_ARGS) {
+			*ibc.idst = ~(*ibc.idst) + 1; //two's complement negative
+		}
+
+		static void exe_IXOR_R(RANDOMX_EXE_ARGS) {
+			*ibc.idst ^= *ibc.isrc;
+		}
+
+		static void exe_IXOR_M(RANDOMX_EXE_ARGS) {
+			*ibc.idst ^= load64(getScratchpadAddress(ibc, scratchpad));
+		}
+
+		static void exe_IROR_R(RANDOMX_EXE_ARGS) {
+			*ibc.idst = rotr(*ibc.idst, *ibc.isrc & 63);
+		}
+
+		static void exe_IROL_R(RANDOMX_EXE_ARGS) {
+			*ibc.idst = rotl(*ibc.idst, *ibc.isrc & 63);
+		}
+
+		static void exe_ISWAP_R(RANDOMX_EXE_ARGS) {
+			int_reg_t temp = *ibc.isrc;
+			*(int_reg_t*)ibc.isrc = *ibc.idst;
+			*ibc.idst = temp;
+		}
+
+		static void exe_FSWAP_R(RANDOMX_EXE_ARGS) {
+			*ibc.fdst = rx_swap_vec_f128(*ibc.fdst);
+		}
+
+		static void exe_FADD_R(RANDOMX_EXE_ARGS) {
+			*ibc.fdst = rx_add_vec_f128(*ibc.fdst, *ibc.fsrc);
+		}
+
+		static void exe_FADD_M(RANDOMX_EXE_ARGS) {
+			rx_vec_f128 fsrc = rx_cvt_packed_int_vec_f128(getScratchpadAddress(ibc, scratchpad));
+			*ibc.fdst = rx_add_vec_f128(*ibc.fdst, fsrc);
+		}
+
+		static void exe_FSUB_R(RANDOMX_EXE_ARGS) {
+			*ibc.fdst = rx_sub_vec_f128(*ibc.fdst, *ibc.fsrc);
+		}
+
+		static void exe_FSUB_M(RANDOMX_EXE_ARGS) {
+			rx_vec_f128 fsrc = rx_cvt_packed_int_vec_f128(getScratchpadAddress(ibc, scratchpad));
+			*ibc.fdst = rx_sub_vec_f128(*ibc.fdst, fsrc);
+		}
+
+		static void exe_FSCAL_R(RANDOMX_EXE_ARGS) {
+			const rx_vec_f128 mask = rx_set1_vec_f128(0x80F0000000000000);
+			*ibc.fdst = rx_xor_vec_f128(*ibc.fdst, mask);
+		}
+
+		static void exe_FMUL_R(RANDOMX_EXE_ARGS) {
+			*ibc.fdst = rx_mul_vec_f128(*ibc.fdst, *ibc.fsrc);
+		}
+
+		static void exe_FDIV_M(RANDOMX_EXE_ARGS) {
+			rx_vec_f128 fsrc = maskRegisterExponentMantissa(
+				config,
+				rx_cvt_packed_int_vec_f128(getScratchpadAddress(ibc, scratchpad))
+			);
+			*ibc.fdst = rx_div_vec_f128(*ibc.fdst, fsrc);
+		}
+
+		static void exe_FSQRT_R(RANDOMX_EXE_ARGS) {
+			*ibc.fdst = rx_sqrt_vec_f128(*ibc.fdst);
+		}
+
+		static void exe_CBRANCH(RANDOMX_EXE_ARGS) {
+			*ibc.idst += ibc.imm;
+			if ((*ibc.idst & ibc.memMask) == 0) {
+				pc = ibc.target;
+			}
+		}
+
+		static void exe_CFROUND(RANDOMX_EXE_ARGS) {
+			rx_set_rounding_mode(rotr(*ibc.isrc, ibc.imm) % 4);
+		}
+
+		static void exe_ISTORE(RANDOMX_EXE_ARGS) {
+			store64(scratchpad + ((*ibc.idst + ibc.imm) & ibc.memMask), *ibc.isrc);
+		}
+	protected:
+		static rx_vec_f128 maskRegisterExponentMantissa(ProgramConfiguration& config, rx_vec_f128 x) {
+			const rx_vec_f128 xmantissaMask = rx_set_vec_f128(dynamicMantissaMask, dynamicMantissaMask);
+			const rx_vec_f128 xexponentMask = rx_load_vec_f128((const double*)&config.eMask);
+			x = rx_and_vec_f128(x, xmantissaMask);
+			x = rx_or_vec_f128(x, xexponentMask);
+			return x;
+		}
+
+	private:
+		static const int_reg_t zero;
+		int registerUsage[RegistersCount];
+		NativeRegisterFile* nreg;
+
+		static void* getScratchpadAddress(InstructionByteCode& ibc, uint8_t* scratchpad) {
+			uint32_t addr = (*ibc.isrc + ibc.imm) & ibc.memMask;
+			return scratchpad + addr;
+		}
+
+#ifdef RANDOMX_GEN_TABLE
+		static InstructionGenBytecode genTable[256];
+
+		void gen_IADD_RS(RANDOMX_GEN_ARGS);
+		void gen_IADD_M(RANDOMX_GEN_ARGS);
+		void gen_ISUB_R(RANDOMX_GEN_ARGS);
+		void gen_ISUB_M(RANDOMX_GEN_ARGS);
+		void gen_IMUL_R(RANDOMX_GEN_ARGS);
+		void gen_IMUL_M(RANDOMX_GEN_ARGS);
+		void gen_IMULH_R(RANDOMX_GEN_ARGS);
+		void gen_IMULH_M(RANDOMX_GEN_ARGS);
+		void gen_ISMULH_R(RANDOMX_GEN_ARGS);
+		void gen_ISMULH_M(RANDOMX_GEN_ARGS);
+		void gen_IMUL_RCP(RANDOMX_GEN_ARGS);
+		void gen_INEG_R(RANDOMX_GEN_ARGS);
+		void gen_IXOR_R(RANDOMX_GEN_ARGS);
+		void gen_IXOR_M(RANDOMX_GEN_ARGS);
+		void gen_IROR_R(RANDOMX_GEN_ARGS);
+		void gen_IROL_R(RANDOMX_GEN_ARGS);
+		void gen_ISWAP_R(RANDOMX_GEN_ARGS);
+		void gen_FSWAP_R(RANDOMX_GEN_ARGS);
+		void gen_FADD_R(RANDOMX_GEN_ARGS);
+		void gen_FADD_M(RANDOMX_GEN_ARGS);
+		void gen_FSUB_R(RANDOMX_GEN_ARGS);
+		void gen_FSUB_M(RANDOMX_GEN_ARGS);
+		void gen_FSCAL_R(RANDOMX_GEN_ARGS);
+		void gen_FMUL_R(RANDOMX_GEN_ARGS);
+		void gen_FDIV_M(RANDOMX_GEN_ARGS);
+		void gen_FSQRT_R(RANDOMX_GEN_ARGS);
+		void gen_CBRANCH(RANDOMX_GEN_ARGS);
+		void gen_CFROUND(RANDOMX_GEN_ARGS);
+		void gen_ISTORE(RANDOMX_GEN_ARGS);
+		void gen_NOP(RANDOMX_GEN_ARGS);
+#endif
+	};
+}

+ 7 - 4
src/common.hpp

@@ -108,12 +108,15 @@ namespace randomx {
 #endif
 
 #if defined(_M_X64) || defined(__x86_64__)
+	#define RANDOMX_HAVE_COMPILER 1
 	class JitCompilerX86;
 	using JitCompiler = JitCompilerX86;
 #elif defined(__aarch64__)
+	#define RANDOMX_HAVE_COMPILER 0
 	class JitCompilerA64;
 	using JitCompiler = JitCompilerA64;
 #else
+	#define RANDOMX_HAVE_COMPILER 0
 	class JitCompilerFallback;
 	using JitCompiler = JitCompilerFallback;
 #endif
@@ -160,14 +163,14 @@ namespace randomx {
 		uint8_t* memory = nullptr;
 	};
 
+	//register file in little-endian byte order
 	struct RegisterFile {
 		int_reg_t r[RegistersCount];
-		fpu_reg_t f[RegistersCount / 2];
-		fpu_reg_t e[RegistersCount / 2];
-		fpu_reg_t a[RegistersCount / 2];
+		fpu_reg_t f[RegisterCountFlt];
+		fpu_reg_t e[RegisterCountFlt];
+		fpu_reg_t a[RegisterCountFlt];
 	};
 
-	typedef void(DatasetReadFunc)(addr_t, MemoryRegisters&, int_reg_t(&reg)[RegistersCount]);
 	typedef void(ProgramFunc)(RegisterFile&, MemoryRegisters&, uint8_t* /* scratchpad */, uint64_t);
 	typedef void(DatasetInitFunc)(randomx_cache* cache, uint8_t* dataset, uint32_t startBlock, uint32_t endBlock);
 

+ 0 - 40
src/instruction_weights.hpp

@@ -71,43 +71,3 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define REPN(x,N) REPNX(x,N)
 #define NUM(x) x
 #define WT(x) NUM(RANDOMX_FREQ_##x)
-
-#define REPCASE0(x)
-#define REPCASE1(x) case __COUNTER__:
-#define REPCASE2(x) REPCASE1(x) case __COUNTER__:
-#define REPCASE3(x) REPCASE2(x) case __COUNTER__:
-#define REPCASE4(x) REPCASE3(x) case __COUNTER__:
-#define REPCASE5(x) REPCASE4(x) case __COUNTER__:
-#define REPCASE6(x) REPCASE5(x) case __COUNTER__:
-#define REPCASE7(x) REPCASE6(x) case __COUNTER__:
-#define REPCASE8(x) REPCASE7(x) case __COUNTER__:
-#define REPCASE9(x) REPCASE8(x) case __COUNTER__:
-#define REPCASE10(x) REPCASE9(x) case __COUNTER__:
-#define REPCASE11(x) REPCASE10(x) case __COUNTER__:
-#define REPCASE12(x) REPCASE11(x) case __COUNTER__:
-#define REPCASE13(x) REPCASE12(x) case __COUNTER__:
-#define REPCASE14(x) REPCASE13(x) case __COUNTER__:
-#define REPCASE15(x) REPCASE14(x) case __COUNTER__:
-#define REPCASE16(x) REPCASE15(x) case __COUNTER__:
-#define REPCASE17(x) REPCASE16(x) case __COUNTER__:
-#define REPCASE18(x) REPCASE17(x) case __COUNTER__:
-#define REPCASE19(x) REPCASE18(x) case __COUNTER__:
-#define REPCASE20(x) REPCASE19(x) case __COUNTER__:
-#define REPCASE21(x) REPCASE20(x) case __COUNTER__:
-#define REPCASE22(x) REPCASE21(x) case __COUNTER__:
-#define REPCASE23(x) REPCASE22(x) case __COUNTER__:
-#define REPCASE24(x) REPCASE23(x) case __COUNTER__:
-#define REPCASE25(x) REPCASE24(x) case __COUNTER__:
-#define REPCASE26(x) REPCASE25(x) case __COUNTER__:
-#define REPCASE27(x) REPCASE26(x) case __COUNTER__:
-#define REPCASE28(x) REPCASE27(x) case __COUNTER__:
-#define REPCASE29(x) REPCASE28(x) case __COUNTER__:
-#define REPCASE30(x) REPCASE29(x) case __COUNTER__:
-#define REPCASE31(x) REPCASE30(x) case __COUNTER__:
-#define REPCASE32(x) REPCASE31(x) case __COUNTER__:
-#define REPCASE64(x) REPCASE32(x) REPCASE32(x)
-#define REPCASE128(x) REPCASE64(x) REPCASE64(x)
-#define REPCASE256(x) REPCASE128(x) REPCASE128(x)
-#define REPCASENX(x,N) REPCASE##N(x)
-#define REPCASEN(x,N) REPCASENX(x,N)
-#define CASE_REP(x) REPCASEN(x, WT(x))

+ 0 - 1
src/instructions_portable.cpp

@@ -26,7 +26,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
-#pragma STDC FENV_ACCESS ON
 #include <cfenv>
 #include <cmath>
 #include "common.hpp"

+ 8 - 0
src/reciprocal.c

@@ -67,3 +67,11 @@ uint64_t randomx_reciprocal(uint64_t divisor) {
 
 	return quotient;
 }
+
+#if !RANDOMX_HAVE_FAST_RECIPROCAL
+
+uint64_t randomx_reciprocal_fast(uint64_t divisor) {
+	return randomx_reciprocal(divisor);
+}
+
+#endif

+ 6 - 0
src/reciprocal.h

@@ -30,6 +30,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <stdint.h>
 
+#if defined(_M_X64) || defined(__x86_64__)
+#define RANDOMX_HAVE_FAST_RECIPROCAL 1
+#else
+#define RANDOMX_HAVE_FAST_RECIPROCAL 0
+#endif
+
 #if defined(__cplusplus)
 extern "C" {
 #endif

+ 1 - 1
src/superscalar_program.hpp

@@ -52,7 +52,7 @@ namespace randomx {
 		int getAddressRegister() {
 			return addrReg;
 		}
-		void setAddressRegister(uint32_t val) {
+		void setAddressRegister(int val) {
 			addrReg = val;
 		}
 

+ 1024 - 0
src/tests/tests.cpp

@@ -0,0 +1,1024 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <cassert>
+#include <iomanip>
+#include "utility.hpp"
+#include "../bytecode_machine.hpp"
+#include "../dataset.hpp"
+#include "../blake2/endian.h"
+#include "../blake2/blake2.h"
+#include "../blake2_generator.hpp"
+#include "../superscalar.hpp"
+#include "../reciprocal.h"
+#include "../intrin_portable.h"
+#include "../jit_compiler.hpp"
+
+struct CacheKey {
+	void* key;
+	size_t size = 0;
+};
+
+randomx_cache* cache;
+randomx_vm* vm = nullptr;
+CacheKey currentKey;
+
+template<size_t N>
+void initCache(const char (&key)[N]) {
+	assert(cache != nullptr);
+	if (N - 1 == currentKey.size && memcmp(currentKey.key, key, N - 1) == 0)
+		return;
+	//std::cout << "randomx_init_cache with key ";
+	//outputHex(std::cout, key, N - 1);
+	//std::cout << std::endl;
+	randomx_init_cache(cache, key, N - 1);
+	currentKey.key = (void*)key;
+	currentKey.size = N - 1;
+	if (vm != nullptr)
+		randomx_vm_set_cache(vm, cache);
+}
+
+template<size_t K, size_t H>
+void calcStringHash(const char(&key)[K], const char(&input)[H], void* output) {
+	initCache(key);
+	assert(vm != nullptr);
+	randomx_calculate_hash(vm, input, H - 1, output);
+}
+
+template<size_t K, size_t H>
+void calcHexHash(const char(&key)[K], const char(&hex)[H], void* output) {
+	initCache(key);
+	assert(vm != nullptr);
+	char input[H / 2];
+	hex2bin((char*)hex, H - 1, input);
+	randomx_calculate_hash(vm, input, sizeof(input), output);
+}
+
+int testNo = 0;
+int skipped = 0;
+
+template<typename FUNC>
+void runTest(const char* name, bool condition, FUNC f) {
+	std::cout << "[";
+	std::cout.width(2);
+	std::cout << std::right << ++testNo << "] ";
+	std::cout.width(40);
+	std::cout << std::left << name << " ... ";
+	std::cout.flush();
+	if (condition) {
+		f();
+		std::cout << "PASSED" << std::endl;
+	}
+	else {
+		std::cout << "SKIPPED" << std::endl;
+		skipped++;
+	}
+}
+
+int main() {
+	char testHash[32];
+
+	//std::cout << "Allocating randomx_cache..." << std::endl;
+	cache = randomx_alloc_cache(RANDOMX_FLAG_DEFAULT);
+
+	runTest("Cache initialization", RANDOMX_ARGON_ITERATIONS == 3 && RANDOMX_ARGON_LANES == 1 && RANDOMX_ARGON_MEMORY == 262144 && stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"),	[]() {
+		initCache("test key 000");
+		uint64_t* cacheMemory = (uint64_t*)cache->memory;
+		assert(cacheMemory[0] == 0x191e0e1d23c02186);
+		assert(cacheMemory[1568413] == 0xf1b62fe6210bf8b1);
+		assert(cacheMemory[33554431] == 0x1f47f056d05cd99b);
+	});
+
+	runTest("SuperscalarHash generator", RANDOMX_SUPERSCALAR_LATENCY == 170, []() {
+		char sprogHash[32];
+		randomx::SuperscalarProgram sprog;
+		const char key[] = "test key 000";
+		constexpr size_t keySize = sizeof(key) - 1;
+		randomx::Blake2Generator gen(key, keySize);
+
+		const char superscalarReferences[10][65] = {
+			"d3a4a6623738756f77e6104469102f082eff2a3e60be7ad696285ef7dfc72a61",
+			"f5e7e0bbc7e93c609003d6359208688070afb4a77165a552ff7be63b38dfbc86",
+			"85ed8b11734de5b3e9836641413a8f36e99e89694f419c8cd25c3f3f16c40c5a",
+			"5dd956292cf5d5704ad99e362d70098b2777b2a1730520be52f772ca48cd3bc0",
+			"6f14018ca7d519e9b48d91af094c0f2d7e12e93af0228782671a8640092af9e5",
+			"134be097c92e2c45a92f23208cacd89e4ce51f1009a0b900dbe83b38de11d791",
+			"268f9392c20c6e31371a5131f82bd7713d3910075f2f0468baafaa1abd2f3187",
+			"c668a05fd909714ed4a91e8d96d67b17e44329e88bc71e0672b529a3fc16be47",
+			"99739351315840963011e4c5d8e90ad0bfed3facdcb713fe8f7138fbf01c4c94",
+			"14ab53d61880471f66e80183968d97effd5492b406876060e595fcf9682f9295",
+		};
+
+		for (int i = 0; i < 10; ++i) {
+			randomx::generateSuperscalar(sprog, gen);
+			blake2b(sprogHash, sizeof(sprogHash), &sprog.programBuffer, sizeof(randomx::Instruction) * sprog.getSize(), nullptr, 0);
+			assert(equalsHex(sprogHash, superscalarReferences[i]));
+		}
+	});
+
+	runTest("Dataset initialization (interpreter)", stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"), []() {
+		initCache("test key 000");
+		uint64_t datasetItem[8];
+		randomx::initDatasetItem(cache, (uint8_t*)&datasetItem, 0);
+		assert(datasetItem[0] == 0x680588a85ae222db);
+		randomx::initDatasetItem(cache, (uint8_t*)&datasetItem, 10000000);
+		assert(datasetItem[0] == 0x7943a1f6186ffb72);
+		randomx::initDatasetItem(cache, (uint8_t*)&datasetItem, 20000000);
+		assert(datasetItem[0] == 0x9035244d718095e1);
+		randomx::initDatasetItem(cache, (uint8_t*)&datasetItem, 30000000);
+		assert(datasetItem[0] == 0x145a5091f7853099);
+	});
+
+	runTest("Dataset initialization (compiler)", RANDOMX_HAVE_COMPILER && stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"), []() {
+		initCache("test key 000");
+		randomx::JitCompiler jit;
+		jit.generateSuperscalarHash(cache->programs, cache->reciprocalCache);
+		jit.generateDatasetInitCode();
+		uint64_t datasetItem[8];
+		jit.getDatasetInitFunc()(cache, (uint8_t*)&datasetItem, 0, 1);
+		assert(datasetItem[0] == 0x680588a85ae222db);
+		jit.getDatasetInitFunc()(cache, (uint8_t*)&datasetItem, 10000000, 10000001);
+		assert(datasetItem[0] == 0x7943a1f6186ffb72);
+		jit.getDatasetInitFunc()(cache, (uint8_t*)&datasetItem, 20000000, 20000001);
+		assert(datasetItem[0] == 0x9035244d718095e1);
+		jit.getDatasetInitFunc()(cache, (uint8_t*)&datasetItem, 30000000, 30000001);
+		assert(datasetItem[0] == 0x145a5091f7853099);
+	});
+
+	runTest("randomx_reciprocal", true, []() {
+		assert(randomx_reciprocal(3) == 12297829382473034410U);
+		assert(randomx_reciprocal(13) == 11351842506898185609U);
+		assert(randomx_reciprocal(33) == 17887751829051686415U);
+		assert(randomx_reciprocal(65537) == 18446462603027742720U);
+		assert(randomx_reciprocal(15000001) == 10316166306300415204U);
+		assert(randomx_reciprocal(3845182035) == 10302264209224146340U);
+		assert(randomx_reciprocal(0xffffffff) == 9223372039002259456U);
+	});
+
+	runTest("randomx_reciprocal_fast", RANDOMX_HAVE_FAST_RECIPROCAL, []() {
+		assert(randomx_reciprocal_fast(3) == 12297829382473034410U);
+		assert(randomx_reciprocal_fast(13) == 11351842506898185609U);
+		assert(randomx_reciprocal_fast(33) == 17887751829051686415U);
+		assert(randomx_reciprocal_fast(65537) == 18446462603027742720U);
+		assert(randomx_reciprocal_fast(15000001) == 10316166306300415204U);
+		assert(randomx_reciprocal_fast(3845182035) == 10302264209224146340U);
+		assert(randomx_reciprocal_fast(0xffffffff) == 9223372039002259456U);
+	});
+
+	randomx::NativeRegisterFile reg;
+	randomx::BytecodeMachine decoder;
+	randomx::InstructionByteCode ibc;
+	alignas(16) randomx::ProgramConfiguration config;
+	constexpr int registerHigh = 192;
+	constexpr int registerDst = 0;
+	constexpr int registerSrc = 1;
+	int pc = 0;
+	constexpr uint32_t imm32 = 3234567890;
+	constexpr uint64_t imm64 = signExtend2sCompl(imm32);
+
+	decoder.beginCompilation(reg);
+
+	runTest("IADD_RS (decode)", RANDOMX_FREQ_IADD_RS > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IADD_RS - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.mod = UINT8_MAX;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IADD_RS);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.shift == 3);
+		assert(ibc.imm == 0);
+	});
+
+	runTest("IADD_RS (execute)", RANDOMX_FREQ_IADD_RS > 0, [&] {
+		reg.r[registerDst] = 0x8000000000000000;
+		reg.r[registerSrc] = 0x1000000000000000;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == 0);
+	});
+
+	runTest("IADD_RS with immediate (decode)", RANDOMX_FREQ_IADD_RS > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IADD_RS - 1;
+		instr.mod = 8;
+		instr.dst = registerHigh | randomx::RegisterNeedsDisplacement;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IADD_RS);
+		assert(ibc.idst == &reg.r[randomx::RegisterNeedsDisplacement]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.shift == 2);
+		assert(ibc.imm == imm64);
+	});
+
+	runTest("IADD_RS with immediate (decode)", RANDOMX_FREQ_IADD_RS > 0, [&] {
+		reg.r[randomx::RegisterNeedsDisplacement] = 0x8000000000000000;
+		reg.r[registerSrc] = 0x2000000000000000;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[randomx::RegisterNeedsDisplacement] == imm64);
+	});
+
+	runTest("IADD_M (decode)", RANDOMX_FREQ_IADD_M > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IADD_M - 1;
+		instr.mod = 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IADD_M);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.imm == imm64);
+		assert(ibc.memMask == randomx::ScratchpadL1Mask);
+	});
+
+	runTest("ISUB_R (decode)", RANDOMX_FREQ_ISUB_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_ISUB_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::ISUB_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+	});
+
+	runTest("ISUB_R (execute)", RANDOMX_FREQ_ISUB_R > 0, [&] {
+		reg.r[registerDst] = 1;
+		reg.r[registerSrc] = 0xFFFFFFFF;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == 0xFFFFFFFF00000002);
+	});
+
+	runTest("ISUB_R with immediate (decode)", RANDOMX_FREQ_ISUB_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_ISUB_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::ISUB_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &ibc.imm);
+	});
+
+	runTest("ISUB_R with immediate (decode)", RANDOMX_FREQ_ISUB_R > 0, [&] {
+		reg.r[registerDst] = 0;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == (~imm64 + 1));
+	});
+
+	runTest("ISUB_M (decode)", RANDOMX_FREQ_ISUB_M > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_ISUB_M - 1;
+		instr.mod = 0;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::ISUB_M);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.imm == imm64);
+		assert(ibc.memMask == randomx::ScratchpadL2Mask);
+	});
+
+	runTest("IMUL_R (decode)", RANDOMX_FREQ_IMUL_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IMUL_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IMUL_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+	});
+
+	runTest("IMUL_R (execute)", RANDOMX_FREQ_IMUL_R > 0, [&] {
+		reg.r[registerDst] = 0xBC550E96BA88A72B;
+		reg.r[registerSrc] = 0xF5391FA9F18D6273;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == 0x28723424A9108E51);
+	});
+
+	runTest("IMUL_R with immediate (decode)", RANDOMX_FREQ_IMUL_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IMUL_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IMUL_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &ibc.imm);
+	});
+
+	runTest("IMUL_R with immediate (execute)", RANDOMX_FREQ_IMUL_R > 0, [&] {
+		reg.r[registerDst] = 1;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == imm64);
+	});
+
+	runTest("IMUL_M (decode)", RANDOMX_FREQ_IMUL_M > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IMUL_M - 1;
+		instr.mod = 0;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IMUL_M);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(*ibc.isrc == 0);
+		assert(ibc.imm == imm64);
+		assert(ibc.memMask == randomx::ScratchpadL3Mask);
+	});
+
+	runTest("IMULH_R (decode)", RANDOMX_FREQ_IMULH_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IMULH_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IMULH_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+	});
+
+	runTest("IMULH_R (execute)", RANDOMX_FREQ_IMULH_R > 0, [&] {
+		reg.r[registerDst] = 0xBC550E96BA88A72B;
+		reg.r[registerSrc] = 0xF5391FA9F18D6273;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == 0xB4676D31D2B34883);
+	});
+
+	runTest("IMULH_R squared (decode)", RANDOMX_FREQ_IMULH_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IMULH_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IMULH_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerDst]);
+	});
+
+	runTest("IMULH_M (decode)", RANDOMX_FREQ_IMULH_M > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IMULH_M - 1;
+		instr.mod = 0;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IMULH_M);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.imm == imm64);
+		assert(ibc.memMask == randomx::ScratchpadL2Mask);
+	});
+
+	runTest("ISMULH_R (decode)", RANDOMX_FREQ_ISMULH_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_ISMULH_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::ISMULH_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+	});
+
+	runTest("ISMULH_R (execute)", RANDOMX_FREQ_ISMULH_R > 0, [&] {
+		reg.r[registerDst] = 0xBC550E96BA88A72B;
+		reg.r[registerSrc] = 0xF5391FA9F18D6273;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == 0x02D93EF1269D3EE5);
+	});
+
+	runTest("ISMULH_R squared (decode)", RANDOMX_FREQ_ISMULH_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_ISMULH_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::ISMULH_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerDst]);
+	});
+
+	runTest("ISMULH_M (decode)", RANDOMX_FREQ_ISMULH_M > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_ISMULH_M - 1;
+		instr.mod = 3;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::ISMULH_M);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.imm == imm64);
+		assert(ibc.memMask == randomx::ScratchpadL1Mask);
+	});
+
+	runTest("IMUL_RCP (decode)", RANDOMX_FREQ_IMUL_RCP > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IMUL_RCP - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IMUL_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &ibc.imm);
+		assert(ibc.imm == randomx_reciprocal(imm32));
+	});
+
+	runTest("IMUL_RCP zero imm32 (decode)", RANDOMX_FREQ_IMUL_RCP > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IMUL_RCP - 1;
+		instr.setImm32(0);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::NOP);
+	});
+
+	runTest("INEG_R (decode)", RANDOMX_FREQ_INEG_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_INEG_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::INEG_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+	});
+
+	runTest("INEG_R (execute)", RANDOMX_FREQ_INEG_R > 0, [&] {
+		reg.r[registerDst] = 0xFFFFFFFFFFFFFFFF;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == 1);
+	});
+	
+	runTest("IXOR_R (decode)", RANDOMX_FREQ_IXOR_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IXOR_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IXOR_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+	});
+
+	runTest("IXOR_R (execute)", RANDOMX_FREQ_IMUL_R > 0, [&] {
+		reg.r[registerDst] = 0x8888888888888888;
+		reg.r[registerSrc] = 0xAAAAAAAAAAAAAAAA;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == 0x2222222222222222);
+	});
+
+	runTest("IXOR_R with immediate (decode)", RANDOMX_FREQ_IXOR_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IXOR_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IXOR_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &ibc.imm);
+	});
+
+	runTest("IXOR_R with immediate (execute)", RANDOMX_FREQ_IXOR_R > 0, [&] {
+		reg.r[registerDst] = 0xFFFFFFFFFFFFFFFF;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == ~imm64);
+	});
+
+	runTest("IXOR_M (decode)", RANDOMX_FREQ_IXOR_M > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IXOR_M - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IXOR_M);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(*ibc.isrc == 0);
+		assert(ibc.imm == imm64);
+		assert(ibc.memMask == randomx::ScratchpadL3Mask);
+	});
+
+	runTest("IROR_R (decode)", RANDOMX_FREQ_IROR_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IROR_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IROR_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+	});
+
+	runTest("IROR_R (execute)", RANDOMX_FREQ_IROR_R > 0, [&] {
+		reg.r[registerDst] = 953360005391419562;
+		reg.r[registerSrc] = 4569451684712230561;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == 0xD835C455069D81EF);
+	});
+
+	runTest("IROL_R (decode)", RANDOMX_FREQ_IROL_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_IROL_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::IROL_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+	});
+
+	runTest("IROL_R (execute)", RANDOMX_FREQ_IROL_R > 0, [&] {
+		reg.r[registerDst] = 953360005391419562;
+		reg.r[registerSrc] = 4569451684712230561;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == 6978065200552740799);
+	});
+
+	runTest("ISWAP_R (decode)", RANDOMX_FREQ_ISWAP_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_ISWAP_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::ISWAP_R);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+	});
+
+	runTest("ISWAP_R (execute)", RANDOMX_FREQ_ISWAP_R > 0, [&] {
+		reg.r[registerDst] = 953360005391419562;
+		reg.r[registerSrc] = 4569451684712230561;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(reg.r[registerDst] == 4569451684712230561);
+		assert(reg.r[registerSrc] == 953360005391419562);
+	});
+
+	runTest("FSWAP_R (decode)", RANDOMX_FREQ_FSWAP_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_FSWAP_R - 1;
+		instr.dst = registerHigh | registerDst;
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::FSWAP_R);
+		assert(ibc.fdst == &reg.f[registerDst]);
+	});
+
+	runTest("FSWAP_R (execute)", RANDOMX_FREQ_FSWAP_R > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		reg.f[registerDst] = rx_set_vec_f128(953360005391419562, 4569451684712230561);
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		rx_store_vec_f128((double*)&vec, reg.f[registerDst]);
+		assert(equalsHex((const char*)&vec, "aa886bb0df033b0da12e95e518f4693f"));
+	});
+
+	runTest("FADD_R (decode)", RANDOMX_FREQ_FADD_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_FADD_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::FADD_R);
+		assert(ibc.fdst == &reg.f[registerDst]);
+		assert(ibc.fsrc == &reg.a[registerSrc]);
+	});
+
+	runTest("FADD_R RoundToNearest (execute)", RANDOMX_FREQ_FADD_R > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		reg.f[registerDst] = rx_set_vec_f128(0x3ffd2c97cc4ef015, 0xc1ce30b3c4223576);
+		reg.a[registerSrc] = rx_set_vec_f128(0x402a26a86a60c8fb, 0x40b8f684057a59e1);
+		rx_set_rounding_mode(RoundToNearest);
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		rx_store_vec_f128((double*)&vec, reg.f[registerDst]);
+		assert(equalsHex(&vec, "b932e048a730cec1fea6ea633bcc2d40"));
+	});
+
+	runTest("FADD_R RoundDown (execute)", RANDOMX_FREQ_FADD_R > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		reg.f[registerDst] = rx_set_vec_f128(0x3ffd2c97cc4ef015, 0xc1ce30b3c4223576);
+		reg.a[registerSrc] = rx_set_vec_f128(0x402a26a86a60c8fb, 0x40b8f684057a59e1);
+		rx_set_rounding_mode(RoundDown);
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		rx_store_vec_f128((double*)&vec, reg.f[registerDst]);
+		assert(equalsHex(&vec, "b932e048a730cec1fda6ea633bcc2d40"));
+	});
+
+	runTest("FADD_R RoundUp (execute)", RANDOMX_FREQ_FADD_R > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		reg.f[registerDst] = rx_set_vec_f128(0x3ffd2c97cc4ef015, 0xc1ce30b3c4223576);
+		reg.a[registerSrc] = rx_set_vec_f128(0x402a26a86a60c8fb, 0x40b8f684057a59e1);
+		rx_set_rounding_mode(RoundUp);
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		rx_store_vec_f128((double*)&vec, reg.f[registerDst]);
+		assert(equalsHex(&vec, "b832e048a730cec1fea6ea633bcc2d40"));
+	});
+
+	runTest("FADD_R RoundToZero (execute)", RANDOMX_FREQ_FADD_R > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		reg.f[registerDst] = rx_set_vec_f128(0x3ffd2c97cc4ef015, 0xc1ce30b3c4223576);
+		reg.a[registerSrc] = rx_set_vec_f128(0x402a26a86a60c8fb, 0x40b8f684057a59e1);
+		rx_set_rounding_mode(RoundToZero);
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		rx_store_vec_f128((double*)&vec, reg.f[registerDst]);
+		assert(equalsHex(&vec, "b832e048a730cec1fda6ea633bcc2d40"));
+	});
+
+	runTest("FADD_M (decode)", RANDOMX_FREQ_FADD_M > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_FADD_M - 1;
+		instr.mod = 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::FADD_M);
+		assert(ibc.fdst == &reg.f[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.imm == imm64);
+		assert(ibc.memMask == randomx::ScratchpadL1Mask);
+	});
+
+	runTest("FADD_M (execute)", RANDOMX_FREQ_FADD_R > 0, [&] {
+		uint64_t mockScratchpad;
+		store64(&mockScratchpad, 0x1234567890abcdef);
+		alignas(16) uint64_t vec[2];
+		reg.f[registerDst] = rx_set_vec_f128(0, 0);
+		reg.r[registerSrc] = 0xFFFFFFFFFFFFE930;
+		rx_set_rounding_mode(RoundToNearest);
+		decoder.executeInstruction(ibc, pc, (uint8_t*)&mockScratchpad, config);
+		rx_store_vec_f128((double*)&vec, reg.f[registerDst]);
+		assert(equalsHex(&vec, "000040840cd5dbc1000000785634b241"));
+	});
+
+	runTest("FSUB_R (decode)", RANDOMX_FREQ_FSUB_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_FSUB_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::FSUB_R);
+		assert(ibc.fdst == &reg.f[registerDst]);
+		assert(ibc.fsrc == &reg.a[registerSrc]);
+	});
+
+	runTest("FSUB_M (decode)", RANDOMX_FREQ_FSUB_M > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_FSUB_M - 1;
+		instr.mod = 2;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::FSUB_M);
+		assert(ibc.fdst == &reg.f[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.imm == imm64);
+		assert(ibc.memMask == randomx::ScratchpadL1Mask);
+	});
+
+	runTest("FSCAL_R (decode)", RANDOMX_FREQ_FSCAL_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_FSCAL_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::FSCAL_R);
+		assert(ibc.fdst == &reg.f[registerDst]);
+	});
+
+	runTest("FSCAL_R (execute)", RANDOMX_FREQ_FSCAL_R > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		reg.f[registerDst] = rx_set_vec_f128(0x41dbc35cef248783, 0x40fdfdabb6173d07);
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		rx_store_vec_f128((double*)&vec, reg.f[registerDst]);
+		assert(equalsHex((const char*)&vec, "073d17b6abfd0dc0838724ef5cc32bc1"));
+	});
+
+	runTest("FMUL_R (decode)", RANDOMX_FREQ_FMUL_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_FMUL_R - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::FMUL_R);
+		assert(ibc.fdst == &reg.e[registerDst]);
+		assert(ibc.fsrc == &reg.a[registerSrc]);
+	});
+
+	runTest("FMUL_R RoundToNearest (execute)", RANDOMX_FREQ_FMUL_R > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		reg.e[registerDst] = rx_set_vec_f128(0x41dbc35cef248783, 0x40fdfdabb6173d07);
+		reg.a[registerSrc] = rx_set_vec_f128(0x40eba861aa31c7c0, 0x41c4561212ae2d50);
+		rx_set_rounding_mode(RoundToNearest);
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		rx_store_vec_f128((double*)&vec, reg.e[registerDst]);
+		assert(equalsHex(&vec, "69697aff350fd3422f1589cdecfed742"));
+	});
+
+	runTest("FMUL_R RoundDown/RoundToZero (execute)", RANDOMX_FREQ_FMUL_R > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		reg.e[registerDst] = rx_set_vec_f128(0x41dbc35cef248783, 0x40fdfdabb6173d07);
+		reg.a[registerSrc] = rx_set_vec_f128(0x40eba861aa31c7c0, 0x41c4561212ae2d50);
+		rx_set_rounding_mode(RoundDown);
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		rx_store_vec_f128((double*)&vec, reg.e[registerDst]);
+		assert(equalsHex(&vec, "69697aff350fd3422e1589cdecfed742"));
+	});
+
+	runTest("FMUL_R RoundUp (execute)", RANDOMX_FREQ_FMUL_R > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		reg.e[registerDst] = rx_set_vec_f128(0x41dbc35cef248783, 0x40fdfdabb6173d07);
+		reg.a[registerSrc] = rx_set_vec_f128(0x40eba861aa31c7c0, 0x41c4561212ae2d50);
+		rx_set_rounding_mode(RoundUp);
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		rx_store_vec_f128((double*)&vec, reg.e[registerDst]);
+		assert(equalsHex(&vec, "6a697aff350fd3422f1589cdecfed742"));
+	});
+
+	runTest("FDIV_M (decode)", RANDOMX_FREQ_FDIV_M > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_FDIV_M - 1;
+		instr.mod = 3;
+		instr.dst = registerHigh | registerDst;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::FDIV_M);
+		assert(ibc.fdst == &reg.e[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.imm == imm64);
+		assert(ibc.memMask == randomx::ScratchpadL1Mask);
+	});
+
+	runTest("FDIV_M RoundToNearest (execute)", RANDOMX_FREQ_FDIV_M > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		alignas(16) uint32_t mockScratchpad[2];
+		store32(&mockScratchpad[0], 0xd350a1b6);
+		store32(&mockScratchpad[1], 0x8b2460d9);
+		store64(&config.eMask[0], 0x3a0000000005d11a);
+		store64(&config.eMask[1], 0x39000000001ba31e);
+		reg.e[registerDst] = rx_set_vec_f128(0x41937f76fede16ee, 0x411b414296ce93b6);
+		reg.r[registerSrc] = 0xFFFFFFFFFFFFE930;
+		rx_set_rounding_mode(RoundToNearest);
+		decoder.executeInstruction(ibc, pc, (uint8_t*)&mockScratchpad, config);
+		rx_store_vec_f128((double*)&vec, reg.e[registerDst]);
+		assert(equalsHex(&vec, "e7b269639484434632474a66635ba547"));
+	});
+
+	runTest("FDIV_M RoundDown/RoundToZero (execute)", RANDOMX_FREQ_FDIV_M > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		alignas(16) uint32_t mockScratchpad[2];
+		store32(&mockScratchpad[0], 0xd350a1b6);
+		store32(&mockScratchpad[1], 0x8b2460d9);
+		store64(&config.eMask[0], 0x3a0000000005d11a);
+		store64(&config.eMask[1], 0x39000000001ba31e);
+		reg.e[registerDst] = rx_set_vec_f128(0x41937f76fede16ee, 0x411b414296ce93b6);
+		reg.r[registerSrc] = 0xFFFFFFFFFFFFE930;
+		rx_set_rounding_mode(RoundDown);
+		decoder.executeInstruction(ibc, pc, (uint8_t*)&mockScratchpad, config);
+		rx_store_vec_f128((double*)&vec, reg.e[registerDst]);
+		assert(equalsHex(&vec, "e6b269639484434632474a66635ba547"));
+	});
+
+	runTest("FDIV_M RoundUp (execute)", RANDOMX_FREQ_FDIV_M > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		alignas(16) uint32_t mockScratchpad[2];
+		store32(&mockScratchpad[0], 0xd350a1b6);
+		store32(&mockScratchpad[1], 0x8b2460d9);
+		store64(&config.eMask[0], 0x3a0000000005d11a);
+		store64(&config.eMask[1], 0x39000000001ba31e);
+		reg.e[registerDst] = rx_set_vec_f128(0x41937f76fede16ee, 0x411b414296ce93b6);
+		reg.r[registerSrc] = 0xFFFFFFFFFFFFE930;
+		rx_set_rounding_mode(RoundUp);
+		decoder.executeInstruction(ibc, pc, (uint8_t*)&mockScratchpad, config);
+		rx_store_vec_f128((double*)&vec, reg.e[registerDst]);
+		assert(equalsHex(&vec, "e7b269639484434633474a66635ba547"));
+	});
+
+	runTest("FSQRT_R (decode)", RANDOMX_FREQ_FSQRT_R > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_FSQRT_R - 1;
+		instr.dst = registerHigh | registerDst;
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::FSQRT_R);
+		assert(ibc.fdst == &reg.e[registerDst]);
+	});
+
+	runTest("FSQRT_R RoundToNearest (execute)", RANDOMX_FREQ_FSQRT_R > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		reg.e[registerDst] = rx_set_vec_f128(0x41b6b21c11affea7, 0x40526a7e778d9824);
+		rx_set_rounding_mode(RoundToNearest);
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		rx_store_vec_f128((double*)&vec, reg.e[registerDst]);
+		assert(equalsHex(&vec, "e81f300b612a21408dbaa33f570ed340"));
+	});
+
+	runTest("FSQRT_R RoundDown/RoundToZero (execute)", RANDOMX_FREQ_FSQRT_R > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		reg.e[registerDst] = rx_set_vec_f128(0x41b6b21c11affea7, 0x40526a7e778d9824);
+		rx_set_rounding_mode(RoundDown);
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		rx_store_vec_f128((double*)&vec, reg.e[registerDst]);
+		assert(equalsHex(&vec, "e81f300b612a21408cbaa33f570ed340"));
+	});
+
+	runTest("FSQRT_R RoundUp (execute)", RANDOMX_FREQ_FSQRT_R > 0, [&] {
+		alignas(16) uint64_t vec[2];
+		reg.e[registerDst] = rx_set_vec_f128(0x41b6b21c11affea7, 0x40526a7e778d9824);
+		rx_set_rounding_mode(RoundUp);
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		rx_store_vec_f128((double*)&vec, reg.e[registerDst]);
+		assert(equalsHex(&vec, "e91f300b612a21408dbaa33f570ed340"));
+	});
+
+	runTest("CBRANCH (decode) 100", RANDOMX_FREQ_CBRANCH > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_CBRANCH - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		instr.mod = 48;
+		decoder.compileInstruction(instr, 100, ibc);
+		assert(ibc.type == randomx::InstructionType::CBRANCH);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.imm == 0xFFFFFFFFC0CB9AD2);
+		assert(ibc.memMask == 0x7F800);
+		assert(ibc.target == pc);
+	});
+
+	runTest("CBRANCH (decode) 200", RANDOMX_FREQ_CBRANCH > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_CBRANCH - 1;
+		instr.dst = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		instr.mod = 48;
+		decoder.compileInstruction(instr, pc = 200, ibc);
+		assert(ibc.type == randomx::InstructionType::CBRANCH);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.imm == 0xFFFFFFFFC0CB9AD2);
+		assert(ibc.memMask == 0x7F800);
+		assert(ibc.target == 100);
+	});
+
+	runTest("CBRANCH not taken (execute)", RANDOMX_FREQ_CBRANCH > 0, [&] {
+		reg.r[registerDst] = 0;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(pc == 200);
+	});
+
+	runTest("CBRANCH taken (execute)", RANDOMX_FREQ_CBRANCH > 0, [&] {
+		reg.r[registerDst] = 0xFFFFFFFFFFFC6800;
+		decoder.executeInstruction(ibc, pc, nullptr, config);
+		assert(pc == ibc.target);
+	});
+
+	runTest("CFROUND (decode)", RANDOMX_FREQ_CFROUND > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_CFROUND - 1;
+		instr.src = registerHigh | registerSrc;
+		instr.setImm32(imm32);
+		decoder.compileInstruction(instr, 100, ibc);
+		assert(ibc.type == randomx::InstructionType::CFROUND);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.imm == 18);
+	});
+
+	runTest("ISTORE L1 (decode)", RANDOMX_FREQ_ISTORE > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_ISTORE - 1;
+		instr.src = registerHigh | registerSrc;
+		instr.dst = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		instr.mod = 1;
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::ISTORE);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.imm == imm64);
+		assert(ibc.memMask == randomx::ScratchpadL1Mask);
+	});
+
+	runTest("ISTORE L2 (decode)", RANDOMX_FREQ_ISTORE > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_ISTORE - 1;
+		instr.src = registerHigh | registerSrc;
+		instr.dst = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		instr.mod = 0;
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::ISTORE);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.imm == imm64);
+		assert(ibc.memMask == randomx::ScratchpadL2Mask);
+	});
+
+	runTest("ISTORE L3 (decode)", RANDOMX_FREQ_ISTORE > 0, [&] {
+		randomx::Instruction instr;
+		instr.opcode = randomx::ceil_ISTORE - 1;
+		instr.src = registerHigh | registerSrc;
+		instr.dst = registerHigh | registerDst;
+		instr.setImm32(imm32);
+		instr.mod = 224;
+		decoder.compileInstruction(instr, pc, ibc);
+		assert(ibc.type == randomx::InstructionType::ISTORE);
+		assert(ibc.idst == &reg.r[registerDst]);
+		assert(ibc.isrc == &reg.r[registerSrc]);
+		assert(ibc.imm == imm64);
+		assert(ibc.memMask == randomx::ScratchpadL3Mask);
+	});
+
+	vm = randomx_create_vm(RANDOMX_FLAG_DEFAULT, cache, nullptr);
+
+	auto test_a = [&] {
+		char hash[RANDOMX_HASH_SIZE];
+		calcStringHash("test key 000", "This is a test", &hash);
+		assert(equalsHex(hash, "207d7cedf2a16590bd33d758e413ad129ce9888e05417984f46296252a7ba3d0"));
+	};
+
+	auto test_b = [&] {
+		char hash[RANDOMX_HASH_SIZE];
+		calcStringHash("test key 000", "Lorem ipsum dolor sit amet", &hash);
+		assert(equalsHex(hash, "76dd2da840d56d38153e0beaca33e7f862c5ead91a052380d99f3a62bf84579b"));
+	};
+
+	auto test_c = [&] {
+		char hash[RANDOMX_HASH_SIZE];
+		calcStringHash("test key 000", "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua", &hash);
+		assert(equalsHex(hash, "109f6a405efe09d302336dce4389127e33aa62d4c782aca7797a628e87839a61"));
+	};
+
+	auto test_d = [&] {
+		char hash[RANDOMX_HASH_SIZE];
+		calcStringHash("test key 001", "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua", &hash);
+		assert(equalsHex(hash, "3cbb82edf9541ab80233cdc47384cea719c8567a8bbaca8f3ff038488ce9c16c"));
+	};
+
+	auto test_e = [&] {
+		char hash[RANDOMX_HASH_SIZE];
+		calcHexHash("test key 001", "0b0b98bea7e805e0010a2126d287a2a0cc833d312cb786385a7c2f9de69d25537f584a9bc9977b00000000666fd8753bf61a8631f12984e3fd44f4014eca629276817b56f32e9b68bd82f416", &hash);
+
+		//outputHex(std::cout, (const char*)hash, sizeof(hash));
+		//std::cout << std::endl;
+
+		assert(equalsHex(hash, "e003ef128b1f96d99d4a0490e03253ef11186002a8ec018cbd4e07b8ec8c82e8"));
+	};
+
+	runTest("Hash test 1a (interpreter)", stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"), test_a);
+
+	runTest("Hash test 1b (interpreter)", stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"), test_b);
+
+	runTest("Hash test 1c (interpreter)", stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"), test_c);
+
+	runTest("Hash test 1d (interpreter)", stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"), test_d);
+
+	runTest("Hash test 1e (interpreter)", stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"), test_e);
+
+	randomx_release_cache(cache);
+	cache = randomx_alloc_cache(RANDOMX_FLAG_JIT);
+	currentKey.size = 0;
+	randomx_destroy_vm(vm);
+	vm = randomx_create_vm(RANDOMX_FLAG_JIT, cache, nullptr);
+
+	runTest("Hash test 2a (compiler)", RANDOMX_HAVE_COMPILER && stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"), test_a);
+
+	runTest("Hash test 2b (compiler)", RANDOMX_HAVE_COMPILER && stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"), test_b);
+
+	runTest("Hash test 2c (compiler)", RANDOMX_HAVE_COMPILER && stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"), test_c);
+
+	runTest("Hash test 2d (compiler)", RANDOMX_HAVE_COMPILER && stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"), test_d);
+
+	runTest("Hash test 2e (compiler)", RANDOMX_HAVE_COMPILER && stringsEqual(RANDOMX_ARGON_SALT, "RandomX\x03"), test_e);
+
+	std::cout << std::endl << "All tests PASSED" << std::endl;
+
+	if (skipped) {
+		std::cout << skipped << " tests were SKIPPED due to incompatible configuration (see above)" << std::endl;
+	}
+}

+ 30 - 0
src/tests/utility.hpp

@@ -41,6 +41,36 @@ inline void outputHex(std::ostream& os, const char* data, int length) {
 	}
 }
 
+char parseNibble(char hex) {
+	hex &= ~0x20;
+	if (hex & 0x40) {
+		hex -= 'A' - 10;
+	}
+	else {
+		hex &= 0xf;
+	}
+	return hex;
+}
+
+void hex2bin(char *in, int length, char *out) {
+	for (int i = 0; i < length; i += 2) {
+		char nibble1 = parseNibble(*in++);
+		char nibble2 = parseNibble(*in++);
+		*out++ = nibble1 << 4 | nibble2;
+	}
+}
+
+constexpr bool stringsEqual(char const * a, char const * b) {
+	return *a == *b && (*a == '\0' || stringsEqual(a + 1, b + 1));
+}
+
+template<size_t N>
+bool equalsHex(const void* hash, const char (&hex)[N]) {
+	char reference[N / 2];
+	hex2bin((char*)hex, N - 1, reference);
+	return memcmp(hash, reference, sizeof(reference)) == 0;
+}
+
 inline void dump(const char* buffer, uint64_t count, const char* name) {
 	std::ofstream fout(name, std::ios::out | std::ios::binary);
 	fout.write(buffer, count);

+ 17 - 563
src/vm_interpreted.cpp

@@ -39,8 +39,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 namespace randomx {
 
-	static int_reg_t Zero = 0;
-
 	template<class Allocator, bool softAes>
 	void InterpretedVm<Allocator, softAes>::setDataset(randomx_dataset* dataset) {
 		datasetPtr = dataset;
@@ -54,223 +52,64 @@ namespace randomx {
 		execute();
 	}
 
-	template<class Allocator, bool softAes>
-	void InterpretedVm<Allocator, softAes>::executeBytecode(int_reg_t(&r)[RegistersCount], rx_vec_f128(&f)[RegisterCountFlt], rx_vec_f128(&e)[RegisterCountFlt], rx_vec_f128(&a)[RegisterCountFlt]) {
-		for (int pc = 0; pc < RANDOMX_PROGRAM_SIZE; ++pc) {
-			executeBytecode(pc, r, f, e, a);
-		}
-	}
-
-	template<class Allocator, bool softAes>
-	FORCE_INLINE void* InterpretedVm<Allocator, softAes>::getScratchpadAddress(InstructionByteCode& ibc) {
-		uint32_t addr = (*ibc.isrc + ibc.imm) & ibc.memMask;
-		return scratchpad + addr;
-	}
-
-	template<class Allocator, bool softAes>
-	FORCE_INLINE rx_vec_f128 InterpretedVm<Allocator, softAes>::maskRegisterExponentMantissa(rx_vec_f128 x) {
-		const rx_vec_f128 xmantissaMask = rx_set_vec_f128(dynamicMantissaMask, dynamicMantissaMask);
-		const rx_vec_f128 xexponentMask = rx_load_vec_f128((const double*)&config.eMask);
-		x = rx_and_vec_f128(x, xmantissaMask);
-		x = rx_or_vec_f128(x, xexponentMask);
-		return x;
-	}
-
-	template<class Allocator, bool softAes>
-	void InterpretedVm<Allocator, softAes>::executeBytecode(int& pc, int_reg_t(&r)[RegistersCount], rx_vec_f128(&f)[RegisterCountFlt], rx_vec_f128(&e)[RegisterCountFlt], rx_vec_f128(&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 = rx_swap_vec_f128(*ibc.fdst);
-			} break;
-
-			case InstructionType::FADD_R: {
-				*ibc.fdst = rx_add_vec_f128(*ibc.fdst, *ibc.fsrc);
-			} break;
-
-			case InstructionType::FADD_M: {
-				rx_vec_f128 fsrc = rx_cvt_packed_int_vec_f128(getScratchpadAddress(ibc));
-				*ibc.fdst = rx_add_vec_f128(*ibc.fdst, fsrc);
-			} break;
-
-			case InstructionType::FSUB_R: {
-				*ibc.fdst = rx_sub_vec_f128(*ibc.fdst, *ibc.fsrc);
-			} break;
-
-			case InstructionType::FSUB_M: {
-				rx_vec_f128 fsrc = rx_cvt_packed_int_vec_f128(getScratchpadAddress(ibc));
-				*ibc.fdst = rx_sub_vec_f128(*ibc.fdst, fsrc);
-			} break;
-
-			case InstructionType::FSCAL_R: {
-				const rx_vec_f128 mask = rx_set1_vec_f128(0x80F0000000000000);
-				*ibc.fdst = rx_xor_vec_f128(*ibc.fdst, mask);
-			} break;
-
-			case InstructionType::FMUL_R: {
-				*ibc.fdst = rx_mul_vec_f128(*ibc.fdst, *ibc.fsrc);
-			} break;
-
-			case InstructionType::FDIV_M: {
-				rx_vec_f128 fsrc = maskRegisterExponentMantissa(rx_cvt_packed_int_vec_f128(getScratchpadAddress(ibc)));
-				*ibc.fdst = rx_div_vec_f128(*ibc.fdst, fsrc);
-			} break;
-
-			case InstructionType::FSQRT_R: {
-				*ibc.fdst = rx_sqrt_vec_f128(*ibc.fdst);
-			} break;
-
-			case InstructionType::CBRANCH: {
-				*ibc.isrc += ibc.imm;
-				if ((*ibc.isrc & ibc.memMask) == 0) {
-					pc = ibc.target;
-				}
-			} break;
-
-			case InstructionType::CFROUND: {
-				rx_set_rounding_mode(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<class Allocator, bool softAes>
 	void InterpretedVm<Allocator, softAes>::execute() {
-		int_reg_t r[RegistersCount] = { 0 };
-		rx_vec_f128 f[RegisterCountFlt];
-		rx_vec_f128 e[RegisterCountFlt];
-		rx_vec_f128 a[RegisterCountFlt];
+
+		NativeRegisterFile nreg;
 
 		for(unsigned i = 0; i < RegisterCountFlt; ++i)
-			a[i] = rx_load_vec_f128(&reg.a[i].lo);
+			nreg.a[i] = rx_load_vec_f128(&reg.a[i].lo);
 
-		precompileProgram(r, f, e, a);
+		compileProgram(program, bytecode, nreg);
 
 		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];
+			uint64_t spMix = nreg.r[config.readReg0] ^ nreg.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);
+				nreg.r[i] ^= load64(scratchpad + spAddr0 + 8 * i);
 
 			for (unsigned i = 0; i < RegisterCountFlt; ++i)
-				f[i] = rx_cvt_packed_int_vec_f128(scratchpad + spAddr1 + 8 * i);
+				nreg.f[i] = rx_cvt_packed_int_vec_f128(scratchpad + spAddr1 + 8 * i);
 
 			for (unsigned i = 0; i < RegisterCountFlt; ++i)
-				e[i] = maskRegisterExponentMantissa(rx_cvt_packed_int_vec_f128(scratchpad + spAddr1 + 8 * (RegisterCountFlt + i)));
+				nreg.e[i] = maskRegisterExponentMantissa(config, rx_cvt_packed_int_vec_f128(scratchpad + spAddr1 + 8 * (RegisterCountFlt + i)));
 
-			executeBytecode(r, f, e, a);
+			executeBytecode(bytecode, scratchpad, config);
 
-			mem.mx ^= r[config.readReg2] ^ r[config.readReg3];
+			mem.mx ^= nreg.r[config.readReg2] ^ nreg.r[config.readReg3];
 			mem.mx &= CacheLineAlignMask;
 			datasetPrefetch(datasetOffset + mem.mx);
-			datasetRead(datasetOffset + mem.ma, r);
+			datasetRead(datasetOffset + mem.ma, nreg.r);
 			std::swap(mem.mx, mem.ma);
 
 			for (unsigned i = 0; i < RegistersCount; ++i)
-				store64(scratchpad + spAddr1 + 8 * i, r[i]);
+				store64(scratchpad + spAddr1 + 8 * i, nreg.r[i]);
 
 			for (unsigned i = 0; i < RegisterCountFlt; ++i)
-				f[i] = rx_xor_vec_f128(f[i], e[i]);
+				nreg.f[i] = rx_xor_vec_f128(nreg.f[i], nreg.e[i]);
 
 			for (unsigned i = 0; i < RegisterCountFlt; ++i)
-				rx_store_vec_f128((double*)(scratchpad + spAddr0 + 16 * i), f[i]);
+				rx_store_vec_f128((double*)(scratchpad + spAddr0 + 16 * i), nreg.f[i]);
 
 			spAddr0 = 0;
 			spAddr1 = 0;
 		}
 
 		for (unsigned i = 0; i < RegistersCount; ++i)
-			store64(&reg.r[i], r[i]);
+			store64(&reg.r[i], nreg.r[i]);
 
 		for (unsigned i = 0; i < RegisterCountFlt; ++i)
-			rx_store_vec_f128(&reg.f[i].lo, f[i]);
+			rx_store_vec_f128(&reg.f[i].lo, nreg.f[i]);
 
 		for (unsigned i = 0; i < RegisterCountFlt; ++i)
-			rx_store_vec_f128(&reg.e[i].lo, e[i]);
+			rx_store_vec_f128(&reg.e[i].lo, nreg.e[i]);
 	}
 
 	template<class Allocator, bool softAes>
@@ -285,391 +124,6 @@ namespace randomx {
 		rx_prefetch_nta(mem.memory + address);
 	}
 
-#include "instruction_weights.hpp"
-
-	template<class Allocator, bool softAes>
-	void InterpretedVm<Allocator, softAes>::precompileProgram(int_reg_t(&r)[RegistersCount], rx_vec_f128(&f)[RegisterCountFlt], rx_vec_f128(&e)[RegisterCountFlt], rx_vec_f128(&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) {
-					uint64_t divisor = instr.getImm32();
-					if (!isPowerOf2(divisor)) {
-						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 = instr.dst % RegistersCount;
-					ibc.isrc = &r[reg];
-					ibc.target = registerUsage[reg];
-					int shift = instr.getModCond() + ConditionOffset;
-					const uint64_t conditionMask = ConditionMask << shift;
-					ibc.imm = signExtend2sCompl(instr.getImm32()) | (1ULL << shift);
-					if (ConditionOffset > 0 || shift > 0) //clear the bit below the condition mask - this limits the number of successive jumps to 2
-						ibc.imm &= ~(1ULL << (shift - 1));
-					ibc.memMask = ConditionMask << shift;
-					//mark all registers as used
-					for (unsigned j = 0; j < RegistersCount; ++j) {
-						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<AlignedAllocator<CacheLineSize>, false>;
 	template class InterpretedVm<AlignedAllocator<CacheLineSize>, true>;
 	template class InterpretedVm<LargePageAllocator, false>;

+ 3 - 30
src/vm_interpreted.hpp

@@ -32,36 +32,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <vector>
 #include "common.hpp"
 #include "virtual_machine.hpp"
+#include "bytecode_machine.hpp"
 #include "intrin_portable.h"
 #include "allocator.hpp"
 
 namespace randomx {
 
-	struct InstructionByteCode {
-		union {
-			int_reg_t* idst;
-			rx_vec_f128* fdst;
-		};
-		union {
-			int_reg_t* isrc;
-			rx_vec_f128* fsrc;
-		};
-		union {
-			uint64_t imm;
-			int64_t simm;
-		};
-		InstructionType type;
-		union {
-			int16_t target;
-			uint16_t shift;
-		};
-		uint32_t memMask;
-	};
-
-	static_assert(sizeof(InstructionByteCode) == 32, "Invalid packing of struct InstructionByteCode");
-
 	template<class Allocator, bool softAes>
-	class InterpretedVm : public VmBase<Allocator, softAes> {
+	class InterpretedVm : public VmBase<Allocator, softAes>, public BytecodeMachine {
 	public:
 		using VmBase<Allocator, softAes>::mem;
 		using VmBase<Allocator, softAes>::scratchpad;
@@ -86,13 +64,8 @@ namespace randomx {
 		virtual void datasetPrefetch(uint64_t blockNumber);
 	private:
 		void execute();
-		void precompileProgram(int_reg_t(&r)[RegistersCount], rx_vec_f128(&f)[RegisterCountFlt], rx_vec_f128(&e)[RegisterCountFlt], rx_vec_f128(&a)[RegisterCountFlt]);
-		void executeBytecode(int_reg_t(&r)[RegistersCount], rx_vec_f128(&f)[RegisterCountFlt], rx_vec_f128(&e)[RegisterCountFlt], rx_vec_f128(&a)[RegisterCountFlt]);
-		void executeBytecode(int& i, int_reg_t(&r)[RegistersCount], rx_vec_f128(&f)[RegisterCountFlt], rx_vec_f128(&e)[RegisterCountFlt], rx_vec_f128(&a)[RegisterCountFlt]);
-		void* getScratchpadAddress(InstructionByteCode& ibc);
-		rx_vec_f128 maskRegisterExponentMantissa(rx_vec_f128);
 
-		InstructionByteCode byteCode[RANDOMX_PROGRAM_SIZE];
+		InstructionByteCode bytecode[RANDOMX_PROGRAM_SIZE];
 	};
 
 	using InterpretedVmDefault = InterpretedVm<AlignedAllocator<CacheLineSize>, true>;

+ 4 - 1
vcxproj/randomx.vcxproj

@@ -106,13 +106,14 @@
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <ClCompile>
-      <WarningLevel>Level3</WarningLevel>
+      <WarningLevel>Level4</WarningLevel>
       <Optimization>MaxSpeed</Optimization>
       <FunctionLevelLinking>true</FunctionLevelLinking>
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <SDLCheck>false</SDLCheck>
       <ConformanceMode>true</ConformanceMode>
       <AssemblerOutput>AssemblyCode</AssemblerOutput>
+      <PreprocessorDefinitions>_MBCS;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <Link>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
@@ -135,6 +136,7 @@ SET ERRORLEVEL = 0</Command>
     <ClCompile Include="..\src\assembly_generator_x86.cpp" />
     <ClCompile Include="..\src\blake2_generator.cpp" />
     <ClCompile Include="..\src\blake2\blake2b.c" />
+    <ClCompile Include="..\src\bytecode_machine.cpp" />
     <ClCompile Include="..\src\vm_compiled_light.cpp" />
     <ClCompile Include="..\src\vm_compiled.cpp" />
     <ClCompile Include="..\src\dataset.cpp" />
@@ -164,6 +166,7 @@ SET ERRORLEVEL = 0</Command>
     <ClInclude Include="..\src\blake2\blamka-round-ref.h" />
     <ClInclude Include="..\src\blake2\endian.h" />
     <ClInclude Include="..\src\blake2_generator.hpp" />
+    <ClInclude Include="..\src\bytecode_machine.hpp" />
     <ClInclude Include="..\src\common.hpp" />
     <ClInclude Include="..\src\jit_compiler.hpp" />
     <ClInclude Include="..\src\jit_compiler_a64.hpp" />

+ 6 - 0
vcxproj/randomx.vcxproj.filters

@@ -78,6 +78,9 @@
     <ClCompile Include="..\src\blake2_generator.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\src\bytecode_machine.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\src\argon2.h">
@@ -179,6 +182,9 @@
     <ClInclude Include="..\src\blake2\blake2.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\src\bytecode_machine.hpp">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <MASM Include="..\src\jit_compiler_x86_static.asm">

+ 132 - 0
vcxproj/tests.vcxproj

@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <VCProjectVersion>15.0</VCProjectVersion>
+    <ProjectGuid>{41F3F4DF-8113-4029-9915-FDDC44C43D49}</ProjectGuid>
+    <RootNamespace>tests</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
+    <ProjectName>tests</ProjectName>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v141</PlatformToolset>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v141</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v141</PlatformToolset>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v141</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup />
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <SDLCheck>true</SDLCheck>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <SDLCheck>true</SDLCheck>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="..\src\tests\tests.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="randomx.vcxproj">
+      <Project>{3346a4ad-c438-4324-8b77-47a16452954b}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\src\tests\utility.hpp" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>

+ 27 - 0
vcxproj/tests.vcxproj.filters

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
+    </Filter>
+    <Filter Include="Resource Files">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\src\tests\utility.hpp">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\src\tests\tests.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+</Project>