superscalar.cpp 34 KB

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