|
| 1 | +//===- Xtensa.cpp ---------------------------------------------------------===// |
| 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 "InputFiles.h" |
| 10 | +#include "Symbols.h" |
| 11 | +#include "Target.h" |
| 12 | +#include <bitset> |
| 13 | +#include <iostream> |
| 14 | +#include <string> |
| 15 | + |
| 16 | +using namespace llvm; |
| 17 | +using namespace llvm::object; |
| 18 | +using namespace llvm::support::endian; |
| 19 | +using namespace llvm::ELF; |
| 20 | +using namespace lld; |
| 21 | +using namespace lld::elf; |
| 22 | + |
| 23 | +namespace { |
| 24 | + |
| 25 | +class Xtensa final : public TargetInfo { |
| 26 | +public: |
| 27 | + Xtensa(); |
| 28 | + RelExpr getRelExpr(RelType type, const Symbol &s, |
| 29 | + const uint8_t *loc) const override; |
| 30 | + void relocate(uint8_t *loc, const Relocation &rel, |
| 31 | + uint64_t val) const override; |
| 32 | +}; |
| 33 | + |
| 34 | +} // namespace |
| 35 | + |
| 36 | +Xtensa::Xtensa() {} |
| 37 | + |
| 38 | +RelExpr Xtensa::getRelExpr(RelType type, const Symbol &s, |
| 39 | + const uint8_t *loc) const { |
| 40 | + switch (type) { |
| 41 | + case R_XTENSA_32: |
| 42 | + return R_ABS; |
| 43 | + case R_XTENSA_SLOT0_OP: |
| 44 | + // This relocation is used for various instructions, with varying ways to |
| 45 | + // calculate the relocation value. This is unlike most ELF architectures, |
| 46 | + // and is arguably bad design (see the comment on R_386_GOT32 in X86.cpp). |
| 47 | + // But that's what compilers emit, so it needs to be supported. |
| 48 | + // |
| 49 | + // We work around this by returning R_PC here and calculating the PC address |
| 50 | + // in Xtensa::relocate based on the relative value. That's ugly. A better |
| 51 | + // solution would be to look at the instruction here and emit various |
| 52 | + // Xtensa-specific RelTypes, but that has another problem: the RelExpr enum |
| 53 | + // is at its maximum size of 64. This will need to be fixed eventually, but |
| 54 | + // for now hack around it and return R_PC. |
| 55 | + return R_PC; |
| 56 | + case R_XTENSA_ASM_EXPAND: |
| 57 | + // This relocation appears to be emitted by the GNU Xtensa compiler as a |
| 58 | + // linker relaxation hint. For example, for the following code: |
| 59 | + // |
| 60 | + // .section .foo |
| 61 | + // .align 4 |
| 62 | + // foo: |
| 63 | + // nop |
| 64 | + // nop |
| 65 | + // call0 bar |
| 66 | + // .align 4 |
| 67 | + // bar: |
| 68 | + // |
| 69 | + // The call0 instruction is compiled to a l32r and callx0 instruction. |
| 70 | + // The LLVM Xtensa backend does not emit this relocation. |
| 71 | + // Because it's a relaxation hint, this relocation can be ignored for now |
| 72 | + // until linker relaxations are implemented. |
| 73 | + return R_NONE; |
| 74 | + case R_XTENSA_PDIFF8: |
| 75 | + case R_XTENSA_PDIFF16: |
| 76 | + case R_XTENSA_PDIFF32: |
| 77 | + case R_XTENSA_NDIFF8: |
| 78 | + case R_XTENSA_NDIFF16: |
| 79 | + case R_XTENSA_NDIFF32: |
| 80 | + // > Xtensa relocations to mark the difference of two local symbols. |
| 81 | + // > These are only needed to support linker relaxation and can be ignored |
| 82 | + // > when not relaxing. |
| 83 | + // Source: |
| 84 | + // https://github.com/espressif/binutils-gdb/commit/30ce8e47fad9b057b6d7af9e1d43061126d34d20: |
| 85 | + // Because we don't do linker relaxation, we can ignore these relocations. |
| 86 | + return R_NONE; |
| 87 | + default: |
| 88 | + error(getErrorLocation(loc) + "unknown relocation (" + Twine(type) + |
| 89 | + ") against symbol " + toString(s)); |
| 90 | + return R_NONE; |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +static inline bool isRRI8Branch(uint8_t *loc) { |
| 95 | + if ((loc[0] & 0x0f) == 0b0111) { |
| 96 | + // instructions: ball, bany, bbc, bbci, bbs, bbsi, beq, bge, bgeu, blt, |
| 97 | + // bltu, bnall, bne, bnone |
| 98 | + return true; |
| 99 | + } |
| 100 | + if ((loc[0] & 0b11'1111) == 0b10'0110) { |
| 101 | + // instructions: beqi, bgei, bnei, blti |
| 102 | + return true; |
| 103 | + } |
| 104 | + if ((loc[0] & 0b1011'1111) == 0b1011'0110) { |
| 105 | + // instructions: bgeui, bltui |
| 106 | + return true; |
| 107 | + } |
| 108 | + // some other instruction |
| 109 | + return false; |
| 110 | +} |
| 111 | + |
| 112 | +void Xtensa::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const { |
| 113 | + switch (rel.type) { |
| 114 | + case R_XTENSA_32: |
| 115 | + write32le(loc, val); |
| 116 | + break; |
| 117 | + case R_XTENSA_SLOT0_OP: { |
| 118 | + // HACK: calculate the instruction location based on the PC-relative |
| 119 | + // relocation value. |
| 120 | + uint64_t dest = rel.sym->getVA(rel.addend); |
| 121 | + uint64_t p = dest - val; |
| 122 | + |
| 123 | + // This relocation is used for various instructions. |
| 124 | + // Look at the instruction to determine how to do the relocation. |
| 125 | + uint8_t opcode = loc[0] & 0x0f; |
| 126 | + if (opcode == 0b0001) { // RI16 format: l32r |
| 127 | + uint64_t val = dest - ((p + 3) & (uint64_t)0xfffffffc); |
| 128 | + checkInt(loc, static_cast<int64_t>(val) >> 2, 16, rel); |
| 129 | + checkAlignment(loc, val, 4, rel); |
| 130 | + write16le(loc + 1, static_cast<int64_t>(val) >> 2); |
| 131 | + } else if (opcode == 0b0101) { // call0, call4, call8, call12 (CALL format) |
| 132 | + uint64_t val = dest - ((p + 4) & (uint64_t)0xfffffffc); |
| 133 | + checkInt(loc, static_cast<int64_t>(val) >> 2, 18, rel); |
| 134 | + checkAlignment(loc, val, 4, rel); |
| 135 | + const int64_t target = static_cast<int64_t>(val) >> 2; |
| 136 | + loc[0] = (loc[0] & 0b0011'1111) | ((target & 0b0000'0011) << 6); |
| 137 | + loc[1] = target >> 2; |
| 138 | + loc[2] = target >> 10; |
| 139 | + } else if ((loc[0] & 0x3f) == 0b00'0110) { // j (CALL format) |
| 140 | + uint64_t val = dest - p + 4; |
| 141 | + checkInt(loc, static_cast<int64_t>(val), 18, rel); |
| 142 | + loc[0] = (loc[0] & 0b0011'1111) | ((val & 0b0000'0011) << 6); |
| 143 | + loc[1] = val >> 2; |
| 144 | + loc[2] = val >> 10; |
| 145 | + } else if (isRRI8Branch(loc)) { // RRI8 format (various branch instructions) |
| 146 | + uint64_t v = val - 4; |
| 147 | + checkInt(loc, static_cast<int64_t>(v), 8, rel); |
| 148 | + loc[2] = v & 0xff; |
| 149 | + } else if ((loc[0] & 0b1000'1111) == 0b1000'1100) { // RI16 format: beqz.n, bnez.n |
| 150 | + uint64_t v = val - 4; |
| 151 | + checkUInt(loc, v, 6, rel); |
| 152 | + loc[0] = (loc[0] & 0xcf) | (v & 0x30); |
| 153 | + loc[1] = (loc[1] & 0x0f) | ((v & 0x0f) << 4); |
| 154 | + } else if ((loc[0] & 0b0011'1111) == 0b0001'0110) { // BRI12 format: beqz, bgez, bltz, bnez |
| 155 | + uint64_t v = val - 4; |
| 156 | + checkInt(loc, static_cast<int64_t>(v), 12, rel); |
| 157 | + loc[1] = ((loc[1] & 0x0f)) | ((v & 0x0f) << 4); |
| 158 | + loc[2] = (v >> 4) & 0xff; |
| 159 | + } else { |
| 160 | + error(getErrorLocation(loc) + |
| 161 | + "unknown opcode for relocation: " + std::to_string(loc[0])); |
| 162 | + } |
| 163 | + break; |
| 164 | + } |
| 165 | + default: |
| 166 | + llvm_unreachable("unknown relocation"); |
| 167 | + } |
| 168 | +} |
| 169 | + |
| 170 | +TargetInfo *elf::getXtensaTargetInfo() { |
| 171 | + static Xtensa target; |
| 172 | + return ⌖ |
| 173 | +} |
0 commit comments