blob: 6c05c91edb95b552303c051200510f404bd056fd [file] [log] [blame] [edit]
// 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:cfg/ir/constant_value.dart';
import 'package:cfg/ir/field.dart';
import 'package:cfg/ir/flow_graph.dart';
import 'package:cfg/ir/functions.dart';
import 'package:cfg/ir/instructions.dart';
import 'package:cfg/ir/local_variable.dart';
import 'package:cfg/ir/source_position.dart';
import 'package:cfg/ir/types.dart';
import 'package:kernel/ast.dart'
as ast
show DartType, Name, VariableDeclaration;
/// Helper class to create IR instructions and populate [FlowGraph].
///
/// Maintains current basic block and appends newly created
/// instructions to the end of the current block.
///
/// Simulates expression stack. When creating IR instructions,
/// their inputs are populated from the expression stack.
///
/// Keeps current source position which is used for all
/// created IR instructions.
///
/// Maintains a stack of entered try-blocks (exception handlers).
/// All newly created basic blocks get a top exception handler.
///
/// All other attributes and optional inputs are passed explicitly
/// when creating IR instructions.
class FlowGraphBuilder {
/// Current graph being constructed.
final FlowGraph graph;
/// Source position for the new instructions.
SourcePosition currentSourcePosition = noPosition;
/// Simulated expression stack.
final List<Definition> stack = [];
/// Last instruction in the current block.
Instruction? _last;
/// Stack of the exception handlers.
List<CatchBlock> _exceptionHandlers = [];
FlowGraphBuilder(CFunction function) : graph = FlowGraph(function) {
_last = graph.entryBlock;
}
/// Finish building CFG.
FlowGraph done() {
assert(!hasOpenBlock);
graph.discoverBlocks();
return graph;
}
/// Start filling [block] with instructions.
void startBlock(Block block) {
_last = block;
if (_exceptionHandlers.isNotEmpty) {
block.exceptionHandler = _exceptionHandlers.last;
}
}
/// Append [instr] to the current block.
void appendInstruction(Instruction instr) {
_last!.appendInstruction(instr);
_last = instr;
}
/// End the current block.
void endBlock() {
_last = null;
}
/// Returns `true` if there is an unfinished basic block being built.
bool get hasOpenBlock => _last != null;
/// Push result of [def] into the expression stack.
void push(Definition def) {
stack.add(def);
}
/// Pop and return value from the top of the expression stack.
Definition pop() => stack.removeLast();
/// Returns value from the top of the expression stack without removing it.
Definition get stackTop => stack.last;
/// Pop [count] values from the expression stack and
/// use them as `first ... first + count - 1` inputs of [instr]
/// (in the reverse order).
void popInputs(Instruction instr, int first, int count) {
for (int i = count - 1; i >= 0; --i) {
instr.setInputAt(first + i, pop());
}
}
/// Pop [count] values from the expression stack.
void drop(int count) {
stack.removeRange(stack.length - count, stack.length);
}
/// Create a new [JoinBlock].
JoinBlock newJoinBlock() => JoinBlock(graph, currentSourcePosition);
/// Create a new [TargetBlock].
TargetBlock newTargetBlock() => TargetBlock(graph, currentSourcePosition);
/// Create a new [CatchBlock].
CatchBlock newCatchBlock() => CatchBlock(graph, currentSourcePosition);
/// Append [Goto] to the graph. Ends current block.
void addGoto(Block target) {
final instr = Goto(graph, currentSourcePosition);
appendInstruction(instr);
final currentBlock = instr.block!;
assert(currentBlock.successors.isEmpty);
currentBlock.successors.add(target);
endBlock();
}
/// Append [Branch] to the graph. Ends current block.
void addBranch(TargetBlock trueSuccessor, TargetBlock falseSuccessor) {
final instr = Branch(graph, currentSourcePosition, pop());
appendInstruction(instr);
final currentBlock = instr.block!;
assert(currentBlock.successors.isEmpty);
currentBlock.successors.add(trueSuccessor);
currentBlock.successors.add(falseSuccessor);
endBlock();
}
/// Append [TryEntry] to the graph. Ends current block.
void addTryEntry(TargetBlock tryBody, CatchBlock catchBlock) {
final instr = TryEntry(graph, currentSourcePosition);
appendInstruction(instr);
final currentBlock = instr.block!;
assert(currentBlock.successors.isEmpty);
currentBlock.successors.add(tryBody);
currentBlock.successors.add(catchBlock);
endBlock();
}
/// Enter try-block with given [exceptionHandler].
void enterTryBlock(CatchBlock exceptionHandler) {
_exceptionHandlers.add(exceptionHandler);
}
/// Leave the last entered try-block.
void leaveTryBlock() {
_exceptionHandlers.removeLast();
}
/// Append [Return] to the graph. Ends current block.
void addReturn() {
final value = pop();
final instr = Return(graph, currentSourcePosition, value);
appendInstruction(instr);
endBlock();
}
/// Append [Comparison] to the graph.
Comparison addComparison(ComparisonOpcode op) {
final right = pop();
final left = pop();
final instr = Comparison(graph, currentSourcePosition, op, left, right);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [Constant] to the entry block of the graph.
Constant addConstant(ConstantValue value) {
final instr = graph.getConstant(value);
if (instr.previous == _last) {
_last = instr;
}
push(instr);
return instr;
}
/// Append `null` [Constant].
Constant addNullConstant() => addConstant(ConstantValue.fromNull());
/// Append `int` [Constant] with given [value].
Constant addIntConstant(int value) =>
addConstant(ConstantValue.fromInt(value));
/// Append `bool` [Constant] with given [value].
Constant addBoolConstant(bool value) =>
addConstant(ConstantValue.fromBool(value));
/// Append [DirectCall] to the graph.
DirectCall addDirectCall(CFunction target, int inputCount, CType type) {
final instr = DirectCall(
graph,
currentSourcePosition,
target,
type,
inputCount: inputCount,
);
popInputs(instr, 0, inputCount);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [InterfaceCall] to the graph.
InterfaceCall addInterfaceCall(
CFunction interfaceTarget,
int inputCount,
CType type,
) {
final instr = InterfaceCall(
graph,
currentSourcePosition,
interfaceTarget,
type,
inputCount: inputCount,
);
popInputs(instr, 0, inputCount);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [ClosureCall] to the graph.
ClosureCall addClosureCall(int inputCount, CType type) {
final instr = ClosureCall(
graph,
currentSourcePosition,
type,
inputCount: inputCount,
);
popInputs(instr, 0, inputCount);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [DynamicCall] to the graph.
DynamicCall addDynamicCall(
ast.Name selector,
DynamicCallKind kind,
int inputCount,
) {
final instr = DynamicCall(
graph,
currentSourcePosition,
selector,
kind,
inputCount: inputCount,
);
popInputs(instr, 0, inputCount);
push(instr);
appendInstruction(instr);
return instr;
}
/// Add [LocalVariable] to the graph.
LocalVariable declareLocalVariable(
String name,
ast.VariableDeclaration? declaration,
CType type,
) {
final v = LocalVariable(
name,
declaration,
graph.localVariables.length,
type,
);
graph.localVariables.add(v);
return v;
}
/// Append [Parameter] instruction to the graph.
Parameter addParameter(LocalVariable variable) {
final instr = Parameter(graph, currentSourcePosition, variable);
appendInstruction(instr);
return instr;
}
/// Append [LoadLocal] to the graph.
LoadLocal addLoadLocal(LocalVariable variable) {
final instr = LoadLocal(graph, currentSourcePosition, variable);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [StoreLocal] to the graph.
///
/// If [leaveValueOnStack] is `true`, then the stored value is left
/// on top of the expression stack.
void addStoreLocal(LocalVariable variable, {bool leaveValueOnStack = false}) {
final value = pop();
final instr = StoreLocal(graph, currentSourcePosition, variable, value);
if (leaveValueOnStack) {
push(value);
}
appendInstruction(instr);
}
/// Append [LoadInstanceField] to the graph.
LoadInstanceField addLoadInstanceField(
CField field, {
bool checkInitialized = false,
}) {
final object = pop();
final instr = LoadInstanceField(
graph,
currentSourcePosition,
field,
object,
checkInitialized: checkInitialized,
);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [StoreInstanceField] to the graph.
StoreInstanceField addStoreInstanceField(
CField field, {
bool checkNotInitialized = false,
}) {
final value = pop();
final object = pop();
final instr = StoreInstanceField(
graph,
currentSourcePosition,
field,
object,
value,
checkNotInitialized: checkNotInitialized,
);
appendInstruction(instr);
return instr;
}
/// Append [LoadStaticField] to the graph.
LoadStaticField addLoadStaticField(
CField field, {
bool checkInitialized = false,
}) {
final instr = LoadStaticField(
graph,
currentSourcePosition,
field,
checkInitialized: checkInitialized,
);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [StoreStaticField] to the graph.
StoreStaticField addStoreStaticField(
CField field, {
bool checkNotInitialized = false,
}) {
final value = pop();
final instr = StoreStaticField(
graph,
currentSourcePosition,
field,
value,
checkNotInitialized: checkNotInitialized,
);
appendInstruction(instr);
return instr;
}
/// Append [Throw] taking an exception as input to the graph.
/// Ends current block.
void addThrow() {
final exception = pop();
final instr = Throw(graph, currentSourcePosition, exception, null);
appendInstruction(instr);
endBlock();
}
/// Append [Throw] taking an exception and stack trace as inputs to
/// the graph. Ends current block.
void addRethrow() {
final stackTrace = pop();
final exception = pop();
final instr = Throw(graph, currentSourcePosition, exception, stackTrace);
appendInstruction(instr);
endBlock();
}
/// Append [NullCheck] to the graph.
NullCheck addNullCheck() {
final object = pop();
final instr = NullCheck(graph, currentSourcePosition, object);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [TypeParameters] to the graph.
///
/// Optional [receiver] input should be passed if there are class type
/// parameters in scope.
TypeParameters addTypeParameters({Definition? receiver}) {
final instr = TypeParameters(graph, currentSourcePosition, receiver);
appendInstruction(instr);
return instr;
}
/// Append [TypeCast] to the graph.
///
/// Optional [typeParameters] input should be passed if tested type
/// depends on type parameters (not fully instantiated).
///
/// Optional [isChecked] argument indicates if this type cast involves
/// check at runtime.
TypeCast addTypeCast(
CType testedType, {
Definition? typeParameters,
bool isChecked = true,
}) {
final object = pop();
final instr = TypeCast(
graph,
currentSourcePosition,
object,
testedType,
typeParameters,
isChecked: isChecked,
);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [TypeTest] to the graph.
///
/// Optional [typeParameters] input should be passed if tested type
/// depends on type parameters (not fully instantiated).
TypeTest addTypeTest(CType testedType, {Definition? typeParameters}) {
final object = pop();
final instr = TypeTest(
graph,
currentSourcePosition,
object,
testedType,
typeParameters,
);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append either [TypeArguments] or [Constant] representing type arguments
/// to the graph.
///
/// Optional [typeParameters] input should be passed if type arguments
/// depend on type parameters (not fully instantiated).
void addTypeArguments(
List<ast.DartType> types, {
Definition? typeParameters,
}) {
if (typeParameters == null) {
addConstant(ConstantValue(TypeArgumentsConstant(types)));
return;
}
final instr = TypeArguments(
graph,
currentSourcePosition,
types,
typeParameters,
);
push(instr);
appendInstruction(instr);
}
/// Append [TypeLiteral] to the graph.
TypeLiteral addTypeLiteral(
ast.DartType uninstantiatedType, {
required Definition typeParameters,
}) {
final instr = TypeLiteral(
graph,
currentSourcePosition,
uninstantiatedType,
typeParameters,
);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [AllocateObject] to the graph.
///
/// Optional [typeArguments] input should be passed if allocating
/// an instance of a generic class.
AllocateObject addAllocateObject(CType type, {Definition? typeArguments}) {
final instr = AllocateObject(
graph,
currentSourcePosition,
type,
typeArguments,
);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [AllocateClosure] to the graph.
AllocateClosure addAllocateClosure(
ClosureFunction function,
CType type,
int inputCount,
) {
final instr = AllocateClosure(
graph,
currentSourcePosition,
function,
type,
inputCount: inputCount,
);
popInputs(instr, 0, inputCount);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [AllocateListLiteral] to the graph.
/// Takes type arguments and elements from the stack as inputs.
AllocateListLiteral addAllocateListLiteral(CType type, int inputCount) {
final instr = AllocateListLiteral(
graph,
currentSourcePosition,
type,
inputCount: inputCount,
);
popInputs(instr, 0, inputCount);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [AllocateMapLiteral] to the graph.
/// Takes type arguments and key/value pairs from the stack as inputs.
AllocateMapLiteral addAllocateMapLiteral(CType type, int inputCount) {
final instr = AllocateMapLiteral(
graph,
currentSourcePosition,
type,
inputCount: inputCount,
);
popInputs(instr, 0, inputCount);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [StringInterpolation] to the graph.
StringInterpolation addStringInterpolation(int inputCount) {
final instr = StringInterpolation(
graph,
currentSourcePosition,
inputCount: inputCount,
);
popInputs(instr, 0, inputCount);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [BinaryIntOp] to the graph.
BinaryIntOp addBinaryIntOp(BinaryIntOpcode op) {
final right = pop();
final left = pop();
final instr = BinaryIntOp(graph, currentSourcePosition, op, left, right);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [UnaryIntOp] to the graph.
UnaryIntOp addUnaryIntOp(UnaryIntOpcode op) {
final operand = pop();
final instr = UnaryIntOp(graph, currentSourcePosition, op, operand);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [BinaryDoubleOp] to the graph.
BinaryDoubleOp addBinaryDoubleOp(BinaryDoubleOpcode op) {
final right = pop();
final left = pop();
final instr = BinaryDoubleOp(graph, currentSourcePosition, op, left, right);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [UnaryDoubleOp] to the graph.
UnaryDoubleOp addUnaryDoubleOp(UnaryDoubleOpcode op) {
final operand = pop();
final instr = UnaryDoubleOp(graph, currentSourcePosition, op, operand);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [UnaryBoolOp] to the graph.
UnaryBoolOp addUnaryBoolOp(UnaryBoolOpcode op) {
final operand = pop();
final instr = UnaryBoolOp(graph, currentSourcePosition, op, operand);
push(instr);
appendInstruction(instr);
return instr;
}
}