diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/ELF_arm.h b/llvm/include/llvm/ExecutionEngine/JITLink/ELF_aarch32.h rename from llvm/include/llvm/ExecutionEngine/JITLink/ELF_arm.h rename to llvm/include/llvm/ExecutionEngine/JITLink/ELF_aarch32.h diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/JITLink.h b/llvm/include/llvm/ExecutionEngine/JITLink/JITLink.h --- a/llvm/include/llvm/ExecutionEngine/JITLink/JITLink.h +++ b/llvm/include/llvm/ExecutionEngine/JITLink/JITLink.h @@ -448,7 +448,7 @@ orc::ExecutorAddrDiff Offset, orc::ExecutorAddrDiff Size, bool IsCallable, bool IsLive) { - assert((Offset + Size) <= Base.getSize() && + assert(((Offset & ~0x1) + Size) <= Base.getSize() && "Symbol extends past end of block"); auto *Sym = Allocator.Allocate(); new (Sym) Symbol(Base, Offset, StringRef(), Size, Linkage::Strong, diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h @@ -0,0 +1,292 @@ +//===-- arm.h - Generic JITLink arm/thumb edge kinds, utilities -*- C++ -*-===// +// +// 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. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_JITLINK_ARM_H +#define LLVM_EXECUTIONENGINE_JITLINK_ARM_H + +#include "TableManager.h" +#include "llvm/ExecutionEngine/JITLink/JITLink.h" +#include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" +#include "llvm/Support/ARMBuildAttributes.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/Error.h" + +#include + +namespace llvm { +namespace jitlink { +namespace arm { + +/// AArch32 fixup types +enum EdgeKind_arm : Edge::Kind { + + /// + /// Relocations of class Data + /// + FirstDataRelocation = Edge::FirstRelocation, + + /// Plain 32-bit value relocation in target endianness + Data_Delta32 = FirstDataRelocation, + + LastDataRelocation = Data_Delta32, + + /// + /// Relocations of class Arm (covers fixed-width 4-byte instruction subset) + /// + FirstArmRelocation, + + Arm_Call = FirstArmRelocation, + + LastArmRelocation = Arm_Call, + + /// + /// Relocations of class Thumb16 and Thumb32 (covers Thumb instruction subset) + /// + FirstThumbRelocation, + + /// Write immediate value for PC-relative branch with link (can bridge between + /// ARM and Thumb). + Thumb_Call = FirstThumbRelocation, + + /// Write immediate value for (unconditional) PC-relative branch without link. + Thumb_Jump24, + + /// Write immediate value to the lower halfword of the destination register + Thumb_MovwAbsNC, + + /// Write immediate value to the top halfword of the destination register + Thumb_MovtAbs, + + LastThumbRelocation = Thumb_MovtAbs, +}; + +/// Human-readable name for a given edge +const char *getEdgeKindName(Edge::Kind K); + +/// Human-readable name for a given CPU architecture kind +const char *getCPUArchName(ARMBuildAttrs::CPUArch K); + +/// AArch32 uses stubs for a number of purposes, like branch range extension +/// or interworking between instruction subsets ARM and Thumb. +/// +/// Stub implementations vary depending on CPU architecture (v4, v6, v7), +/// instruction subset and branch type (absolute/PC-relative). +/// +/// For each kind of stub, the StubsFlavor defines one concrete form that is +/// used throughout the LinkGraph. +/// +/// Stubs are often called "veneers" in the official docs and online. +/// +enum StubsFlavor { + Unsupported = 0, + Thumbv7, +}; + +/// JITLink sub-arch configuration for ARM CPU models +struct ArmConfig { + bool HasBlx = false; + bool HasMovtMovw = false; + bool J1J2BranchEncoding = false; + StubsFlavor Stubs = Unsupported; +}; + +/// Obtain the sub-arch configuration for a given ARM CPU model. +inline ArmConfig getArmConfigForCPUArch(ARMBuildAttrs::CPUArch CPUArch) { + ArmConfig ArmCfg; + switch (CPUArch) { + case ARMBuildAttrs::v7: + case ARMBuildAttrs::v8_A: + ArmCfg.HasBlx = true; + ArmCfg.J1J2BranchEncoding = true; + ArmCfg.HasMovtMovw = true; + ArmCfg.Stubs = Thumbv7; + break; + default: + DEBUG_WITH_TYPE("jitlink", { + dbgs() << " Warning: ARM config not defined for CPU architecture " + << getCPUArchName(CPUArch); + }); + break; + } + return ArmCfg; +} + +/// Immutable pair of halfwords, Hi and Lo, with overflow check +struct HalfWords { + constexpr HalfWords() : Hi(0), Lo(0) {} + constexpr HalfWords(uint32_t Hi, uint32_t Lo) : Hi(Hi), Lo(Lo) { + assert(isUInt<16>(Hi) && "Overflow in first half-word"); + assert(isUInt<16>(Lo) && "Overflow in second half-word"); + } + const uint16_t Hi; // First halfword + const uint16_t Lo; // Second halfword +}; + +template struct FixupInfo {}; + +template <> struct FixupInfo { + static constexpr HalfWords Opcode{0xf000, 0x8000}; + static constexpr HalfWords OpcodeMask{0xf800, 0x8000}; + static constexpr HalfWords ImmMask{0x07ff, 0x2fff}; + static constexpr uint16_t LoBitConditional = 0x1000; +}; + +template <> struct FixupInfo { + static constexpr HalfWords Opcode{0xf000, 0xc000}; + static constexpr HalfWords OpcodeMask{0xf800, 0xc000}; + static constexpr HalfWords ImmMask{0x07ff, 0x2fff}; + static constexpr uint16_t LoBitH = 0x0001; + static constexpr uint16_t LoBitNoBlx = 0x1000; +}; + +template <> struct FixupInfo { + static constexpr HalfWords Opcode{0xf2c0, 0x0000}; + static constexpr HalfWords OpcodeMask{0xfbf0, 0x8000}; + static constexpr HalfWords ImmMask{0x040f, 0x70ff}; + static constexpr HalfWords RegMask{0x0000, 0x0f00}; +}; + +template <> +struct FixupInfo : public FixupInfo { + static constexpr HalfWords Opcode{0xf240, 0x0000}; +}; + +std::optional> readAddendData(Edge::Kind Kind, + const char *FixupPtr, + support::endianness Endian); +std::optional> readAddendArm(Edge::Kind Kind, + const char *FixupPtr); +std::optional> +readAddendThumb(Edge::Kind Kind, const char *FixupPtr, const ArmConfig &ArmCfg); + +inline Expected readAddend(LinkGraph &G, Block &B, const Edge &E, + const ArmConfig &ArmCfg) { + Edge::Kind Kind = E.getKind(); + const char *BlockWorkingMem = B.getContent().data(); + const char *FixupPtr = BlockWorkingMem + E.getOffset(); + + if (Kind <= LastDataRelocation) { + if (auto AddendOrErr = readAddendData(Kind, FixupPtr, G.getEndianness())) + return std::move(*AddendOrErr); + + } else if (Kind <= LastArmRelocation) { + if (auto AddendOrErr = readAddendArm(Kind, FixupPtr)) + return std::move(*AddendOrErr); + + } else if (Kind <= LastThumbRelocation) { + if (auto AddendOrErr = readAddendThumb(Kind, FixupPtr, ArmCfg)) + return std::move(*AddendOrErr); + } + + return make_error( + "In graph " + G.getName() + ", section " + B.getSection().getName() + + " unsupported edge kind " + getEdgeKindName(Kind)); +} + +std::optional applyFixupData(LinkGraph &G, Block &B, const Edge &E, + const ArmConfig &ArmCfg); +std::optional applyFixupArm(LinkGraph &G, Block &B, const Edge &E, + const ArmConfig &ArmCfg); +std::optional applyFixupThumb(LinkGraph &G, Block &B, const Edge &E, + const ArmConfig &ArmCfg); + +/// Apply fixup expression for edge to block content. +inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E, + const ArmConfig &ArmCfg) { + Edge::Kind Kind = E.getKind(); + if (Kind <= LastDataRelocation) { + if (std::optional Err = applyFixupData(G, B, E, ArmCfg)) + return std::move(*Err); + + } else if (Kind <= LastArmRelocation) { + if (std::optional Err = applyFixupArm(G, B, E, ArmCfg)) + return std::move(*Err); + + } else if (Kind <= LastThumbRelocation) { + if (std::optional Err = applyFixupThumb(G, B, E, ArmCfg)) + return std::move(*Err); + } + + return make_error( + "In graph " + G.getName() + ", section " + B.getSection().getName() + + " unsupported edge kind " + getEdgeKindName(Kind)); +} + +/// Stubs builder for a specific StubsFlavor +/// +/// Right now we only have one default stub kind, but we want to extend this +/// and allow creation of specific kinds in the future (e.g. branch range +/// extension or interworking). +/// +/// Let's keep it simple for the moment and not wire this through a GOT. +/// +template +class StubsManager : public TableManager> { +public: + StubsManager() = default; + + /// Name of the object file section that will contain all our stubs. + static StringRef getSectionName() { return "S__STUBS"; } + + /// Implements link-graph traversal via visitExistingEdges(). + bool visitEdge(LinkGraph &G, Block *B, Edge &E) { + if (E.getTarget().isDefined()) + return false; + + switch (E.getKind()) { + case arm::Thumb_Call: + case arm::Thumb_Jump24: { + DEBUG_WITH_TYPE("jitlink", { + dbgs() << " Fixing " << G.getEdgeKindName(E.getKind()) << " edge at " + << B->getFixupAddress(E) << " (" << B->getAddress() << " + " + << formatv("{0:x}", E.getOffset()) << ")\n"; + }); + E.setTarget(this->getEntryForTarget(G, E.getTarget())); + return true; + } + } + return false; + } + + /// Create a branch range extension stub for the class's flavor. + Symbol &createEntry(LinkGraph &G, Symbol &Target); + +private: + /// Create a new node in the link-graph for the given stub template. + template + Block &addStub(LinkGraph &G, const uint8_t (&Code)[Size], + uint64_t Alignment) { + ArrayRef Template(reinterpret_cast(Code), Size); + return G.createContentBlock(getStubsSection(G), Template, + orc::ExecutorAddr(), Alignment, 0); + } + + /// Get or create the object file section that will contain all our stubs. + Section &getStubsSection(LinkGraph &G) { + if (!StubsSection) + StubsSection = &G.createSection(getSectionName(), + orc::MemProt::Read | orc::MemProt::Exec); + return *StubsSection; + } + + Section *StubsSection = nullptr; +}; + +/// Create a branch range extension stub with Thumb encoding for v7 CPUs. +template <> +Symbol &StubsManager::createEntry(LinkGraph &G, Symbol &Target); + +} // namespace arm +} // namespace jitlink +} // namespace llvm + +#endif // LLVM_EXECUTIONENGINE_JITLINK_ARM_H diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/arm.h b/llvm/include/llvm/ExecutionEngine/JITLink/arm.h deleted file mode 100644 --- a/llvm/include/llvm/ExecutionEngine/JITLink/arm.h +++ /dev/null @@ -1,261 +0,0 @@ -//===-- arm.h - Generic JITLink arm/thumb edge kinds, utilities -*- C++ -*-===// -// -// 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. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_EXECUTIONENGINE_JITLINK_ARM_H -#define LLVM_EXECUTIONENGINE_JITLINK_ARM_H - -#include "TableManager.h" -#include "llvm/ExecutionEngine/JITLink/JITLink.h" -#include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" -#include "llvm/Support/ARMBuildAttributes.h" - -namespace llvm { -namespace jitlink { -namespace arm { - -/// ARM/Thumb fixup types. -enum EdgeKind_arm : Edge::Kind { - /// - /// List of relocations applicable in ARM mode - /// - FirstArmRelocation = Edge::FirstRelocation, - - /// Plain 32-bit pointer relocation; optional addend is stored in-place - Arm_Delta32 = FirstArmRelocation, - - /// - /// List of relocations applicable in Thumb mode - /// - FirstThumbRelocation, - - /// PC-relative call instruction (bridges between ARM and Thumb) - Thumb_Call = FirstThumbRelocation, - - /// PC-relative jump instruction - Thumb_Jump24, - - /// Write immediate value to the lower halfword of the destination register - Thumb_MovwAbsNC, - - /// Write immediate value to the top halfword of the destination register - Thumb_MovtAbs, -}; - -/// Human-readable name for a given arm/thumb edge -const char *getEdgeKindName(Edge::Kind K); - -/// Human-readable name for a given ARM CPU architecture kind -const char *getCPUArchName(ARMBuildAttrs::CPUArch K); - -/// Implementation of ARM branch range extension stubs ("veneers") vary with -/// ISA kinds (arm/thumb/interworking), CPU architectures (v4, v6, v7) and -/// branch types (absolute/PC-relative). -enum VeneerKind { - Unsupported = 0, - Thumbv7, -}; - -/// JITLink sub-arch configuration for ARM CPU models -struct ArmConfig { - bool HasBlx = false; - bool HasMovtMovw = false; - bool J1J2BranchEncoding = false; - VeneerKind Veneers = Unsupported; -}; - -/// Obtain the sub-arch configuration for a given ARM CPU model. -inline ArmConfig getArmConfigForCPUArch(ARMBuildAttrs::CPUArch CPUArch) { - ArmConfig ArmCfg; - switch (CPUArch) { - case ARMBuildAttrs::v7: - case ARMBuildAttrs::v8_A: - ArmCfg.HasBlx = true; - ArmCfg.J1J2BranchEncoding = true; - ArmCfg.HasMovtMovw = true; - ArmCfg.Veneers = Thumbv7; - break; - default: - DEBUG_WITH_TYPE("jitlink", { - dbgs() << " Warning: ARM config not defined for CPU architecture " - << getCPUArchName(CPUArch); - }); - break; - } - return ArmCfg; -} - -/// Arm and Thumb branches have a PC bias of 8 and 4 respectively. -inline int64_t getPCBias(Edge::Kind K) { - switch (K) { - case Thumb_Call: - case Thumb_Jump24: - return 4; - default: - return 8; - } -} - -/// Returns extracted bits Val[Hi:Lo]. -inline uint32_t extractBits(uint32_t Val, unsigned Hi, unsigned Lo) { - return (Val & (((1UL << (Hi + 1)) - 1))) >> Lo; -} - -/// Apply fixup expression for edge to block content. -inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E, - const ArmConfig &ArmCfg) { - using namespace support; - - char *BlockWorkingMem = B.getAlreadyMutableContent().data(); - char *FixupPtr = BlockWorkingMem + E.getOffset(); - uint64_t FixupAddress = (B.getAddress() + E.getOffset()).getValue(); - uint64_t TargetAddress = E.getTarget().getAddress().getValue(); - - switch (E.getKind()) { - case Arm_Delta32: { - // Implicit addend is stored in-place. - int64_t Addend = SignExtend64<32>(*(little32_t *)FixupPtr); - int64_t Value = TargetAddress - FixupAddress + Addend; - if (!isInt<32>(Value)) - return makeTargetOutOfRangeError(G, B, E); - *(little32_t *)FixupPtr = Value; - break; - } - case Thumb_Jump24: { - if ((TargetAddress & 0x1) != 0x1) - return make_error("Branch instruction needs interworking " - "veneer when bridging to ARM: " + - StringRef(getEdgeKindName(E.getKind()))); - [[fallthrough]]; - } - case Thumb_Call: { - int64_t Addend = -getPCBias(E.getKind()); - int64_t Value = TargetAddress - FixupAddress + Addend; - - // Least significant byte denotes Thumb state and is not part of the range. - int64_t Distance = Value & ~0x1; - if (ArmCfg.J1J2BranchEncoding ? !isInt<25>(Distance) : !isInt<23>(Distance)) - return makeTargetOutOfRangeError(G, B, E); - - // Value = S:J1:J2:Imm10:Imm11:0 - uint16_t S = extractBits(Value, /*Hi=*/14, /*Lo=*/14); - uint16_t J1 = (((~(Value >> 10)) ^ (Value >> 11)) & 0x2000); - uint16_t J2 = (((~(Value >> 11)) ^ (Value >> 13)) & 0x0800); - uint16_t Imm10 = extractBits(Value, /*Hi=*/11, /*Lo=*/1); - uint16_t Imm11 = extractBits(Value, /*Hi=*/23, /*Lo=*/12); - - uint16_t Opcode = 0xf000; - uint16_t RawLo = 0xd000 & *(ulittle16_t *)(FixupPtr + 2); - - uint32_t Hi = Opcode | S << 10 | Imm11; - uint32_t Lo = RawLo | J1 | J2 | Imm10; - *(ulittle32_t *)FixupPtr = Hi | Lo << 16; - break; - } - case Thumb_MovwAbsNC: { - /// Value = imm4:imm1:imm3:imm8 - int64_t Value = TargetAddress; - uint16_t Imm4 = extractBits(Value, /*Hi=*/15, /*Lo=*/12); - uint16_t Imm1 = extractBits(Value, /*Hi=*/11, /*Lo=*/11); - uint16_t Imm3 = extractBits(Value, /*Hi=*/10, /*Lo=*/8); - uint16_t Imm8 = extractBits(Value, /*Hi=*/7, /*Lo=*/0); - - uint16_t Opcode = 0xf240; - uint16_t RawLo = 0x8f00 & *(ulittle16_t *)(FixupPtr + 2); - - uint32_t Hi = Opcode | Imm1 << 10 | Imm4; - uint32_t Lo = RawLo | Imm3 << 12 | Imm8; - *(ulittle32_t *)FixupPtr = Hi | Lo << 16; - break; - } - case Thumb_MovtAbs: { - /// Value = imm4:imm1:imm3:imm8 - int64_t Value = TargetAddress; - uint16_t Imm4 = extractBits(Value, /*Hi=*/31, /*Lo=*/28); - uint16_t Imm1 = extractBits(Value, /*Hi=*/27, /*Lo=*/27); - uint16_t Imm3 = extractBits(Value, /*Hi=*/26, /*Lo=*/24); - uint16_t Imm8 = extractBits(Value, /*Hi=*/23, /*Lo=*/16); - - uint16_t Opcode = 0xf2c0; - uint16_t RawLo = 0x8f00 & *(ulittle16_t *)(FixupPtr + 2); - - uint32_t Hi = Opcode | Imm1 << 10 | Imm4; - uint32_t Lo = RawLo | Imm3 << 12 | Imm8; - *(ulittle32_t *)FixupPtr = Hi | Lo << 16; - break; - } - default: - return make_error( - "In graph " + G.getName() + ", section " + B.getSection().getName() + - " unsupported edge kind " + getEdgeKindName(E.getKind())); - } - - return Error::success(); -} - -/// Branch range extension stubs builder for a concrete veneer kind. -/// Let's keep it simple for the moment and not wire this through a GOT. -template -class VeneersManager : public TableManager> { -public: - VeneersManager() = default; - - static StringRef getSectionName() { return "Veneer$$Code"; } - - bool visitEdge(LinkGraph &G, Block *B, Edge &E) { - if (E.getTarget().isDefined()) - return false; - - switch (E.getKind()) { - case arm::Thumb_Call: - case arm::Thumb_Jump24: { - DEBUG_WITH_TYPE("jitlink", { - dbgs() << " Fixing " << G.getEdgeKindName(E.getKind()) << " edge at " - << B->getFixupAddress(E) << " (" << B->getAddress() << " + " - << formatv("{0:x}", E.getOffset()) << ")\n"; - }); - E.setTarget(this->getEntryForTarget(G, E.getTarget())); - return true; - } - } - return false; - } - - Symbol &createEntry(LinkGraph &G, Symbol &Target); - -private: - template - Block &addStub(LinkGraph &G, const uint8_t (&Code)[Size], - uint64_t Alignment) { - ArrayRef Template(reinterpret_cast(Code), Size); - return G.createContentBlock(getStubsSection(G), Template, - orc::ExecutorAddr(), Alignment, 0); - } - - Section &getStubsSection(LinkGraph &G) { - if (!StubsSection) - StubsSection = &G.createSection(getSectionName(), - orc::MemProt::Read | orc::MemProt::Exec); - return *StubsSection; - } - - Section *StubsSection = nullptr; -}; - -/// Define an explicit specialization for each veneer kind. -template <> -Symbol &VeneersManager::createEntry(LinkGraph &G, Symbol &Target); - -} // namespace arm -} // namespace jitlink -} // namespace llvm - -#endif // LLVM_EXECUTIONENGINE_JITLINK_ARM_H diff --git a/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt b/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt --- a/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt +++ b/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt @@ -20,8 +20,8 @@ # ELF ELF.cpp ELFLinkGraphBuilder.cpp + ELF_aarch32.cpp ELF_aarch64.cpp - ELF_arm.cpp ELF_i386.cpp ELF_loongarch.cpp ELF_riscv.cpp @@ -34,8 +34,8 @@ COFF_x86_64.cpp # Architectures: + aarch32.cpp aarch64.cpp - arm.cpp i386.cpp loongarch.cpp riscv.cpp diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF.cpp --- a/llvm/lib/ExecutionEngine/JITLink/ELF.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/ELF.cpp @@ -13,8 +13,8 @@ #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_arm.h" #include "llvm/ExecutionEngine/JITLink/ELF_i386.h" #include "llvm/ExecutionEngine/JITLink/ELF_loongarch.h" #include "llvm/ExecutionEngine/JITLink/ELF_riscv.h" @@ -94,7 +94,9 @@ link_ELF_aarch64(std::move(G), std::move(Ctx)); return; case Triple::arm: + case Triple::armeb: case Triple::thumb: + case Triple::thumbeb: link_ELF_arm(std::move(G), std::move(Ctx)); return; case Triple::loongarch32: diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_arm.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp rename from llvm/lib/ExecutionEngine/JITLink/ELF_arm.cpp rename to llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp --- a/llvm/lib/ExecutionEngine/JITLink/ELF_arm.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp @@ -10,14 +10,17 @@ // //===----------------------------------------------------------------------===// -#include "llvm/ExecutionEngine/JITLink/ELF_arm.h" +#include "llvm/ExecutionEngine/JITLink/ELF_aarch32.h" + #include "llvm/BinaryFormat/ELF.h" #include "llvm/ExecutionEngine/JITLink/DWARFRecordSectionSplitter.h" #include "llvm/ExecutionEngine/JITLink/JITLink.h" -#include "llvm/ExecutionEngine/JITLink/arm.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/Support/MathExtras.h" #include "llvm/TargetParser/ARMTargetParser.h" #include "EHFrameSupportImpl.h" @@ -50,14 +53,28 @@ } }; -template -class ELFLinkGraphBuilder_arm : public ELFLinkGraphBuilder { +template +class ELFLinkGraphBuilder_arm + : public ELFLinkGraphBuilder> { private: + using ELFT = object::ELFType; + + // using signed16_t = support::little16_t; + // using signed32_t = support::little32_t; + // using unsigned16_t = support::ulittle16_t; + // using unsigned32_t = support::ulittle32_t; + + // static constexpr support::endianness DataEndianness = support::little; + // static constexpr support::endianness DataEndianness = + // EndianFromELFT::Endian; + static Expected getRelocationKind(const uint32_t Type) { using namespace arm; switch (Type) { case ELF::R_ARM_REL32: - return Arm_Delta32; + return Data_Delta32; + case ELF::R_ARM_CALL: + return Arm_Call; case ELF::R_ARM_THM_CALL: return Thumb_Call; case ELF::R_ARM_THM_JUMP24: @@ -69,7 +86,7 @@ } return make_error( - "Unsupported arm relocation:" + formatv("{0:d}: ", Type) + + "Unsupported arm relocation: " + formatv("{0:d}: ", Type) + object::getELFRelocationTypeName(ELF::EM_ARM, Type)); } @@ -82,21 +99,21 @@ LLVM_DEBUG(dbgs() << "Processing relocations:\n"); using Base = ELFLinkGraphBuilder; - using Self = ELFLinkGraphBuilder_arm; + using Self = ELFLinkGraphBuilder_arm; for (const auto &RelSect : Base::Sections) { auto Name = Base::Obj.getSectionName(RelSect, Base::SectionStringTab); assert(Name && "We traversed section names before in graphifySections()"); if (Name && !excludeSectionByName(*Name)) - if (Error Err = Base::forEachRelRelocation(RelSect, this, - &Self::addSingleRelocation)) + if (Error Err = Base::forEachRelRelocation( + RelSect, this, &Self::addSingleRelRelocation)) return Err; } return Error::success(); } - Error addSingleRelocation(const typename ELFT::Rel &Rel, - const typename ELFT::Shdr &FixupSect, - Block &BlockToFix) { + Error addSingleRelRelocation(const typename ELFT::Rel &Rel, + const typename ELFT::Shdr &FixupSect, + Block &BlockToFix) { using Base = ELFLinkGraphBuilder; uint32_t SymbolIndex = Rel.getSymbol(false); @@ -118,31 +135,40 @@ if (!Kind) return Kind.takeError(); - int64_t Addend = 0; // 'Rel' relocs read implicit addend from fixup address auto FixupAddress = orc::ExecutorAddr(FixupSect.sh_addr) + Rel.r_offset; Edge::OffsetT Offset = FixupAddress - BlockToFix.getAddress(); - Edge GE(*Kind, Offset, *GraphSymbol, Addend); + Edge E(*Kind, Offset, *GraphSymbol, 0); + + using Base = ELFLinkGraphBuilder; + Expected Addend = arm::readAddend(*Base::G, BlockToFix, E, ArmCfg); + if (!Addend) + return Addend.takeError(); + + E.setAddend(*Addend); LLVM_DEBUG({ dbgs() << " "; - printEdge(dbgs(), BlockToFix, GE, arm::getEdgeKindName(*Kind)); + printEdge(dbgs(), BlockToFix, E, arm::getEdgeKindName(*Kind)); dbgs() << "\n"; }); - BlockToFix.addEdge(std::move(GE)); + BlockToFix.addEdge(std::move(E)); return Error::success(); } + ArmConfig ArmCfg; + public: ELFLinkGraphBuilder_arm(StringRef FileName, const object::ELFFile &Obj, - Triple TT) + Triple TT, ArmConfig ArmCfg) : ELFLinkGraphBuilder(Obj, std::move(TT), FileName, - arm::getEdgeKindName) {} + arm::getEdgeKindName), + ArmCfg(std::move(ArmCfg)) {} }; -template Error buildTables_ELF_arm(LinkGraph &G) { +template Error buildTables_ELF_arm(LinkGraph &G) { LLVM_DEBUG(dbgs() << "Visiting edges in graph:\n"); - arm::VeneersManager PLT; + arm::StubsManager PLT; visitExistingEdges(G, PLT); return Error::success(); } @@ -171,10 +197,12 @@ using namespace ARMBuildAttrs; auto Arch = static_cast(ARM::getArchAttr(AK)); + arm::ArmConfig ArmCfg; switch (Arch) { case v7: case v8_A: - assert(getArmConfigForCPUArch(Arch).Veneers != arm::Unsupported && + ArmCfg = getArmConfigForCPUArch(Arch); + assert(ArmCfg.Stubs != arm::Unsupported && "Provide a ARM config for each supported CPU"); break; case Pre_v4: @@ -200,10 +228,24 @@ StringRef(arm::getCPUArchName(Arch))); } - auto &ELFObjFile = cast>(**ELFObj); - return ELFLinkGraphBuilder_arm((*ELFObj)->getFileName(), - ELFObjFile.getELFFile(), TT) - .buildGraph(); + switch (TT.getArch()) { + case Triple::arm: + case Triple::thumb: { + auto &ELFObjFile = cast>(**ELFObj); + return ELFLinkGraphBuilder_arm( + (*ELFObj)->getFileName(), ELFObjFile.getELFFile(), TT, ArmCfg) + .buildGraph(); + } + case Triple::armeb: + case Triple::thumbeb: { + auto &ELFObjFile = cast>(**ELFObj); + return ELFLinkGraphBuilder_arm( + (*ELFObj)->getFileName(), ELFObjFile.getELFFile(), TT, ArmCfg) + .buildGraph(); + } + default: + llvm_unreachable("Invalid arch type for JITLink ELF/arm"); + } } void link_ELF_arm(std::unique_ptr G, @@ -223,8 +265,7 @@ else PassCfg.PrePrunePasses.push_back(markAllSymbolsLive); - // ARM branch range extension stubs are commonly called veneers. - switch (ArmCfg.Veneers) { + switch (ArmCfg.Stubs) { case arm::Thumbv7: PassCfg.PostPrunePasses.push_back(buildTables_ELF_arm); break; diff --git a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp @@ -0,0 +1,467 @@ +//===----- arm.cpp - Generic JITLink arm/thumb edge kinds, 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/ExecutionEngine/JITSymbol.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/Endian.h" + +#define DEBUG_TYPE "jitlink" + +namespace llvm { +namespace jitlink { +namespace arm { + +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). +class ThumbRelocation { +public: + /// Create a read-only reference to a Thumb32 fixup + ThumbRelocation(const char *FixupPtr) + : Hi{*reinterpret_cast(FixupPtr)}, + Lo{*reinterpret_cast(FixupPtr + 2)} {} + + /// Create a writable reference to a Thumb32 fixup + ThumbRelocation(char *FixupPtr) + : ThumbRelocation(static_cast(FixupPtr)) { + Writable = true; + } + + void setLo(uint16_t Imm) { + assert(Writable && "Create with mutable FixupPtr"); + const_cast(Lo) = Imm; + } + void setHi(uint16_t Imm) { + assert(Writable && "Create with mutable FixupPtr"); + const_cast(Hi) = Imm; + } + + const support::ulittle16_t &Hi; // First halfword + const support::ulittle16_t &Lo; // Second halfword + +private: + bool Writable{false}; +}; + +Error makeUnexpectedOpcodeError(Edge::Kind Kind, const ThumbRelocation &R) { + return make_error( + "Invalid opcode for '" + StringRef(getEdgeKindName(Kind)) + + "' relocation: [ 0x" +utohexstr(R.Hi, true, 4) + ", 0x" + + utohexstr(R.Lo, true, 4) + " ]"); +} + +template bool checkOpcode(const ThumbRelocation &R) { + uint16_t Hi = R.Hi & FixupInfo::OpcodeMask.Hi; + uint16_t Lo = R.Lo & FixupInfo::OpcodeMask.Lo; + return Hi == FixupInfo::Opcode.Hi && Lo == FixupInfo::Opcode.Lo; +} + +template +bool checkRegister(const ThumbRelocation &R, HalfWords Reg) { + uint16_t Hi = R.Hi & FixupInfo::RegMask.Hi; + uint16_t Lo = R.Lo & FixupInfo::RegMask.Lo; + return Hi == Reg.Hi && Lo == Reg.Lo; +} + +template +bool writeRegister(ThumbRelocation &R, HalfWords Reg) { + static constexpr HalfWords Mask = FixupInfo::RegMask; + assert((Mask.Hi & Reg.Hi) == Reg.Hi && (Mask.Hi & Reg.Hi) == Reg.Hi && + "Value bits exceed bit range of given mask"); + R.setHi((R.Hi & ~Mask.Hi) | Reg.Hi); + R.setLo((R.Lo & ~Mask.Lo) | Reg.Lo); +} + +template +void writeImmediate(ThumbRelocation &R, HalfWords Imm) { + static constexpr HalfWords Mask = FixupInfo::ImmMask; + assert((Mask.Hi & Imm.Hi) == Imm.Hi && (Mask.Hi & Imm.Hi) == Imm.Hi && + "Value bits exceed bit range of given mask"); + R.setHi((R.Hi & ~Mask.Hi) | Imm.Hi); + R.setLo((R.Lo & ~Mask.Lo) | Imm.Lo); +} + +std::optional> +readAddendData(Edge::Kind Kind, const char *FixupPtr, endianness Endian) { + assert(Endian != native && "Declare explicitly as little or big"); + switch (Kind) { + case Data_Delta32: + return SignExtend64<32>((Endian == little) ? read32(FixupPtr) + : read32(FixupPtr)); + default: + break; + } + return std::nullopt; +} + +std::optional> readAddendArm(Edge::Kind Kind, + const char *FixupPtr) { + return std::nullopt; +} + +std::optional> readAddendThumb(Edge::Kind Kind, + const char *FixupPtr, + const ArmConfig &ArmCfg) { + ThumbRelocation R(FixupPtr); + switch (Kind) { + case Thumb_Call: + if (!checkOpcode(R)) + return makeUnexpectedOpcodeError(Kind, R); + if ((R.Lo & FixupInfo::LoBitNoBlx) == 0 && + (R.Lo & FixupInfo::LoBitH) != 0) + return make_error( + "Least significant bit H must be clear in " + "second halfword for encoding BLX T2: " + + llvm::utohexstr(R.Lo)); + return LLVM_LIKELY(ArmCfg.J1J2BranchEncoding) + ? decodeImmBT4BlT1BlxT2_J1J2(R.Hi, R.Lo) + : decodeImmBT4BlT1BlxT2(R.Hi, R.Lo); + + case Thumb_Jump24: + if (!checkOpcode(R)) + return makeUnexpectedOpcodeError(Kind, R); + if (R.Lo & FixupInfo::LoBitConditional) + return make_error("Relocation expects an unconditional " + "B.W branch instruction: " + + StringRef(getEdgeKindName(Kind))); + return decodeImmBT4BlT1BlxT2_J1J2(R.Hi, R.Lo); + + case Thumb_MovwAbsNC: + if (!checkOpcode(R)) + return makeUnexpectedOpcodeError(Kind, R); + // Initial addend is interpreted as a signed value + return SignExtend64<16>(decodeImmMovtT1MovwT3(R.Hi, R.Lo)); + + case Thumb_MovtAbs: + if (!checkOpcode(R)) + return makeUnexpectedOpcodeError(Kind, R); + // Initial addend is interpreted as a signed value + return SignExtend64<16>(decodeImmMovtT1MovwT3(R.Hi, R.Lo)); + + default: + break; + } + return std::nullopt; +} + +std::optional applyFixupData(LinkGraph &G, Block &B, const Edge &E, + const ArmConfig &ArmCfg) { + 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(Value); + if (LLVM_LIKELY(Endian == little)) + endian::write32(FixupPtr, Imm); + else + endian::write32(FixupPtr, Imm); + }; + + Edge::Kind Kind = E.getKind(); + uint64_t FixupAddress = (B.getAddress() + E.getOffset()).getValue(); + uint64_t TargetAddress = E.getTarget().getAddress().getValue(); + int64_t Addend = E.getAddend(); + + // 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: + break; + } + return std::nullopt; +} + +std::optional applyFixupArm(LinkGraph &G, Block &B, const Edge &E, + const ArmConfig &ArmCfg) { + return std::nullopt; +} + +std::optional applyFixupThumb(LinkGraph &G, Block &B, const Edge &E, + const ArmConfig &ArmCfg) { + char *BlockWorkingMem = B.getAlreadyMutableContent().data(); + ThumbRelocation R(BlockWorkingMem + E.getOffset()); + + Edge::Kind Kind = E.getKind(); + uint64_t FixupAddress = (B.getAddress() + E.getOffset()).getValue(); + uint64_t TargetAddress = E.getTarget().getAddress().getValue(); + int64_t Addend = E.getAddend(); + + switch (Kind) { + case Thumb_Jump24: { + if (!checkOpcode(R)) + return makeUnexpectedOpcodeError(Kind, R); + if (R.Lo & FixupInfo::LoBitConditional) + return make_error("Relocation expects an unconditional " + "B.W branch instruction: " + + StringRef(getEdgeKindName(Kind))); + if ((TargetAddress & 0x1) == 0) + return make_error("Branch relocation needs interworking " + "veneer when bridging to ARM: " + + StringRef(getEdgeKindName(Kind))); + if (!ArmCfg.J1J2BranchEncoding) + return make_error( + "Relocation doesn't support branch " + "encoding without J1/J2 range extensions: " + + StringRef(getEdgeKindName(Kind))); + + int64_t Value = TargetAddress - FixupAddress + Addend; + if (!isInt<25>(Value)) + return makeTargetOutOfRangeError(G, B, E); + + writeImmediate(R, encodeImmBT4BlT1BlxT2_J1J2(Value)); + return Error::success(); + } + + case Thumb_Call: { + if (!checkOpcode(R)) + return makeUnexpectedOpcodeError(Kind, R); + int64_t Value = TargetAddress - FixupAddress + Addend; + if (ArmCfg.J1J2BranchEncoding ? !isInt<25>(Value) : !isInt<22>(Value)) + return makeTargetOutOfRangeError(G, B, E); + + bool TargetIsThumb = Value & 0x1; + bool InstIsBlx = (R.Lo & FixupInfo::LoBitNoBlx) == 0; + uint16_t BlxBitsToClear = + FixupInfo::LoBitNoBlx | FixupInfo::LoBitH; + + if (!TargetIsThumb && InstIsBlx) { + // Fix range value: (1) accounts for 4-byte alignment of destination while + // instruction may only be 2-byte aligned and (2) clears Thumb bit. + Value = alignTo(Value, 4); + R.setLo(R.Lo & ~BlxBitsToClear); + InstIsBlx = true; + } + + writeImmediate(R, LLVM_LIKELY(ArmCfg.J1J2BranchEncoding) + ? encodeImmBT4BlT1BlxT2_J1J2(Value) + : encodeImmBT4BlT1BlxT2(Value)); + + assert((!InstIsBlx || (R.Lo & BlxBitsToClear) == 0) && + "Avoid UB in BLX T2"); + return Error::success(); + } + + case Thumb_MovwAbsNC: { + if (!checkOpcode(R)) + return makeUnexpectedOpcodeError(Kind, R); + uint16_t Value = (TargetAddress + Addend) & 0xffff; + writeImmediate(R, encodeImmMovtT1MovwT3(Value)); + return Error::success(); + } + + case Thumb_MovtAbs: { + if (!checkOpcode(R)) + return makeUnexpectedOpcodeError(Kind, R); + uint16_t Value = ((TargetAddress + Addend) >> 16) & 0xffff; + writeImmediate(R, encodeImmMovtT1MovwT3(Value)); + return Error::success(); + } + + default: + break; + } + return std::nullopt; +} + +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::createEntry(LinkGraph &G, Symbol &Target) { + constexpr uint64_t Alignment = 4; + Block &B = addStub(G, Thumbv7ABS, Alignment); + assert(checkRegister(B.getContent().data(), + encodeRegMovtT1MovwT3(12)) && + checkRegister(B.getContent().data() + 4, + encodeRegMovtT1MovwT3(12)) && + "Linker generated veneers may only corrupt register r12 (IP)"); + B.addEdge(Thumb_MovwAbsNC, 0, Target, 0); + B.addEdge(Thumb_MovtAbs, 4, Target, 0); + return G.addAnonymousSymbol(B, 0x1, B.getSize(), true, false); +} + +const char *getEdgeKindName(Edge::Kind K) { + switch (K) { + case Data_Delta32: + return "R_ARM_REL32"; + case Arm_Call: + return "R_ARM_CALL"; + case Thumb_Call: + return "R_ARM_THM_CALL"; + case Thumb_Jump24: + return "R_ARM_THM_JUMP24"; + case Thumb_MovwAbsNC: + return "R_ARM_THM_MOVW_ABS_NC"; + case Thumb_MovtAbs: + return "R_ARM_THM_MOVT_ABS"; + default: + return getGenericEdgeKindName(K); + } +} + +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 arm +} // namespace jitlink +} // namespace llvm diff --git a/llvm/lib/ExecutionEngine/JITLink/arm.cpp b/llvm/lib/ExecutionEngine/JITLink/arm.cpp deleted file mode 100644 --- a/llvm/lib/ExecutionEngine/JITLink/arm.cpp +++ /dev/null @@ -1,88 +0,0 @@ -//===----- arm.cpp - Generic JITLink arm/thumb edge kinds, 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/arm.h" -#include "llvm/BinaryFormat/ELF.h" - -#define DEBUG_TYPE "jitlink" - -namespace llvm { -namespace jitlink { -namespace arm { - -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 &VeneersManager::createEntry(LinkGraph &G, Symbol &Target) { - constexpr uint64_t Alignment = 4; - Block &B = addStub(G, Thumbv7ABS, Alignment); - B.addEdge(arm::Thumb_MovwAbsNC, 0, Target, 0); - B.addEdge(arm::Thumb_MovtAbs, 4, Target, 0); - return G.addAnonymousSymbol(B, 0, B.getSize(), true, false); -} - -const char *getEdgeKindName(Edge::Kind K) { - switch (K) { - case Arm_Delta32: - return "R_ARM_REL32"; - case Thumb_Call: - return "R_ARM_THM_CALL"; - case Thumb_Jump24: - return "R_ARM_THM_JUMP24"; - case Thumb_MovwAbsNC: - return "R_ARM_THM_MOVW_ABS_NC"; - case Thumb_MovtAbs: - return "R_ARM_THM_MOVT_ABS"; - default: - return getGenericEdgeKindName(K); - } -} - -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 arm -} // namespace jitlink -} // namespace llvm diff --git a/llvm/test/ExecutionEngine/JITLink/arm/ELF_thumbv7_printf.s b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_thumbv7_printf.s rename from llvm/test/ExecutionEngine/JITLink/arm/ELF_thumbv7_printf.s rename to llvm/test/ExecutionEngine/JITLink/AArch32/ELF_thumbv7_printf.s --- a/llvm/test/ExecutionEngine/JITLink/arm/ELF_thumbv7_printf.s +++ b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_thumbv7_printf.s @@ -36,5 +36,5 @@ .type .L.str,%object .section .rodata.str1.1,"aMS",%progbits,1 .L.str: - .asciz "Hello arm!\n" + .asciz "Hello AArch32!\n" .size .L.str, 12 diff --git a/llvm/test/ExecutionEngine/JITLink/arm/lit.local.cfg b/llvm/test/ExecutionEngine/JITLink/AArch32/lit.local.cfg rename from llvm/test/ExecutionEngine/JITLink/arm/lit.local.cfg rename to llvm/test/ExecutionEngine/JITLink/AArch32/lit.local.cfg diff --git a/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp b/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp @@ -0,0 +1,116 @@ +//===------- AArch32Tests.cpp - Unit tests for the AArch32 backend --------===// +// +// 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 + +#include "gtest/gtest.h" + +namespace llvm { +namespace jitlink { +namespace arm { + +HalfWords encodeImmBT4BlT1BlxT2(int64_t Value); +HalfWords encodeImmBT4BlT1BlxT2_J1J2(int64_t Value); +HalfWords encodeImmMovtT1MovwT3(uint32_t Value); +HalfWords encodeRegMovtT1MovwT3(int64_t Value); + +int64_t decodeImmBT4BlT1BlxT2(uint32_t Hi, uint32_t Lo); +int64_t decodeImmBT4BlT1BlxT2_J1J2(uint32_t Hi, uint32_t Lo); +uint32_t decodeImmMovtT1MovwT3(uint32_t Hi, uint32_t Lo); +int64_t decodeRegMovtT1MovwT3(uint32_t Hi, uint32_t Lo); + +} // namespace arm +} // namespace jitlink +} // namespace llvm + +using namespace llvm; +using namespace llvm::jitlink; +using namespace llvm::jitlink::arm; +using namespace llvm::support; +using namespace llvm::support::endian; + +template +static HalfWords makeHalfWords(std::array Mem) { + return HalfWords{read16(Mem.data()), read16(Mem.data() + 2)}; +} + +/// 25-bit branch with link (with J1J2 range extension) +TEST(AArch32Relocations, Thumb_Call_J1J2) { + static_assert(isInt<25>(16777215) && !isInt<25>(16777217), + "Max and first overflow"); + static_assert(isInt<25>(-16777215) && !isInt<25>(-16777217), + "Min and first underflow"); + + constexpr HalfWords ImmMask = FixupInfo::ImmMask; + + static std::array MemPresets{ + makeHalfWords({0xff, 0xf7, 0xfe, 0xef}), // common + makeHalfWords({0x00, 0x00, 0x00, 0x00}), // zeros + makeHalfWords({0xff, 0xff, 0xff, 0xff}), // ones + }; + + auto EncodeDecode = [](int64_t In, HalfWords &Mem) { + HalfWords Imm = encodeImmBT4BlT1BlxT2_J1J2(In); + HalfWords Patched((Mem.Hi & ~ImmMask.Hi) | Imm.Hi, + (Mem.Lo & ~ImmMask.Lo) | Imm.Lo); + return decodeImmBT4BlT1BlxT2_J1J2(Patched.Hi, Patched.Lo); + }; + + for (HalfWords Mem : MemPresets) { + HalfWords UnaffectedBits(Mem.Hi & ~ImmMask.Hi, Mem.Lo & ~ImmMask.Lo); + + EXPECT_EQ(EncodeDecode(1, Mem), 0); // Zero value (LSB was thumb bit) + EXPECT_EQ(EncodeDecode(0x41, Mem), 0x40); // Common value + EXPECT_EQ(EncodeDecode(16777215, Mem), 16777214); // Maximum value + EXPECT_EQ(EncodeDecode(-16777215, Mem), -16777216); // Minimum value + EXPECT_EQ(EncodeDecode(16777217, Mem), -16777216); // First overflow + EXPECT_EQ(EncodeDecode(-16777217, Mem), 16777214); // First underflow + + EXPECT_TRUE(UnaffectedBits.Hi == (Mem.Hi & ~ImmMask.Hi) && + UnaffectedBits.Lo == (Mem.Lo & ~ImmMask.Lo)) + << "Diff outside immediate field"; + } +} + +/// 22-bit branch with link (without J1J2 range extension) +TEST(AArch32Relocations, Thumb_Call_Old) { + static_assert(isInt<22>(2097151) && !isInt<22>(2097153), + "Max and first overflow"); + static_assert(isInt<22>(-2097151) && !isInt<22>(-2097153), + "Min and first underflow"); + + constexpr HalfWords ImmMask = FixupInfo::ImmMask; + + static std::array MemPresets{ + makeHalfWords({0xff, 0xf7, 0xfe, 0xef}), // common + makeHalfWords({0x00, 0x00, 0x00, 0x00}), // zeros + makeHalfWords({0xff, 0xff, 0xff, 0xff}), // ones + }; + + auto EncodeDecode = [](int64_t In, HalfWords &Mem) { + HalfWords Imm = encodeImmBT4BlT1BlxT2(In); + HalfWords Patched((Mem.Hi & ~ImmMask.Hi) | Imm.Hi, + (Mem.Lo & ~ImmMask.Lo) | Imm.Lo); + return decodeImmBT4BlT1BlxT2(Patched.Hi, Patched.Lo); + }; + + for (HalfWords Mem : MemPresets) { + HalfWords UnaffectedBits(Mem.Hi & ~ImmMask.Hi, Mem.Lo & ~ImmMask.Lo); + + EXPECT_EQ(EncodeDecode(1, Mem), 0); // Zero value (LSB was thumb bit) + EXPECT_EQ(EncodeDecode(0x41, Mem), 0x40); // Common value + EXPECT_EQ(EncodeDecode(2097151, Mem), 2097150); // Maximum value + EXPECT_EQ(EncodeDecode(-2097151, Mem), -2097152); // Minimum value + EXPECT_EQ(EncodeDecode(2097153, Mem), -2097152); // First overflow + EXPECT_EQ(EncodeDecode(-2097153, Mem), 2097150); // First underflow + + EXPECT_TRUE(UnaffectedBits.Hi == (Mem.Hi & ~ImmMask.Hi) && + UnaffectedBits.Lo == (Mem.Lo & ~ImmMask.Lo)) + << "Diff outside immediate field"; + } +} diff --git a/llvm/unittests/ExecutionEngine/JITLink/AArch32_test.h b/llvm/unittests/ExecutionEngine/JITLink/AArch32_test.h new file mode 100644 --- /dev/null +++ b/llvm/unittests/ExecutionEngine/JITLink/AArch32_test.h @@ -0,0 +1,5 @@ + +#ifndef LLVM_UNITTESTS_EXECUTIONENGINE_JITLINK_AARCH32_H +#define LLVM_UNITTESTS_EXECUTIONENGINE_JITLINK_AARCH32_H + +#endif // LLVM_UNITTESTS_EXECUTIONENGINE_JITLINK_AARCH32_H diff --git a/llvm/unittests/ExecutionEngine/JITLink/CMakeLists.txt b/llvm/unittests/ExecutionEngine/JITLink/CMakeLists.txt --- a/llvm/unittests/ExecutionEngine/JITLink/CMakeLists.txt +++ b/llvm/unittests/ExecutionEngine/JITLink/CMakeLists.txt @@ -8,6 +8,7 @@ ) add_llvm_unittest(JITLinkTests + AArch32Tests.cpp EHFrameSupportTests.cpp LinkGraphTests.cpp )