| // 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. |
| |
| // We might want to change the initialization of HInstruction so that |
| // `HInstruction.inputs` is initialized to `[]` in the field declaration, and |
| // the subclasses add the input instructions to the existing List. This would |
| // guarantee that `HInstruction.inputs` is a monomorphic. Experiments suggest |
| // that this would improve the SSA time by ~2%. The suggestion to use |
| // super-parameters is unhelpful if we do this, so the suggestion is suppressed. |
| // |
| // ignore_for_file: use_super_parameters |
| |
| // ignore: implementation_imports |
| import 'package:front_end/src/api_unstable/dart2js.dart' show Link; |
| |
| import '../closure.dart'; |
| import '../common.dart'; |
| import '../common/elements.dart'; |
| import '../constants/constant_system.dart' as constant_system; |
| import '../constants/values.dart'; |
| import '../diagnostics/spannable_with_entity.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/specialized_checks.dart' show IsTestSpecialization; |
| import '../js_model/js_world.dart' show JClosedWorld; |
| import '../js_model/type_recipe.dart' |
| show TypeEnvironmentStructure, TypeRecipe, TypeExpressionRecipe; |
| import '../native/behavior.dart'; |
| import '../universe/selector.dart' show Selector; |
| import '../universe/side_effects.dart' show SideEffects; |
| import '../util/util.dart'; |
| import 'invoke_dynamic_specializers.dart'; |
| import 'validate.dart'; |
| |
| abstract class HVisitor<R> { |
| R visitAbs(HAbs node); |
| R visitAdd(HAdd node); |
| R visitAwait(HAwait node); |
| R visitBitAnd(HBitAnd node); |
| R visitBitNot(HBitNot node); |
| R visitBitOr(HBitOr node); |
| R visitBitXor(HBitXor node); |
| R visitBoundsCheck(HBoundsCheck node); |
| R visitBreak(HBreak node); |
| R visitCharCodeAt(HCharCodeAt node); |
| R visitConstant(HConstant node); |
| R visitContinue(HContinue node); |
| R visitCreate(HCreate node); |
| R visitCreateBox(HCreateBox node); |
| R visitDivide(HDivide node); |
| R visitEmbeddedGlobalGet(HEmbeddedGlobalGet node); |
| R visitExit(HExit node); |
| R visitExitTry(HExitTry node); |
| R visitFieldGet(HFieldGet node); |
| R visitFieldSet(HFieldSet node); |
| R visitFunctionReference(HFunctionReference node); |
| R visitInvokeExternal(HInvokeExternal node); |
| R visitForeignCode(HForeignCode node); |
| R visitGetLength(HGetLength node); |
| R visitGoto(HGoto node); |
| R visitGreater(HGreater node); |
| R visitGreaterEqual(HGreaterEqual node); |
| R visitIdentity(HIdentity node); |
| R visitIf(HIf node); |
| R visitIndex(HIndex node); |
| R visitIndexAssign(HIndexAssign node); |
| R visitInterceptor(HInterceptor node); |
| R visitInvokeClosure(HInvokeClosure node); |
| R visitInvokeDynamicGetter(HInvokeDynamicGetter node); |
| R visitInvokeDynamicMethod(HInvokeDynamicMethod node); |
| R visitInvokeDynamicSetter(HInvokeDynamicSetter node); |
| R visitInvokeStatic(HInvokeStatic node); |
| R visitInvokeSuper(HInvokeSuper node); |
| R visitInvokeConstructorBody(HInvokeConstructorBody node); |
| R visitInvokeGeneratorBody(HInvokeGeneratorBody node); |
| R visitIsLateSentinel(HIsLateSentinel node); |
| R visitLateValue(HLateValue node); |
| R visitLazyStatic(HLazyStatic node); |
| R visitLess(HLess node); |
| R visitLessEqual(HLessEqual node); |
| R visitLiteralList(HLiteralList node); |
| R visitLocalGet(HLocalGet node); |
| R visitLocalSet(HLocalSet node); |
| R visitLocalValue(HLocalValue node); |
| R visitLoopBranch(HLoopBranch node); |
| R visitMultiply(HMultiply node); |
| R visitNegate(HNegate node); |
| R visitNot(HNot node); |
| R visitOneShotInterceptor(HOneShotInterceptor node); |
| R visitParameterValue(HParameterValue node); |
| R visitPhi(HPhi node); |
| R visitRangeConversion(HRangeConversion node); |
| R visitReadModifyWrite(HReadModifyWrite node); |
| R visitRef(HRef node); |
| R visitRemainder(HRemainder node); |
| R visitReturn(HReturn node); |
| R visitShiftLeft(HShiftLeft node); |
| R visitShiftRight(HShiftRight node); |
| R visitStatic(HStatic node); |
| R visitStaticStore(HStaticStore node); |
| R visitStringConcat(HStringConcat node); |
| R visitStringify(HStringify node); |
| R visitSubtract(HSubtract node); |
| R visitSwitch(HSwitch node); |
| R visitThis(HThis node); |
| R visitThrow(HThrow node); |
| R visitThrowExpression(HThrowExpression node); |
| R visitTruncatingDivide(HTruncatingDivide node); |
| R visitTry(HTry node); |
| R visitPrimitiveCheck(HPrimitiveCheck node); |
| R visitNullCheck(HNullCheck node); |
| R visitLateReadCheck(HLateReadCheck node); |
| R visitLateWriteOnceCheck(HLateWriteOnceCheck node); |
| R visitLateInitializeOnceCheck(HLateInitializeOnceCheck node); |
| R visitTypeKnown(HTypeKnown node); |
| R visitYield(HYield node); |
| |
| // Instructions for 'dart:_rti'. |
| R visitIsTest(HIsTest node); |
| R visitIsTestSimple(HIsTestSimple node); |
| R visitAsCheck(HAsCheck node); |
| R visitAsCheckSimple(HAsCheckSimple node); |
| R visitSubtypeCheck(HSubtypeCheck node); |
| R visitLoadType(HLoadType node); |
| R visitInstanceEnvironment(HInstanceEnvironment node); |
| R visitTypeEval(HTypeEval node); |
| R visitTypeBind(HTypeBind node); |
| |
| R visitArrayFlagsCheck(HArrayFlagsCheck node); |
| R visitArrayFlagsGet(HArrayFlagsGet node); |
| R visitArrayFlagsSet(HArrayFlagsSet node); |
| } |
| |
| abstract class HGraphVisitor { |
| void visitDominatorTree(HGraph graph) { |
| // Recursion free version of: |
| // |
| // void visitBasicBlockAndSuccessors(HBasicBlock block) { |
| // visitBasicBlock(block); |
| // List dominated = block.dominatedBlocks; |
| // for (int i = 0; i < dominated.length; i++) { |
| // visitBasicBlockAndSuccessors(dominated[i]); |
| // } |
| // } |
| // visitBasicBlockAndSuccessors(graph.entry); |
| |
| _Frame? frame = _Frame(null); |
| frame.block = graph.entry; |
| frame.index = 0; |
| |
| visitBasicBlock(frame.block); |
| |
| while (frame != null) { |
| HBasicBlock block = frame.block; |
| int index = frame.index; |
| if (index < block.dominatedBlocks.length) { |
| frame.index = index + 1; |
| frame = frame.next ??= _Frame(frame); |
| frame.block = block.dominatedBlocks[index]; |
| frame.index = 0; |
| visitBasicBlock(frame.block); |
| continue; |
| } |
| frame = frame.previous; |
| } |
| } |
| |
| void visitPostDominatorTree(HGraph graph) { |
| // Recursion-free version of: |
| // |
| // void visitBasicBlockAndSuccessors(HBasicBlock block) { |
| // List dominated = block.dominatedBlocks; |
| // for (int i = dominated.length - 1; i >= 0; i--) { |
| // visitBasicBlockAndSuccessors(dominated[i]); |
| // } |
| // visitBasicBlock(block); |
| // } |
| // visitBasicBlockAndSuccessors(graph.entry); |
| |
| _Frame? frame = _Frame(null); |
| frame.block = graph.entry; |
| frame.index = frame.block.dominatedBlocks.length; |
| |
| while (frame != null) { |
| HBasicBlock block = frame.block; |
| int index = frame.index; |
| if (index > 0) { |
| frame.index = index - 1; |
| frame = frame.next ??= _Frame(frame); |
| frame.block = block.dominatedBlocks[index - 1]; |
| frame.index = frame.block.dominatedBlocks.length; |
| continue; |
| } |
| visitBasicBlock(block); |
| frame = frame.previous; |
| } |
| } |
| |
| void visitBasicBlock(HBasicBlock block); |
| } |
| |
| class _Frame { |
| final _Frame? previous; |
| _Frame? next; |
| late HBasicBlock block; |
| late int index; |
| _Frame(this.previous); |
| } |
| |
| abstract class HInstructionVisitor extends HGraphVisitor { |
| HBasicBlock? currentBlock; |
| |
| void visitInstruction(HInstruction node); |
| |
| @override |
| void visitBasicBlock(HBasicBlock node) { |
| void visitInstructionList(HInstructionList list) { |
| for ( |
| var instruction = list.first; |
| instruction != null; |
| instruction = instruction.next |
| ) { |
| visitInstruction(instruction); |
| assert(instruction.next != list.first); |
| } |
| } |
| |
| currentBlock = node; |
| visitInstructionList(node); |
| } |
| } |
| |
| class HGraph { |
| late MemberEntity element; // Used for debug printing. |
| late HBasicBlock entry; |
| late HBasicBlock exit; |
| HThis? thisInstruction; |
| |
| /// `true` if this graph should be transformed by a sync*/async/async* |
| /// rewrite. |
| bool needsAsyncRewrite = false; |
| |
| /// If this function requires an async rewrite, this is the element type of |
| /// the generator. |
| DartType? asyncElementType; |
| |
| /// Receiver parameter, set for methods using interceptor calling convention. |
| HParameterValue? explicitReceiverParameter; |
| bool isRecursiveMethod = false; |
| bool calledInLoop = false; |
| bool isLazyInitializer = false; |
| |
| final List<HBasicBlock> blocks = []; |
| |
| /// Nodes containing list allocations for which there is a known fixed length. |
| // TODO(sigmund,sra): consider not storing this explicitly here (e.g. maybe |
| // store it on HInstruction, or maybe this can be computed on demand). |
| final Set<HInstruction> allocatedFixedLists = {}; |
| |
| /// SourceInformation for the 'graph' is the location of the entry |
| SourceInformation? sourceInformation; |
| |
| // We canonicalize all constants used within a graph so we do not |
| // have to worry about them for global value numbering. |
| Map<ConstantValue, HConstant> constants = {}; |
| |
| HGraph() { |
| entry = addNewBlock(); |
| // The exit block will be added later, so it has an id that is |
| // after all others in the system. |
| exit = HBasicBlock(); |
| } |
| |
| void addBlock(HBasicBlock block) { |
| int id = blocks.length; |
| block.id = id; |
| blocks.add(block); |
| assert(identical(blocks[id], block)); |
| } |
| |
| HBasicBlock addNewBlock() { |
| HBasicBlock result = HBasicBlock(); |
| addBlock(result); |
| return result; |
| } |
| |
| HBasicBlock addNewLoopHeaderBlock( |
| JumpTarget? target, |
| List<LabelDefinition> labels, |
| ) { |
| HBasicBlock result = addNewBlock(); |
| result.loopInformation = HLoopInformation(result, target, labels); |
| return result; |
| } |
| |
| HConstant addConstant( |
| ConstantValue constant, |
| JClosedWorld closedWorld, { |
| SourceInformation? sourceInformation, |
| }) { |
| HConstant? result = constants[constant]; |
| // TODO(johnniwinther): Support source information per constant reference. |
| if (result == null) { |
| if (!constant.isConstant) { |
| // We use `null` as the value for invalid constant expressions. |
| constant = const NullConstantValue(); |
| } |
| AbstractValue type = closedWorld.abstractValueDomain |
| .computeAbstractValueForConstant(constant); |
| result = HConstant._internal(constant, type) |
| ..sourceInformation = sourceInformation; |
| entry.addAtExit(result); |
| constants[constant] = result; |
| } else if (result.block == null) { |
| // The constant was not used anymore. |
| entry.addAtExit(result); |
| } |
| return result; |
| } |
| |
| HConstant addDeferredConstant( |
| DeferredGlobalConstantValue constant, |
| SourceInformation? sourceInformation, |
| JClosedWorld closedWorld, |
| ) { |
| return addConstant( |
| constant, |
| closedWorld, |
| sourceInformation: sourceInformation, |
| ); |
| } |
| |
| HConstant addConstantInt(int i, JClosedWorld closedWorld) { |
| return addConstant(constant_system.createIntFromInt(i), closedWorld); |
| } |
| |
| HConstant addConstantIntAsUnsigned(int i, JClosedWorld closedWorld) { |
| return addConstant( |
| constant_system.createInt(BigInt.from(i).toUnsigned(64)), |
| closedWorld, |
| ); |
| } |
| |
| HConstant addConstantDouble(double d, JClosedWorld closedWorld) { |
| return addConstant(constant_system.createDouble(d), closedWorld); |
| } |
| |
| HConstant addConstantString(String str, JClosedWorld closedWorld) { |
| return addConstant(constant_system.createString(str), closedWorld); |
| } |
| |
| HConstant addConstantStringFromName(js.Name name, JClosedWorld closedWorld) { |
| return addConstant(JsNameConstantValue(js.quoteName(name)), closedWorld); |
| } |
| |
| HConstant addConstantBool(bool value, JClosedWorld closedWorld) { |
| return addConstant(constant_system.createBool(value), closedWorld); |
| } |
| |
| HConstant addConstantNull(JClosedWorld closedWorld) { |
| return addConstant(constant_system.createNull(), closedWorld); |
| } |
| |
| HConstant addConstantUnreachable(JClosedWorld closedWorld) { |
| // A constant with an empty type used as the HInstruction of an expression |
| // in an unreachable context. |
| return addConstant(UnreachableConstantValue(), closedWorld); |
| } |
| |
| HConstant addConstantLateSentinel( |
| JClosedWorld closedWorld, { |
| SourceInformation? sourceInformation, |
| }) => addConstant( |
| LateSentinelConstantValue(), |
| closedWorld, |
| sourceInformation: sourceInformation, |
| ); |
| |
| void finalize() { |
| addBlock(exit); |
| exit.open(); |
| exit.close(HExit()); |
| assignDominators(); |
| } |
| |
| void assignDominators() { |
| // Run through the blocks in order of increasing ids so we are |
| // guaranteed that we have computed dominators for all blocks |
| // higher up in the dominator tree. |
| for (int i = 0, length = blocks.length; i < length; i++) { |
| HBasicBlock block = blocks[i]; |
| List<HBasicBlock> predecessors = block.predecessors; |
| if (block.isLoopHeader()) { |
| block.assignCommonDominator(predecessors[0]); |
| } else { |
| for (int j = predecessors.length - 1; j >= 0; j--) { |
| block.assignCommonDominator(predecessors[j]); |
| } |
| } |
| } |
| assignDominatorRanges(); |
| } |
| |
| void assignDominatorRanges() { |
| // DFS walk of dominator tree to assign dfs-in and dfs-out numbers to basic |
| // blocks. A dominator has a dfs-in..dfs-out range that includes the range |
| // of the dominated block. See [HGraphVisitor.visitDominatorTree] for |
| // recursion-free schema. |
| _Frame? frame = _Frame(null); |
| frame.block = entry; |
| frame.index = 0; |
| |
| int dfsNumber = 0; |
| frame.block.dominatorDfsIn = dfsNumber; |
| |
| while (frame != null) { |
| HBasicBlock block = frame.block; |
| int index = frame.index; |
| if (index < block.dominatedBlocks.length) { |
| frame.index = index + 1; |
| frame = frame.next ??= _Frame(frame); |
| frame.block = block.dominatedBlocks[index]; |
| frame.index = 0; |
| frame.block.dominatorDfsIn = ++dfsNumber; |
| continue; |
| } |
| block.dominatorDfsOut = dfsNumber; |
| frame = frame.previous; |
| } |
| } |
| |
| bool isValid() { |
| HValidator validator = HValidator(); |
| validator.visitGraph(this); |
| return validator.isValid; |
| } |
| |
| @override |
| String toString() => 'HGraph($element)'; |
| } |
| |
| class HBaseVisitor<R> extends HGraphVisitor implements HVisitor<R> { |
| HBasicBlock? currentBlock; |
| |
| @override |
| void visitBasicBlock(HBasicBlock node) { |
| currentBlock = node; |
| |
| for ( |
| var instruction = node.first; |
| instruction != null; |
| instruction = instruction.next |
| ) { |
| instruction.accept(this); |
| } |
| } |
| |
| R visitInstruction(HInstruction instruction) => null as R; |
| |
| R visitBinaryArithmetic(HBinaryArithmetic node) => visitInvokeBinary(node); |
| R visitBinaryBitOp(HBinaryBitOp node) => visitInvokeBinary(node); |
| R visitInvoke(HInvoke node) => visitInstruction(node); |
| R visitInvokeBinary(HInvokeBinary node) => visitInstruction(node); |
| R visitInvokeDynamic(HInvokeDynamic node) => visitInvoke(node); |
| R visitInvokeDynamicField(HInvokeDynamicField node) => |
| visitInvokeDynamic(node); |
| R visitInvokeUnary(HInvokeUnary node) => visitInstruction(node); |
| R visitConditionalBranch(HConditionalBranch node) => visitControlFlow(node); |
| R visitControlFlow(HControlFlow node) => visitInstruction(node); |
| R visitFieldAccess(HFieldAccess node) => visitInstruction(node); |
| R visitRelational(HRelational node) => visitInvokeBinary(node); |
| |
| @override |
| R visitAbs(HAbs node) => visitInvokeUnary(node); |
| @override |
| R visitAdd(HAdd node) => visitBinaryArithmetic(node); |
| @override |
| R visitBitAnd(HBitAnd node) => visitBinaryBitOp(node); |
| @override |
| R visitBitNot(HBitNot node) => visitInvokeUnary(node); |
| @override |
| R visitBitOr(HBitOr node) => visitBinaryBitOp(node); |
| @override |
| R visitBitXor(HBitXor node) => visitBinaryBitOp(node); |
| @override |
| R visitBoundsCheck(HBoundsCheck node) => visitCheck(node); |
| @override |
| R visitBreak(HBreak node) => visitJump(node); |
| @override |
| R visitContinue(HContinue node) => visitJump(node); |
| @override |
| R visitCharCodeAt(HCharCodeAt node) => visitInstruction(node); |
| R visitCheck(HCheck node) => visitInstruction(node); |
| @override |
| R visitConstant(HConstant node) => visitInstruction(node); |
| @override |
| R visitCreate(HCreate node) => visitInstruction(node); |
| @override |
| R visitCreateBox(HCreateBox node) => visitInstruction(node); |
| @override |
| R visitDivide(HDivide node) => visitBinaryArithmetic(node); |
| @override |
| R visitEmbeddedGlobalGet(HEmbeddedGlobalGet node) => visitInstruction(node); |
| @override |
| R visitExit(HExit node) => visitControlFlow(node); |
| @override |
| R visitExitTry(HExitTry node) => visitControlFlow(node); |
| @override |
| R visitFieldGet(HFieldGet node) => visitFieldAccess(node); |
| @override |
| R visitFieldSet(HFieldSet node) => visitFieldAccess(node); |
| @override |
| R visitFunctionReference(HFunctionReference node) => visitInstruction(node); |
| @override |
| R visitInvokeExternal(HInvokeExternal node) => visitInstruction(node); |
| @override |
| R visitForeignCode(HForeignCode node) => visitInstruction(node); |
| @override |
| R visitGetLength(HGetLength node) => visitInstruction(node); |
| @override |
| R visitGoto(HGoto node) => visitControlFlow(node); |
| @override |
| R visitGreater(HGreater node) => visitRelational(node); |
| @override |
| R visitGreaterEqual(HGreaterEqual node) => visitRelational(node); |
| @override |
| R visitIdentity(HIdentity node) => visitRelational(node); |
| @override |
| R visitIf(HIf node) => visitConditionalBranch(node); |
| @override |
| R visitIndex(HIndex node) => visitInstruction(node); |
| @override |
| R visitIndexAssign(HIndexAssign node) => visitInstruction(node); |
| @override |
| R visitInterceptor(HInterceptor node) => visitInstruction(node); |
| @override |
| R visitInvokeClosure(HInvokeClosure node) => visitInvokeDynamic(node); |
| @override |
| R visitInvokeConstructorBody(HInvokeConstructorBody node) => |
| visitInvokeStatic(node); |
| @override |
| R visitInvokeGeneratorBody(HInvokeGeneratorBody node) => |
| visitInvokeStatic(node); |
| @override |
| R visitInvokeDynamicMethod(HInvokeDynamicMethod node) => |
| visitInvokeDynamic(node); |
| @override |
| R visitInvokeDynamicGetter(HInvokeDynamicGetter node) => |
| visitInvokeDynamicField(node); |
| @override |
| R visitInvokeDynamicSetter(HInvokeDynamicSetter node) => |
| visitInvokeDynamicField(node); |
| @override |
| R visitInvokeStatic(HInvokeStatic node) => visitInvoke(node); |
| @override |
| R visitInvokeSuper(HInvokeSuper node) => visitInvokeStatic(node); |
| R visitJump(HJump node) => visitControlFlow(node); |
| @override |
| R visitLazyStatic(HLazyStatic node) => visitInstruction(node); |
| @override |
| R visitLess(HLess node) => visitRelational(node); |
| @override |
| R visitLessEqual(HLessEqual node) => visitRelational(node); |
| @override |
| R visitLiteralList(HLiteralList node) => visitInstruction(node); |
| R visitLocalAccess(HLocalAccess node) => visitInstruction(node); |
| @override |
| R visitLocalGet(HLocalGet node) => visitLocalAccess(node); |
| @override |
| R visitLocalSet(HLocalSet node) => visitLocalAccess(node); |
| @override |
| R visitLocalValue(HLocalValue node) => visitInstruction(node); |
| @override |
| R visitLoopBranch(HLoopBranch node) => visitConditionalBranch(node); |
| @override |
| R visitNegate(HNegate node) => visitInvokeUnary(node); |
| @override |
| R visitNot(HNot node) => visitInstruction(node); |
| @override |
| R visitOneShotInterceptor(HOneShotInterceptor node) => |
| visitInvokeDynamic(node); |
| @override |
| R visitPhi(HPhi node) => visitInstruction(node); |
| @override |
| R visitMultiply(HMultiply node) => visitBinaryArithmetic(node); |
| @override |
| R visitParameterValue(HParameterValue node) => visitLocalValue(node); |
| @override |
| R visitRangeConversion(HRangeConversion node) => visitCheck(node); |
| @override |
| R visitReadModifyWrite(HReadModifyWrite node) => visitInstruction(node); |
| @override |
| R visitRef(HRef node) => node.value.accept(this); |
| @override |
| R visitRemainder(HRemainder node) => visitBinaryArithmetic(node); |
| @override |
| R visitReturn(HReturn node) => visitControlFlow(node); |
| @override |
| R visitShiftLeft(HShiftLeft node) => visitBinaryBitOp(node); |
| @override |
| R visitShiftRight(HShiftRight node) => visitBinaryBitOp(node); |
| @override |
| R visitSubtract(HSubtract node) => visitBinaryArithmetic(node); |
| @override |
| R visitSwitch(HSwitch node) => visitControlFlow(node); |
| @override |
| R visitStatic(HStatic node) => visitInstruction(node); |
| @override |
| R visitStaticStore(HStaticStore node) => visitInstruction(node); |
| @override |
| R visitStringConcat(HStringConcat node) => visitInstruction(node); |
| @override |
| R visitStringify(HStringify node) => visitInstruction(node); |
| @override |
| R visitThis(HThis node) => visitParameterValue(node); |
| @override |
| R visitThrow(HThrow node) => visitControlFlow(node); |
| @override |
| R visitThrowExpression(HThrowExpression node) => visitInstruction(node); |
| @override |
| R visitTruncatingDivide(HTruncatingDivide node) => |
| visitBinaryArithmetic(node); |
| @override |
| R visitTry(HTry node) => visitControlFlow(node); |
| @override |
| R visitIsLateSentinel(HIsLateSentinel node) => visitInstruction(node); |
| @override |
| R visitLateValue(HLateValue node) => visitInstruction(node); |
| @override |
| R visitNullCheck(HNullCheck node) => visitCheck(node); |
| R visitLateCheck(HLateCheck node) => visitCheck(node); |
| @override |
| R visitLateReadCheck(HLateReadCheck node) => visitLateCheck(node); |
| @override |
| R visitLateWriteOnceCheck(HLateWriteOnceCheck node) => visitLateCheck(node); |
| @override |
| R visitLateInitializeOnceCheck(HLateInitializeOnceCheck node) => |
| visitLateCheck(node); |
| @override |
| R visitPrimitiveCheck(HPrimitiveCheck node) => visitCheck(node); |
| @override |
| R visitTypeKnown(HTypeKnown node) => visitCheck(node); |
| @override |
| R visitAwait(HAwait node) => visitInstruction(node); |
| @override |
| R visitYield(HYield node) => visitInstruction(node); |
| |
| @override |
| R visitIsTest(HIsTest node) => visitInstruction(node); |
| @override |
| R visitIsTestSimple(HIsTestSimple node) => visitInstruction(node); |
| @override |
| R visitAsCheck(HAsCheck node) => visitCheck(node); |
| @override |
| R visitAsCheckSimple(HAsCheckSimple node) => visitCheck(node); |
| @override |
| R visitSubtypeCheck(HSubtypeCheck node) => visitCheck(node); |
| @override |
| R visitLoadType(HLoadType node) => visitInstruction(node); |
| @override |
| R visitInstanceEnvironment(HInstanceEnvironment node) => |
| visitInstruction(node); |
| @override |
| R visitTypeEval(HTypeEval node) => visitInstruction(node); |
| @override |
| R visitTypeBind(HTypeBind node) => visitInstruction(node); |
| |
| @override |
| R visitArrayFlagsCheck(HArrayFlagsCheck node) => visitCheck(node); |
| @override |
| R visitArrayFlagsGet(HArrayFlagsGet node) => visitInstruction(node); |
| @override |
| R visitArrayFlagsSet(HArrayFlagsSet node) => visitInstruction(node); |
| } |
| |
| class SubGraph { |
| // The first and last block of the sub-graph. |
| final HBasicBlock start; |
| final HBasicBlock end; |
| |
| const SubGraph(this.start, this.end); |
| |
| bool contains(HBasicBlock block) { |
| return start.id <= block.id && block.id <= end.id; |
| } |
| } |
| |
| class SubExpression extends SubGraph { |
| const SubExpression(super.start, super.end); |
| |
| /// Find the condition expression if this sub-expression is a condition. |
| HInstruction? get conditionExpression { |
| HInstruction? last = end.last; |
| if (last is HConditionalBranch || last is HSwitch) return last!.inputs[0]; |
| return null; |
| } |
| } |
| |
| class HInstructionList { |
| HInstruction? first; |
| HInstruction? last; |
| |
| bool get isEmpty { |
| return first == null; |
| } |
| |
| void internalAddAfter(HInstruction? cursor, HInstruction instruction) { |
| if (cursor == null) { |
| assert(isEmpty); |
| first = last = instruction; |
| } else if (identical(cursor, last)) { |
| last!.next = instruction; |
| instruction.previous = last; |
| last = instruction; |
| } else { |
| instruction.previous = cursor; |
| instruction.next = cursor.next; |
| cursor.next!.previous = instruction; |
| cursor.next = instruction; |
| } |
| } |
| |
| void internalAddBefore(HInstruction? cursor, HInstruction instruction) { |
| if (cursor == null) { |
| assert(isEmpty); |
| first = last = instruction; |
| } else if (identical(cursor, first)) { |
| first!.previous = instruction; |
| instruction.next = first; |
| first = instruction; |
| } else { |
| instruction.next = cursor; |
| instruction.previous = cursor.previous; |
| cursor.previous!.next = instruction; |
| cursor.previous = instruction; |
| } |
| } |
| |
| void detach(HInstruction instruction) { |
| assert(_truncatedContainsForAssert(instruction)); |
| assert(instruction.isInBasicBlock()); |
| if (instruction.previous == null) { |
| first = instruction.next; |
| } else { |
| instruction.previous!.next = instruction.next; |
| } |
| if (instruction.next == null) { |
| last = instruction.previous; |
| } else { |
| instruction.next!.previous = instruction.previous; |
| } |
| instruction.previous = null; |
| instruction.next = null; |
| } |
| |
| void remove(HInstruction instruction) { |
| assert(instruction.usedBy.isEmpty); |
| detach(instruction); |
| } |
| |
| /// Linear search for [instruction]. |
| bool contains(HInstruction instruction) { |
| for (var cursor = first; cursor != null; cursor = cursor.next) { |
| if (identical(cursor, instruction)) return true; |
| } |
| |
| return false; |
| } |
| |
| /// Linear search for [instruction], up to a limit of 100. Returns whether |
| /// the instruction is found or the list is too big. |
| /// |
| /// This is used for assertions only: some tests have pathological cases where |
| /// the basic blocks are huge (50K nodes!), and we found that checking for |
| /// [contains] within our assertions made compilation really slow. |
| bool _truncatedContainsForAssert(HInstruction instruction) { |
| int count = 0; |
| for (var cursor = first; cursor != null; cursor = cursor.next) { |
| count++; |
| if (count > 100) return true; |
| if (identical(cursor, instruction)) return true; |
| } |
| |
| return false; |
| } |
| } |
| |
| class HPhiList extends HInstructionList { |
| HPhi? get firstPhi => first as HPhi?; |
| HPhi? get lastPhi => last as HPhi?; |
| } |
| |
| enum _BasicBlockStatus { new_, open, closed } |
| |
| class HBasicBlock extends HInstructionList { |
| // The [id] must be such that any successor's id is greater than |
| // this [id]. The exception are back-edges. |
| int id = -1; |
| |
| _BasicBlockStatus _status = _BasicBlockStatus.new_; |
| |
| var phis = HPhiList(); |
| |
| HLoopInformation? loopInformation; |
| HBlockFlow? blockFlow; |
| HBasicBlock? parentLoopHeader; |
| bool isLive = true; |
| |
| final List<HBasicBlock> predecessors = []; |
| List<HBasicBlock> successors = const []; |
| |
| HBasicBlock? dominator; |
| final List<HBasicBlock> dominatedBlocks = []; |
| int dominatorDfsIn = -1; |
| int dominatorDfsOut = -1; |
| |
| HBasicBlock(); |
| |
| @override |
| int get hashCode => id; |
| |
| @override |
| bool operator ==(other) => identical(this, other); |
| |
| bool get isNew => _status == _BasicBlockStatus.new_; |
| bool get isOpen => _status == _BasicBlockStatus.open; |
| bool get isClosed => _status == _BasicBlockStatus.closed; |
| |
| bool isLoopHeader() { |
| return loopInformation != null; |
| } |
| |
| void setBlockFlow(HBlockInformation blockInfo, HBasicBlock? continuation) { |
| blockFlow = HBlockFlow(blockInfo, continuation); |
| } |
| |
| bool isLabeledBlock() => |
| blockFlow != null && blockFlow!.body is HLabeledBlockInformation; |
| |
| HBasicBlock? get enclosingLoopHeader { |
| if (isLoopHeader()) return this; |
| return parentLoopHeader; |
| } |
| |
| void open() { |
| assert(isNew); |
| _status = _BasicBlockStatus.open; |
| } |
| |
| void close(HControlFlow end) { |
| assert(isOpen); |
| addAfter(last, end); |
| _status = _BasicBlockStatus.closed; |
| } |
| |
| void addAtEntry(HInstruction instruction) { |
| assert(instruction is! HPhi); |
| internalAddBefore(first, instruction); |
| instruction.notifyAddedToBlock(this); |
| } |
| |
| void addAtExit(HInstruction instruction) { |
| assert(isClosed); |
| assert(last is HControlFlow); |
| assert(instruction is! HPhi); |
| internalAddBefore(last, instruction); |
| instruction.notifyAddedToBlock(this); |
| } |
| |
| void moveAtExit(HInstruction instruction) { |
| assert(instruction is! HPhi); |
| assert(instruction.isInBasicBlock()); |
| assert(isClosed); |
| assert(last is HControlFlow); |
| internalAddBefore(last, instruction); |
| instruction.block = this; |
| assert(isValid()); |
| } |
| |
| void add(HInstruction instruction) { |
| assert(instruction is! HControlFlow); |
| assert(instruction is! HPhi); |
| internalAddAfter(last, instruction); |
| instruction.notifyAddedToBlock(this); |
| } |
| |
| void addPhi(HPhi phi) { |
| assert(phi.inputs.isEmpty || phi.inputs.length == predecessors.length); |
| assert(phi.block == null); |
| phis.internalAddAfter(phis.last, phi); |
| phi.notifyAddedToBlock(this); |
| } |
| |
| void removePhi(HPhi phi) { |
| phis.remove(phi); |
| assert(phi.block == this); |
| phi.notifyRemovedFromBlock(); |
| } |
| |
| void addAfter(HInstruction? cursor, HInstruction instruction) { |
| assert(cursor is! HPhi); |
| assert(instruction is! HPhi); |
| assert(isOpen || isClosed); |
| internalAddAfter(cursor, instruction); |
| instruction.notifyAddedToBlock(this); |
| } |
| |
| void addBefore(HInstruction? cursor, HInstruction instruction) { |
| assert(cursor is! HPhi); |
| assert(instruction is! HPhi); |
| assert(isOpen || isClosed); |
| internalAddBefore(cursor, instruction); |
| instruction.notifyAddedToBlock(this); |
| } |
| |
| @override |
| void remove(HInstruction instruction) { |
| assert(isOpen || isClosed); |
| assert(instruction is! HPhi); |
| super.remove(instruction); |
| assert(instruction.block == this); |
| instruction.notifyRemovedFromBlock(); |
| } |
| |
| void addSuccessor(HBasicBlock block) { |
| if (successors.isEmpty) { |
| successors = [block]; |
| } else { |
| successors.add(block); |
| } |
| block.predecessors.add(this); |
| } |
| |
| void postProcessLoopHeader() { |
| assert(isLoopHeader()); |
| // Only the first entry into the loop is from outside the |
| // loop. All other entries must be back edges. |
| for (int i = 1, length = predecessors.length; i < length; i++) { |
| loopInformation!.addBackEdge(predecessors[i]); |
| } |
| } |
| |
| /// Rewrites all uses of the [from] instruction to using the [to] |
| /// instruction instead. |
| void rewrite(HInstruction from, HInstruction to) { |
| for (HInstruction use in from.usedBy) { |
| use.rewriteInput(from, to); |
| } |
| to.usedBy.addAll(from.usedBy); |
| from.usedBy.clear(); |
| } |
| |
| /// Rewrites all uses of the [from] instruction to using either the |
| /// [to] instruction, or a [HCheck] instruction that has better type |
| /// information on [to], and that dominates the user. |
| void rewriteWithBetterUser(HInstruction? from, HInstruction to) { |
| // BUG(11841): Turn this method into a phase to be run after GVN phases. |
| Link<HCheck> better = const Link(); |
| for (HInstruction user in to.usedBy) { |
| if (user == from || user is! HCheck) continue; |
| HCheck check = user; |
| if (check.checkedInput == to) { |
| better = better.prepend(user); |
| } |
| } |
| |
| if (better.isEmpty) return rewrite(from!, to); |
| |
| L1: |
| for (HInstruction user in from!.usedBy) { |
| for (HCheck check in better) { |
| if (check.dominates(user)) { |
| user.rewriteInput(from, check); |
| check.usedBy.add(user); |
| continue L1; |
| } |
| } |
| user.rewriteInput(from, to); |
| to.usedBy.add(user); |
| } |
| from.usedBy.clear(); |
| } |
| |
| bool isExitBlock() { |
| return identical(first, last) && first is HExit; |
| } |
| |
| void addDominatedBlock(HBasicBlock block) { |
| assert(isClosed); |
| assert(id >= 0 && block.id >= 0); |
| assert(!dominatedBlocks.contains(block)); |
| // Keep the list of dominated blocks sorted such that if there are two |
| // succeeding blocks in the list, the predecessor is before the successor. |
| // Assume that we add the dominated blocks in the right order. |
| int index = dominatedBlocks.length; |
| while (index > 0 && dominatedBlocks[index - 1].id > block.id) { |
| index--; |
| } |
| if (index == dominatedBlocks.length) { |
| dominatedBlocks.add(block); |
| } else { |
| dominatedBlocks.insert(index, block); |
| } |
| assert(block.dominator == null); |
| block.dominator = this; |
| } |
| |
| void removeDominatedBlock(HBasicBlock block) { |
| assert(isClosed); |
| assert(id >= 0 && block.id >= 0); |
| int index = dominatedBlocks.indexOf(block); |
| assert(index >= 0); |
| if (index == dominatedBlocks.length - 1) { |
| dominatedBlocks.removeLast(); |
| } else { |
| dominatedBlocks.removeRange(index, index + 1); |
| } |
| assert(identical(block.dominator, this)); |
| block.dominator = null; |
| } |
| |
| void assignCommonDominator(HBasicBlock predecessor) { |
| assert(isClosed); |
| if (dominator == null) { |
| // If this basic block doesn't have a dominator yet we use the |
| // given predecessor as the dominator. |
| predecessor.addDominatedBlock(this); |
| } else if (predecessor.dominator != null) { |
| // If the predecessor has a dominator and this basic block has a |
| // dominator, we find a common parent in the dominator tree and |
| // use that as the dominator. |
| HBasicBlock block0 = dominator!; |
| HBasicBlock block1 = predecessor; |
| while (!identical(block0, block1)) { |
| if (block0.id > block1.id) { |
| block0 = block0.dominator!; |
| } else { |
| block1 = block1.dominator!; |
| } |
| //assert(block0 != null && block1 != null); |
| } |
| if (!identical(dominator, block0)) { |
| dominator!.removeDominatedBlock(this); |
| block0.addDominatedBlock(this); |
| } |
| } |
| } |
| |
| void forEachPhi(void Function(HPhi phi) f) { |
| var current = phis.firstPhi; |
| while (current != null) { |
| final next = current.nextPhi; |
| f(current); |
| current = next; |
| } |
| } |
| |
| void forEachInstruction(void Function(HInstruction instruction) f) { |
| var current = first; |
| while (current != null) { |
| final next = current.next; |
| f(current); |
| current = next; |
| } |
| } |
| |
| bool isValid() { |
| assert(isClosed); |
| HValidator validator = HValidator(); |
| validator.visitBasicBlock(this); |
| return validator.isValid; |
| } |
| |
| bool dominates(HBasicBlock other) { |
| return dominatorDfsIn <= other.dominatorDfsIn && |
| other.dominatorDfsOut <= dominatorDfsOut; |
| } |
| |
| @override |
| String toString() => 'HBasicBlock($id)'; |
| } |
| |
| enum _GvnType { |
| undefined, |
| boundsCheck, |
| interceptor, |
| add, |
| divide, |
| multiply, |
| subtract, |
| shiftLeft, |
| bitOr, |
| bitAnd, |
| bitXor, |
| negate, |
| bitNot, |
| not, |
| identity, |
| greater, |
| greaterEqual, |
| less, |
| lessEqual, |
| static, |
| staticStore, |
| fieldGet, |
| functionReference, |
| typeKnown, |
| invokeStatic, |
| index_, |
| invokeDynamic, |
| shiftRight, |
| truncatingDivide, |
| invokeExternal, |
| foreignCode, |
| remainder, |
| getLength, |
| abs, |
| nullCheck, |
| primitiveCheck, |
| isTest, |
| isTestSimple, |
| asCheck, |
| asCheckSimple, |
| subtypeCheck, |
| loadType, |
| instanceEnvironment, |
| typeEval, |
| typeBind, |
| isLateSentinel, |
| stringConcat, |
| stringify, |
| lateReadCheck, |
| lateWriteOnceCheck, |
| lateInitializeOnceCheck, |
| charCodeAt, |
| arrayFlagsGet, |
| arrayFlagsCheck, |
| embeddedGlobal, |
| } |
| |
| abstract class HInstruction implements SpannableWithEntity { |
| Entity? sourceElement; |
| SourceInformation? sourceInformation; |
| |
| final int id = idCounter++; |
| static int idCounter = 0; |
| |
| // A HInstruction owns its [inputs] list. A fresh list is created in every |
| // base class constructor to ensure that [inputs] is always a growable |
| // List. Although many instructions have a fixed number of inputs (including |
| // zero inputs), having a uniform growable representation is more flexible for |
| // editing, and allows hundreds of method calls on [inputs] to be |
| // devirtualized. |
| final List<HInstruction> inputs; |
| |
| // Instructions that uses this instruction. A user is [usedBy] once per input |
| // that is this instruction. |
| // |
| // y = [x, x + 1, x]; |
| // |
| // The [usedBy] for the instruction `x` has three elements, one for the |
| // addition instruction and two for the list literal instruction, in no |
| // particilar order. |
| final List<HInstruction> usedBy = []; |
| |
| HBasicBlock? block; |
| HInstruction? previous; |
| HInstruction? next; |
| |
| /// Type of the instruction. |
| late AbstractValue instructionType; |
| |
| final SideEffects sideEffects = SideEffects.empty(); |
| bool _useGvn = false; |
| |
| // TODO(sra): Consider whether to reduce instruction size by collecting all |
| // these instruction flags into a bitmask. |
| bool _allowCSE = false; |
| bool _allowDCE = false; |
| |
| // Main constructor copies the list of inputs to ensure ownership. |
| HInstruction(List<HInstruction> initialInputs, this.instructionType) |
| : inputs = [...initialInputs]; |
| |
| // Convenience constructors that avoid an intermediate list. |
| HInstruction._noInput(this.instructionType) : inputs = []; |
| HInstruction._oneInput(HInstruction input, this.instructionType) |
| : inputs = [input]; |
| HInstruction._twoInputs( |
| HInstruction input1, |
| HInstruction input2, |
| this.instructionType, |
| ) : inputs = [input1, input2]; |
| |
| HInstruction._noType() : inputs = []; |
| |
| @override |
| Entity? get sourceEntity => sourceElement; |
| |
| @override |
| SourceSpan? get sourceSpan => sourceInformation?.sourceSpan; |
| |
| @override |
| int get hashCode => id; |
| |
| @override |
| bool operator ==(other) => identical(this, other); |
| |
| bool useGvn() => _useGvn; |
| void setUseGvn() { |
| _useGvn = true; |
| } |
| |
| bool get isMovable => useGvn(); |
| |
| /// A pure instruction is an instruction that does not have any side |
| /// effect, nor any dependency. They can be moved anywhere in the |
| /// graph. |
| bool isPure(AbstractValueDomain domain) { |
| return !sideEffects.hasSideEffects() && |
| !sideEffects.dependsOnSomething() && |
| !canThrow(domain); |
| } |
| |
| /// `true` if an instruction can be eliminated as a common subexpression - |
| /// when the instruction is equivalent to an earlier instruction may be |
| /// replaced by the value of the earlier instruction. Equivalent means that |
| /// the instruction has exactly the same inputs. |
| /// |
| /// This property is set on function invocations on the basis of annotations |
| /// and program analysis. Usually, `allowCSE` means that the instruction is |
| /// idempotent - a second equivalent instruction returns the same value as the |
| /// first instruction without additional observable effects. |
| /// |
| /// If an instruction is pure, it should be marked as `useGvn` instead. |
| bool get allowCSE => _allowCSE; |
| |
| /// `true` if the instruction may be removed when the value is unused. |
| /// |
| /// This property is set on function invocations on the basis of annotations |
| /// and program analysis. Usually `allowDCE` means that the instruction has |
| /// no observable effect. |
| bool get allowDCE => _allowDCE; |
| |
| /// An instruction is an 'allocation' is it is the sole alias for an object. |
| /// This applies to instructions that allocate new objects and can be extended |
| /// to methods that return other allocations without escaping them. |
| bool isAllocation(AbstractValueDomain domain) => false; |
| |
| /// Overridden by [HCheck] to return the actual non-[HCheck] |
| /// instruction it checks against. |
| HInstruction nonCheck() => this; |
| |
| /// Can this node throw an exception? |
| bool canThrow(AbstractValueDomain domain) => false; |
| |
| bool isValue(AbstractValueDomain domain) => |
| domain.isPrimitiveValue(instructionType); |
| |
| AbstractBool isNull(AbstractValueDomain domain) => |
| domain.isNull(instructionType); |
| |
| AbstractBool isLateSentinel(AbstractValueDomain domain) => |
| domain.isLateSentinel(instructionType); |
| |
| AbstractBool isConflicting(AbstractValueDomain domain) => |
| domain.isEmpty(instructionType); |
| |
| AbstractBool isPrimitive(AbstractValueDomain domain) => |
| domain.isPrimitive(instructionType); |
| |
| AbstractBool isPrimitiveNumber(AbstractValueDomain domain) => |
| domain.isPrimitiveNumber(instructionType); |
| |
| AbstractBool isPrimitiveBoolean(AbstractValueDomain domain) => |
| domain.isPrimitiveBoolean(instructionType); |
| |
| AbstractBool isIndexablePrimitive(AbstractValueDomain domain) => |
| domain.isIndexablePrimitive(instructionType); |
| |
| AbstractBool isGrowableArray(AbstractValueDomain domain) => |
| domain.isGrowableArray(instructionType); |
| |
| AbstractBool isModifiableArray(AbstractValueDomain domain) => |
| domain.isModifiableArray(instructionType); |
| |
| AbstractBool isMutableIndexable(AbstractValueDomain domain) => |
| domain.isMutableIndexable(instructionType); |
| |
| AbstractBool isArray(AbstractValueDomain domain) => |
| domain.isArray(instructionType); |
| |
| AbstractBool isPrimitiveString(AbstractValueDomain domain) => |
| domain.isPrimitiveString(instructionType); |
| |
| AbstractBool isInteger(AbstractValueDomain domain) => |
| domain.isInteger(instructionType); |
| |
| AbstractBool isUInt32(AbstractValueDomain domain) => |
| domain.isUInt32(instructionType); |
| |
| AbstractBool isUInt31(AbstractValueDomain domain) => |
| domain.isUInt31(instructionType); |
| |
| AbstractBool isPositiveInteger(AbstractValueDomain domain) => |
| domain.isPositiveInteger(instructionType); |
| |
| AbstractBool isPositiveIntegerOrNull(AbstractValueDomain domain) => |
| domain.isPositiveIntegerOrNull(instructionType); |
| |
| AbstractBool isIntegerOrNull(AbstractValueDomain domain) => |
| domain.isIntegerOrNull(instructionType); |
| |
| AbstractBool isNumber(AbstractValueDomain domain) => |
| domain.isNumber(instructionType); |
| |
| AbstractBool isNumberOrNull(AbstractValueDomain domain) => |
| domain.isNumberOrNull(instructionType); |
| |
| AbstractBool isBoolean(AbstractValueDomain domain) => |
| domain.isBoolean(instructionType); |
| |
| AbstractBool isBooleanOrNull(AbstractValueDomain domain) => |
| domain.isBooleanOrNull(instructionType); |
| |
| AbstractBool isString(AbstractValueDomain domain) => |
| domain.isString(instructionType); |
| |
| AbstractBool isStringOrNull(AbstractValueDomain domain) => |
| domain.isStringOrNull(instructionType); |
| |
| AbstractBool isPrimitiveOrNull(AbstractValueDomain domain) => |
| domain.isPrimitiveOrNull(instructionType); |
| |
| HInstruction? getDartReceiver() => null; |
| bool onlyThrowsNSM() => false; |
| |
| bool isInBasicBlock() => block != null; |
| |
| bool gvnEquals(HInstruction other) { |
| // Check that the type and the sideEffects match. |
| bool hasSameType = typeEquals(other); |
| assert(hasSameType == (_gvnType == other._gvnType)); |
| if (!hasSameType) return false; |
| // Check the data first to ensure we are considering the same element or |
| // selector. |
| if (!dataEquals(other)) return false; |
| assert((useGvn() && other.useGvn()) || (allowCSE && other.allowCSE)); |
| if (sideEffects != other.sideEffects) return false; |
| // Check that the inputs match. |
| final int inputsLength = inputs.length; |
| final List<HInstruction> otherInputs = other.inputs; |
| if (inputsLength != otherInputs.length) return false; |
| for (int i = 0; i < inputsLength; i++) { |
| if (!identical(inputs[i].nonCheck(), otherInputs[i].nonCheck())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| int gvnHashCode() { |
| int result = _gvnType.index; |
| int length = inputs.length; |
| for (int i = 0; i < length; i++) { |
| result = (result * 19) + (inputs[i].nonCheck().id) + (result >> 7); |
| } |
| return result; |
| } |
| |
| // These methods should be overwritten by instructions that |
| // participate in global value numbering or allowCSE. |
| _GvnType get _gvnType => _GvnType.undefined; |
| bool typeEquals(covariant HInstruction other) => false; |
| bool dataEquals(covariant HInstruction other) => false; |
| |
| R accept<R>(HVisitor<R> visitor); |
| |
| void notifyAddedToBlock(HBasicBlock targetBlock) { |
| assert(!isInBasicBlock()); |
| assert(block == null); |
| // Add [this] to the inputs' uses. |
| for (int i = 0; i < inputs.length; i++) { |
| assert(inputs[i].isInBasicBlock()); |
| inputs[i].usedBy.add(this); |
| } |
| block = targetBlock; |
| assert(isValid()); |
| } |
| |
| void notifyRemovedFromBlock() { |
| assert(isInBasicBlock()); |
| assert(usedBy.isEmpty); |
| |
| // Remove [this] from the inputs' uses. |
| for (int i = 0; i < inputs.length; i++) { |
| inputs[i].removeUser(this); |
| } |
| block = null; |
| assert(isValid()); |
| } |
| |
| /// Do a in-place change of [from] to [to]. Warning: this function |
| /// does not update [inputs] and [usedBy]. Use [changeUse] instead. |
| void rewriteInput(HInstruction? from, HInstruction to) { |
| for (int i = 0; i < inputs.length; i++) { |
| if (identical(inputs[i], from)) inputs[i] = to; |
| } |
| } |
| |
| /// Removes all occurrences of [instruction] from [list]. |
| void removeFromList(List<HInstruction> list, HInstruction instruction) { |
| int length = list.length; |
| int i = 0; |
| while (i < length) { |
| if (instruction == list[i]) { |
| list[i] = list[length - 1]; |
| length--; |
| } else { |
| i++; |
| } |
| } |
| list.length = length; |
| } |
| |
| /// Removes all occurrences of [user] from [usedBy]. |
| void removeUser(HInstruction user) { |
| removeFromList(usedBy, user); |
| } |
| |
| // Change all uses of [oldInput] by [this] to [newInput]. Also updates the |
| // [usedBy] of [oldInput] and [newInput]. |
| void changeUse(HInstruction oldInput, HInstruction newInput) { |
| assert(!identical(oldInput, newInput)); |
| for (int i = 0; i < inputs.length; i++) { |
| if (identical(inputs[i], oldInput)) { |
| inputs[i] = newInput; |
| newInput.usedBy.add(this); |
| } |
| } |
| removeFromList(oldInput.usedBy, this); |
| } |
| |
| /// Replace a single input. |
| /// |
| /// Use [changeUse] to change all inputs that are the same value. |
| void replaceInput(int index, HInstruction replacement) { |
| assert(replacement.isInBasicBlock()); |
| inputs[index].usedBy.remove(this); |
| inputs[index] = replacement; |
| replacement.usedBy.add(this); |
| } |
| |
| /// Remove a single input. |
| void removeInput(int index) { |
| inputs[index].usedBy.remove(this); |
| inputs.removeAt(index); |
| } |
| |
| void replaceAllUsersDominatedBy( |
| HInstruction cursor, |
| HInstruction newInstruction, |
| ) { |
| DominatedUses.of(this, cursor).replaceWith(newInstruction); |
| } |
| |
| void moveBefore(HInstruction other) { |
| assert(this is! HControlFlow); |
| assert(this is! HPhi); |
| assert(other is! HPhi); |
| block!.detach(this); |
| other.block!.internalAddBefore(other, this); |
| block = other.block; |
| } |
| |
| bool isConstantBoolean() => false; |
| bool isConstantNull() => false; |
| bool isConstantNumber() => false; |
| bool isConstantInteger() => false; |
| bool isConstantString() => false; |
| bool isConstantFalse() => false; |
| bool isConstantTrue() => false; |
| |
| bool isValid() { |
| HValidator validator = HValidator(); |
| validator.currentBlock = block; |
| validator.visitInstruction(this); |
| return validator.isValid; |
| } |
| |
| bool isCodeMotionInvariant() => false; |
| |
| /// Returns `true` when this HInstruction might be compiled to a JavaScript |
| /// statement, `false` when always compiled to a JavaScript expression. |
| /// |
| /// Some checks are marked as statements even though the generated code is an |
| /// expression. This is done when the value of the generated expression does |
| /// not correspond to the value of the check (usually one of its inputs). |
| bool isJsStatement() => false; |
| |
| bool dominates(HInstruction other) { |
| // An instruction does not dominates itself. |
| if (this == other) return false; |
| if (block != other.block) return block!.dominates(other.block!); |
| |
| for (var current = next; current != null; current = current.next) { |
| if (current == other) return true; |
| } |
| return false; |
| } |
| |
| /// Return whether the instructions do not belong to a loop or |
| /// belong to the same loop. |
| bool hasSameLoopHeaderAs(HInstruction other) { |
| return block!.enclosingLoopHeader == other.block!.enclosingLoopHeader; |
| } |
| |
| @override |
| String toString() => '$runtimeType()'; |
| } |
| |
| mixin HasSettableAllowCSE on HInstruction { |
| set allowCSE(bool value) { |
| _allowCSE = value; |
| } |
| } |
| |
| mixin HasSettableAllowDCE on HInstruction { |
| set allowDCE(bool value) { |
| _allowDCE = value; |
| } |
| } |
| |
| /// An interface implemented by certain kinds of [HInstruction]. This makes it |
| /// possible to discover which annotations were in force in the code from which |
| /// the instruction originated. |
| // TODO(sra): It would be easier to use a mostly-shared Map-like structure that |
| // surfaces the ambient annotations at any point in the code. |
| abstract class InstructionContext { |
| MemberEntity? instructionContext; |
| } |
| |
| /// The set of uses of [source] that are dominated by [dominator]. |
| class DominatedUses { |
| final HInstruction _source; |
| |
| // Two list of matching length holding (instruction, input-index) pairs for |
| // the dominated uses. |
| final List<HInstruction> _instructions = []; |
| final List<int> _indexes = []; |
| |
| DominatedUses._(this._source); |
| |
| /// The uses of [source] that are dominated by [dominator]. |
| /// |
| /// The uses by [dominator] are included in the result, unless |
| /// [excludeDominator] is `true`, so `true` selects uses following |
| /// [dominator]. |
| /// |
| /// The uses include the in-edges of a HPhi node that corresponds to a |
| /// dominated block. (There can be many such edges on a single phi at the exit |
| /// of a loop with many break statements). If [excludePhiOutEdges] is `true` |
| /// then these edge uses are not included. |
| static DominatedUses of( |
| HInstruction source, |
| HInstruction dominator, { |
| bool excludeDominator = false, |
| bool excludePhiOutEdges = false, |
| }) { |
| return DominatedUses._(source) |
| .._compute(source, dominator, excludeDominator, excludePhiOutEdges); |
| } |
| |
| bool get isEmpty => _instructions.isEmpty; |
| bool get isNotEmpty => !isEmpty; |
| int get length => _instructions.length; |
| |
| /// Changes all the uses in the set to [replacement]. |
| void replaceWith(HInstruction replacement) { |
| assert(replacement.isInBasicBlock()); |
| assert(!identical(replacement, _source)); |
| if (isEmpty) return; |
| |
| for (int i = 0; i < _instructions.length; i++) { |
| HInstruction user = _instructions[i]; |
| int index = _indexes[i]; |
| assert( |
| identical(user.inputs[index], _source), |
| 'Input $index of $user changed.' |
| '\n Found: ${user.inputs[index]}\n Expected: $_source', |
| ); |
| user.inputs[index] = replacement; |
| replacement.usedBy.add(user); |
| } |
| |
| // The following loop is a more efficient implementation of: |
| // |
| // for (final user in _instructions) { |
| // _source.usedBy.remove(user); |
| // } |
| // |
| // `List.remove` searches the list to find the key, and then scans the rest |
| // of the list to move the elements up one position. Repeating this is |
| // quadratic. |
| // |
| // The code below combines searching for the next element with move-up |
| // scanning for the previous element(s) to remove several elements in one |
| // pass, provided elements of `_instructions` are in the same order as in |
| // `usedBy`. This is usually the case since the DominatedUses set is |
| // constructed from `_source.usedBy`. |
| |
| final usedBy = _source.usedBy; |
| int instructionsIndex = 0; |
| while (instructionsIndex < _instructions.length) { |
| HInstruction nextToRemove = _instructions[instructionsIndex]; |
| int readIndex = 0, writeIndex = 0; |
| while (readIndex < usedBy.length) { |
| final user = usedBy[readIndex++]; |
| if (identical(user, nextToRemove)) { |
| instructionsIndex++; |
| if (instructionsIndex < _instructions.length) { |
| nextToRemove = _instructions[instructionsIndex]; |
| } else { |
| // Copy rest of the list elements up as-is. |
| while (readIndex < usedBy.length) { |
| usedBy[writeIndex++] = usedBy[readIndex++]; |
| } |
| break; |
| } |
| } else { |
| usedBy[writeIndex++] = user; |
| } |
| } |
| assert(writeIndex < readIndex, 'Should remove at least one per pass'); |
| usedBy.length = writeIndex; |
| } |
| } |
| |
| bool get isSingleton => _instructions.length == 1; |
| |
| HInstruction get single => _instructions.single; |
| |
| Iterable<HInstruction> get instructions => _instructions; |
| |
| void _addUse(HInstruction user, int inputIndex) { |
| _instructions.add(user); |
| _indexes.add(inputIndex); |
| } |
| |
| void _compute( |
| HInstruction source, |
| HInstruction dominator, |
| bool excludeDominator, |
| bool excludePhiOutEdges, |
| ) { |
| assert(dominator is! HPhi); |
| |
| // Keep track of all instructions that we have to deal with later and count |
| // the number of them that are in the dominator's block. |
| Set<HInstruction> users = Setlet(); |
| Set<HInstruction> seen = Setlet(); |
| int usersInDominatorBlock = 0; |
| |
| HBasicBlock dominatorBlock = dominator.block!; |
| |
| // Run through all the users and see if they are dominated, or potentially |
| // dominated, or partially dominated by [dominator]. It is easier to |
| // de-duplicate [usedBy] and process all inputs of an instruction than to |
| // track the repeated elements of usedBy and match them up by index. |
| for (HInstruction current in source.usedBy) { |
| if (!seen.add(current)) continue; |
| HBasicBlock currentBlock = current.block!; |
| if (identical(currentBlock, dominatorBlock)) { |
| // Ignore phi nodes of the dominator instruction block, they come before |
| // the dominator instruction. |
| if (current is! HPhi) { |
| users.add(current); |
| usersInDominatorBlock++; |
| } |
| } else if (dominatorBlock.dominates(currentBlock)) { |
| users.add(current); |
| } else if (!excludePhiOutEdges && current is HPhi) { |
| // A non-dominated HPhi. |
| // See if there a dominated edge into the phi. The input must be |
| // [source] and the position must correspond to a dominated block. |
| List<HBasicBlock> predecessors = currentBlock.predecessors; |
| for (int i = 0; i < predecessors.length; i++) { |
| if (current.inputs[i] != source) continue; |
| HBasicBlock predecessor = predecessors[i]; |
| if (dominatorBlock.dominates(predecessor)) { |
| _addUse(current, i); |
| } |
| } |
| } |
| } |
| |
| // Run through all the instructions before [dominator] and remove them from |
| // the users set. |
| if (usersInDominatorBlock > 0) { |
| for ( |
| var current = dominatorBlock.first; |
| !identical(current, dominator); |
| current = current!.next |
| ) { |
| if (users.remove(current)) { |
| if (--usersInDominatorBlock == 0) break; |
| } |
| } |
| if (excludeDominator) { |
| users.remove(dominator); |
| } |
| } |
| |
| // Convert users into a list of (user, input-index) uses. |
| for (HInstruction user in users) { |
| var inputs = user.inputs; |
| for (int i = 0; i < inputs.length; i++) { |
| if (inputs[i] == source) { |
| _addUse(user, i); |
| } |
| } |
| } |
| } |
| } |
| |
| /// A reference to a [HInstruction] that can hold its own source information. |
| /// |
| /// This used for attaching source information to reads of locals. |
| class HRef extends HInstruction { |
| HRef(HInstruction value, SourceInformation sourceInformation) |
| : super._oneInput(value, value.instructionType) { |
| this.sourceInformation = sourceInformation; |
| } |
| |
| HInstruction get value => inputs[0]; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitRef(this); |
| |
| @override |
| String toString() => 'HRef($value)'; |
| } |
| |
| /// Marker interface for late instructions. Late instructions are used after the |
| /// main optimization phases. They capture codegen decisions just prior to |
| /// generating JavaScript. |
| abstract interface class HLateInstruction {} |
| |
| /// Interface for instructions where the output is constrained to be one of the |
| /// inputs. Used for checks, where the SSA value of the check represents the |
| /// same value as the input, but restricted in some way, e.g., being of a |
| /// refined type or in a checked range. |
| abstract interface class HOutputConstrainedToAnInput implements HInstruction { |
| /// The input which is the 'same' as the output. |
| HInstruction get constrainedInput; |
| } |
| |
| /// A [HCheck] instruction is an instruction that might do a dynamic check at |
| /// runtime on an input instruction. To have proper instruction dependencies in |
| /// the graph, instructions that depend on the check being done reference the |
| /// [HCheck] instruction instead of the input instruction. |
| abstract class HCheck extends HInstruction |
| implements HOutputConstrainedToAnInput { |
| HCheck(super.inputs, super.type) { |
| setUseGvn(); |
| } |
| HCheck._oneInput(super.input, super.type) : super._oneInput() { |
| setUseGvn(); |
| } |
| HCheck._twoInputs(super.input1, super.input2, super.type) |
| : super._twoInputs() { |
| setUseGvn(); |
| } |
| |
| HInstruction get checkedInput => inputs[0]; |
| |
| @override |
| HInstruction get constrainedInput => checkedInput; |
| |
| @override |
| bool isJsStatement() => true; |
| |
| @override |
| bool canThrow(AbstractValueDomain domain) => true; |
| |
| @override |
| HInstruction nonCheck() => checkedInput.nonCheck(); |
| } |
| |
| enum StaticBoundsChecks { |
| alwaysFalse, |
| fullCheck, |
| alwaysAboveZero, |
| alwaysBelowLength, |
| alwaysTrue, |
| } |
| |
| class HBoundsCheck extends HCheck { |
| /// Details which tests have been done statically during compilation. |
| /// Default is that all checks must be performed dynamically. |
| StaticBoundsChecks staticChecks = StaticBoundsChecks.fullCheck; |
| |
| HBoundsCheck( |
| HInstruction index, |
| HInstruction length, |
| HInstruction array, |
| AbstractValue type, |
| ) : super([index, length, array], type); |
| |
| HInstruction get index => inputs[0]; |
| HInstruction get length => inputs[1]; |
| HInstruction get array => inputs[2]; |
| // There can be an additional fourth input which is the index to report to |
| // [ioore]. This is used by the expansion of [JSArray.removeLast]. |
| HInstruction get reportedIndex => inputs.length > 3 ? inputs[3] : index; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitBoundsCheck(this); |
| @override |
| _GvnType get _gvnType => _GvnType.boundsCheck; |
| @override |
| bool typeEquals(other) => other is HBoundsCheck; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| abstract class HConditionalBranch extends HControlFlow { |
| HConditionalBranch(HInstruction condition) { |
| inputs.add(condition); |
| } |
| HInstruction get condition => inputs[0]; |
| HBasicBlock get trueBranch => block!.successors[0]; |
| HBasicBlock get falseBranch => block!.successors[1]; |
| } |
| |
| abstract class HControlFlow extends HInstruction { |
| HControlFlow() : super._noType(); |
| |
| @override |
| bool isJsStatement() => true; |
| |
| /// HControlFlow instructions don't have an abstract value. |
| @override |
| AbstractValue get instructionType => |
| throw UnsupportedError('HControlFlow.instructionType'); |
| } |
| |
| // Allocates and initializes an instance. |
| class HCreate extends HInstruction { |
| final ClassEntity element; |
| |
| /// Does this instruction have reified type information as the last input? |
| final bool hasRtiInput; |
| |
| /// If this field is not `null`, this call is from an inlined constructor and |
| /// we have to register the instantiated type in the code generator. The |
| /// [instructionType] of this node is not enough, because we also need the |
| /// type arguments. See also [SsaFromAstMixin.currentInlinedInstantiations]. |
| List<InterfaceType>? instantiatedTypes; |
| |
| /// If this node creates a closure class, [callMethod] is the call method of |
| /// the closure class. |
| FunctionEntity? callMethod; |
| |
| HCreate( |
| this.element, |
| super.inputs, |
| super.type, |
| SourceInformation? sourceInformation, { |
| this.instantiatedTypes, |
| this.hasRtiInput = false, |
| this.callMethod, |
| }) { |
| this.sourceInformation = sourceInformation; |
| } |
| |
| @override |
| bool isAllocation(AbstractValueDomain domain) => true; |
| |
| HInstruction get rtiInput { |
| assert(hasRtiInput); |
| return inputs.last; |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitCreate(this); |
| |
| @override |
| String toString() => 'HCreate($element, $instantiatedTypes)'; |
| } |
| |
| // Allocates a box to hold mutated captured variables. |
| class HCreateBox extends HInstruction { |
| HCreateBox(super.type) : super._noInput(); |
| |
| @override |
| bool isAllocation(AbstractValueDomain domain) => true; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitCreateBox(this); |
| |
| @override |
| String toString() => 'HCreateBox()'; |
| } |
| |
| abstract class HInvoke extends HInstruction |
| with HasSettableAllowCSE, HasSettableAllowDCE { |
| bool _isAllocation = false; |
| |
| /// [isInterceptedCall] is true if this invocation uses the interceptor |
| /// calling convention where the first input is the methods and the second |
| /// input is the Dart receiver. |
| bool isInterceptedCall = false; |
| |
| /// [_isCallOnInterceptor] is true if this invocation uses the interceptor |
| /// calling convention *and* the interceptor input is an interceptor, and not |
| /// the receiver. A call has `isInterceptedCall == true` and |
| /// `_isCallOnInterceptor == false` after the 'self interceptor' optimization. |
| bool _isCallOnInterceptor = false; |
| |
| HInvoke(super.inputs, super.type) : super() { |
| sideEffects.setAllSideEffects(); |
| sideEffects.setDependsOnSomething(); |
| } |
| static const int argumentsOffset = 1; |
| |
| @override |
| bool canThrow(AbstractValueDomain domain) => true; |
| |
| @override |
| bool isAllocation(AbstractValueDomain domain) => _isAllocation; |
| |
| void setAllocation(bool value) { |
| _isAllocation = value; |
| } |
| |
| bool get isCallOnInterceptor => _isCallOnInterceptor; |
| |
| /// Update 'isCallOnInterceptor'. An intercepted call can go through |
| /// refinements that drop references to unneeded values or arguments: |
| /// |
| /// interceptor.foo(receiver, ...); // isCallOnInterceptor = true |
| /// --> |
| /// receiver.foo(receiver, ...); // isCallOnInterceptor = false |
| /// --> |
| /// receiver.foo(dummy, ...); // isCallOnInterceptor = false |
| void updateIsCallOnInterceptor() { |
| if (isInterceptedCall && _isCallOnInterceptor) { |
| final interceptor = inputs[0].nonCheck(); |
| final receiver = inputs[1].nonCheck(); |
| if (interceptor == receiver) { |
| _isCallOnInterceptor = false; |
| } else if (receiver case HConstant(constant: DummyConstantValue())) { |
| _isCallOnInterceptor = false; |
| } |
| } |
| } |
| } |
| |
| abstract class HInvokeDynamic extends HInvoke implements InstructionContext { |
| final InvokeDynamicSpecializer specializer; |
| |
| Selector _selector; |
| AbstractValue _receiverType; |
| final AbstractValue _originalReceiverType; |
| |
| /// Static type at call-site, often better than union-over-targets. |
| AbstractValue? staticType; |
| |
| /// `true` if the type parameters at the call are known to be invariant with |
| /// respect to the type parameters of the receiver instance. This corresponds |
| /// to the [ir.InstanceInvocation.isInvariant] property. Parametric |
| /// covariance checks of the target may be omitted. If the target has explicit |
| /// `covariant` checks, these might still need to be checked. |
| bool isInvariant = false; |
| |
| /// `true` for an indexed getter or setter if the index is known to be in |
| /// range. This corresponds to the [ir.InstanceInvocation.isBoundsSafe] |
| /// property but and may updated with additional analysis. |
| bool isBoundsSafe = false; |
| |
| // Cached target when non-nullable receiver type and selector determine a |
| // single target. This is in effect a direct call (except for a possible |
| // `null` receiver). The element should only be set if the inputs are correct |
| // for a direct call. These constraints exclude caching a target when the call |
| // needs defaulted arguments, is `noSuchMethod` (legacy), or is a call-through |
| // stub. |
| MemberEntity? element; |
| |
| @override |
| MemberEntity? instructionContext; |
| |
| HInvokeDynamic( |
| Selector selector, |
| this._receiverType, |
| this.element, |
| List<HInstruction> inputs, |
| bool isIntercepted, |
| AbstractValue resultType, |
| ) : _selector = selector, |
| _originalReceiverType = _receiverType, |
| specializer = isIntercepted |
| ? InvokeDynamicSpecializer.lookupSpecializer(selector) |
| : const InvokeDynamicSpecializer(), |
| super(inputs, resultType) { |
| isInterceptedCall = isIntercepted; |
| _isCallOnInterceptor = isIntercepted; |
| updateIsCallOnInterceptor(); |
| } |
| |
| Selector get selector => _selector; |
| |
| set selector(Selector selector) { |
| _selector = selector; |
| element = null; // Cached element would no longer match new selector. |
| } |
| |
| AbstractValue get receiverType => _receiverType; |
| |
| void updateReceiverType( |
| AbstractValueDomain abstractValueDomain, |
| AbstractValue value, |
| ) { |
| _receiverType = abstractValueDomain.intersection( |
| _originalReceiverType, |
| value, |
| ); |
| } |
| |
| /// Returns [value] narrowed by the [staticType]. |
| AbstractValue computeInstructionType( |
| AbstractValue value, |
| AbstractValueDomain abstractValueDomain, |
| ) { |
| if (staticType == null) return value; |
| |
| // When the receiver might be a LegacyJavaScriptObject, we don't trust the |
| // static type. Global type inference is conservative for legacy js-interop |
| // methods, so we should be conservative here too. |
| if (_possiblyLegacyJavaScriptObject( |
| selector, |
| receiverType, |
| abstractValueDomain, |
| )) { |
| return value; |
| } |
| |
| final narrowed = abstractValueDomain.intersection(value, staticType!); |
| // Preserve the sentinel in [value] since the static type does not include |
| // a sentinel and the intersection would remove it. |
| return abstractValueDomain.isLateSentinel(value).isPotentiallyTrue |
| ? abstractValueDomain.includeLateSentinel(narrowed) |
| : narrowed; |
| } |
| |
| static bool _possiblyLegacyJavaScriptObject( |
| Selector selector, |
| AbstractValue type, |
| AbstractValueDomain domain, |
| ) { |
| // Legacy js-interop cannot override `[]`. |
| if (selector.isIndex) return false; |
| if (domain.isPrimitiveOrNull(type).isDefinitelyTrue) return false; |
| if (domain.isInterceptor(type).isDefinitelyFalse) return false; |
| // We get here for typed_data classes. |
| // TODO(sra): Test against LegacyJavaScriptObject explicitly. |
| // TODO(sra): Ensure that regular closures are not conflated with |
| // JavaScriptFunction which also implements `Function`. |
| return true; |
| } |
| |
| @override |
| String toString() => 'invoke dynamic: selector=$selector, mask=$receiverType'; |
| |
| HInstruction get receiver => inputs[0]; |
| |
| @override |
| HInstruction getDartReceiver() { |
| return _isCallOnInterceptor ? inputs[1] : inputs[0]; |
| } |
| |
| /// The type arguments passed in this dynamic invocation. |
| List<DartType> get typeArguments; |
| |
| @override |
| _GvnType get _gvnType => _GvnType.invokeDynamic; |
| |
| @override |
| bool typeEquals(other) => other is HInvokeDynamic; |
| |
| @override |
| bool dataEquals(HInvokeDynamic other) { |
| return selector == other.selector && |
| (useGvn() == other.useGvn() || allowCSE == other.allowCSE); |
| } |
| } |
| |
| class HInvokeClosure extends HInvokeDynamic { |
| @override |
| final List<DartType> typeArguments; |
| |
| HInvokeClosure( |
| Selector selector, |
| AbstractValue receiverType, |
| List<HInstruction> inputs, |
| AbstractValue resultType, |
| this.typeArguments, |
| ) : super(selector, receiverType, null, inputs, false, resultType) { |
| assert(selector.isMaybeClosureCall); |
| assert(selector.callStructure.typeArgumentCount == typeArguments.length); |
| assert(!isInterceptedCall); |
| } |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitInvokeClosure(this); |
| } |
| |
| class HInvokeDynamicMethod extends HInvokeDynamic { |
| @override |
| final List<DartType> typeArguments; |
| |
| HInvokeDynamicMethod( |
| Selector selector, |
| AbstractValue receiverType, |
| List<HInstruction> inputs, |
| AbstractValue resultType, |
| this.typeArguments, |
| SourceInformation? sourceInformation, { |
| bool isIntercepted = false, |
| }) : super(selector, receiverType, null, inputs, isIntercepted, resultType) { |
| this.sourceInformation = sourceInformation; |
| assert(selector.callStructure.typeArgumentCount == typeArguments.length); |
| } |
| |
| @override |
| String toString() => |
| 'invoke dynamic method: selector=$selector, mask=$receiverType'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitInvokeDynamicMethod(this); |
| } |
| |
| abstract class HInvokeDynamicField extends HInvokeDynamic { |
| HInvokeDynamicField( |
| Selector selector, |
| AbstractValue receiverType, |
| MemberEntity? element, |
| List<HInstruction> inputs, |
| bool isIntercepted, |
| AbstractValue resultType, |
| ) : super(selector, receiverType, element, inputs, isIntercepted, resultType); |
| |
| @override |
| String toString() => |
| 'invoke dynamic field: selector=$selector, mask=$receiverType'; |
| } |
| |
| class HInvokeDynamicGetter extends HInvokeDynamicField { |
| HInvokeDynamicGetter( |
| Selector selector, |
| AbstractValue receiverType, |
| MemberEntity? element, |
| List<HInstruction> inputs, |
| bool isIntercepted, |
| AbstractValue resultType, |
| SourceInformation? sourceInformation, |
| ) : super( |
| selector, |
| receiverType, |
| element, |
| inputs, |
| isIntercepted, |
| resultType, |
| ) { |
| this.sourceInformation = sourceInformation; |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitInvokeDynamicGetter(this); |
| |
| bool get isTearOff => element != null && element!.isFunction; |
| |
| @override |
| List<DartType> get typeArguments => const []; |
| |
| // There might be an interceptor input, so `inputs.last` is the dart receiver. |
| @override |
| bool canThrow(AbstractValueDomain domain) => isTearOff |
| ? inputs.last.isNull(domain).isPotentiallyTrue |
| : super.canThrow(domain); |
| |
| @override |
| String toString() => |
| 'invoke dynamic getter: selector=$selector, mask=$receiverType'; |
| } |
| |
| class HInvokeDynamicSetter extends HInvokeDynamicField { |
| /// If `true` a call to the setter is needed for checking the type even |
| /// though the target field is known. |
| bool needsCheck = false; |
| |
| HInvokeDynamicSetter( |
| Selector selector, |
| AbstractValue receiverType, |
| MemberEntity? element, |
| List<HInstruction> inputs, |
| bool isIntercepted, |
| // TODO(johnniwinther): The result type for a setter should be the empty |
| // type. |
| AbstractValue resultType, |
| SourceInformation? sourceInformation, |
| ) : super( |
| selector, |
| receiverType, |
| element, |
| inputs, |
| isIntercepted, |
| resultType, |
| ) { |
| this.sourceInformation = sourceInformation; |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitInvokeDynamicSetter(this); |
| |
| @override |
| List<DartType> get typeArguments => const []; |
| |
| @override |
| String toString() => |
| 'invoke dynamic setter: selector=$selector, mask=$receiverType, element=$element'; |
| } |
| |
| class HInvokeStatic extends HInvoke { |
| final MemberEntity element; |
| |
| /// The type arguments passed in this static invocation. |
| final List<DartType> typeArguments; |
| |
| bool targetCanThrow; |
| |
| @override |
| bool canThrow(AbstractValueDomain domain) => targetCanThrow; |
| |
| /// If this instruction is a call to a constructor, [instantiatedTypes] |
| /// contains the type(s) used in the (Dart) `New` expression(s). The |
| /// [instructionType] of this node is not enough, because we also need the |
| /// type arguments. See also [SsaFromAstMixin.currentInlinedInstantiations]. |
| List<InterfaceType>? instantiatedTypes; |
| |
| /// The first input must be the target. |
| HInvokeStatic( |
| this.element, |
| List<HInstruction> inputs, |
| AbstractValue type, |
| this.typeArguments, { |
| this.targetCanThrow = true, |
| bool isIntercepted = false, |
| }) : super(inputs, type) { |
| isInterceptedCall = isIntercepted; |
| _isCallOnInterceptor = isIntercepted; |
| updateIsCallOnInterceptor(); |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitInvokeStatic(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.invokeStatic; |
| @override |
| bool typeEquals(other) => other is HInvokeStatic; |
| @override |
| bool dataEquals(HInvokeStatic other) => element == other.element; |
| |
| @override |
| String toString() => 'invoke static: $element'; |
| } |
| |
| class HInvokeSuper extends HInvokeStatic { |
| /// The class where the call to super is being done. |
| final ClassEntity caller; |
| final bool isSetter; |
| final Selector selector; |
| |
| HInvokeSuper( |
| MemberEntity element, |
| this.caller, |
| this.selector, |
| List<HInstruction> inputs, |
| bool isIntercepted, |
| AbstractValue type, |
| List<DartType> typeArguments, |
| SourceInformation? sourceInformation, { |
| required this.isSetter, |
| }) : super( |
| element, |
| inputs, |
| type, |
| typeArguments, |
| isIntercepted: isIntercepted, |
| ) { |
| this.sourceInformation = sourceInformation; |
| } |
| |
| HInstruction get receiver => inputs[0]; |
| @override |
| HInstruction getDartReceiver() { |
| return isCallOnInterceptor ? inputs[1] : inputs[0]; |
| } |
| |
| @override |
| String toString() => 'invoke super: $element'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitInvokeSuper(this); |
| |
| HInstruction get value { |
| assert(isSetter); |
| // The 'inputs' are [receiver, value] or [interceptor, receiver, value]. |
| return inputs.last; |
| } |
| } |
| |
| class HInvokeConstructorBody extends HInvokeStatic { |
| // The 'inputs' are |
| // [receiver, arg1, ..., argN] or |
| // [interceptor, receiver, arg1, ... argN]. |
| HInvokeConstructorBody( |
| ConstructorBodyEntity element, |
| List<HInstruction> inputs, |
| AbstractValue type, |
| SourceInformation? sourceInformation, |
| ) : super(element, inputs, type, const []) { |
| this.sourceInformation = sourceInformation; |
| } |
| |
| @override |
| String toString() => 'invoke constructor body: ${element.name}'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitInvokeConstructorBody(this); |
| } |
| |
| class HInvokeGeneratorBody extends HInvokeStatic { |
| // Directly call the JGeneratorBody method. The generator body can be a static |
| // method or a member. The target is directly called. |
| // The 'inputs' are |
| // [arg1, ..., argN] or |
| // [receiver, arg1, ..., argN] or |
| // [interceptor, receiver, arg1, ... argN]. |
| // The 'inputs' may or may not have an additional type argument used for |
| // creating the generator (T for new Completer<T>() inside the body). |
| HInvokeGeneratorBody( |
| FunctionEntity element, |
| List<HInstruction> inputs, |
| AbstractValue type, |
| SourceInformation? sourceInformation, |
| ) : super(element, inputs, type, const []) { |
| this.sourceInformation = sourceInformation; |
| } |
| |
| @override |
| String toString() => 'HInvokeGeneratorBody(${element.name})'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitInvokeGeneratorBody(this); |
| } |
| |
| abstract class HFieldAccess extends HInstruction { |
| final FieldEntity element; |
| |
| HFieldAccess(this.element, List<HInstruction> inputs, AbstractValue type) |
| : super(inputs, type); |
| |
| HInstruction get receiver => inputs[0]; |
| } |
| |
| class HFieldGet extends HFieldAccess { |
| final bool isAssignable; |
| |
| HFieldGet( |
| FieldEntity element, |
| HInstruction receiver, |
| AbstractValue type, |
| SourceInformation? sourceInformation, { |
| required this.isAssignable, |
| }) : super(element, [receiver], type) { |
| this.sourceInformation = sourceInformation; |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| setUseGvn(); |
| if (isAssignable) { |
| sideEffects.setDependsOnInstancePropertyStore(); |
| } |
| } |
| |
| @override |
| bool canThrow(AbstractValueDomain domain) => |
| receiver.isNull(domain).isPotentiallyTrue; |
| |
| @override |
| HInstruction getDartReceiver() => receiver; |
| @override |
| bool onlyThrowsNSM() => true; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitFieldGet(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.fieldGet; |
| @override |
| bool typeEquals(other) => other is HFieldGet; |
| @override |
| bool dataEquals(HFieldGet other) => element == other.element; |
| @override |
| String toString() => "FieldGet(element=$element,type=$instructionType)"; |
| } |
| |
| class HFieldSet extends HFieldAccess { |
| HFieldSet(FieldEntity element, HInstruction receiver, HInstruction value) |
| : super(element, [receiver, value], value.instructionType) { |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| sideEffects.setChangesInstanceProperty(); |
| } |
| |
| @override |
| bool canThrow(AbstractValueDomain domain) => |
| receiver.isNull(domain).isPotentiallyTrue; |
| |
| @override |
| HInstruction getDartReceiver() => receiver; |
| @override |
| bool onlyThrowsNSM() => true; |
| |
| HInstruction get value => inputs[1]; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitFieldSet(this); |
| |
| @override |
| String toString() => "FieldSet(element=$element,type=$instructionType)"; |
| } |
| |
| // Raw reference to a function. |
| class HFunctionReference extends HInstruction { |
| FunctionEntity element; |
| HFunctionReference(this.element, super.type) : super._noInput() { |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| setUseGvn(); |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitFunctionReference(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.functionReference; |
| @override |
| bool typeEquals(other) => other is HFunctionReference; |
| @override |
| bool dataEquals(HFunctionReference other) => element == other.element; |
| @override |
| String toString() => "FunctionReference($element)"; |
| } |
| |
| class HGetLength extends HInstruction { |
| final bool isAssignable; |
| HGetLength(super.receiver, super.type, {required this.isAssignable}) |
| : super._oneInput() { |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| setUseGvn(); |
| if (isAssignable) { |
| sideEffects.setDependsOnInstancePropertyStore(); |
| } |
| } |
| |
| HInstruction get receiver => inputs.single; |
| |
| @override |
| bool canThrow(AbstractValueDomain domain) => |
| receiver.isNull(domain).isPotentiallyTrue; |
| |
| @override |
| HInstruction getDartReceiver() => receiver; |
| @override |
| bool onlyThrowsNSM() => true; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitGetLength(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.getLength; |
| @override |
| bool typeEquals(other) => other is HGetLength; |
| @override |
| bool dataEquals(HGetLength other) => true; |
| @override |
| String toString() => "GetLength()"; |
| } |
| |
| enum ReadModifyWriteKind { assign, prefix, postfix } |
| |
| /// HReadModifyWrite is a late stage instruction for a field (property) update |
| /// via an assignment operation or pre- or post-increment. |
| class HReadModifyWrite extends HInstruction implements HLateInstruction { |
| final FieldEntity element; |
| final String jsOp; |
| final ReadModifyWriteKind opKind; |
| |
| HReadModifyWrite._( |
| this.element, |
| this.jsOp, |
| this.opKind, |
| super.inputs, |
| super.type, |
| ) { |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| sideEffects.setChangesInstanceProperty(); |
| sideEffects.setDependsOnInstancePropertyStore(); |
| } |
| |
| HReadModifyWrite.assignOp( |
| FieldEntity element, |
| String jsOp, |
| HInstruction receiver, |
| HInstruction operand, |
| AbstractValue type, |
| ) : this._(element, jsOp, ReadModifyWriteKind.assign, [ |
| receiver, |
| operand, |
| ], type); |
| |
| HReadModifyWrite.preOp( |
| FieldEntity element, |
| String jsOp, |
| HInstruction receiver, |
| AbstractValue type, |
| ) : this._(element, jsOp, ReadModifyWriteKind.prefix, [receiver], type); |
| |
| HReadModifyWrite.postOp( |
| FieldEntity element, |
| String jsOp, |
| HInstruction receiver, |
| AbstractValue type, |
| ) : this._(element, jsOp, ReadModifyWriteKind.postfix, [receiver], type); |
| |
| HInstruction get receiver => inputs[0]; |
| |
| bool get isPreOp => opKind == ReadModifyWriteKind.prefix; |
| bool get isPostOp => opKind == ReadModifyWriteKind.postfix; |
| bool get isAssignOp => opKind == ReadModifyWriteKind.assign; |
| |
| @override |
| bool canThrow(AbstractValueDomain domain) => |
| receiver.isNull(domain).isPotentiallyTrue; |
| |
| @override |
| HInstruction getDartReceiver() => receiver; |
| @override |
| bool onlyThrowsNSM() => true; |
| |
| HInstruction get value => inputs[1]; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitReadModifyWrite(this); |
| |
| @override |
| String toString() => "ReadModifyWrite $jsOp $opKind $element"; |
| } |
| |
| abstract class HLocalAccess extends HInstruction { |
| final Local variable; |
| |
| HLocalAccess(this.variable, List<HInstruction> inputs, AbstractValue type) |
| : super(inputs, type); |
| |
| HInstruction get receiver => inputs[0]; |
| } |
| |
| class HLocalGet extends HLocalAccess { |
| // No need to use GVN for a [HLocalGet], it is just a local |
| // access. |
| HLocalGet( |
| Local variable, |
| HLocalValue local, |
| AbstractValue type, |
| SourceInformation? sourceInformation, |
| ) : super(variable, [local], type) { |
| this.sourceInformation = sourceInformation; |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitLocalGet(this); |
| |
| HLocalValue get local => inputs[0] as HLocalValue; |
| |
| @override |
| String toString() => 'HLocalGet($local).$hashCode'; |
| } |
| |
| class HLocalSet extends HLocalAccess { |
| HLocalSet( |
| Local variable, |
| HLocalValue local, |
| HInstruction value, |
| AbstractValue type, |
| ) : super(variable, [local, value], type); |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitLocalSet(this); |
| |
| HLocalValue get local => inputs[0] as HLocalValue; |
| HInstruction get value => inputs[1]; |
| } |
| |
| /// Invocation of a native or JS-interop method. |
| /// |
| /// Includes various invocations where the JavaScript form is similar to the |
| /// Dart form: |
| /// |
| /// receiver.property // An instance getter |
| /// receiver.property = value // An instance setter |
| /// receiver.method(arg) // An instance method |
| /// |
| /// Class.property // A static getter |
| /// Class.property = value // A static setter |
| /// Class.method(arg) // A static method |
| /// new Class(arg) // A constructor |
| /// |
| /// HInvokeDynamicMethod can be lowered to HInvokeExternal with the same |
| /// [element]. The difference is a HInvokeDynamicMethod is a call to a |
| /// Dart-calling-convention stub identified by [element] that contains a call to |
| /// the external method, whereas a HInvokeExternal instruction is a direct |
| /// JavaScript call to the external method identified by [element]. |
| class HInvokeExternal extends HInvoke { |
| final FunctionEntity element; |
| |
| // The following fields are functions of [element] that are extracted for |
| // convenience. |
| final NativeBehavior? nativeBehavior; |
| final NativeThrowBehavior throwBehavior; |
| |
| HInvokeExternal( |
| this.element, |
| List<HInstruction> inputs, |
| AbstractValue type, |
| this.nativeBehavior, { |
| SourceInformation? sourceInformation, |
| }) : throwBehavior = nativeBehavior?.throwBehavior ?? NativeThrowBehavior.may, |
| super(inputs, type) { |
| if (nativeBehavior == null) { |
| sideEffects.setAllSideEffects(); |
| sideEffects.setDependsOnSomething(); |
| } else { |
| sideEffects.add(nativeBehavior!.sideEffects); |
| } |
| if (nativeBehavior != null && nativeBehavior!.useGvn) { |
| setUseGvn(); |
| } |
| this.sourceInformation = sourceInformation; |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitInvokeExternal(this); |
| |
| @override |
| bool isJsStatement() => false; |
| |
| @override |
| bool canThrow(AbstractValueDomain domain) { |
| if (element.isInstanceMember) { |
| if (inputs.isNotEmpty) { |
| return inputs.first.isNull(domain).isPotentiallyTrue |
| ? throwBehavior.canThrow |
| : throwBehavior.onNonNull.canThrow; |
| } |
| } |
| return throwBehavior.canThrow; |
| } |
| |
| @override |
| bool onlyThrowsNSM() => throwBehavior.isOnlyNullNSMGuard; |
| |
| @override |
| bool isAllocation(AbstractValueDomain domain) => |
| nativeBehavior != null && |
| nativeBehavior!.isAllocation && |
| isNull(domain).isDefinitelyFalse; |
| |
| /// Returns `true` if the call will throw an NoSuchMethod error if [receiver] |
| /// is `null` before having any other side-effects. |
| bool isNullGuardFor(HInstruction receiver) { |
| if (!element.isInstanceMember) return false; |
| if (inputs.isEmpty) return false; |
| if (inputs.first.nonCheck() != receiver.nonCheck()) return false; |
| return true; |
| } |
| |
| @override |
| _GvnType get _gvnType => _GvnType.invokeExternal; |
| @override |
| bool typeEquals(other) => other is HInvokeExternal; |
| @override |
| bool dataEquals(HInvokeExternal other) { |
| return element == other.element; |
| } |
| |
| @override |
| String toString() => 'HInvokeExternal($element)'; |
| } |
| |
| abstract class HForeign extends HInstruction { |
| HForeign(AbstractValue type, List<HInstruction> inputs) : super(inputs, type); |
| |
| bool get isStatement => false; |
| NativeBehavior? get nativeBehavior => null; |
| |
| @override |
| bool canThrow(AbstractValueDomain domain) { |
| return sideEffects.hasSideEffects() || sideEffects.dependsOnSomething(); |
| } |
| } |
| |
| class HForeignCode extends HForeign { |
| final js.Template codeTemplate; |
| @override |
| final bool isStatement; |
| @override |
| final NativeBehavior? nativeBehavior; |
| late final NativeThrowBehavior throwBehavior; |
| |
| HForeignCode( |
| this.codeTemplate, |
| AbstractValue type, |
| List<HInstruction> inputs, { |
| this.isStatement = false, |
| SideEffects? effects, |
| this.nativeBehavior, |
| NativeThrowBehavior? throwBehavior, |
| }) : throwBehavior = |
| throwBehavior ?? |
| nativeBehavior?.throwBehavior ?? |
| NativeThrowBehavior.may, |
| super(type, inputs) { |
| effects ??= nativeBehavior?.sideEffects; |
| |
| if (effects != null) sideEffects.add(effects); |
| if (nativeBehavior?.useGvn == true) { |
| setUseGvn(); |
| } |
| } |
| |
| HForeignCode.statement( |
| js.Template codeTemplate, |
| List<HInstruction> inputs, |
| SideEffects effects, |
| NativeBehavior nativeBehavior, |
| AbstractValue type, |
| ) : this( |
| codeTemplate, |
| type, |
| inputs, |
| isStatement: true, |
| effects: effects, |
| nativeBehavior: nativeBehavior, |
| ); |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitForeignCode(this); |
| |
| @override |
| bool isJsStatement() => isStatement; |
| |
| @override |
| bool canThrow(AbstractValueDomain domain) { |
| if (inputs.isNotEmpty) { |
| return inputs.first.isNull(domain).isPotentiallyTrue |
| ? throwBehavior.canThrow |
| : throwBehavior.onNonNull.canThrow; |
| } |
| return throwBehavior.canThrow; |
| } |
| |
| @override |
| bool onlyThrowsNSM() => throwBehavior.isOnlyNullNSMGuard; |
| |
| @override |
| bool isAllocation(AbstractValueDomain domain) => |
| nativeBehavior != null && |
| nativeBehavior!.isAllocation && |
| isNull(domain).isDefinitelyFalse; |
| |
| /// Returns `true` if the template will throw an NoSuchMethod error if |
| /// [receiver] is `null` before having any other side-effects. |
| bool isNullGuardFor(HInstruction? receiver) { |
| if (!throwBehavior.isNullNSMGuard) return false; |
| if (inputs.isEmpty) return false; |
| if (inputs.first.nonCheck() != receiver!.nonCheck()) return false; |
| return true; |
| } |
| |
| @override |
| _GvnType get _gvnType => _GvnType.foreignCode; |
| @override |
| bool typeEquals(other) => other is HForeignCode; |
| @override |
| bool dataEquals(HForeignCode other) { |
| if (codeTemplate.source == null) { |
| return other.codeTemplate.source == null && |
| // The ASTs will be equal if identical, and in some limited other |
| // cases, like a ModularExpression. |
| codeTemplate.ast == other.codeTemplate.ast; |
| } |
| return codeTemplate.source == other.codeTemplate.source; |
| } |
| |
| @override |
| String toString() => 'HForeignCode("${codeTemplate.source}")'; |
| } |
| |
| abstract class HInvokeBinary extends HInstruction { |
| HInvokeBinary(super.left, super.right, super.type) : super._twoInputs() { |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| setUseGvn(); |
| } |
| |
| HInstruction get left => inputs[0]; |
| HInstruction get right => inputs[1]; |
| |
| constant_system.BinaryOperation operation(); |
| } |
| |
| abstract class HBinaryArithmetic extends HInvokeBinary { |
| HBinaryArithmetic(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| constant_system.BinaryOperation operation(); |
| } |
| |
| class HAdd extends HBinaryArithmetic { |
| HAdd(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitAdd(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.add; |
| @override |
| _GvnType get _gvnType => _GvnType.add; |
| @override |
| bool typeEquals(other) => other is HAdd; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HDivide extends HBinaryArithmetic { |
| HDivide(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitDivide(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.divide; |
| @override |
| _GvnType get _gvnType => _GvnType.divide; |
| @override |
| bool typeEquals(other) => other is HDivide; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HMultiply extends HBinaryArithmetic { |
| HMultiply(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitMultiply(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.multiply; |
| @override |
| _GvnType get _gvnType => _GvnType.multiply; |
| @override |
| bool typeEquals(other) => other is HMultiply; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HSubtract extends HBinaryArithmetic { |
| HSubtract(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitSubtract(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.subtract; |
| @override |
| _GvnType get _gvnType => _GvnType.subtract; |
| @override |
| bool typeEquals(other) => other is HSubtract; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HTruncatingDivide extends HBinaryArithmetic { |
| HTruncatingDivide(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitTruncatingDivide(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => |
| constant_system.truncatingDivide; |
| @override |
| _GvnType get _gvnType => _GvnType.truncatingDivide; |
| @override |
| bool typeEquals(other) => other is HTruncatingDivide; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HRemainder extends HBinaryArithmetic { |
| HRemainder(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitRemainder(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.remainder; |
| @override |
| _GvnType get _gvnType => _GvnType.remainder; |
| @override |
| bool typeEquals(other) => other is HRemainder; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| /// An [HSwitch] instruction has one input for the incoming |
| /// value, and one input per constant that it can switch on. |
| /// Its block has one successor per constant, and one for the default. |
| class HSwitch extends HControlFlow { |
| HSwitch(HInstruction input) { |
| inputs.add(input); |
| } |
| |
| HConstant constant(int index) => inputs[index + 1] as HConstant; |
| HInstruction get expression => inputs[0]; |
| |
| /// Provides the target to jump to if none of the constants match |
| /// the expression. If the switch had no default case, this is the |
| /// following join-block. |
| HBasicBlock get defaultTarget => block!.successors.last; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitSwitch(this); |
| |
| @override |
| String toString() => "HSwitch cases = $inputs"; |
| } |
| |
| abstract class HBinaryBitOp extends HInvokeBinary { |
| /// JavaScript bitwise operations like `&` produce a 32-bit signed results but |
| /// the Dart-web operations produce an unsigned result. Conversion to unsigned |
| /// might be unnecessary (e.g. the inputs are such that JavaScript operation |
| /// cannot produce a negative value). During instruction selection we |
| /// determine if conversion is unnecessary. |
| bool requiresUintConversion = true; |
| |
| HBinaryBitOp(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| } |
| |
| class HShiftLeft extends HBinaryBitOp { |
| HShiftLeft(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitShiftLeft(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.shiftLeft; |
| @override |
| _GvnType get _gvnType => _GvnType.shiftLeft; |
| @override |
| bool typeEquals(other) => other is HShiftLeft; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HShiftRight extends HBinaryBitOp { |
| HShiftRight(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitShiftRight(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.shiftRight; |
| @override |
| _GvnType get _gvnType => _GvnType.shiftRight; |
| @override |
| bool typeEquals(other) => other is HShiftRight; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HBitOr extends HBinaryBitOp { |
| HBitOr(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitBitOr(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.bitOr; |
| @override |
| _GvnType get _gvnType => _GvnType.bitOr; |
| @override |
| bool typeEquals(other) => other is HBitOr; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HBitAnd extends HBinaryBitOp { |
| HBitAnd(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitBitAnd(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.bitAnd; |
| @override |
| _GvnType get _gvnType => _GvnType.bitAnd; |
| @override |
| bool typeEquals(other) => other is HBitAnd; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HBitXor extends HBinaryBitOp { |
| HBitXor(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitBitXor(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.bitXor; |
| @override |
| _GvnType get _gvnType => _GvnType.bitXor; |
| @override |
| bool typeEquals(other) => other is HBitXor; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| abstract class HInvokeUnary extends HInstruction { |
| HInvokeUnary(super.input, super.type) : super._oneInput() { |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| setUseGvn(); |
| } |
| |
| HInstruction get operand => inputs[0]; |
| |
| constant_system.UnaryOperation operation(); |
| } |
| |
| class HNegate extends HInvokeUnary { |
| HNegate(HInstruction input, AbstractValue type) : super(input, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitNegate(this); |
| |
| @override |
| constant_system.UnaryOperation operation() => constant_system.negate; |
| @override |
| _GvnType get _gvnType => _GvnType.negate; |
| @override |
| bool typeEquals(other) => other is HNegate; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HAbs extends HInvokeUnary { |
| HAbs(HInstruction input, AbstractValue type) : super(input, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitAbs(this); |
| |
| @override |
| constant_system.UnaryOperation operation() => constant_system.abs; |
| @override |
| _GvnType get _gvnType => _GvnType.abs; |
| @override |
| bool typeEquals(other) => other is HAbs; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HBitNot extends HInvokeUnary { |
| /// JavaScript `~` produces a 32-bit signed result the Dart-web operation |
| /// produces an unsigned result. Conversion to unsigned might be unnecessary |
| /// (e.g. the value is immediately masked). During instruction selection we |
| /// determine if conversion is unnecessary. |
| bool requiresUintConversion = true; |
| |
| HBitNot(HInstruction input, AbstractValue type) : super(input, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitBitNot(this); |
| |
| @override |
| constant_system.UnaryOperation operation() => constant_system.bitNot; |
| @override |
| _GvnType get _gvnType => _GvnType.bitNot; |
| @override |
| bool typeEquals(other) => other is HBitNot; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HExit extends HControlFlow { |
| @override |
| String toString() => 'exit'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitExit(this); |
| } |
| |
| class HGoto extends HControlFlow { |
| @override |
| String toString() => 'goto'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitGoto(this); |
| } |
| |
| abstract class HJump extends HControlFlow { |
| final JumpTarget target; |
| final LabelDefinition? label; |
| HJump(this.target, SourceInformation? sourceInformation) : label = null { |
| this.sourceInformation = sourceInformation; |
| } |
| HJump.toLabel( |
| LabelDefinition this.label, |
| SourceInformation? sourceInformation, |
| ) : target = label.target { |
| this.sourceInformation = sourceInformation; |
| } |
| } |
| |
| class HBreak extends HJump { |
| /// Signals that this is a special break instruction for the synthetic loop |
| /// generated for a switch statement with continue statements. See |
| /// [SsaFromAstMixin.buildComplexSwitchStatement] for detail. |
| final bool breakSwitchContinueLoop; |
| |
| HBreak( |
| JumpTarget target, |
| SourceInformation? sourceInformation, { |
| this.breakSwitchContinueLoop = false, |
| }) : super(target, sourceInformation); |
| |
| HBreak.toLabel(LabelDefinition label, SourceInformation? sourceInformation) |
| : breakSwitchContinueLoop = false, |
| super.toLabel(label, sourceInformation); |
| |
| @override |
| String toString() => (label != null) ? 'break ${label!.labelName}' : 'break'; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitBreak(this); |
| } |
| |
| class HContinue extends HJump { |
| HContinue(JumpTarget target, SourceInformation? sourceInformation) |
| : super(target, sourceInformation); |
| |
| HContinue.toLabel(LabelDefinition label, SourceInformation? sourceInformation) |
| : super.toLabel(label, sourceInformation); |
| |
| @override |
| String toString() => |
| (label != null) ? 'continue ${label!.labelName}' : 'continue'; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitContinue(this); |
| } |
| |
| class HTry extends HControlFlow { |
| HLocalValue? exception; |
| HBasicBlock? catchBlock; |
| HBasicBlock? finallyBlock; |
| @override |
| String toString() => 'try'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitTry(this); |
| HBasicBlock get joinBlock => block!.successors.last; |
| } |
| |
| // An [HExitTry] control flow node is used when the body of a try or |
| // the body of a catch contains a return, break or continue. To build |
| // the control flow graph, we explicitly mark the body that |
| // leads to one of this instruction a predecessor of catch and |
| // finally. |
| class HExitTry extends HControlFlow { |
| @override |
| String toString() => 'exit try'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitExitTry(this); |
| HBasicBlock get bodyTrySuccessor => block!.successors[0]; |
| } |
| |
| class HIf extends HConditionalBranch { |
| HBlockFlow? blockInformation; |
| HIf(super.condition); |
| @override |
| String toString() => 'if'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitIf(this); |
| |
| HBasicBlock get thenBlock { |
| assert(identical(block!.dominatedBlocks[0], block!.successors[0])); |
| return block!.successors[0]; |
| } |
| |
| HBasicBlock get elseBlock { |
| assert(identical(block!.dominatedBlocks[1], block!.successors[1])); |
| return block!.successors[1]; |
| } |
| |
| HBasicBlock? get joinBlock => blockInformation!.continuation; |
| } |
| |
| class HLoopBranch extends HConditionalBranch { |
| HLoopBranch(super.condition); |
| @override |
| String toString() => 'loop-branch'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitLoopBranch(this); |
| } |
| |
| class HConstant extends HInstruction { |
| final ConstantValue constant; |
| HConstant._internal(this.constant, super.constantType) : super._noInput(); |
| |
| @override |
| String toString() => 'literal: ${constant.toStructuredText(null)}'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitConstant(this); |
| |
| @override |
| bool isConstantBoolean() => constant is BoolConstantValue; |
| @override |
| bool isConstantNull() => constant is NullConstantValue; |
| @override |
| bool isConstantNumber() => constant is NumConstantValue; |
| @override |
| bool isConstantInteger() => constant is IntConstantValue; |
| @override |
| bool isConstantString() => constant is StringConstantValue; |
| @override |
| bool isConstantFalse() => constant is FalseConstantValue; |
| @override |
| bool isConstantTrue() => constant is TrueConstantValue; |
| |
| // Maybe avoid this if the literal is big? |
| @override |
| bool isCodeMotionInvariant() => true; |
| |
| @override |
| set instructionType(AbstractValue type) { |
| // Only lists can be specialized. The SSA builder uses the |
| // inferrer for finding the type of a constant list. We should |
| // have the constant know its type instead. |
| if (constant is! ListConstantValue) return; |
| super.instructionType = type; |
| } |
| } |
| |
| class HNot extends HInstruction { |
| HNot(super.value, super.type) : super._oneInput() { |
| setUseGvn(); |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitNot(this); |
| @override |
| _GvnType get _gvnType => _GvnType.not; |
| @override |
| bool typeEquals(other) => other is HNot; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| /// An [HLocalValue] represents a local. Unlike [HParameterValue]s its |
| /// first use must be in an HLocalSet. That is, [HParameterValue]s have a |
| /// value from the start, whereas [HLocalValue]s need to be initialized first. |
| class HLocalValue extends HInstruction { |
| HLocalValue(Entity? variable, super.type) : super._noInput() { |
| sourceElement = variable; |
| } |
| |
| @override |
| String toString() => 'local ${sourceElement!.name}'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitLocalValue(this); |
| } |
| |
| class HParameterValue extends HLocalValue { |
| bool _potentiallyUsedAsVariable = true; |
| |
| HParameterValue(Entity? variable, AbstractValue type) : super(variable, type); |
| |
| // [HParameterValue]s are either the value of the parameter (in fully SSA |
| // converted code), or the mutable variable containing the value (in |
| // incompletely SSA converted code, e.g. methods containing exceptions). |
| bool usedAsVariable() { |
| if (_potentiallyUsedAsVariable) { |
| // If the HParameterValue is used as a variable, all of the uses should be |
| // HLocalGet or HLocalSet, so this loop exits fast. |
| for (HInstruction user in usedBy) { |
| if (user is HLocalGet) return true; |
| if (user is HLocalSet && user.local == this) return true; |
| } |
| // An 'ssa-conversion' optimization can make the HParameterValue change |
| // from a variable to a value, but there is no transformation that |
| // re-introduces the variable. |
| // TODO(sra): The builder knows that most parameters are not variables to |
| // begin with, so could initialize [_potentiallyUsedAsVariable] to |
| // `false`. |
| _potentiallyUsedAsVariable = false; |
| } |
| return false; |
| } |
| |
| @override |
| String toString() => 'parameter ${sourceElement!.name}'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitParameterValue(this); |
| } |
| |
| class HThis extends HParameterValue { |
| // [element] can be null for some synthetic members, e.g. `$signature`. |
| HThis(ThisLocal? element, AbstractValue type) : super(element, type); |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitThis(this); |
| |
| @override |
| bool isCodeMotionInvariant() => true; |
| |
| @override |
| String toString() => 'this'; |
| } |
| |
| class HPhi extends HInstruction { |
| HPhi? get previousPhi => previous as HPhi?; |
| HPhi? get nextPhi => next as HPhi?; |
| |
| // The order of the [inputs] must correspond to the order of the |
| // predecessor-edges. That is if an input comes from the first predecessor |
| // of the surrounding block, then the input must be the first in the [HPhi]. |
| HPhi(Local? variable, List<HInstruction> inputs, AbstractValue type) |
| : super(inputs, type) { |
| sourceElement = variable; |
| } |
| HPhi.noInputs(Local? variable, AbstractValue type) : this(variable, [], type); |
| HPhi.singleInput(Local variable, HInstruction input, AbstractValue type) |
| : this(variable, [input], type); |
| HPhi.manyInputs( |
| Local? variable, |
| List<HInstruction> inputs, |
| AbstractValue type, |
| ) : this(variable, inputs, type); |
| |
| void addInput(HInstruction input) { |
| assert(isInBasicBlock()); |
| inputs.add(input); |
| assert(inputs.length <= block!.predecessors.length); |
| input.usedBy.add(this); |
| } |
| |
| @override |
| String toString() => 'phi $id'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitPhi(this); |
| } |
| |
| abstract class HRelational extends HInvokeBinary { |
| bool usesBoolifiedInterceptor = false; |
| HRelational(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| } |
| |
| class HIdentity extends HRelational { |
| // Cached codegen decision. |
| String? singleComparisonOp; // null, '===', '==' |
| |
| HIdentity(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitIdentity(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.identity; |
| @override |
| _GvnType get _gvnType => _GvnType.identity; |
| @override |
| bool typeEquals(other) => other is HIdentity; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HGreater extends HRelational { |
| HGreater(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitGreater(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.greater; |
| @override |
| _GvnType get _gvnType => _GvnType.greater; |
| @override |
| bool typeEquals(other) => other is HGreater; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HGreaterEqual extends HRelational { |
| HGreaterEqual(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitGreaterEqual(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.greaterEqual; |
| @override |
| _GvnType get _gvnType => _GvnType.greaterEqual; |
| @override |
| bool typeEquals(other) => other is HGreaterEqual; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HLess extends HRelational { |
| HLess(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitLess(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.less; |
| @override |
| _GvnType get _gvnType => _GvnType.less; |
| @override |
| bool typeEquals(other) => other is HLess; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| class HLessEqual extends HRelational { |
| HLessEqual(HInstruction left, HInstruction right, AbstractValue type) |
| : super(left, right, type); |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitLessEqual(this); |
| |
| @override |
| constant_system.BinaryOperation operation() => constant_system.lessEqual; |
| @override |
| _GvnType get _gvnType => _GvnType.lessEqual; |
| @override |
| bool typeEquals(other) => other is HLessEqual; |
| @override |
| bool dataEquals(HInstruction other) => true; |
| } |
| |
| /// Return statement, either with or without a value. |
| class HReturn extends HControlFlow { |
| HReturn(HInstruction? value, SourceInformation? sourceInformation) { |
| if (value != null) inputs.add(value); |
| this.sourceInformation = sourceInformation; |
| } |
| @override |
| String toString() => 'return'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitReturn(this); |
| } |
| |
| class HThrowExpression extends HInstruction { |
| final bool withoutHelperFrame; |
| HThrowExpression( |
| super.value, |
| super.type, |
| SourceInformation? sourceInformation, { |
| this.withoutHelperFrame = false, |
| }) : super._oneInput() { |
| this.sourceInformation = sourceInformation; |
| } |
| @override |
| String toString() => 'throw expression'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitThrowExpression(this); |
| @override |
| bool canThrow(AbstractValueDomain domain) => true; |
| } |
| |
| class HAwait extends HInstruction { |
| HAwait(super.value, super.type) : super._oneInput() { |
| sideEffects.setAllSideEffects(); |
| sideEffects.setDependsOnSomething(); |
| } |
| @override |
| String toString() => 'await'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitAwait(this); |
| // An await will throw if its argument is not a real future. |
| @override |
| bool canThrow(AbstractValueDomain domain) => true; |
| } |
| |
| class HYield extends HInstruction { |
| HYield( |
| super.value, |
| this.hasStar, |
| super.type, |
| SourceInformation? sourceInformation, |
| ) : super._oneInput() { |
| this.sourceInformation = sourceInformation; |
| sideEffects.setAllSideEffects(); |
| sideEffects.setDependsOnSomething(); |
| } |
| bool hasStar; |
| @override |
| String toString() => 'yield'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitYield(this); |
| @override |
| bool canThrow(AbstractValueDomain domain) => false; |
| } |
| |
| class HThrow extends HControlFlow { |
| final bool isRethrow; |
| final bool withoutHelperFrame; |
| HThrow( |
| HInstruction value, |
| SourceInformation? sourceInformation, { |
| this.isRethrow = false, |
| this.withoutHelperFrame = false, |
| }) { |
| inputs.add(value); |
| this.sourceInformation = sourceInformation; |
| } |
| @override |
| String toString() => 'throw'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitThrow(this); |
| } |
| |
| // TODO(johnniwinther): Change this to a "HStaticLoad" of a field when we use |
| // CFE constants. It has been used for static tear-offs even though these should |
| // have been constants. |
| class HStatic extends HInstruction { |
| final MemberEntity element; |
| |
| HStatic(this.element, super.type, SourceInformation? sourceInformation) |
| : super._noInput() { |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| if (element.isAssignable) { |
| sideEffects.setDependsOnStaticPropertyStore(); |
| } |
| setUseGvn(); |
| this.sourceInformation = sourceInformation; |
| } |
| @override |
| String toString() => 'static ${element.name}'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitStatic(this); |
| |
| @override |
| int gvnHashCode() => super.gvnHashCode() ^ element.hashCode; |
| @override |
| _GvnType get _gvnType => _GvnType.static; |
| @override |
| bool typeEquals(other) => other is HStatic; |
| @override |
| bool dataEquals(HStatic other) => element == other.element; |
| @override |
| bool isCodeMotionInvariant() => !element.isAssignable; |
| } |
| |
| class HInterceptor extends HInstruction { |
| // This field should originally be null to allow GVN'ing all |
| // [HInterceptor] on the same input. |
| Set<ClassEntity>? interceptedClasses; |
| |
| // inputs[0] is initially the only input, the receiver. |
| |
| // inputs[1] is a constant interceptor when the interceptor is a constant |
| // except for a `null` receiver. This is used when the receiver can't be |
| // falsy, except for `null`, allowing the generation of code like |
| // |
| // (a && C.JSArray_methods).get$first(a) |
| // |
| |
| HInterceptor(super.receiver, super.type) : super._oneInput() { |
| sourceInformation = receiver.sourceInformation; |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| setUseGvn(); |
| } |
| |
| @override |
| String toString() => 'interceptor on $interceptedClasses'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitInterceptor(this); |
| HInstruction get receiver => inputs[0]; |
| |
| bool get isConditionalConstantInterceptor => inputs.length == 2; |
| HConstant get conditionalConstantInterceptor => inputs[1] as HConstant; |
| set conditionalConstantInterceptor(HConstant constant) { |
| assert(!isConditionalConstantInterceptor); |
| inputs.add(constant); |
| } |
| |
| @override |
| _GvnType get _gvnType => _GvnType.interceptor; |
| @override |
| bool typeEquals(other) => other is HInterceptor; |
| @override |
| bool dataEquals(HInterceptor other) { |
| return interceptedClasses == other.interceptedClasses || |
| (interceptedClasses!.length == other.interceptedClasses!.length && |
| interceptedClasses!.containsAll(other.interceptedClasses!)); |
| } |
| } |
| |
| /// A "one-shot" interceptor is a call to a synthesized method that will fetch |
| /// the interceptor of its first parameter, and make a call on a given selector |
| /// with the remaining parameters. |
| /// |
| /// In order to share the same optimizations with regular interceptor calls, |
| /// this class extends [HInvokeDynamic] and also has the null constant as the |
| /// first input. |
| class HOneShotInterceptor extends HInvokeDynamic { |
| @override |
| List<DartType> typeArguments; |
| Set<ClassEntity>? interceptedClasses; |
| |
| HOneShotInterceptor( |
| Selector selector, |
| AbstractValue receiverType, |
| List<HInstruction> inputs, |
| AbstractValue resultType, |
| this.typeArguments, |
| this.interceptedClasses, |
| ) : super(selector, receiverType, null, inputs, true, resultType) { |
| assert(inputs[0].isConstantNull()); |
| assert(selector.callStructure.typeArgumentCount == typeArguments.length); |
| _isCallOnInterceptor = true; |
| } |
| |
| @override |
| String toString() => |
| 'one shot interceptor: selector=$selector, mask=$receiverType'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitOneShotInterceptor(this); |
| } |
| |
| /// An [HLazyStatic] is a static that is initialized lazily at first read. |
| class HLazyStatic extends HInstruction { |
| final FieldEntity element; |
| |
| HLazyStatic(this.element, super.type, SourceInformation? sourceInformation) |
| : super._noInput() { |
| // TODO(4931): The first access has side-effects, but we afterwards we |
| // should be able to GVN. |
| sideEffects.setAllSideEffects(); |
| sideEffects.setDependsOnSomething(); |
| this.sourceInformation = sourceInformation; |
| } |
| |
| @override |
| String toString() => 'lazy static ${element.name}'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitLazyStatic(this); |
| |
| // TODO(4931): We should be able to GVN some lazy static loads. |
| |
| @override |
| bool isCodeMotionInvariant() => false; |
| @override |
| bool canThrow(AbstractValueDomain domain) => true; |
| } |
| |
| class HStaticStore extends HInstruction { |
| FieldEntity element; |
| HStaticStore(this.element, HInstruction value) |
| : super._oneInput(value, value.instructionType) { |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| sideEffects.setChangesStaticProperty(); |
| } |
| @override |
| String toString() => 'static store ${element.name}'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitStaticStore(this); |
| |
| HInstruction get value => inputs.single; |
| |
| @override |
| _GvnType get _gvnType => _GvnType.staticStore; |
| @override |
| bool typeEquals(other) => other is HStaticStore; |
| @override |
| bool dataEquals(HStaticStore other) => element == other.element; |
| } |
| |
| class HLiteralList extends HInstruction { |
| HLiteralList(List<HInstruction> inputs, AbstractValue type) |
| : super(inputs, type); |
| @override |
| String toString() => 'literal list'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitLiteralList(this); |
| |
| @override |
| bool isAllocation(AbstractValueDomain domain) => true; |
| } |
| |
| /// The primitive array indexing operation. Note that this instruction |
| /// does not throw because we generate the checks explicitly. |
| class HIndex extends HInstruction { |
| HIndex(super.receiver, super.index, super.type) : super._twoInputs() { |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| sideEffects.setDependsOnIndexStore(); |
| setUseGvn(); |
| } |
| |
| @override |
| String toString() => 'index operator'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitIndex(this); |
| |
| HInstruction get receiver => inputs[0]; |
| HInstruction get index => inputs[1]; |
| |
| // Implicit dependency on HBoundsCheck or constraints on index. |
| // TODO(27272): Make HIndex dependent on positions of eliminated bounds |
| // checks. |
| @override |
| bool get isMovable => false; |
| |
| @override |
| HInstruction getDartReceiver() => receiver; |
| @override |
| bool onlyThrowsNSM() => true; |
| @override |
| bool canThrow(AbstractValueDomain domain) => |
| receiver.isNull(domain).isPotentiallyTrue; |
| |
| @override |
| _GvnType get _gvnType => _GvnType.index_; |
| @override |
| bool typeEquals(HInstruction other) => other is HIndex; |
| @override |
| bool dataEquals(HIndex other) => true; |
| } |
| |
| /// The primitive array assignment operation. Note that this instruction |
| /// does not throw because we generate the checks explicitly. |
| class HIndexAssign extends HInstruction { |
| HIndexAssign(HInstruction receiver, HInstruction index, HInstruction value) |
| : super([receiver, index, value], value.instructionType) { |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| sideEffects.setChangesIndex(); |
| } |
| @override |
| String toString() => 'index assign operator'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitIndexAssign(this); |
| |
| HInstruction get receiver => inputs[0]; |
| HInstruction get index => inputs[1]; |
| HInstruction get value => inputs[2]; |
| |
| // Implicit dependency on HBoundsCheck or constraints on index. |
| // TODO(27272): Make HIndex dependent on eliminated bounds checks. |
| @override |
| bool get isMovable => false; |
| |
| @override |
| HInstruction getDartReceiver() => receiver; |
| @override |
| bool onlyThrowsNSM() => true; |
| @override |
| bool canThrow(AbstractValueDomain domain) => |
| receiver.isNull(domain).isPotentiallyTrue; |
| } |
| |
| class HCharCodeAt extends HInstruction { |
| HCharCodeAt(super.receiver, super.index, super.type) : super._twoInputs(); |
| |
| @override |
| String toString() => 'HCharCodeAt'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitCharCodeAt(this); |
| |
| HInstruction get receiver => inputs[0]; |
| HInstruction get index => inputs[1]; |
| |
| // Implicit dependency on HBoundsCheck or constraints on index. |
| // TODO(27272): Make HCharCodeAt dependent on positions of eliminated bounds |
| // checks. |
| @override |
| bool get isMovable => false; |
| |
| @override |
| HInstruction getDartReceiver() => receiver; |
| @override |
| bool onlyThrowsNSM() => true; |
| @override |
| bool canThrow(AbstractValueDomain domain) => |
| receiver.isNull(domain).isPotentiallyTrue; |
| |
| @override |
| _GvnType get _gvnType => _GvnType.charCodeAt; |
| @override |
| bool typeEquals(other) => other is HCharCodeAt; |
| @override |
| bool dataEquals(HCharCodeAt other) => true; |
| } |
| |
| /// HLateValue is a late-stage instruction that can be used to force a value |
| /// into a temporary. |
| /// |
| /// HLateValue is useful for naming values that would otherwise be generated at |
| /// use site, for example, if 'this' is used many times, replacing uses of |
| /// 'this' with HLateValue(HThis) will have the effect of copying 'this' to a |
| /// temporary which will reduce the size of minified code. |
| class HLateValue extends HInstruction implements HLateInstruction { |
| HLateValue(HInstruction target) |
| : super._oneInput(target, target.instructionType); |
| |
| HInstruction get target => inputs.single; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitLateValue(this); |
| @override |
| String toString() => 'HLateValue($target)'; |
| } |
| |
| enum PrimitiveCheckKind { argumentType, receiverType } |
| |
| /// Check for receiver or argument type when lowering operation to a primitive, |
| /// e.g. lowering `+` to [HAdd]. |
| /// |
| /// With sound null safety, `a + b` will require `a` and `b` to be non-nullable |
| /// and these checks will become explicit in the source (e.g. `a! + b!`). At |
| /// that time, this check should be removed. If needed, the `!` check can be |
| /// optimized to give the same signals to the JavaScript VM. |
| class HPrimitiveCheck extends HCheck { |
| final DartType typeExpression; |
| final PrimitiveCheckKind kind; |
| |
| // [receiverTypeCheckSelector] is the selector used for a receiver type check |
| // on open-coded operators, e.g. the not-null check on `x` in `x + 1` would be |
| // compiled to the following, for which we need the selector `$add`. |
| // |
| // if (typeof x != "number") x.$add(); |
| // |
| final Selector? receiverTypeCheckSelector; |
| |
| final AbstractValue checkedType; |
| |
| HPrimitiveCheck( |
| this.typeExpression, |
| this.kind, |
| AbstractValue type, |
| HInstruction input, |
| SourceInformation? sourceInformation, { |
| this.receiverTypeCheckSelector, |
| }) : checkedType = type, |
| super._oneInput(input, type) { |
| assert(isReceiverTypeCheck == (receiverTypeCheckSelector != null)); |
| sourceElement = input.sourceElement; |
| this.sourceInformation = sourceInformation; |
| } |
| |
| bool get isArgumentTypeCheck => kind == PrimitiveCheckKind.argumentType; |
| bool get isReceiverTypeCheck => kind == PrimitiveCheckKind.receiverType; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitPrimitiveCheck(this); |
| |
| @override |
| bool isJsStatement() => true; |
| |
| @override |
| _GvnType get _gvnType => _GvnType.primitiveCheck; |
| @override |
| bool typeEquals(HInstruction other) => other is HPrimitiveCheck; |
| @override |
| bool isCodeMotionInvariant() => false; |
| |
| @override |
| bool dataEquals(HPrimitiveCheck other) { |
| return kind == other.kind && |
| checkedType == other.checkedType && |
| receiverTypeCheckSelector == other.receiverTypeCheckSelector; |
| } |
| |
| bool isRedundant(JClosedWorld closedWorld) { |
| AbstractValueDomain abstractValueDomain = closedWorld.abstractValueDomain; |
| // Type is refined from `dynamic`, so it might become non-redundant. |
| if (abstractValueDomain.containsAll(checkedType).isPotentiallyTrue) { |
| return false; |
| } |
| AbstractValue inputType = checkedInput.instructionType; |
| return abstractValueDomain.isIn(inputType, checkedType).isDefinitelyTrue; |
| } |
| |
| @override |
| String toString() => |
| 'HPrimitiveCheck(checkedType=$checkedType, kind=$kind, ' |
| 'checkedInput=$checkedInput)'; |
| } |
| |
| /// A check that the input is not null. This corresponds to the postfix |
| /// null-check operator '!'. |
| /// |
| /// A null check is inserted on the receiver when inlining an instance method or |
| /// field getter or setter when the receiver might be null. In these cases, the |
| /// [selector] and [field] members are assigned. |
| class HNullCheck extends HCheck { |
| // A sticky check is not optimized away on the basis of the input type. |
| final bool sticky; |
| Selector? selector; |
| FieldEntity? field; |
| |
| HNullCheck(super.input, super.type, {this.sticky = false}) |
| : super._oneInput(); |
| |
| @override |
| bool isJsStatement() => true; |
| |
| @override |
| bool isCodeMotionInvariant() => false; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitNullCheck(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.nullCheck; |
| @override |
| bool typeEquals(HInstruction other) => other is HNullCheck; |
| @override |
| bool dataEquals(HNullCheck other) => true; |
| |
| bool isRedundant(JClosedWorld closedWorld) { |
| if (sticky) return false; |
| AbstractValueDomain abstractValueDomain = closedWorld.abstractValueDomain; |
| AbstractValue inputType = checkedInput.instructionType; |
| return abstractValueDomain.isNull(inputType).isDefinitelyFalse; |
| } |
| |
| @override |
| String toString() { |
| String stickyString = sticky ? 'sticky, ' : ''; |
| String fieldString = field == null ? '' : ', $field'; |
| String selectorString = selector == null ? '' : ', $selector'; |
| return 'HNullCheck($stickyString$checkedInput$fieldString$selectorString)'; |
| } |
| } |
| |
| /// A check for a late sentinel to determine if a late field may be read from or |
| /// written to. |
| abstract class HLateCheck extends HCheck { |
| // Checks may be 'trusted' and result in no runtime check. This is done by |
| // compiling with the checks in place and removing them after optimizations. |
| final bool isTrusted; |
| |
| HLateCheck( |
| HInstruction input, |
| HInstruction? name, |
| this.isTrusted, |
| AbstractValue type, |
| ) : super([input, ?name], type); |
| |
| bool get hasName => inputs.length > 1; |
| |
| HInstruction get name { |
| if (hasName) return inputs[1]; |
| throw StateError('HLateCheck.name: no name'); |
| } |
| |
| @override |
| bool isCodeMotionInvariant() => false; |
| } |
| |
| /// A check that a late field has been initialized and can therefore be read. |
| class HLateReadCheck extends HLateCheck { |
| HLateReadCheck( |
| HInstruction input, |
| HInstruction? name, |
| bool isTrusted, |
| AbstractValue type, |
| ) : super(input, name, isTrusted, type); |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitLateReadCheck(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.lateReadCheck; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HLateReadCheck; |
| |
| @override |
| bool dataEquals(HLateReadCheck other) => isTrusted == other.isTrusted; |
| |
| bool isRedundant(JClosedWorld closedWorld) { |
| AbstractValueDomain abstractValueDomain = closedWorld.abstractValueDomain; |
| AbstractValue inputType = checkedInput.instructionType; |
| return abstractValueDomain.isLateSentinel(inputType).isDefinitelyFalse; |
| } |
| |
| @override |
| String toString() { |
| return 'HLateReadCheck($checkedInput)'; |
| } |
| } |
| |
| /// A check that a late final field has not been initialized yet and can |
| /// therefore be written to. |
| /// |
| /// The difference between [HLateWriteOnceCheck] and [HLateInitializeOnceCheck] |
| /// is that the latter occurs on writes performed as part of the initializer |
| /// expression. |
| class HLateWriteOnceCheck extends HLateCheck { |
| HLateWriteOnceCheck( |
| HInstruction input, |
| HInstruction? name, |
| bool isTrusted, |
| AbstractValue type, |
| ) : super(input, name, isTrusted, type); |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitLateWriteOnceCheck(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.lateWriteOnceCheck; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HLateWriteOnceCheck; |
| |
| @override |
| bool dataEquals(HLateWriteOnceCheck other) => isTrusted == other.isTrusted; |
| |
| bool isRedundant(JClosedWorld closedWorld) { |
| AbstractValueDomain abstractValueDomain = closedWorld.abstractValueDomain; |
| AbstractValue inputType = checkedInput.instructionType; |
| return abstractValueDomain.isLateSentinel(inputType).isDefinitelyTrue; |
| } |
| |
| @override |
| String toString() { |
| return 'HLateWriteOnceCheck($checkedInput)'; |
| } |
| } |
| |
| /// A check that a late final field has not been initialized yet and can |
| /// therefore be initialized. |
| /// |
| /// The difference between [HLateWriteOnceCheck] and [HLateInitializeOnceCheck] |
| /// is that the latter occurs on writes performed as part of the initializer |
| /// expression. |
| class HLateInitializeOnceCheck extends HLateCheck { |
| HLateInitializeOnceCheck( |
| HInstruction input, |
| HInstruction? name, |
| bool isTrusted, |
| AbstractValue type, |
| ) : super(input, name, isTrusted, type); |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => |
| visitor.visitLateInitializeOnceCheck(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.lateInitializeOnceCheck; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HLateInitializeOnceCheck; |
| |
| @override |
| bool dataEquals(HLateInitializeOnceCheck other) => |
| isTrusted == other.isTrusted; |
| |
| bool isRedundant(JClosedWorld closedWorld) { |
| AbstractValueDomain abstractValueDomain = closedWorld.abstractValueDomain; |
| AbstractValue inputType = checkedInput.instructionType; |
| return abstractValueDomain.isLateSentinel(inputType).isDefinitelyTrue; |
| } |
| |
| @override |
| String toString() { |
| return 'HLateInitializeOnceCheck($checkedInput)'; |
| } |
| } |
| |
| /// The [HTypeKnown] instruction marks a value with a refined type. |
| class HTypeKnown extends HCheck { |
| final AbstractValue knownType; |
| final bool _isMovable; |
| |
| HTypeKnown.pinned(this.knownType, HInstruction input) |
| : _isMovable = false, |
| super._oneInput(input, knownType); |
| |
| HTypeKnown.witnessed(this.knownType, HInstruction input, HInstruction witness) |
| : _isMovable = true, |
| super._twoInputs(input, witness, knownType); |
| |
| @override |
| String toString() => 'TypeKnown $knownType'; |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitTypeKnown(this); |
| |
| @override |
| bool isJsStatement() => false; |
| |
| @override |
| bool canThrow(AbstractValueDomain domain) => false; |
| |
| bool get isPinned => inputs.length == 1; |
| |
| HInstruction? get witness => inputs.length == 2 ? inputs[1] : null; |
| |
| @override |
| _GvnType get _gvnType => _GvnType.typeKnown; |
| @override |
| bool typeEquals(HInstruction other) => other is HTypeKnown; |
| @override |
| bool isCodeMotionInvariant() => true; |
| @override |
| bool get isMovable => _isMovable && useGvn(); |
| |
| @override |
| bool dataEquals(HTypeKnown other) { |
| return knownType == other.knownType && |
| instructionType == other.instructionType; |
| } |
| |
| bool isRedundant(JClosedWorld closedWorld) { |
| AbstractValueDomain abstractValueDomain = closedWorld.abstractValueDomain; |
| AbstractValue inputType = checkedInput.instructionType; |
| return abstractValueDomain.isIn(inputType, knownType).isDefinitelyTrue; |
| } |
| } |
| |
| class HRangeConversion extends HCheck { |
| HRangeConversion(super.input, super.type) : super._oneInput() { |
| sourceElement = checkedInput.sourceElement; |
| } |
| |
| @override |
| bool get isMovable => false; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitRangeConversion(this); |
| } |
| |
| class HStringConcat extends HInstruction { |
| HStringConcat(super.left, super.right, super.type) : super._twoInputs() { |
| setUseGvn(); |
| } |
| |
| HInstruction get left => inputs[0]; |
| HInstruction get right => inputs[1]; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitStringConcat(this); |
| @override |
| String toString() => "string concat"; |
| |
| @override |
| _GvnType get _gvnType => _GvnType.stringConcat; |
| @override |
| bool typeEquals(HInstruction other) => other is HStringConcat; |
| @override |
| bool dataEquals(HStringConcat other) => true; |
| } |
| |
| /// The part of string interpolation which converts and interpolated expression |
| /// into a String value. |
| class HStringify extends HInstruction { |
| bool _isPure = false; // Some special cases are pure, e.g. int argument. |
| HStringify(super.input, super.resultType) : super._oneInput() { |
| sideEffects.setAllSideEffects(); |
| sideEffects.setDependsOnSomething(); |
| } |
| |
| void setPure() { |
| sideEffects.clearAllDependencies(); |
| sideEffects.clearAllSideEffects(); |
| _isPure = true; |
| setUseGvn(); |
| } |
| |
| @override |
| bool canThrow(AbstractValueDomain domain) => !_isPure; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitStringify(this); |
| @override |
| String toString() => "stringify"; |
| |
| @override |
| _GvnType get _gvnType => _GvnType.stringify; |
| @override |
| bool typeEquals(HInstruction other) => other is HStringify; |
| @override |
| bool dataEquals(HStringify other) => _isPure == other._isPure; |
| } |
| |
| /// Non-block-based (aka. traditional) loop information. |
| class HLoopInformation { |
| final HBasicBlock header; |
| final List<HBasicBlock> blocks = []; |
| final List<HBasicBlock> backEdges = []; |
| final List<LabelDefinition> labels; |
| final JumpTarget? target; |
| |
| /// Corresponding block information for the loop. |
| HLoopBlockInformation? loopBlockInformation; |
| |
| HLoopInformation(this.header, this.target, this.labels); |
| |
| void addBackEdge(HBasicBlock predecessor) { |
| backEdges.add(predecessor); |
| List<HBasicBlock> workQueue = [predecessor]; |
| do { |
| HBasicBlock current = workQueue.removeLast(); |
| addBlock(current, workQueue); |
| } while (workQueue.isNotEmpty); |
| } |
| |
| // Adds a block and transitively all its predecessors in the loop as |
| // loop blocks. |
| void addBlock(HBasicBlock block, List<HBasicBlock> workQueue) { |
| if (identical(block, header)) return; |
| HBasicBlock? parentHeader = block.parentLoopHeader; |
| if (identical(parentHeader, header)) { |
| // Nothing to do in this case. |
| } else if (parentHeader != null) { |
| workQueue.add(parentHeader); |
| } else { |
| block.parentLoopHeader = header; |
| blocks.add(block); |
| workQueue.addAll(block.predecessors); |
| } |
| } |
| } |
| |
| /// Embedding of a [HBlockInformation] for block-structure based traversal |
| /// in a dominator based flow traversal by attaching it to a basic block. |
| /// To go back to dominator-based traversal, a [HSubGraphBlockInformation] |
| /// structure can be added in the block structure. |
| class HBlockFlow { |
| final HBlockInformation body; |
| final HBasicBlock? continuation; // `null` if all paths throw. |
| HBlockFlow(this.body, this.continuation); |
| } |
| |
| /// Information about a syntactic-like structure. |
| abstract class HBlockInformation { |
| HBasicBlock get start; |
| HBasicBlock get end; |
| bool accept(HBlockInformationVisitor visitor); |
| } |
| |
| /// Information about a statement-like structure. |
| abstract class HStatementInformation extends HBlockInformation { |
| @override |
| bool accept(HStatementInformationVisitor visitor); |
| } |
| |
| /// Information about an expression-like structure. |
| abstract class HExpressionInformation extends HBlockInformation { |
| @override |
| bool accept(HExpressionInformationVisitor visitor); |
| HInstruction? get conditionExpression; |
| } |
| |
| abstract class HStatementInformationVisitor { |
| bool visitLabeledBlockInfo(HLabeledBlockInformation info); |
| bool visitLoopInfo(HLoopBlockInformation info); |
| bool visitIfInfo(HIfBlockInformation info); |
| bool visitTryInfo(HTryBlockInformation info); |
| bool visitSwitchInfo(HSwitchBlockInformation info); |
| bool visitSequenceInfo(HStatementSequenceInformation info); |
| // Pseudo-structure embedding a dominator-based traversal into |
| // the block-structure traversal. This will eventually go away. |
| bool visitSubGraphInfo(HSubGraphBlockInformation info); |
| } |
| |
| abstract class HExpressionInformationVisitor { |
| bool visitSubExpressionInfo(HSubExpressionBlockInformation info); |
| } |
| |
| abstract class HBlockInformationVisitor |
| implements HStatementInformationVisitor, HExpressionInformationVisitor {} |
| |
| /// Generic class wrapping a [SubGraph] as a block-information until |
| /// all structures are handled properly. |
| class HSubGraphBlockInformation implements HStatementInformation { |
| final SubGraph? subGraph; |
| HSubGraphBlockInformation(this.subGraph); |
| |
| @override |
| HBasicBlock get start => subGraph!.start; |
| @override |
| HBasicBlock get end => subGraph!.end; |
| |
| @override |
| bool accept(HStatementInformationVisitor visitor) => |
| visitor.visitSubGraphInfo(this); |
| } |
| |
| /// Generic class wrapping a [SubExpression] as a block-information until |
| /// expressions structures are handled properly. |
| class HSubExpressionBlockInformation implements HExpressionInformation { |
| final SubExpression? subExpression; |
| HSubExpressionBlockInformation(this.subExpression); |
| |
| @override |
| HBasicBlock get start => subExpression!.start; |
| @override |
| HBasicBlock get end => subExpression!.end; |
| |
| @override |
| HInstruction? get conditionExpression => subExpression!.conditionExpression; |
| |
| @override |
| bool accept(HExpressionInformationVisitor visitor) => |
| visitor.visitSubExpressionInfo(this); |
| } |
| |
| /// A sequence of separate statements. |
| class HStatementSequenceInformation implements HStatementInformation { |
| final List<HStatementInformation> statements; |
| HStatementSequenceInformation(this.statements); |
| |
| @override |
| HBasicBlock get start => statements[0].start; |
| @override |
| HBasicBlock get end => statements.last.end; |
| |
| @override |
| bool accept(HStatementInformationVisitor visitor) => |
| visitor.visitSequenceInfo(this); |
| } |
| |
| class HLabeledBlockInformation implements HStatementInformation { |
| final HStatementInformation body; |
| final List<LabelDefinition> labels; |
| final JumpTarget? target; |
| final bool isContinue; |
| |
| HLabeledBlockInformation(this.body, this.labels, {this.isContinue = false}) |
| : target = labels[0].target; |
| |
| HLabeledBlockInformation.implicit( |
| this.body, |
| this.target, { |
| this.isContinue = false, |
| }) : labels = const []; |
| |
| @override |
| HBasicBlock get start => body.start; |
| @override |
| HBasicBlock get end => body.end; |
| |
| @override |
| bool accept(HStatementInformationVisitor visitor) => |
| visitor.visitLabeledBlockInfo(this); |
| } |
| |
| enum LoopBlockInformationKind { |
| notALoop, |
| whileLoop, |
| forLoop, |
| doWhileLoop, |
| forInLoop, |
| switchContinueLoop, |
| } |
| |
| class HLoopBlockInformation implements HStatementInformation { |
| final LoopBlockInformationKind kind; |
| final HExpressionInformation? initializer; |
| final HExpressionInformation? condition; |
| final HStatementInformation? body; |
| final HExpressionInformation? updates; |
| final JumpTarget? target; |
| final List<LabelDefinition> labels; |
| final SourceInformation? sourceInformation; |
| |
| HLoopBlockInformation( |
| this.kind, |
| this.initializer, |
| this.condition, |
| this.body, |
| this.updates, |
| this.target, |
| this.labels, |
| this.sourceInformation, |
| ) { |
| assert( |
| (kind == LoopBlockInformationKind.doWhileLoop |
| ? body!.start |
| : condition!.start) |
| .isLoopHeader(), |
| ); |
| } |
| |
| @override |
| HBasicBlock get start { |
| if (initializer != null) return initializer!.start; |
| if (kind == LoopBlockInformationKind.doWhileLoop) { |
| return body!.start; |
| } |
| return condition!.start; |
| } |
| |
| HBasicBlock get loopHeader { |
| return kind == LoopBlockInformationKind.doWhileLoop |
| ? body!.start |
| : condition!.start; |
| } |
| |
| @override |
| HBasicBlock get end { |
| if (updates != null) return updates!.end; |
| if (kind == LoopBlockInformationKind.doWhileLoop && condition != null) { |
| return condition!.end; |
| } |
| return body!.end; |
| } |
| |
| @override |
| bool accept(HStatementInformationVisitor visitor) => |
| visitor.visitLoopInfo(this); |
| } |
| |
| class HIfBlockInformation implements HStatementInformation { |
| final HExpressionInformation? condition; |
| final HStatementInformation? thenGraph; |
| final HStatementInformation? elseGraph; |
| HIfBlockInformation(this.condition, this.thenGraph, this.elseGraph); |
| |
| @override |
| HBasicBlock get start => condition!.start; |
| @override |
| HBasicBlock get end => elseGraph == null ? thenGraph!.end : elseGraph!.end; |
| |
| @override |
| bool accept(HStatementInformationVisitor visitor) => |
| visitor.visitIfInfo(this); |
| } |
| |
| class HTryBlockInformation implements HStatementInformation { |
| final HStatementInformation? body; |
| final HLocalValue? catchVariable; |
| final HStatementInformation? catchBlock; |
| final HStatementInformation? finallyBlock; |
| HTryBlockInformation( |
| this.body, |
| this.catchVariable, |
| this.catchBlock, |
| this.finallyBlock, |
| ); |
| |
| @override |
| HBasicBlock get start => body!.start; |
| @override |
| HBasicBlock get end => |
| finallyBlock == null ? catchBlock!.end : finallyBlock!.end; |
| |
| @override |
| bool accept(HStatementInformationVisitor visitor) => |
| visitor.visitTryInfo(this); |
| } |
| |
| class HSwitchBlockInformation implements HStatementInformation { |
| final HExpressionInformation expression; |
| final List<HStatementInformation> statements; |
| final JumpTarget? target; |
| final List<LabelDefinition> labels; |
| final SourceInformation? sourceInformation; |
| |
| HSwitchBlockInformation( |
| this.expression, |
| this.statements, |
| this.target, |
| this.labels, |
| this.sourceInformation, |
| ); |
| |
| @override |
| HBasicBlock get start => expression.start; |
| @override |
| HBasicBlock get end { |
| // We don't create a switch block if there are no cases. |
| assert(statements.isNotEmpty); |
| return statements.last.end; |
| } |
| |
| @override |
| bool accept(HStatementInformationVisitor visitor) => |
| visitor.visitSwitchInfo(this); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| /// Is-test using Rti form of type expression. |
| /// |
| /// This instruction can be used for any type. Tests for simple types are |
| /// lowered to other instructions, so this instruction remains for types that |
| /// depend on type variables and complex types. |
| class HIsTest extends HInstruction { |
| final AbstractValueWithPrecision checkedAbstractValue; |
| DartType dartType; |
| |
| HIsTest( |
| this.dartType, |
| this.checkedAbstractValue, |
| super.rti, |
| super.checked, |
| super.instructionType, |
| ) : super._twoInputs() { |
| setUseGvn(); |
| } |
| |
| // The type input is first to facilitate the `type.is(value)` codegen pattern. |
| HInstruction get typeInput => inputs[0]; |
| HInstruction get checkedInput => inputs[1]; |
| |
| /// Returns the value of the test (true/false/maybe). Pass [this.checkedInput] |
| /// as [input] to evaluate this test in place. [input] is provided as an |
| /// argument so that other inputs can be tested, for example, to test for |
| /// partial redundancy between a phi's inputs. |
| AbstractBool evaluateOn(HInstruction input, JClosedWorld closedWorld) => |
| _typeTest( |
| input, |
| dartType, |
| checkedAbstractValue, |
| closedWorld, |
| isCast: false, |
| ); |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitIsTest(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.isTest; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HIsTest; |
| |
| @override |
| bool dataEquals(HIsTest other) => true; |
| |
| @override |
| String toString() => 'HIsTest()'; |
| } |
| |
| /// Simple is-test for a known type that can be achieved without reference to an |
| /// Rti describing the type. |
| class HIsTestSimple extends HInstruction { |
| final DartType dartType; |
| final AbstractValueWithPrecision checkedAbstractValue; |
| final IsTestSpecialization specialization; |
| |
| HIsTestSimple( |
| this.dartType, |
| this.checkedAbstractValue, |
| this.specialization, |
| super.checked, |
| super.type, |
| ) : super._oneInput() { |
| setUseGvn(); |
| } |
| |
| HInstruction get checkedInput => inputs[0]; |
| |
| /// See [HIsTest.evaluateOn]. |
| AbstractBool evaluateOn(HInstruction input, JClosedWorld closedWorld) => |
| _typeTest( |
| input, |
| dartType, |
| checkedAbstractValue, |
| closedWorld, |
| isCast: false, |
| ); |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitIsTestSimple(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.isTestSimple; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HIsTestSimple; |
| |
| @override |
| bool dataEquals(HIsTestSimple other) => dartType == other.dartType; |
| |
| @override |
| String toString() => 'HIsTestSimple()'; |
| } |
| |
| AbstractBool _typeTest( |
| HInstruction expression, |
| DartType dartType, |
| AbstractValueWithPrecision checkedAbstractValue, |
| JClosedWorld closedWorld, { |
| required bool isCast, |
| }) { |
| JCommonElements commonElements = closedWorld.commonElements; |
| DartTypes dartTypes = closedWorld.dartTypes; |
| AbstractValueDomain abstractValueDomain = closedWorld.abstractValueDomain; |
| AbstractValue subsetType = expression.instructionType; |
| AbstractValue supersetType = checkedAbstractValue.abstractValue; |
| AbstractBool expressionIsNull = expression.isNull(abstractValueDomain); |
| |
| bool nullIs(DartType type) => |
| dartTypes.isTopType(type) || |
| type is NullableType || |
| type is FutureOrType && nullIs(type.typeArgument) || |
| type.isNull; |
| |
| if (!isCast) { |
| if (expressionIsNull.isDefinitelyTrue) { |
| if (dartType.containsFreeTypeVariables) return AbstractBool.maybe; |
| return AbstractBool.trueOrFalse(nullIs(dartType)); |
| } |
| if (expressionIsNull.isPotentiallyTrue) { |
| if (dartType.isObject) return AbstractBool.maybe; |
| } |
| } else if (expressionIsNull.isDefinitelyTrue && nullIs(dartType)) { |
| return AbstractBool.true_; |
| } |
| |
| if (checkedAbstractValue.isPrecise && |
| abstractValueDomain.isIn(subsetType, supersetType).isDefinitelyTrue) { |
| return AbstractBool.true_; |
| } |
| |
| if (abstractValueDomain |
| .areDisjoint(subsetType, supersetType) |
| .isDefinitelyTrue) { |
| return AbstractBool.false_; |
| } |
| |
| // TODO(39287): Let the abstract value domain fully handle this. |
| // Currently, the abstract value domain cannot (soundly) state that an is-test |
| // is definitely false, so we reuse some of the case-by-case logic from the |
| // old [HIs] optimization. |
| |
| AbstractBool checkInterface(InterfaceType interface) { |
| if (expression.isInteger(abstractValueDomain).isDefinitelyTrue) { |
| if (dartTypes.isSubtype(commonElements.intType, interface)) { |
| return AbstractBool.true_; |
| } |
| if (interface == commonElements.doubleType) { |
| // We let the JS semantics decide for that check. Currently the code we |
| // emit will always return true. |
| return AbstractBool.maybe; |
| } |
| return AbstractBool.false_; |
| } |
| |
| if (expression.isNumber(abstractValueDomain).isDefinitelyTrue) { |
| if (dartTypes.isSubtype(commonElements.numType, interface)) { |
| return AbstractBool.true_; |
| } |
| // We cannot just return false, because the expression may be of type int or |
| // double. |
| return AbstractBool.maybe; |
| } |
| |
| // We need the raw check because we don't have the notion of generics in the |
| // backend. For example, `this` in a class `A<T>` is currently always |
| // considered to have the raw type. |
| if (dartTypes.treatAsRawType(interface)) { |
| return abstractValueDomain.isInstanceOf(subsetType, interface.element); |
| } |
| |
| return AbstractBool.maybe; |
| } |
| |
| AbstractBool isNullAsCheck = isCast ? expressionIsNull : AbstractBool.false_; |
| AbstractBool isNullIsTest = !isCast ? expressionIsNull : AbstractBool.false_; |
| |
| AbstractBool unwrapAndCheck(DartType type) { |
| if (dartTypes.isTopType(dartType)) return AbstractBool.true_; |
| if (type is NeverType) return AbstractBool.false_; |
| if (type is InterfaceType) { |
| if (type.isNull) return expressionIsNull; |
| return ~(isNullAsCheck | isNullIsTest) & checkInterface(type); |
| } |
| if (type is NullableType) { |
| return unwrapAndCheck(type.baseType); |
| } |
| if (type is FutureOrType) { |
| return unwrapAndCheck(type.typeArgument) | AbstractBool.maybe; |
| } |
| return AbstractBool.maybe; |
| } |
| |
| return unwrapAndCheck(dartType); |
| } |
| |
| /// Type cast or type check using Rti form of type expression. |
| class HAsCheck extends HCheck { |
| final AbstractValueWithPrecision checkedType; |
| DartType checkedTypeExpression; |
| final bool isTypeError; |
| |
| HAsCheck( |
| this.checkedType, |
| this.checkedTypeExpression, |
| this.isTypeError, |
| super.rti, |
| super.checked, |
| super.instructionType, |
| ) : super._twoInputs(); |
| |
| // The type input is first to facilitate the `type.as(value)` codegen pattern. |
| HInstruction get typeInput => inputs[0]; |
| @override |
| HInstruction get checkedInput => inputs[1]; |
| |
| @override |
| bool isJsStatement() => false; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitAsCheck(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.asCheck; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HAsCheck; |
| |
| @override |
| bool dataEquals(HAsCheck other) { |
| return isTypeError == other.isTypeError; |
| } |
| |
| /// Returns 'true` is the check always passes. Provide [this.checkedInput] as |
| /// [input] to evaluate this check in place. [input] is provided as an |
| /// argument so that other inputs can be tested, for example, to test for |
| /// partial redundancy between a phi's inputs. |
| bool isRedundantOn(HInstruction input, JClosedWorld closedWorld) => _typeTest( |
| input, |
| checkedTypeExpression, |
| checkedType, |
| closedWorld, |
| isCast: true, |
| ).isDefinitelyTrue; |
| |
| @override |
| String toString() { |
| String error = isTypeError ? 'TypeError' : 'CastError'; |
| return 'HAsCheck($error)'; |
| } |
| } |
| |
| /// Type cast or type check for simple known types that are achieved via a |
| /// simple static call. |
| class HAsCheckSimple extends HCheck { |
| final DartType dartType; |
| final AbstractValueWithPrecision checkedType; |
| final bool isTypeError; |
| final FunctionEntity method; |
| |
| HAsCheckSimple( |
| super.checked, |
| this.dartType, |
| this.checkedType, |
| this.isTypeError, |
| this.method, |
| super.type, |
| ) : super._oneInput(); |
| |
| @override |
| HInstruction get checkedInput => inputs[0]; |
| |
| @override |
| bool isJsStatement() => false; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitAsCheckSimple(this); |
| |
| /// See [HAsCheck.isRedundantOn]. |
| bool isRedundantOn(HInstruction input, JClosedWorld closedWorld) => _typeTest( |
| input, |
| dartType, |
| checkedType, |
| closedWorld, |
| isCast: true, |
| ).isDefinitelyTrue; |
| |
| @override |
| _GvnType get _gvnType => _GvnType.asCheckSimple; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HAsCheckSimple; |
| |
| @override |
| bool dataEquals(HAsCheckSimple other) { |
| return isTypeError == other.isTypeError && dartType == other.dartType; |
| } |
| |
| @override |
| String toString() { |
| String error = isTypeError ? 'TypeError' : 'CastError'; |
| return 'HAsCheckSimple($error)'; |
| } |
| } |
| |
| /// Subtype check comparing two Rti types. |
| class HSubtypeCheck extends HCheck { |
| HSubtypeCheck(super.subtype, super.supertype, super.type) |
| : super._twoInputs() { |
| setUseGvn(); |
| } |
| |
| HInstruction get typeInput => inputs[1]; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitSubtypeCheck(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.subtypeCheck; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HSubtypeCheck; |
| |
| @override |
| bool dataEquals(HSubtypeCheck other) => true; |
| |
| @override |
| String toString() => 'HSubtypeCheck()'; |
| } |
| |
| /// Common supertype for instructions that generate Rti values. |
| abstract interface class HRtiInstruction {} |
| |
| /// Evaluates an Rti type recipe in the global environment. |
| class HLoadType extends HInstruction implements HRtiInstruction { |
| TypeRecipe typeExpression; |
| |
| HLoadType(this.typeExpression, super.instructionType) : super._noInput() { |
| setUseGvn(); |
| } |
| |
| HLoadType.type(DartType dartType, AbstractValue instructionType) |
| : this(TypeExpressionRecipe(dartType), instructionType); |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitLoadType(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.loadType; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HLoadType; |
| |
| @override |
| bool dataEquals(HLoadType other) { |
| return typeExpression == other.typeExpression; |
| } |
| |
| @override |
| String toString() => 'HLoadType($typeExpression)'; |
| } |
| |
| /// The reified Rti environment stored on a class instance. |
| /// |
| /// Classes with reified type arguments have the type environment stored on the |
| /// instance. The reified environment is typically stored as the instance type, |
| /// e.g. `UnmodifiableListView<int>`. |
| class HInstanceEnvironment extends HInstruction implements HRtiInstruction { |
| late AbstractValue codegenInputType; // Assigned in SsaTypeKnownRemover |
| |
| HInstanceEnvironment(super.instance, super.type) : super._oneInput() { |
| setUseGvn(); |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitInstanceEnvironment(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.instanceEnvironment; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HInstanceEnvironment; |
| |
| @override |
| bool dataEquals(HInstanceEnvironment other) => true; |
| |
| @override |
| String toString() => 'HInstanceEnvironment()'; |
| } |
| |
| /// Evaluates an Rti type recipe in an Rti environment. |
| class HTypeEval extends HInstruction implements HRtiInstruction { |
| TypeEnvironmentStructure envStructure; |
| TypeRecipe typeExpression; |
| |
| HTypeEval( |
| super.environment, |
| this.envStructure, |
| this.typeExpression, |
| super.type, |
| ) : super._oneInput() { |
| setUseGvn(); |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitTypeEval(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.typeEval; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HTypeEval; |
| |
| @override |
| bool dataEquals(HTypeEval other) { |
| return TypeRecipe.yieldsSameType( |
| typeExpression, |
| envStructure, |
| other.typeExpression, |
| other.envStructure, |
| ); |
| } |
| |
| @override |
| String toString() => 'HTypeEval($typeExpression)'; |
| } |
| |
| /// Extends an Rti type environment with generic function types. |
| class HTypeBind extends HInstruction implements HRtiInstruction { |
| HTypeBind(super.environment, super.typeArguments, super.type) |
| : super._twoInputs() { |
| setUseGvn(); |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitTypeBind(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.typeBind; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HTypeBind; |
| |
| @override |
| bool dataEquals(HTypeBind other) => true; |
| |
| @override |
| String toString() => 'HTypeBind()'; |
| } |
| |
| /// Check Array or TypedData for permission to modify or grow. |
| /// |
| /// Typical use to check modifiability for `a[i] = 0`. The array flags are |
| /// checked to see if there is a bit that prohibits modification. |
| /// |
| /// a = ... |
| /// f = HArrayFlagsGet(a); |
| /// a2 = HArrayFlagsCheck(a, f, ArrayFlags.unmodifiableCheck, "[]=", "modify") |
| /// a2[i] = 0 |
| /// |
| /// HArrayFlagsGet is a separate instruction so that 'loading' the flags from |
| /// the Array can by hoisted. |
| class HArrayFlagsCheck extends HCheck { |
| HArrayFlagsCheck( |
| HInstruction array, |
| HInstruction arrayFlags, |
| HInstruction checkFlags, |
| HInstruction? operation, |
| HInstruction? verb, |
| AbstractValue type, |
| ) : super([array, arrayFlags, checkFlags, ?operation, ?verb], type); |
| |
| HInstruction get array => inputs[0]; |
| HInstruction get arrayFlags => inputs[1]; |
| HInstruction get checkFlags => inputs[2]; |
| |
| bool get hasOperation => inputs.length > 3; |
| HInstruction get operation => inputs[3]; |
| |
| bool get hasVerb => inputs.length > 4; |
| HInstruction get verb => inputs[4]; |
| |
| // The checked type is the input type, refined to match the flags. |
| AbstractValue computeInstructionType( |
| AbstractValue inputType, |
| AbstractValueDomain domain, |
| ) { |
| // TODO(sra): Depending on the checked flags, the output is fixed-length or |
| // unmodifiable. Refine the type to the degree an AbstractValue can express |
| // that. |
| return inputType; |
| } |
| |
| bool alwaysThrows() { |
| if ((arrayFlags, checkFlags) case ( |
| HConstant(constant: IntConstantValue(intValue: final arrayBits)), |
| HConstant(constant: IntConstantValue(intValue: final checkBits)), |
| ) when arrayBits & checkBits != BigInt.zero) { |
| return true; |
| } |
| return false; |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitArrayFlagsCheck(this); |
| |
| @override |
| bool isJsStatement() => true; |
| |
| @override |
| _GvnType get _gvnType => _GvnType.arrayFlagsCheck; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HArrayFlagsCheck; |
| |
| @override |
| bool dataEquals(HArrayFlagsCheck other) => true; |
| } |
| |
| class HArrayFlagsGet extends HInstruction { |
| HArrayFlagsGet(HInstruction array, AbstractValue type) |
| : super([array], type) { |
| sideEffects.clearAllSideEffects(); |
| sideEffects.clearAllDependencies(); |
| // Dependency on HArrayFlagsSet. |
| sideEffects.setDependsOnInstancePropertyStore(); |
| setUseGvn(); |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitArrayFlagsGet(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.arrayFlagsGet; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HArrayFlagsGet; |
| |
| @override |
| bool dataEquals(HArrayFlagsGet other) => true; |
| } |
| |
| /// Tag an Array or TypedData object to mark it as unmodifiable or fixed-length. |
| /// |
| /// The HArrayFlagsSet instruction represents the tagged Array or TypedData |
| /// object. The instruction type can be different to the `array` input. |
| /// HArrayFlagsSet is used in a 'linear' style - there are no accesses to the |
| /// input after this operation. |
| /// |
| /// To ensure that HArrayFlagsGet (possibly from inlined code) does not float |
| /// past HArrayFlagsSet, we use the 'instance property' effect. |
| class HArrayFlagsSet extends HInstruction |
| implements HOutputConstrainedToAnInput { |
| HArrayFlagsSet(HInstruction array, HInstruction flags, AbstractValue type) |
| : super([array, flags], type) { |
| // For correct ordering with respect to HArrayFlagsGet: |
| sideEffects.setChangesInstanceProperty(); |
| // Be conservative and make HArrayFlagsSet be a memory fence: |
| sideEffects.setAllSideEffects(); |
| sideEffects.setDependsOnSomething(); |
| } |
| |
| HInstruction get array => inputs[0]; |
| HInstruction get flags => inputs[1]; |
| |
| @override |
| HInstruction get constrainedInput => array; |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitArrayFlagsSet(this); |
| |
| @override |
| bool isJsStatement() => true; |
| } |
| |
| class HIsLateSentinel extends HInstruction { |
| HIsLateSentinel(super.value, super.type) : super._oneInput() { |
| setUseGvn(); |
| } |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitIsLateSentinel(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.isLateSentinel; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HIsLateSentinel; |
| |
| @override |
| bool dataEquals(HIsLateSentinel other) => true; |
| |
| @override |
| String toString() => 'HIsLateSentinel()'; |
| } |
| |
| /// Reads an 'embedded global' to access some kind of metadata or value produced |
| /// by the compiler. |
| /// |
| /// This instruction corresponds to the `JS_EMBEDDED_GLOBAL` top level method in |
| /// `foreign_helper.dart`. The [name] should be a constant defined in the |
| /// `_embedded_names` or `_js_shared_embedded_names` library. |
| class HEmbeddedGlobalGet extends HInstruction { |
| final String name; |
| |
| factory HEmbeddedGlobalGet( |
| String name, |
| NativeBehavior nativeBehavior, |
| AbstractValue type, |
| ) { |
| final node = HEmbeddedGlobalGet._(name, type); |
| node.sideEffects.add(nativeBehavior.sideEffects); |
| if (nativeBehavior.useGvn) node.setUseGvn(); |
| return node; |
| } |
| |
| HEmbeddedGlobalGet._(this.name, super.type) : super._noInput(); |
| |
| @override |
| R accept<R>(HVisitor<R> visitor) => visitor.visitEmbeddedGlobalGet(this); |
| |
| @override |
| _GvnType get _gvnType => _GvnType.embeddedGlobal; |
| |
| @override |
| bool typeEquals(HInstruction other) => other is HEmbeddedGlobalGet; |
| |
| @override |
| bool dataEquals(HEmbeddedGlobalGet other) => name == other.name; |
| |
| @override |
| String toString() => 'HEmbeddedGlobalGet($name)'; |
| } |