1 //===-- x86_64.h - Generic JITLink x86-64 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 x86-64 objects.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #ifndef LLVM_EXECUTIONENGINE_JITLINK_X86_64_H
14 #define LLVM_EXECUTIONENGINE_JITLINK_X86_64_H
15 
16 #include "llvm/ExecutionEngine/JITLink/JITLink.h"
17 #include "llvm/ExecutionEngine/JITLink/TableManager.h"
18 
19 #include <limits>
20 
21 namespace llvm {
22 namespace jitlink {
23 namespace x86_64 {
24 
25 /// Represents x86-64 fixups and other x86-64-specific edge kinds.
26 enum EdgeKind_x86_64 : Edge::Kind {
27 
28   /// A plain 64-bit pointer value relocation.
29   ///
30   /// Fixup expression:
31   ///   Fixup <- Target + Addend : uint64
32   ///
33   Pointer64 = Edge::FirstRelocation,
34 
35   /// A plain 32-bit pointer value relocation.
36   ///
37   /// Fixup expression:
38   ///   Fixup <- Target + Addend : uint32
39   ///
40   /// Errors:
41   ///   - The target must reside in the low 32-bits of the address space,
42   ///     otherwise an out-of-range error will be returned.
43   ///
44   Pointer32,
45 
46   /// A signed 32-bit pointer value relocation
47   ///
48   /// Fixup expression:
49   ///   Fixup <- Target + Addend : int32
50   ///
51   /// Errors:
52   ///   - The target must reside in the signed 32-bits([-2**31, 2**32 - 1]) of
53   ///   the address space, otherwise an out-of-range error will be returned.
54   Pointer32Signed,
55 
56   /// A 64-bit delta.
57   ///
58   /// Delta from the fixup to the target.
59   ///
60   /// Fixup expression:
61   ///   Fixup <- Target - Fixup + Addend : int64
62   ///
63   Delta64,
64 
65   /// A 32-bit delta.
66   ///
67   /// Delta from the fixup to the target.
68   ///
69   /// Fixup expression:
70   ///   Fixup <- Target - Fixup + Addend : int64
71   ///
72   /// Errors:
73   ///   - The result of the fixup expression must fit into an int32, otherwise
74   ///     an out-of-range error will be returned.
75   ///
76   Delta32,
77 
78   /// A 64-bit negative delta.
79   ///
80   /// Delta from target back to the fixup.
81   ///
82   /// Fixup expression:
83   ///   Fixup <- Fixup - Target + Addend : int64
84   ///
85   NegDelta64,
86 
87   /// A 32-bit negative delta.
88   ///
89   /// Delta from the target back to the fixup.
90   ///
91   /// Fixup expression:
92   ///   Fixup <- Fixup - Target + Addend : int32
93   ///
94   /// Errors:
95   ///   - The result of the fixup expression must fit into an int32, otherwise
96   ///     an out-of-range error will be returned.
97   NegDelta32,
98 
99   /// A 64-bit GOT delta.
100   ///
101   /// Delta from the global offset table to the target
102   ///
103   /// Fixup expression:
104   ///   Fixup <- Target - GOTSymbol + Addend : int64
105   ///
106   /// Errors:
107   ///   - *ASSERTION* Failure to a null pointer GOTSymbol, which the GOT section
108   ///     symbol was not been defined.
109   Delta64FromGOT,
110 
111   /// A 32-bit PC-relative branch.
112   ///
113   /// Represents a PC-relative call or branch to a target. This can be used to
114   /// identify, record, and/or patch call sites.
115   ///
116   /// The fixup expression for this kind includes an implicit offset to account
117   /// for the PC (unlike the Delta edges) so that a Branch32PCRel with a target
118   /// T and addend zero is a call/branch to the start (offset zero) of T.
119   ///
120   /// Fixup expression:
121   ///   Fixup <- Target - (Fixup + 4) + Addend : int32
122   ///
123   /// Errors:
124   ///   - The result of the fixup expression must fit into an int32, otherwise
125   ///     an out-of-range error will be returned.
126   ///
127   BranchPCRel32,
128 
129   /// A 32-bit PC-relative relocation.
130   ///
131   /// Represents a data/control flow instruction using PC-relative addressing
132   /// to a target.
133   ///
134   /// The fixup expression for this kind includes an implicit offset to account
135   /// for the PC (unlike the Delta edges) so that a PCRel32 with a target
136   /// T and addend zero is a call/branch to the start (offset zero) of T.
137   ///
138   /// Fixup expression:
139   ///   Fixup <- Target - (Fixup + 4) + Addend : int32
140   ///
141   /// Errors:
142   ///   - The result of the fixup expression must fit into an int32, otherwise
143   ///     an out-of-range error will be returned.
144   ///
145   PCRel32,
146 
147   /// A 32-bit PC-relative branch to a pointer jump stub.
148   ///
149   /// The target of this relocation should be a pointer jump stub of the form:
150   ///
151   /// \code{.s}
152   ///   .text
153   ///   jmpq *tgtptr(%rip)
154   ///   ; ...
155   ///
156   ///   .data
157   ///   tgtptr:
158   ///     .quad 0
159   /// \endcode
160   ///
161   /// This edge kind has the same fixup expression as BranchPCRel32, but further
162   /// identifies the call/branch as being to a pointer jump stub. For edges of
163   /// this kind the jump stub should not be bypassed (use
164   /// BranchPCRel32ToPtrJumpStubBypassable for that), but the pointer location
165   /// target may be recorded to allow manipulation at runtime.
166   ///
167   /// Fixup expression:
168   ///   Fixup <- Target - Fixup + Addend - 4 : int32
169   ///
170   /// Errors:
171   ///   - The result of the fixup expression must fit into an int32, otherwise
172   ///     an out-of-range error will be returned.
173   ///
174   BranchPCRel32ToPtrJumpStub,
175 
176   /// A relaxable version of BranchPCRel32ToPtrJumpStub.
177   ///
178   /// The edge kind has the same fixup expression as BranchPCRel32ToPtrJumpStub,
179   /// but identifies the call/branch as being to a pointer jump stub that may be
180   /// bypassed with a direct jump to the ultimate target if the ultimate target
181   /// is within range of the fixup location.
182   ///
183   /// Fixup expression:
184   ///   Fixup <- Target - Fixup + Addend - 4: int32
185   ///
186   /// Errors:
187   ///   - The result of the fixup expression must fit into an int32, otherwise
188   ///     an out-of-range error will be returned.
189   ///
190   BranchPCRel32ToPtrJumpStubBypassable,
191 
192   /// A GOT entry getter/constructor, transformed to Delta32 pointing at the GOT
193   /// entry for the original target.
194   ///
195   /// Indicates that this edge should be transformed into a Delta32 targeting
196   /// the GOT entry for the edge's current target, maintaining the same addend.
197   /// A GOT entry for the target should be created if one does not already
198   /// exist.
199   ///
200   /// Edges of this kind are usually handled by a GOT builder pass inserted by
201   /// default.
202   ///
203   /// Fixup expression:
204   ///   NONE
205   ///
206   /// Errors:
207   ///   - *ASSERTION* Failure to handle edges of this kind prior to the fixup
208   ///     phase will result in an assert/unreachable during the fixup phase.
209   ///
210   RequestGOTAndTransformToDelta32,
211 
212   /// A GOT entry getter/constructor, transformed to Delta64 pointing at the GOT
213   /// entry for the original target.
214   ///
215   /// Indicates that this edge should be transformed into a Delta64 targeting
216   /// the GOT entry for the edge's current target, maintaining the same addend.
217   /// A GOT entry for the target should be created if one does not already
218   /// exist.
219   ///
220   /// Edges of this kind are usually handled by a GOT builder pass inserted by
221   /// default.
222   ///
223   /// Fixup expression:
224   ///   NONE
225   ///
226   /// Errors:
227   ///   - *ASSERTION* Failure to handle edges of this kind prior to the fixup
228   ///     phase will result in an assert/unreachable during the fixup phase.
229   ///
230   RequestGOTAndTransformToDelta64,
231 
232   /// A GOT entry offset within GOT getter/constructor, transformed to
233   /// Delta64FromGOT
234   /// pointing at the GOT entry for the original target
235   ///
236   /// Indicates that this edge should be transformed into a Delta64FromGOT
237   /// targeting
238   /// the GOT entry for the edge's current target, maintaining the same addend.
239   /// A GOT entry for the target should be created if one does not already
240   /// exist.
241   ///
242   /// Edges of this kind are usually handled by a GOT builder pass inserted by
243   /// default
244   ///
245   /// Fixup expression:
246   ///   NONE
247   ///
248   /// Errors:
249   ///   - *ASSERTION* Failure to handle edges of this kind prior to the fixup
250   ///     phase will result in an assert/unreachable during the fixup phase
251   RequestGOTAndTransformToDelta64FromGOT,
252 
253   /// A PC-relative load of a GOT entry, relaxable if GOT entry target is
254   /// in-range of the fixup
255   ///
256   /// TODO: Explain the optimization
257   ///
258   /// Fixup expression
259   ///   Fixup <- Target - (Fixup + 4) + Addend : int32
260   ///
261   /// Errors:
262   ///   - The result of the fixup expression must fit into an int32, otherwise
263   ///     an out-of-range error will be returned.
264   //
265   PCRel32GOTLoadRelaxable,
266 
267   /// A PC-relative REX load of a GOT entry, relaxable if GOT entry target
268   /// is in-range of the fixup.
269   ///
270   /// If the GOT entry target is in-range of the fixup then the load from the
271   /// GOT may be replaced with a direct memory address calculation.
272   ///
273   /// Fixup expression:
274   ///   Fixup <- Target - (Fixup + 4) + Addend : int32
275   ///
276   /// Errors:
277   ///   - The result of the fixup expression must fit into an int32, otherwise
278   ///     an out-of-range error will be returned.
279   ///
280   PCRel32GOTLoadREXRelaxable,
281 
282   /// A GOT entry getter/constructor, transformed to
283   /// PCRel32ToGOTLoadREXRelaxable pointing at the GOT entry for the original
284   /// target.
285   ///
286   /// Indicates that this edge should be lowered to a PC32ToGOTLoadREXRelaxable
287   /// targeting the GOT entry for the edge's current target, maintaining the
288   /// same addend. A GOT entry for the target should be created if one does not
289   /// already exist.
290   ///
291   /// Edges of this kind are usually lowered by a GOT builder pass inserted by
292   /// default.
293   ///
294   /// Fixup expression:
295   ///   NONE
296   ///
297   /// Errors:
298   ///   - *ASSERTION* Failure to handle edges of this kind prior to the fixup
299   ///     phase will result in an assert/unreachable during the fixup phase.
300   ///
301   RequestGOTAndTransformToPCRel32GOTLoadREXRelaxable,
302 
303   /// A GOT entry getter/constructor, transformed to
304   /// PCRel32ToGOTLoadRelaxable pointing at the GOT entry for the original
305   /// target.
306   ///
307   /// Indicates that this edge should be lowered to a PC32ToGOTLoadRelaxable
308   /// targeting the GOT entry for the edge's current target, maintaining the
309   /// same addend. A GOT entry for the target should be created if one does not
310   /// already exist.
311   ///
312   /// Edges of this kind are usually lowered by a GOT builder pass inserted by
313   /// default.
314   ///
315   /// Fixup expression:
316   ///   NONE
317   ///
318   /// Errors:
319   ///   - *ASSERTION* Failure to handle edges of this kind prior to the fixup
320   ///     phase will result in an assert/unreachable during the fixup phase.
321   ///
322   RequestGOTAndTransformToPCRel32GOTLoadRelaxable,
323 
324   /// A PC-relative REX load of a Thread Local Variable Pointer (TLVP) entry,
325   /// relaxable if the TLVP entry target is in-range of the fixup.
326   ///
327   /// If the TLVP entry target is in-range of the fixup then the load from the
328   /// TLVP may be replaced with a direct memory address calculation.
329   ///
330   /// The target of this edge must be a thread local variable entry of the form
331   ///   .quad <tlv getter thunk>
332   ///   .quad <tlv key>
333   ///   .quad <tlv initializer>
334   ///
335   /// Fixup expression:
336   ///   Fixup <- Target - (Fixup + 4) + Addend : int32
337   ///
338   /// Errors:
339   ///   - The result of the fixup expression must fit into an int32, otherwise
340   ///     an out-of-range error will be returned.
341   ///   - The target must be either external, or a TLV entry of the required
342   ///     form, otherwise a malformed TLV entry error will be returned.
343   ///
344   PCRel32TLVPLoadREXRelaxable,
345 
346   /// TODO: Explain the generic edge kind
347   RequestTLSDescInGOTAndTransformToDelta32,
348 
349   /// A TLVP entry getter/constructor, transformed to
350   /// Delta32ToTLVPLoadREXRelaxable.
351   ///
352   /// Indicates that this edge should be transformed into a
353   /// Delta32ToTLVPLoadREXRelaxable targeting the TLVP entry for the edge's
354   /// current target. A TLVP entry for the target should be created if one does
355   /// not already exist.
356   ///
357   /// Fixup expression:
358   ///   NONE
359   ///
360   /// Errors:
361   ///   - *ASSERTION* Failure to handle edges of this kind prior to the fixup
362   ///     phase will result in an assert/unreachable during the fixup phase.
363   ///
364   RequestTLVPAndTransformToPCRel32TLVPLoadREXRelaxable,
365   // First platform specific relocation.
366   FirstPlatformRelocation
367 };
368 
369 /// Returns a string name for the given x86-64 edge. For debugging purposes
370 /// only.
371 const char *getEdgeKindName(Edge::Kind K);
372 
373 /// Returns true if the given uint64_t value is in range for a uint32_t.
374 inline bool isInRangeForImmU32(uint64_t Value) {
375   return Value <= std::numeric_limits<uint32_t>::max();
376 }
377 
378 /// Returns true if the given int64_t value is in range for an int32_t.
379 inline bool isInRangeForImmS32(int64_t Value) {
380   return (Value >= std::numeric_limits<int32_t>::min() &&
381           Value <= std::numeric_limits<int32_t>::max());
382 }
383 
384 /// Apply fixup expression for edge to block content.
385 inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E,
386                         const Symbol *GOTSymbol) {
387   using namespace support;
388 
389   char *BlockWorkingMem = B.getAlreadyMutableContent().data();
390   char *FixupPtr = BlockWorkingMem + E.getOffset();
391   auto FixupAddress = B.getAddress() + E.getOffset();
392 
393   switch (E.getKind()) {
394 
395   case Pointer64: {
396     uint64_t Value = E.getTarget().getAddress().getValue() + E.getAddend();
397     *(ulittle64_t *)FixupPtr = Value;
398     break;
399   }
400 
401   case Pointer32: {
402     uint64_t Value = E.getTarget().getAddress().getValue() + E.getAddend();
403     if (LLVM_LIKELY(isInRangeForImmU32(Value)))
404       *(ulittle32_t *)FixupPtr = Value;
405     else
406       return makeTargetOutOfRangeError(G, B, E);
407     break;
408   }
409   case Pointer32Signed: {
410     int64_t Value = E.getTarget().getAddress().getValue() + E.getAddend();
411     if (LLVM_LIKELY(isInRangeForImmS32(Value)))
412       *(little32_t *)FixupPtr = Value;
413     else
414       return makeTargetOutOfRangeError(G, B, E);
415     break;
416   }
417 
418   case PCRel32:
419   case BranchPCRel32:
420   case BranchPCRel32ToPtrJumpStub:
421   case BranchPCRel32ToPtrJumpStubBypassable:
422   case PCRel32GOTLoadRelaxable:
423   case PCRel32GOTLoadREXRelaxable:
424   case PCRel32TLVPLoadREXRelaxable: {
425     int64_t Value =
426         E.getTarget().getAddress() - (FixupAddress + 4) + E.getAddend();
427     if (LLVM_LIKELY(isInRangeForImmS32(Value)))
428       *(little32_t *)FixupPtr = Value;
429     else
430       return makeTargetOutOfRangeError(G, B, E);
431     break;
432   }
433 
434   case Delta64: {
435     int64_t Value = E.getTarget().getAddress() - FixupAddress + E.getAddend();
436     *(little64_t *)FixupPtr = Value;
437     break;
438   }
439 
440   case Delta32: {
441     int64_t Value = E.getTarget().getAddress() - FixupAddress + E.getAddend();
442     if (LLVM_LIKELY(isInRangeForImmS32(Value)))
443       *(little32_t *)FixupPtr = Value;
444     else
445       return makeTargetOutOfRangeError(G, B, E);
446     break;
447   }
448 
449   case NegDelta64: {
450     int64_t Value = FixupAddress - E.getTarget().getAddress() + E.getAddend();
451     *(little64_t *)FixupPtr = Value;
452     break;
453   }
454 
455   case NegDelta32: {
456     int64_t Value = FixupAddress - E.getTarget().getAddress() + E.getAddend();
457     if (LLVM_LIKELY(isInRangeForImmS32(Value)))
458       *(little32_t *)FixupPtr = Value;
459     else
460       return makeTargetOutOfRangeError(G, B, E);
461     break;
462   }
463   case Delta64FromGOT: {
464     assert(GOTSymbol && "No GOT section symbol");
465     int64_t Value =
466         E.getTarget().getAddress() - GOTSymbol->getAddress() + E.getAddend();
467     *(little64_t *)FixupPtr = Value;
468     break;
469   }
470 
471   default:
472     return make_error<JITLinkError>(
473         "In graph " + G.getName() + ", section " + B.getSection().getName() +
474         "unsupported edge kind" + getEdgeKindName(E.getKind()));
475   }
476 
477   return Error::success();
478 }
479 
480 /// x86_64 pointer size.
481 constexpr uint64_t PointerSize = 8;
482 
483 /// x86-64 null pointer content.
484 extern const char NullPointerContent[PointerSize];
485 
486 /// x86-64 pointer jump stub content.
487 ///
488 /// Contains the instruction sequence for an indirect jump via an in-memory
489 /// pointer:
490 ///   jmpq *ptr(%rip)
491 extern const char PointerJumpStubContent[6];
492 
493 /// Creates a new pointer block in the given section and returns an anonymous
494 /// symbol pointing to it.
495 ///
496 /// If InitialTarget is given then an Pointer64 relocation will be added to the
497 /// block pointing at InitialTarget.
498 ///
499 /// The pointer block will have the following default values:
500 ///   alignment: 64-bit
501 ///   alignment-offset: 0
502 ///   address: highest allowable (~7U)
503 inline Symbol &createAnonymousPointer(LinkGraph &G, Section &PointerSection,
504                                       Symbol *InitialTarget = nullptr,
505                                       uint64_t InitialAddend = 0) {
506   auto &B = G.createContentBlock(PointerSection, NullPointerContent,
507                                  orc::ExecutorAddr(~uint64_t(7)), 8, 0);
508   if (InitialTarget)
509     B.addEdge(Pointer64, 0, *InitialTarget, InitialAddend);
510   return G.addAnonymousSymbol(B, 0, 8, false, false);
511 }
512 
513 /// Create a jump stub block that jumps via the pointer at the given symbol.
514 ///
515 /// The stub block will have the following default values:
516 ///   alignment: 8-bit
517 ///   alignment-offset: 0
518 ///   address: highest allowable: (~5U)
519 inline Block &createPointerJumpStubBlock(LinkGraph &G, Section &StubSection,
520                                          Symbol &PointerSymbol) {
521   auto &B = G.createContentBlock(StubSection, PointerJumpStubContent,
522                                  orc::ExecutorAddr(~uint64_t(5)), 1, 0);
523   B.addEdge(Delta32, 2, PointerSymbol, -4);
524   return B;
525 }
526 
527 /// Create a jump stub that jumps via the pointer at the given symbol and
528 /// an anonymous symbol pointing to it. Return the anonymous symbol.
529 ///
530 /// The stub block will be created by createPointerJumpStubBlock.
531 inline Symbol &createAnonymousPointerJumpStub(LinkGraph &G,
532                                               Section &StubSection,
533                                               Symbol &PointerSymbol) {
534   return G.addAnonymousSymbol(
535       createPointerJumpStubBlock(G, StubSection, PointerSymbol), 0, 6, true,
536       false);
537 }
538 
539 /// Global Offset Table Builder.
540 class GOTTableManager : public TableManager<GOTTableManager> {
541 public:
542   static StringRef getSectionName() { return "$__GOT"; }
543 
544   bool visitEdge(LinkGraph &G, Block *B, Edge &E) {
545     Edge::Kind KindToSet = Edge::Invalid;
546     switch (E.getKind()) {
547     case x86_64::Delta64FromGOT: {
548       // we need to make sure that the GOT section exists, but don't otherwise
549       // need to fix up this edge
550       getGOTSection(G);
551       return false;
552     }
553     case x86_64::RequestGOTAndTransformToPCRel32GOTLoadREXRelaxable:
554       KindToSet = x86_64::PCRel32GOTLoadREXRelaxable;
555       break;
556     case x86_64::RequestGOTAndTransformToPCRel32GOTLoadRelaxable:
557       KindToSet = x86_64::PCRel32GOTLoadRelaxable;
558       break;
559     case x86_64::RequestGOTAndTransformToDelta64:
560       KindToSet = x86_64::Delta64;
561       break;
562     case x86_64::RequestGOTAndTransformToDelta64FromGOT:
563       KindToSet = x86_64::Delta64FromGOT;
564       break;
565     case x86_64::RequestGOTAndTransformToDelta32:
566       KindToSet = x86_64::Delta32;
567       break;
568     default:
569       return false;
570     }
571     assert(KindToSet != Edge::Invalid &&
572            "Fell through switch, but no new kind to set");
573     DEBUG_WITH_TYPE("jitlink", {
574       dbgs() << "  Fixing " << G.getEdgeKindName(E.getKind()) << " edge at "
575              << B->getFixupAddress(E) << " (" << B->getAddress() << " + "
576              << formatv("{0:x}", E.getOffset()) << ")\n";
577     });
578     E.setKind(KindToSet);
579     E.setTarget(getEntryForTarget(G, E.getTarget()));
580     return true;
581   }
582 
583   Symbol &createEntry(LinkGraph &G, Symbol &Target) {
584     return createAnonymousPointer(G, getGOTSection(G), &Target);
585   }
586 
587 private:
588   Section &getGOTSection(LinkGraph &G) {
589     if (!GOTSection)
590       GOTSection = &G.createSection(getSectionName(), MemProt::Read);
591     return *GOTSection;
592   }
593 
594   Section *GOTSection = nullptr;
595 };
596 
597 /// Procedure Linkage Table Builder.
598 class PLTTableManager : public TableManager<PLTTableManager> {
599 public:
600   PLTTableManager(GOTTableManager &GOT) : GOT(GOT) {}
601 
602   static StringRef getSectionName() { return "$__STUBS"; }
603 
604   bool visitEdge(LinkGraph &G, Block *B, Edge &E) {
605     if (E.getKind() == x86_64::BranchPCRel32 && !E.getTarget().isDefined()) {
606       DEBUG_WITH_TYPE("jitlink", {
607         dbgs() << "  Fixing " << G.getEdgeKindName(E.getKind()) << " edge at "
608                << B->getFixupAddress(E) << " (" << B->getAddress() << " + "
609                << formatv("{0:x}", E.getOffset()) << ")\n";
610       });
611       // Set the edge kind to Branch32ToPtrJumpStubBypassable to enable it to
612       // be optimized when the target is in-range.
613       E.setKind(x86_64::BranchPCRel32ToPtrJumpStubBypassable);
614       E.setTarget(getEntryForTarget(G, E.getTarget()));
615       return true;
616     }
617     return false;
618   }
619 
620   Symbol &createEntry(LinkGraph &G, Symbol &Target) {
621     return createAnonymousPointerJumpStub(G, getStubsSection(G),
622                                           GOT.getEntryForTarget(G, Target));
623   }
624 
625 public:
626   Section &getStubsSection(LinkGraph &G) {
627     if (!PLTSection)
628       PLTSection =
629           &G.createSection(getSectionName(), MemProt::Read | MemProt::Exec);
630     return *PLTSection;
631   }
632 
633   GOTTableManager &GOT;
634   Section *PLTSection = nullptr;
635 };
636 
637 /// Optimize the GOT and Stub relocations if the edge target address is in range
638 /// 1. PCRel32GOTLoadRelaxable. For this edge kind, if the target is in range,
639 /// then replace GOT load with lea
640 /// 2. BranchPCRel32ToPtrJumpStubRelaxable. For this edge kind, if the target is
641 /// in range, replace a indirect jump by plt stub with a direct jump to the
642 /// target
643 Error optimizeGOTAndStubAccesses(LinkGraph &G);
644 
645 } // namespace x86_64
646 } // end namespace jitlink
647 } // end namespace llvm
648 
649 #endif // LLVM_EXECUTIONENGINE_JITLINK_X86_64_H
650