谁调用describeConstable(),以及何时调用?

huangapple go评论67阅读模式
英文:

Who calls describeConstable(), and when?

问题

我已经在JDK 15中有点幸运地了解了Constable和类似的东西。我大部分都理解了。

在经历了所有编译器理论并且甚至理解了一点之后,我发现我仍然有这个问题:谁调用ConstabledescribeConstable()方法,以及何时调用? Brian的演示似乎暗示它在编译时以某种方式被访问。对于这些事情我很幼稚,我期望它会出现在使用页面下的jdk.compiler或其他地方。然而,唯一的使用似乎只在jdk.incubator.foreign包中。 (显然,我理解它可能被某些未在使用页面中公开的私有机制使用;因此我提出了这个问题。)

我在一个愚蠢的类的describeConstable()实现中放置了一个Thread.dumpStack(),该类实现了Constable并返回Optional.ofNullable(null),只是为了看看会发生什么……在编译时或运行时都没有发生任何事情。

(我知道,在JDK 12之前,如果你想写动态常量,你必须使用ASM或ByteBuddy或类似的东西。但在我看来,Constable似乎是为了让你的用户类“插入”Java编译器,让它为你完成常量的编写。我也知道java.lang.constant中的类和方法主要是为编译器编写者而设计的,但Constable对我来说似乎是一个例外。最后,我显然明白,我可以随时调用这个方法,但这显然不是它的预期用途。)

编辑:感谢下面一些非常有帮助和耐心的答案和评论,我觉得我开始懂了(我不是编译器专家,这一点现在应该是显而易见的)。虽然我理解一旦存在一个X implements Constable的实例,那么它从describeConstable()返回的ContantDesc必须由其他常量描述符制作(它本身),而且我理解“常量工厂”(例如ClassDesc#of()等)可以在编译时调用,显然必须接受其他常量作为可能需要的任何参数,但我仍然不清楚在编译时如何首次实例化一个_任意的_X implements Constable,同时……它正在被编译(!)以便在编译时调用describeConstable()

请友善地记住,这个问题的答案可能是我对编译器一般性质或它们在静态分析期间进行的花哨操作的一些基本东西,我只看到一个实例方法(describeConstable())需要在对象实例(X implements Constable)上调用,为了有一个对象实例,必须有人调用它的构造函数。对于Java编译器如何知道如何构造我的X implements Constable以及它的任意、可能是多参数的构造函数,这一点对我来说不清楚,以便在编译时调用describeConstable()

英文:

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.

答案1

得分: 10

我将说出我理解和知道的部分。这确实是一个有趣的特性。

谁调用了Constable的describeConstable()?

javac

什么时候调用?

当它首次被调用/需要时。

一个更详细的解释。你知道 lambda 是如何编译的吗?如果不知道,这里是一个非常简短的介绍(以后会对你很有帮助):

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

如果你查看字节码,会有一个 invokedynamic 调用:

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

这将调用一个“引导”方法:

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;
  • 引导方法的名称是:LambdaMetafactory::metafactory
  • 输入方面,它需要一个 Lookup(由 JVM 提供)。
  • javac 提供了一个 MethodType(它描述了方法的返回类型和方法参数类型,在这个例子中是 Runnablerun 方法)。
  • 它会返回一个 CallSite(在这种情况下实际上是一个 ConstantCallSite)。

所以,用非常简单的话来说(可能有点错误),invokedynamic 将调用绑定到一个 ConstantCallSite,它在内部将调用委托给你提供的 Runnablerun 方法的实现(在内部,它会委托给 lambda 定义所在地的“去糖化”的私有方法)。这只会发生一次,在第一次调用时,所有后续的调用都不会经过这个过程。我在其他答案中提供了更多细节,像是这里

相同的机制将用于动态常量(但必须使用 ldc 而不是 invokedynamic)。这个“机制”在 jdk-11 中已经提供。注意类的名称:ConstantBootstraps,我们知道为什么是“引导”,也知道为什么是“常量”。如果你看一下参数,它肯定开始有些意义,因为它实际上与 lambdas 的 invokedynamic 非常相似。


现在你知道为什么需要 Constable/ConstantDesc:这样引导方法才能调用适当的实现。在上面的例子中,javac “知道”(推断/推导等等)lambda 实际上是一个 Runnable。在“常量动态”情况下,这个信息将通过类实现了 Constable 来暗示。这将成为构建常量的“配方”;至少在我的理解中是这样。


只需要注意其他人已经在 JVM 上实现了类似的想法:Scala 的 lazy。但他们在幕后简单地实现了双重检查锁定,而你需要支付那个 volatile 读取的代价,有时... 当然,在 JVM 上实现这个将是有益的;到什么程度和确切的方式尚不清楚;因为这在 javac 中尚未实现,至少在主流的 jdk 中是这样。也许它会是类似于:

// 编造的语法
__@lazy__
private static final MyObject obj = null;

然后这最终会委托给 Constable::describeConstable,或者可能是:

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

private MyObject myProvider(){....}

但我敢打赌,比我聪明得多的人会想出我在这里没有提到的如何使用这个功能的想法。当那发生时(我知道会发生),我需要更新这篇文章。

英文:

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.

huangapple
  • 本文由 发表于 2020年10月17日 05:48:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/64396802.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定