Skip to content

Setting up the Nova target

Before we can start adding instructions, we need to set up some required configuration code. This contains the Nova subtarget, register and instruction information, frame and selection DAG lowering classes and a few others that set up the code generation pipeline.

The code we add here will allow us to build LLVM for the Nova target but not to compile any LLVM IR to assembly code yet.

Adding the NovaSubtarget

TableGen generates the NovaGenSubtarget class. Add the tablegen invocation to CMakeLists.txt:

llvm/lib/Target/Nova/CMakeLists.txt (cmake-subtarget-info)
tablegen(LLVM NovaGenRegisterInfo.inc -gen-register-info)
tablegen(LLVM NovaGenSubtargetInfo.inc -gen-subtarget)

Whip up a new header file for the subtarget class in lib/Target/Nova. Here we include the generated subtarget file from TableGen.

The Subtarget is the central place from where we access the target information per function. We will include some header files that we will create later on.

>> new file
llvm/lib/Target/Nova/NovaSubtarget.h (nova-subtarget-1)
//===-- NovaSubtarget.h - Define Subtarget for the Nova --------*- C++ -*-===//
#ifndef LLVM_LIB_TARGET_NOVA_NOVASUBTARGET_H
#define LLVM_LIB_TARGET_NOVA_NOVASUBTARGET_H
#include "NovaFrameLowering.h"
#include "NovaISelLowering.h"
#include "NovaInstrInfo.h"
#include "NovaRegisterInfo.h"
#include "llvm/CodeGen/SelectionDAGTargetInfo.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/IR/DataLayout.h"
#define GET_SUBTARGETINFO_HEADER
#include "NovaGenSubtargetInfo.inc"

Add the fields for all info classes. These will be created ahead.

llvm/lib/Target/Nova/NovaSubtarget.h (nova-subtarget-2)
#include "NovaGenSubtargetInfo.inc"
namespace llvm {
class NovaSubtarget : public NovaGenSubtargetInfo {
protected:
SelectionDAGTargetInfo TSInfo;
NovaInstrInfo InstrInfo;
NovaFrameLowering FrameLowering;
NovaTargetLowering TLInfo;
NovaRegisterInfo RegInfo;

Add the corresponding getters and the constructor, ending the file.

→ namespace llvm {
↳ class NovaSubtarget : public NovaGenSubtargetInfo {
llvm/lib/Target/Nova/NovaSubtarget.h (nova-subtarget-3)
NovaTargetLowering TLInfo;
NovaRegisterInfo RegInfo;
public:
NovaSubtarget(const Triple &TT, StringRef CPU, StringRef FS,
const TargetMachine &TM)
: NovaGenSubtargetInfo(TT, CPU, CPU, FS), InstrInfo(*this),
FrameLowering(*this, Align(8)), TLInfo(TM, *this) {}
const NovaRegisterInfo *getRegisterInfo() const override { return &RegInfo; }
const NovaInstrInfo *getInstrInfo() const override { return &InstrInfo; }
const NovaFrameLowering *getFrameLowering() const override {
return &FrameLowering;
}
const NovaTargetLowering *getTargetLowering() const override {
return &TLInfo;
}
const SelectionDAGTargetInfo *getSelectionDAGInfo() const override {
return &TSInfo;
}
/// ParseSubtargetFeatures - Parses features string setting specified
/// subtarget options. Definition of function is auto generated by tblgen.
void ParseSubtargetFeatures(StringRef CPU, StringRef TuneCPU, StringRef FS);
};
} // end namespace llvm
#endif

Global functions

Targets have a few global functions to define or create codegen passes which are consolidated in one header file. Create the Nova.h file for our target. This is empty for now.

>> new file
llvm/lib/Target/Nova/Nova.h (nova-h-old)
/// This is for global functions in the Nova target.
#ifndef LLVM_LIB_TARGET_NOVA_NOVA_H
#define LLVM_LIB_TARGET_NOVA_NOVA_H
#include "MCTargetDesc/NovaMCTargetDesc.h"
#endif

The definitions need to be added to the implementation file.

>> new file
llvm/lib/Target/Nova/NovaSubtarget.cpp (nova-subtarget-cpp)
#include "Nova.h"
#include "NovaSubtarget.h"
#include "NovaRegisterInfo.h"
#include "NovaTargetMachine.h"
using namespace llvm;
#define DEBUG_TYPE "nova-subtarget"
#define GET_SUBTARGETINFO_CTOR
#define GET_SUBTARGETINFO_TARGET_DESC
#include "NovaGenSubtargetInfo.inc"

Registering the subtarget

Include the generated file in NovaMCTargetDesc files.

llvm/lib/Target/Nova/MCTargetDesc/NovaMCTargetDesc.cpp (nova-get-subtargetinfo)
#include "NovaGenRegisterInfo.inc"
#define GET_SUBTARGETINFO_MC_DESC
#include "NovaGenSubtargetInfo.inc"
static MCRegisterInfo* createNovaMCRegisterInfo(const Triple &TT) {
llvm/lib/Target/Nova/MCTargetDesc/NovaMCTargetDesc.h (nova-get-subtargetinfo-h)
#include "NovaGenRegisterInfo.inc"
#define GET_SUBTARGETINFO_ENUM
#include "NovaGenSubtargetInfo.inc"

This needs the definition of the MCSubtargetInfo class which is the base.

llvm/lib/Target/Nova/MCTargetDesc/NovaMCTargetDesc.cpp (include-mcsubtarget-info)
#include "NovaMCTargetDesc.h"
#include "NovaTargetInfo.h"
#include "llvm/MC/MCSubtargetInfo.h"

We need to register the subtarget in NovaTargetMachine.cpp as well.

↓ after static MCRegisterInfo* createNovaMCRegisterInfo(const Triple &TT) {
llvm/lib/Target/Nova/MCTargetDesc/NovaMCTargetDesc.cpp (nova-create-subtarget)
}
static MCSubtargetInfo* createNovaSubtargetInfo(const Triple &TT, StringRef CPU, StringRef FS) {
if (CPU.empty())
CPU = "generic";
return createNovaMCSubtargetInfoImpl(TT, CPU, CPU, FS);
}
→ extern "C" void LLVMInitializeNovaTargetMC() {
llvm/lib/Target/Nova/MCTargetDesc/NovaMCTargetDesc.cpp (nova-register-subtarget)
Target *T = &getTheNovaTarget();
TargetRegistry::RegisterMCRegInfo(*T, createNovaMCRegisterInfo);
TargetRegistry::RegisterMCSubtargetInfo(*T, createNovaSubtargetInfo);

MCTargetDesc

The common place for the target descriptions is the NovaMCTargetDesc.h file. This has the instruction set, register info and subtarget information.

We already created this file in the registers section.

TargetObjectFile

Another class that is required but unused is NovaTargetObjectFile.

>> new file
llvm/lib/Target/Nova/NovaTargetObjectFile.h (nova-target-object-file.h)
#ifndef LLVM_LIB_TARGET_NOVA_NOVATARGETOBJECTFILE_H
#define LLVM_LIB_TARGET_NOVA_NOVATARGETOBJECTFILE_H
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
namespace llvm {
class TargetMachine;
class MCContext;
class NovaTargetObjectFile final : public TargetLoweringObjectFileELF {
public:
void Initialize(MCContext &Ctx, const TargetMachine &TM) override;
};
} // namespace llvm
#endif

Write the Initialize definition.

>> new file
llvm/lib/Target/Nova/NovaTargetObjectFile.cpp (nova-target-object-file.cpp)
#include "NovaTargetObjectFile.h"
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
#include "llvm/MC/MCContext.h"
#include "llvm/Target/TargetMachine.h"
using namespace llvm;
void NovaTargetObjectFile::Initialize(MCContext &Ctx, const TargetMachine &TM) {
TargetLoweringObjectFileELF::Initialize(Ctx, TM);
}

TargetMachine

The TargetMachine class is the main entry point for the target. It contains the codegeneration pipeline and other target information.

>> new file
llvm/lib/Target/Nova/NovaTargetMachine.h (nova-target-machine-1)
#ifndef LLVM_LIB_TARGET_NOVA_NOVATARGETMACHINE_H
#define LLVM_LIB_TARGET_NOVA_NOVATARGETMACHINE_H
#include "NovaSubtarget.h"
#include "NovaTargetObjectFile.h"
#include "llvm/CodeGen/CodeGenTargetMachineImpl.h"
#include "llvm/Support/CodeGen.h"
#include "llvm/Target/TargetLoweringObjectFile.h"
#include "llvm/Target/TargetMachine.h"
using namespace llvm;
static const char *NovaDataLayoutString =
"e-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64";
static Reloc::Model getEffectiveRelocModel(std::optional<Reloc::Model> RM) {
if (!RM)
return Reloc::Static;
return *RM;
}

Add the class declaration.

↓ after static Reloc::Model getEffectiveRelocModel(std::optional<Reloc::Model> RM) {
llvm/lib/Target/Nova/NovaTargetMachine.h (nova-target-machine-2)
}
namespace llvm {
class NovaTargetMachine final : public CodeGenTargetMachineImpl {
NovaSubtarget Subtarget;
std::unique_ptr<TargetLoweringObjectFile> TLOF;
public:
NovaTargetMachine(const Target &T, const Triple &TT, StringRef CPU,
StringRef FS, const TargetOptions &Options,
std::optional<Reloc::Model> RM,
std::optional<CodeModel::Model> CM, CodeGenOptLevel OL,
bool JIT)
: CodeGenTargetMachineImpl(T, NovaDataLayoutString, TT, CPU, FS, Options,
getEffectiveRelocModel(RM),
getEffectiveCodeModel(CM, CodeModel::Medium),
OL),
Subtarget(TT, CPU, FS, *this), TLOF(new NovaTargetObjectFile()) {
initAsmInfo();
}
// ~NovaTargetMachine() override = default;
TargetLoweringObjectFile *getObjFileLowering() const override {
return TLOF.get();
}
const NovaSubtarget *getSubtargetImpl(const Function &F) const override {
return &Subtarget;
}
TargetPassConfig *createPassConfig(PassManagerBase &PM) override;
};
} // namespace llvm
#endif

createPassConfig will be added in the next section.

ASMInfo

The MCAsmInfo class holds the information about the assembly language for the target.

>> new file
llvm/lib/Target/Nova/MCTargetDesc/NovaMCAsmInfo.h (nova-mc-asm.h)
#ifndef LLVM_LIB_TARGET_NOVA_MCTARGETDESC_NOVAMCASMINFO_H
#define LLVM_LIB_TARGET_NOVA_MCTARGETDESC_NOVAMCASMINFO_H
#include "llvm/MC/MCAsmInfoELF.h"
namespace llvm {
class Triple;
class NovaMCAsmInfo final : public MCAsmInfoELF {
void anchor() override;
public:
explicit NovaMCAsmInfo(const Triple &TheTriple);
};
} // end namespace llvm
#endif

I am adding the minimum possible stuff to get us running, but you can explore all the fields in the class and customize it here.

>> new file
llvm/lib/Target/Nova/MCTargetDesc/NovaMCAsmInfo.cpp (nova-mc-asm.cpp)
#include "NovaMCAsmInfo.h"
#include "llvm/TargetParser/Triple.h"
using namespace llvm;
void NovaMCAsmInfo::anchor() {}
NovaMCAsmInfo::NovaMCAsmInfo(const Triple& TT) {
IsLittleEndian = false;
AlignmentIsInBytes = true;
}

Like with other target info classes, we need to register the MCAsmInfo class in the NovaMCTargetDesc file. This is done in the Initialize function.

Include the headers.

llvm/lib/Target/Nova/MCTargetDesc/NovaMCTargetDesc.cpp (include-mc-asm-info)
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCInstrInfo.h"
#include "MCTargetDesc/NovaMCAsmInfo.h"
#include "llvm/MC/MCDwarf.h"

Ready the MCAsmInfo instance.

↓ after static MCSubtargetInfo* createNovaSubtargetInfo(const Triple &TT, StringRef CPU, StringRef FS) {
llvm/lib/Target/Nova/MCTargetDesc/NovaMCTargetDesc.cpp (nova-create-asm-info)
}
static MCAsmInfo* createNovaMCAsmInfo(const MCRegisterInfo &MRI, const Triple &TT, const MCTargetOptions &Options) {
MCAsmInfo *X = new NovaMCAsmInfo(TT);
unsigned SP = MRI.getDwarfRegNum(Nova::SP, true);
MCCFIInstruction Inst = MCCFIInstruction::createDefCfaRegister(nullptr, SP);
X->addInitialFrameState(Inst);
return X;
}

Place it into the Target.

→ extern "C" void LLVMInitializeNovaTargetMC() {
llvm/lib/Target/Nova/MCTargetDesc/NovaMCTargetDesc.cpp (register-asm-info)
TargetRegistry::RegisterMCSubtargetInfo(*T, createNovaSubtargetInfo);
TargetRegistry::RegisterMCInstrInfo(*T, createNovaMCInstrInfo);
TargetRegistry::RegisterMCAsmInfo(*T, createNovaMCAsmInfo);

Lastly, don’t forget to add the source files to CMakeLists.txt.

llvm/lib/Target/Nova/CMakeLists.txt (cmake-setting-up)
NovaRegisterInfo.cpp
MCTargetDesc/NovaMCTargetDesc.cpp
NovaTargetObjectFile.cpp
NovaSubtarget.cpp
MCTargetDesc/NovaMCAsmInfo.cpp

With this, we are set to implement instructions.