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