| // 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(); |
| } |
| } |