12

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.

Laird Nelson
  • 15,321
  • 19
  • 73
  • 127
  • The edit that you made is the part where I struggle too, by far. All the samples that Ive seen where using arbitrary methods for the bootstrap, not `describeConstable`. May be someone from Oracle will reply to us... – Eugene Oct 19 '20 at 11:20

1 Answers1

10

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.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • This is helpful; thanks—so why does my `Thread.dumpStack()` never produce output where I can see it? (EDIT: ah, I see you said "as this is not implemented in the javac yet, at least in the mainstream jdk") – Laird Nelson Oct 17 '20 at 03:31
  • So perhaps the shortest possible answer is: "javac _will_ call this method when javac is amended to actually use it, but as of this writing no one calls it." Is that essentially correct? – Laird Nelson Oct 17 '20 at 03:32
  • @LairdNelson exactly. – Eugene Oct 17 '20 at 03:33
  • If you edit your answer somewhat (the lambda tutorial is helpful, but I personally already knew the gist of most of this), I'm more than happy to award you the green checkmark! I think if it's a little briefer it will help people (me) who come to this page years down the line. Thanks for your time! – Laird Nelson Oct 17 '20 at 03:36
  • My one remaining question is: doesn't all of this mean the compiler has to instantiate my `Constable` implementation? Isn't that weird? Or can it just do static analysis and figure out what the bytecode {waves hands furiously} of my `describeConstable()` implementation will be, since by definition it has to yield something that is itself a constant? – Laird Nelson Oct 17 '20 at 03:39
  • 2
    @LairdNelson that’s precisely what has been said in [the talk you’ve linked yourself](https://www.youtube.com/watch?v=iSEjlLFCS3E&t=17m14s), the ability to execute certain methods at compile-time is needed. Technically, there’s no difference between “figure out what the bytecode…” does and just executing the bytecode to get the result. What I really like about the talk, is how it describes everything as straight-forward (and even as been done), but all we got so far, is a bunch of interfaces without the actual intended useful feature. But I guess, that’s makes it a typical Java API. – Holger Oct 17 '20 at 15:46
  • Thanks, @Holger; what is interesting to me is: suppose my `Constable` object has a constructor that takes 237 arguments. How would my `Constable` in such a case be instantiated by the compiler (!) during compilation (!) such that `describeConstable()` could be called? I feel very slow. – Laird Nelson Oct 17 '20 at 16:04
  • It just occurred to me (and I think the talk mentions this as well obliquely) that the only thing that matters from `describeConstable()` is its output, and its output is definitionally made up of constant parts, so perhaps a compiler _could_ actually just {waves hands furiously} grab its bytecode result rather than somehow magically calling a constructor it doesn't really actually know how to call with arguments it doesn't have and actually invoking the method. That is: `describeConstable()` is a very special sort of instance method indeed. This is becoming a little clearer I think. – Laird Nelson Oct 17 '20 at 16:14
  • 1
    @LairdNelson the compiler doesn’t care about the number of arguments. It has less problems processing them than you writing them. But all of them must be “intrinsifiable constant expressions”, as the talk calls them. Those expressions can be arbitrarily complex, as long they are again composed from ICEs. It’s not so different to the way, compile time constants are handled today. – Holger Oct 17 '20 at 16:14
  • I think we're talking past each other: the "arguments" I'm talking about are the arguments to a hypothetical constructor of an object that implements `describeConstable()`, not the arguments that make up the `ConstantDesc` returned by `describeConstable()`. – Laird Nelson Oct 17 '20 at 16:15
  • 2
    That’s precisely what I’m talking about. As the talk described it, the composition of the constant must be made of “intrinsifiable constant expressions”. As the talk also said, this path does not need to be the same as the `describeConstable()` will return afterwards. That’s not much different to how compile time constants work. I can write, `final String s="C"+(int)Math.PI/Thread.NORM_PRIORITY+"DE";` and it will be a compile-time constant, because all inputs are constants, but the resulting value in the constant pool will not reflect this construction, but be the simple four letter string. – Holger Oct 17 '20 at 16:26
  • 2
    OK; I didn't see any restrictions in the javadoc of `Constable` as to who could implement it, i.e. an object that connects to a database in its constructor and grabs a value with which to construct a `DynamicConstDesc` which is then subsequently returned by `getConstable()` seems according to the contract of the interface to be entirely legal. I see no reference to tracked constants or ICEs or anything in the `Constable` documentation. Are you saying there must be, and its omission is a severe documentation bug? – Laird Nelson Oct 17 '20 at 18:21
  • 1
    @LairdNelson Look again at the example of my previous comment. Not every `String` is a compile-time constant, but a `String` constructed via `"C"+(int)Math.PI/Thread.NORM_PRIORITY+"DE"` is. The type of an expression is only one aspect. Likewise, an expression must evaluate to a `Constable` type *and* be an ICE to allow folding at compile-time. This doesn’t restrict which types can implement the interface, it’s just that particular compiler feature (that does not even exist yet) that depends on the formal description of ICEs. The definition will surely appear in the JLS once it is a thing. – Holger Oct 20 '20 at 09:31
  • I understand that I can make a `String` constant out of other constant parts. In this case, of course, _I_ am making the constant and _I_ am causing its parts to come into being. But if I declare a `Frob` class with a complicated constructor, who causes it to come into being such that its `describeConstable` method may be called at some future time by the compiler? Please note I'm not arguing with established compiler theory: I am a rank newbie when it comes to established compiler theory so I am merely asking what are surely base-level questions. And thanks for your time. – Laird Nelson Oct 20 '20 at 14:53