| // 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. | |
| // ignore: implementation_imports | |
| import 'package:_fe_analyzer_shared/src/testing/features.dart'; | |
| import '../elements/entities.dart'; | |
| import '../elements/types.dart'; | |
| import 'nodes.dart'; | |
| /// Log used for unit testing optimizations. | |
| class OptimizationTestLog { | |
| final DartTypes _dartTypes; | |
| OptimizationTestLog(this._dartTypes); | |
| List<OptimizationLogEntry> entries = []; | |
| late final Map<String, Set<HInstruction>> _unconverted = {}; | |
| Features? _register( | |
| String tag, | |
| HInstruction original, | |
| HInstruction? converted, | |
| void Function(Features features) f, | |
| ) { | |
| if (converted == null) { | |
| Set<HInstruction> set = _unconverted[tag] ??= {}; | |
| if (!set.add(original)) { | |
| return null; | |
| } | |
| } | |
| Features features = Features(); | |
| f(features); | |
| entries.add(OptimizationLogEntry(tag, features)); | |
| return features; | |
| } | |
| void registerNullCheck(HInstruction original, HNullCheck check) { | |
| Features features = Features(); | |
| if (check.selector != null) { | |
| features['selector'] = check.selector!.name; | |
| } | |
| if (check.field != null) { | |
| features['field'] = | |
| '${check.field!.enclosingClass!.name}.${check.field!.name}'; | |
| } | |
| entries.add(OptimizationLogEntry('NullCheck', features)); | |
| } | |
| void registerConditionValue( | |
| HInstruction original, | |
| bool value, | |
| String where, | |
| int count, | |
| ) { | |
| Features features = Features(); | |
| features['value'] = '$value'; | |
| features['where'] = where; | |
| features['count'] = '$count'; | |
| entries.add(OptimizationLogEntry('ConditionValue', features)); | |
| } | |
| void registerFieldGet(HInvokeDynamicGetter original, HFieldGet converted) { | |
| Features features = Features(); | |
| final element = converted.element; | |
| features['name'] = '${element.enclosingClass!.name}.${element.name}'; | |
| entries.add(OptimizationLogEntry('FieldGet', features)); | |
| } | |
| void registerFieldSet(HInvokeDynamicSetter original, [HFieldSet? converted]) { | |
| Features features = Features(); | |
| if (converted != null) { | |
| features['name'] = | |
| '${converted.element.enclosingClass!.name}.${converted.element.name}'; | |
| } else { | |
| features['removed'] = original.selector.name; | |
| } | |
| entries.add(OptimizationLogEntry('FieldSet', features)); | |
| } | |
| void registerFieldCall(HInvokeDynamicMethod original, HFieldGet converted) { | |
| Features features = Features(); | |
| final element = converted.element; | |
| features['name'] = '${element.enclosingClass!.name}.${element.name}'; | |
| entries.add(OptimizationLogEntry('FieldCall', features)); | |
| } | |
| void registerConstantFieldGet( | |
| HInvokeDynamicGetter original, | |
| FieldEntity field, | |
| HConstant converted, | |
| ) { | |
| Features features = Features(); | |
| features['name'] = '${field.enclosingClass!.name}.${field.name}'; | |
| features['value'] = converted.constant.toStructuredText(_dartTypes); | |
| entries.add(OptimizationLogEntry('ConstantFieldGet', features)); | |
| } | |
| void registerConstantFieldCall( | |
| HInvokeDynamicMethod original, | |
| FieldEntity field, | |
| HConstant converted, | |
| ) { | |
| Features features = Features(); | |
| features['name'] = '${field.enclosingClass!.name}.${field.name}'; | |
| features['value'] = converted.constant.toStructuredText(_dartTypes); | |
| entries.add(OptimizationLogEntry('ConstantFieldCall', features)); | |
| } | |
| Features? _registerSpecializer( | |
| HInvokeDynamic original, | |
| HInstruction? converted, | |
| String? name, [ | |
| String? unconvertedName, | |
| ]) { | |
| assert(!(converted == null && unconvertedName == null)); | |
| return _register('Specializer', original, converted, (Features features) { | |
| if (converted != null) { | |
| features.add(name!); | |
| } else { | |
| features.add(unconvertedName!); | |
| } | |
| }); | |
| } | |
| void registerIndexAssign(HInvokeDynamic original, HIndexAssign converted) { | |
| _registerSpecializer(original, converted, 'IndexAssign'); | |
| } | |
| void registerIndex(HInvokeDynamic original, HIndex converted) { | |
| _registerSpecializer(original, converted, 'Index'); | |
| } | |
| void registerRemoveLast(HInvokeDynamic original, HInvokeDynamic converted) { | |
| _registerSpecializer(original, converted, 'RemoveLast'); | |
| } | |
| void registerBitNot(HInvokeDynamic original, HBitNot converted) { | |
| _registerSpecializer(original, converted, 'BitNot'); | |
| } | |
| void registerUnaryNegate(HInvokeDynamic original, HNegate converted) { | |
| _registerSpecializer(original, converted, 'Negate'); | |
| } | |
| void registerAbs(HInvokeDynamic original, HAbs converted) { | |
| _registerSpecializer(original, converted, 'Abs'); | |
| } | |
| void registerAdd(HInvokeDynamic original, HInstruction? converted) { | |
| _registerSpecializer(original, converted, 'Add'); | |
| } | |
| void registerDivide(HInvokeDynamic original, HInstruction? converted) { | |
| _registerSpecializer(original, converted, 'Divide'); | |
| } | |
| void registerModulo(HInvokeDynamic original, HInstruction? converted) { | |
| _registerSpecializer(original, converted, 'Modulo', 'DynamicModulo'); | |
| } | |
| void registerRemainder(HInvokeDynamic original, HInstruction? converted) { | |
| _registerSpecializer(original, converted, 'Remainder'); | |
| } | |
| void registerMultiply(HInvokeDynamic original, HInstruction? converted) { | |
| _registerSpecializer(original, converted, 'Multiply'); | |
| } | |
| void registerSubtract(HInvokeDynamic original, HInstruction? converted) { | |
| _registerSpecializer(original, converted, 'Subtract'); | |
| } | |
| void registerTruncatingDivide( | |
| HInvokeDynamic original, | |
| HInstruction? converted, | |
| ) { | |
| _registerSpecializer( | |
| original, | |
| converted, | |
| 'TruncatingDivide', | |
| 'TruncatingDivide.${original.selector.name}', | |
| ); | |
| } | |
| void registerShiftLeft(HInvokeDynamic original, HInstruction? converted) { | |
| _registerSpecializer( | |
| original, | |
| converted, | |
| 'ShiftLeft', | |
| 'ShiftLeft.${original.selector.name}', | |
| ); | |
| } | |
| void registerShiftRight(HInvokeDynamic original, HInstruction? converted) { | |
| _registerSpecializer( | |
| original, | |
| converted, | |
| 'ShiftRight', | |
| 'ShiftRight.${original.selector.name}', | |
| ); | |
| } | |
| void registerShiftRightUnsigned( | |
| HInvokeDynamic original, | |
| HInstruction? converted, | |
| ) { | |
| _registerSpecializer( | |
| original, | |
| converted, | |
| 'ShiftRightUnsigned', | |
| 'ShiftRightUnsigned.${original.selector.name}', | |
| ); | |
| } | |
| void registerBitOr(HInvokeDynamic original, HInstruction? converted) { | |
| _registerSpecializer(original, converted, 'BitOr'); | |
| } | |
| void registerBitAnd(HInvokeDynamic original, HInstruction? converted) { | |
| _registerSpecializer(original, converted, 'BitAnd'); | |
| } | |
| void registerBitXor(HInvokeDynamic original, HInstruction? converted) { | |
| _registerSpecializer(original, converted, 'BitXor'); | |
| } | |
| void registerEquals(HInvokeDynamic original, HInstruction converted) { | |
| _registerSpecializer(original, converted, 'Equals'); | |
| } | |
| void registerLess(HInvokeDynamic original, HInstruction converted) { | |
| _registerSpecializer(original, converted, 'Less'); | |
| } | |
| void registerGreater(HInvokeDynamic original, HInstruction converted) { | |
| _registerSpecializer(original, converted, 'Greater'); | |
| } | |
| void registerLessEqual(HInvokeDynamic original, HInstruction converted) { | |
| _registerSpecializer(original, converted, 'LessEquals'); | |
| } | |
| void registerGreaterEqual(HInvokeDynamic original, HInstruction converted) { | |
| _registerSpecializer(original, converted, 'GreaterEquals'); | |
| } | |
| void registerCodeUnitAt(HInvokeDynamic original) { | |
| Features features = Features(); | |
| features['name'] = original.selector.name; | |
| entries.add(OptimizationLogEntry('CodeUnitAt', features)); | |
| } | |
| void registerCompareTo(HInvokeDynamic original, [HConstant? converted]) { | |
| Features features = Features(); | |
| if (converted != null) { | |
| features['constant'] = converted.constant.toDartText(_dartTypes); | |
| } | |
| entries.add(OptimizationLogEntry('CompareTo', features)); | |
| } | |
| void registerSubstring(HInvokeDynamic original) { | |
| _registerSpecializer(original, null, null, 'substring'); | |
| } | |
| void registerTrim(HInvokeDynamic original) { | |
| _registerSpecializer(original, null, null, 'trim'); | |
| } | |
| void registerPatternMatch(HInvokeDynamic original) { | |
| _registerSpecializer(original, null, null, original.selector.name); | |
| } | |
| void registerRound(HInvokeDynamic original) { | |
| _registerSpecializer(original, null, null, 'Round'); | |
| } | |
| void registerToInt(HInvokeDynamic original) { | |
| _registerSpecializer(original, null, null, 'ToInt'); | |
| } | |
| void registerPrimitiveCheck(HInstruction original, HPrimitiveCheck check) { | |
| Features features = Features(); | |
| features['kind'] = switch (check.kind) { | |
| PrimitiveCheckKind.receiverType => 'receiver', | |
| PrimitiveCheckKind.argumentType => 'argument', | |
| }; | |
| features['type'] = '${check.typeExpression}'; | |
| entries.add(OptimizationLogEntry('PrimitiveCheck', features)); | |
| } | |
| /// Generates optimization log entries that form a histogram. [summarize] | |
| /// projects the instruction to a string that is used as a key for counting | |
| /// instructions. [summarize] returns `null` to omit the instruction. | |
| void instructionHistogram( | |
| String tag, | |
| HGraph graph, | |
| String? Function(HInstruction) summarize, | |
| ) { | |
| Map<String, int> histogram = {}; | |
| for (final block in graph.blocks) { | |
| for (HInstruction? node = block.first; node != null; node = node.next) { | |
| String? description = summarize(node); | |
| if (description != null) { | |
| int count = histogram[description] ?? 0; | |
| histogram[description] = count + 1; | |
| } | |
| } | |
| } | |
| for (final entry in histogram.entries) { | |
| Features features = Features(); | |
| features[entry.key] = '${entry.value}'; | |
| entries.add(OptimizationLogEntry(tag, features)); | |
| } | |
| } | |
| String getText() { | |
| return entries.join(',\n'); | |
| } | |
| @override | |
| String toString() => 'OptimizationLog(${getText()})'; | |
| } | |
| /// A registered optimization. | |
| class OptimizationLogEntry { | |
| /// String that uniquely identifies the optimization kind. | |
| final String tag; | |
| /// Additional data for this optimization. | |
| final Features features; | |
| OptimizationLogEntry(this.tag, this.features); | |
| @override | |
| String toString() => '$tag(${features.getText()})'; | |
| } |