8338546: Speed up ConstantPoolBuilder::classEntry(ClassDesc)
Reviewed-by: asotona, redestad
This commit is contained in:
parent
279086d4ce
commit
caa751c561
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user