blob: 6e998efe0358102d4e1ee127511ec4b8b723fb95 [file] [log] [blame] [edit]
// 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)';
}