[External] : Re: Proposal: java.lang.runtime.Carrier

Jim Laskey james.laskey at oracle.com
Tue Mar 8 19:31:04 UTC 2022


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 <forax at univ-mlv.fr<mailto:forax at 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.goetz at oracle.com<mailto:brian.goetz at oracle.com>>
To: "Jim Laskey" <james.laskey at oracle.com<mailto:james.laskey at oracle.com>>, "amber-spec-experts" <amber-spec-experts at openjdk.java.net<mailto:amber-spec-experts at 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);




-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20220308/a0d5e67c/attachment-0001.htm>


More information about the amber-spec-experts mailing list