The Target Triple
To generate code for llc, we should be able to invoke it as follows:
llc -mtriple=mipsnova test.ll -o test.sWe will register our target as mipsnova in LLVM for this to work.
Architectures and SubArchitectures
Sub-architectures of an architecture are used to represent different versions of
the same architecture. For example, mips has the sub architecture mipsr6 which
corresponds to the MIPS Release 6 architecture. Release 6 differs from previous releases
wherein it adds new and removes few instructions.
See the list of SubArchitectures
↳ class Triple {
LastArchType = ve }; enum SubArchType { NoSubArch,
ARMSubArch_v9_6a, ARMSubArch_v9_5a, ARMSubArch_v9_4a, ARMSubArch_v9_3a, ARMSubArch_v9_2a, ARMSubArch_v9_1a,Triples
The canonical name for a system type has the form cpu-vendor-os.
This is the target triple.
This comes from the autoconf tool, which can make decisions based on the
system type.
Example: i686-pc-linux-gnu
Breaking down,
i686is the CPU architecture, which is a 32-bit Intel x86.pc-linuxis the vendor, which is a generic PC.gnuis the operating system, which is GNU/Linux.
Find your system configuration name
To find your system configuration name, run this command:
bash llvm/cmake/config.guessx86_64-unknown-linux-gnu
To explore further, you can look into this
snippet from the config.guess file:
UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknownUNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknownUNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknownUNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknownecho "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}"x86_64:Linux:5.15.153.1-microsoft-standard-WSL2:#1 SMP Fri Mar 29 23:14:13 UTC 2024
The Triple class parses the architecture, sub-architecture, vendor, os and environment
fields from the string passed in the command line (or in the LLVM module).
Registering our target triple
The CPU architecture type for MipsNova will be mipsnova. Recall that this is
a 32-bit architecture.
Triple in llc
Let’s start by looking at the llc driver.
static cl::opt<std::string>TargetTriple("mtriple", cl::desc("Override target triple for module"));...
static int compileModule(...) { if (!TargetTriple.empty()) IRTargetTriple = Triple::normalize(TargetTriple); TheTriple = Triple(IRTargetTriple);}We see that the normalize method is called on the Triple object.
This rearranges the triple to its canonical form (cpu-vendor-os) by parsing a -
separated string that may have the fields in a different order.
To add our arch name mipsnova to the list of known architectures, we need to
modify a few methods in Triple.
More info: Parsing the triple string
The constructor for Triple takes a string and parses it with these functions.
Right now we are interested in the parseArch method.
SmallVector<StringRef, 4> Components; StringRef(Data).split(Components, '-', /*MaxSplit*/ 3); if (Components.size() > 0) { Arch = parseArch(Components[0]); SubArch = parseSubArch(Components[0]); if (Components.size() > 1) { Vendor = parseVendor(Components[1]); if (Components.size() > 2) { OS = parseOS(Components[2]); if (Components.size() > 3) { Environment = parseEnvironment(Components[3]); ObjectFormat = parseFormat(Components[3]); } } } else { Environment =For that, we add mipsnova to the ArchType enum.
↳ class Triple {
↳ enum ArchType {
mips64el, // MIPS64EL: mips64el, mips64r6el, mipsn32el, mipsn32r6el msp430, // MSP430: msp430 mipsnova, // MipsNova: basically same as mips ppc, // PPC: powerpc ppcle, // PPCLE: powerpc (little endian)Next, we have to add the enum to string conversion for parsing and printing.
case mips: return "mips"; case mipsel: return "mipsel"; case mipsnova: return "mipsnova"; case msp430: return "msp430"; case nvptx64: return "nvptx64"; .Case("mips64", mips64) .Case("mips64el", mips64el) .Case("mipsnova", mipsnova) .Case("msp430", msp430) .Case("ppc64", ppc64) .Cases("mipsel", "mipsallegrexel", "mipsisa32r6el", "mipsr6el", Triple::mipsel) .Case("mipsnova", Triple::mipsnova) .Cases("mips64", "mips64eb", "mipsn32", "mipsisa64r6", "mips64r6", "mipsn32r6", Triple::mips64)Since mipsnova is a 32-bit architecture, we mention that in getArchPointerBitWidth.
case llvm::Triple::mips: case llvm::Triple::mipsel: case llvm::Triple::mipsnova: case llvm::Triple::nvptx: case llvm::Triple::ppc:mipsnova is already 32 bit, so add it after mipsel in get32BitArchVariant.
case Triple::mips: case Triple::mipsel: case Triple::mipsnova: case Triple::nvptx: case Triple::ppc: case Triple::lanai: case Triple::m68k: case Triple::mipsnova: case Triple::msp430: case Triple::r600:Why is this required?
I am not sure myself. It seems like this is used for the LLVM JIT engine but if you know more, please let me know (the GitHub repo url is at the top-right of this page!).
Setting up the target in CMake
The target files live in the llvm/lib/Target/ directory.
We add our directory Nova here and add our target
the list of all targets in llvm/CMakeLists.txt.
LoongArch Mips Nova MSP430 NVPTXCreate the CMakeLists.txt file in our Nova directory.
add_llvm_component_group(Nova)
add_llvm_target(NovaCodeGen MipsNovaTargetMachine.cpp
LINK_COMPONENTS MC TargetParser Target
ADD_TO_COMPONENT Nova )
# tablegen(LLVM NovaGenRegisterInfo.inc -gen-register-info)Finish by adding our source file MipsNovaTargetMachine.cpp to the same directory.
We will get back to this later to fill in the required methods. Right now, this will
enable us to compile llc with mipsnova.
#include "llvm/MC/TargetRegistry.h"#include "llvm/Support/Compiler.h"#include "llvm/TargetParser/Triple.h"
using namespace llvm;
static llvm::Target &getTheNovaTarget() { static Target TheNovaTarget; return TheNovaTarget;}
extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeNovaTargetInfo() { llvm::RegisterTarget<llvm::Triple::mipsnova> X(::getTheNovaTarget(), "mipsnova", "MipsNova (32-bit big endian)", "Nova");};
extern "C" void LLVMInitializeNovaTargetMC() { // TODO: Add initialize target MC}
extern "C" void LLVMInitializeNovaTarget() { // TODO: Add initialize target}Build LLVM with our target
Now, build LLVM with the Nova target.
cd llvm-project
cmake -S llvm -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_BUILD_TESTS=ON \ -DLLVM_PARALLEL_LINK_JOBS=8 \ -DLLVM_TARGETS_TO_BUILD='X86;Nova'
cmake --build build --target llcNow we can see our target listed in the llc help output like so:
build/bin/llc -version...mipsel - MIPS (32-bit little endian)mipsnova - MipsNova (32-bit big endian)x86 - 32-bit X86: Pentium-Pro and above...
Running llc with mipsnova
You can try running the following:
build/bin/llc -mtriple=mipsnova < /dev/nullPLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.Stack dump:0. Program arguments: build/bin/llc -mtriple=mipsnova#0 0x000056117674c440 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (build/bin/llc+0x1dce440)...
What happened?
The target machine for mipsnova is not implemented yet. We will get to that in the next section.
Before registering the target, we would get this error:
llc: error: unable to get target for 'mipsnova', see --version and --triple.Compiling won’t work yet because we haven’t implemented the target machine for mipsnova,
but llc now accepts our target triple!