| 1 | //========- utils/TableGen/X86InstrMappingEmitter.cpp - X86 backend-*- C++ -*-// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | /// |
| 9 | /// This tablegen backend is responsible for emitting the X86 backend |
| 10 | /// instruction mapping. |
| 11 | /// |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "Common/CodeGenInstruction.h" |
| 15 | #include "Common/CodeGenTarget.h" |
| 16 | #include "X86RecognizableInstr.h" |
| 17 | #include "llvm/TableGen/Error.h" |
| 18 | #include "llvm/TableGen/Record.h" |
| 19 | #include "llvm/TableGen/TableGenBackend.h" |
| 20 | #include <map> |
| 21 | #include <set> |
| 22 | |
| 23 | using namespace llvm; |
| 24 | using namespace X86Disassembler; |
| 25 | |
| 26 | namespace { |
| 27 | |
| 28 | class X86InstrMappingEmitter { |
| 29 | const RecordKeeper &Records; |
| 30 | const CodeGenTarget Target; |
| 31 | |
| 32 | // Hold all pontentially compressible EVEX instructions |
| 33 | std::vector<const CodeGenInstruction *> PreCompressionInsts; |
| 34 | // Hold all compressed instructions. Divided into groups with same opcodes |
| 35 | // to make the search more efficient |
| 36 | std::map<uint64_t, std::vector<const CodeGenInstruction *>> CompressedInsts; |
| 37 | |
| 38 | using Entry = |
| 39 | std::pair<const CodeGenInstruction *, const CodeGenInstruction *>; |
| 40 | using PredicateInstMap = |
| 41 | std::map<StringRef, std::vector<const CodeGenInstruction *>>; |
| 42 | |
| 43 | // Hold all compressed instructions that need to check predicate |
| 44 | PredicateInstMap PredicateInsts; |
| 45 | |
| 46 | public: |
| 47 | X86InstrMappingEmitter(const RecordKeeper &R) : Records(R), Target(R) {} |
| 48 | |
| 49 | // run - Output X86 EVEX compression tables. |
| 50 | void run(raw_ostream &OS); |
| 51 | |
| 52 | private: |
| 53 | void emitCompressEVEXTable(ArrayRef<const CodeGenInstruction *> Insts, |
| 54 | raw_ostream &OS); |
| 55 | void emitNFTransformTable(ArrayRef<const CodeGenInstruction *> Insts, |
| 56 | raw_ostream &OS); |
| 57 | void emitND2NonNDTable(ArrayRef<const CodeGenInstruction *> Insts, |
| 58 | raw_ostream &OS); |
| 59 | void emitSSE2AVXTable(ArrayRef<const CodeGenInstruction *> Insts, |
| 60 | raw_ostream &OS); |
| 61 | |
| 62 | // Prints the definition of class X86TableEntry. |
| 63 | void printClassDef(raw_ostream &OS); |
| 64 | // Prints the given table as a C++ array of type X86TableEntry under the guard |
| 65 | // \p Macro. |
| 66 | void printTable(ArrayRef<Entry> Table, StringRef Name, StringRef Macro, |
| 67 | raw_ostream &OS); |
| 68 | }; |
| 69 | } // namespace |
| 70 | |
| 71 | void X86InstrMappingEmitter::printClassDef(raw_ostream &OS) { |
| 72 | OS << "struct X86TableEntry {\n" |
| 73 | " uint16_t OldOpc;\n" |
| 74 | " uint16_t NewOpc;\n" |
| 75 | " bool operator<(const X86TableEntry &RHS) const {\n" |
| 76 | " return OldOpc < RHS.OldOpc;\n" |
| 77 | " }" |
| 78 | " friend bool operator<(const X86TableEntry &TE, unsigned Opc) {\n" |
| 79 | " return TE.OldOpc < Opc;\n" |
| 80 | " }\n" |
| 81 | "};" ; |
| 82 | |
| 83 | OS << "\n\n" ; |
| 84 | } |
| 85 | |
| 86 | static void printMacroBegin(StringRef Macro, raw_ostream &OS) { |
| 87 | OS << "\n#ifdef " << Macro << "\n" ; |
| 88 | } |
| 89 | |
| 90 | static void printMacroEnd(StringRef Macro, raw_ostream &OS) { |
| 91 | OS << "#endif // " << Macro << "\n\n" ; |
| 92 | } |
| 93 | |
| 94 | void X86InstrMappingEmitter::printTable(ArrayRef<Entry> Table, StringRef Name, |
| 95 | StringRef Macro, raw_ostream &OS) { |
| 96 | printMacroBegin(Macro, OS); |
| 97 | |
| 98 | OS << "static const X86TableEntry " << Name << "[] = {\n" ; |
| 99 | |
| 100 | // Print all entries added to the table |
| 101 | for (const auto &Pair : Table) |
| 102 | OS << " { X86::" << Pair.first->getName() |
| 103 | << ", X86::" << Pair.second->getName() << " },\n" ; |
| 104 | |
| 105 | OS << "};\n\n" ; |
| 106 | |
| 107 | printMacroEnd(Macro, OS); |
| 108 | } |
| 109 | |
| 110 | namespace { |
| 111 | class IsMatch { |
| 112 | const CodeGenInstruction *OldInst; |
| 113 | |
| 114 | public: |
| 115 | IsMatch(const CodeGenInstruction *OldInst) : OldInst(OldInst) {} |
| 116 | |
| 117 | bool operator()(const CodeGenInstruction *NewInst) { |
| 118 | RecognizableInstrBase NewRI(*NewInst); |
| 119 | RecognizableInstrBase OldRI(*OldInst); |
| 120 | |
| 121 | // Return false if any of the following fields of does not match. |
| 122 | if (std::tuple(OldRI.IsCodeGenOnly, OldRI.OpMap, NewRI.OpPrefix, |
| 123 | OldRI.HasVEX_4V, OldRI.HasVEX_L, OldRI.HasREX_W, |
| 124 | OldRI.Form) != |
| 125 | std::tuple(NewRI.IsCodeGenOnly, NewRI.OpMap, OldRI.OpPrefix, |
| 126 | NewRI.HasVEX_4V, NewRI.HasVEX_L, NewRI.HasREX_W, NewRI.Form)) |
| 127 | return false; |
| 128 | |
| 129 | for (unsigned I = 0, E = OldInst->Operands.size(); I < E; ++I) { |
| 130 | const Record *OldOpRec = OldInst->Operands[I].Rec; |
| 131 | const Record *NewOpRec = NewInst->Operands[I].Rec; |
| 132 | |
| 133 | if (OldOpRec == NewOpRec) |
| 134 | continue; |
| 135 | |
| 136 | if (isRegisterOperand(Rec: OldOpRec) && isRegisterOperand(Rec: NewOpRec)) { |
| 137 | if (getRegOperandSize(RegRec: OldOpRec) != getRegOperandSize(RegRec: NewOpRec)) |
| 138 | return false; |
| 139 | } else if (isMemoryOperand(Rec: OldOpRec) && isMemoryOperand(Rec: NewOpRec)) { |
| 140 | if (getMemOperandSize(MemRec: OldOpRec) != getMemOperandSize(MemRec: NewOpRec)) |
| 141 | return false; |
| 142 | } else if (isImmediateOperand(Rec: OldOpRec) && isImmediateOperand(Rec: NewOpRec)) { |
| 143 | if (OldOpRec->getValueAsDef(FieldName: "Type" ) != NewOpRec->getValueAsDef(FieldName: "Type" )) |
| 144 | return false; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | return true; |
| 149 | } |
| 150 | }; |
| 151 | } // namespace |
| 152 | |
| 153 | static bool isInteresting(const Record *Rec) { |
| 154 | // _REV instruction should not appear before encoding optimization |
| 155 | return Rec->isSubClassOf(Name: "X86Inst" ) && |
| 156 | !Rec->getValueAsBit(FieldName: "isAsmParserOnly" ) && |
| 157 | !Rec->getName().ends_with(Suffix: "_REV" ); |
| 158 | } |
| 159 | |
| 160 | void X86InstrMappingEmitter::emitCompressEVEXTable( |
| 161 | ArrayRef<const CodeGenInstruction *> Insts, raw_ostream &OS) { |
| 162 | |
| 163 | const std::map<StringRef, StringRef> ManualMap = { |
| 164 | #define ENTRY(OLD, NEW) {#OLD, #NEW}, |
| 165 | #include "X86ManualInstrMapping.def" |
| 166 | }; |
| 167 | const std::set<StringRef> NoCompressSet = { |
| 168 | #define NOCOMP(INSN) #INSN, |
| 169 | #include "X86ManualInstrMapping.def" |
| 170 | }; |
| 171 | |
| 172 | for (const CodeGenInstruction *Inst : Insts) { |
| 173 | const Record *Rec = Inst->TheDef; |
| 174 | StringRef Name = Rec->getName(); |
| 175 | if (!isInteresting(Rec)) |
| 176 | continue; |
| 177 | |
| 178 | // Promoted legacy instruction is in EVEX space, and has REX2-encoding |
| 179 | // alternative. It's added due to HW design and never emitted by compiler. |
| 180 | if (byteFromBitsInit(B: Rec->getValueAsBitsInit(FieldName: "OpMapBits" )) == |
| 181 | X86Local::T_MAP4 && |
| 182 | byteFromBitsInit(B: Rec->getValueAsBitsInit(FieldName: "explicitOpPrefixBits" )) == |
| 183 | X86Local::ExplicitEVEX) |
| 184 | continue; |
| 185 | |
| 186 | if (NoCompressSet.find(x: Name) != NoCompressSet.end()) |
| 187 | continue; |
| 188 | |
| 189 | RecognizableInstrBase RI(*Inst); |
| 190 | |
| 191 | bool IsND = RI.OpMap == X86Local::T_MAP4 && RI.HasEVEX_B && RI.HasVEX_4V; |
| 192 | bool IsSETZUCCm = Name == "SETZUCCm" ; |
| 193 | // Add VEX encoded instructions to one of CompressedInsts vectors according |
| 194 | // to it's opcode. |
| 195 | if (RI.Encoding == X86Local::VEX) |
| 196 | CompressedInsts[RI.Opcode].push_back(x: Inst); |
| 197 | // Add relevant EVEX encoded instructions to PreCompressionInsts |
| 198 | else if (RI.Encoding == X86Local::EVEX && !RI.HasEVEX_K && !RI.HasEVEX_L2 && |
| 199 | (!RI.HasEVEX_B || IsND || IsSETZUCCm)) |
| 200 | PreCompressionInsts.push_back(x: Inst); |
| 201 | } |
| 202 | |
| 203 | std::vector<Entry> Table; |
| 204 | for (const CodeGenInstruction *Inst : PreCompressionInsts) { |
| 205 | const Record *Rec = Inst->TheDef; |
| 206 | uint8_t Opcode = byteFromBitsInit(B: Rec->getValueAsBitsInit(FieldName: "Opcode" )); |
| 207 | StringRef Name = Rec->getName(); |
| 208 | const CodeGenInstruction *NewInst = nullptr; |
| 209 | if (ManualMap.find(x: Name) != ManualMap.end()) { |
| 210 | const Record *NewRec = Records.getDef(Name: ManualMap.at(k: Rec->getName())); |
| 211 | assert(NewRec && "Instruction not found!" ); |
| 212 | NewInst = &Target.getInstruction(InstRec: NewRec); |
| 213 | } else if (Name.ends_with(Suffix: "_EVEX" )) { |
| 214 | if (const auto *NewRec = Records.getDef(Name: Name.drop_back(N: 5))) |
| 215 | NewInst = &Target.getInstruction(InstRec: NewRec); |
| 216 | } else if (Name.ends_with(Suffix: "_ND" )) |
| 217 | // Leave it to ND2NONND table. |
| 218 | continue; |
| 219 | else { |
| 220 | // For each pre-compression instruction look for a match in the |
| 221 | // appropriate vector (instructions with the same opcode) using function |
| 222 | // object IsMatch. |
| 223 | const auto &Insts = CompressedInsts[Opcode]; |
| 224 | auto Match = llvm::find_if(Range: Insts, P: IsMatch(Inst)); |
| 225 | if (Match != Insts.end()) |
| 226 | NewInst = *Match; |
| 227 | } |
| 228 | |
| 229 | if (!NewInst) |
| 230 | continue; |
| 231 | |
| 232 | Table.emplace_back(args&: Inst, args&: NewInst); |
| 233 | auto Predicates = NewInst->TheDef->getValueAsListOfDefs(FieldName: "Predicates" ); |
| 234 | auto It = llvm::find_if(Range&: Predicates, P: [](const Record *R) { |
| 235 | StringRef Name = R->getName(); |
| 236 | return Name == "HasAVXNECONVERT" || Name == "HasAVXVNNI" || |
| 237 | Name == "HasAVXIFMA" || Name == "HasAVXVNNIINT8" || |
| 238 | Name == "HasAVXVNNIINT16" ; |
| 239 | }); |
| 240 | if (It != Predicates.end()) |
| 241 | PredicateInsts[(*It)->getValueAsString(FieldName: "CondString" )].push_back(x: NewInst); |
| 242 | } |
| 243 | |
| 244 | StringRef Macro = "GET_X86_COMPRESS_EVEX_TABLE" ; |
| 245 | printTable(Table, Name: "X86CompressEVEXTable" , Macro, OS); |
| 246 | |
| 247 | // Prints function which checks target feature for compressed instructions. |
| 248 | printMacroBegin(Macro, OS); |
| 249 | OS << "static bool checkPredicate(unsigned Opc, const X86Subtarget " |
| 250 | "*Subtarget) {\n" |
| 251 | << " switch (Opc) {\n" |
| 252 | << " default: return true;\n" ; |
| 253 | for (const auto &[Key, Val] : PredicateInsts) { |
| 254 | for (const auto &Inst : Val) |
| 255 | OS << " case X86::" << Inst->getName() << ":\n" ; |
| 256 | OS << " return " << Key << ";\n" ; |
| 257 | } |
| 258 | OS << " }\n" ; |
| 259 | OS << "}\n\n" ; |
| 260 | printMacroEnd(Macro, OS); |
| 261 | } |
| 262 | |
| 263 | void X86InstrMappingEmitter::emitNFTransformTable( |
| 264 | ArrayRef<const CodeGenInstruction *> Insts, raw_ostream &OS) { |
| 265 | std::vector<Entry> Table; |
| 266 | for (const CodeGenInstruction *Inst : Insts) { |
| 267 | const Record *Rec = Inst->TheDef; |
| 268 | if (!isInteresting(Rec)) |
| 269 | continue; |
| 270 | StringRef Name = Rec->getName(); |
| 271 | if (Name.contains(Other: "_NF" )) |
| 272 | continue; |
| 273 | |
| 274 | if (auto *NewRec = Name.consume_back(Suffix: "_ND" ) |
| 275 | ? Records.getDef(Name: Name.str() + "_NF_ND" ) |
| 276 | : Records.getDef(Name: Name.str() + "_NF" )) { |
| 277 | #ifndef NDEBUG |
| 278 | auto ClobberEFLAGS = [](const Record *R) { |
| 279 | return llvm::any_of( |
| 280 | R->getValueAsListOfDefs("Defs" ), |
| 281 | [](const Record *Def) { return Def->getName() == "EFLAGS" ; }); |
| 282 | }; |
| 283 | if (ClobberEFLAGS(NewRec)) |
| 284 | report_fatal_error("EFLAGS should not be clobbered by " + |
| 285 | NewRec->getName()); |
| 286 | if (!ClobberEFLAGS(Rec)) |
| 287 | report_fatal_error("EFLAGS should be clobbered by " + Rec->getName()); |
| 288 | #endif |
| 289 | Table.emplace_back(args&: Inst, args: &Target.getInstruction(InstRec: NewRec)); |
| 290 | } |
| 291 | } |
| 292 | printTable(Table, Name: "X86NFTransformTable" , Macro: "GET_X86_NF_TRANSFORM_TABLE" , OS); |
| 293 | } |
| 294 | |
| 295 | void X86InstrMappingEmitter::emitND2NonNDTable( |
| 296 | ArrayRef<const CodeGenInstruction *> Insts, raw_ostream &OS) { |
| 297 | |
| 298 | const std::map<StringRef, StringRef> ManualMap = { |
| 299 | #define ENTRY_ND(OLD, NEW) {#OLD, #NEW}, |
| 300 | #include "X86ManualInstrMapping.def" |
| 301 | }; |
| 302 | const std::set<StringRef> NoCompressSet = { |
| 303 | #define NOCOMP_ND(INSN) #INSN, |
| 304 | #include "X86ManualInstrMapping.def" |
| 305 | }; |
| 306 | |
| 307 | std::vector<Entry> Table; |
| 308 | for (const CodeGenInstruction *Inst : Insts) { |
| 309 | const Record *Rec = Inst->TheDef; |
| 310 | StringRef Name = Rec->getName(); |
| 311 | if (!isInteresting(Rec) || NoCompressSet.find(x: Name) != NoCompressSet.end()) |
| 312 | continue; |
| 313 | if (ManualMap.find(x: Name) != ManualMap.end()) { |
| 314 | const auto *NewRec = Records.getDef(Name: ManualMap.at(k: Rec->getName())); |
| 315 | assert(NewRec && "Instruction not found!" ); |
| 316 | auto &NewInst = Target.getInstruction(InstRec: NewRec); |
| 317 | Table.emplace_back(args&: Inst, args: &NewInst); |
| 318 | continue; |
| 319 | } |
| 320 | |
| 321 | if (!Name.ends_with(Suffix: "_ND" )) |
| 322 | continue; |
| 323 | const auto *NewRec = Records.getDef(Name: Name.drop_back(N: 3)); |
| 324 | if (!NewRec) |
| 325 | continue; |
| 326 | const auto &NewInst = Target.getInstruction(InstRec: NewRec); |
| 327 | if (isRegisterOperand(Rec: NewInst.Operands[0].Rec)) |
| 328 | Table.emplace_back(args&: Inst, args: &NewInst); |
| 329 | } |
| 330 | printTable(Table, Name: "X86ND2NonNDTable" , Macro: "GET_X86_ND2NONND_TABLE" , OS); |
| 331 | } |
| 332 | |
| 333 | void X86InstrMappingEmitter::emitSSE2AVXTable( |
| 334 | ArrayRef<const CodeGenInstruction *> Insts, raw_ostream &OS) { |
| 335 | |
| 336 | const std::map<StringRef, StringRef> ManualMap = { |
| 337 | #define ENTRY_SSE2AVX(OLD, NEW) {#OLD, #NEW}, |
| 338 | #include "X86ManualInstrMapping.def" |
| 339 | }; |
| 340 | |
| 341 | std::vector<Entry> Table; |
| 342 | for (const CodeGenInstruction *Inst : Insts) { |
| 343 | const Record *Rec = Inst->TheDef; |
| 344 | StringRef Name = Rec->getName(); |
| 345 | if (!isInteresting(Rec)) |
| 346 | continue; |
| 347 | if (ManualMap.find(x: Name) != ManualMap.end()) { |
| 348 | const auto *NewRec = Records.getDef(Name: ManualMap.at(k: Rec->getName())); |
| 349 | assert(NewRec && "Instruction not found!" ); |
| 350 | const auto &NewInst = Target.getInstruction(InstRec: NewRec); |
| 351 | Table.emplace_back(args&: Inst, args: &NewInst); |
| 352 | continue; |
| 353 | } |
| 354 | |
| 355 | std::string NewName = ("V" + Name).str(); |
| 356 | const auto *AVXRec = Records.getDef(Name: NewName); |
| 357 | if (!AVXRec) |
| 358 | continue; |
| 359 | auto &AVXInst = Target.getInstruction(InstRec: AVXRec); |
| 360 | Table.emplace_back(args&: Inst, args: &AVXInst); |
| 361 | } |
| 362 | printTable(Table, Name: "X86SSE2AVXTable" , Macro: "GET_X86_SSE2AVX_TABLE" , OS); |
| 363 | } |
| 364 | |
| 365 | void X86InstrMappingEmitter::run(raw_ostream &OS) { |
| 366 | emitSourceFileHeader(Desc: "X86 instruction mapping" , OS); |
| 367 | |
| 368 | ArrayRef<const CodeGenInstruction *> Insts = Target.getInstructions(); |
| 369 | printClassDef(OS); |
| 370 | emitCompressEVEXTable(Insts, OS); |
| 371 | emitNFTransformTable(Insts, OS); |
| 372 | emitND2NonNDTable(Insts, OS); |
| 373 | emitSSE2AVXTable(Insts, OS); |
| 374 | } |
| 375 | |
| 376 | static TableGen::Emitter::OptClass<X86InstrMappingEmitter> |
| 377 | X("gen-x86-instr-mapping" , "Generate X86 instruction mapping" ); |
| 378 | |