[ELF] Implement infrastructure for thunk code creation
authorSimon Atanasyan <simon@atanasyan.com>
Thu, 31 Mar 2016 21:26:23 +0000 (21:26 +0000)
committerSimon Atanasyan <simon@atanasyan.com>
Thu, 31 Mar 2016 21:26:23 +0000 (21:26 +0000)
Some targets might require creation of thunks. For example, MIPS targets
require stubs to call PIC code from non-PIC one. The patch implements
infrastructure for thunk code creation and provides support for MIPS
LA25 stubs. Any MIPS PIC code function is invoked with its address
in register $t9. So if we have a branch instruction from non-PIC code
to the PIC one we cannot make the jump directly and need to create a small
stub to save the target function address.
See page 3-38 ftp://www.linux-mips.org/pub/linux/mips/doc/ABI/mipsabi.pdf

- In relocation scanning phase we ask target about thunk creation necessity
by calling `TagetInfo::needsThunk` method. The `InputSection` class
maintains list of Symbols requires thunk creation.

- Reassigning offsets performed for each input sections after relocation
scanning complete because position of each section might change due
thunk creation.

- The patch introduces new dedicated value for DefinedSynthetic symbols
DefinedSynthetic::SectionEnd. Synthetic symbol with that value always
points to the end of the corresponding output section. That allows to
escape updating synthetic symbols if output sections sizes changes after
relocation scanning due thunk creation.

- In the `InputSection::writeTo` method we write thunks after corresponding
input section. Each thunk is written by calling `TargetInfo::writeThunk` method.

- The patch supports the only type of thunk code for each target. For now,
it is enough.

Differential Revision: http://reviews.llvm.org/D17934

llvm-svn: 265059

lld/ELF/InputSection.cpp
lld/ELF/InputSection.h
lld/ELF/OutputSections.cpp
lld/ELF/OutputSections.h
lld/ELF/Symbols.cpp
lld/ELF/Symbols.h
lld/ELF/Target.cpp
lld/ELF/Target.h
lld/ELF/Writer.cpp
lld/test/ELF/Inputs/mips-pic.s [new file with mode: 0644]
lld/test/ELF/mips-npic-call-pic.s [new file with mode: 0644]

index d26c740..b5448ac 100644 (file)
@@ -38,6 +38,13 @@ InputSectionBase<ELFT>::InputSectionBase(elf::ObjectFile<ELFT> *File,
   Align = std::max<uintX_t>(Header->sh_addralign, 1);
 }
 
+template <class ELFT> size_t InputSectionBase<ELFT>::getSize() const {
+  if (auto *D = dyn_cast<InputSection<ELFT>>(this))
+    if (D->getThunksSize() > 0)
+      return D->getThunkOff() + D->getThunksSize();
+  return Header->sh_size;
+}
+
 template <class ELFT> StringRef InputSectionBase<ELFT>::getSectionName() const {
   return check(File->getObj().getSectionName(this->Header));
 }
@@ -105,6 +112,19 @@ InputSectionBase<ELFT> *InputSection<ELFT>::getRelocatedSection() {
   return Sections[this->Header->sh_info];
 }
 
+template <class ELFT> void InputSection<ELFT>::addThunk(SymbolBody &Body) {
+  Body.ThunkIndex = Thunks.size();
+  Thunks.push_back(&Body);
+}
+
+template <class ELFT> uint64_t InputSection<ELFT>::getThunkOff() const {
+  return this->Header->sh_size;
+}
+
+template <class ELFT> uint64_t InputSection<ELFT>::getThunksSize() const {
+  return Thunks.size() * Target->ThunkSize;
+}
+
 // This is used for -r. We can't use memcpy to copy relocations because we need
 // to update symbol table offset and section index for each relocation. So we
 // copy relocations one by one.
@@ -293,6 +313,9 @@ void InputSectionBase<ELFT>::relocate(uint8_t *Buf, uint8_t *BufEnd,
       // If that's the case, we leave the field alone rather than filling it
       // with a possibly incorrect value.
       continue;
+    } else if (Target->needsThunk(Type, *this->getFile(), Body)) {
+      // Get address of a thunk code related to the symbol.
+      SymVA = Body.getThunkVA<ELFT>();
     } else if (Config->EMachine == EM_MIPS) {
       SymVA = adjustMipsSymVA<ELFT>(Type, *File, Body, AddrLoc, SymVA);
     } else if (!Target->needsCopyRel<ELFT>(Type, Body) &&
@@ -333,6 +356,19 @@ template <class ELFT> void InputSection<ELFT>::writeTo(uint8_t *Buf) {
     else
       this->relocate(Buf, BufEnd, EObj.rels(RelSec));
   }
+
+  // The section might have a data/code generated by the linker and need
+  // to be written after the section. Usually these are thunks - small piece
+  // of code used to jump between "incompatible" functions like PIC and non-PIC
+  // or if the jump target too far and its address does not fit to the short
+  // jump istruction.
+  if (!Thunks.empty()) {
+    Buf += OutSecOff + getThunkOff();
+    for (const SymbolBody *S : Thunks) {
+      Target->writeThunk(Buf, S->getVA<ELFT>());
+      Buf += Target->ThunkSize;
+    }
+  }
 }
 
 template <class ELFT>
index c45a16b..650e705 100644 (file)
@@ -56,7 +56,7 @@ public:
   InputSectionBase<ELFT> *Repl;
 
   // Returns the size of this section (even if this is a common or BSS.)
-  size_t getSize() const { return Header->sh_size; }
+  size_t getSize() const;
 
   static InputSectionBase<ELFT> *Discarded;
 
@@ -167,6 +167,17 @@ public:
 
   InputSectionBase<ELFT> *getRelocatedSection();
 
+  // Register thunk related to the symbol. When the section is written
+  // to a mmap'ed file, target is requested to write an actual thunk code.
+  // Now thunks is supported for MIPS target only.
+  void addThunk(SymbolBody &Body);
+
+  // The offset of synthetic thunk code from beginning of this section.
+  uint64_t getThunkOff() const;
+
+  // Size of chunk with thunks code.
+  uint64_t getThunksSize() const;
+
 private:
   template <class RelTy>
   void copyRelocations(uint8_t *Buf, llvm::iterator_range<const RelTy *> Rels);
@@ -176,6 +187,8 @@ private:
 
   // Used by ICF.
   uint64_t GroupId = 0;
+
+  llvm::TinyPtrVector<const SymbolBody *> Thunks;
 };
 
 // MIPS .reginfo section provides information on the registers used by the code
index 8ea9033..bf693b7 100644 (file)
@@ -821,12 +821,6 @@ void OutputSection<ELFT>::addSection(InputSectionBase<ELFT> *C) {
   Sections.push_back(S);
   S->OutSec = this;
   this->updateAlign(S->Align);
-
-  uintX_t Off = this->Header.sh_size;
-  Off = alignTo(Off, S->Align);
-  S->OutSecOff = Off;
-  Off += S->getSize();
-  this->Header.sh_size = Off;
 }
 
 // If an input string is in the form of "foo.N" where N is a number,
@@ -843,8 +837,8 @@ static int getPriority(StringRef S) {
 }
 
 // This function is called after we sort input sections
-// to update their offsets.
-template <class ELFT> void OutputSection<ELFT>::reassignOffsets() {
+// and scan relocations to setup sections' offsets.
+template <class ELFT> void OutputSection<ELFT>::assignOffsets() {
   uintX_t Off = 0;
   for (InputSection<ELFT> *S : Sections) {
     Off = alignTo(Off, S->Align);
@@ -872,7 +866,6 @@ template <class ELFT> void OutputSection<ELFT>::sortInitFini() {
   Sections.clear();
   for (Pair &P : V)
     Sections.push_back(P.second);
-  reassignOffsets();
 }
 
 // Returns true if S matches /Filename.?\.o$/.
@@ -933,7 +926,6 @@ static bool compCtors(const InputSection<ELFT> *A,
 // Read the comment above.
 template <class ELFT> void OutputSection<ELFT>::sortCtorsDtors() {
   std::stable_sort(Sections.begin(), Sections.end(), compCtors<ELFT>);
-  reassignOffsets();
 }
 
 static void fill(uint8_t *Buf, size_t Size, ArrayRef<uint8_t> A) {
index 2c1f735..b066a95 100644 (file)
@@ -87,6 +87,7 @@ public:
   // Typically the first section of each PT_LOAD segment has this flag.
   bool PageAlign = false;
 
+  virtual void assignOffsets() {}
   virtual void finalize() {}
   virtual void writeTo(uint8_t *Buf) {}
   virtual ~OutputSectionBase() = default;
@@ -271,10 +272,10 @@ public:
   void sortInitFini();
   void sortCtorsDtors();
   void writeTo(uint8_t *Buf) override;
+  void assignOffsets() override;
   void finalize() override;
 
 private:
-  void reassignOffsets();
   std::vector<InputSection<ELFT> *> Sections;
 };
 
index 3ea7ceb..b536def 100644 (file)
@@ -37,6 +37,8 @@ static typename ELFT::uint getSymVA(const SymbolBody &Body,
   switch (Body.kind()) {
   case SymbolBody::DefinedSyntheticKind: {
     auto &D = cast<DefinedSynthetic<ELFT>>(Body);
+    if (D.Value == DefinedSynthetic<ELFT>::SectionEnd)
+      return D.Section.getVA() + D.Section.getSize();
     return D.Section.getVA() + D.Value;
   }
   case SymbolBody::DefinedRegularKind: {
@@ -133,6 +135,13 @@ template <class ELFT> typename ELFT::uint SymbolBody::getPltVA() const {
          PltIndex * Target->PltEntrySize;
 }
 
+template <class ELFT> typename ELFT::uint SymbolBody::getThunkVA() const {
+  auto *D = cast<DefinedRegular<ELFT>>(this);
+  auto *S = cast<InputSection<ELFT>>(D->Section);
+  return S->OutSec->getVA() + S->OutSecOff + S->getThunkOff() +
+         ThunkIndex * Target->ThunkSize;
+}
+
 template <class ELFT> typename ELFT::uint SymbolBody::getSize() const {
   if (auto *B = dyn_cast<DefinedElf<ELFT>>(this))
     return B->Sym.st_size;
@@ -298,6 +307,11 @@ template uint32_t SymbolBody::template getSize<ELF32BE>() const;
 template uint64_t SymbolBody::template getSize<ELF64LE>() const;
 template uint64_t SymbolBody::template getSize<ELF64BE>() const;
 
+template uint32_t SymbolBody::template getThunkVA<ELF32LE>() const;
+template uint32_t SymbolBody::template getThunkVA<ELF32BE>() const;
+template uint64_t SymbolBody::template getThunkVA<ELF64LE>() const;
+template uint64_t SymbolBody::template getThunkVA<ELF64BE>() const;
+
 template int SymbolBody::compare<ELF32LE>(SymbolBody *Other);
 template int SymbolBody::compare<ELF32BE>(SymbolBody *Other);
 template int SymbolBody::compare<ELF64LE>(SymbolBody *Other);
index 8db6e22..5f5ce88 100644 (file)
@@ -85,9 +85,11 @@ public:
   uint32_t GotIndex = -1;
   uint32_t GotPltIndex = -1;
   uint32_t PltIndex = -1;
+  uint32_t ThunkIndex = -1;
   bool hasGlobalDynIndex() { return GlobalDynIndex != uint32_t(-1); }
   bool isInGot() const { return GotIndex != -1U; }
   bool isInPlt() const { return PltIndex != -1U; }
+  bool hasThunk() const { return ThunkIndex != -1U; }
 
   void setUsedInRegularObj() { IsUsedInRegularObj = true; }
 
@@ -97,6 +99,7 @@ public:
   template <class ELFT> typename ELFT::uint getGotVA() const;
   template <class ELFT> typename ELFT::uint getGotPltVA() const;
   template <class ELFT> typename ELFT::uint getPltVA() const;
+  template <class ELFT> typename ELFT::uint getThunkVA() const;
   template <class ELFT> typename ELFT::uint getSize() const;
 
   // A SymbolBody has a backreference to a Symbol. Originally they are
@@ -249,6 +252,10 @@ public:
     return S->kind() == SymbolBody::DefinedSyntheticKind;
   }
 
+  // Special value designates that the symbol 'points'
+  // to the end of the section.
+  static const uintX_t SectionEnd = uintX_t(-1);
+
   uintX_t Value;
   const OutputSectionBase<ELFT> &Section;
 };
index d56984b..0e945bc 100644 (file)
@@ -17,6 +17,7 @@
 
 #include "Target.h"
 #include "Error.h"
+#include "InputFiles.h"
 #include "OutputSections.h"
 #include "Symbols.h"
 
@@ -198,9 +199,12 @@ public:
   void writePlt(uint8_t *Buf, uint64_t GotEntryAddr, uint64_t PltEntryAddr,
                 int32_t Index, unsigned RelOff) const override;
   void writeGotHeader(uint8_t *Buf) const override;
+  void writeThunk(uint8_t *Buf, uint64_t S) const override;
   bool needsCopyRelImpl(uint32_t Type) const override;
   bool needsGot(uint32_t Type, const SymbolBody &S) const override;
   bool needsPltImpl(uint32_t Type) const override;
+  bool needsThunk(uint32_t Type, const InputFile &File,
+                  const SymbolBody &S) const override;
   void relocateOne(uint8_t *Loc, uint8_t *BufEnd, uint32_t Type, uint64_t P,
                    uint64_t SA) const override;
   bool isHintRel(uint32_t Type) const override;
@@ -331,6 +335,11 @@ TargetInfo::PltNeed TargetInfo::needsPlt(uint32_t Type,
   return Plt_No;
 }
 
+bool TargetInfo::needsThunk(uint32_t Type, const InputFile &File,
+                            const SymbolBody &S) const {
+  return false;
+}
+
 bool TargetInfo::isTlsInitialExecRel(uint32_t Type) const { return false; }
 
 bool TargetInfo::pointsToLocalDynamicGotEntry(uint32_t Type) const {
@@ -1581,6 +1590,7 @@ template <class ELFT> MipsTargetInfo<ELFT>::MipsTargetInfo() {
   PageSize = 65536;
   PltEntrySize = 16;
   PltZeroSize = 32;
+  ThunkSize = 16;
   UseLazyBinding = true;
   CopyRel = R_MIPS_COPY;
   PltRel = R_MIPS_JUMP_SLOT;
@@ -1695,6 +1705,20 @@ void MipsTargetInfo<ELFT>::writePlt(uint8_t *Buf, uint64_t GotEntryAddr,
 }
 
 template <class ELFT>
+void MipsTargetInfo<ELFT>::writeThunk(uint8_t *Buf, uint64_t S) const {
+  // Write MIPS LA25 thunk code to call PIC function from the non-PIC one.
+  // See MipsTargetInfo::writeThunk for details.
+  const endianness E = ELFT::TargetEndianness;
+  write32<E>(Buf, 0x3c190000);      // lui   $25, %hi(func)
+  write32<E>(Buf + 4, 0x08000000);  // j     func
+  write32<E>(Buf + 8, 0x27390000);  // addiu $25, $25, %lo(func)
+  write32<E>(Buf + 12, 0x00000000); // nop
+  writeMipsHi16<E>(Buf, S);
+  write32<E>(Buf + 4, 0x08000000 | (S >> 2));
+  writeMipsLo16<E>(Buf + 8, S);
+}
+
+template <class ELFT>
 bool MipsTargetInfo<ELFT>::needsCopyRelImpl(uint32_t Type) const {
   return !isRelRelative(Type);
 }
@@ -1715,6 +1739,31 @@ bool MipsTargetInfo<ELFT>::needsPltImpl(uint32_t Type) const {
 }
 
 template <class ELFT>
+bool MipsTargetInfo<ELFT>::needsThunk(uint32_t Type, const InputFile &File,
+                                      const SymbolBody &S) const {
+  // Any MIPS PIC code function is invoked with its address in register $t9.
+  // So if we have a branch instruction from non-PIC code to the PIC one
+  // we cannot make the jump directly and need to create a small stubs
+  // to save the target function address.
+  // See page 3-38 ftp://www.linux-mips.org/pub/linux/mips/doc/ABI/mipsabi.pdf
+  if (Type != R_MIPS_26)
+    return false;
+  auto *F = dyn_cast<ELFFileBase<ELFT>>(&File);
+  if (!F)
+    return false;
+  // If current file has PIC code, LA25 stub is not required.
+  if (F->getObj().getHeader()->e_flags & EF_MIPS_PIC)
+    return false;
+  auto *D = dyn_cast<DefinedRegular<ELFT>>(&S);
+  if (!D || !D->Section)
+    return false;
+  // LA25 is required if target file has PIC code
+  // or target symbol is a PIC symbol.
+  return (D->Section->getFile()->getObj().getHeader()->e_flags & EF_MIPS_PIC) ||
+         (D->Sym.st_other & STO_MIPS_MIPS16) == STO_MIPS_PIC;
+}
+
+template <class ELFT>
 uint64_t MipsTargetInfo<ELFT>::getImplicitAddend(uint8_t *Buf,
                                                  uint32_t Type) const {
   const endianness E = ELFT::TargetEndianness;
index 6dc8ae2..a629511 100644 (file)
@@ -17,6 +17,7 @@
 
 namespace lld {
 namespace elf {
+class InputFile;
 class SymbolBody;
 
 class TargetInfo {
@@ -62,6 +63,11 @@ public:
   enum PltNeed { Plt_No, Plt_Explicit, Plt_Implicit };
   PltNeed needsPlt(uint32_t Type, const SymbolBody &S) const;
 
+  virtual bool needsThunk(uint32_t Type, const InputFile &File,
+                          const SymbolBody &S) const;
+
+  virtual void writeThunk(uint8_t *Buf, uint64_t S) const {}
+
   virtual void relocateOne(uint8_t *Loc, uint8_t *BufEnd, uint32_t Type,
                            uint64_t P, uint64_t SA) const = 0;
   virtual bool isGotRelative(uint32_t Type) const;
@@ -94,6 +100,7 @@ public:
   unsigned PltZeroSize = 0;
   unsigned GotHeaderEntriesNum = 0;
   unsigned GotPltHeaderEntriesNum = 3;
+  uint32_t ThunkSize = 0;
   bool UseLazyBinding = false;
 
 private:
index 76cde10..854722c 100644 (file)
@@ -85,6 +85,9 @@ private:
   bool isOutputDynamic() const {
     return !Symtab.getSharedFiles().empty() || Config->Pic;
   }
+  template <class RelTy>
+  void scanRelocsForThunks(const elf::ObjectFile<ELFT> &File,
+                           iterator_range<const RelTy *> Rels);
 
   void ensureBss();
   void addCommonSymbols(std::vector<DefinedCommon *> &Syms);
@@ -298,6 +301,25 @@ static unsigned handleTlsRelocation(uint32_t Type, SymbolBody &Body,
   return 0;
 }
 
+// Some targets might require creation of thunks for relocations. Now we
+// support only MIPS which requires LA25 thunk to call PIC code from non-PIC
+// one. Scan relocations to find each one requires thunk.
+template <class ELFT>
+template <class RelTy>
+void Writer<ELFT>::scanRelocsForThunks(const elf::ObjectFile<ELFT> &File,
+                                       iterator_range<const RelTy *> Rels) {
+  for (const RelTy &RI : Rels) {
+    uint32_t Type = RI.getType(Config->Mips64EL);
+    uint32_t SymIndex = RI.getSymbol(Config->Mips64EL);
+    SymbolBody &Body = File.getSymbolBody(SymIndex).repl();
+    if (Body.hasThunk() || !Target->needsThunk(Type, File, Body))
+      continue;
+    auto *D = cast<DefinedRegular<ELFT>>(&Body);
+    auto *S = cast<InputSection<ELFT>>(D->Section);
+    S->addThunk(Body);
+  }
+}
+
 // The reason we have to do this early scan is as follows
 // * To mmap the output file, we need to know the size
 // * For that, we need to know how many dynamic relocs we will have.
@@ -479,6 +501,10 @@ void Writer<ELFT>::scanRelocs(InputSectionBase<ELFT> &C,
     Out<ELFT>::RelaDyn->addReloc(
         {Target->RelativeRel, &C, RI.r_offset, true, &Body, Addend});
   }
+
+  // Scan relocations for necessary thunks.
+  if (Config->EMachine == EM_MIPS)
+    scanRelocsForThunks(File, Rels);
 }
 
 template <class ELFT> void Writer<ELFT>::scanRelocs(InputSection<ELFT> &C) {
@@ -1042,6 +1068,9 @@ template <class ELFT> bool Writer<ELFT>::createSections() {
     }
   }
 
+  for (OutputSectionBase<ELFT> *Sec : getSections())
+    Sec->assignOffsets();
+
   // Now that we have defined all possible symbols including linker-
   // synthesized ones. Visit all symbols to give the finishing touches.
   std::vector<DefinedCommon *> CommonSymbols;
@@ -1167,7 +1196,8 @@ template <class ELFT> void Writer<ELFT>::addStartEndSymbols() {
                     OutputSectionBase<ELFT> *OS) {
     if (OS) {
       Symtab.addSynthetic(Start, *OS, 0, STV_DEFAULT);
-      Symtab.addSynthetic(End, *OS, OS->getSize(), STV_DEFAULT);
+      Symtab.addSynthetic(End, *OS, DefinedSynthetic<ELFT>::SectionEnd,
+                          STV_DEFAULT);
     } else {
       Symtab.addIgnored(Start);
       Symtab.addIgnored(End);
@@ -1200,7 +1230,8 @@ void Writer<ELFT>::addStartStopSymbols(OutputSectionBase<ELFT> *Sec) {
       Symtab.addSynthetic(Start, *Sec, 0, STV_DEFAULT);
   if (SymbolBody *B = Symtab.find(Stop))
     if (B->isUndefined())
-      Symtab.addSynthetic(Stop, *Sec, Sec->getSize(), STV_DEFAULT);
+      Symtab.addSynthetic(Stop, *Sec, DefinedSynthetic<ELFT>::SectionEnd,
+                          STV_DEFAULT);
 }
 
 template <class ELFT> static bool needsPtLoad(OutputSectionBase<ELFT> *Sec) {
diff --git a/lld/test/ELF/Inputs/mips-pic.s b/lld/test/ELF/Inputs/mips-pic.s
new file mode 100644 (file)
index 0000000..8809032
--- /dev/null
@@ -0,0 +1,19 @@
+  .option pic2
+
+  .section .text.1,"ax",@progbits
+  .align 4
+  .globl foo1a
+  .type foo1a, @function
+foo1a:
+  nop
+  .globl foo1b
+  .type foo1b, @function
+foo1b:
+  nop
+
+  .section .text.2,"ax",@progbits
+  .align 4
+  .globl foo2
+  .type foo2, @function
+foo2:
+  nop
diff --git a/lld/test/ELF/mips-npic-call-pic.s b/lld/test/ELF/mips-npic-call-pic.s
new file mode 100644 (file)
index 0000000..4c4fc4e
--- /dev/null
@@ -0,0 +1,58 @@
+# Check LA25 stubs creation. This stub code is necessary when
+# non-PIC code calls PIC function.
+
+# RUN: llvm-mc -filetype=obj -triple=mips-unknown-linux \
+# RUN:   %p/Inputs/mips-pic.s -o %t-pic.o
+# RUN: llvm-mc -filetype=obj -triple=mips-unknown-linux %s -o %t-npic.o
+# RUN: ld.lld %t-npic.o %t-pic.o -o %t.exe
+# RUN: llvm-objdump -d %t.exe | FileCheck %s
+
+# REQUIRES: mips
+
+# CHECK:     Disassembly of section .text:
+# CHECK-NEXT: __start:
+# CHECK-NEXT:    20000:       0c 00 80 0a     jal     131112
+#                                                     ^-- 0x20030 .pic.foo1a
+# CHECK-NEXT:    20004:       00 00 00 00     nop
+# CHECK-NEXT:    20008:       0c 00 80 15     jal     131156
+#                                                     ^-- 0x20060 .pic.foo2
+# CHECK-NEXT:    2000c:       00 00 00 00     nop
+# CHECK-NEXT:    20010:       0c 00 80 0e     jal     131128
+#                                                     ^-- 0x20040 .pic.foo1b
+# CHECK-NEXT:    20014:       00 00 00 00     nop
+# CHECK-NEXT:    20018:       0c 00 80 15     jal     131156
+#                                                     ^-- 0x20060 .pic.foo2
+# CHECK-NEXT:    2001c:       00 00 00 00     nop
+#
+# CHECK:      foo1a:
+# CHECK-NEXT:    20020:       00 00 00 00     nop
+#
+# CHECK:      foo1b:
+# CHECK-NEXT:    20024:       00 00 00 00     nop
+#
+# CHECK-NEXT:    20028:       3c 19 00 02     lui     $25, 2
+# CHECK-NEXT:    2002c:       08 00 80 08     j       131104 <foo1a>
+# CHECK-NEXT:    20030:       27 39 00 20     addiu   $25, $25, 32
+# CHECK-NEXT:    20034:       00 00 00 00     nop
+# CHECK-NEXT:    20038:       3c 19 00 02     lui     $25, 2
+# CHECK-NEXT:    2003c:       08 00 80 09     j       131108 <foo1b>
+# CHECK-NEXT:    20040:       27 39 00 24     addiu   $25, $25, 36
+# CHECK-NEXT:    20044:       00 00 00 00     nop
+# CHECK-NEXT:    20048:       00 00 00 00     nop
+# CHECK-NEXT:    2004c:       00 00 00 00     nop
+#
+# CHECK:      foo2:
+# CHECK-NEXT:    20050:       00 00 00 00     nop
+#
+# CHECK-NEXT:    20054:       3c 19 00 02     lui     $25, 2
+# CHECK-NEXT:    20058:       08 00 80 14     j       131152 <foo2>
+# CHECK-NEXT:    2005c:       27 39 00 50     addiu   $25, $25, 80
+# CHECK-NEXT:    20060:       00 00 00 00     nop
+
+  .text
+  .globl __start
+__start:
+  jal foo1a
+  jal foo2
+  jal foo1b
+  jal foo2