|  | // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | import 'dart:collection' show Queue; | 
|  |  | 
|  | import 'package:front_end/src/api_unstable/dart2js.dart' show Link; | 
|  |  | 
|  | import '../common.dart'; | 
|  | import '../common/elements.dart' show JCommonElements; | 
|  | import '../common/metrics.dart'; | 
|  | import '../common/names.dart'; | 
|  | import '../common/codegen.dart' show CodegenRegistry; | 
|  | import '../common/tasks.dart' show Measurer, CompilerTask; | 
|  | import '../constants/constant_system.dart' as constant_system; | 
|  | import '../constants/values.dart'; | 
|  | import '../elements/entities.dart'; | 
|  | import '../elements/jumps.dart'; | 
|  | import '../elements/types.dart'; | 
|  | import '../inferrer/abstract_value_domain.dart'; | 
|  | import '../io/source_information.dart'; | 
|  | import '../js/js.dart' as js; | 
|  | import '../js_backend/interceptor_data.dart'; | 
|  | import '../js_backend/codegen_inputs.dart' show CodegenInputs; | 
|  | import '../js_backend/checked_mode_helpers.dart'; | 
|  | import '../js_backend/native_data.dart'; | 
|  | import '../js_backend/namer.dart' show ModularNamer; | 
|  | import '../js_backend/runtime_types_codegen.dart'; | 
|  | import '../js_backend/runtime_types_new.dart' | 
|  | show RecipeEncoder, RecipeEncoding, indexTypeVariable; | 
|  | import '../js_backend/specialized_checks.dart'; | 
|  | import '../js_backend/type_reference.dart' show TypeReference; | 
|  | import '../js_emitter/js_emitter.dart' show ModularEmitter; | 
|  | import '../js_model/elements.dart' show JGeneratorBody; | 
|  | import '../js_model/js_world.dart' show JClosedWorld; | 
|  | import '../js_model/records.dart' show JRecordClass; | 
|  | import '../js_model/type_recipe.dart'; | 
|  | import '../native/behavior.dart'; | 
|  | import '../options.dart'; | 
|  | import '../tracer.dart' show Tracer; | 
|  | import '../universe/call_structure.dart' show CallStructure; | 
|  | import '../universe/resource_identifier.dart'; | 
|  | import '../universe/selector.dart' show Selector; | 
|  | import '../universe/use.dart' show ConstantUse, DynamicUse, StaticUse, TypeUse; | 
|  | import 'codegen_helpers.dart'; | 
|  | import 'nodes.dart'; | 
|  | import 'variable_allocator.dart'; | 
|  |  | 
|  | mixin CodegenPhase { | 
|  | String get name; | 
|  | void visitGraph(HGraph graph); | 
|  | } | 
|  |  | 
|  | class SsaCodeGeneratorTask extends CompilerTask { | 
|  | final CompilerOptions _options; | 
|  | final SourceInformationStrategy sourceInformationStrategy; | 
|  | _CodegenMetrics? _codegenMetrics; | 
|  | _CodegenMetrics get codegenMetrics => _codegenMetrics ??= _CodegenMetrics(); | 
|  |  | 
|  | SsaCodeGeneratorTask( | 
|  | Measurer super.measurer, this._options, this.sourceInformationStrategy); | 
|  |  | 
|  | @override | 
|  | String get name => 'SSA code generator'; | 
|  |  | 
|  | @override | 
|  | Metrics get metrics => _codegenMetrics ?? Metrics.none(); | 
|  |  | 
|  | js.Fun buildJavaScriptFunction(bool needsAsyncRewrite, FunctionEntity element, | 
|  | List<js.Parameter> parameters, js.Block body) { | 
|  | js.Fun finish(js.AsyncModifier asyncModifier) { | 
|  | return js.Fun(parameters, body, asyncModifier: asyncModifier) | 
|  | .withSourceInformation(sourceInformationStrategy | 
|  | .createBuilderForContext(element) | 
|  | .buildDeclaration(element)) as js.Fun; | 
|  | } | 
|  |  | 
|  | if (needsAsyncRewrite) { | 
|  | return finish(element.asyncMarker.isAsync | 
|  | ? (element.asyncMarker.isYielding | 
|  | ? js.AsyncModifier.asyncStar | 
|  | : js.AsyncModifier.async) | 
|  | : (element.asyncMarker.isYielding | 
|  | ? js.AsyncModifier.syncStar | 
|  | : js.AsyncModifier.sync)); | 
|  | } else { | 
|  | return finish(js.AsyncModifier.sync); | 
|  | } | 
|  | } | 
|  |  | 
|  | js.Expression generateCode( | 
|  | MemberEntity member, | 
|  | HGraph graph, | 
|  | CodegenInputs codegen, | 
|  | JClosedWorld closedWorld, | 
|  | CodegenRegistry registry, | 
|  | ModularNamer namer, | 
|  | ModularEmitter emitter) { | 
|  | js.Expression code; | 
|  | if (member is FieldEntity) { | 
|  | code = generateLazyInitializer( | 
|  | member, graph, codegen, closedWorld, registry, namer, emitter); | 
|  | } else if (member is FunctionEntity) { | 
|  | code = generateMethod( | 
|  | member, graph, codegen, closedWorld, registry, namer, emitter); | 
|  | } else { | 
|  | failedAt(member, 'Cannot generate JavaScript for $member'); | 
|  | } | 
|  | codegen.tracer.traceJavaScriptText('JavaScript', code.debugPrint); | 
|  | return code; | 
|  | } | 
|  |  | 
|  | js.Expression generateLazyInitializer( | 
|  | FieldEntity field, | 
|  | HGraph graph, | 
|  | CodegenInputs codegen, | 
|  | JClosedWorld closedWorld, | 
|  | CodegenRegistry registry, | 
|  | ModularNamer namer, | 
|  | ModularEmitter emitter) { | 
|  | return measure(() { | 
|  | SourceInformation? sourceInformation = sourceInformationStrategy | 
|  | .createBuilderForContext(field) | 
|  | .buildDeclaration(field); | 
|  | SsaCodeGenerator codeGenerator = SsaCodeGenerator( | 
|  | this, | 
|  | _options, | 
|  | codegenMetrics, | 
|  | emitter, | 
|  | codegen.rtiSubstitutions, | 
|  | codegen.rtiRecipeEncoder, | 
|  | namer, | 
|  | codegen.tracer, | 
|  | closedWorld, | 
|  | registry); | 
|  | codeGenerator.visitGraph(graph); | 
|  | codegen.tracer.traceGraph("codegen", graph); | 
|  | return js.Fun(codeGenerator.parameters, codeGenerator.body) | 
|  | .withSourceInformation(sourceInformation); | 
|  | }); | 
|  | } | 
|  |  | 
|  | js.Expression generateMethod( | 
|  | FunctionEntity method, | 
|  | HGraph graph, | 
|  | CodegenInputs codegen, | 
|  | JClosedWorld closedWorld, | 
|  | CodegenRegistry registry, | 
|  | ModularNamer namer, | 
|  | ModularEmitter emitter) { | 
|  | return measure(() { | 
|  | if (method.asyncMarker != AsyncMarker.SYNC) { | 
|  | registry.registerAsyncMarker(method.asyncMarker); | 
|  | } | 
|  | SsaCodeGenerator codeGenerator = SsaCodeGenerator( | 
|  | this, | 
|  | _options, | 
|  | codegenMetrics, | 
|  | emitter, | 
|  | codegen.rtiSubstitutions, | 
|  | codegen.rtiRecipeEncoder, | 
|  | namer, | 
|  | codegen.tracer, | 
|  | closedWorld, | 
|  | registry); | 
|  | codeGenerator.visitGraph(graph); | 
|  | codegen.tracer.traceGraph("codegen", graph); | 
|  | return buildJavaScriptFunction(graph.needsAsyncRewrite, method, | 
|  | codeGenerator.parameters, codeGenerator.body); | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | class _CodegenMetrics extends MetricsBase { | 
|  | int countHIf = 0; | 
|  | int countHIfConstant = 0; | 
|  | int countHIsTest = 0; | 
|  | int countHIsTestSimple = 0; | 
|  | int countHIsLateSentinel = 0; | 
|  | int countHGetLength = 0; | 
|  | int countHIndex = 0; | 
|  | int countHFieldGet = 0; | 
|  | int countSingleTargetInstanceCalls = 0; | 
|  | final countHInterceptor = CountMetric('count.HInterceptor'); | 
|  | final countHInterceptorGet = CountMetric('count.HInterceptor.getInterceptor'); | 
|  | final countHInterceptorOneshot = CountMetric('count.HInterceptor.oneShot'); | 
|  | final countHInterceptorConditionalConstant = | 
|  | CountMetric('count.HInterceptor.conditionalConstant'); | 
|  |  | 
|  | _CodegenMetrics(); | 
|  |  | 
|  | @override | 
|  | String get namespace => 'codegen'; | 
|  |  | 
|  | @override | 
|  | Iterable<Metric> get primary => []; | 
|  |  | 
|  | @override | 
|  | Iterable<Metric> get secondary => [ | 
|  | CountMetric('count.HIf')..add(countHIf), | 
|  | CountMetric('count.HIf.constant')..add(countHIfConstant), | 
|  | CountMetric('count.HIsTest')..add(countHIsTest), | 
|  | CountMetric('count.HIsTestSimple')..add(countHIsTestSimple), | 
|  | CountMetric('count.HIsLateSentinel')..add(countHIsLateSentinel), | 
|  | CountMetric('count.HGetLength')..add(countHGetLength), | 
|  | CountMetric('count.HIndex')..add(countHIndex), | 
|  | CountMetric('count.HFieldGet')..add(countHFieldGet), | 
|  | CountMetric('count.SingleTargetInstance') | 
|  | ..add(countSingleTargetInstanceCalls), | 
|  | countHInterceptor, | 
|  | countHInterceptorGet, | 
|  | countHInterceptorConditionalConstant, | 
|  | countHInterceptorOneshot | 
|  | ]; | 
|  | } | 
|  |  | 
|  | /// Returned by [_expressionType] to tell how code can be generated for | 
|  | /// a subgraph. | 
|  | enum _ExpressionCodegenType { | 
|  | /// The graph must be generated as a statement, which is always possible. | 
|  | statement, | 
|  |  | 
|  | /// The graph can be generated as an expression, or possibly several | 
|  | /// comma-separated expressions. | 
|  | expression, | 
|  |  | 
|  | /// The graph can be generated as an expression, and it only generates | 
|  | /// expressions of the form | 
|  | ///   variable = expression | 
|  | /// which are also valid as parts of a "var" declaration. | 
|  | declaration, | 
|  | } | 
|  |  | 
|  | class SsaCodeGenerator implements HVisitor<void>, HBlockInformationVisitor { | 
|  | /// Whether we are currently generating expressions instead of statements. | 
|  | /// This includes declarations, which are generated as expressions. | 
|  | bool isGeneratingExpression = false; | 
|  |  | 
|  | final CompilerTask _codegenTask; | 
|  | final CompilerOptions _options; | 
|  | final ModularEmitter _emitter; | 
|  | final RuntimeTypesSubstitutions _rtiSubstitutions; | 
|  | final RecipeEncoder _rtiRecipeEncoder; | 
|  | final ModularNamer _namer; | 
|  | final Tracer _tracer; | 
|  | final JClosedWorld _closedWorld; | 
|  | final CodegenRegistry _registry; | 
|  | final _CodegenMetrics _metrics; | 
|  |  | 
|  | final Set<HInstruction> generateAtUseSite = {}; | 
|  | final Set<HIf> controlFlowOperators = {}; | 
|  | final Set<JumpTarget> breakAction = {}; | 
|  | final Set<LabelDefinition> continueAction = {}; | 
|  | final Set<JumpTarget> implicitContinueAction = {}; | 
|  | final List<js.Parameter> parameters = []; | 
|  |  | 
|  | // Using a Block as the current container allows a statement tree to be | 
|  | // constructed that contains the block, and then have the block filled in | 
|  | // later. | 
|  | // TODO(sra): It would be cleaner if the [js.Block] could be unmodifiable but | 
|  | // that would require deferring the construction of the containing AST. | 
|  | js.Block currentContainer = js.Block.empty(); | 
|  | js.Block get body => currentContainer; | 
|  | List<js.Expression> expressionStack = []; | 
|  | List<js.Block> oldContainerStack = []; | 
|  |  | 
|  | /// Contains the names of the instructions, as well as the parallel | 
|  | /// copies to perform on block transitioning. | 
|  | late VariableNames variableNames; | 
|  |  | 
|  | /// `true` when we need to generate a `var` declaration at function entry, | 
|  | /// `false` if we can generate a `var` declaration at first assignment in the | 
|  | /// middle of the function. | 
|  | bool shouldGroupVarDeclarations = false; | 
|  |  | 
|  | /// While generating expressions, we can't insert variable declarations. | 
|  | /// Instead we declare them at the start of the function.  When minifying | 
|  | /// we do this most of the time, because it reduces the size unless there | 
|  | /// is only one variable. | 
|  | final Set<String> collectedVariableDeclarations = {}; | 
|  |  | 
|  | /// Set of variables and parameters that have already been declared. | 
|  | final Set<String> declaredLocals = {}; | 
|  |  | 
|  | late HGraph currentGraph; | 
|  |  | 
|  | // Records a block-information that is being handled specially. | 
|  | // Used to break bad recursion. | 
|  | HBlockInformation? currentBlockInformation; | 
|  | // The subgraph is used to delimit traversal for some constructions, e.g., | 
|  | // if branches. | 
|  | SubGraph? subGraph; | 
|  |  | 
|  | // Pending blocks than need to be visited as part of current subgraph. | 
|  | Queue<HBasicBlock>? blockQueue; | 
|  |  | 
|  | SsaCodeGenerator( | 
|  | this._codegenTask, | 
|  | this._options, | 
|  | this._metrics, | 
|  | this._emitter, | 
|  | this._rtiSubstitutions, | 
|  | this._rtiRecipeEncoder, | 
|  | this._namer, | 
|  | this._tracer, | 
|  | this._closedWorld, | 
|  | this._registry); | 
|  |  | 
|  | JCommonElements get _commonElements => _closedWorld.commonElements; | 
|  |  | 
|  | NativeData get _nativeData => _closedWorld.nativeData; | 
|  |  | 
|  | InterceptorData get _interceptorData => _closedWorld.interceptorData; | 
|  |  | 
|  | AbstractValueDomain get _abstractValueDomain => | 
|  | _closedWorld.abstractValueDomain; | 
|  |  | 
|  | bool isGenerateAtUseSite(HInstruction instruction) { | 
|  | return generateAtUseSite.contains(instruction); | 
|  | } | 
|  |  | 
|  | /// If the [instruction] is not `null` it will be used to attach the position | 
|  | /// to the [statement]. | 
|  | void pushStatement(js.Statement statement) { | 
|  | assert(expressionStack.isEmpty); | 
|  | currentContainer.statements.add(statement); | 
|  | } | 
|  |  | 
|  | void insertStatementAtStart(js.Statement statement) { | 
|  | currentContainer.statements.insert(0, statement); | 
|  | } | 
|  |  | 
|  | /// If the [instruction] is not `null` it will be used to attach the position | 
|  | /// to the [expression]. | 
|  | void pushExpressionAsStatement( | 
|  | js.Expression expression, SourceInformation? sourceInformation) { | 
|  | pushStatement(js.ExpressionStatement(expression) | 
|  | .withSourceInformation(sourceInformation)); | 
|  | } | 
|  |  | 
|  | /// If the [instruction] is not `null` it will be used to attach the position | 
|  | /// to the [expression]. | 
|  | void push(js.Expression expression) { | 
|  | expressionStack.add(expression); | 
|  | } | 
|  |  | 
|  | js.Expression pop() { | 
|  | return expressionStack.removeLast(); | 
|  | } | 
|  |  | 
|  | void preGenerateMethod(HGraph graph) { | 
|  | void runPhase(CodegenPhase phase, {bool traceGraph = true}) { | 
|  | _codegenTask.measureSubtask(phase.name, () => phase.visitGraph(graph)); | 
|  | if (traceGraph) { | 
|  | _tracer.traceGraph(phase.name, graph); | 
|  | } | 
|  | assert(graph.isValid(), 'Graph not valid after ${phase.name}'); | 
|  | } | 
|  |  | 
|  | // Remove trusted late checks first to uncover read-modify-write patterns in | 
|  | // instruction selection. | 
|  | runPhase(SsaTrustedLateCheckRemover(_abstractValueDomain)); | 
|  | runPhase(SsaInstructionSelection(_options, _closedWorld)); | 
|  | runPhase(SsaTypeKnownRemover()); | 
|  | runPhase(SsaTrustedPrimitiveCheckRemover(_options)); | 
|  | runPhase(SsaAssignmentChaining(_closedWorld)); | 
|  | runPhase(SsaInstructionMerger(_abstractValueDomain, generateAtUseSite)); | 
|  | runPhase(SsaConditionMerger(generateAtUseSite, controlFlowOperators)); | 
|  | runPhase(SsaPhiConditioning(generateAtUseSite, controlFlowOperators)); | 
|  | runPhase(SsaShareRegionConstants()); | 
|  |  | 
|  | SsaLiveIntervalBuilder intervalBuilder = | 
|  | SsaLiveIntervalBuilder(generateAtUseSite, controlFlowOperators); | 
|  | runPhase(intervalBuilder, traceGraph: false); | 
|  | SsaVariableAllocator allocator = SsaVariableAllocator( | 
|  | _namer, | 
|  | intervalBuilder.liveInstructions, | 
|  | intervalBuilder.liveIntervals, | 
|  | generateAtUseSite); | 
|  | runPhase(allocator, traceGraph: false); | 
|  | variableNames = allocator.names; | 
|  | shouldGroupVarDeclarations = allocator.names.numberOfVariables > 1; | 
|  | } | 
|  |  | 
|  | void handleDelayedVariableDeclarations(SourceInformation? sourceInformation) { | 
|  | // Create 'var' list at the start of function.  Move assignment statements | 
|  | // from the top of the body into the variable initializers. | 
|  | if (collectedVariableDeclarations.isEmpty) return; | 
|  |  | 
|  | List<js.VariableInitialization> declarations = []; | 
|  | List<js.Statement> statements = currentContainer.statements; | 
|  | int nextStatement = 0; | 
|  |  | 
|  | while (nextStatement < statements.length) { | 
|  | if (collectedVariableDeclarations.isEmpty) break; | 
|  | js.Statement statement = statements[nextStatement]; | 
|  | if (statement is js.ExpressionStatement) { | 
|  | js.Expression expression = statement.expression; | 
|  | if (expression is js.Assignment && !expression.isCompound) { | 
|  | js.Expression left = expression.leftHandSide; | 
|  | if (left is js.VariableReference) { | 
|  | String name = left.name; | 
|  | js.Expression value = expression.value; | 
|  | if (_safeInInitializer(value) && | 
|  | collectedVariableDeclarations.remove(name)) { | 
|  | var initialization = js.VariableInitialization( | 
|  | js.VariableDeclaration(name), value, | 
|  | sourceInformation: expression.sourceInformation); | 
|  | declarations.add(initialization); | 
|  | ++nextStatement; | 
|  | continue; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | List<js.VariableInitialization> uninitialized = []; | 
|  | for (String name in collectedVariableDeclarations) { | 
|  | uninitialized | 
|  | .add(js.VariableInitialization(js.VariableDeclaration(name), null)); | 
|  | } | 
|  | var declarationList = | 
|  | js.VariableDeclarationList(uninitialized + declarations) | 
|  | .withSourceInformation(sourceInformation); | 
|  | statements.replaceRange( | 
|  | 0, nextStatement, [js.ExpressionStatement(declarationList)]); | 
|  | } | 
|  |  | 
|  | // An expression is safe to be pulled into a 'var' initializer if it does not | 
|  | // contain assignments to locals. We don't generate assignments to locals | 
|  | // inside expressions. | 
|  | bool _safeInInitializer(js.Expression node) => true; | 
|  |  | 
|  | void visitGraph(HGraph graph) { | 
|  | preGenerateMethod(graph); | 
|  | currentGraph = graph; | 
|  | visitSubGraph(SubGraph(graph.entry, graph.exit)); | 
|  | handleDelayedVariableDeclarations(graph.sourceInformation); | 
|  | } | 
|  |  | 
|  | void visitSubGraph(SubGraph? newSubGraph) { | 
|  | final oldSubGraph = subGraph; | 
|  | final oldBlockQueue = blockQueue; | 
|  |  | 
|  | subGraph = newSubGraph; | 
|  | blockQueue = Queue(); | 
|  | enterSubGraph(subGraph!.start); | 
|  |  | 
|  | blockQueue = oldBlockQueue; | 
|  | subGraph = oldSubGraph; | 
|  | } | 
|  |  | 
|  | /// Check whether a sub-graph can be generated as an expression, or even | 
|  | /// as a declaration, or if it has to fall back to being generated as | 
|  | /// a statement. | 
|  | /// Expressions are anything that doesn't generate control flow constructs. | 
|  | /// Declarations must only generate assignments on the form "id = expression", | 
|  | /// and not, e.g., expressions where the value isn't assigned, or where it's | 
|  | /// assigned to something that's not a simple variable. | 
|  | _ExpressionCodegenType _expressionType(HExpressionInformation info) { | 
|  | // The only HExpressionInformation used as part of a HBlockInformation is | 
|  | // current HSubExpressionBlockInformation, so it's the only one reaching | 
|  | // here. If we start using the other HExpressionInformation types too, | 
|  | // this code should be generalized. | 
|  | assert(info is HSubExpressionBlockInformation); | 
|  | info as HSubExpressionBlockInformation; | 
|  | HSubExpressionBlockInformation expressionInfo = info; | 
|  | SubGraph limits = expressionInfo.subExpression!; | 
|  |  | 
|  | // Start assuming that we can generate declarations. If we find a | 
|  | // counter-example, we degrade our assumption to either expression or | 
|  | // statement, and in the latter case, we can return immediately since | 
|  | // it can't get any worse. E.g., a function call where the return value | 
|  | // isn't used can't be in a declaration. | 
|  | var result = _ExpressionCodegenType.declaration; | 
|  | HBasicBlock basicBlock = limits.start; | 
|  | do { | 
|  | HInstruction current = basicBlock.first!; | 
|  | while (current != basicBlock.last) { | 
|  | // E.g, bounds check. | 
|  | if (current.isControlFlow()) { | 
|  | return _ExpressionCodegenType.statement; | 
|  | } | 
|  | // HFieldSet generates code on the form "x.y = ...", which isn't valid | 
|  | // in a declaration. | 
|  | if (current.usedBy.isEmpty || current is HFieldSet) { | 
|  | result = _ExpressionCodegenType.expression; | 
|  | } | 
|  | current = current.next!; | 
|  | } | 
|  | if (current is HGoto) { | 
|  | basicBlock = basicBlock.successors[0]; | 
|  | } else if (current is HConditionalBranch) { | 
|  | if (generateAtUseSite.contains(current)) { | 
|  | // Short-circuit control flow operator trickery. | 
|  | // Check the second half, which will continue into the join. | 
|  | // (The first half is [inputs[0]], the second half is [successors[0]], | 
|  | // and [successors[1]] is the join-block). | 
|  | basicBlock = basicBlock.successors[0]; | 
|  | } else { | 
|  | // We allow an expression to end on an HIf (a condition expression). | 
|  | return identical(basicBlock, limits.end) | 
|  | ? result | 
|  | : _ExpressionCodegenType.statement; | 
|  | } | 
|  | } else { | 
|  | // Expression-incompatible control flow. | 
|  | return _ExpressionCodegenType.statement; | 
|  | } | 
|  | } while (limits.contains(basicBlock)); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | bool isJSExpression(HExpressionInformation info) { | 
|  | return !identical(_expressionType(info), _ExpressionCodegenType.statement); | 
|  | } | 
|  |  | 
|  | bool isJSCondition(HExpressionInformation? info) { | 
|  | // Currently we only handle sub-expression graphs. | 
|  | info as HSubExpressionBlockInformation; | 
|  |  | 
|  | SubExpression? limits = info.subExpression; | 
|  | return !identical( | 
|  | _expressionType(info), _ExpressionCodegenType.statement) && | 
|  | (limits!.end.last is HConditionalBranch); | 
|  | } | 
|  |  | 
|  | /// Generate statements from block information. | 
|  | /// If the block information contains expressions, generate only | 
|  | /// assignments, and if it ends in a conditional branch, don't generate | 
|  | /// the condition. | 
|  | void generateStatements(HBlockInformation? block) { | 
|  | if (block is HStatementInformation) { | 
|  | block.accept(this); | 
|  | } else if (block is HSubExpressionBlockInformation) { | 
|  | visitSubGraph(block.subExpression); | 
|  | } else { | 
|  | failedAt(CURRENT_ELEMENT_SPANNABLE, 'Unexpected block: $block'); | 
|  | } | 
|  | } | 
|  |  | 
|  | js.Block generateStatementsInNewBlock(HBlockInformation? block) { | 
|  | js.Block result = js.Block.empty(); | 
|  | js.Block oldContainer = currentContainer; | 
|  | currentContainer = result; | 
|  | generateStatements(block); | 
|  | currentContainer = oldContainer; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /// If the [block] only contains one statement returns that statement. If the | 
|  | /// that statement itself is a block, recursively calls this method. | 
|  | /// | 
|  | /// If the block is empty, returns a new instance of [js.NOP]. | 
|  | js.Statement unwrapStatement(js.Block block) { | 
|  | int len = block.statements.length; | 
|  | if (len == 0) return js.EmptyStatement(); | 
|  | if (len == 1) { | 
|  | js.Statement result = block.statements[0]; | 
|  | if (result is js.Block) return unwrapStatement(result); | 
|  | return result; | 
|  | } | 
|  | return block; | 
|  | } | 
|  |  | 
|  | /// Generate expressions from block information. | 
|  | js.Expression? generateExpression(HExpressionInformation expression) { | 
|  | // Currently we only handle sub-expression graphs. | 
|  | expression as HSubExpressionBlockInformation; | 
|  |  | 
|  | bool oldIsGeneratingExpression = isGeneratingExpression; | 
|  | isGeneratingExpression = true; | 
|  | List<js.Expression> oldExpressionStack = expressionStack; | 
|  | List<js.Expression> sequenceElements = []; | 
|  | expressionStack = sequenceElements; | 
|  | HSubExpressionBlockInformation expressionSubGraph = expression; | 
|  | visitSubGraph(expressionSubGraph.subExpression); | 
|  | expressionStack = oldExpressionStack; | 
|  | isGeneratingExpression = oldIsGeneratingExpression; | 
|  | if (sequenceElements.isEmpty) { | 
|  | // Happens when the initializer, condition or update of a loop is empty. | 
|  | return null; | 
|  | } else if (sequenceElements.length == 1) { | 
|  | return sequenceElements[0]; | 
|  | } else { | 
|  | js.Expression result = sequenceElements.removeLast(); | 
|  | while (sequenceElements.isNotEmpty) { | 
|  | result = js.Binary(',', sequenceElements.removeLast(), result); | 
|  | } | 
|  | return result; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Only visits the arguments starting at inputs[HInvoke.ARGUMENTS_OFFSET]. | 
|  | List<js.Expression> visitArguments(List<HInstruction> inputs, | 
|  | {int start = HInvoke.ARGUMENTS_OFFSET}) { | 
|  | assert(inputs.length >= start); | 
|  | return List.generate(inputs.length - start, (i) { | 
|  | use(inputs[i + start]); | 
|  | return pop(); | 
|  | }, growable: false); | 
|  | } | 
|  |  | 
|  | bool isVariableDeclared(String variableName) { | 
|  | return declaredLocals.contains(variableName) || | 
|  | collectedVariableDeclarations.contains(variableName); | 
|  | } | 
|  |  | 
|  | js.Expression generateExpressionAssignment(String variableName, | 
|  | js.Expression value, SourceInformation? sourceInformation) { | 
|  | // TODO(johnniwinther): Introduce a DeferredVariableUse to handle this | 
|  | // in the SSA codegen or let the JS printer handle it fully and remove it | 
|  | // here. | 
|  | if (value is js.Binary) { | 
|  | js.Binary binary = value; | 
|  | String op = binary.op; | 
|  | if (op == '+' || | 
|  | op == '-' || | 
|  | op == '/' || | 
|  | op == '*' || | 
|  | op == '%' || | 
|  | op == '^' || | 
|  | op == '&' || | 
|  | op == '|') { | 
|  | js.Expression left = binary.left; | 
|  | if (left is js.VariableUse && left.name == variableName) { | 
|  | // We know now, that we can shorten x = x + y into x += y. | 
|  | // Also check for the shortcut where y equals 1: x++ and x--. | 
|  | js.Expression right = binary.right; | 
|  | if ((op == '+' || op == '-') && | 
|  | right is js.LiteralNumber && | 
|  | right.value == "1") { | 
|  | return js.Prefix(op == '+' ? '++' : '--', left); | 
|  | } | 
|  | return js.Assignment.compound(binary.left, op, binary.right); | 
|  | } | 
|  | } | 
|  | } | 
|  | return js.Assignment(js.VariableUse(variableName), value) | 
|  | .withSourceInformation(value.sourceInformation ?? sourceInformation); | 
|  | } | 
|  |  | 
|  | void assignVariable(String variableName, js.Expression value, | 
|  | SourceInformation? sourceInformation) { | 
|  | if (isGeneratingExpression) { | 
|  | // If we are in an expression then we can't declare the variable here. | 
|  | // We have no choice, but to use it and then declare it separately. | 
|  | if (!isVariableDeclared(variableName)) { | 
|  | collectedVariableDeclarations.add(variableName); | 
|  | } | 
|  | push( | 
|  | generateExpressionAssignment(variableName, value, sourceInformation)); | 
|  | // Otherwise if we are trying to declare inline and we are in a statement | 
|  | // then we declare (unless it was already declared). | 
|  | } else if (!shouldGroupVarDeclarations && | 
|  | !declaredLocals.contains(variableName)) { | 
|  | // It may be necessary to remove it from the ones to be declared later. | 
|  | collectedVariableDeclarations.remove(variableName); | 
|  | declaredLocals.add(variableName); | 
|  | js.VariableDeclaration decl = js.VariableDeclaration(variableName); | 
|  | js.VariableInitialization initialization = | 
|  | js.VariableInitialization(decl, value); | 
|  |  | 
|  | pushExpressionAsStatement( | 
|  | js.VariableDeclarationList([initialization]), sourceInformation); | 
|  | } else { | 
|  | // Otherwise we are just going to use it.  If we have not already declared | 
|  | // it then we make sure we will declare it later. | 
|  | if (!declaredLocals.contains(variableName)) { | 
|  | collectedVariableDeclarations.add(variableName); | 
|  | } | 
|  | pushExpressionAsStatement( | 
|  | generateExpressionAssignment(variableName, value, sourceInformation), | 
|  | sourceInformation); | 
|  | } | 
|  | } | 
|  |  | 
|  | void define(HInstruction instruction) { | 
|  | // For simple type checks like i = intTypeCheck(i), we don't have to | 
|  | // emit an assignment, because the intTypeCheck just returns its | 
|  | // argument. | 
|  | bool needsAssignment = true; | 
|  | if (instruction is HOutputConstrainedToAnInput) { | 
|  | if (instruction is HPrimitiveCheck || | 
|  | instruction is HAsCheck || | 
|  | instruction is HAsCheckSimple || | 
|  | instruction is HBoolConversion || | 
|  | instruction is HNullCheck || | 
|  | instruction is HLateReadCheck || | 
|  | instruction is HArrayFlagsSet) { | 
|  | String? inputName = variableNames.getName(instruction.constrainedInput); | 
|  | if (variableNames.getName(instruction) == inputName) { | 
|  | needsAssignment = false; | 
|  | } | 
|  | } | 
|  | } | 
|  | if (instruction is HLocalValue) { | 
|  | needsAssignment = false; | 
|  | } | 
|  |  | 
|  | if (needsAssignment && | 
|  | !instruction.isControlFlow() && | 
|  | variableNames.hasName(instruction)) { | 
|  | visitExpression(instruction); | 
|  | assignVariable(variableNames.getName(instruction)!, pop(), | 
|  | instruction.sourceInformation); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (isGeneratingExpression) { | 
|  | visitExpression(instruction); | 
|  | } else { | 
|  | visitStatement(instruction); | 
|  | } | 
|  | } | 
|  |  | 
|  | HInstruction skipGenerateAtUseCheckInputs(HOutputConstrainedToAnInput check) { | 
|  | HInstruction input = check.constrainedInput; | 
|  | if (input is HOutputConstrainedToAnInput && isGenerateAtUseSite(input)) { | 
|  | return skipGenerateAtUseCheckInputs(input); | 
|  | } | 
|  | return input; | 
|  | } | 
|  |  | 
|  | void use(HInstruction argument) { | 
|  | if (isGenerateAtUseSite(argument)) { | 
|  | visitExpression(argument); | 
|  | } else if (argument is HOutputConstrainedToAnInput && | 
|  | !variableNames.hasName(argument)) { | 
|  | // We have a check that is not generate-at-use and has no name, yet is a | 
|  | // subexpression (we are in 'use'). This happens when we have a chain of | 
|  | // checks on an available unnamed value (e.g. a constant). The checks are | 
|  | // generated as a statement, so we need to skip the generate-at-use check | 
|  | // tree to find the underlying value. | 
|  |  | 
|  | // TODO(sra): We should ensure that this invariant holds: "every | 
|  | // instruction has a name or is generate-at-use". This would require | 
|  | // naming the input or output of the chain-of-checks. | 
|  |  | 
|  | // This can only happen if the checked node also does not have a name. | 
|  | assert(!variableNames.hasName(argument.constrainedInput)); | 
|  |  | 
|  | use(skipGenerateAtUseCheckInputs(argument)); | 
|  | } else { | 
|  | assert(variableNames.hasName(argument)); | 
|  | push(js.VariableUse(variableNames.getName(argument)!)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void visit(HInstruction node) { | 
|  | node.accept(this); | 
|  | } | 
|  |  | 
|  | void visitExpression(HInstruction node) { | 
|  | bool oldIsGeneratingExpression = isGeneratingExpression; | 
|  | isGeneratingExpression = true; | 
|  | visit(node); | 
|  | isGeneratingExpression = oldIsGeneratingExpression; | 
|  | } | 
|  |  | 
|  | void visitStatement(HInstruction node) { | 
|  | assert(!isGeneratingExpression); | 
|  | visit(node); | 
|  | if (!expressionStack.isEmpty) { | 
|  | assert(expressionStack.length == 1); | 
|  | js.Expression expression = pop(); | 
|  | pushExpressionAsStatement(expression, node.sourceInformation); | 
|  | } | 
|  | } | 
|  |  | 
|  | void continueAsBreak(LabelDefinition target) { | 
|  | pushStatement(js.Break(_namer.continueLabelName(target))); | 
|  | } | 
|  |  | 
|  | void implicitContinueAsBreak(JumpTarget target) { | 
|  | pushStatement(js.Break(_namer.implicitContinueLabelName(target))); | 
|  | } | 
|  |  | 
|  | void implicitBreakWithLabel(JumpTarget target) { | 
|  | pushStatement(js.Break(_namer.implicitBreakLabelName(target))); | 
|  | } | 
|  |  | 
|  | js.Statement wrapIntoLabels( | 
|  | js.Statement result, List<LabelDefinition> labels) { | 
|  | for (LabelDefinition label in labels) { | 
|  | if (label.isTarget) { | 
|  | String breakLabelString = _namer.breakLabelName(label); | 
|  | result = js.LabeledStatement(breakLabelString, result); | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // The regular [visitIf] method implements the needed logic. | 
|  | @override | 
|  | bool visitIfInfo(HIfBlockInformation info) => false; | 
|  |  | 
|  | @override | 
|  | bool visitSwitchInfo(HSwitchBlockInformation info) { | 
|  | bool isExpression = isJSExpression(info.expression); | 
|  | if (!isExpression) { | 
|  | generateStatements(info.expression); | 
|  | } | 
|  |  | 
|  | if (isExpression) { | 
|  | push(generateExpression(info.expression)!); | 
|  | } else { | 
|  | use(info.expression.conditionExpression!); | 
|  | } | 
|  | js.Expression key = pop(); | 
|  | bool handledDefault = false; | 
|  | List<js.SwitchClause> cases = []; | 
|  | HSwitch switchInstruction = info.expression.end.last as HSwitch; | 
|  | List<HInstruction> inputs = switchInstruction.inputs; | 
|  | List<HBasicBlock> successors = switchInstruction.block!.successors; | 
|  |  | 
|  | js.Block oldContainer = currentContainer; | 
|  | for (int inputIndex = 1, statementIndex = 0; | 
|  | inputIndex < inputs.length; | 
|  | statementIndex++) { | 
|  | HBasicBlock successor = successors[inputIndex - 1]; | 
|  | // If liveness analysis has figured out that this case is dead, | 
|  | // omit the code for it. | 
|  | if (successor.isLive) { | 
|  | do { | 
|  | final input = inputs[inputIndex]; | 
|  | visit(input); | 
|  | currentContainer = js.Block.empty(); | 
|  | cases.add(js.Case(pop(), currentContainer)); | 
|  | if (input is HConstant && input.constant is NullConstantValue) { | 
|  | // JavaScript case expressions match on `===`, which means that the | 
|  | // just emitted `case null:` will not catch `undefined`. | 
|  | // Add `case void 0:` to catch `undefined`. | 
|  | currentContainer = js.Block.empty(); | 
|  | cases.add( | 
|  | js.Case(js.Prefix('void', js.number(0)), currentContainer)); | 
|  | } | 
|  | inputIndex++; | 
|  | } while ((successors[inputIndex - 1] == successor) && | 
|  | (inputIndex < inputs.length)); | 
|  |  | 
|  | // If this is the last statement, then these cases also belong to the | 
|  | // default block. | 
|  | if (statementIndex == info.statements.length - 1) { | 
|  | currentContainer = js.Block.empty(); | 
|  | cases.add(js.Default(currentContainer)); | 
|  | handledDefault = true; | 
|  | } | 
|  |  | 
|  | generateStatements(info.statements[statementIndex]); | 
|  | } else { | 
|  | // Skip all the case statements that belong to this | 
|  | // block. | 
|  | while ((successors[inputIndex - 1] == successor) && | 
|  | (inputIndex < inputs.length)) { | 
|  | ++inputIndex; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // If the default case is dead, we omit it. Likewise, if it is an | 
|  | // empty block, we omit it, too. | 
|  | if (info.statements.last.start.isLive && !handledDefault) { | 
|  | currentContainer = js.Block.empty(); | 
|  | generateStatements(info.statements.last); | 
|  | if (currentContainer.statements.isNotEmpty) { | 
|  | cases.add(js.Default(currentContainer)); | 
|  | } | 
|  | } | 
|  |  | 
|  | currentContainer = oldContainer; | 
|  |  | 
|  | js.Statement result = | 
|  | js.Switch(key, cases).withSourceInformation(info.sourceInformation); | 
|  | pushStatement(wrapIntoLabels(result, info.labels)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @override | 
|  | bool visitSequenceInfo(HStatementSequenceInformation info) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @override | 
|  | bool visitSubGraphInfo(HSubGraphBlockInformation info) { | 
|  | visitSubGraph(info.subGraph); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @override | 
|  | bool visitSubExpressionInfo(HSubExpressionBlockInformation info) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @override | 
|  | bool visitTryInfo(HTryBlockInformation info) { | 
|  | js.Block body = generateStatementsInNewBlock(info.body); | 
|  | js.Catch? catchPart = null; | 
|  | js.Block? finallyPart = null; | 
|  | if (info.catchBlock != null) { | 
|  | void register(ClassEntity classElement) { | 
|  | _registry | 
|  | // ignore:deprecated_member_use_from_same_package | 
|  | .registerInstantiatedClass(classElement); | 
|  | } | 
|  |  | 
|  | register(_commonElements.jsPlainJavaScriptObjectClass); | 
|  | register(_commonElements.jsUnknownJavaScriptObjectClass); | 
|  |  | 
|  | HLocalValue? exception = info.catchVariable; | 
|  | String name = variableNames.getName(exception)!; | 
|  | js.VariableDeclaration decl = js.VariableDeclaration(name); | 
|  | js.Block catchBlock = generateStatementsInNewBlock(info.catchBlock); | 
|  | catchPart = js.Catch(decl, catchBlock); | 
|  | } | 
|  | if (info.finallyBlock != null) { | 
|  | finallyPart = generateStatementsInNewBlock(info.finallyBlock); | 
|  | } | 
|  | pushStatement(js.Try(body, catchPart, finallyPart)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void visitBodyIgnoreLabels(HLoopBlockInformation info) { | 
|  | if (info.body!.start.isLabeledBlock()) { | 
|  | HBlockInformation? oldInfo = currentBlockInformation; | 
|  | currentBlockInformation = info.body!.start.blockFlow!.body; | 
|  | generateStatements(info.body); | 
|  | currentBlockInformation = oldInfo; | 
|  | } else { | 
|  | generateStatements(info.body); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | bool visitLoopInfo(HLoopBlockInformation info) { | 
|  | HExpressionInformation? condition = info.condition; | 
|  | bool isConditionExpression = isJSCondition(condition); | 
|  |  | 
|  | late js.Loop loop; | 
|  |  | 
|  | switch (info.kind) { | 
|  | // Treat all three "test-first" loops the same way. | 
|  | case LoopBlockInformationKind.forLoop: | 
|  | case LoopBlockInformationKind.whileLoop: | 
|  | case LoopBlockInformationKind.forInLoop: | 
|  | case LoopBlockInformationKind.switchContinueLoop: | 
|  | HExpressionInformation? initialization = info.initializer; | 
|  | var initializationType = _ExpressionCodegenType.statement; | 
|  | if (initialization != null) { | 
|  | initializationType = _expressionType(initialization); | 
|  | if (initializationType == _ExpressionCodegenType.statement) { | 
|  | generateStatements(initialization); | 
|  | initialization = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | // We inserted a basic block to avoid critical edges. This block is | 
|  | // part of the LoopBlockInformation and must therefore be handled here. | 
|  | js.Block oldContainer = currentContainer; | 
|  | js.Block avoidContainer = js.Block.empty(); | 
|  | currentContainer = avoidContainer; | 
|  | assignPhisOfSuccessors(condition!.end.successors.last); | 
|  | bool hasPhiUpdates = !avoidContainer.statements.isEmpty; | 
|  | currentContainer = oldContainer; | 
|  |  | 
|  | if (isConditionExpression && | 
|  | !hasPhiUpdates && | 
|  | info.updates != null && | 
|  | isJSExpression(info.updates!)) { | 
|  | // If we have an updates graph, and it's expressible as an | 
|  | // expression, generate a for-loop. | 
|  | js.Expression? jsInitialization = null; | 
|  | if (initialization != null) { | 
|  | int delayedVariablesCount = collectedVariableDeclarations.length; | 
|  | jsInitialization = generateExpression(initialization); | 
|  | if (!shouldGroupVarDeclarations && | 
|  | delayedVariablesCount < collectedVariableDeclarations.length) { | 
|  | // We just added a new delayed variable-declaration. See if we can | 
|  | // put in a 'var' in front of the initialization to make it go | 
|  | // away. We walk the 'tree' of comma-operators to find the | 
|  | // expressions and see if they are all assignments that can be | 
|  | // converted into declarations. | 
|  |  | 
|  | List<js.Assignment>? assignments; | 
|  |  | 
|  | bool allSimpleAssignments(js.Expression expression) { | 
|  | if (expression is js.Assignment) { | 
|  | js.Assignment assignment = expression; | 
|  | if (assignment.leftHandSide is js.VariableUse && | 
|  | !assignment.isCompound) { | 
|  | (assignments ??= []).add(expression); | 
|  | return true; | 
|  | } | 
|  | } else if (expression is js.Binary && | 
|  | expression.isCommaOperator) { | 
|  | return allSimpleAssignments(expression.left) && | 
|  | allSimpleAssignments(expression.right); | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (jsInitialization != null && | 
|  | allSimpleAssignments(jsInitialization)) { | 
|  | List<js.VariableInitialization> inits = []; | 
|  | for (js.Assignment assignment in assignments!) { | 
|  | final id = (assignment.leftHandSide as js.VariableUse).name; | 
|  | final declaration = js.VariableDeclaration(id); | 
|  | inits.add( | 
|  | js.VariableInitialization(declaration, assignment.value)); | 
|  | collectedVariableDeclarations.remove(id); | 
|  | declaredLocals.add(id); | 
|  | } | 
|  | jsInitialization = js.VariableDeclarationList(inits); | 
|  | } | 
|  | } | 
|  | } | 
|  | js.Expression? jsCondition = generateExpression(condition); | 
|  | js.Expression? jsUpdates = generateExpression(info.updates!); | 
|  | // The body might be labeled. Ignore this when recursing on the | 
|  | // subgraph. | 
|  | // TODO(lrn): Remove this extra labeling when handling all loops | 
|  | // using subgraphs. | 
|  | oldContainer = currentContainer; | 
|  | js.Block body = js.Block.empty(); | 
|  | currentContainer = body; | 
|  | visitBodyIgnoreLabels(info); | 
|  | currentContainer = oldContainer; | 
|  | loop = js.For( | 
|  | jsInitialization, jsCondition, jsUpdates, unwrapStatement(body), | 
|  | sourceInformation: info.sourceInformation); | 
|  | } else { | 
|  | // We have either no update graph, or it's too complex to | 
|  | // put in an expression. | 
|  | if (initialization != null) { | 
|  | generateStatements(initialization); | 
|  | } | 
|  | js.Expression? jsCondition; | 
|  | js.Block oldContainer = currentContainer; | 
|  | js.Block body = js.Block.empty(); | 
|  | if (isConditionExpression && !hasPhiUpdates) { | 
|  | jsCondition = generateExpression(condition); | 
|  | currentContainer = body; | 
|  | } else { | 
|  | jsCondition = newLiteralBool(true, info.sourceInformation); | 
|  | currentContainer = body; | 
|  | generateStatements(condition); | 
|  | use(condition.conditionExpression!); | 
|  | js.Expression ifTest = js.Prefix("!", pop()); | 
|  | js.Statement jsBreak = js.Break(null); | 
|  | js.Statement exitLoop; | 
|  | if (avoidContainer.statements.isEmpty) { | 
|  | exitLoop = jsBreak; | 
|  | } else { | 
|  | avoidContainer.statements.add(jsBreak); | 
|  | exitLoop = avoidContainer; | 
|  | } | 
|  | pushStatement(js.If.noElse(ifTest, exitLoop)); | 
|  | } | 
|  | if (info.updates != null) { | 
|  | wrapLoopBodyForContinue(info); | 
|  | generateStatements(info.updates); | 
|  | } else { | 
|  | visitBodyIgnoreLabels(info); | 
|  | } | 
|  | currentContainer = oldContainer; | 
|  | loop = js.While(jsCondition!, unwrapStatement(body), | 
|  | sourceInformation: info.sourceInformation); | 
|  | } | 
|  | break; | 
|  | case LoopBlockInformationKind.doWhileLoop: | 
|  | if (info.initializer != null) { | 
|  | generateStatements(info.initializer); | 
|  | } | 
|  | // We inserted a basic block to avoid critical edges. This block is | 
|  | // part of the LoopBlockInformation and must therefore be handled here. | 
|  | js.Block oldContainer = currentContainer; | 
|  | js.Block exitAvoidContainer = js.Block.empty(); | 
|  | currentContainer = exitAvoidContainer; | 
|  | assignPhisOfSuccessors(condition!.end.successors.last); | 
|  | bool hasExitPhiUpdates = !exitAvoidContainer.statements.isEmpty; | 
|  | currentContainer = oldContainer; | 
|  |  | 
|  | oldContainer = currentContainer; | 
|  | js.Block body = js.Block.empty(); | 
|  | // If there are phi copies in the block that jumps to the | 
|  | // loop entry, we must emit the condition like this: | 
|  | // do { | 
|  | //   body; | 
|  | //   if (condition) { | 
|  | //     phi updates; | 
|  | //     continue; | 
|  | //   } else { | 
|  | //     break; | 
|  | //   } | 
|  | // } while (true); | 
|  | HBasicBlock avoidEdge = info.end.successors[0]; | 
|  | js.Block updateBody = js.Block.empty(); | 
|  | currentContainer = updateBody; | 
|  | assignPhisOfSuccessors(avoidEdge); | 
|  | bool hasPhiUpdates = !updateBody.statements.isEmpty; | 
|  | currentContainer = body; | 
|  | visitBodyIgnoreLabels(info); | 
|  | if (info.updates != null) { | 
|  | generateStatements(info.updates); | 
|  | } | 
|  | js.Expression? jsCondition; | 
|  | if (isConditionExpression) { | 
|  | jsCondition = generateExpression(condition); | 
|  | } else { | 
|  | generateStatements(condition); | 
|  | use(condition.conditionExpression!); | 
|  | jsCondition = pop(); | 
|  | } | 
|  | if (jsCondition == null) { | 
|  | // If the condition is dead code, we turn the do-while into | 
|  | // a simpler while because we will never reach the condition | 
|  | // at the end of the loop anyway. | 
|  | loop = js.While(newLiteralBool(true, info.sourceInformation), | 
|  | unwrapStatement(body), | 
|  | sourceInformation: info.sourceInformation); | 
|  | } else { | 
|  | if (hasPhiUpdates || hasExitPhiUpdates) { | 
|  | updateBody.statements.add(js.Continue(null)); | 
|  | js.Statement jsBreak = js.Break(null); | 
|  | js.Statement exitLoop; | 
|  | if (exitAvoidContainer.statements.isEmpty) { | 
|  | exitLoop = jsBreak; | 
|  | } else { | 
|  | exitAvoidContainer.statements.add(jsBreak); | 
|  | exitLoop = exitAvoidContainer; | 
|  | } | 
|  | body.statements.add(js.If(jsCondition, updateBody, exitLoop)); | 
|  | jsCondition = newLiteralBool(true, info.sourceInformation); | 
|  | } | 
|  | loop = js.Do(unwrapStatement(body), jsCondition, | 
|  | sourceInformation: info.sourceInformation); | 
|  | } | 
|  | currentContainer = oldContainer; | 
|  | break; | 
|  | case LoopBlockInformationKind.notALoop: | 
|  | failedAt(condition!.conditionExpression!, | 
|  | 'Unexpected loop kind: ${info.kind}.'); | 
|  | } | 
|  | js.Statement result = loop; | 
|  | if (info.kind == LoopBlockInformationKind.switchContinueLoop) { | 
|  | String continueLabelString = | 
|  | _namer.implicitContinueLabelName(info.target!); | 
|  | result = js.LabeledStatement(continueLabelString, result); | 
|  | } | 
|  | pushStatement(wrapIntoLabels(result, info.labels)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @override | 
|  | bool visitLabeledBlockInfo(HLabeledBlockInformation labeledBlockInfo) { | 
|  | Link<Entity> continueOverrides = const Link<Entity>(); | 
|  |  | 
|  | js.Block oldContainer = currentContainer; | 
|  | js.Block body = js.Block.empty(); | 
|  | js.Statement result = body; | 
|  |  | 
|  | currentContainer = body; | 
|  |  | 
|  | // If [labeledBlockInfo.isContinue], the block is an artificial | 
|  | // block around the body of a loop with an update block, so that | 
|  | // continues of the loop can be written as breaks of the body | 
|  | // block. | 
|  | if (labeledBlockInfo.isContinue) { | 
|  | for (LabelDefinition label in labeledBlockInfo.labels) { | 
|  | if (label.isContinueTarget) { | 
|  | String labelName = _namer.continueLabelName(label); | 
|  | result = js.LabeledStatement(labelName, result); | 
|  | continueAction.add(label); | 
|  | continueOverrides = continueOverrides.prepend(label); | 
|  | } | 
|  | } | 
|  | // For handling unlabeled continues from the body of a loop. | 
|  | // TODO(lrn): Consider recording whether the target is in fact | 
|  | // a target of an unlabeled continue, and not generate this if it isn't. | 
|  | JumpTarget target = labeledBlockInfo.target!; | 
|  | String labelName = _namer.implicitContinueLabelName(target); | 
|  | result = js.LabeledStatement(labelName, result); | 
|  | implicitContinueAction.add(target); | 
|  | continueOverrides = continueOverrides.prepend(target); | 
|  | } else { | 
|  | for (LabelDefinition label in labeledBlockInfo.labels) { | 
|  | if (label.isBreakTarget) { | 
|  | String labelName = _namer.breakLabelName(label); | 
|  | result = js.LabeledStatement(labelName, result); | 
|  | } | 
|  | } | 
|  | } | 
|  | JumpTarget target = labeledBlockInfo.target!; | 
|  | if (target.isSwitch) { | 
|  | // This is an extra block around a switch that is generated | 
|  | // as a nested if/else chain. We add an extra break target | 
|  | // so that case code can break. | 
|  | String labelName = _namer.implicitBreakLabelName(target); | 
|  | result = js.LabeledStatement(labelName, result); | 
|  | breakAction.add(target); | 
|  | } | 
|  |  | 
|  | currentContainer = body; | 
|  | generateStatements(labeledBlockInfo.body); | 
|  |  | 
|  | if (labeledBlockInfo.isContinue) { | 
|  | while (!continueOverrides.isEmpty) { | 
|  | continueAction.remove(continueOverrides.head); | 
|  | implicitContinueAction.remove(continueOverrides.head); | 
|  | continueOverrides = continueOverrides.tail!; | 
|  | } | 
|  | } else { | 
|  | breakAction.remove(labeledBlockInfo.target); | 
|  | } | 
|  |  | 
|  | currentContainer = oldContainer; | 
|  | pushStatement(result); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Wraps a loop body in a block to make continues have a target to break | 
|  | // to (if necessary). | 
|  | void wrapLoopBodyForContinue(HLoopBlockInformation info) { | 
|  | JumpTarget? target = info.target; | 
|  | if (target != null && target.isContinueTarget) { | 
|  | js.Block oldContainer = currentContainer; | 
|  | js.Block body = js.Block.empty(); | 
|  | currentContainer = body; | 
|  | js.Statement result = body; | 
|  | for (LabelDefinition label in info.labels) { | 
|  | if (label.isContinueTarget) { | 
|  | String labelName = _namer.continueLabelName(label); | 
|  | result = js.LabeledStatement(labelName, result); | 
|  | continueAction.add(label); | 
|  | } | 
|  | } | 
|  | String labelName = _namer.implicitContinueLabelName(target); | 
|  | result = js.LabeledStatement(labelName, result); | 
|  | implicitContinueAction.add(target); | 
|  | visitBodyIgnoreLabels(info); | 
|  | implicitContinueAction.remove(target); | 
|  | for (LabelDefinition label in info.labels) { | 
|  | if (label.isContinueTarget) { | 
|  | continueAction.remove(label); | 
|  | } | 
|  | } | 
|  | currentContainer = oldContainer; | 
|  | pushStatement(result); | 
|  | } else { | 
|  | // Loop body contains no continues, so we don't need a break target. | 
|  | generateStatements(info.body); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool handleBlockFlow(HBlockFlow block) { | 
|  | HBlockInformation info = block.body; | 
|  | // If we reach here again while handling the attached information, | 
|  | // e.g., because we call visitSubGraph on a subgraph starting on | 
|  | // the same block, don't handle it again. | 
|  | // When the structure graph is complete, we will be able to have | 
|  | // different structures starting on the same basic block (e.g., an | 
|  | // "if" and its condition). | 
|  | if (identical(info, currentBlockInformation)) return false; | 
|  |  | 
|  | HBlockInformation? oldBlockInformation = currentBlockInformation; | 
|  | currentBlockInformation = info; | 
|  | bool success = info.accept(this); | 
|  | currentBlockInformation = oldBlockInformation; | 
|  |  | 
|  | if (success) { | 
|  | HBasicBlock? continuation = block.continuation; | 
|  | if (continuation != null) { | 
|  | continueSubGraph(continuation); | 
|  | } | 
|  | } | 
|  | return success; | 
|  | } | 
|  |  | 
|  | void enterSubGraph(HBasicBlock node) { | 
|  | assert(blockQueue!.isEmpty); | 
|  | continueSubGraph(node); | 
|  | while (blockQueue!.isNotEmpty) { | 
|  | node = blockQueue!.removeFirst(); | 
|  | assert(node.isLive); | 
|  | assert(subGraph!.contains(node)); | 
|  |  | 
|  | // If this node has block-structure based information attached, | 
|  | // try using that to traverse from here. | 
|  | if (node.blockFlow != null && handleBlockFlow(node.blockFlow!)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | iterateBasicBlock(node); | 
|  | } | 
|  | } | 
|  |  | 
|  | void continueSubGraph(HBasicBlock node) { | 
|  | if (!node.isLive) return; | 
|  | // Don't follow edges out of the current sub-graph. | 
|  | if (!subGraph!.contains(node)) return; | 
|  | blockQueue!.add(node); | 
|  | } | 
|  |  | 
|  | void emitAssignment( | 
|  | String destination, String source, SourceInformation? sourceInformation) { | 
|  | assignVariable(destination, js.VariableUse(source), sourceInformation); | 
|  | } | 
|  |  | 
|  | /// Sequentialize a list of conceptually parallel copies. Parallel | 
|  | /// copies may contain cycles, that this method breaks. | 
|  | void sequentializeCopies( | 
|  | Iterable<Copy<HInstruction>> instructionCopies, | 
|  | String tempName, | 
|  | void doAssignment( | 
|  | String target, String source, SourceInformation? sourceInformation)) { | 
|  | Map<String, SourceInformation?> sourceInformationMap = {}; | 
|  |  | 
|  | // Map the instructions to strings. | 
|  | Iterable<Copy<String>> copies = | 
|  | instructionCopies.map((Copy<HInstruction> copy) { | 
|  | String sourceName = variableNames.getName(copy.source)!; | 
|  | sourceInformationMap[sourceName] = copy.source.sourceInformation; | 
|  | String destinationName = variableNames.getName(copy.destination)!; | 
|  | sourceInformationMap[sourceName] = copy.destination.sourceInformation; | 
|  | return Copy<String>(sourceName, destinationName); | 
|  | }); | 
|  |  | 
|  | // Map to keep track of the current location (ie the variable that | 
|  | // holds the initial value) of a variable. | 
|  | Map<String, String> currentLocation = {}; | 
|  |  | 
|  | // Map to keep track of the initial value of a variable. | 
|  | Map<String, String> initialValue = {}; | 
|  |  | 
|  | // List of variables to assign a value. | 
|  | List<String> worklist = []; | 
|  |  | 
|  | // List of variables that we can assign a value to (ie are not | 
|  | // being used anymore). | 
|  | List<String> ready = []; | 
|  |  | 
|  | // Prune [copies] by removing self-copies. | 
|  | List<Copy<String>> prunedCopies = []; | 
|  | for (Copy<String> copy in copies) { | 
|  | if (copy.source != copy.destination) { | 
|  | prunedCopies.add(copy); | 
|  | } | 
|  | } | 
|  | copies = prunedCopies; | 
|  |  | 
|  | // For each copy, set the current location of the source to | 
|  | // itself, and the initial value of the destination to the source. | 
|  | // Add the destination to the list of copies to make. | 
|  | for (Copy<String> copy in copies) { | 
|  | currentLocation[copy.source] = copy.source; | 
|  | initialValue[copy.destination] = copy.source; | 
|  | worklist.add(copy.destination); | 
|  | } | 
|  |  | 
|  | // For each copy, if the destination does not have a current | 
|  | // location, then we can safely assign to it. | 
|  | for (Copy<String> copy in copies) { | 
|  | if (currentLocation[copy.destination] == null) { | 
|  | ready.add(copy.destination); | 
|  | } | 
|  | } | 
|  |  | 
|  | while (!worklist.isEmpty) { | 
|  | while (!ready.isEmpty) { | 
|  | String destination = ready.removeLast(); | 
|  | String source = initialValue[destination]!; | 
|  | // Since [source] might have been updated, use the current | 
|  | // location of [source] | 
|  | String copy = currentLocation[source]!; | 
|  | doAssignment(destination, copy, | 
|  | sourceInformationMap[copy] ?? sourceInformationMap[destination]); | 
|  | // Now [destination] is the current location of [source]. | 
|  | currentLocation[source] = destination; | 
|  | // If [source] hasn't been updated and needs to have a value, | 
|  | // add it to the list of variables that can be updated. Copies | 
|  | // of [source] will now use [destination]. | 
|  | if (source == copy && initialValue[source] != null) { | 
|  | ready.add(source); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Check if we have a cycle. | 
|  | String current = worklist.removeLast(); | 
|  | // If [current] is used as a source, and the assignment has been | 
|  | // done, we are done with this variable. Otherwise there is a | 
|  | // cycle that we break by using a temporary name. | 
|  | if (currentLocation[current] != null && | 
|  | current != currentLocation[initialValue[current]]) { | 
|  | doAssignment(tempName, current, sourceInformationMap[current]); | 
|  | currentLocation[current] = tempName; | 
|  | // [current] can now be safely updated. Copies of [current] | 
|  | // will now use [tempName]. | 
|  | ready.add(current); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void assignPhisOfSuccessors(HBasicBlock node) { | 
|  | CopyHandler? handler = variableNames.getCopyHandler(node); | 
|  | if (handler == null) return; | 
|  |  | 
|  | sequentializeCopies( | 
|  | handler.copies, variableNames.getSwapTemp(), emitAssignment); | 
|  |  | 
|  | for (Copy<HInstruction> copy in handler.assignments) { | 
|  | String name = variableNames.getName(copy.destination)!; | 
|  | use(copy.source); | 
|  | assignVariable(name, pop(), | 
|  | copy.source.sourceInformation ?? copy.destination.sourceInformation); | 
|  | } | 
|  | } | 
|  |  | 
|  | void iterateBasicBlock(HBasicBlock node) { | 
|  | HInstruction instruction = node.first!; | 
|  | while (!identical(instruction, node.last)) { | 
|  | if (!isGenerateAtUseSite(instruction)) { | 
|  | define(instruction); | 
|  | } | 
|  | instruction = instruction.next!; | 
|  | } | 
|  | assignPhisOfSuccessors(node); | 
|  | visit(instruction); | 
|  | } | 
|  |  | 
|  | void handleInvokeBinary( | 
|  | HInvokeBinary node, String op, SourceInformation? sourceInformation) { | 
|  | use(node.left); | 
|  | js.Expression jsLeft = pop(); | 
|  | use(node.right); | 
|  | push(js.Binary(op, jsLeft, pop()).withSourceInformation(sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLateValue(HLateValue node) { | 
|  | use(node.target); | 
|  | } | 
|  |  | 
|  | void visitInvokeBinary(HInvokeBinary node, String op) { | 
|  | handleInvokeBinary(node, op, node.sourceInformation); | 
|  | } | 
|  |  | 
|  | void visitRelational(HRelational node, String op) { | 
|  | handleInvokeBinary(node, op, node.sourceInformation); | 
|  | } | 
|  |  | 
|  | // We want the outcome of bit-operations to be positive. We use the unsigned | 
|  | // shift operator to achieve this. | 
|  | void convertBitOpResultToUnsigned(HInstruction node) { | 
|  | push(js.Binary(">>>", pop(), js.LiteralNumber("0")) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | void visitBitInvokeBinary(HBinaryBitOp node, String op) { | 
|  | visitInvokeBinary(node, op); | 
|  | if (node.requiresUintConversion) convertBitOpResultToUnsigned(node); | 
|  | } | 
|  |  | 
|  | void visitInvokeUnary(HInvokeUnary node, String op) { | 
|  | use(node.operand); | 
|  | push(js.Prefix(op, pop()).withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | void emitIdentityComparison( | 
|  | HIdentity instruction, SourceInformation? sourceInformation, | 
|  | {bool inverse = false}) { | 
|  | String? op = instruction.singleComparisonOp; | 
|  | HInstruction left = instruction.left; | 
|  | HInstruction right = instruction.right; | 
|  | if (op != null) { | 
|  | use(left); | 
|  | js.Expression jsLeft = pop(); | 
|  | use(right); | 
|  | push(js.Binary(mapRelationalOperator(op, inverse), jsLeft, pop()) | 
|  | .withSourceInformation(sourceInformation)); | 
|  | } else { | 
|  | assert(NullConstantValue.JsNull == 'null'); | 
|  | use(left); | 
|  | js.Binary leftEqualsNull = js.Binary("==", pop(), js.LiteralNull()); | 
|  | use(right); | 
|  | js.Binary rightEqualsNull = js.Binary( | 
|  | mapRelationalOperator("==", inverse), pop(), js.LiteralNull()); | 
|  | use(right); | 
|  | use(left); | 
|  | js.Binary tripleEq = | 
|  | js.Binary(mapRelationalOperator("===", inverse), pop(), pop()); | 
|  |  | 
|  | push(js.Conditional(leftEqualsNull, rightEqualsNull, tripleEq) | 
|  | .withSourceInformation(sourceInformation)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitIdentity(HIdentity node) { | 
|  | emitIdentityComparison(node, node.sourceInformation, inverse: false); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitAdd(HAdd node) => visitInvokeBinary(node, '+'); | 
|  | @override | 
|  | void visitDivide(HDivide node) => visitInvokeBinary(node, '/'); | 
|  | @override | 
|  | void visitMultiply(HMultiply node) => visitInvokeBinary(node, '*'); | 
|  | @override | 
|  | void visitSubtract(HSubtract node) => visitInvokeBinary(node, '-'); | 
|  | @override | 
|  | void visitBitAnd(HBitAnd node) => visitBitInvokeBinary(node, '&'); | 
|  |  | 
|  | @override | 
|  | void visitBitOr(HBitOr node) => visitBitInvokeBinary(node, '|'); | 
|  | @override | 
|  | void visitBitXor(HBitXor node) => visitBitInvokeBinary(node, '^'); | 
|  | @override | 
|  | void visitShiftLeft(HShiftLeft node) => visitBitInvokeBinary(node, '<<'); | 
|  | @override | 
|  | void visitShiftRight(HShiftRight node) => visitBitInvokeBinary(node, '>>>'); | 
|  |  | 
|  | @override | 
|  | void visitBitNot(HBitNot node) { | 
|  | visitInvokeUnary(node, '~'); | 
|  | if (node.requiresUintConversion) convertBitOpResultToUnsigned(node); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitTruncatingDivide(HTruncatingDivide node) { | 
|  | assert(node.isUInt31(_abstractValueDomain).isDefinitelyTrue); | 
|  | // TODO(karlklose): Enable this assertion again when type propagation is | 
|  | // fixed. Issue 23555. | 
|  | //    assert(node.left.isUInt32(compiler)); | 
|  | assert(node.right.isPositiveInteger(_abstractValueDomain).isDefinitelyTrue); | 
|  | use(node.left); | 
|  | js.Expression jsLeft = pop(); | 
|  | use(node.right); | 
|  | push(js.Binary('/', jsLeft, pop()) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | push(js.Binary('|', pop(), js.LiteralNumber("0")) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitRemainder(HRemainder node) { | 
|  | return visitInvokeBinary(node, '%'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitNegate(HNegate node) => visitInvokeUnary(node, '-'); | 
|  |  | 
|  | @override | 
|  | void visitAbs(HAbs node) { | 
|  | use(node.operand); | 
|  | push(js | 
|  | .js('Math.abs(#)', pop()) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLess(HLess node) => visitRelational(node, '<'); | 
|  | @override | 
|  | void visitLessEqual(HLessEqual node) => visitRelational(node, '<='); | 
|  | @override | 
|  | void visitGreater(HGreater node) => visitRelational(node, '>'); | 
|  | @override | 
|  | void visitGreaterEqual(HGreaterEqual node) => visitRelational(node, '>='); | 
|  |  | 
|  | @override | 
|  | void visitExit(HExit node) { | 
|  | // Don't do anything. | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitGoto(HGoto node) { | 
|  | HBasicBlock block = node.block!; | 
|  | assert(block.successors.length == 1); | 
|  | List<HBasicBlock> dominated = block.dominatedBlocks; | 
|  | // With the exception of the entry-node which dominates its successor | 
|  | // and the exit node, no block finishing with a 'goto' can have more than | 
|  | // one dominated block (since it has only one successor). | 
|  | // If the successor is dominated by another block, then the other block | 
|  | // is responsible for visiting the successor. | 
|  | if (dominated.isEmpty) return; | 
|  | if (dominated.length > 2) { | 
|  | failedAt(node, 'dominated.length = ${dominated.length}'); | 
|  | } | 
|  | if (dominated.length == 2 && block != currentGraph.entry) { | 
|  | failedAt(node, 'node.block != currentGraph.entry'); | 
|  | } | 
|  | assert(dominated[0] == block.successors[0]); | 
|  | continueSubGraph(dominated.first); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLoopBranch(HLoopBranch node) { | 
|  | assert(node.block == subGraph!.end); | 
|  | // We are generating code for a loop condition. | 
|  | // If we are generating the subgraph as an expression, the | 
|  | // condition will be generated as the expression. | 
|  | // Otherwise, we don't generate the expression, and leave that | 
|  | // to the code that called [visitSubGraph]. | 
|  | if (isGeneratingExpression) { | 
|  | use(node.inputs[0]); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitBreak(HBreak node) { | 
|  | assert(node.block!.successors.length == 1); | 
|  | if (node.label != null) { | 
|  | LabelDefinition label = node.label!; | 
|  | if (breakAction.contains(label.target)) { | 
|  | implicitBreakWithLabel(label.target); | 
|  | } else { | 
|  | pushStatement(js.Break(_namer.breakLabelName(label)) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  | } else { | 
|  | JumpTarget target = node.target; | 
|  | if (breakAction.contains(target)) { | 
|  | implicitBreakWithLabel(target); | 
|  | } else { | 
|  | if (node.breakSwitchContinueLoop) { | 
|  | pushStatement(js.Break(_namer.implicitContinueLabelName(target)) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } else { | 
|  | pushStatement( | 
|  | js.Break(null).withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitContinue(HContinue node) { | 
|  | assert(node.block!.successors.length == 1); | 
|  | if (node.label != null) { | 
|  | LabelDefinition label = node.label!; | 
|  | if (continueAction.contains(label)) { | 
|  | continueAsBreak(label); | 
|  | } else { | 
|  | // TODO(floitsch): should this really be the breakLabelName? | 
|  | pushStatement(js.Continue(_namer.breakLabelName(label)) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  | } else { | 
|  | JumpTarget target = node.target; | 
|  | if (implicitContinueAction.contains(target)) { | 
|  | implicitContinueAsBreak(target); | 
|  | } else { | 
|  | if (target.isSwitch) { | 
|  | pushStatement(js.Continue(_namer.implicitContinueLabelName(target)) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } else { | 
|  | pushStatement( | 
|  | js.Continue(null).withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitExitTry(HExitTry node) { | 
|  | // An [HExitTry] is used to represent the control flow graph of a | 
|  | // try/catch block, ie the try body is always a predecessor | 
|  | // of the catch and finally. Here, we continue visiting the try | 
|  | // body by visiting the block that contains the user-level control | 
|  | // flow instruction. | 
|  | continueSubGraph(node.bodyTrySuccessor); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitTry(HTry node) { | 
|  | // We should never get here. Try/catch/finally is always handled using block | 
|  | // information in [visitTryInfo]. | 
|  | failedAt(node, 'visitTry should not be called.'); | 
|  | } | 
|  |  | 
|  | bool tryControlFlowOperation(HIf node) { | 
|  | if (!controlFlowOperators.contains(node)) return false; | 
|  | final phi = node.joinBlock!.phis.first!; | 
|  | bool atUseSite = isGenerateAtUseSite(phi); | 
|  | // Don't generate a conditional operator in this situation: | 
|  | // i = condition ? bar() : i; | 
|  | // But generate this instead: | 
|  | // if (condition) i = bar(); | 
|  | // Usually, the variable name is longer than 'if' and it takes up | 
|  | // more space to duplicate the name. | 
|  | if (!atUseSite && | 
|  | variableNames.getName(phi) == variableNames.getName(phi.inputs[1])) { | 
|  | return false; | 
|  | } | 
|  | if (!atUseSite) define(phi); | 
|  | continueSubGraph(node.joinBlock!); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void generateIf(HIf node, HIfBlockInformation info) { | 
|  | HStatementInformation? thenGraph = info.thenGraph; | 
|  | HStatementInformation? elseGraph = info.elseGraph; | 
|  | HInstruction condition = node.inputs.single; | 
|  |  | 
|  | js.Expression test; | 
|  | js.Statement thenPart; | 
|  | js.Statement elsePart; | 
|  |  | 
|  | HBasicBlock thenBlock = node.block!.successors[0]; | 
|  | // If we believe we will generate S1 as empty, instead of | 
|  | // | 
|  | //     if (e) S1; else S2; | 
|  | // | 
|  | // try to generate | 
|  | // | 
|  | //     if (!e) S2; else S1; | 
|  | // | 
|  | // It is better to generate `!e` rather than try and negate it later. | 
|  | // Recognize a single then-block with no code and no controlled phis. | 
|  | if (isGenerateAtUseSite(condition) && | 
|  | thenBlock.successors.length == 1 && | 
|  | thenBlock.successors.single == node.joinBlock && | 
|  | node.joinBlock!.phis.isEmpty && | 
|  | thenBlock.first is HGoto) { | 
|  | generateNot(condition, condition.sourceInformation); | 
|  | test = pop(); | 
|  | // Swap branches but visit in same order as register allocator. | 
|  | elsePart = unwrapStatement(generateStatementsInNewBlock(thenGraph)); | 
|  | thenPart = unwrapStatement(generateStatementsInNewBlock(elseGraph)); | 
|  | assert(elsePart is js.EmptyStatement); | 
|  | } else { | 
|  | use(condition); | 
|  | test = pop(); | 
|  | thenPart = unwrapStatement(generateStatementsInNewBlock(thenGraph)); | 
|  | elsePart = unwrapStatement(generateStatementsInNewBlock(elseGraph)); | 
|  | } | 
|  |  | 
|  | js.Statement code = _assembleIfThenElse(test, thenPart, elsePart); | 
|  | pushStatement(code.withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | js.Statement _assembleIfThenElse( | 
|  | js.Expression test, js.Statement thenPart, js.Statement elsePart) { | 
|  | // Peephole rewrites: | 
|  | // | 
|  | //     if (e); else S;   -->   if (!e) S; | 
|  | // | 
|  | //     if (e);   -->   e; | 
|  | // | 
|  | // TODO(sra): We might be able to do better with reshaping the CFG. | 
|  | if (thenPart is js.EmptyStatement) { | 
|  | if (elsePart is js.EmptyStatement) { | 
|  | return js.ExpressionStatement(test); | 
|  | } | 
|  | test = js.Prefix('!', test); | 
|  | js.Statement temp = thenPart; | 
|  | thenPart = elsePart; | 
|  | elsePart = temp; | 
|  | } | 
|  |  | 
|  | if (_options.experimentToBoolean) { | 
|  | if (elsePart is js.EmptyStatement && | 
|  | thenPart is js.ExpressionStatement && | 
|  | thenPart.expression is js.Call) { | 
|  | return js.ExpressionStatement( | 
|  | js.Binary('&&', test, thenPart.expression)); | 
|  | } | 
|  | } | 
|  |  | 
|  | return js.If(test, thenPart, elsePart); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitIf(HIf node) { | 
|  | _metrics.countHIf++; | 
|  | HInstruction condition = node.inputs[0]; | 
|  | if (condition is HConstant) _metrics.countHIfConstant++; | 
|  |  | 
|  | if (tryControlFlowOperation(node)) return; | 
|  |  | 
|  | HIfBlockInformation info = | 
|  | node.blockInformation!.body as HIfBlockInformation; | 
|  |  | 
|  | if (condition is HConstant) { | 
|  | if (condition.constant is TrueConstantValue) { | 
|  | generateStatements(info.thenGraph); | 
|  | } else { | 
|  | generateStatements(info.elseGraph); | 
|  | } | 
|  | } else { | 
|  | generateIf(node, info); | 
|  | } | 
|  |  | 
|  | HBasicBlock? joinBlock = node.joinBlock; | 
|  | if (joinBlock != null && !identical(joinBlock.dominator, node.block)) { | 
|  | // The join block is dominated by a block in one of the branches. | 
|  | // The subgraph traversal never reached it, so we visit it here | 
|  | // instead. | 
|  | continueSubGraph(joinBlock); | 
|  | } | 
|  |  | 
|  | // Visit all the dominated blocks that are not part of the then or else | 
|  | // branches, and is not the join block. | 
|  | // Depending on how the then/else branches terminate | 
|  | // (e.g., return/throw/break) there can be any number of these. | 
|  | List<HBasicBlock> dominated = node.block!.dominatedBlocks; | 
|  | for (int i = 2; i < dominated.length; i++) { | 
|  | continueSubGraph(dominated[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitInterceptor(HInterceptor node) { | 
|  | _metrics.countHInterceptor.add(); | 
|  | if (node.isConditionalConstantInterceptor) { | 
|  | _metrics.countHInterceptorConditionalConstant.add(); | 
|  | assert(node.inputs.length == 2); | 
|  | use(node.receiver); | 
|  | js.Expression receiverExpression = pop(); | 
|  | use(node.conditionalConstantInterceptor); | 
|  | js.Expression constant = pop(); | 
|  | push(js.js('# && #', [receiverExpression, constant])); | 
|  | } else { | 
|  | _metrics.countHInterceptorGet.add(); | 
|  | assert(node.inputs.length == 1); | 
|  | _registry.registerSpecializedGetInterceptor(node.interceptedClasses!); | 
|  | js.Name name = _namer.nameForGetInterceptor(node.interceptedClasses!); | 
|  | js.Expression isolate = _namer.readGlobalObjectForInterceptors(); | 
|  | use(node.receiver); | 
|  | List<js.Expression> arguments = [pop()]; | 
|  | push(js | 
|  | .propertyCall(isolate, name, arguments) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | _registry.registerUseInterceptor(); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitInvokeDynamicMethod(HInvokeDynamicMethod node) { | 
|  | _updateInvokeMetrics(node); | 
|  | use(node.receiver); | 
|  | js.Expression object = pop(); | 
|  | String? methodName; | 
|  | List<js.Expression> arguments = visitArguments(node.inputs); | 
|  | MemberEntity? target = node.element; | 
|  |  | 
|  | // TODO(herhut): The namer should return the appropriate backend name here. | 
|  | if (target != null && !node.isInterceptedCall) { | 
|  | if (target == _commonElements.jsArrayAdd) { | 
|  | methodName = 'push'; | 
|  | } else if (target == _commonElements.jsArrayRemoveLast) { | 
|  | methodName = 'pop'; | 
|  | } else if (_commonElements.isJsStringSplit(target)) { | 
|  | methodName = 'split'; | 
|  | // Split returns a List, so we make sure the backend knows the | 
|  | // list class is instantiated. | 
|  | _registry | 
|  | // ignore:deprecated_member_use_from_same_package | 
|  | .registerInstantiatedClass(_commonElements.listClass); | 
|  | } | 
|  | } | 
|  |  | 
|  | js.Name methodLiteral; | 
|  | if (methodName == null) { | 
|  | methodLiteral = _namer.invocationName(node.selector); | 
|  | registerMethodInvoke(node); | 
|  | } else { | 
|  | methodLiteral = _namer.asName(methodName); | 
|  | } | 
|  | push(js | 
|  | .propertyCall(object, methodLiteral, arguments) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitInvokeConstructorBody(HInvokeConstructorBody node) { | 
|  | final element = node.element as ConstructorBodyEntity; | 
|  | use(node.inputs[0]); | 
|  | js.Expression object = pop(); | 
|  | js.Name methodName = _namer.instanceMethodName(element); | 
|  | List<js.Expression> arguments = visitArguments(node.inputs); | 
|  | push(js | 
|  | .propertyCall(object, methodName, arguments) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | _registry.registerStaticUse(StaticUse.constructorBodyInvoke( | 
|  | element, CallStructure.unnamed(arguments.length))); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitInvokeGeneratorBody(HInvokeGeneratorBody node) { | 
|  | // TODO(sra): Refactor HInvokeGeneratorBody so that `node.element` has this | 
|  | // type. | 
|  | JGeneratorBody element = node.element as JGeneratorBody; | 
|  | if (element.isInstanceMember) { | 
|  | use(node.inputs[0]); | 
|  | js.Expression object = pop(); | 
|  | List<js.Expression> arguments = visitArguments(node.inputs); | 
|  | js.Name methodName = _namer.instanceMethodName(element); | 
|  | push(js | 
|  | .propertyCall(object, methodName, arguments) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } else { | 
|  | push(_emitter.staticFunctionAccess(element)); | 
|  | List<js.Expression> arguments = visitArguments(node.inputs, start: 0); | 
|  | push( | 
|  | js.Call(pop(), arguments, sourceInformation: node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | _registry.registerStaticUse(StaticUse.generatorBodyInvoke(element)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitOneShotInterceptor(HOneShotInterceptor node) { | 
|  | _metrics.countHInterceptor.add(); | 
|  | _metrics.countHInterceptorOneshot.add(); | 
|  | List<js.Expression> arguments = visitArguments(node.inputs); | 
|  | js.Expression isolate = _namer.readGlobalObjectForInterceptors(); | 
|  | Selector selector = node.selector; | 
|  | Set<ClassEntity> classes = | 
|  | _interceptorData.getInterceptedClassesOn(selector.name, _closedWorld); | 
|  | _registry.registerOneShotInterceptor(selector); | 
|  | js.Name methodName = _namer.nameForOneShotInterceptor(selector, classes); | 
|  | push(js | 
|  | .propertyCall(isolate, methodName, arguments) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | if (selector.isGetter) { | 
|  | registerGetter(node); | 
|  | } else if (selector.isSetter) { | 
|  | registerSetter(node); | 
|  | } else { | 
|  | registerMethodInvoke(node); | 
|  | } | 
|  | _registry.registerUseInterceptor(); | 
|  | } | 
|  |  | 
|  | AbstractValue getOptimizedSelectorFor( | 
|  | HInvokeDynamic node, Selector selector, AbstractValue mask) { | 
|  | if (node.element != null) { | 
|  | // Create an artificial type mask to make sure only | 
|  | // [node.element] will be enqueued. We're not using the receiver | 
|  | // type because our optimizations might end up in a state where the | 
|  | // invoke dynamic knows more than the receiver. | 
|  | ClassEntity enclosing = node.element!.enclosingClass!; | 
|  | if (_closedWorld.classHierarchy.isInstantiated(enclosing)) { | 
|  | return _abstractValueDomain.createNonNullExact(enclosing); | 
|  | } else { | 
|  | // The element is mixed in so a non-null subtype mask is the most | 
|  | // precise we have. | 
|  | assert( | 
|  | _closedWorld.isUsedAsMixin(enclosing), | 
|  | failedAt( | 
|  | node, | 
|  | "Element ${node.element} from $enclosing expected " | 
|  | "to be mixed in.")); | 
|  | return _abstractValueDomain.createNonNullSubtype(enclosing); | 
|  | } | 
|  | } | 
|  | return mask; | 
|  | } | 
|  |  | 
|  | void registerMethodInvoke(HInvokeDynamic node) { | 
|  | Selector selector = node.selector; | 
|  |  | 
|  | // If we don't know what we're calling or if we are calling a getter, | 
|  | // we need to register that fact that we may be calling a closure | 
|  | // with the same arguments. | 
|  | MemberEntity? target = node.element; | 
|  | if ((target == null || target.isGetter) && | 
|  | // TODO(johnniwinther): Remove this when kernel adds an `isFunctionCall` | 
|  | // flag to [ir.MethodInvocation]. Currently we can't tell the difference | 
|  | // between a dynamic call and a function call, but we at least know that | 
|  | // toString is not a getter (a potential function call should otherwise | 
|  | // have been register for string concatenation). | 
|  | selector != Selectors.toString_) { | 
|  | // TODO(kasperl): If we have a typed selector for the call, we | 
|  | // may know something about the types of closures that need | 
|  | // the specific closure call method. | 
|  | Selector call = Selector.callClosureFrom(selector); | 
|  | _registry.registerDynamicUse(DynamicUse(call, null, node.typeArguments)); | 
|  | } | 
|  | if (target != null) { | 
|  | // This is a dynamic invocation which we have found to have a single | 
|  | // target but for some reason haven't inlined. We are _still_ accessing | 
|  | // the target dynamically but we don't need to enqueue more than target | 
|  | // for this to work. | 
|  | assert(selector.applies(target), | 
|  | failedAt(node, '$selector does not apply to $target')); | 
|  | assert(!selector.isGetter && !selector.isSetter, | 
|  | "Unexpected direct invocation selector: $selector."); | 
|  | target as FunctionEntity; // TODO(sra): Make node.element have this type. | 
|  | _registry.registerStaticUse(StaticUse.directInvoke( | 
|  | target, selector.callStructure, node.typeArguments)); | 
|  | } else { | 
|  | AbstractValue mask = | 
|  | getOptimizedSelectorFor(node, selector, node.receiverType); | 
|  | _registry | 
|  | .registerDynamicUse(DynamicUse(selector, mask, node.typeArguments)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void registerSetter(HInvokeDynamic node, {bool needsCheck = false}) { | 
|  | final element = node.element; | 
|  | if (element is FieldEntity && !needsCheck) { | 
|  | // This is a dynamic update which we have found to have a single | 
|  | // target but for some reason haven't inlined. We are _still_ accessing | 
|  | // the target dynamically but we don't need to enqueue more than target | 
|  | // for this to work. | 
|  | _registry.registerStaticUse(StaticUse.directSet(element)); | 
|  | } else { | 
|  | Selector selector = node.selector; | 
|  | AbstractValue mask = | 
|  | getOptimizedSelectorFor(node, selector, node.receiverType); | 
|  | _registry | 
|  | .registerDynamicUse(DynamicUse(selector, mask, node.typeArguments)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void registerGetter(HInvokeDynamic node) { | 
|  | final element = node.element; | 
|  | if (element != null && (element.isGetter || element is FieldEntity)) { | 
|  | // This is a dynamic read which we have found to have a single target but | 
|  | // for some reason haven't inlined. We are _still_ accessing the target | 
|  | // dynamically but we don't need to enqueue more than target for this to | 
|  | // work. The test above excludes non-getter functions since the element | 
|  | // represents two targets - a tearoff getter and the torn-off method. | 
|  | _registry.registerStaticUse(StaticUse.directGet(element)); | 
|  | } else { | 
|  | Selector selector = node.selector; | 
|  | AbstractValue mask = | 
|  | getOptimizedSelectorFor(node, selector, node.receiverType); | 
|  | _registry | 
|  | .registerDynamicUse(DynamicUse(selector, mask, node.typeArguments)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void _updateInvokeMetrics(HInvokeDynamic node) { | 
|  | if (node.element != null) _metrics.countSingleTargetInstanceCalls++; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitInvokeDynamicSetter(HInvokeDynamicSetter node) { | 
|  | _updateInvokeMetrics(node); | 
|  | use(node.receiver); | 
|  | js.Name name = _namer.invocationName(node.selector); | 
|  | push(js | 
|  | .propertyCall(pop(), name, visitArguments(node.inputs)) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | registerSetter(node, needsCheck: node.needsCheck); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitInvokeDynamicGetter(HInvokeDynamicGetter node) { | 
|  | _updateInvokeMetrics(node); | 
|  | use(node.receiver); | 
|  | js.Name name = _namer.invocationName(node.selector); | 
|  | push(js | 
|  | .propertyCall(pop(), name, visitArguments(node.inputs)) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | registerGetter(node); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitInvokeClosure(HInvokeClosure node) { | 
|  | Selector call = Selector.callClosureFrom(node.selector); | 
|  | use(node.receiver); | 
|  | push(js | 
|  | .propertyCall( | 
|  | pop(), _namer.invocationName(call), visitArguments(node.inputs)) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | // TODO(kasperl): If we have a typed selector for the call, we | 
|  | // may know something about the types of closures that need | 
|  | // the specific closure call method. | 
|  | _registry.registerDynamicUse(DynamicUse(call, null, node.typeArguments)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitInvokeStatic(HInvokeStatic node) { | 
|  | // TODO(48820): Refactor HInvokeStatic so that the element has static type | 
|  | // FunctionEntity (`element` can be a FieldEntity in subclass HInvokeSuper, | 
|  | // so possibly make HInvokeSuper and HInvokeStatic extend a common | 
|  | // superclass, or have a different node for super-field accesses). | 
|  | FunctionEntity element = node.element as FunctionEntity; | 
|  | node.instantiatedTypes?.forEach(_registry.registerInstantiation); | 
|  |  | 
|  | List<js.Expression> arguments = visitArguments(node.inputs, start: 0); | 
|  |  | 
|  | if (element == _commonElements.jsAllowInterop) { | 
|  | _nativeData.registerAllowInterop(); | 
|  | } | 
|  |  | 
|  | if (_commonElements.isCheckConcurrentModificationError(element)) { | 
|  | // Manually inline the [checkConcurrentModificationError] function.  This | 
|  | // function is only called from a for-loop update.  Ideally we would just | 
|  | // generate the conditional control flow in the builder but it adds basic | 
|  | // blocks in the loop update that interfere with other optimizations and | 
|  | // confuses loop recognition. | 
|  |  | 
|  | assert(arguments.length == 2); | 
|  | FunctionEntity throwFunction = | 
|  | _commonElements.throwConcurrentModificationError; | 
|  | _registry.registerStaticUse( | 
|  | StaticUse.staticInvoke(throwFunction, CallStructure.ONE_ARG)); | 
|  |  | 
|  | // Calling using `(0, #)(#)` instead of `#(#)` separates the property load | 
|  | // of the static function access from the call.  For some reason this | 
|  | // helps V8 see that the call never happens so V8 makes the call a | 
|  | // deoptimization. This removes the call from the optimized loop, making | 
|  | // more optimizations available to the loop.  This form is 50% faster on | 
|  | // some small loop, almost as fast as loops with no concurrent | 
|  | // modification check. | 
|  |  | 
|  | // Create [right] as a separate JS node to give the call a source | 
|  | // location. | 
|  | js.Expression right = js.js('(0, #)(#)', [ | 
|  | _emitter.staticFunctionAccess(throwFunction), | 
|  | arguments[1] | 
|  | ]).withSourceInformation(node.sourceInformation); | 
|  | push(js.js('# || #', [arguments[0], right]).withSourceInformation( | 
|  | node.sourceInformation)); | 
|  | } else { | 
|  | StaticUse staticUse; | 
|  | Object? resourceIdentifierAnnotation; | 
|  | if (element is ConstructorEntity) { | 
|  | CallStructure callStructure = | 
|  | CallStructure.unnamed(arguments.length, node.typeArguments.length); | 
|  | staticUse = StaticUse.constructorInvoke(element, callStructure); | 
|  | } else if (element.isGetter) { | 
|  | staticUse = StaticUse.staticGet(element); | 
|  | } else if (element.isSetter) { | 
|  | staticUse = StaticUse.staticSet(element); | 
|  | } else { | 
|  | assert(element.isFunction); | 
|  | CallStructure callStructure = | 
|  | CallStructure.unnamed(arguments.length, node.typeArguments.length); | 
|  | staticUse = | 
|  | StaticUse.staticInvoke(element, callStructure, node.typeArguments); | 
|  | if (_closedWorld.annotationsData.methodIsResourceIdentifier(element)) { | 
|  | resourceIdentifierAnnotation = _methodResourceIdentifier( | 
|  | element, callStructure, node.inputs, node.sourceInformation); | 
|  | } | 
|  | } | 
|  | _registry.registerStaticUse(staticUse); | 
|  | push(_emitter.staticFunctionAccess(element)); | 
|  | push( | 
|  | js.Call(pop(), arguments, sourceInformation: node.sourceInformation)); | 
|  | if (resourceIdentifierAnnotation != null) { | 
|  | push(pop().withAnnotation(resourceIdentifierAnnotation)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | ResourceIdentifier _methodResourceIdentifier( | 
|  | FunctionEntity element, | 
|  | CallStructure callStructure, | 
|  | List<HInstruction> arguments, | 
|  | SourceInformation? sourceInformation) { | 
|  | ConstantValue? findConstant(HInstruction node) { | 
|  | while (node is HLateValue) node = node.target; | 
|  | return node is HConstant ? node.constant : null; | 
|  | } | 
|  |  | 
|  | final definition = _closedWorld.elementMap.getMemberDefinition(element); | 
|  | final uri = definition.location.uri; | 
|  |  | 
|  | final builder = ResourceIdentifierBuilder(element.name!, uri); | 
|  |  | 
|  | if (sourceInformation != null) { | 
|  | _addSourceInformationToResourceIdentiferBuilder( | 
|  | builder, sourceInformation); | 
|  | } | 
|  | for (int i = 0; i < arguments.length; i++) { | 
|  | builder.add('${i + 1}', findConstant(arguments[i])); | 
|  | } | 
|  |  | 
|  | return builder.finish(); | 
|  | } | 
|  |  | 
|  | void _addSourceInformationToResourceIdentiferBuilder( | 
|  | ResourceIdentifierBuilder builder, SourceInformation sourceInformation) { | 
|  | SourceLocation? location = sourceInformation.startPosition ?? | 
|  | sourceInformation.innerPosition ?? | 
|  | sourceInformation.endPosition; | 
|  | if (location != null) { | 
|  | final sourceUri = location.sourceUri; | 
|  | if (sourceUri != null) { | 
|  | // Is [sourceUri] normalized in some way or does that need to be done | 
|  | // here? | 
|  | builder.addLocation(sourceUri, location.line, location.column); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitInvokeSuper(HInvokeSuper node) { | 
|  | MemberEntity superElement = node.element; | 
|  | Selector selector = node.selector; | 
|  | bool useAliasedSuper = canUseAliasedSuperMember(superElement, selector); | 
|  | if (selector.isGetter) { | 
|  | if (superElement is FieldEntity || superElement.isGetter) { | 
|  | _registry.registerStaticUse(StaticUse.superGet(superElement)); | 
|  | } else { | 
|  | superElement as FunctionEntity; // Not a field so must be a function. | 
|  | _registry.registerStaticUse(StaticUse.superTearOff(superElement)); | 
|  | } | 
|  | } else if (selector.isSetter) { | 
|  | if (superElement is FieldEntity) { | 
|  | _registry.registerStaticUse(StaticUse.superFieldSet(superElement)); | 
|  | } else { | 
|  | assert(superElement.isSetter); | 
|  | superElement as FunctionEntity; // Not a field so must be a function. | 
|  | _registry.registerStaticUse(StaticUse.superSetterSet(superElement)); | 
|  | } | 
|  | } else { | 
|  | superElement as FunctionEntity; // Not a field so must be a function. | 
|  | if (useAliasedSuper) { | 
|  | _registry.registerStaticUse(StaticUse.superInvoke( | 
|  | superElement, CallStructure.unnamed(node.inputs.length))); | 
|  | } else { | 
|  | _registry.registerStaticUse(StaticUse.superInvoke( | 
|  | superElement, CallStructure.unnamed(node.inputs.length - 1))); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (superElement is FieldEntity) { | 
|  | // TODO(sra): We can lower these in the simplifier. | 
|  | js.Name fieldName = _namer.instanceFieldPropertyName(superElement); | 
|  | use(node.getDartReceiver(_closedWorld)); | 
|  | js.PropertyAccess access = js.PropertyAccess(pop(), fieldName) | 
|  | .withSourceInformation(node.sourceInformation) as js.PropertyAccess; | 
|  | if (node.isSetter) { | 
|  | use(node.value); | 
|  | push(js.Assignment(access, pop()) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } else { | 
|  | push(access); | 
|  | } | 
|  | } else if (superElement is FunctionEntity) { | 
|  | if (!useAliasedSuper) { | 
|  | js.Name methodName; | 
|  | if (selector.isGetter && !superElement.isGetter) { | 
|  | // If this is a tear-off, register the fact that a tear-off closure | 
|  | // will be created, and that this tear-off must bypass ordinary | 
|  | // dispatch to ensure the super method is invoked. | 
|  | FunctionEntity helper = _commonElements.closureFromTearOff; | 
|  | _registry.registerStaticUse(StaticUse.staticInvoke( | 
|  | helper, | 
|  | CallStructure.unnamed( | 
|  | node.inputs.length, node.typeArguments.length), | 
|  | node.typeArguments)); | 
|  | methodName = _namer.invocationName(selector); | 
|  | } else { | 
|  | methodName = _namer.instanceMethodName(superElement); | 
|  | } | 
|  |  | 
|  | ClassEntity superClass = superElement.enclosingClass!; | 
|  | push(js.js('#.#.call(#)', [ | 
|  | _emitter.prototypeAccess(superClass), | 
|  | methodName, | 
|  | visitArguments(node.inputs, start: 0) | 
|  | ]).withSourceInformation(node.sourceInformation)); | 
|  | } else { | 
|  | use(node.receiver); | 
|  | push(js.js('#.#(#)', [ | 
|  | pop(), | 
|  | _namer.aliasedSuperMemberPropertyName(superElement), | 
|  | visitArguments(node.inputs, start: 1) | 
|  | ]) // Skip receiver argument. | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  | } else { | 
|  | failedAt(node, 'node.element must be FieldEntity or FunctionEntity'); | 
|  | } | 
|  | } | 
|  |  | 
|  | js.Expression _loadField(js.Expression receiver, FieldEntity field, | 
|  | SourceInformation? sourceInformation) { | 
|  | _registry.registerStaticUse(StaticUse.fieldGet(field)); | 
|  | js.Name name = _namer.instanceFieldPropertyName(field); | 
|  | return js.PropertyAccess(receiver, name) | 
|  | .withSourceInformation(sourceInformation); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitFieldGet(HFieldGet node) { | 
|  | _metrics.countHFieldGet++; | 
|  | use(node.receiver); | 
|  | push(_loadField(pop(), node.element, node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitFieldSet(HFieldSet node) { | 
|  | FieldEntity element = node.element; | 
|  | _registry.registerStaticUse(StaticUse.fieldSet(element)); | 
|  | js.Name name = _namer.instanceFieldPropertyName(element); | 
|  | use(node.receiver); | 
|  | js.Expression receiver = pop(); | 
|  | use(node.value); | 
|  | push(js.Assignment( | 
|  | js.PropertyAccess(receiver, name) | 
|  | .withSourceInformation(node.sourceInformation), | 
|  | pop()) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitGetLength(HGetLength node) { | 
|  | _metrics.countHGetLength++; | 
|  | use(node.receiver); | 
|  | push(js.PropertyAccess.field(pop(), 'length') | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitReadModifyWrite(HReadModifyWrite node) { | 
|  | FieldEntity element = node.element; | 
|  | _registry.registerStaticUse(StaticUse.fieldGet(element)); | 
|  | _registry.registerStaticUse(StaticUse.fieldSet(element)); | 
|  | js.Name name = _namer.instanceFieldPropertyName(element); | 
|  | use(node.receiver); | 
|  | js.Expression fieldReference = js.PropertyAccess(pop(), name); | 
|  | switch (node.opKind) { | 
|  | case ReadModifyWriteKind.prefix: | 
|  | push(js.Prefix(node.jsOp, fieldReference) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | case ReadModifyWriteKind.postfix: | 
|  | push(js.Postfix(node.jsOp, fieldReference) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | case ReadModifyWriteKind.assign: | 
|  | use(node.value); | 
|  | push(js.Assignment.compound(fieldReference, node.jsOp, pop()) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitFunctionReference(HFunctionReference node) { | 
|  | FunctionEntity element = node.element; | 
|  | _registry.registerStaticUse(StaticUse.implicitInvoke(element)); | 
|  | push(_emitter.staticFunctionAccess(element)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLocalGet(HLocalGet node) { | 
|  | use(node.receiver); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLocalSet(HLocalSet node) { | 
|  | use(node.value); | 
|  | assignVariable( | 
|  | variableNames.getName(node.receiver)!, pop(), node.sourceInformation); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitInvokeExternal(HInvokeExternal node) { | 
|  | FunctionEntity target = node.element; | 
|  | List<HInstruction> inputs = node.inputs; | 
|  |  | 
|  | assert(_nativeData.isNativeMember(target), 'non-native target: $node'); | 
|  |  | 
|  | String? targetName = _nativeData.hasFixedBackendName(target) | 
|  | ? _nativeData.getFixedBackendName(target) | 
|  | : target.name; | 
|  |  | 
|  | void invokeWithJavaScriptReceiver(js.Expression receiverExpression) { | 
|  | // JS-interop target names can be paths ("a.b"), so we parse them to | 
|  | // re-associate the property accesses ("#.a.b" is `dot(dot(#,'a'),'b')`). | 
|  | // | 
|  | // Native target names are simple identifiers, so re-parsing is not | 
|  | // necessary, but it is simpler to use the same code. | 
|  | String template; | 
|  | List<Object> templateInputs; | 
|  | if (target.isGetter) { | 
|  | template = '#.$targetName'; | 
|  | templateInputs = [receiverExpression]; | 
|  | } else if (target.isSetter) { | 
|  | assert(inputs.length == (target.isInstanceMember ? 2 : 1)); | 
|  | use(inputs.last); | 
|  | template = '#.$targetName = #'; | 
|  | templateInputs = [receiverExpression, pop()]; | 
|  | } else { | 
|  | var arguments = | 
|  | visitArguments(inputs, start: target.isInstanceMember ? 1 : 0); | 
|  | template = target is ConstructorEntity | 
|  | ? 'new #.$targetName(#)' | 
|  | : '#.$targetName(#)'; | 
|  | templateInputs = [receiverExpression, arguments]; | 
|  | } | 
|  | js.Expression expression = js.js | 
|  | .uncachedExpressionTemplate(template) | 
|  | .instantiateExpression(templateInputs); | 
|  | push(expression.withSourceInformation(node.sourceInformation)); | 
|  | _registry.registerNativeMethod(target); | 
|  | } | 
|  |  | 
|  | if (_nativeData.isJsInteropMember(target)) { | 
|  | if (target.isStatic || target.isTopLevel || target is ConstructorEntity) { | 
|  | String path = _nativeData.getFixedBackendMethodPath(target)!; | 
|  | js.Expression pathExpression = | 
|  | js.js.uncachedExpressionTemplate(path).instantiateExpression([]); | 
|  | invokeWithJavaScriptReceiver(pathExpression); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (_nativeData.isNativeMember(target)) { | 
|  | _registry.registerNativeBehavior(node.nativeBehavior!); | 
|  | if (target.isInstanceMember) { | 
|  | HInstruction receiver = inputs.first; | 
|  | use(receiver); | 
|  | invokeWithJavaScriptReceiver(pop()); | 
|  | return; | 
|  | } | 
|  | if (target.isStatic || target.isTopLevel) { | 
|  | var arguments = visitArguments(inputs, start: 0); | 
|  | js.Expression targetExpression = js.js | 
|  | .uncachedExpressionTemplate(targetName!) | 
|  | .instantiateExpression([]); | 
|  | js.Expression expression; | 
|  | if (target.isGetter) { | 
|  | expression = targetExpression; | 
|  | } else if (target.isSetter) { | 
|  | expression = js.js('# = #', [targetExpression, inputs.single]); | 
|  | } else { | 
|  | assert(target.isFunction); | 
|  | expression = js.js('#(#)', [targetExpression, arguments]); | 
|  | } | 
|  | push(expression.withSourceInformation(node.sourceInformation)); | 
|  | _registry.registerNativeMethod(target); | 
|  | return; | 
|  | } | 
|  |  | 
|  | failedAt(node, 'codegen not implemented (non-instance-member): $node'); | 
|  | } | 
|  | failedAt(node, 'unexpected target: $node'); | 
|  | } | 
|  |  | 
|  | void registerForeignTypes(HForeign node) { | 
|  | NativeBehavior? nativeBehavior = node.nativeBehavior; | 
|  | if (nativeBehavior == null) return; | 
|  | _registry.registerNativeBehavior(nativeBehavior); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitForeignCode(HForeignCode node) { | 
|  | List<HInstruction> inputs = node.inputs; | 
|  | if (node.isJsStatement()) { | 
|  | List<js.Expression> interpolatedExpressions = []; | 
|  | for (int i = 0; i < inputs.length; i++) { | 
|  | use(inputs[i]); | 
|  | interpolatedExpressions.add(pop()); | 
|  | } | 
|  | pushStatement(node.codeTemplate | 
|  | .instantiateStatement(interpolatedExpressions) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } else { | 
|  | List<js.Expression> interpolatedExpressions = []; | 
|  | for (int i = 0; i < inputs.length; i++) { | 
|  | use(inputs[i]); | 
|  | interpolatedExpressions.add(pop()); | 
|  | } | 
|  | push(node.codeTemplate | 
|  | .instantiateExpression(interpolatedExpressions) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | // TODO(sra): Tell world.nativeEnqueuer about the types created here. | 
|  | registerForeignTypes(node); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitCreate(HCreate node) { | 
|  | js.Expression jsClassReference = _emitter.constructorAccess(node.element); | 
|  | List<js.Expression> arguments = visitArguments(node.inputs, start: 0); | 
|  | push(js.New(jsClassReference, arguments) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | // We also use HCreate to instantiate closure classes that belong to | 
|  | // function expressions. We have to register their use here, as otherwise | 
|  | // code for them might not be emitted. | 
|  | if (node.element.isClosure) { | 
|  | _registry | 
|  | // ignore:deprecated_member_use_from_same_package | 
|  | .registerInstantiatedClass(node.element); | 
|  | } | 
|  | if (node.element is JRecordClass) { | 
|  | _registry | 
|  | // ignore:deprecated_member_use_from_same_package | 
|  | .registerInstantiatedClass(node.element); | 
|  | } | 
|  | node.instantiatedTypes?.forEach(_registry.registerInstantiation); | 
|  | final callMethod = node.callMethod; | 
|  | if (callMethod != null) { | 
|  | _registry.registerStaticUse(StaticUse.implicitInvoke(callMethod)); | 
|  | _registry.registerInstantiatedClosure(callMethod); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitCreateBox(HCreateBox node) { | 
|  | push(js.ObjectInitializer([])); | 
|  | } | 
|  |  | 
|  | js.Expression newLiteralBool( | 
|  | bool value, SourceInformation? sourceInformation) { | 
|  | if (_options.enableMinification) { | 
|  | // Use !0 for true, !1 for false. | 
|  | return js.Prefix("!", js.LiteralNumber(value ? "0" : "1")) | 
|  | .withSourceInformation(sourceInformation); | 
|  | } else { | 
|  | return js.LiteralBool(value).withSourceInformation(sourceInformation); | 
|  | } | 
|  | } | 
|  |  | 
|  | void generateConstant( | 
|  | ConstantValue constant, SourceInformation? sourceInformation) { | 
|  | js.Expression expression = _emitter.constantReference(constant); | 
|  | if (!constant.isDummy) { | 
|  | // TODO(johnniwinther): Support source information on synthetic constants. | 
|  | expression = expression.withSourceInformation(sourceInformation); | 
|  | } | 
|  | push(expression); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitConstant(HConstant node) { | 
|  | assert(isGenerateAtUseSite(node)); | 
|  | generateConstant(node.constant, node.sourceInformation); | 
|  |  | 
|  | _registry.registerConstantUse(ConstantUse.literal(node.constant)); | 
|  | ConstantValue constant = node.constant; | 
|  | if (constant is TypeConstantValue) { | 
|  | _registry | 
|  | .registerTypeUse(TypeUse.constTypeLiteral(constant.representedType)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitNot(HNot node) { | 
|  | assert(node.inputs.length == 1); | 
|  | generateNot(node.inputs[0], node.sourceInformation); | 
|  | } | 
|  |  | 
|  | static String mapRelationalOperator(String op, bool inverse) { | 
|  | Map<String, String> inverseOperator = const { | 
|  | "==": "!=", | 
|  | "!=": "==", | 
|  | "===": "!==", | 
|  | "!==": "===", | 
|  | "<": ">=", | 
|  | "<=": ">", | 
|  | ">": "<=", | 
|  | ">=": "<" | 
|  | }; | 
|  | return inverse ? inverseOperator[op]! : op; | 
|  | } | 
|  |  | 
|  | void generateNot(HInstruction input, SourceInformation? sourceInformation) { | 
|  | bool canGenerateOptimizedComparison(HRelational relational) { | 
|  | HInstruction left = relational.left; | 
|  | HInstruction right = relational.right; | 
|  | if (left.isStringOrNull(_abstractValueDomain).isDefinitelyTrue && | 
|  | right.isStringOrNull(_abstractValueDomain).isDefinitelyTrue) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // This optimization doesn't work for NaN, so we only do it if the | 
|  | // type is known to be an integer. | 
|  | return left.isInteger(_abstractValueDomain).isDefinitelyTrue && | 
|  | right.isInteger(_abstractValueDomain).isDefinitelyTrue; | 
|  | } | 
|  |  | 
|  | bool handledBySpecialCase = false; | 
|  | if (isGenerateAtUseSite(input)) { | 
|  | handledBySpecialCase = true; | 
|  | if (input is HIsTestSimple) { | 
|  | _emitIsTestSimple(input, negative: true); | 
|  | } else if (input is HNot) { | 
|  | use(input.inputs[0]); | 
|  | } else if (input is HIdentity) { | 
|  | emitIdentityComparison(input, sourceInformation, inverse: true); | 
|  | } else if (input is HIsLateSentinel) { | 
|  | _emitIsLateSentinel(input, sourceInformation, inverse: true); | 
|  | } else if (input is HRelational && | 
|  | canGenerateOptimizedComparison(input)) { | 
|  | constant_system.BinaryOperation operation = input.operation(); | 
|  | String op = mapRelationalOperator(operation.name, true); | 
|  | handleInvokeBinary(input, op, sourceInformation); | 
|  | } else { | 
|  | handledBySpecialCase = false; | 
|  | } | 
|  | } | 
|  | if (!handledBySpecialCase) { | 
|  | use(input); | 
|  | push(js.Prefix("!", pop()).withSourceInformation(sourceInformation)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitParameterValue(HParameterValue node) { | 
|  | assert(!isGenerateAtUseSite(node)); | 
|  | String name = variableNames.getName(node)!; | 
|  | parameters.add(js.Parameter(name)); | 
|  | declaredLocals.add(name); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLocalValue(HLocalValue node) { | 
|  | assert(!isGenerateAtUseSite(node)); | 
|  | String name = variableNames.getName(node)!; | 
|  | collectedVariableDeclarations.add(name); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitPhi(HPhi node) { | 
|  | // This method is only called for phis that are generated at use | 
|  | // site. A phi can be generated at use site only if it is the | 
|  | // result of a control flow operation. | 
|  | HBasicBlock ifBlock = node.block!.dominator!; | 
|  | assert(controlFlowOperators.contains(ifBlock.last)); | 
|  | HInstruction input = ifBlock.last!.inputs[0]; | 
|  | if (input.isConstantFalse()) { | 
|  | use(node.inputs[1]); | 
|  | } else if (input.isConstantTrue()) { | 
|  | use(node.inputs[0]); | 
|  | } else if (node.inputs[1].isConstantBoolean()) { | 
|  | String operation = node.inputs[1].isConstantFalse() ? '&&' : '||'; | 
|  | if (operation == '||') { | 
|  | generateNot(input, input.sourceInformation); | 
|  | } else { | 
|  | use(input); | 
|  | } | 
|  | js.Expression left = pop(); | 
|  | use(node.inputs[0]); | 
|  | push(js.Binary(operation, left, pop())); | 
|  | } else { | 
|  | use(input); | 
|  | js.Expression test = pop(); | 
|  | use(node.inputs[0]); | 
|  | js.Expression then = pop(); | 
|  | use(node.inputs[1]); | 
|  | push(js.Conditional(test, then, pop())); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitReturn(HReturn node) { | 
|  | if (node.inputs.isEmpty) { | 
|  | pushStatement(js.Return().withSourceInformation(node.sourceInformation)); | 
|  | } else { | 
|  | use(node.inputs.single); | 
|  | pushStatement( | 
|  | js.Return(pop()).withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitThis(HThis node) { | 
|  | push(js.This()); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitThrow(HThrow node) { | 
|  | SourceInformation? sourceInformation = node.sourceInformation; | 
|  | if (node.isRethrow) { | 
|  | use(node.inputs[0]); | 
|  | pushStatement(js.Throw(pop()).withSourceInformation(sourceInformation)); | 
|  | } else { | 
|  | use(node.inputs[0]); | 
|  | _pushCallStatic( | 
|  | _commonElements.wrapExceptionHelper, [pop()], sourceInformation); | 
|  | pushStatement(js.Throw(pop()).withSourceInformation(sourceInformation)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitAwait(HAwait node) { | 
|  | use(node.inputs[0]); | 
|  | push(js.Await(pop()).withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitYield(HYield node) { | 
|  | use(node.inputs[0]); | 
|  | pushStatement(js.DartYield(pop(), node.hasStar) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitRangeConversion(HRangeConversion node) { | 
|  | // Range conversion instructions are removed by the value range | 
|  | // analyzer. | 
|  | assert(false); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitBoundsCheck(HBoundsCheck node) { | 
|  | // TODO(ngeoffray): Separate the two checks of the bounds check, so, | 
|  | // e.g., the zero checks can be shared if possible. | 
|  |  | 
|  | // If the checks always succeeds, we would have removed the bounds check | 
|  | // completely. | 
|  | assert(node.staticChecks != StaticBoundsChecks.alwaysTrue); | 
|  |  | 
|  | if (node.staticChecks == StaticBoundsChecks.alwaysFalse) { | 
|  | _pushThrowWithHelper(_commonElements.throwIndexOutOfRangeException, | 
|  | [node.array, node.reportedIndex], | 
|  | sourceInformation: node.sourceInformation); | 
|  | return; | 
|  | } | 
|  |  | 
|  | HInstruction index = node.index; | 
|  |  | 
|  | // Generate a test for out-of-bounds, either under or over the range.  NaN | 
|  | // values can creep in, and comparisons on NaN are false, so | 
|  | // | 
|  | //     if (i < 0) throw ... | 
|  | // | 
|  | // will fail to throw if `i` is NaN. The test | 
|  | // | 
|  | //     if (!(i >= 0)) ... | 
|  | // | 
|  | // is 'NaN-safe'. | 
|  |  | 
|  | // TODO(sra): Better analysis of possible NaN input. | 
|  | bool indexCanBeNaN = !_isDefinitelyNotNaN(index); | 
|  |  | 
|  | js.Expression? under; | 
|  | js.Expression? over; | 
|  |  | 
|  | if (index.isInteger(_abstractValueDomain).isPotentiallyFalse) { | 
|  | // Combined domain check and low bound check. `a >>> 0 !== a` is true for | 
|  | // `null`, `undefined`, `NaN`, and non-integral number and any integral | 
|  | // number outside the 32-bit unsigned range. | 
|  | use(index); | 
|  | js.Expression jsIndex = pop(); | 
|  | // This test is 'NaN-safe' since `a!==b` is the same as `!(a===b)`. | 
|  | under = js.js("# >>> 0 !== #", [jsIndex, jsIndex]); | 
|  | indexCanBeNaN = false; | 
|  | } else if (node.staticChecks != StaticBoundsChecks.alwaysAboveZero) { | 
|  | use(index); | 
|  | // The index must be an `int`, otherwise we could have used the combined | 
|  | // check above. | 
|  | if (indexCanBeNaN) { | 
|  | under = js.js('!(# >= 0)', pop()); | 
|  | } else { | 
|  | under = js.js('# < 0', pop()); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (node.staticChecks != StaticBoundsChecks.alwaysBelowLength) { | 
|  | use(index); | 
|  | js.Expression jsIndex = pop(); | 
|  | use(node.length); | 
|  | js.Expression jsLength = pop(); | 
|  | if (indexCanBeNaN) { | 
|  | over = js.js('!(# < #)', [jsIndex, jsLength]); | 
|  | } else { | 
|  | over = js.js('# >= #', [jsIndex, jsLength]); | 
|  | } | 
|  | } | 
|  |  | 
|  | assert(over != null || under != null); | 
|  | js.Expression underOver; | 
|  | if (under == null) { | 
|  | underOver = over!; | 
|  | } else if (over == null) { | 
|  | underOver = under; | 
|  | } else { | 
|  | if (under is js.Prefix && | 
|  | under.op == '!' && | 
|  | over is js.Prefix && | 
|  | over.op == '!') { | 
|  | // De Morgans law:  !(a) || !(b)  <->  !(a && b) | 
|  | underOver = js.js('!(# && #)', [under.argument, over.argument]); | 
|  | } else { | 
|  | underOver = js.Binary('||', under, over); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Generate the call to the 'throw' helper in a block in case it needs | 
|  | // multiple statements. | 
|  | js.Block thenBody = js.Block.empty(); | 
|  | js.Block oldContainer = currentContainer; | 
|  | currentContainer = thenBody; | 
|  | _pushThrowWithHelper(_commonElements.throwIndexOutOfRangeException, | 
|  | [node.array, node.reportedIndex], | 
|  | sourceInformation: node.sourceInformation); | 
|  | currentContainer = oldContainer; | 
|  | pushStatement(js.If.noElse(underOver, unwrapStatement(thenBody)) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | bool _isDefinitelyNotNaN(HInstruction node) { | 
|  | if (node is HConstant) { | 
|  | if (node.isInteger(_abstractValueDomain).isDefinitelyTrue) return true; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // TODO(sra): Use some form of dataflow. Starting from a small number you | 
|  | // can add or subtract a small number any number of times and still have a | 
|  | // finite number. Many operations, produce small numbers (some constants, | 
|  | // HGetLength, HBitAnd). This could be used to determine that most loop | 
|  | // indexes are finite and thus not NaN. | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void _pushThrowWithHelper(FunctionEntity helper, List<HInstruction> inputs, | 
|  | {SourceInformation? sourceInformation}) { | 
|  | List<js.Expression> arguments = []; | 
|  | for (final input in inputs) { | 
|  | use(input); | 
|  | arguments.add(pop()); | 
|  | } | 
|  | _pushCallStatic(helper, arguments, sourceInformation); | 
|  | // BUG(4906): Using throw/return here adds to the size of the generated code | 
|  | // but it has the advantage of explicitly telling the JS engine that | 
|  | // this code path will terminate abruptly. Needs more work. | 
|  | pushStatement(js.Return(pop()).withSourceInformation(sourceInformation)); | 
|  | } | 
|  |  | 
|  | void _pushCallStatic(FunctionEntity target, List<js.Expression> arguments, | 
|  | SourceInformation? sourceInformation) { | 
|  | _registry.registerStaticUse(StaticUse.staticInvoke( | 
|  | target, CallStructure.unnamed(arguments.length))); | 
|  | js.Expression jsTarget = _emitter.staticFunctionAccess(target); | 
|  | js.Call call = js.Call(jsTarget, List.of(arguments, growable: false), | 
|  | sourceInformation: sourceInformation); | 
|  | push(call); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitThrowExpression(HThrowExpression node) { | 
|  | use(node.inputs[0]); | 
|  | _pushCallStatic( | 
|  | _commonElements.throwExpressionHelper, [pop()], node.sourceInformation); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitSwitch(HSwitch node) { | 
|  | // Switches are handled using [visitSwitchInfo]. | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitStatic(HStatic node) { | 
|  | MemberEntity element = node.element; | 
|  | if (element is FunctionEntity) { | 
|  | // TODO(sra): Static tear-offs should be constants. | 
|  | push(_emitter | 
|  | .staticClosureAccess(element) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | _registry.registerStaticUse(StaticUse.staticTearOff(element)); | 
|  | } else if (element is FieldEntity) { | 
|  | push(_emitter | 
|  | .staticFieldAccess(element) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | _registry.registerStaticUse(StaticUse.staticGet(element)); | 
|  | } else { | 
|  | failedAt(node, 'HStatic must be a FieldEntity or FunctionEntity'); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLazyStatic(HLazyStatic node) { | 
|  | FieldEntity element = node.element; | 
|  | _registry.registerStaticUse(StaticUse.staticInit(element)); | 
|  | js.Expression lazyGetter = _emitter.isolateLazyInitializerAccess(element); | 
|  | js.Call call = | 
|  | js.Call(lazyGetter, [], sourceInformation: node.sourceInformation); | 
|  | push(call); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitStaticStore(HStaticStore node) { | 
|  | _registry.registerStaticUse(StaticUse.staticSet(node.element)); | 
|  | js.Expression variable = _emitter.staticFieldAccess(node.element); | 
|  | use(node.inputs[0]); | 
|  | push(js.Assignment(variable, pop()) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitStringConcat(HStringConcat node) { | 
|  | use(node.left); | 
|  | js.Expression jsLeft = pop(); | 
|  | use(node.right); | 
|  | push(js.Binary('+', jsLeft, pop()) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitStringify(HStringify node) { | 
|  | HInstruction input = node.inputs.first; | 
|  | if (input.isString(_abstractValueDomain).isDefinitelyTrue) { | 
|  | use(input); | 
|  | } else if (input.isInteger(_abstractValueDomain).isDefinitelyTrue || | 
|  | input.isBoolean(_abstractValueDomain).isDefinitelyTrue) { | 
|  | // JavaScript's + operator with a string for the left operand will convert | 
|  | // the right operand to a string, and the conversion result is correct. | 
|  | use(input); | 
|  | if (node.usedBy.length == 1 && | 
|  | node.usedBy[0] is HStringConcat && | 
|  | node.usedBy[0].inputs[1] == node) { | 
|  | // The context is already <string> + value. | 
|  | } else { | 
|  | // Force an empty string for the first operand. | 
|  | push(js.Binary('+', js.string(""), pop()) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  | } else { | 
|  | FunctionEntity convertToString = | 
|  | _commonElements.stringInterpolationHelper; | 
|  | _registry.registerStaticUse( | 
|  | StaticUse.staticInvoke(convertToString, CallStructure.ONE_ARG)); | 
|  | js.Expression jsHelper = _emitter.staticFunctionAccess(convertToString); | 
|  | use(input); | 
|  | push(js.Call(jsHelper, [pop()], | 
|  | sourceInformation: node.sourceInformation)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLiteralList(HLiteralList node) { | 
|  | _registry | 
|  | // ignore:deprecated_member_use_from_same_package | 
|  | .registerInstantiatedClass(_commonElements.listClass); | 
|  | generateArrayLiteral(node); | 
|  | } | 
|  |  | 
|  | void generateArrayLiteral(HLiteralList node) { | 
|  | List<js.Expression> elements = node.inputs.map((HInstruction input) { | 
|  | use(input); | 
|  | return pop(); | 
|  | }).toList(); | 
|  | push(js.ArrayInitializer(elements) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitIndex(HIndex node) { | 
|  | _metrics.countHIndex++; | 
|  | use(node.receiver); | 
|  | js.Expression receiver = pop(); | 
|  | use(node.index); | 
|  | push(js.PropertyAccess(receiver, pop()) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitIndexAssign(HIndexAssign node) { | 
|  | use(node.receiver); | 
|  | js.Expression receiver = pop(); | 
|  | use(node.index); | 
|  | js.Expression index = pop(); | 
|  | use(node.value); | 
|  | push(js.Assignment(js.PropertyAccess(receiver, index), pop()) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitCharCodeAt(HCharCodeAt node) { | 
|  | use(node.receiver); | 
|  | js.Expression receiver = pop(); | 
|  | use(node.index); | 
|  | push(js.js('#.charCodeAt(#)', [receiver, pop()]).withSourceInformation( | 
|  | node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | void checkTypeOf(HInstruction input, String cmp, String typeName, | 
|  | SourceInformation? sourceInformation) { | 
|  | use(input); | 
|  | js.Expression typeOf = js.Prefix("typeof", pop()); | 
|  | push(js.Binary(cmp, typeOf, js.string(typeName)) | 
|  | .withSourceInformation(sourceInformation)); | 
|  | } | 
|  |  | 
|  | void checkNum( | 
|  | HInstruction input, String cmp, SourceInformation? sourceInformation) { | 
|  | return checkTypeOf(input, cmp, 'number', sourceInformation); | 
|  | } | 
|  |  | 
|  | void checkBool( | 
|  | HInstruction input, String cmp, SourceInformation? sourceInformation) { | 
|  | return checkTypeOf(input, cmp, 'boolean', sourceInformation); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitPrimitiveCheck(HPrimitiveCheck node) { | 
|  | js.Expression test = _generateReceiverOrArgumentTypeTest(node); | 
|  | js.Block oldContainer = currentContainer; | 
|  | js.Block body = currentContainer = js.Block.empty(); | 
|  | final sourceInformation = node.sourceInformation; | 
|  | switch (node.kind) { | 
|  | case PrimitiveCheckKind.argumentType: | 
|  | use(node.checkedInput); | 
|  | _pushCallStatic(_commonElements.throwIllegalArgumentException, [pop()], | 
|  | node.sourceInformation); | 
|  | pushStatement( | 
|  | js.Return(pop()).withSourceInformation(sourceInformation)); | 
|  | case PrimitiveCheckKind.receiverType: | 
|  | use(node.checkedInput); | 
|  | js.Name methodName = | 
|  | _namer.invocationName(node.receiverTypeCheckSelector!); | 
|  | js.Expression call = js.propertyCall( | 
|  | pop(), methodName, []).withSourceInformation(sourceInformation); | 
|  | pushStatement(js.Return(call).withSourceInformation(sourceInformation)); | 
|  | } | 
|  | currentContainer = oldContainer; | 
|  | final then = unwrapStatement(body); | 
|  | pushStatement( | 
|  | js.If.noElse(test, then).withSourceInformation(sourceInformation)); | 
|  | } | 
|  |  | 
|  | js.Expression _generateReceiverOrArgumentTypeTest(HPrimitiveCheck node) { | 
|  | DartType type = node.typeExpression; | 
|  | HInstruction input = node.checkedInput; | 
|  | AbstractValue checkedType = node.checkedType; | 
|  | // This path is no longer used for indexable primitive types. | 
|  | assert(_abstractValueDomain.isJsIndexable(checkedType).isPotentiallyFalse); | 
|  | // Figure out if it is beneficial to use a null check.  V8 generally prefers | 
|  | // 'typeof' checks, but for integers we cannot compile this test into a | 
|  | // single typeof check so the null check is cheaper. | 
|  | if (type == _commonElements.numType) { | 
|  | // input is !num | 
|  | checkNum(input, '!==', input.sourceInformation); | 
|  | return pop(); | 
|  | } | 
|  | if (type == _commonElements.boolType) { | 
|  | // input is !bool | 
|  | checkBool(input, '!==', input.sourceInformation); | 
|  | return pop(); | 
|  | } | 
|  | throw failedAt(input, 'Unexpected check: $type.'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitBoolConversion(HBoolConversion node) { | 
|  | _registry.registerTypeUse(TypeUse.isCheck(_commonElements.boolType)); | 
|  | CheckedModeHelper helper = const CheckedModeHelper('boolConversionCheck'); | 
|  | StaticUse staticUse = helper.getStaticUse(_commonElements); | 
|  | _registry.registerStaticUse(staticUse); | 
|  | use(node.checkedInput); | 
|  | List<js.Expression> arguments = [pop()]; | 
|  | push(js.Call( | 
|  | _emitter.staticFunctionAccess(staticUse.element as FunctionEntity), | 
|  | arguments) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitNullCheck(HNullCheck node) { | 
|  | use(node.checkedInput); | 
|  | // We access a JavaScript member 'toString' as all objects besides `null` | 
|  | // and `undefined` have it. | 
|  |  | 
|  | // TODO(35996): Pick a shorter field. The instruction has a selector and | 
|  | // field that could be used here to pick the 'right' field. The 'field' | 
|  | // might need to be propagated to an earlier HNullCheck. JSArray and | 
|  | // JSString have 'length'. | 
|  | pushStatement(js.ExpressionStatement( | 
|  | js.PropertyAccess.field(pop(), 'toString') | 
|  | .withSourceInformation(node.sourceInformation))); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLateReadCheck(HLateReadCheck node) { | 
|  | // We generate code roughly equivalent to invoking: | 
|  | // | 
|  | // T _lateReadCheck<T>(T value, String name) { | 
|  | //   if (isSentinel(value)) throw LateError.fieldNI(name); | 
|  | //   return value; | 
|  | // } | 
|  |  | 
|  | assert(!node.isRedundant(_closedWorld)); | 
|  |  | 
|  | final sourceInformation = node.sourceInformation; | 
|  |  | 
|  | _emitIsLateSentinel(node.checkedInput, sourceInformation); | 
|  | final condition = pop(); | 
|  |  | 
|  | if (node.hasName) { | 
|  | use(node.name); | 
|  | _pushCallStatic( | 
|  | _commonElements.throwLateFieldNI, [pop()], sourceInformation); | 
|  | } else { | 
|  | _pushCallStatic( | 
|  | _commonElements.throwUnnamedLateFieldNI, const [], sourceInformation); | 
|  | } | 
|  |  | 
|  | // `condition && helper();` is smaller than `if (condition) helper();`. | 
|  | pushStatement(js.js.statement('# && #;', | 
|  | [condition, pop()]).withSourceInformation(sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLateWriteOnceCheck(HLateWriteOnceCheck node) { | 
|  | // We generate code roughly equivalent to invoking: | 
|  | // | 
|  | // void _lateWriteOnceCheck(Object? value, String name) { | 
|  | //   if (!isSentinel(value)) throw LateError.fieldAI(name); | 
|  | // } | 
|  |  | 
|  | assert(!node.isRedundant(_closedWorld)); | 
|  |  | 
|  | final sourceInformation = node.sourceInformation; | 
|  | _emitIsLateSentinel(node.checkedInput, sourceInformation, inverse: true); | 
|  | final condition = pop(); | 
|  |  | 
|  | if (node.hasName) { | 
|  | use(node.name); | 
|  | _pushCallStatic( | 
|  | _commonElements.throwLateFieldAI, [pop()], sourceInformation); | 
|  | } else { | 
|  | _pushCallStatic( | 
|  | _commonElements.throwUnnamedLateFieldAI, [], sourceInformation); | 
|  | } | 
|  |  | 
|  | // `condition && helper();` is smaller than `if (condition) helper();`. | 
|  | pushStatement(js.js.statement('# && #;', | 
|  | [condition, pop()]).withSourceInformation(sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLateInitializeOnceCheck(HLateInitializeOnceCheck node) { | 
|  | // We generate code roughly equivalent to invoking: | 
|  | // | 
|  | // void _lateInitializeOnceCheck(Object? value, String name) { | 
|  | //   if (!isSentinel(value)) throw LateError.fieldADI(name); | 
|  | // } | 
|  |  | 
|  | assert(!node.isRedundant(_closedWorld)); | 
|  |  | 
|  | final sourceInformation = node.sourceInformation; | 
|  | _emitIsLateSentinel(node.checkedInput, sourceInformation, inverse: true); | 
|  | final condition = pop(); | 
|  |  | 
|  | if (node.hasName) { | 
|  | use(node.name); | 
|  | _pushCallStatic( | 
|  | _commonElements.throwLateFieldADI, [pop()], sourceInformation); | 
|  | } else { | 
|  | _pushCallStatic( | 
|  | _commonElements.throwUnnamedLateFieldADI, [], sourceInformation); | 
|  | } | 
|  |  | 
|  | // `condition && helper();` is smaller than `if (condition) helper();`. | 
|  | pushStatement(js.js.statement('# && #;', | 
|  | [condition, pop()]).withSourceInformation(sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitTypeKnown(HTypeKnown node) { | 
|  | // [HTypeKnown] instructions are removed before generating code. | 
|  | assert(false); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitRef(HRef node) { | 
|  | visit(node.value); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitIsTest(HIsTest node) { | 
|  | _metrics.countHIsTest++; | 
|  | _registry.registerTypeUse(TypeUse.isCheck(node.dartType)); | 
|  |  | 
|  | use(node.typeInput); | 
|  | js.Expression first = pop(); | 
|  | use(node.checkedInput); | 
|  | js.Expression second = pop(); | 
|  |  | 
|  | FieldEntity field = _commonElements.rtiIsField; | 
|  | js.Name name = _namer.instanceFieldPropertyName(field); | 
|  |  | 
|  | push(js.js('#.#(#)', [first, name, second]).withSourceInformation( | 
|  | node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitIsTestSimple(HIsTestSimple node) { | 
|  | _metrics.countHIsTestSimple++; | 
|  | _emitIsTestSimple(node); | 
|  | } | 
|  |  | 
|  | void _emitIsTestSimple(HIsTestSimple node, {bool negative = false}) { | 
|  | use(node.checkedInput); | 
|  | js.Expression value = pop(); | 
|  | String relation = negative ? '!=' : '=='; | 
|  |  | 
|  | js.Expression handleNegative(js.Expression test) => | 
|  | negative ? js.Prefix('!', test) : test; | 
|  |  | 
|  | js.Expression typeof(String type) => | 
|  | js.Binary(relation, js.Prefix('typeof', value), js.string(type)); | 
|  |  | 
|  | js.Expression isTest(FunctionEntity helper) { | 
|  | _registry.registerStaticUse( | 
|  | StaticUse.staticInvoke(helper, CallStructure.ONE_ARG)); | 
|  | js.Expression test = | 
|  | js.Call(_emitter.staticFunctionAccess(helper), [value]); | 
|  | return handleNegative(test); | 
|  | } | 
|  |  | 
|  | late js.Expression test; | 
|  | switch (node.specialization) { | 
|  | case SimpleIsTestSpecialization.isNull: | 
|  | case SimpleIsTestSpecialization.isNotNull: | 
|  | // These cases should be lowered using [HIdentity] during optimization. | 
|  | failedAt(node, 'Missing lowering'); | 
|  |  | 
|  | case SimpleIsTestSpecialization.isString: | 
|  | test = typeof("string"); | 
|  | break; | 
|  |  | 
|  | case SimpleIsTestSpecialization.isBool: | 
|  | test = isTest(_commonElements.specializedIsBool); | 
|  | break; | 
|  |  | 
|  | case SimpleIsTestSpecialization.isNum: | 
|  | test = typeof("number"); | 
|  | break; | 
|  |  | 
|  | case SimpleIsTestSpecialization.isInt: | 
|  | test = isTest(_commonElements.specializedIsInt); | 
|  | break; | 
|  |  | 
|  | case SimpleIsTestSpecialization.isArrayTop: | 
|  | test = handleNegative(js.js('Array.isArray(#)', [value])); | 
|  | break; | 
|  |  | 
|  | case InstanceOfIsTestSpecialization(interfaceType: final type): | 
|  | _registry.registerTypeUse(TypeUse.constructorReference(type)); | 
|  | test = handleNegative(js.js('# instanceof #', | 
|  | [value, _emitter.constructorAccess(type.element)])); | 
|  | } | 
|  | push(test.withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitAsCheck(HAsCheck node) { | 
|  | use(node.typeInput); | 
|  | js.Expression first = pop(); | 
|  | use(node.checkedInput); | 
|  | js.Expression second = pop(); | 
|  |  | 
|  | _registry.registerTypeUse(TypeUse.isCheck(node.checkedTypeExpression)); | 
|  |  | 
|  | FieldEntity field = _commonElements.rtiAsField; | 
|  | js.Name name = _namer.instanceFieldPropertyName(field); | 
|  |  | 
|  | push(js.js('#.#(#)', [first, name, second]).withSourceInformation( | 
|  | node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitAsCheckSimple(HAsCheckSimple node) { | 
|  | use(node.checkedInput); | 
|  | FunctionEntity method = node.method; | 
|  | _registry.registerStaticUse( | 
|  | StaticUse.staticInvoke(method, CallStructure.ONE_ARG)); | 
|  | js.Expression methodAccess = _emitter.staticFunctionAccess(method); | 
|  | push(js.js(r'#(#)', [methodAccess, pop()]).withSourceInformation( | 
|  | node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Never visitSubtypeCheck(HSubtypeCheck node) { | 
|  | throw UnimplementedError('SsaCodeGenerator.visitSubtypeCheck  $node'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLoadType(HLoadType node) { | 
|  | // 'findType' will be called somewhere to initialize the type reference. | 
|  | _registry.registerStaticUse(StaticUse.staticInvoke( | 
|  | _commonElements.findType, CallStructure.ONE_ARG)); | 
|  | TypeReference reference = TypeReference(node.typeExpression); | 
|  | reference.forLazyInitializer = currentGraph.isLazyInitializer; | 
|  | push(reference); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitInstanceEnvironment(HInstanceEnvironment node) { | 
|  | HInstruction input = node.inputs.single; | 
|  | use(input); | 
|  | js.Expression receiver = pop(); | 
|  |  | 
|  | void useRtiField() { | 
|  | push(js.js(r'#.#', [receiver, _namer.rtiFieldJsName])); | 
|  | } | 
|  |  | 
|  | void useHelper(FunctionEntity helper) { | 
|  | _registry.registerStaticUse( | 
|  | StaticUse.staticInvoke(helper, CallStructure.ONE_ARG)); | 
|  | js.Expression helperAccess = _emitter.staticFunctionAccess(helper); | 
|  | push(js.js(r'#(#)', [helperAccess, receiver]).withSourceInformation( | 
|  | node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | // Try to use the 'rti' field, or a specialization of 'instanceType'. | 
|  | AbstractValue receiverMask = node.codegenInputType; | 
|  |  | 
|  | AbstractBool isArray = _abstractValueDomain.isInstanceOf( | 
|  | receiverMask, _commonElements.jsArrayClass); | 
|  |  | 
|  | if (isArray.isDefinitelyTrue) { | 
|  | useHelper(_commonElements.arrayInstanceType); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (isArray.isDefinitelyFalse) { | 
|  | // See if the receiver type narrows the set of classes to ones that all | 
|  | // have a stored type field. | 
|  | // TODO(sra): Currently the only convenient query is [getExactClass]. We | 
|  | // should have a (cached) query to iterate over all the concrete classes | 
|  | // in [receiverMask]. | 
|  | // TODO(sra): Store the context class on the HInstanceEnvironment. This | 
|  | // would allow the subtype classes to be iterated. | 
|  | ClassEntity? receiverClass = | 
|  | _abstractValueDomain.getExactClass(receiverMask); | 
|  | if (receiverClass != null) { | 
|  | if (_closedWorld.rtiNeed.classNeedsTypeArguments(receiverClass)) { | 
|  | useRtiField(); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // If the type is not intercepted and is not a closure, use the 'simple' | 
|  | // helper. | 
|  | if (_abstractValueDomain.isInterceptor(receiverMask).isDefinitelyFalse) { | 
|  | if (_abstractValueDomain | 
|  | .isInstanceOf(receiverMask, _commonElements.closureClass) | 
|  | .isDefinitelyFalse) { | 
|  | useHelper(_commonElements.simpleInstanceType); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | useHelper(_commonElements.instanceType); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitTypeEval(HTypeEval node) { | 
|  | // Call `env._eval("recipe")`. | 
|  | use(node.inputs[0]); | 
|  | js.Expression environment = pop(); | 
|  |  | 
|  | // Instead of generating `env._eval("$n")`, generate appropriate field | 
|  | // accesses where possible. | 
|  | TypeEnvironmentStructure envStructure = node.envStructure; | 
|  | TypeRecipe typeExpression = node.typeExpression; | 
|  | if (envStructure is FullTypeEnvironmentStructure && | 
|  | typeExpression is TypeExpressionRecipe) { | 
|  | final type = typeExpression.type; | 
|  | if (type is TypeVariableType) { | 
|  | int? index = indexTypeVariable( | 
|  | _closedWorld, _rtiSubstitutions, envStructure, type); | 
|  | if (index != null) { | 
|  | assert(index >= 1); | 
|  | List<TypeVariableType> bindings = envStructure.bindings; | 
|  | if (bindings.isNotEmpty) { | 
|  | // If the environment is a binding RTI, we should never index past | 
|  | // its length (i.e. into its base), since in that case, we could | 
|  | // eval against the base directly. | 
|  | assert(index <= bindings.length); | 
|  | } else { | 
|  | // If the environment is an interface RTI, use precomputed fields | 
|  | // for common accesses. | 
|  | if (index == 1) { | 
|  | push(_loadField(environment, _commonElements.rtiPrecomputed1Field, | 
|  | node.sourceInformation)); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | js.Expression rest = _loadField(environment, | 
|  | _commonElements.rtiRestField, node.sourceInformation); | 
|  | push(js.PropertyAccess.indexed(rest, index - 1) | 
|  | .withSourceInformation(node.sourceInformation)); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | RecipeEncoding encoding = _rtiRecipeEncoder.encodeRecipe( | 
|  | _emitter, node.envStructure, node.typeExpression); | 
|  | js.Expression recipe = encoding.recipe; | 
|  |  | 
|  | for (TypeVariableType typeVariable in encoding.typeVariables) { | 
|  | _registry.registerTypeUse(TypeUse.namedTypeVariableNewRti(typeVariable)); | 
|  | } | 
|  |  | 
|  | final method = _commonElements.rtiEvalMethod; | 
|  | Selector selector = Selector.fromElement(method); | 
|  | js.Name methodLiteral = _namer.invocationName(selector); | 
|  | push(js.js('#.#(#)', [ | 
|  | environment, | 
|  | methodLiteral, | 
|  | recipe | 
|  | ]).withSourceInformation(node.sourceInformation)); | 
|  |  | 
|  | _registry.registerStaticUse( | 
|  | StaticUse.directInvoke(method, selector.callStructure, null)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitTypeBind(HTypeBind node) { | 
|  | // Call `env1._bind(env2)`. | 
|  | assert(node.inputs.length == 2); | 
|  | use(node.inputs[0]); | 
|  | js.Expression environment = pop(); | 
|  | use(node.inputs[1]); | 
|  | js.Expression extensions = pop(); | 
|  |  | 
|  | final method = _commonElements.rtiBindMethod; | 
|  | Selector selector = Selector.fromElement(method); | 
|  | js.Name methodLiteral = _namer.invocationName(selector); | 
|  | push(js.js('#.#(#)', [ | 
|  | environment, | 
|  | methodLiteral, | 
|  | extensions | 
|  | ]).withSourceInformation(node.sourceInformation)); | 
|  |  | 
|  | _registry.registerStaticUse( | 
|  | StaticUse.directInvoke(method, selector.callStructure, null)); | 
|  | } | 
|  |  | 
|  | void _emitIsLateSentinel( | 
|  | HInstruction input, SourceInformation? sourceInformation, | 
|  | {bool inverse = false}) { | 
|  | use(input); | 
|  | js.Expression value = pop(); | 
|  | js.Expression sentinel = | 
|  | _emitter.constantReference(LateSentinelConstantValue()); | 
|  | push(js.Binary(mapRelationalOperator('===', inverse), value, sentinel) | 
|  | .withSourceInformation(sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitIsLateSentinel(HIsLateSentinel node) { | 
|  | _metrics.countHIsLateSentinel++; | 
|  | _emitIsLateSentinel(node.inputs.single, node.sourceInformation); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitArrayFlagsGet(HArrayFlagsGet node) { | 
|  | use(node.inputs.single); | 
|  | js.Expression array = pop(); | 
|  | js.Expression flags = | 
|  | js.js(r'#.#', [array, _namer.fixedNames.arrayFlagsPropertyName]); | 
|  | if (isGenerateAtUseSite(node) && node.usedBy.single is HArrayFlagsCheck) { | 
|  | // The enclosing expression will be an immediate `& mask`. | 
|  | push(flags); | 
|  | } else { | 
|  | // The flags are reused, possibly hoisted, so force an `undefined` to be a | 
|  | // small integer once rather than at each check. | 
|  | push(js.js(r'# | 0', flags)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitArrayFlagsSet(HArrayFlagsSet node) { | 
|  | use(node.inputs[0]); | 
|  | js.Expression array = pop(); | 
|  | use(node.inputs[1]); | 
|  | js.Expression arrayFlags = pop(); | 
|  | pushStatement(js.js.statement(r'#.# = #;', [ | 
|  | array, | 
|  | _namer.fixedNames.arrayFlagsPropertyName, | 
|  | arrayFlags | 
|  | ]).withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitArrayFlagsCheck(HArrayFlagsCheck node) { | 
|  | use(node.array); | 
|  | js.Expression array = pop(); | 
|  |  | 
|  | js.Expression? test; | 
|  | if (!node.alwaysThrows()) { | 
|  | use(node.arrayFlags); | 
|  | js.Expression arrayFlags = pop(); | 
|  | use(node.checkFlags); | 
|  | js.Expression checkFlags = pop(); | 
|  | test = js.js('# & #', [arrayFlags, checkFlags]); | 
|  | } | 
|  |  | 
|  | List<js.Expression> arguments = [array]; | 
|  |  | 
|  | if (node.hasOperation) { | 
|  | use(node.operation); | 
|  | arguments.add(pop()); | 
|  | } | 
|  |  | 
|  | if (node.hasVerb) { | 
|  | use(node.verb); | 
|  | arguments.add(pop()); | 
|  | } | 
|  |  | 
|  | _pushCallStatic(_commonElements.throwUnsupportedOperation, arguments, | 
|  | node.sourceInformation); | 
|  |  | 
|  | js.Statement check; | 
|  | if (test == null) { | 
|  | check = js.js.statement('#;', pop()); | 
|  | } else { | 
|  | check = js.js.statement('# && #;', [test, pop()]); | 
|  | } | 
|  |  | 
|  | pushStatement(check.withSourceInformation(node.sourceInformation)); | 
|  | } | 
|  | } |