| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:kernel/ast.dart'; |
| import 'package:vm/transformations/type_flow/types.dart' show RecordShape; |
| |
| enum UnboxingKind { |
| boxed, |
| int, |
| double, |
| record, |
| unknown, // Not calculated yet. |
| } |
| |
| class UnboxingType { |
| final UnboxingKind kind; |
| final RecordShape? recordShape; |
| |
| const UnboxingType._(this.kind, this.recordShape); |
| UnboxingType.record(RecordShape shape) : this._(UnboxingKind.record, shape); |
| |
| static const kUnknown = UnboxingType._(UnboxingKind.unknown, null); |
| static const kInt = UnboxingType._(UnboxingKind.int, null); |
| static const kDouble = UnboxingType._(UnboxingKind.double, null); |
| static const kBoxed = UnboxingType._(UnboxingKind.boxed, null); |
| |
| UnboxingType intersect(UnboxingType other) { |
| if (kind == UnboxingKind.unknown) return other; |
| if (other.kind == UnboxingKind.unknown) return this; |
| if (this == other) return this; |
| return kBoxed; |
| } |
| |
| @override |
| bool operator ==(other) => |
| identical(this, other) || |
| (other is UnboxingType && |
| this.kind == other.kind && |
| this.recordShape == other.recordShape); |
| |
| @override |
| int get hashCode => (kind.index * 31) + recordShape.hashCode; |
| |
| void writeToBinary(BinarySink sink) { |
| sink.writeUInt30(kind.index); |
| if (kind == UnboxingKind.record) { |
| recordShape!.writeToBinary(sink); |
| } |
| } |
| |
| factory UnboxingType.readFromBinary(BinarySource source) { |
| final kind = UnboxingKind.values[source.readUInt30()]; |
| final recordShape = |
| (kind == UnboxingKind.record) |
| ? RecordShape.readFromBinary(source) |
| : null; |
| return UnboxingType._(kind, recordShape); |
| } |
| |
| @override |
| String toString() { |
| switch (kind) { |
| case UnboxingKind.boxed: |
| return 'b'; |
| case UnboxingKind.int: |
| return 'i'; |
| case UnboxingKind.double: |
| return 'd'; |
| case UnboxingKind.record: |
| { |
| final sb = StringBuffer(); |
| sb.write('r<'); |
| sb.write(recordShape!.numPositionalFields.toString()); |
| for (final named in recordShape!.namedFields) { |
| sb.write(','); |
| sb.write(named); |
| } |
| sb.write('>'); |
| return sb.toString(); |
| } |
| case UnboxingKind.unknown: |
| return '_|_'; |
| } |
| } |
| } |
| |
| class UnboxingInfoMetadata { |
| /// For GDT selectors the length of this array reflects minimum number of |
| /// direct parameters (excluding this) across all implementations reachable |
| /// through a selector. If there is an override with less direct parameters |
| /// than interface target has declared then |
| /// [hasOverridesWithLessDirectParameters] must be set to `true`. |
| final List<UnboxingType> argsInfo; |
| UnboxingType returnInfo; |
| bool mustUseStackCallingConvention; |
| bool hasOverridesWithLessDirectParameters = false; |
| |
| UnboxingInfoMetadata( |
| int argsLen, { |
| UnboxingType initialValue = UnboxingType.kUnknown, |
| }) : argsInfo = List<UnboxingType>.filled( |
| argsLen, |
| initialValue, |
| growable: true, |
| ), |
| returnInfo = initialValue, |
| mustUseStackCallingConvention = false; |
| |
| factory UnboxingInfoMetadata.readFromBinary(BinarySource source) { |
| final result = UnboxingInfoMetadata( |
| source.readUInt30(), |
| initialValue: UnboxingType.kBoxed, |
| ); |
| final flags = source.readByte(); |
| result.mustUseStackCallingConvention = |
| (flags & _mustUseStackCallingConventionFlag) != 0; |
| result.hasOverridesWithLessDirectParameters = |
| (flags & _hasOverridesWithLessDirectParametersFlag) != 0; |
| if ((flags & _hasUnboxedParameterOrReturnValueFlag) != 0) { |
| for (var i = 0; i < result.argsInfo.length; i++) { |
| result.argsInfo[i] = UnboxingType.readFromBinary(source); |
| } |
| result.returnInfo = UnboxingType.readFromBinary(source); |
| } |
| return result; |
| } |
| |
| void adjustParameterCount(int argsLen) { |
| if (argsLen != argsInfo.length) { |
| hasOverridesWithLessDirectParameters = true; |
| } |
| |
| if (argsLen < argsInfo.length) { |
| argsInfo.length = argsLen; |
| } |
| } |
| |
| bool get hasUnboxedParameterOrReturnValue { |
| if (returnInfo != UnboxingType.kBoxed) return true; |
| for (final argInfo in argsInfo) { |
| if (argInfo != UnboxingType.kBoxed) return true; |
| } |
| return false; |
| } |
| |
| void setFullyBoxed() { |
| argsInfo.length = 0; |
| returnInfo = UnboxingType.kBoxed; |
| mustUseStackCallingConvention = true; |
| } |
| |
| // Returns `true` if this [UnboxingInfoMetadata] matches default one: |
| // all arguments and the return value are boxed, the method is not |
| // forced to use stack based calling convention and there is no override |
| // which uses less direct parameters. |
| // |
| // Trivial metadata can be omitted and not written into the Kernel binary. |
| bool get isTrivial { |
| return !mustUseStackCallingConvention && |
| !hasOverridesWithLessDirectParameters && |
| !hasUnboxedParameterOrReturnValue; |
| } |
| |
| static const _mustUseStackCallingConventionFlag = 1 << 0; |
| static const _hasUnboxedParameterOrReturnValueFlag = 1 << 1; |
| static const _hasOverridesWithLessDirectParametersFlag = 1 << 2; |
| |
| void writeToBinary(BinarySink sink) { |
| sink.writeUInt30(argsInfo.length); |
| final flags = |
| (mustUseStackCallingConvention |
| ? _mustUseStackCallingConventionFlag |
| : 0) | |
| (hasUnboxedParameterOrReturnValue |
| ? _hasUnboxedParameterOrReturnValueFlag |
| : 0) | |
| (hasOverridesWithLessDirectParameters |
| ? _hasOverridesWithLessDirectParametersFlag |
| : 0); |
| sink.writeByte(flags); |
| if ((flags & _hasUnboxedParameterOrReturnValueFlag) != 0) { |
| for (final argInfo in argsInfo) { |
| argInfo.writeToBinary(sink); |
| } |
| returnInfo.writeToBinary(sink); |
| } |
| } |
| |
| /// Remove placeholder parameter info slot for setters that the getter is |
| /// grouped with. |
| UnboxingInfoMetadata toGetterInfo() => |
| UnboxingInfoMetadata(0) |
| ..returnInfo = returnInfo |
| ..mustUseStackCallingConvention = mustUseStackCallingConvention; |
| |
| UnboxingInfoMetadata toFieldInfo() { |
| if (argsInfo.length == 1 && argsInfo[0] == UnboxingType.kUnknown) { |
| // Drop information about the setter if we did not compute anything |
| // useful. |
| return toGetterInfo(); |
| } |
| return this; |
| } |
| |
| @override |
| String toString() { |
| final sb = StringBuffer(); |
| if (mustUseStackCallingConvention) { |
| return '[!regcc]'; |
| } |
| sb.write('('); |
| for (int i = 0; i < argsInfo.length; ++i) { |
| final argInfo = argsInfo[i]; |
| sb.write(argInfo.toString()); |
| if (i != (argsInfo.length - 1)) { |
| sb.write(','); |
| } |
| } |
| sb.write(')'); |
| sb.write('->'); |
| sb.write(returnInfo.toString()); |
| return sb.toString(); |
| } |
| } |
| |
| class UnboxingInfoMetadataRepository |
| extends MetadataRepository<UnboxingInfoMetadata> { |
| static const repositoryTag = 'vm.unboxing-info.metadata'; |
| |
| @override |
| final String tag = repositoryTag; |
| |
| @override |
| final Map<TreeNode, UnboxingInfoMetadata> mapping = |
| <TreeNode, UnboxingInfoMetadata>{}; |
| |
| @override |
| void writeToBinary( |
| UnboxingInfoMetadata metadata, |
| Node node, |
| BinarySink sink, |
| ) { |
| metadata.writeToBinary(sink); |
| } |
| |
| @override |
| UnboxingInfoMetadata readFromBinary(Node node, BinarySource source) { |
| return UnboxingInfoMetadata.readFromBinary(source); |
| } |
| } |