Groovy source code analysis

Keywords: Java Groovy

2021SC@SDUSC

Call Site analysis

Groovy 1.6 introduces Call Site optimization. Call Site optimization is actually the cache of method selection.

Method selection

In static languages (such as Java), the binding of method calls is completed at compile time (not exactly, such as virtual functions, but in general, method calls in static languages are very efficient). In dynamic languages (such as Groovy), the method of invocation is chosen at runtime. This is also one of the important reasons why dynamic languages are slower than static languages.
For example, call "a.call(1)".
If it is Java, the method to be called will be selected during compilation. In this example, the class declared by the a object (note that it is not the real class of the a object, because the real class can only be known at runtime) has a method with the name of call, a parameter and the parameter type of int (the actual situation is much more complex, such as boxing and variable parameter) , if it cannot be found, the compilation fails, otherwise method binding is performed. When disassembling this method call, you can see instructions such as "invokevirtual #4; //Method call:(I)V", indicating that the method is bound, including method name "call", parameter type "I" (int), and return value "V" (void).
In the case of Groovy, all these are completed by the Groovy runtime. When compiling the code, Groovy does not check whether a method can match the call. After compiling with Groovy 1.5.7 and decompiling it into Java code, you can see statements such as "scriptbytecodeadapter. Invokemethodn (Class1, a," call ", new object [] {new integer (1)})". It can be seen that Groovy does not really choose the method to call during compilation, but it is up to the runtime to decide.

Call Site

According to Wikipedia's definition ([url]http://en.wikipedia.org/wiki/Call_site[/url]), Call Site is the call of one line method, for example:

a = sqr(b);c = sqr(b);

There are two call sites.


Call Site optimization

Before Groovy 1.6, for the same Call Site, if the method is called n times, the method will be selected n times, such as:

for (i in 1..3) {    a.call(i)}

Here, Groovy makes three choices for the call method, even if the results of the three choices are the same.
The Call Site optimization introduced in Groovy 1.6 caches the method selection results of the same Call Site. If the parameter types are the same in the next call, call the cached method, otherwise reselect. This is the basic idea of Call Site optimization.

code analysis

Consider the following Groovy Code:

class A {
    def a() {}
    def b() {}
    def b(int i) {}
}
class B {
    def a = new A()
    def c() {
        a.a()
        d()
    }
    def d() {
        a.a()
        a.b()
        a.b(1)
    }
}

We first compile this code with Groovy, and then decompile the compiled class file with jad. Because the methods in class A are empty, we only look at the decompilation results of class B. The following are the c() and d() methods in class B:

public Object c()
{
    CallSite acallsite[] = $getCallSiteArray();
    acallsite[1].call(a);
    return acallsite[2].callCurrent(this);
}
public Object d()
{
    CallSite acallsite[] = $getCallSiteArray();
    acallsite[3].call(a);
    acallsite[4].call(a);
    return acallsite[5].call(a, $const$0); // $const$const $0  is constant 1
}

Let's take a look at $getCallSiteArray():

    private static CallSiteArray $createCallSiteArray()
    {
        return new CallSiteArray($ownClass, new String[] {
            "<$constructor$>", "a", "d", "a", "b", "b" // Method name of each Call Site
        });
    }
    private static CallSite[] $getCallSiteArray()
    {        CallSiteArray callsitearray;
        if($callSiteArray == null || (callsitearray = (CallSiteArray)$callSiteArray.get()) == null)
        {
            callsitearray = $createCallSiteArray();
            $callSiteArray = new SoftReference(callsitearray);
        }
        return callsitearray.array;
    }

$getCallSiteArray() is actually a lazy creation of $callSiteArray.
We can see that "acallsite[1].call(a);" is to call the CallSite with the method name "a", while "acallsite[2].callCurrent(this);" is to call the CallSite with the method name "d", and so on.
Let's take a look at what is done in the constructor of CallSiteArray:

    public CallSiteArray(Class owner, String [] names) {
        this.owner = owner;
        array = new CallSite[names.length];
        for (int i = 0; i < array.length; i++) {
            array[i] = new AbstractCallSite(this, i, names[i]);
        }
    }

Therefore, when "acallsite[1].call(a);" is called for the first time, the call method of AbstractCallSite class is called. The following is the code of this method:

public Object call(Object receiver, Object[] args) throws Throwable {
    return CallSiteArray.defaultCall(this, receiver, args);
}

Let's look at the code of CallSiteArray.defaultCall():

    public static Object defaultCall(CallSite callSite, Object receiver, Object[] args) throws Throwable {
        return createCallSite(callSite, receiver, args).call(receiver, args);
    }
    ...
    private static CallSite createCallSite(CallSite callSite, Object receiver, Object[] args) {
        CallSite site;
        if (receiver == null)
          return new NullCallSite(callSite);
        if (receiver instanceof Class)
          site = createCallStaticSite(callSite, (Class) receiver, args);
        else if (receiver instanceof GroovyObject) {
            site = createPogoSite(callSite, receiver, args); // We only consider this situation
        } else {
            site = createPojoSite(callSite, receiver, args);
        }
        replaceCallSite(callSite, site); // Replace CallSite
        return site;
    }
    private static void replaceCallSite(CallSite oldSite, CallSite newSite)
    {
        oldSite.getArray().array [oldSite.getIndex()] = newSite;
    }

You can see that createCallSite() finally replaces the old CallSite with the new CallSite by calling replaceCallSite(). Therefore, when "acallsite[1].call(a);" is called the second time, the new CallSite is called directly, that is, the CallSite is cached.
Here, we only consider the case of POGO, that is, the createPogoSite() method. The case of POJO is a little more complicated because it involves the case of POJO per instance metaclass (I will analyze its implementation in the next article). The following is the code of createPogoSite():

    private static CallSite createPogoSite(CallSite callSite, Object receiver, Object[] args) {
        if (receiver instanceof GroovyInterceptable)
          return new PogoInterceptableSite(callSite);
        MetaClass metaClass = ((GroovyObject)receiver).getMetaClass();
        if (metaClass instanceof MetaClassImpl) {
            return ((MetaClassImpl)metaClass).createPogoCallSite(callSite, args); // We only consider this situation
        }
        return new PogoMetaClassSite(callSite, metaClass);
    }

We only consider the case where the metaclass of the object is MetaClassImpl (which is also the default of Groovy objects). The following is the code of MetaClassImpl.createPogoCallSite():

    public CallSite createPogoCallSite(CallSite site, Object[] args) {
        if (site.getUsage().get() == 0 && !(this instanceof AdaptingMetaClass))
        {
            Class [] params = MetaClassHelper.convertToTypeArray(args); // Gets the type of the parameter
            MetaMethod metaMethod = getMethodWithCachingInternal(theClass, site, params);// Selection method
            if (metaMethod != null)
               return PogoMetaMethodSite.createPogoMetaMethodSite(site, this, metaMethod, params, args); // If a matching method is found, create a PogoMetaMethodSite and bind the found method to it
        }
        return new PogoMetaClassSite(site, this); //Otherwise, create a PogoMetaClassSite
    }

PogoMetaMethodSite.createPogoMetaMethodSite() is an instance used to create PogoMetaMethodSite or its subclasses according to different situations. Finally, let's take a look at the PogoMetaMethodSite.call() method:

    public Object call(Object receiver, Object[] args) throws Throwable {
        if(checkCall(receiver, args)) 
        { // If the parameter types are the same, the bound method is called
            try {
                return invoke(receiver,args); // Call bound method
            } catch (GroovyRuntimeException gre) {
                throw ScriptBytecodeAdapter.unwrap(gre);
            }
        } else { // Otherwise, create a new CallSite, that is, find the method again
            return CallSiteArray.defaultCall(this, receiver, args);
        }
    }
    protected boolean checkCall(Object receiver, Object[] args) {
        try {
            return usage.get() == 0 && ((GroovyObject)receiver).getMetaClass() == metaClass // Metaclass still be valid & & metaclasshelper.sameclasses (params, args); / / check whether the parameter types are the same
        }
        catch (NullPointerException e) {
            if (receiver == null)
              return false;
            throw e;
        }
        catch (ClassCastException e) {
            if (!(receiver instanceof GroovyObject))
              return false;
            throw e;
        }
    }

Finally, let's summarize the process again:
When "acallsite[1].call(a)" is called for the first time, a new CallSite of the PogoMetaMethodSite class is created through the CallSiteArray.createCallSite() method, and the default AbstractCallSite is overwritten. In the process of creating PogoMetaMethodSite, methods will be selected and bound to the PogoMetaMethodSite. Finally, call this method:
When "acallsite[1].call(a)" is called the second time, PogoMetaMethodSite.call() is called directly. At this time, PogoMetaMethodSite.call() will check whether the parameter type passed in is the same as that of the bound method (i.e. the method found last time). If it is the same, call the bound method, otherwise CallSiteArray.createCallSite() will be called again Method, create a new CallSite object, and re select the method.

In addition to ordinary method calls, there are also calls to current object methods, get / set properties, call constructors, and call static functions. If you are interested, you can learn about them by yourself.

Posted by fotakis on Sat, 06 Nov 2021 01:38:49 -0700