MC: Rework .reloc directive and fix the offset when it evaluates to a constant

* Fix `.reloc constant` to mean section_symbol+constant instead of
  .+constant . The initial .reloc support from MIPS incorrectly
  interpreted the offset.
* Delay the evaluation of the offset expression after
  MCAssembler::layout, deleting a lot of code working with MCFragment.
* Delete many FIXME from https://reviews.llvm.org/D79625
* Some lld/ELF/Arch/LoongArch.cpp relaxation tests rely on .reloc .,
  R_LARCH_ALIGN generating ALIGN relocations at specific location.
  Sort the relocations.
This commit is contained in:
Fangrui Song 2025-07-17 00:36:10 -07:00
parent 34c8533742
commit 3cb0c7f45b
16 changed files with 117 additions and 210 deletions

View File

@ -1671,8 +1671,9 @@ void RelocationScanner::scan(Relocs<RelTy> rels) {
}
// Sort relocations by offset for more efficient searching for
// R_RISCV_PCREL_HI20, R_PPC64_ADDR64 and the branch-to-branch optimization.
if (ctx.arg.emachine == EM_RISCV ||
// R_RISCV_PCREL_HI20, ALIGN relocations, R_PPC64_ADDR64 and the
// branch-to-branch optimization.
if (is_contained({EM_RISCV, EM_LOONGARCH}, ctx.arg.emachine) ||
(ctx.arg.emachine == EM_PPC64 && sec->name == ".toc") ||
ctx.arg.branchToBranch)
llvm::stable_sort(sec->relocs(),

View File

@ -69,6 +69,13 @@ private:
SmallVector<const MCSymbol *, 0> Symbols;
struct RelocDirective {
const MCExpr &Offset;
const MCExpr *Expr;
uint32_t Kind;
};
SmallVector<RelocDirective, 0> relocDirectives;
mutable SmallVector<std::pair<SMLoc, std::string>, 0> PendingErrors;
MCDwarfLineTableParams LTParams;
@ -205,6 +212,7 @@ public:
LLVM_ABI bool registerSection(MCSection &Section);
LLVM_ABI bool registerSymbol(const MCSymbol &Symbol);
void addRelocDirective(RelocDirective RD);
LLVM_ABI void reportError(SMLoc L, const Twine &Msg) const;
// Record pending errors during layout iteration, as they may go away once the

View File

@ -141,7 +141,8 @@ public:
}
private:
void finalizeCGProfileEntry(const MCSymbolRefExpr *&S, uint64_t Offset);
void finalizeCGProfileEntry(const MCSymbolRefExpr *Sym, uint64_t Offset,
const MCSymbolRefExpr *&S);
void finalizeCGProfile();
bool SeenIdent = false;

View File

@ -40,14 +40,6 @@ class MCObjectStreamer : public MCStreamer {
std::unique_ptr<MCAssembler> Assembler;
bool EmitEHFrame;
bool EmitDebugFrame;
struct PendingMCFixup {
const MCSymbol *Sym;
MCFixup Fixup;
MCFragment *DF;
PendingMCFixup(const MCSymbol *McSym, MCFragment *F, MCFixup McFixup)
: Sym(McSym), Fixup(McFixup), DF(F) {}
};
SmallVector<PendingMCFixup, 2> PendingFixups;
struct PendingAssignment {
MCSymbol *Symbol;
@ -63,7 +55,6 @@ class MCObjectStreamer : public MCStreamer {
void emitCFIStartProcImpl(MCDwarfFrameInfo &Frame) override;
void emitCFIEndProcImpl(MCDwarfFrameInfo &Frame) override;
void emitInstructionImpl(const MCInst &Inst, const MCSubtargetInfo &STI);
void resolvePendingFixups();
protected:
MCObjectStreamer(MCContext &Context, std::unique_ptr<MCAsmBackend> TAB,
@ -162,9 +153,8 @@ public:
void emitCVStringTableDirective() override;
void emitCVFileChecksumsDirective() override;
void emitCVFileChecksumOffsetDirective(unsigned FileNo) override;
std::optional<std::pair<bool, std::string>>
emitRelocDirective(const MCExpr &Offset, StringRef Name, const MCExpr *Expr,
SMLoc Loc, const MCSubtargetInfo &STI) override;
void emitRelocDirective(const MCExpr &Offset, StringRef Name,
const MCExpr *Expr, SMLoc Loc = {}) override;
using MCStreamer::emitFill;
void emitFill(const MCExpr &NumBytes, uint64_t FillValue,
SMLoc Loc = SMLoc()) override;

View File

@ -1048,13 +1048,9 @@ public:
virtual void emitSyntaxDirective();
/// Record a relocation described by the .reloc directive. Return std::nullopt
/// if succeeded. Otherwise, return a pair (Name is invalid, error message).
virtual std::optional<std::pair<bool, std::string>>
emitRelocDirective(const MCExpr &Offset, StringRef Name, const MCExpr *Expr,
SMLoc Loc, const MCSubtargetInfo &STI) {
return std::nullopt;
}
/// Record a relocation described by the .reloc directive.
virtual void emitRelocDirective(const MCExpr &Offset, StringRef Name,
const MCExpr *Expr, SMLoc Loc = {}) {}
virtual void emitAddrsig() {}
virtual void emitAddrsigSym(const MCSymbol *Sym) {}

View File

@ -407,9 +407,8 @@ public:
const MCPseudoProbeInlineStack &InlineStack,
MCSymbol *FnSym) override;
std::optional<std::pair<bool, std::string>>
emitRelocDirective(const MCExpr &Offset, StringRef Name, const MCExpr *Expr,
SMLoc Loc, const MCSubtargetInfo &STI) override;
void emitRelocDirective(const MCExpr &Offset, StringRef Name,
const MCExpr *Expr, SMLoc Loc) override;
void emitAddrsig() override;
void emitAddrsigSym(const MCSymbol *Sym) override;
@ -2468,10 +2467,8 @@ void MCAsmStreamer::emitPseudoProbe(uint64_t Guid, uint64_t Index,
EmitEOL();
}
std::optional<std::pair<bool, std::string>>
MCAsmStreamer::emitRelocDirective(const MCExpr &Offset, StringRef Name,
const MCExpr *Expr, SMLoc,
const MCSubtargetInfo &STI) {
void MCAsmStreamer::emitRelocDirective(const MCExpr &Offset, StringRef Name,
const MCExpr *Expr, SMLoc) {
OS << "\t.reloc ";
MAI->printExpr(OS, Offset);
OS << ", " << Name;
@ -2480,7 +2477,6 @@ MCAsmStreamer::emitRelocDirective(const MCExpr &Offset, StringRef Name,
MAI->printExpr(OS, *Expr);
}
EmitEOL();
return std::nullopt;
}
void MCAsmStreamer::emitAddrsig() {

View File

@ -398,6 +398,10 @@ bool MCAssembler::registerSymbol(const MCSymbol &Symbol) {
return Changed;
}
void MCAssembler::addRelocDirective(RelocDirective RD) {
relocDirectives.push_back(RD);
}
/// Write the fragment \p F to the output file.
static void writeFragment(raw_ostream &OS, const MCAssembler &Asm,
const MCFragment &F) {
@ -695,6 +699,27 @@ void MCAssembler::layout() {
// helps check whether a PC-relative fixup is fully resolved.
this->HasFinalLayout = true;
// Resolve .reloc offsets and add fixups.
for (auto &PF : relocDirectives) {
MCValue Res;
auto &O = PF.Offset;
if (!O.evaluateAsValue(Res, *this)) {
getContext().reportError(O.getLoc(), ".reloc offset is not relocatable");
continue;
}
auto *Sym = Res.getAddSym();
auto *F = Sym ? Sym->getFragment() : nullptr;
auto *Sec = F ? F->getParent() : nullptr;
if (Res.getSubSym() || !Sec) {
getContext().reportError(O.getLoc(),
".reloc offset is not relative to a section");
continue;
}
uint64_t Offset = Sym ? Sym->getOffset() + Res.getConstant() : 0;
F->addFixup(MCFixup::create(Offset, PF.Expr, PF.Kind));
}
// Evaluate and apply the fixups, generating relocation entries as necessary.
for (MCSection &Sec : *this) {
for (MCFragment &F : Sec) {

View File

@ -314,8 +314,9 @@ void MCELFStreamer::emitIdent(StringRef IdentString) {
popSection();
}
void MCELFStreamer::finalizeCGProfileEntry(const MCSymbolRefExpr *&SRE,
uint64_t Offset) {
void MCELFStreamer::finalizeCGProfileEntry(const MCSymbolRefExpr *Sym,
uint64_t Offset,
const MCSymbolRefExpr *&SRE) {
const MCSymbol *S = &SRE->getSymbol();
if (S->isTemporary()) {
if (!S->isInSection()) {
@ -328,13 +329,9 @@ void MCELFStreamer::finalizeCGProfileEntry(const MCSymbolRefExpr *&SRE,
S->setUsedInReloc();
SRE = MCSymbolRefExpr::create(S, getContext(), SRE->getLoc());
}
const MCConstantExpr *MCOffset = MCConstantExpr::create(Offset, getContext());
if (std::optional<std::pair<bool, std::string>> Err =
MCObjectStreamer::emitRelocDirective(
*MCOffset, "BFD_RELOC_NONE", SRE, SRE->getLoc(),
*getContext().getSubtargetInfo()))
report_fatal_error("Relocation for CG Profile could not be created: " +
Twine(Err->second));
auto *O = MCBinaryExpr::createAdd(
Sym, MCConstantExpr::create(Offset, getContext()), getContext());
MCObjectStreamer::emitRelocDirective(*O, "BFD_RELOC_NONE", SRE);
}
void MCELFStreamer::finalizeCGProfile() {
@ -347,9 +344,11 @@ void MCELFStreamer::finalizeCGProfile() {
pushSection();
switchSection(CGProfile);
uint64_t Offset = 0;
auto *Sym =
MCSymbolRefExpr::create(CGProfile->getBeginSymbol(), getContext());
for (auto &E : W.getCGProfile()) {
finalizeCGProfileEntry(E.From, Offset);
finalizeCGProfileEntry(E.To, Offset);
finalizeCGProfileEntry(Sym, Offset, E.From);
finalizeCGProfileEntry(Sym, Offset, E.To);
emitIntValue(E.Count, sizeof(uint64_t));
Offset += sizeof(uint64_t);
}

View File

@ -46,35 +46,6 @@ MCAssembler *MCObjectStreamer::getAssemblerPtr() {
return nullptr;
}
// When fixup's offset is a forward declared label, e.g.:
//
// .reloc 1f, R_MIPS_JALR, foo
// 1: nop
//
// postpone adding it to Fixups vector until the label is defined and its offset
// is known.
void MCObjectStreamer::resolvePendingFixups() {
for (PendingMCFixup &PendingFixup : PendingFixups) {
if (!PendingFixup.Sym || PendingFixup.Sym->isUndefined ()) {
getContext().reportError(PendingFixup.Fixup.getLoc(),
"unresolved relocation offset");
continue;
}
PendingFixup.Fixup.setOffset(PendingFixup.Sym->getOffset() +
PendingFixup.Fixup.getOffset());
// If the location symbol to relocate is in MCEncodedFragment,
// put the Fixup into location symbol's fragment. Otherwise
// put into PendingFixup.DF
MCFragment *F = PendingFixup.Sym->getFragment();
if (F->isEncoded())
F->addFixup(PendingFixup.Fixup);
else
PendingFixup.DF->addFixup(PendingFixup.Fixup);
}
PendingFixups.clear();
}
// As a compile-time optimization, avoid allocating and evaluating an MCExpr
// tree for (Hi - Lo) when Hi and Lo are offsets into the same fragment's fixed
// part.
@ -607,76 +578,14 @@ void MCObjectStreamer::emitValueToOffset(const MCExpr *Offset,
insert(getContext().allocFragment<MCOrgFragment>(*Offset, Value, Loc));
}
static std::optional<std::pair<bool, std::string>>
getOffsetAndDataFragment(const MCSymbol &Symbol, uint32_t &RelocOffset,
MCFragment *&DF) {
if (Symbol.isVariable()) {
const MCExpr *SymbolExpr = Symbol.getVariableValue();
MCValue OffsetVal;
if (!SymbolExpr->evaluateAsRelocatable(OffsetVal, nullptr))
return std::make_pair(false,
std::string("symbol in .reloc offset is not "
"relocatable"));
if (OffsetVal.isAbsolute()) {
RelocOffset = OffsetVal.getConstant();
MCFragment *Fragment = Symbol.getFragment();
// FIXME Support symbols with no DF. For example:
// .reloc .data, ENUM_VALUE, <some expr>
if (!Fragment || Fragment->getKind() != MCFragment::FT_Data)
return std::make_pair(false,
std::string("symbol in offset has no data "
"fragment"));
DF = cast<MCFragment>(Fragment);
return std::nullopt;
}
if (OffsetVal.getSubSym())
return std::make_pair(false,
std::string(".reloc symbol offset is not "
"representable"));
const MCSymbol &SA = *OffsetVal.getAddSym();
if (!SA.isDefined())
return std::make_pair(false,
std::string("symbol used in the .reloc offset is "
"not defined"));
if (SA.isVariable())
return std::make_pair(false,
std::string("symbol used in the .reloc offset is "
"variable"));
MCFragment *Fragment = SA.getFragment();
// FIXME Support symbols with no DF. For example:
// .reloc .data, ENUM_VALUE, <some expr>
if (!Fragment || Fragment->getKind() != MCFragment::FT_Data)
return std::make_pair(false,
std::string("symbol in offset has no data "
"fragment"));
RelocOffset = SA.getOffset() + OffsetVal.getConstant();
DF = cast<MCFragment>(Fragment);
} else {
RelocOffset = Symbol.getOffset();
MCFragment *Fragment = Symbol.getFragment();
// FIXME Support symbols with no DF. For example:
// .reloc .data, ENUM_VALUE, <some expr>
if (!Fragment || Fragment->getKind() != MCFragment::FT_Data)
return std::make_pair(false,
std::string("symbol in offset has no data "
"fragment"));
DF = cast<MCFragment>(Fragment);
}
return std::nullopt;
}
std::optional<std::pair<bool, std::string>>
MCObjectStreamer::emitRelocDirective(const MCExpr &Offset, StringRef Name,
const MCExpr *Expr, SMLoc Loc,
const MCSubtargetInfo &STI) {
void MCObjectStreamer::emitRelocDirective(const MCExpr &Offset, StringRef Name,
const MCExpr *Expr, SMLoc Loc) {
std::optional<MCFixupKind> MaybeKind =
Assembler->getBackend().getFixupKind(Name);
if (!MaybeKind)
return std::make_pair(true, std::string("unknown relocation name"));
if (!MaybeKind) {
getContext().reportError(Loc, "unknown relocation name");
return;
}
MCFixupKind Kind = *MaybeKind;
if (Expr)
@ -685,38 +594,14 @@ MCObjectStreamer::emitRelocDirective(const MCExpr &Offset, StringRef Name,
Expr =
MCSymbolRefExpr::create(getContext().createTempSymbol(), getContext());
MCFragment *DF = getOrCreateDataFragment(&STI);
MCValue OffsetVal;
if (!Offset.evaluateAsRelocatable(OffsetVal, nullptr))
return std::make_pair(false,
std::string(".reloc offset is not relocatable"));
if (OffsetVal.isAbsolute()) {
if (OffsetVal.getConstant() < 0)
return std::make_pair(false, std::string(".reloc offset is negative"));
DF->addFixup(MCFixup::create(OffsetVal.getConstant(), Expr, Kind));
return std::nullopt;
auto *O = &Offset;
int64_t Val;
if (Offset.evaluateAsAbsolute(Val, nullptr)) {
auto *SecSym = getCurrentSectionOnly()->getBeginSymbol();
O = MCBinaryExpr::createAdd(MCSymbolRefExpr::create(SecSym, getContext()),
O, getContext(), Loc);
}
if (OffsetVal.getSubSym())
return std::make_pair(false,
std::string(".reloc offset is not representable"));
const MCSymbol &Symbol = *OffsetVal.getAddSym();
if (Symbol.isDefined()) {
uint32_t SymbolOffset = 0;
std::optional<std::pair<bool, std::string>> Error =
getOffsetAndDataFragment(Symbol, SymbolOffset, DF);
if (Error != std::nullopt)
return Error;
DF->addFixup(
MCFixup::create(SymbolOffset + OffsetVal.getConstant(), Expr, Kind));
return std::nullopt;
}
PendingFixups.emplace_back(
&Symbol, DF, MCFixup::create(OffsetVal.getConstant(), Expr, Kind));
return std::nullopt;
getAssembler().addRelocDirective({*O, Expr, Kind});
}
void MCObjectStreamer::emitFill(const MCExpr &NumBytes, uint64_t FillValue,
@ -799,6 +684,5 @@ void MCObjectStreamer::finishImpl() {
// Emit pseudo probes for the current module.
MCPseudoProbeTable::emit(this);
resolvePendingFixups();
getAssembler().Finish();
}

View File

@ -3079,7 +3079,6 @@ bool AsmParser::parseDirectiveAscii(StringRef IDVal, bool ZeroTerminated) {
bool AsmParser::parseDirectiveReloc(SMLoc DirectiveLoc) {
const MCExpr *Offset;
const MCExpr *Expr = nullptr;
SMLoc OffsetLoc = Lexer.getTok().getLoc();
if (parseExpression(Offset))
return true;
@ -3105,13 +3104,7 @@ bool AsmParser::parseDirectiveReloc(SMLoc DirectiveLoc) {
if (parseEOL())
return true;
const MCTargetAsmParser &MCT = getTargetParser();
const MCSubtargetInfo &STI = MCT.getSTI();
if (std::optional<std::pair<bool, std::string>> Err =
getStreamer().emitRelocDirective(*Offset, Name, Expr, DirectiveLoc,
STI))
return Error(Err->first ? NameLoc : OffsetLoc, Err->second);
getStreamer().emitRelocDirective(*Offset, Name, Expr, NameLoc);
return false;
}

View File

@ -559,8 +559,7 @@ void AArch64TargetELFStreamer::finish() {
if (!Sym.isMemtag())
continue;
auto *SRE = MCSymbolRefExpr::create(&Sym, Ctx);
(void)S.emitRelocDirective(*Zero, "BFD_RELOC_NONE", SRE, SMLoc(),
*Ctx.getSubtargetInfo());
S.emitRelocDirective(*Zero, "BFD_RELOC_NONE", SRE);
}
}

View File

@ -2101,7 +2101,7 @@ bool MipsAsmParser::processInstruction(MCInst &Inst, SMLoc IDLoc,
TOut.getStreamer().emitRelocDirective(
*TmpExpr, inMicroMipsMode() ? "R_MICROMIPS_JALR" : "R_MIPS_JALR",
RelocJalrExpr, IDLoc, *STI);
RelocJalrExpr);
TOut.getStreamer().emitLabel(TmpLabel);
}

View File

@ -166,7 +166,7 @@ static void emitDirectiveRelocJalr(const MachineInstr &MI,
OutStreamer.emitRelocDirective(
*OffsetExpr,
Subtarget.inMicroMipsMode() ? "R_MICROMIPS_JALR" : "R_MIPS_JALR",
CaleeExpr, SMLoc(), *TM.getMCSubtargetInfo());
CaleeExpr);
OutStreamer.emitLabel(OffsetLabel);
return;
}

View File

@ -9,15 +9,18 @@
# ASM-NEXT: .reloc .Ltmp1-1, R_X86_64_NONE, foo
# ASM-NEXT: .Ltmp2:
# ASM-NEXT: .reloc 2+.Ltmp2, R_X86_64_NONE, local
# ASM-NEXT: .reloc 1+foo+3, R_X86_64_NONE, data+1
# ASM-NEXT: .Ltmp3:
# ASM-NEXT: .reloc .Ltmp3, BFD_RELOC_NONE, unused
# CHECK: 0x2 R_X86_64_NONE foo 0x0
# CHECK-NEXT: 0x0 R_X86_64_NONE foo 0x0
# CHECK-NEXT: 0x3 R_X86_64_NONE local 0x0
# CHECK-NEXT: 0x4 R_X86_64_NONE data 0x1
# CHECK-NEXT: 0x1 R_X86_64_NONE unused 0x0
# CHECK-NEXT: 0x4 R_X86_64_NONE data 0x1
# CHECK: .rela.my {
# CHECK: 0x0 R_X86_64_NONE foo 0x0
# CHECK-NEXT: 0x4 R_X86_64_NONE foo 0x0
# CHECK-NEXT: 0x8 R_X86_64_NONE foo 0x0
# CHECK-NEXT: }
.text
.globl foo
@ -27,17 +30,25 @@ local:
.reloc .+3-2, R_X86_64_NONE, foo
.reloc .-1, R_X86_64_NONE, foo
.reloc 2+., R_X86_64_NONE, local
.reloc 1+foo+3, R_X86_64_NONE, data+1
.reloc ., BFD_RELOC_NONE, unused
.space 3
.data
.globl data
data:
.reloc 1+foo+3, R_X86_64_NONE, data+1
.long 0
# RUN: not llvm-mc -filetype=obj -triple=x86_64 --defsym=ERR=1 %s 2>&1 | FileCheck %s --check-prefix=ERR
## Constant offsets are relative to the section start.
.section .my
.word 0
.reloc 0, BFD_RELOC_NONE, foo
.word 0
.p2align 3
.reloc 2+2, BFD_RELOC_NONE, foo
.p2align 4
.reloc 8, BFD_RELOC_NONE, foo
.ifdef ERR
.text
.globl a, b
a: ret
@ -45,22 +56,26 @@ b: ret
x: ret
y: ret
# ERR: {{.*}}.s:[[#@LINE+1]]:10: error: expected comma
# RUN: not llvm-mc -filetype=obj -triple=x86_64 --defsym=PARSE=1 %s 2>&1 | FileCheck %s --check-prefix=PARSE
# RUN: not llvm-mc -filetype=obj -triple=x86_64 --defsym=ERR=1 %s 2>&1 | FileCheck %s --check-prefix=ERR
.ifdef PARSE
# PARSE: {{.*}}.s:[[#@LINE+1]]:10: error: expected comma
.reloc 0 R_X86_64_NONE, a
# ERR: {{.*}}.s:[[#@LINE+1]]:8: error: .reloc offset is negative
.reloc -1, R_X86_64_NONE, a
# ERR: {{.*}}.s:[[#@LINE+1]]:8: error: .reloc offset is not relocatable
.reloc 2*., R_X86_64_NONE, a
# ERR: {{.*}}.s:[[#@LINE+1]]:8: error: .reloc offset is not relocatable
.reloc a+a, R_X86_64_NONE, a
## GNU as accepts a-a but rejects b-a.
# ERR: {{.*}}.s:[[#@LINE+1]]:8: error: .reloc offset is not representable
.reloc a-a, R_X86_64_NONE, a
## TODO GNU as accepts x-x and y-x.
# ERR: {{.*}}.s:[[#@LINE+1]]:8: error: .reloc offset is not representable
.reloc x-x, R_X86_64_NONE, a
# ERR: {{.*}}.s:[[#@LINE+1]]:8: error: directional label undefined
# PARSE: {{.*}}.s:[[#@LINE+1]]:8: error: directional label undefined
.reloc 1f, R_X86_64_NONE, a
.endif
.ifdef ERR
.reloc -1, R_X86_64_NONE, a
# ERR: {{.*}}.s:[[#@LINE+1]]:9: error: .reloc offset is not relocatable
.reloc 2*., R_X86_64_NONE, a
# ERR: {{.*}}.s:[[#@LINE+1]]:9: error: .reloc offset is not relocatable
.reloc a+a, R_X86_64_NONE, a
# ERR: {{.*}}.s:[[#@LINE+1]]:9: error: .reloc offset is not relative to a section
.reloc b-a, R_X86_64_NONE, a
# ERR: {{.*}}.s:[[#@LINE+1]]:9: error: .reloc offset is not relative to a section
.reloc x-x, R_X86_64_NONE, a
.endif

View File

@ -2,8 +2,8 @@
# RUN: -target-abi=o32 -filetype=obj -o /dev/null 2>&1 | FileCheck %s
.text
nop
.reloc foo, R_MIPS_32, .text # CHECK: :[[@LINE]]:24: error: unresolved relocation offset
.reloc foo, R_MIPS_32, .text # CHECK: :[[@LINE]]:8: error: .reloc offset is not relative to a section
nop
nop
.reloc bar, R_MIPS_32, .text # CHECK: :[[@LINE]]:24: error: unresolved relocation offset
.reloc bar, R_MIPS_32, .text # CHECK: :[[@LINE]]:8: error: .reloc offset is not relative to a section
nop

View File

@ -58,18 +58,18 @@ bar:
# OBJ-N32-LABEL: Relocations [
# OBJ-N32: 0x4 R_MIPS_NONE .text
# OBJ-N32-NEXT: 0x1C R_MIPS_GOT_OFST .text
# OBJ-N32-NEXT: 0x0 R_MIPS_32 .text
# OBJ-N32-NEXT: 0xC R_MIPS_32 .text
# OBJ-N32-NEXT: 0x10 R_MIPS_CALL16 foo
# OBJ-N32-NEXT: 0x20 R_MIPS_GOT_DISP foo
# OBJ-N32-NEXT: 0x24 R_MIPS_GOT_PAGE .text
# OBJ-N32-NEXT: 0x1C R_MIPS_GOT_OFST .text
# OBJ-N32-NEXT: 0x0 R_MIPS_32 .text
# OBJ-N64-LABEL: Relocations [
# OBJ-N64: 0x4 R_MIPS_NONE/R_MIPS_NONE/R_MIPS_NONE .text 0x0
# OBJ-N64-NEXT: 0x1C R_MIPS_GOT_OFST/R_MIPS_NONE/R_MIPS_NONE .text 0x0
# OBJ-N64-NEXT: 0x0 R_MIPS_32/R_MIPS_NONE/R_MIPS_NONE .text 0x0
# OBJ-N64-NEXT: 0xC R_MIPS_32/R_MIPS_NONE/R_MIPS_NONE .text 0x0
# OBJ-N64-NEXT: 0x10 R_MIPS_CALL16/R_MIPS_NONE/R_MIPS_NONE foo 0x0
# OBJ-N64-NEXT: 0x20 R_MIPS_GOT_DISP/R_MIPS_NONE/R_MIPS_NONE foo 0x0
# OBJ-N64-NEXT: 0x24 R_MIPS_GOT_PAGE/R_MIPS_NONE/R_MIPS_NONE .text 0x0
# OBJ-N64-NEXT: 0x1C R_MIPS_GOT_OFST/R_MIPS_NONE/R_MIPS_NONE .text 0x0
# OBJ-N64-NEXT: 0x0 R_MIPS_32/R_MIPS_NONE/R_MIPS_NONE .text 0x0