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