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