|  | // Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | import '../common.dart'; | 
|  | import '../elements/jumps.dart'; | 
|  | import '../io/source_information.dart'; | 
|  |  | 
|  | import 'builder.dart'; | 
|  | import 'locals_handler.dart'; | 
|  | import 'nodes.dart'; | 
|  |  | 
|  | /// A single break/continue instruction. | 
|  | class _JumpHandlerEntry { | 
|  | final HJump jumpInstruction; | 
|  | final LocalsHandler locals; | 
|  | bool isBreak() => jumpInstruction is HBreak; | 
|  | bool isContinue() => jumpInstruction is HContinue; | 
|  | _JumpHandlerEntry(this.jumpInstruction, this.locals); | 
|  | } | 
|  |  | 
|  | abstract class JumpHandler { | 
|  | factory JumpHandler(KernelSsaGraphBuilder builder, JumpTarget target) { | 
|  | return TargetJumpHandler(builder, target); | 
|  | } | 
|  |  | 
|  | void generateBreak( | 
|  | SourceInformation? sourceInformation, [ | 
|  | LabelDefinition? label, | 
|  | ]); | 
|  | void generateContinue( | 
|  | SourceInformation? sourceInformation, [ | 
|  | LabelDefinition? label, | 
|  | ]); | 
|  | void forEachBreak( | 
|  | void Function(HBreak instruction, LocalsHandler locals) action, | 
|  | ); | 
|  | void forEachContinue( | 
|  | void Function(HContinue instruction, LocalsHandler locals) action, | 
|  | ); | 
|  | bool hasAnyContinue(); | 
|  | bool hasAnyBreak(); | 
|  | void close(); | 
|  | JumpTarget? get target; | 
|  | List<LabelDefinition> get labels; | 
|  | } | 
|  |  | 
|  | /// Jump handler used to avoid null checks when a target isn't used as the | 
|  | /// target of a break, and therefore doesn't need a break handler associated | 
|  | /// with it. | 
|  | class NullJumpHandler implements JumpHandler { | 
|  | final DiagnosticReporter reporter; | 
|  |  | 
|  | NullJumpHandler(this.reporter); | 
|  |  | 
|  | @override | 
|  | void generateBreak( | 
|  | SourceInformation? sourceInformation, [ | 
|  | LabelDefinition? label, | 
|  | ]) { | 
|  | reporter.internalError( | 
|  | currentElementSpannable, | 
|  | 'NullJumpHandler.generateBreak should not be called.', | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void generateContinue( | 
|  | SourceInformation? sourceInformation, [ | 
|  | LabelDefinition? label, | 
|  | ]) { | 
|  | reporter.internalError( | 
|  | currentElementSpannable, | 
|  | 'NullJumpHandler.generateContinue should not be called.', | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void forEachBreak(Function ignored) {} | 
|  | @override | 
|  | void forEachContinue(Function ignored) {} | 
|  | @override | 
|  | void close() {} | 
|  | @override | 
|  | bool hasAnyContinue() => false; | 
|  | @override | 
|  | bool hasAnyBreak() => false; | 
|  |  | 
|  | @override | 
|  | List<LabelDefinition> get labels => const []; | 
|  | @override | 
|  | JumpTarget? get target => null; | 
|  | } | 
|  |  | 
|  | /// Jump handler that records breaks until a target block is available. | 
|  | /// | 
|  | /// Breaks are always forward jumps. Continues in loops are implemented as | 
|  | /// breaks of the body. Continues in switches is currently not handled. | 
|  | class TargetJumpHandler implements JumpHandler { | 
|  | final KernelSsaGraphBuilder builder; | 
|  | @override | 
|  | final JumpTarget target; | 
|  | final List<_JumpHandlerEntry> _jumps = []; | 
|  |  | 
|  | TargetJumpHandler(this.builder, this.target) { | 
|  | assert(builder.jumpTargets[target] == null); | 
|  | builder.jumpTargets[target] = this; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void generateBreak( | 
|  | SourceInformation? sourceInformation, [ | 
|  | LabelDefinition? label, | 
|  | ]) { | 
|  | HInstruction breakInstruction; | 
|  | if (label == null) { | 
|  | breakInstruction = HBreak(target, sourceInformation); | 
|  | } else { | 
|  | breakInstruction = HBreak.toLabel(label, sourceInformation); | 
|  | } | 
|  | LocalsHandler locals = LocalsHandler.from(builder.localsHandler); | 
|  | builder.close(breakInstruction as HJump); | 
|  | _jumps.add(_JumpHandlerEntry(breakInstruction, locals)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void generateContinue( | 
|  | SourceInformation? sourceInformation, [ | 
|  | LabelDefinition? label, | 
|  | ]) { | 
|  | HInstruction continueInstruction; | 
|  | if (label == null) { | 
|  | continueInstruction = HContinue(target, sourceInformation); | 
|  | } else { | 
|  | continueInstruction = HContinue.toLabel(label, sourceInformation); | 
|  | // Switch case continue statements must be handled by the | 
|  | // [SwitchCaseJumpHandler]. | 
|  | assert(!label.target.isSwitchCase); | 
|  | } | 
|  | LocalsHandler locals = LocalsHandler.from(builder.localsHandler); | 
|  | builder.close(continueInstruction as HJump); | 
|  | _jumps.add(_JumpHandlerEntry(continueInstruction, locals)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void forEachBreak( | 
|  | void Function(HBreak instruction, LocalsHandler locals) action, | 
|  | ) { | 
|  | for (_JumpHandlerEntry entry in _jumps) { | 
|  | final jumpInstruction = entry.jumpInstruction; | 
|  | if (jumpInstruction is HBreak) action(jumpInstruction, entry.locals); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void forEachContinue( | 
|  | void Function(HContinue instruction, LocalsHandler locals) action, | 
|  | ) { | 
|  | for (_JumpHandlerEntry entry in _jumps) { | 
|  | final jumpInstruction = entry.jumpInstruction; | 
|  | if (jumpInstruction is HContinue) action(jumpInstruction, entry.locals); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | bool hasAnyContinue() { | 
|  | for (_JumpHandlerEntry entry in _jumps) { | 
|  | if (entry.isContinue()) return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @override | 
|  | bool hasAnyBreak() { | 
|  | for (_JumpHandlerEntry entry in _jumps) { | 
|  | if (entry.isBreak()) return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void close() { | 
|  | // The mapping from TargetElement to JumpHandler is no longer needed. | 
|  | builder.jumpTargets.remove(target); | 
|  | } | 
|  |  | 
|  | @override | 
|  | List<LabelDefinition> get labels { | 
|  | List<LabelDefinition>? result; | 
|  | for (LabelDefinition element in target.labels) { | 
|  | result ??= <LabelDefinition>[]; | 
|  | result.add(element); | 
|  | } | 
|  | return result ?? const <LabelDefinition>[]; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Special [JumpHandler] implementation used to handle continue statements | 
|  | /// targeting switch cases. | 
|  | abstract class SwitchCaseJumpHandler extends TargetJumpHandler { | 
|  | /// Map from switch case targets to indices used to encode the flow of the | 
|  | /// switch case loop. | 
|  | final Map<JumpTarget, int> targetIndexMap = {}; | 
|  |  | 
|  | SwitchCaseJumpHandler(super.builder, super.target); | 
|  |  | 
|  | @override | 
|  | void generateBreak( | 
|  | SourceInformation? sourceInformation, [ | 
|  | LabelDefinition? label, | 
|  | ]) { | 
|  | if (label == null) { | 
|  | // Creates a special break instruction for the synthetic loop generated | 
|  | // for a switch statement with continue statements. See | 
|  | // [SsaFromAstMixin.buildComplexSwitchStatement] for detail. | 
|  |  | 
|  | HInstruction breakInstruction = HBreak( | 
|  | target, | 
|  | sourceInformation, | 
|  | breakSwitchContinueLoop: true, | 
|  | ); | 
|  | LocalsHandler locals = LocalsHandler.from(builder.localsHandler); | 
|  | builder.close(breakInstruction as HJump); | 
|  | _jumps.add(_JumpHandlerEntry(breakInstruction, locals)); | 
|  | } else { | 
|  | super.generateBreak(sourceInformation, label); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool isContinueToSwitchCase(LabelDefinition? label) { | 
|  | return label != null && targetIndexMap.containsKey(label.target); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void generateContinue( | 
|  | SourceInformation? sourceInformation, [ | 
|  | LabelDefinition? label, | 
|  | ]) { | 
|  | if (isContinueToSwitchCase(label)) { | 
|  | // Creates the special instructions 'label = i; continue l;' used in | 
|  | // switch statements with continue statements. See | 
|  | // [SsaFromAstMixin.buildComplexSwitchStatement] for detail. | 
|  |  | 
|  | HInstruction value = builder.graph.addConstantInt( | 
|  | targetIndexMap[label!.target]!, | 
|  | builder.closedWorld, | 
|  | ); | 
|  | builder.localsHandler.updateLocal(target, value); | 
|  |  | 
|  | assert(label.target.labels.contains(label)); | 
|  | HInstruction continueInstruction = HContinue(target, sourceInformation); | 
|  | LocalsHandler locals = LocalsHandler.from(builder.localsHandler); | 
|  | builder.close(continueInstruction as HJump); | 
|  | _jumps.add(_JumpHandlerEntry(continueInstruction, locals)); | 
|  | } else { | 
|  | super.generateContinue(sourceInformation, label); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void close() { | 
|  | // The mapping from TargetElement to JumpHandler is no longer needed. | 
|  | for (JumpTarget target in targetIndexMap.keys) { | 
|  | builder.jumpTargets.remove(target); | 
|  | } | 
|  | super.close(); | 
|  | } | 
|  | } |