Groovy source code analysis

Posted by gl_itch on Sat, 23 Oct 2021 11:27:07 +0200

2021SC@SDUSC

Method call implementation in Groovy

Previously, we analyzed the biggest extension of groovy to Java, that is, metaprogramming. Through the introduction of several core classes in groovy metaprogramming and the analysis of source code, we clearly understand how the bottom layer of groovy implements metaprogramming. Groovy metaprogramming has many uses, such as dynamically creating methods and dynamically intercepting methods through metaprogramming, Method synthesis and so on. If you are interested, you can learn more by yourself. This time, let's analyze the call implementation of methods in groovy.

People familiar with Groovy syntax should know that the values of two variables can be easily exchanged in Groovy, such as:

def (a, b) = [1, 2];
(a, b) = [b, a];

In this way, the values of a and B variables are exchanged. Is it convenient? How is Groovy implemented?

Let's first look at the generated bytecode file. The key codes are as follows:

// Method descriptor #39 ()Ljava/lang/Object;
  // Stack: 4, Locals: 6
  public java.lang.Object run();
       invokestatic Main.$getCallSiteArray() : org.codehaus.groovy.runtime.callsite.CallSite[] [17]
       astore_1
       iconst_2
       anewarray java.lang.Object [41]
       dup
       iconst_0
       iconst_1
       invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47]
       aastore
       dup
       iconst_1
       iconst_2
       invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47]
       aastore
       invokestatic org.codehaus.groovy.runtime.ScriptBytecodeAdapter.createList(java.lang.Object[]) : java.util.List [53]
       astore_2
       aload_1
       ldc <Integer 1> [54]
       aaload
       aload_2
       iconst_0
       invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47]
       invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.call(java.lang.Object, java.lang.Object) : java.lang.Object [57] [nargs: 3]
       astore_3 [a]
       aload_1
       ldc <Integer 2> [58]
       aaload
       aload_2
       iconst_1
       invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47]
       invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.call(java.lang.Object, java.lang.Object) : java.lang.Object [57] [nargs: 3]
       astore 4 [b]
       aload_2
       pop
       iconst_2
       anewarray java.lang.Object [41]
       dup
       iconst_0
       aload 4 [b]
       aastore
       dup
       iconst_1
       aload_3 [a]
       aastore
       invokestatic org.codehaus.groovy.runtime.ScriptBytecodeAdapter.createList(java.lang.Object[]) : java.util.List [53]
       astore 5
       aload_1
       ldc <Integer 3> [59]
       aaload
       aload 5
       iconst_0
       invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47]
       invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.call(java.lang.Object, java.lang.Object) : java.lang.Object [57] [nargs: 3]
       astore_3 [a]
       aload_1
       ldc <Integer 4> [60]
       aaload
       aload 5
       iconst_1
      invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47]
      invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.call(java.lang.Object, java.lang.Object) : java.lang.Object [57] [nargs: 3]
      astore 4 [b]
      aload 5
      areturn
      aconst_null
      areturn

We decompile it, similar to the following code:

public Object main(){
	org.codehaus.groovy.runtime.callsite.CallSite[] callsite = Main.$getCallSiteArray();
	
	List alist = org.codehaus.groovy.runtime.ScriptBytecodeAdapter.createList(new Object[]{1,2});
	
	Object a = callsite[1].call(alist, 0);//It is equivalent to alist.getAt(0) and alist.get(0);
	Object b = callsite[2].call(alist, 1);//Equivalent to alist.getAt(1) equivalent to alist.get(1);
	
	List blist = org.codehaus.groovy.runtime.ScriptBytecodeAdapter.createList(new Object[]{b,a});
	
	a = callsite[3].call(blist, 0);//Equivalent to blist.getAt(0) equivalent to blist.get(0);
	b = callsite[4].call(blist, 1);//Equivalent to blist.getAt(1) equivalent to blist.get(1);
}

private static synthetic SoftReference<CallSiteArray> $callSiteArray;

private static synthetic org.codehaus.groovy.runtime.callsite.CallSite[] $getCallSiteArray(){
	org.codehaus.groovy.runtime.callsite.CallSiteArray rtrun = null;
	
	if(Main.$callSiteArray == null){
		rtrun = Main.$createCallSiteArray();
		Main.$callSiteArray = new SoftReference<CallSiteArray>(temp);
	}else{
		rtrun = Main.$callSiteArray.get();
	}
	return rturn.array;
}

private static synthetic org.codehaus.groovy.runtime.callsite.CallSiteArray $createCallSiteArray(){
	String[] sarry = new String[5];
	Main.$createCallSiteArray_1(sarry)
	
	return new CallSiteArray(Main.class, sarry);
}

private static synthetic void $createCallSiteArray_1(java.lang.String[] sarry){
	sarry[0] = "runScript";
	sarry[1] = "getAt";
	sarry[2] = "getAt";
	sarry[3] = "getAt";
	sarry[4] = "getAt";
}

You can clearly see what the Groovy compiler does.

Although it is very simple, it can be seen that the compiler will create the corresponding CallSiteArray object according to the method call according to the execution mode of groovy. Groovy changes the call of many methods to CallSite.call, which supports many dynamic features. (groovy caches the created CallSite object, which is of SoftReference type.)

For example, in the above example, Groovy creates a CallSite and then calls the getAt method through CallSite.

After Groovy delegates the call to CallSite, at the beginning, the specific implementation of CallSite is CallSiteArray,  

CallSiteArray delegates the call to the org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSite, Object, Object []) method

This method is responsible for creating a specific call point object and executing the corresponding method.

The final call point object created here is   Org.codehaus.groovy.runtime.callsite.pojometamethodsite.pojometamethodsitenounwrappnocoerce. The specific code is as follows:

 public static class PojoMetaMethodSiteNoUnwrapNoCoerce extends PojoMetaMethodSite {

        public PojoMetaMethodSiteNoUnwrapNoCoerce(CallSite site, MetaClassImpl metaClass, MetaMethod metaMethod, Class params[]) {
            super(site, metaClass, metaMethod, params);
        }

        public final Object invoke(Object receiver, Object[] args) throws Throwable {
            try {
                return metaMethod.invoke(receiver,  args);
            } catch (GroovyRuntimeException gre) {
                throw ScriptBytecodeAdapter.unwrap(gre);
            }
        }
    }

The actual call will be delegated to the invoke method of this class.

The specific types of the methodmethod here are:

org.codehaus.groovy.runtime.dgm$243@527e5409[name: getAt params: [int] returns: class java.lang.Object owner: interface java.util.List]

  This class has no source code, only class files. The decompilation is as follows:

import java.util.List;
import org.codehaus.groovy.reflection.CachedClass;
import org.codehaus.groovy.reflection.GeneratedMetaMethod;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;

public class dgm$243 extends GeneratedMetaMethod {
	public dgm$243(String paramString, CachedClass paramCachedClass, Class paramClass, Class[] paramArrayOfClass) {
		super(paramString, paramCachedClass, paramClass, paramArrayOfClass);
	}

	public Object invoke(Object paramObject, Object[] paramArrayOfObject) {
		return DefaultGroovyMethods
				.getAt((List) paramObject, DefaultTypeTransformation.intUnbox(paramArrayOfObject[0]));
	}

	public final Object doMethodInvoke(Object paramObject, Object[] paramArrayOfObject) {
		paramArrayOfObject = coerceArgumentsToClasses(paramArrayOfObject);
		return DefaultGroovyMethods
				.getAt((List) paramObject, DefaultTypeTransformation.intUnbox(paramArrayOfObject[0]));
	}
}

You can see that the specific call is org. Codehaus. Groovy. Runtime. Defaultgroovymethods. Getat (list < T >, int)

/**
     * Support the subscript operator for a List.
     * <pre class="groovyTestCase">def list = [2, "a", 5.3]
     * assert list[1] == "a"</pre>
     *
     * @param self a List
     * @param idx  an index
     * @return the value at the given index
     * @since 1.0
     */
    public static <T> T getAt(List<T> self, int idx) {
        int size = self.size();
        int i = normaliseIndex(idx, size);
        if (i < size) {
            return self.get(i);
        } else {
            return null;
        }
    }

Finally, the call of a method is completed,.

Compared with Java, although it provides high flexibility, it also sacrifices some performance.

Topics: Java Groovy