8246152: Improve String concat bootstrapping
Reviewed-by: forax, psandoz
This commit is contained in:
parent
f3e027c001
commit
1f698a35f2
@ -32,7 +32,6 @@ import sun.invoke.util.Wrapper;
|
|||||||
|
|
||||||
import java.lang.invoke.MethodHandles.Lookup;
|
import java.lang.invoke.MethodHandles.Lookup;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@ -121,141 +120,8 @@ public final class StringConcatFactory {
|
|||||||
|
|
||||||
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the recipe string, and produces a traversable collection of
|
|
||||||
* {@link java.lang.invoke.StringConcatFactory.RecipeElement}-s for generator
|
|
||||||
* strategies. Notably, this class parses out the constants from the recipe
|
|
||||||
* and from other static arguments.
|
|
||||||
*/
|
|
||||||
private static final class Recipe {
|
|
||||||
private final List<RecipeElement> elements;
|
|
||||||
|
|
||||||
public Recipe(String src, Object[] constants) {
|
|
||||||
List<RecipeElement> el = new ArrayList<>();
|
|
||||||
|
|
||||||
int constC = 0;
|
|
||||||
int argC = 0;
|
|
||||||
|
|
||||||
StringBuilder acc = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 0; i < src.length(); i++) {
|
|
||||||
char c = src.charAt(i);
|
|
||||||
|
|
||||||
if (c == TAG_CONST || c == TAG_ARG) {
|
|
||||||
// Detected a special tag, flush all accumulated characters
|
|
||||||
// as a constant first:
|
|
||||||
if (acc.length() > 0) {
|
|
||||||
el.add(new RecipeElement(acc.toString()));
|
|
||||||
acc.setLength(0);
|
|
||||||
}
|
|
||||||
if (c == TAG_CONST) {
|
|
||||||
Object cnst = constants[constC++];
|
|
||||||
el.add(new RecipeElement(cnst));
|
|
||||||
} else if (c == TAG_ARG) {
|
|
||||||
el.add(new RecipeElement(argC++));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Not a special character, this is a constant embedded into
|
|
||||||
// the recipe itself.
|
|
||||||
acc.append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush the remaining characters as constant:
|
|
||||||
if (acc.length() > 0) {
|
|
||||||
el.add(new RecipeElement(acc.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
elements = el;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<RecipeElement> getElements() {
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
Recipe recipe = (Recipe) o;
|
|
||||||
return elements.equals(recipe.elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Recipe{" +
|
|
||||||
"elements=" + elements +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return elements.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class RecipeElement {
|
|
||||||
private final String value;
|
|
||||||
private final int argPos;
|
|
||||||
private final char tag;
|
|
||||||
|
|
||||||
public RecipeElement(Object cnst) {
|
|
||||||
this.value = String.valueOf(Objects.requireNonNull(cnst));
|
|
||||||
this.argPos = -1;
|
|
||||||
this.tag = TAG_CONST;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RecipeElement(int arg) {
|
|
||||||
this.value = null;
|
|
||||||
this.argPos = arg;
|
|
||||||
this.tag = TAG_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getValue() {
|
|
||||||
assert (tag == TAG_CONST);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getArgPos() {
|
|
||||||
assert (tag == TAG_ARG);
|
|
||||||
return argPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public char getTag() {
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
RecipeElement that = (RecipeElement) o;
|
|
||||||
|
|
||||||
if (this.tag != that.tag) return false;
|
|
||||||
if (this.tag == TAG_CONST && (!value.equals(that.value))) return false;
|
|
||||||
if (this.tag == TAG_ARG && (argPos != that.argPos)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "RecipeElement{" +
|
|
||||||
"value='" + value + '\'' +
|
|
||||||
", argPos=" + argPos +
|
|
||||||
", tag=" + tag +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return (int)tag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringConcatFactory bootstrap methods are startup sensitive, and may be
|
// StringConcatFactory bootstrap methods are startup sensitive, and may be
|
||||||
// special cased in java.lang.invokeBootstrapMethodInvoker to ensure
|
// special cased in java.lang.invoke.BootstrapMethodInvoker to ensure
|
||||||
// methods are invoked with exact type information to avoid generating
|
// methods are invoked with exact type information to avoid generating
|
||||||
// code for runtime checks. Take care any changes or additions here are
|
// code for runtime checks. Take care any changes or additions here are
|
||||||
// reflected there as appropriate.
|
// reflected there as appropriate.
|
||||||
@ -332,7 +198,12 @@ public final class StringConcatFactory {
|
|||||||
public static CallSite makeConcat(MethodHandles.Lookup lookup,
|
public static CallSite makeConcat(MethodHandles.Lookup lookup,
|
||||||
String name,
|
String name,
|
||||||
MethodType concatType) throws StringConcatException {
|
MethodType concatType) throws StringConcatException {
|
||||||
return doStringConcat(lookup, name, concatType, true, null);
|
// This bootstrap method is unlikely to be used in practice,
|
||||||
|
// avoid optimizing it at the expense of makeConcatWithConstants
|
||||||
|
|
||||||
|
// Mock the recipe to reuse the concat generator code
|
||||||
|
String recipe = "\u0001".repeat(concatType.parameterCount());
|
||||||
|
return makeConcatWithConstants(lookup, name, concatType, recipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -450,64 +321,20 @@ public final class StringConcatFactory {
|
|||||||
String name,
|
String name,
|
||||||
MethodType concatType,
|
MethodType concatType,
|
||||||
String recipe,
|
String recipe,
|
||||||
Object... constants) throws StringConcatException {
|
Object... constants)
|
||||||
return doStringConcat(lookup, name, concatType, false, recipe, constants);
|
throws StringConcatException
|
||||||
}
|
{
|
||||||
|
|
||||||
private static CallSite doStringConcat(MethodHandles.Lookup lookup,
|
|
||||||
String name,
|
|
||||||
MethodType concatType,
|
|
||||||
boolean generateRecipe,
|
|
||||||
String recipe,
|
|
||||||
Object... constants) throws StringConcatException {
|
|
||||||
Objects.requireNonNull(lookup, "Lookup is null");
|
Objects.requireNonNull(lookup, "Lookup is null");
|
||||||
Objects.requireNonNull(name, "Name is null");
|
Objects.requireNonNull(name, "Name is null");
|
||||||
Objects.requireNonNull(concatType, "Concat type is null");
|
Objects.requireNonNull(concatType, "Concat type is null");
|
||||||
Objects.requireNonNull(constants, "Constants are null");
|
Objects.requireNonNull(constants, "Constants are null");
|
||||||
|
|
||||||
for (Object o : constants) {
|
|
||||||
Objects.requireNonNull(o, "Cannot accept null constants");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((lookup.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) {
|
if ((lookup.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) {
|
||||||
throw new StringConcatException("Invalid caller: " +
|
throw new StringConcatException("Invalid caller: " +
|
||||||
lookup.lookupClass().getName());
|
lookup.lookupClass().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
int cCount = 0;
|
List<String> elements = parseRecipe(concatType, recipe, constants);
|
||||||
int oCount = 0;
|
|
||||||
if (generateRecipe) {
|
|
||||||
// Mock the recipe to reuse the concat generator code
|
|
||||||
char[] value = new char[concatType.parameterCount()];
|
|
||||||
Arrays.fill(value, TAG_ARG);
|
|
||||||
recipe = new String(value);
|
|
||||||
oCount = concatType.parameterCount();
|
|
||||||
} else {
|
|
||||||
Objects.requireNonNull(recipe, "Recipe is null");
|
|
||||||
|
|
||||||
for (int i = 0; i < recipe.length(); i++) {
|
|
||||||
char c = recipe.charAt(i);
|
|
||||||
if (c == TAG_CONST) cCount++;
|
|
||||||
if (c == TAG_ARG) oCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oCount != concatType.parameterCount()) {
|
|
||||||
throw new StringConcatException(
|
|
||||||
"Mismatched number of concat arguments: recipe wants " +
|
|
||||||
oCount +
|
|
||||||
" arguments, but signature provides " +
|
|
||||||
concatType.parameterCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cCount != constants.length) {
|
|
||||||
throw new StringConcatException(
|
|
||||||
"Mismatched number of concat constants: recipe wants " +
|
|
||||||
cCount +
|
|
||||||
" constants, but only " +
|
|
||||||
constants.length +
|
|
||||||
" are passed");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!concatType.returnType().isAssignableFrom(String.class)) {
|
if (!concatType.returnType().isAssignableFrom(String.class)) {
|
||||||
throw new StringConcatException(
|
throw new StringConcatException(
|
||||||
@ -522,22 +349,86 @@ public final class StringConcatFactory {
|
|||||||
MAX_INDY_CONCAT_ARG_SLOTS);
|
MAX_INDY_CONCAT_ARG_SLOTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
Recipe rec = new Recipe(recipe, constants);
|
|
||||||
MethodHandle mh = generate(lookup, concatType, rec);
|
|
||||||
return new ConstantCallSite(mh.asType(concatType));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MethodHandle generate(Lookup lookup, MethodType mt, Recipe recipe) throws StringConcatException {
|
|
||||||
try {
|
try {
|
||||||
return generateMHInlineCopy(mt, recipe);
|
return new ConstantCallSite(
|
||||||
} catch (Error | StringConcatException e) {
|
generateMHInlineCopy(concatType, elements)
|
||||||
// Pass through any error or existing StringConcatException
|
.viewAsType(concatType, true));
|
||||||
|
} catch (Error e) {
|
||||||
|
// Pass through any error
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw new StringConcatException("Generator failed", t);
|
throw new StringConcatException("Generator failed", t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<String> parseRecipe(MethodType concatType,
|
||||||
|
String recipe,
|
||||||
|
Object[] constants)
|
||||||
|
throws StringConcatException
|
||||||
|
{
|
||||||
|
|
||||||
|
Objects.requireNonNull(recipe, "Recipe is null");
|
||||||
|
// Element list containing String constants, or null for arguments
|
||||||
|
List<String> elements = new ArrayList<>();
|
||||||
|
|
||||||
|
int cCount = 0;
|
||||||
|
int oCount = 0;
|
||||||
|
|
||||||
|
StringBuilder acc = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < recipe.length(); i++) {
|
||||||
|
char c = recipe.charAt(i);
|
||||||
|
|
||||||
|
if (c == TAG_CONST) {
|
||||||
|
if (cCount == constants.length) {
|
||||||
|
// Not enough constants
|
||||||
|
throw constantMismatch(concatType, oCount);
|
||||||
|
}
|
||||||
|
// Accumulate constant args along with any constants encoded
|
||||||
|
// into the recipe
|
||||||
|
acc.append(Objects.requireNonNull(constants[cCount++], "Cannot accept null constants"));
|
||||||
|
} else if (c == TAG_ARG) {
|
||||||
|
// Flush any accumulated characters into a constant
|
||||||
|
if (acc.length() > 0) {
|
||||||
|
elements.add(acc.toString());
|
||||||
|
acc.setLength(0);
|
||||||
|
}
|
||||||
|
elements.add(null);
|
||||||
|
oCount++;
|
||||||
|
} else {
|
||||||
|
// Not a special character, this is a constant embedded into
|
||||||
|
// the recipe itself.
|
||||||
|
acc.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the remaining characters as constant:
|
||||||
|
if (acc.length() > 0) {
|
||||||
|
elements.add(acc.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oCount != concatType.parameterCount()) {
|
||||||
|
throw constantMismatch(concatType, oCount);
|
||||||
|
}
|
||||||
|
if (cCount != constants.length) {
|
||||||
|
throw new StringConcatException(
|
||||||
|
"Mismatched number of concat constants: recipe wants " +
|
||||||
|
cCount +
|
||||||
|
" constants, but only " +
|
||||||
|
constants.length +
|
||||||
|
" are passed");
|
||||||
|
}
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringConcatException constantMismatch(MethodType concatType,
|
||||||
|
int oCount) {
|
||||||
|
return new StringConcatException(
|
||||||
|
"Mismatched number of concat arguments: recipe wants " +
|
||||||
|
oCount +
|
||||||
|
" arguments, but signature provides " +
|
||||||
|
concatType.parameterCount());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>This strategy replicates what StringBuilders are doing: it builds the
|
* <p>This strategy replicates what StringBuilders are doing: it builds the
|
||||||
@ -546,35 +437,33 @@ public final class StringConcatFactory {
|
|||||||
* most notably, the private String constructor that accepts byte[] arrays
|
* most notably, the private String constructor that accepts byte[] arrays
|
||||||
* without copying.
|
* without copying.
|
||||||
*/
|
*/
|
||||||
private static MethodHandle generateMHInlineCopy(MethodType mt, Recipe recipe) throws Throwable {
|
private static MethodHandle generateMHInlineCopy(MethodType mt, List<String> elements) {
|
||||||
|
|
||||||
// Fast-path two-argument Object + Object concatenations
|
// Fast-path two-argument Object + Object concatenations
|
||||||
if (recipe.getElements().size() == 2) {
|
if (elements.size() == 2) {
|
||||||
// Two object arguments
|
// Two object arguments
|
||||||
|
String s0 = elements.get(0);
|
||||||
|
String s1 = elements.get(1);
|
||||||
|
|
||||||
if (mt.parameterCount() == 2 &&
|
if (mt.parameterCount() == 2 &&
|
||||||
!mt.parameterType(0).isPrimitive() &&
|
!mt.parameterType(0).isPrimitive() &&
|
||||||
!mt.parameterType(1).isPrimitive() &&
|
!mt.parameterType(1).isPrimitive() &&
|
||||||
recipe.getElements().get(0).getTag() == TAG_ARG &&
|
s0 == null &&
|
||||||
recipe.getElements().get(1).getTag() == TAG_ARG) {
|
s1 == null) {
|
||||||
|
|
||||||
return simpleConcat();
|
return simpleConcat();
|
||||||
|
|
||||||
} else if (mt.parameterCount() == 1 &&
|
} else if (mt.parameterCount() == 1 &&
|
||||||
!mt.parameterType(0).isPrimitive()) {
|
!mt.parameterType(0).isPrimitive()) {
|
||||||
|
|
||||||
// One Object argument, one constant
|
// One Object argument, one constant
|
||||||
MethodHandle mh = simpleConcat();
|
MethodHandle mh = simpleConcat();
|
||||||
|
|
||||||
if (recipe.getElements().get(0).getTag() == TAG_CONST &&
|
if (s0 != null && s1 == null) {
|
||||||
recipe.getElements().get(1).getTag() == TAG_ARG) {
|
|
||||||
// First recipe element is a constant
|
// First recipe element is a constant
|
||||||
return MethodHandles.insertArguments(mh, 0,
|
return MethodHandles.insertArguments(mh, 0, s0);
|
||||||
recipe.getElements().get(0).getValue());
|
|
||||||
|
|
||||||
} else if (recipe.getElements().get(1).getTag() == TAG_CONST &&
|
} else if (s1 != null && s0 == null) {
|
||||||
recipe.getElements().get(0).getTag() == TAG_ARG) {
|
|
||||||
// Second recipe element is a constant
|
// Second recipe element is a constant
|
||||||
return MethodHandles.insertArguments(mh, 1,
|
return MethodHandles.insertArguments(mh, 1, s1);
|
||||||
recipe.getElements().get(1).getValue());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -584,7 +473,8 @@ public final class StringConcatFactory {
|
|||||||
// Create filters and obtain filtered parameter types. Filters would be used in the beginning
|
// Create filters and obtain filtered parameter types. Filters would be used in the beginning
|
||||||
// to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings).
|
// to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings).
|
||||||
// The filtered argument type list is used all over in the combinators below.
|
// The filtered argument type list is used all over in the combinators below.
|
||||||
Class<?>[] ptypes = mt.parameterArray();
|
|
||||||
|
Class<?>[] ptypes = mt.erase().parameterArray();
|
||||||
MethodHandle[] filters = null;
|
MethodHandle[] filters = null;
|
||||||
for (int i = 0; i < ptypes.length; i++) {
|
for (int i = 0; i < ptypes.length; i++) {
|
||||||
MethodHandle filter = stringifierFor(ptypes[i]);
|
MethodHandle filter = stringifierFor(ptypes[i]);
|
||||||
@ -593,7 +483,7 @@ public final class StringConcatFactory {
|
|||||||
filters = new MethodHandle[ptypes.length];
|
filters = new MethodHandle[ptypes.length];
|
||||||
}
|
}
|
||||||
filters[i] = filter;
|
filters[i] = filter;
|
||||||
ptypes[i] = filter.type().returnType();
|
ptypes[i] = String.class;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -602,9 +492,7 @@ public final class StringConcatFactory {
|
|||||||
// assembled bottom-up, which makes the code arguably hard to read.
|
// assembled bottom-up, which makes the code arguably hard to read.
|
||||||
|
|
||||||
// Drop all remaining parameter types, leave only helper arguments:
|
// Drop all remaining parameter types, leave only helper arguments:
|
||||||
MethodHandle mh;
|
MethodHandle mh = MethodHandles.dropArguments(newString(), 2, ptypes);
|
||||||
|
|
||||||
mh = MethodHandles.dropArguments(newString(), 2, ptypes);
|
|
||||||
|
|
||||||
long initialLengthCoder = INITIAL_CODER;
|
long initialLengthCoder = INITIAL_CODER;
|
||||||
|
|
||||||
@ -616,24 +504,21 @@ public final class StringConcatFactory {
|
|||||||
// create prependers that fold in surrounding constants into the argument prepender. This reduces
|
// create prependers that fold in surrounding constants into the argument prepender. This reduces
|
||||||
// the number of unique MH combinator tree shapes we'll create in an application.
|
// the number of unique MH combinator tree shapes we'll create in an application.
|
||||||
String constant = null;
|
String constant = null;
|
||||||
for (RecipeElement el : recipe.getElements()) {
|
int pos = 0;
|
||||||
|
for (String el : elements) {
|
||||||
// Do the prepend, and put "new" index at index 1
|
// Do the prepend, and put "new" index at index 1
|
||||||
switch (el.getTag()) {
|
if (el != null) {
|
||||||
case TAG_CONST: {
|
// Constant element
|
||||||
String constantValue = el.getValue();
|
|
||||||
|
|
||||||
// Eagerly update the initialLengthCoder value
|
// Eagerly update the initialLengthCoder value
|
||||||
initialLengthCoder = JLA.stringConcatMix(initialLengthCoder, constantValue);
|
initialLengthCoder = JLA.stringConcatMix(initialLengthCoder, el);
|
||||||
|
|
||||||
// Collecting into a single constant that we'll either fold
|
// Save the constant and fold it either into the next
|
||||||
// into the next argument prepender, or into the newArray
|
// argument prepender, or into the newArray combinator
|
||||||
// combinator
|
assert (constant == null);
|
||||||
constant = constant == null ? constantValue : constant + constantValue;
|
constant = el;
|
||||||
break;
|
} else {
|
||||||
}
|
|
||||||
case TAG_ARG: {
|
|
||||||
// Add prepender, along with any prefix constant
|
// Add prepender, along with any prefix constant
|
||||||
int pos = el.getArgPos();
|
|
||||||
mh = MethodHandles.filterArgumentsWithCombiner(
|
mh = MethodHandles.filterArgumentsWithCombiner(
|
||||||
mh, 1,
|
mh, 1,
|
||||||
prepender(constant, ptypes[pos]),
|
prepender(constant, ptypes[pos]),
|
||||||
@ -641,10 +526,7 @@ public final class StringConcatFactory {
|
|||||||
2 + pos // selected argument
|
2 + pos // selected argument
|
||||||
);
|
);
|
||||||
constant = null;
|
constant = null;
|
||||||
break;
|
pos++;
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new StringConcatException("Unhandled tag: " + el.getTag());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -686,38 +568,30 @@ public final class StringConcatFactory {
|
|||||||
// combined in as:
|
// combined in as:
|
||||||
// (<args>)String = (<args>)
|
// (<args>)String = (<args>)
|
||||||
|
|
||||||
int ac = -1;
|
pos = -1;
|
||||||
MethodHandle mix = null;
|
MethodHandle mix = null;
|
||||||
for (RecipeElement el : recipe.getElements()) {
|
for (String el : elements) {
|
||||||
switch (el.getTag()) {
|
|
||||||
case TAG_CONST:
|
|
||||||
// Constants already handled in the code above
|
// Constants already handled in the code above
|
||||||
break;
|
if (el == null) {
|
||||||
case TAG_ARG:
|
if (pos >= 0) {
|
||||||
if (ac >= 0) {
|
|
||||||
// Compute new "index" in-place using old value plus the appropriate argument.
|
// Compute new "index" in-place using old value plus the appropriate argument.
|
||||||
mh = MethodHandles.filterArgumentsWithCombiner(mh, 0, mix,
|
mh = MethodHandles.filterArgumentsWithCombiner(mh, 0, mix,
|
||||||
0, // old-index
|
0, // old-index
|
||||||
1 + ac // selected argument
|
1 + pos // selected argument
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ac = el.getArgPos();
|
Class<?> argClass = ptypes[++pos];
|
||||||
Class<?> argClass = ptypes[ac];
|
|
||||||
mix = mixer(argClass);
|
mix = mixer(argClass);
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new StringConcatException("Unhandled tag: " + el.getTag());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the initialLengthCoder value into the final mixer, then
|
// Insert the initialLengthCoder value into the final mixer, then
|
||||||
// fold that into the base method handle
|
// fold that into the base method handle
|
||||||
if (ac >= 0) {
|
if (pos >= 0) {
|
||||||
mix = MethodHandles.insertArguments(mix, 0, initialLengthCoder);
|
mix = MethodHandles.insertArguments(mix, 0, initialLengthCoder);
|
||||||
mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, mix,
|
mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, mix,
|
||||||
1 + ac // selected argument
|
1 + pos // selected argument
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// No mixer (constants only concat), insert initialLengthCoder directly
|
// No mixer (constants only concat), insert initialLengthCoder directly
|
||||||
@ -750,9 +624,10 @@ public final class StringConcatFactory {
|
|||||||
private static final Function<Class<?>, MethodHandle> PREPEND = new Function<>() {
|
private static final Function<Class<?>, MethodHandle> PREPEND = new Function<>() {
|
||||||
@Override
|
@Override
|
||||||
public MethodHandle apply(Class<?> c) {
|
public MethodHandle apply(Class<?> c) {
|
||||||
return JLA.stringConcatHelper("prepend",
|
MethodHandle prepend = JLA.stringConcatHelper("prepend",
|
||||||
methodType(long.class, long.class, byte[].class,
|
methodType(long.class, long.class, byte[].class,
|
||||||
Wrapper.asPrimitiveType(c), String.class));
|
Wrapper.asPrimitiveType(c), String.class));
|
||||||
|
return prepend.rebind();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -767,24 +642,30 @@ public final class StringConcatFactory {
|
|||||||
private static final Function<Class<?>, MethodHandle> MIX = new Function<>() {
|
private static final Function<Class<?>, MethodHandle> MIX = new Function<>() {
|
||||||
@Override
|
@Override
|
||||||
public MethodHandle apply(Class<?> c) {
|
public MethodHandle apply(Class<?> c) {
|
||||||
return JLA.stringConcatHelper("mix", methodType(long.class, long.class, Wrapper.asPrimitiveType(c)));
|
MethodHandle mix = JLA.stringConcatHelper("mix",
|
||||||
|
methodType(long.class, long.class, Wrapper.asPrimitiveType(c)));
|
||||||
|
return mix.rebind();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private @Stable static MethodHandle SIMPLE_CONCAT;
|
private @Stable static MethodHandle SIMPLE_CONCAT;
|
||||||
private static MethodHandle simpleConcat() {
|
private static MethodHandle simpleConcat() {
|
||||||
if (SIMPLE_CONCAT == null) {
|
MethodHandle mh = SIMPLE_CONCAT;
|
||||||
SIMPLE_CONCAT = JLA.stringConcatHelper("simpleConcat", methodType(String.class, Object.class, Object.class));
|
if (mh == null) {
|
||||||
|
MethodHandle simpleConcat = JLA.stringConcatHelper("simpleConcat",
|
||||||
|
methodType(String.class, Object.class, Object.class));
|
||||||
|
SIMPLE_CONCAT = mh = simpleConcat.rebind();
|
||||||
}
|
}
|
||||||
return SIMPLE_CONCAT;
|
return mh;
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Stable static MethodHandle NEW_STRING;
|
private @Stable static MethodHandle NEW_STRING;
|
||||||
private static MethodHandle newString() {
|
private static MethodHandle newString() {
|
||||||
MethodHandle mh = NEW_STRING;
|
MethodHandle mh = NEW_STRING;
|
||||||
if (mh == null) {
|
if (mh == null) {
|
||||||
NEW_STRING = mh =
|
MethodHandle newString = JLA.stringConcatHelper("newString",
|
||||||
JLA.stringConcatHelper("newString", methodType(String.class, byte[].class, long.class));
|
methodType(String.class, byte[].class, long.class));
|
||||||
|
NEW_STRING = mh = newString.rebind();
|
||||||
}
|
}
|
||||||
return mh;
|
return mh;
|
||||||
}
|
}
|
||||||
@ -793,9 +674,9 @@ public final class StringConcatFactory {
|
|||||||
private static MethodHandle newArrayWithSuffix(String suffix) {
|
private static MethodHandle newArrayWithSuffix(String suffix) {
|
||||||
MethodHandle mh = NEW_ARRAY_SUFFIX;
|
MethodHandle mh = NEW_ARRAY_SUFFIX;
|
||||||
if (mh == null) {
|
if (mh == null) {
|
||||||
NEW_ARRAY_SUFFIX = mh =
|
MethodHandle newArrayWithSuffix = JLA.stringConcatHelper("newArrayWithSuffix",
|
||||||
JLA.stringConcatHelper("newArrayWithSuffix",
|
|
||||||
methodType(byte[].class, String.class, long.class));
|
methodType(byte[].class, String.class, long.class));
|
||||||
|
NEW_ARRAY_SUFFIX = mh = newArrayWithSuffix.rebind();
|
||||||
}
|
}
|
||||||
return MethodHandles.insertArguments(mh, 0, suffix);
|
return MethodHandles.insertArguments(mh, 0, suffix);
|
||||||
}
|
}
|
||||||
@ -819,8 +700,8 @@ public final class StringConcatFactory {
|
|||||||
private static MethodHandle objectStringifier() {
|
private static MethodHandle objectStringifier() {
|
||||||
MethodHandle mh = OBJECT_STRINGIFIER;
|
MethodHandle mh = OBJECT_STRINGIFIER;
|
||||||
if (mh == null) {
|
if (mh == null) {
|
||||||
OBJECT_STRINGIFIER = mh =
|
OBJECT_STRINGIFIER = mh = JLA.stringConcatHelper("stringOf",
|
||||||
JLA.stringConcatHelper("stringOf", methodType(String.class, Object.class));
|
methodType(String.class, Object.class));
|
||||||
}
|
}
|
||||||
return mh;
|
return mh;
|
||||||
}
|
}
|
||||||
@ -863,7 +744,7 @@ public final class StringConcatFactory {
|
|||||||
* @return stringifier; null, if not available
|
* @return stringifier; null, if not available
|
||||||
*/
|
*/
|
||||||
private static MethodHandle stringifierFor(Class<?> t) {
|
private static MethodHandle stringifierFor(Class<?> t) {
|
||||||
if (!t.isPrimitive()) {
|
if (t == Object.class) {
|
||||||
return objectStringifier();
|
return objectStringifier();
|
||||||
} else if (t == float.class) {
|
} else if (t == float.class) {
|
||||||
return floatStringifier();
|
return floatStringifier();
|
||||||
|
@ -28,6 +28,7 @@ import java.util.concurrent.Callable;
|
|||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @summary Test input invariants for StringConcatFactory
|
* @summary Test input invariants for StringConcatFactory
|
||||||
|
* @bug 8246152
|
||||||
*
|
*
|
||||||
* @compile StringConcatFactoryInvariants.java
|
* @compile StringConcatFactoryInvariants.java
|
||||||
*
|
*
|
||||||
@ -213,9 +214,15 @@ public class StringConcatFactoryInvariants {
|
|||||||
ok("Static arguments and recipe match",
|
ok("Static arguments and recipe match",
|
||||||
() -> StringConcatFactory.makeConcatWithConstants(lookup, methodName, mtThreshold, recipeThreshold, "bar"));
|
() -> StringConcatFactory.makeConcatWithConstants(lookup, methodName, mtThreshold, recipeThreshold, "bar"));
|
||||||
|
|
||||||
fail("Static arguments and recipe mismatch",
|
fail("Static arguments and recipe mismatch: too few",
|
||||||
|
() -> StringConcatFactory.makeConcatWithConstants(lookup, methodName, mtThreshold, recipeThreshold));
|
||||||
|
|
||||||
|
fail("Static arguments and recipe mismatch: too many",
|
||||||
() -> StringConcatFactory.makeConcatWithConstants(lookup, methodName, mtThreshold, recipeThreshold, "bar", "baz"));
|
() -> StringConcatFactory.makeConcatWithConstants(lookup, methodName, mtThreshold, recipeThreshold, "bar", "baz"));
|
||||||
|
|
||||||
|
fail("Static arguments and recipe mismatch, too many, overflowing constant is null",
|
||||||
|
() -> StringConcatFactory.makeConcatWithConstants(lookup, methodName, mtThreshold, recipeThreshold, "bar", null));
|
||||||
|
|
||||||
// Advanced factory: check for mismatched recipe and dynamic arguments
|
// Advanced factory: check for mismatched recipe and dynamic arguments
|
||||||
fail("Dynamic arguments and recipe mismatch",
|
fail("Dynamic arguments and recipe mismatch",
|
||||||
() -> StringConcatFactory.makeConcatWithConstants(lookup, methodName, mtThreshold, recipeUnderThreshold, constants[0]));
|
() -> StringConcatFactory.makeConcatWithConstants(lookup, methodName, mtThreshold, recipeUnderThreshold, constants[0]));
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
package org.openjdk.bench.java.lang.invoke;
|
||||||
|
|
||||||
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
|
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||||
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Scope;
|
||||||
|
import org.openjdk.jmh.annotations.Setup;
|
||||||
|
import org.openjdk.jmh.annotations.State;
|
||||||
|
|
||||||
|
import java.lang.invoke.CallSite;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.lang.invoke.StringConcatFactory;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check StringConcatFactory bootstrap overheads
|
||||||
|
*/
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||||
|
@State(Scope.Thread)
|
||||||
|
public class StringConcatFactoryBootstraps {
|
||||||
|
|
||||||
|
public MethodType mt =
|
||||||
|
MethodType.methodType(String.class, String.class, int.class,
|
||||||
|
String.class, String.class);
|
||||||
|
public String recipe = "test\u0001foo\u0001\u0002bar\u0001\u0002baz\u0001";
|
||||||
|
public MethodHandles.Lookup lookup;
|
||||||
|
|
||||||
|
@Setup
|
||||||
|
public void setup() {
|
||||||
|
try {
|
||||||
|
lookup = MethodHandles.privateLookupIn(this.getClass(), MethodHandles.lookup());
|
||||||
|
} catch (Exception e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public CallSite makeConcatWithConstants() throws Throwable {
|
||||||
|
return StringConcatFactory.makeConcatWithConstants(lookup, "dummy", mt, recipe, "const1", "const2");
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user