Re: Incremental Java Compile
On Wed, 12 May 2010, Joshua Maurice wrote:
//AA.java
public class AA { public final int x = 1; }
//BB.java
public class BB { public int x = new AA().x; }
//javap -verbose -classpath . BB
public BB();
Code:
Stack=3, Locals=1, Args_size=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: aload_0
5: new #2; //class AA
8: dup
9: invokespecial #3; //Method AA."<init>":()V
12: invokevirtual #4; //Method java/lang/Object.getClass:()Ljava/lang/Class;
15: pop
16: iconst_1
17: putfield #5; //Field x:I
20: return
Specifically note that the instructions to initialize BB.x involve
"iconst_1", which, as I understand it, puts the constant 1 on the
stack. javac, even with -g, inlined the value of a final not-static
int field.
Yeah, this is a bit weird.
Also, what the hell is that getClass call all about? I see that in code i
compile too (javac 1.6.0_16). A bit of googling reveals it's the code
generated to force a null check of a variable, and this is used in
compiling certain contortions involving inner classes. But there's no
inner class here, and there is no way in a month of sundays that the top
of stack can be null at instruction 12 - it's produced by applying dup to
the result of new, and new can never produce a null (right?). So what's it
doing?
Anyway, turning back to the initialisation of x. if you look at the
bytecode of AA, that's also weird. It has a constructor which does
iconst_1 + putfield to initialise x - but x *also* has a ConstantValue
attribute, giving it the value 1. Why both? If you write a verion of AA
where x is static, then there's only a ConstantValue, and no synthetic
clinit or anything touching it. Or instead make it non-final, and of
course it keeps the constructor but loses the ConstantValue.
The good news is that it looks like you can detect 'silently inlinable'
variables by the presence of a ConstantValue attribute. The bad news is
that javac does seem to be violating the VM spec (AIUI) here.
And on the gripping hand, you still have no way to discover the relevance
of AA from CC (the class you mention in a later post).
When i looked into this a while ago, my planned approach was:
1. Keep a table of explicit dependencies between classes (ie CC -> BB, but
not CC -> AA)
2. Keep a tree of direct inheritance relationships, probably including
interface implementation (ie BB -> AA)
3. Define the 'signature' of a class to be the aggregation of its
kind (class or interface), name, list of direct supertypes, the names
and types of its non-private fields, the values of its constant fields,
and the names, parameter types, return types, and exception lists of
its methods. Anything else?
4. When a source file changes, recompile, and compare the signature of the
new class to that of the old class
5. If the signature has changed, walk the inheritance tree, and build
the set of all classes which descend from the class - call this,
including the original class, the family.
6. Use the dependency table to find every class which depends on a member
of the family. Call these the friends.
7. Recompile the family and friends.
8. Repeat the analysis on the newly recompiled files; this is necessary
because changes to constant values can propagate.
If you extend javap to report constant field values, then you can use the
hash of the output of javap has a practical stand-in for a complete
signature. It's a bit oversensitive, because it will change if you add or
remove a static block, or cause the set of secret inner-class backdoor
methods to change, neither of which really change the signature.
I didn't know about ghost dependencies, so i didn't deal with those at
all. But on that subject - am i right in thinking that to build the set of
ghost dependencies, you need to know every name used by the class? If so,
doesn't that already cover this situation? CC uses the name BB.x, and
presumably you have to have an inheritance rule like the above that means
that a change to AA.x means a change to BB.x if there is no actual BB.x.
It is really bloody annoying that compile-time constants can be inlined
like this. Would it be legal for a compiler to *not* inline them? If so,
an option to javac to tell it to do that would be incredibly useful in a
situation like this.
tom
--
inspired by forty-rod whiskey