| Index: pkg/compiler/lib/src/inferrer/ast_inferrer_engine.dart
|
| diff --git a/pkg/compiler/lib/src/inferrer/ast_inferrer_engine.dart b/pkg/compiler/lib/src/inferrer/ast_inferrer_engine.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..66e380b04d09e249bff4e6b45562a6655dfd758d
|
| --- /dev/null
|
| +++ b/pkg/compiler/lib/src/inferrer/ast_inferrer_engine.dart
|
| @@ -0,0 +1,530 @@
|
| +// Copyright (c) 2017, 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 '../common.dart';
|
| +import '../common/names.dart';
|
| +import '../compiler.dart';
|
| +import '../constants/expressions.dart';
|
| +import '../constants/values.dart';
|
| +import '../elements/elements.dart';
|
| +import '../elements/entities.dart';
|
| +import '../resolution/tree_elements.dart';
|
| +import '../tree/nodes.dart' as ast;
|
| +import '../types/constants.dart';
|
| +import '../types/types.dart';
|
| +import '../universe/selector.dart';
|
| +import '../util/util.dart';
|
| +import '../world.dart';
|
| +import 'closure_tracer.dart';
|
| +import 'debug.dart' as debug;
|
| +import 'locals_handler.dart';
|
| +import 'builder.dart';
|
| +import 'builder_kernel.dart';
|
| +import 'inferrer_engine.dart';
|
| +import 'type_graph_dump.dart';
|
| +import 'type_graph_nodes.dart';
|
| +import 'type_system.dart';
|
| +
|
| +class AstInferrerEngine extends InferrerEngineImpl<ast.Node> {
|
| + AstInferrerEngine(Compiler compiler, ClosedWorld closedWorld,
|
| + ClosedWorldRefiner closedWorldRefiner, FunctionEntity mainElement)
|
| + : super(compiler, closedWorld, closedWorldRefiner, mainElement,
|
| + const TypeSystemStrategyImpl());
|
| +
|
| + GlobalTypeInferenceElementData<ast.Node> createElementData() =>
|
| + new AstGlobalTypeInferenceElementData();
|
| +
|
| + void runOverAllElements() {
|
| + if (compiler.disableTypeInference) return;
|
| + if (compiler.options.verbose) {
|
| + compiler.progress.reset();
|
| + }
|
| + sortResolvedAsts().forEach((ResolvedAst resolvedAst) {
|
| + if (compiler.shouldPrintProgress) {
|
| + reporter.log('Added $addedInGraph elements in inferencing graph.');
|
| + compiler.progress.reset();
|
| + }
|
| + // This also forces the creation of the [ElementTypeInformation] to ensure
|
| + // it is in the graph.
|
| + MemberElement member = resolvedAst.element;
|
| + ast.Node body;
|
| + if (resolvedAst.kind == ResolvedAstKind.PARSED) {
|
| + body = resolvedAst.body;
|
| + }
|
| + types.withMember(member, () => analyze(member, body, null));
|
| + });
|
| + reporter.log('Added $addedInGraph elements in inferencing graph.');
|
| +
|
| + TypeGraphDump dump = debug.PRINT_GRAPH ? new TypeGraphDump(this) : null;
|
| +
|
| + dump?.beforeAnalysis();
|
| + buildWorkQueue();
|
| + refine();
|
| +
|
| + // Try to infer element types of lists and compute their escape information.
|
| + types.allocatedLists.values.forEach((TypeInformation info) {
|
| + analyzeListAndEnqueue(info);
|
| + });
|
| +
|
| + // Try to infer the key and value types for maps and compute the values'
|
| + // escape information.
|
| + types.allocatedMaps.values.forEach((TypeInformation info) {
|
| + analyzeMapAndEnqueue(info);
|
| + });
|
| +
|
| + Set<FunctionEntity> bailedOutOn = new Set<FunctionEntity>();
|
| +
|
| + // Trace closures to potentially infer argument types.
|
| + types.allocatedClosures.forEach((dynamic info) {
|
| + void trace(
|
| + Iterable<FunctionEntity> elements, ClosureTracerVisitor tracer) {
|
| + tracer.run();
|
| + if (!tracer.continueAnalyzing) {
|
| + elements.forEach((FunctionEntity _element) {
|
| + MethodElement element = _element;
|
| + MethodElement implementation = element.implementation;
|
| + closedWorldRefiner.registerMightBePassedToApply(element);
|
| + if (debug.VERBOSE) {
|
| + print("traced closure $element as ${true} (bail)");
|
| + }
|
| + implementation.functionSignature
|
| + .forEachParameter((FormalElement _parameter) {
|
| + ParameterElement parameter = _parameter;
|
| + types
|
| + .getInferredTypeOfParameter(parameter)
|
| + .giveUp(this, clearAssignments: false);
|
| + });
|
| + });
|
| + bailedOutOn.addAll(elements);
|
| + return;
|
| + }
|
| + elements
|
| + .where((e) => !bailedOutOn.contains(e))
|
| + .forEach((FunctionEntity _element) {
|
| + MethodElement element = _element;
|
| + MethodElement implementation = element.implementation;
|
| + implementation.functionSignature
|
| + .forEachParameter((FormalElement _parameter) {
|
| + ParameterElement parameter = _parameter;
|
| + ParameterTypeInformation info =
|
| + types.getInferredTypeOfParameter(parameter);
|
| + info.maybeResume();
|
| + workQueue.add(info);
|
| + });
|
| + if (tracer.tracedType.mightBePassedToFunctionApply) {
|
| + closedWorldRefiner.registerMightBePassedToApply(element);
|
| + }
|
| + if (debug.VERBOSE) {
|
| + print("traced closure $element as "
|
| + "${closedWorldRefiner
|
| + .getCurrentlyKnownMightBePassedToApply(element)}");
|
| + }
|
| + });
|
| + }
|
| +
|
| + if (info is ClosureTypeInformation) {
|
| + Iterable<FunctionEntity> elements = [info.closure];
|
| + trace(elements, new ClosureTracerVisitor(elements, info, this));
|
| + } else if (info is CallSiteTypeInformation) {
|
| + if (info is StaticCallSiteTypeInformation &&
|
| + info.selector != null &&
|
| + info.selector.isCall) {
|
| + // This is a constructor call to a class with a call method. So we
|
| + // need to trace the call method here.
|
| + MethodElement calledElement = info.calledElement;
|
| + assert(calledElement.isGenerativeConstructor);
|
| + ClassElement cls = calledElement.enclosingClass;
|
| + MethodElement callMethod = cls.lookupMember(Identifiers.call);
|
| + if (callMethod == null) {
|
| + callMethod = cls.lookupMember(Identifiers.noSuchMethod_);
|
| + }
|
| + assert(callMethod != null, failedAt(cls));
|
| + Iterable<FunctionEntity> elements = [callMethod];
|
| + trace(elements, new ClosureTracerVisitor(elements, info, this));
|
| + } else {
|
| + // We only are interested in functions here, as other targets
|
| + // of this closure call are not a root to trace but an intermediate
|
| + // for some other function.
|
| + Iterable<FunctionEntity> elements = new List<FunctionEntity>.from(
|
| + info.callees.where((e) => e.isFunction));
|
| + trace(elements, new ClosureTracerVisitor(elements, info, this));
|
| + }
|
| + } else if (info is MemberTypeInformation) {
|
| + trace(<FunctionEntity>[info.member],
|
| + new StaticTearOffClosureTracerVisitor(info.member, info, this));
|
| + } else if (info is ParameterTypeInformation) {
|
| + failedAt(
|
| + NO_LOCATION_SPANNABLE, 'Unexpected closure allocation info $info');
|
| + }
|
| + });
|
| +
|
| + dump?.beforeTracing();
|
| +
|
| + // Reset all nodes that use lists/maps that have been inferred, as well
|
| + // as nodes that use elements fetched from these lists/maps. The
|
| + // workset for a new run of the analysis will be these nodes.
|
| + Set<TypeInformation> seenTypes = new Set<TypeInformation>();
|
| + while (!workQueue.isEmpty) {
|
| + TypeInformation info = workQueue.remove();
|
| + if (seenTypes.contains(info)) continue;
|
| + // If the node cannot be reset, we do not need to update its users either.
|
| + if (!info.reset(this)) continue;
|
| + seenTypes.add(info);
|
| + workQueue.addAll(info.users);
|
| + }
|
| +
|
| + workQueue.addAll(seenTypes);
|
| + refine();
|
| +
|
| + if (debug.PRINT_SUMMARY) {
|
| + types.allocatedLists.values.forEach((_info) {
|
| + ListTypeInformation info = _info;
|
| + print('${info.type} '
|
| + 'for ${info.originalType.allocationNode} '
|
| + 'at ${info.originalType.allocationElement} '
|
| + 'after ${info.refineCount}');
|
| + });
|
| + types.allocatedMaps.values.forEach((_info) {
|
| + MapTypeInformation info = _info;
|
| + print('${info.type} '
|
| + 'for ${info.originalType.allocationNode} '
|
| + 'at ${info.originalType.allocationElement} '
|
| + 'after ${info.refineCount}');
|
| + });
|
| + types.allocatedClosures.forEach((TypeInformation info) {
|
| + if (info is ElementTypeInformation) {
|
| + print('${info.getInferredSignature(types)} for '
|
| + '${info.debugName}');
|
| + } else if (info is ClosureTypeInformation) {
|
| + print('${info.getInferredSignature(types)} for '
|
| + '${info.debugName}');
|
| + } else if (info is DynamicCallSiteTypeInformation) {
|
| + for (MemberEntity target in info.targets) {
|
| + if (target is FunctionEntity) {
|
| + print(
|
| + '${types.getInferredSignatureOfMethod(target)} for ${target}');
|
| + } else {
|
| + print(
|
| + '${types.getInferredTypeOfMember(target).type} for ${target}');
|
| + }
|
| + }
|
| + } else if (info is StaticCallSiteTypeInformation) {
|
| + ClassElement cls = info.calledElement.enclosingClass;
|
| + MethodElement callMethod = cls.lookupMember(Identifiers.call);
|
| + print('${types.getInferredSignatureOfMethod(callMethod)} for ${cls}');
|
| + } else {
|
| + print('${info.type} for some unknown kind of closure');
|
| + }
|
| + });
|
| + analyzedElements.forEach((MemberEntity elem) {
|
| + TypeInformation type = types.getInferredTypeOfMember(elem);
|
| + print('${elem} :: ${type} from ${type.assignments} ');
|
| + });
|
| + }
|
| + dump?.afterAnalysis();
|
| +
|
| + reporter.log('Inferred $overallRefineCount types.');
|
| +
|
| + processLoopInformation();
|
| + }
|
| +
|
| + void analyze(MemberEntity element, ast.Node body, ArgumentsTypes arguments) {
|
| + assert(!(element is MemberElement && !element.isDeclaration));
|
| + if (analyzedElements.contains(element)) return;
|
| + analyzedElements.add(element);
|
| +
|
| + dynamic visitor = compiler.options.kernelGlobalInference
|
| + ? new KernelTypeGraphBuilder(element, compiler, this)
|
| + : new ElementGraphBuilder(element, compiler, this);
|
| + TypeInformation type;
|
| + reporter.withCurrentElement(element, () {
|
| + // ignore: UNDEFINED_METHOD
|
| + type = visitor.run();
|
| + });
|
| + addedInGraph++;
|
| +
|
| + if (element.isField) {
|
| + FieldElement field = element;
|
| + if (field.isFinal || field.isConst) {
|
| + // If [element] is final and has an initializer, we record
|
| + // the inferred type.
|
| + if (body != null) {
|
| + if (type is! ListTypeInformation && type is! MapTypeInformation) {
|
| + // For non-container types, the constant handler does
|
| + // constant folding that could give more precise results.
|
| + ConstantExpression constant = field.constant;
|
| + if (constant != null) {
|
| + ConstantValue value =
|
| + compiler.backend.constants.getConstantValue(constant);
|
| + if (value != null) {
|
| + if (value.isFunction) {
|
| + FunctionConstantValue functionConstant = value;
|
| + MethodElement function = functionConstant.element;
|
| + type = types.allocateClosure(function);
|
| + } else {
|
| + // Although we might find a better type, we have to keep
|
| + // the old type around to ensure that we get a complete view
|
| + // of the type graph and do not drop any flow edges.
|
| + TypeMask refinedType = computeTypeMask(closedWorld, value);
|
| + assert(TypeMask.assertIsNormalized(refinedType, closedWorld));
|
| + type = new NarrowTypeInformation(type, refinedType);
|
| + types.allocatedTypes.add(type);
|
| + }
|
| + } else {
|
| + assert(
|
| + field.isInstanceMember ||
|
| + constant.isImplicit ||
|
| + constant.isPotential,
|
| + failedAt(
|
| + field,
|
| + "Constant expression without value: "
|
| + "${constant.toStructuredText()}."));
|
| + }
|
| + }
|
| + }
|
| + recordTypeOfField(field, type);
|
| + } else if (!element.isInstanceMember) {
|
| + recordTypeOfField(field, types.nullType);
|
| + }
|
| + } else if (body == null) {
|
| + // Only update types of static fields if there is no
|
| + // assignment. Instance fields are dealt with in the constructor.
|
| + if (element.isStatic || element.isTopLevel) {
|
| + recordTypeOfField(field, type);
|
| + }
|
| + } else {
|
| + recordTypeOfField(field, type);
|
| + }
|
| + if ((element.isStatic || element.isTopLevel) &&
|
| + body != null &&
|
| + !element.isConst) {
|
| + dynamic argument = body;
|
| + // TODO(13429): We could do better here by using the
|
| + // constant handler to figure out if it's a lazy field or not.
|
| + if (argument.asSend() != null ||
|
| + (argument.asNewExpression() != null && !argument.isConst)) {
|
| + recordTypeOfField(field, types.nullType);
|
| + }
|
| + }
|
| + } else {
|
| + FunctionEntity method = element;
|
| + recordReturnType(method, type);
|
| + }
|
| + }
|
| +
|
| + void updateParameterAssignments(TypeInformation caller, MemberEntity callee,
|
| + ArgumentsTypes arguments, Selector selector, TypeMask mask,
|
| + {bool remove, bool addToQueue: true}) {
|
| + if (callee.name == Identifiers.noSuchMethod_) return;
|
| + if (callee.isField) {
|
| + if (selector.isSetter) {
|
| + ElementTypeInformation info = types.getInferredTypeOfMember(callee);
|
| + if (remove) {
|
| + info.removeAssignment(arguments.positional[0]);
|
| + } else {
|
| + info.addAssignment(arguments.positional[0]);
|
| + }
|
| + if (addToQueue) workQueue.add(info);
|
| + }
|
| + } else if (callee.isGetter) {
|
| + return;
|
| + } else if (selector != null && selector.isGetter) {
|
| + // We are tearing a function off and thus create a closure.
|
| + assert(callee.isFunction);
|
| + MethodElement method = callee;
|
| + MemberTypeInformation info = types.getInferredTypeOfMember(method);
|
| + if (remove) {
|
| + info.closurizedCount--;
|
| + } else {
|
| + info.closurizedCount++;
|
| + if (Elements.isStaticOrTopLevel(method)) {
|
| + types.allocatedClosures.add(info);
|
| + } else {
|
| + // We add the call-site type information here so that we
|
| + // can benefit from further refinement of the selector.
|
| + types.allocatedClosures.add(caller);
|
| + }
|
| + FunctionElement function = method.implementation;
|
| + FunctionSignature signature = function.functionSignature;
|
| + signature.forEachParameter((FormalElement _parameter) {
|
| + ParameterElement parameter = _parameter;
|
| + ParameterTypeInformation info =
|
| + types.getInferredTypeOfParameter(parameter);
|
| + info.tagAsTearOffClosureParameter(this);
|
| + if (addToQueue) workQueue.add(info);
|
| + });
|
| + }
|
| + } else {
|
| + MethodElement method = callee;
|
| + FunctionElement function = method.implementation;
|
| + FunctionSignature signature = function.functionSignature;
|
| + int parameterIndex = 0;
|
| + bool visitingRequiredParameter = true;
|
| + signature.forEachParameter((FormalElement _parameter) {
|
| + ParameterElement parameter = _parameter;
|
| + if (signature.hasOptionalParameters &&
|
| + parameter == signature.optionalParameters.first) {
|
| + visitingRequiredParameter = false;
|
| + }
|
| + TypeInformation type = visitingRequiredParameter
|
| + ? arguments.positional[parameterIndex]
|
| + : signature.optionalParametersAreNamed
|
| + ? arguments.named[parameter.name]
|
| + : parameterIndex < arguments.positional.length
|
| + ? arguments.positional[parameterIndex]
|
| + : null;
|
| + if (type == null) type = getDefaultTypeOfParameter(parameter);
|
| + TypeInformation info = types.getInferredTypeOfParameter(parameter);
|
| + if (remove) {
|
| + info.removeAssignment(type);
|
| + } else {
|
| + info.addAssignment(type);
|
| + }
|
| + parameterIndex++;
|
| + if (addToQueue) workQueue.add(info);
|
| + });
|
| + }
|
| + }
|
| +
|
| + // Sorts the resolved elements by size. We do this for this inferrer
|
| + // to get the same results for [ListTracer] compared to the
|
| + // [SimpleTypesInferrer].
|
| + Iterable<ResolvedAst> sortResolvedAsts() {
|
| + int max = 0;
|
| + Map<int, Setlet<ResolvedAst>> methodSizes = <int, Setlet<ResolvedAst>>{};
|
| + compiler.enqueuer.resolution.processedEntities.forEach((_element) {
|
| + MemberElement element = _element;
|
| + ResolvedAst resolvedAst = element.resolvedAst;
|
| + element = element.implementation;
|
| + if (element.impliesType) return;
|
| + assert(
|
| + element.isField ||
|
| + element.isFunction ||
|
| + element.isConstructor ||
|
| + element.isGetter ||
|
| + element.isSetter,
|
| + failedAt(element, 'Unexpected element kind: ${element.kind}'));
|
| + if (element.isAbstract) return;
|
| + // Put the other operators in buckets by length, later to be added in
|
| + // length order.
|
| + int length = 0;
|
| + if (resolvedAst.kind == ResolvedAstKind.PARSED) {
|
| + TreeElementMapping mapping = resolvedAst.elements;
|
| + length = mapping.getSelectorCount();
|
| + }
|
| + max = length > max ? length : max;
|
| + Setlet<ResolvedAst> set =
|
| + methodSizes.putIfAbsent(length, () => new Setlet<ResolvedAst>());
|
| + set.add(resolvedAst);
|
| + });
|
| +
|
| + List<ResolvedAst> result = <ResolvedAst>[];
|
| + for (int i = 0; i <= max; i++) {
|
| + Setlet<ResolvedAst> set = methodSizes[i];
|
| + if (set != null) result.addAll(set);
|
| + }
|
| + return result;
|
| + }
|
| +}
|
| +
|
| +class TypeSystemStrategyImpl implements TypeSystemStrategy<ast.Node> {
|
| + const TypeSystemStrategyImpl();
|
| +
|
| + @override
|
| + MemberTypeInformation createMemberTypeInformation(
|
| + covariant MemberElement member) {
|
| + assert(member.isDeclaration, failedAt(member));
|
| + if (member.isField) {
|
| + FieldElement field = member;
|
| + return new FieldTypeInformation(field, field.type);
|
| + } else if (member.isGetter) {
|
| + GetterElement getter = member;
|
| + return new GetterTypeInformation(getter, getter.type);
|
| + } else if (member.isSetter) {
|
| + SetterElement setter = member;
|
| + return new SetterTypeInformation(setter);
|
| + } else if (member.isFunction) {
|
| + MethodElement method = member;
|
| + return new MethodTypeInformation(method, method.type);
|
| + } else {
|
| + ConstructorElement constructor = member;
|
| + if (constructor.isFactoryConstructor) {
|
| + return new FactoryConstructorTypeInformation(
|
| + constructor, constructor.type);
|
| + } else {
|
| + return new GenerativeConstructorTypeInformation(constructor);
|
| + }
|
| + }
|
| + }
|
| +
|
| + @override
|
| + ParameterTypeInformation createParameterTypeInformation(
|
| + covariant ParameterElement parameter, TypeSystem<ast.Node> types) {
|
| + assert(parameter.isImplementation, failedAt(parameter));
|
| + FunctionTypedElement function = parameter.functionDeclaration.declaration;
|
| + if (function.isLocal) {
|
| + LocalFunctionElement localFunction = function;
|
| + MethodElement callMethod = localFunction.callMethod;
|
| + return new ParameterTypeInformation.localFunction(
|
| + types.getInferredTypeOfMember(callMethod),
|
| + parameter,
|
| + parameter.type,
|
| + callMethod);
|
| + } else if (function.isInstanceMember) {
|
| + MethodElement method = function;
|
| + return new ParameterTypeInformation.instanceMember(
|
| + types.getInferredTypeOfMember(method),
|
| + parameter,
|
| + parameter.type,
|
| + method,
|
| + new ParameterAssignments());
|
| + } else {
|
| + MethodElement method = function;
|
| + return new ParameterTypeInformation.static(
|
| + types.getInferredTypeOfMember(method),
|
| + parameter,
|
| + parameter.type,
|
| + method,
|
| + // TODO(johnniwinther): Is this still valid now that initializing
|
| + // formals also introduce locals?
|
| + isInitializingFormal: parameter.isInitializingFormal);
|
| + }
|
| + }
|
| +
|
| + @override
|
| + void forEachParameter(
|
| + covariant MethodElement function, void f(Local parameter)) {
|
| + MethodElement impl = function.implementation;
|
| + FunctionSignature signature = impl.functionSignature;
|
| + signature.forEachParameter((FormalElement _parameter) {
|
| + ParameterElement parameter = _parameter;
|
| + f(parameter);
|
| + });
|
| + }
|
| +
|
| + @override
|
| + bool checkMapNode(ast.Node node) {
|
| + return node is ast.LiteralMap;
|
| + }
|
| +
|
| + @override
|
| + bool checkListNode(ast.Node node) {
|
| + return node is ast.LiteralList || node is ast.Send;
|
| + }
|
| +
|
| + @override
|
| + bool checkLoopPhiNode(ast.Node node) {
|
| + return node is ast.Loop || node is ast.SwitchStatement;
|
| + }
|
| +
|
| + @override
|
| + bool checkPhiNode(ast.Node node) {
|
| + return true;
|
| + }
|
| +
|
| + @override
|
| + bool checkClassEntity(covariant ClassElement cls) {
|
| + return cls.isDeclaration;
|
| + }
|
| +}
|
|
|