// 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 'package:kernel/core_types.dart' show CoreTypes; import 'bytecode_serialization.dart' show BufferedWriter, BufferedReader, BytecodeObject, BytecodeSizeStatistics, ForwardReference, NamedEntryStatistics, doubleToIntBits, intBitsToDouble, ObjectReader, ObjectWriter, StringWriter; import 'declarations.dart' show SourceFile, TypeParametersDeclaration; enum ObjectKind { kInvalid, kLibrary, kScript, kClass, kMember, kClosure, kName, kConstObject, kType, kTypeArguments, kArgDesc, } enum ConstTag { kInvalid, kInt, kDouble, kBool, kString, kSymbol, kInstance, kList, kMap, kSet, kRecord, kTearOff, kTearOffInstantiation, } enum TypeTag { kInvalid, kDynamic, kVoid, kNull, kNever, kSimpleType, kGenericType, kTypeParameter, kFunctionType, kRecordType, } /// Name of artificial class containing top-level members of a library. const String topLevelClassName = ''; String objectKindToString(ObjectKind kind) => kind.toString().substring('ObjectKind.k'.length); String nullabilityToString(Nullability nullability) { switch (nullability) { case Nullability.nullable: return '?'; case Nullability.undetermined: return '%'; case Nullability.nonNullable: return ''; } } /// Represents object (library, class, member, closure, type or name) in the /// object table. abstract class ObjectHandle extends BytecodeObject { static const int referenceBit = 1 << 0; static const int indexShift = 1; static const int inlineObject = -1; static const int kindShift = 1; static const int kindMask = 0x0F; static const int flagBit0 = 1 << 5; static const int flagBit1 = 1 << 6; static const int flagBit2 = 1 << 7; static const int flagBit3 = 1 << 8; static const int flagBit4 = 1 << 9; static const int flagBit5 = 1 << 10; static const int flagsMask = flagBit0 | flagBit1 | flagBit2 | flagBit3 | flagBit4 | flagBit5; static int _makeReference(int index) => (index << indexShift) | referenceBit; static int _getIndexFromReference(int reference) { assert((reference & referenceBit) != 0); return reference >> indexShift; } static int _makeHeader(ObjectKind kind, int flags) { assert((kind.index & kindMask) == kind.index); assert((flags & flagsMask) == flags); return (kind.index << kindShift) | flags; } static ObjectKind _getKindFromHeader(int header) { assert((header & referenceBit) == 0); return ObjectKind.values[(header >> kindShift) & kindMask]; } static int _getFlagsFromHeader(int header) { assert((header & referenceBit) == 0); return header & flagsMask; } int _useCount = 0; int? _reference; ObjectHandle(); ObjectKind get kind; int get flags => 0; set flags(int value) {} bool get isCacheable => true; bool get shouldBeIncludedIntoIndexTable => _useCount >= ObjectTable.indexTableUseCountThreshold && isCacheable; factory ObjectHandle._empty(ObjectKind kind, int flags) { switch (kind) { case ObjectKind.kInvalid: return new _InvalidHandle(); case ObjectKind.kLibrary: return new _LibraryHandle._empty(); case ObjectKind.kClass: return new _ClassHandle._empty(); case ObjectKind.kMember: return new _MemberHandle._empty(); case ObjectKind.kClosure: return new _ClosureHandle._empty(); case ObjectKind.kName: return ((flags & _NameHandle.flagIsPublic) != 0) ? new _PublicNameHandle._empty() : _PrivateNameHandle._empty(); case ObjectKind.kTypeArguments: return new _TypeArgumentsHandle._empty(); case ObjectKind.kConstObject: return new _ConstObjectHandle._empty(); case ObjectKind.kArgDesc: return new _ArgDescHandle._empty(); case ObjectKind.kScript: return new _ScriptHandle._empty(); case ObjectKind.kType: final Nullability nullability = ((flags & _TypeHandle.flagIsNullable) != 0) ? Nullability.nullable : Nullability.nonNullable; switch (TypeTag.values[(flags & _TypeHandle.tagMask) ~/ flagBit0]) { case TypeTag.kInvalid: break; case TypeTag.kDynamic: return new _DynamicTypeHandle(); case TypeTag.kVoid: return new _VoidTypeHandle(); case TypeTag.kNull: return new _NullTypeHandle(); case TypeTag.kNever: return new _NeverTypeHandle(nullability); case TypeTag.kSimpleType: return new _SimpleTypeHandle._empty(nullability); case TypeTag.kTypeParameter: return new _TypeParameterHandle._empty(nullability); case TypeTag.kGenericType: return new _GenericTypeHandle._empty(nullability); case TypeTag.kFunctionType: return new _FunctionTypeHandle._empty(nullability); case TypeTag.kRecordType: return new _RecordTypeHandle._empty(nullability); } throw 'Unexpected type tag $flags'; } } void _write(BufferedWriter writer) { int header = _makeHeader(kind, flags); assert((header & referenceBit) == 0); writer.writePackedUInt30(header); writeContents(writer); } void writeContents(BufferedWriter writer); factory ObjectHandle._read(BufferedReader reader, int header) { assert((header & referenceBit) == 0); final ObjectKind kind = _getKindFromHeader(header); final int flags = _getFlagsFromHeader(header); final obj = new ObjectHandle._empty(kind, flags); obj.flags = flags; obj.readContents(reader); return obj; } void readContents(BufferedReader reader); void accountUsesForObjectCopies(int numCopies) {} void indexStrings(StringWriter strings) {} } class _InvalidHandle extends ObjectHandle { _InvalidHandle(); @override ObjectKind get kind => ObjectKind.kInvalid; @override void writeContents(BufferedWriter writer) {} @override void readContents(BufferedReader reader) {} @override String toString() => 'Invalid'; } class _LibraryHandle extends ObjectHandle { late _ConstObjectHandle uri; _LibraryHandle._empty(); _LibraryHandle(this.uri); @override ObjectKind get kind => ObjectKind.kLibrary; @override void writeContents(BufferedWriter writer) { writer.writePackedObject(uri); } @override void readContents(BufferedReader reader) { uri = reader.readPackedObject(); } @override void accountUsesForObjectCopies(int numCopies) { uri._useCount += numCopies; } @override int get hashCode => uri.hashCode + 11; @override bool operator ==(other) => other is _LibraryHandle && this.uri == other.uri; @override String toString() => uri.value; } class _ClassHandle extends ObjectHandle { late _LibraryHandle library; late _NameHandle name; _ClassHandle._empty(); _ClassHandle(this.library, this.name); @override ObjectKind get kind => ObjectKind.kClass; @override void writeContents(BufferedWriter writer) { writer.writePackedObject(library); writer.writePackedObject(name); } @override void readContents(BufferedReader reader) { library = reader.readPackedObject(); name = reader.readPackedObject(); } @override void accountUsesForObjectCopies(int numCopies) { library._useCount += numCopies; name._useCount += numCopies; } @override int get hashCode => _combineHashes(library.hashCode, name.hashCode); @override bool operator ==(other) => other is _ClassHandle && this.library == other.library && this.name == other.name; @override String toString() => name.name == topLevelClassName ? '$library' : '$library::${name.name}'; } class _MemberHandle extends ObjectHandle { static const int flagIsField = ObjectHandle.flagBit0; static const int flagIsConstructor = ObjectHandle.flagBit1; int _flags = 0; late _ClassHandle parent; late _NameHandle name; _MemberHandle._empty(); _MemberHandle(this.parent, this.name, bool isField, bool isConstructor) { if (isField) { _flags |= flagIsField; } if (isConstructor) { _flags |= flagIsConstructor; } } @override ObjectKind get kind => ObjectKind.kMember; @override int get flags => _flags; @override set flags(int value) { _flags = value; } @override void writeContents(BufferedWriter writer) { writer.writePackedObject(parent); writer.writePackedObject(name); } @override void readContents(BufferedReader reader) { parent = reader.readPackedObject(); name = reader.readPackedObject(); } @override void accountUsesForObjectCopies(int numCopies) { parent._useCount += numCopies; name._useCount += numCopies; } @override int get hashCode => _combineHashes(parent.hashCode, name.hashCode); @override bool operator ==(other) => other is _MemberHandle && this.parent == other.parent && this.name == other.name && this.flags == other.flags; @override String toString() => '$parent::${name.name}' + (flags & flagIsField != 0 ? ' (field)' : '') + (flags & flagIsConstructor != 0 ? ' (constructor)' : ''); } class _ClosureHandle extends ObjectHandle { late _MemberHandle enclosingMember; late int closureIndex; _ClosureHandle._empty(); _ClosureHandle(this.enclosingMember, this.closureIndex) { assert(closureIndex >= 0); } @override ObjectKind get kind => ObjectKind.kClosure; @override void writeContents(BufferedWriter writer) { writer.writePackedObject(enclosingMember); writer.writePackedUInt30(closureIndex); } @override void readContents(BufferedReader reader) { enclosingMember = reader.readPackedObject(); closureIndex = reader.readPackedUInt30(); } @override void accountUsesForObjectCopies(int numCopies) { enclosingMember._useCount += numCopies; } @override int get hashCode => _combineHashes(enclosingMember.hashCode, closureIndex); @override bool operator ==(other) => other is _ClosureHandle && this.enclosingMember == other.enclosingMember && this.closureIndex == other.closureIndex; @override String toString() => '$enclosingMember::Closure/$closureIndex'; } abstract class _TypeHandle extends ObjectHandle { static const int tagMask = ObjectHandle.flagBit0 | ObjectHandle.flagBit1 | ObjectHandle.flagBit2 | ObjectHandle.flagBit3; static const int flagIsNullable = ObjectHandle.flagBit4; final TypeTag tag; Nullability nullability; _TypeHandle(this.tag, this.nullability); @override ObjectKind get kind => ObjectKind.kType; @override int get flags => (tag.index * ObjectHandle.flagBit0) | (nullability == Nullability.nullable ? flagIsNullable : 0); @override set flags(int value) { if (value != flags) { throw 'Unable to set flags for _TypeHandle (they are occupied by type tag and nnbd)'; } } } class _DynamicTypeHandle extends _TypeHandle { _DynamicTypeHandle() : super(TypeTag.kDynamic, Nullability.nullable); @override void writeContents(BufferedWriter writer) {} @override void readContents(BufferedReader reader) {} @override int get hashCode => 2029; @override bool operator ==(other) => other is _DynamicTypeHandle; @override String toString() => 'dynamic'; } class _VoidTypeHandle extends _TypeHandle { _VoidTypeHandle() : super(TypeTag.kVoid, Nullability.nullable); @override void writeContents(BufferedWriter writer) {} @override void readContents(BufferedReader reader) {} @override int get hashCode => 2039; @override bool operator ==(other) => other is _VoidTypeHandle; @override String toString() => 'void'; } class _NullTypeHandle extends _TypeHandle { _NullTypeHandle() : super(TypeTag.kNull, Nullability.nullable); @override void writeContents(BufferedWriter writer) {} @override void readContents(BufferedReader reader) {} @override int get hashCode => 2057; @override bool operator ==(other) => other is _NullTypeHandle; @override String toString() => 'Null'; } class _NeverTypeHandle extends _TypeHandle { _NeverTypeHandle(Nullability nullability) : super(TypeTag.kNever, nullability); @override void writeContents(BufferedWriter writer) {} @override void readContents(BufferedReader reader) {} @override int get hashCode => _combineHashes(2049, nullability.index); @override bool operator ==(other) => other is _NeverTypeHandle && this.nullability == other.nullability; @override String toString() => 'Never${nullabilityToString(nullability)}'; } class _SimpleTypeHandle extends _TypeHandle { late _ClassHandle class_; _SimpleTypeHandle._empty(Nullability nullability) : super(TypeTag.kSimpleType, nullability); _SimpleTypeHandle(this.class_, Nullability nullability) : super(TypeTag.kSimpleType, nullability); @override void writeContents(BufferedWriter writer) { writer.writePackedObject(class_); } @override void readContents(BufferedReader reader) { class_ = reader.readPackedObject(); } @override void accountUsesForObjectCopies(int numCopies) { class_._useCount += numCopies; } @override int get hashCode => _combineHashes(class_.hashCode, nullability.index); @override bool operator ==(other) => other is _SimpleTypeHandle && this.class_ == other.class_ && this.nullability == other.nullability; @override String toString() => '$class_${nullabilityToString(nullability)}'; } class TypeParameterDeclaration { final ObjectHandle name; final ObjectHandle? bound; final ObjectHandle? defaultType; TypeParameterDeclaration(this.name, this.bound, this.defaultType); @override int get hashCode => _combineHashes( name.hashCode, _combineHashes(bound.hashCode, defaultType.hashCode), ); @override bool operator ==(other) => other is TypeParameterDeclaration && this.name == other.name && this.bound == other.bound && this.defaultType == other.defaultType; @override String toString() => '${name} extends $bound (default $defaultType)'; } class _TypeParameterHandle extends _TypeHandle { ObjectHandle? parent; late int indexInParent; _TypeParameterHandle._empty(Nullability nullability) : super(TypeTag.kTypeParameter, nullability); _TypeParameterHandle(this.parent, this.indexInParent, Nullability nullability) : super(TypeTag.kTypeParameter, nullability) { assert( parent is _ClassHandle || parent is _MemberHandle || parent is _ClosureHandle || parent == null, ); assert(indexInParent >= 0); } @override bool get isCacheable => (parent != null); @override void writeContents(BufferedWriter writer) { writer.writePackedObject(parent); writer.writePackedUInt30(indexInParent); } @override void readContents(BufferedReader reader) { parent = reader.readPackedObject(); indexInParent = reader.readPackedUInt30(); } @override void accountUsesForObjectCopies(int numCopies) { final parent = this.parent; if (parent != null) { parent._useCount += numCopies; } } @override int get hashCode => _combineHashes( parent.hashCode, _combineHashes(indexInParent, nullability.index), ); @override bool operator ==(other) => other is _TypeParameterHandle && this.parent == other.parent && this.indexInParent == other.indexInParent && this.nullability == other.nullability; @override String toString() => '$parent::TypeParam/$indexInParent${nullabilityToString(nullability)}'; } class _GenericTypeHandle extends _TypeHandle { late _ClassHandle class_; _TypeArgumentsHandle? typeArgs; _GenericTypeHandle._empty(Nullability nullability) : super(TypeTag.kGenericType, nullability); _GenericTypeHandle(this.class_, this.typeArgs, Nullability nullability) : super(TypeTag.kGenericType, nullability); @override bool get isCacheable => typeArgs?.isCacheable ?? true; @override void writeContents(BufferedWriter writer) { writer.writePackedObject(class_); writer.writePackedObject(typeArgs); } @override void readContents(BufferedReader reader) { class_ = reader.readPackedObject(); typeArgs = reader.readPackedObject(); } @override void accountUsesForObjectCopies(int numCopies) { class_._useCount += numCopies; final typeArgs = this.typeArgs; if (typeArgs != null) { typeArgs._useCount += numCopies; } } @override int get hashCode => _combineHashes( class_.hashCode, _combineHashes(typeArgs.hashCode, nullability.index), ); @override bool operator ==(other) => other is _GenericTypeHandle && this.class_ == other.class_ && this.typeArgs == other.typeArgs && this.nullability == other.nullability; @override String toString() => '$class_ $typeArgs${nullabilityToString(nullability)}'; } class NameAndType { _NameHandle name; _TypeHandle type; NameAndType(ObjectHandle name_, ObjectHandle type_) : name = name_ as _NameHandle, type = type_ as _TypeHandle; @override int get hashCode => _combineHashes(name.hashCode, type.hashCode); @override bool operator ==(other) => other is NameAndType && this.name == other.name && this.type == other.type; @override String toString() => '$type ${name.name}'; } class ParameterFlags { // Parameter flags in FunctionDeclaration, ClosureDeclaration and // FunctionType. static const isRequiredFlag = 1 << 0; // Parameter flags in Code. static const isCovariantFlag = 1 << 0; static const isCovariantByClassFlag = 1 << 1; static List? finalizeFlags(List paramFlags) { for (int flags in paramFlags) { if (flags != 0) { return paramFlags; } } return null; } static int getVariableDeclarationFlags( VariableDeclaration variable, bool isCode, ) { int flags = 0; if (isCode) { if (variable.isCovariantByDeclaration) { flags |= isCovariantFlag; } if (variable.isCovariantByClass) { flags |= isCovariantByClassFlag; } } else { if (variable.isRequired) { flags |= isRequiredFlag; } } return flags; } // Get parameter flags for the given [function]. // Returns null if parameters do not have any flags. static List? getFunctionFlags( FunctionNode function, { required bool isCode, }) { final paramFlags = []; if (isCode) { for (var param in function.positionalParameters) { paramFlags.add(getVariableDeclarationFlags(param, isCode)); } } for (var param in function.namedParameters) { paramFlags.add(getVariableDeclarationFlags(param, isCode)); } return finalizeFlags(paramFlags); } static int getNamedTypeFlags(NamedType param) { int flags = 0; if (param.isRequired) { flags |= isRequiredFlag; } return flags; } // Get flags for named parameters of the given [functionType]. // Returns null if named parameters do not have any flags. static List? getFunctionTypeFlags(FunctionType functionType) { final namedParameters = functionType.namedParameters; final paramFlags = List.generate( namedParameters.length, (i) => getNamedTypeFlags(namedParameters[i]), ); return finalizeFlags(paramFlags); } } class _FunctionTypeHandle extends _TypeHandle { static const int flagHasOptionalPositionalParams = 1 << 0; static const int flagHasOptionalNamedParams = 1 << 1; static const int flagHasTypeParams = 1 << 2; static const int flagHasEnclosingTypeParameters = 1 << 3; static const int flagHasParameterFlags = 1 << 4; int functionTypeFlags = 0; late int numEnclosingTypeParameters; TypeParametersDeclaration? typeParameters; late int numRequiredParams; late List<_TypeHandle> positionalParams; late List namedParams; // Only contains the required flag for named parameters when present. late List? parameterFlags; late _TypeHandle returnType; _FunctionTypeHandle._empty(Nullability nullability) : super(TypeTag.kFunctionType, nullability); _FunctionTypeHandle( this.numEnclosingTypeParameters, this.typeParameters, this.numRequiredParams, this.positionalParams, this.namedParams, this.parameterFlags, this.returnType, Nullability nullability, ) : super(TypeTag.kFunctionType, nullability) { assert(numRequiredParams <= positionalParams.length + namedParams.length); if (numRequiredParams < positionalParams.length) { assert(namedParams.isEmpty); functionTypeFlags |= flagHasOptionalPositionalParams; } if (namedParams.isNotEmpty) { assert(numRequiredParams == positionalParams.length); functionTypeFlags |= flagHasOptionalNamedParams; } if (parameterFlags != null) { assert(namedParams.isNotEmpty); assert(parameterFlags!.length == namedParams.length); functionTypeFlags |= flagHasParameterFlags; } if (typeParameters != null) { functionTypeFlags |= flagHasTypeParams; } if (numEnclosingTypeParameters != 0) { functionTypeFlags |= flagHasEnclosingTypeParameters; } } @override void writeContents(BufferedWriter writer) { writer.writePackedUInt30(functionTypeFlags); if ((functionTypeFlags & flagHasEnclosingTypeParameters) != 0) { writer.writePackedUInt30(numEnclosingTypeParameters); } if ((functionTypeFlags & flagHasTypeParams) != 0) { typeParameters!.write(writer); } writer.writePackedUInt30(positionalParams.length + namedParams.length); if (functionTypeFlags & (flagHasOptionalPositionalParams | flagHasOptionalNamedParams) != 0) { writer.writePackedUInt30(numRequiredParams); } for (var param in positionalParams) { writer.writePackedObject(param); } for (var param in namedParams) { writer.writePackedObject(param.name); writer.writePackedObject(param.type); } if ((functionTypeFlags & flagHasParameterFlags) != 0) { final parameterFlags = this.parameterFlags!; writer.writePackedUInt30(parameterFlags.length); parameterFlags.forEach((flags) => writer.writePackedUInt30(flags)); } writer.writePackedObject(returnType); } @override void readContents(BufferedReader reader) { functionTypeFlags = reader.readPackedUInt30(); numEnclosingTypeParameters = ((functionTypeFlags & flagHasEnclosingTypeParameters) != 0) ? reader.readPackedUInt30() : 0; typeParameters = ((functionTypeFlags & flagHasTypeParams) != 0) ? TypeParametersDeclaration.read(reader) : null; final int numParams = reader.readPackedUInt30(); numRequiredParams = numParams; if ((functionTypeFlags & (flagHasOptionalPositionalParams | flagHasOptionalNamedParams)) != 0) { numRequiredParams = reader.readPackedUInt30(); } final bool hasNamedParams = (functionTypeFlags & flagHasOptionalNamedParams) != 0; positionalParams = new List<_TypeHandle>.generate( hasNamedParams ? numRequiredParams : numParams, (_) => reader.readPackedObject(), ); if (hasNamedParams) { namedParams = new List.generate( numParams - numRequiredParams, (_) => new NameAndType( reader.readPackedObject(), reader.readPackedObject(), ), ); } else { namedParams = const []; } parameterFlags = ((functionTypeFlags & flagHasParameterFlags) != 0) ? List.generate( reader.readPackedUInt30(), (_) => reader.readPackedUInt30(), ) : null; returnType = reader.readPackedObject(); } @override void accountUsesForObjectCopies(int numCopies) { positionalParams.forEach((p) { p._useCount += numCopies; }); namedParams.forEach((p) { p.name._useCount += numCopies; p.type._useCount += numCopies; }); } @override bool get isCacheable { if ((functionTypeFlags & flagHasEnclosingTypeParameters) != 0) { return false; } for (var param in positionalParams) { if (!param.isCacheable) { return false; } } for (var param in namedParams) { if (!param.type.isCacheable) { return false; } } if (!returnType.isCacheable) { return false; } return true; } @override int get hashCode { int hash = typeParameters.hashCode; hash = _combineHashes(hash, numEnclosingTypeParameters); hash = _combineHashes(hash, numRequiredParams); hash = _combineHashes(hash, listHashCode(positionalParams)); hash = _combineHashes(hash, listHashCode(namedParams)); if (parameterFlags != null) { hash = _combineHashes(hash, listHashCode(parameterFlags!)); } hash = _combineHashes(hash, returnType.hashCode); hash = _combineHashes(hash, nullability.index); return hash; } @override bool operator ==(other) => other is _FunctionTypeHandle && this.numEnclosingTypeParameters == other.numEnclosingTypeParameters && this.typeParameters == other.typeParameters && this.numRequiredParams == other.numRequiredParams && listEquals(this.positionalParams, other.positionalParams) && listEquals(this.namedParams, other.namedParams) && (identical(this.parameterFlags, other.parameterFlags) || (this.parameterFlags != null && other.parameterFlags != null && listEquals(this.parameterFlags!, other.parameterFlags!))) && this.returnType == other.returnType && this.nullability == other.nullability; @override String toString() { StringBuffer sb = new StringBuffer(); sb.write('FunctionType'); if ((functionTypeFlags & flagHasEnclosingTypeParameters) != 0) { sb.write(' '); } if ((functionTypeFlags & flagHasTypeParams) != 0) { sb.write(' $typeParameters'); } sb.write(' ('); sb.write(positionalParams.sublist(0, numRequiredParams).join(', ')); if (numRequiredParams != positionalParams.length) { if (numRequiredParams > 0) { sb.write(', '); } sb.write('[ ${positionalParams.sublist(numRequiredParams).join(', ')} ]'); } if (namedParams.isNotEmpty) { if (numRequiredParams > 0) { sb.write(', '); } sb.write('{ '); final flags = this.parameterFlags; assert(flags == null || flags.length == namedParams.length); for (int i = 0; i < namedParams.length; ++i) { if (i != 0) { sb.write(', '); } // We only store the required flag. if (flags != null && (flags[i] & ParameterFlags.isRequiredFlag) != 0) { sb.write('required '); } sb.write(namedParams[i]); } sb.write(' }'); } sb.write(')${nullabilityToString(nullability)} -> '); sb.write(returnType); return sb.toString(); } } class _RecordTypeHandle extends _TypeHandle { late List<_TypeHandle> positional; late List named; _RecordTypeHandle._empty(Nullability nullability) : super(TypeTag.kRecordType, nullability); _RecordTypeHandle(this.positional, this.named, Nullability nullability) : super(TypeTag.kRecordType, nullability); @override void writeContents(BufferedWriter writer) { writer.writePackedUInt30(positional.length); writer.writePackedUInt30(named.length); for (var field in positional) { writer.writePackedObject(field); } for (var field in named) { writer.writePackedObject(field.name); writer.writePackedObject(field.type); } } @override void readContents(BufferedReader reader) { final int numPositional = reader.readPackedUInt30(); final int numNamed = reader.readPackedUInt30(); positional = List<_TypeHandle>.generate( numPositional, (_) => reader.readPackedObject(), ); named = List.generate( numNamed, (_) => new NameAndType(reader.readPackedObject(), reader.readPackedObject()), ); } @override void accountUsesForObjectCopies(int numCopies) { positional.forEach((f) { f._useCount += numCopies; }); named.forEach((f) { f.name._useCount += numCopies; f.type._useCount += numCopies; }); } @override bool get isCacheable { for (var f in positional) { if (!f.isCacheable) { return false; } } for (var f in named) { if (!f.type.isCacheable) { return false; } } return true; } @override int get hashCode { int hash = _combineHashes(listHashCode(positional), listHashCode(named)); hash = _combineHashes(hash, nullability.index); return hash; } @override bool operator ==(other) => other is _RecordTypeHandle && listEquals(this.positional, other.positional) && listEquals(this.named, other.named) && this.nullability == other.nullability; @override String toString() { StringBuffer sb = new StringBuffer(); sb.write('RecordType('); sb.write(positional.join(', ')); if (named.isNotEmpty) { if (positional.isNotEmpty) { sb.write(', '); } sb.write(named.join(', ')); } sb.write(')${nullabilityToString(nullability)}'); return sb.toString(); } } abstract class _NameHandle extends ObjectHandle { static const int flagIsPublic = ObjectHandle.flagBit0; String get name; @override ObjectKind get kind => ObjectKind.kName; @override void indexStrings(StringWriter strings) { strings.put(name); } @override String toString() => "'$name'"; } class _PublicNameHandle extends _NameHandle { late String name; _PublicNameHandle._empty(); _PublicNameHandle(this.name); @override int get flags => _NameHandle.flagIsPublic; @override void writeContents(BufferedWriter writer) { writer.writePackedStringReference(name); } @override void readContents(BufferedReader reader) { name = reader.readPackedStringReference(); } @override int get hashCode => name.hashCode; @override bool operator ==(other) => other is _PublicNameHandle && this.name == other.name; @override String toString() => "'$name'"; } class _PrivateNameHandle extends _NameHandle { late _LibraryHandle library; late String name; _PrivateNameHandle._empty(); _PrivateNameHandle(this.library, this.name); @override void writeContents(BufferedWriter writer) { writer.writePackedObject(library); writer.writePackedStringReference(name); } @override void readContents(BufferedReader reader) { library = reader.readPackedObject(); name = reader.readPackedStringReference(); } @override void accountUsesForObjectCopies(int numCopies) { library._useCount += numCopies; } @override int get hashCode => _combineHashes(name.hashCode, library.hashCode); @override bool operator ==(other) => other is _PrivateNameHandle && this.name == other.name && this.library == other.library; @override String toString() => "'$name'"; } class _TypeArgumentsHandle extends ObjectHandle { late List<_TypeHandle> args; _TypeArgumentsHandle._empty(); _TypeArgumentsHandle(this.args); @override ObjectKind get kind => ObjectKind.kTypeArguments; @override bool get isCacheable { for (var arg in args) { if (!arg.isCacheable) { return false; } } return true; } @override void writeContents(BufferedWriter writer) { writer.writePackedList(args); } @override void readContents(BufferedReader reader) { args = reader.readPackedList<_TypeHandle>(); } @override void accountUsesForObjectCopies(int numCopies) { args.forEach((t) { t._useCount += numCopies; }); } @override int get hashCode => listHashCode(args); @override bool operator ==(other) => other is _TypeArgumentsHandle && listEquals(this.args, other.args); @override String toString() => '< ${args.join(', ')} >'; } class _ConstObjectHandle extends ObjectHandle { late ConstTag tag; late dynamic value; ObjectHandle? type; int _hashCode = 0; _ConstObjectHandle._empty(); _ConstObjectHandle(this.tag, this.value, [this.type]); @override ObjectKind get kind => ObjectKind.kConstObject; @override int get flags => tag.index * ObjectHandle.flagBit0; @override set flags(int value) { tag = ConstTag.values[value ~/ ObjectHandle.flagBit0]; assert(tag != ConstTag.kInvalid); } bool get isCacheable => (tag != ConstTag.kInt) && (tag != ConstTag.kBool); @override void writeContents(BufferedWriter writer) { switch (tag) { case ConstTag.kInt: writer.writeSLEB128(value as int); break; case ConstTag.kDouble: writer.writeSLEB128(doubleToIntBits(value as double)); break; case ConstTag.kBool: writer.writeByte((value as bool) ? 1 : 0); break; case ConstTag.kInstance: { final fieldValues = value as Map; writer.writePackedObject(type); writer.writePackedUInt30(fieldValues.length); fieldValues.forEach((ObjectHandle field, ObjectHandle? value) { writer.writePackedObject(field); writer.writePackedObject(value); }); } break; case ConstTag.kList: case ConstTag.kRecord: case ConstTag.kMap: case ConstTag.kSet: { final elems = value as List; writer.writePackedObject(type); writer.writePackedList(elems); } break; case ConstTag.kTearOff: { final target = value as ObjectHandle; writer.writePackedObject(target); } break; case ConstTag.kSymbol: { final name = value as ObjectHandle; writer.writePackedObject(name); } break; case ConstTag.kTearOffInstantiation: { final tearOff = value as ObjectHandle; writer.writePackedObject(tearOff); writer.writePackedObject(type as _TypeArgumentsHandle); } break; case ConstTag.kString: writer.writePackedStringReference(value as String); break; default: throw 'Unexpected constant tag: $tag'; } } @override void readContents(BufferedReader reader) { switch (tag) { case ConstTag.kInt: value = reader.readSLEB128(); break; case ConstTag.kDouble: value = intBitsToDouble(reader.readSLEB128()); break; case ConstTag.kBool: value = reader.readByte() != 0; break; case ConstTag.kInstance: type = reader.readPackedObject(); value = Map.fromEntries( new List>.generate( reader.readPackedUInt30(), (_) => new MapEntry( reader.readPackedObject(), reader.readPackedObject(), ), ), ); break; case ConstTag.kList: case ConstTag.kRecord: case ConstTag.kMap: case ConstTag.kSet: type = reader.readPackedObject(); value = reader.readPackedList(); break; case ConstTag.kTearOff: value = reader.readPackedObject(); break; case ConstTag.kSymbol: value = reader.readPackedObject(); break; case ConstTag.kTearOffInstantiation: value = reader.readPackedObject(); type = reader.readPackedObject(); break; case ConstTag.kString: value = reader.readPackedStringReference(); break; default: throw 'Unexpected constant tag: $tag'; } } @override void accountUsesForObjectCopies(int numCopies) { switch (tag) { case ConstTag.kInt: case ConstTag.kDouble: case ConstTag.kBool: case ConstTag.kString: break; case ConstTag.kInstance: { type!._useCount += numCopies; final fieldValues = value as Map; fieldValues.forEach((ObjectHandle field, ObjectHandle? value) { field._useCount += numCopies; value?._useCount += numCopies; }); } break; case ConstTag.kList: case ConstTag.kRecord: case ConstTag.kMap: case ConstTag.kSet: { final elems = value as List; for (var elem in elems) { elem?._useCount += numCopies; } type!._useCount += numCopies; } break; case ConstTag.kTearOff: { final target = value as ObjectHandle; target._useCount += numCopies; } break; case ConstTag.kSymbol: { final name = value as ObjectHandle; name._useCount += numCopies; } break; case ConstTag.kTearOffInstantiation: { final tearOff = value as ObjectHandle; tearOff._useCount += numCopies; final type = this.type; if (type != null) { type._useCount += numCopies; } } break; default: throw 'Unexpected constant tag: $tag'; } } static int mapHashCode(Map map) { final entryHashes = List.filled(map.length, -1); int i = 0; for (MapEntry entry in map.entries) { entryHashes[i++] = _combineHashes( entry.key.hashCode, entry.value.hashCode, ); } entryHashes.sort(); return listHashCode(entryHashes); } static int listHashCode(List list) { int hash = 31; for (int i = 0; i < list.length; ++i) { hash = _combineHashes(hash, list[i].hashCode); } return hash; } @override int get hashCode { if (_hashCode != 0) { return _hashCode; } switch (tag) { case ConstTag.kInt: case ConstTag.kDouble: case ConstTag.kBool: case ConstTag.kTearOff: case ConstTag.kSymbol: case ConstTag.kString: return _hashCode = value.hashCode; case ConstTag.kInstance: { final fieldValues = value as Map; return _hashCode = _combineHashes( type.hashCode, mapHashCode(fieldValues), ); } case ConstTag.kList: case ConstTag.kRecord: case ConstTag.kMap: case ConstTag.kSet: { final elems = value as List; return _hashCode = _combineHashes(type.hashCode, listHashCode(elems)); } case ConstTag.kTearOffInstantiation: return _hashCode = _combineHashes(value.hashCode, type.hashCode); default: throw 'Unexpected constant tag: $tag'; } } @override bool operator ==(other) { if (identical(this, other)) { return true; } if (other is _ConstObjectHandle && this.tag == other.tag) { switch (tag) { case ConstTag.kInt: case ConstTag.kBool: case ConstTag.kTearOff: case ConstTag.kSymbol: case ConstTag.kString: return this.value == other.value; case ConstTag.kDouble: return this.value.compareTo(other.value) == 0; case ConstTag.kInstance: return this.type == other.type && mapEquals(this.value, other.value); case ConstTag.kList: case ConstTag.kRecord: case ConstTag.kMap: case ConstTag.kSet: return this.type == other.type && listEquals(this.value, other.value); case ConstTag.kTearOffInstantiation: return this.type == other.type && this.value == other.value; default: throw 'Unexpected constant tag: $tag'; } } return false; } @override String toString() { switch (tag) { case ConstTag.kInt: case ConstTag.kDouble: case ConstTag.kBool: case ConstTag.kSymbol: return 'const $value'; case ConstTag.kInstance: return 'const $type $value'; case ConstTag.kList: return 'const List<$type> $value'; case ConstTag.kRecord: return 'const Record<$type> $value'; case ConstTag.kMap: return 'const Map<$type> $value'; case ConstTag.kSet: return 'const Set<$type> $value'; case ConstTag.kTearOff: return 'const tear-off $value'; case ConstTag.kTearOffInstantiation: return 'const $type $value'; case ConstTag.kString: return "'$value'"; default: throw 'Unexpected constant tag: $tag'; } } } class _ArgDescHandle extends ObjectHandle { static const int flagHasNamedArgs = ObjectHandle.flagBit0; static const int flagHasTypeArgs = ObjectHandle.flagBit1; int _flags = 0; late int numArguments; late int numTypeArguments; late List<_PublicNameHandle> argNames; _ArgDescHandle._empty(); _ArgDescHandle(this.numArguments, this.numTypeArguments, this.argNames) { if (argNames.isNotEmpty) { _flags |= flagHasNamedArgs; } if (numTypeArguments > 0) { _flags |= flagHasTypeArgs; } } @override ObjectKind get kind => ObjectKind.kArgDesc; @override int get flags => _flags; @override set flags(int value) { _flags = value; } @override void writeContents(BufferedWriter writer) { writer.writePackedUInt30(numArguments); if ((_flags & flagHasTypeArgs) != 0) { writer.writePackedUInt30(numTypeArguments); } if ((_flags & flagHasNamedArgs) != 0) { writer.writePackedList(argNames); } } @override void readContents(BufferedReader reader) { numArguments = reader.readPackedUInt30(); numTypeArguments = ((_flags & flagHasTypeArgs) != 0) ? reader.readPackedUInt30() : 0; argNames = ((_flags & flagHasNamedArgs) != 0) ? reader.readPackedList<_PublicNameHandle>() : const <_PublicNameHandle>[]; } @override void accountUsesForObjectCopies(int numCopies) { for (var name in argNames) { name._useCount += numCopies; } } @override int get hashCode => _combineHashes( numArguments, _combineHashes(numTypeArguments, listHashCode(argNames)), ); @override bool operator ==(other) => other is _ArgDescHandle && this.numArguments == other.numArguments && this.numTypeArguments == other.numTypeArguments && listEquals(this.argNames, other.argNames); @override String toString() => 'ArgDesc num-args $numArguments, num-type-args $numTypeArguments, names $argNames'; } class _ScriptHandle extends ObjectHandle { static const int flagHasSourceFile = ObjectHandle.flagBit0; int _flags = 0; late ObjectHandle uri; SourceFile? _source; ForwardReference? _sourceForwardReference; _ScriptHandle._empty(); _ScriptHandle(this.uri, this._source) { if (_source != null) { _flags |= flagHasSourceFile; } } @override ObjectKind get kind => ObjectKind.kScript; // Include scripts into index table if there are more than 1 reference // in order to make sure there are no duplicated script objects within the // same bytecode component. @override bool get shouldBeIncludedIntoIndexTable => _useCount > 1; @override int get flags => _flags; @override set flags(int value) { _flags = value; } SourceFile? get source { // Unwrap forward reference on the first access. final sourceForwardReference = this._sourceForwardReference; if (sourceForwardReference != null) { _source = sourceForwardReference.get(); _sourceForwardReference = null; } return _source; } set source(SourceFile? sourceFile) { _source = sourceFile; if (_source != null) { _flags |= flagHasSourceFile; } else { _flags &= ~flagHasSourceFile; } } @override void writeContents(BufferedWriter writer) { writer.writePackedObject(uri); if ((_flags & flagHasSourceFile) != 0) { writer.writeLinkOffset(source!); } } @override void readContents(BufferedReader reader) { uri = reader.readPackedObject(); if ((_flags & flagHasSourceFile) != 0) { // Script handles in the object table may be read before source files, // so use forwarding reference here. _sourceForwardReference = reader .readLinkOffsetAsForwardReference(); } } @override int get hashCode => uri.hashCode; @override bool operator ==(other) => other is _ScriptHandle && this.uri == other.uri; @override String toString() => "$uri${source != null ? '($source)' : ''}"; } class ObjectTable implements ObjectWriter, ObjectReader { /// Object is added to an index table if it is used more than this /// number of times. static const int indexTableUseCountThreshold = 3; final List _objects = []; final Map _canonicalizationCache = {}; final Map _nodeCache = {}; final Map _publicNames = {}; List? _indexTable; late _TypeHandle _dynamicType; late _TypeHandle _voidType; late _TypeHandle _nullType; late _NodeVisitor _nodeVisitor; int numEnclosingFunctionTypeParameters = 0; ObjectTable(CoreTypes coreTypes) { _dynamicType = getOrAddObject(_DynamicTypeHandle()) as _TypeHandle; _voidType = getOrAddObject(_VoidTypeHandle()) as _TypeHandle; _nullType = getOrAddObject(_NullTypeHandle()) as _TypeHandle; _nodeVisitor = _NodeVisitor(this, coreTypes); } // Can be used to add instantiated types and constants which // should not depend on type parameters of the enclosing function. T withoutEnclosingFunctionTypeParameters(T Function() action) { final saved = numEnclosingFunctionTypeParameters; numEnclosingFunctionTypeParameters = 0; final result = action(); numEnclosingFunctionTypeParameters = saved; return result; } ObjectHandle? getHandle(Node? node) { if (node == null) { return null; } ObjectHandle? handle = _nodeCache[node]; if (handle == null) { handle = node.accept(_nodeVisitor); if (handle != null && handle.isCacheable) { _nodeCache[node] = handle; } } else { ++handle._useCount; } return handle; } List getHandles(List nodes) { return List.generate( nodes.length, (int i) => getHandle(nodes[i]), ); } List getNonNullHandles(List nodes) { return List.generate( nodes.length, (int i) => getHandle(nodes[i])!, ); } String mangleGetterName(String name) => 'get:$name'; String mangleSetterName(String name) => 'set:$name'; String mangleSelectorName(String name, bool isGetter, bool isSetter) { if (isGetter) { return mangleGetterName(name); } else if (isSetter) { return mangleSetterName(name); } else { return name; } } String mangleMemberName(Member member, bool isGetter, bool isSetter) { final name = member.name.text; if (isGetter || (member is Procedure && member.isGetter)) { return mangleGetterName(name); } if (isSetter || (member is Procedure && member.isSetter)) { return mangleSetterName(name); } return name; } _PublicNameHandle getPublicNameHandle(String name) { _PublicNameHandle? handle = _publicNames[name]; if (handle == null) { handle = getOrAddObject(_PublicNameHandle(name)) as _PublicNameHandle; _publicNames[name] = handle; } return handle; } ObjectHandle getNameHandle(Library? library, String name) { if (library == null) { return getPublicNameHandle(name); } final libraryHandle = getHandle(library) as _LibraryHandle; return getOrAddObject(_PrivateNameHandle(libraryHandle, name)); } List<_PublicNameHandle> getPublicNameHandles(List names) { if (names.isEmpty) { return const <_PublicNameHandle>[]; } return List<_PublicNameHandle>.generate( names.length, (int i) => getPublicNameHandle(names[i]), ); } ObjectHandle getConstStringHandle(String value) => getOrAddObject(new _ConstObjectHandle(ConstTag.kString, value)); List getConstStringHandles(List values) { if (values.isEmpty) { return const []; } return List.generate( values.length, (int i) => getConstStringHandle(values[i]), ); } ObjectHandle getSelectorNameHandle( Name name, { bool isGetter = false, bool isSetter = false, }) { return getNameHandle( name.library, mangleSelectorName(name.text, isGetter, isSetter), ); } ObjectHandle getTopLevelClassHandle(Library library) { final libraryHandle = getHandle(library) as _LibraryHandle; final name = getPublicNameHandle(topLevelClassName); return getOrAddObject(new _ClassHandle(libraryHandle, name)); } ObjectHandle getMemberHandle( Member member, { bool isGetter = false, bool isSetter = false, }) { final parent = member.parent; _ClassHandle classHandle; if (parent is Class) { classHandle = getHandle(parent) as _ClassHandle; } else if (parent is Library) { classHandle = getTopLevelClassHandle(parent) as _ClassHandle; } else { throw "Unexpected Member's parent ${parent.runtimeType} $parent"; } final nameHandle = getNameHandle( member.name.library, mangleMemberName(member, isGetter, isSetter), ) as _NameHandle; bool isField = member is Field && !isGetter && !isSetter; bool isConstructor = member is Constructor || (member is Procedure && member.isFactory); return getOrAddObject( new _MemberHandle(classHandle, nameHandle, isField, isConstructor), ); } ObjectHandle? getTypeArgumentsHandle(List? typeArgs) { if (typeArgs == null) { return null; } final handles = List<_TypeHandle>.generate( typeArgs.length, (int i) => getHandle(typeArgs[i]) as _TypeHandle, ); return getOrAddObject(new _TypeArgumentsHandle(handles)); } ObjectHandle getArgDescHandle( int numArguments, [ int numTypeArguments = 0, List argNames = const [], ]) { return getOrAddObject( new _ArgDescHandle( numArguments, numTypeArguments, getPublicNameHandles(argNames), ), ); } ObjectHandle getArgDescHandleByArguments( Arguments args, { bool hasReceiver = false, }) { List<_PublicNameHandle> argNames = const <_PublicNameHandle>[]; final namedArguments = args.named; if (namedArguments.isNotEmpty) { argNames = List<_PublicNameHandle>.generate( namedArguments.length, (int i) => getPublicNameHandle(namedArguments[i].name), ); } final int numArguments = args.positional.length + args.named.length + (hasReceiver ? 1 : 0); final int numTypeArguments = args.types.length; return getOrAddObject( new _ArgDescHandle(numArguments, numTypeArguments, argNames), ); } ObjectHandle getScriptHandle(Uri uri, SourceFile? source) { ObjectHandle uriHandle = getPublicNameHandle(uri.toString()); _ScriptHandle handle = getOrAddObject(_ScriptHandle(uriHandle, source)) as _ScriptHandle; if (handle.source == null && source != null) { handle.source = source; } return handle; } List getTypeParameterHandles( List typeParams, ) { if (typeParams.isEmpty) { return const []; } return List.generate( typeParams.length, (int i) => TypeParameterDeclaration( getPublicNameHandle(typeParams[i].name!), getHandle(typeParams[i].bound)!, getHandle(typeParams[i].defaultType)!, ), ); } List getStructuralParameterHandles( List typeParams, ) { if (typeParams.isEmpty) { return const []; } return List.generate( typeParams.length, (int i) => TypeParameterDeclaration( getPublicNameHandle(typeParams[i].name!), getHandle(typeParams[i].bound)!, getHandle(typeParams[i].defaultType)!, ), ); } void declareClosure( FunctionNode function, Member enclosingMember, int closureIndex, ) { final handle = getOrAddObject( new _ClosureHandle( getHandle(enclosingMember) as _MemberHandle, closureIndex, ), ); _nodeCache[function] = handle; } ObjectHandle getOrAddObject(ObjectHandle obj) { assert(obj._useCount == 0); ObjectHandle? canonical = _canonicalizationCache[obj]; if (canonical == null) { assert(_indexTable == null); _objects.add(obj); _canonicalizationCache[obj] = obj; canonical = obj; } ++canonical._useCount; return canonical; } void allocateIndexTable() { int tableSize = 1; // Reserve invalid entry. for (var obj in _objects.reversed) { assert(obj._reference == null); if (obj.shouldBeIncludedIntoIndexTable) { // This object will be included into index table. ++tableSize; } else { // This object will be copied and written inline. Bump use count for // objects referenced from this one for each copy after the first. obj._reference = ObjectHandle.inlineObject; obj.accountUsesForObjectCopies(obj._useCount - 1); } } final indexTable = _indexTable = List.filled( tableSize, _InvalidHandle(), ); int count = 0; indexTable[count++] = _InvalidHandle() .._reference = ObjectHandle._makeReference(0); for (var obj in _objects) { if (obj._reference == null) { obj._reference = ObjectHandle._makeReference(count); indexTable[count++] = obj; } else { assert(obj._reference == ObjectHandle.inlineObject); } } assert(count == tableSize); } @override void writeObject(BytecodeObject? object, BufferedWriter writer) { ObjectHandle? handle = object as ObjectHandle?; if (handle == null) { writer.writePackedUInt30(ObjectHandle._makeReference(0)); return; } final handleReference = handle._reference!; if (handleReference == ObjectHandle.inlineObject) { handle._write(writer); } else { assert(handleReference >= 0); assert((handleReference & ObjectHandle.referenceBit) != 0); writer.writePackedUInt30(handleReference); } } @override BytecodeObject? readObject(BufferedReader reader) { final int header = reader.readPackedUInt30(); if ((header & ObjectHandle.referenceBit) == 0) { return new ObjectHandle._read(reader, header); } else { final int index = ObjectHandle._getIndexFromReference(header); return (index == 0) ? null : _indexTable![index]; } } void write(BufferedWriter writer) { assert(writer.objectWriter == this); final indexTable = _indexTable!; final start = writer.offset; if (BytecodeSizeStatistics.objectTableStats.isEmpty) { for (var kind in ObjectKind.values) { BytecodeSizeStatistics.objectTableStats.add( new NamedEntryStatistics(objectKindToString(kind)), ); } } BufferedWriter contentsWriter = new BufferedWriter.fromWriter(writer); List offsets = List.generate(indexTable.length, (int i) { final offset = contentsWriter.offset; indexTable[i]._write(contentsWriter); final entryStat = BytecodeSizeStatistics.objectTableStats[indexTable[i].kind.index]; entryStat.size += (contentsWriter.offset - offset); ++entryStat.count; return offset; }); writer.writePackedUInt30(indexTable.length); writer.writePackedUInt30(contentsWriter.offset); writer.appendWriter(contentsWriter); for (var offs in offsets) { writer.writePackedUInt30(offs); } // Index strings in objects which will be written inline // in constant pool entries. for (var obj in _objects) { if (obj._reference == ObjectHandle.inlineObject) { obj.indexStrings(writer.stringWriter); } } BytecodeSizeStatistics.objectTableSize += (writer.offset - start); BytecodeSizeStatistics.objectTableEntriesCount += indexTable.length; } ObjectTable.read(BufferedReader reader) { reader.objectReader = this; final int numEntries = reader.readPackedUInt30(); reader.readPackedUInt30(); // Contents length final indexTable = _indexTable = List.filled( numEntries, _InvalidHandle(), ); for (int i = 0; i < numEntries; ++i) { final int header = reader.readPackedUInt30(); indexTable[i] = new ObjectHandle._read(reader, header) .._reference = ObjectHandle._makeReference(i); } // Skip index table. for (int i = 0; i < numEntries; ++i) { reader.readPackedUInt30(); } } @override String toString() { StringBuffer sb = new StringBuffer(); sb.writeln('ObjectTable {'); final indexTable = _indexTable!; for (int i = 0; i < indexTable.length; ++i) { final obj = indexTable[i]; sb.writeln(' [$i] = ${objectKindToString(obj.kind)} $obj'); } sb.writeln('}'); return sb.toString(); } } class _NodeVisitor extends VisitorDefault with VisitorThrowingMixin { final ObjectTable objectTable; final CoreTypes coreTypes; final _typeParameters = {}; _NodeVisitor(this.objectTable, this.coreTypes); @override ObjectHandle? defaultNode(Node node) => throw 'Unexpected node ${node.runtimeType} $node'; @override ObjectHandle? visitLibrary(Library node) { final uri = objectTable.getConstStringHandle(node.importUri.toString()) as _ConstObjectHandle; return objectTable.getOrAddObject(new _LibraryHandle(uri)); } @override ObjectHandle? visitClass(Class node) { final library = objectTable.getHandle(node.enclosingLibrary) as _LibraryHandle; final name = node.name.startsWith('_') ? (objectTable.getOrAddObject( new _PrivateNameHandle(library, node.name), ) as _PrivateNameHandle) : objectTable.getPublicNameHandle(node.name); return objectTable.getOrAddObject(new _ClassHandle(library, name)); } @override ObjectHandle defaultMember(Member node) => objectTable.getMemberHandle(node); @override ObjectHandle? visitDynamicType(DynamicType node) => objectTable._dynamicType; @override ObjectHandle? visitVoidType(VoidType node) => objectTable._voidType; @override ObjectHandle? visitNullType(NullType node) => objectTable._nullType; @override ObjectHandle? visitNeverType(NeverType node) => objectTable.getOrAddObject(new _NeverTypeHandle(node.nullability)); @override ObjectHandle? visitInterfaceType(InterfaceType node) { final classHandle = objectTable.getHandle(node.classNode) as _ClassHandle; if (node.classNode.typeParameters.isEmpty) { return objectTable.getOrAddObject( new _SimpleTypeHandle(classHandle, node.nullability), ); } final typeArgsHandle = objectTable.getTypeArgumentsHandle(node.typeArguments) as _TypeArgumentsHandle?; return objectTable.getOrAddObject( _GenericTypeHandle(classHandle, typeArgsHandle, node.nullability), ); } @override ObjectHandle? visitFutureOrType(FutureOrType node) { final classNode = coreTypes.deprecatedFutureOrClass; final classHandle = objectTable.getHandle(classNode) as _ClassHandle; final typeArgsHandle = objectTable.getTypeArgumentsHandle([node.typeArgument]) as _TypeArgumentsHandle?; final result = objectTable.getOrAddObject( new _GenericTypeHandle(classHandle, typeArgsHandle, node.nullability), ); return result; } @override ObjectHandle? visitTypeParameterType(TypeParameterType node) { final param = node.parameter; final parent = param.declaration; if (parent == null) { throw 'Type parameter $param without declaration'; } ObjectHandle parentHandle; if (parent is Class) { parentHandle = objectTable.getHandle(parent)!; } else if (parent is Member) { parentHandle = objectTable.getHandle(parent)!; } else if (parent is LocalFunction) { parentHandle = objectTable.getHandle(parent.function)!; } else { throw 'Unexpected parent declaration of TypeParameter: ${parent.runtimeType} $parent'; } final indexInParent = parent.typeParameters.indexOf(param); if (indexInParent < 0) { throw 'Type parameter $param is not found in its parent class $parent'; } return objectTable.getOrAddObject( new _TypeParameterHandle(parentHandle, indexInParent, node.nullability), ); } @override ObjectHandle? visitStructuralParameterType(StructuralParameterType node) { final param = node.parameter; final handle = _typeParameters[param] as _TypeParameterHandle; if (handle.nullability == node.nullability) { return handle; } return objectTable.getOrAddObject( new _TypeParameterHandle( handle.parent, handle.indexInParent, node.nullability, ), ); } @override ObjectHandle? visitFunctionType(FunctionType node) { final int numEnclosingTypeParameters = objectTable.numEnclosingFunctionTypeParameters + _typeParameters.length; for (int i = 0; i < node.typeParameters.length; ++i) { _typeParameters[node.typeParameters[i]] = objectTable.getOrAddObject( new _TypeParameterHandle( null, numEnclosingTypeParameters + i, Nullability.nonNullable, ), ); } final typeParameters = node.typeParameters.isNotEmpty ? TypeParametersDeclaration( objectTable.getStructuralParameterHandles(node.typeParameters), ) : null; final positionalParams = List<_TypeHandle>.generate( node.positionalParameters.length, (int i) => objectTable.getHandle(node.positionalParameters[i]) as _TypeHandle, ); final namedParams = List.generate( node.namedParameters.length, (int i) { final param = node.namedParameters[i]; return NameAndType( objectTable.getPublicNameHandle(param.name), objectTable.getHandle(param.type)!, ); }, ); final parameterFlags = ParameterFlags.getFunctionTypeFlags(node); final returnType = objectTable.getHandle(node.returnType) as _TypeHandle; final result = objectTable.getOrAddObject( new _FunctionTypeHandle( numEnclosingTypeParameters, typeParameters, node.requiredParameterCount, positionalParams, namedParams, parameterFlags, returnType, node.nullability, ), ); for (int i = 0; i < node.typeParameters.length; ++i) { _typeParameters.remove(node.typeParameters[i]); } return result; } @override ObjectHandle? visitRecordType(RecordType node) { final positional = List<_TypeHandle>.generate( node.positional.length, (int i) => objectTable.getHandle(node.positional[i]) as _TypeHandle, ); final named = List.generate(node.named.length, (int i) { final namedField = node.named[i]; return NameAndType( objectTable.getPublicNameHandle(namedField.name), objectTable.getHandle(namedField.type)!, ); }); return objectTable.getOrAddObject( _RecordTypeHandle(positional, named, node.nullability), ); } @override ObjectHandle? visitTypedefType(TypedefType node) => objectTable.getHandle(node.unalias); @override ObjectHandle? visitIntersectionType(IntersectionType node) => objectTable.getHandle(node.left); @override ObjectHandle? visitExtensionType(ExtensionType node) => objectTable.getHandle(node.extensionTypeErasure); @override ObjectHandle? visitNullConstant(NullConstant node) => null; @override ObjectHandle? visitBoolConstant(BoolConstant node) => objectTable .getOrAddObject(new _ConstObjectHandle(ConstTag.kBool, node.value)); @override ObjectHandle? visitIntConstant(IntConstant node) => objectTable .getOrAddObject(new _ConstObjectHandle(ConstTag.kInt, node.value)); @override ObjectHandle? visitDoubleConstant(DoubleConstant node) => objectTable .getOrAddObject(new _ConstObjectHandle(ConstTag.kDouble, node.value)); @override ObjectHandle? visitStringConstant(StringConstant node) => objectTable.getConstStringHandle(node.value); @override ObjectHandle? visitSymbolConstant(SymbolConstant node) => objectTable.getOrAddObject( new _ConstObjectHandle( ConstTag.kSymbol, objectTable.getNameHandle( node.libraryReference?.asLibrary, node.name, ), ), ); @override ObjectHandle? visitListConstant(ListConstant node) => objectTable.getOrAddObject( new _ConstObjectHandle( ConstTag.kList, objectTable.getHandles(node.entries), objectTable.getHandle(node.typeArgument), ), ); @override ObjectHandle? visitRecordConstant(RecordConstant node) => objectTable.getOrAddObject( new _ConstObjectHandle( ConstTag.kRecord, objectTable.getHandles([...node.positional, ...node.named.values]), objectTable.getHandle(node.recordType), ), ); @override ObjectHandle? visitMapConstant(MapConstant node) => objectTable.getOrAddObject( new _ConstObjectHandle( ConstTag.kMap, [ for (var e in node.entries) ...[ objectTable.getHandle(e.key), objectTable.getHandle(e.value), ], ], objectTable.getHandle( InterfaceType(coreTypes.mapClass, Nullability.nonNullable, [ node.keyType, node.valueType, ]), ), ), ); @override ObjectHandle? visitSetConstant(SetConstant node) => objectTable.getOrAddObject( new _ConstObjectHandle( ConstTag.kSet, objectTable.getHandles(node.entries), objectTable.getHandle(node.typeArgument), ), ); @override ObjectHandle? visitInstanceConstant(InstanceConstant node) => objectTable.getOrAddObject( new _ConstObjectHandle( ConstTag.kInstance, node.fieldValues.map( (Reference fieldRef, Constant value) => new MapEntry( objectTable.getHandle(fieldRef.asField)!, objectTable.getHandle(value), ), ), objectTable.getHandle( InterfaceType( node.classNode, Nullability.nonNullable, node.typeArguments, ), ), ), ); @override ObjectHandle? visitStaticTearOffConstant(StaticTearOffConstant node) => objectTable.getOrAddObject( new _ConstObjectHandle( ConstTag.kTearOff, objectTable.getHandle(node.target), ), ); @override ObjectHandle? visitConstructorTearOffConstant( ConstructorTearOffConstant node, ) => objectTable.getOrAddObject( new _ConstObjectHandle( ConstTag.kTearOff, objectTable.getHandle(node.target), ), ); @override ObjectHandle? visitRedirectingFactoryTearOffConstant( RedirectingFactoryTearOffConstant node, ) => objectTable.getOrAddObject( new _ConstObjectHandle( ConstTag.kTearOff, objectTable.getHandle(node.target), ), ); @override ObjectHandle? visitTypeLiteralConstant(TypeLiteralConstant node) => objectTable.getHandle(node.type); @override ObjectHandle? visitInstantiationConstant(InstantiationConstant node) => objectTable.getOrAddObject( new _ConstObjectHandle( ConstTag.kTearOffInstantiation, objectTable.getHandle(node.tearOffConstant), objectTable.getTypeArgumentsHandle(node.types), ), ); } int _combineHashes(int hash1, int hash2) => (((hash1 * 31) & 0x3fffffff) + hash2) & 0x3fffffff;