3

here is an article introducing the jit optimization strategy of virtual calls.

But what surpised me is that all optimized virtual calls use same address, like callq 0x000000011418ea00 in the article.

So I'm curious about what it actually does in that address, and how it knows which function to call, since all optimized virtual calls are directed to the same address.

choxsword
  • 3,187
  • 18
  • 44
  • Doesn't the article already explain it? Virtual calls need to lookup the VMT, which is pointed by the instance of the object whose method is being called. The lookup code is the same for all the objects. Or at least it can handle different instances of objects. – Margaret Bloom Apr 25 '21 at 09:09
  • @MargaretBloom I have two question. First is how does the lookup code knows the index of current calling method, I didn't found anywhere setting such index. Second is if the code behind that address is just doing VMT lookup, why is it called "optimized virtual call"? Or rather, what is the optimization? – choxsword Apr 25 '21 at 11:10
  • 2
    You didn't find it where? It's like C++ vtable. Each object instance is created with a list of pointers. The derived object uses all of the base class pointers but overwrites those corresponding to the overridden methods. The index is fixed by the method, what changes is the pointer value. I briefly read that article, the optimization was to inline the method and skip the VMT lookup altegether. – Margaret Bloom Apr 25 '21 at 11:40
  • @MargaretBloom What do you mean by skipping the VMT lookup? Actually I'm curious about how it skips the VMT lookup, because all I can learn from the assembly is nothing but all function callings are directed to the same address. – choxsword Apr 26 '21 at 05:05
  • I've only skimmed through that article but from what I saw, when a method is optimized, it is inlined and the `virtualcall` bytecode is emitted entirely. You are looking at the assembly code of the unoptimized method. When it is optimized, there will be no `virtualcall` and its relative assembly code. It's probably easier to see this if you disassemble the classes yourself, looking at code extracts with poorly stated context is confusing :) – Margaret Bloom Apr 26 '21 at 08:16
  • @MargaretBloom I did tried to disassemble the code myself. But I can only see disassembly of methods, without the detail in that 0x000000011418ea00 address. – choxsword Apr 26 '21 at 09:32
  • Just curious, why the bounty wasn't awarded? – yugr May 06 '21 at 02:39
  • @yugr The answer has been deserted with no satisfying answer for some time. I alomost foget this. – choxsword May 06 '21 at 03:10

1 Answers1

1

My understanding is that there is another optimization happening under the hood, identical code folding.

Code for CustObj::methodCall and CustObj2::methodCall are very similar and differ only by printed string. JVM compiler is thus able to generate identical code for them:

$ javac tmp.java
$ javap -c -constants 'TestVirtualCall2$CustObj.class'
Compiled from "tmp.java"
class TestVirtualCall2$CustObj {
  public void methodCall();
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lconst_0
       4: lcmp
       5: ifne          16
       8: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: ldc           #4                  // String CustObj is very good!
      13: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      16: return
}
$ javap -c -constants 'TestVirtualCall2$CustObj2.class'
Compiled from "tmp.java"
class TestVirtualCall2$CustObj2 extends TestVirtualCall2$CustObj {
  public final void methodCall();
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lconst_0
       4: lcmp
       5: ifne          16
       8: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: ldc           #4                  // String CustObj2 is very good!
      13: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      16: return
}

Note that even string loading command ldc #4 happens to be the same because compiler put strings into same locations in respective classes constant pools.

So JIT will definitely generate just one instance of x86 code for both virtual methods.

P.S.

I think this optimization is more of a random side-effect and wasn't the author's intent. It may make sense to ask him to do more changes for code in CustObj2 class in next revision of his article.

yugr
  • 19,769
  • 3
  • 51
  • 96
  • interesting answer. But in my experiment, all methods tagged with `optimized virtual_call` in the disassembly point to the same address, despite of their concrete difference, which conflicts with your assumption. – choxsword May 06 '21 at 03:07
  • I was suspecting that the stuff behind the scene is a general dispatching method, but I have no evidence to prove that for the moment. – choxsword May 06 '21 at 03:12