(Additional notes complementing the slides.)
Produce binary code or symbolic assembly.
.class file by jasmin.Translate variable names to addresses
Translate expression trees to stack instruction sequences
Translate control structures and boolean operations to jumps
Translate function calls into machine-level calls
Signature (global symbol table):
I: intD: doubleV: voidZ: booleanContext (local symbol table):
newLocal)lookupVar)newBlock, popBlock)State
emit)The emit function:
emit!iconst if ≥ -1 and ≤ 5 (takes 1 byte)bipush if ≥ -128 and ≤ 127 (takes 2 bytes)ldc otherwise (takes ~ 5 bytes)We specify the compiler by so-called compilation schemes (pseudo-code describing syntax-directed traversal).
An expression of type t is compiled to instructions whose effect is to leave a new value of type t on top of the stack, and leave the rest of the stack unchanged.
compileExp(EInt i):
emit(iconst i)
compileExp(EAdd t e₁ e₂):
compileExp(e₁)
compileExp(e₂)
emit(t-add) -- either iadd or dadd
compileExp(EVar x):
a <- lookupVar
emit(t-load a) -- either iload or dload
compileExp(ECall x es)
for (e ∈ es): -- compile function arguments
compileExp(e)
f <- lookupFun(x) -- get the Jasmin name of function x
emit(invokestatic f) -- emit function call
compileExp(EAss x e):
a <- lookupVar x
compileExp(e)
emit(t-store a) -- either istore or dstore
-- Problem here(We omit emit in the following.)
JVM Small-step semantics without jumps:
i : ⟨V,S⟩ → ⟨V',S'⟩
i : JVM instruction (or instruction sequence)
V / V' : variable store before/after
S / S' : stack before/after
We say γ ~ V if environment γ translates to variable store V.
Correctness statement (simplified):
If γ ⊢ e ⇓ ⟨v, γ'⟩ and γ ~ V then compileExp(e) : ⟨V,S⟩ →* ⟨V',S.v⟩ such that γ' ~ V'.
Correct translation of assignment:
compileExp(EAss x e):
a <- lookupVar x
compileExp(e)
emit(t-store a)
emit(t-load a)A statement (sequence) is compiled to instructions whose effect on the stack is nil (no change).
compileStm(SInit t x e):
a <- newLocal t x
compileExp(e)
t-store a -- either istore or dstore
compileStm(SExp t e):
compileExp(e)
t-pop -- either pop or pop2
compileStm(SBlock ss):
newBlock
for (s : ss)
compileStm(s)
popBlockCorrectness statement (simplified):
If γ ⊢ s ⇓ γ' and γ ~ V then compileStm(s) : ⟨V,S⟩ →* ⟨V',S⟩ such that γ' ~ V'.
The simplest compiler will compile boolean expressions to instructions that leave either 0 (for false) or 1 (for true) on the stack.
However, this leads to convoluted translation of control-flow statements like if and while.
Example:
while (i < j) {...}becomes
L0: ;; beginning of loop, check condition
iload_1 ;; i
iload_0 ;; j
if_icmplt L2 ;; i < j ?
iconst_0 ;; no: put false
goto L3
L2: ;; yes: put true
iconst_1
L3: ;; boolean value of i < j is on top of the stack
ifeq L1 ;; if top of stack is 0, exit while loop
...
goto L0 ;; repeat loop
L1: ;; end of loopWe would like to skip the computation of the boolean value, but jump directly according to the condition:
L0: ;; beginning of loop, check condition
iload_1 ;; i
iload_0 ;; j
if_icmpge L1 ;; exit loop if i ≥ j
...
goto L0 ;; repeat loop
L1: ;; end of loopWe can get close to this in a compositional way by compiling boolean expressions as jumps to either label Ltrue (if the expression will get value true) or label Lfalse (otherwise).
compileBool (Exp e, Label Ltrue, Label Lfalse)
For compiling control-flow, we use it as follows:
compileStm(SWhile e s):
Lstart, Ltrue, Lfalse ← newLabel
Lstart:
compileBool (e, Ltrue, Lfalse)
Ltrue:
compileStm(s)
goto Lstart
Lfalse:
compileStm(SIfElse e s₁ s₂):
... compileBool is given by the following compilation schemes:
comparison operators:
compileBool(ELt int e₁ e₂, Ltrue, Lfalse):
compileExp(e₁)
compileExp(e₂)
if_icmplt Ltrue
goto Lfalse
compileBool(EGt t e₁ e₂, Ltrue, Lfalse):
... logical operators:
compileBool(ETrue, Ltrue, Lfalse):
goto ETrue
compileBool(EFalse, Ltrue, Lfalse):
goto EFalse
compileBool(EAnd e₁ e₂, Ltrue, Lfalse):
Ltrue' ← newLabel
compileBool(e₁, Ltrue', Lfalse)
Ltrue':
compileBool(e₂, Ltrue, Lfalse)
compileBool(EOr e₁ e₂, Ltrue, Lfalse):
... Sometimes booleans need to be represented by 0 and 1,
e.g. when assigning to a boolean variable:
compileExp(e) | typeOf(e) == bool:
Ltrue, Lfalse <- newLabel
iconst_1 -- speculate "true"
compileBool(e, Ltrue, Lfalse)
Lfalse: -- no? change to "false"
pop
iconst_0
Ltrue: