| // Copyright (c) 2012, 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 'dart:collection'; |
| |
| import 'package:js_runtime/shared/embedded_names.dart'; |
| |
| import '../closure.dart'; |
| import '../common.dart'; |
| import '../common/codegen.dart' show CodegenRegistry, CodegenWorkItem; |
| import '../common/names.dart' show Identifiers, Selectors; |
| import '../common/tasks.dart' show CompilerTask; |
| import '../compiler.dart' show Compiler; |
| import '../constants/constant_system.dart'; |
| import '../constants/expressions.dart'; |
| import '../constants/values.dart'; |
| import '../core_types.dart' show CoreClasses; |
| import '../dart_types.dart'; |
| import '../diagnostics/messages.dart' show Message, MessageTemplate; |
| import '../dump_info.dart' show InfoReporter; |
| import '../elements/elements.dart'; |
| import '../elements/entities.dart'; |
| import '../elements/modelx.dart' show ConstructorBodyElementX; |
| import '../io/source_information.dart'; |
| import '../js/js.dart' as js; |
| import '../js_backend/backend_helpers.dart' show BackendHelpers; |
| import '../js_backend/js_backend.dart'; |
| import '../js_emitter/js_emitter.dart' show CodeEmitterTask, NativeEmitter; |
| import '../native/native.dart' as native; |
| import '../resolution/operators.dart'; |
| import '../resolution/semantic_visitor.dart'; |
| import '../resolution/tree_elements.dart' show TreeElements; |
| import '../tree/tree.dart' as ast; |
| import '../types/types.dart'; |
| import '../universe/call_structure.dart' show CallStructure; |
| import '../universe/selector.dart' show Selector; |
| import '../universe/side_effects.dart' show SideEffects; |
| import '../universe/use.dart' show DynamicUse, StaticUse; |
| import '../util/util.dart'; |
| import '../world.dart' show ClosedWorld; |
| |
| import 'graph_builder.dart'; |
| import 'jump_handler.dart'; |
| import 'locals_handler.dart'; |
| import 'loop_handler.dart'; |
| import 'nodes.dart'; |
| import 'optimize.dart'; |
| import 'ssa_branch_builder.dart'; |
| import 'type_builder.dart'; |
| import 'types.dart'; |
| |
| class SsaBuilderTask extends CompilerTask { |
| final CodeEmitterTask emitter; |
| final JavaScriptBackend backend; |
| final SourceInformationStrategy sourceInformationFactory; |
| final Compiler compiler; |
| |
| String get name => 'SSA builder'; |
| |
| SsaBuilderTask(JavaScriptBackend backend, this.sourceInformationFactory) |
| : emitter = backend.emitter, |
| backend = backend, |
| compiler = backend.compiler, |
| super(backend.compiler.measurer); |
| |
| DiagnosticReporter get reporter => compiler.reporter; |
| |
| HGraph build(CodegenWorkItem work, ClosedWorld closedWorld) { |
| return measure(() { |
| Element element = work.element.implementation; |
| return reporter.withCurrentElement(element, () { |
| SsaBuilder builder = new SsaBuilder( |
| work.element.implementation, |
| work.resolvedAst, |
| work.registry, |
| backend, |
| closedWorld, |
| emitter.nativeEmitter, |
| sourceInformationFactory); |
| HGraph graph = builder.build(); |
| |
| // Default arguments are handled elsewhere, but we must ensure |
| // that the default values are computed during codegen. |
| if (!identical(element.kind, ElementKind.FIELD)) { |
| FunctionElement function = element; |
| FunctionSignature signature = function.functionSignature; |
| signature.forEachOptionalParameter((ParameterElement parameter) { |
| // This ensures the default value will be computed. |
| ConstantValue constant = |
| backend.constants.getConstantValue(parameter.constant); |
| work.registry.registerCompileTimeConstant(constant); |
| }); |
| } |
| if (backend.tracer.isEnabled) { |
| String name; |
| if (element.isClassMember) { |
| String className = element.enclosingClass.name; |
| String memberName = element.name; |
| name = "$className.$memberName"; |
| if (element.isGenerativeConstructorBody) { |
| name = "$name (body)"; |
| } |
| } else { |
| name = "${element.name}"; |
| } |
| backend.tracer.traceCompilation(name); |
| backend.tracer.traceGraph('builder', graph); |
| } |
| return graph; |
| }); |
| }); |
| } |
| } |
| |
| /** |
| * This class builds SSA nodes for functions represented in AST. |
| */ |
| class SsaBuilder extends ast.Visitor |
| with |
| BaseImplementationOfCompoundsMixin, |
| BaseImplementationOfSetIfNullsMixin, |
| BaseImplementationOfSuperIndexSetIfNullMixin, |
| SemanticSendResolvedMixin, |
| NewBulkMixin, |
| ErrorBulkMixin, |
| GraphBuilder |
| implements SemanticSendVisitor { |
| /// The element for which this SSA builder is being used. |
| final Element target; |
| final ClosedWorld closedWorld; |
| |
| ResolvedAst resolvedAst; |
| |
| /// Used to report information about inlining (which occurs while building the |
| /// SSA graph), when dump-info is enabled. |
| final InfoReporter infoReporter; |
| |
| /// Registry used to enqueue work during codegen, may be null to avoid |
| /// enqueing any work. |
| // TODO(sigmund,johnniwinther): get rid of registry entirely. We should be |
| // able to return the impact as a result after building and avoid enqueing |
| // things here. Later the codegen task can decide whether to enqueue |
| // something. In the past this didn't matter as much because the SSA graph was |
| // used only for codegen, but currently we want to experiment using it for |
| // code-analysis too. |
| final CodegenRegistry registry; |
| |
| /// All results from the global type-inference analysis. |
| final GlobalTypeInferenceResults inferenceResults; |
| |
| /// Results from the global type-inference analysis corresponding to the |
| /// current element being visited. |
| /// |
| /// Invariant: this property is updated together with [resolvedAst]. |
| GlobalTypeInferenceElementResult elementInferenceResults; |
| |
| final JavaScriptBackend backend; |
| final ConstantSystem constantSystem; |
| final RuntimeTypes rti; |
| |
| SourceInformationBuilder sourceInformationBuilder; |
| |
| bool inLazyInitializerExpression = false; |
| |
| // TODO(sigmund): make all comments /// instead of /* */ |
| /* This field is used by the native handler. */ |
| final NativeEmitter nativeEmitter; |
| |
| /** |
| * True if we are visiting the expression of a throw statement; we assume this |
| * is a slow path. |
| */ |
| bool inExpressionOfThrow = false; |
| |
| /** |
| * This stack contains declaration elements of the functions being built |
| * or inlined by this builder. |
| */ |
| final List<Element> sourceElementStack = <Element>[]; |
| |
| HInstruction rethrowableException; |
| |
| /// Returns `true` if the current element is an `async` function. |
| bool get isBuildingAsyncFunction { |
| Element element = sourceElement; |
| return (element is FunctionElement && |
| element.asyncMarker == AsyncMarker.ASYNC); |
| } |
| |
| /// Handles the building of loops. |
| LoopHandler<ast.Node> loopHandler; |
| |
| /// Handles type check building. |
| TypeBuilder typeBuilder; |
| |
| // TODO(sigmund): make most args optional |
| SsaBuilder( |
| this.target, |
| this.resolvedAst, |
| this.registry, |
| JavaScriptBackend backend, |
| this.closedWorld, |
| this.nativeEmitter, |
| SourceInformationStrategy sourceInformationFactory) |
| : this.infoReporter = backend.compiler.dumpInfoTask, |
| this.backend = backend, |
| this.constantSystem = backend.constantSystem, |
| this.rti = backend.rti, |
| this.inferenceResults = backend.compiler.globalInference.results { |
| assert(target.isImplementation); |
| compiler = backend.compiler; |
| elementInferenceResults = _resultOf(target); |
| assert(elementInferenceResults != null); |
| graph.element = target; |
| sourceElementStack.add(target); |
| sourceInformationBuilder = |
| sourceInformationFactory.createBuilderForContext(resolvedAst); |
| graph.sourceInformation = |
| sourceInformationBuilder.buildVariableDeclaration(); |
| localsHandler = new LocalsHandler(this, target, null, compiler); |
| loopHandler = new SsaLoopHandler(this); |
| typeBuilder = new TypeBuilder(this); |
| } |
| |
| BackendHelpers get helpers => backend.helpers; |
| |
| RuntimeTypesEncoder get rtiEncoder => backend.rtiEncoder; |
| |
| DiagnosticReporter get reporter => compiler.reporter; |
| |
| CoreClasses get coreClasses => compiler.coreClasses; |
| |
| Element get targetElement => target; |
| |
| /// Reference to resolved elements in [target]'s AST. |
| TreeElements get elements => resolvedAst.elements; |
| |
| @override |
| SemanticSendVisitor get sendVisitor => this; |
| |
| @override |
| void visitNode(ast.Node node) { |
| internalError(node, "Unhandled node: $node"); |
| } |
| |
| @override |
| void apply(ast.Node node, [_]) { |
| node.accept(this); |
| } |
| |
| /// Returns the current source element. |
| /// |
| /// The returned element is a declaration element. |
| // TODO(johnniwinther): Check that all usages of sourceElement agree on |
| // implementation/declaration distinction. |
| @override |
| Element get sourceElement => sourceElementStack.last; |
| |
| /// Helper to retrieve global inference results for [element] with special |
| /// care for `ConstructorBodyElement`s which don't exist at the time the |
| /// global analysis run. |
| /// |
| /// Note: this helper is used selectively. When we know that we are in a |
| /// context were we don't expect to see a constructor body element, we |
| /// directly fetch the data from the global inference results. |
| GlobalTypeInferenceElementResult _resultOf(AstElement element) => |
| inferenceResults.resultOf( |
| element is ConstructorBodyElementX ? element.constructor : element); |
| |
| /// Build the graph for [target]. |
| HGraph build() { |
| assert(invariant(target, target.isImplementation)); |
| HInstruction.idCounter = 0; |
| // TODO(sigmund): remove `result` and return graph directly, need to ensure |
| // that it can never be null (see result in buildFactory for instance). |
| var result; |
| if (target.isGenerativeConstructor) { |
| result = buildFactory(resolvedAst); |
| } else if (target.isGenerativeConstructorBody || |
| target.isFactoryConstructor || |
| target.isFunction || |
| target.isGetter || |
| target.isSetter) { |
| result = buildMethod(target); |
| } else if (target.isField) { |
| if (target.isInstanceMember) { |
| assert(compiler.options.enableTypeAssertions); |
| result = buildCheckedSetter(target); |
| } else { |
| result = buildLazyInitializer(target); |
| } |
| } else { |
| reporter.internalError(target, 'Unexpected element kind $target.'); |
| } |
| assert(result.isValid()); |
| return result; |
| } |
| |
| void addWithPosition(HInstruction instruction, ast.Node node) { |
| add(attachPosition(instruction, node)); |
| } |
| |
| HTypeConversion buildFunctionTypeConversion( |
| HInstruction original, DartType type, int kind) { |
| HInstruction reifiedType = buildFunctionType(type); |
| return new HTypeConversion.viaMethodOnType( |
| type, kind, original.instructionType, reifiedType, original); |
| } |
| |
| /** |
| * Returns a complete argument list for a call of [function]. |
| */ |
| List<HInstruction> completeSendArgumentsList( |
| FunctionElement function, |
| Selector selector, |
| List<HInstruction> providedArguments, |
| ast.Node currentNode) { |
| assert(invariant(function, function.isImplementation)); |
| assert(providedArguments != null); |
| |
| bool isInstanceMember = function.isInstanceMember; |
| // For static calls, [providedArguments] is complete, default arguments |
| // have been included if necessary, see [makeStaticArgumentList]. |
| if (!isInstanceMember || |
| currentNode == null || // In erroneous code, currentNode can be null. |
| providedArgumentsKnownToBeComplete(currentNode) || |
| function.isGenerativeConstructorBody || |
| selector.isGetter) { |
| // For these cases, the provided argument list is known to be complete. |
| return providedArguments; |
| } else { |
| return completeDynamicSendArgumentsList( |
| selector, function, providedArguments); |
| } |
| } |
| |
| /** |
| * Returns a complete argument list for a dynamic call of [function]. The |
| * initial argument list [providedArguments], created by |
| * [addDynamicSendArgumentsToList], does not include values for default |
| * arguments used in the call. The reason is that the target function (which |
| * defines the defaults) is not known. |
| * |
| * However, inlining can only be performed when the target function can be |
| * resolved statically. The defaults can therefore be included at this point. |
| * |
| * The [providedArguments] list contains first all positional arguments, then |
| * the provided named arguments (the named arguments that are defined in the |
| * [selector]) in a specific order (see [addDynamicSendArgumentsToList]). |
| */ |
| List<HInstruction> completeDynamicSendArgumentsList(Selector selector, |
| FunctionElement function, List<HInstruction> providedArguments) { |
| assert(selector.applies(function)); |
| FunctionSignature signature = function.functionSignature; |
| List<HInstruction> compiledArguments = new List<HInstruction>( |
| signature.parameterCount + 1); // Plus one for receiver. |
| |
| compiledArguments[0] = providedArguments[0]; // Receiver. |
| int index = 1; |
| for (; index <= signature.requiredParameterCount; index++) { |
| compiledArguments[index] = providedArguments[index]; |
| } |
| if (!signature.optionalParametersAreNamed) { |
| signature.forEachOptionalParameter((element) { |
| if (index < providedArguments.length) { |
| compiledArguments[index] = providedArguments[index]; |
| } else { |
| compiledArguments[index] = |
| handleConstantForOptionalParameter(element); |
| } |
| index++; |
| }); |
| } else { |
| /* Example: |
| * void foo(a, {b, d, c}) |
| * foo(0, d = 1, b = 2) |
| * |
| * providedArguments = [0, 2, 1] |
| * selectorArgumentNames = [b, d] |
| * signature.orderedOptionalParameters = [b, c, d] |
| * |
| * For each parameter name in the signature, if the argument name matches |
| * we use the next provided argument, otherwise we get the default. |
| */ |
| List<String> selectorArgumentNames = |
| selector.callStructure.getOrderedNamedArguments(); |
| int namedArgumentIndex = 0; |
| int firstProvidedNamedArgument = index; |
| signature.orderedOptionalParameters.forEach((element) { |
| if (namedArgumentIndex < selectorArgumentNames.length && |
| element.name == selectorArgumentNames[namedArgumentIndex]) { |
| // The named argument was provided in the function invocation. |
| compiledArguments[index] = providedArguments[ |
| firstProvidedNamedArgument + namedArgumentIndex++]; |
| } else { |
| compiledArguments[index] = |
| handleConstantForOptionalParameter(element); |
| } |
| index++; |
| }); |
| } |
| return compiledArguments; |
| } |
| |
| /** |
| * Try to inline [element] within the correct context of the builder. The |
| * insertion point is the state of the builder. |
| */ |
| bool tryInlineMethod(Element element, Selector selector, TypeMask mask, |
| List<HInstruction> providedArguments, ast.Node currentNode, |
| {InterfaceType instanceType}) { |
| registry |
| .addImpact(backend.registerUsedElement(element, forResolution: false)); |
| |
| if (backend.isJsInterop(element) && !element.isFactoryConstructor) { |
| // We only inline factory JavaScript interop constructors. |
| return false; |
| } |
| |
| // Ensure that [element] is an implementation element. |
| element = element.implementation; |
| |
| if (compiler.elementHasCompileTimeError(element)) return false; |
| |
| FunctionElement function = element; |
| ResolvedAst functionResolvedAst = function.resolvedAst; |
| bool insideLoop = loopDepth > 0 || graph.calledInLoop; |
| |
| // Bail out early if the inlining decision is in the cache and we can't |
| // inline (no need to check the hard constraints). |
| bool cachedCanBeInlined = |
| backend.inlineCache.canInline(function, insideLoop: insideLoop); |
| if (cachedCanBeInlined == false) return false; |
| |
| bool meetsHardConstraints() { |
| if (compiler.options.disableInlining) return false; |
| |
| assert(invariant( |
| currentNode != null ? currentNode : element, |
| selector != null || |
| Elements.isStaticOrTopLevel(element) || |
| element.isGenerativeConstructorBody, |
| message: "Missing selector for inlining of $element.")); |
| if (selector != null) { |
| if (!selector.applies(function)) return false; |
| if (mask != null && !mask.canHit(function, selector, closedWorld)) { |
| return false; |
| } |
| } |
| |
| if (backend.isJsInterop(element)) return false; |
| |
| // Don't inline operator== methods if the parameter can be null. |
| if (element.name == '==') { |
| if (element.enclosingClass != coreClasses.objectClass && |
| providedArguments[1].canBeNull()) { |
| return false; |
| } |
| } |
| |
| // Generative constructors of native classes should not be called directly |
| // and have an extra argument that causes problems with inlining. |
| if (element.isGenerativeConstructor && |
| backend.isNativeOrExtendsNative(element.enclosingClass)) { |
| return false; |
| } |
| |
| // A generative constructor body is not seen by global analysis, |
| // so we should not query for its type. |
| if (!element.isGenerativeConstructorBody) { |
| if (inferenceResults.resultOf(element).throwsAlways) { |
| isReachable = false; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool doesNotContainCode() { |
| // A function with size 1 does not contain any code. |
| return InlineWeeder.canBeInlined(functionResolvedAst, 1, true, |
| enableUserAssertions: compiler.options.enableUserAssertions); |
| } |
| |
| bool reductiveHeuristic() { |
| // The call is on a path which is executed rarely, so inline only if it |
| // does not make the program larger. |
| if (isCalledOnce(element)) { |
| return InlineWeeder.canBeInlined(functionResolvedAst, -1, false, |
| enableUserAssertions: compiler.options.enableUserAssertions); |
| } |
| // TODO(sra): Measure if inlining would 'reduce' the size. One desirable |
| // case we miss by doing nothing is inlining very simple constructors |
| // where all fields are initialized with values from the arguments at this |
| // call site. The code is slightly larger (`new Foo(1)` vs `Foo$(1)`) but |
| // that usually means the factory constructor is left unused and not |
| // emitted. |
| // We at least inline bodies that are empty (and thus have a size of 1). |
| return doesNotContainCode(); |
| } |
| |
| bool heuristicSayGoodToGo() { |
| // Don't inline recursively |
| if (inliningStack.any((entry) => entry.function == function)) { |
| return false; |
| } |
| |
| if (element.isSynthesized) return true; |
| |
| // Don't inline across deferred import to prevent leaking code. The only |
| // exception is an empty function (which does not contain code). |
| bool hasOnlyNonDeferredImportPaths = compiler.deferredLoadTask |
| .hasOnlyNonDeferredImportPaths(compiler.currentElement, element); |
| |
| if (!hasOnlyNonDeferredImportPaths) { |
| return doesNotContainCode(); |
| } |
| |
| // Do not inline code that is rarely executed unless it reduces size. |
| if (inExpressionOfThrow || inLazyInitializerExpression) { |
| return reductiveHeuristic(); |
| } |
| |
| if (cachedCanBeInlined == true) { |
| // We may have forced the inlining of some methods. Therefore check |
| // if we can inline this method regardless of size. |
| assert(InlineWeeder.canBeInlined(functionResolvedAst, -1, false, |
| allowLoops: true, |
| enableUserAssertions: compiler.options.enableUserAssertions)); |
| return true; |
| } |
| |
| int numParameters = function.functionSignature.parameterCount; |
| int maxInliningNodes; |
| bool useMaxInliningNodes = true; |
| if (insideLoop) { |
| maxInliningNodes = InlineWeeder.INLINING_NODES_INSIDE_LOOP + |
| InlineWeeder.INLINING_NODES_INSIDE_LOOP_ARG_FACTOR * numParameters; |
| } else { |
| maxInliningNodes = InlineWeeder.INLINING_NODES_OUTSIDE_LOOP + |
| InlineWeeder.INLINING_NODES_OUTSIDE_LOOP_ARG_FACTOR * numParameters; |
| } |
| |
| // If a method is called only once, and all the methods in the |
| // inlining stack are called only once as well, we know we will |
| // save on output size by inlining this method. |
| if (isCalledOnce(element)) { |
| useMaxInliningNodes = false; |
| } |
| bool canInline; |
| canInline = InlineWeeder.canBeInlined( |
| functionResolvedAst, maxInliningNodes, useMaxInliningNodes, |
| enableUserAssertions: compiler.options.enableUserAssertions); |
| if (canInline) { |
| backend.inlineCache.markAsInlinable(element, insideLoop: insideLoop); |
| } else { |
| backend.inlineCache.markAsNonInlinable(element, insideLoop: insideLoop); |
| } |
| return canInline; |
| } |
| |
| void doInlining() { |
| // Add an explicit null check on the receiver before doing the |
| // inlining. We use [element] to get the same name in the |
| // NoSuchMethodError message as if we had called it. |
| if (element.isInstanceMember && |
| !element.isGenerativeConstructorBody && |
| (mask == null || mask.isNullable)) { |
| addWithPosition( |
| new HFieldGet(null, providedArguments[0], commonMasks.dynamicType, |
| isAssignable: false), |
| currentNode); |
| } |
| List<HInstruction> compiledArguments = completeSendArgumentsList( |
| function, selector, providedArguments, currentNode); |
| enterInlinedMethod(function, functionResolvedAst, compiledArguments, |
| instanceType: instanceType); |
| inlinedFrom(functionResolvedAst, () { |
| if (!isReachable) { |
| emitReturn(graph.addConstantNull(closedWorld), null); |
| } else { |
| doInline(functionResolvedAst); |
| } |
| }); |
| leaveInlinedMethod(); |
| } |
| |
| if (meetsHardConstraints() && heuristicSayGoodToGo()) { |
| doInlining(); |
| infoReporter?.reportInlined(element, |
| inliningStack.isEmpty ? target : inliningStack.last.function); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool get allInlinedFunctionsCalledOnce { |
| return inliningStack.isEmpty || inliningStack.last.allFunctionsCalledOnce; |
| } |
| |
| bool isFunctionCalledOnce(element) { |
| // ConstructorBodyElements are not in the type inference graph. |
| if (element is ConstructorBodyElement) return false; |
| return inferenceResults.resultOf(element).isCalledOnce; |
| } |
| |
| bool isCalledOnce(Element element) { |
| return allInlinedFunctionsCalledOnce && isFunctionCalledOnce(element); |
| } |
| |
| inlinedFrom(ResolvedAst resolvedAst, f()) { |
| Element element = resolvedAst.element; |
| assert(element is FunctionElement || element is VariableElement); |
| return reporter.withCurrentElement(element.implementation, () { |
| // The [sourceElementStack] contains declaration elements. |
| SourceInformationBuilder oldSourceInformationBuilder = |
| sourceInformationBuilder; |
| sourceInformationBuilder = |
| sourceInformationBuilder.forContext(resolvedAst); |
| sourceElementStack.add(element.declaration); |
| var result = f(); |
| sourceInformationBuilder = oldSourceInformationBuilder; |
| sourceElementStack.removeLast(); |
| return result; |
| }); |
| } |
| |
| /** |
| * Return null so it is simple to remove the optional parameters completely |
| * from interop methods to match JavaScript semantics for omitted arguments. |
| */ |
| HInstruction handleConstantForOptionalParameterJsInterop(Element parameter) => |
| null; |
| |
| HInstruction handleConstantForOptionalParameter(ParameterElement parameter) { |
| ConstantValue constantValue = |
| backend.constants.getConstantValue(parameter.constant); |
| assert(invariant(parameter, constantValue != null, |
| message: 'No constant computed for $parameter')); |
| return graph.addConstant(constantValue, closedWorld); |
| } |
| |
| ClassElement get currentNonClosureClass { |
| ClassElement cls = sourceElement.enclosingClass; |
| if (cls != null && cls.isClosure) { |
| var closureClass = cls; |
| return closureClass.methodElement.enclosingClass; |
| } else { |
| return cls; |
| } |
| } |
| |
| /// A stack of [DartType]s that have been seen during inlining of factory |
| /// constructors. These types are preserved in [HInvokeStatic]s and |
| /// [HCreate]s inside the inline code and registered during code generation |
| /// for these nodes. |
| // TODO(karlklose): consider removing this and keeping the (substituted) types |
| // of the type variables in an environment (like the [LocalsHandler]). |
| final List<DartType> currentInlinedInstantiations = <DartType>[]; |
| |
| final List<AstInliningState> inliningStack = <AstInliningState>[]; |
| |
| Local returnLocal; |
| DartType returnType; |
| |
| bool inTryStatement = false; |
| |
| ConstantValue getConstantForNode(ast.Node node) { |
| ConstantValue constantValue = |
| backend.constants.getConstantValueForNode(node, elements); |
| assert(invariant(node, constantValue != null, |
| message: 'No constant computed for $node')); |
| return constantValue; |
| } |
| |
| HInstruction addConstant(ast.Node node) { |
| return graph.addConstant(getConstantForNode(node), closedWorld); |
| } |
| |
| /** |
| * Documentation wanted -- johnniwinther |
| * |
| * Invariant: [functionElement] must be an implementation element. |
| */ |
| HGraph buildMethod(FunctionElement functionElement) { |
| assert(invariant(functionElement, functionElement.isImplementation)); |
| graph.calledInLoop = closedWorld.isCalledInLoop(functionElement); |
| ast.FunctionExpression function = resolvedAst.node; |
| assert(function != null); |
| assert(elements.getFunctionDefinition(function) != null); |
| openFunction(functionElement, function); |
| String name = functionElement.name; |
| if (backend.isJsInterop(functionElement)) { |
| push(invokeJsInteropFunction(functionElement, parameters.values.toList(), |
| sourceInformationBuilder.buildGeneric(function))); |
| var value = pop(); |
| closeAndGotoExit(new HReturn( |
| value, sourceInformationBuilder.buildReturn(functionElement.node))); |
| return closeFunction(); |
| } |
| assert(invariant(functionElement, !function.modifiers.isExternal)); |
| |
| // If [functionElement] is `operator==` we explicitly add a null check at |
| // the beginning of the method. This is to avoid having call sites do the |
| // null check. |
| if (name == '==') { |
| if (!backend.operatorEqHandlesNullArgument(functionElement)) { |
| handleIf( |
| node: function, |
| visitCondition: () { |
| HParameterValue parameter = parameters.values.first; |
| push(new HIdentity(parameter, graph.addConstantNull(closedWorld), |
| null, commonMasks.boolType)); |
| }, |
| visitThen: () { |
| closeAndGotoExit(new HReturn( |
| graph.addConstantBool(false, closedWorld), |
| sourceInformationBuilder |
| .buildImplicitReturn(functionElement))); |
| }, |
| visitElse: null, |
| sourceInformation: sourceInformationBuilder.buildIf(function.body)); |
| } |
| } |
| if (const bool.fromEnvironment('unreachable-throw')) { |
| var emptyParameters = |
| parameters.values.where((p) => p.instructionType.isEmpty); |
| if (emptyParameters.length > 0) { |
| addComment('${emptyParameters} inferred as [empty]'); |
| pushInvokeStatic(function.body, helpers.assertUnreachableMethod, []); |
| pop(); |
| return closeFunction(); |
| } |
| } |
| function.body.accept(this); |
| return closeFunction(); |
| } |
| |
| /// Adds a JavaScript comment to the output. The comment will be omitted in |
| /// minified mode. Each line in [text] is preceded with `//` and indented. |
| /// Use sparingly. In order for the comment to be retained it is modeled as |
| /// having side effects which will inhibit code motion. |
| // TODO(sra): Figure out how to keep comment anchored without effects. |
| void addComment(String text) { |
| add(new HForeignCode(js.js.statementTemplateYielding(new js.Comment(text)), |
| commonMasks.dynamicType, <HInstruction>[], |
| isStatement: true)); |
| } |
| |
| HGraph buildCheckedSetter(FieldElement field) { |
| ResolvedAst resolvedAst = field.resolvedAst; |
| openFunction(field, resolvedAst.node); |
| HInstruction thisInstruction = localsHandler.readThis(); |
| // Use dynamic type because the type computed by the inferrer is |
| // narrowed to the type annotation. |
| HInstruction parameter = |
| new HParameterValue(field, commonMasks.dynamicType); |
| // Add the parameter as the last instruction of the entry block. |
| // If the method is intercepted, we want the actual receiver |
| // to be the first parameter. |
| graph.entry.addBefore(graph.entry.last, parameter); |
| HInstruction value = |
| typeBuilder.potentiallyCheckOrTrustType(parameter, field.type); |
| add(new HFieldSet(field, thisInstruction, value)); |
| return closeFunction(); |
| } |
| |
| HGraph buildLazyInitializer(VariableElement variable) { |
| assert(invariant(variable, resolvedAst.element == variable, |
| message: "Unexpected variable $variable for $resolvedAst.")); |
| inLazyInitializerExpression = true; |
| ast.VariableDefinitions node = resolvedAst.node; |
| ast.Node initializer = resolvedAst.body; |
| assert(invariant(variable, initializer != null, |
| message: "Non-constant variable $variable has no initializer.")); |
| openFunction(variable, node); |
| visit(initializer); |
| HInstruction value = pop(); |
| value = typeBuilder.potentiallyCheckOrTrustType(value, variable.type); |
| // In the case of multiple declarations (and some definitions) on the same |
| // line, the source pointer needs to point to the right initialized |
| // variable. So find the specific initialized variable we are referring to. |
| ast.Node sourceInfoNode = initializer; |
| for (var definition in node.definitions) { |
| if (definition is ast.SendSet && |
| definition.selector.asIdentifier().source == variable.name) { |
| sourceInfoNode = definition.assignmentOperator; |
| break; |
| } |
| } |
| |
| closeAndGotoExit(new HReturn( |
| value, sourceInformationBuilder.buildReturn(sourceInfoNode))); |
| return closeFunction(); |
| } |
| |
| /** |
| * Returns the constructor body associated with the given constructor or |
| * creates a new constructor body, if none can be found. |
| * |
| * Returns [:null:] if the constructor does not have a body. |
| */ |
| ConstructorBodyElement getConstructorBody( |
| ResolvedAst constructorResolvedAst) { |
| ConstructorElement constructor = |
| constructorResolvedAst.element.implementation; |
| assert(constructor.isGenerativeConstructor); |
| if (constructorResolvedAst.kind != ResolvedAstKind.PARSED) return null; |
| |
| ast.FunctionExpression node = constructorResolvedAst.node; |
| // If we know the body doesn't have any code, we don't generate it. |
| if (!node.hasBody) return null; |
| if (node.hasEmptyBody) return null; |
| ClassElement classElement = constructor.enclosingClass; |
| ConstructorBodyElement bodyElement; |
| classElement.forEachBackendMember((Element backendMember) { |
| if (backendMember.isGenerativeConstructorBody) { |
| ConstructorBodyElement body = backendMember; |
| if (body.constructor == constructor) { |
| // TODO(kasperl): Find a way of stopping the iteration |
| // through the backend members. |
| bodyElement = backendMember; |
| } |
| } |
| }); |
| if (bodyElement == null) { |
| bodyElement = |
| new ConstructorBodyElementX(constructorResolvedAst, constructor); |
| classElement.addBackendMember(bodyElement); |
| |
| if (constructor.isPatch) { |
| // Create origin body element for patched constructors. |
| ConstructorBodyElementX patch = bodyElement; |
| ConstructorBodyElementX origin = new ConstructorBodyElementX( |
| constructorResolvedAst, constructor.origin); |
| origin.applyPatch(patch); |
| classElement.origin.addBackendMember(bodyElement.origin); |
| } |
| } |
| assert(bodyElement.isGenerativeConstructorBody); |
| return bodyElement; |
| } |
| |
| /** |
| * This method sets up the local state of the builder for inlining [function]. |
| * The arguments of the function are inserted into the [localsHandler]. |
| * |
| * When inlining a function, [:return:] statements are not emitted as |
| * [HReturn] instructions. Instead, the value of a synthetic element is |
| * updated in the [localsHandler]. This function creates such an element and |
| * stores it in the [returnLocal] field. |
| */ |
| void setupStateForInlining( |
| FunctionElement function, List<HInstruction> compiledArguments, |
| {InterfaceType instanceType}) { |
| ResolvedAst resolvedAst = function.resolvedAst; |
| assert(resolvedAst != null); |
| localsHandler = new LocalsHandler(this, function, instanceType, compiler); |
| localsHandler.closureData = |
| compiler.closureToClassMapper.getClosureToClassMapping(resolvedAst); |
| returnLocal = new SyntheticLocal("result", function); |
| localsHandler.updateLocal(returnLocal, graph.addConstantNull(closedWorld)); |
| |
| inTryStatement = false; // TODO(lry): why? Document. |
| |
| int argumentIndex = 0; |
| if (function.isInstanceMember) { |
| localsHandler.updateLocal(localsHandler.closureData.thisLocal, |
| compiledArguments[argumentIndex++]); |
| } |
| |
| FunctionSignature signature = function.functionSignature; |
| signature.orderedForEachParameter((ParameterElement parameter) { |
| HInstruction argument = compiledArguments[argumentIndex++]; |
| localsHandler.updateLocal(parameter, argument); |
| }); |
| |
| ClassElement enclosing = function.enclosingClass; |
| if ((function.isConstructor || function.isGenerativeConstructorBody) && |
| backend.classNeedsRti(enclosing)) { |
| enclosing.typeVariables.forEach((TypeVariableType typeVariable) { |
| HInstruction argument = compiledArguments[argumentIndex++]; |
| localsHandler.updateLocal( |
| localsHandler.getTypeVariableAsLocal(typeVariable), argument); |
| }); |
| } |
| assert(argumentIndex == compiledArguments.length); |
| |
| returnType = signature.type.returnType; |
| stack = <HInstruction>[]; |
| |
| insertTraceCall(function); |
| insertCoverageCall(function); |
| } |
| |
| void restoreState(AstInliningState state) { |
| localsHandler = state.oldLocalsHandler; |
| returnLocal = state.oldReturnLocal; |
| inTryStatement = state.inTryStatement; |
| resolvedAst = state.oldResolvedAst; |
| elementInferenceResults = state.oldElementInferenceResults; |
| returnType = state.oldReturnType; |
| assert(stack.isEmpty); |
| stack = state.oldStack; |
| } |
| |
| /** |
| * Run this builder on the body of the [function] to be inlined. |
| */ |
| void visitInlinedFunction(ResolvedAst resolvedAst) { |
| typeBuilder.potentiallyCheckInlinedParameterTypes( |
| resolvedAst.element.implementation); |
| |
| if (resolvedAst.element.isGenerativeConstructor) { |
| buildFactory(resolvedAst); |
| } else { |
| ast.FunctionExpression functionNode = resolvedAst.node; |
| functionNode.body.accept(this); |
| } |
| } |
| |
| addInlinedInstantiation(DartType type) { |
| if (type != null) { |
| currentInlinedInstantiations.add(type); |
| } |
| } |
| |
| removeInlinedInstantiation(DartType type) { |
| if (type != null) { |
| currentInlinedInstantiations.removeLast(); |
| } |
| } |
| |
| bool providedArgumentsKnownToBeComplete(ast.Node currentNode) { |
| /* When inlining the iterator methods generated for a [:for-in:] loop, the |
| * [currentNode] is the [ForIn] tree. The compiler-generated iterator |
| * invocations are known to have fully specified argument lists, no default |
| * arguments are used. See invocations of [pushInvokeDynamic] in |
| * [visitForIn]. |
| */ |
| return currentNode.asForIn() != null; |
| } |
| |
| /** |
| * Documentation wanted -- johnniwinther |
| * |
| * Invariant: [constructors] must contain only implementation elements. |
| */ |
| void inlineSuperOrRedirect( |
| ResolvedAst constructorResolvedAst, |
| List<HInstruction> compiledArguments, |
| List<ResolvedAst> constructorResolvedAsts, |
| Map<Element, HInstruction> fieldValues, |
| FunctionElement caller) { |
| ConstructorElement callee = constructorResolvedAst.element.implementation; |
| reporter.withCurrentElement(callee, () { |
| constructorResolvedAsts.add(constructorResolvedAst); |
| ClassElement enclosingClass = callee.enclosingClass; |
| if (backend.classNeedsRti(enclosingClass)) { |
| // If [enclosingClass] needs RTI, we have to give a value to its |
| // type parameters. |
| ClassElement currentClass = caller.enclosingClass; |
| // For a super constructor call, the type is the supertype of |
| // [currentClass]. For a redirecting constructor, the type is |
| // the current type. [InterfaceType.asInstanceOf] takes care |
| // of both. |
| InterfaceType type = currentClass.thisType.asInstanceOf(enclosingClass); |
| type = localsHandler.substInContext(type); |
| List<DartType> arguments = type.typeArguments; |
| List<DartType> typeVariables = enclosingClass.typeVariables; |
| if (!type.isRaw) { |
| assert(arguments.length == typeVariables.length); |
| Iterator<DartType> variables = typeVariables.iterator; |
| type.typeArguments.forEach((DartType argument) { |
| variables.moveNext(); |
| TypeVariableType typeVariable = variables.current; |
| localsHandler.updateLocal( |
| localsHandler.getTypeVariableAsLocal(typeVariable), |
| typeBuilder.analyzeTypeArgument(argument, sourceElement)); |
| }); |
| } else { |
| // If the supertype is a raw type, we need to set to null the |
| // type variables. |
| for (TypeVariableType variable in typeVariables) { |
| localsHandler.updateLocal( |
| localsHandler.getTypeVariableAsLocal(variable), |
| graph.addConstantNull(closedWorld)); |
| } |
| } |
| } |
| |
| // For redirecting constructors, the fields will be initialized later |
| // by the effective target. |
| if (!callee.isRedirectingGenerative) { |
| inlinedFrom(constructorResolvedAst, () { |
| buildFieldInitializers( |
| callee.enclosingClass.implementation, fieldValues); |
| }); |
| } |
| |
| int index = 0; |
| FunctionSignature params = callee.functionSignature; |
| params.orderedForEachParameter((ParameterElement parameter) { |
| HInstruction argument = compiledArguments[index++]; |
| // Because we are inlining the initializer, we must update |
| // what was given as parameter. This will be used in case |
| // there is a parameter check expression in the initializer. |
| parameters[parameter] = argument; |
| localsHandler.updateLocal(parameter, argument); |
| // Don't forget to update the field, if the parameter is of the |
| // form [:this.x:]. |
| if (parameter.isInitializingFormal) { |
| InitializingFormalElement fieldParameterElement = parameter; |
| fieldValues[fieldParameterElement.fieldElement] = argument; |
| } |
| }); |
| |
| // Build the initializers in the context of the new constructor. |
| ResolvedAst oldResolvedAst = resolvedAst; |
| resolvedAst = callee.resolvedAst; |
| final oldElementInferenceResults = elementInferenceResults; |
| elementInferenceResults = inferenceResults.resultOf(callee); |
| ClosureClassMap oldClosureData = localsHandler.closureData; |
| ClosureClassMap newClosureData = |
| compiler.closureToClassMapper.getClosureToClassMapping(resolvedAst); |
| localsHandler.closureData = newClosureData; |
| if (resolvedAst.kind == ResolvedAstKind.PARSED) { |
| localsHandler.enterScope(resolvedAst.node, callee); |
| } |
| buildInitializers(callee, constructorResolvedAsts, fieldValues); |
| localsHandler.closureData = oldClosureData; |
| resolvedAst = oldResolvedAst; |
| elementInferenceResults = oldElementInferenceResults; |
| }); |
| } |
| |
| void buildInitializers( |
| ConstructorElement constructor, |
| List<ResolvedAst> constructorResolvedAsts, |
| Map<Element, HInstruction> fieldValues) { |
| assert(invariant( |
| constructor, resolvedAst.element == constructor.declaration, |
| message: "Expected ResolvedAst for $constructor, found $resolvedAst")); |
| if (resolvedAst.kind == ResolvedAstKind.PARSED) { |
| buildParsedInitializers( |
| constructor, constructorResolvedAsts, fieldValues); |
| } else { |
| buildSynthesizedConstructorInitializers( |
| constructor, constructorResolvedAsts, fieldValues); |
| } |
| } |
| |
| void buildSynthesizedConstructorInitializers( |
| ConstructorElement constructor, |
| List<ResolvedAst> constructorResolvedAsts, |
| Map<Element, HInstruction> fieldValues) { |
| assert(invariant(constructor, constructor.isSynthesized, |
| message: "Unexpected unsynthesized constructor: $constructor")); |
| List<HInstruction> arguments = <HInstruction>[]; |
| HInstruction compileArgument(ParameterElement parameter) { |
| return localsHandler.readLocal(parameter); |
| } |
| |
| ConstructorElement target = constructor.definingConstructor.implementation; |
| bool match = !target.isMalformed && |
| CallStructure.addForwardingElementArgumentsToList( |
| constructor, |
| arguments, |
| target, |
| compileArgument, |
| handleConstantForOptionalParameter); |
| if (!match) { |
| if (compiler.elementHasCompileTimeError(constructor)) { |
| return; |
| } |
| // If this fails, the selector we constructed for the call to a |
| // forwarding constructor in a mixin application did not match the |
| // constructor (which, for example, may happen when the libraries are |
| // not compatible for private names, see issue 20394). |
| reporter.internalError( |
| constructor, 'forwarding constructor call does not match'); |
| } |
| inlineSuperOrRedirect(target.resolvedAst, arguments, |
| constructorResolvedAsts, fieldValues, constructor); |
| } |
| |
| /** |
| * Run through the initializers and inline all field initializers. Recursively |
| * inlines super initializers. |
| * |
| * The constructors of the inlined initializers is added to [constructors] |
| * with sub constructors having a lower index than super constructors. |
| * |
| * Invariant: The [constructor] and elements in [constructors] must all be |
| * implementation elements. |
| */ |
| void buildParsedInitializers( |
| ConstructorElement constructor, |
| List<ResolvedAst> constructorResolvedAsts, |
| Map<Element, HInstruction> fieldValues) { |
| assert( |
| invariant(constructor, resolvedAst.element == constructor.declaration)); |
| assert(invariant(constructor, constructor.isImplementation)); |
| assert(invariant(constructor, !constructor.isSynthesized, |
| message: "Unexpected synthesized constructor: $constructor")); |
| ast.FunctionExpression functionNode = resolvedAst.node; |
| |
| bool foundSuperOrRedirect = false; |
| if (functionNode.initializers != null) { |
| Link<ast.Node> initializers = functionNode.initializers.nodes; |
| for (Link<ast.Node> link = initializers; |
| !link.isEmpty; |
| link = link.tail) { |
| assert(link.head is ast.Send); |
| if (link.head is! ast.SendSet) { |
| // A super initializer or constructor redirection. |
| foundSuperOrRedirect = true; |
| ast.Send call = link.head; |
| assert(ast.Initializers.isSuperConstructorCall(call) || |
| ast.Initializers.isConstructorRedirect(call)); |
| FunctionElement target = elements[call].implementation; |
| CallStructure callStructure = |
| elements.getSelector(call).callStructure; |
| Link<ast.Node> arguments = call.arguments; |
| List<HInstruction> compiledArguments; |
| inlinedFrom(resolvedAst, () { |
| compiledArguments = |
| makeStaticArgumentList(callStructure, arguments, target); |
| }); |
| inlineSuperOrRedirect(target.resolvedAst, compiledArguments, |
| constructorResolvedAsts, fieldValues, constructor); |
| } else { |
| // A field initializer. |
| ast.SendSet init = link.head; |
| Link<ast.Node> arguments = init.arguments; |
| assert(!arguments.isEmpty && arguments.tail.isEmpty); |
| inlinedFrom(resolvedAst, () { |
| visit(arguments.head); |
| }); |
| fieldValues[elements[init]] = pop(); |
| } |
| } |
| } |
| |
| if (!foundSuperOrRedirect) { |
| // No super initializer found. Try to find the default constructor if |
| // the class is not Object. |
| ClassElement enclosingClass = constructor.enclosingClass; |
| ClassElement superClass = enclosingClass.superclass; |
| if (!enclosingClass.isObject) { |
| assert(superClass != null); |
| assert(superClass.isResolved); |
| // TODO(johnniwinther): Should we find injected constructors as well? |
| FunctionElement target = superClass.lookupDefaultConstructor(); |
| if (target == null) { |
| reporter.internalError( |
| superClass, "No default constructor available."); |
| } |
| List<HInstruction> arguments = CallStructure.NO_ARGS.makeArgumentsList( |
| const Link<ast.Node>(), |
| target.implementation, |
| null, |
| handleConstantForOptionalParameter); |
| inlineSuperOrRedirect(target.resolvedAst, arguments, |
| constructorResolvedAsts, fieldValues, constructor); |
| } |
| } |
| } |
| |
| /** |
| * Run through the fields of [cls] and add their potential |
| * initializers. |
| * |
| * Invariant: [classElement] must be an implementation element. |
| */ |
| void buildFieldInitializers( |
| ClassElement classElement, Map<Element, HInstruction> fieldValues) { |
| assert(invariant(classElement, classElement.isImplementation)); |
| classElement.forEachInstanceField( |
| (ClassElement enclosingClass, FieldElement member) { |
| if (compiler.elementHasCompileTimeError(member)) return; |
| reporter.withCurrentElement(member, () { |
| ResolvedAst fieldResolvedAst = member.resolvedAst; |
| ast.Node node = fieldResolvedAst.node; |
| ast.Expression initializer = fieldResolvedAst.body; |
| if (initializer == null) { |
| // Unassigned fields of native classes are not initialized to |
| // prevent overwriting pre-initialized native properties. |
| if (!backend.isNativeOrExtendsNative(classElement)) { |
| fieldValues[member] = graph.addConstantNull(closedWorld); |
| } |
| } else { |
| ast.Node right = initializer; |
| ResolvedAst savedResolvedAst = resolvedAst; |
| resolvedAst = fieldResolvedAst; |
| final oldElementInferenceResults = elementInferenceResults; |
| elementInferenceResults = inferenceResults.resultOf(member); |
| // In case the field initializer uses closures, run the |
| // closure to class mapper. |
| compiler.closureToClassMapper.getClosureToClassMapping(resolvedAst); |
| inlinedFrom(fieldResolvedAst, () => right.accept(this)); |
| resolvedAst = savedResolvedAst; |
| elementInferenceResults = oldElementInferenceResults; |
| fieldValues[member] = pop(); |
| } |
| }); |
| }); |
| } |
| |
| /** |
| * Build the factory function corresponding to the constructor |
| * [functionElement]: |
| * - Initialize fields with the values of the field initializers of the |
| * current constructor and super constructors or constructors redirected |
| * to, starting from the current constructor. |
| * - Call the constructor bodies, starting from the constructor(s) in the |
| * super class(es). |
| */ |
| HGraph buildFactory(ResolvedAst resolvedAst) { |
| ConstructorElement functionElement = resolvedAst.element; |
| functionElement = functionElement.implementation; |
| ClassElement classElement = functionElement.enclosingClass.implementation; |
| bool isNativeUpgradeFactory = |
| backend.isNativeOrExtendsNative(classElement) && |
| !backend.isJsInterop(classElement); |
| ast.FunctionExpression function; |
| if (resolvedAst.kind == ResolvedAstKind.PARSED) { |
| function = resolvedAst.node; |
| } |
| |
| // Note that constructors (like any other static function) do not need |
| // to deal with optional arguments. It is the callers job to provide all |
| // arguments as if they were positional. |
| |
| if (inliningStack.isEmpty) { |
| // The initializer list could contain closures. |
| openFunction(functionElement, function); |
| } |
| |
| Map<Element, HInstruction> fieldValues = new Map<Element, HInstruction>(); |
| |
| // Compile the possible initialization code for local fields and |
| // super fields, unless this is a redirecting constructor, in which case |
| // the effective target will initialize these. |
| if (!functionElement.isRedirectingGenerative) { |
| buildFieldInitializers(classElement, fieldValues); |
| } |
| |
| // Compile field-parameters such as [:this.x:]. |
| FunctionSignature params = functionElement.functionSignature; |
| params.orderedForEachParameter((ParameterElement parameter) { |
| if (parameter.isInitializingFormal) { |
| // If the [element] is a field-parameter then |
| // initialize the field element with its value. |
| InitializingFormalElement fieldParameter = parameter; |
| HInstruction parameterValue = localsHandler.readLocal(fieldParameter); |
| fieldValues[fieldParameter.fieldElement] = parameterValue; |
| } |
| }); |
| |
| // Analyze the constructor and all referenced constructors and collect |
| // initializers and constructor bodies. |
| List<ResolvedAst> constructorResolvedAsts = <ResolvedAst>[resolvedAst]; |
| buildInitializers(functionElement, constructorResolvedAsts, fieldValues); |
| |
| // Call the JavaScript constructor with the fields as argument. |
| List<HInstruction> constructorArguments = <HInstruction>[]; |
| List<FieldEntity> fields = <FieldEntity>[]; |
| |
| classElement.forEachInstanceField( |
| (ClassElement enclosingClass, FieldElement member) { |
| HInstruction value = fieldValues[member]; |
| if (value == null) { |
| // Uninitialized native fields are pre-initialized by the native |
| // implementation. |
| assert(invariant( |
| member, isNativeUpgradeFactory || compiler.compilationFailed)); |
| } else { |
| fields.add(member); |
| DartType type = localsHandler.substInContext(member.type); |
| constructorArguments |
| .add(typeBuilder.potentiallyCheckOrTrustType(value, type)); |
| } |
| }, includeSuperAndInjectedMembers: true); |
| |
| InterfaceType type = classElement.thisType; |
| TypeMask ssaType = |
| new TypeMask.nonNullExact(classElement.declaration, closedWorld); |
| List<DartType> instantiatedTypes; |
| addInlinedInstantiation(type); |
| if (!currentInlinedInstantiations.isEmpty) { |
| instantiatedTypes = new List<DartType>.from(currentInlinedInstantiations); |
| } |
| |
| HInstruction newObject; |
| if (!isNativeUpgradeFactory) { |
| // Create the runtime type information, if needed. |
| bool hasRtiInput = false; |
| if (backend.classNeedsRtiField(classElement)) { |
| // Read the values of the type arguments and create a |
| // HTypeInfoExpression to set on the newly create object. |
| hasRtiInput = true; |
| List<HInstruction> typeArguments = <HInstruction>[]; |
| classElement.typeVariables.forEach((TypeVariableType typeVariable) { |
| HInstruction argument = localsHandler |
| .readLocal(localsHandler.getTypeVariableAsLocal(typeVariable)); |
| typeArguments.add(argument); |
| }); |
| |
| HInstruction typeInfo = new HTypeInfoExpression( |
| TypeInfoExpressionKind.INSTANCE, |
| classElement.thisType, |
| typeArguments, |
| commonMasks.dynamicType); |
| add(typeInfo); |
| constructorArguments.add(typeInfo); |
| } |
| |
| newObject = new HCreate(classElement, constructorArguments, ssaType, |
| instantiatedTypes: instantiatedTypes, hasRtiInput: hasRtiInput); |
| if (function != null) { |
| // TODO(johnniwinther): Provide source information for creation through |
| // synthetic constructors. |
| newObject.sourceInformation = |
| sourceInformationBuilder.buildCreate(function); |
| } |
| add(newObject); |
| } else { |
| // Bulk assign to the initialized fields. |
| newObject = graph.explicitReceiverParameter; |
| // Null guard ensures an error if we are being called from an explicit |
| // 'new' of the constructor instead of via an upgrade. It is optimized out |
| // if there are field initializers. |
| add(new HFieldGet(null, newObject, commonMasks.dynamicType, |
| isAssignable: false)); |
| for (int i = 0; i < fields.length; i++) { |
| add(new HFieldSet(fields[i], newObject, constructorArguments[i])); |
| } |
| } |
| removeInlinedInstantiation(type); |
| |
| // Generate calls to the constructor bodies. |
| HInstruction interceptor = null; |
| for (int index = constructorResolvedAsts.length - 1; index >= 0; index--) { |
| ResolvedAst constructorResolvedAst = constructorResolvedAsts[index]; |
| ConstructorBodyElement body = getConstructorBody(constructorResolvedAst); |
| if (body == null) continue; |
| |
| List bodyCallInputs = <HInstruction>[]; |
| if (isNativeUpgradeFactory) { |
| if (interceptor == null) { |
| ConstantValue constant = |
| new InterceptorConstantValue(classElement.thisType); |
| interceptor = graph.addConstant(constant, closedWorld); |
| } |
| bodyCallInputs.add(interceptor); |
| } |
| bodyCallInputs.add(newObject); |
| ast.Node node = constructorResolvedAst.node; |
| ClosureClassMap parameterClosureData = compiler.closureToClassMapper |
| .getClosureToClassMapping(constructorResolvedAst); |
| |
| FunctionSignature functionSignature = body.functionSignature; |
| // Provide the parameters to the generative constructor body. |
| functionSignature.orderedForEachParameter((ParameterElement parameter) { |
| // If [parameter] is boxed, it will be a field in the box passed as the |
| // last parameter. So no need to directly pass it. |
| if (!localsHandler.isBoxed(parameter)) { |
| bodyCallInputs.add(localsHandler.readLocal(parameter)); |
| } |
| }); |
| |
| // If there are locals that escape (ie mutated in closures), we |
| // pass the box to the constructor. |
| // The box must be passed before any type variable. |
| ClosureScope scopeData = parameterClosureData.capturingScopes[node]; |
| if (scopeData != null) { |
| bodyCallInputs.add(localsHandler.readLocal(scopeData.boxElement)); |
| } |
| |
| // Type variables arguments must come after the box (if there is one). |
| ConstructorElement constructor = |
| constructorResolvedAst.element.implementation; |
| ClassElement currentClass = constructor.enclosingClass; |
| if (backend.classNeedsRti(currentClass)) { |
| // If [currentClass] needs RTI, we add the type variables as |
| // parameters of the generative constructor body. |
| currentClass.typeVariables.forEach((TypeVariableType argument) { |
| // TODO(johnniwinther): Substitute [argument] with |
| // `localsHandler.substInContext(argument)`. |
| bodyCallInputs.add(localsHandler |
| .readLocal(localsHandler.getTypeVariableAsLocal(argument))); |
| }); |
| } |
| |
| if (!isNativeUpgradeFactory && // TODO(13836): Fix inlining. |
| tryInlineMethod(body, null, null, bodyCallInputs, function)) { |
| pop(); |
| } else { |
| HInvokeConstructorBody invoke = new HInvokeConstructorBody( |
| body.declaration, bodyCallInputs, commonMasks.nonNullType); |
| invoke.sideEffects = closedWorld.getSideEffectsOfElement(constructor); |
| add(invoke); |
| } |
| } |
| if (inliningStack.isEmpty) { |
| closeAndGotoExit(new HReturn(newObject, |
| sourceInformationBuilder.buildImplicitReturn(functionElement))); |
| return closeFunction(); |
| } else { |
| localsHandler.updateLocal(returnLocal, newObject); |
| return null; |
| } |
| } |
| |
| /** |
| * Documentation wanted -- johnniwinther |
| * |
| * Invariant: [functionElement] must be the implementation element. |
| */ |
| void openFunction(Element element, ast.Node node) { |
| assert(invariant(element, element.isImplementation)); |
| HBasicBlock block = graph.addNewBlock(); |
| open(graph.entry); |
| |
| localsHandler.startFunction(element, node); |
| close(new HGoto()).addSuccessor(block); |
| |
| open(block); |
| |
| // Add the type parameters of the class as parameters of this method. This |
| // must be done before adding the normal parameters, because their types |
| // may contain references to type variables. |
| var enclosing = element.enclosingElement; |
| if ((element.isConstructor || element.isGenerativeConstructorBody) && |
| backend.classNeedsRti(enclosing)) { |
| enclosing.typeVariables.forEach((TypeVariableType typeVariable) { |
| HParameterValue param = |
| addParameter(typeVariable.element, commonMasks.nonNullType); |
| localsHandler.directLocals[ |
| localsHandler.getTypeVariableAsLocal(typeVariable)] = param; |
| }); |
| } |
| |
| if (element is FunctionElement) { |
| FunctionElement functionElement = element; |
| FunctionSignature signature = functionElement.functionSignature; |
| |
| // Put the type checks in the first successor of the entry, |
| // because that is where the type guards will also be inserted. |
| // This way we ensure that a type guard will dominate the type |
| // check. |
| ClosureScope scopeData = localsHandler.closureData.capturingScopes[node]; |
| signature.orderedForEachParameter((ParameterElement parameterElement) { |
| if (element.isGenerativeConstructorBody) { |
| if (scopeData != null && |
| scopeData.isCapturedVariable(parameterElement)) { |
| // The parameter will be a field in the box passed as the |
| // last parameter. So no need to have it. |
| return; |
| } |
| } |
| HInstruction newParameter = |
| localsHandler.directLocals[parameterElement]; |
| if (!element.isConstructor || |
| !(element as ConstructorElement).isRedirectingFactory) { |
| // Redirection factories must not check their argument types. |
| // Example: |
| // |
| // class A { |
| // A(String foo) = A.b; |
| // A(int foo) { print(foo); } |
| // } |
| // main() { |
| // new A(499); // valid even in checked mode. |
| // new A("foo"); // invalid in checked mode. |
| // |
| // Only the final target is allowed to check for the argument types. |
| newParameter = typeBuilder.potentiallyCheckOrTrustType( |
| newParameter, parameterElement.type); |
| } |
| localsHandler.directLocals[parameterElement] = newParameter; |
| }); |
| |
| returnType = signature.type.returnType; |
| } else { |
| // Otherwise it is a lazy initializer which does not have parameters. |
| assert(element is VariableElement); |
| } |
| |
| insertTraceCall(element); |
| insertCoverageCall(element); |
| } |
| |
| insertTraceCall(Element element) { |
| if (JavaScriptBackend.TRACE_METHOD == 'console') { |
| if (element == backend.helpers.traceHelper) return; |
| n(e) => e == null ? '' : e.name; |
| String name = "${n(element.library)}:${n(element.enclosingClass)}." |
| "${n(element)}"; |
| HConstant nameConstant = addConstantString(name); |
| add(new HInvokeStatic(backend.helpers.traceHelper, |
| <HInstruction>[nameConstant], commonMasks.dynamicType)); |
| } |
| } |
| |
| insertCoverageCall(Element element) { |
| if (JavaScriptBackend.TRACE_METHOD == 'post') { |
| if (element == backend.helpers.traceHelper) return; |
| // TODO(sigmund): create a better uuid for elements. |
| HConstant idConstant = |
| graph.addConstantInt(element.hashCode, closedWorld); |
| HConstant nameConstant = addConstantString(element.name); |
| add(new HInvokeStatic(backend.helpers.traceHelper, |
| <HInstruction>[idConstant, nameConstant], commonMasks.dynamicType)); |
| } |
| } |
| |
| void assertIsSubtype( |
| ast.Node node, DartType subtype, DartType supertype, String message) { |
| HInstruction subtypeInstruction = typeBuilder.analyzeTypeArgument( |
| localsHandler.substInContext(subtype), sourceElement); |
| HInstruction supertypeInstruction = typeBuilder.analyzeTypeArgument( |
| localsHandler.substInContext(supertype), sourceElement); |
| HInstruction messageInstruction = graph.addConstantString( |
| new ast.DartString.literal(message), closedWorld); |
| MethodElement element = helpers.assertIsSubtype; |
| var inputs = <HInstruction>[ |
| subtypeInstruction, |
| supertypeInstruction, |
| messageInstruction |
| ]; |
| HInstruction assertIsSubtype = |
| new HInvokeStatic(element, inputs, subtypeInstruction.instructionType); |
| registry?.registerTypeVariableBoundsSubtypeCheck(subtype, supertype); |
| add(assertIsSubtype); |
| } |
| |
| HGraph closeFunction() { |
| // TODO(kasperl): Make this goto an implicit return. |
| if (!isAborted()) closeAndGotoExit(new HGoto()); |
| graph.finalize(); |
| return graph; |
| } |
| |
| void pushWithPosition(HInstruction instruction, ast.Node node) { |
| push(attachPosition(instruction, node)); |
| } |
| |
| /// Pops the most recent instruction from the stack and 'boolifies' it. |
| /// |
| /// Boolification is checking if the value is '=== true'. |
| @override |
| HInstruction popBoolified() { |
| HInstruction value = pop(); |
| if (typeBuilder.checkOrTrustTypes) { |
| return typeBuilder.potentiallyCheckOrTrustType( |
| value, compiler.coreTypes.boolType, |
| kind: HTypeConversion.BOOLEAN_CONVERSION_CHECK); |
| } |
| HInstruction result = new HBoolify(value, commonMasks.boolType); |
| add(result); |
| return result; |
| } |
| |
| HInstruction attachPosition(HInstruction target, ast.Node node) { |
| if (node != null) { |
| target.sourceInformation = sourceInformationBuilder.buildGeneric(node); |
| } |
| return target; |
| } |
| |
| void visit(ast.Node node) { |
| if (node != null) node.accept(this); |
| } |
| |
| /// Visit [node] and pop the resulting [HInstruction]. |
| HInstruction visitAndPop(ast.Node node) { |
| node.accept(this); |
| return pop(); |
| } |
| |
| visitAssert(ast.Assert node) { |
| if (!compiler.options.enableUserAssertions) return; |
| |
| if (!node.hasMessage) { |
| // Generate: |
| // |
| // assertHelper(condition); |
| // |
| visit(node.condition); |
| pushInvokeStatic(node, helpers.assertHelper, [pop()]); |
| pop(); |
| return; |
| } |
| // Assert has message. Generate: |
| // |
| // if (assertTest(condition)) assertThrow(message); |
| // |
| void buildCondition() { |
| visit(node.condition); |
| pushInvokeStatic(node, helpers.assertTest, [pop()]); |
| } |
| |
| void fail() { |
| visit(node.message); |
| pushInvokeStatic(node, helpers.assertThrow, [pop()]); |
| pop(); |
| } |
| |
| handleIf(node: node, visitCondition: buildCondition, visitThen: fail); |
| } |
| |
| visitBlock(ast.Block node) { |
| assert(!isAborted()); |
| if (!isReachable) return; // This can only happen when inlining. |
| for (Link<ast.Node> link = node.statements.nodes; |
| !link.isEmpty; |
| link = link.tail) { |
| visit(link.head); |
| if (!isReachable) { |
| // The block has been aborted by a return or a throw. |
| if (!stack.isEmpty) { |
| reporter.internalError(node, 'Non-empty instruction stack.'); |
| } |
| return; |
| } |
| } |
| assert(!current.isClosed()); |
| if (!stack.isEmpty) { |
| reporter.internalError(node, 'Non-empty instruction stack.'); |
| } |
| } |
| |
| visitClassNode(ast.ClassNode node) { |
| reporter.internalError( |
| node, 'SsaBuilder.visitClassNode should not be called.'); |
| } |
| |
| visitThrowExpression(ast.Expression expression) { |
| bool old = inExpressionOfThrow; |
| try { |
| inExpressionOfThrow = true; |
| visit(expression); |
| } finally { |
| inExpressionOfThrow = old; |
| } |
| } |
| |
| visitExpressionStatement(ast.ExpressionStatement node) { |
| if (!isReachable) return; |
| ast.Throw throwExpression = node.expression.asThrow(); |
| if (throwExpression != null && inliningStack.isEmpty) { |
| visitThrowExpression(throwExpression.expression); |
| handleInTryStatement(); |
| closeAndGotoExit( |
| new HThrow(pop(), sourceInformationBuilder.buildThrow(node))); |
| } else { |
| visit(node.expression); |
| pop(); |
| } |
| } |
| |
| visitFor(ast.For node) { |
| assert(isReachable); |
| assert(node.body != null); |
| void buildInitializer() { |
| ast.Node initializer = node.initializer; |
| if (initializer == null) return; |
| visit(initializer); |
| if (initializer.asExpression() != null) { |
| pop(); |
| } |
| } |
| |
| HInstruction buildCondition() { |
| if (node.condition == null) { |
| return graph.addConstantBool(true, closedWorld); |
| } |
| visit(node.condition); |
| return popBoolified(); |
| } |
| |
| void buildUpdate() { |
| for (ast.Expression expression in node.update) { |
| visit(expression); |
| assert(!isAborted()); |
| // The result of the update instruction isn't used, and can just |
| // be dropped. |
| pop(); |
| } |
| } |
| |
| void buildBody() { |
| visit(node.body); |
| } |
| |
| loopHandler.handleLoop( |
| node, buildInitializer, buildCondition, buildUpdate, buildBody); |
| } |
| |
| visitWhile(ast.While node) { |
| assert(isReachable); |
| HInstruction buildCondition() { |
| visit(node.condition); |
| return popBoolified(); |
| } |
| |
| loopHandler.handleLoop(node, () {}, buildCondition, () {}, () { |
| visit(node.body); |
| }); |
| } |
| |
| visitDoWhile(ast.DoWhile node) { |
| assert(isReachable); |
| LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); |
| localsHandler.startLoop(node); |
| loopDepth++; |
| JumpHandler jumpHandler = loopHandler.beginLoopHeader(node); |
| HLoopInformation loopInfo = current.loopInformation; |
| HBasicBlock loopEntryBlock = current; |
| HBasicBlock bodyEntryBlock = current; |
| JumpTarget target = elements.getTargetDefinition(node); |
| bool hasContinues = target != null && target.isContinueTarget; |
| if (hasContinues) { |
| // Add extra block to hang labels on. |
| // It doesn't currently work if they are on the same block as the |
| // HLoopInfo. The handling of HLabeledBlockInformation will visit a |
| // SubGraph that starts at the same block again, so the HLoopInfo is |
| // either handled twice, or it's handled after the labeled block info, |
| // both of which generate the wrong code. |
| // Using a separate block is just a simple workaround. |
| bodyEntryBlock = openNewBlock(); |
| } |
| localsHandler.enterLoopBody(node); |
| visit(node.body); |
| |
| // If there are no continues we could avoid the creation of the condition |
| // block. This could also lead to a block having multiple entries and exits. |
| HBasicBlock bodyExitBlock; |
| bool isAbortingBody = false; |
| if (current != null) { |
| bodyExitBlock = close(new HGoto()); |
| } else { |
| isAbortingBody = true; |
| bodyExitBlock = lastOpenedBlock; |
| } |
| |
| SubExpression conditionExpression; |
| bool loopIsDegenerate = isAbortingBody && !hasContinues; |
| if (!loopIsDegenerate) { |
| HBasicBlock conditionBlock = addNewBlock(); |
| |
| List<LocalsHandler> continueHandlers = <LocalsHandler>[]; |
| jumpHandler |
| .forEachContinue((HContinue instruction, LocalsHandler locals) { |
| instruction.block.addSuccessor(conditionBlock); |
| continueHandlers.add(locals); |
| }); |
| |
| if (!isAbortingBody) { |
| bodyExitBlock.addSuccessor(conditionBlock); |
| } |
| |
| if (!continueHandlers.isEmpty) { |
| if (!isAbortingBody) continueHandlers.add(localsHandler); |
| localsHandler = |
| savedLocals.mergeMultiple(continueHandlers, conditionBlock); |
| SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock); |
| List<LabelDefinition> labels = jumpHandler.labels; |
| HSubGraphBlockInformation bodyInfo = |
| new HSubGraphBlockInformation(bodyGraph); |
| HLabeledBlockInformation info; |
| if (!labels.isEmpty) { |
| info = |
| new HLabeledBlockInformation(bodyInfo, labels, isContinue: true); |
| } else { |
| info = new HLabeledBlockInformation.implicit(bodyInfo, target, |
| isContinue: true); |
| } |
| bodyEntryBlock.setBlockFlow(info, conditionBlock); |
| } |
| open(conditionBlock); |
| |
| visit(node.condition); |
| assert(!isAborted()); |
| HInstruction conditionInstruction = popBoolified(); |
| HBasicBlock conditionEndBlock = close( |
| new HLoopBranch(conditionInstruction, HLoopBranch.DO_WHILE_LOOP)); |
| |
| HBasicBlock avoidCriticalEdge = addNewBlock(); |
| conditionEndBlock.addSuccessor(avoidCriticalEdge); |
| open(avoidCriticalEdge); |
| close(new HGoto()); |
| avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge. |
| |
| conditionExpression = |
| new SubExpression(conditionBlock, conditionEndBlock); |
| |
| // Avoid a critical edge from the condition to the loop-exit body. |
| HBasicBlock conditionExitBlock = addNewBlock(); |
| open(conditionExitBlock); |
| close(new HGoto()); |
| conditionEndBlock.addSuccessor(conditionExitBlock); |
| |
| loopHandler.endLoop( |
| loopEntryBlock, conditionExitBlock, jumpHandler, localsHandler); |
| |
| loopEntryBlock.postProcessLoopHeader(); |
| SubGraph bodyGraph = new SubGraph(loopEntryBlock, bodyExitBlock); |
| HLoopBlockInformation loopBlockInfo = new HLoopBlockInformation( |
| HLoopBlockInformation.DO_WHILE_LOOP, |
| null, |
| wrapExpressionGraph(conditionExpression), |
| wrapStatementGraph(bodyGraph), |
| null, |
| loopEntryBlock.loopInformation.target, |
| loopEntryBlock.loopInformation.labels, |
| sourceInformationBuilder.buildLoop(node)); |
| loopEntryBlock.setBlockFlow(loopBlockInfo, current); |
| loopInfo.loopBlockInformation = loopBlockInfo; |
| } else { |
| // Since the loop has no back edge, we remove the loop information on the |
| // header. |
| loopEntryBlock.loopInformation = null; |
| |
| if (jumpHandler.hasAnyBreak()) { |
| // Null branchBlock because the body of the do-while loop always aborts, |
| // so we never get to the condition. |
| loopHandler.endLoop(loopEntryBlock, null, jumpHandler, localsHandler); |
| |
| // Since the body of the loop has a break, we attach a synthesized label |
| // to the body. |
| SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock); |
| JumpTarget target = elements.getTargetDefinition(node); |
| LabelDefinition label = target.addLabel(null, 'loop'); |
| label.setBreakTarget(); |
| HLabeledBlockInformation info = new HLabeledBlockInformation( |
| new HSubGraphBlockInformation(bodyGraph), <LabelDefinition>[label]); |
| loopEntryBlock.setBlockFlow(info, current); |
| jumpHandler.forEachBreak((HBreak breakInstruction, _) { |
| HBasicBlock block = breakInstruction.block; |
| block.addAtExit(new HBreak.toLabel(label)); |
| block.remove(breakInstruction); |
| }); |
| } |
| } |
| jumpHandler.close(); |
| loopDepth--; |
| } |
| |
| visitFunctionExpression(ast.FunctionExpression node) { |
| LocalFunctionElement methodElement = elements[node]; |
| ClosureClassMap nestedClosureData = compiler.closureToClassMapper |
| .getClosureToClassMapping(methodElement.resolvedAst); |
| assert(nestedClosureData != null); |
| assert(nestedClosureData.closureClassElement != null); |
| ClosureClassElement closureClassElement = |
| nestedClosureData.closureClassElement; |
| FunctionElement callElement = nestedClosureData.callElement; |
| // TODO(ahe): This should be registered in codegen, not here. |
| // TODO(johnniwinther): Is [registerStaticUse] equivalent to |
| // [addToWorkList]? |
| registry?.registerStaticUse(new StaticUse.foreignUse(callElement)); |
| |
| List<HInstruction> capturedVariables = <HInstruction>[]; |
| closureClassElement.closureFields.forEach((ClosureFieldElement field) { |
| Local capturedLocal = |
| nestedClosureData.getLocalVariableForClosureField(field); |
| assert(capturedLocal != null); |
| capturedVariables.add(localsHandler.readLocal(capturedLocal)); |
| }); |
| |
| TypeMask type = new TypeMask.nonNullExact(closureClassElement, closedWorld); |
| push(new HCreate(closureClassElement, capturedVariables, type) |
| ..sourceInformation = sourceInformationBuilder.buildCreate(node)); |
| |
| registry?.registerInstantiatedClosure(methodElement); |
| } |
| |
| visitFunctionDeclaration(ast.FunctionDeclaration node) { |
| assert(isReachable); |
| visit(node.function); |
| LocalFunctionElement localFunction = |
| elements.getFunctionDefinition(node.function); |
| localsHandler.updateLocal(localFunction, pop()); |
| } |
| |
| @override |
| void visitThisGet(ast.Identifier node, [_]) { |
| stack.add(localsHandler.readThis()); |
| } |
| |
| visitIdentifier(ast.Identifier node) { |
| if (node.isThis()) { |
| visitThisGet(node); |
| } else { |
| reporter.internalError( |
| node, "SsaFromAstMixin.visitIdentifier on non-this."); |
| } |
| } |
| |
| visitIf(ast.If node) { |
| assert(isReachable); |
| handleIf( |
| node: node, |
| visitCondition: () => visit(node.condition), |
| visitThen: () => visit(node.thenPart), |
| visitElse: node.elsePart != null ? () => visit(node.elsePart) : null, |
| sourceInformation: sourceInformationBuilder.buildIf(node)); |
| } |
| |
| @override |
| void visitIfNull(ast.Send node, ast.Node left, ast.Node right, _) { |
| SsaBranchBuilder brancher = new SsaBranchBuilder(this, compiler, node); |
| brancher.handleIfNull(() => visit(left), () => visit(right)); |
| } |
| |
| /// Optimizes logical binary where the left is also a logical binary. |
| /// |
| /// This method transforms the operator by optimizing the case where [left] is |
| /// a logical "and" or logical "or". Then it uses [branchBuilder] to build the |
| /// graph for the optimized expression. |
| /// |
| /// For example, `(x && y) && z` is transformed into `x && (y && z)`: |
| /// |
| /// t0 = boolify(x); |
| /// if (t0) { |
| /// t1 = boolify(y); |
| /// if (t1) { |
| /// t2 = boolify(z); |
| /// } |
| /// t3 = phi(t2, false); |
| /// } |
| /// result = phi(t3, false); |
| void handleLogicalBinaryWithLeftNode( |
| ast.Node left, void visitRight(), SsaBranchBuilder branchBuilder, |
| {bool isAnd}) { |
| ast.Send send = left.asSend(); |
| if (send != null && (isAnd ? send.isLogicalAnd : send.isLogicalOr)) { |
| ast.Node newLeft = send.receiver; |
| Link<ast.Node> link = send.argumentsNode.nodes; |
| assert(link.tail.isEmpty); |
| ast.Node middle = link.head; |
| handleLogicalBinaryWithLeftNode( |
| newLeft, |
| () => handleLogicalBinaryWithLeftNode( |
| middle, visitRight, branchBuilder, |
| isAnd: isAnd), |
| branchBuilder, |
| isAnd: isAnd); |
| } else { |
| branchBuilder.handleLogicalBinary(() => visit(left), visitRight, |
| isAnd: isAnd); |
| } |
| } |
| |
| @override |
| void visitLogicalAnd(ast.Send node, ast.Node left, ast.Node right, _) { |
| SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, compiler, node); |
| handleLogicalBinaryWithLeftNode(left, () => visit(right), branchBuilder, |
| isAnd: true); |
| } |
| |
| @override |
| void visitLogicalOr(ast.Send node, ast.Node left, ast.Node right, _) { |
| SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, compiler, node); |
| handleLogicalBinaryWithLeftNode(left, () => visit(right), branchBuilder, |
| isAnd: false); |
| } |
| |
| @override |
| void visitNot(ast.Send node, ast.Node expression, _) { |
| assert(node.argumentsNode is ast.Prefix); |
| visit(expression); |
| SourceInformation sourceInformation = |
| sourceInformationBuilder.buildGeneric(node); |
| push(new HNot(popBoolified(), commonMasks.boolType) |
| ..sourceInformation = sourceInformation); |
| } |
| |
| @override |
| void visitUnary( |
| ast.Send node, UnaryOperator operator, ast.Node expression, _) { |
| assert(node.argumentsNode is ast.Prefix); |
| HInstruction operand = visitAndPop(expression); |
| |
| // See if we can constant-fold right away. This avoids rewrites later on. |
| if (operand is HConstant) { |
| UnaryOperation operation = constantSystem.lookupUnary(operator); |
| HConstant constant = operand; |
| ConstantValue folded = operation.fold(constant.constant); |
| if (folded != null) { |
| stack.add(graph.addConstant(folded, closedWorld)); |
| return; |
| } |
| } |
| |
| pushInvokeDynamic(node, elements.getSelector(node), |
| elementInferenceResults.typeOfSend(node), [operand], |
| sourceInformation: sourceInformationBuilder.buildGeneric(node)); |
| } |
| |
| @override |
| void visitBinary(ast.Send node, ast.Node left, BinaryOperator operator, |
| ast.Node right, _) { |
| handleBinary(node, left, right); |
| } |
| |
| @override |
| void visitIndex(ast.Send node, ast.Node receiver, ast.Node index, _) { |
| generateDynamicSend(node); |
| } |
| |
| @override |
| void visitEquals(ast.Send node, ast.Node left, ast.Node right, _) { |
| handleBinary(node, left, right); |
| } |
| |
| @override |
| void visitNotEquals(ast.Send node, ast.Node left, ast.Node right, _) { |
| handleBinary(node, left, right); |
| pushWithPosition( |
| new HNot(popBoolified(), commonMasks.boolType), node.selector); |
| } |
| |
| void handleBinary(ast.Send node, ast.Node left, ast.Node right) { |
| visitBinarySend( |
| visitAndPop(left), |
| visitAndPop(right), |
| elements.getSelector(node), |
| elementInferenceResults.typeOfSend(node), |
| node, |
| sourceInformation: |
| sourceInformationBuilder.buildGeneric(node.selector)); |
| } |
| |
| /// TODO(johnniwinther): Merge [visitBinarySend] with [handleBinary] and |
| /// remove use of [location] for source information. |
| void visitBinarySend(HInstruction left, HInstruction right, Selector selector, |
| TypeMask mask, ast.Send send, |
| {SourceInformation sourceInformation}) { |
| pushInvokeDynamic(send, selector, mask, [left, right], |
| sourceInformation: sourceInformation); |
| } |
| |
| HInstruction generateInstanceSendReceiver(ast.Send send) { |
| assert(Elements.isInstanceSend(send, elements)); |
| if (send.receiver == null) { |
| return localsHandler.readThis(); |
| } |
| visit(send.receiver); |
| return pop(); |
| } |
| |
| String noSuchMethodTargetSymbolString(Element error, [String prefix]) { |
| String result = error.name; |
| if (prefix == "set") return "$result="; |
| return result; |
| } |
| |
| /** |
| * Returns a set of interceptor classes that contain the given |
| * [selector]. |
| */ |
| void generateInstanceGetterWithCompiledReceiver( |
| ast.Send send, Selector selector, TypeMask mask, HInstruction receiver) { |
| assert(Elements.isInstanceSend(send, elements)); |
| assert(selector.isGetter); |
| pushInvokeDynamic(send, selector, mask, [receiver], |
| sourceInformation: sourceInformationBuilder.buildGet(send)); |
| } |
| |
| /// Inserts a call to checkDeferredIsLoaded for [prefixElement]. |
| /// If [prefixElement] is [null] ndo nothing. |
| void generateIsDeferredLoadedCheckIfNeeded( |
| PrefixElement prefixElement, ast.Node location) { |
| if (prefixElement == null) return; |
| String loadId = |
| compiler.deferredLoadTask.getImportDeferName(location, prefixElement); |
| HInstruction loadIdConstant = addConstantString(loadId); |
| String uri = prefixElement.deferredImport.uri.toString(); |
| HInstruction uriConstant = addConstantString(uri); |
| Element helper = helpers.checkDeferredIsLoaded; |
| pushInvokeStatic(location, helper, [loadIdConstant, uriConstant]); |
| pop(); |
| } |
| |
| /// Inserts a call to checkDeferredIsLoaded if the send has a prefix that |
| /// resolves to a deferred library. |
| void generateIsDeferredLoadedCheckOfSend(ast.Send node) { |
| generateIsDeferredLoadedCheckIfNeeded( |
| compiler.deferredLoadTask.deferredPrefixElement(node, elements), node); |
| } |
| |
| void handleInvalidStaticGet(ast.Send node, Element element) { |
| SourceInformation sourceInformation = |
| sourceInformationBuilder.buildGet(node); |
| generateThrowNoSuchMethod( |
| node, noSuchMethodTargetSymbolString(element, 'get'), |
| argumentNodes: const Link<ast.Node>(), |
| sourceInformation: sourceInformation); |
| } |
| |
| /// Generate read access of an unresolved static or top level entity. |
| void generateStaticUnresolvedGet(ast.Send node, Element element) { |
| if (element is ErroneousElement) { |
| // An erroneous element indicates an unresolved static getter. |
| handleInvalidStaticGet(node, element); |
| } else { |
| // This happens when [element] has parse errors. |
| assert(invariant(node, element == null || element.isMalformed)); |
| // TODO(ahe): Do something like the above, that is, emit a runtime |
| // error. |
| stack.add(graph.addConstantNull(closedWorld)); |
| } |
| } |
| |
| /// Read a static or top level [field] of constant value. |
| void generateStaticConstGet(ast.Send node, FieldElement field, |
| ConstantExpression constant, SourceInformation sourceInformation) { |
| ConstantValue value = backend.constants.getConstantValue(constant); |
| HConstant instruction; |
| // Constants that are referred via a deferred prefix should be referred |
| // by reference. |
| PrefixElement prefix = |
| compiler.deferredLoadTask.deferredPrefixElement(node, elements); |
| if (prefix != null) { |
| instruction = graph.addDeferredConstant( |
| value, prefix, sourceInformation, compiler, closedWorld); |
| } else { |
| instruction = graph.addConstant(value, closedWorld, |
| sourceInformation: sourceInformation); |
| } |
| stack.add(instruction); |
| // The inferrer may have found a better type than the constant |
| // handler in the case of lists, because the constant handler |
| // does not look at elements in the list. |
| TypeMask type = |
| TypeMaskFactory.inferredTypeForElement(field, globalInferenceResults); |
| if (!type.containsAll(closedWorld) && !instruction.isConstantNull()) { |
| // TODO(13429): The inferrer should know that an element |
| // cannot be null. |
| instruction.instructionType = type.nonNullable(); |
| } |
| } |
| |
| @override |
| void previsitDeferredAccess(ast.Send node, PrefixElement prefix, _) { |
| generateIsDeferredLoadedCheckIfNeeded(prefix, node); |
| } |
| |
| /// Read a static or top level [field]. |
| void generateStaticFieldGet(ast.Send node, FieldElement field) { |
| ConstantExpression constant = field.constant; |
| SourceInformation sourceInformation = |
| sourceInformationBuilder.buildGet(node); |
| if (constant != null) { |
| if (!field.isAssignable) { |
| // A static final or const. Get its constant value and inline it if |
| // the value can be compiled eagerly. |
| generateStaticConstGet(node, field, constant, sourceInformation); |
| } else { |
| // TODO(5346): Try to avoid the need for calling [declaration] before |
| // creating an [HStatic]. |
| HInstruction instruction = new HStatic( |
| field, |
| TypeMaskFactory.inferredTypeForElement( |
| field, globalInferenceResults)) |
| ..sourceInformation = sourceInformation; |
| push(instruction); |
| } |
| } else { |
| HInstruction instruction = new HLazyStatic(field, |
| TypeMaskFactory.inferredTypeForElement(field, globalInferenceResults)) |
| ..sourceInformation = sourceInformation; |
| push(instruction); |
| } |
| } |
| |
| /// Generate a getter invocation of the static or top level [getter]. |
| void generateStaticGetterGet(ast.Send node, MethodElement getter) { |
| SourceInformation sourceInformation = |
| sourceInformationBuilder.buildGet(node); |
| if (getter.isDeferredLoaderGetter) { |
| generateDeferredLoaderGet(node, getter, sourceInformation); |
| } else { |
| pushInvokeStatic(node, getter, <HInstruction>[], |
| sourceInformation: sourceInformation); |
| } |
| } |
| |
| /// Generate a dynamic getter invocation. |
| void generateDynamicGet(ast.Send node) { |
| HInstruction receiver = generateInstanceSendReceiver(node); |
| generateInstanceGetterWithCompiledReceiver(node, elements.getSelector(node), |
| elementInferenceResults.typeOfSend(node), receiver); |
| } |
| |
| /// Generate a closurization of the static or top level [method]. |
| void generateStaticFunctionGet(ast.Send node, MethodElement method) { |
| assert(method.isDeclaration); |
| // TODO(5346): Try to avoid the need for calling [declaration] before |
| // creating an [HStatic]. |
| SourceInformation sourceInformation = |
| sourceInformationBuilder.buildGet(node); |
| push(new HStatic(method, commonMasks.nonNullType) |
| ..sourceInformation = sourceInformation); |
| } |
| |
| /// Read a local variable, function or parameter. |
| void buildLocalGet(LocalElement local, SourceInformation sourceInformation) { |
| stack.add( |
| localsHandler.readLocal(local, sourceInformation: sourceInformation)); |
| } |
| |
| void handleLocalGet(ast.Send node, LocalElement local) { |
| buildLocalGet(local, sourceInformationBuilder.buildGet(node)); |
| } |
| |
| @override |
| void visitDynamicPropertyGet(ast.Send node, ast.Node receiver, Name name, _) { |
| generateDynamicGet(node); |
| } |
| |
| @override |
| void visitIfNotNullDynamicPropertyGet( |
| ast.Send node, ast.Node receiver, Name name, _) { |
| // exp?.x compiled as: |
| // t1 = exp; |
| // result = t1 == null ? t1 : t1.x; |
| // This is equivalent to t1 == null ? null : t1.x, but in the current form |
| // we will be able to later compress it as: |
| // t1 || t1.x |
| HInstruction expression; |
| SsaBranchBuilder brancher = new SsaBranchBuilder(this, compiler, node); |
| brancher.handleConditional( |
| () { |
| expression = visitAndPop(receiver); |
| pushCheckNull(expression); |
| }, |
| () => stack.add(expression), |
| () { |
| generateInstanceGetterWithCompiledReceiver( |
| node, |
| elements.getSelector(node), |
| elementInferenceResults.typeOfSend(node), |
| expression); |
| }); |
| } |
| |
| @override |
| void visitLocalVariableGet(ast.Send node, LocalVariableElement variable, _) { |
| handleLocalGet(node, variable); |
| } |
| |
| @override |
| void visitParameterGet(ast.Send node, ParameterElement parameter, _) { |
| handleLocalGet(node, parameter); |
| } |
| |
| @override |
| void visitLocalFunctionGet(ast.Send node, LocalFunctionElement function, _) { |
| handleLocalGet(node, function); |
| } |
| |
| @override |
| void visitStaticFieldGet(ast.Send node, FieldElement field, _) { |
| generateStaticFieldGet(node, field); |
| } |
| |
| @override |
| void visitStaticFunctionGet(ast.Send node, MethodElement function, _) { |
| generateStaticFunctionGet(node, function); |
| } |
| |
| @override |
| void visitStaticGetterGet(ast.Send node, FunctionElement getter, _) { |
| generateStaticGetterGet(node, getter); |
| } |
| |
| @override |
| void visitThisPropertyGet(ast.Send node, Name name, _) { |
| generateDynamicGet(node); |
| } |
| |
| @override |
| void visitTopLevelFieldGet(ast.Send node, FieldElement field, _) { |
| generateStaticFieldGet(node, field); |
| } |
| |
| @override |
| void visitTopLevelFunctionGet(ast.Send node, MethodElement function, _) { |
| generateStaticFunctionGet(node, function); |
| } |
| |
| @override |
| void visitTopLevelGetterGet(ast.Send node, FunctionElement getter, _) { |
| generateStaticGetterGet(node, getter); |
| } |
| |
| void generateInstanceSetterWithCompiledReceiver( |
| ast.Send send, HInstruction receiver, HInstruction value, |
| {Selector selector, TypeMask mask, ast.Node location}) { |
| assert(invariant(send == null ? location : send, |
| send == null || Elements.isInstanceSend(send, elements), |
| message: "Unexpected instance setter" |
| "${send != null ? " element: ${elements[send]}" : ""}")); |
| if (selector == null) { |
| assert(send != null); |
| selector = elements.getSelector(send); |
| mask ??= elementInferenceResults.typeOfSend(send); |
| } |
| if (location == null) { |
| assert(send != null); |
| location = send; |
| } |
| assert(selector.isSetter); |
| pushInvokeDynamic(location, selector, mask, [receiver, value], |
| sourceInformation: sourceInformationBuilder.buildAssignment(location)); |
| pop(); |
| stack.add(value); |
| } |
| |
| void generateNoSuchSetter( |
| ast.Node location, Element element, HInstruction value) { |
| List<HInstruction> arguments = |
| value == null ? const <HInstruction>[] : <HInstruction>[value]; |
| // An erroneous element indicates an unresolved static setter. |
| generateThrowNoSuchMethod( |
| location, noSuchMethodTargetSymbolString(element, 'set'), |
| argumentValues: arguments); |
| } |
| |
| void generateNonInstanceSetter( |
| ast.SendSet send, Element element, HInstruction value, |
| {ast.Node location}) { |
| if (location == null) { |
| assert(send != null); |
| location = send; |
| } |
| assert(invariant( |
| location, send == null || !Elements.isInstanceSend(send, elements), |
| message: "Unexpected non instance setter: $element.")); |
| if (Elements.isStaticOrTopLevelField(element)) { |
| if (element.isSetter) { |
| pushInvokeStatic(location, element, <HInstruction>[value]); |
| pop(); |
| } else { |
| FieldElement field = element; |
| value = typeBuilder.potentiallyCheckOrTrustType(value, field.type); |
| addWithPosition(new HStaticStore(field, value), location); |
| } |
| stack.add(value); |
| } else if (Elements.isError(element)) { |
| generateNoSuchSetter(location, element, send == null ? null : value); |
| } else if (Elements.isMalformed(element)) { |
| // TODO(ahe): Do something like [generateWrongArgumentCountError]. |
| stack.add(graph.addConstantNull(closedWorld)); |
| } else { |
| stack.add(value); |
| LocalElement local = element; |
| // If the value does not already have a name, give it here. |
| if (value.sourceElement == null) { |
| value.sourceElement = local; |
| } |
| HInstruction checkedOrTrusted = |
| typeBuilder.potentiallyCheckOrTrustType(value, local.type); |
| if (!identical(checkedOrTrusted, value)) { |
| pop(); |
| stack.add(checkedOrTrusted); |
| } |
| |
| localsHandler.updateLocal(local, checkedOrTrusted, |
| sourceInformation: |
| sourceInformationBuilder.buildAssignment(location)); |
| } |
| } |
| |
| HInstruction invokeInterceptor(HInstruction receiver) { |
| HInterceptor interceptor = |
| new HInterceptor(receiver, commonMasks.nonNullType); |
| add(interceptor); |
| return interceptor; |
| } |
| |
| HLiteralList buildLiteralList(List<HInstruction> inputs) { |
| return new HLiteralList(inputs, commonMasks.extendableArrayType); |
| } |
| |
| @override |
| void visitAs(ast.Send node, ast.Node expression, DartType type, _) { |
| HInstruction expressionInstruction = visitAndPop(expression); |
| if (type.isMalformed) { |
| if (type is MalformedType) { |
| ErroneousElement element = type.element; |
| generateTypeError(node, element.message); |
| } else { |
| assert(type is MethodTypeVariableType); |
| stack.add(expressionInstruction); |
| } |
| } else { |
| HInstruction converted = typeBuilder.buildTypeConversion( |
| expressionInstruction, |
| localsHandler.substInContext(type), |
| HTypeConversion.CAST_TYPE_CHECK); |
| if (converted != expressionInstruction) add(converted); |
| stack.add(converted); |
| } |
| } |
| |
| @override |
| void visitIs(ast.Send node, ast.Node expression, DartType type, _) { |
| HInstruction expressionInstruction = visitAndPop(expression); |
| push(buildIsNode(node, type, expressionInstruction)); |
| } |
| |
| @override |
| void visitIsNot(ast.Send node, ast.Node expression, DartType type, _) { |
| HInstruction expressionInstruction = visitAndPop(expression); |
| HInstruction instruction = buildIsNode(node, type, expressionInstruction); |
| add(instruction); |
| push(new HNot(instruction, commonMasks.boolType)); |
| } |
| |
| HInstruction buildIsNode( |
| ast.Node node, DartType type, HInstruction expression) { |
| type = localsHandler.substInContext(type).unaliased; |
| if (type.isMalformed) { |
| String message; |
| if (type is MethodTypeVariableType) { |
| message = "Method type variables are not reified, " |
| "so they cannot be tested with an `is` expression."; |
| } else { |
| assert(type is MalformedType); |
| ErroneousElement element = type.element; |
| message = element.message; |
| } |
| generateTypeError(node, message); |
| HInstruction call = pop(); |
| return new HIs.compound(type, expression, call, commonMasks.boolType); |
| } else if (type.isFunctionType) { |
| List arguments = [buildFunctionType(type), expression]; |
| pushInvokeDynamic( |
| node, |
| new Selector.call(new PrivateName('_isTest', helpers.jsHelperLibrary), |
| CallStructure.ONE_ARG), |
| null, |
| arguments); |
| return new HIs.compound(type, expression, pop(), commonMasks.boolType); |
| } else if (type.isTypeVariable) { |
| HInstruction runtimeType = |
| typeBuilder.addTypeVariableReference(type, sourceElement); |
| Element helper = helpers.checkSubtypeOfRuntimeType; |
| List<HInstruction> inputs = <HInstruction>[expression, runtimeType]; |
| pushInvokeStatic(null, helper, inputs, typeMask: commonMasks.boolType); |
| HInstruction call = pop(); |
| return new HIs.variable(type, expression, call, commonMasks.boolType); |
| } else if (RuntimeTypes.hasTypeArguments(type)) { |
| ClassElement element = type.element; |
| Element helper = helpers.checkSubtype; |
| HInstruction representations = |
| typeBuilder.buildTypeArgumentRepresentations(type, sourceElement); |
| add(representations); |
| js.Name operator = backend.namer.operatorIs(element); |
| HInstruction isFieldName = addConstantStringFromName(operator); |
| HInstruction asFieldName = closedWorld.hasAnyStrictSubtype(element) |
| ? addConstantStringFromName(backend.namer.substitutionName(element)) |
| : graph.addConstantNull(closedWorld); |
| List<HInstruction> inputs = <HInstruction>[ |
| expression, |
| isFieldName, |
| representations, |
| asFieldName |
| ]; |
| pushInvokeStatic(node, helper, inputs, typeMask: commonMasks.boolType); |
| HInstruction call = pop(); |
| return new HIs.compound(type, expression, call, commonMasks.boolType); |
| } else { |
| if (backend.hasDirectCheckFor(type)) { |
| return new HIs.direct(type, expression, commonMasks.boolType); |
| } |
| // The interceptor is not always needed. It is removed by optimization |
| // when the receiver type or tested type permit. |
| return new HIs.raw(type, expression, invokeInterceptor(expression), |
| commonMasks.boolType); |
| } |
| } |
| |
| void addDynamicSendArgumentsToList(ast.Send node, List<HInstruction> list) { |
| CallStructure callStructure = elements.getSelector(node).callStructure; |
| if (callStructure.namedArgumentCount == 0) { |
| addGenericSendArgumentsToList(node.arguments, list); |
| } else { |
| // Visit positional arguments and add them to the list. |
| Link<ast.Node> arguments = node.arguments; |
| int positionalArgumentCount = callStructure.positionalArgumentCount; |
| for (int i = 0; |
| i < positionalArgumentCount; |
| arguments = arguments.tail, i++) { |
| visit(arguments.head); |
| list.add(pop()); |
| } |
| |
| // Visit named arguments and add them into a temporary map. |
| Map<String, HInstruction> instructions = new Map<String, HInstruction>(); |
| List<String> namedArguments = callStructure.namedArguments; |
| int nameIndex = 0; |
| for (; !arguments.isEmpty; arguments = arguments.tail) { |
| visit(arguments.head); |
| instructions[namedArguments[nameIndex++]] = pop(); |
| } |
| |
| // Iterate through the named arguments to add them to the list |
| // of instructions, in an order that can be shared with |
| // selectors with the same named arguments. |
| List<String> orderedNames = callStructure.getOrderedNamedArguments(); |
| for (String name in orderedNames) { |
| list.add(instructions[name]); |
| } |
| } |
| } |
| |
| /** |
| * Returns a list with the evaluated [arguments] in the normalized order. |
| * |
| * Precondition: `this.applies(element, world)`. |
| * Invariant: [element] must be an implementation element. |
| */ |
| List<HInstruction> makeStaticArgumentList(CallStructure callStructure, |
| Link<ast.Node> arguments, FunctionElement element) { |
| assert(invariant(element, element.isImplementation)); |
| |
| HInstruction compileArgument(ast.Node argument) { |
| visit(argument); |
| return pop(); |
| } |
| |
| return callStructure.makeArgumentsList( |
| arguments, |
| element, |
| compileArgument, |
| backend.isJsInterop(element) |
| ? handleConstantForOptionalParameterJsInterop |
| : handleConstantForOptionalParameter); |
| } |
| |
| void addGenericSendArgumentsToList( |
| Link<ast.Node> link, List<HInstruction> list) { |
| for (; !link.isEmpty; link = link.tail) { |
| visit(link.head); |
| list.add(pop()); |
| } |
| } |
| |
| /// Generate a dynamic method, getter or setter invocation. |
| void generateDynamicSend(ast.Send node) { |
| HInstruction receiver = generateInstanceSendReceiver(node); |
| _generateDynamicSend(node, receiver); |
| } |
| |
| void _generateDynamicSend(ast.Send node, HInstruction receiver) { |
| Selector selector = elements.getSelector(node); |
| TypeMask mask = elementInferenceResults.typeOfSend(node); |
| SourceInformation sourceInformation = |
| sourceInformationBuilder.buildCall(node, node.selector); |
| |
| List<HInstruction> inputs = <HInstruction>[]; |
| inputs.add(receiver); |
| addDynamicSendArgumentsToList(node, inputs); |
| |
| pushInvokeDynamic(node, selector, mask, inputs, |
| sourceInformation: sourceInformation); |
| if (selector.isSetter || selector.isIndexSet) { |
| pop(); |
| stack.add(inputs.last); |
| } |
| } |
| |
| @override |
| visitDynamicPropertyInvoke(ast.Send node, ast.Node receiver, |
| ast.NodeList arguments, Selector selector, _) { |
| generateDynamicSend(node); |
| } |
| |
| @override |
| visitIfNotNullDynamicPropertyInvoke(ast.Send node, ast.Node receiver, |
| ast.NodeList arguments, Selector selector, _) { |
| /// Desugar `exp?.m()` to `(t1 = exp) == null ? t1 : t1.m()` |
| HInstruction receiver; |
| SsaBranchBuilder brancher = new SsaBranchBuilder(this, compiler, node); |
| brancher.handleConditional(() { |
| receiver = generateInstanceSendReceiver(node); |
| pushCheckNull(receiver); |
| }, () => stack.add(receiver), () => _generateDynamicSend(node, receiver)); |
| } |
| |
| @override |
| visitThisPropertyInvoke( |
| ast.Send node, ast.NodeList arguments, Selector selector, _) { |
| generateDynamicSend(node); |
| } |
| |
| @override |
| visitExpressionInvoke(ast.Send node, ast.Node expression, |
| ast.NodeList arguments, CallStructure callStructure, _) { |
| generateCallInvoke(node, visitAndPop(expression), |
| sourceInformationBuilder.buildCall(node, node.argumentsNode)); |
| } |
| |
| @override |
| visitThisInvoke( |
| ast.Send node, ast.NodeList arguments, CallStructure callStructure, _) { |
| generateCallInvoke(node, localsHandler.readThis(), |
| sourceInformationBuilder.buildCall(node, node.argumentsNode)); |
| } |
| |
| @override |
| visitParameterInvoke(ast.Send node, ParameterElement parameter, |
| ast.NodeList arguments, CallStructure callStructure, _) { |
| generateCallInvoke(node, localsHandler.readLocal(parameter), |
| sourceInformationBuilder.buildCall(node, node.argumentsNode)); |
| } |
| |
| @override |
| visitLocalVariableInvoke(ast.Send node, LocalVariableElement variable, |
| ast.NodeList arguments, CallStructure callStructure, _) { |
| generateCallInvoke(node, localsHandler.readLocal(variable), |
| sourceInformationBuilder.buildCall(node, node.argumentsNode)); |
| } |
| |
| @override |
| visitLocalFunctionInvoke(ast.Send node, LocalFunctionElement function, |
| ast.NodeList arguments, CallStructure callStructure, _) { |
| generateCallInvoke(node, localsHandler.readLocal(function), |
| sourceInformationBuilder.buildCall(node, node.argumentsNode)); |
| } |
| |
| @override |
| visitLocalFunctionIncompatibleInvoke( |
| ast.Send node, |
| LocalFunctionElement function, |
| ast.NodeList arguments, |
| CallStructure callStructure, |
| _) { |
| generateCallInvoke(node, localsHandler.readLocal(function), |
| sourceInformationBuilder.buildCall(node, node.argumentsNode)); |
| } |
| |
| void handleForeignJs(ast.Send node) { |
| Link<ast.Node> link = node.arguments; |
| // Don't visit the first argument, which is the type, and the second |
| // argument, which is the foreign code. |
| if (link.isEmpty || link.tail.isEmpty) { |
| // We should not get here because the call should be compiled to NSM. |
| reporter.internalError( |
| node.argumentsNode, 'At least two arguments expected.'); |
| } |
| native.NativeBehavior nativeBehavior = elements.getNativeData(node); |
| assert(invariant(node, nativeBehavior != null, |
| message: "No NativeBehavior for $node")); |
| |
| List<HInstruction> inputs = <HInstruction>[]; |
| addGenericSendArgumentsToList(link.tail.tail, inputs); |
| |
| if (nativeBehavior.codeTemplate.positionalArgumentCount != inputs.length) { |
| reporter.reportErrorMessage(node, MessageKind.GENERIC, { |
| 'text': 'Mismatch between number of placeholders' |
| ' and number of arguments.' |
| }); |
| // Result expected on stack. |
| stack.add(graph.addConstantNull(closedWorld)); |
| return; |
| } |
| |
| if (native.HasCapturedPlaceholders.check(nativeBehavior.codeTemplate.ast)) { |
| reporter.reportErrorMessage(node, MessageKind.JS_PLACEHOLDER_CAPTURE); |
| } |
| |
| TypeMask ssaType = |
| TypeMaskFactory.fromNativeBehavior(nativeBehavior, closedWorld); |
| |
| SourceInformation sourceInformation = |
| sourceInformationBuilder.buildCall(node, node.argumentsNode); |
| if (nativeBehavior.codeTemplate.isExpression) { |
| push(new HForeignCode(nativeBehavior.codeTemplate, ssaType, inputs, |
| effects: nativeBehavior.sideEffects, nativeBehavior: nativeBehavior) |
| ..sourceInformation = sourceInformation); |
| } else { |
| push(new HForeignCode(nativeBehavior.codeTemplate, ssaType, inputs, |
| isStatement: true, |
| effects: nativeBehavior.sideEffects, |
| nativeBehavior: nativeBehavior) |
| ..sourceInformation = sourceInformation); |
| } |
| } |
| |
| void handleJsStringConcat(ast.Send node) { |
| List<HInstruction> inputs = <HInstruction>[]; |
| addGenericSendArgumentsToList(node.arguments, inputs); |
| if (inputs.length != 2) { |
| reporter.internalError(node.argumentsNode, 'Two arguments expected.'); |
| } |
| push(new HStringConcat(inputs[0], inputs[1], commonMasks.stringType)); |
| } |
| |
| void handleForeignJsCurrentIsolateContext(ast.Send node) { |
| if (!node.arguments.isEmpty) { |
| reporter.internalError( |
| node, 'Too many arguments to JS_CURRENT_ISOLATE_CONTEXT.'); |
| } |
| |
| if (!backend.hasIsolateSupport) { |
| // If the isolate library is not used, we just generate code |
| // to fetch the static state. |
| String name = backend.namer.staticStateHolder; |
| push(new HForeignCode( |
| js.js.parseForeignJS(name), commonMasks.dynamicType, <HInstruction>[], |
| nativeBehavior: native.NativeBehavior.DEPENDS_OTHER)); |
| } else { |
| // Call a helper method from the isolate library. The isolate |
| // library uses its own isolate structure, that encapsulates |
| // Leg's isolate. |
| Element element = helpers.currentIsolate; |
| if (element == null) { |
| reporter.internalError(node, 'Isolate library and compiler mismatch.'); |
| } |
| pushInvokeStatic(null, element, [], typeMask: commonMasks.dynamicType); |
| } |
| } |
| |
| void handleForeignJsGetFlag(ast.Send node) { |
| List<ast.Node> arguments = node.arguments.toList(); |
| ast.Node argument; |
| switch (arguments.length) { |
| case 0: |
| reporter.reportErrorMessage(node, MessageKind.GENERIC, |
| {'text': 'Error: Expected one argument to JS_GET_FLAG.'}); |
| return; |
| case 1: |
| argument = arguments[0]; |
| break; |
| default: |
| for (int i = 1; i < arguments.length; i++) { |
| reporter.reportErrorMessage(arguments[i], MessageKind.GENERIC, |
| {'text': 'Error: Extra argument to JS_GET_FLAG.'}); |
| } |
| return; |
| } |
| ast.LiteralString string = argument.asLiteralString(); |
| if (string == null) { |
| reporter.reportErrorMessage(argument, MessageKind.GENERIC, |
| {'text': 'Error: Expected a literal string.'}); |
| } |
| String name = string.dartString.slowToString(); |
| bool value = false; |
| switch (name) { |
| case 'MUST_RETAIN_METADATA': |
| value = backend.mustRetainMetadata; |
| break; |
| case 'USE_CONTENT_SECURITY_POLICY': |
| value = compiler.options.useContentSecurityPolicy; |
| break; |
| default: |
| reporter.reportErrorMessage(node, MessageKind.GENERIC, |
| {'text': 'Error: Unknown internal flag "$name".'}); |
| } |
| stack.add(graph.addConstantBool(value, closedWorld)); |
| } |
| |
| void handleForeignJsGetName(ast.Send node) { |
| List<ast.Node> arguments = node.arguments.toList(); |
| ast.Node argument; |
| switch (arguments.length) { |
| case 0: |
| reporter.reportErrorMessage(node, MessageKind.GENERIC, |
| {'text': 'Error: Expected one argument to JS_GET_NAME.'}); |
| return; |
| case 1: |
| argument = arguments[0]; |
| break; |
| default: |
| for (int i = 1; i < arguments.length; i++) { |
| reporter.reportErrorMessage(arguments[i], MessageKind.GENERIC, |
| {'text': 'Error: Extra argument to JS_GET_NAME.'}); |
| } |
| return; |
| } |
| Element element = elements[argument]; |
| if (element == null || |
| element is! EnumConstantElement || |
| element.enclosingClass != helpers.jsGetNameEnum) { |
| reporter.reportErrorMessage(argument, MessageKind.GENERIC, |
| {'text': 'Error: Expected a JsGetName enum value.'}); |
| } |
| EnumConstantElement enumConstant = element; |
| int index = enumConstant.index; |
| stack.add(addConstantStringFromName( |
| backend.namer.getNameForJsGetName(argument, JsGetName.values[index]))); |
| } |
| |
| void handleForeignJsBuiltin(ast.Send node) { |
| List<ast.Node> arguments = node.arguments.toList(); |
| ast.Node argument; |
| if (arguments.length < 2) { |
| reporter.reportErrorMessage(node, MessageKind.GENERIC, |
| {'text': 'Error: Expected at least two arguments to JS_BUILTIN.'}); |
| } |
| |
| Element builtinElement = elements[arguments[1]]; |
| if (builtinElement == null || |
| (builtinElement is! EnumConstantElement) || |
| builtinElement.enclosingClass != helpers.jsBuiltinEnum) { |
| reporter.reportErrorMessage(argument, MessageKind.GENERIC, |
| {'text': 'Error: Expected a JsBuiltin enum value.'}); |
| } |
| EnumConstantElement enumConstant = builtinElement; |
| int index = enumConstant.index; |
| |
| js.Template template = |
| backend.emitter.builtinTemplateFor(JsBuiltin.values[index]); |
| |
| List<HInstruction> compiledArguments = <HInstruction>[]; |
| for (int i = 2; i < arguments.length; i++) { |
| visit(arguments[i]); |
| compiledArguments.add(pop()); |
| } |
| |
| native.NativeBehavior nativeBehavior = elements.getNativeData(node); |
| assert(invariant(node, nativeBehavior != null, |
| message: "No NativeBehavior for $node")); |
| |
| TypeMask ssaType = |
| TypeMaskFactory.fromNativeBehavior(nativeBehavior, closedWorld); |
| |
| push(new HForeignCode(template, ssaType, compiledArguments, |
| nativeBehavior: nativeBehavior)); |
| } |
| |
| void handleForeignJsEmbeddedGlobal(ast.Send node) { |
| List<ast.Node> arguments = node.arguments.toList(); |
| ast.Node globalNameNode; |
| switch (arguments.length) { |
| case 0: |
| case 1: |
| reporter.reportErrorMessage(node, MessageKind.GENERIC, |
| {'text': 'Error: Expected two arguments to JS_EMBEDDED_GLOBAL.'}); |
| return; |
| case 2: |
| // The type has been extracted earlier. We are only interested in the |
| // name in this function. |
| globalNameNode = arguments[1]; |
| break; |
| default: |
| for (int i = 2; i < arguments.length; i++) { |
| reporter.reportErrorMessage(arguments[i], MessageKind.GENERIC, |
| {'text': 'Error: Extra argument to JS_EMBEDDED_GLOBAL.'}); |
| } |
| return; |
| } |
| visit(globalNameNode); |
| HInstruction globalNameHNode = pop(); |
| if (!globalNameHNode.isConstantString()) { |
| reporter.reportErrorMessage(arguments[1], MessageKind.GENERIC, { |
| 'text': 'Error: Expected String as second argument ' |
| 'to JS_EMBEDDED_GLOBAL.' |
| }); |
| return; |
| } |
| HConstant hConstant = globalNameHNode; |
| StringConstantValue constant = hConstant.constant; |
| String globalName = constant.primitiveValue.slowToString(); |
| js.Template expr = js.js.expressionTemplateYielding( |
| backend.emitter.generateEmbeddedGlobalAccess(globalName)); |
| native.NativeBehavior nativeBehavior = elements.getNativeData(node); |
| assert(invariant(node, nativeBehavior != null, |
| message: "No NativeBehavior for $node")); |
| TypeMask ssaType = |
| TypeMaskFactory.fromNativeBehavior(nativeBehavior, closedWorld); |
| push(new HForeignCode(expr, ssaType, const [], |
| nativeBehavior: nativeBehavior)); |
| } |
| |
| void handleJsInterceptorConstant(ast.Send node) { |
| // Single argument must be a TypeConstant which is converted into a |
| // InterceptorConstant. |
| if (!node.arguments.isEmpty && node.arguments.tail.isEmpty) { |
| ast.Node argument = node.arguments.head; |
| visit(argument); |
| HInstruction argumentInstruction = pop(); |
| if (argumentInstruction is HConstant) { |
| ConstantValue argumentConstant = argumentInstruction.constant; |
| if (argumentConstant is TypeConstantValue) { |
| ConstantValue constant = |
| new InterceptorConstantValue(argumentConstant.representedType); |
| HInstruction instruction = graph.addConstant(constant, closedWorld); |
| stack.add(instruction); |
| return; |
| } |
| } |
| } |
| reporter.reportErrorMessage( |
| node, MessageKind.WRONG_ARGUMENT_FOR_JS_INTERCEPTOR_CONSTANT); |
| stack.add(graph.addConstantNull(closedWorld)); |
| } |
| |
| void handleForeignJsCallInIsolate(ast.Send node) { |
| Link<ast.Node> link = node.arguments; |
| if (!backend.hasIsolateSupport) { |
| // If the isolate library is not used, we just invoke the |
| // closure. |
| visit(link.tail.head); |
| push(new HInvokeClosure(new Selector.callClosure(0), |
| <HInstruction>[pop()], commonMasks.dynamicType)); |
| } else { |
| // Call a helper method from the isolate library. |
| Element element = helpers.callInIsolate; |
| if (element == null) { |
| reporter.internalError(node, 'Isolate library and compiler mismatch.'); |
| } |
| List<HInstruction> inputs = <HInstruction>[]; |
| addGenericSendArgumentsToList(link, inputs); |
| pushInvokeStatic(node, element, inputs, |
| typeMask: commonMasks.dynamicType); |
| } |
| } |
| |
| FunctionSignature handleForeignRawFunctionRef(ast<
|