Files
sdk/pkg/dart2bytecode/lib/constant_pool.dart
T
Alexander Markov 6195ea86bc [vm] Remove irregular type arguments parameter from factories
In the VM, factory constructors always had an extra "type arguments"
parameter, even if class is not generic. Factory constructor bodies
were using class type parameters instead of function type parameters.

This results in extra code when calling non-generic factories
which is slightly inefficient in terms of code size and performance.
Also, it creates an additional complexity throughout the system as
factories should be special cased in many places.

This change removes artificial "type arguments" parameter, treating
factory constructors basically as static methods. This matches
kernel AST representation.

TEST=ci

Change-Id: I957583cb2ce9a3c408699880a04036e06b01dd31
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501762
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
2026-05-12 06:11:15 -07:00

928 lines
24 KiB
Dart

// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:kernel/ast.dart';
import 'dbc.dart' show constantPoolIndexLimit, BytecodeLimitExceededException;
import 'bytecode_serialization.dart'
show
BufferedWriter,
BufferedReader,
BytecodeSizeStatistics,
NamedEntryStatistics,
StringTable;
import 'object_table.dart' show ObjectHandle, ObjectTable;
enum ConstantTag {
kInvalid,
kObjectRef,
kClass,
kType,
kStaticField,
kInstanceField,
kTypeArgumentsField,
kClosureFunction,
kEndClosureFunctionScope,
kSubtypeTestCache,
kEmptyTypeArguments,
kDirectCall,
kInterfaceCall,
kInstantiatedInterfaceCall,
kDynamicCall,
kExternalCall,
kFfiCall,
kDeferredLibraryPrefix,
kAllocateClosure,
}
String constantTagToString(ConstantTag tag) =>
tag.toString().substring('ConstantTag.k'.length);
abstract class ConstantPoolEntry {
const ConstantPoolEntry();
ConstantTag get tag;
// Returns number of extra reserved constant pool entries
// following this entry.
int get numReservedEntries => 0;
void write(BufferedWriter writer) {
writer.writeByte(tag.index);
writeValue(writer);
}
void writeValue(BufferedWriter writer);
factory ConstantPoolEntry.read(BufferedReader reader) {
ConstantTag tag = ConstantTag.values[reader.readByte()];
switch (tag) {
case ConstantTag.kInvalid:
break;
case ConstantTag.kStaticField:
return new ConstantStaticField.read(reader);
case ConstantTag.kInstanceField:
return new ConstantInstanceField.read(reader);
case ConstantTag.kClass:
return new ConstantClass.read(reader);
case ConstantTag.kTypeArgumentsField:
return new ConstantTypeArgumentsField.read(reader);
case ConstantTag.kType:
return new ConstantType.read(reader);
case ConstantTag.kClosureFunction:
return new ConstantClosureFunction.read(reader);
case ConstantTag.kEndClosureFunctionScope:
return new ConstantEndClosureFunctionScope.read(reader);
case ConstantTag.kSubtypeTestCache:
return new ConstantSubtypeTestCache.read(reader);
case ConstantTag.kEmptyTypeArguments:
return new ConstantEmptyTypeArguments.read(reader);
case ConstantTag.kObjectRef:
return new ConstantObjectRef.read(reader);
case ConstantTag.kDirectCall:
return new ConstantDirectCall.read(reader);
case ConstantTag.kInterfaceCall:
return new ConstantInterfaceCall.read(reader);
case ConstantTag.kInstantiatedInterfaceCall:
return new ConstantInstantiatedInterfaceCall.read(reader);
case ConstantTag.kDynamicCall:
return new ConstantDynamicCall.read(reader);
case ConstantTag.kExternalCall:
return new ConstantExternalCall.read(reader);
case ConstantTag.kFfiCall:
return new ConstantFfiCall.read(reader);
case ConstantTag.kDeferredLibraryPrefix:
return new ConstantDeferredLibraryPrefix.read(reader);
case ConstantTag.kAllocateClosure:
return new ConstantAllocateClosure.read(reader);
}
throw 'Unexpected constant tag $tag';
}
}
enum InvocationKind {
method, // x.foo(...) or foo(...)
getter, // x.foo
setter, // x.foo = ...
}
class ConstantStaticField extends ConstantPoolEntry {
final ObjectHandle field;
ConstantStaticField(this.field);
@override
ConstantTag get tag => ConstantTag.kStaticField;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedObject(field);
}
ConstantStaticField.read(BufferedReader reader)
: field = reader.readPackedObject();
@override
String toString() => 'StaticField $field';
@override
int get hashCode => field.hashCode;
@override
bool operator ==(other) =>
other is ConstantStaticField && this.field == other.field;
}
class ConstantInstanceField extends ConstantPoolEntry {
final ObjectHandle field;
int get numReservedEntries => 1;
ConstantInstanceField(this.field);
@override
ConstantTag get tag => ConstantTag.kInstanceField;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedObject(field);
}
ConstantInstanceField.read(BufferedReader reader)
: field = reader.readPackedObject();
@override
String toString() => 'InstanceField $field';
@override
int get hashCode => field.hashCode;
@override
bool operator ==(other) =>
other is ConstantInstanceField && this.field == other.field;
}
class ConstantClass extends ConstantPoolEntry {
final ObjectHandle classHandle;
ConstantClass(this.classHandle);
@override
ConstantTag get tag => ConstantTag.kClass;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedObject(classHandle);
}
ConstantClass.read(BufferedReader reader)
: classHandle = reader.readPackedObject();
@override
String toString() => 'Class $classHandle';
@override
int get hashCode => classHandle.hashCode;
@override
bool operator ==(other) =>
other is ConstantClass && this.classHandle == other.classHandle;
}
class ConstantTypeArgumentsField extends ConstantPoolEntry {
final ObjectHandle classHandle;
ConstantTypeArgumentsField(this.classHandle);
@override
ConstantTag get tag => ConstantTag.kTypeArgumentsField;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedObject(classHandle);
}
ConstantTypeArgumentsField.read(BufferedReader reader)
: classHandle = reader.readPackedObject();
@override
String toString() => 'TypeArgumentsField $classHandle';
@override
int get hashCode => classHandle.hashCode;
@override
bool operator ==(other) =>
other is ConstantTypeArgumentsField &&
this.classHandle == other.classHandle;
}
class ConstantType extends ConstantPoolEntry {
final ObjectHandle type;
ConstantType(this.type);
@override
ConstantTag get tag => ConstantTag.kType;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedObject(type);
}
ConstantType.read(BufferedReader reader) : type = reader.readPackedObject();
@override
String toString() => 'Type $type';
@override
int get hashCode => type.hashCode;
@override
bool operator ==(other) => other is ConstantType && this.type == other.type;
}
class ConstantClosureFunction extends ConstantPoolEntry {
final int closureIndex;
ConstantClosureFunction(this.closureIndex);
@override
ConstantTag get tag => ConstantTag.kClosureFunction;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedUInt30(closureIndex);
}
ConstantClosureFunction.read(BufferedReader reader)
: closureIndex = reader.readPackedUInt30();
@override
String toString() {
return 'ClosureFunction $closureIndex';
}
@override
int get hashCode => closureIndex;
@override
bool operator ==(other) =>
other is ConstantClosureFunction &&
this.closureIndex == other.closureIndex;
}
class ConstantEndClosureFunctionScope extends ConstantPoolEntry {
ConstantEndClosureFunctionScope();
@override
ConstantTag get tag => ConstantTag.kEndClosureFunctionScope;
@override
void writeValue(BufferedWriter writer) {}
ConstantEndClosureFunctionScope.read(BufferedReader reader) {}
@override
String toString() => 'EndClosureFunctionScope';
// ConstantEndClosureFunctionScope entries are created per closure and should
// not be merged, so ConstantEndClosureFunctionScope class uses identity
// [hashCode] and [operator ==].
}
class ConstantSubtypeTestCache extends ConstantPoolEntry {
ConstantSubtypeTestCache();
@override
ConstantTag get tag => ConstantTag.kSubtypeTestCache;
@override
void writeValue(BufferedWriter writer) {}
ConstantSubtypeTestCache.read(BufferedReader reader);
@override
String toString() => 'SubtypeTestCache';
// ConstantSubtypeTestCache entries are created per subtype test site and
// should not be merged, so ConstantSubtypeTestCache class uses identity
// [hashCode] and [operator ==].
@override
int get hashCode => identityHashCode(this);
@override
bool operator ==(other) => identical(this, other);
}
class ConstantEmptyTypeArguments extends ConstantPoolEntry {
const ConstantEmptyTypeArguments();
@override
ConstantTag get tag => ConstantTag.kEmptyTypeArguments;
@override
void writeValue(BufferedWriter writer) {}
ConstantEmptyTypeArguments.read(BufferedReader reader);
@override
String toString() => 'EmptyTypeArguments';
@override
int get hashCode => 997;
@override
bool operator ==(other) => other is ConstantEmptyTypeArguments;
}
class ConstantObjectRef extends ConstantPoolEntry {
final ObjectHandle? object;
ConstantObjectRef(this.object);
@override
ConstantTag get tag => ConstantTag.kObjectRef;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedObject(object);
}
ConstantObjectRef.read(BufferedReader reader)
: object = reader.readPackedObject();
@override
String toString() => 'ObjectRef $object';
@override
int get hashCode => object.hashCode;
@override
bool operator ==(other) =>
other is ConstantObjectRef && this.object == other.object;
}
class ConstantDirectCall extends ConstantPoolEntry {
final ObjectHandle target;
final ObjectHandle argDesc;
ConstantDirectCall(this.target, this.argDesc);
// Reserve 1 extra slot for arguments descriptor, following target slot.
int get numReservedEntries => 1;
@override
ConstantTag get tag => ConstantTag.kDirectCall;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedObject(target);
writer.writePackedObject(argDesc);
}
ConstantDirectCall.read(BufferedReader reader)
: target = reader.readPackedObject(),
argDesc = reader.readPackedObject();
@override
String toString() => "DirectCall '$target', $argDesc";
@override
int get hashCode => _combineHashes(target.hashCode, argDesc.hashCode);
@override
bool operator ==(other) =>
other is ConstantDirectCall &&
this.target == other.target &&
this.argDesc == other.argDesc;
}
class ConstantInterfaceCall extends ConstantPoolEntry {
final ObjectHandle target;
final ObjectHandle argDesc;
ConstantInterfaceCall(this.target, this.argDesc);
// Reserve 1 extra slot for arguments descriptor, following target slot.
int get numReservedEntries => 1;
@override
ConstantTag get tag => ConstantTag.kInterfaceCall;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedObject(target);
writer.writePackedObject(argDesc);
}
ConstantInterfaceCall.read(BufferedReader reader)
: target = reader.readPackedObject(),
argDesc = reader.readPackedObject();
@override
String toString() => "InterfaceCall '$target', $argDesc";
@override
int get hashCode => _combineHashes(target.hashCode, argDesc.hashCode);
@override
bool operator ==(other) =>
other is ConstantInterfaceCall &&
this.target == other.target &&
this.argDesc == other.argDesc;
}
class ConstantInstantiatedInterfaceCall extends ConstantPoolEntry {
final ObjectHandle target;
final ObjectHandle argDesc;
final ObjectHandle staticReceiverType;
ConstantInstantiatedInterfaceCall(
this.target,
this.argDesc,
this.staticReceiverType,
);
// Reserve 2 extra slots (3 slots total).
int get numReservedEntries => 2;
@override
ConstantTag get tag => ConstantTag.kInstantiatedInterfaceCall;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedObject(target);
writer.writePackedObject(argDesc);
writer.writePackedObject(staticReceiverType);
}
ConstantInstantiatedInterfaceCall.read(BufferedReader reader)
: target = reader.readPackedObject(),
argDesc = reader.readPackedObject(),
staticReceiverType = reader.readPackedObject();
@override
String toString() =>
"InstantiatedInterfaceCall '$target', $argDesc, receiver $staticReceiverType";
@override
int get hashCode => _combineHashes(
_combineHashes(target.hashCode, argDesc.hashCode),
staticReceiverType.hashCode,
);
@override
bool operator ==(other) =>
other is ConstantInstantiatedInterfaceCall &&
this.target == other.target &&
this.argDesc == other.argDesc &&
this.staticReceiverType == other.staticReceiverType;
}
class ConstantDynamicCall extends ConstantPoolEntry {
final ObjectHandle selectorName;
final ObjectHandle argDesc;
ConstantDynamicCall(this.selectorName, this.argDesc);
// Reserve 1 extra slot for arguments descriptor, following selector slot.
int get numReservedEntries => 1;
@override
ConstantTag get tag => ConstantTag.kDynamicCall;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedObject(selectorName);
writer.writePackedObject(argDesc);
}
ConstantDynamicCall.read(BufferedReader reader)
: selectorName = reader.readPackedObject(),
argDesc = reader.readPackedObject();
@override
String toString() => 'DynamicCall $selectorName, $argDesc';
@override
int get hashCode => _combineHashes(selectorName.hashCode, argDesc.hashCode);
@override
bool operator ==(other) =>
other is ConstantDynamicCall &&
this.selectorName == other.selectorName &&
this.argDesc == other.argDesc;
}
class ConstantExternalCall extends ConstantPoolEntry {
ConstantExternalCall();
@override
ConstantTag get tag => ConstantTag.kExternalCall;
@override
int get numReservedEntries => 1;
@override
void writeValue(BufferedWriter writer) {}
ConstantExternalCall.read(BufferedReader reader);
@override
String toString() => 'ExternalCall';
// Do not merge ConstantExternalCall entries.
@override
int get hashCode => identityHashCode(this);
@override
bool operator ==(other) => identical(this, other);
}
class ConstantFfiCall extends ConstantPoolEntry {
ConstantFfiCall();
@override
ConstantTag get tag => ConstantTag.kFfiCall;
@override
void writeValue(BufferedWriter writer) {}
ConstantFfiCall.read(BufferedReader reader);
@override
String toString() => 'FfiCall';
// Do not merge ConstantFfiCall entries.
@override
int get hashCode => identityHashCode(this);
@override
bool operator ==(other) => identical(this, other);
}
class ConstantDeferredLibraryPrefix extends ConstantPoolEntry {
final ObjectHandle name;
final ObjectHandle enclosingLibrary;
final ObjectHandle targetLibrary;
ConstantDeferredLibraryPrefix(
this.name,
this.enclosingLibrary,
this.targetLibrary,
);
@override
ConstantTag get tag => ConstantTag.kDeferredLibraryPrefix;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedObject(name);
writer.writePackedObject(enclosingLibrary);
writer.writePackedObject(targetLibrary);
}
ConstantDeferredLibraryPrefix.read(BufferedReader reader)
: name = reader.readPackedObject(),
enclosingLibrary = reader.readPackedObject(),
targetLibrary = reader.readPackedObject();
@override
String toString() =>
'DeferredLibraryPrefix $name, $enclosingLibrary -> $targetLibrary';
@override
int get hashCode => _combineHashes(
name.hashCode,
_combineHashes(enclosingLibrary.hashCode, targetLibrary.hashCode),
);
@override
bool operator ==(other) =>
other is ConstantDeferredLibraryPrefix &&
this.name == other.name &&
this.enclosingLibrary == other.enclosingLibrary &&
this.targetLibrary == other.targetLibrary;
}
class ConstantAllocateClosure extends ConstantPoolEntry {
static const int hasDelayedTypeArguments = 1 << 0;
static const int hasInstantiatorTypeArguments = 1 << 1;
static const int hasFunctionTypeArguments = 1 << 2;
final int closureIndex;
final int numElements;
final int flags;
ConstantAllocateClosure(this.closureIndex, this.numElements, this.flags);
@override
ConstantTag get tag => ConstantTag.kAllocateClosure;
// 2 slots: function, encoded length and flags.
@override
int get numReservedEntries => 1;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedUInt30(closureIndex);
writer.writePackedUInt30(numElements);
writer.writePackedUInt30(flags);
}
ConstantAllocateClosure.read(BufferedReader reader)
: closureIndex = reader.readPackedUInt30(),
numElements = reader.readPackedUInt30(),
flags = reader.readPackedUInt30();
@override
String toString() {
return 'AllocateClosure $closureIndex, num-elements: $numElements, flags: $flags';
}
@override
int get hashCode => closureIndex.hashCode;
@override
bool operator ==(other) =>
other is ConstantAllocateClosure &&
this.closureIndex == other.closureIndex &&
this.flags == other.flags &&
this.numElements == other.numElements;
}
/// Reserved constant pool entry.
class _ReservedConstantPoolEntry extends ConstantPoolEntry {
const _ReservedConstantPoolEntry();
ConstantTag get tag => throw 'This constant pool entry is reserved';
void writeValue(BufferedWriter writer) =>
throw 'This constant pool entry is reserved';
@override
String toString() => 'Reserved';
}
class ConstantPool {
final StringTable stringTable;
final ObjectTable objectTable;
final List<ConstantPoolEntry> entries = <ConstantPoolEntry>[];
final Map<ConstantPoolEntry, int> _canonicalizationCache =
<ConstantPoolEntry, int>{};
ConstantPool(this.stringTable, this.objectTable);
int addString(String value) => addObjectRef(new StringConstant(value));
int addName(String name) =>
_add(new ConstantObjectRef(objectTable.getPublicNameHandle(name)));
int addArgDesc(
int numArguments, {
int numTypeArgs = 0,
List<String> argNames = const <String>[],
}) => _add(
new ConstantObjectRef(
objectTable.getArgDescHandle(numArguments, numTypeArgs, argNames),
),
);
int addArgDescByArguments(Arguments args, {bool hasReceiver = false}) => _add(
new ConstantObjectRef(
objectTable.getArgDescHandleByArguments(args, hasReceiver: hasReceiver),
),
);
int addDirectCall(
InvocationKind invocationKind,
Member target,
ObjectHandle argDesc,
) {
final targetHandle = objectTable.getMemberHandle(
target,
isGetter: invocationKind == InvocationKind.getter,
isSetter: invocationKind == InvocationKind.setter,
);
return _add(new ConstantDirectCall(targetHandle, argDesc));
}
int addInterfaceCall(
InvocationKind invocationKind,
Member target,
ObjectHandle argDesc,
) => _add(
new ConstantInterfaceCall(
objectTable.getMemberHandle(
target,
isGetter: invocationKind == InvocationKind.getter,
isSetter: invocationKind == InvocationKind.setter,
),
argDesc,
),
);
int addInstantiatedInterfaceCall(
InvocationKind invocationKind,
Member target,
ObjectHandle argDesc,
DartType staticReceiverType,
) => _add(
new ConstantInstantiatedInterfaceCall(
objectTable.getMemberHandle(
target,
isGetter: invocationKind == InvocationKind.getter,
isSetter: invocationKind == InvocationKind.setter,
),
argDesc,
objectTable.getHandle(staticReceiverType)!,
),
);
int addDynamicCall(
InvocationKind invocationKind,
Name selector,
ObjectHandle argDesc,
) => _add(
new ConstantDynamicCall(
objectTable.getSelectorNameHandle(
selector,
isGetter: invocationKind == InvocationKind.getter,
isSetter: invocationKind == InvocationKind.setter,
),
argDesc,
),
);
int addInstanceCall(
InvocationKind invocationKind,
Member? target,
Name targetName,
ObjectHandle argDesc,
) => (target == null)
? addDynamicCall(invocationKind, targetName, argDesc)
: addInterfaceCall(invocationKind, target, argDesc);
int addExternalCall() => _add(ConstantExternalCall());
int addFfiCall() => _add(ConstantFfiCall());
int addStaticField(Field field) =>
_add(new ConstantStaticField(objectTable.getHandle(field)!));
int addInstanceField(Field field) =>
_add(new ConstantInstanceField(objectTable.getHandle(field)!));
int addClass(Class node) =>
_add(new ConstantClass(objectTable.getHandle(node)!));
int addTypeArgumentsField(Class node) =>
_add(new ConstantTypeArgumentsField(objectTable.getHandle(node)!));
int addType(DartType type) =>
_add(new ConstantType(objectTable.getHandle(type)!));
int addTypeArguments(List<DartType>? typeArgs) =>
_add(new ConstantObjectRef(objectTable.getTypeArgumentsHandle(typeArgs)));
int addClosureFunction(int closureIndex) =>
_add(new ConstantClosureFunction(closureIndex));
int addEndClosureFunctionScope() =>
_add(new ConstantEndClosureFunctionScope());
int addSubtypeTestCache() => _add(new ConstantSubtypeTestCache());
int addEmptyTypeArguments() => _add(const ConstantEmptyTypeArguments());
int addDeferredLibraryPrefix(
String name,
Library enclosingLibrary,
Library targetLibrary,
) => _add(
ConstantDeferredLibraryPrefix(
objectTable.getConstStringHandle(name),
objectTable.getHandle(enclosingLibrary)!,
objectTable.getHandle(targetLibrary)!,
),
);
int addObjectRef(Node? node) {
// Constant objects should not depend on the type parameters of
// the enclosing function.
return objectTable.withoutEnclosingFunctionTypeParameters(
() => _add(new ConstantObjectRef(objectTable.getHandle(node))),
);
}
int addSelectorName(Name name, InvocationKind invocationKind) => _add(
new ConstantObjectRef(
objectTable.getSelectorNameHandle(
name,
isGetter: invocationKind == InvocationKind.getter,
isSetter: invocationKind == InvocationKind.setter,
),
),
);
int addAllocateClosure(
int closureIndex,
int numElements, {
required bool hasDelayedTypeArguments,
required bool hasInstantiatorTypeArguments,
required bool hasFunctionTypeArguments,
}) => _add(
ConstantAllocateClosure(
closureIndex,
numElements,
(hasDelayedTypeArguments
? ConstantAllocateClosure.hasDelayedTypeArguments
: 0) |
(hasInstantiatorTypeArguments
? ConstantAllocateClosure.hasInstantiatorTypeArguments
: 0) |
(hasFunctionTypeArguments
? ConstantAllocateClosure.hasFunctionTypeArguments
: 0),
),
);
int _add(ConstantPoolEntry entry) {
int? index = _canonicalizationCache[entry];
if (index == null) {
index = entries.length;
if (index >= constantPoolIndexLimit) {
throw new ConstantPoolIndexOverflowException();
}
_addEntry(entry);
_canonicalizationCache[entry] = index;
}
return index;
}
void _addEntry(ConstantPoolEntry entry) {
entries.add(entry);
for (int i = 0; i < entry.numReservedEntries; ++i) {
entries.add(const _ReservedConstantPoolEntry());
}
}
void write(BufferedWriter writer) {
final start = writer.offset;
if (BytecodeSizeStatistics.constantPoolStats.isEmpty) {
for (var tag in ConstantTag.values) {
BytecodeSizeStatistics.constantPoolStats.add(
new NamedEntryStatistics(constantTagToString(tag)),
);
}
}
writer.writePackedUInt30(entries.length);
entries.forEach((e) {
if (e is _ReservedConstantPoolEntry) {
return;
}
final entryStart = writer.offset;
e.write(writer);
final entryStat = BytecodeSizeStatistics.constantPoolStats[e.tag.index];
entryStat.size += (writer.offset - entryStart);
++entryStat.count;
});
BytecodeSizeStatistics.constantPoolSize += (writer.offset - start);
}
ConstantPool.read(BufferedReader reader)
: stringTable = reader.stringReader as StringTable,
objectTable = reader.objectReader as ObjectTable {
int len = reader.readPackedUInt30();
for (int i = 0; i < len; i++) {
final e = new ConstantPoolEntry.read(reader);
_addEntry(e);
i += e.numReservedEntries;
}
}
@override
String toString() {
StringBuffer sb = new StringBuffer();
sb.writeln('ConstantPool {');
for (int i = 0; i < entries.length; i++) {
sb.writeln(' [$i] = ${entries[i]}');
}
sb.writeln('}');
return sb.toString();
}
}
int _combineHashes(int hash1, int hash2) =>
(((hash1 * 31) & 0x3fffffff) + hash2) & 0x3fffffff;
class ConstantPoolIndexOverflowException
extends BytecodeLimitExceededException {}