Skip to content

teodutu/compiler-workshop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Compiler-Workshop

Workshop on compilers featuring the DMD as frontend and LLVM as backend

DLang

D is a general-purpose systems-level programming language that excels at metaprogramming and design-by-introspection (basically querying the compiler at compile-time and generating different code based on these queries). On top of these, D also provides a memory-safety mechanism which is less compiler-heavy but also less rigorous than that of Rust.

To install it, run this command (don't forget to run the source command printed by the install script to activate the compiler):

curl -fsS https://dlang.org/install.sh | bash -s dmd

We'll display some of D's features highlighted above. Enter the d-features folder.

This file shows that D is capable of running functions at compile-time provided their inputs are known to the compiler. This feature is called Compile-Time Function Execution (CTFE).

Run make ctfe. Notice that simply compiling the code prints the strings in the main() function. This is possible because both jsonConfig and parsedConfig are immutable which makes them known at compile time because their value cannot change.

__traits()

This is in my opinion the bread and butter of D's strong metaprogramming. __traits() is a construction that interrogates the compiler at compile-time. It asks the compiler for various information that can then be used to customise the generated code.

This feature works well in conjunction with static if statements which use the syntax of regular ifs but are evaluated at compile-time. Similarly, staic foreach statements are foreach statements that unrolled and their code is generated rather than run at compile-time. By far one of the coolest __traits() is __traits(compiles, /* Some D code */) which is true if the D code compiles and is often used like this:

static if (__traits(compiles, /* Some D code */))
    /* The same D code */

I.e. if some code compiles, use it.

Follow the code in d-features/traits_compiles.d. Run it and figure out how it works.

mixin

You can also encounter __traits() in d-features/auto_string.d. In this snippet we use design-by-introspection to automatically generate the toString() method for any struct / class. To generate and insert code at compile-time we use the mixin keyword. Go through the code mentioned above and notice how mixin works with the template AutoToString which in turn uses __traits(getMember) to access the members of a generic struct or class.

Safety is enforeced in D mostly at the function level by using the @safe keyword. This is a rather complex mechanism. that works mostly by forbidding certain memory-related actions such as pointer arithmetic.

Inspect the code in d-features/safe.d. Compile it with make safe and run it with ./safe. Notice that it incorrectly prints the 11th element of the array.

Task: Change unsafe_func() from @trusted to @safe and make the code compile without changing its functionality (it must still print the 11th element of the array). We dare you, we double dare you! You have a partial solution in the safe_func() function, but that function produces an error at runtime. Why?

LLVM

LLVM is one of the most popular compiler backends and the one used by the ldc D compiler. Its most powerful feature and what makes LLVM so customisable is its intermediate representation called LLVM IR. To install it, follow the instructions here. This may take a while and quite a bit of storage space, but it's well worth it.

This IR is a mid-point between the high-level AST created by the frontend and the low-level and machine-dependent assembly output by the compiler. Its assembly-like structure allows for easy optimisations via LLVM passes. These passes are functions that are compiled into libraries that can be loaded with the opt tool to modify existing LLVM IR.

Follow the code in the llvm/ directory. We'll work on sample.c. The LLVM pass is FuncionPass.cpp. It just prints the functions and instructions found and modifies all add instructions into sub.

  1. Run make build-pass to build build/libFunctionPass.so. This library contains the code in FunctionPass.cpp.

  2. Run make generate-llvmir to generate the LLVM IR code corresponding to sample.c. Inspect the code in sample.ll.

  3. Run make run-pass to run the LLVM pass. Notice the output then inspect the resulting file sample_edited.ll.

  4. Run make llvm-interpret to interpret sample_edited.ll using LLVM's interpreter lli.

  5. Run make link-llvm to generate the sample_edited executable from sample_edited.ll. This uses llc to convert the LLVM IR code to Assembly and clang to assemeble and link it into an executable.

  6. Run make clean to delete all resulting files and make celean-pass to remove the build/ folder.

About

Workshop on compilers featuring the DMD as frontend and LLVM as backend

Resources

License

Stars

0 stars

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors