superscalar.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903
  1. /*
  2. Copyright (c) 2018-2019, tevador <tevador@gmail.com>
  3. All rights reserved.
  4. Redistribution and use in source and binary forms, with or without
  5. modification, are permitted provided that the following conditions are met:
  6. * Redistributions of source code must retain the above copyright
  7. notice, this list of conditions and the following disclaimer.
  8. * Redistributions in binary form must reproduce the above copyright
  9. notice, this list of conditions and the following disclaimer in the
  10. documentation and/or other materials provided with the distribution.
  11. * Neither the name of the copyright holder nor the
  12. names of its contributors may be used to endorse or promote products
  13. derived from this software without specific prior written permission.
  14. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  15. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  16. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  17. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  18. FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  19. DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  20. SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  21. CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  22. OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  23. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. */
  25. #include "configuration.h"
  26. #include "program.hpp"
  27. #include "blake2/endian.h"
  28. #include <iostream>
  29. #include <vector>
  30. #include <algorithm>
  31. #include <stdexcept>
  32. #include <iomanip>
  33. #include "superscalar.hpp"
  34. #include "intrin_portable.h"
  35. #include "reciprocal.h"
  36. #include "common.hpp"
  37. namespace randomx {
  38. static bool isMultiplication(SuperscalarInstructionType type) {
  39. return type == SuperscalarInstructionType::IMUL_R || type == SuperscalarInstructionType::IMULH_R || type == SuperscalarInstructionType::ISMULH_R || type == SuperscalarInstructionType::IMUL_RCP;
  40. }
  41. //uOPs (micro-ops) are represented only by the execution port they can go to
  42. namespace ExecutionPort {
  43. using type = int;
  44. constexpr type Null = 0;
  45. constexpr type P0 = 1;
  46. constexpr type P1 = 2;
  47. constexpr type P5 = 4;
  48. constexpr type P01 = P0 | P1;
  49. constexpr type P05 = P0 | P5;
  50. constexpr type P015 = P0 | P1 | P5;
  51. }
  52. //Macro-operation as output of the x86 decoder
  53. //Usually one macro-op = one x86 instruction, but 2 instructions are sometimes fused into 1 macro-op
  54. //Macro-op can consist of 1 or 2 uOPs.
  55. class MacroOp {
  56. public:
  57. MacroOp(const char* name, int size)
  58. : name_(name), size_(size), latency_(0), uop1_(ExecutionPort::Null), uop2_(ExecutionPort::Null) {}
  59. MacroOp(const char* name, int size, int latency, ExecutionPort::type uop)
  60. : name_(name), size_(size), latency_(latency), uop1_(uop), uop2_(ExecutionPort::Null) {}
  61. MacroOp(const char* name, int size, int latency, ExecutionPort::type uop1, ExecutionPort::type uop2)
  62. : name_(name), size_(size), latency_(latency), uop1_(uop1), uop2_(uop2) {}
  63. MacroOp(const MacroOp& parent, bool dependent)
  64. : name_(parent.name_), size_(parent.size_), latency_(parent.latency_), uop1_(parent.uop1_), uop2_(parent.uop2_), dependent_(dependent) {}
  65. const char* getName() const {
  66. return name_;
  67. }
  68. int getSize() const {
  69. return size_;
  70. }
  71. int getLatency() const {
  72. return latency_;
  73. }
  74. ExecutionPort::type getUop1() const {
  75. return uop1_;
  76. }
  77. ExecutionPort::type getUop2() const {
  78. return uop2_;
  79. }
  80. bool isSimple() const {
  81. return uop2_ == ExecutionPort::Null;
  82. }
  83. bool isEliminated() const {
  84. return uop1_ == ExecutionPort::Null;
  85. }
  86. bool isDependent() const {
  87. return dependent_;
  88. }
  89. static const MacroOp Add_rr;
  90. static const MacroOp Add_ri;
  91. static const MacroOp Lea_sib;
  92. static const MacroOp Sub_rr;
  93. static const MacroOp Imul_rr;
  94. static const MacroOp Imul_r;
  95. static const MacroOp Mul_r;
  96. static const MacroOp Mov_rr;
  97. static const MacroOp Mov_ri64;
  98. static const MacroOp Xor_rr;
  99. static const MacroOp Xor_ri;
  100. static const MacroOp Ror_rcl;
  101. static const MacroOp Ror_ri;
  102. static const MacroOp TestJz_fused;
  103. static const MacroOp Xor_self;
  104. static const MacroOp Cmp_ri;
  105. static const MacroOp Setcc_r;
  106. private:
  107. const char* name_;
  108. int size_;
  109. int latency_;
  110. ExecutionPort::type uop1_;
  111. ExecutionPort::type uop2_;
  112. bool dependent_ = false;
  113. };
  114. //Size: 3 bytes
  115. const MacroOp MacroOp::Add_rr = MacroOp("add r,r", 3, 1, ExecutionPort::P015);
  116. const MacroOp MacroOp::Sub_rr = MacroOp("sub r,r", 3, 1, ExecutionPort::P015);
  117. const MacroOp MacroOp::Xor_rr = MacroOp("xor r,r", 3, 1, ExecutionPort::P015);
  118. const MacroOp MacroOp::Imul_r = MacroOp("imul r", 3, 4, ExecutionPort::P1, ExecutionPort::P5);
  119. const MacroOp MacroOp::Mul_r = MacroOp("mul r", 3, 4, ExecutionPort::P1, ExecutionPort::P5);
  120. const MacroOp MacroOp::Mov_rr = MacroOp("mov r,r", 3);
  121. //Size: 4 bytes
  122. const MacroOp MacroOp::Lea_sib = MacroOp("lea r,r+r*s", 4, 1, ExecutionPort::P01);
  123. const MacroOp MacroOp::Imul_rr = MacroOp("imul r,r", 4, 3, ExecutionPort::P1);
  124. const MacroOp MacroOp::Ror_ri = MacroOp("ror r,i", 4, 1, ExecutionPort::P05);
  125. //Size: 7 bytes (can be optionally padded with nop to 8 or 9 bytes)
  126. const MacroOp MacroOp::Add_ri = MacroOp("add r,i", 7, 1, ExecutionPort::P015);
  127. const MacroOp MacroOp::Xor_ri = MacroOp("xor r,i", 7, 1, ExecutionPort::P015);
  128. //Size: 10 bytes
  129. const MacroOp MacroOp::Mov_ri64 = MacroOp("mov rax,i64", 10, 1, ExecutionPort::P015);
  130. //Unused:
  131. const MacroOp MacroOp::Ror_rcl = MacroOp("ror r,cl", 3, 1, ExecutionPort::P0, ExecutionPort::P5);
  132. const MacroOp MacroOp::Xor_self = MacroOp("xor rcx,rcx", 3);
  133. const MacroOp MacroOp::Cmp_ri = MacroOp("cmp r,i", 7, 1, ExecutionPort::P015);
  134. const MacroOp MacroOp::Setcc_r = MacroOp("setcc cl", 3, 1, ExecutionPort::P05);
  135. const MacroOp MacroOp::TestJz_fused = MacroOp("testjz r,i", 13, 0, ExecutionPort::P5);
  136. const MacroOp IMULH_R_ops_array[] = { MacroOp::Mov_rr, MacroOp::Mul_r, MacroOp::Mov_rr };
  137. const MacroOp ISMULH_R_ops_array[] = { MacroOp::Mov_rr, MacroOp::Imul_r, MacroOp::Mov_rr };
  138. const MacroOp IMUL_RCP_ops_array[] = { MacroOp::Mov_ri64, MacroOp(MacroOp::Imul_rr, true) };
  139. class SuperscalarInstructionInfo {
  140. public:
  141. const char* getName() const {
  142. return name_;
  143. }
  144. int getSize() const {
  145. return ops_.size();
  146. }
  147. bool isSimple() const {
  148. return getSize() == 1;
  149. }
  150. int getLatency() const {
  151. return latency_;
  152. }
  153. const MacroOp& getOp(int index) const {
  154. return ops_[index];
  155. }
  156. SuperscalarInstructionType getType() const {
  157. return type_;
  158. }
  159. int getResultOp() const {
  160. return resultOp_;
  161. }
  162. int getDstOp() const {
  163. return dstOp_;
  164. }
  165. int getSrcOp() const {
  166. return srcOp_;
  167. }
  168. static const SuperscalarInstructionInfo ISUB_R;
  169. static const SuperscalarInstructionInfo IXOR_R;
  170. static const SuperscalarInstructionInfo IADD_RS;
  171. static const SuperscalarInstructionInfo IMUL_R;
  172. static const SuperscalarInstructionInfo IROR_C;
  173. static const SuperscalarInstructionInfo IADD_C7;
  174. static const SuperscalarInstructionInfo IXOR_C7;
  175. static const SuperscalarInstructionInfo IADD_C8;
  176. static const SuperscalarInstructionInfo IXOR_C8;
  177. static const SuperscalarInstructionInfo IADD_C9;
  178. static const SuperscalarInstructionInfo IXOR_C9;
  179. static const SuperscalarInstructionInfo IMULH_R;
  180. static const SuperscalarInstructionInfo ISMULH_R;
  181. static const SuperscalarInstructionInfo IMUL_RCP;
  182. static const SuperscalarInstructionInfo NOP;
  183. private:
  184. const char* name_;
  185. SuperscalarInstructionType type_;
  186. std::vector<MacroOp> ops_;
  187. int latency_;
  188. int resultOp_ = 0;
  189. int dstOp_ = 0;
  190. int srcOp_;
  191. SuperscalarInstructionInfo(const char* name)
  192. : name_(name), type_(SuperscalarInstructionType::INVALID), latency_(0) {}
  193. SuperscalarInstructionInfo(const char* name, SuperscalarInstructionType type, const MacroOp& op, int srcOp)
  194. : name_(name), type_(type), latency_(op.getLatency()), srcOp_(srcOp) {
  195. ops_.push_back(MacroOp(op));
  196. }
  197. template <size_t N>
  198. SuperscalarInstructionInfo(const char* name, SuperscalarInstructionType type, const MacroOp(&arr)[N], int resultOp, int dstOp, int srcOp)
  199. : name_(name), type_(type), latency_(0), resultOp_(resultOp), dstOp_(dstOp), srcOp_(srcOp) {
  200. for (unsigned i = 0; i < N; ++i) {
  201. ops_.push_back(MacroOp(arr[i]));
  202. latency_ += ops_.back().getLatency();
  203. }
  204. static_assert(N > 1, "Invalid array size");
  205. }
  206. };
  207. const SuperscalarInstructionInfo SuperscalarInstructionInfo::ISUB_R = SuperscalarInstructionInfo("ISUB_R", SuperscalarInstructionType::ISUB_R, MacroOp::Sub_rr, 0);
  208. const SuperscalarInstructionInfo SuperscalarInstructionInfo::IXOR_R = SuperscalarInstructionInfo("IXOR_R", SuperscalarInstructionType::IXOR_R, MacroOp::Xor_rr, 0);
  209. const SuperscalarInstructionInfo SuperscalarInstructionInfo::IADD_RS = SuperscalarInstructionInfo("IADD_RS", SuperscalarInstructionType::IADD_RS, MacroOp::Lea_sib, 0);
  210. const SuperscalarInstructionInfo SuperscalarInstructionInfo::IMUL_R = SuperscalarInstructionInfo("IMUL_R", SuperscalarInstructionType::IMUL_R, MacroOp::Imul_rr, 0);
  211. const SuperscalarInstructionInfo SuperscalarInstructionInfo::IROR_C = SuperscalarInstructionInfo("IROR_C", SuperscalarInstructionType::IROR_C, MacroOp::Ror_ri, -1);
  212. const SuperscalarInstructionInfo SuperscalarInstructionInfo::IADD_C7 = SuperscalarInstructionInfo("IADD_C7", SuperscalarInstructionType::IADD_C7, MacroOp::Add_ri, -1);
  213. const SuperscalarInstructionInfo SuperscalarInstructionInfo::IXOR_C7 = SuperscalarInstructionInfo("IXOR_C7", SuperscalarInstructionType::IXOR_C7, MacroOp::Xor_ri, -1);
  214. const SuperscalarInstructionInfo SuperscalarInstructionInfo::IADD_C8 = SuperscalarInstructionInfo("IADD_C8", SuperscalarInstructionType::IADD_C8, MacroOp::Add_ri, -1);
  215. const SuperscalarInstructionInfo SuperscalarInstructionInfo::IXOR_C8 = SuperscalarInstructionInfo("IXOR_C8", SuperscalarInstructionType::IXOR_C8, MacroOp::Xor_ri, -1);
  216. const SuperscalarInstructionInfo SuperscalarInstructionInfo::IADD_C9 = SuperscalarInstructionInfo("IADD_C9", SuperscalarInstructionType::IADD_C9, MacroOp::Add_ri, -1);
  217. const SuperscalarInstructionInfo SuperscalarInstructionInfo::IXOR_C9 = SuperscalarInstructionInfo("IXOR_C9", SuperscalarInstructionType::IXOR_C9, MacroOp::Xor_ri, -1);
  218. const SuperscalarInstructionInfo SuperscalarInstructionInfo::IMULH_R = SuperscalarInstructionInfo("IMULH_R", SuperscalarInstructionType::IMULH_R, IMULH_R_ops_array, 1, 0, 1);
  219. const SuperscalarInstructionInfo SuperscalarInstructionInfo::ISMULH_R = SuperscalarInstructionInfo("ISMULH_R", SuperscalarInstructionType::ISMULH_R, ISMULH_R_ops_array, 1, 0, 1);
  220. const SuperscalarInstructionInfo SuperscalarInstructionInfo::IMUL_RCP = SuperscalarInstructionInfo("IMUL_RCP", SuperscalarInstructionType::IMUL_RCP, IMUL_RCP_ops_array, 1, 1, -1);
  221. const SuperscalarInstructionInfo SuperscalarInstructionInfo::NOP = SuperscalarInstructionInfo("NOP");
  222. //these are some of the options how to split a 16-byte window into 3 or 4 x86 instructions.
  223. //RandomX uses instructions with a native size of 3 (sub, xor, mul, mov), 4 (lea, mul), 7 (xor, add immediate) or 10 bytes (mov 64-bit immediate).
  224. //Slots with sizes of 8 or 9 bytes need to be padded with a nop instruction.
  225. const int buffer0[] = { 4, 8, 4 };
  226. const int buffer1[] = { 7, 3, 3, 3 };
  227. const int buffer2[] = { 3, 7, 3, 3 };
  228. const int buffer3[] = { 4, 9, 3 };
  229. const int buffer4[] = { 4, 4, 4, 4 };
  230. const int buffer5[] = { 3, 3, 10 };
  231. class DecoderBuffer {
  232. public:
  233. static const DecoderBuffer Default;
  234. template <size_t N>
  235. DecoderBuffer(const char* name, int index, const int(&arr)[N])
  236. : name_(name), index_(index), counts_(arr), opsCount_(N) {}
  237. const int* getCounts() const {
  238. return counts_;
  239. }
  240. int getSize() const {
  241. return opsCount_;
  242. }
  243. int getIndex() const {
  244. return index_;
  245. }
  246. const char* getName() const {
  247. return name_;
  248. }
  249. const DecoderBuffer* fetchNext(SuperscalarInstructionType instrType, int cycle, int mulCount, Blake2Generator& gen) const {
  250. //If the current RandomX instruction is "IMULH", the next fetch configuration must be 3-3-10
  251. //because the full 128-bit multiplication instruction is 3 bytes long and decodes to 2 uOPs on Intel CPUs.
  252. //Intel CPUs can decode at most 4 uOPs per cycle, so this requires a 2-1-1 configuration for a total of 3 macro ops.
  253. if (instrType == SuperscalarInstructionType::IMULH_R || instrType == SuperscalarInstructionType::ISMULH_R)
  254. return &decodeBuffer3310;
  255. //To make sure that the multiplication port is saturated, a 4-4-4-4 configuration is generated if the number of multiplications
  256. //is lower than the number of cycles.
  257. if (mulCount < cycle + 1)
  258. return &decodeBuffer4444;
  259. //If the current RandomX instruction is "IMUL_RCP", the next buffer must begin with a 4-byte slot for multiplication.
  260. if(instrType == SuperscalarInstructionType::IMUL_RCP)
  261. return (gen.getByte() & 1) ? &decodeBuffer484 : &decodeBuffer493;
  262. //Default: select a random fetch configuration.
  263. return fetchNextDefault(gen);
  264. }
  265. private:
  266. const char* name_;
  267. int index_;
  268. const int* counts_;
  269. int opsCount_;
  270. DecoderBuffer() : index_(-1) {}
  271. static const DecoderBuffer decodeBuffer484;
  272. static const DecoderBuffer decodeBuffer7333;
  273. static const DecoderBuffer decodeBuffer3733;
  274. static const DecoderBuffer decodeBuffer493;
  275. static const DecoderBuffer decodeBuffer4444;
  276. static const DecoderBuffer decodeBuffer3310;
  277. static const DecoderBuffer* decodeBuffers[4];
  278. const DecoderBuffer* fetchNextDefault(Blake2Generator& gen) const {
  279. return decodeBuffers[gen.getByte() & 3];
  280. }
  281. };
  282. const DecoderBuffer DecoderBuffer::decodeBuffer484 = DecoderBuffer("4,8,4", 0, buffer0);
  283. const DecoderBuffer DecoderBuffer::decodeBuffer7333 = DecoderBuffer("7,3,3,3", 1, buffer1);
  284. const DecoderBuffer DecoderBuffer::decodeBuffer3733 = DecoderBuffer("3,7,3,3", 2, buffer2);
  285. const DecoderBuffer DecoderBuffer::decodeBuffer493 = DecoderBuffer("4,9,3", 3, buffer3);
  286. const DecoderBuffer DecoderBuffer::decodeBuffer4444 = DecoderBuffer("4,4,4,4", 4, buffer4);
  287. const DecoderBuffer DecoderBuffer::decodeBuffer3310 = DecoderBuffer("3,3,10", 5, buffer5);
  288. const DecoderBuffer* DecoderBuffer::decodeBuffers[4] = {
  289. &DecoderBuffer::decodeBuffer484,
  290. &DecoderBuffer::decodeBuffer7333,
  291. &DecoderBuffer::decodeBuffer3733,
  292. &DecoderBuffer::decodeBuffer493,
  293. };
  294. const DecoderBuffer DecoderBuffer::Default = DecoderBuffer();
  295. const SuperscalarInstructionInfo* slot_3[] = { &SuperscalarInstructionInfo::ISUB_R, &SuperscalarInstructionInfo::IXOR_R };
  296. const SuperscalarInstructionInfo* slot_3L[] = { &SuperscalarInstructionInfo::ISUB_R, &SuperscalarInstructionInfo::IXOR_R, &SuperscalarInstructionInfo::IMULH_R, &SuperscalarInstructionInfo::ISMULH_R };
  297. const SuperscalarInstructionInfo* slot_4[] = { &SuperscalarInstructionInfo::IROR_C, &SuperscalarInstructionInfo::IADD_RS };
  298. const SuperscalarInstructionInfo* slot_7[] = { &SuperscalarInstructionInfo::IXOR_C7, &SuperscalarInstructionInfo::IADD_C7 };
  299. const SuperscalarInstructionInfo* slot_8[] = { &SuperscalarInstructionInfo::IXOR_C8, &SuperscalarInstructionInfo::IADD_C8 };
  300. const SuperscalarInstructionInfo* slot_9[] = { &SuperscalarInstructionInfo::IXOR_C9, &SuperscalarInstructionInfo::IADD_C9 };
  301. const SuperscalarInstructionInfo* slot_10 = &SuperscalarInstructionInfo::IMUL_RCP;
  302. static bool selectRegister(std::vector<int>& availableRegisters, Blake2Generator& gen, int& reg) {
  303. int index;
  304. if (availableRegisters.size() == 0)
  305. return false;
  306. if (availableRegisters.size() > 1) {
  307. index = gen.getUInt32() % availableRegisters.size();
  308. }
  309. else {
  310. index = 0;
  311. }
  312. reg = availableRegisters[index];
  313. return true;
  314. }
  315. class RegisterInfo {
  316. public:
  317. RegisterInfo() : latency(0), lastOpGroup(SuperscalarInstructionType::INVALID), lastOpPar(-1), value(0) {}
  318. int latency;
  319. SuperscalarInstructionType lastOpGroup;
  320. int lastOpPar;
  321. int value;
  322. };
  323. //"SuperscalarInstruction" consists of one or more macro-ops
  324. class SuperscalarInstruction {
  325. public:
  326. void toInstr(Instruction& instr) { //translate to a RandomX instruction format
  327. instr.opcode = (int)getType();
  328. instr.dst = dst_;
  329. instr.src = src_ >= 0 ? src_ : dst_;
  330. instr.setMod(mod_);
  331. instr.setImm32(imm32_);
  332. }
  333. void createForSlot(Blake2Generator& gen, int slotSize, int fetchType, bool isLast, bool isFirst) {
  334. switch (slotSize)
  335. {
  336. case 3:
  337. //if this is the last slot, we can also select "IMULH" instructions
  338. if (isLast) {
  339. create(slot_3L[gen.getByte() & 3], gen);
  340. }
  341. else {
  342. create(slot_3[gen.getByte() & 1], gen);
  343. }
  344. break;
  345. case 4:
  346. //if this is the 4-4-4-4 buffer, issue multiplications as the first 3 instructions
  347. if (fetchType == 4 && !isLast) {
  348. create(&SuperscalarInstructionInfo::IMUL_R, gen);
  349. }
  350. else {
  351. create(slot_4[gen.getByte() & 1], gen);
  352. }
  353. break;
  354. case 7:
  355. create(slot_7[gen.getByte() & 1], gen);
  356. break;
  357. case 8:
  358. create(slot_8[gen.getByte() & 1], gen);
  359. break;
  360. case 9:
  361. create(slot_9[gen.getByte() & 1], gen);
  362. break;
  363. case 10:
  364. create(slot_10, gen);
  365. break;
  366. default:
  367. UNREACHABLE;
  368. }
  369. }
  370. void create(const SuperscalarInstructionInfo* info, Blake2Generator& gen) {
  371. info_ = info;
  372. reset();
  373. switch (info->getType())
  374. {
  375. case SuperscalarInstructionType::ISUB_R: {
  376. mod_ = 0;
  377. imm32_ = 0;
  378. opGroup_ = SuperscalarInstructionType::IADD_RS;
  379. groupParIsSource_ = true;
  380. } break;
  381. case SuperscalarInstructionType::IXOR_R: {
  382. mod_ = 0;
  383. imm32_ = 0;
  384. opGroup_ = SuperscalarInstructionType::IXOR_R;
  385. groupParIsSource_ = true;
  386. } break;
  387. case SuperscalarInstructionType::IADD_RS: {
  388. mod_ = gen.getByte();
  389. imm32_ = 0;
  390. opGroup_ = SuperscalarInstructionType::IADD_RS;
  391. groupParIsSource_ = true;
  392. } break;
  393. case SuperscalarInstructionType::IMUL_R: {
  394. mod_ = 0;
  395. imm32_ = 0;
  396. opGroup_ = SuperscalarInstructionType::IMUL_R;
  397. groupParIsSource_ = true;
  398. } break;
  399. case SuperscalarInstructionType::IROR_C: {
  400. mod_ = 0;
  401. do {
  402. imm32_ = gen.getByte() & 63;
  403. } while (imm32_ == 0);
  404. opGroup_ = SuperscalarInstructionType::IROR_C;
  405. opGroupPar_ = -1;
  406. } break;
  407. case SuperscalarInstructionType::IADD_C7:
  408. case SuperscalarInstructionType::IADD_C8:
  409. case SuperscalarInstructionType::IADD_C9: {
  410. mod_ = 0;
  411. imm32_ = gen.getUInt32();
  412. opGroup_ = SuperscalarInstructionType::IADD_C7;
  413. opGroupPar_ = -1;
  414. } break;
  415. case SuperscalarInstructionType::IXOR_C7:
  416. case SuperscalarInstructionType::IXOR_C8:
  417. case SuperscalarInstructionType::IXOR_C9: {
  418. mod_ = 0;
  419. imm32_ = gen.getUInt32();
  420. opGroup_ = SuperscalarInstructionType::IXOR_C7;
  421. opGroupPar_ = -1;
  422. } break;
  423. case SuperscalarInstructionType::IMULH_R: {
  424. canReuse_ = true;
  425. mod_ = 0;
  426. imm32_ = 0;
  427. opGroup_ = SuperscalarInstructionType::IMULH_R;
  428. opGroupPar_ = gen.getUInt32();
  429. } break;
  430. case SuperscalarInstructionType::ISMULH_R: {
  431. canReuse_ = true;
  432. mod_ = 0;
  433. imm32_ = 0;
  434. opGroup_ = SuperscalarInstructionType::ISMULH_R;
  435. opGroupPar_ = gen.getUInt32();
  436. } break;
  437. case SuperscalarInstructionType::IMUL_RCP: {
  438. mod_ = 0;
  439. do {
  440. imm32_ = gen.getUInt32();
  441. } while (isZeroOrPowerOf2(imm32_));
  442. opGroup_ = SuperscalarInstructionType::IMUL_RCP;
  443. opGroupPar_ = -1;
  444. } break;
  445. default:
  446. break;
  447. }
  448. }
  449. bool selectDestination(int cycle, bool allowChainedMul, RegisterInfo (&registers)[8], Blake2Generator& gen) {
  450. /*if (allowChainedMultiplication && opGroup_ == SuperscalarInstructionType::IMUL_R)
  451. std::cout << "Selecting destination with chained MUL enabled" << std::endl;*/
  452. std::vector<int> availableRegisters;
  453. //Conditions for the destination register:
  454. // * value must be ready at the required cycle
  455. // * cannot be the same as the source register unless the instruction allows it
  456. // - this avoids optimizable instructions such as "xor r, r" or "sub r, r"
  457. // * register cannot be multiplied twice in a row unless allowChainedMul is true
  458. // - this avoids accumulation of trailing zeroes in registers due to excessive multiplication
  459. // - allowChainedMul is set to true if an attempt to find source/destination registers failed (this is quite rare, but prevents a catastrophic failure of the generator)
  460. // * either the last instruction applied to the register or its source must be different than this instruction
  461. // - this avoids optimizable instruction sequences such as "xor r1, r2; xor r1, r2" or "ror r, C1; ror r, C2" or "add r, C1; add r, C2"
  462. // * register r5 cannot be the destination of the IADD_RS instruction (limitation of the x86 lea instruction)
  463. for (unsigned i = 0; i < 8; ++i) {
  464. if (registers[i].latency <= cycle && (canReuse_ || i != src_) && (allowChainedMul || opGroup_ != SuperscalarInstructionType::IMUL_R || registers[i].lastOpGroup != SuperscalarInstructionType::IMUL_R) && (registers[i].lastOpGroup != opGroup_ || registers[i].lastOpPar != opGroupPar_) && (info_->getType() != SuperscalarInstructionType::IADD_RS || i != RegisterNeedsDisplacement))
  465. availableRegisters.push_back(i);
  466. }
  467. return selectRegister(availableRegisters, gen, dst_);
  468. }
  469. bool selectSource(int cycle, RegisterInfo(&registers)[8], Blake2Generator& gen) {
  470. std::vector<int> availableRegisters;
  471. //all registers that are ready at the cycle
  472. for (unsigned i = 0; i < 8; ++i) {
  473. if (registers[i].latency <= cycle)
  474. availableRegisters.push_back(i);
  475. }
  476. //if there are only 2 available registers for IADD_RS and one of them is r5, select it as the source because it cannot be the destination
  477. if (availableRegisters.size() == 2 && info_->getType() == SuperscalarInstructionType::IADD_RS) {
  478. if (availableRegisters[0] == RegisterNeedsDisplacement || availableRegisters[1] == RegisterNeedsDisplacement) {
  479. opGroupPar_ = src_ = RegisterNeedsDisplacement;
  480. return true;
  481. }
  482. }
  483. if (selectRegister(availableRegisters, gen, src_)) {
  484. if (groupParIsSource_)
  485. opGroupPar_ = src_;
  486. return true;
  487. }
  488. return false;
  489. }
  490. SuperscalarInstructionType getType() {
  491. return info_->getType();
  492. }
  493. int getSource() {
  494. return src_;
  495. }
  496. int getDestination() {
  497. return dst_;
  498. }
  499. SuperscalarInstructionType getGroup() {
  500. return opGroup_;
  501. }
  502. int getGroupPar() {
  503. return opGroupPar_;
  504. }
  505. const SuperscalarInstructionInfo& getInfo() const {
  506. return *info_;
  507. }
  508. static const SuperscalarInstruction Null;
  509. private:
  510. const SuperscalarInstructionInfo* info_;
  511. int src_ = -1;
  512. int dst_ = -1;
  513. int mod_;
  514. uint32_t imm32_;
  515. SuperscalarInstructionType opGroup_;
  516. int opGroupPar_;
  517. bool canReuse_ = false;
  518. bool groupParIsSource_ = false;
  519. void reset() {
  520. src_ = dst_ = -1;
  521. canReuse_ = groupParIsSource_ = false;
  522. }
  523. SuperscalarInstruction(const SuperscalarInstructionInfo* info) : info_(info) {
  524. }
  525. };
  526. const SuperscalarInstruction SuperscalarInstruction::Null = SuperscalarInstruction(&SuperscalarInstructionInfo::NOP);
  527. constexpr int CYCLE_MAP_SIZE = RANDOMX_SUPERSCALAR_LATENCY + 4;
  528. constexpr int LOOK_FORWARD_CYCLES = 4;
  529. constexpr int MAX_THROWAWAY_COUNT = 256;
  530. template<bool commit>
  531. static int scheduleUop(ExecutionPort::type uop, ExecutionPort::type(&portBusy)[CYCLE_MAP_SIZE][3], int cycle) {
  532. //The scheduling here is done optimistically by checking port availability in order P5 -> P0 -> P1 to not overload
  533. //port P1 (multiplication) by instructions that can go to any port.
  534. for (; cycle < CYCLE_MAP_SIZE; ++cycle) {
  535. if ((uop & ExecutionPort::P5) != 0 && !portBusy[cycle][2]) {
  536. if (commit) {
  537. if (trace) std::cout << "; P5 at cycle " << cycle << std::endl;
  538. portBusy[cycle][2] = uop;
  539. }
  540. return cycle;
  541. }
  542. if ((uop & ExecutionPort::P0) != 0 && !portBusy[cycle][0]) {
  543. if (commit) {
  544. if (trace) std::cout << "; P0 at cycle " << cycle << std::endl;
  545. portBusy[cycle][0] = uop;
  546. }
  547. return cycle;
  548. }
  549. if ((uop & ExecutionPort::P1) != 0 && !portBusy[cycle][1]) {
  550. if (commit) {
  551. if (trace) std::cout << "; P1 at cycle " << cycle << std::endl;
  552. portBusy[cycle][1] = uop;
  553. }
  554. return cycle;
  555. }
  556. }
  557. return -1;
  558. }
  559. template<bool commit>
  560. static int scheduleMop(const MacroOp& mop, ExecutionPort::type(&portBusy)[CYCLE_MAP_SIZE][3], int cycle, int depCycle) {
  561. //if this macro-op depends on the previous one, increase the starting cycle if needed
  562. //this handles an explicit dependency chain in IMUL_RCP
  563. if (mop.isDependent()) {
  564. cycle = std::max(cycle, depCycle);
  565. }
  566. //move instructions are eliminated and don't need an execution unit
  567. if (mop.isEliminated()) {
  568. if (commit)
  569. if (trace) std::cout << "; (eliminated)" << std::endl;
  570. return cycle;
  571. }
  572. else if (mop.isSimple()) {
  573. //this macro-op has only one uOP
  574. return scheduleUop<commit>(mop.getUop1(), portBusy, cycle);
  575. }
  576. else {
  577. //macro-ops with 2 uOPs are scheduled conservatively by requiring both uOPs to execute in the same cycle
  578. for (; cycle < CYCLE_MAP_SIZE; ++cycle) {
  579. int cycle1 = scheduleUop<false>(mop.getUop1(), portBusy, cycle);
  580. int cycle2 = scheduleUop<false>(mop.getUop2(), portBusy, cycle);
  581. if (cycle1 >= 0 && cycle1 == cycle2) {
  582. if (commit) {
  583. scheduleUop<true>(mop.getUop1(), portBusy, cycle1);
  584. scheduleUop<true>(mop.getUop2(), portBusy, cycle2);
  585. }
  586. return cycle1;
  587. }
  588. }
  589. }
  590. return -1;
  591. }
  592. void generateSuperscalar(SuperscalarProgram& prog, Blake2Generator& gen) {
  593. ExecutionPort::type portBusy[CYCLE_MAP_SIZE][3];
  594. memset(portBusy, 0, sizeof(portBusy));
  595. RegisterInfo registers[8];
  596. const DecoderBuffer* decodeBuffer = &DecoderBuffer::Default;
  597. SuperscalarInstruction currentInstruction = SuperscalarInstruction::Null;
  598. int macroOpIndex = 0;
  599. int codeSize = 0;
  600. int macroOpCount = 0;
  601. int cycle = 0;
  602. int depCycle = 0;
  603. int retireCycle = 0;
  604. bool portsSaturated = false;
  605. int programSize = 0;
  606. int mulCount = 0;
  607. int decodeCycle;
  608. int throwAwayCount = 0;
  609. //decode instructions for RANDOMX_SUPERSCALAR_LATENCY cycles or until an execution port is saturated.
  610. //Each decode cycle decodes 16 bytes of x86 code.
  611. //Since a decode cycle produces on average 3.45 macro-ops and there are only 3 ALU ports, execution ports are always
  612. //saturated first. The cycle limit is present only to guarantee loop termination.
  613. //Program size is limited to SuperscalarMaxSize instructions.
  614. for (decodeCycle = 0; decodeCycle < RANDOMX_SUPERSCALAR_LATENCY && !portsSaturated && programSize < SuperscalarMaxSize; ++decodeCycle) {
  615. //select a decode configuration
  616. decodeBuffer = decodeBuffer->fetchNext(currentInstruction.getType(), decodeCycle, mulCount, gen);
  617. if (trace) std::cout << "; ------------- fetch cycle " << cycle << " (" << decodeBuffer->getName() << ")" << std::endl;
  618. int bufferIndex = 0;
  619. //fill all instruction slots in the current decode buffer
  620. while (bufferIndex < decodeBuffer->getSize()) {
  621. int topCycle = cycle;
  622. //if we have issued all macro-ops for the current RandomX instruction, create a new instruction
  623. if (macroOpIndex >= currentInstruction.getInfo().getSize()) {
  624. if (portsSaturated || programSize >= SuperscalarMaxSize)
  625. break;
  626. //select an instruction so that the first macro-op fits into the current slot
  627. currentInstruction.createForSlot(gen, decodeBuffer->getCounts()[bufferIndex], decodeBuffer->getIndex(), decodeBuffer->getSize() == bufferIndex + 1, bufferIndex == 0);
  628. macroOpIndex = 0;
  629. if (trace) std::cout << "; " << currentInstruction.getInfo().getName() << std::endl;
  630. }
  631. const MacroOp& mop = currentInstruction.getInfo().getOp(macroOpIndex);
  632. if (trace) std::cout << mop.getName() << " ";
  633. //calculate the earliest cycle when this macro-op (all of its uOPs) can be scheduled for execution
  634. int scheduleCycle = scheduleMop<false>(mop, portBusy, cycle, depCycle);
  635. if (scheduleCycle < 0) {
  636. if (trace) std::cout << "Unable to map operation '" << mop.getName() << "' to execution port (cycle " << cycle << ")" << std::endl;
  637. //__debugbreak();
  638. portsSaturated = true;
  639. break;
  640. }
  641. //find a source register (if applicable) that will be ready when this instruction executes
  642. if (macroOpIndex == currentInstruction.getInfo().getSrcOp()) {
  643. int forward;
  644. //if no suitable operand is ready, look up to LOOK_FORWARD_CYCLES forward
  645. for (forward = 0; forward < LOOK_FORWARD_CYCLES && !currentInstruction.selectSource(scheduleCycle, registers, gen); ++forward) {
  646. if (trace) std::cout << "; src STALL at cycle " << cycle << std::endl;
  647. ++scheduleCycle;
  648. ++cycle;
  649. }
  650. //if no register was found, throw the instruction away and try another one
  651. if (forward == LOOK_FORWARD_CYCLES) {
  652. if (throwAwayCount < MAX_THROWAWAY_COUNT) {
  653. throwAwayCount++;
  654. macroOpIndex = currentInstruction.getInfo().getSize();
  655. if (trace) std::cout << "; THROW away " << currentInstruction.getInfo().getName() << std::endl;
  656. //cycle = topCycle;
  657. continue;
  658. }
  659. //abort this decode buffer
  660. if (trace) std::cout << "Aborting at cycle " << cycle << " with decode buffer " << decodeBuffer->getName() << " - source registers not available for operation " << currentInstruction.getInfo().getName() << std::endl;
  661. currentInstruction = SuperscalarInstruction::Null;
  662. break;
  663. }
  664. if (trace) std::cout << "; src = r" << currentInstruction.getSource() << std::endl;
  665. }
  666. //find a destination register that will be ready when this instruction executes
  667. if (macroOpIndex == currentInstruction.getInfo().getDstOp()) {
  668. int forward;
  669. for (forward = 0; forward < LOOK_FORWARD_CYCLES && !currentInstruction.selectDestination(scheduleCycle, throwAwayCount > 0, registers, gen); ++forward) {
  670. if (trace) std::cout << "; dst STALL at cycle " << cycle << std::endl;
  671. ++scheduleCycle;
  672. ++cycle;
  673. }
  674. if (forward == LOOK_FORWARD_CYCLES) { //throw instruction away
  675. if (throwAwayCount < MAX_THROWAWAY_COUNT) {
  676. throwAwayCount++;
  677. macroOpIndex = currentInstruction.getInfo().getSize();
  678. if (trace) std::cout << "; THROW away " << currentInstruction.getInfo().getName() << std::endl;
  679. //cycle = topCycle;
  680. continue;
  681. }
  682. //abort this decode buffer
  683. if (trace) std::cout << "Aborting at cycle " << cycle << " with decode buffer " << decodeBuffer->getName() << " - destination registers not available" << std::endl;
  684. currentInstruction = SuperscalarInstruction::Null;
  685. break;
  686. }
  687. if (trace) std::cout << "; dst = r" << currentInstruction.getDestination() << std::endl;
  688. }
  689. throwAwayCount = 0;
  690. //recalculate when the instruction can be scheduled for execution based on operand availability
  691. scheduleCycle = scheduleMop<true>(mop, portBusy, scheduleCycle, scheduleCycle);
  692. if (scheduleCycle < 0) {
  693. if (trace) std::cout << "Unable to map operation '" << mop.getName() << "' to execution port (cycle " << scheduleCycle << ")" << std::endl;
  694. portsSaturated = true;
  695. break;
  696. }
  697. //calculate when the result will be ready
  698. depCycle = scheduleCycle + mop.getLatency();
  699. //if this instruction writes the result, modify register information
  700. // RegisterInfo.latency - which cycle the register will be ready
  701. // RegisterInfo.lastOpGroup - the last operation that was applied to the register
  702. // RegisterInfo.lastOpPar - the last operation source value (-1 = constant, 0-7 = register)
  703. if (macroOpIndex == currentInstruction.getInfo().getResultOp()) {
  704. int dst = currentInstruction.getDestination();
  705. RegisterInfo& ri = registers[dst];
  706. retireCycle = depCycle;
  707. ri.latency = retireCycle;
  708. ri.lastOpGroup = currentInstruction.getGroup();
  709. ri.lastOpPar = currentInstruction.getGroupPar();
  710. if (trace) std::cout << "; RETIRED at cycle " << retireCycle << std::endl;
  711. }
  712. codeSize += mop.getSize();
  713. bufferIndex++;
  714. macroOpIndex++;
  715. macroOpCount++;
  716. //terminating condition
  717. if (scheduleCycle >= RANDOMX_SUPERSCALAR_LATENCY) {
  718. portsSaturated = true;
  719. }
  720. cycle = topCycle;
  721. //when all macro-ops of the current instruction have been issued, add the instruction into the program
  722. if (macroOpIndex >= currentInstruction.getInfo().getSize()) {
  723. currentInstruction.toInstr(prog(programSize++));
  724. mulCount += isMultiplication(currentInstruction.getType());
  725. }
  726. }
  727. ++cycle;
  728. }
  729. double ipc = (macroOpCount / (double)retireCycle);
  730. memset(prog.asicLatencies, 0, sizeof(prog.asicLatencies));
  731. //Calculate ASIC latency:
  732. //Assumes 1 cycle latency for all operations and unlimited parallelization.
  733. for (int i = 0; i < programSize; ++i) {
  734. Instruction& instr = prog(i);
  735. int latDst = prog.asicLatencies[instr.dst] + 1;
  736. int latSrc = instr.dst != instr.src ? prog.asicLatencies[instr.src] + 1 : 0;
  737. prog.asicLatencies[instr.dst] = std::max(latDst, latSrc);
  738. }
  739. //address register is the register with the highest ASIC latency
  740. int asicLatencyMax = 0;
  741. int addressReg = 0;
  742. for (int i = 0; i < 8; ++i) {
  743. if (prog.asicLatencies[i] > asicLatencyMax) {
  744. asicLatencyMax = prog.asicLatencies[i];
  745. addressReg = i;
  746. }
  747. prog.cpuLatencies[i] = registers[i].latency;
  748. }
  749. prog.setSize(programSize);
  750. prog.setAddressRegister(addressReg);
  751. prog.cpuLatency = retireCycle;
  752. prog.asicLatency = asicLatencyMax;
  753. prog.codeSize = codeSize;
  754. prog.macroOps = macroOpCount;
  755. prog.decodeCycles = decodeCycle;
  756. prog.ipc = ipc;
  757. prog.mulCount = mulCount;
  758. /*if(INFO) std::cout << "; ALU port utilization:" << std::endl;
  759. if (INFO) std::cout << "; (* = in use, _ = idle)" << std::endl;
  760. int portCycles = 0;
  761. for (int i = 0; i < CYCLE_MAP_SIZE; ++i) {
  762. std::cout << "; " << std::setw(3) << i << " ";
  763. for (int j = 0; j < 3; ++j) {
  764. std::cout << (portBusy[i][j] ? '*' : '_');
  765. portCycles += !!portBusy[i][j];
  766. }
  767. std::cout << std::endl;
  768. }*/
  769. }
  770. void executeSuperscalar(int_reg_t(&r)[8], SuperscalarProgram& prog, std::vector<uint64_t> *reciprocals) {
  771. for (unsigned j = 0; j < prog.getSize(); ++j) {
  772. Instruction& instr = prog(j);
  773. switch ((SuperscalarInstructionType)instr.opcode)
  774. {
  775. case SuperscalarInstructionType::ISUB_R:
  776. r[instr.dst] -= r[instr.src];
  777. break;
  778. case SuperscalarInstructionType::IXOR_R:
  779. r[instr.dst] ^= r[instr.src];
  780. break;
  781. case SuperscalarInstructionType::IADD_RS:
  782. r[instr.dst] += r[instr.src] << instr.getModShift();
  783. break;
  784. case SuperscalarInstructionType::IMUL_R:
  785. r[instr.dst] *= r[instr.src];
  786. break;
  787. case SuperscalarInstructionType::IROR_C:
  788. r[instr.dst] = rotr(r[instr.dst], instr.getImm32());
  789. break;
  790. case SuperscalarInstructionType::IADD_C7:
  791. case SuperscalarInstructionType::IADD_C8:
  792. case SuperscalarInstructionType::IADD_C9:
  793. r[instr.dst] += signExtend2sCompl(instr.getImm32());
  794. break;
  795. case SuperscalarInstructionType::IXOR_C7:
  796. case SuperscalarInstructionType::IXOR_C8:
  797. case SuperscalarInstructionType::IXOR_C9:
  798. r[instr.dst] ^= signExtend2sCompl(instr.getImm32());
  799. break;
  800. case SuperscalarInstructionType::IMULH_R:
  801. r[instr.dst] = mulh(r[instr.dst], r[instr.src]);
  802. break;
  803. case SuperscalarInstructionType::ISMULH_R:
  804. r[instr.dst] = smulh(r[instr.dst], r[instr.src]);
  805. break;
  806. case SuperscalarInstructionType::IMUL_RCP:
  807. if (reciprocals != nullptr)
  808. r[instr.dst] *= (*reciprocals)[instr.getImm32()];
  809. else
  810. r[instr.dst] *= randomx_reciprocal(instr.getImm32());
  811. break;
  812. default:
  813. UNREACHABLE;
  814. }
  815. }
  816. }
  817. }