// 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.constant_pool; import 'package:kernel/ast.dart' hide MapEntry; import 'dbc.dart' show constantPoolIndexLimit, BytecodeLimitExceededException; import 'bytecode_serialization.dart' show BufferedWriter, BufferedReader, BytecodeSizeStatistics, NamedEntryStatistics, StringTable; import 'object_table.dart' show ObjectHandle, ObjectTable; /* In kernel binary, constant pool is encoded in the following way (using notation from pkg/kernel/binary.md): type ConstantPool { List } type ConstantIndex = UInt; abstract type ConstantPoolEntry { Byte tag; } enum InvocationKind { method, // x.foo(...) or foo(...) getter, // x.foo setter // x.foo = ... } type ConstantICData extends ConstantPoolEntry { Byte tag = 7; Byte flags(invocationKindBit0, invocationKindBit1, isDynamic); // Where invocationKind is index into InvocationKind. PackedObject targetName; ConstantIndex argDesc; } type ConstantStaticField extends ConstantPoolEntry { Byte tag = 9; PackedObject field; } // Occupies 2 entries in the constant pool. type ConstantInstanceField extends ConstantPoolEntry { Byte tag = 10; PackedObject field; } type ConstantClass extends ConstantPoolEntry { Byte tag = 11; PackedObject class; } type ConstantTypeArgumentsField extends ConstantPoolEntry { Byte tag = 12; PackedObject class; } type ConstantType extends ConstantPoolEntry { Byte tag = 14; PackedObject type; } type ConstantClosureFunction extends ConstantPoolEntry { Byte tag = 19; UInt closureIndex; } type ConstantEndClosureFunctionScope extends ConstantPoolEntry { Byte tag = 20; } type ConstantNativeEntry extends ConstantPoolEntry { Byte tag = 21; PackedString nativeName; } type ConstantSubtypeTestCache extends ConstantPoolEntry { Byte tag = 22; } type ConstantEmptyTypeArguments extends ConstantPoolEntry { Byte tag = 24; } type ConstantObjectRef extends ConstantPoolEntry { Byte tag = 27; PackedObject object; } // Occupies 2 entries in the constant pool. type ConstantDirectCall extends ConstantPoolEntry { Byte tag = 28; PackedObject target; PackedObject argDesc; } // Occupies 2 entries in the constant pool. type ConstantInterfaceCall extends ConstantPoolEntry { Byte tag = 29; PackedObject target; PackedObject argDesc; } */ enum ConstantTag { kInvalid, kUnused1, kUnused2, kUnused3, kUnused4, kUnused5, kUnused6, kICData, kUnused7, kStaticField, kInstanceField, kClass, kTypeArgumentsField, kUnused8, kType, kUnused9, kUnused10, kUnused11, kUnused12, kClosureFunction, kEndClosureFunctionScope, kNativeEntry, kSubtypeTestCache, kUnused13, kEmptyTypeArguments, kUnused14, kUnused15, kObjectRef, kDirectCall, kInterfaceCall, } String constantTagToString(ConstantTag tag) => tag.toString().substring('ConstantTag.k'.length); abstract class ConstantPoolEntry { const ConstantPoolEntry(); ConstantTag get tag; // Returns number of extra reserved constant pool entries // following this entry. int get numReservedEntries => 0; void write(BufferedWriter writer) { writer.writeByte(tag.index); writeValue(writer); } void writeValue(BufferedWriter writer); factory ConstantPoolEntry.read(BufferedReader reader) { ConstantTag tag = ConstantTag.values[reader.readByte()]; switch (tag) { case ConstantTag.kInvalid: break; case ConstantTag.kICData: return new ConstantICData.read(reader); case ConstantTag.kStaticField: return new ConstantStaticField.read(reader); case ConstantTag.kInstanceField: return new ConstantInstanceField.read(reader); case ConstantTag.kClass: return new ConstantClass.read(reader); case ConstantTag.kTypeArgumentsField: return new ConstantTypeArgumentsField.read(reader); case ConstantTag.kType: return new ConstantType.read(reader); case ConstantTag.kClosureFunction: return new ConstantClosureFunction.read(reader); case ConstantTag.kEndClosureFunctionScope: return new ConstantEndClosureFunctionScope.read(reader); case ConstantTag.kNativeEntry: return new ConstantNativeEntry.read(reader); case ConstantTag.kSubtypeTestCache: return new ConstantSubtypeTestCache.read(reader); case ConstantTag.kEmptyTypeArguments: return new ConstantEmptyTypeArguments.read(reader); case ConstantTag.kObjectRef: return new ConstantObjectRef.read(reader); case ConstantTag.kDirectCall: return new ConstantDirectCall.read(reader); case ConstantTag.kInterfaceCall: return new ConstantInterfaceCall.read(reader); // Make analyzer happy. case ConstantTag.kUnused1: case ConstantTag.kUnused2: case ConstantTag.kUnused3: case ConstantTag.kUnused4: case ConstantTag.kUnused5: case ConstantTag.kUnused6: case ConstantTag.kUnused7: case ConstantTag.kUnused8: case ConstantTag.kUnused9: case ConstantTag.kUnused10: case ConstantTag.kUnused11: case ConstantTag.kUnused12: case ConstantTag.kUnused13: case ConstantTag.kUnused14: case ConstantTag.kUnused15: break; } throw 'Unexpected constant tag $tag'; } } enum InvocationKind { method, getter, setter } String _invocationKindToString(InvocationKind kind) { switch (kind) { case InvocationKind.method: return ''; case InvocationKind.getter: return 'get '; case InvocationKind.setter: return 'set '; } throw 'Unexpected InvocationKind $kind'; } class ConstantICData extends ConstantPoolEntry { static const int invocationKindMask = 3; static const int flagDynamic = 1 << 2; final int _flags; final ObjectHandle targetName; final int argDescConstantIndex; ConstantICData(InvocationKind invocationKind, this.targetName, this.argDescConstantIndex, bool isDynamic) : assert(invocationKind.index <= invocationKindMask), _flags = invocationKind.index | (isDynamic ? flagDynamic : 0); InvocationKind get invocationKind => InvocationKind.values[_flags & invocationKindMask]; bool get isDynamic => (_flags & flagDynamic) != 0; @override ConstantTag get tag => ConstantTag.kICData; @override void writeValue(BufferedWriter writer) { writer.writeByte(_flags); writer.writePackedObject(targetName); writer.writePackedUInt30(argDescConstantIndex); } ConstantICData.read(BufferedReader reader) : _flags = reader.readByte(), targetName = reader.readPackedObject(), argDescConstantIndex = reader.readPackedUInt30(); @override String toString() => 'ICData ' '${isDynamic ? 'dynamic ' : ''}' '${_invocationKindToString(invocationKind)}' 'target-name $targetName, arg-desc CP#$argDescConstantIndex'; // ConstantICData entries are created per call site and should not be merged, // so ConstantICData class uses identity [hashCode] and [operator ==]. @override int get hashCode => identityHashCode(this); @override bool operator ==(other) => identical(this, other); } class ConstantStaticField extends ConstantPoolEntry { final ObjectHandle field; ConstantStaticField(this.field); @override ConstantTag get tag => ConstantTag.kStaticField; @override void writeValue(BufferedWriter writer) { writer.writePackedObject(field); } ConstantStaticField.read(BufferedReader reader) : field = reader.readPackedObject(); @override String toString() => 'StaticField $field'; @override int get hashCode => field.hashCode; @override bool operator ==(other) => other is ConstantStaticField && this.field == other.field; } class ConstantInstanceField extends ConstantPoolEntry { final ObjectHandle field; int get numReservedEntries => 1; ConstantInstanceField(this.field); @override ConstantTag get tag => ConstantTag.kInstanceField; @override void writeValue(BufferedWriter writer) { writer.writePackedObject(field); } ConstantInstanceField.read(BufferedReader reader) : field = reader.readPackedObject(); @override String toString() => 'InstanceField $field'; @override int get hashCode => field.hashCode; @override bool operator ==(other) => other is ConstantInstanceField && this.field == other.field; } class ConstantClass extends ConstantPoolEntry { final ObjectHandle classHandle; ConstantClass(this.classHandle); @override ConstantTag get tag => ConstantTag.kClass; @override void writeValue(BufferedWriter writer) { writer.writePackedObject(classHandle); } ConstantClass.read(BufferedReader reader) : classHandle = reader.readPackedObject(); @override String toString() => 'Class $classHandle'; @override int get hashCode => classHandle.hashCode; @override bool operator ==(other) => other is ConstantClass && this.classHandle == other.classHandle; } class ConstantTypeArgumentsField extends ConstantPoolEntry { final ObjectHandle classHandle; ConstantTypeArgumentsField(this.classHandle); @override ConstantTag get tag => ConstantTag.kTypeArgumentsField; @override void writeValue(BufferedWriter writer) { writer.writePackedObject(classHandle); } ConstantTypeArgumentsField.read(BufferedReader reader) : classHandle = reader.readPackedObject(); @override String toString() => 'TypeArgumentsField $classHandle'; @override int get hashCode => classHandle.hashCode; @override bool operator ==(other) => other is ConstantTypeArgumentsField && this.classHandle == other.classHandle; } class ConstantType extends ConstantPoolEntry { final ObjectHandle type; ConstantType(this.type); @override ConstantTag get tag => ConstantTag.kType; @override void writeValue(BufferedWriter writer) { writer.writePackedObject(type); } ConstantType.read(BufferedReader reader) : type = reader.readPackedObject(); @override String toString() => 'Type $type'; @override int get hashCode => type.hashCode; @override bool operator ==(other) => other is ConstantType && this.type == other.type; } class ConstantClosureFunction extends ConstantPoolEntry { final int closureIndex; ConstantClosureFunction(this.closureIndex); @override ConstantTag get tag => ConstantTag.kClosureFunction; @override void writeValue(BufferedWriter writer) { writer.writePackedUInt30(closureIndex); } ConstantClosureFunction.read(BufferedReader reader) : closureIndex = reader.readPackedUInt30(); @override String toString() { return 'ClosureFunction $closureIndex'; } @override int get hashCode => closureIndex; @override bool operator ==(other) => other is ConstantClosureFunction && this.closureIndex == other.closureIndex; } class ConstantEndClosureFunctionScope extends ConstantPoolEntry { ConstantEndClosureFunctionScope(); @override ConstantTag get tag => ConstantTag.kEndClosureFunctionScope; @override void writeValue(BufferedWriter writer) {} ConstantEndClosureFunctionScope.read(BufferedReader reader) {} @override String toString() => 'EndClosureFunctionScope'; // ConstantEndClosureFunctionScope entries are created per closure and should // not be merged, so ConstantEndClosureFunctionScope class uses identity // [hashCode] and [operator ==]. } class ConstantNativeEntry extends ConstantPoolEntry { final String nativeName; ConstantNativeEntry(this.nativeName); @override ConstantTag get tag => ConstantTag.kNativeEntry; @override void writeValue(BufferedWriter writer) { writer.writePackedStringReference(nativeName); } ConstantNativeEntry.read(BufferedReader reader) : nativeName = reader.readPackedStringReference(); @override String toString() => 'NativeEntry $nativeName'; @override int get hashCode => nativeName.hashCode; @override bool operator ==(other) => other is ConstantNativeEntry && this.nativeName == other.nativeName; } class ConstantSubtypeTestCache extends ConstantPoolEntry { ConstantSubtypeTestCache(); @override ConstantTag get tag => ConstantTag.kSubtypeTestCache; @override void writeValue(BufferedWriter writer) {} ConstantSubtypeTestCache.read(BufferedReader reader); @override String toString() => 'SubtypeTestCache'; // ConstantSubtypeTestCache entries are created per subtype test site and // should not be merged, so ConstantSubtypeTestCache class uses identity // [hashCode] and [operator ==]. @override int get hashCode => identityHashCode(this); @override bool operator ==(other) => identical(this, other); } class ConstantEmptyTypeArguments extends ConstantPoolEntry { const ConstantEmptyTypeArguments(); @override ConstantTag get tag => ConstantTag.kEmptyTypeArguments; @override void writeValue(BufferedWriter writer) {} ConstantEmptyTypeArguments.read(BufferedReader reader); @override String toString() => 'EmptyTypeArguments'; @override int get hashCode => 997; @override bool operator ==(other) => other is ConstantEmptyTypeArguments; } class ConstantObjectRef extends ConstantPoolEntry { final ObjectHandle object; ConstantObjectRef(this.object); @override ConstantTag get tag => ConstantTag.kObjectRef; @override void writeValue(BufferedWriter writer) { writer.writePackedObject(object); } ConstantObjectRef.read(BufferedReader reader) : object = reader.readPackedObject(); @override String toString() => 'ObjectRef $object'; @override int get hashCode => object.hashCode; @override bool operator ==(other) => other is ConstantObjectRef && this.object == other.object; } class ConstantDirectCall extends ConstantPoolEntry { final ObjectHandle target; final ObjectHandle argDesc; ConstantDirectCall(this.target, this.argDesc); // Reserve 1 extra slot for arguments descriptor, following target slot. int get numReservedEntries => 1; @override ConstantTag get tag => ConstantTag.kDirectCall; @override void writeValue(BufferedWriter writer) { writer.writePackedObject(target); writer.writePackedObject(argDesc); } ConstantDirectCall.read(BufferedReader reader) : target = reader.readPackedObject(), argDesc = reader.readPackedObject(); @override String toString() => "DirectCall '$target', $argDesc"; @override int get hashCode => _combineHashes(target.hashCode, argDesc.hashCode); @override bool operator ==(other) => other is ConstantDirectCall && this.target == other.target && this.argDesc == other.argDesc; } class ConstantInterfaceCall extends ConstantPoolEntry { final ObjectHandle target; final ObjectHandle argDesc; ConstantInterfaceCall(this.target, this.argDesc); // Reserve 1 extra slot for arguments descriptor, following target slot. int get numReservedEntries => 1; @override ConstantTag get tag => ConstantTag.kInterfaceCall; @override void writeValue(BufferedWriter writer) { writer.writePackedObject(target); writer.writePackedObject(argDesc); } ConstantInterfaceCall.read(BufferedReader reader) : target = reader.readPackedObject(), argDesc = reader.readPackedObject(); @override String toString() => "InterfaceCall '$target', $argDesc"; @override int get hashCode => _combineHashes(target.hashCode, argDesc.hashCode); @override bool operator ==(other) => other is ConstantInterfaceCall && this.target == other.target && this.argDesc == other.argDesc; } /// Reserved constant pool entry. class _ReservedConstantPoolEntry extends ConstantPoolEntry { const _ReservedConstantPoolEntry(); ConstantTag get tag => throw 'This constant pool entry is reserved'; void writeValue(BufferedWriter writer) => throw 'This constant pool entry is reserved'; @override String toString() => 'Reserved'; } class ConstantPool { final StringTable stringTable; final ObjectTable objectTable; final List entries = []; final Map _canonicalizationCache = {}; ConstantPool(this.stringTable, this.objectTable); int addString(String value) => addObjectRef(new StringConstant(value)); int addArgDesc(int numArguments, {int numTypeArgs = 0, List argNames = const []}) => _add(new ConstantObjectRef( objectTable.getArgDescHandle(numArguments, numTypeArgs, argNames))); int addArgDescByArguments(Arguments args, {bool hasReceiver: false, bool isFactory: false}) => _add(new ConstantObjectRef(objectTable.getArgDescHandleByArguments(args, hasReceiver: hasReceiver, isFactory: isFactory))); int addICData( InvocationKind invocationKind, Name targetName, int argDescCpIndex, {bool isDynamic: false}) => _add(new ConstantICData( invocationKind, objectTable.getSelectorNameHandle(targetName, isGetter: invocationKind == InvocationKind.getter, isSetter: invocationKind == InvocationKind.setter), argDescCpIndex, isDynamic)); int addDirectCall( InvocationKind invocationKind, Member target, ObjectHandle argDesc) => _add(new ConstantDirectCall( objectTable.getMemberHandle(target, isGetter: invocationKind == InvocationKind.getter, isSetter: invocationKind == InvocationKind.setter), argDesc)); int addInterfaceCall( InvocationKind invocationKind, Member target, ObjectHandle argDesc) => _add(new ConstantInterfaceCall( objectTable.getMemberHandle(target, isGetter: invocationKind == InvocationKind.getter, isSetter: invocationKind == InvocationKind.setter), argDesc)); int addInstanceCall(InvocationKind invocationKind, Member target, Name targetName, ObjectHandle argDesc) => (target == null) ? addICData( invocationKind, targetName, _add(new ConstantObjectRef(argDesc)), isDynamic: true) : addInterfaceCall(invocationKind, target, argDesc); int addStaticField(Field field) => _add(new ConstantStaticField(objectTable.getHandle(field))); int addInstanceField(Field field) => _add(new ConstantInstanceField(objectTable.getHandle(field))); int addClass(Class node) => _add(new ConstantClass(objectTable.getHandle(node))); int addTypeArgumentsField(Class node) => _add(new ConstantTypeArgumentsField(objectTable.getHandle(node))); int addType(DartType type) => _add(new ConstantType(objectTable.getHandle(type))); int addTypeArguments(List typeArgs) => _add(new ConstantObjectRef(objectTable.getTypeArgumentsHandle(typeArgs))); int addClosureFunction(int closureIndex) => _add(new ConstantClosureFunction(closureIndex)); int addEndClosureFunctionScope() => _add(new ConstantEndClosureFunctionScope()); int addNativeEntry(String nativeName) => _add(new ConstantNativeEntry(_indexString(nativeName))); int addSubtypeTestCache() => _add(new ConstantSubtypeTestCache()); int addEmptyTypeArguments() => _add(const ConstantEmptyTypeArguments()); int addObjectRef(Node node) => _add(new ConstantObjectRef(objectTable.getHandle(node))); int _add(ConstantPoolEntry entry) { return _canonicalizationCache.putIfAbsent(entry, () { int index = entries.length; if (index >= constantPoolIndexLimit) { throw new ConstantPoolIndexOverflowException(); } _addEntry(entry); return index; }); } void _addEntry(ConstantPoolEntry entry) { entries.add(entry); for (int i = 0; i < entry.numReservedEntries; ++i) { entries.add(const _ReservedConstantPoolEntry()); } } // Currently, string table is written as a part of Component's metadata // *before* constant pools are written. // So we need to index all strings when filling up constant pools. String _indexString(String str) { stringTable.put(str); return str; } void write(BufferedWriter writer) { final start = writer.offset; if (BytecodeSizeStatistics.constantPoolStats.isEmpty) { for (var tag in ConstantTag.values) { BytecodeSizeStatistics.constantPoolStats .add(new NamedEntryStatistics(constantTagToString(tag))); } } writer.writePackedUInt30(entries.length); entries.forEach((e) { if (e is _ReservedConstantPoolEntry) { return; } final entryStart = writer.offset; e.write(writer); final entryStat = BytecodeSizeStatistics.constantPoolStats[e.tag.index]; entryStat.size += (writer.offset - entryStart); ++entryStat.count; }); BytecodeSizeStatistics.constantPoolSize += (writer.offset - start); } ConstantPool.read(BufferedReader reader) : stringTable = reader.stringReader, objectTable = reader.objectReader { int len = reader.readPackedUInt30(); for (int i = 0; i < len; i++) { final e = new ConstantPoolEntry.read(reader); _addEntry(e); i += e.numReservedEntries; } } @override String toString() { StringBuffer sb = new StringBuffer(); sb.writeln('ConstantPool {'); for (int i = 0; i < entries.length; i++) { sb.writeln(' [$i] = ${entries[i]}'); } sb.writeln('}'); return sb.toString(); } } int _combineHashes(int hash1, int hash2) => (((hash1 * 31) & 0x3fffffff) + hash2) & 0x3fffffff; class ConstantPoolIndexOverflowException extends BytecodeLimitExceededException {}