Constant descriptor resolved too soon during constant resolution?

Jorn Vernee jbvernee at xs4all.nl
Sun Aug 26 11:52:51 UTC 2018


Thanks, I saw your patch but had kind of run out of ideas for things to 
try out.

The test case I had now runs fine, but the idea that I was using it for 
was a dead end.

Right now I've settled on the following usage pattern for the API:

```
public class UseCase {

     // public static final MyClass x = new MyClass(); // want to make 
lazy-init, (truly) constant

     public static class MyClass {}

     // private; can only ldc from same compilation unit any ways
     private static final ConstantDesc<MyClass> x = 
DynamicConstantDesc.of(
         MethodHandleDesc.of( // reference to `boostrap` below
             Kind.STATIC,
             ClassDesc.of("UseCase"),
             "boostrap",
             ClassDesc.of("UseCase").inner("MyClass")
         ));

     private static MyClass boostrap() {
         return new MyClass();
     }

     public static MyClass bridge() {  // for use outside class
         return ldc(x);
     }

     public static void main(String[] args) {
         System.out.println(ldc(x)); // use inside class
     }
}
```

And when thinking about the possible future of dynamic constants had 
come up with the following straw man for language support for the above 
pattern:

```
public class EnclosingClass {

     public static class MyClass {}

     // 'const' because it's a reference to a constant pool entry
     /* private */ const MyClass x = new MyClass(); // lazy-init

     // bootstrap method generated from initializer of x

     public static MyClass bridge() { // for outside use
         return x; // ldc
     }

     public static void main(String[] args) {
         System.out.println(x); // ldc
     }

}
```

And also some alternative ideas to make this usage pattern simpler, like 
implementing reflection over lambdas so I could use a method reference 
expression in place of the `MethodHandleDesc.of` code, which is 
currently the biggest chunk. I actually had a working prototype for 
that, but since lambda expressions are not ICE currently that ship 
stranded (I still think lambda reflection is an interesting feature 
though, since it allows more leverage of a very powerful language 
feature).

The story with most of the ideas for folding I've had is that they all 
inadvertently discard or change the timing of side-effects. So I was 
holding off on bringing up more ideas until you had found a way of 
dealing with that. The only thing I can think of myself is either some 
thorough static analysis to check what side-effects a method has, to see 
if it's observably side-effect-less, or just trying to invoke a method 
if the arguments are constant, and then bailing out if a side-effect is 
encountered. Both of those would require time, and could possibly have 
problems with finding all the (non-jdk) classes they need, so doing 
something like that at jlink time seems like a natural fit, since at 
that point you should have access to all/most of the classes of the 
application.

I currently don't have any further comments on the current API. It's 
looking pretty good for classes in java.base, since they have access to 
@Foldable they get to actually push computation to compile time, but for 
user classes the only use case that I can find currently seems to be the 
lazy-init pattern that I've shown above. But like I said before, I don't 
think @Foldable is ready for the public, so I'm  waiting to see what you 
come up with next.

Jorn

Brian Goetz schreef op 2018-08-25 18:11:
> I pushed some updates to the DCD factories (came at it from a
> different direction), and Vicente fixed the bugs you found.  So, give
> it another go?
> 
> On 8/2/2018 11:36 AM, Jorn Vernee wrote:
>> Hello,
>> 
>> I think I have stumbled upon a bug in the condy-folding branch (tip).
>> 
>> This is the code to reproduce:
>> 
>> ```
>> import java.lang.constant.*;
>> import static java.lang.constant.ConstantDescs.*;
>> 
>> public class Main {
>> 
>>     static final DirectMethodHandleDesc MHR_CONCAT = 
>> MethodHandleDesc.of(
>>         DirectMethodHandleDesc.Kind.VIRTUAL,
>>         CR_String,
>>         "concat",
>>         CR_String,
>>         CR_String
>>     );
>> 
>>     public static void main(String[] args) throws Throwable {
>>         ConstantDesc<String> d = 
>> DynamicConstantDesc.<String>of(BSM_INVOKE).withArgs(MHR_CONCAT, 
>> "Hello, ", "world!");
>>         System.out.println(d); // <---- exception thrown here, on 
>> resolution of `d2`
>>     }
>> 
>> }
>> ```
>> 
>> I'm wrapping a call to `ConstantBootstraps.invoke` together with a 
>> method handle for `String.concat` and 2 constant arguments (a toy 
>> example). The resolution should effectively call `"Hello, 
>> ".concat("world!")`. Please note that I'm not resolving the result 
>> string here though, but I'm resolving the descriptor itself. While 
>> resolving the descriptor `d` an exception is thrown:
>> 
>> ```
>> Exception in thread "main" java.lang.BootstrapMethodError: bootstrap 
>> method initialization exception
>>         at 
>> java.base/java.lang.invoke.BootstrapMethodInvoker.invoke(BootstrapMethodInvoker.java:253)
>>         at 
>> java.base/java.lang.invoke.ConstantBootstraps.makeConstant(ConstantBootstraps.java:71)
>>         at 
>> java.base/java.lang.invoke.MethodHandleNatives.linkDynamicConstantImpl(MethodHandleNatives.java:314)
>>         at 
>> java.base/java.lang.invoke.MethodHandleNatives.linkDynamicConstant(MethodHandleNatives.java:306)
>>         at Main.main(Main.java:18)
>> Caused by: java.lang.ClassCastException: Cannot cast 
>> java.lang.invoke.DirectMethodHandle to java.lang.constant.ConstantDesc
>>         at java.base/java.lang.Class.cast(Class.java:3613)
>>         at 
>> java.base/java.lang.invoke.BootstrapMethodInvoker.invokeWithManyArguments(BootstrapMethodInvoker.java:350)
>>         at 
>> java.base/java.lang.invoke.BootstrapMethodInvoker.invoke(BootstrapMethodInvoker.java:195)
>>         ... 4 more
>> ```
>> 
>> This puzzled me for a while, but I _think_ the cause is that the 
>> `MHR_CONCAT` descriptor is being resolved into a `DirectMethodHandle` 
>> too early during resolution? Oddly enough, when using 
>> `Intrinsics.ldc(d)` to resolve the result string, the program 
>> completes successfully, and I can print the resulting string. The 
>> following version also works as expected:
>> 
>> ```
>>     ConstantDesc<String> d = DynamicConstantDesc.of(
>>         BSM_INVOKE, DEFAULT_NAME, CR_Object, new 
>> ConstantDesc<?>[]{MHR_CONCAT, "Hello, ", "world!"}); // No folding 
>> here, because the array is not a constant (?)
>>     System.out.println(d); // This one is OK
>> ```
>> 
>> And it prints: 
>> `DynamicConstantDesc[ConstantBootstraps::invoke(MethodHandleDesc[VIRTUAL/String::concat(String)String],Hello, 
>> ,world!)Object]`. As far as I can tell these 2 snippets should behave 
>> the same (the generated bytecode is quite different though), so I 
>> think this is a bug?
>> 
>> Is there a flag to temporarily turn off folding? I'm not aware of any 
>> javac equivalent of `-XX:+PrintFlagsFinal`, so I don't have a way to 
>> find out about experimental flags.
>> 
>> Also, I hope this is the right place to post bug reports, JBS doesn't 
>> seem to cover amber, and I've seen a few people reporting bugs on 
>> other mailing lists, but I'd expect more people to be doing that, so 
>> maybe I'm in the wrong place?
>> 
>> Best regards,
>> Jorn Vernee


More information about the amber-dev mailing list