[COFF] Create range extension thunks for ARM64
On ARM64, this is normally necessary only after a module exceeds 128 MB in size (while the limit for thumb is 16 MB). For conditional branches, the range limit is only 1 MB though (the same as for thumb), and for the tbz instruction, the range is only 32 KB, which allows for a test much smaller than the full 128 MB. This fixes PR40467. Differential Revision: https://reviews.llvm.org/D57575 llvm-svn: 352929
This commit is contained in:
		
							parent
							
								
									b2b0cab0c3
								
							
						
					
					
						commit
						c9f4d25f26
					
				| 
						 | 
				
			
			@ -670,18 +670,38 @@ const uint8_t ArmThunk[] = {
 | 
			
		|||
    0xe7, 0x44,             // L1: add  pc, ip
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
size_t RangeExtensionThunk::getSize() const {
 | 
			
		||||
size_t RangeExtensionThunkARM::getSize() const {
 | 
			
		||||
  assert(Config->Machine == ARMNT);
 | 
			
		||||
  return sizeof(ArmThunk);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RangeExtensionThunk::writeTo(uint8_t *Buf) const {
 | 
			
		||||
void RangeExtensionThunkARM::writeTo(uint8_t *Buf) const {
 | 
			
		||||
  assert(Config->Machine == ARMNT);
 | 
			
		||||
  uint64_t Offset = Target->getRVA() - RVA - 12;
 | 
			
		||||
  memcpy(Buf + OutputSectionOff, ArmThunk, sizeof(ArmThunk));
 | 
			
		||||
  applyMOV32T(Buf + OutputSectionOff, uint32_t(Offset));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A position independent ARM64 adrp+add thunk, with a maximum range of
 | 
			
		||||
// +/- 4 GB, which is enough for any PE-COFF.
 | 
			
		||||
const uint8_t Arm64Thunk[] = {
 | 
			
		||||
    0x10, 0x00, 0x00, 0x90, // adrp x16, Dest
 | 
			
		||||
    0x10, 0x02, 0x00, 0x91, // add  x16, x16, :lo12:Dest
 | 
			
		||||
    0x00, 0x02, 0x1f, 0xd6, // br   x16
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
size_t RangeExtensionThunkARM64::getSize() const {
 | 
			
		||||
  assert(Config->Machine == ARM64);
 | 
			
		||||
  return sizeof(Arm64Thunk);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RangeExtensionThunkARM64::writeTo(uint8_t *Buf) const {
 | 
			
		||||
  assert(Config->Machine == ARM64);
 | 
			
		||||
  memcpy(Buf + OutputSectionOff, Arm64Thunk, sizeof(Arm64Thunk));
 | 
			
		||||
  applyArm64Addr(Buf + OutputSectionOff + 0, Target->getRVA(), RVA, 12);
 | 
			
		||||
  applyArm64Imm(Buf + OutputSectionOff + 4, Target->getRVA() & 0xfff, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LocalImportChunk::getBaserels(std::vector<Baserel> *Res) {
 | 
			
		||||
  Res->emplace_back(getRVA());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -357,9 +357,18 @@ private:
 | 
			
		|||
  Defined *ImpSymbol;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class RangeExtensionThunk : public Chunk {
 | 
			
		||||
class RangeExtensionThunkARM : public Chunk {
 | 
			
		||||
public:
 | 
			
		||||
  explicit RangeExtensionThunk(Defined *T) : Target(T) {}
 | 
			
		||||
  explicit RangeExtensionThunkARM(Defined *T) : Target(T) {}
 | 
			
		||||
  size_t getSize() const override;
 | 
			
		||||
  void writeTo(uint8_t *Buf) const override;
 | 
			
		||||
 | 
			
		||||
  Defined *Target;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class RangeExtensionThunkARM64 : public Chunk {
 | 
			
		||||
public:
 | 
			
		||||
  explicit RangeExtensionThunkARM64(Defined *T) : Target(T) {}
 | 
			
		||||
  size_t getSize() const override;
 | 
			
		||||
  void writeTo(uint8_t *Buf) const override;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -335,7 +335,7 @@ void OutputSection::writeHeaderTo(uint8_t *Buf) {
 | 
			
		|||
// Check whether the target address S is in range from a relocation
 | 
			
		||||
// of type RelType at address P.
 | 
			
		||||
static bool isInRange(uint16_t RelType, uint64_t S, uint64_t P, int Margin) {
 | 
			
		||||
  assert(Config->Machine == ARMNT);
 | 
			
		||||
  if (Config->Machine == ARMNT) {
 | 
			
		||||
    int64_t Diff = AbsoluteDifference(S, P + 4) + Margin;
 | 
			
		||||
    switch (RelType) {
 | 
			
		||||
    case IMAGE_REL_ARM_BRANCH20T:
 | 
			
		||||
| 
						 | 
				
			
			@ -346,6 +346,21 @@ static bool isInRange(uint16_t RelType, uint64_t S, uint64_t P, int Margin) {
 | 
			
		|||
    default:
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
  } else if (Config->Machine == ARM64) {
 | 
			
		||||
    int64_t Diff = AbsoluteDifference(S, P) + Margin;
 | 
			
		||||
    switch (RelType) {
 | 
			
		||||
    case IMAGE_REL_ARM64_BRANCH26:
 | 
			
		||||
      return isInt<28>(Diff);
 | 
			
		||||
    case IMAGE_REL_ARM64_BRANCH19:
 | 
			
		||||
      return isInt<21>(Diff);
 | 
			
		||||
    case IMAGE_REL_ARM64_BRANCH14:
 | 
			
		||||
      return isInt<16>(Diff);
 | 
			
		||||
    default:
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    llvm_unreachable("Unexpected architecture");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return the last thunk for the given target if it is in range,
 | 
			
		||||
| 
						 | 
				
			
			@ -356,7 +371,17 @@ getThunk(DenseMap<uint64_t, Defined *> &LastThunks, Defined *Target, uint64_t P,
 | 
			
		|||
  Defined *&LastThunk = LastThunks[Target->getRVA()];
 | 
			
		||||
  if (LastThunk && isInRange(Type, LastThunk->getRVA(), P, Margin))
 | 
			
		||||
    return {LastThunk, false};
 | 
			
		||||
  RangeExtensionThunk *C = make<RangeExtensionThunk>(Target);
 | 
			
		||||
  Chunk *C;
 | 
			
		||||
  switch (Config->Machine) {
 | 
			
		||||
  case ARMNT:
 | 
			
		||||
    C = make<RangeExtensionThunkARM>(Target);
 | 
			
		||||
    break;
 | 
			
		||||
  case ARM64:
 | 
			
		||||
    C = make<RangeExtensionThunkARM64>(Target);
 | 
			
		||||
    break;
 | 
			
		||||
  default:
 | 
			
		||||
    llvm_unreachable("Unexpected architecture");
 | 
			
		||||
  }
 | 
			
		||||
  Defined *D = make<DefinedSynthetic>("", C);
 | 
			
		||||
  LastThunk = D;
 | 
			
		||||
  return {D, true};
 | 
			
		||||
| 
						 | 
				
			
			@ -458,7 +483,7 @@ static bool verifyRanges(const std::vector<Chunk *> Chunks) {
 | 
			
		|||
// Assign addresses and add thunks if necessary.
 | 
			
		||||
void Writer::finalizeAddresses() {
 | 
			
		||||
  assignAddresses();
 | 
			
		||||
  if (Config->Machine != ARMNT)
 | 
			
		||||
  if (Config->Machine != ARMNT && Config->Machine != ARM64)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  size_t OrigNumChunks = 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
// REQUIRES: aarch64
 | 
			
		||||
 | 
			
		||||
// RUN: echo -e '.globl _start\n _start:\n bl too_far26\n' > %t.main26.s
 | 
			
		||||
// RUN: echo -e '.globl _start\n _start:\n b.ne too_far19\n' > %t.main19.s
 | 
			
		||||
// RUN: echo -e '.globl _start\n _start:\n tbz x0, #0, too_far14\n' > %t.main14.s
 | 
			
		||||
 | 
			
		||||
// RUN: llvm-mc -filetype=obj -triple=aarch64-windows %t.main26.s -o %t.main26.obj
 | 
			
		||||
// RUN: llvm-mc -filetype=obj -triple=aarch64-windows %t.main19.s -o %t.main19.obj
 | 
			
		||||
// RUN: llvm-mc -filetype=obj -triple=aarch64-windows %t.main14.s -o %t.main14.obj
 | 
			
		||||
// RUN: llvm-mc -filetype=obj -triple=aarch64-windows %S/Inputs/far-arm64-abs.s -o %t.far.obj
 | 
			
		||||
 | 
			
		||||
// RUN: not lld-link -base:0x10000 -entry:_start -subsystem:console %t.main26.obj %t.far.obj -out:%t.exe 2>&1 | FileCheck %s
 | 
			
		||||
// RUN: not lld-link -base:0x10000 -entry:_start -subsystem:console %t.main19.obj %t.far.obj -out:%t.exe 2>&1 | FileCheck %s
 | 
			
		||||
// RUN: not lld-link -base:0x10000 -entry:_start -subsystem:console %t.main14.obj %t.far.obj -out:%t.exe 2>&1 | FileCheck %s
 | 
			
		||||
 | 
			
		||||
// CHECK: relocation out of range
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
// REQUIRES: aarch64
 | 
			
		||||
// RUN: llvm-mc -filetype=obj -triple=aarch64-windows %s -o %t.obj
 | 
			
		||||
// RUN: lld-link -entry:main -subsystem:console %t.obj -out:%t.exe -verbose 2>&1 | FileCheck -check-prefix=VERBOSE %s
 | 
			
		||||
// RUN: llvm-objdump -d %t.exe | FileCheck -check-prefix=DISASM %s
 | 
			
		||||
 | 
			
		||||
// VERBOSE: Added 1 thunks with margin {{.*}} in 1 passes
 | 
			
		||||
 | 
			
		||||
    .globl main
 | 
			
		||||
    .globl func1
 | 
			
		||||
    .text
 | 
			
		||||
main:
 | 
			
		||||
    tbz w0, #0, func1
 | 
			
		||||
    ret
 | 
			
		||||
    .section .text$a, "xr"
 | 
			
		||||
    .space 0x8000
 | 
			
		||||
    .section .text$b, "xr"
 | 
			
		||||
func1:
 | 
			
		||||
    ret
 | 
			
		||||
 | 
			
		||||
// DISASM: 0000000140001000 .text:
 | 
			
		||||
// DISASM: 140001000:      40 00 00 36     tbz     w0, #0, #8 <.text+0x8>
 | 
			
		||||
// DISASM: 140001004:      c0 03 5f d6     ret
 | 
			
		||||
// DISASM: 140001008:      50 00 00 90     adrp    x16, #32768
 | 
			
		||||
// DISASM: 14000100c:      10 52 00 91     add     x16, x16, #20
 | 
			
		||||
// DISASM: 140001010:      00 02 1f d6     br      x16
 | 
			
		||||
 | 
			
		||||
// DISASM: 140009014:      c0 03 5f d6     ret
 | 
			
		||||
		Loading…
	
		Reference in New Issue