Brian asked me to talk a little about the String Templates use case for 
Carrier. ( String Templates 
JEP<https://bugs.openjdk.java.net/browse/JDK-8273943> for background.)

Going the example route, the following Java code constructs a TemplatedString 
from a templated string literal that contains three embedded expressions, x, y, 
and x + y;

    int x = 10;
    int y = 20;
    TemplatedString ts = "Adding \{x} and \{y} equals \{x + y}."

Naively, we could capture the interesting bits of the templated string literal 
at runtime using a generic GenericTS record;

    public record GenericTS(String template, List<Object> values) implements 
TemplatedString {}
    ...
    TemplatedString ts = new GenericTS("Adding \uFFFC and \uFFFC equals 
\uFFFC.", List.of(x, y, x + y));
                                           // `\uFFFC` are placeholder 
characters


There are several drawbacks using this generic record, but let's just focus on 
the List.of(x, y, x + y). Clearly, using a list as a carrier would force all 
the int expression values to be boxed. For an optimizing template policy, such 
as STR (concatenation), boxing would be a performance killer. We need a way to 
carry values without boxing.

Another approach is to use an anonymous class.

    TemplatedString ts = new TemplatedString() {
        private final int exp$1 = x;
        private final int exp$2 = y;
        private final int exp$3 = x + y;

        public String       template() { return "Adding \uFFFC and \uFFFC 
equals \uFFFC."; }
        public List<Object> values()   { return List.of(exp$1, exp$2, exp$3); }
        ...
    });

Using the fields of an anonymous class allows the TemplatedString to be 
constructed without boxing and allow an optimizing TemplatePolicy to access 
values without boxing (via MethodHandles.)

The anonymous class downside is that we end up with hundreds of these often 
very similar classes. For example;

    TemplatedString ts1 = "Adding \{x} and \{y} equals \{x + y}."
    TemplatedString ts2 = "Subtracting \{x} from \{y} equals \{y - x}."

Even though the templates are different, the underlying carrier is still three 
int values. The compiler could fold similarly shaped anonymous classes at 
compile time, but that would only work for a single compilation unit. What is 
needed is a runtime solution.

That's where java.lang.runtime.Carrier kicks in. Carrier provides an optimal 
carrier aligning to types of values that are to be carried, spinning up 
anonymous classes if needed and reusing anonymous classes when similar shape.

On Mar 3, 2022, at 2:20 PM, Remi Forax 
<fo...@univ-mlv.fr<mailto:fo...@univ-mlv.fr>> wrote:

For the pattern matching,
we also need a 'with' method, that return a method handle that takes a carrier 
and a value and return a new carrier with the component value updated.

  static MethodHandle withComponent(MethodType methodType, int i)
  // returns a mh (Carrier;T) -> Carrier with T the type of the component

It can be built on top of constructor() + component() but i think that i should 
be part of the API instead of every user of the Carrier API trying to 
re-implement it.

In term of spec, Jim, can you rename "component getter" to "component accessor" 
which is the term used by records.

Rémi

________________________________
From: "Brian Goetz" <brian.go...@oracle.com<mailto:brian.go...@oracle.com>>
To: "Jim Laskey" <james.las...@oracle.com<mailto:james.las...@oracle.com>>, 
"amber-spec-experts" 
<amber-spec-experts@openjdk.java.net<mailto:amber-spec-experts@openjdk.java.net>>
Sent: Thursday, March 3, 2022 4:29:51 PM
Subject: Re: Proposal: java.lang.runtime.Carrier
Thanks Jim.

As background, (some form of) this code originated in a prototype for pattern 
matching, where we needed a carrier for a tuple (T, U, V) to carry the results 
of a match from a deconstruction pattern (or other declared pattern) on the 
stack as a return value.  We didn't want to spin a custom class per pattern, 
and we didn't want to commit to the actual layout, because we wanted to 
preserve the ability to switch later to a value class.  So the idea is you 
describe the carrier you want as a MethodType, and there's a condy that gives 
you an MH that maps that shape of arguments to an opaque carrier (the 
constructor), and other condys that give you MHs that map from the carrier to 
the individual bindings.  So pattern matching will stick those MHs in CP slots.

The carrier might be some bespoke thing (e.g., record anon(T t, U u, V v)), or 
something that holds an Object[], or something with three int fields and two 
ref fields, or whatever the runtime decides to serve up.

The template mechanism wants almost exactly the same thing for bundling the 
parameters for uninterprted template strings.

Think of it as a macro-box; instead of boxing primitives to Object and Objects 
to varargs, there's a single boxing operation from a tuple to an opaque type.



On 3/3/2022 8:57 AM, 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