8338546: Speed up ConstantPoolBuilder::classEntry(ClassDesc)

Reviewed-by: asotona, redestad
This commit is contained in:
Chen Liang 2024-09-24 14:28:05 +00:00
parent 279086d4ce
commit caa751c561
7 changed files with 647 additions and 95 deletions

View File

@ -86,14 +86,24 @@ public abstract sealed class AbstractPoolEntry {
return stringHash | NON_ZERO;
}
public static Utf8Entry rawUtf8EntryFromStandardAttributeName(String name) {
//assuming standard attribute names are all US_ASCII
var raw = name.getBytes(StandardCharsets.US_ASCII);
return new Utf8EntryImpl(null, 0, raw, 0, raw.length);
static int hashClassFromUtf8(boolean isArray, Utf8EntryImpl content) {
int hash = content.contentHash();
return hashClassFromDescriptor(isArray ? hash : Util.descriptorStringHash(content.length(), hash));
}
static int hashClassFromDescriptor(int descriptorHash) {
return hash1(ClassFile.TAG_CLASS, descriptorHash);
}
static boolean isArrayDescriptor(Utf8EntryImpl cs) {
// Do not throw out-of-bounds for empty strings
return !cs.isEmpty() && cs.charAt(0) == '[';
}
@SuppressWarnings("unchecked")
public static <T extends PoolEntry> T maybeClone(ConstantPoolBuilder cp, T entry) {
if (cp.canWriteDirect(entry.constantPool()))
return entry;
return (T)((AbstractPoolEntry)entry).clone(cp);
}
@ -146,7 +156,7 @@ public abstract sealed class AbstractPoolEntry {
private final int offset;
private final int rawLen;
// Set in any state other than RAW
private @Stable int hash;
private @Stable int contentHash;
private @Stable int charLen;
// Set in CHAR state
private @Stable char[] chars;
@ -165,10 +175,10 @@ public abstract sealed class AbstractPoolEntry {
}
Utf8EntryImpl(ConstantPool cpm, int index, String s) {
this(cpm, index, s, hashString(s.hashCode()));
this(cpm, index, s, s.hashCode());
}
Utf8EntryImpl(ConstantPool cpm, int index, String s, int hash) {
Utf8EntryImpl(ConstantPool cpm, int index, String s, int contentHash) {
super(cpm, index, 0);
this.rawBytes = null;
this.offset = 0;
@ -176,7 +186,7 @@ public abstract sealed class AbstractPoolEntry {
this.state = State.STRING;
this.stringValue = s;
this.charLen = s.length();
this.hash = hash;
this.contentHash = contentHash;
}
Utf8EntryImpl(ConstantPool cpm, int index, Utf8EntryImpl u) {
@ -185,7 +195,7 @@ public abstract sealed class AbstractPoolEntry {
this.offset = u.offset;
this.rawLen = u.rawLen;
this.state = u.state;
this.hash = u.hash;
this.contentHash = u.contentHash;
this.charLen = u.charLen;
this.chars = u.chars;
this.stringValue = u.stringValue;
@ -236,7 +246,7 @@ public abstract sealed class AbstractPoolEntry {
int singleBytes = JLA.countPositives(rawBytes, offset, rawLen);
int hash = ArraysSupport.hashCodeOfUnsigned(rawBytes, offset, singleBytes, 0);
if (singleBytes == rawLen) {
this.hash = hashString(hash);
this.contentHash = hash;
charLen = rawLen;
state = State.BYTE;
}
@ -294,7 +304,7 @@ public abstract sealed class AbstractPoolEntry {
throw malformedInput(px);
}
}
this.hash = hashString(hash);
this.contentHash = hash;
charLen = chararr_count;
this.chars = chararr;
state = State.CHAR;
@ -307,8 +317,6 @@ public abstract sealed class AbstractPoolEntry {
@Override
public Utf8EntryImpl clone(ConstantPoolBuilder cp) {
if (cp.canWriteDirect(constantPool))
return this;
return (state == State.STRING && rawBytes == null)
? (Utf8EntryImpl) cp.utf8Entry(stringValue)
: ((SplitConstantPool) cp).maybeCloneUtf8Entry(this);
@ -316,9 +324,13 @@ public abstract sealed class AbstractPoolEntry {
@Override
public int hashCode() {
return hashString(contentHash());
}
int contentHash() {
if (state == State.RAW)
inflate();
return hash;
return contentHash;
}
@Override
@ -389,6 +401,38 @@ public abstract sealed class AbstractPoolEntry {
return stringValue().equals(u.stringValue());
}
/**
* Returns if this utf8 entry's content equals a substring
* of {@code s} obtained as {@code s.substring(start, end - start)}.
* This check avoids a substring allocation.
*/
public boolean equalsRegion(String s, int start, int end) {
// start and end values trusted
if (state == State.RAW)
inflate();
int len = charLen;
if (len != end - start)
return false;
var sv = stringValue;
if (sv != null) {
return sv.regionMatches(0, s, start, len);
}
var chars = this.chars;
if (chars != null) {
for (int i = 0; i < len; i++)
if (chars[i] != s.charAt(start + i))
return false;
} else {
var bytes = this.rawBytes;
for (int i = 0; i < len; i++)
if (bytes[offset + i] != s.charAt(start + i))
return false;
}
return true;
}
@Override
public boolean equalsString(String s) {
if (state == State.RAW)
@ -397,7 +441,7 @@ public abstract sealed class AbstractPoolEntry {
case STRING:
return stringValue.equals(s);
case CHAR:
if (charLen != s.length() || hash != hashString(s.hashCode()))
if (charLen != s.length() || contentHash != s.hashCode())
return false;
for (int i=0; i<charLen; i++)
if (chars[i] != s.charAt(i))
@ -406,7 +450,7 @@ public abstract sealed class AbstractPoolEntry {
state = State.STRING;
return true;
case BYTE:
if (rawLen != s.length() || hash != hashString(s.hashCode()))
if (rawLen != s.length() || contentHash != s.hashCode())
return false;
for (int i=0; i<rawLen; i++)
if (rawBytes[offset+i] != s.charAt(i))
@ -519,7 +563,8 @@ public abstract sealed class AbstractPoolEntry {
public static final class ClassEntryImpl extends AbstractNamedEntry implements ClassEntry {
public ClassDesc sym = null;
public @Stable ClassDesc sym;
private @Stable int hash;
ClassEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl name) {
super(cpm, TAG_CLASS, index, name);
@ -530,15 +575,15 @@ public abstract sealed class AbstractPoolEntry {
return TAG_CLASS;
}
ClassEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl name, int hash, ClassDesc sym) {
super(cpm, ClassFile.TAG_CLASS, index, name);
this.hash = hash;
this.sym = sym;
}
@Override
public ClassEntry clone(ConstantPoolBuilder cp) {
if (cp.canWriteDirect(constantPool)) {
return this;
} else {
ClassEntryImpl ret = (ClassEntryImpl)cp.classEntry(ref1);
ret.sym = sym;
return ret;
}
return ((SplitConstantPool) cp).cloneClassEntry(this);
}
@Override
@ -547,19 +592,42 @@ public abstract sealed class AbstractPoolEntry {
if (sym != null) {
return sym;
}
return this.sym = Util.toClassDesc(asInternalName());
if (isArrayDescriptor(ref1)) {
sym = ref1.fieldTypeSymbol(); // array, symbol already available
} else {
sym = ClassDesc.ofInternalName(asInternalName()); // class or interface
}
return this.sym = sym;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o instanceof ClassEntryImpl cce) {
return cce.name().equals(this.name());
} else if (o instanceof ClassEntry c) {
return c.asSymbol().equals(this.asSymbol());
if (o instanceof ClassEntryImpl other) {
return equalsEntry(other);
}
return false;
}
boolean equalsEntry(ClassEntryImpl other) {
var tsym = this.sym;
var osym = other.sym;
if (tsym != null && osym != null) {
return tsym.equals(osym);
}
return ref1.equalsUtf8(other.ref1);
}
@Override
public int hashCode() {
var hash = this.hash;
if (hash != 0)
return hash;
return this.hash = hashClassFromUtf8(isArrayDescriptor(ref1), ref1);
}
}
public static final class PackageEntryImpl extends AbstractNamedEntry implements PackageEntry {
@ -575,7 +643,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public PackageEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.packageEntry(ref1);
return cp.packageEntry(ref1);
}
@Override
@ -606,7 +674,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public ModuleEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.moduleEntry(ref1);
return cp.moduleEntry(ref1);
}
@Override
@ -648,9 +716,6 @@ public abstract sealed class AbstractPoolEntry {
@Override
public NameAndTypeEntry clone(ConstantPoolBuilder cp) {
if (cp.canWriteDirect(constantPool)) {
return this;
}
return cp.nameAndTypeEntry(ref1, ref2);
}
@ -715,7 +780,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public FieldRefEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.fieldRefEntry(ref1, ref2);
return cp.fieldRefEntry(ref1, ref2);
}
}
@ -733,7 +798,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public MethodRefEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.methodRefEntry(ref1, ref2);
return cp.methodRefEntry(ref1, ref2);
}
}
@ -751,7 +816,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public InterfaceMethodRefEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.interfaceMethodRefEntry(ref1, ref2);
return cp.interfaceMethodRefEntry(ref1, ref2);
}
}
@ -847,7 +912,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public InvokeDynamicEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.invokeDynamicEntry(bootstrap(), nameAndType());
return cp.invokeDynamicEntry(bootstrap(), nameAndType());
}
}
@ -872,7 +937,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public ConstantDynamicEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.constantDynamicEntry(bootstrap(), nameAndType());
return cp.constantDynamicEntry(bootstrap(), nameAndType());
}
}
@ -929,7 +994,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public MethodHandleEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.methodHandleEntry(refKind, reference);
return cp.methodHandleEntry(refKind, reference);
}
@Override
@ -969,9 +1034,6 @@ public abstract sealed class AbstractPoolEntry {
@Override
public MethodTypeEntry clone(ConstantPoolBuilder cp) {
if (cp.canWriteDirect(constantPool)) {
return this;
}
return cp.methodTypeEntry(ref1);
}
@ -1020,7 +1082,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public StringEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.stringEntry(ref1);
return cp.stringEntry(ref1);
}
@Override
@ -1063,7 +1125,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public IntegerEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.intEntry(val);
return cp.intEntry(val);
}
@Override
@ -1109,7 +1171,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public FloatEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.floatEntry(val);
return cp.floatEntry(val);
}
@Override
@ -1159,7 +1221,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public LongEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.longEntry(val);
return cp.longEntry(val);
}
@Override
@ -1209,7 +1271,7 @@ public abstract sealed class AbstractPoolEntry {
@Override
public DoubleEntry clone(ConstantPoolBuilder cp) {
return cp.canWriteDirect(constantPool) ? this : cp.doubleEntry(val);
return cp.doubleEntry(val);
}
@Override

View File

@ -37,6 +37,8 @@ import java.lang.classfile.attribute.BootstrapMethodsAttribute;
import java.lang.classfile.constantpool.*;
import java.util.Objects;
import jdk.internal.constant.ConstantUtils;
import static java.lang.classfile.ClassFile.TAG_CLASS;
import static java.lang.classfile.ClassFile.TAG_CONSTANTDYNAMIC;
import static java.lang.classfile.ClassFile.TAG_DOUBLE;
@ -371,7 +373,6 @@ public final class SplitConstantPool implements ConstantPoolBuilder {
PoolEntry e = entryByIndex(map.getIndexByToken(token));
if (e.tag() == ClassFile.TAG_UTF8
&& e instanceof AbstractPoolEntry.Utf8EntryImpl ce
&& ce.hashCode() == hash
&& target.equals(ce.stringValue()))
return ce;
}
@ -398,25 +399,111 @@ public final class SplitConstantPool implements ConstantPoolBuilder {
return null;
}
private AbstractPoolEntry.Utf8EntryImpl tryFindUtf8OfRegion(int hash, String target, int start, int end) {
EntryMap map = map();
while (true) {
for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) {
PoolEntry e = entryByIndex(map.getIndexByToken(token));
if (e.tag() == ClassFile.TAG_UTF8
&& e instanceof AbstractPoolEntry.Utf8EntryImpl ce
&& ce.equalsRegion(target, start, end))
return ce;
}
if (!doneFullScan) {
fullScan();
continue;
}
return null;
}
}
private AbstractPoolEntry.ClassEntryImpl tryFindClassOrInterface(int hash, ClassDesc cd) {
while (true) {
EntryMap map = map();
for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) {
PoolEntry e = entryByIndex(map.getIndexByToken(token));
if (e.tag() == TAG_CLASS
&& e instanceof AbstractPoolEntry.ClassEntryImpl ce) {
var esym = ce.sym;
if (esym != null) {
if (cd.equals(esym)) {
return ce; // definite match
}
continue; // definite mismatch
}
// no symbol available
var desc = cd.descriptorString();
if (ce.ref1.equalsRegion(desc, 1, desc.length() - 1)) {
// definite match, propagate symbol
ce.sym = cd;
return ce;
}
// definite mismatch
}
}
if (!doneFullScan) {
fullScan();
continue;
}
return null;
}
}
private AbstractPoolEntry.ClassEntryImpl classEntryForClassOrInterface(ClassDesc cd) {
var desc = cd.descriptorString();
int hash = AbstractPoolEntry.hashClassFromDescriptor(desc.hashCode());
var ce = tryFindClassOrInterface(hash, cd);
if (ce != null)
return ce;
var utfHash = Util.internalNameHash(desc);
var utf = tryFindUtf8OfRegion(AbstractPoolEntry.hashString(utfHash), desc, 1, desc.length() - 1);
if (utf == null)
utf = internalAdd(new AbstractPoolEntry.Utf8EntryImpl(this, size, ConstantUtils.dropFirstAndLastChar(desc), utfHash));
return internalAdd(new AbstractPoolEntry.ClassEntryImpl(this, size, utf, hash, cd));
}
private AbstractPoolEntry.ClassEntryImpl tryFindClassEntry(int hash, AbstractPoolEntry.Utf8EntryImpl utf8) {
EntryMap map = map();
for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) {
PoolEntry e = entryByIndex(map.getIndexByToken(token));
if (e.tag() == ClassFile.TAG_CLASS
&& e instanceof AbstractPoolEntry.ClassEntryImpl ce
&& ce.ref1.equalsUtf8(utf8))
return ce;
}
if (!doneFullScan) {
fullScan();
return tryFindClassEntry(hash, utf8);
}
return null;
}
@Override
public Utf8Entry utf8Entry(ClassDesc desc) {
public AbstractPoolEntry.Utf8EntryImpl utf8Entry(ClassDesc desc) {
var utf8 = utf8Entry(desc.descriptorString());
utf8.typeSym = desc;
if (utf8.typeSym == null)
utf8.typeSym = desc;
return utf8;
}
@Override
public Utf8Entry utf8Entry(MethodTypeDesc desc) {
var utf8 = utf8Entry(desc.descriptorString());
utf8.typeSym = desc;
if (utf8.typeSym == null)
utf8.typeSym = desc;
return utf8;
}
@Override
public AbstractPoolEntry.Utf8EntryImpl utf8Entry(String s) {
int hash = AbstractPoolEntry.hashString(s.hashCode());
var ce = tryFindUtf8(hash, s);
return ce == null ? internalAdd(new AbstractPoolEntry.Utf8EntryImpl(this, size, s, hash)) : ce;
int contentHash = s.hashCode();
var ce = tryFindUtf8(AbstractPoolEntry.hashString(contentHash), s);
return ce == null ? internalAdd(new AbstractPoolEntry.Utf8EntryImpl(this, size, s, contentHash)) : ce;
}
AbstractPoolEntry.Utf8EntryImpl maybeCloneUtf8Entry(Utf8Entry entry) {
@ -429,9 +516,37 @@ public final class SplitConstantPool implements ConstantPoolBuilder {
@Override
public AbstractPoolEntry.ClassEntryImpl classEntry(Utf8Entry nameEntry) {
AbstractPoolEntry.Utf8EntryImpl ne = maybeCloneUtf8Entry(nameEntry);
var e = (AbstractPoolEntry.ClassEntryImpl) findEntry(TAG_CLASS, ne);
return e == null ? internalAdd(new AbstractPoolEntry.ClassEntryImpl(this, size, ne)) : e;
var ne = maybeCloneUtf8Entry(nameEntry);
return classEntry(ne, AbstractPoolEntry.isArrayDescriptor(ne));
}
AbstractPoolEntry.ClassEntryImpl classEntry(AbstractPoolEntry.Utf8EntryImpl ne, boolean isArray) {
int hash = AbstractPoolEntry.hashClassFromUtf8(isArray, ne);
var e = tryFindClassEntry(hash, ne);
return e == null ? internalAdd(new AbstractPoolEntry.ClassEntryImpl(this, size, ne, hash,
isArray && ne.typeSym instanceof ClassDesc cd ? cd : null)) : e;
}
@Override
public ClassEntry classEntry(ClassDesc cd) {
if (cd.isClassOrInterface()) { // implicit null check
return classEntryForClassOrInterface(cd);
}
if (cd.isArray()) {
return classEntry(utf8Entry(cd), true);
}
throw new IllegalArgumentException("Cannot be encoded as ClassEntry: " + cd.displayName());
}
AbstractPoolEntry.ClassEntryImpl cloneClassEntry(AbstractPoolEntry.ClassEntryImpl e) {
var ce = tryFindClassEntry(e.hashCode(), e.ref1);
if (ce != null) {
return ce;
}
var utf8 = maybeCloneUtf8Entry(e.ref1); // call order matters
return internalAdd(new AbstractPoolEntry.ClassEntryImpl(this, size,
utf8, e.hashCode(), e.sym));
}
@Override
@ -458,36 +573,24 @@ public final class SplitConstantPool implements ConstantPoolBuilder {
@Override
public FieldRefEntry fieldRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) {
AbstractPoolEntry.ClassEntryImpl oe = (AbstractPoolEntry.ClassEntryImpl) owner;
AbstractPoolEntry.NameAndTypeEntryImpl ne = (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType;
if (!canWriteDirect(oe.constantPool))
oe = classEntry(owner.name());
if (!canWriteDirect(ne.constantPool))
ne = nameAndTypeEntry(nameAndType.name(), nameAndType.type());
var oe = AbstractPoolEntry.maybeClone(this, (AbstractPoolEntry.ClassEntryImpl) owner);
var ne = AbstractPoolEntry.maybeClone(this, (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType);
var e = (AbstractPoolEntry.FieldRefEntryImpl) findEntry(TAG_FIELDREF, oe, ne);
return e == null ? internalAdd(new AbstractPoolEntry.FieldRefEntryImpl(this, size, oe, ne)) : e;
}
@Override
public MethodRefEntry methodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) {
AbstractPoolEntry.ClassEntryImpl oe = (AbstractPoolEntry.ClassEntryImpl) owner;
AbstractPoolEntry.NameAndTypeEntryImpl ne = (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType;
if (!canWriteDirect(oe.constantPool))
oe = classEntry(owner.name());
if (!canWriteDirect(ne.constantPool))
ne = nameAndTypeEntry(nameAndType.name(), nameAndType.type());
var oe = AbstractPoolEntry.maybeClone(this, (AbstractPoolEntry.ClassEntryImpl) owner);
var ne = AbstractPoolEntry.maybeClone(this, (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType);
var e = (AbstractPoolEntry.MethodRefEntryImpl) findEntry(TAG_METHODREF, oe, ne);
return e == null ? internalAdd(new AbstractPoolEntry.MethodRefEntryImpl(this, size, oe, ne)) : e;
}
@Override
public InterfaceMethodRefEntry interfaceMethodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) {
AbstractPoolEntry.ClassEntryImpl oe = (AbstractPoolEntry.ClassEntryImpl) owner;
AbstractPoolEntry.NameAndTypeEntryImpl ne = (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType;
if (!canWriteDirect(oe.constantPool))
oe = classEntry(owner.name());
if (!canWriteDirect(ne.constantPool))
ne = nameAndTypeEntry(nameAndType.name(), nameAndType.type());
var oe = AbstractPoolEntry.maybeClone(this, (AbstractPoolEntry.ClassEntryImpl) owner);
var ne = AbstractPoolEntry.maybeClone(this, (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType);
var e = (AbstractPoolEntry.InterfaceMethodRefEntryImpl) findEntry(TAG_INTERFACEMETHODREF, oe, ne);
return e == null ? internalAdd(new AbstractPoolEntry.InterfaceMethodRefEntryImpl(this, size, oe, ne)) : e;
}
@ -506,15 +609,7 @@ public final class SplitConstantPool implements ConstantPoolBuilder {
@Override
public MethodHandleEntry methodHandleEntry(int refKind, MemberRefEntry reference) {
if (!canWriteDirect(reference.constantPool())) {
reference = switch (reference.tag()) {
case TAG_FIELDREF -> fieldRefEntry(reference.owner(), reference.nameAndType());
case TAG_METHODREF -> methodRefEntry(reference.owner(), reference.nameAndType());
case TAG_INTERFACEMETHODREF -> interfaceMethodRefEntry(reference.owner(), reference.nameAndType());
default -> throw new IllegalArgumentException(String.format("Bad tag %d", reference.tag()));
};
}
reference = AbstractPoolEntry.maybeClone(this, reference);
int hash = AbstractPoolEntry.hash2(TAG_METHODHANDLE, refKind, reference.index());
EntryMap map1 = map();
for (int token = map1.firstToken(hash); token != -1; token = map1.nextToken(hash, token)) {
@ -538,8 +633,7 @@ public final class SplitConstantPool implements ConstantPoolBuilder {
if (!canWriteDirect(bootstrapMethodEntry.constantPool()))
bootstrapMethodEntry = bsmEntry(bootstrapMethodEntry.bootstrapMethod(),
bootstrapMethodEntry.arguments());
if (!canWriteDirect(nameAndType.constantPool()))
nameAndType = nameAndTypeEntry(nameAndType.name(), nameAndType.type());
nameAndType = AbstractPoolEntry.maybeClone(this, nameAndType);
int hash = AbstractPoolEntry.hash2(TAG_INVOKEDYNAMIC,
bootstrapMethodEntry.bsmIndex(), nameAndType.index());
EntryMap map1 = map();
@ -569,8 +663,7 @@ public final class SplitConstantPool implements ConstantPoolBuilder {
if (!canWriteDirect(bootstrapMethodEntry.constantPool()))
bootstrapMethodEntry = bsmEntry(bootstrapMethodEntry.bootstrapMethod(),
bootstrapMethodEntry.arguments());
if (!canWriteDirect(nameAndType.constantPool()))
nameAndType = nameAndTypeEntry(nameAndType.name(), nameAndType.type());
nameAndType = AbstractPoolEntry.maybeClone(this, nameAndType);
int hash = AbstractPoolEntry.hash2(TAG_CONSTANTDYNAMIC,
bootstrapMethodEntry.bsmIndex(), nameAndType.index());
EntryMap map1 = map();
@ -628,8 +721,7 @@ public final class SplitConstantPool implements ConstantPoolBuilder {
@Override
public BootstrapMethodEntry bsmEntry(MethodHandleEntry methodReference,
List<LoadableConstantEntry> arguments) {
if (!canWriteDirect(methodReference.constantPool()))
methodReference = methodHandleEntry(methodReference.kind(), methodReference.reference());
methodReference = AbstractPoolEntry.maybeClone(this, methodReference);
for (LoadableConstantEntry a : arguments) {
if (!canWriteDirect(a.constantPool())) {
// copy args list

View File

@ -50,6 +50,7 @@ import java.lang.classfile.constantpool.NameAndTypeEntry;
import java.lang.constant.ModuleDesc;
import java.lang.reflect.AccessFlag;
import jdk.internal.access.SharedSecrets;
import jdk.internal.vm.annotation.Stable;
import static java.lang.classfile.ClassFile.ACC_STATIC;
import java.lang.classfile.attribute.CodeAttribute;
@ -337,4 +338,82 @@ public class Util {
interface WritableLocalVariable {
boolean writeLocalTo(BufWriterImpl buf);
}
/**
* Returns the hash code of an internal name given the class or interface L descriptor.
*/
public static int internalNameHash(String desc) {
if (desc.length() > 0xffff)
throw new IllegalArgumentException("String too long: ".concat(Integer.toString(desc.length())));
return (desc.hashCode() - pow31(desc.length() - 1) * 'L' - ';') * INVERSE_31;
}
/**
* Returns the hash code of a class or interface L descriptor given the internal name.
*/
public static int descriptorStringHash(int length, int hash) {
if (length > 0xffff)
throw new IllegalArgumentException("String too long: ".concat(Integer.toString(length)));
return 'L' * pow31(length + 1) + hash * 31 + ';';
}
// k is at most 65536, length of Utf8 entry + 1
public static int pow31(int k) {
int r = 1;
// calculate the power contribution from index-th octal digit
// from least to most significant (right to left)
// e.g. decimal 26=octal 32, power(26)=powerOctal(2,0)*powerOctal(3,1)
for (int i = 0; i < SIGNIFICANT_OCTAL_DIGITS; i++) {
r *= powerOctal(k & 7, i);
k >>= 3;
}
return r;
}
// The inverse of 31 in Z/2^32Z* modulo group, a * INVERSE_31 * 31 = a
static final int INVERSE_31 = 0xbdef7bdf;
// k is at most 65536 = octal 200000, only consider 6 octal digits
// Note: 31 powers repeat beyond 1 << 27, only 9 octal digits matter
static final int SIGNIFICANT_OCTAL_DIGITS = 6;
// for base k, storage is k * log_k(N)=k/ln(k) * ln(N)
// k = 2 or 4 is better for space at the cost of more multiplications
/**
* The code below is as if:
* {@snippet lang=java :
* int[] powers = new int[7 * SIGNIFICANT_OCTAL_DIGITS];
*
* for (int i = 1, k = 31; i <= 7; i++, k *= 31) {
* int t = powers[powersIndex(i, 0)] = k;
* for (int j = 1; j < SIGNIFICANT_OCTAL_DIGITS; j++) {
* t *= t;
* t *= t;
* t *= t;
* powers[powersIndex(i, j)] = t;
* }
* }
* }
* This is converted to explicit initialization to avoid bootstrap overhead.
* Validated in UtilTest.
*/
static final @Stable int[] powers = new int[] {
0x0000001f, 0x000003c1, 0x0000745f, 0x000e1781, 0x01b4d89f, 0x34e63b41, 0x67e12cdf,
0x94446f01, 0x50a9de01, 0x84304d01, 0x7dd7bc01, 0x8ca02b01, 0xff899a01, 0x25940901,
0x4dbf7801, 0xe3bef001, 0xc1fe6801, 0xe87de001, 0x573d5801, 0x0e3cd001, 0x0d7c4801,
0x54fbc001, 0xb9f78001, 0x2ef34001, 0xb3ef0001, 0x48eac001, 0xede68001, 0xa2e24001,
0x67de0001, 0xcfbc0001, 0x379a0001, 0x9f780001, 0x07560001, 0x6f340001, 0xd7120001,
0x3ef00001, 0x7de00001, 0xbcd00001, 0xfbc00001, 0x3ab00001, 0x79a00001, 0xb8900001,
};
static int powersIndex(int digit, int index) {
return (digit - 1) + index * 7;
}
// (31 ^ digit) ^ (8 * index) = 31 ^ (digit * (8 ^ index))
// digit: 0 - 7
// index: 0 - SIGNIFICANT_OCTAL_DIGITS - 1
private static int powerOctal(int digit, int index) {
return digit == 0 ? 1 : powers[powersIndex(digit, index)];
}
}

View File

@ -23,11 +23,14 @@
/*
* @test
* @bug 8304031 8338406
* @bug 8304031 8338406 8338546
* @summary Testing handling of various constant descriptors in ClassFile API.
* @modules java.base/jdk.internal.constant
* java.base/jdk.internal.classfile.impl
* @run junit ConstantDescSymbolsTest
*/
import java.lang.classfile.constantpool.ConstantPoolBuilder;
import java.lang.constant.ClassDesc;
import java.lang.constant.DynamicConstantDesc;
import java.lang.constant.MethodHandleDesc;
@ -36,8 +39,14 @@ import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.function.Supplier;
import java.lang.classfile.ClassFile;
import java.util.stream.Stream;
import jdk.internal.classfile.impl.AbstractPoolEntry;
import jdk.internal.constant.ConstantUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static java.lang.classfile.ClassFile.ACC_PUBLIC;
import static java.lang.constant.ConstantDescs.*;
@ -102,4 +111,58 @@ final class ConstantDescSymbolsTest {
assertEquals(DEFAULT_NAME, cb.name);
assertEquals(CondyBoot.class, cb.type);
}
static Stream<ClassDesc> classOrInterfaceEntries() {
return Stream.of(
CD_Object, CD_Float, CD_Long, CD_String, ClassDesc.of("Ape"),
CD_String.nested("Whatever"), CD_MethodHandles_Lookup, ClassDesc.ofInternalName("one/Two"),
ClassDesc.ofDescriptor("La/b/C;"), ConstantDescSymbolsTest.class.describeConstable().orElseThrow(),
CD_Boolean, CD_ConstantBootstraps, CD_MethodHandles
);
}
@ParameterizedTest
@MethodSource("classOrInterfaceEntries")
void testConstantPoolBuilderClassOrInterfaceEntry(ClassDesc cd) {
assertTrue(cd.isClassOrInterface());
ConstantPoolBuilder cp = ConstantPoolBuilder.of();
var internal = ConstantUtils.dropFirstAndLastChar(cd.descriptorString());
// 1. ClassDesc
var ce = cp.classEntry(cd);
assertSame(cd, ce.asSymbol(), "Symbol propagation on create");
// 1.1. Bare addition
assertTrue(ce.name().equalsString(internal), "Adding to bare pool");
// 1.2. Lookup existing
assertSame(ce, cp.classEntry(cd), "Finding by identical CD");
// 1.3. Lookup existing - equal but different ClassDesc
var cd1 = ClassDesc.ofDescriptor(cd.descriptorString());
assertSame(ce, cp.classEntry(cd1), "Finding by another equal CD");
// 1.3.1. Lookup existing - equal but different ClassDesc, equal but different string
var cd2 = ClassDesc.ofDescriptor("" + cd.descriptorString());
assertSame(ce, cp.classEntry(cd2), "Finding by another equal CD");
// 1.4. Lookup existing - with utf8 internal name
var utf8 = cp.utf8Entry(internal);
assertSame(ce, cp.classEntry(utf8), "Finding CD by UTF8");
// 2. ClassEntry exists, no ClassDesc
cp = ConstantPoolBuilder.of();
utf8 = cp.utf8Entry(internal);
ce = cp.classEntry(utf8);
var found = cp.classEntry(cd);
assertSame(ce, found, "Finding non-CD CEs with CD");
assertEquals(cd, ce.asSymbol(), "Symbol propagation on find");
// 3. Utf8Entry exists, no ClassEntry
cp = ConstantPoolBuilder.of();
utf8 = cp.utf8Entry(internal);
ce = cp.classEntry(cd);
assertSame(utf8, ce.name(), "Reusing existing utf8 entry");
assertEquals(cd, ce.asSymbol(), "Symbol propagation on create with utf8");
}
}

View File

@ -23,18 +23,25 @@
/*
* @test
* @bug 8338546
* @summary Testing ClassFile Util.
* @library java.base
* @modules java.base/jdk.internal.constant
* java.base/jdk.internal.classfile.impl
* @build java.base/jdk.internal.classfile.impl.*
* @run junit UtilTest
*/
import java.lang.classfile.ClassFile;
import java.lang.classfile.Opcode;
import java.lang.constant.MethodTypeDesc;
import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import java.util.BitSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import jdk.internal.classfile.impl.RawBytecodeHelper;
import jdk.internal.classfile.impl.Util;
import jdk.internal.classfile.impl.UtilAccess;
import jdk.internal.constant.ConstantUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@ -84,6 +91,50 @@ class UtilTest {
assertEquals(Util.parameterSlots(MethodTypeDesc.ofDescriptor(methodDesc)), slots);
}
@Test
void testPow31() {
int p = 1;
// Our calculation only prepares up to 65536,
// max length of CP Utf8 + 1
for (int i = 0; i <= 65536; i++) {
final int t = i;
assertEquals(p, Util.pow31(i), () -> "31's power to " + t);
p *= 31;
}
}
@ParameterizedTest
@ValueSource(classes = {
Long.class,
Object.class,
Util.class,
Test.class,
CopyOnWriteArrayList.class,
AtomicReferenceFieldUpdater.class
})
void testInternalNameHash(Class<?> type) {
var cd = type.describeConstable().orElseThrow();
assertEquals(ConstantUtils.binaryToInternal(type.getName()).hashCode(), Util.internalNameHash(cd.descriptorString()));
}
// Ensures the initialization statement of the powers array is filling in the right values
@Test
void testPowersArray() {
int[] powers = new int[7 * UtilAccess.significantOctalDigits()];
for (int i = 1, k = 31; i <= 7; i++, k *= 31) {
int t = powers[UtilAccess.powersIndex(i, 0)] = k;
for (int j = 1; j < UtilAccess.significantOctalDigits(); j++) {
t *= t;
t *= t;
t *= t;
powers[UtilAccess.powersIndex(i, j)] = t;
}
}
assertArrayEquals(powers, UtilAccess.powersTable());
}
@Test
void testOpcodeLengthTable() {
var lengths = new byte[0x100];

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.classfile.impl;
public final class UtilAccess {
public static int significantOctalDigits() {
return Util.SIGNIFICANT_OCTAL_DIGITS;
}
public static int powersIndex(int digit, int index) {
return Util.powersIndex(digit, index);
}
public static int[] powersTable() {
return Util.powers;
}
public static int reverse31() {
return Util.INVERSE_31;
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright (c) 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.jdk.classfile;
import java.lang.classfile.constantpool.ConstantPoolBuilder;
import java.lang.constant.ClassDesc;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import static java.lang.constant.ConstantDescs.*;
import static org.openjdk.bench.jdk.classfile.TestConstants.*;
/**
* Tests constant pool builder lookup performance for ClassEntry.
* Note that ClassEntry is available only for reference types.
*/
@Warmup(iterations = 3)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
@Fork(value = 1, jvmArgsAppend = {"--enable-preview"})
@State(Scope.Benchmark)
public class ConstantPoolBuildingClassEntry {
// JDK-8338546
ConstantPoolBuilder builder;
List<ClassDesc> classDescs;
List<ClassDesc> nonIdenticalClassDescs;
List<String> internalNames;
List<ClassDesc> nonDuplicateClassDescs;
List<String> nonDuplicateInternalNames;
int size;
@Setup(Level.Iteration)
public void setup() {
builder = ConstantPoolBuilder.of();
// Note these can only be reference types, no primitives
classDescs = List.of(
CD_Byte, CD_Object, CD_Long.arrayType(), CD_String, CD_String, CD_Object, CD_Short,
CD_MethodHandle, CD_MethodHandle, CD_Object, CD_Character, CD_List, CD_ArrayList,
CD_List, CD_Set, CD_Integer, CD_Object.arrayType(), CD_Enum, CD_Object, CD_MethodHandles_Lookup,
CD_Long, CD_Set, CD_Object, CD_Character, CD_Integer, CD_System, CD_String, CD_String,
CD_CallSite, CD_Collection, CD_List, CD_Collection, CD_String, CD_int.arrayType()
);
size = classDescs.size();
nonIdenticalClassDescs = classDescs.stream().map(cd -> {
var ret = ClassDesc.ofDescriptor(new String(cd.descriptorString()));
ret.hashCode(); // pre-compute hash code for cd
return ret;
}).toList();
internalNames = classDescs.stream().map(cd -> {
// also sets up builder
cd.hashCode(); // pre-computes hash code for cd
var ce = builder.classEntry(cd);
var ret = ce.name().stringValue();
ret.hashCode(); // pre-computes hash code for stringValue
return ret;
}).toList();
nonDuplicateClassDescs = List.copyOf(new LinkedHashSet<>(classDescs));
nonDuplicateInternalNames = nonDuplicateClassDescs.stream().map(cd ->
builder.classEntry(cd).asInternalName()).toList();
}
// Copied from jdk.internal.classfile.impl.Util::toInternalName
// to reduce internal dependencies
public static String toInternalName(ClassDesc cd) {
var desc = cd.descriptorString();
if (desc.charAt(0) == 'L')
return desc.substring(1, desc.length() - 1);
throw new IllegalArgumentException(desc);
}
/**
* Looking up with identical ClassDesc objects. Happens in bytecode generators reusing
* constant CD_Xxx.
*/
@Benchmark
public void identicalLookup(Blackhole bh) {
for (var cd : classDescs) {
bh.consume(builder.classEntry(cd));
}
}
/**
* Looking up with non-identical ClassDesc objects. Happens in bytecode generators
* using ad-hoc Class.describeConstable().orElseThrow() or other parsed ClassDesc.
* Cannot use identity fast path compared to {@link #identicalLookup}.
*/
@Benchmark
public void nonIdenticalLookup(Blackhole bh) {
for (var cd : nonIdenticalClassDescs) {
bh.consume(builder.classEntry(cd));
}
}
/**
* Looking up with internal names. Closest to ASM behavior.
* Baseline for {@link #identicalLookup}.
*/
@Benchmark
public void internalNameLookup(Blackhole bh) {
for (var name : internalNames) {
bh.consume(builder.classEntry(builder.utf8Entry(name)));
}
}
/**
* The default implementation provided by {@link ConstantPoolBuilder#classEntry(ClassDesc)}.
* Does substring so needs to rehash and has no caching, should be very slow.
*/
@Benchmark
public void oldStyleLookup(Blackhole bh) {
for (var cd : classDescs) {
var s = cd.isClassOrInterface() ? toInternalName(cd) : cd.descriptorString();
bh.consume(builder.classEntry(builder.utf8Entry(s)));
}
}
/**
* Measures performance of creating new class entries in new constant pools with symbols.
*/
@Benchmark
public void freshCreationWithDescs(Blackhole bh) {
var cp = ConstantPoolBuilder.of();
for (var cd : nonDuplicateClassDescs) {
bh.consume(cp.classEntry(cd));
}
}
/**
* Measures performance of creating new class entries in new constant pools with internal names.
*/
@Benchmark
public void freshCreationWithInternalNames(Blackhole bh) {
var cp = ConstantPoolBuilder.of();
for (var name : nonDuplicateInternalNames) {
bh.consume(cp.classEntry(cp.utf8Entry(name)));
}
}
}