| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:kernel/ast.dart' as ir; |
| |
| import '../closure.dart'; |
| import '../common.dart'; |
| import '../common/codegen.dart' show CodegenRegistry; |
| import '../common/names.dart'; |
| import '../common_elements.dart'; |
| import '../compiler.dart'; |
| import '../constants/values.dart' |
| show |
| ConstantValue, |
| InterceptorConstantValue, |
| StringConstantValue, |
| TypeConstantValue; |
| import '../dump_info.dart'; |
| import '../elements/entities.dart'; |
| import '../elements/jumps.dart'; |
| import '../elements/names.dart'; |
| import '../elements/types.dart'; |
| import '../io/source_information.dart'; |
| import '../js/js.dart' as js; |
| import '../js_backend/allocator_analysis.dart' show JAllocatorAnalysis; |
| import '../js_backend/backend.dart' show JavaScriptBackend; |
| import '../js_backend/runtime_types.dart' show RuntimeTypesSubstitutions; |
| import '../js_emitter/js_emitter.dart' show NativeEmitter; |
| import '../js_model/locals.dart' |
| show forEachOrderedParameter, GlobalLocalsMap, JumpVisitor; |
| import '../js_model/elements.dart' show JGeneratorBody; |
| import '../kernel/element_map.dart'; |
| import '../kernel/kernel_backend_strategy.dart'; |
| import '../native/native.dart' as native; |
| import '../types/abstract_value_domain.dart'; |
| import '../types/types.dart'; |
| import '../universe/call_structure.dart'; |
| import '../universe/feature.dart'; |
| import '../universe/selector.dart'; |
| import '../universe/side_effects.dart' show SideEffects; |
| import '../universe/target_checks.dart' show TargetChecks; |
| import '../universe/use.dart' |
| show ConstantUse, ConstrainedDynamicUse, StaticUse; |
| import '../universe/world_builder.dart' show CodegenWorldBuilder; |
| import '../world.dart'; |
| import 'graph_builder.dart'; |
| import 'jump_handler.dart'; |
| import 'kernel_string_builder.dart'; |
| import 'locals_handler.dart'; |
| import 'loop_handler.dart'; |
| import 'nodes.dart'; |
| import 'ssa.dart'; |
| import 'ssa_branch_builder.dart'; |
| import 'switch_continue_analysis.dart'; |
| import 'type_builder.dart'; |
| |
| // TODO(johnniwinther): Merge this with [KernelInliningState]. |
| class StackFrame { |
| final StackFrame parent; |
| final MemberEntity member; |
| final AsyncMarker asyncMarker; |
| final KernelToLocalsMap localsMap; |
| final KernelToTypeInferenceMap typeInferenceMap; |
| final SourceInformationBuilder<ir.Node> sourceInformationBuilder; |
| |
| StackFrame(this.parent, this.member, this.asyncMarker, this.localsMap, |
| this.typeInferenceMap, this.sourceInformationBuilder); |
| } |
| |
| class KernelSsaGraphBuilder extends ir.Visitor |
| with GraphBuilder, SsaBuilderFieldMixin { |
| final MemberEntity targetElement; |
| final MemberEntity initialTargetElement; |
| |
| final JClosedWorld closedWorld; |
| final CodegenWorldBuilder _worldBuilder; |
| final CodegenRegistry registry; |
| final ClosureDataLookup closureDataLookup; |
| JAllocatorAnalysis _allocatorAnalysis; |
| |
| /// A stack of [InterfaceType]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<InterfaceType> currentImplicitInstantiations = <InterfaceType>[]; |
| |
| /// Used to report information about inlining (which occurs while building the |
| /// SSA graph), when dump-info is enabled. |
| final InfoReporter _infoReporter; |
| |
| HInstruction rethrowableException; |
| |
| final Compiler compiler; |
| |
| @override |
| JavaScriptBackend get backend => compiler.backend; |
| |
| final SourceInformationStrategy<ir.Node> _sourceInformationStrategy; |
| final KernelToElementMapForBuilding _elementMap; |
| final GlobalTypeInferenceResults _globalInferenceResults; |
| final GlobalLocalsMap _globalLocalsMap; |
| LoopHandler<ir.Node> loopHandler; |
| TypeBuilder typeBuilder; |
| |
| final NativeEmitter nativeEmitter; |
| |
| // [ir.Let] and [ir.LocalInitializer] bindings. |
| final Map<ir.VariableDeclaration, HInstruction> letBindings = |
| <ir.VariableDeclaration, HInstruction>{}; |
| |
| /// True if we are visiting the expression of a throw statement; we assume |
| /// this is a slow path. |
| bool _inExpressionOfThrow = false; |
| |
| final List<KernelInliningState> _inliningStack = <KernelInliningState>[]; |
| Local _returnLocal; |
| DartType _returnType; |
| bool _inLazyInitializerExpression = false; |
| |
| StackFrame _currentFrame; |
| |
| KernelSsaGraphBuilder( |
| this.initialTargetElement, |
| InterfaceType instanceType, |
| this.compiler, |
| this._elementMap, |
| this._globalInferenceResults, |
| this._globalLocalsMap, |
| this.closedWorld, |
| this._worldBuilder, |
| this.registry, |
| this.closureDataLookup, |
| this.nativeEmitter, |
| this._sourceInformationStrategy) |
| : this.targetElement = _effectiveTargetElementFor(initialTargetElement), |
| _infoReporter = compiler.dumpInfoTask, |
| _allocatorAnalysis = closedWorld.allocatorAnalysis { |
| _enterFrame(targetElement); |
| this.loopHandler = new KernelLoopHandler(this); |
| typeBuilder = new KernelTypeBuilder(this, _elementMap, _globalLocalsMap); |
| graph.element = targetElement; |
| graph.sourceInformation = |
| _sourceInformationBuilder.buildVariableDeclaration(); |
| this.localsHandler = new LocalsHandler(this, targetElement, targetElement, |
| instanceType, nativeData, interceptorData); |
| } |
| |
| KernelToLocalsMap get localsMap => _currentFrame.localsMap; |
| |
| CommonElements get _commonElements => _elementMap.commonElements; |
| |
| KernelToTypeInferenceMap get _typeInferenceMap => |
| _currentFrame.typeInferenceMap; |
| |
| SourceInformationBuilder get _sourceInformationBuilder => |
| _currentFrame.sourceInformationBuilder; |
| |
| static MemberEntity _effectiveTargetElementFor(MemberEntity member) { |
| if (member is JGeneratorBody) return member.function; |
| return member; |
| } |
| |
| void _enterFrame(MemberEntity member) { |
| AsyncMarker asyncMarker = AsyncMarker.SYNC; |
| ir.FunctionNode function = getFunctionNode(_elementMap, member); |
| if (function != null) { |
| asyncMarker = getAsyncMarker(function); |
| } |
| _currentFrame = new StackFrame( |
| _currentFrame, |
| member, |
| asyncMarker, |
| _globalLocalsMap.getLocalsMap(member), |
| new KernelToTypeInferenceMapImpl(member, _globalInferenceResults), |
| _currentFrame != null |
| ? _currentFrame.sourceInformationBuilder.forContext(member) |
| : _sourceInformationStrategy.createBuilderForContext(member)); |
| } |
| |
| void _leaveFrame() { |
| _currentFrame = _currentFrame.parent; |
| } |
| |
| HGraph build() { |
| return reporter.withCurrentElement(localsMap.currentMember, () { |
| // TODO(het): no reason to do this here... |
| HInstruction.idCounter = 0; |
| MemberDefinition definition = |
| _elementMap.getMemberDefinition(initialTargetElement); |
| |
| switch (definition.kind) { |
| case MemberKind.regular: |
| case MemberKind.closureCall: |
| ir.Node target = definition.node; |
| if (target is ir.Procedure) { |
| if (target.isExternal) { |
| buildExternalFunctionNode( |
| targetElement, _ensureDefaultArgumentValues(target.function)); |
| } else { |
| buildFunctionNode( |
| targetElement, _ensureDefaultArgumentValues(target.function)); |
| } |
| } else if (target is ir.Field) { |
| if (handleConstantField(targetElement, registry, closedWorld)) { |
| // No code is generated for `targetElement`: All references inline |
| // the constant value. |
| return null; |
| } else if (targetElement.isStatic || targetElement.isTopLevel) { |
| backend.constants.registerLazyStatic(targetElement); |
| } |
| buildField(target); |
| } else if (target is ir.FunctionExpression) { |
| buildFunctionNode( |
| targetElement, _ensureDefaultArgumentValues(target.function)); |
| } else if (target is ir.FunctionDeclaration) { |
| buildFunctionNode( |
| targetElement, _ensureDefaultArgumentValues(target.function)); |
| } else { |
| throw 'No case implemented to handle target: ' |
| '$target for $targetElement'; |
| } |
| break; |
| case MemberKind.constructor: |
| ir.Constructor constructor = definition.node; |
| _ensureDefaultArgumentValues(constructor.function); |
| buildConstructor(targetElement, constructor); |
| break; |
| case MemberKind.constructorBody: |
| ir.Constructor constructor = definition.node; |
| _ensureDefaultArgumentValues(constructor.function); |
| buildConstructorBody(constructor); |
| break; |
| case MemberKind.closureField: |
| failedAt(targetElement, "Unexpected closure field: $targetElement"); |
| break; |
| case MemberKind.signature: |
| ir.Node target = definition.node; |
| ir.FunctionNode originalClosureNode; |
| if (target is ir.Procedure) { |
| originalClosureNode = target.function; |
| } else if (target is ir.FunctionExpression) { |
| originalClosureNode = target.function; |
| } else if (target is ir.FunctionDeclaration) { |
| originalClosureNode = target.function; |
| } else { |
| failedAt( |
| targetElement, |
| "Unexpected function signature: " |
| "$targetElement inside a non-closure: $target"); |
| } |
| buildMethodSignature(originalClosureNode); |
| break; |
| case MemberKind.generatorBody: |
| buildGeneratorBody( |
| initialTargetElement, _functionNodeOf(definition.node)); |
| break; |
| } |
| assert(graph.isValid()); |
| |
| if (backend.tracer.isEnabled) { |
| MemberEntity member = definition.member; |
| String name = member.name; |
| if (member.isInstanceMember || |
| member.isConstructor || |
| member.isStatic) { |
| name = "${member.enclosingClass.name}.$name"; |
| if (definition.kind == MemberKind.constructorBody) { |
| name += " (body)"; |
| } |
| } |
| backend.tracer.traceCompilation(name); |
| backend.tracer.traceGraph('builder', graph); |
| } |
| |
| return graph; |
| }); |
| } |
| |
| ir.FunctionNode _functionNodeOf(ir.TreeNode node) { |
| if (node is ir.Member) return node.function; |
| if (node is ir.FunctionDeclaration) return node.function; |
| if (node is ir.FunctionExpression) return node.function; |
| return null; |
| } |
| |
| ir.FunctionNode _ensureDefaultArgumentValues(ir.FunctionNode function) { |
| // Register all [function]'s default argument values. |
| // |
| // Default values might be (or contain) functions that are not referenced |
| // from anywhere else so we need to ensure these are enqueued. Stubs and |
| // `Function.apply` data are created after the codegen queue is closed, so |
| // we force these functions into the queue by registering the constants as |
| // used in advance. See language/cyclic_default_values_test.dart for an |
| // example. |
| // |
| // TODO(sra): We could be more precise if stubs and `Function.apply` data |
| // were generated by the codegen enqueuer. In practice even in huge programs |
| // there are only very small number of constants created here that are not |
| // actually used. |
| void registerDefaultValue(ir.VariableDeclaration node) { |
| ConstantValue constantValue = |
| _elementMap.getConstantValue(node.initializer, implicitNull: true); |
| assert( |
| constantValue != null, |
| failedAt(_elementMap.getMethod(function.parent), |
| 'No constant computed for $node')); |
| registry?.registerConstantUse(new ConstantUse.init(constantValue)); |
| } |
| |
| function.positionalParameters |
| .skip(function.requiredParameterCount) |
| .forEach(registerDefaultValue); |
| function.namedParameters.forEach(registerDefaultValue); |
| return function; |
| } |
| |
| @override |
| ConstantValue getFieldInitialConstantValue(FieldEntity field) { |
| assert(field == targetElement); |
| return _elementMap.getFieldConstantValue(field); |
| } |
| |
| void buildField(ir.Field node) { |
| _inLazyInitializerExpression = node.isStatic; |
| FieldEntity field = _elementMap.getMember(node); |
| openFunction(field, checks: TargetChecks.none); |
| if (node.isInstanceMember && options.enableTypeAssertions) { |
| HInstruction thisInstruction = localsHandler.readThis( |
| sourceInformation: _sourceInformationBuilder.buildGet(node)); |
| // Use dynamic type because the type computed by the inferrer is |
| // narrowed to the type annotation. |
| HInstruction parameter = |
| new HParameterValue(field, abstractValueDomain.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.potentiallyCheckOrTrustTypeOfParameter( |
| parameter, _getDartTypeIfValid(node.type)); |
| add(new HFieldSet(abstractValueDomain, field, thisInstruction, value)); |
| } else { |
| if (node.initializer != null) { |
| node.initializer.accept(this); |
| HInstruction fieldValue = pop(); |
| HInstruction checkInstruction = |
| typeBuilder.potentiallyCheckOrTrustTypeOfAssignment( |
| fieldValue, _getDartTypeIfValid(node.type)); |
| stack.add(checkInstruction); |
| } else { |
| stack.add(graph.addConstantNull(closedWorld)); |
| } |
| HInstruction value = pop(); |
| closeAndGotoExit(new HReturn(abstractValueDomain, value, |
| _sourceInformationBuilder.buildReturn(node))); |
| } |
| closeFunction(); |
| } |
| |
| DartType _getDartTypeIfValid(ir.DartType type) { |
| if (type is ir.InvalidType) return null; |
| return _elementMap.getDartType(type); |
| } |
| |
| /// 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) { |
| InterfaceType type = commonElements.boolType; |
| return typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(value, type, |
| kind: HTypeConversion.BOOLEAN_CONVERSION_CHECK); |
| } |
| HInstruction result = new HBoolify(value, abstractValueDomain.boolType); |
| add(result); |
| return result; |
| } |
| |
| /// Extend current method parameters with parameters for the class type |
| /// parameters. If the class has type parameters but does not need them, bind |
| /// to `dynamic` (represented as `null`) so the bindings are available for |
| /// building types up the inheritance chain of generative constructors. |
| void _addClassTypeVariablesIfNeeded(MemberEntity member) { |
| if (!member.isConstructor && member is! ConstructorBodyEntity) { |
| return; |
| } |
| ClassEntity cls = member.enclosingClass; |
| InterfaceType thisType = _elementMap.elementEnvironment.getThisType(cls); |
| if (thisType.typeArguments.isEmpty) { |
| return; |
| } |
| bool needsTypeArguments = rtiNeed.classNeedsTypeArguments(cls); |
| thisType.typeArguments.forEach((DartType _typeVariable) { |
| TypeVariableType typeVariableType = _typeVariable; |
| HInstruction param; |
| if (needsTypeArguments) { |
| param = addParameter( |
| typeVariableType.element, abstractValueDomain.nonNullType); |
| } else { |
| // Unused, so bind to `dynamic`. |
| param = graph.addConstantNull(closedWorld); |
| } |
| Local local = localsHandler.getTypeVariableAsLocal(typeVariableType); |
| localsHandler.directLocals[local] = param; |
| }); |
| } |
| |
| /// Extend current method parameters with parameters for the function type |
| /// variables. |
| /// |
| /// TODO(johnniwinther): Do we need this? |
| /// If the method has type variables but does not need them, bind to `dynamic` |
| /// (represented as `null`). |
| void _addFunctionTypeVariablesIfNeeded(MemberEntity member) { |
| if (member is! FunctionEntity) return; |
| |
| List<TypeVariableType> typeVariables = |
| _elementMap.elementEnvironment.getFunctionTypeVariables(member); |
| if (typeVariables.isEmpty) { |
| return; |
| } |
| bool needsTypeArguments = rtiNeed.methodNeedsTypeArguments(member); |
| typeVariables.forEach((TypeVariableType typeVariableType) { |
| HInstruction param; |
| if (needsTypeArguments) { |
| param = addParameter( |
| typeVariableType.element, abstractValueDomain.nonNullType); |
| } else { |
| // Unused, so bind to `dynamic`. |
| param = graph.addConstantNull(closedWorld); |
| } |
| Local local = localsHandler.getTypeVariableAsLocal(typeVariableType); |
| localsHandler.directLocals[local] = param; |
| functionTypeParameterLocals.add(local); |
| }); |
| } |
| |
| List<Local> functionTypeParameterLocals = <Local>[]; |
| |
| /// Builds a generative constructor. |
| /// |
| /// Generative constructors are built in stages, in effect inlining the |
| /// initializers and constructor bodies up the inheritance chain. |
| /// |
| /// 1. Extend method parameters with parameters the class's type parameters. |
| /// |
| /// 2. Add type checks for value parameters (might need result of (1)). |
| /// |
| /// 3. Walk inheritance chain to build bindings for type parameters of |
| /// superclasses and mixed-in classes. |
| /// |
| /// 4. Collect initializer values. Walk up inheritance chain to collect field |
| /// initializers from field declarations, initializing parameters and |
| /// initializer. |
| /// |
| /// 5. Create reified type information for instance. |
| /// |
| /// 6. Allocate instance and assign initializers and reified type information |
| /// to fields by calling JavaScript constructor. |
| /// |
| /// 7. Walk inheritance chain to call or inline constructor bodies. |
| /// |
| /// All the bindings are put in the constructor's locals handler. The |
| /// implication is that a class cannot be extended or mixed-in twice. If we in |
| /// future support repeated uses of a mixin class, we should do so by cloning |
| /// the mixin class in the Kernel input. |
| void buildConstructor(ConstructorEntity constructor, ir.Constructor node) { |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildCreate(node); |
| ClassEntity cls = constructor.enclosingClass; |
| |
| if (_inliningStack.isEmpty) { |
| openFunction(constructor, |
| functionNode: node.function, checks: TargetChecks.none); |
| } |
| |
| // [fieldValues] accumulates the field initializer values, which may be |
| // overwritten by initializer-list initializers. |
| ConstructorData constructorData = new ConstructorData(); |
| _buildInitializers(node, constructorData); |
| |
| List<HInstruction> constructorArguments = <HInstruction>[]; |
| // Doing this instead of fieldValues.forEach because we haven't defined the |
| // order of the arguments here. We can define that with JElements. |
| bool isCustomElement = nativeData.isNativeOrExtendsNative(cls) && |
| !nativeData.isJsInteropClass(cls); |
| InterfaceType thisType = _elementMap.elementEnvironment.getThisType(cls); |
| List<FieldEntity> fields = <FieldEntity>[]; |
| _worldBuilder.forEachInstanceField(cls, |
| (ClassEntity enclosingClass, FieldEntity member) { |
| HInstruction value = constructorData.fieldValues[member]; |
| if (value == null) { |
| assert( |
| _allocatorAnalysis.isInitializedInAllocator(member) || |
| isCustomElement || |
| reporter.hasReportedError, |
| 'No initializer value for field ${member}'); |
| } else { |
| fields.add(member); |
| DartType type = _elementMap.elementEnvironment.getFieldType(member); |
| type = localsHandler.substInContext(type); |
| constructorArguments.add( |
| typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(value, type)); |
| } |
| }); |
| |
| addImplicitInstantiation(thisType); |
| List<DartType> instantiatedTypes = |
| new List<InterfaceType>.from(currentImplicitInstantiations); |
| |
| HInstruction newObject; |
| if (isCustomElement) { |
| // 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, abstractValueDomain.dynamicType, |
| isAssignable: false)); |
| for (int i = 0; i < fields.length; i++) { |
| add(new HFieldSet(abstractValueDomain, fields[i], newObject, |
| constructorArguments[i])); |
| } |
| } else { |
| // Create the runtime type information, if needed. |
| bool needsTypeArguments = |
| closedWorld.rtiNeed.classNeedsTypeArguments(cls); |
| if (needsTypeArguments) { |
| // Read the values of the type arguments and create a |
| // HTypeInfoExpression to set on the newly created object. |
| List<HInstruction> typeArguments = <HInstruction>[]; |
| InterfaceType thisType = |
| _elementMap.elementEnvironment.getThisType(cls); |
| for (DartType typeVariable in thisType.typeArguments) { |
| HInstruction argument = localsHandler |
| .readLocal(localsHandler.getTypeVariableAsLocal(typeVariable)); |
| typeArguments.add(argument); |
| } |
| |
| HInstruction typeInfo = new HTypeInfoExpression( |
| TypeInfoExpressionKind.INSTANCE, |
| thisType, |
| typeArguments, |
| abstractValueDomain.dynamicType); |
| add(typeInfo); |
| constructorArguments.add(typeInfo); |
| } |
| |
| newObject = new HCreate(cls, constructorArguments, |
| abstractValueDomain.createNonNullExact(cls), sourceInformation, |
| instantiatedTypes: instantiatedTypes, |
| hasRtiInput: needsTypeArguments); |
| |
| add(newObject); |
| } |
| removeImplicitInstantiation(thisType); |
| |
| HInstruction interceptor; |
| // Generate calls to the constructor bodies. |
| for (ir.Constructor body in constructorData.constructorChain.reversed) { |
| if (_isEmptyStatement(body.function.body)) continue; |
| |
| List<HInstruction> bodyCallInputs = <HInstruction>[]; |
| if (isCustomElement) { |
| if (interceptor == null) { |
| ConstantValue constant = new InterceptorConstantValue(cls); |
| interceptor = graph.addConstant(constant, closedWorld); |
| } |
| bodyCallInputs.add(interceptor); |
| } |
| bodyCallInputs.add(newObject); |
| |
| // Pass uncaptured arguments first, captured arguments in a box, then type |
| // arguments. |
| |
| ConstructorEntity inlinedConstructor = _elementMap.getConstructor(body); |
| |
| inlinedFrom(inlinedConstructor, () { |
| void handleParameter(ir.VariableDeclaration node) { |
| Local parameter = localsMap.getLocalVariable(node); |
| // 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)); |
| } |
| } |
| |
| // Provide the parameters to the generative constructor body. |
| body.function.positionalParameters.forEach(handleParameter); |
| body.function.namedParameters.toList() |
| ..sort(namedOrdering) |
| ..forEach(handleParameter); |
| |
| // If there are locals that escape (i.e. mutated in closures), we pass the |
| // box to the constructor. |
| CapturedScope scopeData = |
| closureDataLookup.getCapturedScope(inlinedConstructor); |
| if (scopeData.requiresContextBox) { |
| bodyCallInputs.add(localsHandler.readLocal(scopeData.context)); |
| } |
| |
| // Pass type arguments. |
| ClassEntity inlinedConstructorClass = inlinedConstructor.enclosingClass; |
| if (closedWorld.rtiNeed |
| .classNeedsTypeArguments(inlinedConstructorClass)) { |
| InterfaceType thisType = _elementMap.elementEnvironment |
| .getThisType(inlinedConstructorClass); |
| for (DartType typeVariable in thisType.typeArguments) { |
| DartType result = localsHandler.substInContext(typeVariable); |
| HInstruction argument = |
| typeBuilder.analyzeTypeArgument(result, sourceElement); |
| bodyCallInputs.add(argument); |
| } |
| } |
| |
| ConstructorBodyEntity constructorBody = |
| _elementMap.getConstructorBody(body); |
| if (!isCustomElement && // TODO(13836): Fix inlining. |
| _tryInlineMethod(constructorBody, null, null, bodyCallInputs, node, |
| sourceInformation)) { |
| pop(); |
| } else { |
| _invokeConstructorBody(body, bodyCallInputs, |
| _sourceInformationBuilder.buildDeclaration(constructor)); |
| } |
| }); |
| } |
| |
| if (_inliningStack.isEmpty) { |
| closeAndGotoExit( |
| new HReturn(abstractValueDomain, newObject, sourceInformation)); |
| closeFunction(); |
| } else { |
| localsHandler.updateLocal(_returnLocal, newObject, |
| sourceInformation: sourceInformation); |
| } |
| } |
| |
| static bool _isEmptyStatement(ir.Statement body) { |
| if (body is ir.EmptyStatement) return true; |
| if (body is ir.Block) return body.statements.every(_isEmptyStatement); |
| return false; |
| } |
| |
| void _invokeConstructorBody(ir.Constructor constructor, |
| List<HInstruction> inputs, SourceInformation sourceInformation) { |
| MemberEntity constructorBody = _elementMap.getConstructorBody(constructor); |
| HInvokeConstructorBody invoke = new HInvokeConstructorBody(constructorBody, |
| inputs, abstractValueDomain.nonNullType, sourceInformation); |
| add(invoke); |
| } |
| |
| /// Sets context for generating code that is the result of inlining |
| /// [inlinedTarget]. |
| inlinedFrom(MemberEntity inlinedTarget, f()) { |
| reporter.withCurrentElement(inlinedTarget, () { |
| _enterFrame(inlinedTarget); |
| var result = f(); |
| _leaveFrame(); |
| return result; |
| }); |
| } |
| |
| void _ensureTypeVariablesForInitializers( |
| ConstructorData constructorData, ClassEntity enclosingClass) { |
| if (!constructorData.includedClasses.add(enclosingClass)) return; |
| if (rtiNeed.classNeedsTypeArguments(enclosingClass)) { |
| // If [enclosingClass] needs RTI, we have to give a value to its type |
| // parameters. For a super constructor call, the type is the supertype |
| // of current class. For a redirecting constructor, the type is the |
| // current type. [LocalsHandler.substInContext] takes care of both. |
| InterfaceType thisType = |
| _elementMap.elementEnvironment.getThisType(enclosingClass); |
| InterfaceType type = localsHandler.substInContext(thisType); |
| List<DartType> arguments = type.typeArguments; |
| List<DartType> typeVariables = thisType.typeArguments; |
| 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)); |
| }); |
| } |
| } |
| |
| /// Collects the values for field initializers for the direct fields of |
| /// [clazz]. |
| void _collectFieldValues(ir.Class clazz, ConstructorData constructorData) { |
| ClassEntity cls = _elementMap.getClass(clazz); |
| _worldBuilder.forEachDirectInstanceField(cls, (FieldEntity field) { |
| _ensureTypeVariablesForInitializers( |
| constructorData, field.enclosingClass); |
| |
| MemberDefinition definition = _elementMap.getMemberDefinition(field); |
| ir.Field node; |
| switch (definition.kind) { |
| case MemberKind.regular: |
| node = definition.node; |
| break; |
| default: |
| failedAt(field, "Unexpected member definition $definition."); |
| } |
| |
| if (node.initializer == null) { |
| // Unassigned fields of native classes are not initialized to |
| // prevent overwriting pre-initialized native properties. |
| if (!nativeData.isNativeOrExtendsNative(cls)) { |
| if (!_allocatorAnalysis.isInitializedInAllocator(field)) { |
| constructorData.fieldValues[field] = |
| graph.addConstantNull(closedWorld); |
| } |
| } |
| } else if (node.initializer is! ir.NullLiteral || |
| !nativeData.isNativeClass(cls)) { |
| // Compile the initializer in the context of the field so we know that |
| // class type parameters are accessed as values. |
| // TODO(sra): It would be sufficient to know the context was a field |
| // initializer. |
| if (!_allocatorAnalysis.isInitializedInAllocator(field)) { |
| inlinedFrom(field, () { |
| node.initializer.accept(this); |
| constructorData.fieldValues[field] = pop(); |
| }); |
| } |
| } |
| }); |
| } |
| |
| static bool isRedirectingConstructor(ir.Constructor constructor) => |
| constructor.initializers |
| .any((initializer) => initializer is ir.RedirectingInitializer); |
| |
| /// Collects field initializers all the way up the inheritance chain. |
| void _buildInitializers( |
| ir.Constructor constructor, ConstructorData constructorData) { |
| assert( |
| _elementMap.getConstructor(constructor) == localsMap.currentMember, |
| failedAt( |
| localsMap.currentMember, |
| 'Expected ${localsMap.currentMember} ' |
| 'but found ${_elementMap.getConstructor(constructor)}.')); |
| constructorData.constructorChain.add(constructor); |
| |
| if (!isRedirectingConstructor(constructor)) { |
| // Compute values for field initializers, but only if this is not a |
| // redirecting constructor, since the target will compute the fields. |
| _collectFieldValues(constructor.enclosingClass, constructorData); |
| } |
| var foundSuperOrRedirectCall = false; |
| for (var initializer in constructor.initializers) { |
| if (initializer is ir.FieldInitializer) { |
| // TODO(sra): Skip fields initialized in allocator. |
| initializer.value.accept(this); |
| constructorData.fieldValues[_elementMap.getField(initializer.field)] = |
| pop(); |
| } else if (initializer is ir.SuperInitializer) { |
| assert(!foundSuperOrRedirectCall); |
| foundSuperOrRedirectCall = true; |
| _inlineSuperInitializer(initializer, constructorData, constructor); |
| } else if (initializer is ir.RedirectingInitializer) { |
| assert(!foundSuperOrRedirectCall); |
| foundSuperOrRedirectCall = true; |
| _inlineRedirectingInitializer( |
| initializer, constructorData, constructor); |
| } else if (initializer is ir.LocalInitializer) { |
| // LocalInitializer is like a let-expression that is in scope for the |
| // rest of the initializers. |
| ir.VariableDeclaration variable = initializer.variable; |
| assert(variable.isFinal); |
| variable.initializer.accept(this); |
| HInstruction value = pop(); |
| // TODO(sra): Apply inferred type information. |
| letBindings[variable] = value; |
| } else if (initializer is ir.AssertInitializer) { |
| // Assert in initializer is currently not supported in dart2js. |
| // TODO(johnniwinther): Support assert in initializer. |
| } else if (initializer is ir.InvalidInitializer) { |
| assert(false, 'ir.InvalidInitializer not handled'); |
| } else { |
| assert(false, 'Unhandled initializer ir.${initializer.runtimeType}'); |
| } |
| } |
| |
| if (!foundSuperOrRedirectCall) { |
| assert( |
| _elementMap.getClass(constructor.enclosingClass) == |
| _elementMap.commonElements.objectClass || |
| constructor.initializers.any(_ErroneousInitializerVisitor.check), |
| 'All constructors should have super- or redirecting- initializers,' |
| ' except Object()' |
| ' ${constructor.initializers}'); |
| } |
| } |
| |
| List<HInstruction> _normalizeAndBuildArguments( |
| ir.FunctionNode function, ir.Arguments arguments) { |
| var builtArguments = <HInstruction>[]; |
| var positionalIndex = 0; |
| function.positionalParameters.forEach((ir.VariableDeclaration node) { |
| if (positionalIndex < arguments.positional.length) { |
| arguments.positional[positionalIndex++].accept(this); |
| builtArguments.add(pop()); |
| } else { |
| ConstantValue constantValue = |
| _elementMap.getConstantValue(node.initializer, implicitNull: true); |
| assert( |
| constantValue != null, |
| failedAt(_elementMap.getMethod(function.parent), |
| 'No constant computed for $node')); |
| builtArguments.add(graph.addConstant(constantValue, closedWorld)); |
| } |
| }); |
| function.namedParameters.toList() |
| ..sort(namedOrdering) |
| ..forEach((ir.VariableDeclaration node) { |
| var correspondingNamed = arguments.named |
| .firstWhere((named) => named.name == node.name, orElse: () => null); |
| if (correspondingNamed != null) { |
| correspondingNamed.value.accept(this); |
| builtArguments.add(pop()); |
| } else { |
| ConstantValue constantValue = _elementMap |
| .getConstantValue(node.initializer, implicitNull: true); |
| assert( |
| constantValue != null, |
| failedAt(_elementMap.getMethod(function.parent), |
| 'No constant computed for $node')); |
| builtArguments.add(graph.addConstant(constantValue, closedWorld)); |
| } |
| }); |
| |
| return builtArguments; |
| } |
| |
| /// Inlines the given redirecting [constructor]'s initializers by collecting |
| /// its field values and building its constructor initializers. We visit super |
| /// constructors all the way up to the [Object] constructor. |
| void _inlineRedirectingInitializer(ir.RedirectingInitializer initializer, |
| ConstructorData constructorData, ir.Constructor caller) { |
| var superOrRedirectConstructor = initializer.target; |
| var arguments = _normalizeAndBuildArguments( |
| superOrRedirectConstructor.function, initializer.arguments); |
| |
| // Redirecting initializer already has [localsHandler] bindings for type |
| // parameters from the redirecting constructor. |
| |
| // For redirecting constructors, the fields will be initialized later by the |
| // effective target, so we don't do it here. |
| |
| _inlineSuperOrRedirectCommon(initializer, superOrRedirectConstructor, |
| arguments, constructorData, caller); |
| } |
| |
| /// Inlines the given super [constructor]'s initializers by collecting its |
| /// field values and building its constructor initializers. We visit super |
| /// constructors all the way up to the [Object] constructor. |
| void _inlineSuperInitializer(ir.SuperInitializer initializer, |
| ConstructorData constructorData, ir.Constructor caller) { |
| var target = initializer.target; |
| var arguments = |
| _normalizeAndBuildArguments(target.function, initializer.arguments); |
| |
| ir.Class callerClass = caller.enclosingClass; |
| ir.Supertype supertype = callerClass.supertype; |
| |
| if (callerClass.mixedInType != null) { |
| _collectFieldValues(callerClass.mixedInType.classNode, constructorData); |
| } |
| |
| // The class of the super-constructor may not be the supertype class. In |
| // this case, we must go up the class hierarchy until we reach the class |
| // containing the super-constructor. |
| while (supertype.classNode != target.enclosingClass) { |
| // Fields from unnamed mixin application classes (ie Object&Foo) get |
| // "collected" with the regular supertype fields, so we must bind type |
| // parameters from both the supertype and the supertype's mixin classes |
| // before collecting the field values. |
| _collectFieldValues(supertype.classNode, constructorData); |
| supertype = supertype.classNode.supertype; |
| } |
| supertype = supertype.classNode.supertype; |
| |
| _inlineSuperOrRedirectCommon( |
| initializer, target, arguments, constructorData, caller); |
| } |
| |
| void _inlineSuperOrRedirectCommon( |
| ir.Initializer initializer, |
| ir.Constructor constructor, |
| List<HInstruction> arguments, |
| ConstructorData constructorData, |
| ir.Constructor caller) { |
| var index = 0; |
| |
| ConstructorEntity element = _elementMap.getConstructor(constructor); |
| ScopeInfo oldScopeInfo = localsHandler.scopeInfo; |
| |
| inlinedFrom(element, () { |
| void handleParameter(ir.VariableDeclaration node) { |
| Local parameter = localsMap.getLocalVariable(node); |
| HInstruction argument = arguments[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); |
| } |
| |
| constructor.function.positionalParameters.forEach(handleParameter); |
| constructor.function.namedParameters.toList() |
| ..sort(namedOrdering) |
| ..forEach(handleParameter); |
| |
| _ensureTypeVariablesForInitializers( |
| constructorData, element.enclosingClass); |
| |
| // Set the locals handler state as if we were inlining the constructor. |
| ScopeInfo newScopeInfo = closureDataLookup.getScopeInfo(element); |
| localsHandler.scopeInfo = newScopeInfo; |
| localsHandler.enterScope(closureDataLookup.getCapturedScope(element), |
| _sourceInformationBuilder.buildDeclaration(element)); |
| _buildInitializers(constructor, constructorData); |
| }); |
| localsHandler.scopeInfo = oldScopeInfo; |
| } |
| |
| /// Constructs a special signature function for a closure. It is unique in |
| /// that no corresponding ir.Node actually exists for it. We just use the |
| /// targetElement. |
| void buildMethodSignature(ir.FunctionNode originalClosureNode) { |
| openFunction(targetElement); |
| List<HInstruction> typeArguments = <HInstruction>[]; |
| |
| // Add function type variables. |
| FunctionType functionType = |
| _elementMap.getFunctionType(originalClosureNode); |
| functionType.forEachTypeVariable((TypeVariableType typeVariableType) { |
| if (options.strongMode || |
| typeVariableType.element.typeDeclaration is ClassEntity) { |
| DartType result = localsHandler.substInContext(typeVariableType); |
| HInstruction argument = |
| typeBuilder.analyzeTypeArgument(result, sourceElement); |
| typeArguments.add(argument); |
| } |
| }); |
| push(new HTypeInfoExpression( |
| TypeInfoExpressionKind.COMPLETE, |
| _elementMap.getFunctionType(originalClosureNode), |
| typeArguments, |
| abstractValueDomain.functionType)); |
| HInstruction value = pop(); |
| close(new HReturn(abstractValueDomain, value, |
| _sourceInformationBuilder.buildReturn(originalClosureNode))) |
| .addSuccessor(graph.exit); |
| |
| closeFunction(); |
| } |
| |
| /// Builds generative constructor body. |
| void buildConstructorBody(ir.Constructor constructor) { |
| openFunction(_elementMap.getConstructorBody(constructor), |
| functionNode: constructor.function, checks: TargetChecks.none); |
| constructor.function.body.accept(this); |
| closeFunction(); |
| } |
| |
| /// Builds a SSA graph for FunctionNodes, found in FunctionExpressions and |
| /// Procedures. |
| void buildFunctionNode( |
| FunctionEntity function, ir.FunctionNode functionNode) { |
| if (functionNode.asyncMarker != ir.AsyncMarker.Sync) { |
| buildGenerator(function, functionNode); |
| return; |
| } |
| |
| // TODO(sra): Static methods with no tear-off can be generated with no |
| // checks. |
| // TODO(sra): Instance methods can be generated with reduced checks if |
| // called only from non-dynamic call-sites. |
| openFunction(function, functionNode: functionNode); |
| |
| // If [functionNode] 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 (function.name == '==') { |
| if (!_commonElements.operatorEqHandlesNullArgument(function)) { |
| handleIf( |
| visitCondition: () { |
| HParameterValue parameter = parameters.values.first; |
| push(new HIdentity(parameter, graph.addConstantNull(closedWorld), |
| null, abstractValueDomain.boolType)); |
| }, |
| visitThen: () { |
| closeAndGotoExit(new HReturn( |
| abstractValueDomain, |
| graph.addConstantBool(false, closedWorld), |
| _sourceInformationBuilder.buildReturn(functionNode))); |
| }, |
| visitElse: null, |
| sourceInformation: _sourceInformationBuilder.buildIf(functionNode)); |
| } |
| } |
| if (const bool.fromEnvironment('unreachable-throw')) { |
| var emptyParameters = parameters.values |
| .where((p) => abstractValueDomain.isEmpty(p.instructionType)); |
| if (emptyParameters.length > 0) { |
| addComment('${emptyParameters} inferred as [empty]'); |
| add(new HInvokeStatic( |
| commonElements.assertUnreachableMethod, |
| <HInstruction>[], |
| abstractValueDomain.dynamicType, |
| const <DartType>[])); |
| closeFunction(); |
| return; |
| } |
| } |
| functionNode.body.accept(this); |
| 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)), |
| abstractValueDomain.dynamicType, <HInstruction>[], |
| isStatement: true)); |
| } |
| |
| /// Builds a SSA graph for a sync*/async/async* generator. We generate a |
| /// entry function which tail-calls a body function. The entry contains |
| /// per-invocation checks and the body, which is later transformed, contains |
| /// the re-entrant 'state machine' code. |
| void buildGenerator(FunctionEntity function, ir.FunctionNode functionNode) { |
| openFunction(function, functionNode: functionNode); |
| |
| // Prepare to tail-call the body. |
| |
| // Is 'buildAsyncBody' the best location for the entry? |
| var sourceInformation = _sourceInformationBuilder.buildAsyncBody(); |
| |
| // Forward all the parameters to the body. |
| List<HInstruction> inputs = <HInstruction>[]; |
| if (graph.thisInstruction != null) { |
| inputs.add(graph.thisInstruction); |
| } |
| if (graph.explicitReceiverParameter != null) { |
| inputs.add(graph.explicitReceiverParameter); |
| } |
| for (Local local in parameters.keys) { |
| inputs.add(localsHandler.readLocal(local)); |
| } |
| for (Local local in functionTypeParameterLocals) { |
| inputs.add(localsHandler.readLocal(local)); |
| } |
| |
| // Add the type parameter for the generator's element type. |
| DartType elementType = _elementMap.elementEnvironment |
| .getAsyncOrSyncStarElementType(function.asyncMarker, _returnType); |
| |
| if (elementType.containsFreeTypeVariables) { |
| // Type must be computed in the entry function, where the type variables |
| // are in scope, and passed to the body function. |
| inputs.add(typeBuilder.analyzeTypeArgument(elementType, function)); |
| } else { |
| // Types with no type variables can be emitted as part of the generator, |
| // avoiding an extra argument. |
| if (_generatedEntryIsEmpty()) { |
| // If the entry function is empty (e.g. no argument checks) and the type |
| // can be generated in body, 'inline' the body by generating it in |
| // place. This works because the subsequent transformation of the code |
| // is 'correct' for the empty entry function code. |
| graph.needsAsyncRewrite = true; |
| graph.asyncElementType = elementType; |
| functionNode.body.accept(this); |
| closeFunction(); |
| return; |
| } |
| } |
| |
| JGeneratorBody body = _elementMap.getGeneratorBody(function); |
| backend.outputUnitData.registerColocatedMembers(function, body); |
| push(new HInvokeGeneratorBody( |
| body, |
| inputs, |
| abstractValueDomain.dynamicType, // TODO: better type. |
| sourceInformation)); |
| |
| closeAndGotoExit( |
| new HReturn(abstractValueDomain, pop(), sourceInformation)); |
| |
| closeFunction(); |
| } |
| |
| /// Builds a SSA graph for a sync*/async/async* generator body. |
| void buildGeneratorBody( |
| JGeneratorBody function, ir.FunctionNode functionNode) { |
| FunctionEntity entry = function.function; |
| openFunction(entry, functionNode: functionNode, checks: TargetChecks.none); |
| graph.needsAsyncRewrite = true; |
| if (!function.elementType.containsFreeTypeVariables) { |
| // We can generate the element type in place |
| graph.asyncElementType = function.elementType; |
| } |
| functionNode.body.accept(this); |
| closeFunction(); |
| } |
| |
| bool _generatedEntryIsEmpty() { |
| HBasicBlock block = current; |
| // If `block.id` is not 1 then we generated some control flow. |
| if (block.id != 1) return false; |
| for (HInstruction node = block.first; node != null; node = node.next) { |
| if (node is HGoto) continue; |
| return false; |
| } |
| return true; |
| } |
| |
| void _potentiallyAddFunctionParameterTypeChecks( |
| ir.FunctionNode function, TargetChecks targetChecks) { |
| // 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. |
| |
| if (targetChecks.checkTypeParameters) { |
| checkTypeVariableBounds(targetElement); |
| } |
| |
| MemberDefinition definition = |
| _elementMap.getMemberDefinition(targetElement); |
| bool nodeIsConstructorBody = definition.kind == MemberKind.constructorBody; |
| |
| void _handleParameter(ir.VariableDeclaration variable) { |
| Local local = localsMap.getLocalVariable(variable); |
| if (nodeIsConstructorBody && |
| closureDataLookup.getCapturedScope(targetElement).isBoxed(local)) { |
| // If local is boxed, then `variable` will be a field inside the box |
| // passed as the last parameter, so no need to update our locals |
| // handler or check types at this point. |
| return; |
| } |
| HInstruction newParameter = localsHandler.directLocals[local]; |
| DartType type = _getDartTypeIfValid(variable.type); |
| |
| if (options.strongMode) { |
| if (targetChecks.checkAllParameters || |
| (targetChecks.checkCovariantParameters && |
| (variable.isGenericCovariantImpl || variable.isCovariant))) { |
| newParameter = typeBuilder.potentiallyCheckOrTrustTypeOfParameter( |
| newParameter, type); |
| } else { |
| newParameter = typeBuilder.trustTypeOfParameter(newParameter, type); |
| } |
| } else { |
| newParameter = typeBuilder.potentiallyCheckOrTrustTypeOfParameter( |
| newParameter, type); |
| } |
| |
| localsHandler.directLocals[local] = newParameter; |
| } |
| |
| function.positionalParameters.forEach(_handleParameter); |
| function.namedParameters.toList()..forEach(_handleParameter); |
| } |
| |
| void checkTypeVariableBounds(FunctionEntity method) { |
| if (rtiNeed.methodNeedsTypeArguments(method) && |
| options.parameterCheckPolicy.isEmitted) { |
| ir.FunctionNode function = getFunctionNode(_elementMap, method); |
| for (ir.TypeParameter typeParameter in function.typeParameters) { |
| Local local = localsMap.getLocalTypeVariable( |
| new ir.TypeParameterType(typeParameter), _elementMap); |
| HInstruction newParameter = localsHandler.directLocals[local]; |
| DartType bound = _getDartTypeIfValid(typeParameter.bound); |
| if (!bound.isDynamic && |
| !bound.isVoid && |
| bound != _commonElements.objectType) { |
| _assertIsType( |
| newParameter, |
| bound, |
| "The type argument '", |
| "' is not a subtype of the type variable bound '", |
| "' of type variable '${local.name}' in '${method.name}'."); |
| } |
| } |
| } |
| } |
| |
| /// Builds a SSA graph for FunctionNodes of external methods. |
| void buildExternalFunctionNode( |
| FunctionEntity function, ir.FunctionNode functionNode) { |
| // TODO(johnniwinther): Non-js-interop external functions should |
| // throw a runtime error. |
| assert(functionNode.body == null); |
| openFunction(function, functionNode: functionNode); |
| |
| if (closedWorld.nativeData.isNativeMember(targetElement)) { |
| nativeEmitter.nativeMethods.add(targetElement); |
| String nativeName; |
| if (closedWorld.nativeData.hasFixedBackendName(targetElement)) { |
| nativeName = closedWorld.nativeData.getFixedBackendName(targetElement); |
| } else { |
| nativeName = targetElement.name; |
| } |
| |
| String templateReceiver = ''; |
| List<String> templateArguments = <String>[]; |
| List<HInstruction> inputs = <HInstruction>[]; |
| if (targetElement.isInstanceMember) { |
| templateReceiver = '#.'; |
| inputs.add(localsHandler.readThis( |
| sourceInformation: |
| _sourceInformationBuilder.buildGet(functionNode))); |
| } |
| |
| for (ir.VariableDeclaration param in functionNode.positionalParameters) { |
| templateArguments.add('#'); |
| Local local = localsMap.getLocalVariable(param); |
| // Convert Dart function to JavaScript function. |
| HInstruction argument = localsHandler.readLocal(local); |
| ir.DartType type = param.type; |
| if (type is ir.FunctionType) { |
| int arity = type.positionalParameters.length; |
| _pushStaticInvocation( |
| _commonElements.closureConverter, |
| [argument, graph.addConstantInt(arity, closedWorld)], |
| abstractValueDomain.dynamicType, |
| const <DartType>[]); |
| argument = pop(); |
| } |
| inputs.add(argument); |
| } |
| |
| String arguments = templateArguments.join(','); |
| |
| // TODO(sra): Use declared type or NativeBehavior type. |
| AbstractValue typeMask = abstractValueDomain.dynamicType; |
| String template; |
| if (targetElement.isGetter) { |
| template = '${templateReceiver}$nativeName'; |
| } else if (targetElement.isSetter) { |
| template = '${templateReceiver}$nativeName = ${arguments}'; |
| } else { |
| template = '${templateReceiver}$nativeName(${arguments})'; |
| } |
| |
| push(new HForeignCode( |
| js.js.uncachedExpressionTemplate(template), typeMask, inputs, |
| effects: new SideEffects())); |
| // TODO(johnniwinther): Provide source information. |
| HInstruction value = pop(); |
| if (targetElement.isSetter) { |
| value = graph.addConstantNull(closedWorld); |
| } |
| close(new HReturn(abstractValueDomain, value, |
| _sourceInformationBuilder.buildReturn(functionNode))) |
| .addSuccessor(graph.exit); |
| } |
| // TODO(sra): Handle JS-interop methods. |
| closeFunction(); |
| } |
| |
| void addImplicitInstantiation(DartType type) { |
| if (type != null) { |
| currentImplicitInstantiations.add(type); |
| } |
| } |
| |
| void removeImplicitInstantiation(DartType type) { |
| if (type != null) { |
| currentImplicitInstantiations.removeLast(); |
| } |
| } |
| |
| void openFunction(MemberEntity member, |
| {ir.FunctionNode functionNode, TargetChecks checks}) { |
| // TODO(sra): Pass from all sites. |
| checks ??= TargetChecks.dynamicChecks; |
| |
| Map<Local, AbstractValue> parameterMap = <Local, AbstractValue>{}; |
| if (functionNode != null) { |
| void handleParameter(ir.VariableDeclaration node) { |
| Local local = localsMap.getLocalVariable(node); |
| parameterMap[local] = |
| _typeInferenceMap.getInferredTypeOfParameter(local); |
| } |
| |
| functionNode.positionalParameters.forEach(handleParameter); |
| functionNode.namedParameters.toList() |
| ..sort(namedOrdering) |
| ..forEach(handleParameter); |
| _returnType = _elementMap.getDartType(functionNode.returnType); |
| } |
| |
| HBasicBlock block = graph.addNewBlock(); |
| open(graph.entry); |
| |
| localsHandler.startFunction( |
| targetElement, |
| closureDataLookup.getScopeInfo(targetElement), |
| closureDataLookup.getCapturedScope(targetElement), |
| parameterMap, |
| _sourceInformationBuilder.buildDeclaration(targetElement), |
| isGenerativeConstructorBody: targetElement is ConstructorBodyEntity); |
| close(new HGoto(abstractValueDomain)).addSuccessor(block); |
| |
| open(block); |
| |
| _addClassTypeVariablesIfNeeded(member); |
| _addFunctionTypeVariablesIfNeeded(member); |
| |
| if (functionNode != null) { |
| _potentiallyAddFunctionParameterTypeChecks(functionNode, checks); |
| } |
| _insertTraceCall(member); |
| _insertCoverageCall(member); |
| } |
| |
| void closeFunction() { |
| if (!isAborted()) closeAndGotoExit(new HGoto(abstractValueDomain)); |
| graph.finalize(abstractValueDomain); |
| } |
| |
| @override |
| void defaultExpression(ir.Expression expression) { |
| // TODO(het): This is only to get tests working. |
| _trap('Unhandled ir.${expression.runtimeType} $expression'); |
| } |
| |
| @override |
| void defaultStatement(ir.Statement statement) { |
| _trap('Unhandled ir.${statement.runtimeType} $statement'); |
| pop(); |
| } |
| |
| void _trap(String message) { |
| HInstruction nullValue = graph.addConstantNull(closedWorld); |
| HInstruction errorMessage = graph.addConstantString(message, closedWorld); |
| HInstruction trap = new HForeignCode( |
| js.js.parseForeignJS("#.#"), |
| abstractValueDomain.dynamicType, |
| <HInstruction>[nullValue, errorMessage]); |
| trap.sideEffects |
| ..setAllSideEffects() |
| ..setDependsOnSomething(); |
| push(trap); |
| } |
| |
| /// Returns the current source element. This is used by the type builder. |
| /// |
| /// The returned element is a declaration element. |
| // TODO(efortuna): Update this when we implement inlining. |
| // TODO(sra): Re-implement type builder using Kernel types and the |
| // `target` for context. |
| @override |
| MemberEntity get sourceElement => _currentFrame.member; |
| |
| @override |
| void visitCheckLibraryIsLoaded(ir.CheckLibraryIsLoaded checkLoad) { |
| ImportEntity import = _elementMap.getImport(checkLoad.import); |
| String loadId = deferredLoadTask.getImportDeferName( |
| _elementMap.getSpannable(targetElement, checkLoad), import); |
| HInstruction prefixConstant = graph.addConstantString(loadId, closedWorld); |
| HInstruction uriConstant = |
| graph.addConstantString('${import.uri}', closedWorld); |
| _pushStaticInvocation( |
| _commonElements.checkDeferredIsLoaded, |
| [prefixConstant, uriConstant], |
| _typeInferenceMap |
| .getReturnTypeOf(_commonElements.checkDeferredIsLoaded), |
| const <DartType>[]); |
| } |
| |
| @override |
| void visitLoadLibrary(ir.LoadLibrary loadLibrary) { |
| String loadId = deferredLoadTask.getImportDeferName( |
| _elementMap.getSpannable(targetElement, loadLibrary), |
| _elementMap.getImport(loadLibrary.import)); |
| // TODO(efortuna): Source information! |
| push(new HInvokeStatic( |
| commonElements.loadDeferredLibrary, |
| [graph.addConstantString(loadId, closedWorld)], |
| abstractValueDomain.nonNullType, |
| const <DartType>[], |
| targetCanThrow: false)); |
| } |
| |
| @override |
| void visitBlock(ir.Block block) { |
| assert(!isAborted()); |
| if (!isReachable) return; // This can only happen when inlining. |
| for (ir.Statement statement in block.statements) { |
| statement.accept(this); |
| if (!isReachable) { |
| // The block has been aborted by a return or a throw. |
| if (stack.isNotEmpty) { |
| reporter.internalError( |
| NO_LOCATION_SPANNABLE, 'Non-empty instruction stack.'); |
| } |
| return; |
| } |
| } |
| assert(!current.isClosed()); |
| if (stack.isNotEmpty) { |
| reporter.internalError( |
| NO_LOCATION_SPANNABLE, 'Non-empty instruction stack'); |
| } |
| } |
| |
| @override |
| void visitEmptyStatement(ir.EmptyStatement node) { |
| // Empty statement adds no instructions to current block. |
| } |
| |
| @override |
| void visitExpressionStatement(ir.ExpressionStatement node) { |
| if (!isReachable) return; |
| ir.Expression expression = node.expression; |
| if (expression is ir.Throw && _inliningStack.isEmpty) { |
| _visitThrowExpression(expression.expression); |
| handleInTryStatement(); |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildThrow(node.expression); |
| closeAndGotoExit( |
| new HThrow(abstractValueDomain, pop(), sourceInformation)); |
| } else { |
| expression.accept(this); |
| pop(); |
| } |
| } |
| |
| /// Returns true if the [type] is a valid return type for an asynchronous |
| /// function. |
| /// |
| /// Asynchronous functions return a `Future`, and a valid return is thus |
| /// either dynamic, Object, or Future. |
| /// |
| /// We do not accept the internal Future implementation class. |
| bool isValidAsyncReturnType(DartType type) { |
| // TODO(sigurdm): In an internal library a function could be declared: |
| // |
| // _FutureImpl foo async => 1; |
| // |
| // This should be valid (because the actual value returned from an async |
| // function is a `_FutureImpl`), but currently false is returned in this |
| // case. |
| return type.isDynamic || |
| type == _commonElements.objectType || |
| (type is InterfaceType && type.element == _commonElements.futureClass); |
| } |
| |
| @override |
| void visitReturnStatement(ir.ReturnStatement node) { |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildReturn(node); |
| HInstruction value; |
| if (node.expression == null) { |
| value = graph.addConstantNull(closedWorld); |
| } else { |
| node.expression.accept(this); |
| value = pop(); |
| if (_currentFrame.asyncMarker == AsyncMarker.ASYNC) { |
| if (options.enableTypeAssertions && |
| !isValidAsyncReturnType(_returnType)) { |
| generateTypeError( |
| "Async function returned a Future," |
| " was declared to return a ${_returnType}.", |
| sourceInformation); |
| pop(); |
| return; |
| } |
| } else { |
| value = typeBuilder.potentiallyCheckOrTrustTypeOfAssignment( |
| value, _returnType); |
| } |
| } |
| handleInTryStatement(); |
| _emitReturn(value, sourceInformation); |
| } |
| |
| @override |
| void visitForStatement(ir.ForStatement node) { |
| assert(isReachable); |
| assert(node.body != null); |
| void buildInitializer() { |
| for (ir.VariableDeclaration declaration in node.variables) { |
| declaration.accept(this); |
| } |
| } |
| |
| HInstruction buildCondition() { |
| if (node.condition == null) { |
| return graph.addConstantBool(true, closedWorld); |
| } |
| node.condition.accept(this); |
| return popBoolified(); |
| } |
| |
| void buildUpdate() { |
| for (ir.Expression expression in node.updates) { |
| expression.accept(this); |
| assert(!isAborted()); |
| // The result of the update instruction isn't used, and can just |
| // be dropped. |
| pop(); |
| } |
| } |
| |
| void buildBody() { |
| node.body.accept(this); |
| } |
| |
| JumpTarget jumpTarget = localsMap.getJumpTargetForFor(node); |
| loopHandler.handleLoop( |
| node, |
| localsMap.getCapturedLoopScope(closureDataLookup, node), |
| jumpTarget, |
| buildInitializer, |
| buildCondition, |
| buildUpdate, |
| buildBody, |
| _sourceInformationBuilder.buildLoop(node)); |
| } |
| |
| @override |
| void visitForInStatement(ir.ForInStatement node) { |
| if (node.isAsync) { |
| _buildAsyncForIn(node); |
| } else if (_typeInferenceMap.isJsIndexableIterator( |
| node, abstractValueDomain)) { |
| // If the expression being iterated over is a JS indexable type, we can |
| // generate an optimized version of for-in that uses indexing. |
| _buildForInIndexable(node); |
| } else { |
| _buildForInIterator(node); |
| } |
| } |
| |
| /// Builds the graph for a for-in node with an indexable expression. |
| /// |
| /// In this case we build: |
| /// |
| /// int end = a.length; |
| /// for (int i = 0; |
| /// i < a.length; |
| /// checkConcurrentModificationError(a.length == end, a), ++i) { |
| /// <declaredIdentifier> = a[i]; |
| /// <body> |
| /// } |
| _buildForInIndexable(ir.ForInStatement node) { |
| SyntheticLocal indexVariable = localsHandler.createLocal('_i'); |
| |
| // These variables are shared by initializer, condition, body and update. |
| HInstruction array; // Set in buildInitializer. |
| bool isFixed; // Set in buildInitializer. |
| HInstruction originalLength = null; // Set for growable lists. |
| |
| HInstruction buildGetLength(SourceInformation sourceInformation) { |
| HGetLength result = new HGetLength( |
| array, abstractValueDomain.positiveIntType, |
| isAssignable: !isFixed) |
| ..sourceInformation = sourceInformation; |
| add(result); |
| return result; |
| } |
| |
| void buildConcurrentModificationErrorCheck() { |
| if (originalLength == null) return; |
| // The static call checkConcurrentModificationError() is expanded in |
| // codegen to: |
| // |
| // array.length == _end || throwConcurrentModificationError(array) |
| // |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildForInMoveNext(node); |
| HInstruction length = buildGetLength(sourceInformation); |
| push(new HIdentity( |
| length, originalLength, null, abstractValueDomain.boolType) |
| ..sourceInformation = sourceInformation); |
| _pushStaticInvocation( |
| _commonElements.checkConcurrentModificationError, |
| [pop(), array], |
| _typeInferenceMap.getReturnTypeOf( |
| _commonElements.checkConcurrentModificationError), |
| const <DartType>[], |
| sourceInformation: sourceInformation); |
| pop(); |
| } |
| |
| void buildInitializer() { |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildForInIterator(node); |
| |
| node.iterable.accept(this); |
| array = pop(); |
| isFixed = |
| abstractValueDomain.isFixedLengthJsIndexable(array.instructionType); |
| localsHandler.updateLocal( |
| indexVariable, graph.addConstantInt(0, closedWorld), |
| sourceInformation: sourceInformation); |
| originalLength = buildGetLength(sourceInformation); |
| } |
| |
| HInstruction buildCondition() { |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildForInMoveNext(node); |
| HInstruction index = localsHandler.readLocal(indexVariable, |
| sourceInformation: sourceInformation); |
| HInstruction length = buildGetLength(sourceInformation); |
| HInstruction compare = |
| new HLess(index, length, null, abstractValueDomain.boolType) |
| ..sourceInformation = sourceInformation; |
| add(compare); |
| return compare; |
| } |
| |
| void buildBody() { |
| // If we had mechanically inlined ArrayIterator.moveNext(), it would have |
| // inserted the ConcurrentModificationError check as part of the |
| // condition. It is not necessary on the first iteration since there is |
| // no code between calls to `get iterator` and `moveNext`, so the test is |
| // moved to the loop update. |
| |
| // Find a type for the element. Use the element type of the indexer of the |
| // array, as this is stronger than the iterator's `get current` type, for |
| // example, `get current` includes null. |
| // TODO(sra): The element type of a container type mask might be better. |
| AbstractValue type = _typeInferenceMap.inferredIndexType(node); |
| |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildForInCurrent(node); |
| HInstruction index = localsHandler.readLocal(indexVariable, |
| sourceInformation: sourceInformation); |
| HInstruction value = new HIndex(array, index, null, type) |
| ..sourceInformation = sourceInformation; |
| add(value); |
| |
| Local loopVariableLocal = localsMap.getLocalVariable(node.variable); |
| localsHandler.updateLocal(loopVariableLocal, value, |
| sourceInformation: sourceInformation); |
| // Hint to name loop value after name of loop variable. |
| if (loopVariableLocal is! SyntheticLocal) { |
| value.sourceElement ??= loopVariableLocal; |
| } |
| |
| node.body.accept(this); |
| } |
| |
| void buildUpdate() { |
| // See buildBody as to why we check here. |
| buildConcurrentModificationErrorCheck(); |
| |
| // TODO(sra): It would be slightly shorter to generate `a[i++]` in the |
| // body (and that more closely follows what an inlined iterator would do) |
| // but the code is horrible as `i+1` is carried around the loop in an |
| // additional variable. |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildForInSet(node); |
| HInstruction index = localsHandler.readLocal(indexVariable, |
| sourceInformation: sourceInformation); |
| HInstruction one = graph.addConstantInt(1, closedWorld); |
| HInstruction addInstruction = |
| new HAdd(index, one, null, abstractValueDomain.positiveIntType) |
| ..sourceInformation = sourceInformation; |
| add(addInstruction); |
| localsHandler.updateLocal(indexVariable, addInstruction, |
| sourceInformation: sourceInformation); |
| } |
| |
| loopHandler.handleLoop( |
| node, |
| localsMap.getCapturedLoopScope(closureDataLookup, node), |
| localsMap.getJumpTargetForForIn(node), |
| buildInitializer, |
| buildCondition, |
| buildUpdate, |
| buildBody, |
| _sourceInformationBuilder.buildLoop(node)); |
| } |
| |
| _buildForInIterator(ir.ForInStatement node) { |
| // Generate a structure equivalent to: |
| // Iterator<E> $iter = <iterable>.iterator; |
| // while ($iter.moveNext()) { |
| // <variable> = $iter.current; |
| // <body> |
| // } |
| |
| // The iterator is shared between initializer, condition and body. |
| HInstruction iterator; |
| |
| void buildInitializer() { |
| AbstractValue mask = _typeInferenceMap.typeOfIterator(node); |
| node.iterable.accept(this); |
| HInstruction receiver = pop(); |
| _pushDynamicInvocation( |
| node, |
| mask, |
| Selectors.iterator, |
| <HInstruction>[receiver], |
| const <DartType>[], |
| _sourceInformationBuilder.buildForInIterator(node)); |
| iterator = pop(); |
| } |
| |
| HInstruction buildCondition() { |
| AbstractValue mask = _typeInferenceMap.typeOfIteratorMoveNext(node); |
| _pushDynamicInvocation( |
| node, |
| mask, |
| Selectors.moveNext, |
| <HInstruction>[iterator], |
| const <DartType>[], |
| _sourceInformationBuilder.buildForInMoveNext(node)); |
| return popBoolified(); |
| } |
| |
| void buildBody() { |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildForInCurrent(node); |
| AbstractValue mask = _typeInferenceMap.typeOfIteratorCurrent(node); |
| _pushDynamicInvocation(node, mask, Selectors.current, [iterator], |
| const <DartType>[], sourceInformation); |
| |
| Local loopVariableLocal = localsMap.getLocalVariable(node.variable); |
| HInstruction value = typeBuilder.potentiallyCheckOrTrustTypeOfAssignment( |
| pop(), _getDartTypeIfValid(node.variable.type)); |
| localsHandler.updateLocal(loopVariableLocal, value, |
| sourceInformation: sourceInformation); |
| // Hint to name loop value after name of loop variable. |
| if (loopVariableLocal is! SyntheticLocal) { |
| value.sourceElement ??= loopVariableLocal; |
| } |
| node.body.accept(this); |
| } |
| |
| loopHandler.handleLoop( |
| node, |
| localsMap.getCapturedLoopScope(closureDataLookup, node), |
| localsMap.getJumpTargetForForIn(node), |
| buildInitializer, |
| buildCondition, |
| () {}, |
| buildBody, |
| _sourceInformationBuilder.buildLoop(node)); |
| } |
| |
| void _buildAsyncForIn(ir.ForInStatement node) { |
| // The async-for is implemented with a StreamIterator. |
| HInstruction streamIterator; |
| |
| node.iterable.accept(this); |
| |
| List<HInstruction> arguments = [pop()]; |
| ClassEntity cls = _commonElements.streamIterator; |
| DartType typeArg = _elementMap.getDartType(node.variable.type); |
| InterfaceType instanceType = |
| localsHandler.substInContext(new InterfaceType(cls, [typeArg])); |
| addImplicitInstantiation(instanceType); |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildForInIterator(node); |
| // TODO(johnniwinther): Pass type arguments to constructors like calling |
| // a generic method. |
| if (rtiNeed.classNeedsTypeArguments(cls)) { |
| _addTypeArguments(arguments, [typeArg], sourceInformation); |
| } |
| ConstructorEntity constructor = _commonElements.streamIteratorConstructor; |
| _pushStaticInvocation(constructor, arguments, |
| _typeInferenceMap.getReturnTypeOf(constructor), const <DartType>[], |
| instanceType: instanceType, sourceInformation: sourceInformation); |
| |
| streamIterator = pop(); |
| |
| void buildInitializer() {} |
| |
| HInstruction buildCondition() { |
| AbstractValue mask = _typeInferenceMap.typeOfIteratorMoveNext(node); |
| _pushDynamicInvocation( |
| node, |
| mask, |
| Selectors.moveNext, |
| [streamIterator], |
| const <DartType>[], |
| _sourceInformationBuilder.buildForInMoveNext(node)); |
| HInstruction future = pop(); |
| push(new HAwait(future, abstractValueDomain.dynamicType)); |
| return popBoolified(); |
| } |
| |
| void buildBody() { |
| AbstractValue mask = _typeInferenceMap.typeOfIteratorCurrent(node); |
| _pushDynamicInvocation( |
| node, |
| mask, |
| Selectors.current, |
| [streamIterator], |
| const <DartType>[], |
| _sourceInformationBuilder.buildForInIterator(node)); |
| localsHandler.updateLocal( |
| localsMap.getLocalVariable(node.variable), pop()); |
| node.body.accept(this); |
| } |
| |
| void buildUpdate() {} |
| |
| // Creates a synthetic try/finally block in case anything async goes amiss. |
| TryCatchFinallyBuilder tryBuilder = new TryCatchFinallyBuilder( |
| this, _sourceInformationBuilder.buildLoop(node)); |
| // Build fake try body: |
| loopHandler.handleLoop( |
| node, |
| localsMap.getCapturedLoopScope(closureDataLookup, node), |
| localsMap.getJumpTargetForForIn(node), |
| buildInitializer, |
| buildCondition, |
| buildUpdate, |
| buildBody, |
| _sourceInformationBuilder.buildLoop(node)); |
| |
| void finalizerFunction() { |
| _pushDynamicInvocation(node, null, Selectors.cancel, [streamIterator], |
| const <DartType>[], _sourceInformationBuilder.buildGeneric(node)); |
| add(new HAwait(pop(), abstractValueDomain.dynamicType)); |
| } |
| |
| tryBuilder |
| ..closeTryBody() |
| ..buildFinallyBlock(finalizerFunction) |
| ..cleanUp(); |
| } |
| |
| HInstruction callSetRuntimeTypeInfo(HInstruction typeInfo, |
| HInstruction newObject, SourceInformation sourceInformation) { |
| // Set the runtime type information on the object. |
| FunctionEntity typeInfoSetterFn = _commonElements.setRuntimeTypeInfo; |
| // TODO(efortuna): Insert source information in this static invocation. |
| _pushStaticInvocation(typeInfoSetterFn, <HInstruction>[newObject, typeInfo], |
| abstractValueDomain.dynamicType, const <DartType>[], |
| sourceInformation: sourceInformation); |
| |
| // The new object will now be referenced through the |
| // `setRuntimeTypeInfo` call. We therefore set the type of that |
| // instruction to be of the object's type. |
| assert( |
| stack.last is HInvokeStatic || stack.last == newObject, |
| failedAt( |
| CURRENT_ELEMENT_SPANNABLE, |
| "Unexpected `stack.last`: Found ${stack.last}, " |
| "expected ${newObject} or an HInvokeStatic. " |
| "State: typeInfo=$typeInfo, stack=$stack.")); |
| stack.last.instructionType = newObject.instructionType; |
| return pop(); |
| } |
| |
| @override |
| void visitWhileStatement(ir.WhileStatement node) { |
| assert(isReachable); |
| HInstruction buildCondition() { |
| node.condition.accept(this); |
| return popBoolified(); |
| } |
| |
| loopHandler.handleLoop( |
| node, |
| localsMap.getCapturedLoopScope(closureDataLookup, node), |
| localsMap.getJumpTargetForWhile(node), |
| () {}, |
| buildCondition, |
| () {}, () { |
| node.body.accept(this); |
| }, _sourceInformationBuilder.buildLoop(node)); |
| } |
| |
| @override |
| visitDoStatement(ir.DoStatement node) { |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildLoop(node); |
| // TODO(efortuna): I think this can be rewritten using |
| // LoopHandler.handleLoop with some tricks about when the "update" happens. |
| LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); |
| CapturedLoopScope loopClosureInfo = |
| localsMap.getCapturedLoopScope(closureDataLookup, node); |
| localsHandler.startLoop(loopClosureInfo, sourceInformation); |
| JumpTarget target = localsMap.getJumpTargetForDo(node); |
| JumpHandler jumpHandler = loopHandler.beginLoopHeader(node, target); |
| HLoopInformation loopInfo = current.loopInformation; |
| HBasicBlock loopEntryBlock = current; |
| HBasicBlock bodyEntryBlock = current; |
| 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(loopClosureInfo, sourceInformation); |
| node.body.accept(this); |
| |
| // 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(abstractValueDomain)); |
| } 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); |
| |
| node.condition.accept(this); |
| assert(!isAborted()); |
| HInstruction conditionInstruction = popBoolified(); |
| HBasicBlock conditionEndBlock = close(new HLoopBranch(abstractValueDomain, |
| conditionInstruction, HLoopBranch.DO_WHILE_LOOP)); |
| |
| HBasicBlock avoidCriticalEdge = addNewBlock(); |
| conditionEndBlock.addSuccessor(avoidCriticalEdge); |
| open(avoidCriticalEdge); |
| close(new HGoto(abstractValueDomain)); |
| 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(abstractValueDomain)); |
| 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, |
| sourceInformation); |
| 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 = localsMap.getJumpTargetForDo(node); |
| LabelDefinition label = |
| target.addLabel(null, 'loop', isBreakTarget: true); |
| 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( |
| abstractValueDomain, label, sourceInformation)); |
| block.remove(breakInstruction); |
| }); |
| } |
| } |
| jumpHandler.close(); |
| } |
| |
| @override |
| void visitIfStatement(ir.IfStatement node) { |
| handleIf( |
| visitCondition: () => node.condition.accept(this), |
| visitThen: () => node.then.accept(this), |
| visitElse: () => node.otherwise?.accept(this), |
| sourceInformation: _sourceInformationBuilder.buildIf(node)); |
| } |
| |
| void handleIf( |
| {ir.Node node, |
| void visitCondition(), |
| void visitThen(), |
| void visitElse(), |
| SourceInformation sourceInformation}) { |
| SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, |
| node == null ? node : _elementMap.getSpannable(targetElement, node)); |
| branchBuilder.handleIf(visitCondition, visitThen, visitElse, |
| sourceInformation: sourceInformation); |
| } |
| |
| @override |
| void visitAsExpression(ir.AsExpression node) { |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildAs(node); |
| node.operand.accept(this); |
| HInstruction expressionInstruction = pop(); |
| |
| if (node.type is ir.InvalidType) { |
| generateTypeError('invalid type', sourceInformation); |
| return; |
| } |
| |
| DartType type = _elementMap.getDartType(node.type); |
| if (!node.isTypeError || options.implicitDowncastCheckPolicy.isEmitted) { |
| HInstruction converted = typeBuilder.buildTypeConversion( |
| expressionInstruction, |
| localsHandler.substInContext(type), |
| node.isTypeError |
| ? HTypeConversion.CHECKED_MODE_CHECK |
| : HTypeConversion.CAST_TYPE_CHECK, |
| sourceInformation: sourceInformation); |
| if (converted != expressionInstruction) { |
| add(converted); |
| } |
| stack.add(converted); |
| } else { |
| stack.add(expressionInstruction); |
| } |
| } |
| |
| void generateError(FunctionEntity function, String message, |
| AbstractValue typeMask, SourceInformation sourceInformation) { |
| HInstruction errorMessage = graph.addConstantString(message, closedWorld); |
| _pushStaticInvocation( |
| function, [errorMessage], typeMask, const <DartType>[], |
| sourceInformation: sourceInformation); |
| } |
| |
| void generateTypeError(String message, SourceInformation sourceInformation) { |
| generateError( |
| _commonElements.throwTypeError, |
| message, |
| _typeInferenceMap.getReturnTypeOf(_commonElements.throwTypeError), |
| sourceInformation); |
| } |
| |
| void generateUnsupportedError( |
| String message, SourceInformation sourceInformation) { |
| generateError( |
| _commonElements.throwUnsupportedError, |
| message, |
| _typeInferenceMap |
| .getReturnTypeOf(_commonElements.throwUnsupportedError), |
| sourceInformation); |
| } |
| |
| @override |
| void visitAssertStatement(ir.AssertStatement node) { |
| if (!options.enableUserAssertions) return; |
| if (node.message == null) { |
| node.condition.accept(this); |
| _pushStaticInvocation( |
| _commonElements.assertHelper, |
| <HInstruction>[pop()], |
| _typeInferenceMap.getReturnTypeOf(_commonElements.assertHelper), |
| const <DartType>[]); |
| pop(); |
| return; |
| } |
| |
| // if (assertTest(condition)) assertThrow(message); |
| void buildCondition() { |
| node.condition.accept(this); |
| _pushStaticInvocation( |
| _commonElements.assertTest, |
| <HInstruction>[pop()], |
| _typeInferenceMap.getReturnTypeOf(_commonElements.assertTest), |
| const <DartType>[]); |
| } |
| |
| void fail() { |
| node.message.accept(this); |
| _pushStaticInvocation( |
| _commonElements.assertThrow, |
| <HInstruction>[pop()], |
| _typeInferenceMap.getReturnTypeOf(_commonElements.assertThrow), |
| const <DartType>[]); |
| pop(); |
| } |
| |
| handleIf(visitCondition: buildCondition, visitThen: fail); |
| } |
| |
| /// Creates a [JumpHandler] for a statement. The node must be a jump |
| /// target. If there are no breaks or continues targeting the statement, |
| /// a special "null handler" is returned. |
| /// |
| /// [isLoopJump] is true when the jump handler is for a loop. This is used |
| /// to distinguish the synthesized loop created for a switch statement with |
| /// continue statements from simple switch statements. |
| JumpHandler createJumpHandler(ir.TreeNode node, JumpTarget target, |
| {bool isLoopJump: false}) { |
| if (target == null) { |
| // No breaks or continues to this node. |
| return new NullJumpHandler(reporter); |
| } |
| if (isLoopJump && node is ir.SwitchStatement) { |
| return new KernelSwitchCaseJumpHandler(this, target, node, localsMap); |
| } |
| |
| return new JumpHandler(this, target); |
| } |
| |
| @override |
| void visitBreakStatement(ir.BreakStatement node) { |
| assert(!isAborted()); |
| handleInTryStatement(); |
| JumpTarget target = localsMap.getJumpTargetForBreak(node); |
| assert(target != null); |
| JumpHandler handler = jumpTargets[target]; |
| assert(handler != null); |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildGoto(node); |
| if (localsMap.generateContinueForBreak(node)) { |
| if (handler.labels.isNotEmpty) { |
| handler.generateContinue(sourceInformation, handler.labels.first); |
| } else { |
| handler.generateContinue(sourceInformation); |
| } |
| } else { |
| if (handler.labels.isNotEmpty) { |
| handler.generateBreak(sourceInformation, handler.labels.first); |
| } else { |
| handler.generateBreak(sourceInformation); |
| } |
| } |
| } |
| |
| @override |
| void visitLabeledStatement(ir.LabeledStatement node) { |
| ir.Statement body = node.body; |
| if (JumpVisitor.canBeBreakTarget(body)) { |
| // loops and switches handle breaks on their own |
| body.accept(this); |
| return; |
| } |
| JumpTarget jumpTarget = localsMap.getJumpTargetForLabel(node); |
| if (jumpTarget == null) { |
| // The label is not needed. |
| body.accept(this); |
| return; |
| } |
| |
| JumpHandler handler = createJumpHandler(node, jumpTarget); |
| |
| LocalsHandler beforeLocals = new LocalsHandler.from(localsHandler); |
| |
| HBasicBlock newBlock = openNewBlock(); |
| body.accept(this); |
| SubGraph bodyGraph = new SubGraph(newBlock, lastOpenedBlock); |
| |
| HBasicBlock joinBlock = graph.addNewBlock(); |
| List<LocalsHandler> breakHandlers = <LocalsHandler>[]; |
| handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) { |
| breakInstruction.block.addSuccessor(joinBlock); |
| breakHandlers.add(locals); |
| }); |
| |
| if (!isAborted()) { |
| goto(current, joinBlock); |
| breakHandlers.add(localsHandler); |
| } |
| |
| open(joinBlock); |
| localsHandler = beforeLocals.mergeMultiple(breakHandlers, joinBlock); |
| |
| // There was at least one reachable break, so the label is needed. |
| newBlock.setBlockFlow( |
| new HLabeledBlockInformation( |
| new HSubGraphBlockInformation(bodyGraph), handler.labels), |
| joinBlock); |
| handler.close(); |
| } |
| |
| /// Loop through the cases in a switch and create a mapping of case |
| /// expressions to constants. |
| Map<ir.Expression, ConstantValue> _buildSwitchCaseConstants( |
| ir.SwitchStatement switchStatement) { |
| Map<ir.Expression, ConstantValue> constants = |
| new Map<ir.Expression, ConstantValue>(); |
| for (ir.SwitchCase switchCase in switchStatement.cases) { |
| for (ir.Expression caseExpression in switchCase.expressions) { |
| ConstantValue constant = _elementMap.getConstantValue(caseExpression); |
| constants[caseExpression] = constant; |
| } |
| } |
| return constants; |
| } |
| |
| @override |
| void visitContinueSwitchStatement(ir.ContinueSwitchStatement node) { |
| handleInTryStatement(); |
| JumpTarget target = localsMap.getJumpTargetForContinueSwitch(node); |
| assert(target != null); |
| JumpHandler handler = jumpTargets[target]; |
| assert(handler != null); |
| assert(target.labels.isNotEmpty); |
| handler.generateContinue( |
| _sourceInformationBuilder.buildGoto(node), target.labels.first); |
| } |
| |
| @override |
| void visitSwitchStatement(ir.SwitchStatement node) { |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildSwitch(node); |
| // The switch case indices must match those computed in |
| // [KernelSwitchCaseJumpHandler]. |
| bool hasContinue = false; |
| Map<ir.SwitchCase, int> caseIndex = new Map<ir.SwitchCase, int>(); |
| int switchIndex = 1; |
| bool hasDefault = false; |
| for (ir.SwitchCase switchCase in node.cases) { |
| if (_isDefaultCase(switchCase)) { |
| hasDefault = true; |
| } |
| if (SwitchContinueAnalysis.containsContinue(switchCase.body)) { |
| hasContinue = true; |
| } |
| caseIndex[switchCase] = switchIndex; |
| switchIndex++; |
| } |
| |
| JumpHandler jumpHandler = |
| createJumpHandler(node, localsMap.getJumpTargetForSwitch(node)); |
| if (!hasContinue) { |
| // If the switch statement has no switch cases targeted by continue |
| // statements we encode the switch statement directly. |
| _buildSimpleSwitchStatement(node, jumpHandler, sourceInformation); |
| } else { |
| _buildComplexSwitchStatement( |
| node, jumpHandler, caseIndex, hasDefault, sourceInformation); |
| } |
| } |
| |
| /// Helper for building switch statements. |
| static bool _isDefaultCase(ir.SwitchCase switchCase) => |
| switchCase == null || switchCase.isDefault; |
| |
| /// Helper for building switch statements. |
| HInstruction _buildExpression(ir.SwitchStatement switchStatement) { |
| switchStatement.expression.accept(this); |
| return pop(); |
| } |
| |
| /// Helper method for creating the list of constants that make up the |
| /// switch case branches. |
| List<ConstantValue> _getSwitchConstants( |
| ir.SwitchStatement parentSwitch, ir.SwitchCase switchCase) { |
| Map<ir.Expression, ConstantValue> constantsLookup = |
| _buildSwitchCaseConstants(parentSwitch); |
| List<ConstantValue> constantList = <ConstantValue>[]; |
| if (switchCase != null) { |
| for (var expression in switchCase.expressions) { |
| constantList.add(constantsLookup[expression]); |
| } |
| } |
| return constantList; |
| } |
| |
| /// Builds a simple switch statement which does not handle uses of continue |
| /// statements to labeled switch cases. |
| void _buildSimpleSwitchStatement(ir.SwitchStatement switchStatement, |
| JumpHandler jumpHandler, SourceInformation sourceInformation) { |
| void buildSwitchCase(ir.SwitchCase switchCase) { |
| switchCase.body.accept(this); |
| } |
| |
| _handleSwitch( |
| switchStatement, |
| jumpHandler, |
| _buildExpression, |
| switchStatement.cases, |
| _getSwitchConstants, |
| _isDefaultCase, |
| buildSwitchCase, |
| sourceInformation); |
| jumpHandler.close(); |
| } |
| |
| /// Builds a switch statement that can handle arbitrary uses of continue |
| /// statements to labeled switch cases. |
| void _buildComplexSwitchStatement( |
| ir.SwitchStatement switchStatement, |
| JumpHandler jumpHandler, |
| Map<ir.SwitchCase, int> caseIndex, |
| bool hasDefault, |
| SourceInformation sourceInformation) { |
| // If the switch statement has switch cases targeted by continue |
| // statements we create the following encoding: |
| // |
| // switch (e) { |
| // l_1: case e0: s_1; break; |
| // l_2: case e1: s_2; continue l_i; |
| // ... |
| // l_n: default: s_n; continue l_j; |
| // } |
| // |
| // is encoded as |
| // |
| // var target; |
| // switch (e) { |
| // case e1: target = 1; break; |
| // case e2: target = 2; break; |
| // ... |
| // default: target = n; break; |
| // } |
| // l: while (true) { |
| // switch (target) { |
| // case 1: s_1; break l; |
| // case 2: s_2; target = i; continue l; |
| // ... |
| // case n: s_n; target = j; continue l; |
| // } |
| // } |
| // |
| // This is because JS does not have this same "continue label" semantics so |
| // we encode it in the form of a state machine. |
| |
| JumpTarget switchTarget = localsMap.getJumpTargetForSwitch(switchStatement); |
| localsHandler.updateLocal(switchTarget, graph.addConstantNull(closedWorld)); |
| |
| var switchCases = switchStatement.cases; |
| if (!hasDefault) { |
| // Use null as the marker for a synthetic default clause. |
| // The synthetic default is added because otherwise there would be no |
| // good place to give a default value to the local. |
| switchCases = new List<ir.SwitchCase>.from(switchCases); |
| switchCases.add(null); |
| } |
| |
| void buildSwitchCase(ir.SwitchCase switchCase) { |
| SourceInformation caseSourceInformation = sourceInformation; |
| if (switchCase != null) { |
| caseSourceInformation = _sourceInformationBuilder.buildGoto(switchCase); |
| // Generate 'target = i; break;' for switch case i. |
| int index = caseIndex[switchCase]; |
| HInstruction value = graph.addConstantInt(index, closedWorld); |
| localsHandler.updateLocal(switchTarget, value, |
| sourceInformation: caseSourceInformation); |
| } else { |
| // Generate synthetic default case 'target = null; break;'. |
| HInstruction nullValue = graph.addConstantNull(closedWorld); |
| localsHandler.updateLocal(switchTarget, nullValue, |
| sourceInformation: caseSourceInformation); |
| } |
| jumpTargets[switchTarget].generateBreak(caseSourceInformation); |
| } |
| |
| _handleSwitch( |
| switchStatement, |
| jumpHandler, |
| _buildExpression, |
| switchCases, |
| _getSwitchConstants, |
| _isDefaultCase, |
| buildSwitchCase, |
| sourceInformation); |
| jumpHandler.close(); |
| |
| HInstruction buildCondition() => graph.addConstantBool(true, closedWorld); |
| |
| void buildSwitch() { |
| HInstruction buildExpression(ir.SwitchStatement notUsed) { |
| return localsHandler.readLocal(switchTarget); |
| } |
| |
| List<ConstantValue> getConstants( |
| ir.SwitchStatement parentSwitch, ir.SwitchCase switchCase) { |
| return <ConstantValue>[ |
| constantSystem.createIntFromInt(caseIndex[switchCase]) |
| ]; |
| } |
| |
| void buildSwitchCase(ir.SwitchCase switchCase) { |
| switchCase.body.accept(this); |
| if (!isAborted()) { |
| // Ensure that we break the loop if the case falls through. (This |
| // is only possible for the last case.) |
| jumpTargets[switchTarget].generateBreak(sourceInformation); |
| } |
| } |
| |
| // Pass a [NullJumpHandler] because the target for the contained break |
| // is not the generated switch statement but instead the loop generated |
| // in the call to [handleLoop] below. |
| _handleSwitch( |
| switchStatement, // nor is buildExpression. |
| new NullJumpHandler(reporter), |
| buildExpression, |
| switchStatement.cases, |
| getConstants, |
| (_) => false, // No case is default. |
| buildSwitchCase, |
| sourceInformation); |
| } |
| |
| void buildLoop() { |
| loopHandler.handleLoop( |
| switchStatement, |
| localsMap.getCapturedLoopScope(closureDataLookup, switchStatement), |
| switchTarget, |
| () {}, |
| buildCondition, |
| () {}, |
| buildSwitch, |
| _sourceInformationBuilder.buildLoop(switchStatement)); |
| } |
| |
| if (hasDefault) { |
| buildLoop(); |
| } else { |
| // If the switch statement has no default case, surround the loop with |
| // a test of the target. So: |
| // `if (target) while (true) ...` If there's no default case, target is |
| // null, so we don't drop into the while loop. |
| void buildCondition() { |
| js.Template code = js.js.parseForeignJS('#'); |
| push(new HForeignCode(code, abstractValueDomain.boolType, |
| [localsHandler.readLocal(switchTarget)], |
| nativeBehavior: native.NativeBehavior.PURE)); |
| } |
| |
| handleIf( |
| node: switchStatement, |
| visitCondition: buildCondition, |
| visitThen: buildLoop, |
| visitElse: () => {}, |
| sourceInformation: sourceInformation); |
| } |
| } |
| |
| /// Creates a switch statement. |
| /// |
| /// [jumpHandler] is the [JumpHandler] for the created switch statement. |
| /// [buildSwitchCase] creates the statements for the switch case. |
| void _handleSwitch( |
| ir.SwitchStatement switchStatement, |
| JumpHandler jumpHandler, |
| HInstruction buildExpression(ir.SwitchStatement statement), |
| List<ir.SwitchCase> switchCases, |
| List<ConstantValue> getConstants( |
| ir.SwitchStatement parentSwitch, ir.SwitchCase switchCase), |
| bool isDefaultCase(ir.SwitchCase switchCase), |
| void buildSwitchCase(ir.SwitchCase switchCase), |
| SourceInformation sourceInformation) { |
| HBasicBlock expressionStart = openNewBlock(); |
| HInstruction expression = buildExpression(switchStatement); |
| |
| if (switchCases.isEmpty) { |
| return; |
| } |
| |
| HSwitch switchInstruction = |
| new HSwitch(abstractValueDomain, <HInstruction>[expression]); |
| HBasicBlock expressionEnd = close(switchInstruction); |
| LocalsHandler savedLocals = localsHandler; |
| |
| List<HStatementInformation> statements = <HStatementInformation>[]; |
| bool hasDefault = false; |
| for (ir.SwitchCase switchCase in switchCases) { |
| HBasicBlock block = graph.addNewBlock(); |
| for (ConstantValue constant |
| in getConstants(switchStatement, switchCase)) { |
| HConstant hConstant = graph.addConstant(constant, closedWorld); |
| switchInstruction.inputs.add(hConstant); |
| hConstant.usedBy.add(switchInstruction); |
| expressionEnd.addSuccessor(block); |
| } |
| |
| if (isDefaultCase(switchCase)) { |
| // An HSwitch has n inputs and n+1 successors, the last being the |
| // default case. |
| expressionEnd.addSuccessor(block); |
| hasDefault = true; |
| } |
| open(block); |
| localsHandler = new LocalsHandler.from(savedLocals); |
| buildSwitchCase(switchCase); |
| if (!isAborted() && |
| // TODO(johnniwinther): Reinsert this if `isReachable` is no longer |
| // set to `false` when `_tryInlineMethod` sees an always throwing |
| // method. |
| //switchCase == switchCases.last && |
| !isDefaultCase(switchCase)) { |
| // If there is no default, we will add one later to avoid |
| // the critical edge. So we generate a break statement to make |
| // sure the last case does not fall through to the default case. |
| jumpHandler.generateBreak(sourceInformation); |
| } |
| statements.add( |
| new HSubGraphBlockInformation(new SubGraph(block, lastOpenedBlock))); |
| } |
| |
| // Add a join-block if necessary. |
| // We create [joinBlock] early, and then go through the cases that might |
| // want to jump to it. In each case, if we add [joinBlock] as a successor |
| // of another block, we also add an element to [caseHandlers] that is used |
| // to create the phis in [joinBlock]. |
| // If we never jump to the join block, [caseHandlers] will stay empty, and |
| // the join block is never added to the graph. |
| HBasicBlock joinBlock = new HBasicBlock(); |
| List<LocalsHandler> caseHandlers = <LocalsHandler>[]; |
| jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) { |
| instruction.block.addSuccessor(joinBlock); |
| caseHandlers.add(locals); |
| }); |
| jumpHandler.forEachContinue((HContinue instruction, LocalsHandler locals) { |
| assert( |
| false, |
| failedAt(_elementMap.getSpannable(targetElement, switchStatement), |
| 'Continue cannot target a switch.')); |
| }); |
| if (!isAborted()) { |
| current.close(new HGoto(abstractValueDomain)); |
| lastOpenedBlock.addSuccessor(joinBlock); |
| caseHandlers.add(localsHandler); |
| } |
| if (!hasDefault) { |
| // Always create a default case, to avoid a critical edge in the |
| // graph. |
| HBasicBlock defaultCase = addNewBlock(); |
| expressionEnd.addSuccessor(defaultCase); |
| open(defaultCase); |
| close(new HGoto(abstractValueDomain)); |
| defaultCase.addSuccessor(joinBlock); |
| caseHandlers.add(savedLocals); |
| statements.add(new HSubGraphBlockInformation( |
| new SubGraph(defaultCase, defaultCase))); |
| } |
| assert(caseHandlers.length == joinBlock.predecessors.length); |
| if (caseHandlers.length != 0) { |
| graph.addBlock(joinBlock); |
| open(joinBlock); |
| if (caseHandlers.length == 1) { |
| localsHandler = caseHandlers[0]; |
| } else { |
| localsHandler = savedLocals.mergeMultiple(caseHandlers, joinBlock); |
| } |
| } else { |
| // The joinblock is not used. |
| joinBlock = null; |
| } |
| |
| HSubExpressionBlockInformation expressionInfo = |
| new HSubExpressionBlockInformation( |
| new SubExpression(expressionStart, expressionEnd)); |
| expressionStart.setBlockFlow( |
| new HSwitchBlockInformation(expressionInfo, statements, |
| jumpHandler.target, jumpHandler.labels, sourceInformation), |
| joinBlock); |
| |
| jumpHandler.close(); |
| } |
| |
| @override |
| void visitConditionalExpression(ir.ConditionalExpression node) { |
| SsaBranchBuilder brancher = new SsaBranchBuilder(this); |
| brancher.handleConditional(() => node.condition.accept(this), |
| () => node.then.accept(this), () => node.otherwise.accept(this)); |
| } |
| |
| @override |
| void visitLogicalExpression(ir.LogicalExpression node) { |
| SsaBranchBuilder brancher = new SsaBranchBuilder(this); |
| String operator = node.operator; |
| // ir.LogicalExpression claims to allow '??' as an operator but currently |
| // that is expanded into a let-tree. |
| assert(operator == '&&' || operator == '||'); |
| _handleLogicalExpression(node.left, () => node.right.accept(this), brancher, |
| operator, _sourceInformationBuilder.buildBinary(node)); |
| } |
| |
| /// Optimizes logical binary expression where the left has the same logical |
| /// binary operator. |
| /// |
| /// 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)`: |
| /// |
| void _handleLogicalExpression( |
| ir.Expression left, |
| void visitRight(), |
| SsaBranchBuilder brancher, |
| String operator, |
| SourceInformation sourceInformation) { |
| if (left is ir.LogicalExpression && left.operator == operator) { |
| ir.Expression innerLeft = left.left; |
| ir.Expression middle = left.right; |
| _handleLogicalExpression( |
| innerLeft, |
| () => _handleLogicalExpression(middle, visitRight, brancher, operator, |
| _sourceInformationBuilder.buildBinary(middle)), |
| brancher, |
| operator, |
| sourceInformation); |
| } else { |
| brancher.handleLogicalBinary( |
| () => left.accept(this), visitRight, sourceInformation, |
| isAnd: operator == '&&'); |
| } |
| } |
| |
| @override |
| void visitIntLiteral(ir.IntLiteral node) { |
| stack.add(graph.addConstantIntAsUnsigned(node.value, closedWorld)); |
| } |
| |
| @override |
| void visitDoubleLiteral(ir.DoubleLiteral node) { |
| stack.add(graph.addConstantDouble(node.value, closedWorld)); |
| } |
| |
| @override |
| void visitBoolLiteral(ir.BoolLiteral node) { |
| stack.add(graph.addConstantBool(node.value, closedWorld)); |
| } |
| |
| @override |
| void visitStringLiteral(ir.StringLiteral node) { |
| stack.add(graph.addConstantString(node.value, closedWorld)); |
| } |
| |
| @override |
| void visitSymbolLiteral(ir.SymbolLiteral node) { |
| stack.add( |
| graph.addConstant(_elementMap.getConstantValue(node), closedWorld)); |
| registry?.registerConstSymbol(node.value); |
| } |
| |
| @override |
| void visitNullLiteral(ir.NullLiteral node) { |
| stack.add(graph.addConstantNull(closedWorld)); |
| } |
| |
| /// Set the runtime type information if necessary. |
| HInstruction _setListRuntimeTypeInfoIfNeeded(HInstruction object, |
| InterfaceType type, SourceInformation sourceInformation) { |
| if (!rtiNeed.classNeedsTypeArguments(type.element) || type.treatAsRaw) { |
| return object; |
| } |
| List<HInstruction> arguments = <HInstruction>[]; |
| for (DartType argument in type.typeArguments) { |
| arguments.add(typeBuilder.analyzeTypeArgument(argument, sourceElement)); |
| } |
| // TODO(15489): Register at codegen. |
| registry?.registerInstantiation(type); |
| return callSetRuntimeTypeInfoWithTypeArguments( |
| type, arguments, object, sourceInformation); |
| } |
| |
| @override |
| void visitListLiteral(ir.ListLiteral node) { |
| HInstruction listInstruction; |
| if (node.isConst) { |
| listInstruction = |
| graph.addConstant(_elementMap.getConstantValue(node), closedWorld); |
| } else { |
| List<HInstruction> elements = <HInstruction>[]; |
| for (ir.Expression element in node.expressions) { |
| element.accept(this); |
| elements.add(pop()); |
| } |
| listInstruction = buildLiteralList(elements); |
| add(listInstruction); |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildListLiteral(node); |
| InterfaceType type = localsHandler.substInContext( |
| _commonElements.listType(_elementMap.getDartType(node.typeArgument))); |
| listInstruction = _setListRuntimeTypeInfoIfNeeded( |
| listInstruction, type, sourceInformation); |
| } |
| |
| AbstractValue type = _typeInferenceMap.typeOfListLiteral( |
| targetElement, node, abstractValueDomain); |
| if (!abstractValueDomain.containsAll(type)) { |
| listInstruction.instructionType = type; |
| } |
| stack.add(listInstruction); |
| } |
| |
| @override |
| void visitMapLiteral(ir.MapLiteral node) { |
| if (node.isConst) { |
| stack.add( |
| graph.addConstant(_elementMap.getConstantValue(node), closedWorld)); |
| return; |
| } |
| |
| // The map literal constructors take the key-value pairs as a List |
| List<HInstruction> constructorArgs = <HInstruction>[]; |
| for (ir.MapEntry mapEntry in node.entries) { |
| mapEntry.accept(this); |
| constructorArgs.add(pop()); |
| constructorArgs.add(pop()); |
| } |
| |
| // The constructor is a procedure because it's a factory. |
| FunctionEntity constructor; |
| List<HInstruction> inputs = <HInstruction>[]; |
| if (constructorArgs.isEmpty) { |
| constructor = _commonElements.mapLiteralConstructorEmpty; |
| } else { |
| constructor = _commonElements.mapLiteralConstructor; |
| HLiteralList argList = buildLiteralList(constructorArgs); |
| add(argList); |
| inputs.add(argList); |
| } |
| |
| assert( |
| constructor is ConstructorEntity && constructor.isFactoryConstructor); |
| |
| InterfaceType type = localsHandler.substInContext(_commonElements.mapType( |
| _elementMap.getDartType(node.keyType), |
| _elementMap.getDartType(node.valueType))); |
| ClassEntity cls = constructor.enclosingClass; |
| |
| if (rtiNeed.classNeedsTypeArguments(cls)) { |
| List<HInstruction> typeInputs = <HInstruction>[]; |
| type.typeArguments.forEach((DartType argument) { |
| typeInputs |
| .add(typeBuilder.analyzeTypeArgument(argument, sourceElement)); |
| }); |
| |
| // We lift this common call pattern into a helper function to save space |
| // in the output. |
| if (typeInputs |
| .every((HInstruction input) => input.isNull(abstractValueDomain))) { |
| if (constructorArgs.isEmpty) { |
| constructor = _commonElements.mapLiteralUntypedEmptyMaker; |
| } else { |
| constructor = _commonElements.mapLiteralUntypedMaker; |
| } |
| } else { |
| inputs.addAll(typeInputs); |
| } |
| } |
| |
| // If runtime type information is needed and the map literal has no type |
| // parameters, 'constructor' is a static function that forwards the call to |
| // the factory constructor without type parameters. |
| assert(constructor.isFunction || |
| (constructor is ConstructorEntity && constructor.isFactoryConstructor)); |
| |
| // The instruction type will always be a subtype of the mapLiteralClass, but |
| // type inference might discover a more specific type, or find nothing (in |
| // dart2js unit tests). |
| |
| AbstractValue mapType = abstractValueDomain |
| .createNonNullSubtype(_commonElements.mapLiteralClass); |
| AbstractValue returnTypeMask = |
| _typeInferenceMap.getReturnTypeOf(constructor); |
| AbstractValue instructionType = |
| abstractValueDomain.intersection(mapType, returnTypeMask); |
| |
| addImplicitInstantiation(type); |
| _pushStaticInvocation( |
| constructor, inputs, instructionType, const <DartType>[]); |
| removeImplicitInstantiation(type); |
| } |
| |
| @override |
| void visitMapEntry(ir.MapEntry mapEntry) { |
| // Visit value before the key because each will push an expression to the |
| // stack, so when we pop them off, the key is popped first, then the value. |
| mapEntry.value.accept(this); |
| mapEntry.key.accept(this); |
| } |
| |
| @override |
| void visitTypeLiteral(ir.TypeLiteral node) { |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildGet(node); |
| ir.DartType type = node.type; |
| if (type is ir.InterfaceType || |
| type is ir.DynamicType || |
| type is ir.TypedefType || |
| type is ir.FunctionType) { |
| ConstantValue constant = _elementMap.getConstantValue(node); |
| stack.add(graph.addConstant(constant, closedWorld, |
| sourceInformation: sourceInformation)); |
| return; |
| } |
| assert( |
| type is ir.TypeParameterType, |
| failedAt( |
| CURRENT_ELEMENT_SPANNABLE, "Unexpected type literal ${node}.")); |
| // For other types (e.g. TypeParameterType, function types from expanded |
| // typedefs), look-up or construct a reified type representation and convert |
| // to a RuntimeType. |
| |
| DartType dartType = _elementMap.getDartType(type); |
| dartType = localsHandler.substInContext(dartType); |
| HInstruction value = typeBuilder.analyzeTypeArgument( |
| dartType, sourceElement, |
| sourceInformation: sourceInformation); |
| _pushStaticInvocation( |
| _commonElements.runtimeTypeToString, |
| <HInstruction>[value], |
| abstractValueDomain.stringType, |
| const <DartType>[], |
| sourceInformation: sourceInformation); |
| _pushStaticInvocation( |
| _commonElements.createRuntimeType, |
| <HInstruction>[pop()], |
| _typeInferenceMap.getReturnTypeOf(_commonElements.createRuntimeType), |
| const <DartType>[], |
| sourceInformation: sourceInformation); |
| } |
| |
| @override |
| void visitStaticGet(ir.StaticGet node) { |
| ir.Member staticTarget = node.target; |
| SourceInformation sourceInformation = |
| _sourceInformationBuilder.buildGet(node); |
| if (staticTarget is ir.Procedure && |
| staticTarget.kind == ir.ProcedureKind.Getter) { |
| FunctionEntity getter = _elementMap.getMember(staticTarget); |
| // Invoke the getter |
| _pushStaticInvocation(getter, const <HInstruction>[], |
| _typeInferenceMap.getReturnTypeOf(getter), const <DartType>[], |
| sourceInformation: sourceInformation); |
| } else if (staticTarget is ir.Field) { |
| FieldEntity field = _elementMap.getField(staticTarget); |
| ConstantValue value = _elementMap.getFieldConstantValue(field); |
| if (value != null) { |
| if (!field.isAssignable) { |
| var unit = compiler.backend.outputUnitData.outputUnitForMember(field); |
| // TODO(sigmund): this is not equivalent to what the old FE does: if |
| // there is no prefix the old FE wouldn't treat this in any special |
| // way. Also, if the prefix points to a constant in the main output |
| // unit, the old FE would still generate a deferred wrapper here. |
| if (!compiler.backend.outputUnitData |
| .hasOnlyNonDeferredImportPaths(targetElement, field)) { |
| stack.add(graph.addDeferredConstant( |
| value, unit, sourceInformation, compiler, closedWorld)); |
| } else { |
| stack.add(graph.addConstant(value, closedWorld, |
| sourceInformation: sourceInformation)); |
| } |
| } else { |
| push(new HStatic(field, _typeInferenceMap.getInferredTypeOf(field), |
| sourceInformation)); |
| } |
| } else { |
| push(new HLazyStatic(field, _typeInferenceMap.getInferredTypeOf(field), |
| sourceInformation)); |
| } |
| } else { |
| MemberEntity member = _elementMap.getMember(staticTarget); |
| push(new HStatic(member, _typeInferenceMap.getInferredTypeOf(member), |
| sourceInformation)); |
| } |
| } |
| |
| @override |
| void visitStaticSet(ir.StaticSet node) { |
| node.value.accept(this); |
| HInstruction value = pop(); |
| |
| ir.Member staticTarget = node.target; |
| if (staticTarget is ir.Procedure) { |
| FunctionEntity setter = _elementMap.getMember(staticTarget); |
| // Invoke the setter |
| _pushStaticInvocation(setter, <HInstruction>[value], |
| _typeInferenceMap.getReturnTypeOf(setter), const <DartType>[]); |
| pop(); |
| } else { |
| add(new HStaticStore( |
| abstractValueDomain, |
| _elementMap.getMember(staticTarget), |
| typeBuilder.potentiallyCheckOrTrustTypeOfAssignment( |
| value, _getDartTypeIfValid(staticTarget.setterType)))); |
| } |
| stack.add(value); |
| } |
| |
| @override |
| void visitPropertyGet(ir.PropertyGet node) { |
| node.receiver.accept(this); |
| HInstruction receiver = pop(); |
| |
| _pushDynamicInvocation( |
| node, |
| _typeInferenceMap.receiverTypeOfGet(node), |
| new Selector.getter(_elementMap.getName(node.name)), |
| <HInstruction>[receiver], |
| const <DartType>[], |
| _sourceInformationBuilder.buildGet(node)); |
| } |
| |
| @override |
| void visitVariableGet(ir.VariableGet node) { |
| ir.VariableDeclaration variable = node.variable; |
| HInstruction letBinding = letBindings[variable]; |
| if (letBinding != null) { |
| stack.add(letBinding); |
| return; |
| } |
| |
| Local local = localsMap.getLocalVariable(node.variable); |
| stack.add(localsHandler.readLocal(local, |
| sourceInformation: _sourceInformationBuilder.buildGet(node))); |
| } |
| |
| @override |
| void visitPropertySet(ir.PropertySet node) { |
| node.receiver.accept(this); |
| HInstruction receiver = pop(); |
| node.value.accept(this); |
| HInstruction value = pop(); |
| |
| _pushDynamicInvocation( |
| node, |
| _typeInferenceMap.receiverTypeOfSet(node, abstractValueDomain), |
| new Selector.setter(_elementMap.getName(node.name)), |
| <HInstruction>[receiver, value], |
| const <DartType>[], |
| _sourceInformationBuilder.buildAssignment(node)); |
| |
| pop(); |
| stack.add(value); |
| } |
| |
| @override |
| void visitDirectPropertyGet(ir.DirectPropertyGet node) { |
| node.receiver.accept(this); |
| HInstruction receiver = pop(); |
| |
| // Fake direct call with a dynamic call. |
| // TODO(sra): Implement direct invocations properly. |
| _pushDynamicInvocation( |
| node, |
| _typeInferenceMap.receiverTypeOfDirectGet(node), |
| new Selector.getter(_elementMap.getMember(node.target).memberName), |
| <HInstruction>[receiver], |
| const <DartType>[], |
|