Signature:
I
: int
D
: double
V
: void
Z
: boolean
Context (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)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.
EAdd t e₁ e₂):
compileExp(
compileExp(e₁)
compileExp(e₂)-add) -- either iadd or dadd
emit(t
EVar x):
compileExp(<- lookupVar
a -load a) -- either iload or dload
emit(t
EAss x e):
compileExp(<- lookupVar x
a
compileExp(e)-store a) -- either istore or dstore
emit(t-- 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'.
A statement (sequence) is compiled to instructions whose effect on the stack is nil (no change).
SInit t x e):
compileStm(<- newLocal t x
a
compileExp(e)-store a -- either istore or dstore
t
SExp t e):
compileStm(
compileExp(e)-pop -- either pop or pop2
t
SBlock ss):
compileStm(
newBlock: ss)
for (s
compileStm(s) popBlock
Correctness 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
;; beginning of loop, check condition
L0: ;; i
iload_1 ;; j
iload_0 ;; i < j ?
if_icmplt L2 ;; no: put false
iconst_0
goto L3;; yes: put true
L2:
iconst_1;; boolean value of i < j is on top of the stack
L3: ;; if top of stack is 0, exit while loop
ifeq L1 ...
;; repeat loop
goto L0 ;; end of loop L1:
We would like to skip the computation of the boolean value, but jump directly according to the condition:
;; beginning of loop, check condition
L0: ;; i
iload_1 ;; j
iload_0 ;; exit loop if i ≥ j
if_icmpge L1 ...
;; repeat loop
goto L0 ;; end of loop L1:
We 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:
SWhile e s):
compileStm(Lstart, Ltrue, Lfalse ← newLabel
Lstart:
Ltrue, Lfalse)
compileBool (e, Ltrue:
compileStm(s)Lstart
goto Lfalse:
SIfElse e s₁ s₂):
compileStm(...
compileBool
is given by the following compilation schemes:
comparison operators:
ELt int e₁ e₂, Ltrue, Lfalse):
compileBool(
compileExp(e₁)
compileExp(e₂)Ltrue
if_icmplt Lfalse
goto
EGt t e₁ e₂, Ltrue, Lfalse):
compileBool(...
logical operators:
ETrue, Ltrue, Lfalse):
compileBool(ETrue
goto
EFalse, Ltrue, Lfalse):
compileBool(EFalse
goto
EAnd e₁ e₂, Ltrue, Lfalse):
compileBool(Ltrue' ← newLabel
Ltrue', Lfalse)
compileBool(e₁, Ltrue':
Ltrue, Lfalse)
compileBool(e₂,
EOr e₁ e₂, Ltrue, Lfalse):
compileBool(...
Sometimes booleans need to be represented by 0 and 1, e.g. when assigning to a boolean variable:
| typeOf(e) == bool:
compileExp(e) Ltrue, Lfalse <- newLabel
-- speculate "true"
iconst_1 Ltrue, Lfalse)
compileBool(e, Lfalse: -- no? change to "false"
pop
iconst_0Ltrue: