Discussion:
[groovy-dev] Groovy.transform.CompileStatic produces odd Bytecode
Dave Cowden
2014-11-30 18:56:55 UTC
Permalink
Sorry to cross-post: I posted this on users but the realized that the dev
list is probably the right place to post...

Consider this simple class:

@groovy.transform.CompileStatic
class ScriptTestClass{
void test_method(String x,String y,String z){
x = "foo";
}
}

When compiled to bytecode, i get the below bytecode ( results of javap -c
-v ScriptTestClass.class, edited for the subject method only ):

Results of javap -c -v ScriptTestClass.class ( just the subject method
though ):

public void test_method(java.lang.String, java.lang.String,
java.lang.String);
flags: ACC_PUBLIC
Code:
stack=1, locals=5, args_size=4
0: ldc #32 // String foo
2: astore 4
4: aload 4
6: astore_1
7: aload 4
9: pop
10: return
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LScriptTestClass;
0 10 1 x Ljava/lang/String;
0 10 2 y Ljava/lang/String;
0 10 3 z Ljava/lang/String;
LineNumberTable:
line 4: 0

Clearly, **ASTORE / ALOAD 4** are not appropriate here. Slot 4 is not even
defined in the local variable table. In fact, they appear extraneous. The
bytecode is correct if these are removed.
​I cannot really say this bytecode is _Wrong_ because it technically
works. But its certainly not what I'd expect. ​
The correct bytecode ( which I get when i write this same code in a Java
class is:

public void test_method(java.lang.String, java.lang.String,
java.lang.String);
flags: ACC_PUBLIC
Code:
stack=1, locals=4, args_size=4
0: ldc #7 // String foo
2: astore_1
3: return
LineNumberTable:
line 26: 0
line 27: 3
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this Ltesting/ScriptTestClass;
0 4 1 x Ljava/lang/String;
0 4 2 y Ljava/lang/String;
0 4 3 z Ljava/lang/String;

I suspect this may be some leftovers from what the code looks like when
@CompileStatic is removed-- i think in that case there are other variables
involved.

Ideas/Thoughts?
Cédric Champeau
2014-12-01 08:48:59 UTC
Permalink
Hi,

First of all, I'd like to ask why you wonder about the bytecode here: is
it because you have an issue or is it just of personal interest? Now the
answer is pretty simple: this is an artifact of the common
infrastructure we have for both dynamic and static bytecode. Even if the
code you see is suboptimal, it's not wrong at all. In Groovy, every
expression has a return value so we need a local variable (slot 4 here)
to store it. The fact that this return value is not used is something
which is *not* analyzed by the compiler (in both dynamic and static
mode), so it can't be optimized not to use a local variable. Making it
so would be better because it would reduce the size of the bytecode and
make it look more Java-like, hence making it more likely to be JIT'ed
(both because the size of the bytecode is reduced and because it can
look closer to patterns recognized by the JIT), but it is hard work to
do so.
Post by Dave Cowden
Sorry to cross-post: I posted this on users but the realized that the
dev list is probably the right place to post...
@groovy.transform.CompileStatic
class ScriptTestClass{
void test_method(String x,String y,String z){
x = "foo";
}
}
When compiled to bytecode, i get the below bytecode ( results of javap
Results of javap -c -v ScriptTestClass.class ( just the subject method
public void test_method(java.lang.String, java.lang.String,
java.lang.String);
flags: ACC_PUBLIC
stack=1, locals=5, args_size=4
0: ldc #32 // String foo
2: astore 4
4: aload 4
6: astore_1
7: aload 4
9: pop
10: return
Start Length Slot Name Signature
0 10 0 this LScriptTestClass;
0 10 1 x Ljava/lang/String;
0 10 2 y Ljava/lang/String;
0 10 3 z Ljava/lang/String;
line 4: 0
Clearly, **ASTORE / ALOAD 4** are not appropriate here. Slot 4 is not
even defined in the local variable table. In fact, they appear
extraneous. The bytecode is correct if these are removed.
​ I cannot really say this bytecode is _Wrong_ because it technically
works. But its certainly not what I'd expect. ​
The correct bytecode ( which I get when i write this same code in a
public void test_method(java.lang.String, java.lang.String,
java.lang.String);
flags: ACC_PUBLIC
stack=1, locals=4, args_size=4
0: ldc #7 // String foo
2: astore_1
3: return
line 26: 0
line 27: 3
Start Length Slot Name Signature
0 4 0 this Ltesting/ScriptTestClass;
0 4 1 x Ljava/lang/String;
0 4 2 y Ljava/lang/String;
0 4 3 z Ljava/lang/String;
I suspect this may be some leftovers from what the code looks like
variables involved.
Ideas/Thoughts?
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
Dave Cowden
2014-12-01 12:05:33 UTC
Permalink
Hi, Cedric:

Thanks for the reply!
I'm interested in the bytecode because i'm doing a strange project :)
I'm using groovy to extend the functionality of java classes for which I do
not have the source. To do this, i'm compiling groovy classes, and then
using the bytecode to inject into existing methods of other ( existing )
classes.

In this case, the extra bytecodes referencing local variables that are not
expected causes odd behavior for the target method, which uses those slots
for other variables.

Nonetheless, thanks for your explanation!
Dave
Post by Cédric Champeau
Hi,
First of all, I'd like to ask why you wonder about the bytecode here: is
it because you have an issue or is it just of personal interest? Now the
answer is pretty simple: this is an artifact of the common infrastructure
we have for both dynamic and static bytecode. Even if the code you see is
suboptimal, it's not wrong at all. In Groovy, every expression has a return
value so we need a local variable (slot 4 here) to store it. The fact that
this return value is not used is something which is *not* analyzed by the
compiler (in both dynamic and static mode), so it can't be optimized not to
use a local variable. Making it so would be better because it would reduce
the size of the bytecode and make it look more Java-like, hence making it
more likely to be JIT'ed (both because the size of the bytecode is reduced
and because it can look closer to patterns recognized by the JIT), but it
is hard work to do so.
Sorry to cross-post: I posted this on users but the realized that the
dev list is probably the right place to post...
@groovy.transform.CompileStatic
class ScriptTestClass{
void test_method(String x,String y,String z){
x = "foo";
}
}
When compiled to bytecode, i get the below bytecode ( results of javap
Results of javap -c -v ScriptTestClass.class ( just the subject method
public void test_method(java.lang.String, java.lang.String,
java.lang.String);
flags: ACC_PUBLIC
stack=1, locals=5, args_size=4
0: ldc #32 // String foo
2: astore 4
4: aload 4
6: astore_1
7: aload 4
9: pop
10: return
Start Length Slot Name Signature
0 10 0 this LScriptTestClass;
0 10 1 x Ljava/lang/String;
0 10 2 y Ljava/lang/String;
0 10 3 z Ljava/lang/String;
line 4: 0
Clearly, **ASTORE / ALOAD 4** are not appropriate here. Slot 4 is not
even defined in the local variable table. In fact, they appear extraneous.
The bytecode is correct if these are removed.
​ I cannot really say this bytecode is _Wrong_ because it technically
works. But its certainly not what I'd expect. ​
The correct bytecode ( which I get when i write this same code in a Java
public void test_method(java.lang.String, java.lang.String,
java.lang.String);
flags: ACC_PUBLIC
stack=1, locals=4, args_size=4
0: ldc #7 // String foo
2: astore_1
3: return
line 26: 0
line 27: 3
Start Length Slot Name Signature
0 4 0 this Ltesting/ScriptTestClass;
0 4 1 x Ljava/lang/String;
0 4 2 y Ljava/lang/String;
0 4 3 z Ljava/lang/String;
I suspect this may be some leftovers from what the code looks like when
@CompileStatic is removed-- i think in that case there are other variables
involved.
Ideas/Thoughts?
--
Cédric Champeau
SpringSource - Pivotalhttp://twitter.com/CedricChampeauhttp://melix.github.io/bloghttp://spring.io/ http://www.gopivotal.com/
Loading...