// 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 entries = []; final Map _canonicalizationCache = {}; 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 argNames = const [], }) => _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? 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 {}