mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
8327247: C2 uses up to 2GB of RAM to compile complex string concat in extreme cases
Reviewed-by: mchung, shade
This commit is contained in:
parent
2b7176a55a
commit
5e2ced4b9e
2 changed files with 297 additions and 8 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2024, 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
|
||||
|
@ -27,17 +27,20 @@ package java.lang.invoke;
|
|||
|
||||
import jdk.internal.access.JavaLangAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
import jdk.internal.misc.VM;
|
||||
import jdk.internal.org.objectweb.asm.ClassWriter;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.internal.util.ClassFileDumper;
|
||||
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 java.util.Set;
|
||||
|
||||
import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG;
|
||||
import static java.lang.invoke.MethodType.methodType;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
|
||||
/**
|
||||
* <p>Methods to facilitate the creation of String concatenation methods, that
|
||||
|
@ -98,6 +101,13 @@ import static java.lang.invoke.MethodType.methodType;
|
|||
*/
|
||||
public final class StringConcatFactory {
|
||||
|
||||
private static final int HIGH_ARITY_THRESHOLD;
|
||||
|
||||
static {
|
||||
String highArity = VM.getSavedProperty("java.lang.invoke.StringConcat.highArityThreshold");
|
||||
HIGH_ARITY_THRESHOLD = highArity != null ? Integer.parseInt(highArity) : 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag used to demarcate an ordinary argument.
|
||||
*/
|
||||
|
@ -354,9 +364,14 @@ public final class StringConcatFactory {
|
|||
}
|
||||
|
||||
try {
|
||||
return new ConstantCallSite(
|
||||
generateMHInlineCopy(concatType, constantStrings)
|
||||
.viewAsType(concatType, true));
|
||||
if (concatType.parameterCount() < HIGH_ARITY_THRESHOLD) {
|
||||
return new ConstantCallSite(
|
||||
generateMHInlineCopy(concatType, constantStrings)
|
||||
.viewAsType(concatType, true));
|
||||
} else {
|
||||
return new ConstantCallSite(
|
||||
SimpleStringBuilderStrategy.generate(lookup, concatType, constantStrings));
|
||||
}
|
||||
} catch (Error e) {
|
||||
// Pass through any error
|
||||
throw e;
|
||||
|
@ -1032,4 +1047,235 @@ public final class StringConcatFactory {
|
|||
// no instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Bytecode StringBuilder strategy.
|
||||
*
|
||||
* <p>This strategy emits StringBuilder chains as similar as possible
|
||||
* to what javac would. No exact sizing of parameters or estimates.
|
||||
*/
|
||||
private static final class SimpleStringBuilderStrategy {
|
||||
static final int CLASSFILE_VERSION = 52; // JDK 8
|
||||
static final String METHOD_NAME = "concat";
|
||||
// ClassFileDumper replaced java.lang.invoke.ProxyClassDumper in JDK 21
|
||||
// -- see JDK-8304846
|
||||
static final ClassFileDumper DUMPER =
|
||||
ClassFileDumper.getInstance("java.lang.invoke.StringConcatFactory.dump", "stringConcatClasses");
|
||||
|
||||
/**
|
||||
* Ensure a capacity in the initial StringBuilder to accommodate all
|
||||
* constants plus this factor times the number of arguments.
|
||||
*/
|
||||
static final int ARGUMENT_SIZE_FACTOR = 4;
|
||||
|
||||
static final Set<Lookup.ClassOption> SET_OF_STRONG = Set.of(STRONG);
|
||||
|
||||
private SimpleStringBuilderStrategy() {
|
||||
// no instantiation
|
||||
}
|
||||
|
||||
private static MethodHandle generate(Lookup lookup, MethodType args, String[] constants) throws Exception {
|
||||
String className = getClassName(lookup.lookupClass());
|
||||
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
|
||||
|
||||
cw.visit(CLASSFILE_VERSION,
|
||||
ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC,
|
||||
className,
|
||||
null,
|
||||
"java/lang/Object",
|
||||
null
|
||||
);
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(
|
||||
ACC_PUBLIC + ACC_STATIC + ACC_FINAL,
|
||||
METHOD_NAME,
|
||||
args.toMethodDescriptorString(),
|
||||
null,
|
||||
null);
|
||||
|
||||
mv.visitCode();
|
||||
|
||||
|
||||
// Prepare StringBuilder instance
|
||||
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
|
||||
mv.visitInsn(DUP);
|
||||
|
||||
int len = 0;
|
||||
for (String constant : constants) {
|
||||
if (constant != null) {
|
||||
len += constant.length();
|
||||
}
|
||||
}
|
||||
len += args.parameterCount() * ARGUMENT_SIZE_FACTOR;
|
||||
iconst(mv, len);
|
||||
mv.visitMethodInsn(
|
||||
INVOKESPECIAL,
|
||||
"java/lang/StringBuilder",
|
||||
"<init>",
|
||||
"(I)V",
|
||||
false
|
||||
);
|
||||
|
||||
// At this point, we have a blank StringBuilder on stack, fill it in with .append calls.
|
||||
{
|
||||
int off = 0;
|
||||
for (int c = 0; c < args.parameterCount(); c++) {
|
||||
if (constants[c] != null) {
|
||||
mv.visitLdcInsn(constants[c]);
|
||||
sbAppend(mv, "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
|
||||
}
|
||||
Class<?> cl = args.parameterType(c);
|
||||
mv.visitVarInsn(getLoadOpcode(cl), off);
|
||||
off += getParameterSize(cl);
|
||||
String desc = getSBAppendDesc(cl);
|
||||
sbAppend(mv, desc);
|
||||
}
|
||||
if (constants[constants.length - 1] != null) {
|
||||
mv.visitLdcInsn(constants[constants.length - 1]);
|
||||
sbAppend(mv, "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
|
||||
}
|
||||
}
|
||||
|
||||
mv.visitMethodInsn(
|
||||
INVOKEVIRTUAL,
|
||||
"java/lang/StringBuilder",
|
||||
"toString",
|
||||
"()Ljava/lang/String;",
|
||||
false
|
||||
);
|
||||
|
||||
mv.visitInsn(ARETURN);
|
||||
|
||||
mv.visitMaxs(-1, -1);
|
||||
mv.visitEnd();
|
||||
cw.visitEnd();
|
||||
|
||||
byte[] classBytes = cw.toByteArray();
|
||||
try {
|
||||
Lookup hiddenLookup = lookup.makeHiddenClassDefiner(className, classBytes, SET_OF_STRONG, DUMPER)
|
||||
.defineClassAsLookup(true);
|
||||
Class<?> innerClass = hiddenLookup.lookupClass();
|
||||
return hiddenLookup.findStatic(innerClass, METHOD_NAME, args);
|
||||
} catch (Exception e) {
|
||||
throw new StringConcatException("Exception while spinning the class", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void sbAppend(MethodVisitor mv, String desc) {
|
||||
mv.visitMethodInsn(
|
||||
INVOKEVIRTUAL,
|
||||
"java/lang/StringBuilder",
|
||||
"append",
|
||||
desc,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The generated class is in the same package as the host class as
|
||||
* it's the implementation of the string concatenation for the host
|
||||
* class.
|
||||
*/
|
||||
private static String getClassName(Class<?> hostClass) {
|
||||
String name = hostClass.isHidden() ? hostClass.getName().replace('/', '_')
|
||||
: hostClass.getName();
|
||||
return name.replace('.', '/') + "$$StringConcat";
|
||||
}
|
||||
|
||||
private static String getSBAppendDesc(Class<?> cl) {
|
||||
if (cl.isPrimitive()) {
|
||||
if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) {
|
||||
return "(I)Ljava/lang/StringBuilder;";
|
||||
} else if (cl == Boolean.TYPE) {
|
||||
return "(Z)Ljava/lang/StringBuilder;";
|
||||
} else if (cl == Character.TYPE) {
|
||||
return "(C)Ljava/lang/StringBuilder;";
|
||||
} else if (cl == Double.TYPE) {
|
||||
return "(D)Ljava/lang/StringBuilder;";
|
||||
} else if (cl == Float.TYPE) {
|
||||
return "(F)Ljava/lang/StringBuilder;";
|
||||
} else if (cl == Long.TYPE) {
|
||||
return "(J)Ljava/lang/StringBuilder;";
|
||||
} else {
|
||||
throw new IllegalStateException("Unhandled primitive StringBuilder.append: " + cl);
|
||||
}
|
||||
} else if (cl == String.class) {
|
||||
return "(Ljava/lang/String;)Ljava/lang/StringBuilder;";
|
||||
} else {
|
||||
return "(Ljava/lang/Object;)Ljava/lang/StringBuilder;";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getStringValueOfDesc(Class<?> cl) {
|
||||
if (cl.isPrimitive()) {
|
||||
if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) {
|
||||
return "(I)Ljava/lang/String;";
|
||||
} else if (cl == Boolean.TYPE) {
|
||||
return "(Z)Ljava/lang/String;";
|
||||
} else if (cl == Character.TYPE) {
|
||||
return "(C)Ljava/lang/String;";
|
||||
} else if (cl == Double.TYPE) {
|
||||
return "(D)Ljava/lang/String;";
|
||||
} else if (cl == Float.TYPE) {
|
||||
return "(F)Ljava/lang/String;";
|
||||
} else if (cl == Long.TYPE) {
|
||||
return "(J)Ljava/lang/String;";
|
||||
} else {
|
||||
throw new IllegalStateException("Unhandled String.valueOf: " + cl);
|
||||
}
|
||||
} else if (cl == String.class) {
|
||||
return "(Ljava/lang/String;)Ljava/lang/String;";
|
||||
} else {
|
||||
return "(Ljava/lang/Object;)Ljava/lang/String;";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The following method is copied from
|
||||
* org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very small
|
||||
* and fast Java bytecode manipulation framework.
|
||||
* Copyright (c) 2000-2005 INRIA, France Telecom All rights reserved.
|
||||
*/
|
||||
private static void iconst(MethodVisitor mv, final int cst) {
|
||||
if (cst >= -1 && cst <= 5) {
|
||||
mv.visitInsn(ICONST_0 + cst);
|
||||
} else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) {
|
||||
mv.visitIntInsn(BIPUSH, cst);
|
||||
} else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) {
|
||||
mv.visitIntInsn(SIPUSH, cst);
|
||||
} else {
|
||||
mv.visitLdcInsn(cst);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getLoadOpcode(Class<?> c) {
|
||||
if (c == Void.TYPE) {
|
||||
throw new InternalError("Unexpected void type of load opcode");
|
||||
}
|
||||
return ILOAD + getOpcodeOffset(c);
|
||||
}
|
||||
|
||||
private static int getOpcodeOffset(Class<?> c) {
|
||||
if (c.isPrimitive()) {
|
||||
if (c == Long.TYPE) {
|
||||
return 1;
|
||||
} else if (c == Float.TYPE) {
|
||||
return 2;
|
||||
} else if (c == Double.TYPE) {
|
||||
return 3;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getParameterSize(Class<?> c) {
|
||||
if (c == Void.TYPE) {
|
||||
return 0;
|
||||
} else if (c == Long.TYPE || c == Double.TYPE) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue