< prev index next >
src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java
Print this page
*** 23,33 ****
* questions.
*/
package java.lang.invoke;
! import jdk.internal.misc.Unsafe;
import jdk.internal.misc.VM;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
--- 23,34 ----
* questions.
*/
package java.lang.invoke;
! import jdk.internal.access.JavaLangAccess;
! import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.VM;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
*** 40,49 ****
--- 41,53 ----
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
+ import static java.lang.invoke.MethodHandles.lookup;
+ import static java.lang.invoke.MethodType.methodType;
+ import static java.lang.invoke.MethodHandleNatives.Constants.*;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
/**
* <p>Methods to facilitate the creation of String concatenation methods, that
* can be used to efficiently concatenate a known number of arguments of known
*** 131,140 ****
--- 135,146 ----
/**
* Default strategy to use for concatenation.
*/
private static final Strategy DEFAULT_STRATEGY = Strategy.MH_INLINE_SIZED_EXACT;
+ private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
+
private enum Strategy {
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder}.
*/
BC_SB,
*** 187,198 ****
/**
* Dump generated classes to disk, for debugging purposes.
*/
private static final ProxyClassesDumper DUMPER;
- private static final Class<?> STRING_HELPER;
-
static {
// In case we need to double-back onto the StringConcatFactory during this
// static initialization, make sure we have the reasonable defaults to complete
// the static initialization properly. After that, actual users would use
// the proper values we have read from the properties.
--- 193,202 ----
*** 200,215 ****
// CACHE_ENABLE = false; // implied
// CACHE = null; // implied
// DEBUG = false; // implied
// DUMPER = null; // implied
- try {
- STRING_HELPER = Class.forName("java.lang.StringConcatHelper");
- } catch (Throwable e) {
- throw new AssertionError(e);
- }
-
final String strategy =
VM.getSavedProperty("java.lang.invoke.stringConcat");
CACHE_ENABLE = Boolean.parseBoolean(
VM.getSavedProperty("java.lang.invoke.stringConcat.cache"));
DEBUG = Boolean.parseBoolean(
--- 204,213 ----
*** 721,732 ****
When cache is enabled, we want to cache as much as we can.
However, there are two peculiarities:
a) The generated class should stay within the same package as the
! host class, to allow Unsafe.defineAnonymousClass access controls
! to work properly. JDK may choose to fail with IllegalAccessException
when accessing a VM anonymous class with non-privileged callers,
see JDK-8058575.
b) If we mark the stub with some prefix, say, derived from the package
name because of (a), we can technically use that stub in other packages.
--- 719,729 ----
When cache is enabled, we want to cache as much as we can.
However, there are two peculiarities:
a) The generated class should stay within the same package as the
! host class. JDK may choose to fail with IllegalAccessException
when accessing a VM anonymous class with non-privileged callers,
see JDK-8058575.
b) If we mark the stub with some prefix, say, derived from the package
name because of (a), we can technically use that stub in other packages.
*** 745,755 ****
case BC_SB_SIZED_EXACT: {
if (CACHE_ENABLE) {
String pkgName = hostClass.getPackageName();
return (pkgName != null && !pkgName.isEmpty() ? pkgName.replace('.', '/') + "/" : "") + "Stubs$$StringConcat";
} else {
! return hostClass.getName().replace('.', '/') + "$$StringConcat";
}
}
case MH_SB_SIZED:
case MH_SB_SIZED_EXACT:
case MH_INLINE_SIZED_EXACT:
--- 742,754 ----
case BC_SB_SIZED_EXACT: {
if (CACHE_ENABLE) {
String pkgName = hostClass.getPackageName();
return (pkgName != null && !pkgName.isEmpty() ? pkgName.replace('.', '/') + "/" : "") + "Stubs$$StringConcat";
} else {
! String name = hostClass.isHiddenClass() ? hostClass.getName().replace('/', '_')
! : hostClass.getName();
! return name.replace('.', '/') + "$$StringConcat";
}
}
case MH_SB_SIZED:
case MH_SB_SIZED_EXACT:
case MH_INLINE_SIZED_EXACT:
*** 817,827 ****
*
* <p>This strategy spins up the bytecode that has the same StringBuilder
* chain javac would otherwise emit. This strategy uses only the public API,
* and comes as the baseline for the current JDK behavior. On other words,
* this strategy moves the javac generated bytecode to runtime. The
! * generated bytecode is loaded via Unsafe.defineAnonymousClass, but with
* the caller class coming from the BSM -- in other words, the protection
* guarantees are inherited from the method where invokedynamic was
* originally called. This means, among other things, that the bytecode is
* verified for all non-JDK uses.
*
--- 816,826 ----
*
* <p>This strategy spins up the bytecode that has the same StringBuilder
* chain javac would otherwise emit. This strategy uses only the public API,
* and comes as the baseline for the current JDK behavior. On other words,
* this strategy moves the javac generated bytecode to runtime. The
! * generated bytecode is loaded via Lookup::defineClass, but with
* the caller class coming from the BSM -- in other words, the protection
* guarantees are inherited from the method where invokedynamic was
* originally called. This means, among other things, that the bytecode is
* verified for all non-JDK uses.
*
*** 846,856 ****
* StringBuilder should have. The conversion is done via the public
* String.valueOf and/or Object.toString methods, and does not touch any
* private String API.
*/
private static final class BytecodeStringBuilderStrategy {
- static final Unsafe UNSAFE = Unsafe.getUnsafe();
static final int CLASSFILE_VERSION = 52;
static final String METHOD_NAME = "concat";
private BytecodeStringBuilderStrategy() {
// no instantiation
--- 845,854 ----
*** 859,869 ****
private static MethodHandle generate(Lookup lookup, String className, MethodType args, Recipe recipe, Mode mode) throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
cw.visit(CLASSFILE_VERSION,
ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC,
! className, // Unsafe.defineAnonymousClass would append an unique ID
null,
"java/lang/Object",
null
);
--- 857,867 ----
private static MethodHandle generate(Lookup lookup, String className, MethodType args, Recipe recipe, Mode mode) throws Exception {
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
);
*** 872,881 ****
--- 870,880 ----
METHOD_NAME,
args.toMethodDescriptorString(),
null,
null);
+ // use of @ForceInline no longer has any effect
mv.visitAnnotation("Ljdk/internal/vm/annotation/ForceInline;", true);
mv.visitCode();
Class<?>[] arr = args.parameterArray();
boolean[] guaranteedNonNull = new boolean[arr.length];
*** 1141,1155 ****
mv.visitEnd();
cw.visitEnd();
byte[] classBytes = cw.toByteArray();
try {
! Class<?> hostClass = lookup.lookupClass();
! Class<?> innerClass = UNSAFE.defineAnonymousClass(hostClass, classBytes, null);
! UNSAFE.ensureClassInitialized(innerClass);
! dumpIfEnabled(innerClass.getName(), classBytes);
! return Lookup.IMPL_LOOKUP.findStatic(innerClass, METHOD_NAME, args);
} catch (Exception e) {
dumpIfEnabled(className + "$$FAILED", classBytes);
throw new StringConcatException("Exception while spinning the class", e);
}
}
--- 1140,1152 ----
mv.visitEnd();
cw.visitEnd();
byte[] classBytes = cw.toByteArray();
try {
! Class<?> innerClass = lookup.defineHiddenClass(classBytes,true).lookupClass();
! dumpIfEnabled(className, classBytes);
! return lookup.findStatic(innerClass, METHOD_NAME, args);
} catch (Exception e) {
dumpIfEnabled(className + "$$FAILED", classBytes);
throw new StringConcatException("Exception while spinning the class", e);
}
}
*** 1268,1279 ****
*
* <p>This strategy avoids spinning up the bytecode by building the
* computation on MethodHandle combinators. The computation is built with
* public MethodHandle APIs, resolved from a public Lookup sequence, and
* ends up calling the public StringBuilder API. Therefore, this strategy
! * does not use any private API at all, even the Unsafe.defineAnonymousClass,
! * since everything is handled under cover by java.lang.invoke APIs.
*
* <p><b>{@link Strategy#MH_SB_SIZED_EXACT}: "MethodHandles StringBuilder,
* sized exactly".</b>
*
* <p>This strategy improves on @link Strategy#MH_SB_SIZED}, by first
--- 1265,1276 ----
*
* <p>This strategy avoids spinning up the bytecode by building the
* computation on MethodHandle combinators. The computation is built with
* public MethodHandle APIs, resolved from a public Lookup sequence, and
* ends up calling the public StringBuilder API. Therefore, this strategy
! * does not use any private API at all since everything is handled under
! * cover by java.lang.invoke APIs.
*
* <p><b>{@link Strategy#MH_SB_SIZED_EXACT}: "MethodHandles StringBuilder,
* sized exactly".</b>
*
* <p>This strategy improves on @link Strategy#MH_SB_SIZED}, by first
*** 1281,1291 ****
* StringBuilder should have. The conversion is done via the public
* String.valueOf and/or Object.toString methods, and does not touch any
* private String API.
*/
private static final class MethodHandleStringBuilderStrategy {
-
private MethodHandleStringBuilderStrategy() {
// no instantiation
}
private static MethodHandle generate(MethodType mt, Recipe recipe, Mode mode) throws Exception {
--- 1278,1287 ----
*** 1459,1468 ****
--- 1455,1466 ----
sum += v;
}
return sum;
}
+ private static final Lookup MHSBS_LOOKUP = lookup();
+
private static final ConcurrentMap<Integer, MethodHandle> SUMMERS;
// This one is deliberately non-lambdified to optimize startup time:
private static final Function<Integer, MethodHandle> SUMMER = new Function<Integer, MethodHandle>() {
@Override
*** 1472,1484 ****
} else if (cnt <= 8) {
// Variable-arity collectors are not as efficient as small-count methods,
// unroll some initial sizes.
Class<?>[] cls = new Class<?>[cnt];
Arrays.fill(cls, int.class);
! return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, cls);
} else {
! return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, int.class, int[].class)
.asCollector(int[].class, cnt - 1);
}
}
};
--- 1470,1482 ----
} else if (cnt <= 8) {
// Variable-arity collectors are not as efficient as small-count methods,
// unroll some initial sizes.
Class<?>[] cls = new Class<?>[cnt];
Arrays.fill(cls, int.class);
! return lookupStatic(MHSBS_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, cls);
} else {
! return lookupStatic(MHSBS_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, int.class, int[].class)
.asCollector(int[].class, cnt - 1);
}
}
};
*** 1489,1500 ****
Lookup publicLookup = MethodHandles.publicLookup();
NEW_STRING_BUILDER = lookupConstructor(publicLookup, StringBuilder.class, int.class);
STRING_LENGTH = lookupVirtual(publicLookup, String.class, "length", int.class);
BUILDER_TO_STRING = lookupVirtual(publicLookup, StringBuilder.class, "toString", String.class);
if (DEBUG) {
! BUILDER_TO_STRING_CHECKED = lookupStatic(MethodHandles.Lookup.IMPL_LOOKUP,
! MethodHandleStringBuilderStrategy.class, "toStringChecked", String.class, StringBuilder.class);
} else {
BUILDER_TO_STRING_CHECKED = null;
}
}
--- 1487,1498 ----
Lookup publicLookup = MethodHandles.publicLookup();
NEW_STRING_BUILDER = lookupConstructor(publicLookup, StringBuilder.class, int.class);
STRING_LENGTH = lookupVirtual(publicLookup, String.class, "length", int.class);
BUILDER_TO_STRING = lookupVirtual(publicLookup, StringBuilder.class, "toString", String.class);
if (DEBUG) {
! BUILDER_TO_STRING_CHECKED = lookupStatic(MHSBS_LOOKUP, MethodHandleStringBuilderStrategy.class,
! "toStringChecked", String.class, StringBuilder.class);
} else {
BUILDER_TO_STRING_CHECKED = null;
}
}
*** 1514,1525 ****
* particular implementation details for String, this opens the door for
* building a very optimal concatenation sequence. This is the only strategy
* that requires porting if there are private JDK changes occur.
*/
private static final class MethodHandleInlineCopyStrategy {
- static final Unsafe UNSAFE = Unsafe.getUnsafe();
-
private MethodHandleInlineCopyStrategy() {
// no instantiation
}
static MethodHandle generate(MethodType mt, Recipe recipe) throws Throwable {
--- 1512,1521 ----
*** 1734,1754 ****
// This one is deliberately non-lambdified to optimize startup time:
private static final Function<Class<?>, MethodHandle> PREPEND = new Function<>() {
@Override
public MethodHandle apply(Class<?> c) {
! return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "prepend", long.class, long.class, byte[].class,
! String.class, Wrapper.asPrimitiveType(c), String.class);
}
};
// This one is deliberately non-lambdified to optimize startup time:
private static final Function<Class<?>, MethodHandle> MIX = new Function<>() {
@Override
public MethodHandle apply(Class<?> c) {
! return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mix", long.class, long.class,
! Wrapper.asPrimitiveType(c));
}
};
private static final MethodHandle SIMPLE;
private static final MethodHandle NEW_STRING;
--- 1730,1750 ----
// This one is deliberately non-lambdified to optimize startup time:
private static final Function<Class<?>, MethodHandle> PREPEND = new Function<>() {
@Override
public MethodHandle apply(Class<?> c) {
! return JLA.stringConcatHelper("prepend",
! methodType(long.class, long.class, byte[].class,
! String.class, Wrapper.asPrimitiveType(c), String.class));
}
};
// This one is deliberately non-lambdified to optimize startup time:
private static final Function<Class<?>, MethodHandle> MIX = new Function<>() {
@Override
public MethodHandle apply(Class<?> c) {
! return JLA.stringConcatHelper("mix", methodType(long.class, long.class, Wrapper.asPrimitiveType(c)));
}
};
private static final MethodHandle SIMPLE;
private static final MethodHandle NEW_STRING;
*** 1757,1778 ****
private static final ConcurrentMap<Class<?>, MethodHandle> MIXERS;
private static final long INITIAL_CODER;
static {
try {
! MethodHandle initCoder = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "initialCoder", long.class);
INITIAL_CODER = (long) initCoder.invoke();
} catch (Throwable e) {
throw new AssertionError(e);
}
PREPENDERS = new ConcurrentHashMap<>();
MIXERS = new ConcurrentHashMap<>();
! SIMPLE = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "simpleConcat", String.class, Object.class, Object.class);
! NEW_STRING = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "newString", String.class, byte[].class, long.class);
! NEW_ARRAY = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "newArray", byte[].class, long.class);
}
}
/**
* Public gateways to public "stringify" methods. These methods have the form String apply(T obj), and normally
--- 1753,1774 ----
private static final ConcurrentMap<Class<?>, MethodHandle> MIXERS;
private static final long INITIAL_CODER;
static {
try {
! MethodHandle initCoder = JLA.stringConcatHelper("initialCoder", methodType(long.class));
INITIAL_CODER = (long) initCoder.invoke();
} catch (Throwable e) {
throw new AssertionError(e);
}
PREPENDERS = new ConcurrentHashMap<>();
MIXERS = new ConcurrentHashMap<>();
! SIMPLE = JLA.stringConcatHelper("simpleConcat", methodType(String.class, Object.class, Object.class));
! NEW_STRING = JLA.stringConcatHelper("newString", methodType(String.class, byte[].class, long.class));
! NEW_ARRAY = JLA.stringConcatHelper( "newArray", methodType(byte[].class, long.class));
}
}
/**
* Public gateways to public "stringify" methods. These methods have the form String apply(T obj), and normally
*** 1782,1792 ****
private Stringifiers() {
// no instantiation
}
private static final MethodHandle OBJECT_INSTANCE =
! lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "stringOf", String.class, Object.class);
private static class FloatStringifiers {
private static final MethodHandle FLOAT_INSTANCE =
lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, float.class);
--- 1778,1788 ----
private Stringifiers() {
// no instantiation
}
private static final MethodHandle OBJECT_INSTANCE =
! JLA.stringConcatHelper("stringOf", methodType(String.class, Object.class));
private static class FloatStringifiers {
private static final MethodHandle FLOAT_INSTANCE =
lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, float.class);
< prev index next >