Skip to content

The Target Triple

To generate code for llc, we should be able to invoke it as follows:

Terminal window
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
→ namespace llvm {
↳ class Triple {
llvm/include/llvm/TargetParser/Triple.h (sub-arch)
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:

llvm-project
bash llvm/cmake/config.guess
x86_64-unknown-linux-gnu

To explore further, you can look into this snippet from the config.guess file:

from config.guess
UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown
UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
echo "${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.

llvm/tools/llc/llc.cpp
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.

llvm/lib/TargetParser/Triple.cpp (triple2)
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.

→ namespace llvm {
↳ class Triple {
↳ enum ArchType {
llvm/include/llvm/TargetParser/Triple.h (triple1)
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.

→ StringRef Triple::getArchTypeName(ArchType Kind) {
llvm/lib/TargetParser/Triple.cpp (get-arch-type-name)
case mips: return "mips";
case mipsel: return "mipsel";
case mipsnova: return "mipsnova";
case msp430: return "msp430";
case nvptx64: return "nvptx64";
→ Triple::ArchType Triple::getArchTypeForLLVMName(StringRef Name) {
llvm/lib/TargetParser/Triple.cpp (case-triple)
.Case("mips64", mips64)
.Case("mips64el", mips64el)
.Case("mipsnova", mipsnova)
.Case("msp430", msp430)
.Case("ppc64", ppc64)
→ static Triple::ArchType parseArch(StringRef ArchName) {
llvm/lib/TargetParser/Triple.cpp (parse-arch-nova)
.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.

→ unsigned Triple::getArchPointerBitWidth(llvm::Triple::ArchType Arch) {
llvm/lib/TargetParser/Triple.cpp (32-bit-nova-ptr)
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.

→ Triple Triple::get32BitArchVariant() const {
llvm/lib/TargetParser/Triple.cpp (32bit-variant)
case Triple::mips:
case Triple::mipsel:
case Triple::mipsnova:
case Triple::nvptx:
case Triple::ppc:
→ Triple Triple::get64BitArchVariant() const {
llvm/lib/TargetParser/Triple.cpp (64bit-variant)
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.

llvm/CMakeLists.txt (nova-all-targets-cmake)
LoongArch
Mips
Nova
MSP430
NVPTX

Create the CMakeLists.txt file in our Nova directory.

>> new file
llvm/lib/Target/Nova/CMakeLists.txt (nova-cmake1)
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.

>> new file
llvm/lib/Target/Nova/NovaTargetMachine.cpp (nova-tm-cpp)
#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.

Terminal window
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:

Terminal window
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:

Terminal window
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!