Master the fundamental concepts of code generation through this focused micro-challenge.
The final backend maps IR operations to machine instructions. Clang's x86 backend and GCC's rtx emission translate add to addl, spills temporaries to stack slots, and follow the System V AMD64 ABI for calling conventions.
Each IR opcode maps to one or more assembly instructions. Loads from variables become mov from memory. Adds become add between registers. Compare and branch become cmp + je/jne.
asmLoading…
Production compilers embed this step inside a longer pipeline. GCC flows through cpp, cc1, assembly, and ld; Clang uses the driver, Sema, LLVM IR passes, and a target backend. LLVM bitcode, JVM bytecode, and WASM are other familiar IRs at the same layer. The exercise isolates one pass so you can test it alone before chaining it to the next stage.
You will generate x86 assembly from your IR. This exercise asks you to map arithmetic, moves, and branches to assembly syntax and manage register and stack operands for a simple instruction subset.
Translate a three-address-code function into x86-64 assembly (Intel syntax, System V calling convention) using a fixed template per TAC instruction: the simplest real code generator: every variable lives in a stack slot and every operation goes through rax.
A header line func NAME(p1, p2, …) (at most 6 parameters) followed by TAC, one instruction per line:
cLoading…
Operands are variable names or integer literals. Each call is immediately preceded by its n param lines.
Every variable gets an 8-byte slot. Parameters take slots 1, 2, … in order; other variables follow in order of first appearance, reading each instruction left to right (labels, jump targets and callee names are not variables). Slot k is [rbp-8k]. The frame size is 8 × slots rounded up to a multiple of 16, which keeps rsp 16-byte aligned at every call.
An operand prints as its slot ([rbp-16]) or, for a literal, as the number itself.
Instructions are indented 4 spaces; labels are flush left as .LNAME:.
| TAC | Assembly |
|---|---|
| prologue | NAME: · push rbp · mov rbp, rsp · sub rsp, FRAME (omitted if 0) · then mov [rbp-8k], REG for parameter k, registers rdi rsi rdx rcx r8 r9 |
x = a | mov rax, A · mov X, rax |
x = a + b / - / * | mov rax, A · add / sub / imul rax, B · mov X, rax |
x = a / b / % | mov rax, A · cqo · mov rcx, B · idiv rcx · mov X, rax (quotient) / mov X, rdx (remainder) |
x = a < b / == | mov rax, A · cmp rax, B · setl al / sete al · movzx rax, al · mov X, rax |
L: | .LL: |
goto L | jmp .LL |
if a goto L / ifFalse a goto L | mov rax, A · cmp rax, 0 · jne .LL / je .LL |
param a | nothing yet: remembered for the next call |
x = call f, n | mov REG_i, A_i for each remembered param (same register order) · call f · mov X, rax |
return a | mov rax, A · jmp .Lreturn |
| end of function | .Lreturn: · mov rsp, rbp · pop rbp · ret |
Finish with a comment line ; S slots, F-byte frame.
Input:
cLoading…
Output:
cLoading…
Hidden tests cover loops with conditional jumps, division and remainder, comparisons, calls with several arguments, and a function with no parameters.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it works