1

I'm seeing some strange results when i view the bytecode for a Groovy Script that was compiled with @groovy.transform.CompileStatic

Here is the simplest class that duplicates the problem:

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

When compiled to bytecode, i get this 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. In fact, they appear extraneous. The bytecode is correct if these are removed. 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;

Why does @CompileStatic produce the bytecodes accessing local variable slot 4?

I'm using d Groovy version 2.3.7, and Java 1.7

In case it is relevant, the code I'm using to get bytes from the Groovy source is below.

This code parses a script as a class and gets the bytes:

protected ClassNode loadGroovyTestClassAsBytecode(String classSource) throws Exception{
    ClassNode classNode = new ClassNode();
    String scriptName = "ScriptTestClass.groovy";      
    Class groovyClass = groovyClassLoader.parseClass(classSource,scriptName);        
    String className = groovyClass.getName() + ".class";
    byte[] classBytes = groovyClassLoader.getClassBytes(className);

}

'groovyClassLoader' above is an instance of the following classloader, which allows fetching the bytes after loading:

public class CachingGroovyClassLoader extends GroovyClassLoader {

    private Map<String, byte[]> classBytes = new HashMap<String, byte[]>();

    public CachingGroovyClassLoader(){

    }
    public CachingGroovyClassLoader(ClassLoader parent){
        super(parent);
    }

    public byte[] getClassBytes(String name) throws IOException{
        return IOUtils.toByteArray(getResourceAsStream(name));
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        if (classBytes.containsKey(name)) {
            return new ByteArrayInputStream(classBytes.get(name));
        }
        return super.getResourceAsStream(name);
    }

    @Override
    protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
        // These six lines copied from Groovy itself, with the intention to
        // return a subclass
        InnerLoader loader = AccessController.doPrivileged(new PrivilegedAction<InnerLoader>() {
            public InnerLoader run() {
                return new InnerLoader(CachingGroovyClassLoader.this);
            }
        });
        return new BytecodeClassCollector(classBytes, loader, unit, su);
    }

    public static class BytecodeClassCollector extends ClassCollector {
        private final Map<String, byte[]> classBytes;

        public BytecodeClassCollector(Map<String, byte[]> classBytes, InnerLoader loader, CompilationUnit unit,
                SourceUnit su) {
            super(loader, unit, su);
            this.classBytes = classBytes;
        }

        @Override
        protected Class<?> onClassNode(ClassWriter classWriter, ClassNode classNode) {            
            classBytes.put(classNode.getName() + ".class", classWriter.toByteArray());
            return super.onClassNode(classWriter, classNode);
        }
    }

}

EDIT:

I had a request to post the raw class files, but i dont know how to do that here. Here are a couple more things that might be helpful:

results of javap ScriptTestClass.class:

Compiled from "ScriptTestClass.groovy"
public class ScriptTestClass implements groovy.lang.GroovyObject {
  public static transient boolean __$stMC;
  public static long __timeStamp;
  public static long __timeStamp__239_neverHappen1417366615662;
  public ScriptTestClass();
  public void test_method(java.lang.String, java.lang.String, java.lang.String);
  public java.lang.Object this$dist$invoke$1(java.lang.String, java.lang.Object);
  public void this$dist$set$1(java.lang.String, java.lang.Object);
  public java.lang.Object this$dist$get$1(java.lang.String);
  protected groovy.lang.MetaClass $getStaticMetaClass();
  public groovy.lang.MetaClass getMetaClass();
  public void setMetaClass(groovy.lang.MetaClass);
  public java.lang.Object invokeMethod(java.lang.String, java.lang.Object);
  public java.lang.Object getProperty(java.lang.String);
  public void setProperty(java.lang.String, java.lang.Object);
  public static void __$swapInit();
  static {};
  public void super$1$wait();
  public java.lang.String super$1$toString();
  public void super$1$wait(long);
  public void super$1$wait(long, int);
  public void super$1$notify();
  public void super$1$notifyAll();
  public java.lang.Class super$1$getClass();
  public java.lang.Object super$1$clone();
  public boolean super$1$equals(java.lang.Object);
  public int super$1$hashCode();
  public void super$1$finalize();
  static java.lang.Class class$(java.lang.String);
}
user1680772
  • 143
  • 2
  • 7
  • Can you post the raw classfile please? – Antimony Nov 30 '14 at 04:03
  • Hi, thanks for helping! I dont know how to upload complete files here. I do have the classfile. What format would you like it in? Or should i PM it to you? – user1680772 Nov 30 '14 at 17:00
  • This is something best discussed on the groovy-dev mailing list. "Does this make any sense to anyone" doesn't make a good Stack Overflow question. – Peter Niederwieser Nov 30 '14 at 18:10
  • Ok. Is there another way to phrase this question that does make it a good SO question? IE, is the way the question is phrased the issue, or is it simply too detailed to be on SO? – user1680772 Nov 30 '14 at 18:17

1 Answers1

0

I was able to answer this question with the help of the Groovy-dev list.

In short, they are there because there are other use cases for which they are needed, but they are not needed in this case, and the effort to remove them in the static case is prohibitive.

The exact answer ( thanks to Cedric Champeau on groovy-dev ) was:

​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.

user1680772
  • 143
  • 2
  • 7