ClassSpecializer is designed for cases beyond generating tuples, where
some extra behavioral contract, and/or fixed field set, is required
across all the generated classes.
That said, ClassSpecializer should support tuple generation nicely, for
Carrier.
Maurizio’s point is a good one, although if I were Jim I’d hesitate
to use something complicated to generate classes for just this one
simple case. OTOH, our sense of what is “simple” sometimes needs
adjustment. In the end, the class file generation might be simple, but
the infrastructure of generating and registering classes (and allowing
them to be unloaded in some cases) is rather subtle, and maintainers
will thank us for centralizing it.
So, Jim, please do take a look at ClassSpecializer. It’s there for
use cases like this one, even if in the end we don’t select it in this
use case.
On 3 Mar 2022, at 10:49, Maurizio Cimadamore wrote:
Seems sensible.
As a possible "test", we could perhaps use this mechanism in the JDK
implementation of LambdaForms? We do have places where we spin
"species" classes:
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java
(that said, maybe species classes contain a bit more than just data,
so perhaps that's a wrong fit - but anyway, worth talking a look for
possible code duplication).
Maurizio
On 03/03/2022 13:57, Jim Laskey wrote:
We propose to provide a runtime /anonymous carrier class object
generator/; *java.lang.runtime.Carrier*. This generator class is
designed to share /anonymous classes/ when shapes are similar. For
example, if several clients require objects containing two integer
fields, then *Carrier* will ensure that each client generates
carrier objects using the same underlying anonymous class.
Providing this mechanism decouples the strategy for carrier class
generation from the client facility. One could implement one class
per shape; one class for all shapes (with an Object[]), or something
in the middle; having this decision behind a bootstrap means that it
can be evolved at runtime, and optimized differently for different
situations.
Motivation
The String Templates JEP draft
<https://bugs.openjdk.java.net/browse/JDK-8273943> proposes the
introduction of a /TemplatedString/ object for the primary
purpose of /carrying/ the /template/ and associated
/values/ derived from a /template literal/. To avoid value boxing,
early prototypes described these /carrier/objects using
/per-callsite/ anonymous classes shaped by value types, The use of
distinct anonymous classes here is overkill, especially considering
that many of these classes are similar; containing one or two object
fields and/or one or two integral fields. /Pattern matching/ has a
similar issue when carrying the values for the /holes/ of a pattern.
With potentially hundreds (thousands?) of template literals or
patterns per application, we need to find an alternate approach for
these /value carriers/.
Description
In general terms, the *Carrier* class simply caches anonymous
classes keyed on shape. To further increase similarity in shape, the
ordering of value types is handled by the API and not in the
underlying anonymous class. If one client requires an object with one
object value and one integer value and a second client requires an
object with one integer value and one object value, then both clients
will use the same underlying anonymous class. Further, types are
folded as either integer (byte, short, int, boolean, char, float),
long (long, double) or object. [We've seen that performance hit by
folding the long group into the integer group is significant, hence
the separate group.]
The *Carrier* API uses MethodType parameter types to describe the
shape of a carrier. This incorporates with the primary use case where
bootstrap methods need to capture indy non-static arguments. The API
has three static methods;
|// Return a constructor MethodHandle for a carrier with components
// aligning with the parameter types of the supplied methodType.
static MethodHandle constructor(MethodType methodType) // Return a
component getter MethodHandle for component i. static MethodHandle
component(MethodType methodType, int i) // Return component getter
MethodHandles for all the carrier's components. static MethodHandle[]
components(MethodType methodType)|
Examples
|import java.lang.runtime.Carrier; ... // Define the carrier
description. MethodType methodType =
MethodType.methodType(Object.class, byte.class, short.class,
char.class, int.class, long.class, float.class, double.class,
boolean.class, String.class); // Fetch the carrier constructor.
MethodHandle constructor = Carrier.constructor(methodType); // Create
a carrier object. Object object =
(Object)constructor.invokeExact((byte)0xFF, (short)0xFFFF, 'C',
0xFFFFFFFF, 0xFFFFFFFFFFFFFFFFL, 1.0f / 3.0f, 1.0 / 3.0, true,
"abcde"); // Get an array of accessors for the carrier object.
MethodHandle[] components = Carrier.components(methodType); // Access
fields. byte b = (byte)components[0].invokeExact(object); short s =
(short)components[1].invokeExact(object); char c
=(char)components[2].invokeExact(object); int i =
(int)components[3].invokeExact(object); long l =
(long)components[4].invokeExact(object); float f
=(float)components[5].invokeExact(object); double d =
(double)components[6].invokeExact(object); boolean tf
(boolean)components[7].invokeExact(object); String s =
(String)components[8].invokeExact(object)); // Access a specific
field. MethodHandle component = Carrier.component(methodType, 3); int
ii = (int)component.invokeExact(object);|