Chapter 7. Generating Code for Target Architecture

The code generated by the compiler finally has to execute on the target machines. The abstract form of the LLVM IR helps to generate code for various architectures. The target machine can be anything – CPU, GPU, DSP's, and so on. The target machine has some defining aspects such as the register sets, the instruction set, the calling convention of the function, and the instruction pipeline. These aspects or properties are generated using the tablegen tool so that they can be used easily while programming code generation for the machine.

LLVM has a pipeline structure for the backend, where instructions travel through phases—from the LLVM IR to SelectionDAG, then to MachineDAG, then to MachineInstr, and finally to MCInst. The IR is converted to SelectionDAG. SelectionDAG then goes through legalization and optimizations. After this stage, the DAG nodes are mapped to target instructions (instruction selection). The DAG then goes through instruction scheduling, emitting linear sequences of instructions. The virtual registers are then allotted the target machine registers, which involves optimal register allocation minimizing memory spills.

This chapter describes how to represent target architecture. It also describes how to emit assembly code.

The topics discussed in this chapter are as follows:

  • Defining registers and register sets
  • Defining the calling convention
  • Defining the instruction set
  • Implementing frame lowering
  • Selecting an instruction
  • Printing an instruction
  • Registering a target

Sample backend

To understand target code generation, we define a simple RISC-type architecture TOY machine with minimal registers, say r0-r3, a stack pointer SP, a link register, LR (for storing the return address); and a CPSR – current state program register. The calling convention of this toy backend is similar to the ARM thumb-like architecture—arguments passed to the function will be stored in register sets r0-r1, and the return value will be stored in r0.

Defining registers and register sets

Register sets are defined using the tablegen tool. Tablegen helps to maintain large number of records of domain specific information. It factors out the common features of these records. This helps in reducing duplication in the description and forms a structural way of representing domain information. Please visit http://llvm.org/docs/TableGen/ to understand tablegen in detail. TableGen files are interpreted by the TableGen binary: llvm-tblgen.

We have described our sample backend in the preceding paragraph, which has four registers (r0-r3), a stack register (SP), and a link register (LR). These can be specified in the TOYRegisterInfo.td file. The tablegen function provides the Register class, which can be extended to specify the registers. Create a new file named TOYRegisterInfo.td.

The registers can be defined by extending the Register class.

class TOYReg<bits<16> Enc, string n> : Register<n> {
let HWEncoding = Enc;
let Namespace = "TOY";
}

The registers r0-r3 belong to a general purpose Register class. This can be specified by extending RegisterClass.

foreach i = 0-3 in {
def R#i : R<i, "r"#i >;
}

def GRRegs : RegisterClass<"TOY", [i32], 32,
(add R0, R1, R2, R3, SP)>;

The remainings, register SP, LR, and CPSR, can be defined as follows:

def SP : TOYReg<13, "sp">;
def LR : TOYReg<14, "lr">;
def CPSR  : TOYReg<16, "cpsr">;

When the whole thing is put together, the TOYRegisterInfo.td looks like the following:

class TOYReg<bits<16> Enc, string n> : Register<n> {
let HWEncoding = Enc;
let Namespace = "TOY";
}

foreach i = 0-3 in {
def R#i : R<i, "r"#i >;
}

def SP : TOYReg<13, "sp">;
def LR : TOYReg<14, "lr">;
def GRRegs : RegisterClass<"TOY", [i32], 32,
(add R0, R1, R2, R3, SP)>;

We can put this file in a new folder named TOY in the parent folder named Target in the llvm's root directory, which is llvm_root_directory/lib/Target/TOY/ TOYRegisterInfo.td

The tablegen tool llvm-tablegen, processes this .td file to generate the .inc file, which generally has enums generated for these registers. These enums can be used in the .cpp files, in which the registers can be referenced as TOY::R0.

Defining the calling convention

The calling convention specifies how values are passed to and returned from a function call. Our TOY architecture specifies that two arguments are passed in two registers, r0 and r1, while the remaining ones are passed to the stack. Calling convention defined is then used in the Instruction Selection phase by referring to the function pointer.

While defining a calling convention, we have to represent two sections—one to define the convention return value, and other to define the convention for argument passing. The parent class CallingConv is inherited to define the calling convention.

In our TOY architecture, the return value is stored in r0 register. If there are more arguments, integer values get stored in stack slots that are 4 bytes in size and 4-byte aligned. This can be declared in TOYCallingConv.td as follows:

def RetCC_TOY : CallingConv<[
CCIfType<[i32], CCAssignToReg<[R0]>>,
CCIfType<[i32], CCAssignToStack<4, 4>>
]>;

The argument passing convention can be defined as follows:

def CC_TOY : CallingConv<[
CCIfType<[i8, i16], CCPromoteToType<i32>>,
CCIfType<[i32], CCAssignToReg<[R0, R1]>>,
CCIfType<[i32], CCAssignToStack<4, 4>>
]>;

The preceding declaration says three things, which are as follows:

  • If the datatype of the arguments is i8 or i16, it will get promoted to i32
  • The first two arguments will be stored in register r0 and r1
  • If there are more arguments, they will be stored in Stack

We also define the callee-saved register since callee-saved registers are used to hold long-lived values that should be preserved across calls.

def CC_Save : CalleeSavedRegs<(add R2, R3)>;

The llvm-tablegen tool generates a TOYCallingConv.inc file after building the project, which will be included in the Instruction Selection phase in the TOYISelLowering.cpp file.

Defining the instruction set

Architectures have rich instruction sets to represent various operations supported by the target machine. Typically, three things need to be defined in the target description file when representing the instructions:

  • operands
  • the assembly string
  • the instruction pattern

The specification contains a list of definitions or outputs, and a list of uses or inputs. There can be different operand classes, such as the Register class, and the immediate and more complex register+imm operands.

For example, we define register to register addition for our Toy machine as follows in TOYInstrInfo.td:

def ADDrr : InstTOY<(outs GRRegs:$dst),
(ins GRRegs:$src1, GRRegs:$src2),
"add $dst, $src1,z$src2",
[(set i32:$dst, (add i32:$src1, i32:$src2))]>;

In the above declaration, the 'ins' has two registers $src1 and $src2 belonging to the general purpose register class, which holds the two operands. The result of the operation will be put into 'outs', which is a $dst register belonging to the general purpose Register class. The assembly string is "add $dst, $src1,z$src2". The values of $src1, $src2 and $dst will be determined at the time of register allocation. So, an assembly will be generated for add between two registers, like this:

add r0, r0, r1

We saw above how a simple instruction can be represented using tablegen. Similar to the add register to register instruction, a subtract register from a register instruction can be defined. We leave it to the readers to try it out. A more detailed representation of complex instructions can be examined from the ARM or X86 architecture specifications in the project code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.15.43