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