diff options
author | Stefan GrƤnitz <stefan.graenitz@gmail.com> | 2023-03-23 11:10:39 +0100 |
---|---|---|
committer | Stefan GrƤnitz <stefan.graenitz@gmail.com> | 2023-03-23 11:25:45 +0100 |
commit | c2de8ff92753acdb1ace7a27cc11cb09f28eb8fa (patch) | |
tree | 4c3af5ce764ff1a16f97d6cb17915b1d343759c6 /llvm/lib/ExecutionEngine | |
parent | d25751779baa37356265b004edc7e55ee4a4c383 (diff) | |
download | llvm-c2de8ff92753acdb1ace7a27cc11cb09f28eb8fa.zip llvm-c2de8ff92753acdb1ace7a27cc11cb09f28eb8fa.tar.gz llvm-c2de8ff92753acdb1ace7a27cc11cb09f28eb8fa.tar.bz2 |
[JITLink] Initial AArch32 backend
This first version lays the foundations for AArch32 support in JITLink. ELFLinkGraphBuilder_aarch32 processes REL-type relocations and populates LinkGraphs from ELF object files for both big- and little-endian systems. The ArmCfg member controls subarchitecture-specific details throughout the linking process (i.e. it's passed to ELFJITLinker_aarch32).
Relocation types follow the ABI documentation's division into classes: Data (endian-sensitive), Arm (32-bit little-endian) and Thumb (2x 16-bit little-endian, "Thumb32" in the docs). The implementation of instruction encoding/decoding for relocation resolution is implemented symmetrically and is testable in isolation (see AArch32 category in JITLinkTests).
Callable Thumb functions are marked with a ThumbSymbol target-flag and stored in the LinkGraph with their real addresses. The thumb-bit is added back in when the owning JITDylib requests the address for such a symbol.
The StubsManager can generate (absolute) Thumb-state stubs for branch range extensions on v7+ targets. Proper GOT/PLT handling is not yet implemented.
This patch is based on the backend implementation in ez-clang and has just enough functionality to model the infrastructure and link a Thumb function `main()` that calls `printf()` to dump "Hello Arm!" on Armv7a. It was tested on Raspberry Pi with 32-bit Raspbian OS.
Reviewed By: lhames
Differential Revision: https://reviews.llvm.org/D144083
Diffstat (limited to 'llvm/lib/ExecutionEngine')
-rw-r--r-- | llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt | 2 | ||||
-rw-r--r-- | llvm/lib/ExecutionEngine/JITLink/ELF.cpp | 9 | ||||
-rw-r--r-- | llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h | 21 | ||||
-rw-r--r-- | llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp | 299 | ||||
-rw-r--r-- | llvm/lib/ExecutionEngine/JITLink/aarch32.cpp | 514 | ||||
-rw-r--r-- | llvm/lib/ExecutionEngine/Orc/ObjectLinkingLayer.cpp | 6 |
6 files changed, 850 insertions, 1 deletions
diff --git a/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt b/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt index 52ff5e8..bc86f45 100644 --- a/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt +++ b/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt @@ -20,6 +20,7 @@ add_llvm_component_library(LLVMJITLink # ELF ELF.cpp ELFLinkGraphBuilder.cpp + ELF_aarch32.cpp ELF_aarch64.cpp ELF_i386.cpp ELF_loongarch.cpp @@ -33,6 +34,7 @@ add_llvm_component_library(LLVMJITLink COFF_x86_64.cpp # Architectures: + aarch32.cpp aarch64.cpp i386.cpp loongarch.cpp diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF.cpp index ef0f19a..340a0ce 100644 --- a/llvm/lib/ExecutionEngine/JITLink/ELF.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/ELF.cpp @@ -13,6 +13,7 @@ #include "llvm/ExecutionEngine/JITLink/ELF.h" #include "llvm/BinaryFormat/ELF.h" +#include "llvm/ExecutionEngine/JITLink/ELF_aarch32.h" #include "llvm/ExecutionEngine/JITLink/ELF_aarch64.h" #include "llvm/ExecutionEngine/JITLink/ELF_i386.h" #include "llvm/ExecutionEngine/JITLink/ELF_loongarch.h" @@ -69,6 +70,8 @@ createLinkGraphFromELFObject(MemoryBufferRef ObjectBuffer) { switch (*TargetMachineArch) { case ELF::EM_AARCH64: return createLinkGraphFromELFObject_aarch64(ObjectBuffer); + case ELF::EM_ARM: + return createLinkGraphFromELFObject_aarch32(ObjectBuffer); case ELF::EM_LOONGARCH: return createLinkGraphFromELFObject_loongarch(ObjectBuffer); case ELF::EM_RISCV: @@ -90,6 +93,12 @@ void link_ELF(std::unique_ptr<LinkGraph> G, case Triple::aarch64: link_ELF_aarch64(std::move(G), std::move(Ctx)); return; + case Triple::arm: + case Triple::armeb: + case Triple::thumb: + case Triple::thumbeb: + link_ELF_aarch32(std::move(G), std::move(Ctx)); + return; case Triple::loongarch32: case Triple::loongarch64: link_ELF_loongarch(std::move(G), std::move(Ctx)); diff --git a/llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h b/llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h index 9d2d495..1d98acf 100644 --- a/llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h +++ b/llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h @@ -127,6 +127,12 @@ protected: Error graphifySections(); Error graphifySymbols(); + /// Override in derived classes to suppress certain sections in the link + /// graph. + virtual bool excludeSection(const typename ELFT::Shdr &Sect) const { + return false; + } + /// Traverse all matching ELFT::Rela relocation records in the given section. /// The handler function Func should be callable with this signature: /// Error(const typename ELFT::Rela &, @@ -321,6 +327,13 @@ template <typename ELFT> Error ELFLinkGraphBuilder<ELFT>::graphifySections() { auto Name = Obj.getSectionName(Sec, SectionStringTab); if (!Name) return Name.takeError(); + if (excludeSection(Sec)) { + LLVM_DEBUG({ + dbgs() << " " << SecIndex << ": Skipping section \"" << *Name + << "\" explicitly\n"; + }); + continue; + } // Skip null sections. if (Sec.sh_type == ELF::SHT_NULL) { @@ -564,6 +577,10 @@ Error ELFLinkGraphBuilder<ELFT>::forEachRelaRelocation( LLVM_DEBUG(dbgs() << " skipped (dwarf section)\n\n"); return Error::success(); } + if (excludeSection(**FixupSection)) { + LLVM_DEBUG(dbgs() << " skipped (fixup section excluded explicitly)\n\n"); + return Error::success(); + } // Lookup the link-graph node corresponding to the target section name. auto *BlockToFix = getGraphBlock(RelSect.sh_info); @@ -610,6 +627,10 @@ Error ELFLinkGraphBuilder<ELFT>::forEachRelRelocation( LLVM_DEBUG(dbgs() << " skipped (dwarf section)\n\n"); return Error::success(); } + if (excludeSection(**FixupSection)) { + LLVM_DEBUG(dbgs() << " skipped (fixup section excluded explicitly)\n\n"); + return Error::success(); + } // Lookup the link-graph node corresponding to the target section name. auto *BlockToFix = getGraphBlock(RelSect.sh_info); diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp new file mode 100644 index 0000000..0010088 --- /dev/null +++ b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp @@ -0,0 +1,299 @@ +//===----- ELF_aarch32.cpp - JIT linker implementation for arm/thumb ------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// ELF/aarch32 jit-link implementation. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ExecutionEngine/JITLink/ELF_aarch32.h" + +#include "llvm/BinaryFormat/ELF.h" +#include "llvm/ExecutionEngine/JITLink/JITLink.h" +#include "llvm/ExecutionEngine/JITLink/aarch32.h" +#include "llvm/Object/ELF.h" +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/TargetParser/ARMTargetParser.h" + +#include "ELFLinkGraphBuilder.h" +#include "JITLinkGeneric.h" + +#define DEBUG_TYPE "jitlink" + +using namespace llvm::object; + +namespace llvm { +namespace jitlink { + +/// Translate from ELF relocation type to JITLink-internal edge kind. +Expected<aarch32::EdgeKind_aarch32> getJITLinkEdgeKind(uint32_t ELFType) { + switch (ELFType) { + case ELF::R_ARM_REL32: + return aarch32::Data_Delta32; + case ELF::R_ARM_CALL: + return aarch32::Arm_Call; + case ELF::R_ARM_THM_CALL: + return aarch32::Thumb_Call; + case ELF::R_ARM_THM_JUMP24: + return aarch32::Thumb_Jump24; + case ELF::R_ARM_THM_MOVW_ABS_NC: + return aarch32::Thumb_MovwAbsNC; + case ELF::R_ARM_THM_MOVT_ABS: + return aarch32::Thumb_MovtAbs; + } + + return make_error<JITLinkError>( + "Unsupported aarch32 relocation " + formatv("{0:d}: ", ELFType) + + object::getELFRelocationTypeName(ELF::EM_ARM, ELFType)); +} + +/// Translate from JITLink-internal edge kind back to ELF relocation type. +Expected<uint32_t> getELFRelocationType(Edge::Kind Kind) { + switch (static_cast<aarch32::EdgeKind_aarch32>(Kind)) { + case aarch32::Data_Delta32: + return ELF::R_ARM_REL32; + case aarch32::Arm_Call: + return ELF::R_ARM_CALL; + case aarch32::Thumb_Call: + return ELF::R_ARM_THM_CALL; + case aarch32::Thumb_Jump24: + return ELF::R_ARM_THM_JUMP24; + case aarch32::Thumb_MovwAbsNC: + return ELF::R_ARM_THM_MOVW_ABS_NC; + case aarch32::Thumb_MovtAbs: + return ELF::R_ARM_THM_MOVT_ABS; + } + + return make_error<JITLinkError>(formatv("Invalid aarch32 edge {0:d}: ", + Kind)); +} + +/// Get a human-readable name for the given ELF AArch32 edge kind. +const char *getELFAArch32EdgeKindName(Edge::Kind R) { + // No ELF-specific edge kinds yet + return aarch32::getEdgeKindName(R); +} + +class ELFJITLinker_aarch32 : public JITLinker<ELFJITLinker_aarch32> { + friend class JITLinker<ELFJITLinker_aarch32>; + +public: + ELFJITLinker_aarch32(std::unique_ptr<JITLinkContext> Ctx, + std::unique_ptr<LinkGraph> G, PassConfiguration PassCfg, + aarch32::ArmConfig ArmCfg) + : JITLinker(std::move(Ctx), std::move(G), std::move(PassCfg)), + ArmCfg(std::move(ArmCfg)) {} + +private: + aarch32::ArmConfig ArmCfg; + + Error applyFixup(LinkGraph &G, Block &B, const Edge &E) const { + return aarch32::applyFixup(G, B, E, ArmCfg); + } +}; + +template <support::endianness DataEndianness> +class ELFLinkGraphBuilder_aarch32 + : public ELFLinkGraphBuilder<ELFType<DataEndianness, false>> { +private: + using ELFT = ELFType<DataEndianness, false>; + using Base = ELFLinkGraphBuilder<ELFT>; + + bool excludeSection(const typename ELFT::Shdr &Sect) const override { + // TODO: An .ARM.exidx (Exception Index table) entry is 8-bytes in size and + // consists of 2 words. It might be sufficient to process only relocations + // in the the second word (offset 4). Please find more details in: Exception + // Handling ABI for the ArmĀ® Architecture -> Index table entries + if (Sect.sh_type == ELF::SHT_ARM_EXIDX) + return true; + return false; + } + + Error addRelocations() override { + LLVM_DEBUG(dbgs() << "Processing relocations:\n"); + using Self = ELFLinkGraphBuilder_aarch32<DataEndianness>; + for (const auto &RelSect : Base::Sections) { + if (Error Err = Base::forEachRelRelocation(RelSect, this, + &Self::addSingleRelRelocation)) + return Err; + } + return Error::success(); + } + + Error addSingleRelRelocation(const typename ELFT::Rel &Rel, + const typename ELFT::Shdr &FixupSect, + Block &BlockToFix) { + uint32_t SymbolIndex = Rel.getSymbol(false); + auto ObjSymbol = Base::Obj.getRelocationSymbol(Rel, Base::SymTabSec); + if (!ObjSymbol) + return ObjSymbol.takeError(); + + Symbol *GraphSymbol = Base::getGraphSymbol(SymbolIndex); + if (!GraphSymbol) + return make_error<StringError>( + formatv("Could not find symbol at given index, did you add it to " + "JITSymbolTable? index: {0}, shndx: {1} Size of table: {2}", + SymbolIndex, (*ObjSymbol)->st_shndx, + Base::GraphSymbols.size()), + inconvertibleErrorCode()); + + uint32_t Type = Rel.getType(false); + Expected<aarch32::EdgeKind_aarch32> Kind = getJITLinkEdgeKind(Type); + if (!Kind) + return Kind.takeError(); + + auto FixupAddress = orc::ExecutorAddr(FixupSect.sh_addr) + Rel.r_offset; + Edge::OffsetT Offset = FixupAddress - BlockToFix.getAddress(); + Edge E(*Kind, Offset, *GraphSymbol, 0); + + Expected<int64_t> Addend = + aarch32::readAddend(*Base::G, BlockToFix, E, ArmCfg); + if (!Addend) + return Addend.takeError(); + + E.setAddend(*Addend); + LLVM_DEBUG({ + dbgs() << " "; + printEdge(dbgs(), BlockToFix, E, getELFAArch32EdgeKindName(*Kind)); + dbgs() << "\n"; + }); + + BlockToFix.addEdge(std::move(E)); + return Error::success(); + } + + aarch32::ArmConfig ArmCfg; + +protected: + TargetFlagsType makeTargetFlags(const typename ELFT::Sym &Sym) override { + if (Sym.getValue() & 0x01) + return aarch32::ThumbSymbol; + return TargetFlagsType{}; + } + + orc::ExecutorAddrDiff getRawOffset(const typename ELFT::Sym &Sym, + TargetFlagsType Flags) override { + assert((makeTargetFlags(Sym) & Flags) == Flags); + static constexpr uint64_t ThumbBit = 0x01; + return Sym.getValue() & ~ThumbBit; + } + +public: + ELFLinkGraphBuilder_aarch32(StringRef FileName, const ELFFile<ELFT> &Obj, + Triple TT, aarch32::ArmConfig ArmCfg) + : ELFLinkGraphBuilder<ELFT>(Obj, std::move(TT), FileName, + getELFAArch32EdgeKindName), + ArmCfg(std::move(ArmCfg)) {} +}; + +template <aarch32::StubsFlavor Flavor> +Error buildTables_ELF_aarch32(LinkGraph &G) { + LLVM_DEBUG(dbgs() << "Visiting edges in graph:\n"); + + aarch32::StubsManager<Flavor> PLT; + visitExistingEdges(G, PLT); + return Error::success(); +} + +Expected<std::unique_ptr<LinkGraph>> +createLinkGraphFromELFObject_aarch32(MemoryBufferRef ObjectBuffer) { + LLVM_DEBUG({ + dbgs() << "Building jitlink graph for new input " + << ObjectBuffer.getBufferIdentifier() << "...\n"; + }); + + auto ELFObj = ObjectFile::createELFObjectFile(ObjectBuffer); + if (!ELFObj) + return ELFObj.takeError(); + + // Find out what exact AArch32 instruction set and features we target. + auto TT = (*ELFObj)->makeTriple(); + ARM::ArchKind AK = ARM::parseArch(TT.getArchName()); + if (AK == ARM::ArchKind::INVALID) + return make_error<JITLinkError>( + "Failed to build ELF link graph: Invalid ARM ArchKind"); + + // Resolve our internal configuration for the target. If at some point the + // CPUArch alone becomes too unprecise, we can find more details in the + // Tag_CPU_arch_profile. + aarch32::ArmConfig ArmCfg; + using namespace ARMBuildAttrs; + auto Arch = static_cast<CPUArch>(ARM::getArchAttr(AK)); + switch (Arch) { + case v7: + case v8_A: + ArmCfg = aarch32::getArmConfigForCPUArch(Arch); + assert(ArmCfg.Stubs != aarch32::Unsupported && + "Provide a config for each supported CPU"); + break; + default: + return make_error<JITLinkError>( + "Failed to build ELF link graph: Unsupported CPU arch " + + StringRef(aarch32::getCPUArchName(Arch))); + } + + // Populate the link-graph. + switch (TT.getArch()) { + case Triple::arm: + case Triple::thumb: { + auto &ELFFile = cast<ELFObjectFile<ELF32LE>>(**ELFObj).getELFFile(); + return ELFLinkGraphBuilder_aarch32<support::little>( + (*ELFObj)->getFileName(), ELFFile, TT, ArmCfg) + .buildGraph(); + } + case Triple::armeb: + case Triple::thumbeb: { + auto &ELFFile = cast<ELFObjectFile<ELF32BE>>(**ELFObj).getELFFile(); + return ELFLinkGraphBuilder_aarch32<support::big>((*ELFObj)->getFileName(), + ELFFile, TT, ArmCfg) + .buildGraph(); + } + default: + return make_error<JITLinkError>( + "Failed to build ELF/aarch32 link graph: Invalid target triple " + + TT.getTriple()); + } +} + +void link_ELF_aarch32(std::unique_ptr<LinkGraph> G, + std::unique_ptr<JITLinkContext> Ctx) { + const Triple &TT = G->getTargetTriple(); + + using namespace ARMBuildAttrs; + ARM::ArchKind AK = ARM::parseArch(TT.getArchName()); + auto CPU = static_cast<CPUArch>(ARM::getArchAttr(AK)); + aarch32::ArmConfig ArmCfg = aarch32::getArmConfigForCPUArch(CPU); + + PassConfiguration PassCfg; + if (Ctx->shouldAddDefaultTargetPasses(TT)) { + // Add a mark-live pass. + if (auto MarkLive = Ctx->getMarkLivePass(TT)) + PassCfg.PrePrunePasses.push_back(std::move(MarkLive)); + else + PassCfg.PrePrunePasses.push_back(markAllSymbolsLive); + + switch (ArmCfg.Stubs) { + case aarch32::Thumbv7: + PassCfg.PostPrunePasses.push_back( + buildTables_ELF_aarch32<aarch32::Thumbv7>); + break; + case aarch32::Unsupported: + llvm_unreachable("Check before building graph"); + } + } + + if (auto Err = Ctx->modifyPassConfig(*G, PassCfg)) + return Ctx->notifyFailed(std::move(Err)); + + ELFJITLinker_aarch32::link(std::move(Ctx), std::move(G), std::move(PassCfg), + std::move(ArmCfg)); +} + +} // namespace jitlink +} // namespace llvm diff --git a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp new file mode 100644 index 0000000..6f49a45 --- /dev/null +++ b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp @@ -0,0 +1,514 @@ +//===--------- aarch32.cpp - Generic JITLink arm/thumb utilities ----------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Generic utilities for graphs representing arm/thumb objects. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ExecutionEngine/JITLink/aarch32.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/BinaryFormat/ELF.h" +#include "llvm/ExecutionEngine/JITLink/JITLink.h" +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/MathExtras.h" + +#define DEBUG_TYPE "jitlink" + +namespace llvm { +namespace jitlink { +namespace aarch32 { + +using namespace support; +using namespace support::endian; + +/// Encode 22-bit immediate value for branch instructions without J1J2 range +/// extension (formats B T4, BL T1 and BLX T2). +/// +/// 00000:Imm11H:Imm11L:0 -> [ 00000:Imm11H, 00000:Imm11L ] +/// J1^ ^J2 will always be 1 +/// +HalfWords encodeImmBT4BlT1BlxT2(int64_t Value) { + constexpr uint32_t J1J2 = 0x2800; + uint32_t Imm11H = (Value >> 12) & 0x07ff; + uint32_t Imm11L = (Value >> 1) & 0x07ff; + return HalfWords{Imm11H, Imm11L | J1J2}; +} + +/// Decode 22-bit immediate value for branch instructions without J1J2 range +/// extension (formats B T4, BL T1 and BLX T2). +/// +/// [ 00000:Imm11H, 00000:Imm11L ] -> 00000:Imm11H:Imm11L:0 +/// J1^ ^J2 will always be 1 +/// +int64_t decodeImmBT4BlT1BlxT2(uint32_t Hi, uint32_t Lo) { + uint32_t Imm11H = Hi & 0x07ff; + uint32_t Imm11L = Lo & 0x07ff; + return SignExtend64<22>(Imm11H << 12 | Imm11L << 1); +} + +/// Encode 25-bit immediate value for branch instructions with J1J2 range +/// extension (formats B T4, BL T1 and BLX T2). +/// +/// S:I1:I2:Imm10:Imm11:0 -> [ 00000:S:Imm10, 00:J1:0:J2:Imm11 ] +/// +HalfWords encodeImmBT4BlT1BlxT2_J1J2(int64_t Value) { + uint32_t S = (Value >> 14) & 0x0400; + uint32_t J1 = (((~(Value >> 10)) ^ (Value >> 11)) & 0x2000); + uint32_t J2 = (((~(Value >> 11)) ^ (Value >> 13)) & 0x0800); + uint32_t Imm10 = (Value >> 12) & 0x03ff; + uint32_t Imm11 = (Value >> 1) & 0x07ff; + return HalfWords{S | Imm10, J1 | J2 | Imm11}; +} + +/// Decode 25-bit immediate value for branch instructions with J1J2 range +/// extension (formats B T4, BL T1 and BLX T2). +/// +/// [ 00000:S:Imm10, 00:J1:0:J2:Imm11] -> S:I1:I2:Imm10:Imm11:0 +/// +int64_t decodeImmBT4BlT1BlxT2_J1J2(uint32_t Hi, uint32_t Lo) { + uint32_t S = Hi & 0x0400; + uint32_t I1 = ~((Lo ^ (Hi << 3)) << 10) & 0x00800000; + uint32_t I2 = ~((Lo ^ (Hi << 1)) << 11) & 0x00400000; + uint32_t Imm10 = Hi & 0x03ff; + uint32_t Imm11 = Lo & 0x07ff; + return SignExtend64<25>(S << 14 | I1 | I2 | Imm10 << 12 | Imm11 << 1); +} + +/// Encode 16-bit immediate value for move instruction formats MOVT T1 and +/// MOVW T3. +/// +/// Imm4:Imm1:Imm3:Imm8 -> [ 00000:i:000000:Imm4, 0:Imm3:0000:Imm8 ] +/// +HalfWords encodeImmMovtT1MovwT3(uint16_t Value) { + uint32_t Imm4 = (Value >> 12) & 0x0f; + uint32_t Imm1 = (Value >> 11) & 0x01; + uint32_t Imm3 = (Value >> 8) & 0x07; + uint32_t Imm8 = Value & 0xff; + return HalfWords{Imm1 << 10 | Imm4, Imm3 << 12 | Imm8}; +} + +/// Decode 16-bit immediate value from move instruction formats MOVT T1 and +/// MOVW T3. +/// +/// [ 00000:i:000000:Imm4, 0:Imm3:0000:Imm8 ] -> Imm4:Imm1:Imm3:Imm8 +/// +uint16_t decodeImmMovtT1MovwT3(uint32_t Hi, uint32_t Lo) { + uint32_t Imm4 = Hi & 0x0f; + uint32_t Imm1 = (Hi >> 10) & 0x01; + uint32_t Imm3 = (Lo >> 12) & 0x07; + uint32_t Imm8 = Lo & 0xff; + uint32_t Imm16 = Imm4 << 12 | Imm1 << 11 | Imm3 << 8 | Imm8; + assert(Imm16 <= 0xffff && "Decoded value out-of-range"); + return Imm16; +} + +/// Encode register ID for instruction formats MOVT T1 and MOVW T3. +/// +/// Rd4 -> [0000000000000000, 0000:Rd4:00000000] +/// +HalfWords encodeRegMovtT1MovwT3(int64_t Value) { + uint32_t Rd4 = (Value & 0x0f) << 8; + return HalfWords{0, Rd4}; +} + +/// Decode register ID from instruction formats MOVT T1 and MOVW T3. +/// +/// [0000000000000000, 0000:Rd4:00000000] -> Rd4 +/// +int64_t decodeRegMovtT1MovwT3(uint32_t Hi, uint32_t Lo) { + uint32_t Rd4 = (Lo >> 8) & 0x0f; + return Rd4; +} + +/// 32-bit Thumb instructions are stored as two little-endian halfwords. +/// An instruction at address A encodes bytes A+1, A in the first halfword (Hi), +/// followed by bytes A+3, A+2 in the second halfword (Lo). +struct WritableThumbRelocation { + /// Create a writable reference to a Thumb32 fixup. + WritableThumbRelocation(char *FixupPtr) + : Hi{*reinterpret_cast<support::ulittle16_t *>(FixupPtr)}, + Lo{*reinterpret_cast<support::ulittle16_t *>(FixupPtr + 2)} {} + + support::ulittle16_t &Hi; // First halfword + support::ulittle16_t &Lo; // Second halfword +}; + +struct ThumbRelocation { + /// Create a read-only reference to a Thumb32 fixup. + ThumbRelocation(const char *FixupPtr) + : Hi{*reinterpret_cast<const support::ulittle16_t *>(FixupPtr)}, + Lo{*reinterpret_cast<const support::ulittle16_t *>(FixupPtr + 2)} {} + + /// Create a read-only Thumb32 fixup from a writeable one. + ThumbRelocation(WritableThumbRelocation &Writable) + : Hi{Writable.Hi}, Lo(Writable.Lo) {} + + const support::ulittle16_t &Hi; // First halfword + const support::ulittle16_t &Lo; // Second halfword +}; + +Error makeUnexpectedOpcodeError(const LinkGraph &G, const ThumbRelocation &R, + Edge::Kind Kind) { + return make_error<JITLinkError>( + formatv("Invalid opcode [ 0x{0:x4}, 0x{1:x4} ] for relocation: {2}", R.Hi, + R.Lo, G.getEdgeKindName(Kind))); +} + +template <EdgeKind_aarch32 Kind> bool checkOpcode(const ThumbRelocation &R) { + uint16_t Hi = R.Hi & FixupInfo<Kind>::OpcodeMask.Hi; + uint16_t Lo = R.Lo & FixupInfo<Kind>::OpcodeMask.Lo; + return Hi == FixupInfo<Kind>::Opcode.Hi && Lo == FixupInfo<Kind>::Opcode.Lo; +} + +template <EdgeKind_aarch32 Kind> +bool checkRegister(const ThumbRelocation &R, HalfWords Reg) { + uint16_t Hi = R.Hi & FixupInfo<Kind>::RegMask.Hi; + uint16_t Lo = R.Lo & FixupInfo<Kind>::RegMask.Lo; + return Hi == Reg.Hi && Lo == Reg.Lo; +} + +template <EdgeKind_aarch32 Kind> +bool writeRegister(WritableThumbRelocation &R, HalfWords Reg) { + static constexpr HalfWords Mask = FixupInfo<Kind>::RegMask; + assert((Mask.Hi & Reg.Hi) == Reg.Hi && (Mask.Hi & Reg.Hi) == Reg.Hi && + "Value bits exceed bit range of given mask"); + R.Hi = (R.Hi & ~Mask.Hi) | Reg.Hi; + R.Lo = (R.Lo & ~Mask.Lo) | Reg.Lo; +} + +template <EdgeKind_aarch32 Kind> +void writeImmediate(WritableThumbRelocation &R, HalfWords Imm) { + static constexpr HalfWords Mask = FixupInfo<Kind>::ImmMask; + assert((Mask.Hi & Imm.Hi) == Imm.Hi && (Mask.Hi & Imm.Hi) == Imm.Hi && + "Value bits exceed bit range of given mask"); + R.Hi = (R.Hi & ~Mask.Hi) | Imm.Hi; + R.Lo = (R.Lo & ~Mask.Lo) | Imm.Lo; +} + +Expected<int64_t> readAddendData(LinkGraph &G, Block &B, const Edge &E) { + endianness Endian = G.getEndianness(); + assert(Endian != native && "Declare as little or big explicitly"); + + Edge::Kind Kind = E.getKind(); + const char *BlockWorkingMem = B.getContent().data(); + const char *FixupPtr = BlockWorkingMem + E.getOffset(); + + switch (Kind) { + case Data_Delta32: + return SignExtend64<32>((Endian == little) ? read32<little>(FixupPtr) + : read32<big>(FixupPtr)); + default: + return make_error<JITLinkError>( + "In graph " + G.getName() + ", section " + B.getSection().getName() + + " can not read implicit addend for aarch32 edge kind " + + G.getEdgeKindName(E.getKind())); + } +} + +Expected<int64_t> readAddendArm(LinkGraph &G, Block &B, const Edge &E) { + Edge::Kind Kind = E.getKind(); + + switch (Kind) { + case Arm_Call: + return make_error<JITLinkError>( + "Addend extraction for relocation type not yet implemented: " + + StringRef(G.getEdgeKindName(Kind))); + default: + return make_error<JITLinkError>( + "In graph " + G.getName() + ", section " + B.getSection().getName() + + " can not read implicit addend for aarch32 edge kind " + + G.getEdgeKindName(E.getKind())); + } +} + +Expected<int64_t> readAddendThumb(LinkGraph &G, Block &B, const Edge &E, + const ArmConfig &ArmCfg) { + ThumbRelocation R(B.getContent().data() + E.getOffset()); + Edge::Kind Kind = E.getKind(); + + switch (Kind) { + case Thumb_Call: + if (!checkOpcode<Thumb_Call>(R)) + return makeUnexpectedOpcodeError(G, R, Kind); + return LLVM_LIKELY(ArmCfg.J1J2BranchEncoding) + ? decodeImmBT4BlT1BlxT2_J1J2(R.Hi, R.Lo) + : decodeImmBT4BlT1BlxT2(R.Hi, R.Lo); + + case Thumb_Jump24: + if (!checkOpcode<Thumb_Jump24>(R)) + return makeUnexpectedOpcodeError(G, R, Kind); + if (R.Lo & FixupInfo<Thumb_Jump24>::LoBitConditional) + return make_error<JITLinkError>("Relocation expects an unconditional " + "B.W branch instruction: " + + StringRef(G.getEdgeKindName(Kind))); + return LLVM_LIKELY(ArmCfg.J1J2BranchEncoding) + ? decodeImmBT4BlT1BlxT2_J1J2(R.Hi, R.Lo) + : decodeImmBT4BlT1BlxT2(R.Hi, R.Lo); + + case Thumb_MovwAbsNC: + if (!checkOpcode<Thumb_MovwAbsNC>(R)) + return makeUnexpectedOpcodeError(G, R, Kind); + // Initial addend is interpreted as a signed value + return SignExtend64<16>(decodeImmMovtT1MovwT3(R.Hi, R.Lo)); + + case Thumb_MovtAbs: + if (!checkOpcode<Thumb_MovtAbs>(R)) + return makeUnexpectedOpcodeError(G, R, Kind); + // Initial addend is interpreted as a signed value + return SignExtend64<16>(decodeImmMovtT1MovwT3(R.Hi, R.Lo)); + + default: + return make_error<JITLinkError>( + "In graph " + G.getName() + ", section " + B.getSection().getName() + + " can not read implicit addend for aarch32 edge kind " + + G.getEdgeKindName(E.getKind())); + } +} + +Error applyFixupData(LinkGraph &G, Block &B, const Edge &E) { + using namespace support; + + char *BlockWorkingMem = B.getAlreadyMutableContent().data(); + char *FixupPtr = BlockWorkingMem + E.getOffset(); + + auto Write32 = [FixupPtr, Endian = G.getEndianness()](int64_t Value) { + assert(Endian != native && "Must be explicit: little or big"); + assert(isInt<32>(Value) && "Must be in signed 32-bit range"); + uint32_t Imm = static_cast<int32_t>(Value); + if (LLVM_LIKELY(Endian == little)) + endian::write32<little>(FixupPtr, Imm); + else + endian::write32<big>(FixupPtr, Imm); + }; + + Edge::Kind Kind = E.getKind(); + uint64_t FixupAddress = (B.getAddress() + E.getOffset()).getValue(); + int64_t Addend = E.getAddend(); + Symbol &TargetSymbol = E.getTarget(); + uint64_t TargetAddress = TargetSymbol.getAddress().getValue(); + assert(!TargetSymbol.hasTargetFlags(ThumbSymbol)); + + // Regular data relocations have size 4, alignment 1 and write the full 32-bit + // result to the place; no need for overflow checking. There are three + // exceptions: R_ARM_ABS8, R_ARM_ABS16, R_ARM_PREL31 + switch (Kind) { + case Data_Delta32: { + int64_t Value = TargetAddress - FixupAddress + Addend; + if (!isInt<32>(Value)) + return makeTargetOutOfRangeError(G, B, E); + Write32(Value); + return Error::success(); + } + default: + return make_error<JITLinkError>( + "In graph " + G.getName() + ", section " + B.getSection().getName() + + " encountered unfixable aarch32 edge kind " + + G.getEdgeKindName(E.getKind())); + } +} + +Error applyFixupArm(LinkGraph &G, Block &B, const Edge &E) { + Edge::Kind Kind = E.getKind(); + + switch (Kind) { + case Arm_Call: + return make_error<JITLinkError>( + "Fix-up for relocation type not yet implemented: " + + StringRef(G.getEdgeKindName(Kind))); + default: + return make_error<JITLinkError>( + "In graph " + G.getName() + ", section " + B.getSection().getName() + + " encountered unfixable aarch32 edge kind " + + G.getEdgeKindName(E.getKind())); + } +} + +Error applyFixupThumb(LinkGraph &G, Block &B, const Edge &E, + const ArmConfig &ArmCfg) { + WritableThumbRelocation R(B.getAlreadyMutableContent().data() + + E.getOffset()); + + Edge::Kind Kind = E.getKind(); + uint64_t FixupAddress = (B.getAddress() + E.getOffset()).getValue(); + int64_t Addend = E.getAddend(); + Symbol &TargetSymbol = E.getTarget(); + uint64_t TargetAddress = TargetSymbol.getAddress().getValue(); + if (TargetSymbol.hasTargetFlags(ThumbSymbol)) + TargetAddress |= 0x01; + + switch (Kind) { + case Thumb_Jump24: { + if (!checkOpcode<Thumb_Jump24>(R)) + return makeUnexpectedOpcodeError(G, R, Kind); + if (R.Lo & FixupInfo<Thumb_Jump24>::LoBitConditional) + return make_error<JITLinkError>("Relocation expects an unconditional " + "B.W branch instruction: " + + StringRef(G.getEdgeKindName(Kind))); + if (!(TargetSymbol.hasTargetFlags(ThumbSymbol))) + return make_error<JITLinkError>("Branch relocation needs interworking " + "stub when bridging to ARM: " + + StringRef(G.getEdgeKindName(Kind))); + + int64_t Value = TargetAddress - FixupAddress + Addend; + if (LLVM_LIKELY(ArmCfg.J1J2BranchEncoding)) { + if (!isInt<25>(Value)) + return makeTargetOutOfRangeError(G, B, E); + writeImmediate<Thumb_Jump24>(R, encodeImmBT4BlT1BlxT2_J1J2(Value)); + } else { + if (!isInt<22>(Value)) + return makeTargetOutOfRangeError(G, B, E); + writeImmediate<Thumb_Jump24>(R, encodeImmBT4BlT1BlxT2(Value)); + } + + return Error::success(); + } + + case Thumb_Call: { + if (!checkOpcode<Thumb_Call>(R)) + return makeUnexpectedOpcodeError(G, R, Kind); + + int64_t Value = TargetAddress - FixupAddress + Addend; + + // The call instruction itself is Thumb. The call destination can either be + // Thumb or Arm. We use BL to stay in Thumb and BLX to change to Arm. + bool TargetIsArm = !TargetSymbol.hasTargetFlags(ThumbSymbol); + bool InstrIsBlx = (R.Lo & FixupInfo<Thumb_Call>::LoBitNoBlx) == 0; + if (TargetIsArm != InstrIsBlx) { + if (LLVM_LIKELY(TargetIsArm)) { + // Change opcode BL -> BLX and fix range value (account for 4-byte + // aligned destination while instruction may only be 2-byte aligned + // and clear Thumb bit). + R.Lo = R.Lo & ~FixupInfo<Thumb_Call>::LoBitNoBlx; + R.Lo = R.Lo & ~FixupInfo<Thumb_Call>::LoBitH; + Value = alignTo(Value, 4); + } else { + // Change opcode BLX -> BL and set Thumb bit + R.Lo = R.Lo & ~FixupInfo<Thumb_Call>::LoBitNoBlx; + Value |= 0x01; + } + } + + if (LLVM_LIKELY(ArmCfg.J1J2BranchEncoding)) { + if (!isInt<25>(Value)) + return makeTargetOutOfRangeError(G, B, E); + writeImmediate<Thumb_Call>(R, encodeImmBT4BlT1BlxT2_J1J2(Value)); + } else { + if (!isInt<22>(Value)) + return makeTargetOutOfRangeError(G, B, E); + writeImmediate<Thumb_Call>(R, encodeImmBT4BlT1BlxT2(Value)); + } + + assert(((R.Lo & FixupInfo<Thumb_Call>::LoBitNoBlx) || + (R.Lo & FixupInfo<Thumb_Call>::LoBitH) == 0) && + "Opcode BLX implies H bit is clear (avoid UB in BLX T2)"); + return Error::success(); + } + + case Thumb_MovwAbsNC: { + if (!checkOpcode<Thumb_MovwAbsNC>(R)) + return makeUnexpectedOpcodeError(G, R, Kind); + uint16_t Value = (TargetAddress + Addend) & 0xffff; + writeImmediate<Thumb_MovwAbsNC>(R, encodeImmMovtT1MovwT3(Value)); + return Error::success(); + } + + case Thumb_MovtAbs: { + if (!checkOpcode<Thumb_MovtAbs>(R)) + return makeUnexpectedOpcodeError(G, R, Kind); + uint16_t Value = ((TargetAddress + Addend) >> 16) & 0xffff; + writeImmediate<Thumb_MovtAbs>(R, encodeImmMovtT1MovwT3(Value)); + return Error::success(); + } + + default: + return make_error<JITLinkError>( + "In graph " + G.getName() + ", section " + B.getSection().getName() + + " encountered unfixable aarch32 edge kind " + + G.getEdgeKindName(E.getKind())); + } +} + +const uint8_t Thumbv7ABS[] = { + 0x40, 0xf2, 0x00, 0x0c, // movw r12, #0x0000 ; lower 16-bit + 0xc0, 0xf2, 0x00, 0x0c, // movt r12, #0x0000 ; upper 16-bit + 0x60, 0x47 // bx r12 +}; + +template <> +Symbol &StubsManager<Thumbv7>::createEntry(LinkGraph &G, Symbol &Target) { + constexpr uint64_t Alignment = 4; + Block &B = addStub(G, Thumbv7ABS, Alignment); + LLVM_DEBUG({ + const char *StubPtr = B.getContent().data(); + HalfWords Reg12 = encodeRegMovtT1MovwT3(12); + assert(checkRegister<Thumb_MovwAbsNC>(StubPtr, Reg12) && + checkRegister<Thumb_MovtAbs>(StubPtr + 4, Reg12) && + "Linker generated stubs may only corrupt register r12 (IP)"); + }); + B.addEdge(Thumb_MovwAbsNC, 0, Target, 0); + B.addEdge(Thumb_MovtAbs, 4, Target, 0); + Symbol &Stub = G.addAnonymousSymbol(B, 0, B.getSize(), true, false); + Stub.setTargetFlags(ThumbSymbol); + return Stub; +} + +const char *getEdgeKindName(Edge::Kind K) { +#define KIND_NAME_CASE(K) \ + case K: \ + return #K; + + switch (K) { + KIND_NAME_CASE(Data_Delta32) + KIND_NAME_CASE(Arm_Call) + KIND_NAME_CASE(Thumb_Call) + KIND_NAME_CASE(Thumb_Jump24) + KIND_NAME_CASE(Thumb_MovwAbsNC) + KIND_NAME_CASE(Thumb_MovtAbs) + default: + return getGenericEdgeKindName(K); + } +#undef KIND_NAME_CASE +} + +const char *getCPUArchName(ARMBuildAttrs::CPUArch K) { +#define CPUARCH_NAME_CASE(K) \ + case K: \ + return #K; + + using namespace ARMBuildAttrs; + switch (K) { + CPUARCH_NAME_CASE(Pre_v4) + CPUARCH_NAME_CASE(v4) + CPUARCH_NAME_CASE(v4T) + CPUARCH_NAME_CASE(v5T) + CPUARCH_NAME_CASE(v5TE) + CPUARCH_NAME_CASE(v5TEJ) + CPUARCH_NAME_CASE(v6) + CPUARCH_NAME_CASE(v6KZ) + CPUARCH_NAME_CASE(v6T2) + CPUARCH_NAME_CASE(v6K) + CPUARCH_NAME_CASE(v7) + CPUARCH_NAME_CASE(v6_M) + CPUARCH_NAME_CASE(v6S_M) + CPUARCH_NAME_CASE(v7E_M) + CPUARCH_NAME_CASE(v8_A) + CPUARCH_NAME_CASE(v8_R) + CPUARCH_NAME_CASE(v8_M_Base) + CPUARCH_NAME_CASE(v8_M_Main) + CPUARCH_NAME_CASE(v8_1_M_Main) + CPUARCH_NAME_CASE(v9_A) + } + llvm_unreachable("Missing CPUArch in switch?"); +#undef CPUARCH_NAME_CASE +} + +} // namespace aarch32 +} // namespace jitlink +} // namespace llvm diff --git a/llvm/lib/ExecutionEngine/Orc/ObjectLinkingLayer.cpp b/llvm/lib/ExecutionEngine/Orc/ObjectLinkingLayer.cpp index 2c270cd..83a09b8 100644 --- a/llvm/lib/ExecutionEngine/Orc/ObjectLinkingLayer.cpp +++ b/llvm/lib/ExecutionEngine/Orc/ObjectLinkingLayer.cpp @@ -8,6 +8,7 @@ #include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" #include "llvm/ExecutionEngine/JITLink/EHFrameSupport.h" +#include "llvm/ExecutionEngine/JITLink/aarch32.h" #include "llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h" #include "llvm/ExecutionEngine/Orc/ObjectFileInterface.h" #include "llvm/ExecutionEngine/Orc/Shared/ObjectFormats.h" @@ -40,7 +41,10 @@ bool hasInitializerSection(jitlink::LinkGraph &G) { } JITTargetAddress getJITSymbolPtrForSymbol(Symbol &Sym) { - return Sym.getAddress().getValue(); + uint64_t CallableAddr = Sym.getAddress().getValue(); + if (Sym.isCallable() && Sym.hasTargetFlags(aarch32::ThumbSymbol)) + CallableAddr |= 0x01; // thumb bit + return CallableAddr; } JITSymbolFlags getJITSymbolFlagsForSymbol(Symbol &Sym) { |