| // 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. | |
| // ignore: implementation_imports | |
| import 'package:_js_interop_checks/src/js_interop.dart' | |
| show getDartJSInteropJSName; | |
| // ignore: implementation_imports | |
| import 'package:front_end/src/api_prototype/external_effect.dart' | |
| as ir | |
| show ExternalEffect; | |
| // ignore: implementation_imports | |
| import 'package:front_end/src/api_prototype/static_weak_references.dart' | |
| as ir | |
| show StaticWeakReferences; | |
| import 'package:js_runtime/synced/embedded_names.dart'; | |
| import 'package:js_shared/synced/embedded_names.dart' | |
| show JsBuiltin, JsGetName, TYPES; | |
| import 'package:kernel/ast.dart' as ir; | |
| import 'package:kernel/type_environment.dart' as ir; | |
| import '../closure.dart'; | |
| import '../common.dart'; | |
| import '../common/codegen.dart' show CodegenRegistry; | |
| import '../common/elements.dart'; | |
| import '../common/names.dart'; | |
| import '../constants/constant_system.dart' as constant_system; | |
| import '../constants/values.dart'; | |
| import '../deferred_load/output_unit.dart' show OutputUnit; | |
| import '../dump_info.dart'; | |
| import '../elements/entities.dart'; | |
| import '../elements/jumps.dart'; | |
| import '../elements/names.dart'; | |
| import '../elements/types.dart'; | |
| import '../inferrer/abstract_value_domain.dart'; | |
| import '../inferrer/types.dart'; | |
| import '../io/source_information.dart'; | |
| import '../ir/util.dart'; | |
| import '../js/js.dart' as js; | |
| import '../js_backend/backend.dart' show FunctionInlineCache; | |
| import '../js_backend/field_analysis.dart' | |
| show FieldAnalysisData, JFieldAnalysis; | |
| import '../js_backend/inferred_data.dart'; | |
| import '../js_backend/interceptor_data.dart'; | |
| import '../js_backend/namer.dart' show ModularNamer; | |
| import '../js_backend/native_data.dart'; | |
| import '../js_backend/runtime_types_resolution.dart'; | |
| import '../js_emitter/code_emitter_task.dart' show ModularEmitter; | |
| import '../js_model/class_type_variable_access.dart'; | |
| import '../js_model/element_map.dart'; | |
| import '../js_model/elements.dart' show JGeneratorBody, JParameterStub; | |
| import '../js_model/js_strategy.dart'; | |
| import '../js_model/js_world.dart' show JClosedWorld; | |
| import '../js_model/locals.dart' show GlobalLocalsMap, JumpVisitor; | |
| import '../js_model/records.dart' show RecordData, JRecordGetter; | |
| import '../js_model/type_recipe.dart'; | |
| import '../kernel/invocation_mirror.dart'; | |
| import '../native/behavior.dart'; | |
| import '../native/js.dart'; | |
| import '../options.dart'; | |
| import '../tracer.dart'; | |
| import '../universe/call_structure.dart'; | |
| import '../universe/feature.dart'; | |
| import '../universe/record_shape.dart'; | |
| import '../universe/selector.dart'; | |
| import '../universe/target_checks.dart' show TargetChecks; | |
| import '../universe/use.dart' show ConstantUse, StaticUse, TypeUse; | |
| import 'branch_builder.dart'; | |
| import 'jump_handler.dart'; | |
| import 'locals_handler.dart'; | |
| import 'loop_handler.dart'; | |
| import 'metrics.dart'; | |
| import 'nodes.dart'; | |
| import 'string_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; | |
| // [ir.Let] and [ir.LocalInitializer] bindings. | |
| final Map<ir.Variable, HInstruction> letBindings; | |
| final KernelToTypeInferenceMap typeInferenceMap; | |
| final SourceInformationBuilder sourceInformationBuilder; | |
| final ir.StaticTypeContext? staticTypeContext; | |
| StackFrame( | |
| this.parent, | |
| this.member, | |
| this.asyncMarker, | |
| this.localsMap, | |
| this.letBindings, | |
| this.typeInferenceMap, | |
| this.sourceInformationBuilder, | |
| this.staticTypeContext, | |
| ); | |
| } | |
| class KernelSsaGraphBuilder extends ir.VisitorDefault<void> | |
| with ir.VisitorVoidMixin { | |
| /// Holds the resulting SSA graph. | |
| final HGraph graph = HGraph(); | |
| /// True if the builder is processing nodes inside a try statement. This is | |
| /// important for generating control flow out of a try block like returns or | |
| /// breaks. | |
| bool _inTryStatement = false; | |
| /// Used to track the locals while building the graph. | |
| late LocalsHandler localsHandler; | |
| /// A stack of instructions. | |
| /// | |
| /// We build the SSA graph by simulating a stack machine. | |
| List<HInstruction> stack = []; | |
| /// The count of nested loops we are currently building. | |
| /// | |
| /// The loop nesting is consulted when inlining a function invocation. The | |
| /// inlining heuristics take this information into account. | |
| int loopDepth = 0; | |
| /// A mapping from jump targets to their handlers. | |
| Map<JumpTarget, JumpHandler> jumpTargets = {}; | |
| final CompilerOptions options; | |
| final DiagnosticReporter reporter; | |
| final ModularEmitter _emitter; | |
| final ModularNamer _namer; | |
| final MemberEntity targetElement; | |
| final MemberEntity _initialTargetElement; | |
| final JClosedWorld closedWorld; | |
| final CodegenRegistry registry; | |
| final ClosureData _closureDataLookup; | |
| final RecordData _recordData; | |
| final Tracer _tracer; | |
| /// 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 = []; | |
| /// Used to report information about inlining (which occurs while building the | |
| /// SSA graph), when dump-info is enabled. | |
| final InfoReporter? _infoReporter; | |
| final SsaMetrics _metrics; | |
| HInstruction? _rethrowableException; | |
| final SourceInformationStrategy _sourceInformationStrategy; | |
| final JsToElementMap _elementMap; | |
| final GlobalTypeInferenceResults globalInferenceResults; | |
| late final LoopHandler _loopHandler; | |
| late final TypeBuilder _typeBuilder; | |
| /// 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 = []; | |
| Local? _returnLocal; | |
| DartType? _returnType; | |
| StackFrame? _currentFrame; | |
| final FunctionInlineCache _inlineCache; | |
| final InlineDataCache _inlineDataCache; | |
| KernelSsaGraphBuilder( | |
| this.options, | |
| this.reporter, | |
| this._initialTargetElement, | |
| InterfaceType? instanceType, | |
| this._infoReporter, | |
| this._metrics, | |
| this._elementMap, | |
| this.globalInferenceResults, | |
| this.closedWorld, | |
| this.registry, | |
| this._namer, | |
| this._emitter, | |
| this._tracer, | |
| this._sourceInformationStrategy, | |
| this._inlineCache, | |
| this._inlineDataCache, | |
| ) : targetElement = _effectiveTargetElementFor(_initialTargetElement), | |
| _closureDataLookup = closedWorld.closureDataLookup, | |
| _recordData = closedWorld.recordData { | |
| _enterFrame(targetElement, null); | |
| _loopHandler = KernelLoopHandler(this); | |
| _typeBuilder = KernelTypeBuilder(this, _elementMap); | |
| graph.element = targetElement; | |
| graph.sourceInformation = _sourceInformationBuilder | |
| .buildVariableDeclaration(); | |
| localsHandler = LocalsHandler( | |
| this, | |
| targetElement, | |
| targetElement, | |
| instanceType, | |
| _nativeData, | |
| _interceptorData, | |
| ); | |
| } | |
| KernelToLocalsMap get _localsMap => _currentFrame!.localsMap; | |
| Map<ir.Variable, HInstruction> get _letBindings => _currentFrame!.letBindings; | |
| JCommonElements get _commonElements => _elementMap.commonElements; | |
| JElementEnvironment get _elementEnvironment => _elementMap.elementEnvironment; | |
| JFieldAnalysis get _fieldAnalysis => closedWorld.fieldAnalysis; | |
| KernelToTypeInferenceMap get _typeInferenceMap => | |
| _currentFrame!.typeInferenceMap; | |
| SourceInformationBuilder get _sourceInformationBuilder => | |
| _currentFrame!.sourceInformationBuilder; | |
| AbstractValueDomain get _abstractValueDomain => | |
| closedWorld.abstractValueDomain; | |
| NativeData get _nativeData => closedWorld.nativeData; | |
| InterceptorData get _interceptorData => closedWorld.interceptorData; | |
| RuntimeTypesNeed get _rtiNeed => closedWorld.rtiNeed; | |
| GlobalLocalsMap get _globalLocalsMap => | |
| globalInferenceResults.globalLocalsMap; | |
| InferredData get _inferredData => globalInferenceResults.inferredData; | |
| DartTypes get dartTypes => closedWorld.dartTypes; | |
| void push(HInstruction instruction) { | |
| add(instruction); | |
| stack.add(instruction); | |
| } | |
| HInstruction pop() { | |
| return stack.removeLast(); | |
| } | |
| /// Pushes a boolean checking [expression] against null. | |
| void pushCheckNull(HInstruction expression) { | |
| push( | |
| HIdentity( | |
| expression, | |
| graph.addConstantNull(closedWorld), | |
| _abstractValueDomain.boolType, | |
| ), | |
| ); | |
| } | |
| HBasicBlock? _current; | |
| /// The current block to add instructions to. Might be null, if we are | |
| /// visiting dead code, but see [_isReachable]. | |
| HBasicBlock? get current => _current; | |
| set current(HBasicBlock? c) { | |
| _isReachable = c != null; | |
| _current = c; | |
| } | |
| /// The most recently opened block. Has the same value as [current] while | |
| /// the block is open, but unlike [current], it isn't cleared when the | |
| /// current block is closed. | |
| late HBasicBlock lastOpenedBlock; | |
| /// Indicates whether the current block is dead (because it has a throw or a | |
| /// return further up). If this is false, then [current] may be null. If the | |
| /// block is dead then it may also be aborted, but for simplicity we only | |
| /// abort on statement boundaries, not in the middle of expressions. See | |
| /// [isAborted]. | |
| bool _isReachable = true; | |
| /// Is the current statement or expression nested in a [ir.BlockExpression]? | |
| bool _inBlockExpression = false; | |
| HLocalValue? lastAddedParameter; | |
| Map<Local, HInstruction> parameters = {}; | |
| late Set<Local> elidedParameters; | |
| HBasicBlock addNewBlock() { | |
| HBasicBlock block = graph.addNewBlock(); | |
| // If adding a new block during building of an expression, it is due to | |
| // conditional expressions or short-circuit logical operators. | |
| return block; | |
| } | |
| void open(HBasicBlock block) { | |
| block.open(); | |
| current = block; | |
| lastOpenedBlock = block; | |
| } | |
| HBasicBlock close(HControlFlow end) { | |
| HBasicBlock result = current!; | |
| current!.close(end); | |
| current = null; | |
| return result; | |
| } | |
| HBasicBlock _closeAndGotoExit(HControlFlow end) { | |
| HBasicBlock result = current!; | |
| current!.close(end); | |
| current = null; | |
| result.addSuccessor(graph.exit); | |
| return result; | |
| } | |
| void goto(HBasicBlock from, HBasicBlock to) { | |
| from.close(HGoto()); | |
| from.addSuccessor(to); | |
| } | |
| void _prepareEntryBlock() { | |
| HBasicBlock block = graph.addNewBlock(); | |
| // Create `graph.entry` as an initially empty block. `graph.entry` is | |
| // treated specially (holding parameters, local variables and constants) | |
| // but cannot receive constants before it has been closed. By closing it | |
| // here, we can use constants in the code that sets up the function. | |
| open(graph.entry); | |
| close(HGoto()).addSuccessor(block); | |
| open(block); | |
| } | |
| bool isAborted() { | |
| return current == null; | |
| } | |
| /// Creates a new block, transitions to it from any current block, and | |
| /// opens the new block. | |
| HBasicBlock openNewBlock() { | |
| HBasicBlock newBlock = addNewBlock(); | |
| if (!isAborted()) goto(current!, newBlock); | |
| open(newBlock); | |
| return newBlock; | |
| } | |
| void add(HInstruction instruction) { | |
| current!.add(instruction); | |
| } | |
| HLocalValue addParameter( | |
| Entity? parameter, | |
| AbstractValue type, { | |
| bool isElided = false, | |
| }) { | |
| HLocalValue result = isElided | |
| ? HLocalValue(parameter, type) | |
| : HParameterValue(parameter, type); | |
| if (lastAddedParameter == null) { | |
| graph.entry.addBefore(graph.entry.first, result); | |
| } else { | |
| graph.entry.addAfter(lastAddedParameter, result); | |
| } | |
| lastAddedParameter = result; | |
| return result; | |
| } | |
| HSubGraphBlockInformation? wrapStatementGraph(SubGraph? statements) { | |
| if (statements == null) return null; | |
| return HSubGraphBlockInformation(statements); | |
| } | |
| HExpressionInformation? wrapExpressionGraph(SubExpression? expression) { | |
| if (expression == null) return null; | |
| return HSubExpressionBlockInformation(expression); | |
| } | |
| HLiteralList _buildLiteralList(List<HInstruction> inputs) { | |
| return HLiteralList(inputs, _abstractValueDomain.growableListType); | |
| } | |
| /// Called when control flow is about to change, in which case we need to | |
| /// specify special successors if we are already in a try/catch/finally block. | |
| void _handleInTryStatement() { | |
| if (!_inTryStatement) return; | |
| HBasicBlock block = close(HExitTry()); | |
| HBasicBlock newBlock = graph.addNewBlock(); | |
| block.addSuccessor(newBlock); | |
| open(newBlock); | |
| } | |
| DartType _getStaticType(ir.Expression node) { | |
| // TODO(johnniwinther): Substitute the type by the this type and type | |
| // arguments of the current frame. | |
| ir.DartType type = node.getStaticType(_currentFrame!.staticTypeContext!); | |
| return _elementMap.getDartType(type); | |
| } | |
| DartType _getStaticForInIteratorType(ir.ForInStatement node) { | |
| // TODO(johnniwinther): Substitute the type by the this type and type | |
| // arguments of the current frame. | |
| ir.DartType type = node.getIteratorType(_currentFrame!.staticTypeContext!); | |
| return _elementMap.getDartType(type); | |
| } | |
| DartType _getStaticForInElementType(ir.ForInStatement node) { | |
| // TODO(johnniwinther): Substitute the type by the this type and type | |
| // arguments of the current frame. | |
| ir.DartType type = node.getElementType(_currentFrame!.staticTypeContext!); | |
| return _elementMap.getDartType(type); | |
| } | |
| static MemberEntity _effectiveTargetElementFor(MemberEntity member) { | |
| if (member is JGeneratorBody) return member.function; | |
| return member; | |
| } | |
| void _enterFrame( | |
| MemberEntity member, | |
| SourceInformation? callSourceInformation, | |
| ) { | |
| AsyncMarker asyncMarker = AsyncMarker.sync; | |
| final function = getFunctionNode(_elementMap, member); | |
| if (function != null) { | |
| asyncMarker = getAsyncMarker(function); | |
| } | |
| final elementMap = closedWorld.elementMap; | |
| final memberNode = elementMap.getMemberContextNode(member); | |
| _currentFrame = StackFrame( | |
| _currentFrame, | |
| member, | |
| asyncMarker, | |
| _globalLocalsMap.getLocalsMap(member), | |
| {}, | |
| KernelToTypeInferenceMapImpl(member, globalInferenceResults), | |
| _currentFrame != null | |
| ? _currentFrame!.sourceInformationBuilder.forContext( | |
| member, | |
| callSourceInformation, | |
| ) | |
| : _sourceInformationStrategy.createBuilderForContext(member), | |
| memberNode != null | |
| ? ir.StaticTypeContext( | |
| memberNode, | |
| elementMap.typeEnvironment, | |
| cache: ir.StaticTypeCacheImpl(), | |
| ) | |
| : null, | |
| ); | |
| } | |
| 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) { | |
| // Skip interop extension type object literal constructors as | |
| // that's handled within `visitStaticInvocation`. | |
| // TODO(54968): We should handle the lowering for object literal | |
| // constructors in the interop transformer somehow instead and | |
| // avoid assuming all such members are object literal constructors | |
| // or otherwise paying the cost to verify by indexing extension | |
| // types. | |
| final isObjectLiteralConstructor = | |
| target.isExtensionTypeMember && | |
| target.function.namedParameters.isNotEmpty; | |
| if (isObjectLiteralConstructor) return null; | |
| _buildExternalFunctionNode( | |
| targetElement as FunctionEntity, | |
| _ensureDefaultArgumentValues(target.function), | |
| ); | |
| } else { | |
| _buildFunctionNode( | |
| targetElement as FunctionEntity, | |
| _ensureDefaultArgumentValues(target.function), | |
| ); | |
| } | |
| } else if (target is ir.Field) { | |
| FieldAnalysisData fieldData = closedWorld.fieldAnalysis | |
| .getFieldData(targetElement as FieldEntity); | |
| if (fieldData.initialValue != null) { | |
| registry.registerConstantUse( | |
| ConstantUse.init(fieldData.initialValue!), | |
| ); | |
| if (targetElement.isStatic || targetElement.isTopLevel) { | |
| /// No code is created for this field: All references inline the | |
| /// constant value. | |
| return null; | |
| } | |
| } else if (fieldData.isLazy) { | |
| registry.registerStaticUse( | |
| StaticUse.staticInvoke( | |
| closedWorld.commonElements.throwLateFieldADI, | |
| CallStructure.oneArg, | |
| ), | |
| ); | |
| } | |
| if (targetElement.isInstanceMember) { | |
| if (fieldData.isEffectivelyFinal || | |
| !closedWorld.annotationsData | |
| .getParameterCheckPolicy(targetElement) | |
| .isEmitted) { | |
| // No need for a checked setter. | |
| return null; | |
| } | |
| } | |
| if (targetElement.isInstanceMember) { | |
| _buildInstanceFieldSetter(target); | |
| } else { | |
| _buildStaticFieldInitializer(target); | |
| } | |
| } else if (target is ir.LocalFunction) { | |
| _buildFunctionNode( | |
| targetElement as FunctionEntity, | |
| _ensureDefaultArgumentValues(target.function), | |
| ); | |
| } else { | |
| throw 'No case implemented to handle target: ' | |
| '$target for $targetElement'; | |
| } | |
| break; | |
| case MemberKind.constructor: | |
| final constructor = definition.node as ir.Constructor; | |
| _ensureDefaultArgumentValues(constructor.function); | |
| _buildConstructor(targetElement as ConstructorEntity, constructor); | |
| break; | |
| case MemberKind.constructorBody: | |
| final constructor = definition.node as ir.Constructor; | |
| _ensureDefaultArgumentValues(constructor.function); | |
| _buildConstructorBody(constructor); | |
| break; | |
| case MemberKind.closureField: | |
| // Closure fields have no setter and therefore never require any code. | |
| return null; | |
| case MemberKind.signature: | |
| ir.Node target = definition.node; | |
| ir.FunctionNode originalClosureNode; | |
| if (target is ir.Procedure) { | |
| originalClosureNode = target.function; | |
| } else if (target is ir.LocalFunction) { | |
| originalClosureNode = target.function; | |
| } else { | |
| failedAt( | |
| targetElement, | |
| "Unexpected function signature: " | |
| "$targetElement inside a non-closure: $target", | |
| ); | |
| } | |
| _buildMethodSignature(originalClosureNode); | |
| break; | |
| case MemberKind.parameterStub: | |
| _buildParameterStub( | |
| _initialTargetElement as JParameterStub, | |
| _functionNodeOf(definition.node)!, | |
| ); | |
| case MemberKind.generatorBody: | |
| _buildGeneratorBody( | |
| _initialTargetElement as JGeneratorBody, | |
| _functionNodeOf(definition.node)!, | |
| ); | |
| break; | |
| case MemberKind.recordGetter: | |
| _buildRecordGetter( | |
| _initialTargetElement as JRecordGetter, | |
| definition as RecordGetterDefinition, | |
| ); | |
| break; | |
| } | |
| assert(graph.isValid(), "Invalid graph for $_initialTargetElement."); | |
| if (_tracer.isEnabled) { | |
| MemberEntity member = _initialTargetElement; | |
| String name = member.name ?? '<null>'; | |
| if (member.isInstanceMember || | |
| member is ConstructorEntity || | |
| member.isStatic) { | |
| name = "${member.enclosingClass!.name}.$name"; | |
| if (definition.kind == MemberKind.constructorBody) { | |
| name += " (body)"; | |
| } | |
| } | |
| _tracer.traceCompilation(name); | |
| _tracer.traceGraph('builder', graph); | |
| } | |
| return graph; | |
| }); | |
| } | |
| ir.FunctionNode? _functionNodeOf(ir.Node node) { | |
| if (node is ir.Member) return node.function; | |
| if (node is ir.LocalFunction) 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.FunctionParameter node) { | |
| final constantValue = _elementMap.getConstantValue( | |
| node.defaultValue, | |
| implicitNull: true, | |
| ); | |
| assert( | |
| constantValue != null, | |
| failedAt( | |
| _elementMap.getMethod(function.parent as ir.Procedure), | |
| 'No constant computed for $node', | |
| ), | |
| ); | |
| registry.registerConstantUse(ConstantUse.init(constantValue!)); | |
| } | |
| function.positionalParameters | |
| .skip(function.requiredParameterCount) | |
| .forEach(registerDefaultValue); | |
| function.namedParameters.forEach(registerDefaultValue); | |
| return function; | |
| } | |
| void _buildInstanceFieldSetter(ir.Field node) { | |
| assert(!node.isStatic); | |
| FieldEntity field = _elementMap.getMember(node) as FieldEntity; | |
| _openFunction(field, checks: TargetChecks.none); | |
| 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 = 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); | |
| DartType type = _getDartTypeIfValid(node.type); | |
| HInstruction value = _typeBuilder.potentiallyCheckOrTrustTypeOfParameter( | |
| field, | |
| parameter, | |
| type, | |
| ); | |
| // TODO(sra): Pass source information to | |
| // [potentiallyCheckOrTrustTypeOfParameter]. | |
| // TODO(sra): The source information should indicate the field and | |
| // possibly its type but not the initializer. | |
| value.sourceInformation ??= _sourceInformationBuilder.buildSet(node); | |
| if (!_fieldAnalysis.getFieldData(field).isElided) { | |
| add(HFieldSet(field, thisInstruction, value)); | |
| } | |
| _closeFunction(); | |
| } | |
| void _buildStaticFieldInitializer(ir.Field node) { | |
| assert(node.isStatic); | |
| graph.isLazyInitializer = true; | |
| FieldEntity field = _elementMap.getMember(node) as FieldEntity; | |
| _openFunction(field, checks: TargetChecks.none); | |
| if (node.initializer != null) { | |
| node.initializer!.accept(this); | |
| HInstruction fieldValue = pop(); | |
| HInstruction checkInstruction = _typeBuilder | |
| .potentiallyCheckOrTrustTypeOfAssignment( | |
| field, | |
| fieldValue, | |
| _getDartTypeIfValid(node.type), | |
| ); | |
| stack.add(checkInstruction); | |
| } else { | |
| stack.add(graph.addConstantNull(closedWorld)); | |
| } | |
| HInstruction value = pop(); | |
| _closeAndGotoExit( | |
| HReturn(value, _sourceInformationBuilder.buildReturn(node)), | |
| ); | |
| _closeFunction(); | |
| } | |
| DartType _getDartTypeIfValid(ir.DartType type) { | |
| if (type is ir.InvalidType) return dartTypes.dynamicType(); | |
| return _elementMap.getDartType(type); | |
| } | |
| /// 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 is! ConstructorEntity && member is! ConstructorBodyEntity) { | |
| return; | |
| } | |
| ClassEntity cls = member.enclosingClass!; | |
| InterfaceType thisType = _elementEnvironment.getThisType(cls); | |
| if (thisType.typeArguments.isEmpty) { | |
| return; | |
| } | |
| bool needsTypeArguments = _rtiNeed.classNeedsTypeArguments(cls); | |
| for (var typeVariable in thisType.typeArguments) { | |
| final typeVariableType = typeVariable as 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; | |
| } | |
| } | |
| /// Extend current method parameters with parameters for the function type | |
| /// variables. | |
| void _addFunctionTypeVariablesIfNeeded(MemberEntity member) { | |
| if (member is! FunctionEntity) return; | |
| FunctionEntity function = member; | |
| List<TypeVariableType> typeVariables = _elementEnvironment | |
| .getFunctionTypeVariables(function); | |
| if (typeVariables.isEmpty) { | |
| return; | |
| } | |
| bool needsTypeArguments = _rtiNeed.methodNeedsTypeArguments(function); | |
| bool elideTypeParameters = function.parameterStructure.typeParameters == 0; | |
| for (TypeVariableType typeVariable in typeVariables) { | |
| HInstruction param; | |
| bool erased = false; | |
| if (elideTypeParameters) { | |
| // Add elided type parameters. | |
| param = _computeTypeArgumentDefaultValue(function, typeVariable); | |
| erased = true; | |
| } else if (needsTypeArguments) { | |
| param = addParameter( | |
| typeVariable.element, | |
| _abstractValueDomain.nonNullType, | |
| ); | |
| } else { | |
| // Unused, so bind to bound. | |
| param = _computeTypeArgumentDefaultValue(function, typeVariable); | |
| erased = true; | |
| } | |
| Local local = localsHandler.getTypeVariableAsLocal(typeVariable); | |
| localsHandler.directLocals[local] = param; | |
| if (!erased) { | |
| _functionTypeParameterLocals.add(local); | |
| } | |
| } | |
| } | |
| // Locals for function type parameters that can be forwarded, in argument | |
| // position order. | |
| final List<Local> _functionTypeParameterLocals = []; | |
| /// 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) { | |
| final sourceInformation = _sourceInformationBuilder.buildCreate(node); | |
| ClassEntity cls = constructor.enclosingClass; | |
| if (_inliningStack.isEmpty) { | |
| _openFunction( | |
| constructor, | |
| functionNode: node.function, | |
| parameterStructure: constructor.parameterStructure, | |
| checks: TargetChecks.none, | |
| ); | |
| } | |
| // [constructorData.fieldValues] accumulates the field initializer values, | |
| // which may be overwritten by initializer-list initializers. | |
| ConstructorData constructorData = ConstructorData(); | |
| _buildInitializers(node, constructorData); | |
| List<HInstruction> constructorArguments = []; | |
| // 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 = _elementEnvironment.getThisType(cls); | |
| List<FieldEntity> fields = []; | |
| _elementEnvironment.forEachInstanceField(cls, ( | |
| ClassEntity enclosingClass, | |
| FieldEntity member, | |
| ) { | |
| HInstruction? value = constructorData.fieldValues[member]; | |
| FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(member); | |
| if (value == null) { | |
| assert( | |
| fieldData.isInitializedInAllocator || | |
| isCustomElement || | |
| reporter.hasReportedError, | |
| 'No initializer value for field $member', | |
| ); | |
| } else { | |
| if (!fieldData.isElided) { | |
| fields.add(member); | |
| DartType type = _elementEnvironment.getFieldType(member); | |
| type = localsHandler.substInContext(type); | |
| constructorArguments.add( | |
| _typeBuilder.potentiallyCheckOrTrustTypeOfAssignment( | |
| member, | |
| value, | |
| type, | |
| ), | |
| ); | |
| } | |
| } | |
| }); | |
| _addImplicitInstantiation(thisType); | |
| List<InterfaceType> instantiatedTypes = List<InterfaceType>.of( | |
| _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. | |
| newObject = HNullCheck( | |
| newObject, | |
| _abstractValueDomain.excludeNull(newObject.instructionType), | |
| )..sourceInformation = sourceInformation; | |
| add(newObject); | |
| for (int i = 0; i < fields.length; i++) { | |
| final value = constructorArguments[i]; | |
| add(HFieldSet(fields[i], newObject, value)); | |
| } | |
| } else { | |
| // Create the runtime type information, if needed. | |
| bool needsTypeArguments = closedWorld.rtiNeed.classNeedsTypeArguments( | |
| cls, | |
| ); | |
| if (needsTypeArguments) { | |
| InterfaceType thisType = _elementEnvironment.getThisType(cls); | |
| HInstruction typeArgument = _typeBuilder.analyzeTypeArgument( | |
| thisType, | |
| sourceElement, | |
| sourceInformation: sourceInformation, | |
| ); | |
| constructorArguments.add(typeArgument); | |
| } | |
| newObject = 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 = []; | |
| if (isCustomElement) { | |
| if (interceptor == null) { | |
| ConstantValue constant = 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, | |
| _sourceInformationBuilder.buildCall(body, body), | |
| () { | |
| final constructorBody = _elementMap.getConstructorBody(body); | |
| void handleParameter(ir.Variable node, {required bool isElided}) { | |
| if (isElided) return; | |
| 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. | |
| forEachOrderedParameter( | |
| _elementMap, | |
| constructorBody, | |
| handleParameter, | |
| ); | |
| // If there are locals that escape (i.e. mutated in closures), we pass the | |
| // box to the constructor. | |
| CapturedScope scopeData = _closureDataLookup.getCapturedScope( | |
| constructorBody, | |
| ); | |
| if (scopeData.requiresContextBox) { | |
| bodyCallInputs.add(localsHandler.readLocal(scopeData.contextBox!)); | |
| } | |
| // Pass type arguments. | |
| ClassEntity inlinedConstructorClass = constructorBody.enclosingClass!; | |
| if (closedWorld.rtiNeed.classNeedsTypeArguments( | |
| inlinedConstructorClass, | |
| )) { | |
| InterfaceType thisType = _elementEnvironment.getThisType( | |
| inlinedConstructorClass, | |
| ); | |
| for (DartType typeVariable in thisType.typeArguments) { | |
| DartType result = localsHandler.substInContext(typeVariable); | |
| HInstruction argument = _typeBuilder.analyzeTypeArgument( | |
| result, | |
| sourceElement, | |
| ); | |
| bodyCallInputs.add(argument); | |
| } | |
| } | |
| if (!isCustomElement && // TODO(13836): Fix inlining. | |
| _tryInlineMethod( | |
| constructorBody, | |
| null, | |
| null, | |
| bodyCallInputs, | |
| null, | |
| node, | |
| sourceInformation, | |
| )) { | |
| pop(); | |
| } else { | |
| _invokeConstructorBody( | |
| body, | |
| bodyCallInputs, | |
| _sourceInformationBuilder.buildDeclaration(constructor), | |
| ); | |
| } | |
| }, | |
| ); | |
| } | |
| if (_inliningStack.isEmpty) { | |
| _closeAndGotoExit(HReturn(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 = HInvokeConstructorBody( | |
| constructorBody as ConstructorBodyEntity, | |
| inputs, | |
| _abstractValueDomain.nonNullType, | |
| sourceInformation, | |
| ); | |
| add(invoke); | |
| } | |
| /// Sets context for generating code that is the result of inlining | |
| /// [inlinedTarget]. | |
| void _inlinedFrom( | |
| MemberEntity inlinedTarget, | |
| SourceInformation? callSourceInformation, | |
| void Function() f, | |
| ) { | |
| reporter.withCurrentElement(inlinedTarget, () { | |
| _enterFrame(inlinedTarget, callSourceInformation); | |
| f(); | |
| _leaveFrame(); | |
| }); | |
| } | |
| 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 = _elementEnvironment.getThisType(enclosingClass); | |
| final type = localsHandler.substInContext(thisType) as InterfaceType; | |
| List<DartType> arguments = type.typeArguments; | |
| List<DartType> typeVariables = thisType.typeArguments; | |
| assert(arguments.length == typeVariables.length); | |
| Iterator<DartType> variables = typeVariables.iterator; | |
| for (var argument in type.typeArguments) { | |
| variables.moveNext(); | |
| final typeVariable = variables.current as TypeVariableType; | |
| 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); | |
| _elementEnvironment.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 as ir.Field; | |
| break; | |
| case MemberKind.constructor: | |
| case MemberKind.constructorBody: | |
| case MemberKind.closureCall: | |
| case MemberKind.closureField: | |
| case MemberKind.signature: | |
| case MemberKind.generatorBody: | |
| case MemberKind.recordGetter: | |
| case MemberKind.parameterStub: | |
| failedAt(field, "Unexpected member definition $definition."); | |
| } | |
| bool ignoreAllocatorAnalysis = false; | |
| if (_nativeData.isNativeOrExtendsNative(cls)) { | |
| // @Native classes have 'fields' which are really getters/setter. Do | |
| // not try to initialize e.g. 'tagName'. | |
| if (_nativeData.isNativeClass(cls)) return; | |
| // Fields that survive this test are fields of custom elements. | |
| ignoreAllocatorAnalysis = true; | |
| } | |
| if (ignoreAllocatorAnalysis || | |
| !_fieldAnalysis.getFieldData(field).isInitializedInAllocator) { | |
| final initializer = node.initializer; | |
| if (initializer == null) { | |
| constructorData.fieldValues[field] = graph.addConstantNull( | |
| closedWorld, | |
| ); | |
| } else { | |
| // 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. | |
| _inlinedFrom( | |
| field, | |
| _sourceInformationBuilder.buildAssignment(initializer), | |
| () { | |
| 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) { | |
| FieldEntity field = _elementMap.getField(initializer.field); | |
| if (!_fieldAnalysis.getFieldData(field).isInitializedInAllocator) { | |
| initializer.value.accept(this); | |
| constructorData.fieldValues[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.Variable 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) { | |
| initializer.statement.accept(this); | |
| } 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.isExternal || | |
| 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, | |
| ) { | |
| List<HInstruction> builtArguments = []; | |
| var positionalIndex = 0; | |
| for (var parameter in function.positionalParameters) { | |
| if (positionalIndex < arguments.positional.length) { | |
| arguments.positional[positionalIndex++].accept(this); | |
| builtArguments.add(pop()); | |
| } else { | |
| builtArguments.add(_defaultValueForParameter(parameter)); | |
| } | |
| } | |
| // Evaluate named arguments in given order. | |
| Map<String, HInstruction> namedArguments = _visitNamedArguments(arguments); | |
| // And add them to `builtArguments` in calling-convention order. | |
| function.namedParameters.toList() | |
| ..sort(namedOrdering) | |
| ..forEach((ir.NamedParameter parameter) { | |
| var argument = namedArguments[parameter.parameterName]; | |
| argument ??= _defaultValueForParameter(parameter); | |
| builtArguments.add(argument); | |
| }); | |
| 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, | |
| ) { | |
| ir.Constructor superOrRedirectConstructor = initializer.target; | |
| List<HInstruction> 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, | |
| ) { | |
| ir.Constructor target = initializer.target; | |
| List<HInstruction> arguments = _normalizeAndBuildArguments( | |
| target.function, | |
| initializer.arguments, | |
| ); | |
| ir.Class callerClass = caller.enclosingClass; | |
| ir.Supertype supertype = callerClass.supertype!; | |
| // 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!; | |
| } | |
| _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); | |
| final oldScopeMember = localsHandler.scopeMember; | |
| _inlinedFrom( | |
| element, | |
| _sourceInformationBuilder.buildCall(initializer, initializer), | |
| () { | |
| void handleParameter(ir.FunctionParameter 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. | |
| localsHandler.setupScope(element); | |
| localsHandler.enterScope( | |
| _closureDataLookup.getCapturedScope(element), | |
| _sourceInformationBuilder.buildDeclaration(element), | |
| ); | |
| _buildInitializers(constructor, constructorData); | |
| }, | |
| ); | |
| localsHandler.setupScope(oldScopeMember); | |
| } | |
| /// Constructs a special signature function for a closure. | |
| void _buildMethodSignature(ir.FunctionNode originalClosureNode) { | |
| // The signature function has no corresponding ir.Node, so we just use the | |
| // targetElement to set up the type environment. | |
| _openFunction(targetElement, checks: TargetChecks.none); | |
| FunctionType functionType = _elementMap.getFunctionType( | |
| originalClosureNode, | |
| ); | |
| HInstruction rti = _typeBuilder.analyzeTypeArgument( | |
| functionType, | |
| sourceElement, | |
| ); | |
| close( | |
| HReturn(rti, _sourceInformationBuilder.buildReturn(originalClosureNode)), | |
| ).addSuccessor(graph.exit); | |
| _closeFunction(); | |
| } | |
| /// Constructs a parameter stub based on the signature of [stubMember]. | |
| void _buildParameterStub( | |
| JParameterStub stubMember, | |
| ir.FunctionNode targetFunctionNode, | |
| ) { | |
| final stubTarget = stubMember.target; | |
| final stubParameterStructure = stubMember.parameterStructure; | |
| _openFunction( | |
| stubMember, | |
| functionNode: targetFunctionNode, | |
| parameterStructure: stubParameterStructure, | |
| checks: TargetChecks.none, | |
| ); | |
| final sourceInformation = _sourceInformationBuilder.buildStub( | |
| stubTarget, | |
| stubParameterStructure.callStructure, | |
| ); | |
| final isIntercepted = closedWorld.interceptorData.isInterceptedMethod( | |
| stubMember, | |
| ); | |
| final isNativeOrJsInterop = closedWorld.nativeData.hasFixedBackendName( | |
| stubTarget, | |
| ); | |
| List<HInstruction> invokeInputs = []; | |
| if (stubMember.isInstanceMember) { | |
| if (isIntercepted) { | |
| // Intercepted instance members need to include the receiver parameter | |
| // in the set of inputs. | |
| final receiverParameter = graph.explicitReceiverParameter!; | |
| final interceptor = _interceptorFor( | |
| receiverParameter, | |
| sourceInformation, | |
| ); | |
| if (!isNativeOrJsInterop) { | |
| // Intercepted stubbed native calls are invoked directly on the | |
| // receiver rather than forwarding to another interceptor. | |
| invokeInputs.add(interceptor); | |
| } | |
| invokeInputs.add(receiverParameter); | |
| } else { | |
| // Non-intercepted instance member calls use `this` as the receiver. | |
| // Use `graph.thisInstruction` which accounts for `this` being different | |
| // in closures. | |
| invokeInputs.add(graph.thisInstruction!); | |
| } | |
| } | |
| final stubNamedParameters = stubParameterStructure.requiredNamedParameters; | |
| final parameterLocals = parameters.keys.toList(); | |
| int? indexOfLastOptionalArgumentInParameters = | |
| invokeInputs.length + stubParameterStructure.positionalParameters; | |
| void updateLocalIfNecessary(DartType type, Local local) { | |
| if (!isNativeOrJsInterop) return; | |
| type = type.withoutNullability; | |
| if (type is FunctionType) { | |
| push( | |
| HInvokeStatic( | |
| _commonElements.closureConverter, | |
| [ | |
| localsHandler.readLocal(local), | |
| graph.addConstantInt(type.parameterTypes.length, closedWorld), | |
| ], | |
| _abstractValueDomain.functionType, | |
| const [], | |
| targetCanThrow: false, | |
| ), | |
| ); | |
| localsHandler.updateLocal( | |
| local, | |
| pop(), | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| } | |
| int count = 0; | |
| _elementEnvironment.forEachParameter(stubTarget, ( | |
| DartType type, | |
| String? name, | |
| ConstantValue? value, | |
| ) { | |
| if (count < stubParameterStructure.positionalParameters) { | |
| final local = parameterLocals[count]; | |
| updateLocalIfNecessary(type, local); | |
| invokeInputs.add(localsHandler.readLocal(local)); | |
| } else if (stubNamedParameters.contains(name)) { | |
| // The locals may not match the order of this forEach so find the right | |
| // one linearly. | |
| final local = parameterLocals.firstWhere( | |
| (parameter) => parameter.name == name, | |
| ); | |
| updateLocalIfNecessary(type, local); | |
| invokeInputs.add(localsHandler.readLocal(local)); | |
| indexOfLastOptionalArgumentInParameters = invokeInputs.length; | |
| } else if (value == null) { | |
| invokeInputs.add(graph.addConstantNull(closedWorld)); | |
| } else { | |
| final defaultValue = graph.addConstant(value, closedWorld); | |
| invokeInputs.add(defaultValue); | |
| if (!defaultValue.isConstantNull()) { | |
| indexOfLastOptionalArgumentInParameters = invokeInputs.length; | |
| } | |
| } | |
| count++; | |
| }); | |
| final targetTypeArguments = closedWorld.elementEnvironment | |
| .getFunctionTypeVariables(stubTarget); | |
| if (targetTypeArguments.isNotEmpty) { | |
| if (stubParameterStructure.typeParameters == 0) { | |
| // This stub does not include type parameters so use RTI to make the | |
| // appropriate defaults. | |
| for (final typeVariable in targetTypeArguments) { | |
| invokeInputs.add( | |
| _typeBuilder.analyzeTypeArgument(typeVariable, stubMember), | |
| ); | |
| } | |
| } else { | |
| // `_functionTypeParameterLocals` might be empty if type arguments are | |
| // elided from the target member. | |
| for (final local in _functionTypeParameterLocals) { | |
| invokeInputs.add(localsHandler.readLocal(local)); | |
| } | |
| } | |
| } | |
| // We treat the return type of all these invocations as dynamic. | |
| // Though we can know the return type of the target method, this doesn't | |
| // help us emit better code. | |
| final returnType = _abstractValueDomain.dynamicType; | |
| if (isNativeOrJsInterop) { | |
| // Native calls don't pass trailing null optional parameters. | |
| final nativeInputs = invokeInputs.sublist( | |
| 0, | |
| indexOfLastOptionalArgumentInParameters, | |
| ); | |
| push( | |
| HInvokeExternal( | |
| stubTarget, | |
| nativeInputs, | |
| returnType, | |
| closedWorld.nativeData.getNativeMethodBehavior(stubTarget), | |
| sourceInformation: sourceInformation, | |
| ), | |
| ); | |
| _maybeAddInteropNullAssertionForMember( | |
| stubTarget, | |
| nativeInputs.length, | |
| sourceInformation: sourceInformation, | |
| ); | |
| } else if (stubTarget.isInstanceMember) { | |
| if (stubTarget.enclosingClass!.isClosure) { | |
| push( | |
| HInvokeClosure( | |
| stubTarget.parameterStructure.callStructure.callSelector, | |
| _abstractValueDomain.dynamicType, | |
| invokeInputs, | |
| returnType, | |
| targetTypeArguments, | |
| ), | |
| ); | |
| } else if (stubMember.needsSuper) { | |
| push( | |
| HInvokeSuper( | |
| stubTarget, | |
| stubMember.enclosingClass!, | |
| Selector.fromElement(stubTarget), | |
| invokeInputs, | |
| isIntercepted, | |
| returnType, | |
| targetTypeArguments, | |
| sourceInformation, | |
| isSetter: false, | |
| ), | |
| ); | |
| } else { | |
| push( | |
| HInvokeDynamicMethod( | |
| Selector.fromElement(stubTarget), | |
| _abstractValueDomain.dynamicType, | |
| invokeInputs, | |
| returnType, | |
| targetTypeArguments, | |
| sourceInformation, | |
| isIntercepted: isIntercepted, | |
| )..element = stubTarget, | |
| ); | |
| } | |
| } else { | |
| push( | |
| HInvokeStatic( | |
| stubTarget, | |
| invokeInputs, | |
| returnType, | |
| targetTypeArguments, | |
| isIntercepted: isIntercepted, | |
| targetCanThrow: !_inferredData.getCannotThrow(stubTarget), | |
| ), | |
| ); | |
| } | |
| close(HReturn(pop(), sourceInformation)).addSuccessor(graph.exit); | |
| _closeFunction(); | |
| } | |
| /// Builds generative constructor body. | |
| void _buildConstructorBody(ir.Constructor constructor) { | |
| FunctionEntity constructorBody = _elementMap.getConstructorBody( | |
| constructor, | |
| ); | |
| _openFunction( | |
| constructorBody, | |
| functionNode: constructor.function, | |
| parameterStructure: constructorBody.parameterStructure, | |
| checks: TargetChecks.none, | |
| ); | |
| if (!constructorBody.isExternal) { | |
| constructor.function.body!.accept(this); | |
| } | |
| _closeFunction(); | |
| } | |
| void _buildRecordGetter( | |
| JRecordGetter getter, | |
| RecordGetterDefinition definition, | |
| ) { | |
| ClassEntity getterClass = getter.enclosingClass!; | |
| int indexInShape = definition.indexInShape; | |
| final representation = _recordData.representationForClass(getterClass)!; | |
| final path = _recordData.pathForAccess(representation.shape, indexInShape); | |
| // TODO(50081): Attribute all synthetic records code to the nearest class | |
| // declared in Dart. Worst case, it can all be attributed to the `Record` | |
| // interface. | |
| SourceInformation? sourceInformation; | |
| // Manually set up entry. This does not work... | |
| // | |
| // _openFunction(getter, checks: TargetChecks.none); | |
| // | |
| // ...since we don't have a scope model for the localsHandler. What we | |
| // should have is a lightweight localsHandler for synthetic methods. | |
| // | |
| // TODO(51310): Split [_openFunction] into parts which can be used for | |
| // synthetic methods. | |
| _prepareEntryBlock(); | |
| // Create a 'this' parameter. | |
| // | |
| // Intercepted getters have two parameters (this, receiver) and other | |
| // getters have one (this). Add them at the beginning of the entry block. | |
| final typeOfThis = _abstractValueDomain.createNonNullSubclass(getterClass); | |
| HThis thisInstruction = HThis(null, typeOfThis); | |
| graph.thisInstruction = thisInstruction; | |
| graph.entry.addAtEntry(thisInstruction); | |
| lastAddedParameter = thisInstruction; | |
| if (_interceptorData.isInterceptedMethod(getter)) { | |
| SyntheticLocal parameter = localsHandler.createLocal('receiver'); | |
| HParameterValue value = HParameterValue(parameter, typeOfThis); | |
| graph.explicitReceiverParameter = value; | |
| graph.entry.addAfter(thisInstruction, value); | |
| lastAddedParameter = value; | |
| } | |
| HInstruction receiver = thisInstruction; | |
| AbstractValue resultType = _abstractValueDomain.dynamicType; | |
| if (path.index == null) { | |
| HFieldGet fieldGet = HFieldGet( | |
| path.field, | |
| receiver, | |
| resultType, | |
| sourceInformation, | |
| isAssignable: false, | |
| ); | |
| push(fieldGet); | |
| } else { | |
| HFieldGet fieldGet = HFieldGet( | |
| path.field, | |
| receiver, | |
| _abstractValueDomain.constListType, | |
| sourceInformation, | |
| isAssignable: false, | |
| ); | |
| push(fieldGet); | |
| final list = pop(); | |
| push( | |
| HIndex( | |
| list, | |
| graph.addConstantInt(path.index!, closedWorld), | |
| resultType, | |
| ), | |
| ); | |
| } | |
| HInstruction value = pop(); | |
| _closeAndGotoExit(HReturn(value, sourceInformation)); | |
| _closeFunction(); | |
| } | |
| /// Builds an SSA graph for FunctionNodes, found in FunctionExpressions and | |
| /// Procedures. | |
| void _buildFunctionNode( | |
| FunctionEntity function, | |
| ir.FunctionNode functionNode, | |
| ) { | |
| if (functionNode.asyncMarker != ir.AsyncMarker.Sync) { | |
| if (functionNode.asyncMarker == ir.AsyncMarker.SyncStar) { | |
| _buildSyncStarGenerator(function, functionNode); | |
| } else { | |
| _buildGenerator(function, functionNode); | |
| } | |
| return; | |
| } | |
| _openFunction( | |
| function, | |
| functionNode: functionNode, | |
| parameterStructure: function.parameterStructure, | |
| checks: _checksForFunction(function), | |
| ); | |
| if (options.experimentUnreachableMethodsThrow) { | |
| var emptyParameters = parameters.values.where( | |
| (parameter) => _abstractValueDomain | |
| .isEmpty(parameter.instructionType) | |
| .isDefinitelyTrue, | |
| ); | |
| if (emptyParameters.isNotEmpty) { | |
| _addComment('$emptyParameters inferred as [empty]'); | |
| add( | |
| HInvokeStatic( | |
| _commonElements.assertUnreachableMethod, | |
| [], | |
| _abstractValueDomain.dynamicType, | |
| const [], | |
| ), | |
| ); | |
| _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( | |
| HForeignCode( | |
| js.js.statementTemplateYielding(js.Comment(text)), | |
| _abstractValueDomain.dynamicType, | |
| [], | |
| isStatement: true, | |
| ), | |
| ); | |
| } | |
| /// Builds an 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 reentrant 'state machine' code. | |
| void _buildGenerator(FunctionEntity function, ir.FunctionNode functionNode) { | |
| _openFunction( | |
| function, | |
| functionNode: functionNode, | |
| parameterStructure: function.parameterStructure, | |
| checks: _checksForFunction(function), | |
| ); | |
| // 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 = []; | |
| if (graph.thisInstruction != null) { | |
| inputs.add(graph.thisInstruction!); | |
| } | |
| if (graph.explicitReceiverParameter != null) { | |
| inputs.add(graph.explicitReceiverParameter!); | |
| } | |
| for (Local local in parameters.keys) { | |
| if (!elidedParameters.contains(local)) { | |
| 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 = _elementEnvironment.getAsyncOrSyncStarElementType( | |
| function, | |
| _returnType!, | |
| ); | |
| // TODO(sra): [elementType] can contain free type variables that are erased | |
| // due to no rtiNeed. We will get getter code if these type variables are | |
| // substituted with an <any> or <erased> type. | |
| 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); | |
| push( | |
| HInvokeGeneratorBody( | |
| body, | |
| inputs, | |
| _abstractValueDomain.dynamicType, // TODO: better type. | |
| sourceInformation, | |
| ), | |
| ); | |
| _closeAndGotoExit(HReturn(pop(), sourceInformation)); | |
| _closeFunction(); | |
| } | |
| /// Builds an SSA graph for a sync* method. A sync* method is split into an | |
| /// entry function and a body function. The entry function calls the body | |
| /// function and wraps the result in an `_SyncStarIterable<T>`. The body | |
| /// function is a separate entity (GeneratorBodyEntity) that is compiled via | |
| /// SSA and the transformed into a reentrant state-machine. | |
| /// | |
| /// Here we generate the entry function which is approximately like this: | |
| /// | |
| /// Iterable<T> foo(parameters) { | |
| /// return _makeSyncStarIterable<T>(foo$body(parameters)); | |
| /// } | |
| void _buildSyncStarGenerator( | |
| FunctionEntity function, | |
| ir.FunctionNode functionNode, | |
| ) { | |
| _openFunction( | |
| function, | |
| functionNode: functionNode, | |
| parameterStructure: function.parameterStructure, | |
| checks: _checksForFunction(function), | |
| ); | |
| // Prepare to call the body generator. | |
| // Is 'buildAsyncBody' the best location for the entry? | |
| var sourceInformation = _sourceInformationBuilder.buildAsyncBody(); | |
| // Forward all the parameters to the body. | |
| List<HInstruction> inputs = []; | |
| if (graph.thisInstruction != null) { | |
| inputs.add(graph.thisInstruction!); | |
| } | |
| if (graph.explicitReceiverParameter != null) { | |
| inputs.add(graph.explicitReceiverParameter!); | |
| } | |
| for (Local local in parameters.keys) { | |
| if (!elidedParameters.contains(local)) { | |
| inputs.add(localsHandler.readLocal(local)); | |
| } | |
| } | |
| for (Local local in _functionTypeParameterLocals) { | |
| inputs.add(localsHandler.readLocal(local)); | |
| } | |
| JGeneratorBody body = _elementMap.getGeneratorBody(function); | |
| push( | |
| HInvokeGeneratorBody( | |
| body, | |
| inputs, | |
| _abstractValueDomain.dynamicType, // Untyped JavaScript thunk. | |
| sourceInformation, | |
| ), | |
| ); | |
| // Call `_makeSyncStarIterable<T>(body)`. This usually gets inlined. | |
| final elementType = _elementEnvironment.getAsyncOrSyncStarElementType( | |
| function, | |
| _returnType!, | |
| ); | |
| FunctionEntity method = _commonElements.syncStarIterableFactory; | |
| List<HInstruction> arguments = [pop()]; | |
| List<DartType> typeArguments = const []; | |
| if (_rtiNeed.methodNeedsTypeArguments(method)) { | |
| typeArguments = [elementType]; | |
| _addTypeArguments(arguments, typeArguments, sourceInformation); | |
| } | |
| _pushStaticInvocation( | |
| method, | |
| arguments, | |
| _typeInferenceMap.getReturnTypeOf(method), | |
| typeArguments, | |
| sourceInformation: sourceInformation, | |
| ); | |
| _closeAndGotoExit(HReturn(pop(), sourceInformation)); | |
| _closeFunction(); | |
| } | |
| /// Builds an SSA graph for a async/async* generator body. | |
| void _buildGeneratorBody( | |
| JGeneratorBody function, | |
| ir.FunctionNode functionNode, | |
| ) { | |
| FunctionEntity entry = function.function; | |
| _openFunction( | |
| entry, | |
| functionNode: functionNode, | |
| parameterStructure: function.parameterStructure, | |
| 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; | |
| if (node is HLoadType) continue; // Orphaned if check is redundant. | |
| return false; | |
| } | |
| return true; | |
| } | |
| void _potentiallyAddFunctionParameterTypeChecks( | |
| MemberEntity member, | |
| 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 as FunctionEntity); | |
| } | |
| MemberDefinition definition = _elementMap.getMemberDefinition( | |
| targetElement, | |
| ); | |
| bool nodeIsConstructorBody = definition.kind == MemberKind.constructorBody; | |
| void handleParameter(ir.FunctionParameter parameter) { | |
| Local local = _localsMap.getLocalVariable(parameter); | |
| if (nodeIsConstructorBody && | |
| _closureDataLookup | |
| .getCapturedScope(targetElement) | |
| .isBoxedVariable(_localsMap, 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; | |
| } | |
| if (elidedParameters.contains(local)) { | |
| // Elided parameters are initialized to a default value that is | |
| // statically checked. | |
| return; | |
| } | |
| HInstruction newParameter = localsHandler.readLocal(local); | |
| DartType type = _getDartTypeIfValid(parameter.type); | |
| if (targetChecks.checkAllParameters || | |
| (targetChecks.checkCovariantParameters && | |
| (parameter.isCovariantByClass || | |
| parameter.isCovariantByDeclaration))) { | |
| newParameter = _typeBuilder.potentiallyCheckOrTrustTypeOfParameter( | |
| targetElement, | |
| newParameter, | |
| type, | |
| ); | |
| } else { | |
| newParameter = _typeBuilder.trustTypeOfParameter( | |
| targetElement, | |
| newParameter, | |
| type, | |
| ); | |
| } | |
| localsHandler.updateLocal(local, newParameter); | |
| } | |
| function.positionalParameters.forEach(handleParameter); | |
| function.namedParameters.toList().forEach(handleParameter); | |
| } | |
| void _checkTypeVariableBounds(FunctionEntity method) { | |
| if (_rtiNeed.methodNeedsTypeArguments(method) && | |
| closedWorld.annotationsData.getParameterCheckPolicy(method).isEmitted) { | |
| ir.FunctionNode function = getFunctionNode(_elementMap, method)!; | |
| for (ir.TypeParameter typeParameter in function.typeParameters) { | |
| Local local = _localsMap.getLocalTypeVariableEntity( | |
| _elementMap | |
| .getTypeVariableType( | |
| ir.TypeParameterType(typeParameter, ir.Nullability.nonNullable), | |
| ) | |
| .element, | |
| ); | |
| HInstruction newParameter = localsHandler.directLocals[local]!; | |
| DartType bound = _getDartTypeIfValid(typeParameter.bound); | |
| if (!dartTypes.isTopType(bound)) { | |
| registry.registerTypeUse(TypeUse.typeVariableBoundCheck(bound)); | |
| // TODO(sigmund): method name here is not minified, should it be? | |
| _checkTypeBound(newParameter, bound, local.name!, method.name!); | |
| } | |
| } | |
| } | |
| } | |
| /// Builds an SSA graph for FunctionNodes of external methods. | |
| void _buildExternalFunctionNode( | |
| FunctionEntity function, | |
| ir.FunctionNode functionNode, | |
| ) { | |
| assert(functionNode.body == null); | |
| if (closedWorld.nativeData.isNativeMember(targetElement)) { | |
| _buildExternalNativeFunctionNode(function, functionNode); | |
| return; | |
| } | |
| if (function.name == '==') { | |
| if (_buildSpecialRuntimeEqualsMethod(function, functionNode)) return; | |
| } | |
| final member = _elementMap.getMemberContextNode(function); | |
| // `external` functions in `dart:_foreign_helper` or annotated with | |
| // `@pragma('external-effect')` are queued for compilation in a modular or | |
| // staged compile, so just generate an empty function. The actual call sites | |
| // for these methods are recognized and replaced, so the method generated | |
| // here is never called. | |
| if (_commonElements.isForeignHelper(function) || | |
| (member != null && | |
| ir.ExternalEffect.isAnnotatedWithExternalEffect( | |
| member, | |
| closedWorld.elementMap.coreTypes, | |
| ))) { | |
| _openFunction( | |
| function, | |
| functionNode: functionNode, | |
| parameterStructure: function.parameterStructure, | |
| checks: _checksForFunction(function), | |
| ); | |
| _closeFunction(); | |
| return; | |
| } | |
| failedAt(currentElementSpannable, 'Unknown external method $function'); | |
| } | |
| bool _buildSpecialRuntimeEqualsMethod( | |
| FunctionEntity function, | |
| ir.FunctionNode functionNode, | |
| ) { | |
| assert(function.name == '=='); | |
| if (function.enclosingClass == _commonElements.jsNullClass) { | |
| _openFunction( | |
| function, | |
| functionNode: functionNode, | |
| parameterStructure: function.parameterStructure, | |
| checks: _checksForFunction(function), | |
| ); | |
| HInstruction instance = graph.addConstantNull(closedWorld); | |
| HInstruction parameter = parameters.values.first; | |
| HInstruction value = HIdentity( | |
| instance, | |
| parameter, | |
| _abstractValueDomain.boolType, | |
| ); | |
| add(value); | |
| _closeAndGotoExit( | |
| HReturn(value, _sourceInformationBuilder.buildReturn(functionNode)), | |
| ); | |
| _closeFunction(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Builds an SSA graph for FunctionNodes of external methods that are | |
| /// 'native' or 'js-interop' methods. This produces a graph for a method with | |
| /// Dart calling conventions that forwards to the actual JavaScript `external` | |
| /// method. | |
| void _buildExternalNativeFunctionNode( | |
| FunctionEntity function, | |
| ir.FunctionNode functionNode, | |
| ) { | |
| bool isJsInterop = closedWorld.nativeData.isJsInteropMember(function); | |
| _openFunction( | |
| function, | |
| functionNode: functionNode, | |
| parameterStructure: function.parameterStructure, | |
| checks: _checksForFunction(function), | |
| ); | |
| List<HInstruction> inputs = []; | |
| if (targetElement.isInstanceMember) { | |
| inputs.add( | |
| localsHandler.readThis( | |
| sourceInformation: _sourceInformationBuilder.buildGet(functionNode), | |
| ), | |
| ); | |
| } | |
| void handleParameter(ir.FunctionParameter param) { | |
| Local local = _localsMap.getLocalVariable(param); | |
| // Convert Dart function to JavaScript function. | |
| HInstruction argument = localsHandler.readLocal(local); | |
| ir.DartType type = param.type; | |
| if (!isJsInterop && type is ir.FunctionType) { | |
| int arity = type.positionalParameters.length; | |
| _pushStaticInvocation( | |
| _commonElements.closureConverter, | |
| [argument, graph.addConstantInt(arity, closedWorld)], | |
| _abstractValueDomain.dynamicType, | |
| const <DartType>[], | |
| sourceInformation: null, | |
| ); | |
| argument = pop(); | |
| } | |
| inputs.add(argument); | |
| } | |
| for ( | |
| int position = 0; | |
| position < function.parameterStructure.positionalParameters; | |
| position++ | |
| ) { | |
| handleParameter(functionNode.positionalParameters[position]); | |
| } | |
| if (functionNode.namedParameters.isNotEmpty) { | |
| List<ir.NamedParameter> namedParameters = functionNode.namedParameters | |
| // Filter elided parameters. | |
| .where( | |
| (p) => function.parameterStructure.namedParameters.contains( | |
| p.parameterName, | |
| ), | |
| ) | |
| .toList(); | |
| // Sort by file offset to visit parameters in declaration order. | |
| namedParameters.sort(nativeOrdering); | |
| namedParameters.forEach(handleParameter); | |
| } | |
| NativeBehavior nativeBehavior = _nativeData.getNativeMethodBehavior( | |
| function, | |
| ); | |
| AbstractValue returnType = _typeInferenceMap.typeFromNativeBehavior( | |
| nativeBehavior, | |
| closedWorld, | |
| ); | |
| final sourceInformation = _sourceInformationBuilder.buildCall( | |
| functionNode, | |
| functionNode, | |
| ); | |
| push( | |
| HInvokeExternal( | |
| targetElement as FunctionEntity, | |
| inputs, | |
| returnType, | |
| nativeBehavior, | |
| sourceInformation: sourceInformation, | |
| ), | |
| ); | |
| HInstruction value; | |
| if (options.nativeNullAssertions && nodeIsInWebLibrary(functionNode)) { | |
| value = pop(); | |
| DartType type = _getDartTypeIfValid(functionNode.returnType); | |
| if (dartTypes.isNonNullable(type)) { | |
| push( | |
| HNullCheck( | |
| value, | |
| _abstractValueDomain.excludeNull(returnType), | |
| sticky: true, | |
| )..sourceInformation = sourceInformation, | |
| ); | |
| value = pop(); | |
| } | |
| } else if (_nativeData.isJsInteropMember(targetElement)) { | |
| if (targetElement.isInstanceMember) { | |
| _maybeAddInteropNullAssertionForMember( | |
| targetElement as FunctionEntity, | |
| inputs.length, | |
| sourceInformation: sourceInformation, | |
| ); | |
| } else { | |
| _maybeAddInteropNullAssertionForStatic( | |
| _getDartTypeIfValid(functionNode.returnType), | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| value = pop(); | |
| } else { | |
| value = pop(); | |
| } | |
| if (targetElement.isSetter) { | |
| _closeAndGotoExit(HGoto()); | |
| } else { | |
| _emitReturn(value, _sourceInformationBuilder.buildReturn(functionNode)); | |
| } | |
| _closeFunction(); | |
| } | |
| void _addImplicitInstantiation(DartType? type) { | |
| if (type is InterfaceType) { | |
| _currentImplicitInstantiations.add(type); | |
| } else { | |
| assert(type == null); | |
| } | |
| } | |
| void _removeImplicitInstantiation(DartType? type) { | |
| if (type != null) { | |
| _currentImplicitInstantiations.removeLast(); | |
| } | |
| } | |
| TargetChecks _checksForFunction(FunctionEntity function) { | |
| if (!function.isInstanceMember) { | |
| // Static methods with no tear-off can be generated with no checks. | |
| final access = closedWorld.getMemberAccess(function); | |
| if (access != null && access.reads.isEmpty) { | |
| return TargetChecks.none; | |
| } | |
| } | |
| // TODO(sra): Instance methods can be generated with reduced checks if | |
| // called only from non-dynamic call-sites. | |
| return TargetChecks.dynamicChecks; | |
| } | |
| void _openFunction( | |
| MemberEntity member, { | |
| ir.FunctionNode? functionNode, | |
| ParameterStructure? parameterStructure, | |
| required TargetChecks checks, | |
| }) { | |
| Map<Local, AbstractValue> parameterMap = {}; | |
| List<ir.Variable> elidedParameters = []; | |
| Set<Local> elidedParameterSet = {}; | |
| if (functionNode != null) { | |
| void handleParameter( | |
| ir.Variable node, { | |
| required bool isOptional, | |
| required bool isElided, | |
| }) { | |
| Local local = _localsMap.getLocalVariable(node); | |
| if (isElided) { | |
| elidedParameters.add(node); | |
| elidedParameterSet.add(local); | |
| } | |
| parameterMap[local] = _typeInferenceMap.getInferredTypeOfParameter( | |
| local, | |
| member, | |
| ); | |
| } | |
| forEachOrderedParameterByFunctionNode( | |
| functionNode, | |
| parameterStructure!, | |
| handleParameter, | |
| ); | |
| _returnType = _elementMap.getDartType(functionNode.returnType); | |
| } | |
| _prepareEntryBlock(); | |
| localsHandler.startFunction( | |
| targetElement, | |
| parameterMap, | |
| elidedParameterSet, | |
| _sourceInformationBuilder.buildDeclaration(targetElement), | |
| isGenerativeConstructorBody: targetElement is ConstructorBodyEntity, | |
| ); | |
| ir.Member? memberContextNode = _elementMap.getMemberContextNode(member); | |
| if (memberContextNode != null) { | |
| for (ir.Variable node in elidedParameters) { | |
| Local local = _localsMap.getLocalVariable(node); | |
| localsHandler.updateLocal(local, _defaultValueForParameter(node)); | |
| } | |
| } | |
| _addClassTypeVariablesIfNeeded(member); | |
| _addFunctionTypeVariablesIfNeeded(member); | |
| // If [member] 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. The null check is added before the argument type checks since in | |
| // strong mode, the parameter type might be non-nullable. | |
| if (member is FunctionEntity && member.name == '==') { | |
| if (functionNode == null) { | |
| throw StateError("'==' should have functionNode"); | |
| } | |
| if (!_commonElements.operatorEqHandlesNullArgument(member)) { | |
| _handleIf( | |
| visitCondition: () { | |
| final parameter = parameters.values.first; | |
| push( | |
| HIdentity( | |
| parameter, | |
| graph.addConstantNull(closedWorld), | |
| _abstractValueDomain.boolType, | |
| ), | |
| ); | |
| }, | |
| visitThen: () { | |
| _closeAndGotoExit( | |
| HReturn( | |
| graph.addConstantBool(false, closedWorld), | |
| _sourceInformationBuilder.buildReturn(functionNode), | |
| ), | |
| ); | |
| }, | |
| visitElse: null, | |
| sourceInformation: _sourceInformationBuilder.buildIf(functionNode), | |
| ); | |
| } | |
| } | |
| if (functionNode != null) { | |
| _potentiallyAddFunctionParameterTypeChecks(member, functionNode, checks); | |
| } | |
| _insertCoverageCall(member); | |
| } | |
| void _closeFunction() { | |
| if (!isAborted()) _closeAndGotoExit(HGoto()); | |
| graph.finalize(); | |
| } | |
| @override | |
| void defaultNode(ir.Node node) { | |
| throw UnsupportedError('Unhandled node $node (${node.runtimeType})'); | |
| } | |
| /// Returns the current source element. This is used by the type builder. | |
| // TODO(efortuna): Update this when we implement inlining. | |
| // TODO(sra): Re-implement type builder using Kernel types and the | |
| // `target` for context. | |
| MemberEntity get sourceElement => _currentFrame!.member; | |
| @override | |
| void visitCheckLibraryIsLoaded(ir.CheckLibraryIsLoaded node) { | |
| ImportEntity import = _elementMap.getImport(node.import); | |
| String loadId = closedWorld.outputUnitData.getImportDeferName( | |
| _elementMap.getSpannable(targetElement, node), | |
| import, | |
| ); | |
| HInstruction prefixConstant = graph.addConstantString(loadId, closedWorld); | |
| _pushStaticInvocation( | |
| _commonElements.checkDeferredIsLoaded, | |
| [prefixConstant], | |
| _typeInferenceMap.getReturnTypeOf(_commonElements.checkDeferredIsLoaded), | |
| const <DartType>[], | |
| sourceInformation: null, | |
| ); | |
| } | |
| @override | |
| void visitLoadLibrary(ir.LoadLibrary node) { | |
| String loadId = closedWorld.outputUnitData.getImportDeferName( | |
| _elementMap.getSpannable(targetElement, node), | |
| _elementMap.getImport(node.import), | |
| ); | |
| final priority = closedWorld.annotationsData.getLoadLibraryPriority(node); | |
| final sourceInformation = _sourceInformationBuilder.buildCall(node, node); | |
| push( | |
| HInvokeStatic( | |
| _commonElements.loadDeferredLibrary, | |
| [ | |
| graph.addConstantString(loadId, closedWorld), | |
| graph.addConstantString(priority, closedWorld), | |
| ], | |
| _abstractValueDomain.nonNullType, | |
| const <DartType>[], | |
| targetCanThrow: false, | |
| )..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| @override | |
| void visitBlock(ir.Block node) { | |
| assert(!isAborted()); | |
| // [block] can be unreachable at the beginning of a block if an | |
| // ir.BlockExpression that is a subexpression of an expression that contains | |
| // a throwing prior subexpression, e.g. `[throw e, {...[]}]`. | |
| if (!_isReachable) return; | |
| localsHandler.enterScope( | |
| _closureDataLookup.getCapturedBlockScope(node), | |
| _sourceInformationBuilder.buildBlock(node), | |
| ); | |
| for (ir.Statement statement in node.statements) { | |
| statement.accept(this); | |
| if (!_isReachable) { | |
| // The block has been aborted by a return or a throw. | |
| if (stack.isNotEmpty) { | |
| reporter.internalError( | |
| noLocationSpannable, | |
| 'Non-empty instruction stack.', | |
| ); | |
| } | |
| return; | |
| } | |
| } | |
| assert(!current!.isClosed); | |
| if (stack.isNotEmpty) { | |
| reporter.internalError( | |
| noLocationSpannable, | |
| '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; | |
| // Handle a `throw` expression in statement-position, with control flow to | |
| // the exit. (In expression position the throw does not create control-flow | |
| // out of CFG region for the expression). | |
| if (expression is ir.Throw && | |
| _inliningStack.isEmpty && | |
| !_inBlockExpression) { | |
| _visitThrowExpression(expression.expression); | |
| _handleInTryStatement(); | |
| final sourceInformation = _sourceInformationBuilder.buildThrow( | |
| node.expression, | |
| ); | |
| _closeAndGotoExit( | |
| HThrow( | |
| pop(), | |
| sourceInformation, | |
| withoutHelperFrame: closedWorld.annotationsData | |
| .throwWithoutHelperFrame(node), | |
| ), | |
| ); | |
| } else { | |
| expression.accept(this); | |
| pop(); | |
| } | |
| } | |
| @override | |
| void visitConstantExpression(ir.ConstantExpression node) { | |
| ConstantValue value = _elementMap.getConstantValue(node)!; | |
| final sourceInformation = _sourceInformationBuilder.buildGet(node); | |
| if (!closedWorld.outputUnitData.hasOnlyNonDeferredImportPathsToConstant( | |
| targetElement, | |
| value, | |
| )) { | |
| OutputUnit outputUnit = closedWorld.outputUnitData.outputUnitForConstant( | |
| value, | |
| ); | |
| final deferredConstant = DeferredGlobalConstantValue(value, outputUnit); | |
| registry.registerConstantUse(ConstantUse.deferred(deferredConstant)); | |
| stack.add( | |
| graph.addDeferredConstant( | |
| deferredConstant, | |
| sourceInformation, | |
| closedWorld, | |
| ), | |
| ); | |
| } else { | |
| stack.add( | |
| graph.addConstant( | |
| value, | |
| closedWorld, | |
| sourceInformation: sourceInformation, | |
| ), | |
| ); | |
| } | |
| } | |
| @override | |
| void visitReturnStatement(ir.ReturnStatement node) { | |
| final sourceInformation = _sourceInformationBuilder.buildReturn(node); | |
| HInstruction? value; | |
| if (node.expression != null) { | |
| node.expression!.accept(this); | |
| value = pop(); | |
| if (_currentFrame!.asyncMarker == AsyncMarker.async) { | |
| // TODO(johnniwinther): Is this special-casing of async still needed | |
| // or should we use the general check below? | |
| /*if (options.enableTypeAssertions && | |
| !isValidAsyncReturnType(_returnType)) { | |
| generateTypeError( | |
| "Async function returned a Future," | |
| " was declared to return a ${_returnType}.", | |
| sourceInformation); | |
| pop(); | |
| return; | |
| }*/ | |
| } else { | |
| value = _typeBuilder.potentiallyCheckOrTrustTypeOfAssignment( | |
| _currentFrame!.member, | |
| value, | |
| _returnType!, | |
| ); | |
| } | |
| } | |
| _handleInTryStatement(); | |
| if (_inliningStack.isEmpty && targetElement.isSetter) { | |
| if (node.parent is ir.FunctionNode) { | |
| // An arrow function definition of a setter has a ReturnStatement as a | |
| // body, e.g. "set foo(x) => this._x = x;". There is no way to access | |
| // the returned value, so don't emit a return. | |
| return; | |
| } | |
| } | |
| // TODO(43456): Better unreachable code removal. `_isReachable` removes | |
| // more code, but also `return`s that pattern-match against more compact | |
| // arrow functions. The `return`s also help the JavaScript VM. | |
| // TODO(b/276976255): Using `_isReachable` causes a test failure. | |
| if (!isAborted()) { | |
| _emitReturn(value, sourceInformation); | |
| } | |
| } | |
| @override | |
| void visitForStatement(ir.ForStatement node) { | |
| assert(_isReachable); | |
| 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 pop(); | |
| } | |
| 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); | |
| } | |
| final jumpTarget = _localsMap.getJumpTargetForFor(node); | |
| _loopHandler.handleLoop( | |
| node, | |
| _closureDataLookup.getCapturedLoopScope(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> | |
| /// } | |
| void _buildForInIndexable(ir.ForInStatement node) { | |
| SyntheticLocal indexVariable = localsHandler.createLocal('_i'); | |
| // These variables are shared by initializer, condition, body and update. | |
| late final HInstruction array; // Set in buildInitializer. | |
| late final bool isFixed; // Set in buildInitializer. | |
| HInstruction? originalLength; // Set for growable lists. | |
| HInstruction buildGetLength(SourceInformation? sourceInformation) { | |
| HGetLength result = 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) | |
| // | |
| final sourceInformation = _sourceInformationBuilder.buildForInMoveNext( | |
| node, | |
| ); | |
| HInstruction length = buildGetLength(sourceInformation); | |
| push( | |
| HIdentity(length, originalLength!, _abstractValueDomain.boolType) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| _pushStaticInvocation( | |
| _commonElements.checkConcurrentModificationError, | |
| [pop(), array], | |
| _typeInferenceMap.getReturnTypeOf( | |
| _commonElements.checkConcurrentModificationError, | |
| ), | |
| const <DartType>[], | |
| sourceInformation: sourceInformation, | |
| ); | |
| pop(); | |
| } | |
| void buildInitializer() { | |
| final sourceInformation = _sourceInformationBuilder.buildForInIterator( | |
| node, | |
| ); | |
| node.iterable.accept(this); | |
| array = pop(); | |
| isFixed = _abstractValueDomain | |
| .isFixedLengthJsIndexable(array.instructionType) | |
| .isDefinitelyTrue; | |
| localsHandler.updateLocal( | |
| indexVariable, | |
| graph.addConstantInt(0, closedWorld), | |
| sourceInformation: sourceInformation, | |
| ); | |
| originalLength = buildGetLength(sourceInformation); | |
| } | |
| HInstruction buildCondition() { | |
| final sourceInformation = _sourceInformationBuilder.buildForInMoveNext( | |
| node, | |
| ); | |
| HInstruction index = localsHandler.readLocal( | |
| indexVariable, | |
| sourceInformation: sourceInformation, | |
| ); | |
| HInstruction length = buildGetLength(sourceInformation); | |
| HInstruction compare = HLess(index, length, _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); | |
| final sourceInformation = _sourceInformationBuilder.buildForInCurrent( | |
| node, | |
| ); | |
| HInstruction index = localsHandler.readLocal( | |
| indexVariable, | |
| sourceInformation: sourceInformation, | |
| ); | |
| // No bound check is necessary on indexer as it is immediately guarded by | |
| // the condition. | |
| HInstruction value = HIndex(array, index, type) | |
| ..sourceInformation = sourceInformation; | |
| final staticType = _abstractValueDomain | |
| .createFromStaticType(_getStaticForInElementType(node)) | |
| .abstractValue; | |
| value.instructionType = _abstractValueDomain.intersection( | |
| value.instructionType, | |
| staticType, | |
| ); | |
| 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. | |
| final sourceInformation = _sourceInformationBuilder.buildForInSet(node); | |
| HInstruction index = localsHandler.readLocal( | |
| indexVariable, | |
| sourceInformation: sourceInformation, | |
| ); | |
| HInstruction one = graph.addConstantInt(1, closedWorld); | |
| HInstruction addInstruction = HAdd( | |
| index, | |
| one, | |
| _abstractValueDomain.positiveIntType, | |
| )..sourceInformation = sourceInformation; | |
| add(addInstruction); | |
| localsHandler.updateLocal( | |
| indexVariable, | |
| addInstruction, | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| _loopHandler.handleLoop( | |
| node, | |
| _closureDataLookup.getCapturedLoopScope(node), | |
| _localsMap.getJumpTargetForForIn(node), | |
| buildInitializer, | |
| buildCondition, | |
| buildUpdate, | |
| buildBody, | |
| _sourceInformationBuilder.buildLoop(node), | |
| ); | |
| } | |
| void _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. | |
| late final HInstruction iterator; | |
| final iteratorType = _getStaticForInIteratorType(node); | |
| void buildInitializer() { | |
| final receiverType = _typeInferenceMap.typeOfIterator(node); | |
| node.iterable.accept(this); | |
| HInstruction receiver = pop(); | |
| _pushDynamicInvocation( | |
| node, | |
| _getStaticType(node.iterable), | |
| receiverType, | |
| Selectors.iterator, | |
| [receiver], | |
| const <DartType>[], | |
| _sourceInformationBuilder.buildForInIterator(node), | |
| ); | |
| iterator = pop(); | |
| } | |
| HInstruction buildCondition() { | |
| final receiverType = _typeInferenceMap.typeOfIteratorMoveNext(node); | |
| _pushDynamicInvocation( | |
| node, | |
| iteratorType, | |
| receiverType, | |
| Selectors.moveNext, | |
| [iterator], | |
| const <DartType>[], | |
| _sourceInformationBuilder.buildForInMoveNext(node), | |
| ); | |
| return pop(); | |
| } | |
| void buildBody() { | |
| final sourceInformation = _sourceInformationBuilder.buildForInCurrent( | |
| node, | |
| ); | |
| final receiverType = _typeInferenceMap.typeOfIteratorCurrent(node); | |
| _pushDynamicInvocation( | |
| node, | |
| iteratorType, | |
| receiverType, | |
| Selectors.current, | |
| [iterator], | |
| const <DartType>[], | |
| sourceInformation, | |
| ); | |
| Local loopVariableLocal = _localsMap.getLocalVariable(node.variable); | |
| HInstruction value = _typeBuilder.potentiallyCheckOrTrustTypeOfAssignment( | |
| _currentFrame!.member, | |
| 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, | |
| _closureDataLookup.getCapturedLoopScope(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); | |
| final instanceType = localsHandler.substInContext( | |
| dartTypes.interfaceType(cls, [typeArg]), | |
| ) as InterfaceType; | |
| _addImplicitInstantiation(instanceType); | |
| final 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() { | |
| final receiverType = _typeInferenceMap.typeOfIteratorMoveNext(node); | |
| _pushDynamicInvocation( | |
| node, | |
| instanceType, | |
| receiverType, | |
| Selectors.moveNext, | |
| [streamIterator], | |
| const <DartType>[], | |
| _sourceInformationBuilder.buildForInMoveNext(node), | |
| ); | |
| HInstruction future = pop(); | |
| push(HAwait(future, _abstractValueDomain.dynamicType)); | |
| return pop(); | |
| } | |
| void buildBody() { | |
| final receiverType = _typeInferenceMap.typeOfIteratorCurrent(node); | |
| _pushDynamicInvocation( | |
| node, | |
| instanceType, | |
| receiverType, | |
| 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 = TryCatchFinallyBuilder( | |
| this, | |
| _sourceInformationBuilder.buildLoop(node), | |
| ); | |
| // Build fake try body: | |
| _loopHandler.handleLoop( | |
| node, | |
| _closureDataLookup.getCapturedLoopScope(node), | |
| _localsMap.getJumpTargetForForIn(node), | |
| buildInitializer, | |
| buildCondition, | |
| buildUpdate, | |
| buildBody, | |
| _sourceInformationBuilder.buildLoop(node), | |
| ); | |
| void finalizerFunction() { | |
| _pushDynamicInvocation( | |
| node, | |
| instanceType, | |
| null, | |
| Selectors.cancel, | |
| [streamIterator], | |
| const <DartType>[], | |
| _sourceInformationBuilder | |
| // ignore:deprecated_member_use_from_same_package | |
| .buildGeneric(node), | |
| ); | |
| add(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.setArrayType; | |
| // TODO(efortuna): Insert source information in this static invocation. | |
| _pushStaticInvocation( | |
| typeInfoSetterFn, | |
| [newObject, typeInfo], | |
| _abstractValueDomain.dynamicType, | |
| const <DartType>[], | |
| sourceInformation: sourceInformation, | |
| ); | |
| // The new object will now be referenced through the | |
| // `setArrayType` 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( | |
| currentElementSpannable, | |
| "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 pop(); | |
| } | |
| _loopHandler.handleLoop( | |
| node, | |
| _closureDataLookup.getCapturedLoopScope(node), | |
| _localsMap.getJumpTargetForWhile(node), | |
| () {}, | |
| buildCondition, | |
| () {}, | |
| () { | |
| node.body.accept(this); | |
| }, | |
| _sourceInformationBuilder.buildLoop(node), | |
| ); | |
| } | |
| @override | |
| void visitDoStatement(ir.DoStatement node) { | |
| final 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 = LocalsHandler.from(localsHandler); | |
| CapturedLoopScope loopClosureInfo = _closureDataLookup.getCapturedLoopScope( | |
| node, | |
| ); | |
| localsHandler.startLoop(loopClosureInfo, sourceInformation); | |
| final target = _localsMap.getJumpTargetForDo(node); | |
| JumpHandler jumpHandler = _loopHandler.beginLoopHeader(node, target); | |
| final currentBlock = current!; | |
| final loopInfo = currentBlock.loopInformation!; | |
| HBasicBlock loopEntryBlock = currentBlock; | |
| HBasicBlock bodyEntryBlock = currentBlock; | |
| 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(HGoto()); | |
| } else { | |
| isAbortingBody = true; | |
| bodyExitBlock = lastOpenedBlock; | |
| } | |
| SubExpression conditionExpression; | |
| bool loopIsDegenerate = isAbortingBody && !hasContinues; | |
| if (!loopIsDegenerate) { | |
| HBasicBlock conditionBlock = addNewBlock(); | |
| List<LocalsHandler> continueHandlers = <LocalsHandler>[]; | |
| jumpHandler.forEachContinue(( | |
| HContinue instruction, | |
| LocalsHandler locals, | |
| ) { | |
| instruction.block!.addSuccessor(conditionBlock); | |
| continueHandlers.add(locals); | |
| }); | |
| if (!isAbortingBody) { | |
| bodyExitBlock.addSuccessor(conditionBlock); | |
| } | |
| if (continueHandlers.isNotEmpty) { | |
| if (!isAbortingBody) continueHandlers.add(localsHandler); | |
| localsHandler = savedLocals.mergeMultiple( | |
| continueHandlers, | |
| conditionBlock, | |
| ); | |
| SubGraph bodyGraph = SubGraph(bodyEntryBlock, bodyExitBlock); | |
| List<LabelDefinition> labels = jumpHandler.labels; | |
| HSubGraphBlockInformation bodyInfo = HSubGraphBlockInformation( | |
| bodyGraph, | |
| ); | |
| HLabeledBlockInformation info; | |
| if (labels.isNotEmpty) { | |
| info = HLabeledBlockInformation(bodyInfo, labels, isContinue: true); | |
| } else { | |
| info = HLabeledBlockInformation.implicit( | |
| bodyInfo, | |
| target, | |
| isContinue: true, | |
| ); | |
| } | |
| bodyEntryBlock.setBlockFlow(info, conditionBlock); | |
| } | |
| open(conditionBlock); | |
| node.condition.accept(this); | |
| assert(!isAborted()); | |
| HInstruction conditionInstruction = pop(); | |
| HBasicBlock conditionEndBlock = close(HLoopBranch(conditionInstruction)); | |
| HBasicBlock avoidCriticalEdge = addNewBlock(); | |
| conditionEndBlock.addSuccessor(avoidCriticalEdge); | |
| open(avoidCriticalEdge); | |
| close(HGoto()); | |
| avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge. | |
| conditionExpression = SubExpression(conditionBlock, conditionEndBlock); | |
| // Avoid a critical edge from the condition to the loop-exit body. | |
| HBasicBlock conditionExitBlock = addNewBlock(); | |
| open(conditionExitBlock); | |
| close(HGoto()); | |
| conditionEndBlock.addSuccessor(conditionExitBlock); | |
| _loopHandler.endLoop( | |
| loopEntryBlock, | |
| conditionExitBlock, | |
| jumpHandler, | |
| localsHandler, | |
| ); | |
| loopEntryBlock.postProcessLoopHeader(); | |
| SubGraph bodyGraph = SubGraph(loopEntryBlock, bodyExitBlock); | |
| final newLoopInfo = loopEntryBlock.loopInformation!; | |
| HLoopBlockInformation loopBlockInfo = HLoopBlockInformation( | |
| LoopBlockInformationKind.doWhileLoop, | |
| null, | |
| wrapExpressionGraph(conditionExpression), | |
| wrapStatementGraph(bodyGraph), | |
| null, | |
| newLoopInfo.target, | |
| newLoopInfo.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 = SubGraph(bodyEntryBlock, bodyExitBlock); | |
| JumpTarget target = _localsMap.getJumpTargetForDo(node)!; | |
| final label = target.addLabel('loop', isBreakTarget: true); | |
| HLabeledBlockInformation info = HLabeledBlockInformation( | |
| HSubGraphBlockInformation(bodyGraph), | |
| <LabelDefinition>[label], | |
| ); | |
| loopEntryBlock.setBlockFlow(info, current); | |
| jumpHandler.forEachBreak((HBreak breakInstruction, _) { | |
| HBasicBlock block = breakInstruction.block!; | |
| block.addAtExit(HBreak.toLabel(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, | |
| required void Function() visitCondition, | |
| required void Function() visitThen, | |
| void Function()? visitElse, | |
| SourceInformation? sourceInformation, | |
| }) { | |
| SsaBranchBuilder branchBuilder = SsaBranchBuilder( | |
| this, | |
| node == null ? null : _elementMap.getSpannable(targetElement, node), | |
| ); | |
| branchBuilder.handleIf( | |
| visitCondition, | |
| visitThen, | |
| visitElse, | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| @override | |
| void visitAsExpression(ir.AsExpression node) { | |
| // Recognize these special cases, where expression e has static type `T?`: | |
| // | |
| // e as T | |
| // (e as dynamic) as T | |
| // | |
| // These patterns can only fail if `e` results in a `null` value. The | |
| // second pattern occurs when `e as dynamic` is used get an implicit | |
| // downcast in order to make use of the different policies for explicit and | |
| // implicit downcasts. | |
| // | |
| // The pattern match is syntactic which ensures the type bindings are | |
| // consistent, i.e. from the same instance of a type variable scope. | |
| ir.Expression operand = _skipCastsToDynamic(node.operand); | |
| operand.accept(this); | |
| bool isNullRemovalPattern = false; | |
| final operandType = _getStaticType(operand); | |
| DartType type = _elementMap.getDartType(node.type); | |
| if (!node.isCovarianceCheck) { | |
| if (_elementMap.types.isSubtype(operandType, type)) { | |
| // Skip unneeded casts. | |
| return; | |
| } | |
| if (_elementMap.types.isSubtype( | |
| operandType, | |
| _elementMap.types.nullableType(type), | |
| )) { | |
| isNullRemovalPattern = true; | |
| } | |
| } | |
| final sourceInformation = _sourceInformationBuilder.buildAs(node); | |
| HInstruction expressionInstruction = pop(); | |
| if (node.type is ir.InvalidType) { | |
| _generateTypeError('invalid type', sourceInformation); | |
| return; | |
| } | |
| CheckPolicy policy; | |
| final currentFrame = _currentFrame!; | |
| if (node.isTypeError) { | |
| policy = closedWorld.annotationsData.getImplicitDowncastCheckPolicy( | |
| currentFrame.member, | |
| ); | |
| } else { | |
| policy = closedWorld.annotationsData.getExplicitCastCheckPolicy( | |
| currentFrame.member, | |
| ); | |
| } | |
| if (!policy.isEmitted) { | |
| stack.add(expressionInstruction); | |
| return; | |
| } | |
| void generateCheck() { | |
| HInstruction converted = _typeBuilder.buildAsCheck( | |
| expressionInstruction, | |
| localsHandler.substInContext(type), | |
| isTypeError: node.isTypeError, | |
| sourceInformation: sourceInformation, | |
| ); | |
| if (converted != expressionInstruction) { | |
| add(converted); | |
| } | |
| stack.add(converted); | |
| } | |
| if (isNullRemovalPattern) { | |
| // Generate a conditional to test only `null` values: | |
| // | |
| // temp = e; | |
| // temp == null ? temp as T : temp | |
| SsaBranchBuilder(this).handleConditional( | |
| () { | |
| push( | |
| HIdentity( | |
| expressionInstruction, | |
| graph.addConstantNull(closedWorld), | |
| _abstractValueDomain.boolType, | |
| ), | |
| ); | |
| }, | |
| generateCheck, | |
| () { | |
| stack.add(expressionInstruction); | |
| }, | |
| ); | |
| } else { | |
| generateCheck(); | |
| } | |
| } | |
| static ir.Expression _skipCastsToDynamic(ir.Expression node) { | |
| if (node is ir.AsExpression && node.type is ir.DynamicType) { | |
| return _skipCastsToDynamic(node.operand); | |
| } | |
| return node; | |
| } | |
| @override | |
| void visitNullCheck(ir.NullCheck node) { | |
| node.operand.accept(this); | |
| HInstruction expression = pop(); | |
| final sourceInformation = _sourceInformationBuilder.buildUnary(node); | |
| push( | |
| HNullCheck( | |
| expression, | |
| _abstractValueDomain.excludeNull(expression.instructionType), | |
| )..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| 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; | |
| var sourceInformation = _sourceInformationBuilder.buildAssert(node); | |
| if (node.message == null) { | |
| node.condition.accept(this); | |
| _pushStaticInvocation( | |
| _commonElements.assertHelper, | |
| [pop()], | |
| _typeInferenceMap.getReturnTypeOf(_commonElements.assertHelper), | |
| const <DartType>[], | |
| sourceInformation: sourceInformation, | |
| ); | |
| pop(); | |
| return; | |
| } | |
| // if (assertTest(condition)) assertThrow(message); | |
| void buildCondition() { | |
| node.condition.accept(this); | |
| _pushStaticInvocation( | |
| _commonElements.assertTest, | |
| [pop()], | |
| _typeInferenceMap.getReturnTypeOf(_commonElements.assertTest), | |
| const <DartType>[], | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| void fail() { | |
| node.message!.accept(this); | |
| _pushStaticInvocation( | |
| _commonElements.assertThrow, | |
| [pop()], | |
| _typeInferenceMap.getReturnTypeOf(_commonElements.assertThrow), | |
| const <DartType>[], | |
| sourceInformation: sourceInformation, | |
| ); | |
| 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 NullJumpHandler(reporter); | |
| } | |
| if (isLoopJump && node is ir.SwitchStatement) { | |
| return KernelSwitchCaseJumpHandler(this, target, node, _localsMap); | |
| } | |
| return JumpHandler(this, target); | |
| } | |
| @override | |
| void visitBreakStatement(ir.BreakStatement node) { | |
| assert(!isAborted()); | |
| _handleInTryStatement(); | |
| JumpTarget target = _localsMap.getJumpTargetForBreak(node); | |
| JumpHandler handler = jumpTargets[target]!; | |
| final sourceInformation = _sourceInformationBuilder.buildGoto(node); | |
| if (_localsMap.generateContinueForBreak(node)) { | |
| LabelDefinition? continueLabel; | |
| for (final label in handler.labels) { | |
| if (label.isContinueTarget) { | |
| continueLabel = label; | |
| break; | |
| } | |
| } | |
| if (continueLabel != null) { | |
| handler.generateContinue(sourceInformation, continueLabel); | |
| } else { | |
| handler.generateContinue(sourceInformation); | |
| } | |
| } else { | |
| LabelDefinition? breakLabel; | |
| for (final label in handler.labels) { | |
| if (label.isBreakTarget) { | |
| breakLabel = label; | |
| break; | |
| } | |
| } | |
| if (breakLabel != null) { | |
| handler.generateBreak(sourceInformation, breakLabel); | |
| } 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; | |
| } | |
| final jumpTarget = _localsMap.getJumpTargetForLabel(node); | |
| if (jumpTarget == null) { | |
| // The label is not needed. | |
| body.accept(this); | |
| return; | |
| } | |
| JumpHandler handler = createJumpHandler(node, jumpTarget); | |
| LocalsHandler beforeLocals = LocalsHandler.from(localsHandler); | |
| HBasicBlock newBlock = openNewBlock(); | |
| body.accept(this); | |
| SubGraph bodyGraph = SubGraph(newBlock, lastOpenedBlock); | |
| // Create join block only if reached, otherwise it won't have a dominator. | |
| late final HBasicBlock joinBlock = graph.addNewBlock(); | |
| List<LocalsHandler> breakHandlers = []; | |
| handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) { | |
| breakInstruction.block!.addSuccessor(joinBlock); | |
| breakHandlers.add(locals); | |
| }); | |
| if (!isAborted()) { | |
| goto(current!, joinBlock); | |
| breakHandlers.add(localsHandler); | |
| } | |
| if (breakHandlers.isNotEmpty) { | |
| open(joinBlock); | |
| localsHandler = beforeLocals.mergeMultiple(breakHandlers, joinBlock); | |
| // There was at least one reachable break, so the label is needed. | |
| newBlock.setBlockFlow( | |
| HLabeledBlockInformation( | |
| 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 = {}; | |
| for (ir.SwitchCase switchCase in switchStatement.cases) { | |
| for (ir.Expression caseExpression in switchCase.expressions) { | |
| final constant = _elementMap.getConstantValue(caseExpression)!; | |
| constants[caseExpression] = constant; | |
| } | |
| } | |
| return constants; | |
| } | |
| @override | |
| void visitContinueSwitchStatement(ir.ContinueSwitchStatement node) { | |
| _handleInTryStatement(); | |
| JumpTarget target = _localsMap.getJumpTargetForContinueSwitch(node); | |
| final handler = jumpTargets[target]!; | |
| assert(target.labels.isNotEmpty); | |
| handler.generateContinue( | |
| _sourceInformationBuilder.buildGoto(node), | |
| target.labels.first, | |
| ); | |
| } | |
| @override | |
| void visitSwitchStatement(ir.SwitchStatement node) { | |
| final sourceInformation = _sourceInformationBuilder.buildSwitch(node); | |
| // The switch case indices must match those computed in | |
| // [KernelSwitchCaseJumpHandler]. | |
| bool hasContinue = false; | |
| Map<ir.SwitchCase, int> caseIndex = {}; | |
| 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 = []; | |
| 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. | |
| // TODO(https://dartbug.com/51777): Consider alternative with single switch | |
| // statement. | |
| JumpTarget switchTarget = _localsMap.getJumpTargetForSwitch( | |
| switchStatement, | |
| )!; | |
| localsHandler.updateLocal(switchTarget, graph.addConstantNull(closedWorld)); | |
| var switchCases = List<ir.SwitchCase?>.of(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 = List<ir.SwitchCase?>.of(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 [constant_system.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. | |
| NullJumpHandler(reporter), | |
| buildExpression, | |
| switchStatement.cases, | |
| getConstants, | |
| (_) => false, // No case is default. | |
| buildSwitchCase, | |
| sourceInformation, | |
| ); | |
| } | |
| void buildLoop() { | |
| _loopHandler.handleLoop( | |
| switchStatement, | |
| _closureDataLookup.getCapturedLoopScope(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( | |
| HForeignCode(code, _abstractValueDomain.boolType, [ | |
| localsHandler.readLocal(switchTarget), | |
| ], nativeBehavior: 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 Function(ir.SwitchStatement statement) buildExpression, | |
| List<ir.SwitchCase?> switchCases, | |
| List<ConstantValue> Function( | |
| ir.SwitchStatement parentSwitch, | |
| ir.SwitchCase? switchCase, | |
| ) | |
| getConstants, | |
| bool Function(ir.SwitchCase? switchCase) isDefaultCase, | |
| void Function(ir.SwitchCase? switchCase) buildSwitchCase, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| HBasicBlock expressionStart = openNewBlock(); | |
| HInstruction expression = buildExpression(switchStatement); | |
| if (switchCases.isEmpty) { | |
| return; | |
| } | |
| HSwitch switchInstruction = HSwitch(expression); | |
| HBasicBlock expressionEnd = close(switchInstruction); | |
| LocalsHandler savedLocals = localsHandler; | |
| List<HStatementInformation> statements = []; | |
| 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 = 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( | |
| HSubGraphBlockInformation(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 = HBasicBlock(); | |
| List<LocalsHandler> caseHandlers = []; | |
| 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(HGoto()); | |
| 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(HGoto()); | |
| defaultCase.addSuccessor(joinBlock); | |
| caseHandlers.add(savedLocals); | |
| statements.add( | |
| HSubGraphBlockInformation(SubGraph(defaultCase, defaultCase)), | |
| ); | |
| } | |
| assert(caseHandlers.length == joinBlock.predecessors.length); | |
| if (caseHandlers.isNotEmpty) { | |
| 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 = | |
| HSubExpressionBlockInformation( | |
| SubExpression(expressionStart, expressionEnd), | |
| ); | |
| expressionStart.setBlockFlow( | |
| HSwitchBlockInformation( | |
| expressionInfo, | |
| statements, | |
| jumpHandler.target, | |
| jumpHandler.labels, | |
| sourceInformation, | |
| ), | |
| joinBlock, | |
| ); | |
| jumpHandler.close(); | |
| } | |
| @override | |
| void visitConditionalExpression(ir.ConditionalExpression node) { | |
| SsaBranchBuilder brancher = SsaBranchBuilder(this); | |
| brancher.handleConditional( | |
| () => node.condition.accept(this), | |
| () => node.then.accept(this), | |
| () => node.otherwise.accept(this), | |
| ); | |
| } | |
| @override | |
| void visitLogicalExpression(ir.LogicalExpression node) { | |
| SsaBranchBuilder brancher = SsaBranchBuilder(this); | |
| _handleLogicalExpression( | |
| node.left, | |
| () => node.right.accept(this), | |
| brancher, | |
| node.operatorEnum, | |
| _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 Function() visitRight, | |
| SsaBranchBuilder brancher, | |
| ir.LogicalExpressionOperator operatorEnum, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (left is ir.LogicalExpression && left.operatorEnum == operatorEnum) { | |
| ir.Expression innerLeft = left.left; | |
| ir.Expression middle = left.right; | |
| _handleLogicalExpression( | |
| innerLeft, | |
| () => _handleLogicalExpression( | |
| middle, | |
| visitRight, | |
| brancher, | |
| operatorEnum, | |
| _sourceInformationBuilder.buildBinary(middle), | |
| ), | |
| brancher, | |
| operatorEnum, | |
| sourceInformation, | |
| ); | |
| } else { | |
| brancher.handleLogicalBinary( | |
| () => left.accept(this), | |
| visitRight, | |
| sourceInformation, | |
| isAnd: operatorEnum == ir.LogicalExpressionOperator.AND, | |
| ); | |
| } | |
| } | |
| @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, | |
| ) { | |
| // [type] could be `List<T>`, so ensure it is `JSArray<T>`. | |
| InterfaceType arrayType = dartTypes.interfaceType( | |
| _commonElements.jsArrayClass, | |
| type.typeArguments, | |
| ); | |
| if (!_rtiNeed.classNeedsTypeArguments(type.element) || | |
| _equivalentToMissingRti(arrayType)) { | |
| return object; | |
| } | |
| HInstruction rti = _typeBuilder.analyzeTypeArgument( | |
| arrayType, | |
| sourceElement, | |
| ); | |
| // TODO(15489): Register at codegen. | |
| registry.registerInstantiation(type); | |
| return _callSetRuntimeTypeInfo(rti, object, sourceInformation); | |
| } | |
| @override | |
| void visitListLiteral(ir.ListLiteral node) { | |
| HInstruction listInstruction; | |
| if (node.isConst) { | |
| listInstruction = graph.addConstant( | |
| _elementMap.getConstantValue(node)!, | |
| closedWorld, | |
| ); | |
| } else { | |
| List<HInstruction> elements = []; | |
| for (ir.Expression element in node.expressions) { | |
| element.accept(this); | |
| elements.add(pop()); | |
| } | |
| listInstruction = _buildLiteralList(elements); | |
| add(listInstruction); | |
| final sourceInformation = _sourceInformationBuilder.buildListLiteral( | |
| node, | |
| ); | |
| final type = localsHandler.substInContext( | |
| _commonElements.listType(_elementMap.getDartType(node.typeArgument)), | |
| ) as InterfaceType; | |
| listInstruction = _setListRuntimeTypeInfoIfNeeded( | |
| listInstruction, | |
| type, | |
| sourceInformation, | |
| ); | |
| } | |
| AbstractValue type = _typeInferenceMap.typeOfListLiteral( | |
| node, | |
| _abstractValueDomain, | |
| ); | |
| if (_abstractValueDomain.containsAll(type).isDefinitelyFalse) { | |
| listInstruction.instructionType = type; | |
| } | |
| stack.add(listInstruction); | |
| } | |
| @override | |
| void visitSetLiteral(ir.SetLiteral node) { | |
| if (node.isConst) { | |
| stack.add( | |
| graph.addConstant(_elementMap.getConstantValue(node)!, closedWorld), | |
| ); | |
| return; | |
| } | |
| // The set literal constructors take the elements as a List. | |
| List<HInstruction> elements = []; | |
| for (ir.Expression element in node.expressions) { | |
| element.accept(this); | |
| elements.add(pop()); | |
| } | |
| // The constructor is a procedure because it's a factory. | |
| FunctionEntity constructor; | |
| List<HInstruction> inputs = []; | |
| if (elements.isEmpty) { | |
| constructor = _commonElements.setLiteralConstructorEmpty; | |
| } else { | |
| constructor = _commonElements.setLiteralConstructor; | |
| HLiteralList argList = _buildLiteralList(elements); | |
| add(argList); | |
| inputs.add(argList); | |
| } | |
| assert( | |
| constructor is ConstructorEntity && constructor.isFactoryConstructor, | |
| ); | |
| final type = localsHandler.substInContext( | |
| _commonElements.setType(_elementMap.getDartType(node.typeArgument)), | |
| ) as InterfaceType; | |
| final cls = constructor.enclosingClass!; | |
| if (_rtiNeed.classNeedsTypeArguments(cls)) { | |
| List<HInstruction> typeInputs = []; | |
| for (var argument in type.typeArguments) { | |
| typeInputs.add( | |
| _typeBuilder.analyzeTypeArgument(argument, sourceElement), | |
| ); | |
| } | |
| inputs.addAll(typeInputs); | |
| } | |
| // If runtime type information is needed and the set literal has no type | |
| // parameter, 'constructor' is a static function that forwards the call to | |
| // the factory constructor without a type parameter. | |
| assert( | |
| constructor.isFunction || | |
| (constructor is ConstructorEntity && | |
| constructor.isFactoryConstructor), | |
| ); | |
| // The instruction type will always be a subtype of the setLiteralClass, but | |
| // type inference might discover a more specific type or find nothing (in | |
| // dart2js unit tests). | |
| AbstractValue setType = _abstractValueDomain.createNonNullSubtype( | |
| _commonElements.setLiteralClass, | |
| ); | |
| AbstractValue returnTypeMask = _typeInferenceMap.getReturnTypeOf( | |
| constructor, | |
| ); | |
| AbstractValue instructionType = _abstractValueDomain.intersection( | |
| setType, | |
| returnTypeMask, | |
| ); | |
| _addImplicitInstantiation(type); | |
| _pushStaticInvocation( | |
| constructor, | |
| inputs, | |
| instructionType, | |
| const <DartType>[], | |
| sourceInformation: _sourceInformationBuilder.buildNew(node), | |
| ); | |
| _removeImplicitInstantiation(type); | |
| } | |
| @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 = []; | |
| for (ir.MapLiteralEntry mapEntry in node.entries) { | |
| mapEntry.key.accept(this); | |
| constructorArgs.add(pop()); | |
| mapEntry.value.accept(this); | |
| constructorArgs.add(pop()); | |
| } | |
| // The constructor is a procedure because it's a factory. | |
| FunctionEntity constructor; | |
| List<HInstruction> inputs = []; | |
| 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, | |
| ); | |
| final type = localsHandler.substInContext( | |
| _commonElements.mapType( | |
| _elementMap.getDartType(node.keyType), | |
| _elementMap.getDartType(node.valueType), | |
| ), | |
| ) as InterfaceType; | |
| final cls = constructor.enclosingClass!; | |
| if (_rtiNeed.classNeedsTypeArguments(cls)) { | |
| List<HInstruction> typeInputs = []; | |
| for (var argument in type.typeArguments) { | |
| typeInputs.add( | |
| _typeBuilder.analyzeTypeArgument(argument, sourceElement), | |
| ); | |
| } | |
| 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>[], | |
| sourceInformation: _sourceInformationBuilder.buildNew(node), | |
| ); | |
| _removeImplicitInstantiation(type); | |
| } | |
| @override | |
| void visitMapLiteralEntry(ir.MapLiteralEntry node) { | |
| failedAt( | |
| currentElementSpannable, | |
| 'ir.MapEntry should be handled in visitMapLiteral', | |
| ); | |
| } | |
| @override | |
| void visitRecordLiteral(ir.RecordLiteral node) { | |
| SourceInformation? sourceInformation = _sourceInformationBuilder | |
| .buildCreate(node); | |
| assert(!node.isConst); | |
| List<HInstruction> inputs = []; | |
| for (ir.Expression expression in node.positional) { | |
| expression.accept(this); | |
| inputs.add(pop()); | |
| } | |
| for (ir.NamedExpression namedExpression in node.named) { | |
| namedExpression.value.accept(this); | |
| inputs.add(pop()); | |
| } | |
| // TODO(50701): Choose class depending in inferred type of record fields | |
| // which might be better than the static type. | |
| RecordType dartType = | |
| _elementMap.getDartType(node.recordType) as RecordType; | |
| if (dartType.containsTypeVariables) { | |
| dartType = localsHandler.substInContext(dartType) as RecordType; | |
| } | |
| final recordRepresentation = _recordData.representationForStaticType( | |
| dartType, | |
| ); | |
| ClassEntity recordClass = recordRepresentation.cls; | |
| if (recordRepresentation.usesList) { | |
| // TODO(50081): Can we use `.constListType`? | |
| push(HLiteralList(inputs, _abstractValueDomain.fixedListType)); | |
| inputs = [pop()]; | |
| } | |
| AbstractValue type = | |
| _typeInferenceMap.typeOfRecordLiteral(node, _abstractValueDomain) ?? | |
| _abstractValueDomain.createFromStaticType(dartType).abstractValue; | |
| final allocation = HCreate(recordClass, inputs, type, sourceInformation); | |
| // TODO(50701): With traced record types there might be a better type. | |
| push(allocation); | |
| } | |
| @override | |
| void visitRecordIndexGet(ir.RecordIndexGet node) { | |
| final shape = recordShapeOfRecordType(node.receiverType); | |
| return _handleRecordFieldGet(node, node.receiver, shape, node.index); | |
| } | |
| @override | |
| void visitRecordNameGet(ir.RecordNameGet node) { | |
| final shape = recordShapeOfRecordType(node.receiverType); | |
| int index = shape.indexOfFieldName(node.name); | |
| return _handleRecordFieldGet(node, node.receiver, shape, index); | |
| } | |
| void _handleRecordFieldGet( | |
| ir.Expression node, | |
| ir.TreeNode receiverNode, | |
| RecordShape shape, | |
| int indexInShape, | |
| ) { | |
| receiverNode.accept(this); | |
| HInstruction receiver = pop(); | |
| SourceInformation? sourceInformation = _sourceInformationBuilder.buildGet( | |
| node, | |
| ); | |
| if (_recordData.representationForShape(shape) != null) { | |
| final recordType = | |
| _typeInferenceMap.receiverTypeOfGet(node) ?? | |
| _abstractValueDomain | |
| .createFromStaticType(_getStaticType(node)) | |
| .abstractValue; | |
| final fieldType = _abstractValueDomain.getGetterTypeInRecord( | |
| recordType, | |
| shape.getterNameOfIndex(indexInShape), | |
| ); | |
| final path = _recordData.pathForAccess(shape, indexInShape); | |
| if (path.index == null) { | |
| HFieldGet fieldGet = HFieldGet( | |
| path.field, | |
| receiver, | |
| fieldType, | |
| sourceInformation, | |
| isAssignable: false, | |
| ); | |
| push(fieldGet); | |
| } else { | |
| HFieldGet fieldGet = HFieldGet( | |
| path.field, | |
| receiver, | |
| _abstractValueDomain.constListType, | |
| sourceInformation, | |
| isAssignable: false, | |
| ); | |
| push(fieldGet); | |
| final list = pop(); | |
| push( | |
| HIndex( | |
| list, | |
| graph.addConstantInt(indexInShape, closedWorld), | |
| fieldType, | |
| ), | |
| ); | |
| } | |
| } else { | |
| // There are no records with this shape, so the path here must be | |
| // infeasible. | |
| push( | |
| HInvokeStatic( | |
| _commonElements.assertUnreachableMethod, | |
| [], | |
| _abstractValueDomain.emptyType, | |
| const [], | |
| )..sourceInformation = sourceInformation, | |
| ); | |
| // TODO(50081): Should we make subsequent code unreachable? | |
| } | |
| } | |
| @override | |
| void visitTypeLiteral(ir.TypeLiteral node) { | |
| final sourceInformation = _sourceInformationBuilder.buildGet(node); | |
| ir.DartType type = node.type; | |
| DartType dartType = _elementMap.getDartType(type); | |
| if (!dartType.containsTypeVariables) { | |
| final constant = _elementMap.getConstantValue(node)!; | |
| stack.add( | |
| graph.addConstant( | |
| constant, | |
| closedWorld, | |
| sourceInformation: sourceInformation, | |
| ), | |
| ); | |
| return; | |
| } | |
| // 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 = localsHandler.substInContext(dartType); | |
| HInstruction value = _typeBuilder.analyzeTypeArgument( | |
| dartType, | |
| sourceElement, | |
| sourceInformation: sourceInformation, | |
| ); | |
| _pushStaticInvocation( | |
| _commonElements.createRuntimeType, | |
| [value], | |
| _typeInferenceMap.getReturnTypeOf(_commonElements.createRuntimeType), | |
| const <DartType>[], | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| @override | |
| void visitStaticGet(ir.StaticGet node) { | |
| ir.Member staticTarget = node.target; | |
| final sourceInformation = _sourceInformationBuilder.buildGet(node); | |
| if (staticTarget is ir.Procedure && | |
| staticTarget.kind == ir.ProcedureKind.Getter) { | |
| final getter = _elementMap.getMember(staticTarget) as FunctionEntity; | |
| // Invoke the getter | |
| _pushStaticInvocation( | |
| getter, | |
| const [], | |
| _typeInferenceMap.getReturnTypeOf(getter), | |
| const <DartType>[], | |
| sourceInformation: sourceInformation, | |
| ); | |
| } else if (staticTarget is ir.Field) { | |
| FieldEntity field = _elementMap.getField(staticTarget); | |
| FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(field); | |
| if (fieldData.isEager) { | |
| push( | |
| HStatic( | |
| field, | |
| _typeInferenceMap.getInferredTypeOf(field), | |
| sourceInformation, | |
| ), | |
| ); | |
| } else if (fieldData.isEffectivelyConstant) { | |
| OutputUnit outputUnit = closedWorld.outputUnitData.outputUnitForMember( | |
| field, | |
| ); | |
| final initialValue = fieldData.initialValue!; | |
| // 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 (!closedWorld.outputUnitData.hasOnlyNonDeferredImportPaths( | |
| targetElement, | |
| field, | |
| )) { | |
| final deferredConstant = DeferredGlobalConstantValue( | |
| initialValue, | |
| outputUnit, | |
| ); | |
| registry.registerConstantUse(ConstantUse.deferred(deferredConstant)); | |
| stack.add( | |
| graph.addDeferredConstant( | |
| deferredConstant, | |
| sourceInformation, | |
| closedWorld, | |
| ), | |
| ); | |
| } else { | |
| stack.add( | |
| graph.addConstant( | |
| initialValue, | |
| closedWorld, | |
| sourceInformation: sourceInformation, | |
| ), | |
| ); | |
| } | |
| } else { | |
| assert( | |
| fieldData.isLazy, | |
| "Unexpected field data for $field: $fieldData", | |
| ); | |
| push( | |
| HLazyStatic( | |
| field, | |
| _typeInferenceMap.getInferredTypeOf(field), | |
| sourceInformation, | |
| ), | |
| ); | |
| } | |
| } else { | |
| // TODO(johnniwinther): This is a constant tear off, so we should have | |
| // created a constant value instead. Remove this case when we use CFE | |
| // constants. | |
| final member = _elementMap.getMember(staticTarget); | |
| push( | |
| HStatic( | |
| member, | |
| _typeInferenceMap.getInferredTypeOf(member), | |
| sourceInformation, | |
| ), | |
| ); | |
| } | |
| } | |
| @override | |
| void visitStaticTearOff(ir.StaticTearOff node) { | |
| // TODO(johnniwinther): This is a constant tear off, so we should have | |
| // created a constant value instead. Remove this case when we use CFE | |
| // constants. | |
| ir.Member staticTarget = node.target; | |
| final sourceInformation = _sourceInformationBuilder.buildGet(node); | |
| final member = _elementMap.getMember(staticTarget); | |
| push( | |
| 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) { | |
| final setter = _elementMap.getMember(staticTarget) as FunctionEntity; | |
| // Invoke the setter | |
| _pushStaticInvocation( | |
| setter, | |
| [value], | |
| _typeInferenceMap.getReturnTypeOf(setter), | |
| const <DartType>[], | |
| sourceInformation: _sourceInformationBuilder.buildSet(node), | |
| ); | |
| pop(); | |
| } else { | |
| final target = _elementMap.getMember(staticTarget) as FieldEntity; | |
| if (!_fieldAnalysis.getFieldData(target).isElided) { | |
| add( | |
| HStaticStore( | |
| target, | |
| _typeBuilder.potentiallyCheckOrTrustTypeOfAssignment( | |
| target, | |
| value, | |
| _getDartTypeIfValid(staticTarget.setterType), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| stack.add(value); | |
| } | |
| void _handlePropertyGet( | |
| ir.Expression node, | |
| ir.Expression receiver, | |
| ir.Name name, | |
| ) { | |
| receiver.accept(this); | |
| HInstruction receiverInstruction = pop(); | |
| _pushDynamicInvocation( | |
| node, | |
| _getStaticType(receiver), | |
| _typeInferenceMap.receiverTypeOfGet(node), | |
| Selector.getter(_elementMap.getName(name)), | |
| [receiverInstruction], | |
| const <DartType>[], | |
| _sourceInformationBuilder.buildGet(node), | |
| ); | |
| } | |
| @override | |
| void visitInstanceGet(ir.InstanceGet node) { | |
| _handlePropertyGet(node, node.receiver, node.name); | |
| } | |
| @override | |
| void visitInstanceTearOff(ir.InstanceTearOff node) { | |
| _handlePropertyGet(node, node.receiver, node.name); | |
| } | |
| @override | |
| void visitDynamicGet(ir.DynamicGet node) { | |
| _handlePropertyGet(node, node.receiver, node.name); | |
| } | |
| @override | |
| void visitFunctionTearOff(ir.FunctionTearOff node) { | |
| _handlePropertyGet(node, node.receiver, ir.Name.callName); | |
| } | |
| @override | |
| void visitVariableGet(ir.VariableGet node) { | |
| ir.Variable variable = node.variable; | |
| final letBinding = _letBindings[variable]; | |
| if (letBinding != null) { | |
| stack.add(letBinding); | |
| return; | |
| } | |
| final local = _localsMap.getLocalVariable(node.variable); | |
| final readLocal = localsHandler.readLocal( | |
| local, | |
| sourceInformation: _sourceInformationBuilder.buildGet(node), | |
| ); | |
| if (node.promotedType != null) { | |
| // Use the prompted type by inserting a HTypeKnown refinement node. | |
| // | |
| // The front end does not tell us _why_ the type was promoted, so the | |
| // HTypeKnown is pinned at the variable reference rather than at the | |
| // control flow reponsible for the promotion. Pinning at the variable | |
| // reference impairs code motion. | |
| // | |
| // Many of the promoted types are also refined by the optimizer (in | |
| // SsaTypeConversionInserter, where controlling phis are handled). This | |
| // causes a similar HTypeKnown to be inserted, but pinned at a location | |
| // responsible the promotion. The optimizer-inserted HTypeKnown nodes | |
| // dominate the nodes inserted here, allowing the nodes inserted here to | |
| // be removed as redundant, sometimes lifting the code motion | |
| // restriction. There is not an exact match between the front-end | |
| // inference, so some of the promotions remain. | |
| final trusted = _typeBuilder.trustPromotedType( | |
| readLocal, | |
| _getDartTypeIfValid(node.promotedType!), | |
| ); | |
| if (trusted != readLocal) { | |
| push(trusted); | |
| return; | |
| } | |
| } | |
| stack.add(readLocal); | |
| } | |
| void _handlePropertySet( | |
| ir.Expression node, | |
| ir.Expression receiver, | |
| ir.Name name, | |
| ir.Expression value, | |
| ) { | |
| receiver.accept(this); | |
| HInstruction receiverInstruction = pop(); | |
| value.accept(this); | |
| HInstruction valueInstruction = pop(); | |
| _pushDynamicInvocation( | |
| node, | |
| _getStaticType(receiver), | |
| _typeInferenceMap.receiverTypeOfSet(node, _abstractValueDomain), | |
| Selector.setter(_elementMap.getName(name)), | |
| [receiverInstruction, valueInstruction], | |
| const <DartType>[], | |
| _sourceInformationBuilder.buildAssignment(node), | |
| ); | |
| pop(); | |
| stack.add(valueInstruction); | |
| } | |
| @override | |
| void visitInstanceSet(ir.InstanceSet node) { | |
| _handlePropertySet(node, node.receiver, node.name, node.value); | |
| } | |
| @override | |
| void visitDynamicSet(ir.DynamicSet node) { | |
| _handlePropertySet(node, node.receiver, node.name, node.value); | |
| } | |
| @override | |
| void visitSuperPropertySet(ir.SuperPropertySet node) { | |
| final sourceInformation = _sourceInformationBuilder.buildAssignment(node); | |
| node.value.accept(this); | |
| HInstruction value = pop(); | |
| final target = getEffectiveSuperTarget(node.interfaceTarget); | |
| MemberEntity member = _elementMap.getMember(target); | |
| _buildInvokeSuper( | |
| _elementMap.getSelector(node), | |
| _elementMap.getClass(_containingClass(node)), | |
| member, | |
| [value], | |
| const <DartType>[], | |
| sourceInformation, | |
| ); | |
| pop(); | |
| stack.add(value); | |
| } | |
| @override | |
| void visitVariableSet(ir.VariableSet node) { | |
| node.value.accept(this); | |
| HInstruction value = pop(); | |
| _visitLocalSetter( | |
| node.variable, | |
| value, | |
| _sourceInformationBuilder.buildAssignment(node), | |
| ); | |
| } | |
| @override | |
| void visitVariableDeclaration(ir.VariableDeclaration node) { | |
| defaultVariable(node.variable); | |
| } | |
| @override | |
| void visitVariableStatement(ir.VariableStatement node) { | |
| visitVariableDeclaration(node.declaration); | |
| } | |
| @override | |
| void defaultVariable(ir.Variable node) { | |
| Local local = _localsMap.getLocalVariable(node); | |
| if (node.initializer == null) { | |
| HInstruction initialValue = graph.addConstantNull(closedWorld); | |
| localsHandler.updateLocal(local, initialValue); | |
| } else if (node.isConst) { | |
| final constant = _elementMap.getConstantValue(node.initializer); | |
| assert(constant != null, failedAt(currentElementSpannable)); | |
| HInstruction initialValue = graph.addConstant(constant!, closedWorld); | |
| localsHandler.updateLocal(local, initialValue); | |
| } else { | |
| node.initializer!.accept(this); | |
| HInstruction initialValue = pop(); | |
| _visitLocalSetter( | |
| node, | |
| initialValue, | |
| _sourceInformationBuilder.buildAssignment(node), | |
| ); | |
| // Ignore value | |
| pop(); | |
| } | |
| } | |
| void _visitLocalSetter( | |
| ir.Variable variable, | |
| HInstruction value, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| Local local = _localsMap.getLocalVariable(variable); | |
| // Give the value a name if it doesn't have one already. | |
| value.sourceElement ??= local; | |
| stack.add(value); | |
| localsHandler.updateLocal( | |
| local, | |
| _typeBuilder.potentiallyCheckOrTrustTypeOfAssignment( | |
| _currentFrame!.member, | |
| value, | |
| _getDartTypeIfValid(variable.type), | |
| ), | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| @override | |
| void visitLet(ir.Let node) { | |
| ir.Variable variable = node.variable; | |
| variable.initializer!.accept(this); | |
| HInstruction initializedValue = pop(); | |
| // TODO(sra): Apply inferred type information. | |
| _letBindings[variable] = initializedValue; | |
| // TODO(43456): Use `!_isReachable` for better dead code removal. | |
| if (isAborted()) { | |
| stack.add(graph.addConstantUnreachable(closedWorld)); | |
| } else { | |
| node.body.accept(this); | |
| } | |
| } | |
| @override | |
| void visitBlockExpression(ir.BlockExpression node) { | |
| node.body.accept(this); | |
| // Body can be partially generated due to an exception exit and be missing | |
| // bindings referenced in the value. | |
| if (!_isReachable) { | |
| stack.add(graph.addConstantUnreachable(closedWorld)); | |
| } else { | |
| final previous = _inBlockExpression; | |
| try { | |
| _inBlockExpression = true; | |
| node.value.accept(this); | |
| } finally { | |
| _inBlockExpression = previous; | |
| } | |
| } | |
| } | |
| /// Generate instructions to evaluate the positional arguments in source | |
| /// order. | |
| List<HInstruction> _visitPositionalArguments(ir.Arguments arguments) { | |
| List<HInstruction> result = []; | |
| for (ir.Expression argument in arguments.positional) { | |
| argument.accept(this); | |
| result.add(pop()); | |
| } | |
| return result; | |
| } | |
| /// Generate instructions to evaluate the named arguments in source order. | |
| /// Returns a fresh map from parameter name to evaluated argument. | |
| Map<String, HInstruction> _visitNamedArguments(ir.Arguments arguments) { | |
| Map<String, HInstruction> values = {}; | |
| for (ir.NamedExpression argument in arguments.named) { | |
| argument.value.accept(this); | |
| values[argument.name] = pop(); | |
| } | |
| return values; | |
| } | |
| /// Builds the list of instructions for the expressions in the arguments to a | |
| /// dynamic target (member function). Dynamic targets use stubs to add | |
| /// defaulted arguments, so (unlike static targets) we do not add the default | |
| /// values. | |
| List<HInstruction> _visitArgumentsForDynamicTarget( | |
| Selector selector, | |
| ir.Arguments arguments, | |
| List<DartType> typeArguments, [ | |
| SourceInformation? sourceInformation, | |
| ]) { | |
| List<HInstruction> values = _visitPositionalArguments(arguments); | |
| if (arguments.named.isNotEmpty) { | |
| Map<String, HInstruction> namedValues = _visitNamedArguments(arguments); | |
| for (String name in selector.callStructure.getOrderedNamedArguments()) { | |
| values.add(namedValues[name]!); | |
| } | |
| } | |
| _addTypeArguments(values, typeArguments, sourceInformation); | |
| return values; | |
| } | |
| /// Build the argument list for JS-interop invocations, which have slightly | |
| /// different semantics than dart because of JS's null vs undefined and lack | |
| /// of named arguments. A `null` argument indicates an optional argument which | |
| /// was not provided. | |
| List<HInstruction?> _visitArgumentsForNativeStaticTarget( | |
| ir.FunctionNode target, | |
| ir.Arguments arguments, | |
| ) { | |
| // Visit arguments in source order, then re-order and fill in defaults. | |
| List<HInstruction?> values = List.of(_visitPositionalArguments(arguments)); | |
| if (target.namedParameters.isNotEmpty) { | |
| // Only anonymous factory or extension type literal constructors involving | |
| // JS interop are allowed to have named parameters. Otherwise, throw an | |
| // error. | |
| final member = target.parent as ir.Member; | |
| final function = _elementMap.getMember(member) as FunctionEntity; | |
| bool isAnonymousFactory = | |
| function is ConstructorEntity && | |
| function.isFactoryConstructor && | |
| _nativeData.isAnonymousJsInteropClass(function.enclosingClass); | |
| // JS interop checks assert that the only extension type interop member | |
| // that has named parameters is an object literal constructor. | |
| // TODO(54968): We should handle the lowering for object literal | |
| // constructors in the interop transformer somehow instead and avoid | |
| // assuming all such members are object literal constructors or | |
| // otherwise paying the cost to verify by indexing extension types. | |
| bool isObjectLiteralConstructor = member.isExtensionTypeMember; | |
| if (isAnonymousFactory || isObjectLiteralConstructor) { | |
| // TODO(sra): Have a "CompiledArguments" structure to just update with | |
| // what values we have rather than creating a map and de-populating it. | |
| Map<String, HInstruction> namedValues = _visitNamedArguments(arguments); | |
| // Visit named arguments in parameter-position order, selecting provided | |
| // or default value. | |
| var namedParameters = target.namedParameters.toList(); | |
| assert( | |
| namedValues.keys.every( | |
| (k) => namedParameters.any((p) => p.parameterName == k), | |
| ), | |
| ); | |
| namedParameters.sort(nativeOrdering); | |
| for (ir.NamedParameter parameter in namedParameters) { | |
| final value = namedValues[parameter.parameterName]; | |
| values.add(value); | |
| } | |
| } | |
| } | |
| return values; | |
| } | |
| /// Fills [typeArguments] with the type arguments needed for [selector] and | |
| /// returns the selector corresponding to the passed type arguments. | |
| /// | |
| /// If [isImplicitCall] is `true`, the target of the invocation can be either | |
| /// the target of the [selector] or of the corresponding `.call` selector. In | |
| /// this case we need to check both selectors to see if we need to pass type | |
| /// arguments. This occurs for field/getter invocations. | |
| Selector _fillDynamicTypeArguments( | |
| Selector selector, | |
| ir.Arguments arguments, | |
| List<DartType> typeArguments, { | |
| bool isImplicitCall = false, | |
| }) { | |
| if (selector.typeArgumentCount > 0) { | |
| if (_rtiNeed.selectorNeedsTypeArguments(selector) || | |
| (isImplicitCall && | |
| _rtiNeed.selectorNeedsTypeArguments(selector.toCallSelector()))) { | |
| typeArguments.addAll(arguments.types.map(_elementMap.getDartType)); | |
| } else { | |
| return selector.toNonGeneric(); | |
| } | |
| } | |
| return selector; | |
| } | |
| List<DartType> _getConstructorTypeArguments( | |
| ConstructorEntity constructor, | |
| ir.Arguments arguments, | |
| ) { | |
| // TODO(johnniwinther): Pass type arguments to constructors like calling | |
| // a generic method. | |
| return const <DartType>[]; | |
| } | |
| // TODO(johnniwinther): Remove this when type arguments are passed to | |
| // constructors like calling a generic method. | |
| List<DartType> _getClassTypeArguments( | |
| ClassEntity cls, | |
| ir.Arguments arguments, | |
| ) { | |
| if (_rtiNeed.classNeedsTypeArguments(cls)) { | |
| return arguments.types.map(_elementMap.getDartType).toList(); | |
| } | |
| return const <DartType>[]; | |
| } | |
| List<DartType> _getStaticTypeArguments( | |
| FunctionEntity function, | |
| ir.Arguments arguments, | |
| ) { | |
| if (_rtiNeed.methodNeedsTypeArguments(function)) { | |
| return arguments.types.map(_elementMap.getDartType).toList(); | |
| } | |
| return const <DartType>[]; | |
| } | |
| /// Build argument list in canonical order for a static [target], including | |
| /// filling in the default argument value. | |
| List<HInstruction> _visitArgumentsForStaticTarget( | |
| ir.FunctionNode target, | |
| ParameterStructure parameterStructure, | |
| ir.Arguments arguments, | |
| List<DartType> typeArguments, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| // Visit arguments in source order, then re-order and fill in defaults. | |
| List<HInstruction> values = _visitPositionalArguments(arguments); | |
| while (values.length < parameterStructure.positionalParameters) { | |
| ir.PositionalParameter parameter = | |
| target.positionalParameters[values.length]; | |
| values.add(_defaultValueForParameter(parameter)); | |
| } | |
| if (parameterStructure.namedParameters.isNotEmpty) { | |
| Map<String, HInstruction> namedValues = _visitNamedArguments(arguments); | |
| // Visit named arguments in parameter-position order, selecting provided | |
| // or default value. | |
| // TODO(sra): Ensure the stored order is canonical so we don't have to | |
| // sort. The old builder uses CallStructure.makeArgumentList which depends | |
| // on the old element model. | |
| List<ir.NamedParameter> namedParameters = | |
| target.namedParameters | |
| // Filter elided parameters. | |
| .where( | |
| (p) => parameterStructure.namedParameters.contains( | |
| p.parameterName, | |
| ), | |
| ) | |
| .toList() | |
| ..sort(namedOrdering); | |
| for (ir.NamedParameter parameter in namedParameters) { | |
| final value = namedValues[parameter.parameterName]; | |
| if (value == null) { | |
| values.add(_defaultValueForParameter(parameter)); | |
| } else { | |
| values.add(value); | |
| namedValues.remove(parameter.parameterName); | |
| } | |
| } | |
| assert(namedValues.isEmpty); | |
| } else { | |
| assert(arguments.named.isEmpty); | |
| } | |
| _addTypeArguments(values, typeArguments, sourceInformation); | |
| return values; | |
| } | |
| void _addTypeArguments( | |
| List<HInstruction?> values, | |
| List<DartType> typeArguments, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (typeArguments.isEmpty) return; | |
| for (DartType type in typeArguments) { | |
| values.add( | |
| _typeBuilder.analyzeTypeArgument( | |
| type, | |
| sourceElement, | |
| sourceInformation: sourceInformation, | |
| ), | |
| ); | |
| } | |
| } | |
| HInstruction _defaultValueForParameter(ir.Variable parameter) { | |
| final constant = _elementMap.getConstantValue( | |
| parameter.initializer, | |
| implicitNull: true, | |
| ); | |
| assert(constant != null, failedAt(currentElementSpannable)); | |
| return graph.addConstant(constant!, closedWorld); | |
| } | |
| @override | |
| void visitStaticInvocation(ir.StaticInvocation node) { | |
| if (ir.StaticWeakReferences.isWeakReference(node)) { | |
| final weakTarget = ir.StaticWeakReferences.getWeakReferenceTarget(node); | |
| if (_elementMap.containsMethod(weakTarget)) { | |
| final argument = ir.StaticWeakReferences.getWeakReferenceArgument(node); | |
| argument.accept(this); | |
| return; | |
| } | |
| stack.add(graph.addConstantNull(closedWorld)); | |
| return; | |
| } | |
| if (ir.ExternalEffect.isExternalEffect(node)) { | |
| stack.add(graph.addConstantNull(closedWorld)); | |
| return; | |
| } | |
| ir.Procedure target = node.target; | |
| final sourceInformation = _sourceInformationBuilder.buildCall(node, node); | |
| final function = _elementMap.getMember(target) as FunctionEntity; | |
| if (_commonElements.isForeignHelper(function)) { | |
| _handleInvokeStaticForeign(node, function, sourceInformation); | |
| return; | |
| } | |
| if (_commonElements.isExtractTypeArguments(function) && | |
| _handleExtractTypeArguments(node, sourceInformation)) { | |
| return; | |
| } | |
| AbstractValue typeMask = _typeInferenceMap.getReturnTypeOf(function); | |
| List<DartType> typeArguments = _getStaticTypeArguments( | |
| function, | |
| node.arguments, | |
| ); | |
| // Recognize e.g. `bool.fromEnvironment('x')` | |
| // TODO(sra): Can we delete this code now that the CFE does constant folding | |
| // for us during loading? | |
| if (function.isExternal && | |
| function is ConstructorEntity && | |
| function.isFromEnvironmentConstructor) { | |
| if (node.isConst) { | |
| // Just like all const constructors (see visitConstructorInvocation). | |
| stack.add( | |
| graph.addConstant( | |
| _elementMap.getConstantValue(node)!, | |
| closedWorld, | |
| sourceInformation: sourceInformation, | |
| ), | |
| ); | |
| } else { | |
| _generateUnsupportedError( | |
| '${function.enclosingClass.name}.${function.name} ' | |
| 'can only be used as a const constructor', | |
| sourceInformation, | |
| ); | |
| } | |
| return; | |
| } | |
| if (closedWorld.nativeData.isJsInteropMember(function)) { | |
| final arguments = _visitArgumentsForNativeStaticTarget( | |
| target.function, | |
| node.arguments, | |
| ); | |
| if (function is ConstructorEntity && function.isFactoryConstructor) { | |
| _handleInvokeNativeFactoryConstructor( | |
| node, | |
| function, | |
| typeMask, | |
| arguments, | |
| sourceInformation, | |
| ); | |
| return; | |
| } | |
| // Static methods currently ignore the type parameters. | |
| _pushStaticNativeInvocation( | |
| function, | |
| arguments, | |
| typeMask, | |
| typeArguments, | |
| sourceInformation: sourceInformation, | |
| ); | |
| } else { | |
| final arguments = _visitArgumentsForStaticTarget( | |
| target.function, | |
| function.parameterStructure, | |
| node.arguments, | |
| typeArguments, | |
| sourceInformation, | |
| ); | |
| if (function is ConstructorEntity && function.isFactoryConstructor) { | |
| _handleInvokeFactoryConstructor( | |
| node, | |
| function, | |
| typeMask, | |
| arguments, | |
| sourceInformation, | |
| ); | |
| return; | |
| } | |
| // Static methods currently ignore the type parameters. | |
| _pushStaticInvocation( | |
| function, | |
| arguments, | |
| typeMask, | |
| typeArguments, | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| } | |
| void _handleInvokeFactoryConstructor( | |
| ir.StaticInvocation invocation, | |
| ConstructorEntity function, | |
| AbstractValue typeMask, | |
| List<HInstruction> arguments, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| // Recognize `JSArray<E>.typed(allocation)`. | |
| if (function == _commonElements.jsArrayTypedConstructor) { | |
| if (invocation.arguments.named.isEmpty) { | |
| if (invocation.arguments.positional.length == 1) { | |
| assert(arguments.length == 1); | |
| _handleInvokeJSArrayTypedConstructor( | |
| invocation, | |
| function, | |
| typeMask, | |
| arguments, | |
| sourceInformation, | |
| ); | |
| return; | |
| } | |
| } | |
| } | |
| InterfaceType instanceType = _elementMap.createInterfaceType( | |
| invocation.target.enclosingClass!, | |
| invocation.arguments.types, | |
| ); | |
| // Factory constructors take type parameters. | |
| List<DartType> typeArguments = _getConstructorTypeArguments( | |
| function, | |
| invocation.arguments, | |
| ); | |
| // This could be a List factory constructor that returned a fresh list and | |
| // we have a call-site-specific type from type inference. | |
| var allocatedListType = globalInferenceResults.typeOfNewList(invocation); | |
| AbstractValue resultType = allocatedListType ?? typeMask; | |
| // TODO(johnniwinther): Remove this when type arguments are passed to | |
| // constructors like calling a generic method. | |
| _addTypeArguments( | |
| arguments, | |
| _getClassTypeArguments(function.enclosingClass, invocation.arguments), | |
| sourceInformation, | |
| ); | |
| instanceType = localsHandler.substInContext(instanceType) as InterfaceType; | |
| _addImplicitInstantiation(instanceType); | |
| _pushStaticInvocation( | |
| function, | |
| arguments, | |
| resultType, | |
| typeArguments, | |
| sourceInformation: sourceInformation, | |
| instanceType: instanceType, | |
| ); | |
| if (allocatedListType != null) { | |
| HInstruction newInstance = stack.last.nonCheck(); | |
| if (newInstance is HInvokeStatic) { | |
| newInstance.setAllocation(true); | |
| } | |
| // Is the constructor call one from which we can extract the length | |
| // argument? | |
| bool isFixedList = false; | |
| if (_abstractValueDomain.isGrowableArray(resultType).isDefinitelyFalse) { | |
| // These constructors all take a length as the first argument. | |
| if (_commonElements.isNamedListConstructor('filled', function) || | |
| _commonElements.isNamedListConstructor('generate', function) || | |
| _commonElements.isNamedJSArrayConstructor('fixed', function) || | |
| _commonElements.isNamedJSArrayConstructor( | |
| 'allocateFixed', | |
| function, | |
| )) { | |
| isFixedList = true; | |
| } | |
| } | |
| if (_abstractValueDomain.isTypedArray(resultType).isDefinitelyTrue) { | |
| // The unnamed constructors of typed arrays take a length as the first | |
| // argument. | |
| if (function.name == '') isFixedList = true; | |
| // TODO(sra): Can this misfire? | |
| } | |
| if (isFixedList) { | |
| if (newInstance is HInvokeStatic || newInstance is HForeignCode) { | |
| graph.allocatedFixedLists.add(newInstance); | |
| } | |
| } | |
| } | |
| } | |
| void _handleInvokeNativeFactoryConstructor( | |
| ir.StaticInvocation invocation, | |
| ConstructorEntity function, | |
| AbstractValue typeMask, | |
| List<HInstruction?> arguments, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| InterfaceType instanceType = _elementMap.createInterfaceType( | |
| invocation.target.enclosingClass!, | |
| invocation.arguments.types, | |
| ); | |
| // Factory constructors take type parameters. | |
| List<DartType> typeArguments = _getConstructorTypeArguments( | |
| function, | |
| invocation.arguments, | |
| ); | |
| // TODO(johnniwinther): Remove this when type arguments are passed to | |
| // constructors like calling a generic method. | |
| _addTypeArguments( | |
| arguments, | |
| _getClassTypeArguments(function.enclosingClass, invocation.arguments), | |
| sourceInformation, | |
| ); | |
| instanceType = localsHandler.substInContext(instanceType) as InterfaceType; | |
| _addImplicitInstantiation(instanceType); | |
| _pushStaticNativeInvocation( | |
| function, | |
| arguments, | |
| typeMask, | |
| typeArguments, | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| /// Handle the `JSArray<E>.typed` constructor, which returns its argument, | |
| /// which must be a JSArray, with the JSArray type Rti information added on a | |
| /// property. | |
| void _handleInvokeJSArrayTypedConstructor( | |
| ir.StaticInvocation invocation, | |
| ConstructorEntity function, | |
| AbstractValue typeMask, | |
| List<HInstruction> arguments, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| // TODO(sra): We rely here on inlining the identity-like factory | |
| // constructor. Instead simply select the single argument and add the type. | |
| // Factory constructors take type parameters. | |
| List<DartType> typeArguments = _getConstructorTypeArguments( | |
| function, | |
| invocation.arguments, | |
| ); | |
| // TODO(johnniwinther): Remove this when type arguments are passed to | |
| // constructors like calling a generic method. | |
| _addTypeArguments( | |
| arguments, | |
| _getClassTypeArguments(function.enclosingClass, invocation.arguments), | |
| sourceInformation, | |
| ); | |
| _pushStaticInvocation( | |
| function, | |
| arguments, | |
| typeMask, | |
| typeArguments, | |
| sourceInformation: sourceInformation, | |
| ); | |
| InterfaceType type = _elementMap.createInterfaceType( | |
| invocation.target.enclosingClass!, | |
| invocation.arguments.types, | |
| ); | |
| stack.add(_setListRuntimeTypeInfoIfNeeded(pop(), type, sourceInformation)); | |
| } | |
| /// Replace calls to `extractTypeArguments` with equivalent code. Returns | |
| /// `true` if `extractTypeArguments` is handled. | |
| bool _handleExtractTypeArguments( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| // Expand calls as follows: | |
| // | |
| // r = extractTypeArguments<Map>(e, f) | |
| // --> | |
| // environment = HInstanceEnvironment(e); | |
| // T1 = HTypeEval( environment, 'Map.K'); | |
| // T2 = HTypeEval( environment, 'Map.V'); | |
| // r = f<T1, T2>(); | |
| // | |
| // TODO(sra): Should we add a check before the variable extraction? We could | |
| // add a type check (which would permit `null`), or add an is-check with an | |
| // explicit throw. | |
| if (invocation.arguments.positional.length != 2) return false; | |
| if (invocation.arguments.named.isNotEmpty) return false; | |
| var types = invocation.arguments.types; | |
| if (types.length != 1) return false; | |
| // The type should be a single type name. | |
| ir.DartType type = types.first; | |
| DartType typeValue = localsHandler.substInContext( | |
| _elementMap.getDartType(type), | |
| ); | |
| if (typeValue is! InterfaceType) return false; | |
| InterfaceType interfaceType = typeValue; | |
| if (!dartTypes.treatAsRawType(interfaceType)) return false; | |
| ClassEntity cls = interfaceType.element; | |
| InterfaceType thisType = _elementEnvironment.getThisType(cls); | |
| List<HInstruction> arguments = _visitPositionalArguments( | |
| invocation.arguments, | |
| ); | |
| HInstruction object = arguments[0]; | |
| HInstruction closure = arguments[1]; | |
| List<HInstruction> inputs = [closure]; | |
| List<DartType> typeArguments = []; | |
| closedWorld.registerExtractTypeArguments(cls); | |
| HInstruction instanceType = HInstanceEnvironment( | |
| object, | |
| _abstractValueDomain.dynamicType, | |
| ); | |
| add(instanceType); | |
| TypeEnvironmentStructure envStructure = FullTypeEnvironmentStructure( | |
| classType: thisType, | |
| ); | |
| for (var variable in thisType.typeArguments) { | |
| assert(variable is TypeVariableType); | |
| typeArguments.add(variable); | |
| TypeRecipe recipe = TypeExpressionRecipe(variable); | |
| HInstruction typeEval = HTypeEval( | |
| instanceType, | |
| envStructure, | |
| recipe, | |
| _abstractValueDomain.dynamicType, | |
| ); | |
| add(typeEval); | |
| inputs.add(typeEval); | |
| } | |
| // TODO(sra): In compliance mode, insert a check that [closure] is a | |
| // function of N type arguments. | |
| Selector selector = Selector.callClosure( | |
| 0, | |
| const <String>[], | |
| typeArguments.length, | |
| ); | |
| final receiverStaticType = _getStaticType( | |
| invocation.arguments.positional[1], | |
| ); | |
| AbstractValue receiverType = _abstractValueDomain | |
| .createFromStaticType(receiverStaticType) | |
| .abstractValue; | |
| push( | |
| HInvokeClosure( | |
| selector, | |
| receiverType, | |
| inputs, | |
| _abstractValueDomain.dynamicType, | |
| typeArguments, | |
| ), | |
| ); | |
| return true; | |
| } | |
| void _handleInvokeStaticForeign( | |
| ir.StaticInvocation invocation, | |
| MemberEntity member, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| final name = member.name; | |
| if (name == 'JS') { | |
| _handleForeignJs(invocation, sourceInformation); | |
| } else if (name == 'DART_CLOSURE_TO_JS') { | |
| _handleForeignDartClosureToJs( | |
| invocation, | |
| 'DART_CLOSURE_TO_JS', | |
| sourceInformation, | |
| ); | |
| } else if (name == 'RAW_DART_FUNCTION_REF') { | |
| _handleForeignRawFunctionRef( | |
| invocation, | |
| 'RAW_DART_FUNCTION_REF', | |
| sourceInformation, | |
| ); | |
| } else if (name == 'JS_GET_NAME') { | |
| _handleForeignJsGetName(invocation, sourceInformation); | |
| } else if (name == 'JS_EMBEDDED_GLOBAL') { | |
| _handleForeignJsEmbeddedGlobal(invocation, sourceInformation); | |
| } else if (name == 'JS_BUILTIN') { | |
| _handleForeignJsBuiltin(invocation, sourceInformation); | |
| } else if (name == 'JS_TRUE') { | |
| _handleForeignJsBool(true, invocation, sourceInformation); | |
| } else if (name == 'JS_FALSE') { | |
| _handleForeignJsBool(false, invocation, sourceInformation); | |
| } else if (name == 'JS_EFFECT') { | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| } else if (name == 'JS_INTERCEPTOR_CONSTANT') { | |
| _handleJsInterceptorConstant(invocation, sourceInformation); | |
| } else if (name == 'getInterceptor') { | |
| _handleForeignGetInterceptor(invocation, sourceInformation); | |
| } else if (name == 'getJSArrayInteropRti') { | |
| _handleForeignGetJSArrayInteropRti(invocation, sourceInformation); | |
| } else if (name == 'JS_RAW_EXCEPTION') { | |
| _handleJsRawException(invocation, sourceInformation); | |
| } else if (name == 'JS_STRING_CONCAT') { | |
| _handleJsStringConcat(invocation, sourceInformation); | |
| } else if (name == '_createInvocationMirror') { | |
| _handleCreateInvocationMirror(invocation, sourceInformation); | |
| } else if (name == 'TYPE_REF') { | |
| _handleForeignTypeRef(invocation, sourceInformation); | |
| } else if (name == 'createJsSentinel') { | |
| _handleForeignCreateJsSentinel(invocation, sourceInformation); | |
| } else if (name == 'isJsSentinel') { | |
| _handleForeignIsJsSentinel(invocation, sourceInformation); | |
| } else if (name == '_lateReadCheck') { | |
| _handleLateReadCheck(invocation, sourceInformation); | |
| } else if (name == '_lateWriteOnceCheck') { | |
| _handleLateWriteOnceCheck(invocation, sourceInformation); | |
| } else if (name == '_lateInitializeOnceCheck') { | |
| _handleLateInitializeOnceCheck(invocation, sourceInformation); | |
| } else if (name == 'HCharCodeAt') { | |
| _handleCharCodeAt(invocation, sourceInformation); | |
| } else if (name == 'HArrayFlagsGet') { | |
| _handleArrayFlagsGet(invocation, sourceInformation); | |
| } else if (name == 'HArrayFlagsSet') { | |
| _handleArrayFlagsSet(invocation, sourceInformation); | |
| } else if (name == 'HArrayFlagsCheck') { | |
| _handleArrayFlagsCheck(invocation, sourceInformation); | |
| } else { | |
| reporter.internalError( | |
| _elementMap.getSpannable(targetElement, invocation), | |
| "Unknown foreign: $name", | |
| ); | |
| } | |
| } | |
| String _readStringLiteral(ir.Expression node) { | |
| if (node is ir.StringLiteral) { | |
| return node.value; | |
| } | |
| if (node is ir.ConstantExpression) { | |
| final constant = node.constant; | |
| if (constant is ir.StringConstant) { | |
| return constant.value; | |
| } | |
| } | |
| return reporter.internalError( | |
| _elementMap.getSpannable(targetElement, node), | |
| "Unexpected string literal: " | |
| "${node is ir.ConstantExpression ? node.constant : node}", | |
| ); | |
| } | |
| int _readIntLiteral(ir.Expression node) { | |
| if (node is ir.IntLiteral) { | |
| return node.value; | |
| } | |
| if (node is ir.ConstantExpression) { | |
| final constant = node.constant; | |
| if (constant is ir.IntConstant) { | |
| return constant.value; | |
| } else if (constant is ir.DoubleConstant) { | |
| assert( | |
| constant.value.floor() == constant.value, | |
| "Unexpected int literal value ${constant.value}.", | |
| ); | |
| return constant.value.toInt(); | |
| } | |
| } | |
| return reporter.internalError( | |
| _elementMap.getSpannable(targetElement, node), | |
| "Unexpected int literal: " | |
| "${node is ir.ConstantExpression ? node.constant : node}", | |
| ); | |
| } | |
| void _handleCreateInvocationMirror( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| String name = _readStringLiteral(invocation.arguments.positional[0]); | |
| final typeArgumentsLiteral = | |
| invocation.arguments.positional[1] as ir.ListLiteral; | |
| List<DartType> typeArguments = typeArgumentsLiteral.expressions.map(( | |
| ir.Expression expression, | |
| ) { | |
| final typeLiteral = expression as ir.TypeLiteral; | |
| return _elementMap.getDartType(typeLiteral.type); | |
| }).toList(); | |
| final positionalArgumentsLiteral = | |
| invocation.arguments.positional[2] as ir.ListLiteral; | |
| ir.Expression namedArgumentsLiteral = invocation.arguments.positional[3]; | |
| Map<String, ir.Expression> namedArguments = {}; | |
| int kind = _readIntLiteral(invocation.arguments.positional[4]); | |
| Name memberName = Name(name, _currentFrame!.member.library.canonicalUri); | |
| Selector selector; | |
| switch (InvocationMirrorKind.values[kind]) { | |
| case InvocationMirrorKind.getter: | |
| selector = Selector.getter(memberName); | |
| break; | |
| case InvocationMirrorKind.setter: | |
| selector = Selector.setter(memberName); | |
| break; | |
| case InvocationMirrorKind.method: | |
| if (memberName == Names.indexName) { | |
| selector = Selector.index(); | |
| } else if (memberName == Names.indexSetName) { | |
| selector = Selector.indexSet(); | |
| } else { | |
| if (namedArgumentsLiteral is ir.MapLiteral) { | |
| for (var entry in namedArgumentsLiteral.entries) { | |
| String key = _readStringLiteral(entry.key); | |
| namedArguments[key] = entry.value; | |
| } | |
| } else if (namedArgumentsLiteral is ir.ConstantExpression && | |
| namedArgumentsLiteral.constant is ir.MapConstant) { | |
| final constant = namedArgumentsLiteral.constant as ir.MapConstant; | |
| for (ir.ConstantMapEntry entry in constant.entries) { | |
| final key = entry.key as ir.StringConstant; | |
| namedArguments[key.value] = ir.ConstantExpression(entry.value); | |
| } | |
| } else { | |
| reporter.internalError( | |
| computeSourceSpanFromTreeNode(invocation), | |
| "Unexpected named arguments value in createInvocationMirror: " | |
| "$namedArgumentsLiteral.", | |
| ); | |
| } | |
| CallStructure callStructure = CallStructure( | |
| positionalArgumentsLiteral.expressions.length, | |
| namedArguments.keys.toList(), | |
| typeArguments.length, | |
| ); | |
| if (Selector.isOperatorName(name)) { | |
| selector = Selector( | |
| SelectorKind.operator, | |
| memberName, | |
| callStructure, | |
| ); | |
| } else { | |
| selector = Selector.call(memberName, callStructure); | |
| } | |
| } | |
| break; | |
| } | |
| final libraryUri = memberName.isPrivate ? memberName.uri : null; | |
| HConstant nameConstant = graph.addConstant( | |
| constant_system.createSymbol( | |
| closedWorld.commonElements, | |
| name, | |
| libraryUri, | |
| ), | |
| closedWorld, | |
| )..sourceInformation = sourceInformation; | |
| List<HInstruction> arguments = []; | |
| for (ir.Expression argument in positionalArgumentsLiteral.expressions) { | |
| argument.accept(this); | |
| arguments.add(pop()); | |
| } | |
| if (namedArguments.isNotEmpty) { | |
| Map<String, HInstruction> namedValues = {}; | |
| namedArguments.forEach((String name, ir.Expression value) { | |
| value.accept(this); | |
| namedValues[name] = pop(); | |
| }); | |
| for (String name in selector.callStructure.getOrderedNamedArguments()) { | |
| arguments.add(namedValues[name]!); | |
| } | |
| } | |
| _addTypeArguments(arguments, typeArguments, sourceInformation); | |
| HInstruction argumentsInstruction = _buildLiteralList(arguments) | |
| ..sourceInformation = sourceInformation; | |
| add(argumentsInstruction); | |
| List<HInstruction> argumentNames = <HInstruction>[]; | |
| for (String argumentName | |
| in selector.callStructure.getOrderedNamedArguments()) { | |
| ConstantValue argumentNameConstant = constant_system.createString( | |
| argumentName, | |
| ); | |
| argumentNames.add( | |
| graph.addConstant(argumentNameConstant, closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| HInstruction argumentNamesInstruction = _buildLiteralList(argumentNames) | |
| ..sourceInformation = sourceInformation; | |
| add(argumentNamesInstruction); | |
| HInstruction typeArgumentCount = graph.addConstantInt( | |
| typeArguments.length, | |
| closedWorld, | |
| )..sourceInformation = sourceInformation; | |
| js.Name internalName = _namer.invocationName(selector); | |
| ConstantValue kindConstant = constant_system.createIntFromInt( | |
| selector.invocationMirrorKind.index, | |
| ); | |
| _pushStaticInvocation( | |
| _commonElements.createUnmangledInvocationMirror, | |
| [ | |
| nameConstant, | |
| graph.addConstantStringFromName(internalName, closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| graph.addConstant(kindConstant, closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| argumentsInstruction, | |
| argumentNamesInstruction, | |
| typeArgumentCount, | |
| ], | |
| _abstractValueDomain.dynamicType, | |
| const <DartType>[], | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| bool _unexpectedForeignArguments( | |
| ir.StaticInvocation invocation, { | |
| required int minPositional, | |
| int? maxPositional, | |
| int typeArgumentCount = 0, | |
| }) { | |
| String pluralizeArguments(int count, [String adjective = '']) { | |
| if (count == 0) return 'no ${adjective}arguments'; | |
| if (count == 1) return 'one ${adjective}argument'; | |
| if (count == 2) return 'two ${adjective}arguments'; | |
| return '$count ${adjective}arguments'; | |
| } | |
| String name() => invocation.target.name.text; | |
| ir.Arguments arguments = invocation.arguments; | |
| bool bad = false; | |
| if (arguments.types.length != typeArgumentCount) { | |
| String expected = pluralizeArguments(typeArgumentCount, 'type '); | |
| String actual = pluralizeArguments(arguments.types.length, 'type '); | |
| reporter.reportErrorMessage( | |
| _elementMap.getSpannable(targetElement, invocation), | |
| MessageKind.generic, | |
| {'text': "Error: '${name()}' takes $expected, not $actual."}, | |
| ); | |
| bad = true; | |
| } | |
| if (arguments.positional.length < minPositional) { | |
| String phrase = pluralizeArguments(minPositional); | |
| if (maxPositional != minPositional) phrase = 'at least $phrase'; | |
| reporter.reportErrorMessage( | |
| _elementMap.getSpannable(targetElement, invocation), | |
| MessageKind.generic, | |
| {'text': "Error: Too few arguments. '${name()}' takes $phrase."}, | |
| ); | |
| bad = true; | |
| } | |
| if (maxPositional != null && arguments.positional.length > maxPositional) { | |
| String phrase = pluralizeArguments(maxPositional); | |
| if (maxPositional != minPositional) phrase = 'at most $phrase'; | |
| reporter.reportErrorMessage( | |
| _elementMap.getSpannable(targetElement, invocation), | |
| MessageKind.generic, | |
| {'text': "Error: Too many arguments. '${name()}' takes $phrase."}, | |
| ); | |
| bad = true; | |
| } | |
| if (arguments.named.isNotEmpty) { | |
| reporter.reportErrorMessage( | |
| _elementMap.getSpannable(targetElement, invocation), | |
| MessageKind.generic, | |
| {'text': "Error: '${name()}' does not take named arguments."}, | |
| ); | |
| bad = true; | |
| } | |
| return bad; | |
| } | |
| /// Returns the value of the string argument. The argument must evaluate to a | |
| /// constant. If there is an error, the error is reported and `null` is | |
| /// returned. | |
| String? _foreignConstantStringArgument( | |
| ir.StaticInvocation invocation, | |
| int position, | |
| String methodName, [ | |
| String adjective = '', | |
| ]) { | |
| ir.Expression argument = invocation.arguments.positional[position]; | |
| argument.accept(this); | |
| HInstruction instruction = pop(); | |
| if (instruction is HConstant) { | |
| ConstantValue constant = instruction.constant; | |
| if (constant is StringConstantValue) return constant.stringValue; | |
| } | |
| reporter.reportErrorMessage( | |
| _elementMap.getSpannable(targetElement, argument), | |
| MessageKind.generic, | |
| { | |
| 'text': | |
| "Error: Expected String constant as ${adjective}argument " | |
| "to '$methodName'.", | |
| }, | |
| ); | |
| return null; | |
| } | |
| void _handleForeignDartClosureToJs( | |
| ir.StaticInvocation invocation, | |
| String name, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| // TODO(sra): Do we need to wrap the closure in something that saves the | |
| // current isolate? | |
| _handleForeignRawFunctionRef(invocation, name, sourceInformation); | |
| } | |
| void _handleForeignRawFunctionRef( | |
| ir.StaticInvocation invocation, | |
| String name, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 1, | |
| maxPositional: 1, | |
| )) { | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| ir.Expression closure = invocation.arguments.positional.single; | |
| String problem = 'requires a static method or top-level method'; | |
| bool handleTarget(ir.Procedure procedure) { | |
| final function = procedure.function; | |
| if (function.requiredParameterCount == | |
| function.positionalParameters.length && | |
| function.namedParameters.isEmpty) { | |
| push( | |
| HFunctionReference( | |
| _elementMap.getMethod(procedure), | |
| _abstractValueDomain.dynamicType, | |
| )..sourceInformation = sourceInformation, | |
| ); | |
| return true; | |
| } | |
| problem = 'does not handle a closure with optional parameters'; | |
| return false; | |
| } | |
| if (closure is ir.StaticGet) { | |
| ir.Member staticTarget = closure.target; | |
| if (staticTarget is ir.Procedure) { | |
| if (staticTarget.kind == ir.ProcedureKind.Method) { | |
| if (handleTarget(staticTarget)) { | |
| return; | |
| } | |
| } | |
| } | |
| } else if (closure is ir.ConstantExpression) { | |
| final tearOff = closure.constant; | |
| if (tearOff is ir.StaticTearOffConstant) { | |
| ir.Procedure member = tearOff.target; | |
| if (handleTarget(member)) { | |
| return; | |
| } | |
| } | |
| } | |
| reporter.reportErrorMessage( | |
| _elementMap.getSpannable(targetElement, invocation), | |
| MessageKind.generic, | |
| {'text': "'$name' $problem."}, | |
| ); | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld)..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| void _handleJsRawException( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 0, | |
| maxPositional: 0, | |
| )) { | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| if (_rethrowableException != null) { | |
| stack.add(_rethrowableException!); | |
| return; | |
| } | |
| reporter.reportErrorMessage( | |
| _elementMap.getSpannable(targetElement, invocation), | |
| MessageKind.generic, | |
| {'text': "Error: JS_RAW_EXCEPTION() must be in a 'catch' block."}, | |
| ); | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld)..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _handleForeignJsGetName( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 1, | |
| maxPositional: 1, | |
| )) { | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| ir.Node argument = invocation.arguments.positional.first; | |
| argument.accept(this); | |
| HInstruction instruction = pop(); | |
| if (instruction is HConstant) { | |
| final name = _getNameForJsGetName(instruction.constant, _namer)!; | |
| stack.add( | |
| graph.addConstantStringFromName(name, closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| reporter.reportErrorMessage( | |
| _elementMap.getSpannable(targetElement, argument), | |
| MessageKind.generic, | |
| {'text': 'Error: Expected a JsGetName enum value.'}, | |
| ); | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld)..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| int? _extractEnumIndexFromConstantValue( | |
| ConstantValue constant, | |
| ClassEntity classElement, | |
| ) { | |
| if (constant is ConstructedConstantValue && | |
| constant.type.element == classElement) { | |
| assert(constant.fields.isNotEmpty); | |
| for (var field in constant.fields.keys) { | |
| if (field.memberName.text == "index") { | |
| final indexConstant = constant.fields[field]; | |
| if (indexConstant is IntConstantValue) { | |
| return indexConstant.intValue.toInt(); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| /// Returns the [js.Name] for the `JsGetName` [constant] value. | |
| js.Name? _getNameForJsGetName(ConstantValue constant, ModularNamer namer) { | |
| final index = _extractEnumIndexFromConstantValue( | |
| constant, | |
| _commonElements.jsGetNameEnum, | |
| ); | |
| if (index == null) return null; | |
| return namer.getNameForJsGetName( | |
| currentElementSpannable, | |
| JsGetName.values[index], | |
| ); | |
| } | |
| void _handleForeignJsEmbeddedGlobal( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 2, | |
| maxPositional: 2, | |
| )) { | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| final globalName = _foreignConstantStringArgument( | |
| invocation, | |
| 1, | |
| 'JS_EMBEDDED_GLOBAL', | |
| 'second ', | |
| )!; | |
| NativeBehavior nativeBehavior = _elementMap | |
| .getNativeBehaviorForJsEmbeddedGlobalCall(invocation); | |
| AbstractValue ssaType = _typeInferenceMap.typeFromNativeBehavior( | |
| nativeBehavior, | |
| closedWorld, | |
| ); | |
| push( | |
| HEmbeddedGlobalGet(globalName, nativeBehavior, ssaType) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _handleForeignJsBuiltin( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments(invocation, minPositional: 2)) { | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| List<ir.Expression> arguments = invocation.arguments.positional; | |
| ir.Expression nameArgument = arguments[1]; | |
| nameArgument.accept(this); | |
| HInstruction instruction = pop(); | |
| js.Template? template; | |
| if (instruction is HConstant) { | |
| template = _getJsBuiltinTemplate(instruction.constant, _emitter); | |
| } | |
| if (template == null) { | |
| reporter.reportErrorMessage( | |
| _elementMap.getSpannable(targetElement, nameArgument), | |
| MessageKind.generic, | |
| {'text': 'Error: Expected a JsBuiltin enum value.'}, | |
| ); | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| List<HInstruction> inputs = []; | |
| for (ir.Expression argument in arguments.skip(2)) { | |
| argument.accept(this); | |
| inputs.add(pop()); | |
| } | |
| NativeBehavior nativeBehavior = _elementMap | |
| .getNativeBehaviorForJsBuiltinCall(invocation); | |
| AbstractValue ssaType = _typeInferenceMap.typeFromNativeBehavior( | |
| nativeBehavior, | |
| closedWorld, | |
| ); | |
| push( | |
| HForeignCode(template, ssaType, inputs, nativeBehavior: nativeBehavior) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| /// Returns the [js.Template] for the `JsBuiltin` [constant] value. | |
| js.Template? _getJsBuiltinTemplate( | |
| ConstantValue constant, | |
| ModularEmitter emitter, | |
| ) { | |
| final index = _extractEnumIndexFromConstantValue( | |
| constant, | |
| _commonElements.jsBuiltinEnum, | |
| ); | |
| if (index == null) return null; | |
| return _templateForBuiltin(JsBuiltin.values[index]); | |
| } | |
| /// Returns the JS template for the given [builtin]. | |
| js.Template? _templateForBuiltin(JsBuiltin builtin) { | |
| switch (builtin) { | |
| case JsBuiltin.dartObjectConstructor: | |
| ClassEntity objectClass = closedWorld.commonElements.objectClass; | |
| return js.js.expressionTemplateYielding( | |
| _emitter.constructorAccess(objectClass), | |
| ); | |
| case JsBuiltin.dartClosureConstructor: | |
| ClassEntity closureClass = closedWorld.commonElements.closureClass; | |
| // TODO(sra): Should add a dependency on the constructor used as a | |
| // token. | |
| registry | |
| // ignore:deprecated_member_use_from_same_package | |
| .registerInstantiatedClass(closureClass); | |
| return js.js.expressionTemplateYielding( | |
| _emitter.constructorAccess(closureClass), | |
| ); | |
| case JsBuiltin.getMetadata: | |
| String metadataAccess = _emitter.generateEmbeddedGlobalAccessString( | |
| METADATA, | |
| ); | |
| return js.js.expressionTemplateFor("$metadataAccess[#]"); | |
| case JsBuiltin.getType: | |
| String typesAccess = _emitter.generateEmbeddedGlobalAccessString(TYPES); | |
| return js.js.expressionTemplateFor("$typesAccess[#]"); | |
| case JsBuiltin.isJsInteropTypeArgument: | |
| reporter.internalError( | |
| noLocationSpannable, | |
| "Unhandled Builtin: $builtin", | |
| ); | |
| } | |
| } | |
| void _handleForeignJsBool( | |
| bool value, | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| _unexpectedForeignArguments(invocation, minPositional: 0, maxPositional: 0); | |
| stack.add( | |
| graph.addConstantBool(value, closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _handleJsInterceptorConstant( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| // Single argument must be a TypeConstant which is converted into a | |
| // InterceptorConstant. | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 1, | |
| maxPositional: 1, | |
| )) { | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| ir.Expression argument = invocation.arguments.positional.single; | |
| argument.accept(this); | |
| HInstruction argumentInstruction = pop(); | |
| if (argumentInstruction is HConstant) { | |
| ConstantValue argumentConstant = argumentInstruction.constant; | |
| if (argumentConstant is TypeConstantValue) { | |
| final interfaceType = | |
| argumentConstant.representedType.withoutNullability; | |
| if (interfaceType is InterfaceType) { | |
| // TODO(sra): Check that type is a subclass of [Interceptor]. | |
| ConstantValue constant = InterceptorConstantValue( | |
| interfaceType.element, | |
| ); | |
| HInstruction instruction = graph.addConstant(constant, closedWorld) | |
| ..sourceInformation = sourceInformation; | |
| stack.add(instruction); | |
| return; | |
| } | |
| } | |
| } | |
| reporter.reportErrorMessage( | |
| _elementMap.getSpannable(targetElement, invocation), | |
| MessageKind.wrongArgumentForJSInterceptorConstant, | |
| ); | |
| stack.add( | |
| graph.addConstantNull(closedWorld)..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _handleForeignGetInterceptor( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| // Single argument is the intercepted object. | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 1, | |
| maxPositional: 1, | |
| )) { | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| ir.Expression argument = invocation.arguments.positional.single; | |
| argument.accept(this); | |
| HInstruction argumentInstruction = pop(); | |
| HInstruction instruction = _interceptorFor( | |
| argumentInstruction, | |
| sourceInformation, | |
| )..sourceInformation = sourceInformation; | |
| stack.add(instruction); | |
| } | |
| void _handleForeignGetJSArrayInteropRti( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 0, | |
| maxPositional: 0, | |
| )) { | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| // TODO(sra): This should be JSArray<any>, created via | |
| // _elementEnvironment.getJsInteropType(_elementEnvironment.jsArrayClass); | |
| InterfaceType interopType = dartTypes.interfaceType( | |
| _commonElements.jsArrayClass, | |
| [dartTypes.dynamicType()], | |
| ); | |
| HInstruction rti = HLoadType.type( | |
| interopType, | |
| _abstractValueDomain.dynamicType, | |
| )..sourceInformation = sourceInformation; | |
| push(rti); | |
| } | |
| bool _equivalentToMissingRti(InterfaceType type) { | |
| assert(type.element == _commonElements.jsArrayClass); | |
| return dartTypes.isTopType(type.typeArguments.single); | |
| } | |
| void _handleForeignJs( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 2, | |
| maxPositional: null, | |
| typeArgumentCount: 1, | |
| )) { | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| NativeBehavior nativeBehavior = _elementMap.getNativeBehaviorForJsCall( | |
| invocation, | |
| ); | |
| List<HInstruction> inputs = []; | |
| for (ir.Expression argument in invocation.arguments.positional.skip(2)) { | |
| argument.accept(this); | |
| inputs.add(pop()); | |
| } | |
| final codeTemplate = nativeBehavior.codeTemplate!; | |
| if (codeTemplate.positionalArgumentCount != inputs.length) { | |
| reporter.reportErrorMessage( | |
| _elementMap.getSpannable(targetElement, invocation), | |
| MessageKind.generic, | |
| { | |
| 'text': | |
| 'Mismatch between number of placeholders' | |
| ' and number of arguments.', | |
| }, | |
| ); | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| if (HasCapturedPlaceholders.check(codeTemplate.ast)) { | |
| reporter.reportErrorMessage( | |
| _elementMap.getSpannable(targetElement, invocation), | |
| MessageKind.jsPlaceholderCapture, | |
| ); | |
| } | |
| AbstractValue ssaType = _typeInferenceMap.typeFromNativeBehavior( | |
| nativeBehavior, | |
| closedWorld, | |
| ); | |
| HInstruction code = HForeignCode( | |
| codeTemplate, | |
| ssaType, | |
| inputs, | |
| isStatement: !codeTemplate.isExpression, | |
| effects: nativeBehavior.sideEffects, | |
| nativeBehavior: nativeBehavior, | |
| )..sourceInformation = sourceInformation; | |
| push(code); | |
| DartType type = _getDartTypeIfValid(invocation.arguments.types.single); | |
| final trustedMask = _typeBuilder.trustTypeMask(type); | |
| if (trustedMask != null) { | |
| // We only allow the type argument to narrow `dynamic`, which probably | |
| // comes from an unspecified return type in the NativeBehavior. | |
| if (_abstractValueDomain | |
| .containsAll(code.instructionType) | |
| .isPotentiallyTrue) { | |
| // Overwrite the type with the narrower type. | |
| code.instructionType = trustedMask; | |
| } | |
| // It is acceptable for the type parameter to be broader than the | |
| // specified type. | |
| } | |
| _maybeAddNullCheckOnJS(invocation); | |
| } | |
| /// If [invocation] is a `JS()` invocation in a web library and the static | |
| /// type is non-nullable, add a check to make sure it isn't null. | |
| void _maybeAddNullCheckOnJS(ir.StaticInvocation invocation) { | |
| if (options.nativeNullAssertions && | |
| nodeIsInWebLibrary(invocation) && | |
| closedWorld.dartTypes.isNonNullable(_getStaticType(invocation))) { | |
| HInstruction code = pop(); | |
| push( | |
| HNullCheck( | |
| code, | |
| _abstractValueDomain.excludeNull(code.instructionType), | |
| sticky: true, | |
| ), | |
| ); | |
| } | |
| } | |
| void _maybeAddInteropNullAssertionForMember( | |
| FunctionEntity member, | |
| int argumentCount, { | |
| SourceInformation? sourceInformation, | |
| }) { | |
| if (options.interopNullAssertions) { | |
| final functionType = _elementEnvironment.getFunctionType(member); | |
| if (dartTypes.isNonNullable(functionType.returnType)) { | |
| final name = PublicName( | |
| _nativeData.computeUnescapedJSInteropName(member.name!), | |
| ); | |
| _addInteropNullAssertionForSelector( | |
| Selector.call(name, CallStructure.unnamed(argumentCount)), | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| } | |
| } | |
| void _maybeAddInteropNullAssertionForSelector( | |
| Selector selector, { | |
| SourceInformation? sourceInformation, | |
| }) { | |
| if (options.interopNullAssertions && | |
| _nativeData.interopNullChecks[selector] == | |
| InteropNullCheckKind.callerCheck) { | |
| _addInteropNullAssertion(sourceInformation: sourceInformation); | |
| } | |
| } | |
| void _addInteropNullAssertionForSelector( | |
| Selector selector, { | |
| SourceInformation? sourceInformation, | |
| }) { | |
| if (_nativeData.interopNullChecks[selector] == | |
| InteropNullCheckKind.callerCheck) { | |
| _addInteropNullAssertion(sourceInformation: sourceInformation); | |
| } | |
| } | |
| void _maybeAddInteropNullAssertionForStatic( | |
| DartType returnType, { | |
| SourceInformation? sourceInformation, | |
| }) { | |
| if (options.interopNullAssertions && | |
| closedWorld.dartTypes.isNonNullable(returnType)) { | |
| _addInteropNullAssertion(sourceInformation: sourceInformation); | |
| } | |
| } | |
| void _addInteropNullAssertion({SourceInformation? sourceInformation}) { | |
| final value = pop(); | |
| _pushStaticInvocation( | |
| _commonElements.interopNullAssertion, | |
| [value], | |
| value.instructionType, | |
| const <DartType>[], | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| void _handleJsStringConcat( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 2, | |
| maxPositional: 2, | |
| )) { | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments); | |
| push( | |
| HStringConcat(inputs[0], inputs[1], _abstractValueDomain.stringType) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _handleCharCodeAt( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 2, | |
| maxPositional: 2, | |
| )) { | |
| // Result expected on stack. | |
| stack.add( | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments); | |
| push( | |
| HCharCodeAt(inputs[0], inputs[1], _abstractValueDomain.uint31Type) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _handleArrayFlagsGet( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 1, | |
| maxPositional: 1, | |
| )) { | |
| // Result expected on stack. | |
| stack.add(graph.addConstantNull(closedWorld)); | |
| return; | |
| } | |
| List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments); | |
| final array = inputs.single; | |
| push( | |
| HArrayFlagsGet(array, _abstractValueDomain.uint31Type) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _handleArrayFlagsSet( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 2, | |
| maxPositional: 2, | |
| typeArgumentCount: 1, | |
| )) { | |
| // Result expected on stack. | |
| stack.add(graph.addConstantNull(closedWorld)); | |
| return; | |
| } | |
| List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments); | |
| final array = inputs[0]; | |
| final flags = inputs[1]; | |
| // TODO(sra): Use the flags to improve in the AbstractValue, which may | |
| // contain powerset domain bits outside of the conventional type | |
| // system. Perhaps do this in types_propagation. | |
| DartType type = _getDartTypeIfValid(invocation.arguments.types.single); | |
| AbstractValue? instructionType = _typeBuilder.trustTypeMask(type); | |
| // TODO(sra): Better type | |
| instructionType ??= _abstractValueDomain.dynamicType; | |
| push( | |
| HArrayFlagsSet(array, flags, instructionType) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _handleArrayFlagsCheck( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 4, | |
| maxPositional: 5, | |
| typeArgumentCount: 1, | |
| )) { | |
| // Result expected on stack. | |
| stack.add(graph.addConstantNull(closedWorld)); | |
| return; | |
| } | |
| List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments); | |
| final array = inputs[0]; | |
| final arrayFlags = inputs[1]; | |
| final checkFlags = inputs[2]; | |
| final operation = inputs[3]; | |
| final verb = inputs.length > 4 ? inputs[4] : null; | |
| // TODO(sra): Use the flags to improve in the AbstractValue, which may | |
| // contain powerset domain bits outside of the conventional type | |
| // system. Perhaps do this in types_propagation. | |
| DartType type = _getDartTypeIfValid(invocation.arguments.types.single); | |
| AbstractValue? instructionType = _typeBuilder.trustTypeMask(type); | |
| // TODO(sra): Better type | |
| instructionType ??= _abstractValueDomain.dynamicType; | |
| push( | |
| HArrayFlagsCheck( | |
| array, | |
| arrayFlags, | |
| checkFlags, | |
| operation, | |
| verb, | |
| instructionType, | |
| )..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _handleForeignTypeRef( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 0, | |
| maxPositional: 0, | |
| typeArgumentCount: 1, | |
| )) { | |
| stack.add( | |
| // Result expected on stack. | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| DartType type = _elementMap.getDartType(invocation.arguments.types.single); | |
| push( | |
| HLoadType.type(type, _abstractValueDomain.dynamicType) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _handleForeignCreateJsSentinel( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 0, | |
| maxPositional: 0, | |
| typeArgumentCount: 1, | |
| )) { | |
| stack.add( | |
| // Result expected on stack. | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| stack.add( | |
| graph.addConstantLateSentinel( | |
| closedWorld, | |
| sourceInformation: sourceInformation, | |
| ), | |
| ); | |
| } | |
| void _handleForeignIsJsSentinel( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 1, | |
| maxPositional: 1, | |
| )) { | |
| stack.add( | |
| // Result expected on stack. | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| HInstruction checkedExpression = _visitPositionalArguments( | |
| invocation.arguments, | |
| ).single; | |
| push( | |
| HIsLateSentinel(checkedExpression, _abstractValueDomain.boolType) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| // TODO(fishythefish): Support specialization of late sentinels based on type. | |
| void _handleLateReadCheck( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 2, | |
| maxPositional: 2, | |
| typeArgumentCount: 1, | |
| )) { | |
| stack.add( | |
| // Result expected on stack. | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| List<HInstruction> arguments = _visitPositionalArguments( | |
| invocation.arguments, | |
| ); | |
| HInstruction value = arguments[0]; | |
| final name = options.omitLateNames ? null : arguments[1]; | |
| CheckPolicy policy = closedWorld.annotationsData | |
| .getLateVariableCheckPolicyAt(invocation); | |
| push( | |
| HLateReadCheck( | |
| value, | |
| name, | |
| policy.isTrusted, | |
| _abstractValueDomain.excludeLateSentinel(value.instructionType), | |
| )..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _handleLateWriteOnceCheck( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 2, | |
| maxPositional: 2, | |
| )) { | |
| stack.add( | |
| // Result expected on stack. | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| List<HInstruction> arguments = _visitPositionalArguments( | |
| invocation.arguments, | |
| ); | |
| HInstruction value = arguments[0]; | |
| final name = options.omitLateNames ? null : arguments[1]; | |
| CheckPolicy policy = closedWorld.annotationsData | |
| .getLateVariableCheckPolicyAt(invocation); | |
| push( | |
| HLateWriteOnceCheck( | |
| value, | |
| name, | |
| policy.isTrusted, | |
| _abstractValueDomain.dynamicType, | |
| )..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _handleLateInitializeOnceCheck( | |
| ir.StaticInvocation invocation, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (_unexpectedForeignArguments( | |
| invocation, | |
| minPositional: 2, | |
| maxPositional: 2, | |
| )) { | |
| stack.add( | |
| // Result expected on stack. | |
| graph.addConstantNull(closedWorld) | |
| ..sourceInformation = sourceInformation, | |
| ); | |
| return; | |
| } | |
| List<HInstruction> arguments = _visitPositionalArguments( | |
| invocation.arguments, | |
| ); | |
| HInstruction value = arguments[0]; | |
| final name = options.omitLateNames ? null : arguments[1]; | |
| CheckPolicy policy = closedWorld.annotationsData | |
| .getLateVariableCheckPolicyAt(invocation); | |
| push( | |
| HLateInitializeOnceCheck( | |
| value, | |
| name, | |
| policy.isTrusted, | |
| _abstractValueDomain.dynamicType, | |
| )..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| void _pushStaticInvocation( | |
| FunctionEntity target, | |
| List<HInstruction> arguments, | |
| AbstractValue typeMask, | |
| List<DartType> typeArguments, { | |
| SourceInformation? sourceInformation, | |
| InterfaceType? instanceType, | |
| }) { | |
| // TODO(redemption): Pass current node if needed. | |
| if (_tryInlineMethod( | |
| target, | |
| null, | |
| null, | |
| arguments, | |
| typeArguments, | |
| null, | |
| sourceInformation, | |
| instanceType: instanceType, | |
| )) { | |
| return; | |
| } | |
| if (closedWorld.nativeData.isJsInteropMember(target)) { | |
| _pushStaticNativeInvocation( | |
| target, | |
| arguments, | |
| typeMask, | |
| typeArguments, | |
| sourceInformation: sourceInformation, | |
| ); | |
| return; | |
| } | |
| HInvokeStatic instruction = HInvokeStatic( | |
| target, | |
| arguments, | |
| typeMask, | |
| typeArguments, | |
| targetCanThrow: !_inferredData.getCannotThrow(target), | |
| )..sourceInformation = sourceInformation; | |
| if (_currentImplicitInstantiations.isNotEmpty) { | |
| instruction.instantiatedTypes = List<InterfaceType>.of( | |
| _currentImplicitInstantiations, | |
| ); | |
| } | |
| instruction.sideEffects.setTo( | |
| _inferredData.getSideEffectsOfElement(target), | |
| ); | |
| instruction.allowCSE = closedWorld.annotationsData.allowCSE(target); | |
| instruction.allowDCE = closedWorld.annotationsData.allowDCE(target); | |
| push(instruction); | |
| } | |
| void _pushStaticNativeInvocation( | |
| FunctionEntity target, | |
| List<HInstruction?> arguments, | |
| AbstractValue typeMask, | |
| List<DartType> typeArguments, { | |
| SourceInformation? sourceInformation, | |
| }) { | |
| _invokeJsInteropFunction( | |
| target, | |
| arguments, | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| void _pushDynamicInvocation( | |
| ir.Node node, | |
| DartType staticReceiverType, | |
| AbstractValue? receiverType, | |
| Selector selector, | |
| List<HInstruction> arguments, | |
| List<DartType> typeArguments, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| AbstractValue typeBound = _abstractValueDomain | |
| .createFromStaticType(staticReceiverType) | |
| .abstractValue; | |
| receiverType = receiverType == null | |
| ? typeBound | |
| : _abstractValueDomain.intersection(receiverType, typeBound); | |
| // We prefer to not inline certain operations on indexables, | |
| // because the constant folder will handle them better and turn | |
| // them into simpler instructions that allow further | |
| // optimizations. | |
| bool isOptimizableOperationOnIndexable( | |
| Selector selector, | |
| MemberEntity element, | |
| ) { | |
| bool isLength = selector.isGetter && selector.name == "length"; | |
| if (isLength || selector.isIndex) { | |
| return closedWorld.classHierarchy.isSubtypeOf( | |
| element.enclosingClass!, | |
| _commonElements.jsIndexableClass, | |
| ); | |
| } else if (selector.isIndexSet) { | |
| return closedWorld.classHierarchy.isSubtypeOf( | |
| element.enclosingClass!, | |
| _commonElements.jsMutableIndexableClass, | |
| ); | |
| } else { | |
| return false; | |
| } | |
| } | |
| bool isOptimizableOperation(Selector selector, MemberEntity element) { | |
| final cls = element.enclosingClass; | |
| if (isOptimizableOperationOnIndexable(selector, element)) return true; | |
| if (!_interceptorData.interceptedClasses.contains(cls)) return false; | |
| if (selector.isOperator) return true; | |
| if (selector.isSetter) return true; | |
| if (selector.isIndex) return true; | |
| if (selector.isIndexSet) return true; | |
| if (element == _commonElements.jsArrayAdd || | |
| element == _commonElements.jsArrayRemoveLast || | |
| _commonElements.isJsStringSplit(element)) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| final element = closedWorld.locateSingleMember(selector, receiverType); | |
| if (element != null && | |
| element is FunctionEntity && | |
| !(element.isGetter && selector.isCall) && | |
| !(element.isFunction && selector.isGetter) && | |
| !isOptimizableOperation(selector, element)) { | |
| if (_tryInlineMethod( | |
| element, | |
| selector, | |
| receiverType, | |
| arguments, | |
| typeArguments, | |
| node, | |
| sourceInformation, | |
| )) { | |
| return; | |
| } | |
| } | |
| HInstruction receiver = arguments.first; | |
| List<HInstruction> inputs = []; | |
| bool isIntercepted = closedWorld.interceptorData.isInterceptedSelector( | |
| selector, | |
| ); | |
| if (isIntercepted) { | |
| HInterceptor interceptor = _interceptorFor(receiver, sourceInformation); | |
| inputs.add(interceptor); | |
| } | |
| inputs.addAll(arguments); | |
| AbstractValue resultType = _typeInferenceMap.resultTypeOfSelector( | |
| selector, | |
| receiverType, | |
| ); | |
| HInvokeDynamic invoke; | |
| if (selector.isGetter) { | |
| _metrics.countGettersTotal.add(); | |
| invoke = HInvokeDynamicGetter( | |
| selector, | |
| receiverType, | |
| element, | |
| inputs, | |
| isIntercepted, | |
| resultType, | |
| sourceInformation, | |
| ); | |
| } else if (selector.isSetter) { | |
| _metrics.countSettersTotal.add(); | |
| invoke = HInvokeDynamicSetter( | |
| selector, | |
| receiverType, | |
| element, | |
| inputs, | |
| isIntercepted, | |
| resultType, | |
| sourceInformation, | |
| ); | |
| } else if (selector.isMaybeClosureCall) { | |
| assert(!isIntercepted); | |
| invoke = HInvokeClosure( | |
| selector, | |
| receiverType, | |
| inputs, | |
| resultType, | |
| typeArguments, | |
| )..sourceInformation = sourceInformation; | |
| } else { | |
| invoke = HInvokeDynamicMethod( | |
| selector, | |
| receiverType, | |
| inputs, | |
| resultType, | |
| typeArguments, | |
| sourceInformation, | |
| isIntercepted: isIntercepted, | |
| ); | |
| } | |
| invoke.instructionContext = _currentFrame!.member; | |
| if (node is ir.InstanceInvocation) { | |
| invoke.isInvariant = node.isInvariant; | |
| invoke.isBoundsSafe = node.isBoundsSafe; | |
| if (node.receiver is ir.ThisExpression) { | |
| // If the receiver of an instance invocation is `this` then the call is | |
| // invariant with respect to the class type variables. | |
| invoke.isInvariant = true; | |
| } | |
| } else if (node is ir.InstanceSet) { | |
| if (node.receiver is ir.ThisExpression) { | |
| // If the receiver of an instance invocation is `this` then the call is | |
| // invariant with respect to the class type variables. | |
| invoke.isInvariant = true; | |
| } | |
| } | |
| if (element != null) { | |
| invoke.allowCSE = closedWorld.annotationsData.allowCSE(element); | |
| invoke.allowDCE = closedWorld.annotationsData.allowDCE(element); | |
| } | |
| if (node is ir.InstanceInvocation || | |
| node is ir.FunctionInvocation || | |
| node is ir.InstanceGet) { | |
| final staticType = _abstractValueDomain | |
| .createFromStaticType(_getStaticType(node as ir.Expression)) | |
| .abstractValue; | |
| invoke.staticType = staticType; | |
| invoke.instructionType = invoke.computeInstructionType( | |
| resultType, | |
| _abstractValueDomain, | |
| ); | |
| } | |
| push(invoke); | |
| if (element != null && | |
| _abstractValueDomain.isNull(resultType).isDefinitelyFalse) { | |
| _maybeAddInteropNullAssertionForSelector( | |
| selector, | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| } | |
| void _invokeJsInteropFunction( | |
| FunctionEntity element, | |
| List<HInstruction?> arguments, { | |
| SourceInformation? sourceInformation, | |
| }) { | |
| assert(closedWorld.nativeData.isJsInteropMember(element)); | |
| bool isAnonymousFactory = | |
| element is ConstructorEntity && | |
| element.isFactoryConstructor && | |
| _nativeData.isAnonymousJsInteropClass(element.enclosingClass); | |
| ir.Node node = _elementMap.getMemberDefinition(element).node; | |
| // JS interop checks assert that the only inline class interop member that | |
| // has named parameters is an object literal constructor. We could do a more | |
| // robust check by visiting all inline classes and recording descriptors, | |
| // but that's expensive. | |
| bool isObjectLiteralConstructor = | |
| node is ir.Procedure && | |
| node.isExtensionTypeMember && | |
| node.function.namedParameters.isNotEmpty; | |
| if (isAnonymousFactory || isObjectLiteralConstructor) { | |
| // Constructor that is syntactic sugar for creating a JavaScript object | |
| // literal. | |
| int i = 0; | |
| int positions = 0; | |
| List<HInstruction> filteredArguments = []; | |
| Map<String, js.Expression> parameterNameMap = {}; | |
| // Note: we don't use `constructor.parameterStructure` here because | |
| // we don't elide parameters to js-interop external static targets | |
| // (including factory constructors.) | |
| // TODO(johnniwinther): can we elide those parameters? This should be | |
| // consistent with what we do with instance methods. | |
| final procedure = node as ir.Procedure; | |
| List<ir.NamedParameter> namedParameters = procedure | |
| .function | |
| .namedParameters | |
| .toList(); | |
| namedParameters.sort(nativeOrdering); | |
| for (ir.NamedParameter parameter in namedParameters) { | |
| String parameterName = parameter.parameterName; | |
| // TODO(jacobr): consider throwing if parameter names do not match | |
| // names of properties in the class. | |
| final argument = arguments[i]; | |
| if (argument != null) { | |
| filteredArguments.add(argument); | |
| var customName = getDartJSInteropJSName(parameter); | |
| var jsName = (customName.isNotEmpty && isObjectLiteralConstructor) | |
| ? customName | |
| : _nativeData.computeUnescapedJSInteropName(parameterName); | |
| parameterNameMap[jsName] = js.InterpolatedExpression(positions++); | |
| } | |
| i++; | |
| } | |
| var codeTemplate = js.Template(null, js.objectLiteral(parameterNameMap)); | |
| var nativeBehavior = NativeBehavior()..codeTemplate = codeTemplate; | |
| registry.registerNativeMethod(element); | |
| // TODO(efortuna): Source information. | |
| push( | |
| HForeignCode( | |
| codeTemplate, | |
| _abstractValueDomain.dynamicType, | |
| filteredArguments, | |
| nativeBehavior: nativeBehavior, | |
| ), | |
| ); | |
| return; | |
| } | |
| // Strip off trailing arguments that were not specified. | |
| // we could assert that the trailing arguments are all null. | |
| // TODO(jacobr): rewrite named arguments to an object literal matching | |
| // the factory constructor case. | |
| List<HInstruction> inputs = arguments.whereType<HInstruction>().toList(); | |
| var nativeBehavior = NativeBehavior()..sideEffects.setAllSideEffects(); | |
| DartType type = element is ConstructorEntity | |
| ? _elementEnvironment.getThisType(element.enclosingClass) | |
| : _elementEnvironment.getFunctionType(element).returnType; | |
| // Native behavior effects here are similar to native/behavior.dart. | |
| // The return type is dynamic because we don't trust js-interop type | |
| // declarations. | |
| nativeBehavior.typesReturned.add(dartTypes.dynamicType()); | |
| // The allocation effects include the declared type if it is native (which | |
| // includes js interop types). | |
| final nonNullableType = type.withoutNullability; | |
| if (nonNullableType is InterfaceType && | |
| _nativeData.isNativeClass(nonNullableType.element)) { | |
| nativeBehavior.typesInstantiated.add(nonNullableType); | |
| } | |
| // It also includes any other JS interop type. Technically, a JS interop API | |
| // could return anything, so the sound thing to do would be to assume that | |
| // anything that may come from JS as instantiated. In order to prevent the | |
| // resulting code bloat (e.g. from `dart:html`), we unsoundly assume that | |
| // only JS interop types are returned. | |
| nativeBehavior.typesInstantiated.add( | |
| _elementEnvironment.getThisType( | |
| _commonElements.jsLegacyJavaScriptObjectClass, | |
| ), | |
| ); | |
| AbstractValue instructionType = _typeInferenceMap.typeFromNativeBehavior( | |
| nativeBehavior, | |
| closedWorld, | |
| ); | |
| push( | |
| HInvokeExternal( | |
| element, | |
| inputs, | |
| instructionType, | |
| nativeBehavior, | |
| sourceInformation: sourceInformation, | |
| ), | |
| ); | |
| _maybeAddInteropNullAssertionForStatic( | |
| type, | |
| sourceInformation: sourceInformation, | |
| ); | |
| } | |
| @override | |
| void visitFunctionNode(ir.FunctionNode node) { | |
| final sourceInformation = _sourceInformationBuilder.buildCreate(node); | |
| ClosureRepresentationInfo closureInfo = _closureDataLookup.getClosureInfo( | |
| node.parent as ir.LocalFunction, | |
| ); | |
| ClassEntity closureClassEntity = closureInfo.closureClassEntity!; | |
| List<HInstruction> capturedVariables = []; | |
| _elementEnvironment.forEachInstanceField(closureClassEntity, ( | |
| _, | |
| FieldEntity field, | |
| ) { | |
| if (_fieldAnalysis.getFieldData(field).isElided) return; | |
| capturedVariables.add( | |
| localsHandler.readLocal( | |
| closureInfo.getLocalForField(_localsMap, field), | |
| ), | |
| ); | |
| }); | |
| AbstractValue type = _abstractValueDomain.createNonNullExact( | |
| closureClassEntity, | |
| ); | |
| // TODO(efortuna): Add source information here. | |
| push( | |
| HCreate( | |
| closureClassEntity, | |
| capturedVariables, | |
| type, | |
| sourceInformation, | |
| callMethod: closureInfo.callMethod, | |
| ), | |
| ); | |
| } | |
| @override | |
| void visitFunctionDeclaration(ir.FunctionDeclaration node) { | |
| assert(_isReachable); | |
| node.function.accept(this); | |
| Local local = _localsMap.getLocalVariable(node.variable); | |
| localsHandler.updateLocal(local, pop()); | |
| } | |
| @override | |
| void visitFunctionExpression(ir.FunctionExpression node) { | |
| node.function.accept(this); | |
| } | |
| @override | |
| void visitInstantiation(ir.Instantiation node) { | |
| List<HInstruction> arguments = []; | |
| node.expression.accept(this); | |
| arguments.add(pop()); | |
| // A generic function instantiation is created by calling a helper function | |
| // which takes the arguments. | |
| int typeArgumentCount = node.typeArguments.length; | |
| FunctionEntity target = _commonElements.getInstantiateFunction( | |
| typeArgumentCount, | |
| ); | |
| final expressionType = _getStaticType(node.expression); | |
| final functionType = expressionType.withoutNullability as FunctionType; | |
| bool typeArgumentsNeeded = _rtiNeed.methodNeedsTypeArguments(target); | |
| List<DartType> typeArguments = node.typeArguments | |
| .map( | |
| (type) => typeArgumentsNeeded | |
| ? _elementMap.getDartType(type) | |
| : _commonElements.dynamicType, | |
| ) | |
| .toList(); | |
| registry.registerGenericInstantiation( | |
| GenericInstantiation(functionType, typeArguments), | |
| ); | |
| // TODO(sra): Add instantiations to SourceInformationBuilder. | |
| SourceInformation? sourceInformation; | |
| // TODO(47484): Allow callee to have different calling convention for type | |
| // arguments. | |
| if (typeArgumentsNeeded) { | |
| _addTypeArguments(arguments, typeArguments, sourceInformation); | |
| } | |
| bool targetCanThrow = false; // TODO(sra): Is this true? | |
| // TODO(sra): Use [_pushStaticInvocation] to allow inlining. We don't now | |
| // because inference can't tell that the call has no side-effects. | |
| HInstruction instruction = HInvokeStatic( | |
| target, | |
| arguments, | |
| _abstractValueDomain.functionType, | |
| <DartType>[], | |
| targetCanThrow: targetCanThrow, | |
| ); | |
| instruction.sourceInformation = sourceInformation; | |
| instruction.sideEffects | |
| ..clearAllDependencies() | |
| ..clearAllSideEffects(); | |
| push(instruction); | |
| } | |
| void _handleMethodInvocation( | |
| ir.Expression node, | |
| ir.Expression receiver, | |
| ir.Arguments arguments, { | |
| bool isImplicitCall = false, | |
| }) { | |
| receiver.accept(this); | |
| HInstruction receiverInstruction = pop(); | |
| Selector selector = _elementMap.getSelector(node); | |
| List<DartType> typeArguments = []; | |
| selector = _fillDynamicTypeArguments( | |
| selector, | |
| arguments, | |
| typeArguments, | |
| isImplicitCall: isImplicitCall, | |
| ); | |
| _pushDynamicInvocation( | |
| node, | |
| _getStaticType(receiver), | |
| _typeInferenceMap.receiverTypeOfInvocation(node, _abstractValueDomain), | |
| selector, | |
| [ | |
| receiverInstruction, | |
| ..._visitArgumentsForDynamicTarget(selector, arguments, typeArguments), | |
| ], | |
| typeArguments, | |
| _sourceInformationBuilder.buildCall(receiver, node), | |
| ); | |
| } | |
| @override | |
| void visitInstanceInvocation(ir.InstanceInvocation node) { | |
| _handleMethodInvocation(node, node.receiver, node.arguments); | |
| } | |
| @override | |
| void visitInstanceGetterInvocation(ir.InstanceGetterInvocation node) { | |
| _handleMethodInvocation( | |
| node, | |
| node.receiver, | |
| node.arguments, | |
| isImplicitCall: true, | |
| ); | |
| } | |
| @override | |
| void visitDynamicInvocation(ir.DynamicInvocation node) { | |
| _handleMethodInvocation(node, node.receiver, node.arguments); | |
| } | |
| @override | |
| void visitFunctionInvocation(ir.FunctionInvocation node) { | |
| _handleMethodInvocation(node, node.receiver, node.arguments); | |
| } | |
| @override | |
| void visitLocalFunctionInvocation(ir.LocalFunctionInvocation node) { | |
| Local local = _localsMap.getLocalVariable(node.variable); | |
| stack.add( | |
| localsHandler.readLocal( | |
| local, | |
| sourceInformation: _sourceInformationBuilder.buildGet(node), | |
| ), | |
| ); | |
| HInstruction receiverInstruction = pop(); | |
| Selector selector = _elementMap.getSelector(node); | |
| List<DartType> typeArguments = []; | |
| selector = _fillDynamicTypeArguments( | |
| selector, | |
| node.arguments, | |
| typeArguments, | |
| ); | |
| _pushDynamicInvocation( | |
| node, | |
| _elementMap.getDartType(node.variable.type), | |
| _typeInferenceMap.receiverTypeOfInvocation(node, _abstractValueDomain), | |
| selector, | |
| [ | |
| receiverInstruction, | |
| ..._visitArgumentsForDynamicTarget( | |
| selector, | |
| node.arguments, | |
| typeArguments, | |
| ), | |
| ], | |
| typeArguments, | |
| _sourceInformationBuilder.buildCall(node, node), | |
| ); | |
| } | |
| void _handleEquals( | |
| ir.Expression node, | |
| ir.Expression left, | |
| HInstruction leftInstruction, | |
| HInstruction rightInstruction, | |
| ) { | |
| _pushDynamicInvocation( | |
| node, | |
| _getStaticType(left), | |
| _typeInferenceMap.receiverTypeOfInvocation(node, _abstractValueDomain), | |
| Selectors.equals, | |
| [leftInstruction, rightInstruction], | |
| const <DartType>[], | |
| _sourceInformationBuilder.buildCall(left, node), | |
| ); | |
| } | |
| @override | |
| void visitEqualsNull(ir.EqualsNull node) { | |
| node.expression.accept(this); | |
| HInstruction receiverInstruction = pop(); | |
| _handleEquals( | |
| node, | |
| node.expression, | |
| receiverInstruction, | |
| graph.addConstantNull(closedWorld), | |
| ); | |
| } | |
| @override | |
| void visitEqualsCall(ir.EqualsCall node) { | |
| node.left.accept(this); | |
| HInstruction leftInstruction = pop(); | |
| node.right.accept(this); | |
| HInstruction rightInstruction = pop(); | |
| return _handleEquals(node, node.left, leftInstruction, rightInstruction); | |
| } | |
| HInterceptor _interceptorFor( | |
| HInstruction intercepted, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| HInterceptor interceptor = HInterceptor( | |
| intercepted, | |
| _abstractValueDomain.nonNullType, | |
| )..sourceInformation = sourceInformation; | |
| add(interceptor); | |
| return interceptor; | |
| } | |
| static ir.Class _containingClass(ir.TreeNode? node) { | |
| while (node != null) { | |
| if (node is ir.Class) return node; | |
| node = node.parent; | |
| } | |
| throw ArgumentError.value(node, 'node', 'No containing class found.'); | |
| } | |
| HInstruction _buildInvokeSuper( | |
| Selector selector, | |
| ClassEntity containingClass, | |
| MemberEntity target, | |
| List<HInstruction> arguments, | |
| List<DartType> typeArguments, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| HInstruction receiver = localsHandler.readThis( | |
| sourceInformation: sourceInformation, | |
| ); | |
| List<HInstruction> inputs = []; | |
| bool isIntercepted = closedWorld.interceptorData.isInterceptedSelector( | |
| selector, | |
| ); | |
| if (isIntercepted) { | |
| inputs.add(_interceptorFor(receiver, sourceInformation)); | |
| } | |
| inputs.add(receiver); | |
| inputs.addAll(arguments); | |
| late final AbstractValue typeMask; | |
| if (selector.isGetter && target.isGetter || | |
| !selector.isGetter && target is FunctionEntity) { | |
| typeMask = _typeInferenceMap.getReturnTypeOf(target as FunctionEntity); | |
| } else { | |
| typeMask = _abstractValueDomain.dynamicType; | |
| } | |
| HInstruction instruction = HInvokeSuper( | |
| target, | |
| containingClass, | |
| selector, | |
| inputs, | |
| isIntercepted, | |
| typeMask, | |
| typeArguments, | |
| sourceInformation, | |
| isSetter: selector.isSetter || selector.isIndexSet, | |
| ); | |
| // TODO(natebiggs): Pass typeMask below and make non-nullable. | |
| instruction.sideEffects.setTo( | |
| _inferredData.getSideEffectsOfSelector(selector, null), | |
| ); | |
| push(instruction); | |
| return instruction; | |
| } | |
| @override | |
| void visitSuperPropertyGet(ir.SuperPropertyGet node) { | |
| final sourceInformation = _sourceInformationBuilder.buildGet(node); | |
| final target = getEffectiveSuperTarget(node.interfaceTarget); | |
| MemberEntity member = _elementMap.getMember(target); | |
| if (member is FieldEntity) { | |
| FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(member); | |
| if (fieldData.isEffectivelyConstant) { | |
| final value = fieldData.constantValue!; | |
| stack.add( | |
| graph.addConstant( | |
| value, | |
| closedWorld, | |
| sourceInformation: sourceInformation, | |
| ), | |
| ); | |
| return; | |
| } | |
| } | |
| _buildInvokeSuper( | |
| _elementMap.getSelector(node), | |
| _elementMap.getClass(_containingClass(node)), | |
| member, | |
| const <HInstruction>[], | |
| const <DartType>[], | |
| sourceInformation, | |
| ); | |
| } | |
| @override | |
| void visitSuperMethodInvocation(ir.SuperMethodInvocation node) { | |
| final sourceInformation = _sourceInformationBuilder.buildCall(node, node); | |
| final superTarget = getEffectiveSuperTarget(node.interfaceTarget); | |
| MemberEntity member = _elementMap.getMember(superTarget); | |
| List<DartType> typeArguments = _getStaticTypeArguments( | |
| member as FunctionEntity, | |
| node.arguments, | |
| ); | |
| MemberDefinition targetDefinition = _elementMap.getMemberDefinition(member); | |
| final target = targetDefinition.node as ir.Procedure; | |
| List<HInstruction> arguments = _visitArgumentsForStaticTarget( | |
| target.function, | |
| member.parameterStructure, | |
| node.arguments, | |
| typeArguments, | |
| sourceInformation, | |
| ); | |
| _buildInvokeSuper( | |
| _elementMap.getSelector(node), | |
| _elementMap.getClass(_containingClass(node)), | |
| member, | |
| arguments, | |
| typeArguments, | |
| sourceInformation, | |
| ); | |
| } | |
| void _checkTypeBound( | |
| HInstruction typeInstruction, | |
| DartType bound, | |
| String variableName, | |
| String methodName, | |
| ) { | |
| HInstruction boundInstruction = _typeBuilder.analyzeTypeArgument( | |
| localsHandler.substInContext(bound), | |
| sourceElement, | |
| ); | |
| HInstruction variableNameInstruction = graph.addConstantString( | |
| variableName, | |
| closedWorld, | |
| ); | |
| HInstruction methodNameInstruction = graph.addConstantString( | |
| methodName, | |
| closedWorld, | |
| ); | |
| FunctionEntity element = _commonElements.checkTypeBound; | |
| List<HInstruction> inputs = [ | |
| typeInstruction, | |
| boundInstruction, | |
| variableNameInstruction, | |
| methodNameInstruction, | |
| ]; | |
| HInstruction checkBound = HInvokeStatic( | |
| element, | |
| inputs, | |
| typeInstruction.instructionType, | |
| const <DartType>[], | |
| ); | |
| add(checkBound); | |
| } | |
| @override | |
| void visitConstructorInvocation(ir.ConstructorInvocation node) { | |
| final sourceInformation = _sourceInformationBuilder.buildNew(node); | |
| ir.Constructor target = node.target; | |
| if (node.isConst) { | |
| ConstantValue constant = _elementMap.getConstantValue(node)!; | |
| stack.add( | |
| graph.addConstant( | |
| constant, | |
| closedWorld, | |
| sourceInformation: sourceInformation, | |
| ), | |
| ); | |
| return; | |
| } | |
| ConstructorEntity constructor = _elementMap.getConstructor(target); | |
| ClassEntity cls = constructor.enclosingClass; | |
| AbstractValue typeMask = _abstractValueDomain.createNonNullExact(cls); | |
| InterfaceType instanceType = _elementMap.createInterfaceType( | |
| target.enclosingClass, | |
| node.arguments.types, | |
| ); | |
| instanceType = localsHandler.substInContext(instanceType) as InterfaceType; | |
| if (_nativeData.isJsInteropMember(constructor)) { | |
| List<HInstruction?> arguments = []; | |
| List<DartType> typeArguments = _getConstructorTypeArguments( | |
| constructor, | |
| node.arguments, | |
| ); | |
| arguments.addAll( | |
| _visitArgumentsForNativeStaticTarget(target.function, node.arguments), | |
| ); | |
| // TODO(johnniwinther): Remove this when type arguments are passed to | |
| // constructors like calling a generic method. | |
| _addTypeArguments( | |
| arguments, | |
| _getClassTypeArguments(cls, node.arguments), | |
| sourceInformation, | |
| ); | |
| _addImplicitInstantiation(instanceType); | |
| _pushStaticNativeInvocation( | |
| constructor, | |
| arguments, | |
| typeMask, | |
| typeArguments, | |
| sourceInformation: sourceInformation, | |
| ); | |
| _removeImplicitInstantiation(instanceType); | |
| } else { | |
| List<HInstruction> arguments = []; | |
| if (constructor.isGenerativeConstructor && | |
| _nativeData.isNativeOrExtendsNative(constructor.enclosingClass)) { | |
| // Native class generative constructors take a pre-constructed object. | |
| arguments.add(graph.addConstantNull(closedWorld)); | |
| } | |
| List<DartType> typeArguments = _getConstructorTypeArguments( | |
| constructor, | |
| node.arguments, | |
| ); | |
| arguments.addAll( | |
| _visitArgumentsForStaticTarget( | |
| target.function, | |
| constructor.parameterStructure, | |
| node.arguments, | |
| typeArguments, | |
| sourceInformation, | |
| ), | |
| ); | |
| // TODO(johnniwinther): Remove this when type arguments are passed to | |
| // constructors like calling a generic method. | |
| _addTypeArguments( | |
| arguments, | |
| _getClassTypeArguments(cls, node.arguments), | |
| sourceInformation, | |
| ); | |
| _addImplicitInstantiation(instanceType); | |
| _pushStaticInvocation( | |
| constructor, | |
| arguments, | |
| typeMask, | |
| typeArguments, | |
| sourceInformation: sourceInformation, | |
| instanceType: instanceType, | |
| ); | |
| _removeImplicitInstantiation(instanceType); | |
| } | |
| } | |
| @override | |
| void visitIsExpression(ir.IsExpression node) { | |
| node.operand.accept(this); | |
| HInstruction expression = pop(); | |
| _pushIsTest( | |
| _widenCheckedTypeForOperand(node.type, node.operand), | |
| expression, | |
| _sourceInformationBuilder.buildIs(node), | |
| ); | |
| } | |
| /// Returns a type that is equivalent to `checkedType`, but possibly simpler. | |
| /// For example, if operand has static type `Iterable<E>` and checkedType is | |
| /// `List<E>`, returns `List<dynamic>` since that is an easier test that does | |
| /// not reference any type variables. | |
| ir.DartType _widenCheckedTypeForOperand( | |
| ir.DartType checkedType, | |
| ir.Expression operand, | |
| ) { | |
| if (checkedType is ir.InterfaceType) { | |
| if (checkedType.typeArguments.isEmpty) return checkedType; | |
| final operandType = operand.getStaticType( | |
| _currentFrame!.staticTypeContext!, | |
| ); | |
| final sufficiency = closedWorld.elementMap.typeEnvironment | |
| .computeTypeShapeCheckSufficiency( | |
| expressionStaticType: operandType, | |
| checkTargetType: checkedType, | |
| ); | |
| // If `true` the caller only needs to check nullabillity and the actual | |
| // concrete class, no need to check [testedAgainstType] arguments. | |
| if (sufficiency == ir.TypeShapeCheckSufficiency.interfaceShape) { | |
| return closedWorld.elementMap.coreTypes.rawType( | |
| checkedType.classNode, | |
| operandType.nullability == ir.Nullability.nonNullable | |
| ? ir.Nullability.nonNullable | |
| : checkedType.nullability, | |
| ); | |
| } | |
| } | |
| return checkedType; | |
| } | |
| void _pushIsTest( | |
| ir.DartType type, | |
| HInstruction expression, | |
| SourceInformation? sourceInformation, | |
| ) { | |
| if (type is ir.InvalidType) { | |
| // TODO(sra): Make InvalidType carry a message. | |
| _generateTypeError('invalid type', sourceInformation); | |
| pop(); | |
| stack.add(graph.addConstantBool(true, closedWorld)); | |
| return; | |
| } | |
| DartType typeValue = localsHandler.substInContext( | |
| _elementMap.getDartType(type), | |
| ); | |
| HInstruction rti = _typeBuilder.analyzeTypeArgument( | |
| typeValue, | |
| sourceElement, | |
| ); | |
| AbstractValueWithPrecision checkedType = _abstractValueDomain | |
| .createFromStaticType(typeValue); | |
| push( | |
| HIsTest( | |
| typeValue, | |
| checkedType, | |
| rti, | |
| expression, | |
| _abstractValueDomain.boolType, | |
| )..sourceInformation = sourceInformation, | |
| ); | |
| } | |
| @override | |
| void visitThrow(ir.Throw node) { | |
| _visitThrowExpression(node.expression); | |
| if (_isReachable) { | |
| final sourceInformation = _sourceInformationBuilder.buildThrow(node); | |
| _handleInTryStatement(); | |
| push( | |
| HThrowExpression( | |
| pop(), | |
| _abstractValueDomain.emptyType, | |
| sourceInformation, | |
| withoutHelperFrame: closedWorld.annotationsData | |
| .throwWithoutHelperFrame(node), | |
| ), | |
| ); | |
| _isReachable = false; | |
| } | |
| } | |
| void _visitThrowExpression(ir.Expression expression) { | |
| bool old = _inExpressionOfThrow; | |
| try { | |
| _inExpressionOfThrow = true; | |
| expression.accept(this); | |
| } finally { | |
| _inExpressionOfThrow = old; | |
| } | |
| } | |
| @override | |
| void visitYieldStatement(ir.YieldStatement node) { | |
| node.expression.accept(this); | |
| add( | |
| HYield( | |
| pop(), | |
| node.isYieldStar, | |
| _abstractValueDomain.emptyType, | |
| _sourceInformationBuilder.buildYield(node), | |
| ), | |
| ); | |
| } | |
| @override | |
| void visitAwaitExpression(ir.AwaitExpression node) { | |
| node.operand.accept(this); | |
| HInstruction awaited = pop(); | |
| // TODO(herhut): Improve this type. | |
| push( | |
| HAwait(awaited, _abstractValueDomain.dynamicType) | |
| ..sourceInformation = _sourceInformationBuilder.buildAwait(node), | |
| ); | |
| } | |
| @override | |
| void visitRethrow(ir.Rethrow node) { | |
| HInstruction? exception = _rethrowableException; | |
| if (exception == null) { | |
| exception = graph.addConstantNull(closedWorld); | |
| reporter.internalError( | |
| _elementMap.getSpannable(targetElement, node), | |
| 'rethrowableException should not be null.', | |
| ); | |
| } | |
| _handleInTryStatement(); | |
| final sourceInformation = _sourceInformationBuilder.buildThrow(node); | |
| _closeAndGotoExit(HThrow(exception, sourceInformation, isRethrow: true)); | |
| // ir.Rethrow is an expression so we need to push a value - a constant with | |
| // no type. | |
| stack.add(graph.addConstantUnreachable(closedWorld)); | |
| } | |
| @override | |
| void visitThisExpression(ir.ThisExpression node) { | |
| stack.add( | |
| localsHandler.readThis( | |
| sourceInformation: _sourceInformationBuilder.buildGet(node), | |
| ), | |
| ); | |
| } | |
| @override | |
| void visitNot(ir.Not node) { | |
| node.operand.accept(this); | |
| push( | |
| HNot(pop(), _abstractValueDomain.boolType) | |
| ..sourceInformation = _sourceInformationBuilder.buildUnary(node), | |
| ); | |
| } | |
| @override | |
| void visitStringConcatenation(ir.StringConcatenation node) { | |
| KernelStringBuilder stringBuilder = KernelStringBuilder(this); | |
| node.accept(stringBuilder); | |
| stack.add(stringBuilder.result!); | |
| } | |
| @override | |
| void visitTryCatch(ir.TryCatch node) { | |
| TryCatchFinallyBuilder tryBuilder = TryCatchFinallyBuilder( | |
| this, | |
| _sourceInformationBuilder.buildTry(node), | |
| ); | |
| node.body.accept(this); | |
| tryBuilder | |
| ..closeTryBody() | |
| ..buildCatch(node) | |
| ..cleanUp(); | |
| } | |
| /// `try { ... } catch { ... } finally { ... }` statements are a little funny | |
| /// because a try can have one or both of {catch|finally}. The way this is | |
| /// encoded in kernel AST are two separate classes with no common superclass | |
| /// aside from Statement. If a statement has both `catch` and `finally` | |
| /// clauses then it is encoded in kernel as so that the TryCatch is the body | |
| /// statement of the TryFinally. To produce more efficient code rather than | |
| /// nested try statements, the visitors avoid one potential level of | |
| /// recursion. | |
| @override | |
| void visitTryFinally(ir.TryFinally node) { | |
| TryCatchFinallyBuilder tryBuilder = TryCatchFinallyBuilder( | |
| this, | |
| _sourceInformationBuilder.buildTry(node), | |
| ); | |
| // We do these shenanigans to produce better looking code that doesn't | |
| // have nested try statements. | |
| final nodeBody = node.body; | |
| if (nodeBody is ir.TryCatch) { | |
| nodeBody.body.accept(this); | |
| tryBuilder | |
| ..closeTryBody() | |
| ..buildCatch(nodeBody); | |
| } else { | |
| node.body.accept(this); | |
| tryBuilder.closeTryBody(); | |
| } | |
| tryBuilder | |
| ..buildFinallyBlock(() { | |
| node.finalizer.accept(this); | |
| }) | |
| ..cleanUp(); | |
| } | |
| @override | |
| void visitFileUriExpression(ir.FileUriExpression node) { | |
| node.expression.accept(this); | |
| } | |
| bool _tryInlineMethod( | |
| FunctionEntity function, | |
| Selector? selector, | |
| AbstractValue? mask, | |
| List<HInstruction> providedArguments, | |
| List<DartType>? typeArguments, | |
| ir.Node? currentNode, | |
| SourceInformation? sourceInformation, { | |
| InterfaceType? instanceType, | |
| }) { | |
| final inlined = _doTryInlineMethod( | |
| function, | |
| selector, | |
| mask, | |
| providedArguments, | |
| typeArguments, | |
| currentNode, | |
| sourceInformation, | |
| instanceType: instanceType, | |
| ); | |
| if (inlined) { | |
| _metrics.countMethodInlined.add(); | |
| } else { | |
| _metrics.countMethodNotInlined.add(); | |
| } | |
| return inlined; | |
| } | |
| /// Try to inline [element] within the correct context of the builder. The | |
| /// insertion point is the state of the builder. | |
| bool _doTryInlineMethod( | |
| FunctionEntity function, | |
| Selector? selector, | |
| AbstractValue? mask, | |
| List<HInstruction> providedArguments, | |
| List<DartType>? typeArguments, | |
| ir.Node? currentNode, | |
| SourceInformation? sourceInformation, { | |
| InterfaceType? instanceType, | |
| }) { | |
| if (function.isExternal) { | |
| // Don't inline external methods; these should just fail at runtime. | |
| return false; | |
| } | |
| if (_nativeData.isJsInteropMember(function) && | |
| !(function is ConstructorEntity && function.isFactoryConstructor)) { | |
| // We only inline factory JavaScript interop constructors. | |
| return false; | |
| } | |
| // Check if inlining is disabled for the current element (includes globally) | |
| // before making decisions on the basis of the callee so that cached callee | |
| // decisions are not a function of the call site's method. | |
| if (closedWorld.annotationsData.hasDisableInlining(_currentFrame!.member)) { | |
| return false; | |
| } | |
| // Don't inline functions marked with 'allow-dce' since we need the call | |
| // instruction to recognize the whole call as unused. We might be able to | |
| // inline simple methods afterwards. | |
| if (closedWorld.annotationsData.allowDCE(function)) { | |
| return false; | |
| } | |
| // Don't inline functions marked with 'allow-cse' since we need the call | |
| // instructions to recognize repeated calls. We might be able to inline | |
| // simple methods afterwards. If this is the only call site, we will never | |
| // find the repeated call, so we should consider inlining here. | |
| if (closedWorld.annotationsData.allowCSE(function) && | |
| !_isCalledOnce(function)) { | |
| return false; | |
| } | |
| bool insideLoop = loopDepth > 0 || graph.calledInLoop; | |
| // Bail out early if the inlining decision is in the cache and we can't | |
| // inline (no need to check the hard constraints). | |
| if (_inlineCache.markedAsNoInline(function)) return false; | |
| final cachedCanBeInlined = _inlineCache.canInline( | |
| function, | |
| insideLoop: insideLoop, | |
| ); | |
| if (cachedCanBeInlined == false) return false; | |
| bool meetsHardConstraints() { | |
| assert( | |
| selector != null || | |
| function.isStatic || | |
| function.isTopLevel || | |
| function is ConstructorEntity || | |
| function is ConstructorBodyEntity, | |
| failedAt(function, "Missing selector for inlining of $function."), | |
| ); | |
| if (selector != null) { | |
| if (!selector.applies(function)) return false; | |
| if (mask != null && | |
| _abstractValueDomain | |
| .isTargetingMember(mask, function, selector.memberName) | |
| .isDefinitelyFalse) { | |
| return false; | |
| } | |
| } | |
| if (_nativeData.isJsInteropMember(function)) return false; | |
| // Don't inline operator== methods if the parameter can be null. | |
| if (function.name == '==') { | |
| if (providedArguments[1] | |
| .isNull(_abstractValueDomain) | |
| .isPotentiallyTrue) { | |
| return false; | |
| } | |
| } | |
| // Generative constructors of native classes should not be called directly | |
| // and have an extra argument that causes problems with inlining. | |
| if (function is ConstructorEntity && | |
| function.isGenerativeConstructor && | |
| _nativeData.isNativeOrExtendsNative(function.enclosingClass)) { | |
| return false; | |
| } | |
| // A generative constructor body is not seen by global analysis, | |
| // so we should not query for its type. | |
| if (function is! ConstructorBodyEntity) { | |
| if (globalInferenceResults.resultOfMember(function).throwsAlways) { | |
| // TODO(johnniwinther): It seems wrong to set `isReachable` to `false` | |
| // since we are _not_ going to inline [function]. This has | |
| // implications in switch cases where we might need to insert a | |
| // `break` that was skipped due to `isReachable` being `false`. | |
| _isReachable = false; | |
| return false; | |
| } | |
| } | |
| // Record getters are synthetic and have no bodies so we cannot inline | |
| // them at this point. | |
| if (function is JRecordGetter) return false; | |
| return true; | |
| } | |
| bool doesNotContainCode(InlineData inlineData) { | |
| // A function with size 1 does not contain any code. | |
| return inlineData.canBeInlined(maxInliningNodes: 1); | |
| } | |
| bool reductiveHeuristic(InlineData inlineData) { | |
| // The call is on a path which is executed rarely, so inline only if it | |
| // does not make the program larger. | |
| if (_isCalledOnce(function)) { | |
| return inlineData.canBeInlined(); | |
| } | |
| if (inlineData.canBeInlinedReductive( | |
| argumentCount: providedArguments.length, | |
| )) { | |
| return true; | |
| } | |
| return doesNotContainCode(inlineData); | |
| } | |
| bool heuristicSayGoodToGo() { | |
| // Don't inline recursively, | |
| if (_inliningStack.any((entry) => entry.function == function)) { | |
| return false; | |
| } | |
| // Don't inline across deferred import to prevent leaking code. The only | |
| // exception is an empty function (which does not contain code). | |
| bool hasOnlyNonDeferredImportPaths = closedWorld.outputUnitData | |
| .hasOnlyNonDeferredImportPaths(_initialTargetElement, function); | |
| InlineData inlineData = _inlineDataCache.getInlineData( | |
| _elementMap, | |
| function, | |
| ); | |
| if (!hasOnlyNonDeferredImportPaths) { | |
| return doesNotContainCode(inlineData); | |
| } | |
| // Do not inline code that is rarely executed unless it reduces size. | |
| if (_inExpressionOfThrow || graph.isLazyInitializer) { | |
| return reductiveHeuristic(inlineData); | |
| } | |
| if (cachedCanBeInlined == true) { | |
| // We may have forced the inlining of some methods. Therefore check | |
| // if we can inline this method regardless of size. | |
| String? reason; | |
| assert( | |
| (reason = inlineData.cannotBeInlinedReason(allowLoops: true)) == null, | |
| failedAt(function, "Cannot inline $function: $reason"), | |
| ); | |
| return true; | |
| } | |
| int numParameters = function.parameterStructure.totalParameters; | |
| int? maxInliningNodes; | |
| if (insideLoop) { | |
| maxInliningNodes = | |
| InlineWeeder.inliningNodesInsideLoop + | |
| InlineWeeder.inliningNodesInsideLoopArgFactor * numParameters; | |
| } else { | |
| maxInliningNodes = | |
| InlineWeeder.inliningNodesOutsideLoop + | |
| InlineWeeder.inliningNodesOutsideLoopArgFactor * numParameters; | |
| } | |
| bool markedTryInline = _inlineCache.markedAsTryInline(function); | |
| bool calledOnce = _isCalledOnce(function); | |
| // If a method is called only once, and all the methods in the inlining | |
| // stack are called only once as well, we know we will save on output size | |
| // by inlining this method. | |
| if (markedTryInline || calledOnce) { | |
| maxInliningNodes = null; | |
| } | |
| bool allowLoops = false; | |
| if (markedTryInline) { | |
| allowLoops = true; | |
| } | |
| bool canInline = inlineData.canBeInlined( | |
| maxInliningNodes: maxInliningNodes, | |
| allowLoops: allowLoops, | |
| ); | |
| if (markedTryInline) { | |
| if (canInline) { | |
| _inlineCache.markAsInlinable(function, insideLoop: true); | |
| _inlineCache.markAsInlinable(function, insideLoop: false); | |
| } else { | |
| _inlineCache.markAsNonInlinable(function, insideLoop: true); | |
| _inlineCache.markAsNonInlinable(function, insideLoop: false); | |
| } | |
| } else if (calledOnce) { | |
| // TODO(34203): We can't update the decision due to imprecision in the | |
| // calledOnce data, described in Issue 34203. | |
| } else { | |
| if (canInline) { | |
| _inlineCache.markAsInlinable(function, insideLoop: insideLoop); | |
| } else { | |
| if (_isFunctionCalledOnce(function)) { | |
| // TODO(34203): We can't update the decision due to imprecision in | |
| // the calledOnce data, described in Issue 34203. | |
| } else { | |
| _inlineCache.markAsNonInlinable(function, insideLoop: insideLoop); | |
| } | |
| } | |
| } | |
| return canInline; | |
| } | |
| void doInlining() { | |
| if (function is ConstructorEntity) { | |
| registry.registerStaticUse( | |
| StaticUse.constructorInlining(function, instanceType), | |
| ); | |
| } else { | |
| assert( | |
| instanceType == null, | |
| "Unexpected instance type for $function: $instanceType", | |
| ); | |
| registry.registerStaticUse( | |
| StaticUse.methodInlining(function, typeArguments), | |
| ); | |
| } | |
| // Add an explicit null check on the receiver before doing the inlining. | |
| if (function.isInstanceMember && | |
| function is! ConstructorBodyEntity && | |
| (mask == null || | |
| _abstractValueDomain.isNull(mask).isPotentiallyTrue)) { | |
| HNullCheck guard = | |
| HNullCheck(providedArguments[0], _abstractValueDomain.dynamicType) | |
| ..selector = selector | |
| ..sourceInformation = sourceInformation; | |
| add(guard); | |
| providedArguments[0] = guard; | |
| } | |
| List<HInstruction> compiledArguments = _completeCallArgumentsList( | |
| function, | |
| selector, | |
| providedArguments, | |
| currentNode, | |
| ); | |
| _enterInlinedMethod(function, compiledArguments, instanceType); | |
| _inlinedFrom(function, sourceInformation, () { | |
| if (!_isReachable) { | |
| _emitReturn( | |
| graph.addConstantUnreachable(closedWorld), | |
| sourceInformation, | |
| ); | |
| } else { | |
| _doInline(function); | |
| } | |
| }); | |
| _leaveInlinedMethod(); | |
| } | |
| if (meetsHardConstraints() && heuristicSayGoodToGo()) { | |
| doInlining(); | |
| _infoReporter?.reportInlined( | |
| function, | |
| _inliningStack.isEmpty ? targetElement : _inliningStack.last.function, | |
| ); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Returns a complete argument list for a call of [function]. | |
| List<HInstruction> _completeCallArgumentsList( | |
| FunctionEntity function, | |
| Selector? selector, | |
| List<HInstruction> providedArguments, | |
| ir.Node? currentNode, | |
| ) { | |
| bool isInstanceMember = function.isInstanceMember; | |
| // For static calls, [providedArguments] is complete, default arguments | |
| // have been included if necessary, see [makeStaticArgumentList]. | |
| if (!isInstanceMember || | |
| currentNode == null || // In erroneous code, currentNode can be null. | |
| function is ConstructorBodyEntity || | |
| selector!.isGetter) { | |
| // For these cases, the provided argument list is known to be complete. | |
| return providedArguments; | |
| } else { | |
| return _completeDynamicCallArgumentsList( | |
| selector, | |
| function, | |
| providedArguments, | |
| ); | |
| } | |
| } | |
| /// Returns a complete argument list for a dynamic call of [function]. The | |
| /// initial argument list [providedArguments], created by | |
| /// [addDynamicSendArgumentsToList], does not include values for default | |
| /// arguments used in the call. The reason is that the target function (which | |
| /// defines the defaults) is not known. | |
| /// | |
| /// However, inlining can only be performed when the target function can be | |
| /// resolved statically. The defaults can therefore be included at this point. | |
| /// | |
| /// The [providedArguments] list contains first all positional arguments, then | |
| /// the provided named arguments (the named arguments that are defined in the | |
| /// [selector]) in a specific order (see [addDynamicSendArgumentsToList]). | |
| List<HInstruction> _completeDynamicCallArgumentsList( | |
| Selector selector, | |
| FunctionEntity function, | |
| List<HInstruction> providedArguments, | |
| ) { | |
| assert(selector.applies(function)); | |
| CallStructure callStructure = selector.callStructure; | |
| ParameterStructure parameterStructure = function.parameterStructure; | |
| List<String> selectorArgumentNames = selector.callStructure | |
| .getOrderedNamedArguments(); | |
| bool methodNeedsTypeArguments = _rtiNeed.methodNeedsTypeArguments(function); | |
| List<HInstruction> compiledArguments = []; | |
| // Copy receiver. | |
| compiledArguments.add(providedArguments[0]); | |
| /// Offset of positional arguments in [providedArguments]. | |
| int positionalArgumentOffset = 1; | |
| /// Offset of named arguments in [providedArguments]. | |
| int namedArgumentOffset = callStructure.positionalArgumentCount + 1; | |
| int positionalArgumentIndex = 0; | |
| int namedArgumentIndex = 0; | |
| _elementEnvironment.forEachParameter(function, ( | |
| DartType type, | |
| String? name, | |
| ConstantValue? defaultValue, | |
| ) { | |
| if (positionalArgumentIndex < parameterStructure.positionalParameters) { | |
| if (positionalArgumentIndex < callStructure.positionalArgumentCount) { | |
| compiledArguments.add( | |
| providedArguments[positionalArgumentOffset + | |
| positionalArgumentIndex++], | |
| ); | |
| } else { | |
| assert( | |
| defaultValue != null, | |
| failedAt(function, 'No constant computed for parameter $name'), | |
| ); | |
| compiledArguments.add(graph.addConstant(defaultValue!, closedWorld)); | |
| } | |
| } else { | |
| // Example: | |
| // void foo(a, {b, d, c}) | |
| // foo(0, d = 1, b = 2) | |
| // | |
| // providedArguments = [0, 2, 1] | |
| // selectorArgumentNames = [b, d] | |
| // parameterStructure.namedParameters = [b, c, d] | |
| // | |
| // For each parameter name in the signature, if the argument name | |
| // matches we use the next provided argument, otherwise we get the | |
| // default. | |
| if (namedArgumentIndex < selectorArgumentNames.length && | |
| name == selectorArgumentNames[namedArgumentIndex]) { | |
| // The named argument was provided in the function invocation. | |
| compiledArguments.add( | |
| providedArguments[namedArgumentOffset + namedArgumentIndex++], | |
| ); | |
| } else { | |
| assert( | |
| defaultValue != null, | |
| failedAt(function, 'No constant computed for parameter $name'), | |
| ); | |
| compiledArguments.add(graph.addConstant(defaultValue!, closedWorld)); | |
| } | |
| } | |
| }); | |
| if (methodNeedsTypeArguments) { | |
| if (callStructure.typeArgumentCount == | |
| parameterStructure.typeParameters) { | |
| /// Offset of type arguments in [providedArguments]. | |
| int typeArgumentOffset = callStructure.argumentCount + 1; | |
| // Pass explicit type arguments. | |
| for ( | |
| int typeArgumentIndex = 0; | |
| typeArgumentIndex < callStructure.typeArgumentCount; | |
| typeArgumentIndex++ | |
| ) { | |
| compiledArguments.add( | |
| providedArguments[typeArgumentOffset + typeArgumentIndex], | |
| ); | |
| } | |
| } else { | |
| assert(callStructure.typeArgumentCount == 0); | |
| // Pass type variable bounds as type arguments. | |
| for (TypeVariableType typeVariable | |
| in _elementEnvironment.getFunctionTypeVariables(function)) { | |
| compiledArguments.add( | |
| _computeTypeArgumentDefaultValue(function, typeVariable), | |
| ); | |
| } | |
| } | |
| } | |
| return compiledArguments; | |
| } | |
| HInstruction _computeTypeArgumentDefaultValue( | |
| FunctionEntity function, | |
| TypeVariableType typeVariable, | |
| ) { | |
| DartType bound = _elementEnvironment.getTypeVariableDefaultType( | |
| typeVariable.element, | |
| ); | |
| return _typeBuilder.analyzeTypeArgument(bound, function); | |
| } | |
| /// This method is invoked before inlining the body of [function] into this | |
| /// [SsaGraphBuilder]. | |
| void _enterInlinedMethod( | |
| FunctionEntity function, | |
| List<HInstruction> compiledArguments, | |
| InterfaceType? instanceType, | |
| ) { | |
| KernelInliningState state = KernelInliningState( | |
| function, | |
| _returnLocal, | |
| _returnType, | |
| stack, | |
| localsHandler, | |
| _inTryStatement, | |
| _isCalledOnce(function), | |
| ); | |
| _inliningStack.add(state); | |
| // Setting up the state of the (AST) builder is performed even when the | |
| // inlined function is in IR, because the irInliner uses the [returnElement] | |
| // of the AST builder. | |
| _setupStateForInlining(function, compiledArguments, instanceType); | |
| } | |
| /// This method sets up the local state of the builder for inlining | |
| /// [function]. The arguments of the function are inserted into the | |
| /// [localsHandler]. | |
| /// | |
| /// When inlining a function, `return` statements are not emitted as | |
| /// [HReturn] instructions. Instead, the value of a synthetic element is | |
| /// updated in the [localsHandler]. This function creates such an element and | |
| /// stores it in the [_returnLocal] field. | |
| void _setupStateForInlining( | |
| FunctionEntity function, | |
| List<HInstruction> compiledArguments, | |
| InterfaceType? instanceType, | |
| ) { | |
| localsHandler = LocalsHandler( | |
| this, | |
| function, | |
| function, | |
| instanceType ?? _elementMap.getMemberThisType(function), | |
| _nativeData, | |
| _interceptorData, | |
| ); | |
| localsHandler.setupScope(function); | |
| CapturedScope scopeData = _closureDataLookup.getCapturedScope(function); | |
| bool forGenerativeConstructorBody = function is ConstructorBodyEntity; | |
| _returnLocal = SyntheticLocal("result", function, function); | |
| localsHandler.updateLocal( | |
| _returnLocal!, | |
| graph.addConstantNull(closedWorld), | |
| ); | |
| _inTryStatement = false; // TODO(lry): why? Document. | |
| int argumentIndex = 0; | |
| if (function.isInstanceMember) { | |
| localsHandler.updateLocal( | |
| localsHandler.thisLocal!, | |
| compiledArguments[argumentIndex++], | |
| ); | |
| } | |
| KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(function); | |
| forEachOrderedParameter(_elementMap, function, ( | |
| ir.Variable variable, { | |
| required bool isElided, | |
| }) { | |
| Local local = localsMap.getLocalVariable(variable); | |
| if (isElided) { | |
| localsHandler.updateLocal(local, _defaultValueForParameter(variable)); | |
| return; | |
| } | |
| if (forGenerativeConstructorBody && | |
| scopeData.isBoxedVariable(_localsMap, local)) { | |
| // The parameter will be a field in the box passed as the last | |
| // parameter. So no need to have it. | |
| return; | |
| } | |
| HInstruction argument = compiledArguments[argumentIndex++]; | |
| localsHandler.updateLocal(local, argument); | |
| }); | |
| bool hasTypeParameters = function.parameterStructure.typeParameters > 0; | |
| bool needsTypeArguments = _rtiNeed.methodNeedsTypeArguments(function); | |
| for (TypeVariableType typeVariable | |
| in _elementEnvironment.getFunctionTypeVariables(function)) { | |
| HInstruction argument; | |
| if (hasTypeParameters && needsTypeArguments) { | |
| argument = compiledArguments[argumentIndex++]; | |
| } else { | |
| argument = _computeTypeArgumentDefaultValue(function, typeVariable); | |
| } | |
| localsHandler.updateLocal( | |
| localsHandler.getTypeVariableAsLocal(typeVariable), | |
| argument, | |
| ); | |
| } | |
| if (forGenerativeConstructorBody && scopeData.requiresContextBox) { | |
| HInstruction box = compiledArguments[argumentIndex++]; | |
| assert(box is HCreateBox); | |
| // TODO(sra): Make inlining of closures work. We should always call | |
| // enterScope, and pass in the inlined 'this' as well as the 'box'. | |
| localsHandler.enterScope( | |
| scopeData, | |
| null, | |
| inlinedBox: box, | |
| forGenerativeConstructorBody: forGenerativeConstructorBody, | |
| ); | |
| } | |
| final enclosing = function.enclosingClass; | |
| if ((function is ConstructorEntity || function is ConstructorBodyEntity) && | |
| _rtiNeed.classNeedsTypeArguments(enclosing!)) { | |
| InterfaceType thisType = _elementEnvironment.getThisType(enclosing); | |
| for (var (typeVariable as TypeVariableType) in thisType.typeArguments) { | |
| HInstruction argument = compiledArguments[argumentIndex++]; | |
| localsHandler.updateLocal( | |
| localsHandler.getTypeVariableAsLocal(typeVariable), | |
| argument, | |
| ); | |
| } | |
| } | |
| assert( | |
| argumentIndex == compiledArguments.length || | |
| !_rtiNeed.methodNeedsTypeArguments(function) && | |
| compiledArguments.length - argumentIndex == | |
| function.parameterStructure.typeParameters, | |
| failedAt( | |
| function, | |
| "Only $argumentIndex of ${compiledArguments.length} " | |
| "arguments have been read from: $compiledArguments passed to " | |
| "$function.", | |
| ), | |
| ); | |
| _returnType = _elementEnvironment.getFunctionType(function).returnType; | |
| stack = <HInstruction>[]; | |
| _insertCoverageCall(function); | |
| } | |
| void _leaveInlinedMethod() { | |
| HInstruction result = localsHandler.readLocal(_returnLocal!); | |
| KernelInliningState state = _inliningStack.removeLast(); | |
| _restoreState(state); | |
| stack.add(result); | |
| } | |
| void _restoreState(KernelInliningState state) { | |
| localsHandler = state.oldLocalsHandler; | |
| _returnLocal = state.oldReturnLocal; | |
| _inTryStatement = state.inTryStatement; | |
| _returnType = state.oldReturnType; | |
| assert(stack.isEmpty); | |
| stack = state.oldStack; | |
| } | |
| void _emitReturn(HInstruction? value, SourceInformation? sourceInformation) { | |
| if (_inliningStack.isEmpty) { | |
| _closeAndGotoExit(HReturn(value, sourceInformation)); | |
| } else { | |
| value ??= graph.addConstantNull(closedWorld); | |
| localsHandler.updateLocal(_returnLocal!, value); | |
| } | |
| } | |
| void _doInline(FunctionEntity function) { | |
| _visitInlinedFunction(function); | |
| } | |
| /// Run this builder on the body of the [function] to be inlined. | |
| void _visitInlinedFunction(FunctionEntity function) { | |
| _potentiallyCheckInlinedParameterTypes(function); | |
| MemberDefinition definition = _elementMap.getMemberDefinition(function); | |
| switch (definition.kind) { | |
| case MemberKind.constructor: | |
| _buildConstructor( | |
| function as ConstructorEntity, | |
| definition.node as ir.Constructor, | |
| ); | |
| return; | |
| case MemberKind.constructorBody: | |
| final constructor = definition.node as ir.Constructor; | |
| constructor.function.body!.accept(this); | |
| return; | |
| case MemberKind.regular: | |
| ir.Node node = definition.node; | |
| if (node is ir.Constructor) { | |
| node.function.body!.accept(this); | |
| return; | |
| } else if (node is ir.Procedure) { | |
| node.function.body!.accept(this); | |
| return; | |
| } | |
| break; | |
| case MemberKind.closureCall: | |
| final node = definition.node as ir.LocalFunction; | |
| node.function.body!.accept(this); | |
| return; | |
| case MemberKind.closureField: | |
| case MemberKind.generatorBody: | |
| case MemberKind.recordGetter: | |
| case MemberKind.signature: | |
| case MemberKind.parameterStub: | |
| } | |
| failedAt(function, "Unexpected inlined function: $definition"); | |
| } | |
| /// Generates type tests for the parameters of the inlined function. | |
| void _potentiallyCheckInlinedParameterTypes(FunctionEntity function) { | |
| // TODO(sra): Incorporate properties of call site to help determine which | |
| // type parameters and value parameters need to be checked. | |
| bool trusted = false; | |
| if (function.isStatic || | |
| function.isTopLevel || | |
| function is ConstructorEntity || | |
| function is ConstructorBodyEntity) { | |
| // We inline static methods, top-level methods, constructors and | |
| // constructor bodies only from direct call sites. | |
| trusted = true; | |
| } | |
| if (!trusted) { | |
| _checkTypeVariableBounds(function); | |
| } | |
| KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(function); | |
| forEachOrderedParameter(_elementMap, function, ( | |
| ir.Variable variable, { | |
| required bool isElided, | |
| }) { | |
| Local parameter = localsMap.getLocalVariable(variable); | |
| HInstruction argument = localsHandler.readLocal(parameter); | |
| DartType type = localsMap.getLocalType(_elementMap, parameter); | |
| HInstruction checkedOrTrusted; | |
| if (trusted) { | |
| checkedOrTrusted = _typeBuilder.trustTypeOfParameter( | |
| function, | |
| argument, | |
| type, | |
| ); | |
| } else { | |
| checkedOrTrusted = _typeBuilder.potentiallyCheckOrTrustTypeOfParameter( | |
| function, | |
| argument, | |
| type, | |
| ); | |
| } | |
| localsHandler.updateLocal(parameter, checkedOrTrusted); | |
| }); | |
| } | |
| bool get _allInlinedFunctionsCalledOnce { | |
| return _inliningStack.isEmpty || _inliningStack.last.allFunctionsCalledOnce; | |
| } | |
| bool _isFunctionCalledOnce(FunctionEntity element) { | |
| // ConstructorBodyElements are not in the type inference graph. | |
| if (element is ConstructorBodyEntity) { | |
| // If there are no subclasses with constructors that have this constructor | |
| // as a superconstructor, it is called once by the generative | |
| // constructor's factory. A simplified version is to check this is a | |
| // constructor body for a leaf class. | |
| ClassEntity class_ = element.enclosingClass!; | |
| if (closedWorld.classHierarchy.isDirectlyInstantiated(class_)) { | |
| return !closedWorld.classHierarchy.isIndirectlyInstantiated(class_); | |
| } | |
| return false; | |
| } | |
| return globalInferenceResults.resultOfMember(element).isCalledOnce; | |
| } | |
| bool _isCalledOnce(FunctionEntity element) { | |
| return _allInlinedFunctionsCalledOnce && _isFunctionCalledOnce(element); | |
| } | |
| void _insertCoverageCall(MemberEntity element) { | |
| if (!options.experimentCallInstrumentation) return; | |
| if (element == _commonElements.traceHelper) return; | |
| // TODO(sigmund): create a better uuid for elements. | |
| HConstant idConstant = graph.addConstantInt(element.hashCode, closedWorld); | |
| String? n(Entity? e) => e == null ? '' : e.name; | |
| String name = | |
| "${n(element.library)}:${n(element.enclosingClass)}." | |
| "${n(element)}"; | |
| HConstant nameConstant = graph.addConstantString(name, closedWorld); | |
| add( | |
| HInvokeStatic( | |
| _commonElements.traceHelper, | |
| [idConstant, nameConstant], | |
| _abstractValueDomain.dynamicType, | |
| const <DartType>[], | |
| ), | |
| ); | |
| } | |
| } | |
| /// Data collected to create a constructor. | |
| class ConstructorData { | |
| /// Inlined (super) constructors. | |
| final List<ir.Constructor> constructorChain = []; | |
| /// Initial values for all instance fields. | |
| final Map<FieldEntity, HInstruction> fieldValues = {}; | |
| /// Classes for which type variables have been prepared. | |
| final Set<ClassEntity> includedClasses = {}; | |
| } | |
| class KernelInliningState { | |
| final FunctionEntity function; | |
| final Local? oldReturnLocal; | |
| final DartType? oldReturnType; | |
| final List<HInstruction> oldStack; | |
| final LocalsHandler oldLocalsHandler; | |
| final bool inTryStatement; | |
| final bool allFunctionsCalledOnce; | |
| KernelInliningState( | |
| this.function, | |
| this.oldReturnLocal, | |
| this.oldReturnType, | |
| this.oldStack, | |
| this.oldLocalsHandler, | |
| this.inTryStatement, | |
| this.allFunctionsCalledOnce, | |
| ); | |
| @override | |
| String toString() => | |
| 'KernelInliningState($function,' | |
| 'allFunctionsCalledOnce=$allFunctionsCalledOnce)'; | |
| } | |
| /// Class in charge of building try, catch and/or finally blocks. This handles | |
| /// the instructions that need to be output and the dominator calculation of | |
| /// this sequence of code. | |
| class TryCatchFinallyBuilder { | |
| final KernelSsaGraphBuilder kernelBuilder; | |
| final SourceInformation? trySourceInformation; | |
| late final HBasicBlock enterBlock; | |
| late final HBasicBlock startTryBlock; | |
| HBasicBlock? endTryBlock; | |
| HBasicBlock? startCatchBlock; | |
| HBasicBlock? endCatchBlock; | |
| HBasicBlock? startFinallyBlock; | |
| HBasicBlock? endFinallyBlock; | |
| HBasicBlock? exitBlock; | |
| late final HTry tryInstruction; | |
| HLocalValue? exception; | |
| /// True if the code surrounding this try statement was also part of a | |
| /// try/catch/finally statement. | |
| late final bool previouslyInTryStatement; | |
| SubGraph? bodyGraph; | |
| SubGraph? catchGraph; | |
| SubGraph? finallyGraph; | |
| // The original set of locals that were defined before this try block. | |
| // The catch block and the finally block must not reuse the existing locals | |
| // handler. None of the variables that have been defined in the body-block | |
| // will be used, but for loops we will add (unnecessary) phis that will | |
| // reference the body variables. This makes it look as if the variables were | |
| // used in a non-dominated block. | |
| LocalsHandler? originalSavedLocals; | |
| TryCatchFinallyBuilder(this.kernelBuilder, this.trySourceInformation) { | |
| tryInstruction = HTry(); | |
| originalSavedLocals = LocalsHandler.from(kernelBuilder.localsHandler); | |
| enterBlock = kernelBuilder.openNewBlock(); | |
| kernelBuilder.close(tryInstruction); | |
| previouslyInTryStatement = kernelBuilder._inTryStatement; | |
| kernelBuilder._inTryStatement = true; | |
| startTryBlock = kernelBuilder.graph.addNewBlock(); | |
| kernelBuilder.open(startTryBlock); | |
| } | |
| void _addExitTrySuccessor(HBasicBlock? successor) { | |
| if (successor == null) return; | |
| // Iterate over all blocks created inside this try/catch, and | |
| // attach successor information to blocks that end with | |
| // [HExitTry]. | |
| for (int i = startTryBlock.id; i < successor.id; i++) { | |
| HBasicBlock block = kernelBuilder.graph.blocks[i]; | |
| var last = block.last; | |
| if (last is HExitTry) { | |
| block.addSuccessor(successor); | |
| } | |
| } | |
| } | |
| void _addOptionalSuccessor(HBasicBlock block1, HBasicBlock? block2) { | |
| if (block2 != null) block1.addSuccessor(block2); | |
| } | |
| /// Helper function to set up basic block successors for try-catch-finally | |
| /// sequences. | |
| void _setBlockSuccessors() { | |
| // Setup all successors. The entry block that contains the [HTry] | |
| // has 1) the body, 2) the catch, 3) the finally, and 4) the exit | |
| // blocks as successors. | |
| final exit = exitBlock!; | |
| enterBlock.addSuccessor(startTryBlock); | |
| _addOptionalSuccessor(enterBlock, startCatchBlock); | |
| _addOptionalSuccessor(enterBlock, startFinallyBlock); | |
| enterBlock.addSuccessor(exit); | |
| // The body has either the catch or the finally block as successor. | |
| final endTry = endTryBlock; | |
| if (endTry != null) { | |
| assert(startCatchBlock != null || startFinallyBlock != null); | |
| endTry.addSuccessor(startCatchBlock ?? startFinallyBlock!); | |
| endTry.addSuccessor(exit); | |
| } | |
| // The catch block has either the finally or the exit block as | |
| // successor. | |
| endCatchBlock?.addSuccessor(startFinallyBlock ?? exit); | |
| // The finally block has the exit block as successor. | |
| endFinallyBlock?.addSuccessor(exit); | |
| // If a block inside try/catch aborts (eg with a return statement), | |
| // we explicitly mark this block a predecessor of the catch | |
| // block and the finally block. | |
| _addExitTrySuccessor(startCatchBlock); | |
| _addExitTrySuccessor(startFinallyBlock); | |
| } | |
| /// Build the finally{} clause of a try/{catch}/finally statement. Note this | |
| /// does not examine the body of the try clause, only the finally portion. | |
| void buildFinallyBlock(void Function() buildFinalizer) { | |
| kernelBuilder.localsHandler = LocalsHandler.from(originalSavedLocals!); | |
| startFinallyBlock = kernelBuilder.graph.addNewBlock(); | |
| kernelBuilder.open(startFinallyBlock!); | |
| buildFinalizer(); | |
| if (!kernelBuilder.isAborted()) { | |
| endFinallyBlock = kernelBuilder.close(HGoto()); | |
| } | |
| tryInstruction.finallyBlock = startFinallyBlock; | |
| finallyGraph = SubGraph(startFinallyBlock!, kernelBuilder.lastOpenedBlock); | |
| } | |
| void closeTryBody() { | |
| // We use a [HExitTry] instead of a [HGoto] for the try block | |
| // because it will have multiple successors: the join block, and | |
| // the catch or finally block. | |
| if (!kernelBuilder.isAborted()) { | |
| endTryBlock = kernelBuilder.close(HExitTry()); | |
| } | |
| bodyGraph = SubGraph(startTryBlock, kernelBuilder.lastOpenedBlock); | |
| } | |
| void buildCatch(ir.TryCatch tryCatch) { | |
| kernelBuilder.localsHandler = LocalsHandler.from(originalSavedLocals!); | |
| startCatchBlock = kernelBuilder.graph.addNewBlock(); | |
| kernelBuilder.open(startCatchBlock!); | |
| // Note that the name of this local is irrelevant. | |
| SyntheticLocal local = kernelBuilder.localsHandler.createLocal('exception'); | |
| exception = HLocalValue( | |
| local, | |
| kernelBuilder._abstractValueDomain.nonNullType, | |
| )..sourceInformation = trySourceInformation; | |
| kernelBuilder.add(exception!); | |
| final oldRethrowableException = kernelBuilder._rethrowableException; | |
| kernelBuilder._rethrowableException = exception; | |
| AbstractValue unwrappedType = kernelBuilder._typeInferenceMap | |
| .getReturnTypeOf(kernelBuilder._commonElements.exceptionUnwrapper); | |
| // Global type analysis does not currently understand that strong mode | |
| // `Object` is not nullable, so is imprecise in the return type of the | |
| // unwrapper, which leads to unnecessary checks for 'on Object'. | |
| unwrappedType = kernelBuilder._abstractValueDomain.excludeNull( | |
| unwrappedType, | |
| ); | |
| kernelBuilder._pushStaticInvocation( | |
| kernelBuilder._commonElements.exceptionUnwrapper, | |
| [exception!], | |
| unwrappedType, | |
| const <DartType>[], | |
| sourceInformation: trySourceInformation, | |
| ); | |
| final unwrappedException = kernelBuilder.pop() as HInvokeStatic; | |
| unwrappedException.sideEffects | |
| ..clearAllDependencies() | |
| ..clearAllSideEffects(); | |
| unwrappedException.targetCanThrow = false; | |
| tryInstruction.exception = exception; | |
| int catchesIndex = 0; | |
| void pushCondition(ir.Catch catchBlock) { | |
| kernelBuilder._pushIsTest( | |
| catchBlock.guard, | |
| unwrappedException, | |
| kernelBuilder._sourceInformationBuilder.buildCatch(catchBlock), | |
| ); | |
| } | |
| void visitThen() { | |
| ir.Catch catchBlock = tryCatch.catches[catchesIndex]; | |
| catchesIndex++; | |
| if (catchBlock.exception != null) { | |
| Local exceptionVariable = kernelBuilder._localsMap.getLocalVariable( | |
| catchBlock.exception!, | |
| ); | |
| kernelBuilder.localsHandler.updateLocal( | |
| exceptionVariable, | |
| unwrappedException, | |
| sourceInformation: kernelBuilder._sourceInformationBuilder.buildCatch( | |
| catchBlock, | |
| ), | |
| ); | |
| } | |
| if (catchBlock.stackTrace != null) { | |
| kernelBuilder._pushStaticInvocation( | |
| kernelBuilder._commonElements.traceFromException, | |
| [exception!], | |
| kernelBuilder._typeInferenceMap.getReturnTypeOf( | |
| kernelBuilder._commonElements.traceFromException, | |
| ), | |
| const <DartType>[], | |
| sourceInformation: kernelBuilder._sourceInformationBuilder.buildCatch( | |
| catchBlock, | |
| ), | |
| ); | |
| HInstruction traceInstruction = kernelBuilder.pop(); | |
| Local traceVariable = kernelBuilder._localsMap.getLocalVariable( | |
| catchBlock.stackTrace!, | |
| ); | |
| kernelBuilder.localsHandler.updateLocal( | |
| traceVariable, | |
| traceInstruction, | |
| sourceInformation: kernelBuilder._sourceInformationBuilder.buildCatch( | |
| catchBlock, | |
| ), | |
| ); | |
| } | |
| catchBlock.body.accept(kernelBuilder); | |
| } | |
| void visitElse() { | |
| if (catchesIndex >= tryCatch.catches.length) { | |
| kernelBuilder._closeAndGotoExit( | |
| HThrow(exception!, exception!.sourceInformation, isRethrow: true), | |
| ); | |
| } else { | |
| ir.Catch nextCatch = tryCatch.catches[catchesIndex]; | |
| kernelBuilder._handleIf( | |
| visitCondition: () { | |
| pushCondition(nextCatch); | |
| }, | |
| visitThen: visitThen, | |
| visitElse: visitElse, | |
| sourceInformation: kernelBuilder._sourceInformationBuilder.buildCatch( | |
| nextCatch, | |
| ), | |
| ); | |
| } | |
| } | |
| ir.Catch firstBlock = tryCatch.catches[catchesIndex]; | |
| kernelBuilder._handleIf( | |
| visitCondition: () { | |
| pushCondition(firstBlock); | |
| }, | |
| visitThen: visitThen, | |
| visitElse: visitElse, | |
| sourceInformation: kernelBuilder._sourceInformationBuilder.buildCatch( | |
| firstBlock, | |
| ), | |
| ); | |
| if (!kernelBuilder.isAborted()) { | |
| endCatchBlock = kernelBuilder.close(HGoto()); | |
| } | |
| kernelBuilder._rethrowableException = oldRethrowableException; | |
| tryInstruction.catchBlock = startCatchBlock; | |
| catchGraph = SubGraph(startCatchBlock!, kernelBuilder.lastOpenedBlock); | |
| } | |
| void cleanUp() { | |
| exitBlock = kernelBuilder.graph.addNewBlock(); | |
| _setBlockSuccessors(); | |
| // Use the locals handler not altered by the catch and finally | |
| // blocks. | |
| kernelBuilder.localsHandler = originalSavedLocals!; | |
| kernelBuilder.open(exitBlock!); | |
| enterBlock.setBlockFlow( | |
| HTryBlockInformation( | |
| kernelBuilder.wrapStatementGraph(bodyGraph), | |
| exception, | |
| kernelBuilder.wrapStatementGraph(catchGraph), | |
| kernelBuilder.wrapStatementGraph(finallyGraph), | |
| ), | |
| exitBlock, | |
| ); | |
| kernelBuilder._inTryStatement = previouslyInTryStatement; | |
| } | |
| } | |
| class KernelTypeBuilder extends TypeBuilder { | |
| final JsToElementMap _elementMap; | |
| KernelTypeBuilder(super.builder, this._elementMap); | |
| @override | |
| ClassTypeVariableAccess computeTypeVariableAccess(MemberEntity member) { | |
| return _elementMap.getClassTypeVariableAccessForMember(member); | |
| } | |
| } | |
| class _ErroneousInitializerVisitor extends ir.VisitorDefault<bool> | |
| with ir.VisitorDefaultValueMixin<bool> { | |
| _ErroneousInitializerVisitor(); | |
| // TODO(30809): Use const constructor. | |
| static bool check(ir.Initializer initializer) => | |
| initializer.accept(_ErroneousInitializerVisitor()); | |
| @override | |
| bool defaultInitializer(ir.Node node) => false; | |
| @override | |
| bool visitInvalidInitializer(ir.InvalidInitializer node) => true; | |
| @override | |
| bool visitLocalInitializer(ir.LocalInitializer node) { | |
| return node.value.accept(this); | |
| } | |
| // Expressions: Does the expression always throw? | |
| @override | |
| bool get defaultValue => false; | |
| @override | |
| bool visitThrow(ir.Throw node) => true; | |
| // TODO(sra): We might need to match other expressions that always throw but | |
| // in a subexpression. | |
| } | |
| /// Special [JumpHandler] implementation used to handle continue statements | |
| /// targeting switch cases. | |
| class KernelSwitchCaseJumpHandler extends SwitchCaseJumpHandler { | |
| KernelSwitchCaseJumpHandler( | |
| KernelSsaGraphBuilder builder, | |
| JumpTarget target, | |
| ir.SwitchStatement switchStatement, | |
| KernelToLocalsMap localsMap, | |
| ) : super(builder, target) { | |
| // The switch case indices must match those computed in | |
| // [KernelSsaBuilder.buildSwitchCaseConstants]. | |
| // Switch indices are 1-based so we can bypass the synthetic loop when no | |
| // cases match simply by branching on the index (which defaults to null). | |
| // TODO | |
| int switchIndex = 1; | |
| for (ir.SwitchCase switchCase in switchStatement.cases) { | |
| final continueTarget = localsMap.getJumpTargetForSwitchCase(switchCase); | |
| if (continueTarget != null) { | |
| targetIndexMap[continueTarget] = switchIndex; | |
| assert(builder.jumpTargets[continueTarget] == null); | |
| builder.jumpTargets[continueTarget] = this; | |
| } | |
| switchIndex++; | |
| } | |
| } | |
| } | |
| class InlineData { | |
| bool isConstructor = false; | |
| bool codeAfterReturn = false; | |
| bool hasLoop = false; | |
| bool hasClosure = false; | |
| bool hasTry = false; | |
| bool hasAsyncAwait = false; | |
| bool hasThrow = false; | |
| bool hasLongString = false; | |
| bool hasExternalConstantConstructorCall = false; | |
| bool hasTypeArguments = false; | |
| bool hasArgumentDefaulting = false; | |
| bool hasCast = false; | |
| bool hasIf = false; | |
| bool hasLabel = false; // TODO(51652): Remove when inlining works with labels. | |
| List<int> argumentCounts = []; | |
| int regularNodeCount = 0; | |
| int callCount = 0; | |
| int reductiveNodeCount = 0; | |
| InlineData(); | |
| bool canBeInlined({int? maxInliningNodes, bool allowLoops = false}) { | |
| return cannotBeInlinedReason( | |
| maxInliningNodes: maxInliningNodes, | |
| allowLoops: allowLoops, | |
| ) == | |
| null; | |
| } | |
| String? cannotBeInlinedReason({ | |
| int? maxInliningNodes, | |
| bool allowLoops = false, | |
| }) { | |
| if (hasLoop && !allowLoops) { | |
| return 'loop'; | |
| } else if (hasTry) { | |
| return 'try'; | |
| } else if (hasClosure) { | |
| return 'closure'; | |
| } else if (codeAfterReturn) { | |
| return 'code after return'; | |
| } else if (hasAsyncAwait) { | |
| return 'async/await'; | |
| } else if (maxInliningNodes != null && | |
| regularNodeCount - 1 > maxInliningNodes) { | |
| return 'too many nodes (${regularNodeCount - 1}>$maxInliningNodes)'; | |
| } | |
| if (hasLabel) return 'has label'; | |
| return null; | |
| } | |
| bool canBeInlinedReductive({required int argumentCount}) { | |
| return cannotBeInlinedReductiveReason(argumentCount: argumentCount) == null; | |
| } | |
| String? cannotBeInlinedReductiveReason({required int argumentCount}) { | |
| if (hasTry) { | |
| return 'try'; | |
| } else if (hasClosure) { | |
| return 'closure'; | |
| } else if (codeAfterReturn) { | |
| return 'code after return'; | |
| } else if (hasAsyncAwait) { | |
| return 'async/await'; | |
| } else if (callCount > 1) { | |
| return 'too many calls'; | |
| } else if (hasThrow) { | |
| return 'throw'; | |
| } else if (hasLongString) { | |
| return 'long string'; | |
| } else if (hasExternalConstantConstructorCall) { | |
| return 'external const constructor'; | |
| } else if (hasTypeArguments) { | |
| return 'type arguments'; | |
| } else if (hasArgumentDefaulting) { | |
| return 'argument defaulting'; | |
| } else if (hasCast) { | |
| return 'cast'; | |
| } else if (hasIf) { | |
| return 'if'; | |
| } else if (isConstructor) { | |
| return 'constructor'; | |
| } | |
| if (hasLabel) return 'has label'; | |
| for (int count in argumentCounts) { | |
| if (count > argumentCount) { | |
| return 'increasing arguments'; | |
| } | |
| } | |
| // Node budget that covers one call and the passed-in arguments. | |
| // The +1 also allows a top-level zero-argument to be inlined if it | |
| // returns a constant. | |
| int maxInliningNodes = argumentCount + 1; | |
| if (reductiveNodeCount > maxInliningNodes) { | |
| return 'too many nodes ($reductiveNodeCount>$maxInliningNodes)'; | |
| } | |
| return null; | |
| } | |
| @override | |
| String toString() { | |
| StringBuffer sb = StringBuffer(); | |
| sb.write('InlineData('); | |
| String comma = ''; | |
| if (isConstructor) { | |
| sb.write('isConstructor'); | |
| comma = ','; | |
| } | |
| if (codeAfterReturn) { | |
| sb.write(comma); | |
| sb.write('codeAfterReturn'); | |
| comma = ','; | |
| } | |
| if (hasLoop) { | |
| sb.write(comma); | |
| sb.write('hasLoop'); | |
| comma = ','; | |
| } | |
| if (hasClosure) { | |
| sb.write(comma); | |
| sb.write('hasClosure'); | |
| comma = ','; | |
| } | |
| if (hasTry) { | |
| sb.write(comma); | |
| sb.write('hasTry'); | |
| comma = ','; | |
| } | |
| if (hasAsyncAwait) { | |
| sb.write(comma); | |
| sb.write('hasAsyncAwait'); | |
| comma = ','; | |
| } | |
| if (hasThrow) { | |
| sb.write(comma); | |
| sb.write('hasThrow'); | |
| comma = ','; | |
| } | |
| if (hasLongString) { | |
| sb.write(comma); | |
| sb.write('hasLongString'); | |
| comma = ','; | |
| } | |
| if (hasExternalConstantConstructorCall) { | |
| sb.write(comma); | |
| sb.write('hasExternalConstantConstructorCall'); | |
| comma = ','; | |
| } | |
| if (hasTypeArguments) { | |
| sb.write(comma); | |
| sb.write('hasTypeArguments'); | |
| comma = ','; | |
| } | |
| if (hasArgumentDefaulting) { | |
| sb.write(comma); | |
| sb.write('hasArgumentDefaulting'); | |
| comma = ','; | |
| } | |
| if (hasCast) { | |
| sb.write(comma); | |
| sb.write('hasCast'); | |
| comma = ','; | |
| } | |
| if (hasIf) { | |
| sb.write(comma); | |
| sb.write('hasIf'); | |
| comma = ','; | |
| } | |
| if (hasLabel) { | |
| sb.write(comma); | |
| sb.write('hasLabel'); | |
| comma = ','; | |
| } | |
| if (argumentCounts.isNotEmpty) { | |
| sb.write(comma); | |
| sb.write('argumentCounts={${argumentCounts.join(',')}}'); | |
| comma = ','; | |
| } | |
| sb.write(comma); | |
| sb.write('regularNodeCount=$regularNodeCount,'); | |
| sb.write('callCount=$callCount,'); | |
| sb.write('reductiveNodeCount=$reductiveNodeCount'); | |
| sb.write(')'); | |
| return sb.toString(); | |
| } | |
| } | |
| class InlineDataCache { | |
| final bool enableUserAssertions; | |
| final bool omitImplicitCasts; | |
| InlineDataCache({ | |
| this.enableUserAssertions = false, | |
| this.omitImplicitCasts = false, | |
| }); | |
| final Map<FunctionEntity, InlineData> _cache = {}; | |
| InlineData getInlineData(JsToElementMap elementMap, FunctionEntity function) { | |
| return _cache[function] ??= InlineWeeder.computeInlineData( | |
| elementMap, | |
| function, | |
| enableUserAssertions: enableUserAssertions, | |
| omitImplicitCasts: omitImplicitCasts, | |
| ); | |
| } | |
| } | |
| class InlineWeeder extends ir.VisitorDefault<void> with ir.VisitorVoidMixin { | |
| // Invariant: *INSIDE_LOOP* > *OUTSIDE_LOOP* | |
| static const inliningNodesOutsideLoop = 15; | |
| static const inliningNodesOutsideLoopArgFactor = 3; | |
| static const inliningNodesInsideLoop = 34; | |
| static const inliningNodesInsideLoopArgFactor = 4; | |
| final bool enableUserAssertions; | |
| final bool omitImplicitCasts; | |
| final InlineData data = InlineData(); | |
| bool seenReturn = false; | |
| /// Whether node-count is collector to determine if a function can be | |
| /// inlined. | |
| bool countRegularNode = true; | |
| /// Whether node-count is collected to determine if inlining a function is | |
| /// very likely to reduce code size. | |
| /// | |
| /// For the reductive analysis: | |
| /// We allow the body to be a single function call that does not have any more | |
| /// inputs than the inlinee. | |
| /// | |
| /// We allow the body to be the return of an 'eligible' constant. A constant | |
| /// is 'eligible' if it is not large (e.g. a long string). | |
| /// | |
| /// We skip 'e as{TypeError} T' when the checks are omitted. | |
| // | |
| // TODO(sra): Consider slightly expansive simple constructors where all we | |
| // gain is a 'new' keyword, e.g. `new X.Foo(a)` vs `X.Foo$(a)`. | |
| // | |
| // TODO(25231): Make larger string constants eligible by sharing references. | |
| bool countReductiveNode = true; | |
| // When handling a generative constructor factory, the super constructor calls | |
| // are 'inlined', so tend to reuse the same parameters. [discountParameters] | |
| // is true to avoid double-counting these parameters. | |
| bool discountParameters = false; | |
| InlineWeeder({ | |
| this.enableUserAssertions = false, | |
| this.omitImplicitCasts = false, | |
| }); | |
| static InlineData computeInlineData( | |
| JsToElementMap elementMap, | |
| FunctionEntity function, { | |
| bool enableUserAssertions = false, | |
| bool omitImplicitCasts = false, | |
| }) { | |
| InlineWeeder visitor = InlineWeeder( | |
| enableUserAssertions: enableUserAssertions, | |
| omitImplicitCasts: omitImplicitCasts, | |
| ); | |
| final node = getFunctionNode(elementMap, function)!; | |
| if (function is ConstructorEntity) { | |
| visitor.data.isConstructor = true; | |
| MemberDefinition definition = elementMap.getMemberDefinition(function); | |
| ir.Node node = definition.node; | |
| if (node is ir.Constructor) { | |
| visitor.skipReductiveNodes(() { | |
| visitor.handleGenerativeConstructorFactory(node); | |
| }); | |
| return visitor.data; | |
| } | |
| } | |
| node.accept(visitor); | |
| return visitor.data; | |
| } | |
| void skipRegularNodes(void Function() f) { | |
| bool oldCountRegularNode = countRegularNode; | |
| countRegularNode = false; | |
| f(); | |
| countRegularNode = oldCountRegularNode; | |
| } | |
| void skipReductiveNodes(void Function() f) { | |
| bool oldCountReductiveNode = countReductiveNode; | |
| countReductiveNode = false; | |
| f(); | |
| countReductiveNode = oldCountReductiveNode; | |
| } | |
| void registerRegularNode([int count = 1]) { | |
| if (countRegularNode) { | |
| data.regularNodeCount += count; | |
| if (seenReturn) { | |
| data.codeAfterReturn = true; | |
| } | |
| } | |
| } | |
| void registerReductiveNode() { | |
| if (countReductiveNode) { | |
| data.reductiveNodeCount++; | |
| if (seenReturn) { | |
| data.codeAfterReturn = true; | |
| } | |
| } | |
| } | |
| void unregisterReductiveNode() { | |
| if (countReductiveNode) { | |
| data.reductiveNodeCount--; | |
| } | |
| } | |
| void visit(ir.Node? node) => node?.accept(this); | |
| void visitList(List<ir.Node> nodes) { | |
| for (ir.Node node in nodes) { | |
| visit(node); | |
| } | |
| } | |
| @override | |
| void defaultNode(ir.Node node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| node.visitChildren(this); | |
| } | |
| @override | |
| void visitConstantExpression(ir.ConstantExpression node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| ir.Constant constant = node.constant; | |
| // Avoid copying long strings into call site. | |
| if (constant is ir.StringConstant && isLongString(constant.value)) { | |
| data.hasLongString = true; | |
| } | |
| } | |
| @override | |
| void visitReturnStatement(ir.ReturnStatement node) { | |
| registerRegularNode(); | |
| node.visitChildren(this); | |
| seenReturn = true; | |
| } | |
| @override | |
| void visitThrow(ir.Throw node) { | |
| registerRegularNode(); | |
| data.hasThrow = true; | |
| node.visitChildren(this); | |
| } | |
| void _handleLoop(ir.Node node) { | |
| // It's actually not difficult to inline a method with a loop, but our | |
| // measurements show that it's currently better to not inline a method that | |
| // contains a loop. | |
| data.hasLoop = true; | |
| node.visitChildren(this); | |
| } | |
| @override | |
| void visitForStatement(ir.ForStatement node) { | |
| _handleLoop(node); | |
| } | |
| @override | |
| void visitForInStatement(ir.ForInStatement node) { | |
| _handleLoop(node); | |
| } | |
| @override | |
| void visitWhileStatement(ir.WhileStatement node) { | |
| _handleLoop(node); | |
| } | |
| @override | |
| void visitDoStatement(ir.DoStatement node) { | |
| _handleLoop(node); | |
| } | |
| @override | |
| void visitTryCatch(ir.TryCatch node) { | |
| data.hasTry = true; | |
| } | |
| @override | |
| void visitTryFinally(ir.TryFinally node) { | |
| data.hasTry = true; | |
| } | |
| @override | |
| void visitFunctionExpression(ir.FunctionExpression node) { | |
| registerRegularNode(); | |
| data.hasClosure = true; | |
| } | |
| @override | |
| void visitFunctionDeclaration(ir.FunctionDeclaration node) { | |
| registerRegularNode(); | |
| data.hasClosure = true; | |
| } | |
| @override | |
| void visitFunctionNode(ir.FunctionNode node) { | |
| if (node.asyncMarker != ir.AsyncMarker.Sync) { | |
| data.hasAsyncAwait = true; | |
| } | |
| // TODO(sra): Cost of parameter checking? | |
| skipReductiveNodes(() { | |
| visitList(node.typeParameters); | |
| visitList(node.positionalParameters); | |
| visitList(node.namedParameters); | |
| visit(node.returnType); | |
| }); | |
| visit(node.body); | |
| } | |
| @override | |
| void visitConditionalExpression(ir.ConditionalExpression node) { | |
| // Heuristic: In "parameter ? A : B" there is a high probability that | |
| // parameter is a constant. Assuming the parameter is constant, we can | |
| // compute a count that is bounded by the largest arm rather than the sum of | |
| // both arms. | |
| ir.Expression condition = node.condition; | |
| visit(condition); | |
| int commonPrefixCount = data.regularNodeCount; | |
| visit(node.then); | |
| int thenCount = data.regularNodeCount - commonPrefixCount; | |
| data.regularNodeCount = commonPrefixCount; | |
| visit(node.otherwise); | |
| int elseCount = data.regularNodeCount - commonPrefixCount; | |
| data.regularNodeCount = commonPrefixCount + thenCount + elseCount; | |
| if (condition is ir.VariableGet && | |
| condition.variable.parent is ir.FunctionNode) { | |
| data.regularNodeCount = | |
| commonPrefixCount + (thenCount > elseCount ? thenCount : elseCount); | |
| } | |
| // This is last so that [tooDifficult] is always updated. | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| skipRegularNodes(() => visit(node.staticType)); | |
| } | |
| @override | |
| void visitAssertInitializer(ir.AssertInitializer node) { | |
| if (!enableUserAssertions) return; | |
| node.visitChildren(this); | |
| } | |
| @override | |
| void visitAssertStatement(ir.AssertStatement node) { | |
| if (!enableUserAssertions) return; | |
| defaultNode(node); | |
| } | |
| void registerCall() { | |
| ++data.callCount; | |
| } | |
| @override | |
| void visitEmptyStatement(ir.EmptyStatement node) { | |
| registerRegularNode(); | |
| } | |
| @override | |
| void visitExpressionStatement(ir.ExpressionStatement node) { | |
| registerRegularNode(); | |
| node.visitChildren(this); | |
| } | |
| @override | |
| void visitLabeledStatement(ir.LabeledStatement node) { | |
| registerRegularNode(); | |
| data.hasLabel = true; | |
| node.visitChildren(this); | |
| } | |
| @override | |
| void visitSwitchStatement(ir.SwitchStatement node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| // Don't visit 'SwitchStatement.expressionType'. | |
| node.expression.accept(this); | |
| visitList(node.cases); | |
| } | |
| @override | |
| void visitBlock(ir.Block node) { | |
| registerRegularNode(); | |
| node.visitChildren(this); | |
| } | |
| /// Returns `true` if [value] is considered a long string for which copying | |
| /// should be avoided. | |
| bool isLongString(String value) => value.length > 14; | |
| @override | |
| void visitStringLiteral(ir.StringLiteral node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| // Avoid copying long strings into call site. | |
| if (isLongString(node.value)) { | |
| data.hasLongString = true; | |
| } | |
| } | |
| @override | |
| void visitInstanceGet(ir.InstanceGet node) { | |
| registerCall(); | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| skipReductiveNodes(() => visit(node.name)); | |
| visit(node.receiver); | |
| } | |
| @override | |
| void visitInstanceTearOff(ir.InstanceTearOff node) { | |
| registerCall(); | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| skipReductiveNodes(() => visit(node.name)); | |
| visit(node.receiver); | |
| } | |
| @override | |
| void visitDynamicGet(ir.DynamicGet node) { | |
| registerCall(); | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| skipReductiveNodes(() => visit(node.name)); | |
| visit(node.receiver); | |
| } | |
| @override | |
| void visitInstanceSet(ir.InstanceSet node) { | |
| registerCall(); | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| skipReductiveNodes(() => visit(node.name)); | |
| visit(node.receiver); | |
| visit(node.value); | |
| } | |
| @override | |
| void visitDynamicSet(ir.DynamicSet node) { | |
| registerCall(); | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| skipReductiveNodes(() => visit(node.name)); | |
| visit(node.receiver); | |
| visit(node.value); | |
| } | |
| @override | |
| void visitVariableGet(ir.VariableGet node) { | |
| if (discountParameters && node.variable.parent is ir.FunctionNode) return; | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| skipReductiveNodes(() => visit(node.promotedType)); | |
| } | |
| @override | |
| void visitThisExpression(ir.ThisExpression node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| } | |
| @override | |
| void visitStaticGet(ir.StaticGet node) { | |
| // Assume lazy-init static, loaded via a call: `$.$get$foo()`. | |
| registerCall(); | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| } | |
| @override | |
| void visitConstructorInvocation(ir.ConstructorInvocation node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| if (node.isConst) { | |
| // A const constructor call compiles to a constant pool reference. | |
| skipReductiveNodes(() => node.visitChildren(this)); | |
| } else { | |
| registerCall(); | |
| _processArguments(node.arguments, node.target.function); | |
| } | |
| } | |
| @override | |
| void visitStaticInvocation(ir.StaticInvocation node) { | |
| registerRegularNode(); | |
| if (node.isConst) { | |
| data.hasExternalConstantConstructorCall = true; | |
| skipReductiveNodes(() => node.visitChildren(this)); | |
| } else { | |
| registerCall(); | |
| registerReductiveNode(); | |
| _processArguments(node.arguments, node.target.function); | |
| } | |
| } | |
| @override | |
| void visitInstanceInvocation(ir.InstanceInvocation node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| registerCall(); | |
| visit(node.receiver); | |
| skipReductiveNodes(() => visit(node.name)); | |
| _processArguments(node.arguments, null); | |
| } | |
| @override | |
| void visitInstanceGetterInvocation(ir.InstanceGetterInvocation node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| registerCall(); | |
| visit(node.receiver); | |
| skipReductiveNodes(() => visit(node.name)); | |
| _processArguments(node.arguments, null); | |
| } | |
| @override | |
| void visitDynamicInvocation(ir.DynamicInvocation node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| registerCall(); | |
| visit(node.receiver); | |
| skipReductiveNodes(() => visit(node.name)); | |
| _processArguments(node.arguments, null); | |
| } | |
| @override | |
| void visitFunctionInvocation(ir.FunctionInvocation node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| registerCall(); | |
| visit(node.receiver); | |
| skipReductiveNodes(() => visit(node.name)); | |
| _processArguments(node.arguments, null); | |
| } | |
| @override | |
| void visitLocalFunctionInvocation(ir.LocalFunctionInvocation node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| registerCall(); | |
| _processArguments(node.arguments, null); | |
| // Account for the implicit access to the local variable: | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| } | |
| @override | |
| void visitEqualsNull(ir.EqualsNull node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| visit(node.expression); | |
| } | |
| @override | |
| void visitEqualsCall(ir.EqualsCall node) { | |
| registerRegularNode(); | |
| registerReductiveNode(); | |
| registerCall(); | |
| visit(node.left); | |
| visit(node.right); | |
| } | |
| void _processArguments(ir.Arguments arguments, ir.FunctionNode? target) { | |
| registerRegularNode(); | |
| if (arguments.types.isNotEmpty) { | |
| data.hasTypeArguments = true; | |
| skipReductiveNodes(() => visitList(arguments.types)); | |
| } | |
| int count = arguments.positional.length + arguments.named.length; | |
| data.argumentCounts.add(count); | |
| if (target != null) { | |
| // Disallow defaulted optional arguments since they will be passed | |
| // explicitly. | |
| if (target.positionalParameters.length + target.namedParameters.length > | |
| count) { | |
| data.hasArgumentDefaulting = true; | |
| } | |
| } | |
| visitList(arguments.positional); | |
| for (ir.NamedExpression expression in arguments.named) { | |
| registerRegularNode(); | |
| expression.value.accept(this); | |
| } | |
| } | |
| @override | |
| void visitAsExpression(ir.AsExpression node) { | |
| registerRegularNode(); | |
| visit(node.operand); | |
| skipReductiveNodes(() => visit(node.type)); | |
| if (!(node.isTypeError && omitImplicitCasts)) { | |
| data.hasCast = true; | |
| } | |
| } | |
| @override | |
| void defaultVariable(ir.Variable node) { | |
| registerRegularNode(); | |
| skipReductiveNodes(() { | |
| visitList(node.annotations); | |
| visit(node.type); | |
| }); | |
| visit(node.initializer); | |
| // A local variable is an alias for the initializer expression. | |
| if (node.initializer != null) { | |
| unregisterReductiveNode(); // discount one reference to the variable. | |
| } | |
| } | |
| @override | |
| void visitIfStatement(ir.IfStatement node) { | |
| registerRegularNode(); | |
| node.visitChildren(this); | |
| data.hasIf = true; | |
| } | |
| void handleGenerativeConstructorFactory(ir.Constructor node) { | |
| // Generative constructors are compiled to a factory constructor which | |
| // contains inlined all the initializations up the inheritance chain and | |
| // then call each of the constructor bodies down the inheritance chain. | |
| ir.Constructor? constructor = node; | |
| Set<ir.Field> initializedFields = {}; | |
| bool hasCallToSomeConstructorBody = false; | |
| inheritance_loop: | |
| while (constructor != null) { | |
| ir.Constructor? superConstructor; | |
| for (var initializer in constructor.initializers) { | |
| if (initializer is ir.RedirectingInitializer) { | |
| // Discount the size of the arguments by references that are | |
| // pass-through. | |
| // TODO(sra): Need to add size of defaulted arguments. | |
| var discountParametersOld = discountParameters; | |
| discountParameters = true; | |
| initializer.arguments.accept(this); | |
| discountParameters = discountParametersOld; | |
| constructor = initializer.target; | |
| continue inheritance_loop; | |
| } else if (initializer is ir.SuperInitializer) { | |
| superConstructor = initializer.target; | |
| // Discount the size of the arguments by references that are | |
| // pass-through. | |
| // TODO(sra): Need to add size of defaulted arguments. | |
| var discountParametersOld = discountParameters; | |
| discountParameters = true; | |
| initializer.arguments.accept(this); | |
| discountParameters = discountParametersOld; | |
| } else if (initializer is ir.FieldInitializer) { | |
| initializedFields.add(initializer.field); | |
| initializer.value.accept(this); | |
| } else if (initializer is ir.AssertInitializer) { | |
| if (enableUserAssertions) { | |
| initializer.accept(this); | |
| } | |
| } else { | |
| initializer.accept(this); | |
| } | |
| } | |
| _handleFields(constructor!.enclosingClass, initializedFields); | |
| // There will be a call to the constructor's body, which might be empty | |
| // and inlined away. | |
| var function = constructor.function; | |
| var body = function.body!; | |
| if (!isEmptyBody(body)) { | |
| // All of the parameters are passed to the body. | |
| int parameterCount = | |
| function.positionalParameters.length + | |
| function.namedParameters.length + | |
| function.typeParameters.length; | |
| hasCallToSomeConstructorBody = true; | |
| registerCall(); | |
| // A body call looks like "t.Body$(arguments);", i.e. an expression | |
| // statement with an instance member call, but the receiver is not | |
| // counted in the arguments. I'm guessing about 6 nodes for this. | |
| registerRegularNode( | |
| 6 + parameterCount * inliningNodesOutsideLoopArgFactor, | |
| ); | |
| // We can't inline a generative constructor factory when one of the | |
| // bodies rewrites the environment to put locals or parameters into a | |
| // box. The box is created in the generative constructor factory since | |
| // the box may be shared between closures in the initializer list and | |
| // closures in the constructor body. | |
| var bodyVisitor = InlineWeederBodyClosure(); | |
| body.accept(bodyVisitor); | |
| if (bodyVisitor.tooDifficult) { | |
| data.hasClosure = true; | |
| } | |
| } | |
| if (superConstructor != null) { | |
| // 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. | |
| ir.Supertype supertype = constructor.enclosingClass.supertype!; | |
| while (supertype.classNode != superConstructor.enclosingClass) { | |
| _handleFields(supertype.classNode, initializedFields); | |
| supertype = supertype.classNode.supertype!; | |
| } | |
| } | |
| constructor = superConstructor; | |
| } | |
| // In addition to the initializer expressions and body calls, there is an | |
| // allocator call. | |
| if (hasCallToSomeConstructorBody) { | |
| // A temporary is required so we have | |
| // | |
| // t=new ...; | |
| // ...; | |
| // use(t); | |
| // | |
| // I'm guessing it takes about 4 nodes to introduce the temporary and | |
| // assign it. | |
| registerRegularNode(4); // A temporary is required. | |
| } | |
| // The initial field values are passed to the allocator. | |
| registerRegularNode( | |
| initializedFields.length * inliningNodesOutsideLoopArgFactor, | |
| ); | |
| } | |
| void _handleFields(ir.Class cls, Set<ir.Field> initializedFields) { | |
| for (ir.Field field in cls.fields) { | |
| if (!field.isInstanceMember) continue; | |
| final initializer = field.initializer; | |
| if (initializer == null || | |
| initializer is ir.ConstantExpression && | |
| initializer.constant is ir.PrimitiveConstant || | |
| initializer is ir.BasicLiteral) { | |
| // Simple field initializers happen in the allocator, so do not | |
| // contribute to the size of the generative constructor factory. | |
| // TODO(sra): Use FieldInfo which tells us if the field is elided or | |
| // initialized in the allocator. | |
| continue; | |
| } | |
| if (!initializedFields.add(field)) continue; | |
| initializer.accept(this); | |
| } | |
| // If [cls] is a mixin application, include fields from mixed in class. | |
| if (cls.mixedInType != null) { | |
| _handleFields(cls.mixedInType!.classNode, initializedFields); | |
| } | |
| } | |
| bool isEmptyBody(ir.Statement body) { | |
| if (body is ir.EmptyStatement) return true; | |
| if (body is ir.Block) return body.statements.every(isEmptyBody); | |
| if (body is ir.AssertStatement && !enableUserAssertions) return true; | |
| return false; | |
| } | |
| } | |
| /// Visitor to detect environment-rewriting that prevents inlining | |
| /// (e.g. closures). | |
| class InlineWeederBodyClosure extends ir.VisitorDefault<void> | |
| with ir.VisitorVoidMixin { | |
| bool tooDifficult = false; | |
| InlineWeederBodyClosure(); | |
| @override | |
| void defaultNode(ir.Node node) { | |
| if (tooDifficult) return; | |
| node.visitChildren(this); | |
| } | |
| @override | |
| void visitFunctionExpression(ir.FunctionExpression node) { | |
| tooDifficult = true; | |
| } | |
| @override | |
| void visitFunctionDeclaration(ir.FunctionDeclaration node) { | |
| tooDifficult = true; | |
| } | |
| @override | |
| void visitFunctionNode(ir.FunctionNode node) { | |
| assert(false); | |
| if (node.asyncMarker != ir.AsyncMarker.Sync) { | |
| tooDifficult = true; | |
| return; | |
| } | |
| node.visitChildren(this); | |
| } | |
| } |