【问题标题】:Odd Bytecodes from Groovy CompileStatic来自 Groovy CompileStatic 的奇数字节码
【发布时间】:2015-01-28 08:20:51
【问题描述】:

当我查看使用 @groovy.transform.CompileStatic 编译的 Groovy 脚本的字节码时,我看到了一些奇怪的结果

这是复制问题的最简单的类:

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

当编译为字节码时,我得到了这个字节码(javap -c -v ScriptTestClass.class 的结果,仅针对主题方法编辑):

javap -c -v ScriptTestClass.class 的结果(虽然只是主题方法):

  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

显然,ASTORE / ALOAD 4 在这里不合适。事实上,它们看起来是多余的。如果这些被删除,则字节码是正确的。正确的字节码(当我在 Java 类中编写相同的代码时得到的是:

  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;

为什么@CompileStatic 会生成访问局部变量槽 4 的字节码?

我正在使用 d Groovy 版本 2.3.7 和 Java 1.7

如果相关,我用来从 Groovy 源获取字节的代码如下。

此代码将脚本解析为一个类并获取字节:

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'是以下类加载器的一个实例,它允许在加载后获取字节:

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);
        }
    }

}

编辑:

我有一个发布原始类文件的请求,但我不知道该怎么做。这里 还有几件事可能会有所帮助:

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);
}

【问题讨论】:

  • 你能发布原始类文件吗?
  • 您好,感谢您的帮助!我不知道如何在这里上传完整的文件。我确实有类文件。您希望它采用什么格式?还是我应该 PM 给你?
  • 这是 groovy-dev 邮件列表中最好讨论的内容。 “这对任何人都有意义吗”并不是一个好的 Stack Overflow 问题。
  • 好的。有没有另一种方式来表达这个问题,这确实使它成为一个很好的 SO 问题? IE,问题的表述方式是问题,还是太详细而无法讨论?

标签: java groovy bytecode


【解决方案1】:

我能够在 Groovy-dev 列表的帮助下回答这个问题。

简而言之,它们之所以存在,是因为存在其他需要它们的用例,但在这种情况下不需要它们,并且在静态情况下移除它们的努力是令人望而却步的。

确切的答案(感谢 groovy-dev 上的 Cedric Champeau)是:

​现在答案很简单:这是通用基础设施的产物 我们有动态和静态字节码。 即使您看到的代码不是最理想的,也完全没有错。在 Groovy,每个表达式都有一个返回值,所以我们需要一个本地的 变量(此处为插槽 4)来存储它。这个返回值是 not used 是编译器分析的东西(在这两种情况下) 动态和静态模式),所以不能优化不使用本地 多变的。这样做会更好,因为它会减少 字节码的大小并使它看起来更像Java,因此使它 更有可能被 JIT 处理(都是因为字节码的大小是 减少,因为它可以看起来更接近于识别的模式 JIT),但这样做很难。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-21
    • 1970-01-01
    • 2014-05-17
    • 2017-12-11
    相关资源
    最近更新 更多