// Copyright (c) 2018, 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. library vm.bytecode.object_table; import 'package:kernel/ast.dart' hide MapEntry; 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 'generics.dart' show getInstantiatorTypeArguments, hasInstantiatorTypeArguments; import 'declarations.dart' show SourceFile, TypeParametersDeclaration; import 'recursive_types_validator.dart' show RecursiveTypesValidator; /* Bytecode object table is encoded in the following way (using notation from pkg/kernel/binary.md): type ObjectTable { UInt numEntries // Total size of ‘objects’ in bytes. UInt objectsSize ObjectContents[numEntries] objects // Offsets relative to ‘objects’. UInt[numEntries] objectOffsets } // Either reference to an object in object table, or object contents // written inline (determined by bit 0). PackedObject = ObjectReference | ObjectContents type ObjectReference { // Bit 0 (reference bit): 1 // Bits 1+: index in object table UInt reference } type ObjectContents { // Bit 0 (reference bit): 0 // Bits 1-4: object kind // Bits 5+ object flags UInt header } // Invalid/null object (always present at index 0). type InvalidObject extends ObjectContents { kind = 0; } type Library extends ObjectContents { kind = 1; PackedObject importUri; } type Class extends ObjectContents { kind = 2; PackedObject library; // Empty name is used for artificial class containing top-level // members of a library. PackedObject name; } type Member extends ObjectContents { kind = 3; flags = (isField, isConstructor); PackedObject class; PackedObject name; } type Closure extends ObjectContents { kind = 4; PackedObject enclosingMember; UInt closureIndex; } type Name extends ObjectContents { kind = 9; // Invalid for public names PackedObject library; // Getters are prefixed with 'get:'. // Setters are prefixed with 'set:'. PackedString string; } // Type arguments vector. type TypeArguments extends ObjectContents { kind = 10; List args; } abstract type ConstObject extends ObjectContents { kind = 12; flags = constantTag (4 bits) } type ConstInstance extends ConstObject { kind = 12 constantTag (flags) = 1 PackedObject type; List> fieldValues; } type ConstInt extends ConstValue { kind = 12 constantTag (flags) = 2 SLEB128 value; } type ConstDouble extends ConstValue { kind = 12 constantTag (flags) = 3 // double bits are reinterpreted as 64-bit int SLEB128 value; } type ConstList extends ConstObject { kind = 12 constantTag (flags) = 4 PackedObject elemType; List entries; } type ConstTearOff extends ConstObject { kind = 12 constantTag (flags) = 5 PackedObject target; } type ConstBool extends ConstValue { kind = 12 constantTag = 6 Byte isTrue; } type ConstSymbol extends ConstObject { kind = 12 constantTag (flags) = 7 PackedObject name; } type ConstTearOffInstantiation extends ConstObject { kind = 12 constantTag (flags) = 8 PackedObject tearOff; PackedObject typeArguments; } type ArgDesc extends ObjectContents { kind = 13; flags = (hasNamedArgs, hasTypeArgs) UInt numArguments if hasTypeArgs UInt numTypeArguments if hasNamedArgs List argNames; } type Script extends ObjectContents { kind = 14 flags = (hasSourceFile) PackedObject uri if hasSourceFile UInt sourceFileOffset } abstract type Type extends ObjectContents { kind = 15 flags = typeTag (4 bits) } type DynamicType extends Type { kind = 15 typeTag (flags) = 1 } type VoidType extends Type { kind = 15 typeTag (flags) = 2 } type NeverType extends Type { kind = 15 typeTag (flags) = 9 } // SimpleType can be used only for types without instantiator type arguments. type SimpleType extends Type { kind = 15 typeTag (flags) = 3 PackedObject class } type TypeParameter extends Type { kind = 15 typeTag (flags) = 4 // Class, Member or Closure declaring this type parameter. // Null (Invalid) if declared by function type. PackedObject parent UInt indexInParent } // Non-recursive finalized generic type. type GenericType extends Type { kind = 15 typeTag (flags) = 5 PackedObject class // Flattened type arguments vector. PackedObject typeArgs } // Recursive finalized generic type. type RecursiveGenericType extends Type { kind = 15 typeTag (flags) = 6 // This id is used to reference recursive types using RecursiveTypeRef. // Type should be declared using RecursiveGenericType before it can be referenced. // The root type should have zero recursiveId. UInt recursiveId PackedObject class // Flattened type arguments vector. PackedObject typeArgs } type RecursiveTypeRef extends Type { kind = 15 typeTag (flags) = 7 UInt recursiveId } type FunctionType extends Type { kind = 15 typeTag (flags) = 8 UInt functionTypeFlags(hasOptionalPositionalParams, hasOptionalNamedParams, hasTypeParams) if hasTypeParams TypeParametersDeclaration typeParameters UInt numParameters if hasOptionalPositionalParams || hasOptionalNamedParams UInt numRequiredParameters Type[] positionalParameters NameAndType[] namedParameters PackedObject returnType } type TypeParametersDeclaration { UInt numTypeParameters PackedObject[numTypeParameters] typeParameterNames PackedObject[numTypeParameters] typeParameterBounds } type NameAndType { PackedObject name; PackedObject type; } */ enum ObjectKind { kInvalid, kLibrary, kClass, kMember, kClosure, kUnused1, kUnused2, kUnused3, kUnused4, kName, kTypeArguments, kUnused5, kConstObject, kArgDesc, kScript, kType, } enum ConstTag { kInvalid, kInstance, kInt, kDouble, kList, kTearOff, kBool, kSymbol, kTearOffInstantiation, kString, } enum TypeTag { kInvalid, kDynamic, kVoid, kSimpleType, kTypeParameter, kGenericType, kRecursiveGenericType, kRecursiveTypeRef, kFunctionType, kNever, } /// Name of artificial class containing top-level members of a library. const String topLevelClassName = ''; String objectKindToString(ObjectKind kind) => kind.toString().substring('ObjectKind.k'.length); /// 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: Nullability nullability = Nullability .values[(flags & _TypeHandle.nullabilityMask) ~/ flagBit4]; 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.kNever: return new _NeverTypeHandle(); 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.kRecursiveGenericType: return new _RecursiveGenericTypeHandle._empty(nullability); case TypeTag.kRecursiveTypeRef: return new _RecursiveTypeRefHandle._empty(nullability); case TypeTag.kFunctionType: return new _FunctionTypeHandle._empty(nullability); } throw 'Unexpected type tag $flags'; case ObjectKind.kUnused1: case ObjectKind.kUnused2: case ObjectKind.kUnused3: case ObjectKind.kUnused4: case ObjectKind.kUnused5: break; } throw 'Unexpected object kind $kind'; } 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 { _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 { _LibraryHandle library; _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; _ClassHandle parent; _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 { _MemberHandle enclosingMember; 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 nullabilityMask = ObjectHandle.flagBit4 | ObjectHandle.flagBit5; 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.index * ObjectHandle.flagBit4); @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 _NeverTypeHandle extends _TypeHandle { _NeverTypeHandle() : super(TypeTag.kNever, Nullability.nonNullable); @override void writeContents(BufferedWriter writer) {} @override void readContents(BufferedReader reader) {} @override int get hashCode => 2049; @override bool operator ==(other) => other is _NeverTypeHandle; @override String toString() => 'Never'; } class _SimpleTypeHandle extends _TypeHandle { _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) { if (class_ != null) { 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 // TODO(regis): Print nullability, only if nnbd experiment is enabled? String toString() => '$class_'; } class _TypeParameterHandle extends _TypeHandle { ObjectHandle parent; 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) { 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 // TODO(regis): Print nullability, only if nnbd experiment is enabled? String toString() => '$parent::TypeParam/$indexInParent'; } class _GenericTypeHandle extends _TypeHandle { _ClassHandle class_; _TypeArgumentsHandle typeArgs; _GenericTypeHandle._empty(Nullability nullability) : super(TypeTag.kGenericType, nullability); _GenericTypeHandle(this.class_, this.typeArgs, Nullability nullability) : super(TypeTag.kGenericType, nullability); @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; 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 // TODO(regis): Print nullability, only if nnbd experiment is enabled? String toString() => '$class_ $typeArgs'; } class _RecursiveGenericTypeHandle extends _TypeHandle { int id; _ClassHandle class_; _TypeArgumentsHandle typeArgs; _RecursiveGenericTypeHandle._empty(Nullability nullability) : super(TypeTag.kRecursiveGenericType, nullability); _RecursiveGenericTypeHandle( this.id, this.class_, this.typeArgs, Nullability nullability) : super(TypeTag.kRecursiveGenericType, nullability); @override bool get isCacheable => (id == 0); @override void writeContents(BufferedWriter writer) { writer.writePackedUInt30(id); writer.writePackedObject(class_); writer.writePackedObject(typeArgs); } @override void readContents(BufferedReader reader) { id = reader.readPackedUInt30(); class_ = reader.readPackedObject(); typeArgs = reader.readPackedObject(); } @override void accountUsesForObjectCopies(int numCopies) { class_._useCount += numCopies; if (typeArgs != null) { typeArgs._useCount += numCopies; } } @override int get hashCode => _combineHashes( class_.hashCode, _combineHashes(typeArgs.hashCode, nullability.index)); @override bool operator ==(other) => other is _RecursiveGenericTypeHandle && this.class_ == other.class_ && this.typeArgs == other.typeArgs && this.nullability == other.nullability; @override // TODO(regis): Print nullability, only if nnbd experiment is enabled? String toString() => '(recursive #$id) $class_ $typeArgs'; } class _RecursiveTypeRefHandle extends _TypeHandle { int id; _RecursiveTypeRefHandle._empty(Nullability nullability) : super(TypeTag.kRecursiveTypeRef, nullability); _RecursiveTypeRefHandle(this.id) : super(TypeTag.kRecursiveTypeRef, Nullability.legacy); @override bool get isCacheable => false; @override void writeContents(BufferedWriter writer) { writer.writePackedUInt30(id); } @override void readContents(BufferedReader reader) { id = reader.readPackedUInt30(); } @override int get hashCode => id; @override bool operator ==(other) => other is _RecursiveTypeRefHandle && this.id == other.id; @override String toString() => 'recursive-ref #$id'; } class NameAndType { _NameHandle name; _TypeHandle type; NameAndType(this.name, this.type); @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 // TODO(regis): Print nullability, always or only if nnbd experiment is enabled? String toString() => '$type ${name.name}'; } class _FunctionTypeHandle extends _TypeHandle { static const int flagHasOptionalPositionalParams = 1 << 0; static const int flagHasOptionalNamedParams = 1 << 1; static const int flagHasTypeParams = 1 << 2; int functionTypeFlags = 0; List typeParams; int numRequiredParams; List<_TypeHandle> positionalParams; List namedParams; _TypeHandle returnType; _FunctionTypeHandle._empty(Nullability nullability) : super(TypeTag.kFunctionType, nullability); _FunctionTypeHandle( this.typeParams, this.numRequiredParams, this.positionalParams, this.namedParams, 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 (typeParams.isNotEmpty) { functionTypeFlags |= flagHasTypeParams; } } @override void writeContents(BufferedWriter writer) { writer.writePackedUInt30(functionTypeFlags); if ((functionTypeFlags & flagHasTypeParams) != 0) { new TypeParametersDeclaration(typeParams).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); } writer.writePackedObject(returnType); } @override void readContents(BufferedReader reader) { functionTypeFlags = reader.readPackedUInt30(); if ((functionTypeFlags & flagHasTypeParams) != 0) { typeParams = new TypeParametersDeclaration.read(reader).typeParams; } else { typeParams = const []; } 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 []; } 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 { 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 = listHashCode(typeParams); hash = _combineHashes(hash, numRequiredParams); hash = _combineHashes(hash, listHashCode(positionalParams)); hash = _combineHashes(hash, listHashCode(namedParams)); hash = _combineHashes(hash, returnType.hashCode); return hash; } @override bool operator ==(other) => other is _FunctionTypeHandle && listEquals(this.typeParams, other.typeParams) && this.numRequiredParams == other.numRequiredParams && listEquals(this.positionalParams, other.positionalParams) && listEquals(this.namedParams, other.namedParams) && this.returnType == other.returnType; @override // TODO(regis): Print nullability, only if nnbd experiment is enabled? String toString() { StringBuffer sb = new StringBuffer(); sb.write('FunctionType'); if (typeParams.isNotEmpty) { sb.write(' <${typeParams.join(', ')}>'); } 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('{ ${namedParams.join(', ')} }'); } sb.write(') -> '); sb.write(returnType); 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 { 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 { _LibraryHandle library; String name; _PrivateNameHandle._empty(); _PrivateNameHandle(this.library, this.name) { assert(library != null); } @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 { 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 { ConstTag tag; 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: { 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: 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: { 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; if (type != null) { type._useCount += numCopies; } } break; default: throw 'Unexpected constant tag: $tag'; } } @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)); } break; case ConstTag.kList: { final elems = value as List; return _hashCode = _combineHashes(type.hashCode, listHashCode(elems)); } break; 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: 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 <$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; int numArguments; int numTypeArguments; 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>() : null; } @override void accountUsesForObjectCopies(int numCopies) { if (argNames != null) { 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; 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. 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 = new List(); final Map _canonicalizationCache = {}; final Map _nodeCache = {}; final Map _publicNames = {}; List _indexTable; _TypeHandle _dynamicType; _TypeHandle _voidType; _TypeHandle _neverType; CoreTypes coreTypes; _NodeVisitor _nodeVisitor; ObjectTable() { _dynamicType = getOrAddObject(new _DynamicTypeHandle()); _voidType = getOrAddObject(new _VoidTypeHandle()); _neverType = getOrAddObject(new _NeverTypeHandle()); _nodeVisitor = new _NodeVisitor(this); } 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) { final handles = new List(nodes.length); for (int i = 0; i < nodes.length; ++i) { handles[i] = getHandle(nodes[i]); } return handles; } 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.name; if (isGetter || (member is Procedure && member.isGetter)) { return mangleGetterName(name); } if (isSetter || (member is Procedure && member.isSetter)) { return mangleSetterName(name); } return name; } ObjectHandle getPublicNameHandle(String name) { assert(name != null); _PublicNameHandle handle = _publicNames[name]; if (handle == null) { handle = getOrAddObject(new _PublicNameHandle(name)); _publicNames[name] = handle; } return handle; } ObjectHandle getNameHandle(Library library, String name) { if (library == null) { return getPublicNameHandle(name); } assert(name != null); final libraryHandle = library != null ? getHandle(library) : null; return getOrAddObject(new _PrivateNameHandle(libraryHandle, name)); } List<_PublicNameHandle> getPublicNameHandles(List names) { if (names.isEmpty) { return const <_PublicNameHandle>[]; } final handles = new List<_PublicNameHandle>(names.length); for (int i = 0; i < names.length; ++i) { handles[i] = getPublicNameHandle(names[i]); } return handles; } ObjectHandle getConstStringHandle(String value) => getOrAddObject(new _ConstObjectHandle(ConstTag.kString, value)); List getConstStringHandles(List values) { if (values.isEmpty) { return const []; } final handles = new List(values.length); for (int i = 0; i < values.length; ++i) { handles[i] = getConstStringHandle(values[i]); } return handles; } ObjectHandle getSelectorNameHandle(Name name, {bool isGetter: false, bool isSetter: false}) { return getNameHandle( name.library, mangleSelectorName(name.name, isGetter, isSetter)); } ObjectHandle getTopLevelClassHandle(Library library) { final libraryHandle = getHandle(library); final name = getPublicNameHandle(topLevelClassName); return getOrAddObject(new _ClassHandle(libraryHandle, name)); } ObjectHandle getMemberHandle(Member member, {bool isGetter: false, bool isSetter: false}) { final parent = member.parent; ObjectHandle classHandle; if (parent is Class) { classHandle = getHandle(parent); } else if (parent is Library) { classHandle = getTopLevelClassHandle(parent); } else { throw "Unexpected Member's parent ${parent.runtimeType} $parent"; } final nameHandle = getNameHandle( member.name.library, mangleMemberName(member, isGetter, isSetter)); 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 = new List<_TypeHandle>(typeArgs.length); for (int i = 0; i < typeArgs.length; ++i) { handles[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, bool isFactory: false}) { List<_PublicNameHandle> argNames = const <_PublicNameHandle>[]; final namedArguments = args.named; if (namedArguments.isNotEmpty) { argNames = new List<_PublicNameHandle>(namedArguments.length); for (int i = 0; i < namedArguments.length; ++i) { argNames[i] = getPublicNameHandle(namedArguments[i].name); } } final int numArguments = args.positional.length + args.named.length + (hasReceiver ? 1 : 0) + // VM expects that type arguments vector passed to a factory // constructor is counted in numArguments, and not counted in // numTypeArgs. // TODO(alexmarkov): Clean this up. (isFactory ? 1 : 0); final int numTypeArguments = isFactory ? 0 : args.types.length; return getOrAddObject( new _ArgDescHandle(numArguments, numTypeArguments, argNames)); } ObjectHandle getScriptHandle(Uri uri, SourceFile source) { ObjectHandle uriHandle = getPublicNameHandle(uri.toString()); _ScriptHandle handle = getOrAddObject(new _ScriptHandle(uriHandle, source)); if (handle.source == null && source != null) { handle.source = source; } return handle; } List getTypeParameterHandles(List typeParams) { if (typeParams.isEmpty) { return const []; } final namesAndBounds = new List(); for (TypeParameter tp in typeParams) { namesAndBounds.add( new NameAndType(getPublicNameHandle(tp.name), getHandle(tp.bound))); } return namesAndBounds; } void declareClosure( FunctionNode function, Member enclosingMember, int closureIndex) { final handle = getOrAddObject( new _ClosureHandle(getHandle(enclosingMember), 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); } } _indexTable = new List(tableSize); int count = 0; _indexTable[count++] = new _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; } if (handle._reference == ObjectHandle.inlineObject) { handle._write(writer); } else { assert(handle._reference >= 0); assert((handle._reference & ObjectHandle.referenceBit) != 0); writer.writePackedUInt30(handle._reference); } } @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); assert(_indexTable != null); 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 = new List(_indexTable.length); for (int i = 0; i < _indexTable.length; ++i) { offsets[i] = contentsWriter.offset; _indexTable[i]._write(contentsWriter); final entryStat = BytecodeSizeStatistics.objectTableStats[_indexTable[i].kind.index]; entryStat.size += (contentsWriter.offset - offsets[i]); ++entryStat.count; } 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 _indexTable = new List(numEntries); 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 {'); 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 Visitor { final ObjectTable objectTable; final _typeParameters = {}; final Map _recursiveTypeIds = {}; final recursiveTypesValidator = new RecursiveTypesValidator(); _NodeVisitor(this.objectTable); @override ObjectHandle defaultNode(Node node) => throw 'Unexpected node ${node.runtimeType} $node'; @override ObjectHandle visitLibrary(Library node) { final uri = objectTable.getConstStringHandle(node.importUri.toString()); return objectTable.getOrAddObject(new _LibraryHandle(uri)); } @override ObjectHandle visitClass(Class node) { final ObjectHandle library = objectTable.getHandle(node.enclosingLibrary); final name = node.name.startsWith('_') ? objectTable.getOrAddObject(new _PrivateNameHandle(library, node.name)) : 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 visitNeverType(NeverType node) => objectTable._neverType; @override ObjectHandle visitBottomType(BottomType node) => objectTable.getHandle(objectTable.coreTypes.nullType); @override ObjectHandle visitInterfaceType(InterfaceType node) { final classHandle = objectTable.getHandle(node.classNode); if (!hasInstantiatorTypeArguments(node.classNode)) { return objectTable .getOrAddObject(new _SimpleTypeHandle(classHandle, node.nullability)); } // Non-finalized types are not recursive, but finalization of // generic types includes flattening of type arguments and types could // become recursive. Consider the following example: // // class Base {} // class Foo extends Base> {} // // Foo is not recursive, but finalized type is recursive: // Foo* = Foo [ Base [ Foo* ], int ] // // Object table serialization/deserialization cannot handle cycles between // objects, so recursive types require extra care when serializing. // Back references to the already serialized types are represented as // _RecursiveTypeRefHandle objects, which are only valid in the context // of enclosing top-level _RecursiveGenericType. // int recursiveId = _recursiveTypeIds[node]; if (recursiveId != null) { return objectTable .getOrAddObject(new _RecursiveTypeRefHandle(recursiveId)); } recursiveTypesValidator.validateType(node); final isRecursive = recursiveTypesValidator.isRecursive(node); if (isRecursive) { recursiveId = _recursiveTypeIds.length; _recursiveTypeIds[node] = recursiveId; } List instantiatorArgs = getInstantiatorTypeArguments(node.classNode, node.typeArguments); ObjectHandle typeArgsHandle = objectTable.getTypeArgumentsHandle(instantiatorArgs); final result = objectTable.getOrAddObject(isRecursive ? new _RecursiveGenericTypeHandle( recursiveId, classHandle, typeArgsHandle, node.nullability) : new _GenericTypeHandle( classHandle, typeArgsHandle, node.nullability)); if (isRecursive) { _recursiveTypeIds.remove(node); } return result; } @override ObjectHandle visitTypeParameterType(TypeParameterType node) { final param = node.parameter; final handle = _typeParameters[param]; if (handle != null) { final typeParameterHandle = handle as _TypeParameterHandle; if (typeParameterHandle.nullability == node.nullability) { return handle; } return objectTable.getOrAddObject(new _TypeParameterHandle( typeParameterHandle.parent, typeParameterHandle.indexInParent, node.nullability)); } final parent = param.parent; if (parent == null) { throw 'Type parameter $param without parent, but not declared by function type'; } ObjectHandle parentHandle; int indexInParent; if (parent is Class) { parentHandle = objectTable.getHandle(parent); indexInParent = parent.typeParameters.indexOf(param); if (indexInParent < 0) { throw 'Type parameter $param is not found in its parent class $parent'; } } else if (parent is FunctionNode) { final funcParent = parent.parent; if (funcParent is Member) { parentHandle = objectTable.getHandle(funcParent); } else if (funcParent is FunctionExpression || funcParent is FunctionDeclaration) { parentHandle = objectTable.getHandle(parent); } else { throw 'Unexpected parent of FunctionNode: ${funcParent.runtimeType} $funcParent'; } indexInParent = parent.typeParameters.indexOf(node.parameter); if (indexInParent < 0) { throw 'Type parameter $param is not found in its parent function $parent'; } } else { throw 'Unexpected parent of TypeParameter: ${parent.runtimeType} $parent'; } return objectTable.getOrAddObject(new _TypeParameterHandle( parentHandle, indexInParent, node.nullability)); } @override ObjectHandle visitFunctionType(FunctionType node) { final int numEnclosingTypeParameters = _typeParameters.length; for (int i = 0; i < node.typeParameters.length; ++i) { _typeParameters[node.typeParameters[i]] = objectTable.getOrAddObject( new _TypeParameterHandle( null, numEnclosingTypeParameters + i, Nullability.legacy)); // Nullability.legacy is a dummy value, since TypeParameter does not // specify nullability, only the reference to a TypeParameter does, i.e. // TypeParameterType. } final positionalParams = new List<_TypeHandle>(); for (var param in node.positionalParameters) { positionalParams.add(objectTable.getHandle(param)); } final namedParams = new List(); for (var param in node.namedParameters) { namedParams.add(new NameAndType( objectTable.getPublicNameHandle(param.name), objectTable.getHandle(param.type))); } final returnType = objectTable.getHandle(node.returnType); final result = objectTable.getOrAddObject(new _FunctionTypeHandle( objectTable.getTypeParameterHandles(node.typeParameters), node.requiredParameterCount, positionalParams, namedParams, returnType, node.nullability)); for (int i = 0; i < node.typeParameters.length; ++i) { _typeParameters.remove(node.typeParameters[i]); } return result; } @override ObjectHandle visitTypedefType(TypedefType node) => objectTable.getHandle(node.unalias); @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 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(new InterfaceType( node.classNode, Nullability.legacy, node.typeArguments)))); @override ObjectHandle visitTearOffConstant(TearOffConstant node) => objectTable.getOrAddObject(new _ConstObjectHandle( ConstTag.kTearOff, objectTable.getHandle(node.procedure))); @override ObjectHandle visitTypeLiteralConstant(TypeLiteralConstant node) => objectTable.getHandle(node.type); @override ObjectHandle visitPartialInstantiationConstant( PartialInstantiationConstant 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;