mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
8247681: Improve bootstrapping of unary concatenations
Reviewed-by: jlaskey, psandoz
This commit is contained in:
parent
1d87958ead
commit
34c79640e7
6 changed files with 149 additions and 19 deletions
|
@ -426,6 +426,22 @@ final class StringConcatHelper {
|
||||||
return newString(buf, indexCoder);
|
return newString(buf, indexCoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce a String from a concatenation of single argument, which we
|
||||||
|
* end up using for trivial concatenations like {@code "" + arg}.
|
||||||
|
*
|
||||||
|
* This will always create a new Object to comply with JLS 15.18.1:
|
||||||
|
* "The String object is newly created unless the expression is a
|
||||||
|
* compile-time constant expression".
|
||||||
|
*
|
||||||
|
* @param arg the only argument
|
||||||
|
* @return String resulting string
|
||||||
|
*/
|
||||||
|
@ForceInline
|
||||||
|
static String newStringOf(Object arg) {
|
||||||
|
return new String(stringOf(arg));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We need some additional conversion for Objects in general, because
|
* We need some additional conversion for Objects in general, because
|
||||||
* {@code String.valueOf(Object)} may return null. String conversion rules
|
* {@code String.valueOf(Object)} may return null. String conversion rules
|
||||||
|
|
|
@ -447,9 +447,18 @@ public final class StringConcatFactory {
|
||||||
*/
|
*/
|
||||||
private static MethodHandle generateMHInlineCopy(MethodType mt, List<String> elements) {
|
private static MethodHandle generateMHInlineCopy(MethodType mt, List<String> elements) {
|
||||||
|
|
||||||
// Fast-path two-argument Object + Object concatenations
|
// Fast-path unary concatenations
|
||||||
|
if (elements.size() == 1) {
|
||||||
|
String s0 = elements.get(0);
|
||||||
|
if (s0 == null) {
|
||||||
|
return unaryConcat(mt.parameterType(0));
|
||||||
|
} else {
|
||||||
|
return MethodHandles.insertArguments(unaryConcat(Object.class), 0, s0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fast-path binary concatenations
|
||||||
if (elements.size() == 2) {
|
if (elements.size() == 2) {
|
||||||
// Two object arguments
|
// Two arguments
|
||||||
String s0 = elements.get(0);
|
String s0 = elements.get(0);
|
||||||
String s1 = elements.get(1);
|
String s1 = elements.get(1);
|
||||||
|
|
||||||
|
@ -459,20 +468,22 @@ public final class StringConcatFactory {
|
||||||
s0 == null &&
|
s0 == null &&
|
||||||
s1 == null) {
|
s1 == null) {
|
||||||
return simpleConcat();
|
return simpleConcat();
|
||||||
} else if (mt.parameterCount() == 1 &&
|
} else if (mt.parameterCount() == 1) {
|
||||||
!mt.parameterType(0).isPrimitive()) {
|
// One argument, one constant
|
||||||
|
String constant;
|
||||||
// One Object argument, one constant
|
int constIdx;
|
||||||
MethodHandle mh = simpleConcat();
|
if (s1 == null) {
|
||||||
|
constant = s0;
|
||||||
if (s0 != null && s1 == null) {
|
constIdx = 0;
|
||||||
// First recipe element is a constant
|
} else {
|
||||||
return MethodHandles.insertArguments(mh, 0, s0);
|
constant = s1;
|
||||||
|
constIdx = 1;
|
||||||
} else if (s1 != null && s0 == null) {
|
}
|
||||||
// Second recipe element is a constant
|
if (constant.isEmpty()) {
|
||||||
return MethodHandles.insertArguments(mh, 1, s1);
|
return unaryConcat(mt.parameterType(0));
|
||||||
|
} else if (!mt.parameterType(0).isPrimitive()) {
|
||||||
|
// Non-primitive argument
|
||||||
|
return MethodHandles.insertArguments(simpleConcat(), constIdx, constant);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// else... fall-through to slow-path
|
// else... fall-through to slow-path
|
||||||
|
@ -732,6 +743,76 @@ public final class StringConcatFactory {
|
||||||
return mh;
|
return mh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @Stable static MethodHandle INT_STRINGIFIER;
|
||||||
|
private static MethodHandle intStringifier() {
|
||||||
|
MethodHandle mh = INT_STRINGIFIER;
|
||||||
|
if (mh == null) {
|
||||||
|
INT_STRINGIFIER = mh =
|
||||||
|
lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, int.class);
|
||||||
|
}
|
||||||
|
return mh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Stable static MethodHandle LONG_STRINGIFIER;
|
||||||
|
private static MethodHandle longStringifier() {
|
||||||
|
MethodHandle mh = LONG_STRINGIFIER;
|
||||||
|
if (mh == null) {
|
||||||
|
LONG_STRINGIFIER = mh =
|
||||||
|
lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, long.class);
|
||||||
|
}
|
||||||
|
return mh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Stable static MethodHandle CHAR_STRINGIFIER;
|
||||||
|
private static MethodHandle charStringifier() {
|
||||||
|
MethodHandle mh = CHAR_STRINGIFIER;
|
||||||
|
if (mh == null) {
|
||||||
|
CHAR_STRINGIFIER = mh =
|
||||||
|
lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, char.class);
|
||||||
|
}
|
||||||
|
return mh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Stable static MethodHandle BOOLEAN_STRINGIFIER;
|
||||||
|
private static MethodHandle booleanStringifier() {
|
||||||
|
MethodHandle mh = BOOLEAN_STRINGIFIER;
|
||||||
|
if (mh == null) {
|
||||||
|
BOOLEAN_STRINGIFIER = mh =
|
||||||
|
lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, boolean.class);
|
||||||
|
}
|
||||||
|
return mh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Stable static MethodHandle NEW_STRINGIFIER;
|
||||||
|
private static MethodHandle newStringifier() {
|
||||||
|
MethodHandle mh = NEW_STRINGIFIER;
|
||||||
|
if (mh == null) {
|
||||||
|
NEW_STRINGIFIER = mh = JLA.stringConcatHelper("newStringOf",
|
||||||
|
methodType(String.class, Object.class));
|
||||||
|
}
|
||||||
|
return mh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodHandle unaryConcat(Class<?> cl) {
|
||||||
|
if (!cl.isPrimitive()) {
|
||||||
|
return newStringifier();
|
||||||
|
} else if (cl == int.class || cl == short.class || cl == byte.class) {
|
||||||
|
return intStringifier();
|
||||||
|
} else if (cl == long.class) {
|
||||||
|
return longStringifier();
|
||||||
|
} else if (cl == char.class) {
|
||||||
|
return charStringifier();
|
||||||
|
} else if (cl == boolean.class) {
|
||||||
|
return booleanStringifier();
|
||||||
|
} else if (cl == float.class) {
|
||||||
|
return floatStringifier();
|
||||||
|
} else if (cl == double.class) {
|
||||||
|
return doubleStringifier();
|
||||||
|
} else {
|
||||||
|
throw new InternalError("Unhandled type for unary concatenation: " + cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final ConcurrentMap<Class<?>, MethodHandle> PREPENDERS;
|
private static final ConcurrentMap<Class<?>, MethodHandle> PREPENDERS;
|
||||||
private static final ConcurrentMap<Class<?>, MethodHandle> NULL_PREPENDERS;
|
private static final ConcurrentMap<Class<?>, MethodHandle> NULL_PREPENDERS;
|
||||||
private static final ConcurrentMap<Class<?>, MethodHandle> MIXERS;
|
private static final ConcurrentMap<Class<?>, MethodHandle> MIXERS;
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @summary Test implicit String concatenations, multiple shapes.
|
* @summary Test implicit String concatenations, multiple shapes.
|
||||||
* @bug 8148483 8245959
|
* @bug 8148483 8245959 8247681
|
||||||
*
|
*
|
||||||
* @compile ImplicitStringConcatShapes.java
|
* @compile ImplicitStringConcatShapes.java
|
||||||
* @run main/othervm -Xverify:all ImplicitStringConcatShapes
|
* @run main/othervm -Xverify:all ImplicitStringConcatShapes
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @summary Test implicit String concatenations, multiple shapes.
|
* @summary Test implicit String concatenations, multiple shapes.
|
||||||
* @bug 8148483 8245959
|
* @bug 8148483 8245959 8247681
|
||||||
*
|
*
|
||||||
* @compile ImplicitStringConcatShapes.java
|
* @compile ImplicitStringConcatShapes.java
|
||||||
* @run main/othervm -Xverify:all ImplicitStringConcatShapes
|
* @run main/othervm -Xverify:all ImplicitStringConcatShapes
|
||||||
|
|
|
@ -28,7 +28,7 @@ import java.util.concurrent.Callable;
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @summary Test input invariants for StringConcatFactory
|
* @summary Test input invariants for StringConcatFactory
|
||||||
* @bug 8246152
|
* @bug 8246152 8247681
|
||||||
*
|
*
|
||||||
* @compile StringConcatFactoryInvariants.java
|
* @compile StringConcatFactoryInvariants.java
|
||||||
*
|
*
|
||||||
|
@ -46,6 +46,7 @@ public class StringConcatFactoryInvariants {
|
||||||
MethodType mt = MethodType.methodType(String.class, String.class, int.class);
|
MethodType mt = MethodType.methodType(String.class, String.class, int.class);
|
||||||
String recipe = "" + TAG_ARG + TAG_ARG + TAG_CONST;
|
String recipe = "" + TAG_ARG + TAG_ARG + TAG_CONST;
|
||||||
Object[][] constants = new Object[][] {
|
Object[][] constants = new Object[][] {
|
||||||
|
new String[] { "" },
|
||||||
new String[] { "bar" },
|
new String[] { "bar" },
|
||||||
new Integer[] { 1 },
|
new Integer[] { 1 },
|
||||||
new Short[] { 2 },
|
new Short[] { 2 },
|
||||||
|
@ -60,6 +61,7 @@ public class StringConcatFactoryInvariants {
|
||||||
// The string representation that should end up if the corresponding
|
// The string representation that should end up if the corresponding
|
||||||
// Object[] in constants is used as an argument to makeConcatWithConstants
|
// Object[] in constants is used as an argument to makeConcatWithConstants
|
||||||
String[] constantString = new String[] {
|
String[] constantString = new String[] {
|
||||||
|
"",
|
||||||
"bar",
|
"bar",
|
||||||
"1",
|
"1",
|
||||||
"2",
|
"2",
|
||||||
|
@ -116,6 +118,21 @@ public class StringConcatFactoryInvariants {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check unary expressions with pre- and postfix constants
|
||||||
|
{
|
||||||
|
String constArgRecipe = "" + TAG_CONST + TAG_ARG;
|
||||||
|
String argConstRecipe = "" + TAG_ARG + TAG_CONST;
|
||||||
|
MethodType unaryMt = MethodType.methodType(String.class, String.class);
|
||||||
|
|
||||||
|
for (int i = 0; i < constants.length; i++) {
|
||||||
|
CallSite prefixCS = StringConcatFactory.makeConcatWithConstants(lookup, methodName, unaryMt, constArgRecipe, constants[i]);
|
||||||
|
test(constantString[i].concat("foo"), (String) prefixCS.getTarget().invokeExact("foo"));
|
||||||
|
|
||||||
|
CallSite postfixCS = StringConcatFactory.makeConcatWithConstants(lookup, methodName, unaryMt, argConstRecipe, constants[i]);
|
||||||
|
test("foo".concat(constantString[i]), (String) postfixCS.getTarget().invokeExact("foo"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Simple factory, check for nulls:
|
// Simple factory, check for nulls:
|
||||||
failNPE("Lookup is null",
|
failNPE("Lookup is null",
|
||||||
() -> StringConcatFactory.makeConcat(null, methodName, mt));
|
() -> StringConcatFactory.makeConcat(null, methodName, mt));
|
||||||
|
|
|
@ -24,11 +24,14 @@ package org.openjdk.bench.java.lang;
|
||||||
|
|
||||||
import org.openjdk.jmh.annotations.Benchmark;
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||||
|
import org.openjdk.jmh.annotations.Fork;
|
||||||
|
import org.openjdk.jmh.annotations.Measurement;
|
||||||
import org.openjdk.jmh.annotations.Mode;
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
import org.openjdk.jmh.annotations.Param;
|
import org.openjdk.jmh.annotations.Param;
|
||||||
import org.openjdk.jmh.annotations.Scope;
|
import org.openjdk.jmh.annotations.Scope;
|
||||||
import org.openjdk.jmh.annotations.State;
|
import org.openjdk.jmh.annotations.State;
|
||||||
|
import org.openjdk.jmh.annotations.Warmup;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -38,6 +41,9 @@ import java.util.concurrent.TimeUnit;
|
||||||
@BenchmarkMode(Mode.AverageTime)
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||||
@State(Scope.Thread)
|
@State(Scope.Thread)
|
||||||
|
@Warmup(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
|
||||||
|
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
|
||||||
|
@Fork(3)
|
||||||
public class StringConcat {
|
public class StringConcat {
|
||||||
|
|
||||||
@Param("4711")
|
@Param("4711")
|
||||||
|
@ -73,6 +79,16 @@ public class StringConcat {
|
||||||
return emptyString + stringValue;
|
return emptyString + stringValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public String concatEmptyConstInt() {
|
||||||
|
return "" + intValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public String concatEmptyConstString() {
|
||||||
|
return "" + stringValue;
|
||||||
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
public String concatMethodConstString() {
|
public String concatMethodConstString() {
|
||||||
return "string".concat(stringValue);
|
return "string".concat(stringValue);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue