xref: /llvm-project/llvm/lib/ExecutionEngine/JITLink/x86_64.cpp (revision 8fb29ba287d72392bd7900c33d2a8d2149126dbe)
1 //===----- x86_64.cpp - Generic JITLink x86-64 edge kinds, utilities ------===//
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 #include "llvm/ExecutionEngine/JITLink/x86_64.h"
14 
15 #define DEBUG_TYPE "jitlink"
16 
17 namespace llvm {
18 namespace jitlink {
19 namespace x86_64 {
20 
21 const char *getEdgeKindName(Edge::Kind K) {
22   switch (K) {
23   case Pointer64:
24     return "Pointer64";
25   case Pointer32:
26     return "Pointer32";
27   case Pointer32Signed:
28     return "Pointer32Signed";
29   case Pointer16:
30     return "Pointer16";
31   case Pointer8:
32     return "Pointer8";
33   case Delta64:
34     return "Delta64";
35   case Delta32:
36     return "Delta32";
37   case Delta16:
38     return "Delta16";
39   case Delta8:
40     return "Delta8";
41   case NegDelta64:
42     return "NegDelta64";
43   case NegDelta32:
44     return "NegDelta32";
45   case Size64:
46     return "Size64";
47   case Size32:
48     return "Size32";
49   case Delta64FromGOT:
50     return "Delta64FromGOT";
51   case PCRel32:
52     return "PCRel32";
53   case BranchPCRel32:
54     return "BranchPCRel32";
55   case BranchPCRel32ToPtrJumpStub:
56     return "BranchPCRel32ToPtrJumpStub";
57   case BranchPCRel32ToPtrJumpStubBypassable:
58     return "BranchPCRel32ToPtrJumpStubBypassable";
59   case RequestGOTAndTransformToDelta32:
60     return "RequestGOTAndTransformToDelta32";
61   case RequestGOTAndTransformToDelta64:
62     return "RequestGOTAndTransformToDelta64";
63   case RequestGOTAndTransformToDelta64FromGOT:
64     return "RequestGOTAndTransformToDelta64FromGOT";
65   case PCRel32GOTLoadREXRelaxable:
66     return "PCRel32GOTLoadREXRelaxable";
67   case RequestGOTAndTransformToPCRel32GOTLoadREXRelaxable:
68     return "RequestGOTAndTransformToPCRel32GOTLoadREXRelaxable";
69   case PCRel32GOTLoadRelaxable:
70     return "PCRel32GOTLoadRelaxable";
71   case RequestGOTAndTransformToPCRel32GOTLoadRelaxable:
72     return "RequestGOTAndTransformToPCRel32GOTLoadRelaxable";
73   case PCRel32TLVPLoadREXRelaxable:
74     return "PCRel32TLVPLoadREXRelaxable";
75   case RequestTLVPAndTransformToPCRel32TLVPLoadREXRelaxable:
76     return "RequestTLVPAndTransformToPCRel32TLVPLoadREXRelaxable";
77   default:
78     return getGenericEdgeKindName(static_cast<Edge::Kind>(K));
79   }
80 }
81 
82 const char NullPointerContent[PointerSize] = {0x00, 0x00, 0x00, 0x00,
83                                               0x00, 0x00, 0x00, 0x00};
84 
85 const char PointerJumpStubContent[6] = {
86     static_cast<char>(0xFFu), 0x25, 0x00, 0x00, 0x00, 0x00};
87 
88 const char ReentryTrampolineContent[5] = {
89   static_cast<char>(0xe8), 0x00, 0x00, 0x00, 0x00
90 };
91 
92 void GOTTableManager::registerExistingEntries() {
93   for (auto *EntrySym : GOTSection->symbols()) {
94     assert(EntrySym->getBlock().edges_size() == 1 &&
95            "GOT block edge count != 1");
96     registerPreExistingEntry(EntrySym->getBlock().edges().begin()->getTarget(),
97                              *EntrySym);
98   }
99 }
100 
101 void PLTTableManager::registerExistingEntries() {
102   for (auto *EntrySym : StubsSection->symbols()) {
103     assert(EntrySym->getBlock().edges_size() == 1 &&
104            "PLT block edge count != 1");
105     auto &GOTSym = EntrySym->getBlock().edges().begin()->getTarget();
106     assert(GOTSym.getBlock().edges_size() == 1 && "GOT block edge count != 1");
107     registerPreExistingEntry(GOTSym.getBlock().edges().begin()->getTarget(),
108                              *EntrySym);
109   }
110 }
111 
112 Error optimizeGOTAndStubAccesses(LinkGraph &G) {
113   LLVM_DEBUG(dbgs() << "Optimizing GOT entries and stubs:\n");
114 
115   for (auto *B : G.blocks())
116     for (auto &E : B->edges()) {
117       if (E.getKind() == x86_64::PCRel32GOTLoadRelaxable ||
118           E.getKind() == x86_64::PCRel32GOTLoadREXRelaxable) {
119 #ifndef NDEBUG
120         bool REXPrefix = E.getKind() == x86_64::PCRel32GOTLoadREXRelaxable;
121         assert(E.getOffset() >= (REXPrefix ? 3u : 2u) &&
122                "GOT edge occurs too early in block");
123 #endif
124         auto *FixupData = reinterpret_cast<uint8_t *>(
125                               const_cast<char *>(B->getContent().data())) +
126                           E.getOffset();
127         const uint8_t Op = FixupData[-2];
128         const uint8_t ModRM = FixupData[-1];
129 
130         auto &GOTEntryBlock = E.getTarget().getBlock();
131         assert(GOTEntryBlock.getSize() == G.getPointerSize() &&
132                "GOT entry block should be pointer sized");
133         assert(GOTEntryBlock.edges_size() == 1 &&
134                "GOT entry should only have one outgoing edge");
135         auto &GOTTarget = GOTEntryBlock.edges().begin()->getTarget();
136         orc::ExecutorAddr TargetAddr = GOTTarget.getAddress();
137         orc::ExecutorAddr EdgeAddr = B->getFixupAddress(E);
138         int64_t Displacement = TargetAddr - EdgeAddr + 4;
139         bool TargetInRangeForImmU32 = isUInt<32>(TargetAddr.getValue());
140         bool DisplacementInRangeForImmS32 = isInt<32>(Displacement);
141 
142         // If both of the Target and displacement is out of range, then
143         // there isn't optimization chance.
144         if (!(TargetInRangeForImmU32 || DisplacementInRangeForImmS32))
145           continue;
146 
147         // Transform "mov foo@GOTPCREL(%rip),%reg" to "lea foo(%rip),%reg".
148         if (Op == 0x8b && DisplacementInRangeForImmS32) {
149           FixupData[-2] = 0x8d;
150           E.setKind(x86_64::Delta32);
151           E.setTarget(GOTTarget);
152           E.setAddend(E.getAddend() - 4);
153           LLVM_DEBUG({
154             dbgs() << "  Replaced GOT load wih LEA:\n    ";
155             printEdge(dbgs(), *B, E, getEdgeKindName(E.getKind()));
156             dbgs() << "\n";
157           });
158           continue;
159         }
160 
161         // Transform call/jmp instructions
162         if (Op == 0xff && TargetInRangeForImmU32) {
163           if (ModRM == 0x15) {
164             // ABI says we can convert "call *foo@GOTPCREL(%rip)" to "nop; call
165             // foo" But lld convert it to "addr32 call foo, because that makes
166             // result expression to be a single instruction.
167             FixupData[-2] = 0x67;
168             FixupData[-1] = 0xe8;
169             LLVM_DEBUG({
170               dbgs() << "  replaced call instruction's memory operand wih imm "
171                         "operand:\n    ";
172               printEdge(dbgs(), *B, E, getEdgeKindName(E.getKind()));
173               dbgs() << "\n";
174             });
175           } else {
176             // Transform "jmp *foo@GOTPCREL(%rip)" to "jmp foo; nop"
177             assert(ModRM == 0x25 && "Invalid ModRm for call/jmp instructions");
178             FixupData[-2] = 0xe9;
179             FixupData[3] = 0x90;
180             E.setOffset(E.getOffset() - 1);
181             LLVM_DEBUG({
182               dbgs() << "  replaced jmp instruction's memory operand wih imm "
183                         "operand:\n    ";
184               printEdge(dbgs(), *B, E, getEdgeKindName(E.getKind()));
185               dbgs() << "\n";
186             });
187           }
188           E.setKind(x86_64::Pointer32);
189           E.setTarget(GOTTarget);
190           continue;
191         }
192       } else if (E.getKind() == x86_64::BranchPCRel32ToPtrJumpStubBypassable) {
193         auto &StubBlock = E.getTarget().getBlock();
194         assert(StubBlock.getSize() == sizeof(PointerJumpStubContent) &&
195                "Stub block should be stub sized");
196         assert(StubBlock.edges_size() == 1 &&
197                "Stub block should only have one outgoing edge");
198 
199         auto &GOTBlock = StubBlock.edges().begin()->getTarget().getBlock();
200         assert(GOTBlock.getSize() == G.getPointerSize() &&
201                "GOT block should be pointer sized");
202         assert(GOTBlock.edges_size() == 1 &&
203                "GOT block should only have one outgoing edge");
204 
205         auto &GOTTarget = GOTBlock.edges().begin()->getTarget();
206         orc::ExecutorAddr EdgeAddr = B->getAddress() + E.getOffset();
207         orc::ExecutorAddr TargetAddr = GOTTarget.getAddress();
208 
209         int64_t Displacement = TargetAddr - EdgeAddr + 4;
210         if (isInt<32>(Displacement)) {
211           E.setKind(x86_64::BranchPCRel32);
212           E.setTarget(GOTTarget);
213           LLVM_DEBUG({
214             dbgs() << "  Replaced stub branch with direct branch:\n    ";
215             printEdge(dbgs(), *B, E, getEdgeKindName(E.getKind()));
216             dbgs() << "\n";
217           });
218         }
219       }
220     }
221 
222   return Error::success();
223 }
224 
225 } // end namespace x86_64
226 } // end namespace jitlink
227 } // end namespace llvm
228