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