[JITLink][RISCV] Initial Support RISCV64 in JITLink
authorluxufan <932494295@qq.com>
Mon, 5 Jul 2021 12:04:17 +0000 (20:04 +0800)
committerluxufan <932494295@qq.com>
Fri, 23 Jul 2021 15:47:30 +0000 (23:47 +0800)
This patch is the initial support, it implements translation from object file to JIT link graph, and very few relocations were supported. Currently, the test file ELF_pc_indirect.s is passed, the HelloWorld program(compiled with mno-relax flag) can be linked correctly and run on instruction emulator correctly.

In the downstream implementation, I have implemented the GOT, PLT function, and EHFrame and some optimization will be implement soon. I will organize the code in to patches, then gradually send it to upstream.

Differential Revision: https://reviews.llvm.org/D105429

llvm/include/llvm/ExecutionEngine/JITLink/ELF_riscv.h [new file with mode: 0644]
llvm/include/llvm/ExecutionEngine/JITLink/riscv.h [new file with mode: 0644]
llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt
llvm/lib/ExecutionEngine/JITLink/ELF.cpp
llvm/lib/ExecutionEngine/JITLink/ELF_riscv.cpp [new file with mode: 0644]
llvm/test/ExecutionEngine/JITLink/RISCV/ELF_pc_indirect.s [new file with mode: 0644]

diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/ELF_riscv.h b/llvm/include/llvm/ExecutionEngine/JITLink/ELF_riscv.h
new file mode 100644 (file)
index 0000000..1339ab5
--- /dev/null
@@ -0,0 +1,38 @@
+//===----- ELF_riscv.h - JIT link functions for ELF/riscv ----*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+//===----------------------------------------------------------------------===//
+//
+// jit-link functions for ELF/riscv.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_JITLINK_ELF_RISCV_H
+#define LLVM_EXECUTIONENGINE_JITLINK_ELF_RISCV_H
+
+#include "llvm/ExecutionEngine/JITLink/JITLink.h"
+
+namespace llvm {
+namespace jitlink {
+
+/// Create a LinkGraph from an ELF/riscv relocatable object
+///
+/// Note: The graph does not take ownership of the underlying buffer, nor copy
+/// its contents. The caller is responsible for ensuring that the object buffer
+/// outlives the graph.
+Expected<std::unique_ptr<LinkGraph>>
+createLinkGraphFromELFObject_riscv(MemoryBufferRef ObjectBuffer);
+
+/// jit-link the given object buffer, which must be a ELF riscv object file.
+void link_ELF_riscv(std::unique_ptr<LinkGraph> G,
+                    std::unique_ptr<JITLinkContext> Ctx);
+
+} // end namespace jitlink
+} // end namespace llvm
+
+#endif // LLVM_EXECUTIONENGINE_JITLINK_ELF_RISCV64_H
diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/riscv.h b/llvm/include/llvm/ExecutionEngine/JITLink/riscv.h
new file mode 100644 (file)
index 0000000..a4509f3
--- /dev/null
@@ -0,0 +1,84 @@
+//===--  riscv.h  - Generic JITLink riscv 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 riscv objects.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_JITLINK_RISCV_H
+#define LLVM_EXECUTIONENGINE_JITLINK_RISCV_H
+
+#include "llvm/ExecutionEngine/JITLink/JITLink.h"
+
+namespace llvm {
+namespace jitlink {
+namespace riscv {
+
+/// Represets riscv fixups
+enum EdgeKind_riscv : Edge::Kind {
+
+  // TODO: Capture and replace to generic fixups
+  /// A plain 32-bit pointer value relocation
+  ///
+  /// Fixup expression:
+  ///   Fixup <= Target + Addend : uint32
+  ///
+  R_RISCV_32 = Edge::FirstRelocation,
+
+  /// A plain 64-bit pointer value relocation
+  ///
+  /// Fixup expression:
+  ///   Fixup <- Target + Addend : uint32
+  ///
+  R_RISCV_64,
+
+  /// High 20 bits of 32-bit pointer value relocation
+  ///
+  /// Fixup expression
+  ///   Fixup <- (Target + Addend + 0x800) >> 12
+  R_RISCV_HI20,
+
+  /// Low 12 bits of 32-bit pointer value relocation
+  ///
+  /// Fixup expression
+  ///   Fixup <- (Target + Addend) & 0xFFF
+  R_RISCV_LO12_I,
+  /// High 20 bits of PC relative relocation
+  ///
+  /// Fixup expression:
+  ///   Fixup <- (Target - Fixup + Addend + 0x800) >> 12
+  R_RISCV_PCREL_HI20,
+
+  /// Low 12 bits of PC relative relocation, used by I type instruction format
+  ///
+  /// Fixup expression:
+  ///   Fixup <- (Target - Fixup + Addend) & 0xFFF
+  R_RISCV_PCREL_LO12_I,
+
+  /// Low 12 bits of PC relative relocation, used by S type instruction format
+  ///
+  /// Fixup expression:
+  ///   Fixup <- (Target - Fixup + Addend) & 0xFFF
+  R_RISCV_PCREL_LO12_S,
+
+  /// PC relative call
+  ///
+  /// Fixup expression:
+  ///   Fixup <- (Target - Fixup + Addend)
+  R_RISCV_CALL
+
+};
+
+/// Returns a string name for the given riscv edge. For debugging purposes
+/// only
+const char *getEdgeKindName(Edge::Kind K);
+} // namespace riscv
+} // namespace jitlink
+} // namespace llvm
+
+#endif
index ab4b24a..eedcdb9 100644 (file)
@@ -16,9 +16,11 @@ add_llvm_component_library(LLVMJITLink
 
   ELF.cpp
   ELFLinkGraphBuilder.cpp
+  ELF_riscv.cpp
   ELF_x86_64.cpp
 
   # Architectures:
+  riscv.cpp
   x86_64.cpp
 
   ADDITIONAL_HEADER_DIRS
index 3a270d8..252e44f 100644 (file)
@@ -14,6 +14,7 @@
 #include "llvm/ExecutionEngine/JITLink/ELF.h"
 
 #include "llvm/BinaryFormat/ELF.h"
+#include "llvm/ExecutionEngine/JITLink/ELF_riscv.h"
 #include "llvm/ExecutionEngine/JITLink/ELF_x86_64.h"
 #include "llvm/Object/ELF.h"
 #include "llvm/Support/Endian.h"
@@ -64,6 +65,8 @@ createLinkGraphFromELFObject(MemoryBufferRef ObjectBuffer) {
     return TargetMachineArch.takeError();
 
   switch (*TargetMachineArch) {
+  case ELF::EM_RISCV:
+    return createLinkGraphFromELFObject_riscv(ObjectBuffer);
   case ELF::EM_X86_64:
     return createLinkGraphFromELFObject_x86_64(ObjectBuffer);
   default:
@@ -76,6 +79,10 @@ createLinkGraphFromELFObject(MemoryBufferRef ObjectBuffer) {
 void link_ELF(std::unique_ptr<LinkGraph> G,
               std::unique_ptr<JITLinkContext> Ctx) {
   switch (G->getTargetTriple().getArch()) {
+  case Triple::riscv32:
+  case Triple::riscv64:
+    link_ELF_riscv(std::move(G), std::move(Ctx));
+    return;
   case Triple::x86_64:
     link_ELF_x86_64(std::move(G), std::move(Ctx));
     return;
diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_riscv.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_riscv.cpp
new file mode 100644 (file)
index 0000000..d0e65ef
--- /dev/null
@@ -0,0 +1,315 @@
+//===------- ELF_riscv.cpp -JIT linker implementation for ELF/riscv -------===//
+//
+// 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/riscv jit-link implementation.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ExecutionEngine/JITLink/ELF_riscv.h"
+#include "llvm/ExecutionEngine/JITLink/JITLink.h"
+#include "llvm/ExecutionEngine/JITLink/riscv.h"
+#include "llvm/Object/ELF.h"
+#include "llvm/Object/ELFObjectFile.h"
+
+#include "ELFLinkGraphBuilder.h"
+#include "JITLinkGeneric.h"
+
+#define DEBUG_TYPE "jitlink"
+using namespace llvm;
+
+namespace llvm {
+namespace jitlink {
+
+static Expected<const Edge &> getRISCVPCRelHi20(const Edge &E) {
+  using namespace riscv;
+  assert((E.getKind() == R_RISCV_PCREL_LO12_I ||
+          E.getKind() == R_RISCV_PCREL_LO12_S) &&
+         "Can only have high relocation for R_RISCV_PCREL_LO12_I or "
+         "R_RISCV_PCREL_LO12_S");
+
+  const Symbol &Sym = E.getTarget();
+  const Block &B = Sym.getBlock();
+  JITTargetAddress Offset = Sym.getOffset();
+
+  struct Comp {
+    bool operator()(const Edge &Lhs, JITTargetAddress Offset) {
+      return Lhs.getOffset() < Offset;
+    }
+    bool operator()(JITTargetAddress Offset, const Edge &Rhs) {
+      return Offset < Rhs.getOffset();
+    }
+  };
+
+  auto Bound =
+      std::equal_range(B.edges().begin(), B.edges().end(), Offset, Comp{});
+
+  for (auto It = Bound.first; It != Bound.second; ++It) {
+    if (It->getKind() == R_RISCV_PCREL_HI20)
+      return *It;
+  }
+
+  return make_error<JITLinkError>(
+      "No HI20 PCREL relocation type be found for LO12 PCREL relocation type");
+}
+
+static uint32_t extractBits(uint64_t Num, unsigned High, unsigned Low) {
+  return (Num & ((1ULL << (High + 1)) - 1)) >> Low;
+}
+
+class ELFJITLinker_riscv : public JITLinker<ELFJITLinker_riscv> {
+  friend class JITLinker<ELFJITLinker_riscv>;
+
+public:
+  ELFJITLinker_riscv(std::unique_ptr<JITLinkContext> Ctx,
+                     std::unique_ptr<LinkGraph> G, PassConfiguration PassConfig)
+      : JITLinker(std::move(Ctx), std::move(G), std::move(PassConfig)) {}
+
+private:
+  Error applyFixup(LinkGraph &G, Block &B, const Edge &E) const {
+    using namespace riscv;
+    using namespace llvm::support;
+
+    char *BlockWorkingMem = B.getAlreadyMutableContent().data();
+    char *FixupPtr = BlockWorkingMem + E.getOffset();
+    JITTargetAddress FixupAddress = B.getAddress() + E.getOffset();
+    switch (E.getKind()) {
+    case R_RISCV_HI20: {
+      int64_t Value = E.getTarget().getAddress() + E.getAddend();
+      int32_t Hi = (Value + 0x800) & 0xFFFFF000;
+      uint32_t RawInstr = *(little32_t *)FixupPtr;
+      *(little32_t *)FixupPtr = (RawInstr & 0xFFF) | static_cast<uint32_t>(Hi);
+      break;
+    }
+    case R_RISCV_LO12_I: {
+      int64_t Value = E.getTarget().getAddress() + E.getAddend();
+      int32_t Lo = Value & 0xFFF;
+      uint32_t RawInstr = *(little32_t *)FixupPtr;
+      *(little32_t *)FixupPtr =
+          (RawInstr & 0xFFFFF) | (static_cast<uint32_t>(Lo & 0xFFF) << 20);
+      break;
+    }
+    case R_RISCV_CALL: {
+      int64_t Value = E.getTarget().getAddress() + E.getAddend() - FixupAddress;
+      int32_t Hi = (Value + 0x800) & 0xFFFFF000;
+      int32_t Lo = Value & 0xFFF;
+      uint32_t RawInstrAuipc = *(little32_t *)FixupPtr;
+      uint32_t RawInstrJalr = *(little32_t *)(FixupPtr + 4);
+      *(little32_t *)FixupPtr = RawInstrAuipc | static_cast<uint32_t>(Hi);
+      *(little32_t *)(FixupPtr + 4) =
+          RawInstrJalr | (static_cast<uint32_t>(Lo) << 20);
+      break;
+    }
+    case R_RISCV_PCREL_HI20: {
+      int64_t Value = E.getTarget().getAddress() + E.getAddend() - FixupAddress;
+      int32_t Hi = (Value + 0x800) & 0xFFFFF000;
+      uint32_t RawInstr = *(little32_t *)FixupPtr;
+      *(little32_t *)FixupPtr = (RawInstr & 0xFFF) | static_cast<uint32_t>(Hi);
+      break;
+    }
+    case R_RISCV_PCREL_LO12_I: {
+      auto RelHI20 = getRISCVPCRelHi20(E);
+      if (!RelHI20)
+        return RelHI20.takeError();
+      int64_t Value = RelHI20->getTarget().getAddress() +
+                      RelHI20->getAddend() - E.getTarget().getAddress();
+      int64_t Lo = Value & 0xFFF;
+      uint32_t RawInstr = *(little32_t *)FixupPtr;
+      *(little32_t *)FixupPtr =
+          (RawInstr & 0xFFFFF) | (static_cast<uint32_t>(Lo & 0xFFF) << 20);
+      break;
+    }
+    case R_RISCV_PCREL_LO12_S: {
+      auto RelHI20 = getRISCVPCRelHi20(E);
+      int64_t Value = RelHI20->getTarget().getAddress() +
+                      RelHI20->getAddend() - E.getTarget().getAddress();
+      int64_t Lo = Value & 0xFFF;
+      uint32_t Imm31_25 = extractBits(Lo, 11, 5) << 25;
+      uint32_t Imm11_7 = extractBits(Lo, 4, 0) << 7;
+      uint32_t RawInstr = *(little32_t *)FixupPtr;
+
+      *(little32_t *)FixupPtr = (RawInstr & 0x1FFF07F) | Imm31_25 | Imm11_7;
+      break;
+    }
+    }
+    return Error::success();
+  }
+};
+
+template <typename ELFT>
+class ELFLinkGraphBuilder_riscv : public ELFLinkGraphBuilder<ELFT> {
+private:
+  static Expected<riscv::EdgeKind_riscv>
+  getRelocationKind(const uint32_t Type) {
+    using namespace riscv;
+    switch (Type) {
+    case ELF::R_RISCV_32:
+      return EdgeKind_riscv::R_RISCV_32;
+    case ELF::R_RISCV_64:
+      return EdgeKind_riscv::R_RISCV_64;
+    case ELF::R_RISCV_HI20:
+      return EdgeKind_riscv::R_RISCV_HI20;
+    case ELF::R_RISCV_LO12_I:
+      return EdgeKind_riscv::R_RISCV_LO12_I;
+    case ELF::R_RISCV_CALL:
+      return EdgeKind_riscv::R_RISCV_CALL;
+    case ELF::R_RISCV_PCREL_HI20:
+      return EdgeKind_riscv::R_RISCV_PCREL_HI20;
+    case ELF::R_RISCV_PCREL_LO12_I:
+      return EdgeKind_riscv::R_RISCV_PCREL_LO12_I;
+    case ELF::R_RISCV_PCREL_LO12_S:
+      return EdgeKind_riscv::R_RISCV_PCREL_LO12_S;
+    }
+
+    return make_error<JITLinkError>("Unsupported riscv relocation:" +
+                                    formatv("{0:d}", Type));
+  }
+
+  Error addRelocations() override {
+    using Base = ELFLinkGraphBuilder<ELFT>;
+    LLVM_DEBUG(dbgs() << "Adding relocations\n");
+
+    // TODO a partern is forming of iterate some sections but only give me
+    // ones I am interested, I should abstract that concept some where
+    for (auto &SecRef : Base::Sections) {
+      if (SecRef.sh_type != ELF::SHT_RELA && SecRef.sh_type != ELF::SHT_REL)
+        continue;
+      auto RelSectName = Base::Obj.getSectionName(SecRef);
+      if (!RelSectName)
+        return RelSectName.takeError();
+
+      LLVM_DEBUG({
+        dbgs() << "Adding relocations from section " << *RelSectName << "\n";
+      });
+
+      auto UpdateSection = Base::Obj.getSection(SecRef.sh_info);
+      if (!UpdateSection)
+        return UpdateSection.takeError();
+
+      auto UpdateSectionName = Base::Obj.getSectionName(**UpdateSection);
+      if (!UpdateSectionName)
+        return UpdateSectionName.takeError();
+      // Don't process relocations for debug sections.
+      if (Base::isDwarfSection(*UpdateSectionName)) {
+        LLVM_DEBUG({
+          dbgs() << "  Target is dwarf section " << *UpdateSectionName
+                 << ". Skipping.\n";
+        });
+        continue;
+      } else
+        LLVM_DEBUG({
+          dbgs() << "  For target section " << *UpdateSectionName << "\n";
+        });
+
+      auto *JITSection = Base::G->findSectionByName(*UpdateSectionName);
+      if (!JITSection)
+        return make_error<llvm::StringError>(
+            "Refencing a section that wasn't added to graph" +
+                *UpdateSectionName,
+            llvm::inconvertibleErrorCode());
+
+      auto Relocations = Base::Obj.relas(SecRef);
+      if (!Relocations)
+        return Relocations.takeError();
+
+      for (const auto &Rela : *Relocations) {
+        auto Type = Rela.getType(false);
+
+        LLVM_DEBUG({
+          dbgs() << "Relocation Type: " << Type << "\n"
+                 << "Name: " << Base::Obj.getRelocationTypeName(Type) << "\n";
+        });
+
+        auto SymbolIndex = Rela.getSymbol(false);
+        auto Symbol = Base::Obj.getRelocationSymbol(Rela, Base::SymTabSec);
+        if (!Symbol)
+          return Symbol.takeError();
+
+        auto BlockToFix = *(JITSection->blocks().begin());
+        auto *TargetSymbol = Base::getGraphSymbol(SymbolIndex);
+
+        if (!TargetSymbol) {
+          return make_error<llvm::StringError>(
+              "Could not find symbol at given index, did you add it to "
+              "JITSymbolTable? index: " +
+                  std::to_string(SymbolIndex) + ", shndx: " +
+                  std::to_string((*Symbol)->st_shndx) + " Size of table: " +
+                  std::to_string(Base::GraphSymbols.size()),
+              llvm::inconvertibleErrorCode());
+        }
+        int64_t Addend = Rela.r_addend;
+        JITTargetAddress FixupAddress =
+            (*UpdateSection)->sh_addr + Rela.r_offset;
+
+        LLVM_DEBUG({
+          dbgs() << "Processing relocation at "
+                 << format("0x%016" PRIx64, FixupAddress) << "\n";
+        });
+        auto Kind = getRelocationKind(Type);
+        if (!Kind)
+          return Kind.takeError();
+
+        BlockToFix->addEdge(*Kind, FixupAddress - BlockToFix->getAddress(),
+                            *TargetSymbol, Addend);
+      }
+    }
+    return Error::success();
+  }
+
+public:
+  ELFLinkGraphBuilder_riscv(StringRef FileName,
+                            const object::ELFFile<ELFT> &Obj, const Triple T)
+      : ELFLinkGraphBuilder<ELFT>(Obj, std::move(T), FileName,
+                                  riscv::getEdgeKindName) {}
+};
+
+Expected<std::unique_ptr<LinkGraph>>
+createLinkGraphFromELFObject_riscv(MemoryBufferRef ObjectBuffer) {
+  LLVM_DEBUG({
+    dbgs() << "Building jitlink graph for new input "
+           << ObjectBuffer.getBufferIdentifier() << "...\n";
+  });
+
+  auto ELFObj = object::ObjectFile::createELFObjectFile(ObjectBuffer);
+  if (!ELFObj)
+    return ELFObj.takeError();
+
+  if ((*ELFObj)->getArch() == Triple::riscv64) {
+    auto &ELFObjFile = cast<object::ELFObjectFile<object::ELF64LE>>(**ELFObj);
+    return ELFLinkGraphBuilder_riscv<object::ELF64LE>(
+               (*ELFObj)->getFileName(), ELFObjFile.getELFFile(),
+               (*ELFObj)->makeTriple())
+        .buildGraph();
+  } else {
+    assert((*ELFObj)->getArch() == Triple::riscv32 &&
+           "Invalid triple for RISCV ELF object file");
+    auto &ELFObjFile = cast<object::ELFObjectFile<object::ELF32LE>>(**ELFObj);
+    return ELFLinkGraphBuilder_riscv<object::ELF32LE>(
+               (*ELFObj)->getFileName(), ELFObjFile.getELFFile(),
+               (*ELFObj)->makeTriple())
+        .buildGraph();
+  }
+}
+
+void link_ELF_riscv(std::unique_ptr<LinkGraph> G,
+                    std::unique_ptr<JITLinkContext> Ctx) {
+  PassConfiguration Config;
+  const Triple &TT = G->getTargetTriple();
+  if (Ctx->shouldAddDefaultTargetPasses(TT)) {
+    if (auto MarkLive = Ctx->getMarkLivePass(TT))
+      Config.PrePrunePasses.push_back(std::move(MarkLive));
+    else
+      Config.PrePrunePasses.push_back(markAllSymbolsLive);
+  }
+  if (auto Err = Ctx->modifyPassConfig(*G, Config))
+    return Ctx->notifyFailed(std::move(Err));
+
+  ELFJITLinker_riscv::link(std::move(Ctx), std::move(G), std::move(Config));
+}
+
+} // namespace jitlink
+} // namespace llvm
diff --git a/llvm/test/ExecutionEngine/JITLink/RISCV/ELF_pc_indirect.s b/llvm/test/ExecutionEngine/JITLink/RISCV/ELF_pc_indirect.s
new file mode 100644 (file)
index 0000000..875c8c8
--- /dev/null
@@ -0,0 +1,39 @@
+# RUN: rm -rf %t && mkdir -p %t
+# RUN: llvm-mc -triple=riscv64 -position-independent -filetype=obj -o %t/elf_riscv64_sm_pic_reloc.o %s
+# RUN: llvm-mc -triple=riscv32 -position-independent -filetype=obj -o %t/elf_riscv32_sm_pic_reloc.o %s
+# RUN: llvm-jitlink -noexec -slab-allocate 100Kb -slab-address 0xfff00000 \
+# RUN:              -check %s %t/elf_riscv64_sm_pic_reloc.o
+# RUN: llvm-jitlink -noexec -slab-allocate 100Kb -slab-address 0xfff00000 \
+# RUN:              -check %s %t/elf_riscv32_sm_pic_reloc.o
+#
+# Test ELF small/PIC relocations
+
+        .text
+        .file   "testcase.c"
+
+# Empty main entry point.
+        .globl  main
+        .p2align  1
+        .type   main,@function
+main:
+        ret
+
+        .size   main, .-main
+
+# Test R_RISCV_PCREL_HI20 and R_RISCV_PCREL_LO
+# jitlink-check: decode_operand(test_pcrel32, 1) = ((named_data - test_pcrel32) + 0x800)[31:12] 
+        .globl  test_pcrel32
+        .p2align  1
+        .type   test_pcrel32,@function
+test_pcrel32:
+        auipc a0, %pcrel_hi(named_data)
+        lw  a0, %pcrel_lo(test_pcrel32)(a0)
+
+        .size   test_pcrel32, .-test_pcrel32
+
+        .data
+        .type   named_data,@object
+        .p2align  1
+named_data:
+        .quad   42
+        .size   named_data, 4