0% found this document useful (0 votes)
57 views12 pages

Method Inlining, Dynamic Class Loading, and Type Soundness

This document discusses method inlining in the presence of dynamic class loading and proposes a framework to prove correctness and type safety. It describes how method inlining optimizations can be invalidated by later class loading, requiring revirtualization. Previous work introduced techniques like on-stack replacement and code patching to handle invalidation. The proposed framework models an optimizing semantics alongside a non-optimizing semantics. When a class loads, the optimizing semantics performs whole-program analysis, inlines newly loaded code, and patches previously loaded code using a new construct for in-place code updates. The two semantics are proved equivalent, establishing correctness. Type soundness is also proved, ensuring optimizations preserve types.

Uploaded by

Saran Sekaran
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
57 views12 pages

Method Inlining, Dynamic Class Loading, and Type Soundness

This document discusses method inlining in the presence of dynamic class loading and proposes a framework to prove correctness and type safety. It describes how method inlining optimizations can be invalidated by later class loading, requiring revirtualization. Previous work introduced techniques like on-stack replacement and code patching to handle invalidation. The proposed framework models an optimizing semantics alongside a non-optimizing semantics. When a class loads, the optimizing semantics performs whole-program analysis, inlines newly loaded code, and patches previously loaded code using a new construct for in-place code updates. The two semantics are proved equivalent, establishing correctness. Type soundness is also proved, ensuring optimizations preserve types.

Uploaded by

Saran Sekaran
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 12

Method Inlining, Dynamic Class Loading,

and Type Soundness

Neal Glew
1
and Jens Palsberg
2
1
Intel Corporation, Santa Clara, CA 95054, USA, [email protected]
2
UCLA Computer Science Dept, Los Angeles, CA 90095, [email protected]
Abstract. Method inlining is an optimisation that can be invalidated by later class loading.
A program analysis based on the current loaded classes might determine that a method call
has a unique target, but later class loading could add targets. If a compiler speculatively
inlines methods based on current information, then it will have to undo the inlining when
later classes invalidate the assumptions. The problem with undoing inlining is that it might
be executing at the time of undo and therefore require a complicated, on-the-y update of the
program state. Previous work presented techniques for dealing with invalidation including
on-stack replacement, preexistence analysis, extant analysis, and code patching. Until now,
it has been an open question whether such operations can be done in a type-safe manner,
and no formal proof of correctness exists in the literature.
In this paper we present a framework for reasoning about method inlining, dynamic class
loading, and type soundness. Our example language has both a nonoptimising and an op-
timising semantics. At the point of dynamically loading a class, the optimising semantics
does a whole-program analysis, inlines calls in the newly loaded code, and patches the in-
validated inlinings in all previously loaded code. The patching is based on a new construct
that models in-place update of machine codea technique used by some virtual machines
to do speculative method inlining. The two semantics are equivalent and both have a type
soundness propertyproving correctness and type safety of the optimisation. Our frame-
work can form the basis of a new generation of virtual machines that represent optimised
code in a typed low-level language.
1 Introduction
Devirtualisation and consequent method inlining is an important, perhaps crucial, optimisation for
object-oriented languages. Previous work has explored inlining for whole programs (see, e.g., [4])
and for programs that use dynamic class loading (see, e.g., [9, 3, 1, 5, 12]). That work has devised
analysis and transformations for devirtualisation and method inlining, shown the eectiveness of
these optimisations, and argued informally for their correctness.
When a method inlining is invalidated by class loading, it is necessary to revirtualise the call,
that is, intuitively, replace the inlined code with the call itself. Intuitively, devirtualisation and
revirtualisation are inverses of each other. Our goal is to show that while existing revirtualisation
techniques are usually not phrased for typed intermediate languages, revirtualisation can be type
safe. We will prove the correctness of a well-known revirtualisation transformation in the presence
of dynamic class loading, and we will prove that the transformation preserves typeability. As a
further contribution, we work with a typed framework for dynamic class loading that may be of
independent interest.
At the end of this section we will describe our result in more detail. First we review the issues
with dynamic class loading, and describe the problems of proving correctness and preserving
typeability.
1.1 Dynamic Class Loading
To review the issues with devirtualisation in the presence of dynamic class loading, consider this
example program in Java:

Extended abstract
class B { String cName;
void m() { ... } B x;
} if (...) { x = new C(); }
class C extends B { else { x = (B)(Class.forName(cName).newInstance());
void m() { ... } }
} x.m();
The expression Class.forName(cName).newInstance() loads a class with name cName and then
instantiates it. The program uses class loading and it type checks. Suppose now that we do a ow
analysis of the program and determine that the call site x.m() currently has a unique receiver,
namely the m method in class C (we will write C::m for this method). We could then go ahead and
inline the call to that method, knowing that later class loading might invalidate that inliningwe
call this speculative inlining. To see how such invalidation could happen, suppose that the else
branch gets executed and that it loads a class D which is a subclass of B and which overrides the
method m. Now there are two possible targets for the call x.m() so the inlining of x.m() has been
invalidated. At the Java level there is no easy way to patch the inlined call such that it reverts to
doing a dynamic dispatch.
The main problem is that an invalidated method inlining may be in a currently executing
method and therefore require a complicated, on-the-y update of the program state. Previous
work has presented techniques for minimizing the needed updates. For example, the preexistence
analysis of Detlefs and Agesen [5] determines a set of methods for which on-the-y changes to
the stack will not be needed. Additionally, the extant analysis of Sreedhar, Burke, and Choi [12]
determines a set of method inlinings which cannot be invalidated by class loading. Still, aggressive
inlining and class loading will inevitably require updates of the code memory, the stack memory,
or both. Our goal is to show how a particular form of such operations can be done in a type-safe
manner, and to prove its correctness.
Some virtual machines (for example [9] and ORP [2, 3]) use a patching technique, which is a
form of in-place code modication to revert to unoptimised code. The basic idea is to compile the
call x.m() to the following code:
...
label l1:
[Inline x.C::m()]
label l3:
...
[out of line]
label l2:
x.m();
jump l3;
(Where out of line means after the end of the function being compiled.) Then if a class is loaded
that invalidates the inlining, the virtual machine writes a jump l2; instruction at address l1. The
virtual machine keeps datastructures on the side to store the list of assumptions being made, and
the patches required to revirtualise when the assumptions no longer hold. So long as this write
is atomic and properly ordered with respect to the execution of other threads, this update will
revirtualise the code including any existing stack frames that are executing the code.
1.2 Correctness
That the technique of speculative inlining with patching is correct is far less obvious than the
correctness of inlining for whole programs. One goal of this paper is to provide a framework that
allows proving the correctness of current and future inlining techniques in the presence of dynamic
class loading. In particular we will prove correct speculative devirtualisation with patching to
revirtualise when dynamic class loading invalidates previous assumptions.
Devising a framework for proving correctness of optimisations in the presence of dynamic class
loading is not straightforward. Traditionally, such optimisations are seen as transformations of
programs before they are run. The language theoretician formalises the transformation as a map-
ping from programs to programs and proves that a program and its transformation have the same
semantics. For transformations like speculative devirtualisation with patching, transformations are
done by the just-in-time compiler (JIT) when it compiles both the initial program and any dynam-
ically loaded classes. Furthermore, dynamic class loading might trigger transformations of existing
code such as patching needed to revirtualise inlinings invalidated by the newly loaded class. We
cannot formalise this as mapping of programs to programs. Instead we view optimisation as a
second semantics for programs. This optimising semantics includes the transformations of classes
and patching of code as part of its denition. Correctness is then proving that this semantics and
the standard semantics give the same meaning to a program.
1.3 Type Preservation
Security and reliability of software is becoming increasingly more important in the internet age
just witness the spate of recent worms ravaging the internet. One way to increase the reliability
(and contribute to the security) of the compilation process is to typecheck its output. For languages
like Java and CLI that are implemented with a virtual machine that uses JITs, such typechecking
could be done on the output of the JITs, thus partially or fully eliminating JITs from the trusted
computing base. As JITs are becoming increasingly sophisticated, employing advanced optimi-
sations such as method inlining and escape analysis, checking their output provides signicant
benets to reliability and security.
All of this presupposes that JITs can produce output that typechecks, and in particular that
devirtualisation can be done in a typeability preserving manner. Our own previous work [7] shows
how traditional whole program devirtualisation techniques can be done in a typeability preserving
manner without sacricing performance. This paper and other work in this area (see [7] for refer-
ences) has addressed only whole program analysis and optimisationit does not address dynamic
class loading.
Typeability preservation for speculative devirtualisation and dynamic class loading is dicult
for the following reasons: As we observed in our previous paper, for the output of devirtualisation
to typecheck some static type information must change to reect the ow analysis that drives the
optimisation. For example, in the above program x must change to type C for the devirtualisation
of x.m() to x.C::m() to typecheck. If the devirtualisation is done speculatively, then this changing
of static type information must be done speculatively. Newly loaded classes that invalidate the
speculative assumptions must cause the static type information to change back. For example, if
the forName is executed and produces a class that does not have C::m as its m method, then xs
type must change back to B along with the revirtualisation of x.m().
Formalising how all this type changing is done is challenging. Fortunately, there is one analysis
that does not require type changing, as it is never more precise than the statically declared types.
That analysis is Class Hierarchy Analysis (CHA [4]). Furthermore this analyses is a popular
choice in JITs that do devirtualisation as it is linear time and more sophisticated analysis are
cubic time making them too expensive for JITs. This paper concentrates on CHA, and shows
that speculative devirtualisation with patching based on CHA preserves typeability. Future work
should investigate if the appropriate type changing can be formalised and proven correct for the
more precise analyseswe believe this is an interesting and challenging problem.
As with correctness, proving typeability preservation is not just showing that the transforma-
tion takes typeable programs to typeable programs. Instead, we show that the optimising semantics
is type safe. In particular, proving the standard type-preservation lemma for the optimising seman-
tics will require showing that speculative devirtualisation and patching transformations preserve
typeability.
1.4 Our Result
We present a framework for reasoning about method inlining, dynamic class loading, and type
soundness. As described above, this framework consists of two semantics for a language with dy-
namic class loading. One semantics is standard and follows previous work on formalising Java-like
languages. The other semantics includes as part of its denition of the reduction of programs, the
application of speculative devirtualisation to newly loaded classes and the application of patching
to revirtualise invalidated devirtualisations. We prove correctness by showing the equivalence of
these two semantics, and we prove typeability preservation by proving the type safety of the op-
timising semantics. Our contributions are two: rst, the framework itself is the only attempt we
know of to formalise and prove results about optimisation in the presence of dynamic class load-
ing, and second, we show that speculative devirtualisation using CHA and patching is typeability
preserving.
Our framework can form the basis of a new generation of virtual machines that represent
optimised code in a typed, low-level language. We view our work as a foundation for type-safe
just-in-time compilation, extending previous work which supports type-safe o-line compilation
[10, 13, 11, 14, 6].
Our approach does method inlining in two steps: (1) change some dynamic method invocations
to static method invocations and (2) inline the static method invocations. The idea is, as in previous
work, that in a dynamic method dispatch e.m(e
1
, ..., e
n
), if all the objects that e could
evaluate to are instances of classes which inherit m from a xed class D, then the dynamic dispatch
can be transformed to a static dispatch e.D::m(e
1
, ..., e
n
). (The expression e.D::m(e
1
, ...,
e
n
) invokes Ds version of m on e with e
1
through e
n
as arguments.) A static dispatch e.D::m(e
1
,
..., e
n
) can be inlined to e

this, x
1
, . . . , x
n
:= e, e
1
, . . . , e
n
where D has for method m, body
e

and parameters x
1
through x
n
. This is nothing other than applying a nonstandard reduction
rule at compile time, and it is straightforward to show that the rule is typability preserving. Our
optimising semantics explicitly performs the rst of the two steps; the other step is left implicit.
We have made a number of design choices for our framework, all compatible with current
virtual-machine technology. First of all, as already described, the ow analysis used during class
loading and JIT compilation is CHA. As demonstrated by Glew and Palsberg [7], CHA is the
simplest ow analysis which supports type-safe method inlining in a setting without class loading.
Moreover, CHA is fast and therefore an attractive choice for a JIT compiler.
Second, our framework assumes that all inlinings can be invalidated. This design choice sim-
plies our notation considerably. Note here that Sreedhar, Burke, and Choi [12] showed that in
practice there are many inlinings that cannot be invalidated by class loading. Such stable inlinings
are called unconditionally-monomorphic call sites. There is no conceptual problem with adding
support for unconditionally-monomorphic call sites to our framework. Such an addition would lead
to space savings in the generated code, but not execution-time savings.
Third, our framework uses a simple construct called dynnew which abbreviates the Java ex-
pression Class.forName(...).newInstance(), that is, an operation that loads some class and
immediately instantiates it. Using this construct means that we do not need to model the re-
sult of Class.forName(...) and deal with objects that reify classes, simplifying the operational
semantics. It is also easy to type check: it has type Object.
Fourth, we abstract some details of patching. In particular, we assume that it is an atomic
operation. An actual implementation of the technique in a virtual machine must ensure this atom-
icity. It must atomically write the jump instruction, it must ensure consistency between icache
and dcache, and must correctly deal with weak memory ordering on architectures with this model.
Such concerns are very low-level, we abstract them into an atomic operation that is appropriate
for the intermediate language level that we are considering.
Once the framework is dened, the main technical challenge is to reason about the optimising
semantics. In contrast to most work on type preservation and optimisation, our semantics depends
on the optimisation which in turn depends on the ow analysis which in turn must be a conservative
approximation of the semantics. In other words, the semantics, the transformation, and the analysis
are all interdependent and we must therefore reason about them together.
The following section presents our variant of Featherweight Java. Section 3 presents the op-
timising semantics that does class-hierarchy analysis, speculative devirtualisation, and patching,
and proves it type safe. Section 4 proves operational correctness, that is, the equivalence of the
standard semantics and the optimising semantics.
2 The Language
We formalise our results in Featherweight Java [8] (FJ) extended with a static dispatch construct
and facilities for dynamic class loading and dynamic patching, a language we call FJD. The lan-
guage and its presentation follow the original FJ paper as closely as possible, and is modeled
after our previous paper on type-safe method inlining [7]. In this extended abstract, we limit the
informal explanations to static dispatch, dynamic class loading, and dynamic patching.
2.1 Syntax
The syntax of FJD is:
P ::= (CD;S;e)
CD ::= class C extends C {C f; K M}
K ::= C(C f) {super(f); this.f = f;}
M ::= C m(C x) {return e;}
e ::= x

[ e.f

[ new

C(e) [ (C)

e [ e.m(e)

[ e.C::m(e)

[
dynnew

[ let x = e in x.C::m(y) patchto

x.m(y)
We use S to range over sets of labels. Each S is called a patch set.
There are eight forms of expression: variables x

, eld selection e.f

, object constructors new

C(e), casts (C)

e, dynamic method invocations e.m(e)

, static method invocations e.C::m(e)

,
combined dynamic class loading and object creation dynnew

, and patching (see below). Static


method invocation invokes Cs version of method m on object e, which should be in C or one of its
subclasses. Dynamic class loading loads a new class and creates a new object of it.
Patching is a conditional construct: the expression let x = e in x.C::m(y) patchto

x.m(y)
evaluates to the static dispatch x.C::m(y)x := e unless the label is in the patch set S, in which
case it evaluates to the dynamic dispatch x.m(y)x := e. Our transformation uses patching to
optimise a dynamic dispatch to a static dispatch with the label initially not in the patch set. If
class loading invalidates the optimisation, it adds the label into the patch set. The construct is
formalised in Section 3.3. We use a specialised patch construct that is combined with a standard
let construct to simplify our proofs. A more general construct has subtle order of evaluation issues,
and the specialised construct is sucient for our needs. We reserve patching for our transformation,
and assume that it does not appear in the source program.
Metavariable ranges over a set of labels. Notice that there is a label associated with every
expression; we assume that labels are unique in the source program. For a program P, labels(P)
denotes the set of labels used in P. To simplify the technical denitions later, all the eld names and
argument names must be distinct. Any well-typed program can easily be transformed to satisfy
these conditions. Function lab maps an expression to its label.
Some auxiliary denitions that are used in the rest of the paper appear in Figure 1. Unlike the
FJ paper, we do not make the list of class declarations global, but have them appear explicitly
as parameters to functions, predicates, and rules. Function elds(CD, C) returns a list of Cs elds
and their types; mtype(CD, C, m) returns the type of method m in class C, this type has the form
C C where C is the return type and C are the argument types; mbody(CD, C, m) returns the
body of method m in class C, this has the form (x, e) where e is the expression to evaluate and
x are the parameter names; impl (CD, C, m) returns the class from which class C inherits method
m (this might be C itself if C declares m), this has the form D::m where D is the class. Predicate
override(CD, D, m, C C) is true when method m of type C C may be declared in a subclass of
D. It checks that if D declares or inherits m then it has the same type, as required by Javas type
Field Lookup:
elds(CD, Object) =
(1)
CD(C) = class C extends D {C f; K M} elds(CD, D) = D g
elds(CD, C) = D g, C f
(2)
Method Type Lookup:
CD(C) = class C extends D {C f; K M} B0 m(B x) {return e;} M
mtype(CD, C, m) = B B0
(3)
CD(C) = class C extends D {C f; K M} m not dened in M
mtype(CD, C, m) = mtype(CD, D, m)
(4)
Method Body Lookup:
CD(C) = class C extends D {C f; K M} B0 m(B x) {return e;} M
mbody(CD, C, m) = (x, e)
(5)
CD(C) = class C extends D {C f; K M} m not dened in M
mbody(CD, C, m) = mbody(CD, D, m)
(6)
Class of Method Lookup:
CD(C) = class C extends D {C f; K M} B0 m(B x) {return e;} M
impl (CD, C, m) = C::m
(7)
CD(C) = class C extends D {C f; K M} m not dened in M
impl (CD, C, m) = impl (CD, D, m)
(8)
Valid Method Overriding:
mtype(CD, D, m) = D D0 implies C = D and C0 = D0
override(CD, D, m, C C0)
(9)
Fig. 1. Auxiliary Denitions
system. The more general rule with contravariant argument types and covariant result types could
be used, and the results of this paper would still hold (with minor changes to some of our rules).
2.2 Standard Operational Semantics
The language has two dierent operational semantics. The one presented in this section can be
thought of as the reference semantics for the languageany optimisation should simulate this
semantics. The other semantics models the devirtualisation optimisation we are trying to formalise.
It is a semantics rather than just a transformation because of the need to transform and patch at
dynamic loading time. Details and further motivation are given in Section 3.
The semantics is the same as for FJ and our previous paper [7], except for the two new constructs
and labels on the reduction relation. A reduction is optionally labeled with a class denition and
list of eld initialiser expressions. This label indicates the class that was loaded and what initial
eld values were used. The sequence of such pairs is called the dynamic load sequence of the
execution. The semantics appears in Figure 2. Metavariable X ranges over evaluation contexts,
which are expressions with exactly one hole; Xe) denotes the expression formed by replacing
the hole in X by the expression e. Unlike the FJ paper, in addition to making the list of class
declarations explicit in the rules we make the evaluation context explicit as well.
Because the language is functional and each class has exactly one constructor of a particular
form, the values of the language, which are all objects, can be represented using object constructors
new

C(e). Field access reduces to the appropriate element of e. The cast (D)
1
new
2
C(e) reduces
to the object new
2
C(e) if C is a subclass of D. If C is not a subclass of D then the cast is
elds(CD, C) = C f
(CD; S; Xnew

1
C(e).fi

2
) s (CD; S; Xei)
(10)
CD C <: D
(CD; S; X(D)

1
new

2
C(e)) s (CD; S; Xnew

2
C(e))
(11)
mbody(CD, C, m) = (x, e
0
)
(CD; S; Xnew

1
C(e).m(d)

2
) s (CD; S; Xe0{this, x := new

1
C(e), d})
(12)
mbody(CD, D, m) = (x, e
0
)
(CD; S; Xnew

1
C(e).D::m(d)

2
) s (CD; S; Xe0{this, x := new

1
C(e), d})
(13)
CD = class C extends
(CD; S; Xdynnew

)
CD,e
s (CD,CD; S; Xnew C(e))
(14)
Fig. 2. Standard Operational Semantics
irreducible representing that the cast fails as a checked run-time error. The reduction rule for
method invocation new
1
C(e).m(d)
2
looks up the method body of m in C, if this is (, x, e
0
) then
the reduced expression is the body e
0
with the actuals d substituted for the formals x and the
object new
1
C(e) substituted for this. Static method invocation new
1
C(e).D::m(d)

reduces
similarly except that the method is looked up in D, not C. Note that this method lookup can be
done at compile time and a static method invocation can be implemented as a direct call rather
than an indirect call through a virtual-dispatch table. The rule for dynnew

simply adds the new


class to the list of classes in the program and reduces to a new expression using the eld initialisers.
An irreducible expression is stuck if it is of the form x, Xe.f

), Xe.m(e)

), or Xe.D::m(e)

).
The type system prevents stuck expressions from occurring during execution of a program. Ir-
reducible expressions that are not stuck are of the form v::=new

C(v) or X(C)
1
new
2
D(e))
where D is not a subclass of C; the former represents normal termination with a fully evaluated
object, the latter represents a failed cast.
2.3 Type System
The type system consists of the following judgements:
Judgement Meaning
CD C <: D C is a subtype of D
CD; e C e is well formed and of type C
CD M OK in C M is well formed in class C
CD CD OK CD is well formed
P C P is well formed and of type C
A typing context has the form x:C where there are no duplicate variable names. The only types
are the names of classes, and such a type includes all instances of that class and its subclasses. The
rules appear in Figure 3. The bar notation denotes sequences of typing judgements, so CD; e C
abbreviates CD; e
1
C
1
, . . . , CD; e
n
C
n
.
The rules for constructors and method invocation check that each actual has a subtype of
the corresponding formal. The typing rule for dynamic method dispatch looks up the type of
the method in the class of the receiver. The typing rule for static method dispatch e.D::m(e)

requires that e has some subtype of D and looks up the type of the method in D. Note that in the
rule for patchto, the two branches must have the same type. While the rst expression (for our
transformation) is a speculative optimisation that will be patched when it is no longer semantically
correct, it continues to type check after class loading.
The rules are syntax directed, with the exception of the rules for subtyping. So, disregarding the
details of how subtyping judgments are derived, for any program there is exactly one derivation
possible. Thus for a program P and any appearing in it, each expression labeled by has a
Subtyping:
CD C <: C
CD C <: D CD D <: E
CD C <: E
CD(C) = class C extends D {...}
CD C <: D
(15)
Expression Typing:
CD; x

(x)
(16)
CD; e0 C0 elds(CD, C0) = C f
CD; e0.f

i
Ci
(17)
elds(CD, C) = D f CD; e E CD E <: D
CD; new

C(e) C
(18)
CD; e0 D
CD; (C)

e
0
C
(19)
CD; e0 C0 mtype(CD, C0, m) = D C CD; e C CD C <: D
CD; e0.m(e)

C
(20)
CD; e0 C0 CD C0 <: D mtype(CD, D, m) = E E0 CD; e C CD C <: E
CD; e0.D::m(e)

E0
(21)
CD; dynnew

Object
(22)
CD; e C CD; , x : C x.C::m(y)

D CD; , x : C x.m(y)

D
CD; let x = e in x.C::m(y) patchto

x.m(y) D
(23)
Method Typing:
CD; this : C, x : C e0 E0 CD E0 <: C0 CD(C) = class C extends D {...}
override(CD, D, m, C C0)
CD C0 m(C x) {return e0;} OK in C
(24)
Class Typing:
CD M OK in C elds(CD, D) = D g K = C(D g, C f) {super(g); this.f=f;}
CD class C extends D {C f; K M} OK
(25)
Program Typing:
CD CD OK CD; e C
(CD;S;e) C
(26)
Fig. 3. Typing Rules
uniquely determined static type. Each labels at most one expression. Let static-type(P, ) be the
type of in program P.
In the presence of dynamic loading it is not enough that the original program type checks.
All dynamically loaded classes must also type check for there to be a type safety guarantee. This
is formalised as follows. An execution of a program with classes CD with dynamic load sequence
(CD
1
, e
1
), . . .) (this sequence could be nite or innite) is type correct exactly when
(CD, CD
1
, . . . , CD
i
; ; new

C(e
i
)) C
for all appropriate i where CD
i
= class C extends and is fresh. Given these denitions, the
following type soundness result holds. Its proof is similar to standard type soundness proofs [8].
Theorem 1. A type correct execution of a well-typed program cannot get stuck.
3 Devirtualisation
This section presents a devirtualisation optimisation that speculatively devirtualises given current
information and then patches when that information is invalidated by dynamic class loading.
3.1 Class Hierarchy Analysis
Any devirtualisation optimisation is based on a ow analysis. This ow analysis maps each expres-
sion in the program to a set of classes such that the expression always evaluates to an instance of
one of those classes. In practice, a just-in-time compiler can only aord to use a simple analysis
such as Class Hierarchy Analysis (CHA) or a variant of it. CHA maps an expression to all the
classes that are subtypes of the static type of the expression. This is a crude approximation to
ow set, but surprisingly eective in practice.
Formally, subclasses(P, C) is the set of subclasses of C (including C) in program P. CHA is
dened as:
CHA(P, ) = subclasses(P, static-type(P, ))
3.2 Program Transformation
The program transformation uses CHA to transform a program fragment in a compositional
fashion. It transforms each program fragment into a similar program fragment with the same
label, and turns some dynamic method invocations into patchable static method invocations.
Specically, a dynamic call is changed to a static call when CHA determines that there is a unique
target method.
The transformation consists of these parts, and appears in Figure 4.
Transformation Meaning
[[CD]]
P
the transformation of CD within program P
[[M]]
P
the transformation of M within program P
[[e]]
P
the transformation of e within program P
[[class C extends D {C f; K M}]]
P
= class C extends D {C f; K [[M]]
P
}
[[D m(E x) {return e;}]]
P
= D m(E x) {return [[e]]
P
;}
[[x

]]
P
= x

[[e.f

]]
P
= [[e]]
P
.f

[[new

D(e)]]
P
= new

D([[e]]
P
)
[[(D)

e]]
P
= (D)

[[e]]
P
[[e.m(e)

]]
P
= let x,x = [[e]]
P
,[[e]]
P
in
x.D::m(x) patchto

x.m(x)
where E CHA(P, lab(e)) : impl (classes(P), E, m) = D::m, and x and x are fresh
[[e.m(e)

]]
P
= [[e]]
P
.m([[e]]
P
)

otherwise
[[e.D::m(e)

]]
P
= [[e]]
P
.D::m([[e]]
P
)

[[dynnew

]]
P
= dynnew

Fig. 4. The Transformation of Dynamic to Static Dispatch


The transformation preserves typability, the proof is straightforward.
Theorem 2. If P=(CD;S;d), and C, M, and e appear in P then:
If CD CD OK then [[CD]]
P
[[CD]]
P
OK.
If CD M OK in D then [[CD]]
P
[[M]]
P
OK in D.
If CD; e D then [[CD]]
P
; [[e]]
P
D.
3.3 Optimised Semantics
The optimised semantics uses CHA to speculatively devirtualise method calls, and patching to
undo these optimisations when dynamically loaded classes invalid the speculative assumption.
The initial program is optimised, so is any dynamically loaded class and its corresponding eld
initialisers. The existing classes could also be reoptimised at dynamic load time, but we do not
include this step (it would be straightforward to add).
Usually an operational semantics is dened just by a reduction relation between program
states. For FJD however, the optimised semantics is dened by its initial states and by a reduction
relation. The initial execution state for a program P = (CD;S;e) is ([[CD]]
P
; S; [[e]]
P
). The reduction
relation is dened in Figure 5.
elds(CD, C) = C f
(CD; S; Xnew

1
C(e).fi

2
) o (CD; S; Xei)
(27)
CD C <: D
(CD; S; X(D)

1
new

2
C(e)) o (CD; S; Xnew

2
C(e))
(28)
mbody(CD, C, m) = (x, e
0
)
(CD; S; Xnew

1
C(e).m(d)

2
) o (CD; S; Xe0{this, x := new

1
C(e), d})
(29)
mbody(CD, D, m) = (x, e
0
)
(CD; S; Xnew

1
C(e).D::m(d)

2
) o (CD; S; Xe0{this, x := new

1
C(e), d})
(30)
CD = class C extends
P = (CD,CD; S; Xnew

C(e)) CD

= [[CD]]
P
e

= [[e]]
P
S

= S poly(P)
(CD; S; Xdynnew

)
CD,e
o (CD,CD

; S

; Xnew

C(e

))
(31)
/ S x{x := e} = new

1
C(d1) y{x := e} = d2 mbody(CD, D, m) = (z, e
0
)
(CD; S; Xe0{this, z := new

1
C(d1), d2})
L
o P
(CD; S; Xlet x = e in x.D::m(y) patchto

x.m(y))
L
o P
(32)
S x{x := e} = new

1
C(d1) y{x := e} = d2 mbody(CD, C, m) = (z, e
0
)
(CD; S; Xe0{this, z := new

1
C(d1), d2})
L
o P
(CD; S; Xlet x = e in x.D::m(y) patchto

x.m(y))
L
o P
(33)
Fig. 5. Optimised Operational Semantics
The main dierences between the semantics are the rule for dynamic new and patching. In
the optimised semantics the newly loaded class and eld initialisers are optimised using the new
program. Furthermore, the patch set is updated to include all labels that are now polymorphic
call sitesthe set poly(P), which is dened as follows.
poly(P) = [ let ...,x=e,... in x.D::m(x) patchto

in P and
E CHA(P, lab(e)) : impl (classes(P), E, m) = D::m
The patching construct executes the static dispatch if the label is not in the patch set, and
executes the dynamic dispatch if the label is in the patch set. It is zero time, in that, it does
not reduce to the dispatch but rather to what the dispatch reduces to. This is a technical device
to simplify the proof of operational correctness. It also reects the cheap implementation cost of
the construct. Other than these remarks, the two rules just combine standard rules for let, the
dispatch rules, and the condition.
The optimised semantics is also type safe. The proof is straightforward given the previous
theorem.
Theorem 3. A type correct, optimised execution of a well-typed program cannot get stuck.
4 Correctness
This section will prove that the optimisation is correct. Specically it will show that the optimised
semantics simulates the standard semantics and vice versa.
To state the result we need a correspondence relation. This relation generalises the transfor-
mation slightly to reect the fact that the transformation is applied at consecutive loading points
rather than all at once. Its denition appears in Figure 6. Essentially, where the left program has a
dynamic dispatch the right program may have one of two expressions. It can have a corresponding
dynamic dispatch. It can also have a static dispatch that is patched to a dynamic dispatch if the
dynamic dispatch is monomorphic in the current program (the subscript P on the relation) or if the
patch label is in the current patch set (the subscript S on the relation). Correspondence extends
to contexts by considering a hole as corresponding to a hole.
corresponds
P,S
(x

, x

)
(34)
corresponds
P,S
(e1, e2)
corresponds
P,S
(e1.f

, e2.f

)
(35)
corresponds
P,S
(e1, e2)
corresponds
P,S
(new

C(e1), new

C(e2))
(36)
corresponds
P,S
(e1, e2)
corresponds
P,S
((C)

e
1
, (C)

e
2
)
(37)
corresponds
P,S
(e1, e2) corresponds
P,S
(e1, e2)
corresponds
P,S
(e1.m(e
1
)

, e2.m(e
2
)

)
(38)
D CHA(P, lab(e1)) : impl (classes(P), D, m) = C::m
corresponds
P,S
(e1, e2) corresponds
P,S
(e1, e2)
e

= let x,x = e2,e2 in x.C::m(x) patchto

x.m(x)
corresponds
P,S
(e1.m(e
1
)

, e

)
(39)
S corresponds
P,S
(e1, e2) corresponds
P,S
(e1, e2)
e

= let x,x = e2,e2 in x.C::m(x) patchto

x.m(x)
corresponds
P,S
(e1.m(e
1
)

, e

)
(40)
corresponds
P,S
(e1, e2) corresponds
P,S
(e1, e2)
corresponds
P,S
(e1.C::m(e
1
)

, e2.C::m(e
2
)

)
(41)
corresponds
P,S
(dynnew

, dynnew

)
(42)
corresponds
P,S
(e1, e2)
corresponds
P,S
(D m(C x) {return e1;}, D m(C x) {return e2;})
(43)
corresponds
P,S
(M1, M2)
corresponds
P,S
(class C extends D {C f; K M1}, class C extends D {C f; K M2})
(44)
P = (CD1;S1;e1) corresponds
P,S
2
(CD1, CD2) corresponds
P,S
2
(e1, e2)
corresponds(P, (CD1;S2;e2))
(45)
Fig. 6. The Correspondence Relation
The transformation is a special case of correspondence.
Theorem 4.
If P

is the initial state for program P in the optimised semantics then corresponds(P, P

).
For any P and S, corresponds
P,S
(CD, [[CD]]
P
).
For any P and S, corresponds
P,S
(e, [[e]]
P
).
The optimised semantics simulates the standard semantics and vice versa.
Theorem 5. If corresponds(P
1
, P

1
) then:
If P
1
L

s
P
2
then P

1
L

o
P

2
and corresponds(P
2
, P

2
) for some P

2
.
If P

1
L

o
P

2
then P
1
L

s
P
2
and corresponds(P
2
, P

2
) for some P
2
.
We have proven both theorems, but omit the proofs due to a lack of space.
References
1. Matthew Arnold and Barbara Ryder. Thin guards: A simple and eective technique for reducing the
penalty of dynamic class loading. In Sixteenth European Conference on Object-Oriented Programming,
volume 2374 of Lecture Notes in Computer Science, pages 498524, Malaga Spain, June 2002.
2. Michal Cierniak, Marsha Eng, Neal Glew, Brian Lewis, and James Stichnoth. The open runtime
platform: A exible high-performance managed runtime environment. Intel Technical Journal, 7(1),
February 2003.
3. Michal Cierniak, Guei-Yuan Lueh, and James Stichnoth. Practicing judo: Java under dynamic op-
timizations. In ACM SIGPLAN Conference on Programming Language Design and Implementation,
pages 1326, British Columbia, Canada, June 2000.
4. Jerey Dean, David Grove, and Craig Chambers. Optimization of object-oriented programs using
static class hierarchy analysis. In Proceedings of European Conference on Object-Oriented Program-
ming, pages 77101, Aarhus, Denmark, August 1995. Springer-Verlag (LNCS 952).
5. David Detlefs and Ole Agesen. Inlining of virtual methods. In Proceedings of ECOOP99, European
Conference on Object-Oriented Programming, pages 258278. Springer-Verlag (LNCS 1628), 1999.
6. Neal Glew. An ecient class and object encoding. In Proceedings of OOPSLA00, ACM SIGPLAN
Conference on Object-Oriented Programming Systems, Languages and Applications, pages 311324,
Minneapolis, Minnesota, October 2000.
7. Neal Glew and Jens Palsberg. Type-safe method inlining. In Proceedings of ECOOP02, European
Conference on Object-Oriented Programming, pages 525544. Springer-Verlag (LNCS 2374), Malaga,
Spain, June 2002.
8. Atsushi Igarashi, Benjamion Pierce, and Philip Wadler. Featherweight Java: A minimal core cal-
culus for Java and GJ. In ACM SIGPLAN Conference on Object-Oriented Programming, Systems,
Languages, and Applications, pages 132146, Denver, CO, USA, October 1999.
9. Kazuaki Ishizaki, Motohiro Kawahito, Toshiaki Yasue, Hideaki Komatsu, and Toshio Nakatani. A
study of devirtualization techniques for a Java just-in-time compiler. In Proceedings of the Fifteenth
Annual Conference on Object-Oriented Programming Systems, Languages, and Applications (OOP-
SLA00), 2000.
10. Greg Morrisett, David Tarditi, Perry Cheng, Christopher Stone, Robert Harper, and Peter Lee. The
TIL/ML compiler: Performance and safety through types. In ACM SIGPLAN Workshop on Compiler
Support for System Software, Tucson, AZ, USA, February 1996.
11. Greg Morrisett, David Walker, Karl Crary, and Neal Glew. From System F to typed assembly language.
ACM Transactions on Progamming Languages and Systems, 21(3):528569, May 1999.
12. Vugranam Sreedhar, Michael Burke, and Jong-Deok Choi. A framework for interprocedural optimiza-
tion in the presence of dynamic class loading. In Proceedings of PLDI00, ACM SIGPLAN Conference
on Programming Language Design and Implementation, pages 196207, 2000.
13. David Tarditi, Greg Morrisett, Perry Cheng, Christopher Stone, Robert Harper, and Peter Lee. TIL:
A type-directed optimizing compiler for ML. In 1996 ACM SIGPLAN Conference on Programming
Language Design and Implementation, pages 181192, Philadelphia, PA, USA, May 1996. ACM Press.
14. Andrew Wright, Suresh Jagannathan, Cristian Ungureanu, and Aaron Hertzmann. Compiling Java to
a typed lambda-calculus: A preliminary report. In ACM Workshop on Types in Compilation, Kyoto,
Japan, March 1998.

You might also like