With all this preparation done, emitting the file boils down to a single function call:

PM.run(*M);

The ToolOutputFile class automatically deletes the file if we do not explicitly request that we want to keep it. This makes error handling easier as there are potentially many places where we need to handle errors and only one place is reached if everything goes well. We successfully emitted the code, so we want to keep the file:

Out->keep();

Finally, we must report success to the caller:

return true;
}

Calling the emit() method with llvm::Module, which we created with a call to the CodeGenerator class, emits the code as requested.
Suppose you have the greatest common divisor algorithm in tinylang stored in the Gcd.mod file:

MODULE Gcd;
PROCEDURE GCD(a, b: INTEGER) : INTEGER;
VAR t: INTEGER;
BEGIN
IF b = 0 THEN
RETURN a;
END;
WHILE b 0 DO
t := a MOD b;
a := b;
b := t;
END;
RETURN a;
END GCD;
END Gcd.

To translate this to the Gcd.o object file, type the following:

$ tinylang –filetype=obj Gcd.mod

If you’d like to inspect the generated IR code directly on the screen, type the following:

$ tinylang –filetype=asm –emit-llvm -o – Gcd.mod

With the current state of the implementation, it is not possible to create a complete program in tinylang. However, you can use a small C program called callgcd.c to test the generated object file. Note the use of the mangled name to call the GCD function:

include
extern long _t3Gcd3GCD(long, long);
int main(int argc, char *argv[]) {
printf(„gcd(25, 20) = %ld\n”, _t3Gcd3GCD(25, 20));
printf(„gcd(3, 5) = %ld\n”, _t3Gcd3GCD(3, 5));
printf(„gcd(21, 28) = %ld\n”, _t3Gcd3GCD(21, 28));
return 0;
}

To compile and run the whole application with clang, type the following:

$ tinylang –filetype=obj Gcd.mod
$ clang callgcd.c Gcd.o -o gcd
$ gcd

Let’s celebrate! At this point, we have created a complete compiler by reading the source language up and emitting assembler code or an object file.
Summary
In this chapter, you learned how to implement a code generator for LLVM IR code. Basic blocks are important data structures that hold all the instructions and express branches. You learned how to create basic blocks for the control statements of the source language and how to add instructions to a basic block. You applied a modern algorithm to handle local variables in functions, leading to less IR code. The goal of a compiler is to generate assembler text or an object file for the input, so you also added a simple compilation pipeline. With this knowledge, you will be able to generate LLVM IR code and assembler text or object code for your language compiler.
In the next chapter, you’ll learn how to deal with aggregate data structures and how to ensure that function calls comply with the rules of your platform.

Leave a Reply

Your email address will not be published. Required fields are marked *