A compiler for (a subset of) Racket -> x86-64, written in Racket
- Add tests that have nested
ifs in then/else positions - Add tests that have
ifs in assignments
- Harvard assembly tutorial
- x86-64 for the compiler writer
- x86-64 cheatsheet
- Indiana University compiler course
- Original ICFP pearl on nanopass compilers
- Andy Keep dissertation on nanopass compilers
- An Incremental Approach to Compiler Construction, Nada Amin's implementation (which can compile itself!)
- Partial computation, Yoshihiko Futamura
- Interference graphs of programs in SSA-form
- Racket nanopass library
- Intel x86 Manual
- Add a
.rkttest file in/tests, e.g.
; /tests/var_test_12.rkt
(let ([x (read)])
(+ x 2))
- Add a
.infile containing inputs to all thereadcalls, e.g.
; /tests/var_test_12.in
3
- Add a
.resfile containingt the expected output, e.g.
; /test/var_test_12.res
5
- Run tests
racket run-tests.rkt
- Compile the
.sfile with-gto generate debugging symbols, e.g.
gcc -g var_test_11.s
- Load the
.outfile into gdb
gdb var_test_11.out
- Set break points by passing addresses via
<label> + <offset>, e.g.
(gdb) info break
No breakpoints or watchpoints.
(gdb) break *main+2
Breakpoint 1 at 0x1124: file var_test_11.s, line 8.
Use del <number> to delete breakpoint (number comes from info break).
4. Use run to step through the program, stopping at each break point and
cont to continue till the next breakpoint. Use info registers to inspect
register contents, print/<bxd> <reg> to print contents of register <reg>
in binary/hex/decimal, e.g.
(gdb) print/d $rax
prints the contents of register rax in decimal
5. Use info frame to show stack frame info and x/<offset><bxd><bhwg> <addr>
to examine contents at that address. E.g.
(gdb) x/4xw $sp
prints "four words (w) of memory above the stack pointer (here, $sp) in
hexadecimal (x)".
- The
passesvariable insrun-tests.rktruns the passes in the very order.
The runtime.c file needs to be compiled and linked with the assembly
code that the compiler produces. To compile runtime.c, do the
following
gcc -c -g -std=c99 runtime.c
This will produce a file named runtime.o. The -g flag is to tell the
compiler to produce debug information.
Next, suppose the compiler has translated the Racket program in file
foo.rkt into the x86 assembly program in file foo.s (The .s filename
extension is the standard one for assembly programs.) To produce
an executable program, do
gcc -g runtime.o foo.s
which will produce the executable program named a.out.
- In the shrink pass, I had implemented
<=ashow ever, this duplicates(match e ... [(Prim '<= `(,e1 ,e2)) (let ([e1 (shrink-exp e1)] [e2 (shrink-exp e2)]) (If (Prim '< `(,e1 ,e2)) (Bool #t) (Prim 'eq? `(,e1 ,e2))))] ...)e1ande2! A compiler must never duplicate code -- in this case, this duplication can introduce issues if either of them are a(read)call, since the compiled program may potentially have to read the file twice instead of once! - Before a tail call, the callee-saved registers must be popped-off from the
stack. However, observe that if a callee-saved register is being used as the
argument to
TailJmp, we have an absurdity! The function address first getsleaqed into a callee-saved register, but then all callee-saved registers getpoped, so thisleaqis overwritten andjmp's target is not what we want it to be.
rspmust always be of the form8 + 16 * n, for some naturaln. Ifpushqs/popqs break this alignment, it must be realigned viasubq. For instance, ifrsp(which always getspushqed in all functions) andrbx(say function uses it locally) getpushqed, then the stack is currently aligned to 8 + 8 = 16 bytes, which isn't 8+16n. Sosubq $8, %rspmust be generated to realign to 8+16n. If onlyrbpwere being pushed, then this extrasubqwould not have been necessary.- Stuff that lazy explicate control achieves
- Avoids duplicate block generation
- Avoids dead block generation