6.4 Proof of Correctness
7.1.1 Compiling source program constructs in bytecode instructions
The compiler is defined inductively over the structure of the source constructs. In the following, we shall focus only on the compiler cases which we consider interesting or particular and we will assume that the rest of the cases are evident from the compiler definition.
Fig. 7.1 shows the compilation scheme for expressions. Let us look in more detail the com- pilation of the instance creation expression new Class(E). This case is not trivial because of the way the new instance is initialized. The first instruction in the compilation is the instruction s: new Classwhich creates a new reference in the heap and pushes it on the stack top (see for the operational semantics of the instruction in Section 3.8). Once the new instance is created the instructor must be invoked in order to initialize its (instance) fields. This is done by first dupli- cating the reference (instruction dup) and then invoking the constructorconstr(Class) of Class
by passing it as argument the duplicated reference. This has as effect that after the invokation of the constructor the stack top contains the reference to the newly created instance which is now initialized. Note that this compilation follows closely the JVM specification (see [75,§7.8]) which mandates how standard Java compilers must compile instance creation expressions.
ps,IntConst, sqm = s:pushIntConst
ps,null, sqm = s:pushnull
ps,this, sqm = s:loadthis
ps,E.f, eqm = ps,E, e−1qm;
e:getfieldf
ps,E.m(), eqm = ps,E, e−1qm;
e:invokem
ps,Var, sqm = s:loadVar
ps,E1opE2, eqm = p s,E1, e0qm; pe0+ 1,E 2, e−1qm; e:arith op ps,(Class)E, eqm = ps,E, e−1qm; e:checkcast Class; ps,newClass(), s+ 2qm = s:new Class; s+ 1 :dup;
s+ 2 :invokeconstr(Class);
Figure 7.1: Definition of the compiler for expressions
Fig. 7.2 presents the definition of the compiler function for statements which does not affect the exception handler table neither affect the specification tables. Let us explain in more detail the rule for conditional statement. First the conditional expression is compiled. Its compilation comprises instructions from indexstoe00+ 1 where the latter is the conditional branch instruction
e00+ 1 : ifcond e000+ 2. Remind that the ifcond instruction will compare the two stack top
elements (w.r.t. some condition ) and if they fulfill the condition in question, the control will be transfered to instruction at index e000+ 2, otherwise the next instruction at index e00+ 2 will be
executed. Note that at indexe000+ 2 starts the compilation of the then branch
pe000+ 2,S
1, eqmand
at indexe00+ 2 starts the compilation of the else branch
pe00+ 2,S
2), e000qm. After the compilation
of the else branch follows ae00+ 1 : gotoe+ 1 instruction which jumps at the first instruction
outside the compilation of the branch statement.
Fig. 7.3 shows the compiler definition for statements whose compilation will change the the exception handler of the current method. The first such statement is the try catch statement.
ps,S1;S2, eqm = ps,S1, e0qm
pe0+ 1,S2, eqm
ps,if(E1cond E2)then{S1}else{S2}, eqm =
ps,E1, e0qm; pe0+ 1,E2, e00q m; e00+ 1 :ifconde000+ 2; pe00+ 2,S 2, e000qm e000+ 1 :gotoe; pe000+ 2,S 1, e−1qm; e:nop ps,Var=E, eqm = ps,E, e−1qm e:storeVar ps,E1.f=E2, eqm = ps,E1, e0qm; pe0+ 1,E 2, e−1qm; e:putfield f ps,athrowE, eqm = ps,E, e−1qm; e:athrow; ps,returnE, eqm = ps,E, e−1qm; e:return
Figure 7.2: Definition of the compiler for statements
The compiler compiles the normal statement S1 and the exception handlerS2. Note that in the
exception handler table of the bytecode representation of methodm, a new line is added which states that the if one of bytecode instructions from index sto indexe0 throw a not handled exception of typeExcTypecontrol will be transfered to the instruction at indexExcType.
Fig 7.3 contains also the compilation scheme for a try finally statement. Remind that the semantics of the statement is that however the try statement terminates execution the finally statement must be executed after it and the whole statement terminates execution as the try terminated execution. Thus, the compilation of statement S1 is followed by the compilation of
statement S2. This should assure that after the normal execution of S1 S2 will be executed.
However, we have also to guarantee that S2 will execute after S1 if the latter terminates on
exception. For this, we create an exception handler which protectsS1from any exception thrown.
The exception handler stores the reference to the thrown object in variable l then executes the compiled finally statement S2, then loads the exception reference on the stack and re-throws it.
This compilation differs from the compilation scheme in the JVM specification for finally state- ments, which requires that the subroutines must be compiled using jsr and ret instructions. However, the semantics of the programs produced by the compiler presented here and a compiler which follows closely the JVM specification is equivalent. In the following, we discuss informally why this is true. The semantics of a jsrk instruction is to jump to the first instruction of the compiled subroutine which starts at indexkand pushes on the operand stack the index of the next instruction of the jsr that caused the execution of the subroutine. The first instruction of the compilation of the subroutine stores the stack top element in the local variable at index k ( i.e. stores in the local variable at index k the index of the instruction following thejsrinstruction). Thus, after the code of the subroutine is executed, the retk instruction jumps to the instruction following the corresponding jsr. This behavior can be actually simulated by programs without
jsr and ret but which inline the subroutine code at the places where a jsr to the subroutine is done. We assume that the local variable l is not used in the compilation of the statementS2,
which guarantees that after any execution which terminates normally of pe00+ 3,S
2, e−2qm the
local variablel will still hold the thrown object. We also assume that the statement S1 does not
contain areturninstruction.
We have put separately in Fig.7.4 the compiler definition for loop statements as its compilation is particular because it is the unique case where the specification tables of the current method
7.1 Compiler 99
ps,try{S1}catch(ExcTypeVar){S2}, eqm=
ps,S1, e0qm; e0+ 1 :gotoe; pe0+ 2,S2, e−1q m; e:nop addExcHandler(m, s, e0, e0+ 2,ExcType) ps,try{S1}finally{S2}, eqm= ps,S1, e0qm; pe0+ 1,S 2, e00qm; e00+ 1 :gotoe;
{default exception handler}
e00+ 2 :storel; pe00+ 3,S 2, e−3qm; e−2 :loadl; e−1 :athrow; e:nop addExcHandler(m, s, e0, e00+ 2, Exception)
Figure 7.3: Definition of the compiler for statements that change the exception handler table
are affected. Particularly, the compiler adds a new element in the table of loop invariants of the method m. This new element relates the indexe0+ 1 with the loop invariantINVand the list of
modified expressionsmodif. In the following, the index of the first instruction of the compilation of the loop test (in the figure this is the index e0+ 1) will be frequently referred to. Thus, we
introduce the notationloopEntryS to refer to the first instruction in the compilation of the test of the loop statementS. Actually, in the control flow graph this instruction represents a loop entry instruction following Def. 3.9.1, Section 3.9.
ps,while(E1cond E2)[INV,modif] {S}, eqm=
s:gotoe0+ 1; ps+ 1,S, e0q m; pe0+ 1,E 1, e00qm; pe00+ 1,E 2, e−1qm; e:ifconds+ 1;
addLoopSpec(m, e0+ 1,INV, modif)
Figure 7.4: Definition of the compiler for the loop statement
For an illustration, we show in Fig. 7.5 the source of method square which calculates the the square of the parameter i. First, the absolute value of i is stored in the variable v. Then the while statement calculates the sum of the impair positive numbers whose whole division by 2 is smaller than v which is the square of i. The example is also provided with specification written in JML. The specification states that the method returns the square of its parameter and that the loop invariant is (0 <= s) && (s<= v) && sqr == s∗s. In Fig. 7.6, we then show the respective compilation of method square. The example shows the correspondence between the bytecode (left) resulting from the compilation described above and the source lines(right) of method square from Fig. 7.5.
We can see in the figure how the branch statement is compiled (bytecode instructions from 4 to 12). It also shows us the somewhat unusual way into which the while statement is compiled. More particularly, the compilation of the test of thewhileis after its body while semantically the test must be executed at every iteration before the loop body. The compilation is actually correct because the instruction 15goto28 jumps to the compilation of the linewhile(s<v) and thus, the execution proceeds in the expected way. We have also marked the instructions which correspond
1 //@ ensures \r e s u l t == i∗i ; 2 public int s q u a r e ( int i ) {
3 int s q r = 0 ; 4 int v = 0 ; 5 i f ( i < 0 ) 6 then { 7 v =−i ;} 8 e l s e { 9 v = i ;} 10 int s = 0 ; 11 /∗@ loop modifies s , s q r ;
12 @ loop invariant ( 0 <= s ) && ( s <= v ) && s q r == s∗s ; 13 @∗/ 14 while( s < v ) { 15 s q r = s q r + 2∗s + 1 ; 16 s = s +1;} 17 return s q r ;}
Figure 7.5: method square written in our source language
to the loop entry and loop end which are the instructions at index 28 and 27.
Note that the bytecode has been generated by a standard Java compiler. We have modified the compilation of s = s+1; to match the definition of our compiler2 and introduced the nop
instructions. Note also that we keep the same names on bytecode and source. This is done for the sake of simplicity and in this chapter, we shall use always this convention.