blob: b397b413ffd9c01ede1210b2d3307ffdf156e4a2 [file] [log] [blame]
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:kernel/ast.dart' as ast show DartType, Name;
import 'package:cfg/ir/constant_value.dart';
import 'package:cfg/ir/flow_graph.dart';
import 'package:cfg/ir/functions.dart';
import 'package:cfg/ir/local_variable.dart';
import 'package:cfg/ir/source_position.dart';
import 'package:cfg/ir/types.dart';
import 'package:cfg/ir/use_lists.dart';
import 'package:cfg/ir/visitor.dart';
import 'package:cfg/utils/misc.dart';
/// Base class for all instructions.
abstract base class Instruction {
/// Enclosing control-flow graph.
final FlowGraph graph;
/// Index in the [FlowGraph.instructions].
final int id;
/// Source position associated with this instruction.
final SourcePosition sourcePosition;
/// Explicit inputs.
final UsesArray _inputs;
/// Enclosing basic block.
Block? block;
/// Next instruction in basic block.
Instruction? next;
/// Previous instruction in basic block.
Instruction? previous;
/// Values implicitly used by this instruction
/// e.g. for exception handling and deoptimization).
UsesArray? implicitInputs;
/// Create a new instruction.
///
/// The newly created instruction belongs to [graph] but not linked into
/// a basic block and uses lists.
Instruction(this.graph, this.sourcePosition, int inputCount)
: id = graph.instructions.length,
_inputs = inputCount == 0
? graph.emptyUsesArray
: UsesArray.allocate(graph, inputCount) {
for (int i = 0; i < inputCount; ++i) {
_inputs.at(graph, i).init(graph, this);
}
graph.instructions.add(this);
}
/// Returns true if this instruction is linked into a list of instructions
/// in a basic block (also imlplies inputs are linked in their use lists).
bool get isInGraph => block != null;
/// Number of explicit inputs.
int get inputCount => _inputs.getLength(graph);
/// [Use] corresponding to an [i]-th explicit input.
Use inputAt(int i) => _inputs.at(graph, i);
/// [Definition] of [i]-th explicit input.
Definition inputDefAt(int i) => inputAt(i).getDefinition(graph);
/// Set [i]-th input to the given [value]. Can be done only once.
void setInputAt(int i, Definition value) {
final input = inputAt(i);
assert(input.getNext(graph) == Use.Null);
assert(input.getPrevious(graph) == Use.Null);
input.setDefinition(graph, value);
}
/// Replace [i]-th input with [value]. This instruction should be
/// linked into a basic block.
void replaceInputAt(int i, Definition value) {
assert(isInGraph);
removeInputFromUseList(i);
setInputAt(i, value);
addInputToUseList(i);
}
/// Reduce number of inputs to [newInputCount].
void truncateInputs(int newInputCount) {
_inputs.truncateTo(graph, newInputCount);
}
/// Link this instruction to the [next] instruction in basic block.
void linkTo(Instruction next) {
assert(!identical(this, next));
this.next = next;
next.previous = this;
next.block = this.block;
}
/// Add [inputIndex]-th input to use list of its definition.
///
/// This is a low-level operation which is rarely needed.
/// Prefer using [appendInstruction] or [insertAfter].
void addInputToUseList(int inputIndex) {
final input = inputAt(inputIndex);
assert(input.getNext(graph) == Use.Null);
assert(input.getPrevious(graph) == Use.Null);
final def = input.getDefinition(graph);
final nextUse = def._inputUses;
if (nextUse != Use.Null) {
assert(nextUse.getPrevious(graph) == Use.Null);
input.setNext(graph, nextUse);
nextUse.setPrevious(graph, input);
}
def._inputUses = input;
}
void _addInputsToUseLists() {
for (int i = 0, n = inputCount; i < n; ++i) {
addInputToUseList(i);
}
assert(implicitInputs == null);
}
/// Remove [inputIndex]-th input from use list of its definition.
///
/// This is a low-level operation which is rarely needed.
/// Prefer using [replaceInputAt] or [removeFromGraph].
void removeInputFromUseList(int inputIndex) {
final input = inputAt(inputIndex);
assert(input.getInstruction(graph) == this);
final nextUse = input.getNext(graph);
final prevUse = input.getPrevious(graph);
if (prevUse == Use.Null) {
final def = input.getDefinition(graph);
assert(def._inputUses == input);
def._inputUses = nextUse;
} else {
prevUse.setNext(graph, nextUse);
}
if (nextUse != Use.Null) {
nextUse.setPrevious(graph, prevUse);
}
input.setNext(graph, Use.Null);
input.setPrevious(graph, Use.Null);
}
/// Remove all inputs from their use lists.
void removeInputsFromUseLists() {
for (int i = 0, n = inputCount; i < n; ++i) {
removeInputFromUseList(i);
}
}
/// Append an unlinked [instr] after this instruction,
/// which should be the last instruction appended into basic block.
///
/// Also, add all inputs of [instr] to their use lists.
void appendInstruction(Instruction instr) {
assert(isInGraph);
assert(!instr.isInGraph);
assert(this.next == null);
assert(this.block!.lastInstruction == this);
assert(instr.next == null);
assert(instr.previous == null);
assert(instr.block == null);
linkTo(instr);
instr._addInputsToUseLists();
block!.lastInstruction = instr;
}
/// Insert this instruction between [previous] and [previous.next].
/// (which should be linked into a basic block).
///
/// If [addInputsToUseLists], then also add all inputs to their use lists.
void insertAfter(Instruction previous, {bool addInputsToUseLists = true}) {
assert(previous.isInGraph);
assert(!isInGraph);
final next = previous.next!;
assert(previous.block != null);
assert(this.next == null);
assert(this.previous == null);
assert(this.block == null);
this.previous = previous;
this.next = next;
this.block = previous.block;
next.previous = this;
previous.next = this;
if (addInputsToUseLists) {
_addInputsToUseLists();
}
}
/// Insert this instruction between [next.previous] and [next].
/// (which should be linked into a basic block).
///
/// If [addInputsToUseLists], then also add all inputs to their use lists.
void insertBefore(Instruction next, {bool addInputsToUseLists = true}) {
assert(next.isInGraph);
insertAfter(next.previous!, addInputsToUseLists: addInputsToUseLists);
}
/// Unlink this instruction from its basic block and use lists.
void removeFromGraph() {
assert(isInGraph);
assert(block != null);
assert(this != block);
removeInputsFromUseLists();
final next = this.next;
final previous = this.previous!;
previous.next = next;
if (next != null) {
next.previous = previous;
} else {
assert(this is ControlFlowInstruction);
assert(this == block!.lastInstruction);
block!.lastInstruction = previous;
}
this.next = null;
this.previous = null;
this.block = null;
assert(!isInGraph);
}
/// Whether this instruction can potentially throw a Dart exception.
bool get canThrow;
/// Whether this instruction can have any visible side-effects.
bool get hasSideEffects;
/// Returns true if this instruction is idempotent (i.e.
/// repeating this instruction does not have any effect),
/// and it is a subject to value numbering.
bool get isIdempotent => false;
/// Returns true if extra instruction attributes are equal.
/// Used only for idempotent instructions of the same type.
bool attributesEqual(Instruction other) =>
throw 'Not implemented for ${runtimeType}';
R accept<R>(InstructionVisitor<R> v);
}
/// Trait of instructions which can potentially throw Dart exceptions.
base mixin CanThrow on Instruction {
bool get canThrow => true;
}
/// Trait of instructions which cannot throw Dart exceptions.
base mixin NoThrow on Instruction {
bool get canThrow => false;
}
/// Trait of instructions which can have visible side-effects.
base mixin HasSideEffects on Instruction {
bool get hasSideEffects => true;
}
/// Trait of instructions which do not have any side-effects.
base mixin Pure on Instruction {
bool get hasSideEffects => false;
}
/// Trait of idempotent instructions.
base mixin Idempotent on Instruction {
bool get isIdempotent => true;
}
/// Base class for instructions which yield a value.
abstract base class Definition extends Instruction {
Use _inputUses = Use.Null;
Use _implicitUses = Use.Null;
Definition(super.graph, super.sourcePosition, super.inputCount);
UsesIterable get inputUses => UsesIterable(graph, _inputUses);
UsesIterable get implicitUses => UsesIterable(graph, _implicitUses);
bool get hasInputUses => _inputUses != Use.Null;
bool get hasImplicitUses => _implicitUses != Use.Null;
bool get hasUses => hasInputUses || hasImplicitUses;
/// Replace all uses of this instruction with [other].
void replaceUsesWith(Definition other) {
if (hasInputUses) {
Use last = Use.Null;
for (final use in inputUses) {
use.setDefinition(graph, other);
last = use;
}
final tail = other._inputUses;
last.setNext(graph, tail);
if (tail != Use.Null) {
tail.setPrevious(graph, last);
}
other._inputUses = this._inputUses;
this._inputUses = Use.Null;
}
if (hasImplicitUses) {
Use last = Use.Null;
for (final use in implicitUses) {
use.setDefinition(graph, other);
last = use;
}
final tail = other._implicitUses;
last.setNext(graph, tail);
if (tail != Use.Null) {
tail.setPrevious(graph, last);
}
other._implicitUses = this._implicitUses;
this._implicitUses = Use.Null;
}
}
/// Result type of this instruction.
CType get type;
/// Whether this instruction can yield a zero value.
bool get canBeZero => true;
/// Whether this instruction can yield a negative value.
bool get canBeNegative => true;
/// Returns the only instruction which uses result of this instruction as
/// an explicit input.
///
/// Returns `null` if there are no or multiple uses.
Instruction? get singleUser {
if (hasInputUses && !hasImplicitUses) {
final use = _inputUses;
if (use.getNext(graph) == Use.Null) {
return use.getInstruction(graph);
}
}
return null;
}
}
/// Forward iterator over instructions in the block.
final class _InstructionsIterator implements Iterator<Instruction> {
Instruction? _current;
Instruction? _next;
_InstructionsIterator(this._next);
@override
bool moveNext() {
_current = _next;
_next = _current?.next;
return (_current != null);
}
@override
Instruction get current => _current!;
}
/// Backwards iterator over instructions in the block.
final class _ReverseInstructionsIterator implements Iterator<Instruction> {
Instruction? _current;
Instruction _previous;
_ReverseInstructionsIterator(this._previous);
@override
bool moveNext() {
final instr = _previous.previous;
if (instr == null) {
assert(_previous is Block);
_current = null;
return false;
} else {
_current = _previous;
_previous = instr;
return true;
}
}
@override
Instruction get current => _current!;
}
/// Iterable for backwards iteration of instructions in the block.
final class _ReverseInstructionsIterable extends Iterable<Instruction> {
final Block _block;
_ReverseInstructionsIterable(this._block);
@override
Iterator<Instruction> get iterator =>
_ReverseInstructionsIterator(_block.lastInstruction);
}
/// Basic block.
abstract base class Block extends Instruction
with NoThrow, Pure, Iterable<Instruction> {
final List<Block> predecessors = [];
final List<Block> successors = [];
late Instruction lastInstruction;
CatchBlock? exceptionHandler;
int preorderNumber = -1;
int postorderNumber = -1;
Block(FlowGraph graph, SourcePosition sourcePosition)
: super(graph, sourcePosition, 0) {
this.block = this;
this.lastInstruction = this;
}
/// Replace [oldPredecessor] with [newPredecessor].
void replacePredecessor(Block oldPredecessor, Block newPredecessor) {
final predecessors = this.predecessors;
for (int i = 0, n = predecessors.length; i < n; ++i) {
if (predecessors[i] == oldPredecessor) {
predecessors[i] = newPredecessor;
}
}
}
/// Iterate over instructions in the block from the first to the last.
/// Iteration is robust wrt removal of the current instruction.
@override
Iterator<Instruction> get iterator => _InstructionsIterator(next);
/// Iterate over instructions in the block from the last to the first.
/// Iteration is robust wrt removal of the current instruction.
Iterable<Instruction> get reversed => _ReverseInstructionsIterable(this);
}
/// The entry block in the [FlowGraph].
///
/// Contains all [Constant] instructions and [Parameter] instructions
/// corresponding to the function parameters.
final class EntryBlock extends Block {
EntryBlock(super.graph, super.sourcePosition);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitEntryBlock(this);
}
/// Iterator over [Phi] instructions in the [JoinBlock].
final class _PhiIterator implements Iterator<Phi> {
Phi? _current;
Phi? _next;
_PhiIterator(JoinBlock block) {
final nextInstruction = block.next;
_next = (nextInstruction is Phi) ? nextInstruction : null;
}
@override
bool moveNext() {
_current = _next;
final nextInstruction = _current?.next;
_next = (nextInstruction is Phi) ? nextInstruction : null;
return (_current != null);
}
@override
Phi get current => _current!;
}
/// Iterable over [Phi] instructions in the [JoinBlock].
final class _PhiIterable extends Iterable<Phi> {
final JoinBlock _block;
_PhiIterable(this._block);
@override
Iterator<Phi> get iterator => _PhiIterator(_block);
}
/// Basic block which can have multiple predecessors.
///
/// May contain [Phi] instructions in the beginning of the block.
final class JoinBlock extends Block {
JoinBlock(super.graph, super.sourcePosition);
Iterable<Phi> get phis => _PhiIterable(this);
bool get hasPhis => next is Phi;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitJoinBlock(this);
}
/// Target block of a branch.
final class TargetBlock extends Block {
TargetBlock(super.graph, super.sourcePosition);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitTargetBlock(this);
}
/// Exception handler block.
///
/// May contain [Parameter] instructions in the beginning of the block
/// to represent incoming values of local variables, exception object and
/// stack trace.
final class CatchBlock extends Block {
CatchBlock(super.graph, super.sourcePosition);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitCatchBlock(this);
}
/// Marker interface for instructions which can end basic block.
abstract class ControlFlowInstruction {}
/// Unconditional jump to the sole successor of this basic block.
final class Goto extends Instruction
with NoThrow, Pure
implements ControlFlowInstruction {
Goto(FlowGraph graph, SourcePosition sourcePosition)
: super(graph, sourcePosition, 0);
Block get target => block!.successors.single;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitGoto(this);
}
/// Conditional branch to either true of false successors of this basic block.
final class Branch extends Instruction
with NoThrow, Pure
implements ControlFlowInstruction {
Branch(FlowGraph graph, SourcePosition sourcePosition, Definition condition)
: super(graph, sourcePosition, 1) {
setInputAt(0, condition);
}
Definition get condition => inputDefAt(0);
TargetBlock get trueSuccessor => block!.successors[0] as TargetBlock;
TargetBlock get falseSuccessor => block!.successors[1] as TargetBlock;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitBranch(this);
}
/// Unconditional jump to the try block body (the first succesor of this
/// basic block). The second successor of this basic block is a catch block.
final class TryEntry extends Instruction
with NoThrow, Pure
implements ControlFlowInstruction {
TryEntry(FlowGraph graph, SourcePosition sourcePosition)
: super(graph, sourcePosition, 0);
TargetBlock get tryBody => block!.successors[0] as TargetBlock;
CatchBlock get catchBlock => block!.successors[1] as CatchBlock;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitTryEntry(this);
}
/// The result of instruction `v = Phi(v[0],...,v[N-1])` is `v[i]` when
/// control is transferred from the i-th predecessor block.
///
/// Number of inputs always matches number of predecessor blocks.
final class Phi extends Definition with NoThrow, Pure {
/// Local variable for which this [Phi] was originally created.
final LocalVariable variable;
Phi(super.graph, super.sourcePosition, this.variable, super.inputCount);
@override
CType get type => variable.type;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitPhi(this);
}
/// Return value from the current function.
final class Return extends Instruction
with NoThrow, Pure
implements ControlFlowInstruction {
Return(FlowGraph graph, SourcePosition sourcePosition, Definition value)
: super(graph, sourcePosition, 1) {
setInputAt(0, value);
}
@override
R accept<R>(InstructionVisitor<R> v) => v.visitReturn(this);
}
enum ComparisonOpcode {
// Simple object pointer equality.
equal('=='),
notEqual('!='),
// identical with special cases for num.
identical('==='),
notIdentical('!=='),
// int comparisons.
intEqual('int =='),
intNotEqual('int !='),
intLess('int <'),
intLessOrEqual('int <='),
intGreater('int >'),
intGreaterOrEqual('int >='),
// int bitwise tests
intTestIsZero('int & == 0'),
intTestIsNotZero('int & != 0'),
// double comparisons.
doubleEqual('double =='),
doubleNotEqual('double !='),
doubleLess('double <'),
doubleLessOrEqual('double <='),
doubleGreater('double >'),
doubleGreaterOrEqual('double >=');
final String token;
const ComparisonOpcode(this.token);
bool get isIntComparison => switch (this) {
intEqual ||
intNotEqual ||
intLess ||
intLessOrEqual ||
intGreater ||
intGreaterOrEqual ||
intTestIsZero ||
intTestIsNotZero => true,
_ => false,
};
bool get isDoubleComparison => switch (this) {
doubleEqual ||
doubleNotEqual ||
doubleLess ||
doubleLessOrEqual ||
doubleGreater ||
doubleGreaterOrEqual => true,
_ => false,
};
ComparisonOpcode flipOperands() => switch (this) {
equal ||
notEqual ||
identical ||
notIdentical ||
intEqual ||
intNotEqual ||
intTestIsZero ||
intTestIsNotZero ||
doubleEqual ||
doubleNotEqual => this,
intLess => intGreaterOrEqual,
intLessOrEqual => intGreater,
intGreater => intLessOrEqual,
intGreaterOrEqual => intLess,
doubleLess => doubleGreaterOrEqual,
doubleLessOrEqual => doubleGreater,
doubleGreater => doubleLessOrEqual,
doubleGreaterOrEqual => doubleLess,
};
bool get canBeNegated => switch (this) {
doubleLess ||
doubleLessOrEqual ||
doubleGreater ||
doubleGreaterOrEqual => false,
_ => true,
};
ComparisonOpcode negate() => switch (this) {
equal => notEqual,
notEqual => equal,
identical => notIdentical,
notIdentical => identical,
intEqual => intNotEqual,
intNotEqual => intEqual,
intLess => intGreaterOrEqual,
intLessOrEqual => intGreater,
intGreater => intLessOrEqual,
intGreaterOrEqual => intLess,
intTestIsZero => intTestIsNotZero,
intTestIsNotZero => intTestIsZero,
doubleEqual => doubleNotEqual,
doubleNotEqual => doubleEqual,
doubleLess ||
doubleLessOrEqual ||
doubleGreater ||
doubleGreaterOrEqual => throw '${token} cannot be negated',
};
}
/// Compare two operands.
final class Comparison extends Definition with NoThrow, Pure, Idempotent {
ComparisonOpcode op;
Comparison(
FlowGraph graph,
SourcePosition sourcePosition,
this.op,
Definition left,
Definition right,
) : super(graph, sourcePosition, 2) {
setInputAt(0, left);
setInputAt(1, right);
}
Definition get left => inputDefAt(0);
Definition get right => inputDefAt(1);
@override
CType get type => const BoolType();
@override
bool attributesEqual(covariant Comparison other) => op == other.op;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitComparison(this);
}
/// Instruction representing a constant value.
///
/// Should not be created directly, use [FlowGraph.getConstant] instead.
final class Constant extends Definition with NoThrow, Pure {
final ConstantValue value;
Constant(FlowGraph graph, this.value) : super(graph, noPosition, 0);
@override
bool get canBeZero => value.isZero;
@override
bool get canBeNegative => value.isNegative;
@override
CType get type => value.type;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitConstant(this);
}
/// Base class for various calls.
abstract base class CallInstruction extends Definition
with CanThrow, HasSideEffects {
CallInstruction(super.graph, super.sourcePosition, super.inputCount);
bool get hasTypeArguments => inputCount > 0 && inputDefAt(0) is TypeArguments;
TypeArguments? get typeArguments =>
hasTypeArguments ? inputDefAt(0) as TypeArguments : null;
}
/// Direct call of the target function.
final class DirectCall extends CallInstruction {
final CFunction target;
@override
final CType type;
DirectCall(
super.graph,
super.sourcePosition,
this.target,
super.inputCount,
this.type,
);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitDirectCall(this);
}
/// Interface call via given interface target.
final class InterfaceCall extends CallInstruction {
final CFunction interfaceTarget;
@override
final CType type;
InterfaceCall(
super.graph,
super.sourcePosition,
this.interfaceTarget,
super.inputCount,
this.type,
);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitInterfaceCall(this);
}
enum DynamicCallKind { method, getter, setter }
/// Dynamic call via given selector.
final class DynamicCall extends CallInstruction {
final ast.Name selector;
final DynamicCallKind kind;
DynamicCall(
super.graph,
super.sourcePosition,
this.selector,
this.kind,
super.inputCount,
);
@override
CType get type => const TopType();
@override
R accept<R>(InstructionVisitor<R> v) => v.visitDynamicCall(this);
}
/// Parameter of a function or a catch block.
final class Parameter extends Definition with NoThrow, Pure {
final LocalVariable variable;
Parameter(FlowGraph graph, SourcePosition sourcePosition, this.variable)
: super(graph, sourcePosition, 0);
bool get isFunctionParameter => block is EntryBlock;
bool get isCatchParameter => block is CatchBlock;
@override
CType get type => variable.type;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitParameter(this);
}
/// Loads value from the local variable.
///
/// [LoadLocal] instructions are only used before
/// IR is converted to SSA form.
final class LoadLocal extends Definition with NoThrow, Pure {
final LocalVariable variable;
LoadLocal(FlowGraph graph, SourcePosition sourcePosition, this.variable)
: super(graph, sourcePosition, 0);
@override
CType get type => variable.type;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitLoadLocal(this);
}
/// Store value to the local variable.
///
/// [StoreLocal] instructions are only used before
/// IR is converted to SSA form.
final class StoreLocal extends Instruction with NoThrow, HasSideEffects {
final LocalVariable variable;
StoreLocal(
FlowGraph graph,
SourcePosition sourcePosition,
this.variable,
Definition value,
) : super(graph, sourcePosition, 1) {
setInputAt(0, value);
}
Definition get value => inputDefAt(0);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitStoreLocal(this);
}
/// Throw given exception object. Also takes optional stack trace
/// input to rethrow exception object without collecting a new stack trace.
final class Throw extends Instruction
with CanThrow, HasSideEffects
implements ControlFlowInstruction {
Throw(
FlowGraph graph,
SourcePosition sourcePosition,
Definition exception,
Definition? stackTrace,
) : super(graph, sourcePosition, stackTrace != null ? 2 : 1) {
setInputAt(0, exception);
if (stackTrace != null) {
setInputAt(1, stackTrace);
}
}
@override
R accept<R>(InstructionVisitor<R> v) => v.visitThrow(this);
}
/// Represents collection of class and function type parameters.
final class TypeParameters extends Definition with NoThrow, Pure {
TypeParameters(
FlowGraph graph,
SourcePosition sourcePosition,
Definition? receiver,
) : super(graph, sourcePosition, receiver != null ? 1 : 0) {
if (receiver != null) {
setInputAt(0, receiver);
}
}
@override
CType get type => const TypeParametersType();
@override
R accept<R>(InstructionVisitor<R> v) => v.visitTypeParameters(this);
}
/// Casts input object to the given type.
///
/// Checked casts throw TypeError if
/// object is not assignable to the given type.
final class TypeCast extends Definition with CanThrow, Pure, Idempotent {
/// Target type for the type cast.
final CType testedType;
/// Whether this type cast involves check at runtime.
bool isChecked;
TypeCast(
FlowGraph graph,
SourcePosition sourcePosition,
Definition object,
this.testedType,
Definition? typeParameters, {
this.isChecked = true,
}) : super(graph, sourcePosition, typeParameters != null ? 2 : 1) {
setInputAt(0, object);
if (typeParameters != null) {
setInputAt(1, typeParameters);
}
}
Definition get operand => inputDefAt(0);
Definition? get typeParameters => (inputCount > 1) ? inputDefAt(1) : null;
@override
CType get type => testedType;
@override
bool get canThrow => isChecked;
@override
bool attributesEqual(covariant TypeCast other) =>
// 'isChecked' is not taken into account as checked and unchecked casts
// against the same type are congruent wrt value numbering.
testedType == other.testedType;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitTypeCast(this);
}
/// Test if input object is assignable to the given type.
final class TypeTest extends Definition with NoThrow, Pure, Idempotent {
final CType testedType;
TypeTest(
FlowGraph graph,
SourcePosition sourcePosition,
Definition object,
this.testedType,
Definition? typeParameters,
) : super(graph, sourcePosition, typeParameters != null ? 2 : 1) {
setInputAt(0, object);
if (typeParameters != null) {
setInputAt(1, typeParameters);
}
}
Definition get operand => inputDefAt(0);
Definition? get typeParameters => (inputCount > 1) ? inputDefAt(1) : null;
@override
CType get type => const BoolType();
@override
bool attributesEqual(covariant TypeTest other) =>
testedType == other.testedType;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitTypeTest(this);
}
/// Represents a list of type arguments passed to a call.
///
/// Only used as the first input of call instructions.
final class TypeArguments extends Definition with NoThrow, Pure, Idempotent {
final List<ast.DartType> types;
TypeArguments(
FlowGraph graph,
SourcePosition sourcePosition,
this.types,
Definition? typeParameters,
) : super(graph, sourcePosition, typeParameters != null ? 1 : 0) {
if (typeParameters != null) {
setInputAt(0, typeParameters);
}
}
Definition? get typeParameters => (inputCount > 0) ? inputDefAt(0) : null;
@override
CType get type => const TypeArgumentsType();
@override
bool attributesEqual(covariant TypeArguments other) =>
listEquals(types, other.types);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitTypeArguments(this);
}
enum BinaryIntOpcode {
add('+'),
sub('-'),
mul('*'),
truncatingDiv('~/'),
mod('%'),
rem('remainder'),
bitOr('|'),
bitAnd('&'),
bitXor('^'),
shiftLeft('<<'),
shiftRight('>>'),
unsignedShiftRight('>>>');
final String token;
const BinaryIntOpcode(this.token);
bool get isCommutative => switch (this) {
add || mul || bitOr || bitAnd || bitXor => true,
_ => false,
};
}
/// Binary operation on two int operands.
final class BinaryIntOp extends Definition with Pure, Idempotent {
BinaryIntOpcode op;
BinaryIntOp(
FlowGraph graph,
SourcePosition sourcePosition,
this.op,
Definition left,
Definition right,
) : super(graph, sourcePosition, 2) {
setInputAt(0, left);
setInputAt(1, right);
}
Definition get left => inputDefAt(0);
Definition get right => inputDefAt(1);
@override
bool get canThrow => switch (op) {
BinaryIntOpcode.truncatingDiv ||
BinaryIntOpcode.mod ||
BinaryIntOpcode.rem => right.canBeZero,
BinaryIntOpcode.shiftLeft ||
BinaryIntOpcode.shiftRight ||
BinaryIntOpcode.unsignedShiftRight => right.canBeNegative,
_ => false,
};
@override
CType get type => const IntType();
@override
bool attributesEqual(covariant BinaryIntOp other) => op == other.op;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitBinaryIntOp(this);
}
enum UnaryIntOpcode {
neg('-'),
bitNot('~'),
toDouble('toDouble'),
abs('abs'),
sign('sign');
final String token;
const UnaryIntOpcode(this.token);
}
/// Unary operation on the int operand.
final class UnaryIntOp extends Definition with NoThrow, Pure, Idempotent {
UnaryIntOpcode op;
UnaryIntOp(
FlowGraph graph,
SourcePosition sourcePosition,
this.op,
Definition operand,
) : super(graph, sourcePosition, 1) {
setInputAt(0, operand);
}
Definition get operand => inputDefAt(0);
@override
CType get type => switch (op) {
UnaryIntOpcode.toDouble => const DoubleType(),
_ => const IntType(),
};
@override
bool attributesEqual(covariant UnaryIntOp other) => op == other.op;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitUnaryIntOp(this);
}
enum BinaryDoubleOpcode {
add('+'),
sub('-'),
mul('*'),
div('/'),
truncatingDiv('~/'),
mod('%'),
rem('remainder');
final String token;
const BinaryDoubleOpcode(this.token);
bool get isCommutative => switch (this) {
add || mul => true,
_ => false,
};
}
/// Binary operation on two double operands.
final class BinaryDoubleOp extends Definition with NoThrow, Pure, Idempotent {
BinaryDoubleOpcode op;
BinaryDoubleOp(
FlowGraph graph,
SourcePosition sourcePosition,
this.op,
Definition left,
Definition right,
) : super(graph, sourcePosition, 2) {
setInputAt(0, left);
setInputAt(1, right);
}
Definition get left => inputDefAt(0);
Definition get right => inputDefAt(1);
@override
CType get type => switch (op) {
BinaryDoubleOpcode.truncatingDiv => const IntType(),
_ => const DoubleType(),
};
@override
bool attributesEqual(covariant BinaryDoubleOp other) => op == other.op;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitBinaryDoubleOp(this);
}
enum UnaryDoubleOpcode {
neg('-'),
abs('abs'),
sign('sign'),
square('square'),
round('round'),
floor('floor'),
ceil('ceil'),
truncate('truncate'),
roundToDouble('roundToDouble'),
floorToDouble('floorToDouble'),
ceilToDouble('ceilToDouble'),
truncateToDouble('truncateToDouble');
final String token;
const UnaryDoubleOpcode(this.token);
}
/// Unary operation on the double operand.
final class UnaryDoubleOp extends Definition with NoThrow, Pure, Idempotent {
UnaryDoubleOpcode op;
UnaryDoubleOp(
FlowGraph graph,
SourcePosition sourcePosition,
this.op,
Definition operand,
) : super(graph, sourcePosition, 1) {
setInputAt(0, operand);
}
Definition get operand => inputDefAt(0);
@override
CType get type => switch (op) {
UnaryDoubleOpcode.round ||
UnaryDoubleOpcode.floor ||
UnaryDoubleOpcode.ceil ||
UnaryDoubleOpcode.truncate => const IntType(),
_ => const DoubleType(),
};
@override
bool attributesEqual(covariant UnaryDoubleOp other) => op == other.op;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitUnaryDoubleOp(this);
}
/// Marker for the back-end specific instructions.
base mixin BackendInstruction on Instruction {}
/// Combined comparison and branch.
///
/// Certain back-ends may create [CompareAndBranch] during lowering.
final class CompareAndBranch extends Instruction
with NoThrow, Pure, BackendInstruction
implements ControlFlowInstruction {
ComparisonOpcode op;
CompareAndBranch(
FlowGraph graph,
SourcePosition sourcePosition,
this.op,
Definition left,
Definition right,
) : super(graph, sourcePosition, 2) {
setInputAt(0, left);
setInputAt(1, right);
}
Definition get left => inputDefAt(0);
Definition get right => inputDefAt(1);
TargetBlock get trueSuccessor => block!.successors[0] as TargetBlock;
TargetBlock get falseSuccessor => block!.successors[1] as TargetBlock;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitCompareAndBranch(this);
}
/// Base class for move operations, part of [ParallelMove].
abstract base class MoveOp {}
/// In native back-ends, register allocator inserts [ParallelMove]
/// instructions to copy values atomically between registers
/// and memory locations.
final class ParallelMove extends Instruction
with NoThrow, HasSideEffects, BackendInstruction {
final List<MoveOp> moves = [];
ParallelMove(FlowGraph graph) : super(graph, noPosition, 0);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitParallelMove(this);
}