//===-- PPCMachObjectWriter.cpp - PPC Mach-O Writer -----------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "MCTargetDesc/PPCMCTargetDesc.h"
#include "MCTargetDesc/PPCFixupKinds.h"
#include "llvm/ADT/Twine.h"
#include "llvm/MC/MCAsmLayout.h"
#include "llvm/MC/MCAssembler.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCMachObjectWriter.h"
#include "llvm/MC/MCSectionMachO.h"
#include "llvm/MC/MCValue.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/MachO.h"
using namespace llvm;
namespace {
class PPCMachObjectWriter : public MCMachObjectTargetWriter {
bool recordScatteredRelocation(MachObjectWriter *Writer,
const MCAssembler &Asm,
const MCAsmLayout &Layout,
const MCFragment *Fragment,
const MCFixup &Fixup, MCValue Target,
unsigned Log2Size, uint64_t &FixedValue);
void RecordPPCRelocation(MachObjectWriter *Writer, const MCAssembler &Asm,
const MCAsmLayout &Layout,
const MCFragment *Fragment, const MCFixup &Fixup,
MCValue Target, uint64_t &FixedValue);
public:
PPCMachObjectWriter(bool Is64Bit, uint32_t CPUType, uint32_t CPUSubtype)
: MCMachObjectTargetWriter(Is64Bit, CPUType, CPUSubtype) {}
void recordRelocation(MachObjectWriter *Writer, MCAssembler &Asm,
const MCAsmLayout &Layout, const MCFragment *Fragment,
const MCFixup &Fixup, MCValue Target,
uint64_t &FixedValue) override {
if (Writer->is64Bit()) {
report_fatal_error("Relocation emission for MachO/PPC64 unimplemented.");
} else
RecordPPCRelocation(Writer, Asm, Layout, Fragment, Fixup, Target,
FixedValue);
}
};
}
/// computes the log2 of the size of the relocation,
/// used for relocation_info::r_length.
static unsigned getFixupKindLog2Size(unsigned Kind) {
switch (Kind) {
default:
report_fatal_error("log2size(FixupKind): Unhandled fixup kind!");
case FK_PCRel_1:
case FK_Data_1:
return 0;
case FK_PCRel_2:
case FK_Data_2:
return 1;
case FK_PCRel_4:
case PPC::fixup_ppc_brcond14:
case PPC::fixup_ppc_half16:
case PPC::fixup_ppc_br24:
case FK_Data_4:
return 2;
case FK_PCRel_8:
case FK_Data_8:
return 3;
}
return 0;
}
/// Translates generic PPC fixup kind to Mach-O/PPC relocation type enum.
/// Outline based on PPCELFObjectWriter::getRelocType().
static unsigned getRelocType(const MCValue &Target,
const MCFixupKind FixupKind, // from
// Fixup.getKind()
const bool IsPCRel) {
const MCSymbolRefExpr::VariantKind Modifier =
Target.isAbsolute() ? MCSymbolRefExpr::VK_None
: Target.getSymA()->getKind();
// determine the type of the relocation
unsigned Type = MachO::GENERIC_RELOC_VANILLA;
if (IsPCRel) { // relative to PC
switch ((unsigned)FixupKind) {
default:
report_fatal_error("Unimplemented fixup kind (relative)");
case PPC::fixup_ppc_br24:
Type = MachO::PPC_RELOC_BR24; // R_PPC_REL24
break;
case PPC::fixup_ppc_brcond14:
Type = MachO::PPC_RELOC_BR14;
break;
case PPC::fixup_ppc_half16:
switch (Modifier) {
default:
llvm_unreachable("Unsupported modifier for half16 fixup");
case MCSymbolRefExpr::VK_PPC_HA:
Type = MachO::PPC_RELOC_HA16;
break;
case MCSymbolRefExpr::VK_PPC_LO:
Type = MachO::PPC_RELOC_LO16;
break;
case MCSymbolRefExpr::VK_PPC_HI:
Type = MachO::PPC_RELOC_HI16;
break;
}
break;
}
} else {
switch ((unsigned)FixupKind) {
default:
report_fatal_error("Unimplemented fixup kind (absolute)!");
case PPC::fixup_ppc_half16:
switch (Modifier) {
default:
llvm_unreachable("Unsupported modifier for half16 fixup");
case MCSymbolRefExpr::VK_PPC_HA:
Type = MachO::PPC_RELOC_HA16_SECTDIFF;
break;
case MCSymbolRefExpr::VK_PPC_LO:
Type = MachO::PPC_RELOC_LO16_SECTDIFF;
break;
case MCSymbolRefExpr::VK_PPC_HI:
Type = MachO::PPC_RELOC_HI16_SECTDIFF;
break;
}
break;
case FK_Data_4:
break;
case FK_Data_2:
break;
}
}
return Type;
}
static void makeRelocationInfo(MachO::any_relocation_info &MRE,
const uint32_t FixupOffset, const uint32_t Index,
const unsigned IsPCRel, const unsigned Log2Size,
const unsigned IsExtern, const unsigned Type) {
MRE.r_word0 = FixupOffset;
// The bitfield offsets that work (as determined by trial-and-error)
// are different than what is documented in the mach-o manuals.
// This appears to be an endianness issue; reversing the order of the
// documented bitfields in <llvm/Support/MachO.h> fixes this (but
// breaks x86/ARM assembly).
MRE.r_word1 = ((Index << 8) | // was << 0
(IsPCRel << 7) | // was << 24
(Log2Size << 5) | // was << 25
(IsExtern << 4) | // was << 27
(Type << 0)); // was << 28
}
static void
makeScatteredRelocationInfo(MachO::any_relocation_info &MRE,
const uint32_t Addr, const unsigned Type,
const unsigned Log2Size, const unsigned IsPCRel,
const uint32_t Value2) {
// For notes on bitfield positions and endianness, see:
// https://developer.apple.com/library/mac/documentation/developertools/conceptual/MachORuntime/Reference/reference.html#//apple_ref/doc/uid/20001298-scattered_relocation_entry
MRE.r_word0 = ((Addr << 0) | (Type << 24) | (Log2Size << 28) |
(IsPCRel << 30) | MachO::R_SCATTERED);
MRE.r_word1 = Value2;
}
/// Compute fixup offset (address).
static uint32_t getFixupOffset(const MCAsmLayout &Layout,
const MCFragment *Fragment,
const MCFixup &Fixup) {
uint32_t FixupOffset = Layout.getFragmentOffset(Fragment) + Fixup.getOffset();
// On Mach-O, ppc_fixup_half16 relocations must refer to the
// start of the instruction, not the second halfword, as ELF does
if (unsigned(Fixup.getKind()) == PPC::fixup_ppc_half16)
FixupOffset &= ~uint32_t(3);
return FixupOffset;
}
/// \return false if falling back to using non-scattered relocation,
/// otherwise true for normal scattered relocation.
/// based on X86MachObjectWriter::recordScatteredRelocation
/// and ARMMachObjectWriter::recordScatteredRelocation
bool PPCMachObjectWriter::recordScatteredRelocation(
MachObjectWriter *Writer, const MCAssembler &Asm, const MCAsmLayout &Layout,
const MCFragment *Fragment, const MCFixup &Fixup, MCValue Target,
unsigned Log2Size, uint64_t &FixedValue) {
// caller already computes these, can we just pass and reuse?
const uint32_t FixupOffset = getFixupOffset(Layout, Fragment, Fixup);
const MCFixupKind FK = Fixup.getKind();
const unsigned IsPCRel = Writer->isFixupKindPCRel(Asm, FK);
const unsigned Type = getRelocType(Target, FK, IsPCRel);
// Is this a local or SECTDIFF relocation entry?
// SECTDIFF relocation entries have symbol subtractions,
// and require two entries, the first for the add-symbol value,
// the second for the subtract-symbol value.
// See <reloc.h>.
const MCSymbol *A = &Target.getSymA()->getSymbol();
if (!A->getFragment())
report_fatal_error("symbol '" + A->getName() +
"' can not be undefined in a subtraction expression");
uint32_t Value = Writer->getSymbolAddress(*A, Layout);
uint64_t SecAddr = Writer->getSectionAddress(A->getFragment()->getParent());
FixedValue += SecAddr;
uint32_t Value2 = 0;
if (const MCSymbolRefExpr *B = Target.getSymB()) {
const MCSymbol *SB = &B->getSymbol();
if (!SB->getFragment())
report_fatal_error("symbol '" + B->getSymbol().getName() +
"' can not be undefined in a subtraction expression");
// FIXME: is Type correct? see include/llvm/Support/MachO.h
Value2 = Writer->getSymbolAddress(B->getSymbol(), Layout);
FixedValue -= Writer->getSectionAddress(SB->getFragment()->getParent());
}
// FIXME: does FixedValue get used??
// Relocations are written out in reverse order, so the PAIR comes first.
if (Type == MachO::PPC_RELOC_SECTDIFF ||
Type == MachO::PPC_RELOC_HI16_SECTDIFF ||
Type == MachO::PPC_RELOC_LO16_SECTDIFF ||
Type == MachO::PPC_RELOC_HA16_SECTDIFF ||
Type == MachO::PPC_RELOC_LO14_SECTDIFF ||
Type == MachO::PPC_RELOC_LOCAL_SECTDIFF) {
// X86 had this piece, but ARM does not
// If the offset is too large to fit in a scattered relocation,
// we're hosed. It's an unfortunate limitation of the MachO format.
if (FixupOffset > 0xffffff) {
char Buffer[32];
format("0x%x", FixupOffset).print(Buffer, sizeof(Buffer));
Asm.getContext().reportError(Fixup.getLoc(),
Twine("Section too large, can't encode "
"r_address (") +
Buffer + ") into 24 bits of scattered "
"relocation entry.");
return false;
}
// Is this supposed to follow MCTarget/PPCAsmBackend.cpp:adjustFixupValue()?
// see PPCMCExpr::evaluateAsRelocatableImpl()
uint32_t other_half = 0;
switch (Type) {
case MachO::PPC_RELOC_LO16_SECTDIFF:
other_half = (FixedValue >> 16) & 0xffff;
// applyFixupOffset longer extracts the high part because it now assumes
// this was already done.
// It looks like this is not true for the FixedValue needed with Mach-O
// relocs.
// So we need to adjust FixedValue again here.
FixedValue &= 0xffff;
break;
case MachO::PPC_RELOC_HA16_SECTDIFF:
other_half = FixedValue & 0xffff;
FixedValue =
((FixedValue >> 16) + ((FixedValue & 0x8000) ? 1 : 0)) & 0xffff;
break;
case MachO::PPC_RELOC_HI16_SECTDIFF:
other_half = FixedValue & 0xffff;
FixedValue = (FixedValue >> 16) & 0xffff;
break;
default:
llvm_unreachable("Invalid PPC scattered relocation type.");
break;
}
MachO::any_relocation_info MRE;
makeScatteredRelocationInfo(MRE, other_half, MachO::GENERIC_RELOC_PAIR,
Log2Size, IsPCRel, Value2);
Writer->addRelocation(nullptr, Fragment->getParent(), MRE);
} else {
// If the offset is more than 24-bits, it won't fit in a scattered
// relocation offset field, so we fall back to using a non-scattered
// relocation. This is a bit risky, as if the offset reaches out of
// the block and the linker is doing scattered loading on this
// symbol, things can go badly.
//
// Required for 'as' compatibility.
if (FixupOffset > 0xffffff)
return false;
}
MachO::any_relocation_info MRE;
makeScatteredRelocationInfo(MRE, FixupOffset, Type, Log2Size, IsPCRel, Value);
Writer->addRelocation(nullptr, Fragment->getParent(), MRE);
return true;
}
// see PPCELFObjectWriter for a general outline of cases
void PPCMachObjectWriter::RecordPPCRelocation(
MachObjectWriter *Writer, const MCAssembler &Asm, const MCAsmLayout &Layout,
const MCFragment *Fragment, const MCFixup &Fixup, MCValue Target,
uint64_t &FixedValue) {
const MCFixupKind FK = Fixup.getKind(); // unsigned
const unsigned Log2Size = getFixupKindLog2Size(FK);
const bool IsPCRel = Writer->isFixupKindPCRel(Asm, FK);
const unsigned RelocType = getRelocType(Target, FK, IsPCRel);
// If this is a difference or a defined symbol plus an offset, then we need a
// scattered relocation entry. Differences always require scattered
// relocations.
if (Target.getSymB() &&
// Q: are branch targets ever scattered?
RelocType != MachO::PPC_RELOC_BR24 &&
RelocType != MachO::PPC_RELOC_BR14) {
recordScatteredRelocation(Writer, Asm, Layout, Fragment, Fixup, Target,
Log2Size, FixedValue);
return;
}
// this doesn't seem right for RIT_PPC_BR24
// Get the symbol data, if any.
const MCSymbol *A = nullptr;
if (Target.getSymA())
A = &Target.getSymA()->getSymbol();
// See <reloc.h>.
const uint32_t FixupOffset = getFixupOffset(Layout, Fragment, Fixup);
unsigned Index = 0;
unsigned Type = RelocType;
const MCSymbol *RelSymbol = nullptr;
if (Target.isAbsolute()) { // constant
// SymbolNum of 0 indicates the absolute section.
//
// FIXME: Currently, these are never generated (see code below). I cannot
// find a case where they are actually emitted.
report_fatal_error("FIXME: relocations to absolute targets "
"not yet implemented");
// the above line stolen from ARM, not sure
} else {
// Resolve constant variables.
if (A->isVariable()) {
int64_t Res;
if (A->getVariableValue()->evaluateAsAbsolute(
Res, Layout, Writer->getSectionAddressMap())) {
FixedValue = Res;
return;
}
}
// Check whether we need an external or internal relocation.
if (Writer->doesSymbolRequireExternRelocation(*A)) {
RelSymbol = A;
// For external relocations, make sure to offset the fixup value to
// compensate for the addend of the symbol address, if it was
// undefined. This occurs with weak definitions, for example.
if (!A->isUndefined())
FixedValue -= Layout.getSymbolOffset(*A);
} else {
// The index is the section ordinal (1-based).
const MCSection &Sec = A->getSection();
Index = Sec.getOrdinal() + 1;
FixedValue += Writer->getSectionAddress(&Sec);
}
if (IsPCRel)
FixedValue -= Writer->getSectionAddress(Fragment->getParent());
}
// struct relocation_info (8 bytes)
MachO::any_relocation_info MRE;
makeRelocationInfo(MRE, FixupOffset, Index, IsPCRel, Log2Size, false, Type);
Writer->addRelocation(RelSymbol, Fragment->getParent(), MRE);
}
MCObjectWriter *llvm::createPPCMachObjectWriter(raw_pwrite_stream &OS,
bool Is64Bit, uint32_t CPUType,
uint32_t CPUSubtype) {
return createMachObjectWriter(
new PPCMachObjectWriter(Is64Bit, CPUType, CPUSubtype), OS,
/*IsLittleEndian=*/false);
}