1//===-- RISCVMachObjectWriter.cpp - RISC-V Mach Object Writer -------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "MCTargetDesc/RISCVFixupKinds.h"
10#include "MCTargetDesc/RISCVMCTargetDesc.h"
11#include "RISCVMCAsmInfo.h"
12#include "llvm/ADT/StringExtras.h"
13#include "llvm/ADT/Twine.h"
14#include "llvm/BinaryFormat/MachO.h"
15#include "llvm/MC/MCAsmInfo.h"
16#include "llvm/MC/MCAsmInfoDarwin.h"
17#include "llvm/MC/MCAssembler.h"
18#include "llvm/MC/MCContext.h"
19#include "llvm/MC/MCExpr.h"
20#include "llvm/MC/MCFixup.h"
21#include "llvm/MC/MCMachObjectWriter.h"
22#include "llvm/MC/MCSection.h"
23#include "llvm/MC/MCSectionMachO.h"
24#include "llvm/MC/MCSymbol.h"
25#include "llvm/MC/MCValue.h"
26#include "llvm/Support/Casting.h"
27#include "llvm/Support/MathExtras.h"
28#include <cassert>
29#include <cstdint>
30
31using namespace llvm;
32
33namespace {
34
35class RISCVMachObjectWriter : public MCMachObjectTargetWriter {
36 bool getRISCVFixupKindMachOInfo(const MCFixup &Fixup, unsigned &RelocType,
37 const MCValue Sym, unsigned &Log2Size,
38 const MCAssembler &Asm);
39
40public:
41 RISCVMachObjectWriter(uint32_t CPUType, uint32_t CPUSubtype)
42 : MCMachObjectTargetWriter(false, CPUType, CPUSubtype) {}
43
44 void recordRelocation(MachObjectWriter *Writer, MCAssembler &Asm,
45 const MCFragment *Fragment, const MCFixup &Fixup,
46 MCValue Target, uint64_t &FixedValue) override;
47};
48
49} // end anonymous namespace
50
51bool RISCVMachObjectWriter::getRISCVFixupKindMachOInfo(const MCFixup &Fixup,
52 unsigned &RelocType,
53 const MCValue Sym,
54 unsigned &Log2Size,
55 const MCAssembler &Asm) {
56 RelocType = unsigned(MachO::RISCV_RELOC_UNSIGNED);
57 Log2Size = ~0U;
58
59 if (Sym.getSpecifier() == RISCV::S_GOT_HI) {
60 Log2Size = Log2_32(Value: 4);
61 RelocType = unsigned(MachO::RISCV_RELOC_GOT_HI20);
62 return true;
63 }
64
65 switch (Fixup.getKind()) {
66 default:
67 return false;
68
69 case FK_Data_1:
70 Log2Size = Log2_32(Value: 1);
71 return true;
72 case FK_Data_2:
73 Log2Size = Log2_32(Value: 2);
74 return true;
75 case FK_Data_4:
76 Log2Size = Log2_32(Value: 4);
77 return true;
78 case FK_Data_8:
79 Log2Size = Log2_32(Value: 8);
80 return true;
81 case RISCV::fixup_riscv_pcrel_lo12_i:
82 case RISCV::fixup_riscv_pcrel_lo12_s:
83 llvm_unreachable("lo12 fixups should have been resolved elsewhere");
84 case RISCV::fixup_riscv_lo12_i:
85 case RISCV::fixup_riscv_lo12_s:
86 Log2Size = Log2_32(Value: 4);
87 RelocType = MachO::RISCV_RELOC_LO12;
88 return true;
89 case RISCV::fixup_riscv_pcrel_hi20:
90 Log2Size = Log2_32(Value: 4);
91 if (Sym.getSpecifier() != RISCV::S_PCREL_HI) {
92 Asm.getContext().reportError(L: Fixup.getLoc(),
93 Msg: "unknown AUIPC relocation kind");
94 return false;
95 }
96 RelocType = unsigned(MachO::RISCV_RELOC_HI20);
97 return true;
98 case RISCV::fixup_riscv_hi20:
99 Log2Size = Log2_32(Value: 4);
100 RelocType = unsigned(MachO::RISCV_RELOC_HI20);
101 return true;
102 case RISCV::fixup_riscv_call:
103 case RISCV::fixup_riscv_call_plt:
104 case RISCV::fixup_riscv_jal:
105 Log2Size = Log2_32(Value: 4);
106 RelocType = unsigned(MachO::RISCV_RELOC_BRANCH21);
107 return true;
108 }
109}
110
111static bool canUseLocalRelocation(const MCSectionMachO &Section,
112 const MCSymbol &Symbol, unsigned Log2Size) {
113 // Debug info sections can use local relocations.
114 if (Section.hasAttribute(Value: MachO::S_ATTR_DEBUG))
115 return true;
116
117 // Otherwise, only pointer sized relocations are supported.
118 if (Log2Size != 2)
119 return false;
120
121 // But only if they don't point to a few forbidden sections.
122 if (!Symbol.isInSection())
123 return true;
124 const MCSectionMachO &RefSec =
125 static_cast<const MCSectionMachO &>(Symbol.getSection());
126 if (RefSec.getType() == MachO::S_CSTRING_LITERALS)
127 return false;
128
129 if (RefSec.getSegmentName() == "__DATA" &&
130 RefSec.getName() == "__objc_classrefs")
131 return false;
132
133 return true;
134}
135
136static void emitRelocation(MachObjectWriter *Writer, const MCFragment *Fragment,
137 uint32_t FixupOffset, const MCSymbol *RelSymbol,
138 unsigned Index, bool IsPCRel, unsigned Log2Size,
139 unsigned Type) {
140
141 assert(isUInt<2>(Log2Size) && "Invalid Log2Size");
142 assert(isUInt<4>(Type) && "Invalid type");
143 assert(isUInt<24>(Index) && "Invalid Index");
144 MachO::any_relocation_info MRE;
145 MRE.r_word0 = FixupOffset;
146 MRE.r_word1 =
147 (Index << 0) | (IsPCRel << 24) | (Log2Size << 25) | (Type << 28);
148 Writer->addRelocation(RelSymbol, Sec: Fragment->getParent(), MRE);
149}
150
151static bool checkSymbolBase(const MCSymbol *Base, const MCSymbol *Symbol,
152 const MCFixup &Fixup, MCAssembler &Asm) {
153 if (!Base) {
154 Asm.getContext().reportError(
155 L: Fixup.getLoc(),
156 Msg: "unsupported relocation of local symbol '" + Symbol->getName() +
157 "'. Must have non-local symbol earlier in section.");
158 return false;
159 }
160 return true;
161}
162
163template <unsigned Bits>
164static bool isValidInt(const uint64_t &FixedValue, const char *Msg,
165 MCAssembler &Asm, const SMLoc Loc) {
166 const bool IsValid = isInt<Bits>(FixedValue);
167 if (!IsValid)
168 Asm.getContext().reportError(L: Loc, Msg);
169 return IsValid;
170}
171
172extern const MCFixup *getPCRelHiFixup(const MCSpecifierExpr &Expr,
173 const MCFragment **DFOut);
174
175void RISCVMachObjectWriter::recordRelocation(
176 MachObjectWriter *Writer, MCAssembler &Asm, const MCFragment *Fragment,
177 const MCFixup &Fixup, MCValue Target, uint64_t &FixedValue) {
178 const bool IsPCRel =
179 Fixup.isPCRel() || Target.getSpecifier() == RISCV::S_GOT_HI;
180
181 // See <reloc.h>.
182 uint32_t FixupOffset = Asm.getFragmentOffset(F: *Fragment);
183 unsigned Log2Size = 0;
184 int64_t Value = 0;
185 unsigned Index = 0;
186 unsigned Type = 0;
187 const unsigned Kind = Fixup.getKind();
188 const MCSymbol *RelSymbol = nullptr;
189 bool RequireExtraAddend = false;
190 uint64_t ExtraAddendValue = 0;
191
192 FixupOffset += Fixup.getOffset();
193
194 // RISC-V pcrel relocation addends do not include the section offset.
195 if (IsPCRel)
196 FixedValue += FixupOffset;
197
198 // AUIPC fixups use relocations for the whole symbol value and only
199 // put the addend in the instruction itself. Clear out any value the
200 // generic code figured out from the symbol definition.
201 if (Kind == RISCV::fixup_riscv_pcrel_hi20)
202 FixedValue = 0;
203
204 // %pcrel_lo relocations directly target the same symbol as the
205 // corresponding AUIPC, and encode (inline) an offset to that AUIPC
206 // in the immediate field of the instruction itself.
207 if (Kind == RISCV::fixup_riscv_pcrel_lo12_i ||
208 Kind == RISCV::fixup_riscv_pcrel_lo12_s) {
209 const MCFragment *AUIPCDF;
210 const MCFixup *AUIPCFixup =
211 getPCRelHiFixup(Expr: cast<MCSpecifierExpr>(Val: *Fixup.getValue()), DFOut: &AUIPCDF);
212 assert(AUIPCFixup);
213
214 // Calculate the offset from this fixup to the AUIPC it references, this
215 // will be put into the instruction itself.
216 FixedValue =
217 Asm.getFragmentOffset(F: *AUIPCDF) + AUIPCFixup->getOffset() - FixupOffset;
218 if (!isInt<12>(x: FixedValue)) {
219 RequireExtraAddend = true;
220 ExtraAddendValue = FixedValue;
221 if (!isValidInt<24>(
222 FixedValue: ExtraAddendValue,
223 Msg: "AUIPC out of range of corresponding %pcrel_lo instruction", Asm,
224 Loc: Fixup.getLoc()))
225 return;
226 }
227
228 // Retarget the rest of this function to reference the AUIPC's symbol.
229 MCValue RealTarget;
230 if (!AUIPCFixup->getValue()->evaluateAsValue(Res&: RealTarget, Asm)) {
231 Asm.getContext().reportError(L: AUIPCFixup->getLoc(),
232 Msg: "cannot understand AUIPC target");
233 return;
234 }
235 if (RealTarget.getSubSym()) {
236 Asm.getContext().reportError(L: AUIPCFixup->getLoc(),
237 Msg: "AUIPC target with symbol difference");
238 return;
239 }
240
241 Target = MCValue::get(SymA: RealTarget.getAddSym(), /*SymB*/ nullptr,
242 Val: RealTarget.getConstant(), Specifier: RISCV::S_PCREL_LO);
243
244 Log2Size = Log2_32(Value: 4);
245 const auto Spec = RealTarget.getSpecifier();
246 Type = Spec == RISCV::S_GOT_HI ? MachO::RISCV_RELOC_GOT_LO12
247 : MachO::RISCV_RELOC_LO12;
248 } else if (!getRISCVFixupKindMachOInfo(Fixup, RelocType&: Type, Sym: Target, Log2Size, Asm)) {
249 Asm.getContext().reportError(L: Fixup.getLoc(), Msg: "unknown RISC-V fixup kind");
250 return;
251 }
252
253 // imm19 relocations are for conditional branches, which require
254 // assembler local symbols. If we got here, that's not what we have,
255 // so report an error.
256 if (Kind == RISCV::fixup_riscv_branch ||
257 Kind == RISCV::fixup_riscv_rvc_jump ||
258 Kind == RISCV::fixup_riscv_rvc_branch) {
259 Asm.getContext().reportError(
260 L: Fixup.getLoc(), Msg: "conditional branch requires assembler-local"
261 " label. '" +
262 Target.getAddSym()->getName() + "' is external.");
263 return;
264 }
265
266 Value = Target.getConstant();
267
268 // Only .word and %pcrel_lo instructions inline the pc-relative
269 // offset in the instruction, but only if such offset fits the
270 // 12-bit immediate of an addi or an lw instrucion.
271 if ((Type != MachO::RISCV_RELOC_UNSIGNED && Type != MachO::RISCV_RELOC_LO12 &&
272 Type != MachO::RISCV_RELOC_GOT_LO12) ||
273 RequireExtraAddend)
274 FixedValue = 0;
275
276 // Emit relocations.
277
278 // Constants.
279 if (Target.isAbsolute()) {
280 // FIXME: Should this always be extern?
281 // SymbolNum of 0 indicates the absolute section.
282 if (IsPCRel) {
283 Asm.getContext().reportError(L: Fixup.getLoc(),
284 Msg: "PC relative absolute relocation!");
285 return;
286 }
287 emitRelocation(Writer, Fragment, FixupOffset, RelSymbol, Index,
288 /*IsPCRel*/ false, /*Log2Size*/ ~0U,
289 /*Type*/ MachO::RISCV_RELOC_UNSIGNED);
290 return;
291 }
292
293 // A - B + constant
294 if (const MCSymbol *B = Target.getSubSym()) {
295 const MCSymbol *A = Target.getAddSym();
296 const MCSymbol *A_Base = Writer->getAtom(S: *A);
297 const MCSymbol *B_Base = Writer->getAtom(S: *B);
298
299 // We don't support PCrel relocations of differences.
300 if (IsPCRel) {
301 Asm.getContext().reportError(L: Fixup.getLoc(),
302 Msg: "unsupported pc-relative relocation of "
303 "difference");
304 return;
305 }
306
307 // Ensure both symbols have base atoms for external relocations.
308 if (!checkSymbolBase(Base: A_Base, Symbol: A, Fixup, Asm) ||
309 !checkSymbolBase(Base: B_Base, Symbol: B, Fixup, Asm))
310 return;
311
312 if (A_Base && A_Base == B_Base) {
313 Asm.getContext().reportError(
314 L: Fixup.getLoc(), Msg: "unsupported relocation with identical base");
315 return;
316 }
317
318 Value +=
319 (!A->getFragment() ? 0 : Writer->getSymbolAddress(S: *A)) -
320 (!A_Base || !A_Base->getFragment() ? 0
321 : Writer->getSymbolAddress(S: *A_Base));
322 Value -=
323 (!B->getFragment() ? 0 : Writer->getSymbolAddress(S: *B)) -
324 (!B_Base || !B_Base->getFragment() ? 0
325 : Writer->getSymbolAddress(S: *B_Base));
326
327 // If there's any addend left to handle, inline it in the instruction's
328 // immediate.
329 FixedValue = Value;
330 if (!isValidInt<12>(
331 FixedValue,
332 Msg: "AUIPC out of range of corresponding %pcrel_lo instruction", Asm,
333 Loc: Fixup.getLoc()))
334 return;
335
336 emitRelocation(Writer, Fragment, FixupOffset, /*RelSymbol*/ A_Base, Index,
337 IsPCRel, Log2Size, /*Type*/ MachO::RISCV_RELOC_UNSIGNED);
338 // struct relocation_info (8 bytes)
339 emitRelocation(Writer, Fragment, FixupOffset, /*RelSymbol*/ B_Base, Index,
340 IsPCRel, Log2Size, /*Type*/ MachO::RISCV_RELOC_SUBTRACTOR);
341 return;
342 }
343
344 // A + constant
345 if (const MCSymbol *Symbol = Target.getAddSym()) {
346 assert(!Target.getSubSym() && "invalid expression");
347 const MCSectionMachO &Section =
348 static_cast<const MCSectionMachO &>(*Fragment->getParent());
349
350 const bool CanUseLocalRelocation =
351 canUseLocalRelocation(Section, Symbol: *Symbol, Log2Size);
352 if (Symbol->isTemporary() && (Value || !CanUseLocalRelocation)) {
353 if (!Symbol->isInSection()) {
354 checkSymbolBase(Base: nullptr, Symbol, Fixup, Asm);
355 return;
356 }
357 const MCSection &Sec = Symbol->getSection();
358 if (!MCAsmInfoDarwin::isSectionAtomizableBySymbols(Section: Sec))
359 Symbol->setUsedInReloc();
360 }
361
362 const MCSymbol *Base = Writer->getAtom(S: *Symbol);
363 // If the symbol is a variable it can either be in a section and
364 // we have a base or it is absolute and should have been expanded.
365 assert(!Symbol->isVariable() || Base);
366
367 // Relocations inside debug sections always use local relocations when
368 // possible. This seems to be done because the debugger doesn't fully
369 // understand relocation entries and expects to find values that
370 // have already been fixed up.
371 if (Symbol->isInSection()) {
372 if (Section.hasAttribute(Value: MachO::S_ATTR_DEBUG))
373 Base = nullptr;
374 }
375
376 // RISC-V uses external relocations as much as possible. For debug
377 // sections, and for pointer-sized relocations (.quad), we allow section
378 // relocations. It's code sections that run into trouble.
379 if (Base) {
380 RelSymbol = Base;
381
382 // Add the local offset, if needed.
383 if (Base != Symbol)
384 Value += Asm.getSymbolOffset(S: *Symbol) - Asm.getSymbolOffset(S: *Base);
385 } else if (Symbol->isInSection()) {
386 if (!CanUseLocalRelocation) {
387 checkSymbolBase(Base: nullptr, Symbol, Fixup, Asm);
388 return;
389 }
390 // Adjust the relocation to be section-relative.
391 // The index is the section ordinal (1-based).
392 const MCSection &Sec = Symbol->getSection();
393 Index = Sec.getOrdinal() + 1;
394 Value += Writer->getSymbolAddress(S: *Symbol);
395
396 if (IsPCRel)
397 Value -= Writer->getFragmentAddress(Asm, Fragment) + Fixup.getOffset();
398 } else {
399 llvm_unreachable(
400 "This constant variable should have been expanded during evaluation");
401 }
402 if (Type == MachO::RISCV_RELOC_UNSIGNED) {
403 // If there's any addend left to handle, encode it in the instruction.
404 FixedValue = Value;
405 // struct relocation_info (8 bytes)
406 emitRelocation(Writer, Fragment, FixupOffset, RelSymbol, Index,
407 /*IsPCRel*/ false, Log2Size,
408 /*Type*/ MachO::RISCV_RELOC_UNSIGNED);
409 return;
410 }
411 // We have an addend offset that is encoded in the relocation
412 // record, not inlined in the instruction.
413 if (Value) {
414 if (!isValidInt<24>(FixedValue: Value, Msg: "addend too big for relocation", Asm,
415 Loc: Fixup.getLoc()))
416 return;
417
418 emitRelocation(Writer, Fragment, FixupOffset, RelSymbol, Index, IsPCRel,
419 Log2Size, Type);
420 // Now set up the Addend relocation.
421 emitRelocation(Writer, Fragment, FixupOffset, /*RelSymbol*/ nullptr,
422 Index: Value & 0xffffff, /*IsPCRel*/ false, /*Log2Size*/ 2,
423 /*Type*/ MachO::RISCV_RELOC_ADDEND);
424 return;
425 }
426
427 emitRelocation(Writer, Fragment, FixupOffset, RelSymbol, Index, IsPCRel,
428 Log2Size, Type);
429 }
430
431 if (RequireExtraAddend) {
432 emitRelocation(Writer, Fragment, FixupOffset, /*RelSymbol*/ nullptr,
433 Index: ExtraAddendValue & 0xffffff, /*IsPCRel*/ false,
434 /*Log2Size*/ 2, /*Type*/ MachO::RISCV_RELOC_ADDEND);
435 }
436}
437
438std::unique_ptr<MCObjectTargetWriter>
439llvm::createRISCVMachObjectWriter(uint32_t CPUType, uint32_t CPUSubtype) {
440 return std::make_unique<RISCVMachObjectWriter>(args&: CPUType, args&: CPUSubtype);
441}
442