8285932: Implementation of JEP 430 String Templates (Preview)

Reviewed-by: mcimadamore, rriggs, darcy
This commit is contained in:
Jim Laskey 2023-05-10 11:34:01 +00:00
parent da2c930262
commit 4aa65cbeef
74 changed files with 9309 additions and 99 deletions

View file

@ -27,10 +27,15 @@ package java.lang.invoke;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.javac.PreviewFeature;
import jdk.internal.util.FormatConcatItem;
import jdk.internal.vm.annotation.Stable;
import sun.invoke.util.Wrapper;
import java.lang.invoke.MethodHandles.Lookup;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import static java.lang.invoke.MethodType.methodType;
@ -110,8 +115,14 @@ public final class StringConcatFactory {
* While the maximum number of argument slots that indy call can handle is 253,
* we do not use all those slots, to let the strategies with MethodHandle
* combinators to use some arguments.
*
* @since 21
*/
private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200;
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
public static final int MAX_INDY_CONCAT_ARG_SLOTS;
// Use static initialize block to avoid MAX_INDY_CONCAT_ARG_SLOTS being treating
// as a constant for constant folding.
static { MAX_INDY_CONCAT_ARG_SLOTS = 200; }
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
@ -321,6 +332,7 @@ public final class StringConcatFactory {
{
Objects.requireNonNull(lookup, "Lookup is null");
Objects.requireNonNull(name, "Name is null");
Objects.requireNonNull(recipe, "Recipe is null");
Objects.requireNonNull(concatType, "Concat type is null");
Objects.requireNonNull(constants, "Constants are null");
@ -488,14 +500,12 @@ public final class StringConcatFactory {
// Use int as the logical type for subword integral types
// (byte and short). char and boolean require special
// handling so don't change the logical type of those
if (cl == byte.class || cl == short.class) {
ptypes[i] = int.class;
}
ptypes[i] = promoteToIntType(ptypes[i]);
// Object, float and double will be eagerly transformed
// into a (non-null) String as a first step after invocation.
// Set up to use String as the logical type for such arguments
// internally.
else if (cl == Object.class) {
if (cl == Object.class) {
if (objFilters == null) {
objFilters = new MethodHandle[ptypes.length];
}
@ -664,7 +674,6 @@ public final class StringConcatFactory {
return argPositions;
}
private static MethodHandle foldInLastMixers(MethodHandle mh, long initialLengthCoder, int pos, Class<?>[] ptypes, int count) {
MethodHandle mix = switch (count) {
case 1 -> mixer(ptypes[pos]);
@ -710,6 +719,9 @@ public final class StringConcatFactory {
int idx = classIndex(cl);
MethodHandle prepend = PREPENDERS[idx];
if (prepend == null) {
if (idx == STRING_CONCAT_ITEM) {
cl = FormatConcatItem.class;
}
PREPENDERS[idx] = prepend = JLA.stringConcatHelper("prepend",
methodType(long.class, long.class, byte[].class,
Wrapper.asPrimitiveType(cl), String.class)).rebind();
@ -722,13 +734,15 @@ public final class StringConcatFactory {
LONG_IDX = 2,
BOOLEAN_IDX = 3,
STRING_IDX = 4,
TYPE_COUNT = 5;
STRING_CONCAT_ITEM = 5,
TYPE_COUNT = 6;
private static int classIndex(Class<?> cl) {
if (cl == String.class) return STRING_IDX;
if (cl == int.class) return INT_IDX;
if (cl == boolean.class) return BOOLEAN_IDX;
if (cl == char.class) return CHAR_IDX;
if (cl == long.class) return LONG_IDX;
if (cl == String.class) return STRING_IDX;
if (cl == int.class) return INT_IDX;
if (cl == boolean.class) return BOOLEAN_IDX;
if (cl == char.class) return CHAR_IDX;
if (cl == long.class) return LONG_IDX;
if (FormatConcatItem.class.isAssignableFrom(cl)) return STRING_CONCAT_ITEM;
throw new IllegalArgumentException("Unexpected class: " + cl);
}
@ -986,6 +1000,33 @@ public final class StringConcatFactory {
private static final @Stable MethodHandle[] MIXERS = new MethodHandle[TYPE_COUNT];
private static final long INITIAL_CODER = JLA.stringConcatInitialCoder();
/**
* Promote integral types to int.
*/
private static Class<?> promoteToIntType(Class<?> t) {
// use int for subword integral types; still need special mixers
// and prependers for char, boolean
return t == byte.class || t == short.class ? int.class : t;
}
/**
* Returns a stringifier for references and floats/doubles only.
* Always returns null for other primitives.
*
* @param t class to stringify
* @return stringifier; null, if not available
*/
private static MethodHandle stringifierFor(Class<?> t) {
if (t == Object.class) {
return objectStringifier();
} else if (t == float.class) {
return floatStringifier();
} else if (t == double.class) {
return doubleStringifier();
}
return null;
}
private static MethodHandle stringValueOf(Class<?> ptype) {
try {
return MethodHandles.publicLookup()
@ -998,4 +1039,304 @@ public final class StringConcatFactory {
private StringConcatFactory() {
// no instantiation
}
/**
* Simplified concatenation method to facilitate {@link StringTemplate}
* concatenation. This method returns a single concatenation method that
* interleaves fragments and values. fragment|value|fragment|value|...|value|fragment.
* The number of fragments must be one more that the number of ptypes.
* The total number of slots used by the ptypes must be less than or equal
* to {@link #MAX_INDY_CONCAT_ARG_SLOTS}.
*
* @param fragments list of string fragments
* @param ptypes list of expression types
*
* @return the {@link MethodHandle} for concatenation
*
* @throws StringConcatException If any of the linkage invariants are violated.
* @throws NullPointerException If any of the incoming arguments is null.
* @throws IllegalArgumentException If the number of value slots exceed {@link #MAX_INDY_CONCAT_ARG_SLOTS}.
*
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
public static MethodHandle makeConcatWithTemplate(
List<String> fragments,
List<Class<?>> ptypes)
throws StringConcatException
{
Objects.requireNonNull(fragments, "fragments is null");
Objects.requireNonNull(ptypes, "ptypes is null");
ptypes = List.copyOf(ptypes);
if (fragments.size() != ptypes.size() + 1) {
throw new IllegalArgumentException("fragments size not equal ptypes size plus one");
}
if (ptypes.isEmpty()) {
return MethodHandles.constant(String.class, fragments.get(0));
}
Class<?>[] ttypes = new Class<?>[ptypes.size()];
MethodHandle[] filters = new MethodHandle[ptypes.size()];
int slots = 0;
int pos = 0;
for (Class<?> ptype : ptypes) {
slots += ptype == long.class || ptype == double.class ? 2 : 1;
if (MAX_INDY_CONCAT_ARG_SLOTS < slots) {
throw new StringConcatException("Too many concat argument slots: " +
slots + ", can only accept " + MAX_INDY_CONCAT_ARG_SLOTS);
}
boolean isSpecialized = ptype.isPrimitive();
boolean isFormatConcatItem = FormatConcatItem.class.isAssignableFrom(ptype);
Class<?> ttype = isSpecialized ? promoteToIntType(ptype) :
isFormatConcatItem ? FormatConcatItem.class : Object.class;
MethodHandle filter = isFormatConcatItem ? null : stringifierFor(ttype);
if (filter != null) {
filters[pos] = filter;
ttype = String.class;
}
ttypes[pos++] = ttype;
}
MethodHandle mh = MethodHandles.dropArguments(newString(), 2, ttypes);
long initialLengthCoder = INITIAL_CODER;
String lastFragment = "";
pos = 0;
for (String fragment : fragments) {
lastFragment = fragment;
if (ttypes.length <= pos) {
break;
}
Class<?> ttype = ttypes[pos];
// (long,byte[],ttype) -> long
MethodHandle prepender = prepender(lastFragment.isEmpty() ? null : fragment, ttype);
initialLengthCoder = JLA.stringConcatMix(initialLengthCoder, fragment);
// (byte[],long,ttypes...) -> String (unchanged)
mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepender,1, 0, 2 + pos);
pos++;
}
MethodHandle newArrayCombinator = lastFragment.isEmpty() ? newArray() :
newArrayWithSuffix(lastFragment);
// (long,ttypes...) -> String
mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, newArrayCombinator,
1 // index
);
pos = 0;
for (Class<?> ttype : ttypes) {
// (long,ttype) -> long
MethodHandle mix = mixer(ttypes[pos]);
boolean lastPType = pos == ttypes.length - 1;
if (lastPType) {
// (ttype) -> long
mix = MethodHandles.insertArguments(mix, 0, initialLengthCoder);
// (ttypes...) -> String
mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, mix,
1 + pos // selected argument
);
} else {
// (long,ttypes...) -> String
mh = MethodHandles.filterArgumentsWithCombiner(mh, 0, mix,
0, // old-index
1 + pos // selected argument
);
}
pos++;
}
mh = MethodHandles.filterArguments(mh, 0, filters);
MethodType mt = MethodType.methodType(String.class, ptypes);
mh = mh.viewAsType(mt, true);
return mh;
}
/**
* This method breaks up large concatenations into separate
* {@link MethodHandle MethodHandles} based on the number of slots required
* per {@link MethodHandle}. Each {@link MethodHandle} after the first will
* have an extra {@link String} slot for the result from the previous
* {@link MethodHandle}.
* {@link #makeConcatWithTemplate}
* is used to construct the {@link MethodHandle MethodHandles}. The total
* number of slots used by the ptypes is open ended. However, care must
* be given when combining the {@link MethodHandle MethodHandles} so that
* the combine total does not exceed the 255 slot limit.
*
* @param fragments list of string fragments
* @param ptypes list of expression types
* @param maxSlots maximum number of slots per {@link MethodHandle}.
*
* @return List of {@link MethodHandle MethodHandles}
*
* @throws IllegalArgumentException If maxSlots is not between 1 and
* MAX_INDY_CONCAT_ARG_SLOTS.
* @throws StringConcatException If any of the linkage invariants are violated.
* @throws NullPointerException If any of the incoming arguments is null.
* @throws IllegalArgumentException If the number of value slots exceed {@link #MAX_INDY_CONCAT_ARG_SLOTS}.
*
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
public static List<MethodHandle> makeConcatWithTemplateCluster(
List<String> fragments,
List<Class<?>> ptypes,
int maxSlots)
throws StringConcatException
{
Objects.requireNonNull(fragments, "fragments is null");
Objects.requireNonNull(ptypes, "ptypes is null");
if (fragments.size() != ptypes.size() + 1) {
throw new StringConcatException("fragments size not equal ptypes size plus one");
}
if (maxSlots < 1 || MAX_INDY_CONCAT_ARG_SLOTS < maxSlots) {
throw new IllegalArgumentException("maxSlots must be between 1 and " +
MAX_INDY_CONCAT_ARG_SLOTS);
}
if (ptypes.isEmpty()) {
return List.of(MethodHandles.constant(String.class, fragments.get(0)));
}
List<MethodHandle> mhs = new ArrayList<>();
List<String> fragmentsSection = new ArrayList<>();
List<Class<?>> ptypeSection = new ArrayList<>();
int slots = 0;
int pos = 0;
for (Class<?> ptype : ptypes) {
boolean lastPType = pos == ptypes.size() - 1;
fragmentsSection.add(fragments.get(pos));
ptypeSection.add(ptype);
slots += ptype == long.class || ptype == double.class ? 2 : 1;
if (maxSlots <= slots || lastPType) {
fragmentsSection.add(lastPType ? fragments.get(pos + 1) : "");
MethodHandle mh = makeConcatWithTemplate(fragmentsSection,
ptypeSection);
mhs.add(mh);
fragmentsSection.clear();
fragmentsSection.add("");
ptypeSection.clear();
ptypeSection.add(String.class);
slots = 1;
}
pos++;
}
return mhs;
}
/**
* This method creates a {@link MethodHandle} expecting one input, the
* receiver of the supplied getters. This method uses
* {@link #makeConcatWithTemplateCluster}
* to create the intermediate {@link MethodHandle MethodHandles}.
*
* @param fragments list of string fragments
* @param getters list of getter {@link MethodHandle MethodHandles}
* @param maxSlots maximum number of slots per {@link MethodHandle} in
* cluster.
*
* @return the {@link MethodHandle} for concatenation
*
* @throws IllegalArgumentException If maxSlots is not between 1 and
* MAX_INDY_CONCAT_ARG_SLOTS or if the
* getters don't use the same argument type
* @throws StringConcatException If any of the linkage invariants are violated
* @throws NullPointerException If any of the incoming arguments is null
* @throws IllegalArgumentException If the number of value slots exceed {@link #MAX_INDY_CONCAT_ARG_SLOTS}.
*
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
public static MethodHandle makeConcatWithTemplateGetters(
List<String> fragments,
List<MethodHandle> getters,
int maxSlots)
throws StringConcatException
{
Objects.requireNonNull(fragments, "fragments is null");
Objects.requireNonNull(getters, "getters is null");
if (fragments.size() != getters.size() + 1) {
throw new StringConcatException("fragments size not equal getters size plus one");
}
if (maxSlots < 1 || MAX_INDY_CONCAT_ARG_SLOTS < maxSlots) {
throw new IllegalArgumentException("maxSlots must be between 1 and " +
MAX_INDY_CONCAT_ARG_SLOTS);
}
if (getters.size() == 0) {
throw new StringConcatException("no getters supplied");
}
Class<?> receiverType = null;
List<Class<?>> ptypes = new ArrayList<>();
for (MethodHandle getter : getters) {
MethodType mt = getter.type();
Class<?> returnType = mt.returnType();
if (returnType == void.class || mt.parameterCount() != 1) {
throw new StringConcatException("not a getter " + mt);
}
if (receiverType == null) {
receiverType = mt.parameterType(0);
} else if (receiverType != mt.parameterType(0)) {
throw new StringConcatException("not the same receiever type " +
mt + " needs " + receiverType);
}
ptypes.add(returnType);
}
MethodType resultType = MethodType.methodType(String.class, receiverType);
List<MethodHandle> clusters = makeConcatWithTemplateCluster(fragments, ptypes,
maxSlots);
MethodHandle mh = null;
Iterator<MethodHandle> getterIterator = getters.iterator();
for (MethodHandle cluster : clusters) {
MethodType mt = cluster.type();
MethodHandle[] filters = new MethodHandle[mt.parameterCount()];
int pos = 0;
if (mh != null) {
filters[pos++] = mh;
}
while (pos < filters.length) {
filters[pos++] = getterIterator.next();
}
cluster = MethodHandles.filterArguments(cluster, 0, filters);
mh = MethodHandles.permuteArguments(cluster, resultType,
new int[filters.length]);
}
return mh;
}
}