8338930: StringConcatFactory hardCoded string concatenation strategy

Reviewed-by: redestad, liach
This commit is contained in:
Shaojin Wen 2024-09-10 12:33:07 +00:00 committed by Claes Redestad
parent ad104932e6
commit 4d597de893
3 changed files with 130 additions and 49 deletions

View File

@ -2647,6 +2647,10 @@ public final class System {
return String.COMPACT_STRINGS ? String.LATIN1 : String.UTF16; return String.COMPACT_STRINGS ? String.LATIN1 : String.UTF16;
} }
public byte stringCoder(String str) {
return str.coder();
}
public int getCharsLatin1(long i, int index, byte[] buf) { public int getCharsLatin1(long i, int index, byte[] buf) {
return StringLatin1.getChars(i, index, buf); return StringLatin1.getChars(i, index, buf);
} }

View File

@ -119,12 +119,16 @@ import static java.lang.invoke.MethodType.methodType;
*/ */
public final class StringConcatFactory { public final class StringConcatFactory {
private static final int HIGH_ARITY_THRESHOLD; private static final int HIGH_ARITY_THRESHOLD;
private static final int CACHE_THRESHOLD;
private static final int FORCE_INLINE_THRESHOLD; private static final int FORCE_INLINE_THRESHOLD;
static { static {
String highArity = VM.getSavedProperty("java.lang.invoke.StringConcat.highArityThreshold"); String highArity = VM.getSavedProperty("java.lang.invoke.StringConcat.highArityThreshold");
HIGH_ARITY_THRESHOLD = highArity != null ? Integer.parseInt(highArity) : 0; HIGH_ARITY_THRESHOLD = highArity != null ? Integer.parseInt(highArity) : 0;
String cacheThreshold = VM.getSavedProperty("java.lang.invoke.StringConcat.cacheThreshold");
CACHE_THRESHOLD = cacheThreshold != null ? Integer.parseInt(cacheThreshold) : 256;
String inlineThreshold = VM.getSavedProperty("java.lang.invoke.StringConcat.inlineThreshold"); String inlineThreshold = VM.getSavedProperty("java.lang.invoke.StringConcat.inlineThreshold");
FORCE_INLINE_THRESHOLD = inlineThreshold != null ? Integer.parseInt(inlineThreshold) : 16; FORCE_INLINE_THRESHOLD = inlineThreshold != null ? Integer.parseInt(inlineThreshold) : 16;
} }
@ -1179,17 +1183,21 @@ public final class StringConcatFactory {
* int arg0, long arg1, boolean arg2, char arg3, String arg5) * int arg0, long arg1, boolean arg2, char arg3, String arg5)
* </pre></blockquote> * </pre></blockquote>
*/ */
private static MethodTypeDesc prependArgs(MethodType concatArgs) { private static MethodTypeDesc prependArgs(MethodType concatArgs, boolean staticConcat) {
int parameterCount = concatArgs.parameterCount(); int parameterCount = concatArgs.parameterCount();
var paramTypes = new ClassDesc[parameterCount + 4]; int prefixArgs = staticConcat ? 3 : 4;
var paramTypes = new ClassDesc[parameterCount + prefixArgs];
paramTypes[0] = CD_int; // length paramTypes[0] = CD_int; // length
paramTypes[1] = CD_byte; // coder paramTypes[1] = CD_byte; // coder
paramTypes[2] = CD_Array_byte; // buff paramTypes[2] = CD_Array_byte; // buff
paramTypes[3] = CD_Array_String; // constants
if (!staticConcat) {
paramTypes[3] = CD_Array_String; // constants
}
for (int i = 0; i < parameterCount; i++) { for (int i = 0; i < parameterCount; i++) {
var cl = concatArgs.parameterType(i); var cl = concatArgs.parameterType(i);
paramTypes[i + 4] = needStringOf(cl) ? CD_String : ConstantUtils.classDesc(cl); paramTypes[i + prefixArgs] = needStringOf(cl) ? CD_String : ConstantUtils.classDesc(cl);
} }
return MethodTypeDescImpl.ofValidated(CD_int, paramTypes); return MethodTypeDescImpl.ofValidated(CD_int, paramTypes);
} }
@ -1254,32 +1262,42 @@ public final class StringConcatFactory {
return handle.bindTo(concat1); return handle.bindTo(concat1);
} }
var weakConstructorHandle = CACHE.get(concatArgs); boolean forceInline = concatArgs.parameterCount() < FORCE_INLINE_THRESHOLD;
if (weakConstructorHandle != null) { boolean staticConcat = concatArgs.parameterCount() >= CACHE_THRESHOLD;
MethodHandlePair handlePair = weakConstructorHandle.get();
if (handlePair != null) { if (!staticConcat) {
try { var weakConstructorHandle = CACHE.get(concatArgs);
var instance = handlePair.constructor.invokeBasic((Object)constants); if (weakConstructorHandle != null) {
return handlePair.concatenator.bindTo(instance); MethodHandlePair handlePair = weakConstructorHandle.get();
} catch (Throwable e) { if (handlePair != null) {
throw new StringConcatException("Exception while utilizing the hidden class", e); try {
var instance = handlePair.constructor.invokeBasic((Object)constants);
return handlePair.concatenator.bindTo(instance);
} catch (Throwable e) {
throw new StringConcatException("Exception while utilizing the hidden class", e);
}
} }
} }
} }
MethodTypeDesc lengthArgs = lengthArgs(concatArgs), MethodTypeDesc lengthArgs = lengthArgs(concatArgs),
coderArgs = coderArgsIfMaybeUTF16(concatArgs), coderArgs = coderArgsIfMaybeUTF16(concatArgs),
prependArgs = prependArgs(concatArgs); prependArgs = prependArgs(concatArgs, staticConcat);
byte[] classBytes = ClassFile.of().build(CD_CONCAT, byte[] classBytes = ClassFile.of().build(CD_CONCAT,
new Consumer<ClassBuilder>() { new Consumer<ClassBuilder>() {
final boolean forceInline = concatArgs.parameterCount() < FORCE_INLINE_THRESHOLD;
@Override @Override
public void accept(ClassBuilder clb) { public void accept(ClassBuilder clb) {
clb.withSuperclass(CD_StringConcatBase) if (staticConcat) {
.withFlags(ACC_FINAL | ACC_SUPER | ACC_SYNTHETIC) clb.withSuperclass(CD_Object)
.withMethodBody(INIT_NAME, MTD_INIT, 0, CONSTRUCTOR_BUILDER) .withFlags(ACC_ABSTRACT | ACC_SUPER | ACC_SYNTHETIC);
.withMethod("length", } else {
clb.withSuperclass(CD_StringConcatBase)
.withFlags(ACC_FINAL | ACC_SUPER | ACC_SYNTHETIC)
.withMethodBody(INIT_NAME, MTD_INIT, 0, CONSTRUCTOR_BUILDER);
}
clb.withMethod("length",
lengthArgs, lengthArgs,
ACC_STATIC | ACC_PRIVATE, ACC_STATIC | ACC_PRIVATE,
new Consumer<MethodBuilder>() { new Consumer<MethodBuilder>() {
@ -1298,18 +1316,20 @@ public final class StringConcatFactory {
if (forceInline) { if (forceInline) {
mb.with(FORCE_INLINE); mb.with(FORCE_INLINE);
} }
mb.withCode(generatePrependMethod(prependArgs)); mb.withCode(generatePrependMethod(prependArgs, staticConcat, constants));
} }
}) })
.withMethod(METHOD_NAME, .withMethod(METHOD_NAME,
ConstantUtils.methodTypeDesc(concatArgs), ConstantUtils.methodTypeDesc(concatArgs),
ACC_FINAL, staticConcat ? ACC_STATIC | ACC_FINAL : ACC_FINAL,
new Consumer<MethodBuilder>() { new Consumer<MethodBuilder>() {
public void accept(MethodBuilder mb) { public void accept(MethodBuilder mb) {
if (forceInline) { if (forceInline) {
mb.with(FORCE_INLINE); mb.with(FORCE_INLINE);
} }
mb.withCode(generateConcatMethod( mb.withCode(generateConcatMethod(
staticConcat,
constants,
CD_CONCAT, CD_CONCAT,
concatArgs, concatArgs,
lengthArgs, lengthArgs,
@ -1335,6 +1355,11 @@ public final class StringConcatFactory {
try { try {
var hiddenClass = lookup.makeHiddenClassDefiner(CLASS_NAME, classBytes, Set.of(), DUMPER) var hiddenClass = lookup.makeHiddenClassDefiner(CLASS_NAME, classBytes, Set.of(), DUMPER)
.defineClass(true, null); .defineClass(true, null);
if (staticConcat) {
return lookup.findStatic(hiddenClass, METHOD_NAME, concatArgs);
}
var constructor = lookup.findConstructor(hiddenClass, CONSTRUCTOR_METHOD_TYPE); var constructor = lookup.findConstructor(hiddenClass, CONSTRUCTOR_METHOD_TYPE);
var concatenator = lookup.findVirtual(hiddenClass, METHOD_NAME, concatArgs); var concatenator = lookup.findVirtual(hiddenClass, METHOD_NAME, concatArgs);
CACHE.put(concatArgs, new SoftReference<>(new MethodHandlePair(constructor, concatenator))); CACHE.put(concatArgs, new SoftReference<>(new MethodHandlePair(constructor, concatenator)));
@ -1410,6 +1435,8 @@ public final class StringConcatFactory {
* </pre></blockquote> * </pre></blockquote>
*/ */
private static Consumer<CodeBuilder> generateConcatMethod( private static Consumer<CodeBuilder> generateConcatMethod(
boolean staticConcat,
String[] constants,
ClassDesc concatClass, ClassDesc concatClass,
MethodType concatArgs, MethodType concatArgs,
MethodTypeDesc lengthArgs, MethodTypeDesc lengthArgs,
@ -1421,7 +1448,7 @@ public final class StringConcatFactory {
public void accept(CodeBuilder cb) { public void accept(CodeBuilder cb) {
// Compute parameter variable slots // Compute parameter variable slots
int paramCount = concatArgs.parameterCount(), int paramCount = concatArgs.parameterCount(),
thisSlot = cb.receiverSlot(), thisSlot = staticConcat ? 0 : cb.receiverSlot(),
lengthSlot = cb.allocateLocal(TypeKind.INT), lengthSlot = cb.allocateLocal(TypeKind.INT),
coderSlot = cb.allocateLocal(TypeKind.BYTE), coderSlot = cb.allocateLocal(TypeKind.BYTE),
bufSlot = cb.allocateLocal(TypeKind.REFERENCE), bufSlot = cb.allocateLocal(TypeKind.REFERENCE),
@ -1457,11 +1484,30 @@ public final class StringConcatFactory {
} }
} }
int coder = JLA.stringInitCoder(),
length = 0;
if (staticConcat) {
for (var constant : constants) {
coder |= JLA.stringCoder(constant);
length += constant.length();
}
}
/* /*
* coder = coder(this.coder, arg0, arg1, ... argN); * coder = coder(this.coder, arg0, arg1, ... argN);
*/ */
cb.aload(thisSlot) if (staticConcat) {
.getfield(concatClass, "coder", CD_byte); // coder can only be 0 or 1
if (coder == 0) {
cb.iconst_0();
} else {
cb.iconst_1();
}
} else {
cb.aload(thisSlot)
.getfield(concatClass, "coder", CD_byte);
}
if (coderArgs != null) { if (coderArgs != null) {
for (int i = 0; i < paramCount; i++) { for (int i = 0; i < paramCount; i++) {
var cl = concatArgs.parameterType(i); var cl = concatArgs.parameterType(i);
@ -1480,8 +1526,13 @@ public final class StringConcatFactory {
/* /*
* length = length(this.length, arg0, arg1, ..., argN); * length = length(this.length, arg0, arg1, ..., argN);
*/ */
cb.aload(thisSlot) if (staticConcat) {
.getfield(concatClass, "length", CD_int); cb.ldc(length);
} else {
cb.aload(thisSlot)
.getfield(concatClass, "length", CD_int);
}
for (int i = 0; i < paramCount; i++) { for (int i = 0; i < paramCount; i++) {
var cl = concatArgs.parameterType(i); var cl = concatArgs.parameterType(i);
int paramSlot = cb.parameterSlot(i); int paramSlot = cb.parameterSlot(i);
@ -1498,25 +1549,35 @@ public final class StringConcatFactory {
* suffix = constants[paramCount]; * suffix = constants[paramCount];
* length -= suffix.length(); * length -= suffix.length();
*/ */
cb.aload(thisSlot) if (staticConcat) {
.getfield(concatClass, "constants", CD_Array_String) cb.ldc(constants[paramCount].length())
.dup() .isub()
.astore(constantsSlot) .istore(lengthSlot);
.ldc(paramCount) } else {
.aaload() cb.aload(thisSlot)
.dup() .getfield(concatClass, "constants", CD_Array_String)
.astore(suffixSlot) .dup()
.invokevirtual(CD_String, "length", MTD_int) .astore(constantsSlot)
.isub() .ldc(paramCount)
.istore(lengthSlot); .aaload()
.dup()
.astore(suffixSlot)
.invokevirtual(CD_String, "length", MTD_int)
.isub()
.istore(lengthSlot);
}
/* /*
* Allocate buffer : * Allocate buffer :
* *
* buf = newArrayWithSuffix(suffix, length, coder) * buf = newArrayWithSuffix(suffix, length, coder)
*/ */
cb.aload(suffixSlot) if (staticConcat) {
.iload(lengthSlot) cb.ldc(constants[paramCount]);
} else {
cb.aload(suffixSlot);
}
cb.iload(lengthSlot)
.iload(coderSlot) .iload(coderSlot)
.invokestatic(CD_StringConcatHelper, "newArrayWithSuffix", MTD_NEW_ARRAY_SUFFIX) .invokestatic(CD_StringConcatHelper, "newArrayWithSuffix", MTD_NEW_ARRAY_SUFFIX)
.astore(bufSlot); .astore(bufSlot);
@ -1526,8 +1587,10 @@ public final class StringConcatFactory {
*/ */
cb.iload(lengthSlot) cb.iload(lengthSlot)
.iload(coderSlot) .iload(coderSlot)
.aload(bufSlot) .aload(bufSlot);
.aload(constantsSlot); if (!staticConcat) {
cb.aload(constantsSlot);
}
for (int i = 0; i < paramCount; i++) { for (int i = 0; i < paramCount; i++) {
var cl = concatArgs.parameterType(i); var cl = concatArgs.parameterType(i);
int paramSlot = cb.parameterSlot(i); int paramSlot = cb.parameterSlot(i);
@ -1651,7 +1714,10 @@ public final class StringConcatFactory {
* } * }
* </pre></blockquote> * </pre></blockquote>
*/ */
private static Consumer<CodeBuilder> generatePrependMethod(MethodTypeDesc prependArgs) { private static Consumer<CodeBuilder> generatePrependMethod(
MethodTypeDesc prependArgs,
boolean staticConcat, String[] constants
) {
return new Consumer<CodeBuilder>() { return new Consumer<CodeBuilder>() {
@Override @Override
public void accept(CodeBuilder cb) { public void accept(CodeBuilder cb) {
@ -1670,7 +1736,7 @@ public final class StringConcatFactory {
* buf, arg1, constant[1]), buf, arg0, constant[0]); * buf, arg1, constant[1]), buf, arg0, constant[0]);
*/ */
cb.iload(lengthSlot); cb.iload(lengthSlot);
for (int i = prependArgs.parameterCount() - 1; i >= 4; i--) { for (int i = prependArgs.parameterCount() - 1, end = staticConcat ? 3 : 4; i >= end; i--) {
var cl = prependArgs.parameterType(i); var cl = prependArgs.parameterType(i);
var kind = TypeKind.from(cl); var kind = TypeKind.from(cl);
@ -1691,11 +1757,17 @@ public final class StringConcatFactory {
cb.iload(coderSlot) cb.iload(coderSlot)
.aload(bufSlot) .aload(bufSlot)
.loadLocal(kind, cb.parameterSlot(i)) .loadLocal(kind, cb.parameterSlot(i));
.aload(constantsSlot)
.ldc(i - 4) if (staticConcat) {
.aaload() cb.ldc(constants[i - 3]);
.invokestatic(CD_StringConcatHelper, "prepend", methodTypeDesc); } else {
cb.aload(constantsSlot)
.ldc(i - 4)
.aaload();
}
cb.invokestatic(CD_StringConcatHelper, "prepend", methodTypeDesc);
} }
cb.ireturn(); cb.ireturn();
} }

View File

@ -465,6 +465,11 @@ public interface JavaLangAccess {
*/ */
byte stringInitCoder(); byte stringInitCoder();
/**
* Get the Coder of String, which is used by StringConcatFactory to calculate the initCoder of constants
*/
byte stringCoder(String str);
/** /**
* Join strings * Join strings
*/ */