From 9c383dc2ba04ddfb28a8327e563a1ead68d93377 Mon Sep 17 00:00:00 2001 From: tevador Date: Sat, 13 Apr 2019 12:02:08 +0200 Subject: [PATCH] Added superscalar-stats Fixed makefile --- makefile | 18 +- randomx.sln | 11 + src/Blake2Generator.cpp | 1 + src/superscalarGenerator.cpp | 1 - src/tests/superscalar-stats.cpp | 61 +++ src/variant4_random_math.h | 441 ---------------------- vcxproj/randomx.vcxproj | 1 - vcxproj/randomx.vcxproj.filters | 3 - vcxproj/superscalar-stats.vcxproj | 126 +++++++ vcxproj/superscalar-stats.vcxproj.filters | 31 ++ 10 files changed, 239 insertions(+), 455 deletions(-) create mode 100644 src/tests/superscalar-stats.cpp delete mode 100644 src/variant4_random_math.h create mode 100644 vcxproj/superscalar-stats.vcxproj create mode 100644 vcxproj/superscalar-stats.vcxproj.filters diff --git a/makefile b/makefile index 7dde5ae..42fd931 100644 --- a/makefile +++ b/makefile @@ -9,7 +9,7 @@ OBJDIR=obj LDFLAGS=-lpthread CPPSRC=src/argon2_core.c src/Cache.cpp src/divideByConstantCodegen.c src/Instruction.cpp src/JitCompilerX86.cpp src/Program.cpp src/VirtualMachine.cpp src/argon2_ref.c src/CompiledVirtualMachine.cpp src/executeProgram-linux.cpp src/instructionsPortable.cpp src/LightClientAsyncWorker.cpp src/softAes.cpp src/virtualMemory.cpp src/AssemblyGeneratorX86.cpp src/dataset.cpp src/hashAes1Rx4.cpp src/InterpretedVirtualMachine.cpp src/main.cpp src/TestAluFpu.cpp src/blake2/blake2b.c TOBJS=$(addprefix $(OBJDIR)/,instructionsPortable.o TestAluFpu.o) -ROBJS=$(addprefix $(OBJDIR)/,argon2_core.o argon2_ref.o AssemblyGeneratorX86.o blake2b.o CompiledVirtualMachine.o CompiledLightVirtualMachine.o dataset.o JitCompilerX86.o instructionsPortable.o Instruction.o InterpretedVirtualMachine.o main.o softAes.o VirtualMachine.o Cache.o virtualMemory.o reciprocal.o LightClientAsyncWorker.o hashAes1Rx4.o LightProgramGenerator.o) +ROBJS=$(addprefix $(OBJDIR)/,argon2_core.o argon2_ref.o AssemblyGeneratorX86.o blake2b.o CompiledVirtualMachine.o CompiledLightVirtualMachine.o dataset.o JitCompilerX86.o instructionsPortable.o Instruction.o InterpretedVirtualMachine.o main.o softAes.o VirtualMachine.o Cache.o virtualMemory.o reciprocal.o hashAes1Rx4.o superscalarGenerator.o Blake2Generator.o) ifeq ($(PLATFORM),amd64) ROBJS += $(OBJDIR)/JitCompilerX86-static.o $(OBJDIR)/squareHash.o CXXFLAGS += -maes @@ -58,7 +58,7 @@ $(OBJDIR)/argon2_core.o: $(addprefix $(SRCDIR)/,argon2_core.c argon2_core.h blak $(OBJDIR)/argon2_ref.o: $(addprefix $(SRCDIR)/,argon2_ref.c argon2.h argon2_core.h blake2/blake2.h blake2/blake2-impl.h blake2/blamka-round-ref.h blake2/endian.h) | $(OBJDIR) $(CC) $(CCFLAGS) -c $(SRCDIR)/argon2_ref.c -o $@ -$(OBJDIR)/AssemblyGeneratorX86.o: $(addprefix $(SRCDIR)/,AssemblyGeneratorX86.cpp AssemblyGeneratorX86.hpp Instruction.hpp common.hpp instructionWeights.hpp blake2/endian.h reciprocal.h Program.hpp configuration.h) | $(OBJDIR) +$(OBJDIR)/AssemblyGeneratorX86.o: $(addprefix $(SRCDIR)/,AssemblyGeneratorX86.cpp AssemblyGeneratorX86.hpp Instruction.hpp common.hpp instructionWeights.hpp blake2/endian.h reciprocal.h Program.hpp configuration.h superscalarGenerator.hpp) | $(OBJDIR) $(CXX) $(CXXFLAGS) -c $(SRCDIR)/AssemblyGeneratorX86.cpp -o $@ $(OBJDIR)/blake2b.o: $(addprefix $(SRCDIR)/blake2/,blake2b.c blake2.h blake2-impl.h endian.h) | $(OBJDIR) @@ -79,7 +79,7 @@ $(OBJDIR)/reciprocal.o: $(addprefix $(SRCDIR)/,reciprocal.c reciprocal.h) | $(OB $(OBJDIR)/hashAes1Rx4.o: $(addprefix $(SRCDIR)/,hashAes1Rx4.cpp softAes.h intrinPortable.h blake2/endian.h) | $(OBJDIR) $(CXX) $(CXXFLAGS) -c $(SRCDIR)/hashAes1Rx4.cpp -o $@ -$(OBJDIR)/JitCompilerX86.o: $(addprefix $(SRCDIR)/,JitCompilerX86.cpp JitCompilerX86.hpp Instruction.hpp instructionWeights.hpp common.hpp blake2/endian.h Program.hpp reciprocal.h virtualMemory.hpp configuration.h) | $(OBJDIR) +$(OBJDIR)/JitCompilerX86.o: $(addprefix $(SRCDIR)/,JitCompilerX86.cpp JitCompilerX86.hpp Instruction.hpp instructionWeights.hpp common.hpp blake2/endian.h Program.hpp reciprocal.h virtualMemory.hpp configuration.h superscalarGenerator.hpp) | $(OBJDIR) $(CXX) $(CXXFLAGS) -c $(SRCDIR)/JitCompilerX86.cpp -o $@ $(OBJDIR)/JitCompilerX86-static.o: $(addprefix $(SRCDIR)/,JitCompilerX86-static.S $(addprefix asm/program_, prologue_linux.inc prologue_load.inc epilogue_linux.inc epilogue_store.inc read_dataset.inc loop_load.inc loop_store.inc xmm_constants.inc read_dataset_light.inc read_dataset_light_sub.inc)) | $(OBJDIR) @@ -94,16 +94,16 @@ $(OBJDIR)/instructionsPortable.o: $(addprefix $(SRCDIR)/,instructionsPortable.cp $(OBJDIR)/Instruction.o: $(addprefix $(SRCDIR)/,Instruction.cpp Instruction.hpp instructionWeights.hpp blake2/endian.h common.hpp configuration.h) | $(OBJDIR) $(CXX) $(CXXFLAGS) -c $(SRCDIR)/Instruction.cpp -o $@ -$(OBJDIR)/InterpretedVirtualMachine.o: $(addprefix $(SRCDIR)/,InterpretedVirtualMachine.cpp InterpretedVirtualMachine.hpp instructionWeights.hpp VirtualMachine.hpp common.hpp blake2/endian.h Program.hpp Instruction.hpp intrinPortable.h dataset.hpp Cache.hpp virtualMemory.hpp LightClientAsyncWorker.hpp configuration.h) | $(OBJDIR) +$(OBJDIR)/InterpretedVirtualMachine.o: $(addprefix $(SRCDIR)/,InterpretedVirtualMachine.cpp InterpretedVirtualMachine.hpp instructionWeights.hpp VirtualMachine.hpp common.hpp blake2/endian.h Program.hpp Instruction.hpp intrinPortable.h dataset.hpp Cache.hpp virtualMemory.hpp configuration.h) | $(OBJDIR) $(CXX) $(CXXFLAGS) -c $(SRCDIR)/InterpretedVirtualMachine.cpp -o $@ -$(OBJDIR)/LightClientAsyncWorker.o: $(addprefix $(SRCDIR)/,LightClientAsyncWorker.cpp LightClientAsyncWorker.hpp common.hpp) | $(OBJDIR) - $(CXX) $(CXXFLAGS) -c $(SRCDIR)/LightClientAsyncWorker.cpp -o $@ +$(OBJDIR)/superscalarGenerator.o: $(addprefix $(SRCDIR)/,superscalarGenerator.cpp superscalarGenerator.hpp Program.hpp blake2/blake2.h blake2/endian.h configuration.h Blake2Generator.hpp) | $(OBJDIR) + $(CXX) $(CXXFLAGS) -c $(SRCDIR)/superscalarGenerator.cpp -o $@ -$(OBJDIR)/LightProgramGenerator.o: $(addprefix $(SRCDIR)/,LightProgramGenerator.cpp LightProgramGenerator.hpp Program.hpp blake2/blake2.h blake2/endian.h configuration.h) | $(OBJDIR) - $(CXX) $(CXXFLAGS) -c $(SRCDIR)/LightProgramGenerator.cpp -o $@ +$(OBJDIR)/Blake2Generator.o: $(addprefix $(SRCDIR)/,Blake2Generator.cpp blake2/blake2.h blake2/endian.h common.hpp Blake2Generator.hpp) | $(OBJDIR) + $(CXX) $(CXXFLAGS) -c $(SRCDIR)/Blake2Generator.cpp -o $@ -$(OBJDIR)/main.o: $(addprefix $(SRCDIR)/,main.cpp InterpretedVirtualMachine.hpp Stopwatch.hpp blake2/blake2.h VirtualMachine.hpp common.hpp blake2/endian.h Program.hpp Instruction.hpp intrinPortable.h CompiledVirtualMachine.hpp JitCompilerX86.hpp AssemblyGeneratorX86.hpp dataset.hpp Cache.hpp virtualMemory.hpp hashAes1Rx4.hpp softAes.h configuration.h) | $(OBJDIR) +$(OBJDIR)/main.o: $(addprefix $(SRCDIR)/,main.cpp InterpretedVirtualMachine.hpp Stopwatch.hpp blake2/blake2.h VirtualMachine.hpp common.hpp blake2/endian.h Program.hpp Instruction.hpp intrinPortable.h CompiledVirtualMachine.hpp JitCompilerX86.hpp AssemblyGeneratorX86.hpp dataset.hpp Cache.hpp virtualMemory.hpp hashAes1Rx4.hpp softAes.h configuration.h superscalarGenerator.hpp) | $(OBJDIR) $(CXX) $(CXXFLAGS) -c $(SRCDIR)/main.cpp -o $@ $(OBJDIR)/Program.o: $(addprefix $(SRCDIR)/,Program.cpp Program.hpp configuration.h) | $(OBJDIR) diff --git a/randomx.sln b/randomx.sln index c4d5a2a..f50aa70 100644 --- a/randomx.sln +++ b/randomx.sln @@ -11,6 +11,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "superscalar-avalanche", "vc EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "superscalar-init", "vcxproj\superscalar-init.vcxproj", "{E59DC709-9B12-4A53-BAF3-79398821C376}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "superscalar-stats", "vcxproj\superscalar-stats.vcxproj", "{0173D560-8C12-46B3-B467-0C6E7573AA0B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -43,6 +45,14 @@ Global {E59DC709-9B12-4A53-BAF3-79398821C376}.Release|x64.Build.0 = Release|x64 {E59DC709-9B12-4A53-BAF3-79398821C376}.Release|x86.ActiveCfg = Release|Win32 {E59DC709-9B12-4A53-BAF3-79398821C376}.Release|x86.Build.0 = Release|Win32 + {0173D560-8C12-46B3-B467-0C6E7573AA0B}.Debug|x64.ActiveCfg = Debug|x64 + {0173D560-8C12-46B3-B467-0C6E7573AA0B}.Debug|x64.Build.0 = Debug|x64 + {0173D560-8C12-46B3-B467-0C6E7573AA0B}.Debug|x86.ActiveCfg = Debug|Win32 + {0173D560-8C12-46B3-B467-0C6E7573AA0B}.Debug|x86.Build.0 = Debug|Win32 + {0173D560-8C12-46B3-B467-0C6E7573AA0B}.Release|x64.ActiveCfg = Release|x64 + {0173D560-8C12-46B3-B467-0C6E7573AA0B}.Release|x64.Build.0 = Release|x64 + {0173D560-8C12-46B3-B467-0C6E7573AA0B}.Release|x86.ActiveCfg = Release|Win32 + {0173D560-8C12-46B3-B467-0C6E7573AA0B}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -50,6 +60,7 @@ Global GlobalSection(NestedProjects) = preSolution {CF34A7EF-7DC9-4077-94A5-76F5425EA938} = {4A4A689F-86AF-41C0-A974-1080506D0923} {E59DC709-9B12-4A53-BAF3-79398821C376} = {4A4A689F-86AF-41C0-A974-1080506D0923} + {0173D560-8C12-46B3-B467-0C6E7573AA0B} = {4A4A689F-86AF-41C0-A974-1080506D0923} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4EBC03DB-AE37-4141-8147-692F16E0ED02} diff --git a/src/Blake2Generator.cpp b/src/Blake2Generator.cpp index 2879088..98f0869 100644 --- a/src/Blake2Generator.cpp +++ b/src/Blake2Generator.cpp @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with RandomX. If not, see. */ +#include #include "blake2/blake2.h" #include "blake2/endian.h" #include "Blake2Generator.hpp" diff --git a/src/superscalarGenerator.cpp b/src/superscalarGenerator.cpp index d4fd32a..e74dc50 100644 --- a/src/superscalarGenerator.cpp +++ b/src/superscalarGenerator.cpp @@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License along with RandomX. If not, see. */ -#include #include "configuration.h" #include "Program.hpp" #include "blake2/endian.h" diff --git a/src/tests/superscalar-stats.cpp b/src/tests/superscalar-stats.cpp new file mode 100644 index 0000000..ef6bd08 --- /dev/null +++ b/src/tests/superscalar-stats.cpp @@ -0,0 +1,61 @@ +/* +Copyright (c) 2019 tevador + +This file is part of RandomX. + +RandomX is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RandomX is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RandomX. If not, see. +*/ + +#include +#include +#include "../superscalarGenerator.hpp" +#include "../Blake2Generator.hpp" + +const uint8_t seed[32] = { 191, 182, 222, 175, 249, 89, 134, 104, 241, 68, 191, 62, 162, 166, 61, 64, 123, 191, 227, 193, 118, 60, 188, 53, 223, 133, 175, 24, 123, 230, 55, 74 }; + +int main() { + + constexpr int count = 100000; + int64_t asicLatency = 0; + int64_t codesize = 0; + int64_t cpuLatency = 0; + int64_t macroOps = 0; + int64_t mulCount = 0; + int64_t size = 0; + for (int i = 0; i < count; ++i) { + RandomX::SuperscalarProgram prog; + RandomX::Blake2Generator gen(seed, i); + RandomX::generateSuperscalar(prog, gen); + asicLatency += prog.asicLatency; + codesize += prog.codeSize; + cpuLatency += prog.cpuLatency; + macroOps += prog.macroOps; + mulCount += prog.mulCount; + size += prog.getSize(); + + if ((i + 1) % (count / 100) == 0) { + std::cout << "Completed " << ((i + 1) / (count / 100)) << "% ..." << std::endl; + } + } + + std::cout << "Avg. IPC: " << (macroOps / (double)cpuLatency) << std::endl; + std::cout << "Avg. ASIC latency: " << (asicLatency / (double)count) << std::endl; + std::cout << "Avg. CPU latency: " << (cpuLatency / (double)count) << std::endl; + std::cout << "Avg. code size: " << (codesize / (double)count) << std::endl; + std::cout << "Avg. x86 ops: " << (macroOps / (double)count) << std::endl; + std::cout << "Avg. mul. count: " << (mulCount / (double)count) << std::endl; + std::cout << "Avg. RandomX ops: " << (size / (double)count) << std::endl; + + return 0; +} \ No newline at end of file diff --git a/src/variant4_random_math.h b/src/variant4_random_math.h deleted file mode 100644 index 3ae1841..0000000 --- a/src/variant4_random_math.h +++ /dev/null @@ -1,441 +0,0 @@ -#ifndef VARIANT4_RANDOM_MATH_H -#define VARIANT4_RANDOM_MATH_H - -// Register size can be configured to either 32 bit (uint32_t) or 64 bit (uint64_t) -typedef uint32_t v4_reg; - -enum V4_Settings -{ - // Generate code with minimal theoretical latency = 45 cycles, which is equivalent to 15 multiplications - TOTAL_LATENCY = 15 * 3, - - // Always generate at least 60 instructions - NUM_INSTRUCTIONS_MIN = 60, - - // Never generate more than 70 instructions (final RET instruction doesn't count here) - NUM_INSTRUCTIONS_MAX = 70, - - // Available ALUs for MUL - // Modern CPUs typically have only 1 ALU which can do multiplications - ALU_COUNT_MUL = 1, - - // Total available ALUs - // Modern CPUs have 4 ALUs, but we use only 3 because random math executes together with other main loop code - ALU_COUNT = 3, -}; - -enum V4_InstructionList -{ - MUL, // a*b - ADD, // a+b + C, C is an unsigned 32-bit constant - SUB, // a-b - ROR, // rotate right "a" by "b & 31" bits - ROL, // rotate left "a" by "b & 31" bits - XOR, // a^b - RET, // finish execution - V4_INSTRUCTION_COUNT = RET, -}; - -// V4_InstructionDefinition is used to generate code from random data -// Every random sequence of bytes is a valid code -// -// There are 9 registers in total: -// - 4 variable registers -// - 5 constant registers initialized from loop variables -// This is why dst_index is 2 bits -enum V4_InstructionDefinition -{ - V4_OPCODE_BITS = 3, - V4_DST_INDEX_BITS = 2, - V4_SRC_INDEX_BITS = 3, -}; - -struct V4_Instruction -{ - uint8_t opcode; - uint8_t dst_index; - uint8_t src_index; - uint32_t C; -}; - -#ifndef FORCEINLINE -#if defined(__GNUC__) -#define FORCEINLINE __attribute__((always_inline)) inline -#elif defined(_MSC_VER) -#define FORCEINLINE __forceinline -#else -#define FORCEINLINE inline -#endif -#endif - -#ifndef UNREACHABLE_CODE -#if defined(__GNUC__) -#define UNREACHABLE_CODE __builtin_unreachable() -#elif defined(_MSC_VER) -#define UNREACHABLE_CODE __assume(false) -#else -#define UNREACHABLE_CODE -#endif -#endif - -// Random math interpreter's loop is fully unrolled and inlined to achieve 100% branch prediction on CPU: -// every switch-case will point to the same destination on every iteration of Cryptonight main loop -// -// This is about as fast as it can get without using low-level machine code generation -static FORCEINLINE void v4_random_math(const struct V4_Instruction* code, v4_reg* r) -{ - enum - { - REG_BITS = sizeof(v4_reg) * 8, - }; - -#define V4_EXEC(i) \ - { \ - const struct V4_Instruction* op = code + i; \ - const v4_reg src = r[op->src_index]; \ - v4_reg* dst = r + op->dst_index; \ - switch (op->opcode) \ - { \ - case MUL: \ - *dst *= src; \ - break; \ - case ADD: \ - *dst += src + op->C; \ - break; \ - case SUB: \ - *dst -= src; \ - break; \ - case ROR: \ - { \ - const uint32_t shift = src % REG_BITS; \ - *dst = (*dst >> shift) | (*dst << ((REG_BITS - shift) % REG_BITS)); \ - } \ - break; \ - case ROL: \ - { \ - const uint32_t shift = src % REG_BITS; \ - *dst = (*dst << shift) | (*dst >> ((REG_BITS - shift) % REG_BITS)); \ - } \ - break; \ - case XOR: \ - *dst ^= src; \ - break; \ - case RET: \ - return; \ - default: \ - UNREACHABLE_CODE; \ - break; \ - } \ - } - -#define V4_EXEC_10(j) \ - V4_EXEC(j + 0) \ - V4_EXEC(j + 1) \ - V4_EXEC(j + 2) \ - V4_EXEC(j + 3) \ - V4_EXEC(j + 4) \ - V4_EXEC(j + 5) \ - V4_EXEC(j + 6) \ - V4_EXEC(j + 7) \ - V4_EXEC(j + 8) \ - V4_EXEC(j + 9) - - // Generated program can have 60 + a few more (usually 2-3) instructions to achieve required latency - // I've checked all block heights < 10,000,000 and here is the distribution of program sizes: - // - // 60 27960 - // 61 105054 - // 62 2452759 - // 63 5115997 - // 64 1022269 - // 65 1109635 - // 66 153145 - // 67 8550 - // 68 4529 - // 69 102 - - // Unroll 70 instructions here - V4_EXEC_10(0); // instructions 0-9 - V4_EXEC_10(10); // instructions 10-19 - V4_EXEC_10(20); // instructions 20-29 - V4_EXEC_10(30); // instructions 30-39 - V4_EXEC_10(40); // instructions 40-49 - V4_EXEC_10(50); // instructions 50-59 - V4_EXEC_10(60); // instructions 60-69 - -#undef V4_EXEC_10 -#undef V4_EXEC -} - -// If we don't have enough data available, generate more -static FORCEINLINE void check_data(size_t* data_index, const size_t bytes_needed, int8_t* data, const size_t data_size) -{ - if (*data_index + bytes_needed > data_size) - { - hash_extra_blake(data, data_size, (char*) data); - *data_index = 0; - } -} - -// Generates as many random math operations as possible with given latency and ALU restrictions -// "code" array must have space for NUM_INSTRUCTIONS_MAX+1 instructions -static inline int v4_random_math_init(struct V4_Instruction* code, const uint64_t height) -{ - // MUL is 3 cycles, 3-way addition and rotations are 2 cycles, SUB/XOR are 1 cycle - // These latencies match real-life instruction latencies for Intel CPUs starting from Sandy Bridge and up to Skylake/Coffee lake - // - // AMD Ryzen has the same latencies except 1-cycle ROR/ROL, so it'll be a bit faster than Intel Sandy Bridge and newer processors - // Surprisingly, Intel Nehalem also has 1-cycle ROR/ROL, so it'll also be faster than Intel Sandy Bridge and newer processors - // AMD Bulldozer has 4 cycles latency for MUL (slower than Intel) and 1 cycle for ROR/ROL (faster than Intel), so average performance will be the same - // Source: https://www.agner.org/optimize/instruction_tables.pdf - const int op_latency[V4_INSTRUCTION_COUNT] = { 3, 2, 1, 2, 2, 1 }; - - // Instruction latencies for theoretical ASIC implementation - const int asic_op_latency[V4_INSTRUCTION_COUNT] = { 3, 1, 1, 1, 1, 1 }; - - // Available ALUs for each instruction - const int op_ALUs[V4_INSTRUCTION_COUNT] = { ALU_COUNT_MUL, ALU_COUNT, ALU_COUNT, ALU_COUNT, ALU_COUNT, ALU_COUNT }; - - int8_t data[32]; - memset(data, 0, sizeof(data)); - uint64_t tmp = SWAP64LE(height); - memcpy(data, &tmp, sizeof(uint64_t)); - data[20] = -38; // change seed - - // Set data_index past the last byte in data - // to trigger full data update with blake hash - // before we start using it - size_t data_index = sizeof(data); - - int code_size; - - // There is a small chance (1.8%) that register R8 won't be used in the generated program - // So we keep track of it and try again if it's not used - bool r8_used; - do { - int latency[9]; - int asic_latency[9]; - - // Tracks previous instruction and value of the source operand for registers R0-R3 throughout code execution - // byte 0: current value of the destination register - // byte 1: instruction opcode - // byte 2: current value of the source register - // - // Registers R4-R8 are constant and are treated as having the same value because when we do - // the same operation twice with two constant source registers, it can be optimized into a single operation - uint32_t inst_data[9] = { 0, 1, 2, 3, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF }; - - bool alu_busy[TOTAL_LATENCY + 1][ALU_COUNT]; - bool is_rotation[V4_INSTRUCTION_COUNT]; - bool rotated[4]; - int rotate_count = 0; - - memset(latency, 0, sizeof(latency)); - memset(asic_latency, 0, sizeof(asic_latency)); - memset(alu_busy, 0, sizeof(alu_busy)); - memset(is_rotation, 0, sizeof(is_rotation)); - memset(rotated, 0, sizeof(rotated)); - is_rotation[ROR] = true; - is_rotation[ROL] = true; - - int num_retries = 0; - code_size = 0; - - int total_iterations = 0; - r8_used = false; - - // Generate random code to achieve minimal required latency for our abstract CPU - // Try to get this latency for all 4 registers - while (((latency[0] < TOTAL_LATENCY) || (latency[1] < TOTAL_LATENCY) || (latency[2] < TOTAL_LATENCY) || (latency[3] < TOTAL_LATENCY)) && (num_retries < 64)) - { - // Fail-safe to guarantee loop termination - ++total_iterations; - if (total_iterations > 256) - break; - - check_data(&data_index, 1, data, sizeof(data)); - - const uint8_t c = ((uint8_t*)data)[data_index++]; - - // MUL = opcodes 0-2 - // ADD = opcode 3 - // SUB = opcode 4 - // ROR/ROL = opcode 5, shift direction is selected randomly - // XOR = opcodes 6-7 - uint8_t opcode = c & ((1 << V4_OPCODE_BITS) - 1); - if (opcode == 5) - { - check_data(&data_index, 1, data, sizeof(data)); - opcode = (data[data_index++] >= 0) ? ROR : ROL; - } - else if (opcode >= 6) - { - opcode = XOR; - } - else - { - opcode = (opcode <= 2) ? MUL : (opcode - 2); - } - - uint8_t dst_index = (c >> V4_OPCODE_BITS) & ((1 << V4_DST_INDEX_BITS) - 1); - uint8_t src_index = (c >> (V4_OPCODE_BITS + V4_DST_INDEX_BITS)) & ((1 << V4_SRC_INDEX_BITS) - 1); - - const int a = dst_index; - int b = src_index; - - // Don't do ADD/SUB/XOR with the same register - if (((opcode == ADD) || (opcode == SUB) || (opcode == XOR)) && (a == b)) - { - // Use register R8 as source instead - b = 8; - src_index = 8; - } - - // Don't do rotation with the same destination twice because it's equal to a single rotation - if (is_rotation[opcode] && rotated[a]) - { - continue; - } - - // Don't do the same instruction (except MUL) with the same source value twice because all other cases can be optimized: - // 2xADD(a, b, C) = ADD(a, b*2, C1+C2), same for SUB and rotations - // 2xXOR(a, b) = NOP - if ((opcode != MUL) && ((inst_data[a] & 0xFFFF00) == (opcode << 8) + ((inst_data[b] & 255) << 16))) - { - continue; - } - - // Find which ALU is available (and when) for this instruction - int next_latency = (latency[a] > latency[b]) ? latency[a] : latency[b]; - int alu_index = -1; - while (next_latency < TOTAL_LATENCY) - { - for (int i = op_ALUs[opcode] - 1; i >= 0; --i) - { - if (!alu_busy[next_latency][i]) - { - // ADD is implemented as two 1-cycle instructions on a real CPU, so do an additional availability check - if ((opcode == ADD) && alu_busy[next_latency + 1][i]) - { - continue; - } - - // Rotation can only start when previous rotation is finished, so do an additional availability check - if (is_rotation[opcode] && (next_latency < rotate_count * op_latency[opcode])) - { - continue; - } - - alu_index = i; - break; - } - } - if (alu_index >= 0) - { - break; - } - ++next_latency; - } - - // Don't generate instructions that leave some register unchanged for more than 7 cycles - if (next_latency > latency[a] + 7) - { - continue; - } - - next_latency += op_latency[opcode]; - - if (next_latency <= TOTAL_LATENCY) - { - if (is_rotation[opcode]) - { - ++rotate_count; - } - - // Mark ALU as busy only for the first cycle when it starts executing the instruction because ALUs are fully pipelined - alu_busy[next_latency - op_latency[opcode]][alu_index] = true; - latency[a] = next_latency; - - // ASIC is supposed to have enough ALUs to run as many independent instructions per cycle as possible, so latency calculation for ASIC is simple - asic_latency[a] = ((asic_latency[a] > asic_latency[b]) ? asic_latency[a] : asic_latency[b]) + asic_op_latency[opcode]; - - rotated[a] = is_rotation[opcode]; - - inst_data[a] = code_size + (opcode << 8) + ((inst_data[b] & 255) << 16); - - code[code_size].opcode = opcode; - code[code_size].dst_index = dst_index; - code[code_size].src_index = src_index; - code[code_size].C = 0; - - if (src_index == 8) - { - r8_used = true; - } - - if (opcode == ADD) - { - // ADD instruction is implemented as two 1-cycle instructions on a real CPU, so mark ALU as busy for the next cycle too - alu_busy[next_latency - op_latency[opcode] + 1][alu_index] = true; - - // ADD instruction requires 4 more random bytes for 32-bit constant "C" in "a = a + b + C" - check_data(&data_index, sizeof(uint32_t), data, sizeof(data)); - uint32_t t; - memcpy(&t, data + data_index, sizeof(uint32_t)); - code[code_size].C = SWAP32LE(t); - data_index += sizeof(uint32_t); - } - - ++code_size; - if (code_size >= NUM_INSTRUCTIONS_MIN) - { - break; - } - } - else - { - ++num_retries; - } - } - - // ASIC has more execution resources and can extract as much parallelism from the code as possible - // We need to add a few more MUL and ROR instructions to achieve minimal required latency for ASIC - // Get this latency for at least 1 of the 4 registers - const int prev_code_size = code_size; - while ((code_size < NUM_INSTRUCTIONS_MAX) && (asic_latency[0] < TOTAL_LATENCY) && (asic_latency[1] < TOTAL_LATENCY) && (asic_latency[2] < TOTAL_LATENCY) && (asic_latency[3] < TOTAL_LATENCY)) - { - int min_idx = 0; - int max_idx = 0; - for (int i = 1; i < 4; ++i) - { - if (asic_latency[i] < asic_latency[min_idx]) min_idx = i; - if (asic_latency[i] > asic_latency[max_idx]) max_idx = i; - } - - const uint8_t pattern[3] = { ROR, MUL, MUL }; - const uint8_t opcode = pattern[(code_size - prev_code_size) % 3]; - latency[min_idx] = latency[max_idx] + op_latency[opcode]; - asic_latency[min_idx] = asic_latency[max_idx] + asic_op_latency[opcode]; - - code[code_size].opcode = opcode; - code[code_size].dst_index = min_idx; - code[code_size].src_index = max_idx; - code[code_size].C = 0; - ++code_size; - } - - // There is ~98.15% chance that loop condition is false, so this loop will execute only 1 iteration most of the time - // It never does more than 4 iterations for all block heights < 10,000,000 - } while (!r8_used || (code_size < NUM_INSTRUCTIONS_MIN) || (code_size > NUM_INSTRUCTIONS_MAX)); - - // It's guaranteed that NUM_INSTRUCTIONS_MIN <= code_size <= NUM_INSTRUCTIONS_MAX here - // Add final instruction to stop the interpreter - code[code_size].opcode = RET; - code[code_size].dst_index = 0; - code[code_size].src_index = 0; - code[code_size].C = 0; - - return code_size; -} - -#endif \ No newline at end of file diff --git a/vcxproj/randomx.vcxproj b/vcxproj/randomx.vcxproj index d646143..60504e9 100644 --- a/vcxproj/randomx.vcxproj +++ b/vcxproj/randomx.vcxproj @@ -174,7 +174,6 @@ - diff --git a/vcxproj/randomx.vcxproj.filters b/vcxproj/randomx.vcxproj.filters index 77939bd..30d2109 100644 --- a/vcxproj/randomx.vcxproj.filters +++ b/vcxproj/randomx.vcxproj.filters @@ -151,9 +151,6 @@ Header Files - - Header Files - Header Files diff --git a/vcxproj/superscalar-stats.vcxproj b/vcxproj/superscalar-stats.vcxproj new file mode 100644 index 0000000..8f71529 --- /dev/null +++ b/vcxproj/superscalar-stats.vcxproj @@ -0,0 +1,126 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {0173D560-8C12-46B3-B467-0C6E7573AA0B} + superscalarstats + 10.0.17763.0 + + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + MaxSpeed + true + true + false + true + + + true + true + + + + + Level3 + Disabled + true + true + + + + + Level3 + Disabled + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/vcxproj/superscalar-stats.vcxproj.filters b/vcxproj/superscalar-stats.vcxproj.filters new file mode 100644 index 0000000..bd32a77 --- /dev/null +++ b/vcxproj/superscalar-stats.vcxproj.filters @@ -0,0 +1,31 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file