| //===-- RISCVMachObjectWriter.cpp - RISC-V Mach Object Writer -------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "MCTargetDesc/RISCVFixupKinds.h" |
| #include "MCTargetDesc/RISCVMCTargetDesc.h" |
| #include "RISCVMCAsmInfo.h" |
| #include "llvm/ADT/Twine.h" |
| #include "llvm/BinaryFormat/MachO.h" |
| #include "llvm/MC/MCAsmInfo.h" |
| #include "llvm/MC/MCAsmInfoDarwin.h" |
| #include "llvm/MC/MCAssembler.h" |
| #include "llvm/MC/MCContext.h" |
| #include "llvm/MC/MCExpr.h" |
| #include "llvm/MC/MCFixup.h" |
| #include "llvm/MC/MCMachObjectWriter.h" |
| #include "llvm/MC/MCSection.h" |
| #include "llvm/MC/MCSectionMachO.h" |
| #include "llvm/MC/MCSymbol.h" |
| #include "llvm/MC/MCValue.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/MathExtras.h" |
| #include <cassert> |
| #include <cstdint> |
| |
| using namespace llvm; |
| |
| namespace { |
| |
| class RISCVMachObjectWriter : public MCMachObjectTargetWriter { |
| bool getRISCVFixupKindMachOInfo(const MCFixup &Fixup, unsigned &RelocType, |
| const MCValue Sym, unsigned &Log2Size, |
| const MCAssembler &Asm); |
| |
| public: |
| RISCVMachObjectWriter(uint32_t CPUType, uint32_t CPUSubtype) |
| : MCMachObjectTargetWriter(false, CPUType, CPUSubtype) {} |
| |
| void recordRelocation(MachObjectWriter *Writer, MCAssembler &Asm, |
| const MCFragment *Fragment, const MCFixup &Fixup, |
| MCValue Target, uint64_t &FixedValue) override; |
| }; |
| |
| } // end anonymous namespace |
| |
| bool RISCVMachObjectWriter::getRISCVFixupKindMachOInfo(const MCFixup &Fixup, |
| unsigned &RelocType, |
| const MCValue Sym, |
| unsigned &Log2Size, |
| const MCAssembler &Asm) { |
| RelocType = unsigned(MachO::RISCV_RELOC_UNSIGNED); |
| Log2Size = ~0U; |
| |
| if (Sym.getSpecifier() == RISCV::S_GOT_HI) { |
| Log2Size = Log2_32(4); |
| RelocType = unsigned(MachO::RISCV_RELOC_GOT_HI20); |
| return true; |
| } |
| |
| switch (Fixup.getKind()) { |
| default: |
| return false; |
| |
| case FK_Data_1: |
| Log2Size = Log2_32(1); |
| return true; |
| case FK_Data_2: |
| Log2Size = Log2_32(2); |
| return true; |
| case FK_Data_4: |
| Log2Size = Log2_32(4); |
| return true; |
| case FK_Data_8: |
| Log2Size = Log2_32(8); |
| return true; |
| case RISCV::fixup_riscv_pcrel_lo12_i: |
| case RISCV::fixup_riscv_pcrel_lo12_s: |
| llvm_unreachable("lo12 fixups should have been resolved elsewhere"); |
| case RISCV::fixup_riscv_lo12_i: |
| case RISCV::fixup_riscv_lo12_s: |
| Log2Size = Log2_32(4); |
| RelocType = MachO::RISCV_RELOC_LO12; |
| return true; |
| case RISCV::fixup_riscv_pcrel_hi20: |
| Log2Size = Log2_32(4); |
| if (Sym.getSpecifier() != RISCV::S_PCREL_HI) { |
| Asm.getContext().reportError(Fixup.getLoc(), |
| "unknown AUIPC relocation kind"); |
| return false; |
| } |
| RelocType = unsigned(MachO::RISCV_RELOC_HI20); |
| return true; |
| case RISCV::fixup_riscv_hi20: |
| Log2Size = Log2_32(4); |
| RelocType = unsigned(MachO::RISCV_RELOC_HI20); |
| return true; |
| case RISCV::fixup_riscv_call: |
| case RISCV::fixup_riscv_call_plt: |
| case RISCV::fixup_riscv_jal: |
| Log2Size = Log2_32(4); |
| RelocType = unsigned(MachO::RISCV_RELOC_BRANCH21); |
| return true; |
| } |
| } |
| |
| static bool canUseLocalRelocation(const MCSectionMachO &Section, |
| const MCSymbol &Symbol, unsigned Log2Size) { |
| // Debug info sections can use local relocations. |
| if (Section.hasAttribute(MachO::S_ATTR_DEBUG)) |
| return true; |
| |
| // Otherwise, only pointer sized relocations are supported. |
| if (Log2Size != 2) |
| return false; |
| |
| // But only if they don't point to a few forbidden sections. |
| if (!Symbol.isInSection()) |
| return true; |
| const MCSectionMachO &RefSec = |
| static_cast<const MCSectionMachO &>(Symbol.getSection()); |
| if (RefSec.getType() == MachO::S_CSTRING_LITERALS) |
| return false; |
| |
| if (RefSec.getSegmentName() == "__DATA" && |
| RefSec.getName() == "__objc_classrefs") |
| return false; |
| |
| return true; |
| } |
| |
| static void emitRelocation(MachObjectWriter *Writer, const MCFragment *Fragment, |
| uint32_t FixupOffset, const MCSymbol *RelSymbol, |
| unsigned Index, bool IsPCRel, unsigned Log2Size, |
| unsigned Type) { |
| |
| assert(isUInt<2>(Log2Size) && "Invalid Log2Size"); |
| assert(isUInt<4>(Type) && "Invalid type"); |
| assert(isUInt<24>(Index) && "Invalid Index"); |
| MachO::any_relocation_info MRE; |
| MRE.r_word0 = FixupOffset; |
| MRE.r_word1 = |
| (Index << 0) | (IsPCRel << 24) | (Log2Size << 25) | (Type << 28); |
| Writer->addRelocation(RelSymbol, Fragment->getParent(), MRE); |
| } |
| |
| static bool checkSymbolBase(const MCSymbol *Base, const MCSymbol *Symbol, |
| const MCFixup &Fixup, MCAssembler &Asm) { |
| if (!Base) { |
| Asm.getContext().reportError( |
| Fixup.getLoc(), |
| "unsupported relocation of local symbol '" + Symbol->getName() + |
| "'. Must have non-local symbol earlier in section."); |
| return false; |
| } |
| return true; |
| } |
| |
| template <unsigned Bits> |
| static bool isValidInt(const uint64_t &FixedValue, const char *Msg, |
| MCAssembler &Asm, const SMLoc Loc) { |
| const bool IsValid = isInt<Bits>(FixedValue); |
| if (!IsValid) |
| Asm.getContext().reportError(Loc, Msg); |
| return IsValid; |
| } |
| |
| extern const MCFixup *getPCRelHiFixup(const MCSpecifierExpr &Expr, |
| const MCFragment **DFOut); |
| |
| void RISCVMachObjectWriter::recordRelocation( |
| MachObjectWriter *Writer, MCAssembler &Asm, const MCFragment *Fragment, |
| const MCFixup &Fixup, MCValue Target, uint64_t &FixedValue) { |
| const bool IsPCRel = |
| Fixup.isPCRel() || Target.getSpecifier() == RISCV::S_GOT_HI; |
| |
| // See <reloc.h>. |
| uint32_t FixupOffset = Asm.getFragmentOffset(*Fragment); |
| unsigned Log2Size = 0; |
| int64_t Value = 0; |
| unsigned Index = 0; |
| unsigned Type = 0; |
| const unsigned Kind = Fixup.getKind(); |
| const MCSymbol *RelSymbol = nullptr; |
| bool RequireExtraAddend = false; |
| uint64_t ExtraAddendValue = 0; |
| |
| FixupOffset += Fixup.getOffset(); |
| |
| // RISC-V pcrel relocation addends do not include the section offset. |
| if (IsPCRel) |
| FixedValue += FixupOffset; |
| |
| // AUIPC fixups use relocations for the whole symbol value and only |
| // put the addend in the instruction itself. Clear out any value the |
| // generic code figured out from the symbol definition. |
| if (Kind == RISCV::fixup_riscv_pcrel_hi20) |
| FixedValue = 0; |
| |
| // %pcrel_lo relocations directly target the same symbol as the |
| // corresponding AUIPC, and encode (inline) an offset to that AUIPC |
| // in the immediate field of the instruction itself. |
| if (Kind == RISCV::fixup_riscv_pcrel_lo12_i || |
| Kind == RISCV::fixup_riscv_pcrel_lo12_s) { |
| const MCFragment *AUIPCDF; |
| const MCFixup *AUIPCFixup = |
| getPCRelHiFixup(cast<MCSpecifierExpr>(*Fixup.getValue()), &AUIPCDF); |
| assert(AUIPCFixup); |
| |
| // Calculate the offset from this fixup to the AUIPC it references, this |
| // will be put into the instruction itself. |
| FixedValue = |
| Asm.getFragmentOffset(*AUIPCDF) + AUIPCFixup->getOffset() - FixupOffset; |
| if (!isInt<12>(FixedValue)) { |
| RequireExtraAddend = true; |
| ExtraAddendValue = FixedValue; |
| if (!isValidInt<24>( |
| ExtraAddendValue, |
| "AUIPC out of range of corresponding %pcrel_lo instruction", Asm, |
| Fixup.getLoc())) |
| return; |
| } |
| |
| // Retarget the rest of this function to reference the AUIPC's symbol. |
| MCValue RealTarget; |
| if (!AUIPCFixup->getValue()->evaluateAsValue(RealTarget, Asm)) { |
| Asm.getContext().reportError(AUIPCFixup->getLoc(), |
| "cannot understand AUIPC target"); |
| return; |
| } |
| if (RealTarget.getSubSym()) { |
| Asm.getContext().reportError(AUIPCFixup->getLoc(), |
| "AUIPC target with symbol difference"); |
| return; |
| } |
| |
| Target = MCValue::get(RealTarget.getAddSym(), /*SymB*/ nullptr, |
| RealTarget.getConstant(), RISCV::S_PCREL_LO); |
| |
| Log2Size = Log2_32(4); |
| const auto Spec = RealTarget.getSpecifier(); |
| Type = Spec == RISCV::S_GOT_HI ? MachO::RISCV_RELOC_GOT_LO12 |
| : MachO::RISCV_RELOC_LO12; |
| } else if (!getRISCVFixupKindMachOInfo(Fixup, Type, Target, Log2Size, Asm)) { |
| Asm.getContext().reportError(Fixup.getLoc(), "unknown RISC-V fixup kind"); |
| return; |
| } |
| |
| // imm19 relocations are for conditional branches, which require |
| // assembler local symbols. If we got here, that's not what we have, |
| // so report an error. |
| if (Kind == RISCV::fixup_riscv_branch || |
| Kind == RISCV::fixup_riscv_rvc_jump || |
| Kind == RISCV::fixup_riscv_rvc_branch) { |
| Asm.getContext().reportError( |
| Fixup.getLoc(), "conditional branch requires assembler-local" |
| " label. '" + |
| Target.getAddSym()->getName() + "' is external."); |
| return; |
| } |
| |
| Value = Target.getConstant(); |
| |
| // Only .word and %pcrel_lo instructions inline the pc-relative |
| // offset in the instruction, but only if such offset fits the |
| // 12-bit immediate of an addi or an lw instrucion. |
| if ((Type != MachO::RISCV_RELOC_UNSIGNED && Type != MachO::RISCV_RELOC_LO12 && |
| Type != MachO::RISCV_RELOC_GOT_LO12) || |
| RequireExtraAddend) |
| FixedValue = 0; |
| |
| // Emit relocations. |
| |
| // Constants. |
| if (Target.isAbsolute()) { |
| // FIXME: Should this always be extern? |
| // SymbolNum of 0 indicates the absolute section. |
| if (IsPCRel) { |
| Asm.getContext().reportError(Fixup.getLoc(), |
| "PC relative absolute relocation!"); |
| return; |
| } |
| emitRelocation(Writer, Fragment, FixupOffset, RelSymbol, Index, |
| /*IsPCRel*/ false, /*Log2Size*/ ~0U, |
| /*Type*/ MachO::RISCV_RELOC_UNSIGNED); |
| return; |
| } |
| |
| // A - B + constant |
| if (const MCSymbol *B = Target.getSubSym()) { |
| const MCSymbol *A = Target.getAddSym(); |
| const MCSymbol *A_Base = Writer->getAtom(*A); |
| const MCSymbol *B_Base = Writer->getAtom(*B); |
| |
| // We don't support PCrel relocations of differences. |
| if (IsPCRel) { |
| Asm.getContext().reportError(Fixup.getLoc(), |
| "unsupported pc-relative relocation of " |
| "difference"); |
| return; |
| } |
| |
| // Ensure both symbols have base atoms for external relocations. |
| if (!checkSymbolBase(A_Base, A, Fixup, Asm) || |
| !checkSymbolBase(B_Base, B, Fixup, Asm)) |
| return; |
| |
| if (A_Base && A_Base == B_Base) { |
| Asm.getContext().reportError( |
| Fixup.getLoc(), "unsupported relocation with identical base"); |
| return; |
| } |
| |
| Value += |
| (!A->getFragment() ? 0 : Writer->getSymbolAddress(*A)) - |
| (!A_Base || !A_Base->getFragment() ? 0 |
| : Writer->getSymbolAddress(*A_Base)); |
| Value -= |
| (!B->getFragment() ? 0 : Writer->getSymbolAddress(*B)) - |
| (!B_Base || !B_Base->getFragment() ? 0 |
| : Writer->getSymbolAddress(*B_Base)); |
| |
| // If there's any addend left to handle, inline it in the data being |
| // relocated. Unlike the 12-bit immediate of an addi or an lw, the data |
| // is wide enough to store the whole addend. |
| FixedValue = Value; |
| |
| emitRelocation(Writer, Fragment, FixupOffset, /*RelSymbol*/ A_Base, Index, |
| IsPCRel, Log2Size, /*Type*/ MachO::RISCV_RELOC_UNSIGNED); |
| // struct relocation_info (8 bytes) |
| emitRelocation(Writer, Fragment, FixupOffset, /*RelSymbol*/ B_Base, Index, |
| IsPCRel, Log2Size, /*Type*/ MachO::RISCV_RELOC_SUBTRACTOR); |
| return; |
| } |
| |
| // A + constant |
| if (const MCSymbol *Symbol = Target.getAddSym()) { |
| assert(!Target.getSubSym() && "invalid expression"); |
| const MCSectionMachO &Section = |
| static_cast<const MCSectionMachO &>(*Fragment->getParent()); |
| |
| const bool CanUseLocalRelocation = |
| canUseLocalRelocation(Section, *Symbol, Log2Size); |
| if (Symbol->isTemporary() && (Value || !CanUseLocalRelocation)) { |
| if (!Symbol->isInSection()) { |
| checkSymbolBase(nullptr, Symbol, Fixup, Asm); |
| return; |
| } |
| const MCSection &Sec = Symbol->getSection(); |
| if (!MCAsmInfoDarwin::isSectionAtomizableBySymbols(Sec)) |
| Symbol->setUsedInReloc(); |
| } |
| |
| const MCSymbol *Base = Writer->getAtom(*Symbol); |
| // If the symbol is a variable it can either be in a section and |
| // we have a base or it is absolute and should have been expanded. |
| assert(!Symbol->isVariable() || Base); |
| |
| // Relocations inside debug sections always use local relocations when |
| // possible. This seems to be done because the debugger doesn't fully |
| // understand relocation entries and expects to find values that |
| // have already been fixed up. |
| if (Symbol->isInSection()) { |
| if (Section.hasAttribute(MachO::S_ATTR_DEBUG)) |
| Base = nullptr; |
| } |
| |
| // RISC-V uses external relocations as much as possible. For debug |
| // sections, and for pointer-sized relocations (.quad), we allow section |
| // relocations. It's code sections that run into trouble. |
| if (Base) { |
| RelSymbol = Base; |
| |
| // Add the local offset, if needed. |
| if (Base != Symbol) |
| Value += Asm.getSymbolOffset(*Symbol) - Asm.getSymbolOffset(*Base); |
| } else if (Symbol->isInSection()) { |
| if (!CanUseLocalRelocation) { |
| checkSymbolBase(nullptr, Symbol, Fixup, Asm); |
| return; |
| } |
| // Adjust the relocation to be section-relative. |
| // The index is the section ordinal (1-based). |
| const MCSection &Sec = Symbol->getSection(); |
| Index = Sec.getOrdinal() + 1; |
| Value += Writer->getSymbolAddress(*Symbol); |
| |
| if (IsPCRel) |
| Value -= Writer->getFragmentAddress(Asm, Fragment) + Fixup.getOffset(); |
| } else { |
| llvm_unreachable( |
| "This constant variable should have been expanded during evaluation"); |
| } |
| if (Type == MachO::RISCV_RELOC_UNSIGNED) { |
| // If there's any addend left to handle, encode it in the instruction. |
| FixedValue = Value; |
| // struct relocation_info (8 bytes) |
| emitRelocation(Writer, Fragment, FixupOffset, RelSymbol, Index, |
| /*IsPCRel*/ false, Log2Size, |
| /*Type*/ MachO::RISCV_RELOC_UNSIGNED); |
| return; |
| } |
| // We have an addend offset that is encoded in the relocation |
| // record, not inlined in the instruction. |
| if (Value) { |
| if (!isValidInt<24>(Value, "addend too big for relocation", Asm, |
| Fixup.getLoc())) |
| return; |
| |
| emitRelocation(Writer, Fragment, FixupOffset, RelSymbol, Index, IsPCRel, |
| Log2Size, Type); |
| // Now set up the Addend relocation. |
| emitRelocation(Writer, Fragment, FixupOffset, /*RelSymbol*/ nullptr, |
| Value & 0xffffff, /*IsPCRel*/ false, /*Log2Size*/ 2, |
| /*Type*/ MachO::RISCV_RELOC_ADDEND); |
| return; |
| } |
| |
| emitRelocation(Writer, Fragment, FixupOffset, RelSymbol, Index, IsPCRel, |
| Log2Size, Type); |
| } |
| |
| if (RequireExtraAddend) { |
| emitRelocation(Writer, Fragment, FixupOffset, /*RelSymbol*/ nullptr, |
| ExtraAddendValue & 0xffffff, /*IsPCRel*/ false, |
| /*Log2Size*/ 2, /*Type*/ MachO::RISCV_RELOC_ADDEND); |
| } |
| } |
| |
| std::unique_ptr<MCObjectTargetWriter> |
| llvm::createRISCVMachObjectWriter(uint32_t CPUType, uint32_t CPUSubtype) { |
| return std::make_unique<RISCVMachObjectWriter>(CPUType, CPUSubtype); |
| } |