Difference between revisions of "NaplesPU Tools"

From NaplesPU Documentation
Jump to: navigation, search
 
(16 intermediate revisions by 2 users not shown)
Line 1: Line 1:
A new backend for llvm needs to be added and registered. After registration, llvm tools are able to lookup and use the new target at runtime.
+
NaplesPU is provided with a set of additional tools in order to fully support the compilation process of an application.  
In the following, we will show which files are involved in the registration phase and which changes are required.  
 
  
==== CMakeLists.txt ====
+
== elf2hex ==
<syntaxhighlight lang="cpp" line='line'>
+
elf2hex is the tool required for elf code transformation. It works on an input ELF code to produce a hex output file as the memory image to be written on device memory. NaplesPU relies on its own implementation of the elf2hex tool, located in elf2hex.cpp. In our purposes, the tool produces two different outputs:
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+
* MANGO compliant memory image, a 512-bit little-endian aligned image ready to be flashed on the MANGO platform;
set(CMAKE_INSTALL_PREFIX "/usr/local/llvm-nuplus/" CACHE PATH "NuPlusLLVM install prefix" FORCE)
+
* Standalone compliant memory image, a 512-bit little-endian aligned image to be used in a standalone environment.
endif()
 
</syntaxhighlight>
 
This specifies the installation path that, in our case, is: "/usr/local/llvm-nuplus/".
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
set(LLVM_ALL_TARGETS
 
  AArch64
 
  AMDGPU
 
  ARM
 
  BPF
 
  Hexagon
 
  Mips
 
  MSP430
 
  NVPTX
 
  PowerPC
 
  Sparc
 
  SystemZ
 
  X86
 
  XCore
 
  NuPlus
 
  )
 
</syntaxhighlight>
 
The target name should be added to the LLVM_ALL_TARGETS list.
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
set(LLVM_TARGETS_TO_BUILD "NuPlus" CACHE STRING "Semicolon-separated list of targets to build, or \"all\".")
 
</syntaxhighlight>
 
In a standard version of llvm, the LLVM_TARGETS_TO_BUILD variable is set to "all" in order to compile llvm with all the provided target backends. In out custom compiler, we set this variable to "NuPlus". In this way the compiler is built just targeting the NuPlus architecture.
 
  
<syntaxhighlight lang="cpp" line='line'>
+
== Disassembler ==
set(LLVM_DEFAULT_TARGET_TRIPLE "nuplus-none-none" CACHE STRING "Default target for which LLVM will generate code." )
+
A disassembler attempts to revert back the assembly code from the binary one. In order to be able to support NaplesPU code disassembly, LLVM requires to implement a Disassembler class. The implementation is provided in [[NaplesPUDisassembler.cpp]]. Disassembly methods rely on auto-generated ''decoder tables'' from TableGen files. TableGen also offers a way to force a specific decoder method to be called, specifying the ''DecoderMethod'' field on instructions.
</syntaxhighlight>
 
Finally, we remove the possibility of targeting a different architecture then NuPlus.
 
  
==== cmake/config-ix.cmake ====
+
<syntaxhighlight>
<syntaxhighlight lang="cpp" line='line'>
+
def V16MEMri : Operand<v16i32> {
if (LLVM_NATIVE_ARCH MATCHES "i[2-6]86")
+
  let EncoderMethod = "encodeMemoryOpValue";
  set(LLVM_NATIVE_ARCH X86)
+
  let DecoderMethod = "decodeVectorWMemoryOpValue";
 
...
 
...
elseif (LLVM_NATIVE_ARCH MATCHES "nuplus")
+
}
  set(LLVM_NATIVE_ARCH NuPlus)
 
</syntaxhighlight>
 
 
 
==== include/llvm/ADT/Triple.h ====
 
<syntaxhighlight lang="cpp" line='line'>
 
enum ArchType {
 
    UnknownArch,
 
    arm,            // ARM (little endian): arm, armv.*, xscale
 
    ...
 
    nuplus,        // NuPlus
 
</syntaxhighlight>
 
We add NuPlus to the ArchType list.
 
 
 
==== include/llvm/IR/Intrinsics.td ====
 
<syntaxhighlight lang="cpp" line='line'>
 
include "llvm/IR/IntrinsicsNuPlus.td"
 
</syntaxhighlight>
 
In order to use target-specific intrinsics, we include the path to IntrinsicsNuPlus.td, i.e. the file containing the NuPlus intrinsics.
 
 
 
==== include/llvm/Object/ELFObjectFile.h ====
 
<syntaxhighlight lang="cpp" line='line'>
 
StringRef ELFObjectFile<ELFT>::getFileFormatName() const {
 
...
 
  case ELF::EM_NUPLUS:
 
    return "ELF32-nuplus";
 
</syntaxhighlight>
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
unsigned ELFObjectFile<ELFT>::getArch() const {
 
...
 
  case ELF::EM_NUPLUS:
 
    return Triple::nuplus;
 
</syntaxhighlight>
 
 
 
==== include/llvm/Object/RelocVisitor.h ====
 
<syntaxhighlight lang="cpp" line='line'>
 
RelocToApply visitELF(uint32_t RelocType, RelocationRef R, uint64_t Value) {
 
  if (ObjToVisit.getBytesInAddress() == 8) {
 
    ...
 
  } else if (ObjToVisit.getBytesInAddress() == 4) {
 
    switch (ObjToVisit.getArch()) {
 
    ...
 
      case Triple::nuplus:
 
        switch (RelocType) {
 
        case llvm::ELF::R_NUPLUS_ABS32:
 
          return visitELF_NUPLUS_ABS32(R, Value);
 
        case llvm::ELF::R_NUPLUS_BRANCH:
 
          return visitELF_NUPLUS_BRANCH(R, Value);
 
        case llvm::ELF::R_NUPLUS_PCREL_LEA:
 
          return visitELF_NUPLUS_PCREL_LEA(R, Value);
 
        default:
 
          HasError = true;
 
          return RelocToApply();
 
        }
 
</syntaxhighlight>
 
Add the different kind of supported relocations inside the function visitELF for 32-bit object file.
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
  //NuPlus ELF
 
  RelocToApply visitELF_NUPLUS_ABS32(RelocationRef R, uint64_t Value) {
 
    int64_t Addend = getELFAddend(R);
 
    int64_t Res =  Value + Addend;
 
    // Overflow check allows for both signed and unsigned interpretation.
 
    if (Res < INT32_MIN || Res > UINT32_MAX)
 
      HasError = true;
 
    return RelocToApply(static_cast<uint32_t>(Res), 4);
 
  }
 
  RelocToApply visitELF_NUPLUS_BRANCH(RelocationRef R, uint64_t Value) {
 
    int64_t Res =  Value + getELFAddend(R) - R.getOffset();
 
    return RelocToApply(static_cast<uint32_t>(Res), 4);
 
  }
 
  RelocToApply visitELF_NUPLUS_PCREL_LEA(RelocationRef R, uint64_t Value) {
 
    int64_t Res =  Value + getELFAddend(R) - R.getOffset();
 
    return RelocToApply(static_cast<uint32_t>(Res), 4);
 
  }
 
</syntaxhighlight>
 
Implement one visitELF function for each case in the above switch statement.
 
 
 
==== include/llvm/Support/ELF.h ====
 
<syntaxhighlight lang="cpp" line='line'>
 
enum {
 
  EM_NONE          = 0, // No machine
 
  ...
 
  EM_NUPLUS        = 1000, // NuPlus
 
</syntaxhighlight>
 
Add the NuPlus architecture to the list of registered ELF machine architectures. Note that the number is arbitrarily chosen, but different architectures must have different numbers.
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
// ELF relocation types for NuPlus
 
enum {
 
#include "ELFRelocs/NuPlus.def"
 
};
 
</syntaxhighlight>
 
We also include the file supporting the NuPlus specific relocation types.
 
 
 
==== include/llvm/Support/ELFRelocs/NuPlus.def ====
 
<syntaxhighlight lang="cpp" line='line'>
 
#ifndef ELF_RELOC
 
#error "ELF_RELOC must be defined"
 
#endif
 
 
 
ELF_RELOC(R_NUPLUS_NONE,                  0x00)
 
ELF_RELOC(R_NUPLUS_ABS32,                  0x01)
 
ELF_RELOC(R_NUPLUS_BRANCH,                0x02)
 
ELF_RELOC(R_NUPLUS_PCREL_MEM,              0x03)
 
ELF_RELOC(R_NUPLUS_PCREL_MEM_EXT,          0x04)
 
ELF_RELOC(R_NUPLUS_PCREL_LEA,              0x05)
 
</syntaxhighlight>
 
The different kind of relocations of NuPlus are specified in this file.
 
 
 
==== lib/MC/MCObjectFileInfo.cpp ====
 
<syntaxhighlight lang="cpp" line='line'>
 
void MCObjectFileInfo::initELFMCObjectFileInfo(const Triple &T) {
 
  ...
 
  case Triple::nuplus:
 
    FDECFIEncoding = dwarf::DW_EH_PE_sdata4; //4 bytes signed value
 
    break;
 
  ...
 
  case Triple::nuplus:
 
    PersonalityEncoding = dwarf::DW_EH_PE_absptr;
 
    LSDAEncoding = dwarf::DW_EH_PE_absptr;
 
    TTypeEncoding = dwarf::DW_EH_PE_absptr;
 
</syntaxhighlight>
 
We included the NuPlus DWARF extensions to the initELFMCObjectFileInfo function. In particular, the FDECFIEncoding is used for the Frame Description Entry (FDE) and Call Frame Information (CFI) records of the .eh_frame section. The LSDAEncoding is used for the language-specific data area (LSDA). DW_EH_PE_sdata4 means a 4 bytes signed value, while in DW_EH_PE_absptr the value is a literal pointer whose size is determined by the architecture. For more information, check [https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA/dwarfext.html DWARF Extensions] and [https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA/ehframechpt.html Exception Frames]
 
 
 
==== lib/Object/ELF.cpp ====
 
<syntaxhighlight lang="cpp" line='line'>
 
StringRef getELFRelocationTypeName(uint32_t Machine, uint32_t Type) {
 
  switch (Machine) {
 
  ...
 
  case ELF::EM_NUPLUS:
 
    switch (Type) {
 
      #include "llvm/Support/ELFRelocs/NuPlus.def"
 
    default:
 
      break;
 
    }
 
    break;
 
</syntaxhighlight>
 
 
 
==== lib/ObjectYAML/ELFYAML.cpp ====
 
<syntaxhighlight lang="cpp" line='line'>
 
ScalarEnumerationTraits<ELFYAML::ELF_EM>::enumeration(IO &IO, ELFYAML::ELF_EM &Value) {
 
  ...
 
  ECase(EM_NUPLUS)
 
</syntaxhighlight>
 
  
==== lib/Support/Triple.cpp ====
+
static DecodeStatus decodeVectorWMemoryOpValue(...)
<syntaxhighlight lang="cpp" line='line'>
+
{
const char *Triple::getArchTypeName(ArchType Kind) {
+
   return decodeMemoryOpValue(Inst, Insn, Address, Decoder,
   switch (Kind) {
+
        NaplesPU::VR512WRegClassID);
  ...
+
}
  case nuplus:         return "nuplus";
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
+
== Object File Dumper ==
 
+
LLVM provides a custom tool to work as an object file dumper, that is a utility that prints the contents of object files and of the final linked image. For this reason, it is primarily used to debug the generated machine code. In the LLVM implementation, the ''llvm-objdump'' binary relies on the Disassembler classes, in order to revert the binary code into the assembly back.
<syntaxhighlight lang="cpp" line='line'>
 
const char *Triple::getArchTypePrefix(ArchType Kind) {
 
  switch (Kind) {
 
  ...
 
  case nuplus:      return "nuplus";
 
</syntaxhighlight>
 
 
 
 
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
Triple::ArchType Triple::getArchTypeForLLVMName(StringRef Name) {
 
  ...
 
  .Case("nuplus", nuplus)
 
</syntaxhighlight>
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
static Triple::ArchType parseArch(StringRef ArchName) {
 
  ...
 
  .Case("nuplus", Triple::nuplus)
 
</syntaxhighlight>
 
 
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
static Triple::ObjectFormatType getDefaultFormat(const Triple &T) {
 
  switch (T.getArch()) {
 
  ...
 
  case Triple::nuplus:
 
    return Triple::ELF;
 
</syntaxhighlight>
 
 
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
static unsigned getArchPointerBitWidth(llvm::Triple::ArchType Arch) {
 
  switch (Arch) {
 
  ...
 
  case llvm::Triple::nuplus:
 
    return 32;
 
</syntaxhighlight>
 
 
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
Triple Triple::get32BitArchVariant() const {
 
  Triple T(*this);
 
  switch (getArch()) {
 
  ...
 
  case Triple::nuplus:
 
    // Already 32-bit.
 
    break;
 
</syntaxhighlight>
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
Triple Triple::get64BitArchVariant() const {
 
  Triple T(*this);
 
  switch (getArch()) {
 
  ...
 
  case Triple::nuplus:
 
    T.setArch(UnknownArch);
 
    break;
 
</syntaxhighlight>
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
Triple Triple::getBigEndianArchVariant() const {
 
  ...
 
  switch (getArch()) {
 
  ...
 
  case Triple::nuplus:
 
    T.setArch(UnknownArch);
 
    break;
 
</syntaxhighlight>
 
NuPlus is intentionally unsupported here
 
 
 
<syntaxhighlight lang="cpp" line='line'>
 
bool Triple::isLittleEndian() const {
 
  switch (getArch()) {
 
  ...
 
  case Triple::nuplus:
 
    return true;
 
</syntaxhighlight>
 

Latest revision as of 17:22, 21 June 2019

NaplesPU is provided with a set of additional tools in order to fully support the compilation process of an application.

elf2hex

elf2hex is the tool required for elf code transformation. It works on an input ELF code to produce a hex output file as the memory image to be written on device memory. NaplesPU relies on its own implementation of the elf2hex tool, located in elf2hex.cpp. In our purposes, the tool produces two different outputs:

  • MANGO compliant memory image, a 512-bit little-endian aligned image ready to be flashed on the MANGO platform;
  • Standalone compliant memory image, a 512-bit little-endian aligned image to be used in a standalone environment.

Disassembler

A disassembler attempts to revert back the assembly code from the binary one. In order to be able to support NaplesPU code disassembly, LLVM requires to implement a Disassembler class. The implementation is provided in NaplesPUDisassembler.cpp. Disassembly methods rely on auto-generated decoder tables from TableGen files. TableGen also offers a way to force a specific decoder method to be called, specifying the DecoderMethod field on instructions.

def V16MEMri : Operand<v16i32> {
  let EncoderMethod = "encodeMemoryOpValue";
  let DecoderMethod = "decodeVectorWMemoryOpValue";
...
}

static DecodeStatus decodeVectorWMemoryOpValue(...) 
{
  return decodeMemoryOpValue(Inst, Insn, Address, Decoder,
         NaplesPU::VR512WRegClassID);
}

Object File Dumper

LLVM provides a custom tool to work as an object file dumper, that is a utility that prints the contents of object files and of the final linked image. For this reason, it is primarily used to debug the generated machine code. In the LLVM implementation, the llvm-objdump binary relies on the Disassembler classes, in order to revert the binary code into the assembly back.