What can we improve in JSR292 for Java 9?
John Rose
john.r.rose at oracle.com
Sat Mar 7 05:31:20 UTC 2015
On Mar 4, 2015, at 2:47 PM, Charles Oliver Nutter <headius at headius.com> wrote:
>
> Busy week, finally circling back to this thread...
>
> On Wed, Feb 25, 2015 at 8:29 PM, John Rose <john.r.rose at oracle.com> wrote:
>>> * A loop handle :-)
>>>
>>> Given a body and a test, run the body until the test is false. I'm
>>> guessing there's a good reason we don't have this already.
>>
>> A few reasons: 1. You can code your own easily.
>
> I can't code one that will specialize for every call path, though,
> unless I generate a new loop body for every call path.
Good point; if we had an intrinsic we could specialize this more readily, across more degrees of freedom.
OTOH: What you are proposing is a workaround for a basic weakness in our system which we must eventually address.
There is no reason why you shouldn't be able to write a fully generic algorithm (generic across calling sequences) and get good performance for each distinct instance of the algorithm. (Cue rants about the Loop Customization Problem.)
But, as with try/finally, if there is a common use case which can only be expressed in a strained fashion using existing API points, we should provide a more natural notation for that use case; both users and JVM will find it easier to DTRT.
> 2. There's no One True Loop the way there is a One True If.
>> The "run until test is false" model assumes all the real work is
>> done with side-effects, which are off-center from the MH model.
>
> This I can appreciate. My mental model of MHs started to trend toward
> a general-purpose IR, and I believe if I had some sort of backward
> branch it could be that. But I understand if that's the wrong
> conceptual model, and I realize now that MHs are basically call stack
> adapters with a bit of forward branching thrown in.
Good; you are hitting me where it counts, in the bytecodes.
> It does feel like there's a need for better representation of
> branch-joining or phi or whatever you want to call it, though.
More details on this, please? The tail-part of a GWT is a phi. Or are you talking about phis on back-edges?
I agree we need a loop, if only because we want to compile them to (the equivalent of) backwards-branching bytecodes. It's the same argument as for try/finally.
>> 3. A really clean looping mechanism probably needs a sprinkle
>> of tail call optimization.
>>
>> I'm not saying that loops should never have side effects, but I
>> am saying that a loop mechanism should not mandate them.
>>
>> Maybe this is general enough:
>>
>> MHs.loop(init, predicate, body)(*a)
>> => { let i = init(*a); while (predicate(i, *a)) { i = body(i, *a); } return i; }
>>
>> ...where the type of i depends on init, and if init returns void then you
>> have a classic side-effect-only loop.
>
> Ahh yes, this makes sense. If it were unrolled, it would simply be a
> series of folds and drops as each iteration through the body modified
> the condition in some way. So then we just need it to work without
> unrolling.
Glad you like it. This looks like a candidate, then.
(I wish we had a similar candidate for invokespecial/super. That is badly twisted around the verifier.)
> My silly use case for this would be to emit simple expressions
> entirely as a MH chain, so we'd get the benefit of MH optimizations
> without generating our own bytecode (and with forced inlining and
> perhaps a richer semantic representation than just bytecode). It's not
> a very compelling case, of course, since I could just emit bytecode
> too.
If it's a common use case, even if you could do it with bytecode spinning, it is reasonable to support a MH version, to help users avoid the friction of switching notations between MHs and bytecodes.
>
>>> * try/finally as a core atom of MethodHandles API.
>>>
>>> Libraries like invokebinder provide a shortcut API To generating the
>>> large tree of handles needed for try/finally, but the JVM may not be
>>> able to optimize that tree as well as a purpose-built adapter.
>>
>> I agree there. We should put this in.
>>
>> MHs.tryFinally(target, cleanup)(*a)
>> => { try { return target(*a); } finally { cleanup(*a); } }
>>
>> (Even here there are non-universalities; what if the cleanup
>> wants to see the return value and/or the thrown exception?
>> Should it take those as one or two leading arguments?)
>
> In InvokeBinder, the finally is expected to require no additional
> arguments compared to the try body, since that was the use case I
> needed.
Good to know. We start from what we know we need.
> You bring up a good point...and perhaps the built-in JSR292
> tryFinally should take *two* handles: one for the exceptional path
> (with exception in hand) and one for the non-exceptional path (with
> return value in hand)? The exceptional path would be expected to
> return the same type as the try body or re-raise the exception. The
> non-exceptional path would be expected to return void.
Yup. Or, the exception path could be a single MH that takes a "union" argument of U(normal-value, exception) and returns the expected value. In the JVM a natural union is often just a 2-tuple; the catch MH would take arguments (T, X, ...) where T is the normal return type (or null/zero in the case of exception) and X is the exception (or null in the case of normal return).
As Vladimir notes, maybe this is a bridge too far. It's really the same question as before: Given that you could compose this clumsily from existing API points, the real questions are (1) is this a common use case, and (2) would a direct notation make it easier for the JVM to work in terms of "natural" code?
>
>> We now have MHs.collectArguments. Do you want MHs.spreadArguments
>> to reverse the effect? Or is there something else I'm missing?
>
> I want to be able to group arguments at any position in the argument
> list. Example:
>
> Incoming signature: (String foo, int a1, int a2, int a3, Object obj)
> Target signature: (String foo, int[] as, Object obj)
>
> ... without permuting arguments
Yup, that is spreadArguments, position/count parameters. The inverse would be collectArguments, again with position/count: Which we have.
I think we have three winners so far:
1. try/finally
2. loop
3. spreadArguments
(Still needing clarity: PICs, profiling hooks, invokespecial/super.)
>> Idea of the day: An ASM-like library for method handles.
>> Make a MethodHandleReader which can run a visitor over the MH.
>> The ops of the visitor would be a selection of public MH operations
>> like filter, collect, spread, lookup, etc.
>> Also ASM-like, the library would have a MethodHandleWriter
>> would could be hooked up with the reader to make filters.
>
> That would certainly cover it! I'd expect this to add:
>
> * MethodHandleVisitor interface of some kind
> * MethodHandle#accept(MethodHandleVisitor) or similar
Yup. As Vladimir points out, the risk is that after LF encoding we might not be able to recover something expressible in the public API.
> * MethodHandleType enum with all the base MH types (so we're not
> forcing all types into a static interface).
Good idea. You mean the enum could be compatibly extended in the future? (Can't we do that with default methods in interfaces now?)
— John
More information about the mlvm-dev
mailing list