| // 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. |
| |
| import '../elements/entities.dart'; |
| import '../elements/types.dart'; |
| // ignore: implementation_imports |
| import 'package:_fe_analyzer_shared/src/testing/features.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()})'; |
| } |