Valhalla EG 20190925

John Rose john.r.rose at oracle.com
Wed Sep 25 20:33:59 UTC 2019


On Sep 25, 2019, at 10:04 AM, David Simms <david.simms at oracle.com> wrote:
> 
>     - Remi: As a compiler strategy ?
>     - DanH: Allow user to hand write (e.g. pre-exisiting mig, i.e. Optional)
>         - Prefer compiler does the work
>     - Brian: sealing would provide trust to the VM
>     - John: sealed super-type that is empty (empty interface), a good way to go ?
>         - Brian: possible
>     - Brian: which method we go with, must be driven primarily by primitives, how to get to int.
>         - Something has got to give, fuzzing current type relationships. "Digusting Hacks", "Special Behavior".
>         - Eclair and other food analogies continue, but we need to figure out what we get for a given path first

I think the consensus is that for new value types (new eclairs) the JVM doesn’t have to do much
and the translation strategy just emits standard JVM stuff.  Sealing will let the JVM do optimizations
at the JIT level.  In the verifier and interpreter, sealed interfaces are probably not going to have
special treatment so there is a small pre-JIT performance debit, compared with analogously
“sealed” super-classes.  (For superclasses, and not for interfaces, the verifier provides static
type checking which the interpreter can exploit for a modest performance bump.)

Old types migrated to value types are a different matter.  Examples are ju.Optional and
(maybe) jl.Integer.  Consensus is that some kind of powerful low-level bridging mechanism
from the JVM will go a long way toward making the new forms of these things compatible
with old linkage requests (old client code).  My hope is that bridges are the main contribution
from the JVM.

These two use cases correspond to two ends of an implementation spectrum for the
eclair pattern.  A thin-skinned eclair (Brian says paper-wrapped muffin) corresponds to
an empty marker interface B and a value class V (the bulky muffin inside) that does all
the work. The translation strategy emits linkage requests only against V.  If the user
writes method calls against B, they are lowered to calls on V (with an invisible cast,
and possible NPE).  The legitimacy of lowering calls on B to V methods is proven
by the fact (which javac can observe) that B is sealed to V alone.  So B can be
physically empty.  This is a nice thing for the JVM, since the JVM doesn’t like
to see redundant inputs; they just cause useless binary compatibility hazards
and expensive useless corner cases.

The other end of the eclair spectrum would be a thick wrapper and tiny filling.
This seens desirable when the box type has a previous career as a value base
class like ju.Optional.  Linkage requests from both legacy code and new code
would apply to the box type B and not to the value type V.  The value type V
would be minimized down to some sort of record-like type.  This is easiest
to think about when V has a single field, as is the case with jl.Integer.
In that case, Integer supply ad hoc box logic while the inner “int” would
be the value type.  Note that “int” has no methods; perhaps there is a way
for B (the thick layer of box) to supply *all* methods, and V (the crunchy
center) to supply *only* the state component.  That also would be a
sweet spot for the JVM, again because there is no duplication between
the two classes B and V.  I think Brian called this a “cannoli”.  If it were
savory it would be a tamale or dim sum bun with a fluffy outside and
small bite at the center.

I observe a spectrum here because it seems reasonable to allow users
to “get their hands on” B in new code also, refactoring it from a blank
interface to something more interesting.  I can’t imagine why the
user wouldn’t just put everything down on V in new code, but that’s
just a temporary “can’t imagine”, not a true “would never happen”.
Or maybe in a migration case the new crunchy kernel V has some
natural new methods which interact with the old methods on B.

If V is to be minimized by moving everything onto B, it follows that
(as Brian and David observed) something has to give.  Maybe B
as an interface needs to get more power to define V’s state vector.

Or maybe B needs to be a different kind of super.  If B is an abstract
super class that “abstractly” defines one field (or several fields) of state,
then the concrete V can be totally blank, assuming there is a way for
a value class to inherit state components.  (Spinning the constructor
is hard here, but maybe we can appeal to records for that.)  So V
is a record type all of whose components are inherited from B but
concretized in V record-wise.

Another option (besides interface or abstract class) is that the super
of V is a template B of which V is really an instance B<‘value'>.  This is not
parametric polymorphism, but it is possibly a legitimate use of very
ad hoc template polymorphism.  Another way to think about using
templates is that there are three types:  B<_>, B<‘legacy’> and B<‘value’>.
The job of the legacy class would be shared by the first two, while
the new stuff would be expanded out from the third type.  The types
would all be named B (and B.class is the Object.getClass, always).
The species would carry the distinctions between the three types.
In this case, we might have jl.Integer<_> as a template that carries
the legacy protocol (in all its glory), while Integer<‘legacy’> is an
identity object in the old style (so you can still say “new Integer(5)”).
Meanwhile the sibling subtype Integer<‘value’> is a true value type,
the inside of the eclair.

(The template parameters ‘legacy’ and ‘value’ might be booleans
or enums, or even a pair of types.  It doesn’t matter much.  Note,
however, that this is likely to be a use case for template arguments
which are *not* types, just as C++ has many such use cases.)

— John


More information about the valhalla-spec-observers mailing list