8338532: Speed up the ClassFile API MethodTypeDesc#ofDescriptor

Reviewed-by: redestad, liach
This commit is contained in:
Shaojin Wen 2024-08-21 14:56:30 +00:00 committed by Chen Liang
parent 918cf11454
commit 3aeb6733f9
11 changed files with 201 additions and 110 deletions

View File

@ -36,6 +36,7 @@ import static java.util.stream.Collectors.joining;
import static jdk.internal.constant.ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS;
import static jdk.internal.constant.ConstantUtils.arrayDepth;
import static jdk.internal.constant.ConstantUtils.binaryToInternal;
import static jdk.internal.constant.ConstantUtils.forPrimitiveType;
import static jdk.internal.constant.ConstantUtils.internalToBinary;
import static jdk.internal.constant.ConstantUtils.validateBinaryClassName;
import static jdk.internal.constant.ConstantUtils.validateInternalClassName;
@ -164,7 +165,7 @@ public sealed interface ClassDesc
static ClassDesc ofDescriptor(String descriptor) {
// implicit null-check
return (descriptor.length() == 1)
? Wrapper.forPrimitiveType(descriptor.charAt(0)).basicClassDescriptor()
? forPrimitiveType(descriptor, 0)
// will throw IAE on descriptor.length == 0 or if array dimensions too long
: ReferenceClassDescImpl.of(descriptor);
}

View File

@ -240,31 +240,31 @@ public final class ConstantDescs {
CD_Object, CD_Object);
/** {@link ClassDesc} representing the primitive type {@code int} */
public static final ClassDesc CD_int = new PrimitiveClassDescImpl("I");
public static final ClassDesc CD_int = PrimitiveClassDescImpl.CD_int;
/** {@link ClassDesc} representing the primitive type {@code long} */
public static final ClassDesc CD_long = new PrimitiveClassDescImpl("J");
public static final ClassDesc CD_long = PrimitiveClassDescImpl.CD_long;
/** {@link ClassDesc} representing the primitive type {@code float} */
public static final ClassDesc CD_float = new PrimitiveClassDescImpl("F");
public static final ClassDesc CD_float = PrimitiveClassDescImpl.CD_float;
/** {@link ClassDesc} representing the primitive type {@code double} */
public static final ClassDesc CD_double = new PrimitiveClassDescImpl("D");
public static final ClassDesc CD_double = PrimitiveClassDescImpl.CD_double;
/** {@link ClassDesc} representing the primitive type {@code short} */
public static final ClassDesc CD_short = new PrimitiveClassDescImpl("S");
public static final ClassDesc CD_short = PrimitiveClassDescImpl.CD_short;
/** {@link ClassDesc} representing the primitive type {@code byte} */
public static final ClassDesc CD_byte = new PrimitiveClassDescImpl("B");
public static final ClassDesc CD_byte = PrimitiveClassDescImpl.CD_byte;
/** {@link ClassDesc} representing the primitive type {@code char} */
public static final ClassDesc CD_char = new PrimitiveClassDescImpl("C");
public static final ClassDesc CD_char = PrimitiveClassDescImpl.CD_char;
/** {@link ClassDesc} representing the primitive type {@code boolean} */
public static final ClassDesc CD_boolean = new PrimitiveClassDescImpl("Z");
public static final ClassDesc CD_boolean = PrimitiveClassDescImpl.CD_boolean;
/** {@link ClassDesc} representing the primitive type {@code void} */
public static final ClassDesc CD_void = new PrimitiveClassDescImpl("V");
public static final ClassDesc CD_void = PrimitiveClassDescImpl.CD_void;
/**
* {@link MethodHandleDesc} representing {@link MethodHandles#classData(Lookup, String, Class) MethodHandles.classData}

View File

@ -25,6 +25,7 @@
package java.lang.invoke;
import sun.invoke.util.Wrapper;
import jdk.internal.constant.ConstantUtils;
import static java.lang.invoke.MethodHandleNatives.mapLookupExceptionToError;
import static java.util.Objects.requireNonNull;
@ -112,7 +113,7 @@ public final class ConstantBootstraps {
throw new IllegalArgumentException(String.format("not primitive: %s", name));
}
return Wrapper.forPrimitiveType(name.charAt(0)).primitiveType();
return ConstantUtils.forPrimitiveType(name, 0).resolveConstantDesc(lookup);
}
/**

View File

@ -35,6 +35,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static jdk.internal.constant.PrimitiveClassDescImpl.*;
/**
* Helper methods for the implementation of {@code java.lang.constant}.
*/
@ -269,59 +271,40 @@ public final class ConstantUtils {
return s.substring(1, s.length() - 1);
}
/**
* Parses a method descriptor string, and return a list of field descriptor
* strings, return type first, then parameter types
*
* @param descriptor the descriptor string
* @return the list of types
* @throws IllegalArgumentException if the descriptor string is not valid
*/
public static List<ClassDesc> parseMethodDescriptor(String descriptor) {
int cur = 0, end = descriptor.length();
ArrayList<ClassDesc> ptypes = new ArrayList<>();
ptypes.add(null); // placeholder for return type
if (cur >= end || descriptor.charAt(cur) != '(')
throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
++cur; // skip '('
while (cur < end && descriptor.charAt(cur) != ')') {
int len = skipOverFieldSignature(descriptor, cur, end, false);
if (len == 0)
throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
ptypes.add(resolveClassDesc(descriptor, cur, len));
cur += len;
}
if (cur >= end)
throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
++cur; // skip ')'
int rLen = skipOverFieldSignature(descriptor, cur, end, true);
if (rLen == 0 || cur + rLen != end)
throw new IllegalArgumentException("Bad method descriptor: " + descriptor);
ptypes.set(0, resolveClassDesc(descriptor, cur, rLen));
return ptypes;
public static PrimitiveClassDescImpl forPrimitiveType(String descriptor, int offset) {
return switch (descriptor.charAt(offset)) {
case JVM_SIGNATURE_BYTE -> CD_byte;
case JVM_SIGNATURE_CHAR -> CD_char;
case JVM_SIGNATURE_FLOAT -> CD_float;
case JVM_SIGNATURE_DOUBLE -> CD_double;
case JVM_SIGNATURE_INT -> CD_int;
case JVM_SIGNATURE_LONG -> CD_long;
case JVM_SIGNATURE_SHORT -> CD_short;
case JVM_SIGNATURE_VOID -> CD_void;
case JVM_SIGNATURE_BOOLEAN -> CD_boolean;
default -> throw badMethodDescriptor(descriptor);
};
}
private static ClassDesc resolveClassDesc(String descriptor, int start, int len) {
static ClassDesc resolveClassDesc(String descriptor, int start, int len) {
if (len == 1) {
return Wrapper.forPrimitiveType(descriptor.charAt(start)).basicClassDescriptor();
return forPrimitiveType(descriptor, start);
}
// Pre-verified in parseMethodDescriptor; avoid redundant verification
// Pre-verified in MethodTypeDescImpl#ofDescriptor; avoid redundant verification
return ReferenceClassDescImpl.ofValidated(descriptor.substring(start, start + len));
}
static IllegalArgumentException badMethodDescriptor(String descriptor) {
return new IllegalArgumentException("Bad method descriptor: " + descriptor);
}
private static final char JVM_SIGNATURE_ARRAY = '[';
private static final char JVM_SIGNATURE_BYTE = 'B';
private static final char JVM_SIGNATURE_CHAR = 'C';
private static final char JVM_SIGNATURE_CLASS = 'L';
private static final char JVM_SIGNATURE_ENDCLASS = ';';
private static final char JVM_SIGNATURE_ENUM = 'E';
private static final char JVM_SIGNATURE_FLOAT = 'F';
private static final char JVM_SIGNATURE_DOUBLE = 'D';
private static final char JVM_SIGNATURE_FUNC = '(';
private static final char JVM_SIGNATURE_ENDFUNC = ')';
private static final char JVM_SIGNATURE_INT = 'I';
private static final char JVM_SIGNATURE_LONG = 'J';
private static final char JVM_SIGNATURE_SHORT = 'S';
@ -334,17 +317,22 @@ public final class ConstantUtils {
* @param descriptor the descriptor string
* @param start the starting index into the string
* @param end the ending index within the string
* @param voidOK is void acceptable?
* @return the length of the descriptor, or 0 if it is not a descriptor
* @throws IllegalArgumentException if the descriptor string is not valid
*/
@SuppressWarnings("fallthrough")
static int skipOverFieldSignature(String descriptor, int start, int end, boolean voidOK) {
static int skipOverFieldSignature(String descriptor, int start, int end) {
int arrayDim = 0;
int index = start;
while (index < end) {
switch (descriptor.charAt(index)) {
case JVM_SIGNATURE_VOID: if (!voidOK) { return 0; }
if (index < end) {
char ch;
while ((ch = descriptor.charAt(index++)) == JVM_SIGNATURE_ARRAY) {
arrayDim++;
}
if (arrayDim > MAX_ARRAY_TYPE_DESC_DIMENSIONS) {
throw maxArrayTypeDescDimensions();
}
switch (ch) {
case JVM_SIGNATURE_BOOLEAN:
case JVM_SIGNATURE_BYTE:
case JVM_SIGNATURE_CHAR:
@ -353,16 +341,16 @@ public final class ConstantUtils {
case JVM_SIGNATURE_FLOAT:
case JVM_SIGNATURE_LONG:
case JVM_SIGNATURE_DOUBLE:
return index - start + 1;
return index - start;
case JVM_SIGNATURE_CLASS:
// state variable for detection of illegal states, such as:
// empty unqualified name, '//', leading '/', or trailing '/'
boolean legal = false;
while (++index < end) {
switch (descriptor.charAt(index)) {
while (index < end) {
switch (descriptor.charAt(index++)) {
case ';' -> {
// illegal state on parser exit indicates empty unqualified name or trailing '/'
return legal ? index - start + 1 : 0;
return legal ? index - start : 0;
}
case '.', '[' -> {
// do not permit '.' or '['
@ -377,21 +365,17 @@ public final class ConstantUtils {
legal = true;
}
}
return 0;
case JVM_SIGNATURE_ARRAY:
arrayDim++;
if (arrayDim > MAX_ARRAY_TYPE_DESC_DIMENSIONS) {
throw new IllegalArgumentException(String.format("Cannot create an array type descriptor with more than %d dimensions",
ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS));
}
// The rest of what's there better be a legal descriptor
index++;
voidOK = false;
break;
default:
return 0;
break;
}
}
return 0;
}
private static IllegalArgumentException maxArrayTypeDescDimensions() {
return new IllegalArgumentException(String.format(
"Cannot create an array type descriptor with more than %d dimensions",
ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS));
}
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, Alibaba Group Holding Limited. 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 +28,25 @@ package jdk.internal.constant;
import jdk.internal.vm.annotation.Stable;
import java.lang.constant.ClassDesc;
import java.lang.constant.ConstantDescs;
import java.lang.constant.MethodTypeDesc;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import static java.util.Objects.requireNonNull;
import static jdk.internal.constant.ConstantUtils.badMethodDescriptor;
import static jdk.internal.constant.ConstantUtils.resolveClassDesc;
import static jdk.internal.constant.ConstantUtils.skipOverFieldSignature;
import static jdk.internal.constant.ConstantUtils.EMPTY_CLASSDESC;
import static jdk.internal.constant.PrimitiveClassDescImpl.CD_void;
/**
* A <a href="package-summary.html#nominal">nominal descriptor</a> for a
* {@link MethodType}. A {@linkplain MethodTypeDescImpl} corresponds to a
@ -91,7 +100,7 @@ public final class MethodTypeDescImpl implements MethodTypeDesc {
*/
public static MethodTypeDescImpl ofValidated(ClassDesc returnType, ClassDesc... trustedArgTypes) {
if (trustedArgTypes.length == 0)
return new MethodTypeDescImpl(returnType, ConstantUtils.EMPTY_CLASSDESC);
return new MethodTypeDescImpl(returnType, EMPTY_CLASSDESC);
return new MethodTypeDescImpl(returnType, trustedArgTypes);
}
@ -105,18 +114,98 @@ public final class MethodTypeDescImpl implements MethodTypeDesc {
* @jvms 4.3.3 Method Descriptors
*/
public static MethodTypeDescImpl ofDescriptor(String descriptor) {
// Implicit null-check of descriptor
List<ClassDesc> ptypes = ConstantUtils.parseMethodDescriptor(descriptor);
int args = ptypes.size() - 1;
ClassDesc[] paramTypes = args > 0
? ptypes.subList(1, args + 1).toArray(ConstantUtils.EMPTY_CLASSDESC)
: ConstantUtils.EMPTY_CLASSDESC;
int length = descriptor.length();
int rightBracket, retTypeLength;
if (descriptor.charAt(0) != '('
|| (rightBracket = (descriptor.charAt(1) == ')' ? 1 : descriptor.lastIndexOf(')'))) <= 0
|| (retTypeLength = length - rightBracket - 1) == 0
|| (retTypeLength != 1 // if retTypeLength == 1, check correctness in resolveClassDesc
&& retTypeLength != skipOverFieldSignature(descriptor, rightBracket + 1, length))
) {
throw badMethodDescriptor(descriptor);
}
MethodTypeDescImpl result = ofValidated(ptypes.get(0), paramTypes);
var returnType = resolveClassDesc(descriptor, rightBracket + 1, retTypeLength);
if (length == 3 && returnType == CD_void) {
return (MethodTypeDescImpl) ConstantDescs.MTD_void;
}
var paramTypes = paramTypes(descriptor, 1, rightBracket);
var result = new MethodTypeDescImpl(returnType, paramTypes);
result.cachedDescriptorString = descriptor;
return result;
}
private static ClassDesc[] paramTypes(String descriptor, int start, int end) {
if (start == end) {
return EMPTY_CLASSDESC;
}
/*
* If the length of the first 8 parameters is < 256, save them in lengths to avoid ArrayList allocation
* Stop storing for the last parameter (we can compute length), or if too many parameters or too long.
*/
// little endian storage - lowest byte is encoded length 0
long packedLengths = 0;
int packedCount = 0;
int cur = start;
while (cur < end) {
int len = skipOverFieldSignature(descriptor, cur, end);
if (len == 0) {
throw badMethodDescriptor(descriptor);
}
cur += len;
if (len > 0xFF || packedCount >= Long.SIZE / Byte.SIZE || cur == end) {
// Cannot or do not have to pack this item, but is already scanned and valid
break;
}
packedLengths = packedLengths | (((long) len) << (Byte.SIZE * packedCount++));
}
// Invariant: packedCount parameters encoded in packedLengths,
// And another valid parameter pointed by cur
// Recover encoded elements
ClassDesc[] paramTypes = null;
List<ClassDesc> paramTypeList = null;
if (cur == end) {
paramTypes = new ClassDesc[packedCount + 1];
} else {
paramTypeList = new ArrayList<>(32);
}
int last = start;
for (int i = 0; i < packedCount; i++) {
int len = Byte.toUnsignedInt((byte) (packedLengths >> (Byte.SIZE * i)));
var cd = resolveClassDesc(descriptor, last, len);
if (paramTypes != null) {
paramTypes[i] = cd;
} else {
paramTypeList.add(cd);
}
last += len;
}
var lastCd = resolveClassDesc(descriptor, last, cur - last);
if (paramTypes != null) {
paramTypes[packedCount] = lastCd;
return paramTypes;
}
paramTypeList.add(lastCd);
return buildParamTypes(descriptor, cur, end, paramTypeList);
}
// slow path
private static ClassDesc[] buildParamTypes(String descriptor, int cur, int end, List<ClassDesc> list) {
while (cur < end) {
int len = skipOverFieldSignature(descriptor, cur, end);
if (len == 0)
throw badMethodDescriptor(descriptor);
list.add(resolveClassDesc(descriptor, cur, len));
cur += len;
}
return list.toArray(EMPTY_CLASSDESC);
}
@Override
public ClassDesc returnType() {

View File

@ -29,6 +29,7 @@ import java.lang.constant.ConstantDescs;
import java.lang.constant.DynamicConstantDesc;
import java.lang.invoke.MethodHandles;
import jdk.internal.vm.annotation.Stable;
import sun.invoke.util.Wrapper;
import static java.util.Objects.requireNonNull;
@ -40,7 +41,35 @@ import static java.util.Objects.requireNonNull;
public final class PrimitiveClassDescImpl
extends DynamicConstantDesc<Class<?>> implements ClassDesc {
/** {@link ClassDesc} representing the primitive type {@code int} */
public static final PrimitiveClassDescImpl CD_int = new PrimitiveClassDescImpl("I");
/** {@link ClassDesc} representing the primitive type {@code long} */
public static final PrimitiveClassDescImpl CD_long = new PrimitiveClassDescImpl("J");
/** {@link ClassDesc} representing the primitive type {@code float} */
public static final PrimitiveClassDescImpl CD_float = new PrimitiveClassDescImpl("F");
/** {@link ClassDesc} representing the primitive type {@code double} */
public static final PrimitiveClassDescImpl CD_double = new PrimitiveClassDescImpl("D");
/** {@link ClassDesc} representing the primitive type {@code short} */
public static final PrimitiveClassDescImpl CD_short = new PrimitiveClassDescImpl("S");
/** {@link ClassDesc} representing the primitive type {@code byte} */
public static final PrimitiveClassDescImpl CD_byte = new PrimitiveClassDescImpl("B");
/** {@link ClassDesc} representing the primitive type {@code char} */
public static final PrimitiveClassDescImpl CD_char = new PrimitiveClassDescImpl("C");
/** {@link ClassDesc} representing the primitive type {@code boolean} */
public static final PrimitiveClassDescImpl CD_boolean = new PrimitiveClassDescImpl("Z");
/** {@link ClassDesc} representing the primitive type {@code void} */
public static final PrimitiveClassDescImpl CD_void = new PrimitiveClassDescImpl("V");
private final String descriptor;
private @Stable Wrapper lazyWrapper; // initialized only after this
/**
* Creates a {@linkplain ClassDesc} given a descriptor string for a primitive
@ -52,14 +81,18 @@ public final class PrimitiveClassDescImpl
* describe a valid primitive type
* @jvms 4.3 Descriptors
*/
public PrimitiveClassDescImpl(String descriptor) {
private PrimitiveClassDescImpl(String descriptor) {
super(ConstantDescs.BSM_PRIMITIVE_CLASS, requireNonNull(descriptor), ConstantDescs.CD_Class);
if (descriptor.length() != 1
|| "VIJCSBFDZ".indexOf(descriptor.charAt(0)) < 0)
throw new IllegalArgumentException(String.format("not a valid primitive type descriptor: %s", descriptor));
this.descriptor = descriptor;
}
public Wrapper wrapper() {
var wrapper = this.lazyWrapper;
if (wrapper != null)
return wrapper;
return this.lazyWrapper = Wrapper.forBasicType(descriptorString().charAt(0));
}
@Override
public String descriptorString() {
return descriptor;
@ -67,7 +100,7 @@ public final class PrimitiveClassDescImpl
@Override
public Class<?> resolveConstantDesc(MethodHandles.Lookup lookup) {
return Wrapper.forBasicType(descriptorString().charAt(0)).primitiveType();
return wrapper().primitiveType();
}
@Override

View File

@ -52,7 +52,7 @@ public final class ReferenceClassDescImpl implements ClassDesc {
*/
public static ReferenceClassDescImpl of(String descriptor) {
int dLen = descriptor.length();
int len = ConstantUtils.skipOverFieldSignature(descriptor, 0, dLen, false);
int len = ConstantUtils.skipOverFieldSignature(descriptor, 0, dLen);
if (len <= 1 || len != dLen)
throw new IllegalArgumentException(String.format("not a valid reference type descriptor: %s", descriptor));
return new ReferenceClassDescImpl(descriptor);
@ -66,7 +66,7 @@ public final class ReferenceClassDescImpl implements ClassDesc {
* @jvms 4.3.2 Field Descriptors
*/
public static ReferenceClassDescImpl ofValidated(String descriptor) {
assert ConstantUtils.skipOverFieldSignature(descriptor, 0, descriptor.length(), false)
assert ConstantUtils.skipOverFieldSignature(descriptor, 0, descriptor.length())
== descriptor.length() : descriptor;
return new ReferenceClassDescImpl(descriptor);
}

View File

@ -304,21 +304,6 @@ public enum Wrapper {
throw newIllegalArgumentException("not primitive: " + type);
}
/** Return the wrapper that corresponds to the provided basic type char.
* The basic type char must be for one of the eight primitive types, or void.
* @throws IllegalArgumentException for unexpected types
*/
public static Wrapper forPrimitiveType(char basicTypeChar) {
Wrapper w = FROM_CHAR[(basicTypeChar + (basicTypeChar >> 1)) & 0xf];
if (w == null || w.basicTypeChar != basicTypeChar) {
throw basicTypeError(basicTypeChar);
}
if (w == OBJECT) {
throw newIllegalArgumentException("not primitive: " + basicTypeChar);
}
return w;
}
/** Return the wrapper that wraps values into the given wrapper type.
* If it is {@code Object}, return {@code OBJECT}.
* Otherwise, it must be a wrapper type.

View File

@ -69,7 +69,7 @@ public class ConstantUtilsTest {
}
public void testSkipOverFieldSignatureVoid() {
int ret = ConstantUtils.skipOverFieldSignature("(V)V", 1, 4, false);
int ret = ConstantUtils.skipOverFieldSignature("(V)V", 1, 4);
assertEquals(ret, 0, "Descriptor of (V)V starting at index 1, void disallowed");
}
}

View File

@ -58,9 +58,14 @@ public class MethodTypeDescFactories {
@Param({
"(Ljava/lang/Object;Ljava/lang/String;)I",
"()V",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
"(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;",
"()Ljava/lang/Object;",
"([IJLjava/lang/String;Z)Ljava/util/List;",
"()[Ljava/lang/String;",
"(..IIJ)V",
"([III.Z[B..[.[B).",
"(.....................)."
})
public String descString;

View File

@ -112,11 +112,4 @@ public class Wrappers {
bh.consume(Wrapper.forBasicType(c));
}
}
@Benchmark
public void forPrimitiveType(Blackhole bh) throws Throwable {
for (char c : PRIM_TYPES) {
bh.consume(Wrapper.forPrimitiveType(c));
}
}
}