Introduction

Intermediate-code generation turns the syntax tree into three-address code with syntax-directed rules. Arithmetic expressions become sequences of temporaries; array references become address arithmetic; Boolean conditions become jumping code, in which the value of a condition is represented by where control goes rather than by a stored 0 or 1. Statements such as if, if-else and while are built from labels and jumps around that code.

Jumps forward to code not yet generated create a problem for a one-pass translator: the target is not known when the jump is emitted. Backpatching solves it by emitting jumps with empty targets, keeping lists of them, and filling them when the target becomes known. Examination questions ask for array addresses, the number of instructions in short-circuit code, and the true and false lists produced by backpatching.

Prerequisites

Core concepts

  • E.addr and E.code. For an expression, addr is the name, constant or temporary holding its value; code is the instruction sequence computing it. gen(...) emits one instruction; new Temp() creates a temporary.
  • Row-major layout. Elements of a multi-dimensional array are stored row by row: the last subscript varies fastest. C uses row-major order.
  • Jumping code. A Boolean expression BB has two inherited labels, B.trueB.true and B.falseB.false; its code jumps to one of them.
  • Short-circuit evaluation. In B1∥B2B_1 \mathbin{\|} B_2, if B1B_1 is true, B2B_2 is not evaluated; in B1&&B2B_1 \mathbin{\&\&} B_2, if B1B_1 is false, B2B_2 is not evaluated.
  • Backpatching. Instructions are numbered; incomplete jumps are collected on lists truelisttruelist and falselistfalselist; backpatch(p, i) writes target ii into every jump on list pp; merge(p1, p2) concatenates lists; makelist(i) creates the list {i}\{i\}.

How it works

Expressions and assignments

The SDD for assignments is

S→id=E ;{ gen(top.get(id.lexeme) ′=′ E.addr) }S \to \textbf{id} = E\ ;\quad \{\ gen(top.get(\textbf{id}.lexeme)\ '='\ E.addr)\ \}
E→E1+E2{ E.addr=new Temp(); gen(E.addr ′=′ E1.addr ′+′ E2.addr) }E \to E_1 + E_2\quad \{\ E.addr = \textbf{new}\ Temp();\ gen(E.addr\ '='\ E_1.addr\ '+'\ E_2.addr)\ \}
E→−E1{ E.addr=new Temp(); gen(E.addr ′=′ ′minus′ E1.addr) }E \to -E_1 \quad \{\ E.addr = \textbf{new}\ Temp();\ gen(E.addr\ '='\ '\textbf{minus}'\ E_1.addr)\ \}
E→(E1){ E.addr=E1.addr }E→id{ E.addr=top.get(id.lexeme) }E \to (E_1) \quad \{\ E.addr = E_1.addr\ \} \qquad E \to \textbf{id} \quad \{\ E.addr = top.get(\textbf{id}.lexeme)\ \}

This is a postfix SDT: every instruction is emitted when its production is reduced, so instructions appear in postorder, one per operator.

Array addressing

For a one-dimensional array with element width ww and lower bound 0, A[i] is at base+i×wbase + i \times w. For a two-dimensional array A[n1][n2] in row-major order with lower bounds 0,

addr(A[i1][i2])=base+(i1×n2+i2)×w\text{addr}(A[i_1][i_2]) = base + (i_1 \times n_2 + i_2) \times w

With lower bounds low1low_1 and low2low_2, replace i1i_1 by i1−low1i_1 - low_1 and i2i_2 by i2−low2i_2 - low_2. In general the offset is built by Horner's rule: ((i1n2+i2)n3+i3)⋯((i_1 n_2 + i_2) n_3 + i_3) \cdots, times ww. Column-major order (Fortran) reverses the roles: base+(i2×n1+i1)×wbase + (i_2 \times n_1 + i_1) \times w for two dimensions.

Example 1. int A[10][20] with w=4w = 4 at base 1000. Row-major: A[3][5]A[3][5] is at 1000+(3×20+5)×4=12601000 + (3 \times 20 + 5) \times 4 = 1260. Column-major: 1000+(5×10+3)×4=12121000 + (5 \times 10 + 3) \times 4 = 1212.

Example 2. B[1..6][-2..2] with w=8w = 8 at base 2000 (so n2=5n_2 = 5). Then B[4][1]B[4][1] is at 2000+((4−1)×5+(1−(−2)))×8=2000+18×8=21442000 + ((4 - 1) \times 5 + (1 - (-2))) \times 8 = 2000 + 18 \times 8 = 2144.

Example 3: three dimensions. int A[4][5][6] with w=4w = 4 at base 1000. By Horner's rule the row-major offset of A[2][3][4]A[2][3][4] in elements is (2×5+3)×6+4=82(2 \times 5 + 3) \times 6 + 4 = 82, so the address is 1000+82×4=13281000 + 82 \times 4 = 1328. The first dimension's size, 4, never enters the formula. If the same array is declared with lower bounds 1, as A[1..4][1..5][1..6], the subscripts become 1,2,31, 2, 3: (1×5+2)×6+3=45(1 \times 5 + 2) \times 6 + 3 = 45, and the address of A[2][3][4]A[2][3][4] is 1000+180=11801000 + 180 = 1180.

The three-address code for x = A[i][j] with int A[10][20] (row width 20×4=8020 \times 4 = 80) is

Text
t1 = i * 80
t2 = j * 4
t3 = t1 + t2
t4 = A[t3]
x  = t4

Here A[t3] uses the byte offset t3 from the base of A. Lower bounds other than 0 can be folded into a compile-time constant subtracted once, since base−(low1n2+low2)wbase - (low_1 n_2 + low_2) w does not depend on the subscripts.

Boolean expressions as jumping code

The control-flow SDD gives each BB the inherited labels B.trueB.true and B.falseB.false:

ProductionRules
B→B1∣B2B \to B_1 \mathbin{|} B_2B1.true=B.trueB_1.true = B.true; B1.false=newlabel()B_1.false = newlabel(); B2.true=B.trueB_2.true = B.true; B2.false=B.falseB_2.false = B.false; code = B1B_1.code, label(B1.falseB_1.false), B2B_2.code
B→B1&&B2B \to B_1 \mathbin{\&\&} B_2B1.true=newlabel()B_1.true = newlabel(); B1.false=B.falseB_1.false = B.false; B2.true=B.trueB_2.true = B.true; B2.false=B.falseB_2.false = B.false; code = B1B_1.code, label(B1.trueB_1.true), B2B_2.code
B→ !B1B \to\ !B_1B1.true=B.falseB_1.true = B.false; B1.false=B.trueB_1.false = B.true; code = B1B_1.code
B→E1 rel E2B \to E_1\ \textbf{rel}\ E_2code = E1E_1.code, E2E_2.code, if E1.addr rel E2.addrE_1.addr\ \textbf{rel}\ E_2.addr goto B.trueB.true, goto B.falseB.false
B→trueB \to \textbf{true} / false\textbf{false}goto B.trueB.true / goto B.falseB.false

Negation generates no code: it swaps the labels.

For statements, with S.nextS.next the label after SS:

  • if (B) S1: B.true=newlabel()B.true = newlabel(), B.false=S1.next=S.nextB.false = S_1.next = S.next; code = BB.code, label(B.trueB.true), S1S_1.code.
  • if (B) S1 else S2: B.trueB.true and B.falseB.false are new labels; after S1S_1.code comes goto S.next.
  • while (B) S1: begin=newlabel()begin = newlabel(), B.true=newlabel()B.true = newlabel(), B.false=S.nextB.false = S.next, S1.next=beginS_1.next = begin; code = label(beginbegin), BB.code, label(B.trueB.true), S1S_1.code, goto begin.

Example 4. if (x < 100 || x > 200 && x != y) x = 0; with S.next=L1S.next = L_1 (&& binds tighter than ||):

Text
    if x < 100 goto L2
    goto L3
L3: if x > 200 goto L4
    goto L1
L4: if x != y goto L2
    goto L1
L2: x = 0
L1:

Short-circuiting is visible in the trace. With y=0y = 0, x=50x = 50 needs one comparison, x=150x = 150 two, and x=250x = 250 three. The goto L3 jumps to the next instruction; a peephole pass, or the fall-through variant of the rules, removes such redundant jumps.

Backpatching

In one pass, a jump's target may lie ahead. The backpatching scheme gives BB synthesized lists B.truelistB.truelist and B.falselistB.falselist of instruction numbers whose targets are still empty, and uses a marker M→ε {M.instr=nextinstr}M \to \varepsilon\ \{M.instr = nextinstr\} to record where code for the next part starts:

ProductionActions
B→B1∣M B2B \to B_1 \mathbin{|} M\ B_2backpatch(B1.falselist,M.instrB_1.falselist, M.instr); B.truelistB.truelist = merge(B1.truelist,B2.truelistB_1.truelist, B_2.truelist); B.falselist=B2.falselistB.falselist = B_2.falselist
B→B1&&M B2B \to B_1 \mathbin{\&\&} M\ B_2backpatch(B1.truelist,M.instrB_1.truelist, M.instr); B.truelist=B2.truelistB.truelist = B_2.truelist; B.falselistB.falselist = merge(B1.falselist,B2.falselistB_1.falselist, B_2.falselist)
B→ !B1B \to\ !B_1B.truelist=B1.falselistB.truelist = B_1.falselist; B.falselist=B1.truelistB.falselist = B_1.truelist
B→E1 rel E2B \to E_1\ \textbf{rel}\ E_2B.truelistB.truelist = makelist(nextinstr); B.falselistB.falselist = makelist(nextinstr + 1); emit if E1.addr rel E2.addr goto _ and goto _

Example 5. The same condition, x < 100 || x > 200 && x != y, with instructions numbered from 100. The three comparisons emit six instructions, 100 to 105. Reducing x>200&&x≠yx > 200 \mathbin{\&\&} x \ne y backpatches its first truelist {102}\{102\} with M.instr=104M.instr = 104. Reducing the || backpatches {101}\{101\} with M.instr=102M.instr = 102. The result is

Text
100: if x < 100 goto _
101: goto 102
102: if x > 200 goto 104
103: goto _
104: if x != y goto _
105: goto _

with B.truelist={100,104}B.truelist = \{100, 104\} and B.falselist={103,105}B.falselist = \{103, 105\}. The enclosing statement fills these later: for if (B) M S1, backpatch(B.truelist,M.instrB.truelist, M.instr) points them at the start of S1S_1, and S.nextlistS.nextlist = merge(B.falselist,S1.nextlistB.falselist, S_1.nextlist) is passed upward.

Backpatching of x < 100 or x > 200 and x != y from instruction 100
Figure 1. Backpatching x < 100 || x > 200 && x != y from instruction 100: the && step fills 102 with 104, the || step fills 101 with 102, and truelist 100, 104 and falselist 103, 105 are left for the enclosing statement.

Example 6: while with backpatching. For while (a < b) a = a + 1 starting at 100, with S→while M1 (B) M2 S1S \to \textbf{while}\ M_1\ (B)\ M_2\ S_1: the condition emits 100 and 101; the body emits 102 and 103; then goto M1.instrM_1.instr is emitted. Backpatching B.truelist={100}B.truelist = \{100\} with M2.instr=102M_2.instr = 102 gives

Text
100: if a < b goto 102
101: goto _
102: t1 = a + 1
103: a = t1
104: goto 100

and S.nextlist=B.falselist={101}S.nextlist = B.falselist = \{101\}, to be filled with whatever instruction follows the loop.

Example 7: if-else with backpatching. The scheme uses two markers and a marker NN that emits the jump over the else part:

S→if (B) M1 S1 N else M2 S2S \to \textbf{if}\ (B)\ M_1\ S_1\ N\ \textbf{else}\ M_2\ S_2

with backpatch(B.truelist,M1.instrB.truelist, M_1.instr), backpatch(B.falselist,M2.instrB.falselist, M_2.instr) and S.nextlistS.nextlist = merge(S1.nextlist,N.nextlist,S2.nextlistS_1.nextlist, N.nextlist, S_2.nextlist); N→εN \to \varepsilon sets N.nextlistN.nextlist = makelist(nextinstr) and emits goto _. For if (a < b) x = 1; else x = 2; starting at 100:

Text
100: if a < b goto 102
101: goto 104
102: x = 1
103: goto _
104: x = 2

Five instructions. B.truelist={100}B.truelist = \{100\} is patched with M1.instr=102M_1.instr = 102 and B.falselist={101}B.falselist = \{101\} with M2.instr=104M_2.instr = 104. The assignments leave no jumps, so S.nextlist=N.nextlist={103}S.nextlist = N.nextlist = \{103\}: only the jump at the end of the then-part waits for the instruction after the statement. The else-part simply falls through to it.

Important properties and observations

  • Each relational test costs two instructions in the basic scheme (a conditional and an unconditional jump); &&, || and ! emit none of their own.
  • Labels are inherited; lists are synthesized. The label scheme needs inherited attributes (L-attributed), which suits top-down translation; backpatching uses only synthesized lists plus markers, which suits bottom-up one-pass translation.
  • The marker's instr is the target for the second operand, used by || for B1B_1's false exits and by && for B1B_1's true exits.
  • Row-major offset of A[i1][i2]A[i_1][i_2] is (i1n2+i2)w(i_1 n_2 + i_2) w; the number of columns n2n_2 matters, the number of rows n1n_1 does not.
  • Lists hold instruction numbers, not labels, and a jump is patched exactly once.

Common misconceptions.

  • "!B generates a negation instruction." It only swaps the true and false exits (or lists).
  • "Short-circuit code evaluates all comparisons and combines them." It stops as soon as the result is known.
  • "Column-major uses the number of columns." It uses the number of rows n1n_1 as the multiplier.

GATE examination perspective

  • Array address calculation. Combine this with row-major or column-major order, non-zero lower bounds and a given element width.
  • Three or more dimensions. Apply Horner's rule over the sizes of all dimensions except the first, after subtracting each lower bound (Example 3).
  • if-else layout. The then-part ends with a jump over the else-part; that jump is the one left on S.nextlistS.nextlist (Example 7).
  • Three-address code for array references and assignments. Count instructions and temporaries.
  • Short-circuit code. Count instructions or comparisons executed for given inputs; know that && binds tighter than ||.
  • Backpatching. Compute truelist and falselist, find which instruction gets which target, or fill in the targets of a numbered code fragment.
  • Statement translation. If, if-else and while layouts; where goto begin goes.

Summary

Sources

  1. A. V. Aho, M. S. Lam, R. Sethi, J. D. Ullman, Compilers, Principles, Techniques, and Tools, 2nd ed., Pearson Addison-Wesley, 2007: Section 6.4 (operations within expressions, incremental translation, addressing array elements, translation of array references), Section 6.6 (Boolean expressions, short-circuit code, flow-of-control statements, control-flow translation of Boolean expressions, avoiding redundant gotos), Section 6.7 (one-pass code generation using backpatching, backpatching for Boolean expressions, flow-of-control statements).

Next steps