The Target Triple
To generate code for llc
, we should be able to invoke it as follows:
llc -mtriple=mipsnova test.ll -o test.s
We 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,
i686
is the CPU architecture, which is a 32-bit Intel x86.pc-linux
is the vendor, which is a generic PC.gnu
is 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.guess
x86_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 NVPTX
Create 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 llc
Now 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/null
PLEASE 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!