xref: /freebsd/contrib/llvm-project/llvm/include/llvm/ExecutionEngine/JITLink/ppc64.h (revision 5f757f3ff9144b609b3c433dfd370cc6bdc191ad)
1 //===--- ppc64.h - Generic JITLink ppc64 edge kinds, utilities --*- 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 // Generic utilities for graphs representing 64-bit PowerPC objects.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #ifndef LLVM_EXECUTIONENGINE_JITLINK_PPC64_H
14 #define LLVM_EXECUTIONENGINE_JITLINK_PPC64_H
15 
16 #include "llvm/ExecutionEngine/JITLink/JITLink.h"
17 #include "llvm/ExecutionEngine/JITLink/TableManager.h"
18 #include "llvm/Support/Endian.h"
19 
20 namespace llvm::jitlink::ppc64 {
21 
22 /// Represents ppc64 fixups and other ppc64-specific edge kinds.
23 enum EdgeKind_ppc64 : Edge::Kind {
24   Pointer64 = Edge::FirstRelocation,
25   Pointer32,
26   Pointer16,
27   Pointer16DS,
28   Pointer16HA,
29   Pointer16HI,
30   Pointer16HIGH,
31   Pointer16HIGHA,
32   Pointer16HIGHER,
33   Pointer16HIGHERA,
34   Pointer16HIGHEST,
35   Pointer16HIGHESTA,
36   Pointer16LO,
37   Pointer16LODS,
38   Pointer14,
39   Delta64,
40   Delta34,
41   Delta32,
42   NegDelta32,
43   Delta16,
44   Delta16HA,
45   Delta16HI,
46   Delta16LO,
47   TOC,
48   TOCDelta16,
49   TOCDelta16DS,
50   TOCDelta16HA,
51   TOCDelta16HI,
52   TOCDelta16LO,
53   TOCDelta16LODS,
54   RequestGOTAndTransformToDelta34,
55   CallBranchDelta,
56   // Need to restore r2 after the bl, suggesting the bl is followed by a nop.
57   CallBranchDeltaRestoreTOC,
58   // Request calling function with TOC.
59   RequestCall,
60   // Request calling function without TOC.
61   RequestCallNoTOC,
62   RequestTLSDescInGOTAndTransformToTOCDelta16HA,
63   RequestTLSDescInGOTAndTransformToTOCDelta16LO,
64   RequestTLSDescInGOTAndTransformToDelta34,
65 };
66 
67 enum PLTCallStubKind {
68   // Setup function entry(r12) and long branch to target using TOC.
69   LongBranch,
70   // Save TOC pointer, setup function entry and long branch to target using TOC.
71   LongBranchSaveR2,
72   // Setup function entry(r12) and long branch to target without using TOC.
73   LongBranchNoTOC,
74 };
75 
76 extern const char NullPointerContent[8];
77 extern const char PointerJumpStubContent_big[20];
78 extern const char PointerJumpStubContent_little[20];
79 extern const char PointerJumpStubNoTOCContent_big[32];
80 extern const char PointerJumpStubNoTOCContent_little[32];
81 
82 struct PLTCallStubReloc {
83   Edge::Kind K;
84   size_t Offset;
85   Edge::AddendT A;
86 };
87 
88 struct PLTCallStubInfo {
89   ArrayRef<char> Content;
90   SmallVector<PLTCallStubReloc, 2> Relocs;
91 };
92 
93 template <llvm::endianness Endianness>
94 inline PLTCallStubInfo pickStub(PLTCallStubKind StubKind) {
95   constexpr bool isLE = Endianness == llvm::endianness::little;
96   switch (StubKind) {
97   case LongBranch: {
98     ArrayRef<char> Content =
99         isLE ? PointerJumpStubContent_little : PointerJumpStubContent_big;
100     // Skip save r2.
101     Content = Content.slice(4);
102     size_t Offset = isLE ? 0 : 2;
103     return PLTCallStubInfo{
104         Content,
105         {{TOCDelta16HA, Offset, 0}, {TOCDelta16LO, Offset + 4, 0}},
106     };
107   }
108   case LongBranchSaveR2: {
109     ArrayRef<char> Content =
110         isLE ? PointerJumpStubContent_little : PointerJumpStubContent_big;
111     size_t Offset = isLE ? 4 : 6;
112     return PLTCallStubInfo{
113         Content,
114         {{TOCDelta16HA, Offset, 0}, {TOCDelta16LO, Offset + 4, 0}},
115     };
116   }
117   case LongBranchNoTOC: {
118     ArrayRef<char> Content = isLE ? PointerJumpStubNoTOCContent_little
119                                   : PointerJumpStubNoTOCContent_big;
120     size_t Offset = isLE ? 16 : 18;
121     Edge::AddendT Addend = isLE ? 8 : 10;
122     return PLTCallStubInfo{
123         Content,
124         {{Delta16HA, Offset, Addend}, {Delta16LO, Offset + 4, Addend + 4}},
125     };
126   }
127   }
128   llvm_unreachable("Unknown PLTCallStubKind enum");
129 }
130 
131 inline Symbol &createAnonymousPointer(LinkGraph &G, Section &PointerSection,
132                                       Symbol *InitialTarget = nullptr,
133                                       uint64_t InitialAddend = 0) {
134   assert(G.getPointerSize() == sizeof(NullPointerContent) &&
135          "LinkGraph's pointer size should be consistent with size of "
136          "NullPointerContent");
137   Block &B = G.createContentBlock(PointerSection, NullPointerContent,
138                                   orc::ExecutorAddr(), G.getPointerSize(), 0);
139   if (InitialTarget)
140     B.addEdge(Pointer64, 0, *InitialTarget, InitialAddend);
141   return G.addAnonymousSymbol(B, 0, G.getPointerSize(), false, false);
142 }
143 
144 template <llvm::endianness Endianness>
145 inline Symbol &createAnonymousPointerJumpStub(LinkGraph &G,
146                                               Section &StubSection,
147                                               Symbol &PointerSymbol,
148                                               PLTCallStubKind StubKind) {
149   PLTCallStubInfo StubInfo = pickStub<Endianness>(StubKind);
150   Block &B = G.createContentBlock(StubSection, StubInfo.Content,
151                                   orc::ExecutorAddr(), 4, 0);
152   for (auto const &Reloc : StubInfo.Relocs)
153     B.addEdge(Reloc.K, Reloc.Offset, PointerSymbol, Reloc.A);
154   return G.addAnonymousSymbol(B, 0, StubInfo.Content.size(), true, false);
155 }
156 
157 template <llvm::endianness Endianness>
158 class TOCTableManager : public TableManager<TOCTableManager<Endianness>> {
159 public:
160   // FIXME: `llvm-jitlink -check` relies this name to be $__GOT.
161   static StringRef getSectionName() { return "$__GOT"; }
162 
163   bool visitEdge(LinkGraph &G, Block *B, Edge &E) {
164     Edge::Kind K = E.getKind();
165     switch (K) {
166     case TOCDelta16HA:
167     case TOCDelta16LO:
168     case TOCDelta16DS:
169     case TOCDelta16LODS:
170     case CallBranchDeltaRestoreTOC:
171     case RequestCall:
172       // Create TOC section if TOC relocation, PLT or GOT is used.
173       getOrCreateTOCSection(G);
174       return false;
175     case RequestGOTAndTransformToDelta34:
176       E.setKind(ppc64::Delta34);
177       E.setTarget(createEntry(G, E.getTarget()));
178       return true;
179     default:
180       return false;
181     }
182   }
183 
184   Symbol &createEntry(LinkGraph &G, Symbol &Target) {
185     return createAnonymousPointer(G, getOrCreateTOCSection(G), &Target);
186   }
187 
188 private:
189   Section &getOrCreateTOCSection(LinkGraph &G) {
190     TOCSection = G.findSectionByName(getSectionName());
191     if (!TOCSection)
192       TOCSection = &G.createSection(getSectionName(), orc::MemProt::Read);
193     return *TOCSection;
194   }
195 
196   Section *TOCSection = nullptr;
197 };
198 
199 template <llvm::endianness Endianness>
200 class PLTTableManager : public TableManager<PLTTableManager<Endianness>> {
201 public:
202   PLTTableManager(TOCTableManager<Endianness> &TOC) : TOC(TOC) {}
203 
204   static StringRef getSectionName() { return "$__STUBS"; }
205 
206   // FIXME: One external symbol can only have one PLT stub in a object file.
207   // This is a limitation when we need different PLT stubs for the same symbol.
208   // For example, we need two different PLT stubs for `bl __tls_get_addr` and
209   // `bl __tls_get_addr@notoc`.
210   bool visitEdge(LinkGraph &G, Block *B, Edge &E) {
211     bool isExternal = E.getTarget().isExternal();
212     Edge::Kind K = E.getKind();
213     if (K == ppc64::RequestCall) {
214       if (isExternal) {
215         E.setKind(ppc64::CallBranchDeltaRestoreTOC);
216         this->StubKind = LongBranchSaveR2;
217         // FIXME: We assume the addend to the external target is zero. It's
218         // quite unusual that the addend of an external target to be non-zero as
219         // if we have known the layout of the external object.
220         E.setTarget(this->getEntryForTarget(G, E.getTarget()));
221         // Addend to the stub is zero.
222         E.setAddend(0);
223       } else
224         // TODO: There are cases a local function call need a call stub.
225         // 1. Caller uses TOC, the callee doesn't, need a r2 save stub.
226         // 2. Caller doesn't use TOC, the callee does, need a r12 setup stub.
227         // 3. Branching target is out of range.
228         E.setKind(ppc64::CallBranchDelta);
229       return true;
230     }
231     if (K == ppc64::RequestCallNoTOC) {
232       E.setKind(ppc64::CallBranchDelta);
233       this->StubKind = LongBranchNoTOC;
234       E.setTarget(this->getEntryForTarget(G, E.getTarget()));
235       return true;
236     }
237     return false;
238   }
239 
240   Symbol &createEntry(LinkGraph &G, Symbol &Target) {
241     return createAnonymousPointerJumpStub<Endianness>(
242         G, getOrCreateStubsSection(G), TOC.getEntryForTarget(G, Target),
243         this->StubKind);
244   }
245 
246 private:
247   Section &getOrCreateStubsSection(LinkGraph &G) {
248     PLTSection = G.findSectionByName(getSectionName());
249     if (!PLTSection)
250       PLTSection = &G.createSection(getSectionName(),
251                                     orc::MemProt::Read | orc::MemProt::Exec);
252     return *PLTSection;
253   }
254 
255   TOCTableManager<Endianness> &TOC;
256   Section *PLTSection = nullptr;
257   PLTCallStubKind StubKind;
258 };
259 
260 /// Returns a string name for the given ppc64 edge. For debugging purposes
261 /// only.
262 const char *getEdgeKindName(Edge::Kind K);
263 
264 inline static uint16_t ha(uint64_t x) { return (x + 0x8000) >> 16; }
265 inline static uint64_t lo(uint64_t x) { return x & 0xffff; }
266 inline static uint16_t hi(uint64_t x) { return x >> 16; }
267 inline static uint64_t high(uint64_t x) { return (x >> 16) & 0xffff; }
268 inline static uint64_t higha(uint64_t x) {
269   return ((x + 0x8000) >> 16) & 0xffff;
270 }
271 inline static uint64_t higher(uint64_t x) { return (x >> 32) & 0xffff; }
272 inline static uint64_t highera(uint64_t x) {
273   return ((x + 0x8000) >> 32) & 0xffff;
274 }
275 inline static uint16_t highest(uint64_t x) { return x >> 48; }
276 inline static uint16_t highesta(uint64_t x) { return (x + 0x8000) >> 48; }
277 
278 // Prefixed instruction introduced in ISAv3.1 consists of two 32-bit words,
279 // prefix word and suffix word, i.e., prefixed_instruction = concat(prefix_word,
280 // suffix_word). That's to say, for a prefixed instruction encoded in uint64_t,
281 // the most significant 32 bits belong to the prefix word. The prefix word is at
282 // low address for both big/little endian. Byte order in each word still follows
283 // its endian.
284 template <llvm::endianness Endianness>
285 inline static uint64_t readPrefixedInstruction(const char *Loc) {
286   constexpr bool isLE = Endianness == llvm::endianness::little;
287   uint64_t Inst = support::endian::read64<Endianness>(Loc);
288   return isLE ? (Inst << 32) | (Inst >> 32) : Inst;
289 }
290 
291 template <llvm::endianness Endianness>
292 inline static void writePrefixedInstruction(char *Loc, uint64_t Inst) {
293   constexpr bool isLE = Endianness == llvm::endianness::little;
294   Inst = isLE ? (Inst << 32) | (Inst >> 32) : Inst;
295   support::endian::write64<Endianness>(Loc, Inst);
296 }
297 
298 template <llvm::endianness Endianness>
299 inline Error relocateHalf16(char *FixupPtr, int64_t Value, Edge::Kind K) {
300   switch (K) {
301   case Delta16:
302   case Pointer16:
303   case TOCDelta16:
304     support::endian::write16<Endianness>(FixupPtr, Value);
305     break;
306   case Pointer16DS:
307   case TOCDelta16DS:
308     support::endian::write16<Endianness>(FixupPtr, Value & ~3);
309     break;
310   case Delta16HA:
311   case Pointer16HA:
312   case TOCDelta16HA:
313     support::endian::write16<Endianness>(FixupPtr, ha(Value));
314     break;
315   case Delta16HI:
316   case Pointer16HI:
317   case TOCDelta16HI:
318     support::endian::write16<Endianness>(FixupPtr, hi(Value));
319     break;
320   case Pointer16HIGH:
321     support::endian::write16<Endianness>(FixupPtr, high(Value));
322     break;
323   case Pointer16HIGHA:
324     support::endian::write16<Endianness>(FixupPtr, higha(Value));
325     break;
326   case Pointer16HIGHER:
327     support::endian::write16<Endianness>(FixupPtr, higher(Value));
328     break;
329   case Pointer16HIGHERA:
330     support::endian::write16<Endianness>(FixupPtr, highera(Value));
331     break;
332   case Pointer16HIGHEST:
333     support::endian::write16<Endianness>(FixupPtr, highest(Value));
334     break;
335   case Pointer16HIGHESTA:
336     support::endian::write16<Endianness>(FixupPtr, highesta(Value));
337     break;
338   case Delta16LO:
339   case Pointer16LO:
340   case TOCDelta16LO:
341     support::endian::write16<Endianness>(FixupPtr, lo(Value));
342     break;
343   case Pointer16LODS:
344   case TOCDelta16LODS:
345     support::endian::write16<Endianness>(FixupPtr, lo(Value) & ~3);
346     break;
347   default:
348     return make_error<JITLinkError>(
349         StringRef(getEdgeKindName(K)) +
350         " relocation does not write at half16 field");
351   }
352   return Error::success();
353 }
354 
355 /// Apply fixup expression for edge to block content.
356 template <llvm::endianness Endianness>
357 inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E,
358                         const Symbol *TOCSymbol) {
359   char *BlockWorkingMem = B.getAlreadyMutableContent().data();
360   char *FixupPtr = BlockWorkingMem + E.getOffset();
361   orc::ExecutorAddr FixupAddress = B.getAddress() + E.getOffset();
362   int64_t S = E.getTarget().getAddress().getValue();
363   int64_t A = E.getAddend();
364   int64_t P = FixupAddress.getValue();
365   int64_t TOCBase = TOCSymbol ? TOCSymbol->getAddress().getValue() : 0;
366   Edge::Kind K = E.getKind();
367 
368   DEBUG_WITH_TYPE("jitlink", {
369     dbgs() << "    Applying fixup on " << G.getEdgeKindName(K)
370            << " edge, (S, A, P, .TOC.) = (" << formatv("{0:x}", S) << ", "
371            << formatv("{0:x}", A) << ", " << formatv("{0:x}", P) << ", "
372            << formatv("{0:x}", TOCBase) << ")\n";
373   });
374 
375   switch (K) {
376   case Pointer64: {
377     uint64_t Value = S + A;
378     support::endian::write64<Endianness>(FixupPtr, Value);
379     break;
380   }
381   case Delta16:
382   case Delta16HA:
383   case Delta16HI:
384   case Delta16LO: {
385     int64_t Value = S + A - P;
386     if (LLVM_UNLIKELY(!isInt<32>(Value))) {
387       return makeTargetOutOfRangeError(G, B, E);
388     }
389     return relocateHalf16<Endianness>(FixupPtr, Value, K);
390   }
391   case TOC:
392     support::endian::write64<Endianness>(FixupPtr, TOCBase);
393     break;
394   case Pointer16:
395   case Pointer16DS:
396   case Pointer16HA:
397   case Pointer16HI:
398   case Pointer16HIGH:
399   case Pointer16HIGHA:
400   case Pointer16HIGHER:
401   case Pointer16HIGHERA:
402   case Pointer16HIGHEST:
403   case Pointer16HIGHESTA:
404   case Pointer16LO:
405   case Pointer16LODS: {
406     uint64_t Value = S + A;
407     if (LLVM_UNLIKELY(!isInt<32>(Value))) {
408       return makeTargetOutOfRangeError(G, B, E);
409     }
410     return relocateHalf16<Endianness>(FixupPtr, Value, K);
411   }
412   case Pointer14: {
413     static const uint32_t Low14Mask = 0xfffc;
414     uint64_t Value = S + A;
415     assert((Value & 3) == 0 && "Pointer14 requires 4-byte alignment");
416     if (LLVM_UNLIKELY(!isInt<16>(Value))) {
417       return makeTargetOutOfRangeError(G, B, E);
418     }
419     uint32_t Inst = support::endian::read32<Endianness>(FixupPtr);
420     support::endian::write32<Endianness>(FixupPtr, (Inst & ~Low14Mask) |
421                                                        (Value & Low14Mask));
422     break;
423   }
424   case TOCDelta16:
425   case TOCDelta16DS:
426   case TOCDelta16HA:
427   case TOCDelta16HI:
428   case TOCDelta16LO:
429   case TOCDelta16LODS: {
430     int64_t Value = S + A - TOCBase;
431     if (LLVM_UNLIKELY(!isInt<32>(Value))) {
432       return makeTargetOutOfRangeError(G, B, E);
433     }
434     return relocateHalf16<Endianness>(FixupPtr, Value, K);
435   }
436   case CallBranchDeltaRestoreTOC:
437   case CallBranchDelta: {
438     int64_t Value = S + A - P;
439     if (LLVM_UNLIKELY(!isInt<26>(Value))) {
440       return makeTargetOutOfRangeError(G, B, E);
441     }
442     uint32_t Inst = support::endian::read32<Endianness>(FixupPtr);
443     support::endian::write32<Endianness>(FixupPtr, (Inst & 0xfc000003) |
444                                                        (Value & 0x03fffffc));
445     if (K == CallBranchDeltaRestoreTOC) {
446       uint32_t NopInst = support::endian::read32<Endianness>(FixupPtr + 4);
447       assert(NopInst == 0x60000000 &&
448              "NOP should be placed here for restoring r2");
449       (void)NopInst;
450       // Restore r2 by instruction 0xe8410018 which is `ld r2, 24(r1)`.
451       support::endian::write32<Endianness>(FixupPtr + 4, 0xe8410018);
452     }
453     break;
454   }
455   case Delta64: {
456     int64_t Value = S + A - P;
457     support::endian::write64<Endianness>(FixupPtr, Value);
458     break;
459   }
460   case Delta34: {
461     int64_t Value = S + A - P;
462     if (!LLVM_UNLIKELY(isInt<34>(Value)))
463       return makeTargetOutOfRangeError(G, B, E);
464     static const uint64_t SI0Mask = 0x00000003ffff0000;
465     static const uint64_t SI1Mask = 0x000000000000ffff;
466     static const uint64_t FullMask = 0x0003ffff0000ffff;
467     uint64_t Inst = readPrefixedInstruction<Endianness>(FixupPtr) & ~FullMask;
468     writePrefixedInstruction<Endianness>(
469         FixupPtr, Inst | ((Value & SI0Mask) << 16) | (Value & SI1Mask));
470     break;
471   }
472   case Delta32: {
473     int64_t Value = S + A - P;
474     if (LLVM_UNLIKELY(!isInt<32>(Value))) {
475       return makeTargetOutOfRangeError(G, B, E);
476     }
477     support::endian::write32<Endianness>(FixupPtr, Value);
478     break;
479   }
480   case NegDelta32: {
481     int64_t Value = P - S + A;
482     if (LLVM_UNLIKELY(!isInt<32>(Value))) {
483       return makeTargetOutOfRangeError(G, B, E);
484     }
485     support::endian::write32<Endianness>(FixupPtr, Value);
486     break;
487   }
488   default:
489     return make_error<JITLinkError>(
490         "In graph " + G.getName() + ", section " + B.getSection().getName() +
491         " unsupported edge kind " + getEdgeKindName(E.getKind()));
492   }
493   return Error::success();
494 }
495 
496 } // end namespace llvm::jitlink::ppc64
497 
498 #endif // LLVM_EXECUTIONENGINE_JITLINK_PPC64_H
499