Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Who calls describeConstable(), and when?

Tags:

java

constants

I have stumbled somewhat happily into Constable and the like in JDK 15. I mostly understand.

After frolicking through all the compiler theory and even understanding a little bit of it, I find I still have this question: Who calls a Constable's describeConstable() method, and when? Brian's presentation seemed to imply it is somehow accessed at compile time. Being naïve about such things, I was expecting it to show up in the usage page under jdk.compiler or something. Instead, the only consumption seems to be in the jdk.incubator.foreign package. (Obviously I understand it may be used by some private machinery somewhere that isn't exposed by the usage page; hence my question.)

I placed a Thread.dumpStack() in a describeConstable() implementation of a dumb class that implements Constable and that returns Optional.ofNullable(null) just to see what would happen and…nothing happened at compile- or runtime.

(I do know that until JDK 12 if you wanted to write dynamic constants you had to use ASM or ByteBuddy or something similar. To my naïve eyes it looks, though, like Constable is there to allow your user class to "plug into" the Java compiler and allow it to do the constant writing for you. I am also aware that the classes and methods in java.lang.constant are primarily intended for compiler writers, but Constable seemed to me to be a bit of an exception. Finally, I obviously understand that I can call this method any time I wish, but that's clearly not what it's intended for.)

EDIT: Thanks (very much) to some of the extremely helpful and patient answers and comments below, I think I'm starting to get it (I'm not a compiler guy which by this point should be quite obvious). While I understand that once an instance of X implements Constable exists then the ContantDesc it returns from its describeConstable() must be made (itself) of other constant descriptors, and while I understand that "constant factories" (such as ClassDesc#of() and so on) may be called at compile time and obviously must accept only other constants as any arguments they might require, I'm still not clear on how an arbitrary X implements Constable is instantiated during compilation in the first place while…it is being compiled (!) such that describeConstable() can be called on it at compile time.

Please kindly bear in mind the answer to this question may be something rudimentary that I'm missing about compilers in general, or the sorts of hijinks they get up to during static analysis. I just see an instance method (describeConstable()) that needs to be invoked on an instance of an object (X implements Constable) and in order to have an instance of an object someone has to call its constructor. It's unclear to me how the Java compiler could know how to construct my X implements Constable with its arbitrary, possibly multi-argument constructor so that it could then call describeConstable() on it.

like image 480
Laird Nelson Avatar asked Oct 16 '20 21:10

Laird Nelson


1 Answers

I will say what I understood and know, so far. It is indeed an interesting feature.

Who calls a Constable's describeConstable()

javac will.

and when?

when it is first called/needed.

A more detailed explanation. Do you know how lambdas are compiled? If not, here is the very short intro in that (it will help a lot later):

Runnable r = () -> {System.out.println("easy, peasy");};
r.run();

if you look at the bytecode, there is going to be an invokedynamic call:

invokedynamic #7,  0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;

this, in turn, will call a "bootstrap" method:

BootstrapMethods:
 0: #39 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  • The name of the bootstrap method is: LambdaMetafactory::metafactory.

  • As input, it takes a Lookup (provided by the JVM)

  • Among other things, javac provides a MethodType (it describes the return type and method argument types of the method, in this case it is run from Runnable)

  • It will return a CallSite (in this case it is actually a ConstantCallSite).

So, in rather very simple words (and most probably a bit wrong), invokedynamic binds the invocation to a ConstantCallSite, that internally delegates the call to an implementation of Runnable with a run method that you have provided (internally it delegates to a "de-sugared" private method of where the lambda is defined). This happens only once, at the first invocation, all subsequent calls don't go through this pain. Somehow more details I provided in other answers, like here.

The same mechanism will be used for dynamic constants (but it has to use ldc and not invokedynamic). The "machinery" was already provided in jdk-11. Notice the name of the class : ConstantBootstraps, well we know why "bootstrap" and we know why "Constant". If you look at the arguments, it surely starts to make some sense, as it really resembles the invokedynamic for lambdas.


Now you know why Constable/ConstantDesc is needed: so that the bootstrap method calls the proper implementation. In the case above, javac "knew" (inferred/deducted/etc) that lambda is really a Runnable. In the case of "constant dynamic" this information will be implied by the fact that the class implements Constable. This is going to be the "recipe" of how to build your constant; at least in my understanding.


Just note that others have already done this (just the idea) on the JVM: Scala's lazy. But they simply implemented doubled check locking behind the curtains and you pay for that volatile read, at times... Of course implementing this on the JVM will be beneficial; to what degree and exactly how is yet to be known; as this is not implemented in the javac yet, at least in the mainstream jdk. May be it will be something along the lines of:

// made-up syntax
__@lazy__
private static final MyObject obj = null;

and this will eventually delegate to Constable::describeConstable or may be :

__@lazy(provider="myProvider")__
private static final MyObject obj = null;

private MyObject myProvider(){....}

But I bet that people that are much smarter than me will come up with ideas on how to use this that I have not mentioned here. When that happens (and I know it will), I will need to update this post.

like image 155
Eugene Avatar answered Sep 28 '22 00:09

Eugene