// 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 } // 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, } enum TypeTag { kInvalid, kDynamic, kVoid, kSimpleType, kTypeParameter, kGenericType, kRecursiveGenericType, kRecursiveTypeRef, kFunctionType, } /// 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 flagsMask = flagBit0 | flagBit1 | flagBit2 | flagBit3; 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; 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 new _NameHandle._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: switch (TypeTag.values[flags ~/ flagBit0]) { case TypeTag.kInvalid: break; case TypeTag.kDynamic: return new _DynamicTypeHandle(); case TypeTag.kVoid: return new _VoidTypeHandle(); case TypeTag.kSimpleType: return new _SimpleTypeHandle._empty(); case TypeTag.kTypeParameter: return new _TypeParameterHandle._empty(); case TypeTag.kGenericType: return new _GenericTypeHandle._empty(); case TypeTag.kRecursiveGenericType: return new _RecursiveGenericTypeHandle._empty(); case TypeTag.kRecursiveTypeRef: return new _RecursiveTypeRefHandle._empty(); case TypeTag.kFunctionType: return new _FunctionTypeHandle._empty(); } 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 { _NameHandle 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.name; } 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 { final TypeTag tag; _TypeHandle(this.tag); @override ObjectKind get kind => ObjectKind.kType; @override int get flags => tag.index * ObjectHandle.flagBit0; @override set flags(int value) { if (value != flags) { throw 'Unable to set flags for _TypeHandle (they are occupied by type tag)'; } } } class _DynamicTypeHandle extends _TypeHandle { _DynamicTypeHandle() : super(TypeTag.kDynamic); @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); @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 _SimpleTypeHandle extends _TypeHandle { _ClassHandle class_; _SimpleTypeHandle._empty() : super(TypeTag.kSimpleType); _SimpleTypeHandle(this.class_) : super(TypeTag.kSimpleType); @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 => class_.hashCode + 11; @override bool operator ==(other) => other is _SimpleTypeHandle && this.class_ == other.class_; @override String toString() => '$class_'; } class _TypeParameterHandle extends _TypeHandle { ObjectHandle parent; int indexInParent; _TypeParameterHandle._empty() : super(TypeTag.kTypeParameter); _TypeParameterHandle(this.parent, this.indexInParent) : super(TypeTag.kTypeParameter) { 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, indexInParent); @override bool operator ==(other) => other is _TypeParameterHandle && this.parent == other.parent && this.indexInParent == other.indexInParent; @override String toString() => '$parent::TypeParam/$indexInParent'; } class _GenericTypeHandle extends _TypeHandle { _ClassHandle class_; _TypeArgumentsHandle typeArgs; _GenericTypeHandle._empty() : super(TypeTag.kGenericType); _GenericTypeHandle(this.class_, this.typeArgs) : super(TypeTag.kGenericType); @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, typeArgs.hashCode); @override bool operator ==(other) => other is _GenericTypeHandle && this.class_ == other.class_ && this.typeArgs == other.typeArgs; @override String toString() => '$class_ $typeArgs'; } class _RecursiveGenericTypeHandle extends _TypeHandle { int id; _ClassHandle class_; _TypeArgumentsHandle typeArgs; _RecursiveGenericTypeHandle._empty() : super(TypeTag.kRecursiveGenericType); _RecursiveGenericTypeHandle(this.id, this.class_, this.typeArgs) : super(TypeTag.kRecursiveGenericType); @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, typeArgs.hashCode); @override bool operator ==(other) => other is _RecursiveGenericTypeHandle && this.class_ == other.class_ && this.typeArgs == other.typeArgs; @override String toString() => '(recursive #$id) $class_ $typeArgs'; } class _RecursiveTypeRefHandle extends _TypeHandle { int id; _RecursiveTypeRefHandle._empty() : super(TypeTag.kRecursiveTypeRef); _RecursiveTypeRefHandle(this.id) : super(TypeTag.kRecursiveTypeRef); @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 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() : super(TypeTag.kFunctionType); _FunctionTypeHandle(this.typeParams, this.numRequiredParams, this.positionalParams, this.namedParams, this.returnType) : super(TypeTag.kFunctionType) { 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 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(); } } class _NameHandle extends ObjectHandle { _LibraryHandle library; String name; _NameHandle._empty(); _NameHandle(this.library, this.name); @override ObjectKind get kind => ObjectKind.kName; @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) { if (library != null) { library._useCount += numCopies; } } @override void indexStrings(StringWriter strings) { strings.put(name); } @override int get hashCode => _combineHashes(name.hashCode, library.hashCode); @override bool operator ==(other) => other is _NameHandle && 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 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; _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; 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; default: throw 'Unexpected constant tag: $tag'; } } @override void accountUsesForObjectCopies(int numCopies) { switch (tag) { case ConstTag.kInt: case ConstTag.kDouble: case ConstTag.kBool: 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 { switch (tag) { case ConstTag.kInt: case ConstTag.kDouble: case ConstTag.kBool: case ConstTag.kTearOff: case ConstTag.kSymbol: return value.hashCode; case ConstTag.kInstance: { final fieldValues = value as Map; return _combineHashes(type.hashCode, mapHashCode(fieldValues)); } break; case ConstTag.kList: { final elems = value as List; return _combineHashes(type.hashCode, listHashCode(elems)); } break; case ConstTag.kTearOffInstantiation: return _combineHashes(value.hashCode, type.hashCode); default: throw 'Unexpected constant tag: $tag'; } } @override bool operator ==(other) { if (other is _ConstObjectHandle && this.tag == other.tag) { switch (tag) { case ConstTag.kInt: case ConstTag.kBool: case ConstTag.kTearOff: case ConstTag.kSymbol: 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'; 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<_NameHandle> 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<_NameHandle>() : 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; @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 ${source.importUri})' : ''}"; } 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 = {}; List _indexTable; _TypeHandle _dynamicType; _TypeHandle _voidType; CoreTypes coreTypes; _NodeVisitor _nodeVisitor; ObjectTable() { _dynamicType = getOrAddObject(new _DynamicTypeHandle()); _voidType = getOrAddObject(new _VoidTypeHandle()); _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) => nodes.map((n) => getHandle(n)).toList(); 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 getNameHandle(Library library, String name) { assert(name != null); final libraryHandle = library != null ? getHandle(library) : null; return getOrAddObject(new _NameHandle(libraryHandle, name)); } 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 = getNameHandle(null, 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"; } if (member is Constructor || member is Procedure && member.isFactory) {} 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 List<_TypeHandle> handles = typeArgs.map((t) => getHandle(t) as _TypeHandle).toList(); return getOrAddObject(new _TypeArgumentsHandle(handles)); } ObjectHandle getArgDescHandle(int numArguments, [int numTypeArguments = 0, List argNames = const []]) { return getOrAddObject(new _ArgDescHandle( numArguments, numTypeArguments, argNames .map<_NameHandle>((name) => getNameHandle(null, name)) .toList())); } ObjectHandle getArgDescHandleByArguments(Arguments args, {bool hasReceiver: false, bool isFactory: false}) { return getArgDescHandle( 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), isFactory ? 0 : args.types.length, new List.from(args.named.map((ne) => ne.name))); } ObjectHandle getScriptHandle(Uri uri, SourceFile source) { ObjectHandle uriHandle = getNameHandle(null, uri.toString()); _ScriptHandle handle = getOrAddObject(new _ScriptHandle(uriHandle, source)); if (handle.source == null && source != null) { handle.source = source; } return handle; } 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.putIfAbsent(obj, () { assert(_indexTable == null); _objects.add(obj); return 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._useCount >= indexTableUseCountThreshold && obj.isCacheable) { // 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.writeBytes(contentsWriter.takeBytes()); 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.getNameHandle(null, node.importUri.toString()); return objectTable.getOrAddObject(new _LibraryHandle(uri)); } @override ObjectHandle visitClass(Class node) { final ObjectHandle library = objectTable.getHandle(node.enclosingLibrary); final name = objectTable.getOrAddObject( new _NameHandle(node.name.startsWith('_') ? library : null, 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 visitBottomType(BottomType node) => objectTable.getHandle(objectTable.coreTypes.nullClass.rawType); @override ObjectHandle visitInterfaceType(InterfaceType node) { final classHandle = objectTable.getHandle(node.classNode); if (!hasInstantiatorTypeArguments(node.classNode)) { return objectTable.getOrAddObject(new _SimpleTypeHandle(classHandle)); } // 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) : new _GenericTypeHandle(classHandle, typeArgsHandle)); if (isRecursive) { _recursiveTypeIds.remove(node); } return result; } @override ObjectHandle visitTypeParameterType(TypeParameterType node) { final param = node.parameter; final handle = _typeParameters[param]; if (handle != null) { return handle; } 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)); } @override ObjectHandle visitFunctionType(FunctionType node) { final typeParameters = new List<_TypeParameterHandle>.generate( node.typeParameters.length, (i) => objectTable.getOrAddObject(new _TypeParameterHandle(null, i))); for (int i = 0; i < node.typeParameters.length; ++i) { _typeParameters[node.typeParameters[i]] = typeParameters[i]; } 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.getNameHandle(null, param.name), objectTable.getHandle(param.type))); } final returnType = objectTable.getHandle(node.returnType); final result = objectTable.getOrAddObject(new _FunctionTypeHandle( node.typeParameters .map((tp) => new NameAndType( objectTable.getNameHandle(null, tp.name), objectTable.getHandle(tp.bound))) .toList(), node.requiredParameterCount, positionalParams, namedParams, returnType)); 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.getNameHandle(null, 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, new List.from( node.entries.map((Constant c) => objectTable.getHandle(c))), 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, 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;