Programming Language Technology, DAT151/DIT231
(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
: int
D
: double
V
: void
Z
: boolean
Context (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.
EInt i):
compileExp(
emit(iconst i)
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
ECall x es)
compileExp(: -- compile function arguments
for (e ∈ es)
compileExp(e)<- lookupFun(x) -- get the Jasmin name of function x
f -- emit function call
emit(invokestatic f)
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'.
Correct translation of assignment:
EAss x e):
compileExp(<- lookupVar x
a
compileExp(e)-store a)
emit(t-load a) emit(t
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: