And in the future, when we have templated classes, some carriers may well 
become specializations of arity-indexed base classes (CarrierTuple1<T>, 
CarrierTuple2<T,U>, etc), where the VM takes responsibility for nasty things 
like when to unload specializations.

On Mar 9, 2022, at 12:23 PM, John Rose 
<john.r.r...@oracle.com<mailto:john.r.r...@oracle.com>> wrote:


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 carrierobjects 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);


Reply via email to