[cfg] Support more operations in CFG IR and AST-to-CFG translation

Add support for the following AST nodes:

TypeLiteral, ListLiteral, MapLiteral, InstanceTearOff, NullCheck,
StringConcatenation, FunctionExpression (preliminary),
FunctionDeclaration (preliminary), FunctionInvocation,
LocalFunctionInvocation, Not, LogicalExpression.

Add the following CFG IR instructions:

ClosureCall, NullCheck, TypeLiteral, AllocateClosure,
AllocateListLiteral, AllocateMapLiteral, StringInterpolation,
UnaryBoolOp.

Also add the following back-end-specific CFG IR instructions:

AllocateList, SetListElement.

Issue: https://github.com/dart-lang/sdk/issues/61635
Change-Id: I6de65ef14c454745ed618e17640a216912fe05c5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/471865
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov
2026-01-12 07:31:28 -08:00
committed by Commit Queue
parent 909786e1b9
commit e314cccd3b
25 changed files with 1478 additions and 42 deletions
+225 -9
View File
@@ -30,10 +30,6 @@ import 'package:kernel/type_environment.dart' show StaticTypeContext;
/// - stack overflow/interrupt checks;
/// - assert statements;
/// - async/async*/sync*/await/yield/yield*;
/// - standalone logical expressions (||, &&, !);
/// - null checks;
/// - string concatenation;
/// - list, set and map literals;
/// - record access and literals;
/// - deferred libraries.
///
@@ -379,6 +375,45 @@ class AstToIr extends ast.RecursiveVisitor {
builder.addNullConstant();
}
@override
void visitTypeLiteral(ast.TypeLiteral node) {
final typeParameters = _typeParametersForType(node.type);
if (typeParameters != null) {
builder.addTypeLiteral(node.type, typeParameters: typeParameters);
} else {
builder.addConstant(ConstantValue(ast.TypeLiteralConstant(node.type)));
}
}
@override
void visitListLiteral(ast.ListLiteral node) {
assert(!node.isConst);
final inputCount = node.expressions.length + 1;
builder.addTypeArguments([
node.typeArgument,
], typeParameters: _typeParametersForType(node.typeArgument));
_translateNodes(node.expressions);
if (_handleUnreachableExpression(inputCount)) return;
builder.addAllocateListLiteral(inputCount);
}
@override
void visitMapLiteral(ast.MapLiteral node) {
assert(!node.isConst);
final inputCount = (node.entries.length << 1) + 1;
final typeArgs = <ast.DartType>[node.keyType, node.valueType];
builder.addTypeArguments(
typeArgs,
typeParameters: _typeParametersForTypes(typeArgs),
);
for (final entry in node.entries) {
_translateNode(entry.key);
_translateNode(entry.value);
}
if (_handleUnreachableExpression(inputCount)) return;
builder.addAllocateMapLiteral(inputCount);
}
@override
void visitConstantExpression(ast.ConstantExpression node) {
builder.addConstant(ConstantValue(node.constant));
@@ -448,18 +483,36 @@ class AstToIr extends ast.RecursiveVisitor {
@override
void visitStaticGet(ast.StaticGet node) {
final target = functionRegistry.getFunction(node.target, isGetter: true);
builder.addDirectCall(target, 0, _staticType(node));
final member = node.target;
if (member is ast.Field) {
final field = CField(member);
builder.addLoadStaticField(
field,
checkInitialized: field.isLate || field.hasInitializer,
);
} else {
final target = functionRegistry.getFunction(node.target, isGetter: true);
builder.addDirectCall(target, 0, _staticType(node));
}
}
@override
void visitStaticSet(ast.StaticSet node) {
final target = functionRegistry.getFunction(node.target, isSetter: true);
_translateNode(node.value);
if (_handleUnreachableExpression(1)) return;
final value = builder.stackTop;
builder.addDirectCall(target, 1, const TopType(const ast.VoidType()));
builder.pop();
final member = node.target;
if (member is ast.Field) {
final field = CField(member);
builder.addStoreStaticField(
field,
checkNotInitialized: field.isLate && field.isFinal,
);
} else {
final target = functionRegistry.getFunction(node.target, isSetter: true);
builder.addDirectCall(target, 1, const TopType(const ast.VoidType()));
builder.pop();
}
builder.push(value);
}
@@ -518,6 +571,17 @@ class AstToIr extends ast.RecursiveVisitor {
builder.push(value);
}
@override
void visitInstanceTearOff(ast.InstanceTearOff node) {
final interfaceTarget = functionRegistry.getFunction(
node.interfaceTarget,
isTearOff: true,
);
_translateNode(node.receiver);
if (_handleUnreachableExpression(1)) return;
builder.addInterfaceCall(interfaceTarget, 1, _staticType(node));
}
@override
void visitEqualsCall(ast.EqualsCall node) {
_translateNode(node.left);
@@ -1136,6 +1200,13 @@ class AstToIr extends ast.RecursiveVisitor {
builder.addNullConstant();
}
@override
void visitNullCheck(ast.NullCheck node) {
_translateNode(node.operand);
if (_handleUnreachableExpression(1)) return;
builder.addNullCheck();
}
@override
void visitIsExpression(ast.IsExpression node) {
_translateNode(node.operand);
@@ -1349,6 +1420,151 @@ class AstToIr extends ast.RecursiveVisitor {
builder.pop();
builder.push(instance);
}
@override
void visitStringConcatenation(ast.StringConcatenation node) {
final inputCount = node.expressions.length;
_translateNodes(node.expressions);
if (_handleUnreachableExpression(inputCount)) return;
builder.addStringInterpolation(inputCount);
}
void _translateClosure(ast.LocalFunction node, CType type) {
final closureFunction =
functionRegistry.getFunction(function.member, localFunction: node)
as ClosureFunction;
// TODO: pass captured contexts and type parameters.
builder.addAllocateClosure(closureFunction, type, 0);
}
@override
void visitFunctionExpression(ast.FunctionExpression node) {
_translateClosure(node, _staticType(node));
}
@override
void visitFunctionDeclaration(ast.FunctionDeclaration node) {
final local = localVarIndexer.variableForDeclaration(node.variable);
_translateClosure(node, local.type);
builder.addStoreLocal(local);
}
@override
void visitFunctionInvocation(ast.FunctionInvocation node) {
final inputCount = _translateArguments(node.receiver, node.arguments);
if (_handleUnreachableExpression(inputCount)) return;
if (node.kind == ast.FunctionAccessKind.FunctionType) {
builder.addClosureCall(inputCount, _staticType(node));
} else {
builder.addDynamicCall(
ast.Name.callName,
DynamicCallKind.method,
inputCount,
);
}
}
@override
void visitLocalFunctionInvocation(ast.LocalFunctionInvocation node) {
final local = localVarIndexer.variableForDeclaration(node.variable);
builder.addLoadLocal(local);
final inputCount = _translateArguments(null, node.arguments);
if (_handleUnreachableExpression(inputCount + 1)) return;
builder.addClosureCall(inputCount + 1, _staticType(node));
}
/// Translate logical expression (!x, x || y, x && y) for value.
void _translateConditionForValue(ast.Expression node) {
// Created lazily, only if there are extra edges with true/false results.
JoinBlock? done;
late final resultVar = builder.declareLocalVariable(
'#temp',
null,
const BoolType(),
);
void addExtraEdges(bool result, List<Block> blocks) {
for (final block in blocks) {
builder.startBlock(block);
builder.addBoolConstant(result);
builder.addStoreLocal(resultVar);
builder.addGoto(done ??= builder.newJoinBlock());
}
}
var negated = false;
for (ast.Expression? expr = node; expr != null;) {
switch (expr) {
case ast.Not():
negated = !negated;
expr = expr.operand;
break;
case ast.LogicalExpression():
var (leftTrue, leftFalse) = _translateConditionForControl(expr.left);
var op = expr.operatorEnum;
if (negated) {
op = switch (op) {
.AND => .OR,
.OR => .AND,
};
final tmp = leftTrue;
leftTrue = leftFalse;
leftFalse = tmp;
}
switch (op) {
case .AND:
addExtraEdges(false, leftFalse);
if (leftTrue.isEmpty) {
expr = null;
break;
}
builder.startBlock(_joinBlocks(leftTrue));
expr = expr.right;
case .OR:
addExtraEdges(true, leftTrue);
if (leftFalse.isEmpty) {
expr = null;
break;
}
builder.startBlock(_joinBlocks(leftFalse));
expr = expr.right;
}
break;
case _:
_translateNode(expr);
if (builder.hasOpenBlock) {
if (negated) {
builder.addUnaryBoolOp(UnaryBoolOpcode.not);
}
if (done != null) {
builder.addStoreLocal(resultVar);
builder.addGoto(done!);
}
} else {
builder.drop(1);
}
expr = null;
break;
}
}
if (done != null) {
builder.startBlock(done!);
builder.addLoadLocal(resultVar);
} else {
_handleUnreachableExpression(0);
}
}
@override
void visitNot(ast.Not node) {
_translateConditionForValue(node);
}
@override
void visitLogicalExpression(ast.LogicalExpression node) {
_translateConditionForValue(node);
}
}
/// Mapping between AST nodes and CFG IR [LocalVariable].
+36
View File
@@ -216,6 +216,42 @@ class ConstantFolding {
return ConstantValue.fromDouble(x.truncateToDouble());
}
}
ConstantValue? unaryBoolOp(UnaryBoolOpcode op, ConstantValue operand) {
final x = operand.boolValue;
switch (op) {
case UnaryBoolOpcode.not:
return ConstantValue.fromBool(!x);
}
}
String? computeToString(ConstantValue value) {
if (value.isString) {
return value.stringValue;
} else if (value.isInt) {
return value.intValue.toString();
} else if (value.isBool) {
return value.boolValue.toString();
} else if (value.isNull) {
return null.toString();
} else if (value.isDouble) {
return value.doubleValue.toString();
} else {
return null;
}
}
ConstantValue? stringInterpolation(List<ConstantValue> operands) {
final buf = StringBuffer();
for (final operand in operands) {
final str = computeToString(operand);
if (str == null) {
return null;
}
buf.write(str);
}
return ConstantValue.fromString(buf.toString());
}
}
/// Constant type arguments.
+108
View File
@@ -228,6 +228,20 @@ class FlowGraphBuilder {
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,
@@ -380,6 +394,15 @@ class FlowGraphBuilder {
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
@@ -453,6 +476,22 @@ class FlowGraphBuilder {
return 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
@@ -469,6 +508,66 @@ class FlowGraphBuilder {
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(int inputCount) {
final instr = AllocateListLiteral(
graph,
currentSourcePosition,
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(int inputCount) {
final instr = AllocateMapLiteral(
graph,
currentSourcePosition,
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();
@@ -506,4 +605,13 @@ class FlowGraphBuilder {
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;
}
}
+40 -1
View File
@@ -240,6 +240,9 @@ final class FlowGraphChecker extends Pass implements InstructionVisitor<void> {
@override
void visitInterfaceCall(InterfaceCall instr) {}
@override
void visitClosureCall(ClosureCall instr) {}
@override
void visitDynamicCall(DynamicCall instr) {}
@@ -282,9 +285,13 @@ final class FlowGraphChecker extends Pass implements InstructionVisitor<void> {
assert(instr.canThrow);
}
@override
void visitNullCheck(NullCheck instr) {}
@override
void visitTypeParameters(TypeParameters instr) {
// TypeParameters can only be used in TypeCast, TypeTest and TypeArguments.
// TypeParameters can only be used in TypeCast, TypeTest,
// TypeArguments and TypeLiteral.
for (final use in instr.inputUses) {
final user = use.getInstruction(graph);
switch (user) {
@@ -294,6 +301,8 @@ final class FlowGraphChecker extends Pass implements InstructionVisitor<void> {
assert(instr == user.typeParameters);
case TypeArguments():
assert(instr == user.typeParameters);
case TypeLiteral():
assert(instr == user.typeParameters);
default:
throw 'Unexpected user ${IrToText.instruction(user)} of TypeParameters';
}
@@ -323,15 +332,34 @@ final class FlowGraphChecker extends Pass implements InstructionVisitor<void> {
assert(user.typeArguments == instr);
case AllocateObject():
assert(user.typeArguments == instr);
case AllocateListLiteral():
assert(user.typeArguments == instr);
case AllocateMapLiteral():
assert(user.typeArguments == instr);
default:
throw 'Unexpected user ${IrToText.instruction(user)} of TypeArguments';
}
}
}
@override
void visitTypeLiteral(TypeLiteral instr) {}
@override
void visitAllocateObject(AllocateObject instr) {}
@override
void visitAllocateClosure(AllocateClosure instr) {}
@override
void visitAllocateListLiteral(AllocateListLiteral instr) {}
@override
void visitAllocateMapLiteral(AllocateMapLiteral instr) {}
@override
void visitStringInterpolation(StringInterpolation instr) {}
@override
void visitComparison(Comparison instr) {
if (instr.op.isIntComparison) {
@@ -365,6 +393,17 @@ final class FlowGraphChecker extends Pass implements InstructionVisitor<void> {
assert(instr.operand.type is DoubleType);
}
@override
void visitUnaryBoolOp(UnaryBoolOp instr) {
assert(instr.operand.type is BoolType);
}
@override
void visitAllocateList(AllocateList instr) {}
@override
void visitSetListElement(SetListElement instr) {}
@override
void visitParallelMove(ParallelMove instr) {}
}
+231 -4
View File
@@ -3,7 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:cfg/ir/field.dart';
import 'package:kernel/ast.dart' as ast show DartType, InterfaceType, Name;
import 'package:cfg/ir/global_context.dart';
import 'package:kernel/ast.dart'
as ast
show DartType, InterfaceType, Name, Nullability;
import 'package:cfg/ir/constant_value.dart';
import 'package:cfg/ir/flow_graph.dart';
import 'package:cfg/ir/functions.dart';
@@ -803,12 +806,32 @@ final class InterfaceCall extends CallInstruction {
this.interfaceTarget,
this.type, {
required super.inputCount,
});
}) : assert(inputCount > 0);
Definition get receiver => inputDefAt(0);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitInterfaceCall(this);
}
/// Call closure function using the given closure instance.
final class ClosureCall extends CallInstruction {
@override
final CType type;
ClosureCall(
super.graph,
super.sourcePosition,
this.type, {
required super.inputCount,
}) : assert(inputCount > 0);
Definition get closure => inputDefAt(0);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitClosureCall(this);
}
enum DynamicCallKind { method, getter, setter }
/// Dynamic call via given selector.
@@ -822,7 +845,9 @@ final class DynamicCall extends CallInstruction {
this.selector,
this.kind, {
required super.inputCount,
});
}) : assert(inputCount > 0);
Definition get receiver => inputDefAt(0);
@override
CType get type => const TopType();
@@ -1044,6 +1069,25 @@ final class Throw extends Instruction
R accept<R>(InstructionVisitor<R> v) => v.visitThrow(this);
}
/// Checks that input object is not null. Throws TypeError if object is null.
final class NullCheck extends Definition with CanThrow, Pure, Idempotent {
@override
late final CType type = operand.type.toNonNullableType;
NullCheck(super.graph, super.sourcePosition, Definition object)
: super(inputCount: 1) {
setInputAt(0, object);
}
Definition get operand => inputDefAt(0);
@override
bool attributesEqual(covariant NullCheck other) => true;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitNullCheck(this);
}
/// Represents collection of class and function type parameters.
final class TypeParameters extends Definition with NoThrow, Pure {
TypeParameters(super.graph, super.sourcePosition, Definition? receiver)
@@ -1138,7 +1182,8 @@ final class TypeTest extends Definition with NoThrow, Pure, Idempotent {
/// Represents a list of type arguments passed to a call or an instance
/// allocation.
///
/// Only used as the first input of call instructions and [AllocateObject].
/// Only used as the first input of call instructions, [AllocateObject],
/// [AllocateListLiteral] and [AllocateMapLiteral].
final class TypeArguments extends Definition with NoThrow, Pure, Idempotent {
final List<ast.DartType> types;
TypeArguments(
@@ -1165,6 +1210,32 @@ final class TypeArguments extends Definition with NoThrow, Pure, Idempotent {
R accept<R>(InstructionVisitor<R> v) => v.visitTypeArguments(this);
}
/// Represents a type literal which uses type parameters.
final class TypeLiteral extends Definition with NoThrow, Pure, Idempotent {
final ast.DartType uninstantiatedType;
TypeLiteral(
super.graph,
super.sourcePosition,
this.uninstantiatedType,
Definition typeParameters,
) : super(inputCount: 1) {
setInputAt(0, typeParameters);
}
Definition get typeParameters => inputDefAt(0);
@override
CType get type =>
StaticType(GlobalContext.instance.coreTypes.typeNonNullableRawType);
@override
bool attributesEqual(covariant TypeLiteral other) =>
this.uninstantiatedType == other.uninstantiatedType;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitTypeLiteral(this);
}
/// Allocate an instance of given type.
///
/// If type is a generic class, then [AllocateObject] can take [TypeArguments] as an input.
@@ -1196,6 +1267,94 @@ final class AllocateObject extends Definition with CanThrow, Pure {
R accept<R>(InstructionVisitor<R> v) => v.visitAllocateObject(this);
}
/// Allocate a closure instance.
///
/// Takes captured values as inputs.
final class AllocateClosure extends Definition with CanThrow, Pure {
final ClosureFunction function;
@override
final CType type;
AllocateClosure(
super.graph,
super.sourcePosition,
this.function,
this.type, {
required super.inputCount,
});
@override
R accept<R>(InstructionVisitor<R> v) => v.visitAllocateClosure(this);
}
/// Allocate a new List literal with given type arguments and elements.
final class AllocateListLiteral extends Definition with CanThrow, Pure {
@override
late final CType type = StaticType(
ast.InterfaceType(
GlobalContext.instance.coreTypes.listClass,
ast.Nullability.nonNullable,
typeArguments.types,
),
);
AllocateListLiteral(
super.graph,
super.sourcePosition, {
required super.inputCount,
}) : assert(inputCount > 0);
TypeArguments get typeArguments => inputDefAt(0) as TypeArguments;
Definition elementAt(int index) => inputDefAt(index + 1);
int get length => inputCount - 1;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitAllocateListLiteral(this);
}
/// Allocate a new Map literal with given type arguments and key-value pairs.
final class AllocateMapLiteral extends Definition with CanThrow, Pure {
@override
late final CType type = StaticType(
ast.InterfaceType(
GlobalContext.instance.coreTypes.mapClass,
ast.Nullability.nonNullable,
typeArguments.types,
),
);
AllocateMapLiteral(
super.graph,
super.sourcePosition, {
required super.inputCount,
}) : assert(inputCount > 0 && inputCount.isOdd);
TypeArguments get typeArguments => inputDefAt(0) as TypeArguments;
Definition keyAt(int index) => inputDefAt((index << 1) + 1);
Definition valueAt(int index) => inputDefAt((index << 1) + 2);
int get length => (inputCount - 1) >> 1;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitAllocateMapLiteral(this);
}
/// Interpolate given objects into a String.
final class StringInterpolation extends Definition
with CanThrow, HasSideEffects {
StringInterpolation(
super.graph,
super.sourcePosition, {
required super.inputCount,
});
@override
CType get type => const StringType();
@override
R accept<R>(InstructionVisitor<R> v) => v.visitStringInterpolation(this);
}
enum BinaryIntOpcode {
add('+'),
sub('-'),
@@ -1387,6 +1546,34 @@ final class UnaryDoubleOp extends Definition with NoThrow, Pure, Idempotent {
R accept<R>(InstructionVisitor<R> v) => v.visitUnaryDoubleOp(this);
}
enum UnaryBoolOpcode {
not('!');
final String token;
const UnaryBoolOpcode(this.token);
}
/// Unary operation on the bool operand.
final class UnaryBoolOp extends Definition with NoThrow, Pure, Idempotent {
UnaryBoolOpcode op;
UnaryBoolOp(super.graph, super.sourcePosition, this.op, Definition operand)
: super(inputCount: 1) {
setInputAt(0, operand);
}
Definition get operand => inputDefAt(0);
@override
CType get type => const BoolType();
@override
bool attributesEqual(covariant UnaryBoolOp other) => op == other.op;
@override
R accept<R>(InstructionVisitor<R> v) => v.visitUnaryBoolOp(this);
}
/// Marker for the back-end specific instructions.
base mixin BackendInstruction on Instruction {}
@@ -1418,6 +1605,46 @@ final class CompareAndBranch extends Instruction
R accept<R>(InstructionVisitor<R> v) => v.visitCompareAndBranch(this);
}
/// Allocate a fixed-size List of given length.
final class AllocateList extends Definition
with CanThrow, Pure, BackendInstruction {
AllocateList(super.graph, super.sourcePosition, Definition length)
: super(inputCount: 1) {
setInputAt(0, length);
}
Definition get length => inputDefAt(0);
CType get type =>
StaticType(GlobalContext.instance.coreTypes.listNonNullableRawType);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitAllocateList(this);
}
/// Set value of [index]-th element of the given fixed-size List.
final class SetListElement extends Instruction
with NoThrow, HasSideEffects, BackendInstruction {
SetListElement(
super.graph,
super.sourcePosition,
Definition list,
Definition index,
Definition value,
) : super(inputCount: 3) {
setInputAt(0, list);
setInputAt(1, index);
setInputAt(2, value);
}
Definition get list => inputDefAt(0);
Definition get index => inputDefAt(1);
Definition get value => inputDefAt(2);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitSetListElement(this);
}
/// Base class for move operations, part of [ParallelMove].
abstract base class MoveOp {}
+4
View File
@@ -109,6 +109,9 @@ final class IrToText extends VoidInstructionVisitor {
case StoreField():
_buffer.write(instr.field);
_buffer.write(', ');
case TypeLiteral():
_buffer.write(instr.uninstantiatedType.getDisplayString());
_buffer.write(', ');
case _:
}
for (int i = 0, n = instr.inputCount; i < n; ++i) {
@@ -180,6 +183,7 @@ final class IrToText extends VoidInstructionVisitor {
UnaryIntOp() => 'UnaryIntOp ${instr.op.token}',
BinaryDoubleOp() => 'BinaryDoubleOp ${instr.op.token}',
UnaryDoubleOp() => 'UnaryDoubleOp ${instr.op.token}',
UnaryBoolOp() => 'UnaryBoolOp ${instr.op.token}',
_ => instr.runtimeType.toString(),
};
}
+66
View File
@@ -68,6 +68,12 @@ sealed class CType {
bool isSubtypeOf(CType other) => GlobalContext.instance.typeEnvironment
.isSubtypeOf(this.dartType, other.dartType);
/// Returns true if value of this type can be `null`.
bool get isNullable;
/// Return non-nullable variant of this type (if possible).
CType get toNonNullableType;
@override
bool operator ==(Object other) =>
other is CType &&
@@ -91,6 +97,12 @@ final class IntType extends CType {
ast.DartType get dartType =>
_dartType ?? GlobalContext.instance.coreTypes.intNonNullableRawType;
@override
bool get isNullable => false;
@override
CType get toNonNullableType => this;
@override
String toString() => 'int';
}
@@ -108,6 +120,12 @@ final class DoubleType extends CType {
ast.DartType get dartType =>
_dartType ?? GlobalContext.instance.coreTypes.doubleNonNullableRawType;
@override
bool get isNullable => false;
@override
CType get toNonNullableType => this;
@override
String toString() => 'double';
}
@@ -125,6 +143,12 @@ final class BoolType extends CType {
ast.DartType get dartType =>
_dartType ?? GlobalContext.instance.coreTypes.boolNonNullableRawType;
@override
bool get isNullable => false;
@override
CType get toNonNullableType => this;
@override
String toString() => 'bool';
}
@@ -142,6 +166,12 @@ final class StringType extends CType {
ast.DartType get dartType =>
_dartType ?? GlobalContext.instance.coreTypes.stringNonNullableRawType;
@override
bool get isNullable => false;
@override
CType get toNonNullableType => this;
@override
String toString() => 'String';
}
@@ -159,6 +189,12 @@ final class ObjectType extends CType {
ast.DartType get dartType =>
_dartType ?? GlobalContext.instance.coreTypes.objectNonNullableRawType;
@override
bool get isNullable => false;
@override
CType get toNonNullableType => this;
@override
String toString() => 'Object';
}
@@ -173,6 +209,12 @@ final class NullType extends CType {
@override
ast.DartType get dartType => const ast.NullType();
@override
bool get isNullable => true;
@override
CType get toNonNullableType => const NeverType();
@override
String toString() => 'Null';
}
@@ -187,6 +229,12 @@ final class NeverType extends CType {
@override
ast.DartType get dartType => const ast.NeverType.nonNullable();
@override
bool get isNullable => false;
@override
CType get toNonNullableType => this;
@override
String toString() => 'Never';
}
@@ -203,6 +251,12 @@ final class TopType extends CType {
@override
ast.DartType get dartType => _dartType ?? const ast.DynamicType();
@override
bool get isNullable => true;
@override
CType get toNonNullableType => const ObjectType();
@override
String toString() => '<top>';
}
@@ -217,6 +271,12 @@ final class StaticType extends CType {
StaticType(this.dartType);
@override
bool get isNullable => dartType.isPotentiallyNullable;
@override
CType get toNonNullableType => CType.fromStaticType(dartType.toNonNull());
@override
String toString() => dartType.getDisplayString();
}
@@ -234,6 +294,12 @@ sealed class ExtendedType extends CType {
@override
bool isSubtypeOf(CType other) => this == other;
@override
bool get isNullable => false;
@override
CType get toNonNullableType => this;
@override
bool operator ==(Object other) =>
other is ExtendedType && this.kind == other.kind;
+24
View File
@@ -21,6 +21,7 @@ abstract interface class InstructionVisitor<R> {
R visitConstant(Constant instr);
R visitDirectCall(DirectCall instr);
R visitInterfaceCall(InterfaceCall instr);
R visitClosureCall(ClosureCall instr);
R visitDynamicCall(DynamicCall instr);
R visitParameter(Parameter instr);
R visitLoadLocal(LoadLocal instr);
@@ -30,17 +31,26 @@ abstract interface class InstructionVisitor<R> {
R visitLoadStaticField(LoadStaticField instr);
R visitStoreStaticField(StoreStaticField instr);
R visitThrow(Throw instr);
R visitNullCheck(NullCheck instr);
R visitTypeParameters(TypeParameters instr);
R visitTypeCast(TypeCast instr);
R visitTypeTest(TypeTest instr);
R visitTypeArguments(TypeArguments instr);
R visitTypeLiteral(TypeLiteral instr);
R visitAllocateObject(AllocateObject instr);
R visitAllocateClosure(AllocateClosure instr);
R visitAllocateListLiteral(AllocateListLiteral instr);
R visitAllocateMapLiteral(AllocateMapLiteral instr);
R visitStringInterpolation(StringInterpolation instr);
R visitBinaryIntOp(BinaryIntOp instr);
R visitUnaryIntOp(UnaryIntOp instr);
R visitBinaryDoubleOp(BinaryDoubleOp instr);
R visitUnaryDoubleOp(UnaryDoubleOp instr);
R visitUnaryBoolOp(UnaryBoolOp instr);
// Back-end specific instructions.
R visitCompareAndBranch(CompareAndBranch instr);
R visitAllocateList(AllocateList instr);
R visitSetListElement(SetListElement instr);
R visitParallelMove(ParallelMove instr);
}
@@ -73,6 +83,7 @@ abstract mixin class DefaultInstructionVisitor<R>
R visitConstant(Constant instr) => defaultInstruction(instr);
R visitDirectCall(DirectCall instr) => defaultInstruction(instr);
R visitInterfaceCall(InterfaceCall instr) => defaultInstruction(instr);
R visitClosureCall(ClosureCall instr) => defaultInstruction(instr);
R visitDynamicCall(DynamicCall instr) => defaultInstruction(instr);
R visitParameter(Parameter instr) => defaultInstruction(instr);
R visitLoadLocal(LoadLocal instr) => defaultInstruction(instr);
@@ -84,18 +95,31 @@ abstract mixin class DefaultInstructionVisitor<R>
R visitLoadStaticField(LoadStaticField instr) => defaultInstruction(instr);
R visitStoreStaticField(StoreStaticField instr) => defaultInstruction(instr);
R visitThrow(Throw instr) => defaultInstruction(instr);
R visitNullCheck(NullCheck instr) => defaultInstruction(instr);
R visitTypeParameters(TypeParameters instr) => defaultInstruction(instr);
R visitTypeCast(TypeCast instr) => defaultInstruction(instr);
R visitTypeTest(TypeTest instr) => defaultInstruction(instr);
R visitTypeArguments(TypeArguments instr) => defaultInstruction(instr);
R visitTypeLiteral(TypeLiteral instr) => defaultInstruction(instr);
R visitAllocateObject(AllocateObject instr) => defaultInstruction(instr);
R visitAllocateClosure(AllocateClosure instr) => defaultInstruction(instr);
R visitAllocateListLiteral(AllocateListLiteral instr) =>
defaultInstruction(instr);
R visitAllocateMapLiteral(AllocateMapLiteral instr) =>
defaultInstruction(instr);
R visitStringInterpolation(StringInterpolation instr) =>
defaultInstruction(instr);
R visitBinaryIntOp(BinaryIntOp instr) => defaultInstruction(instr);
R visitUnaryIntOp(UnaryIntOp instr) => defaultInstruction(instr);
R visitBinaryDoubleOp(BinaryDoubleOp instr) => defaultInstruction(instr);
R visitUnaryDoubleOp(UnaryDoubleOp instr) => defaultInstruction(instr);
R visitUnaryBoolOp(UnaryBoolOp instr) => defaultInstruction(instr);
// Back-end specific instructions.
R visitCompareAndBranch(CompareAndBranch instr) =>
defaultBackendInstruction(instr);
R visitAllocateList(AllocateList instr) => defaultBackendInstruction(instr);
R visitSetListElement(SetListElement instr) =>
defaultBackendInstruction(instr);
R visitParallelMove(ParallelMove instr) => defaultBackendInstruction(instr);
}
@@ -288,6 +288,11 @@ final class ConstantPropagation extends Pass
_setNonConstant(instr);
}
@override
void visitClosureCall(ClosureCall instr) {
_setNonConstant(instr);
}
@override
void visitDynamicCall(DynamicCall instr) {
_setNonConstant(instr);
@@ -325,6 +330,22 @@ final class ConstantPropagation extends Pass
@override
void visitThrow(Throw instr) {}
@override
void visitNullCheck(NullCheck instr) {
if (_isNonConstant(instr.operand)) {
_setNonConstant(instr);
return;
}
ConstantValue? operand = _getConstantValue(instr.operand);
if (operand != null) {
if (!operand.isNull) {
_setResult(instr, operand);
} else {
_setNonConstant(instr);
}
}
}
@override
void visitTypeParameters(TypeParameters instr) {
_setNonConstant(instr);
@@ -366,11 +387,55 @@ final class ConstantPropagation extends Pass
_setNonConstant(instr);
}
@override
void visitTypeLiteral(TypeLiteral instr) {
_setNonConstant(instr);
}
@override
void visitAllocateObject(AllocateObject instr) {
_setNonConstant(instr);
}
@override
void visitAllocateClosure(AllocateClosure instr) {
_setNonConstant(instr);
}
@override
void visitAllocateListLiteral(AllocateListLiteral instr) {
_setNonConstant(instr);
}
@override
void visitAllocateMapLiteral(AllocateMapLiteral instr) {
_setNonConstant(instr);
}
@override
void visitStringInterpolation(StringInterpolation instr) {
for (int i = 0, n = instr.inputCount; i < n; ++i) {
if (_isNonConstant(instr.inputDefAt(i))) {
_setNonConstant(instr);
return;
}
}
final operands = <ConstantValue>[];
for (int i = 0, n = instr.inputCount; i < n; ++i) {
final operand = _getConstantValue(instr.inputDefAt(i));
if (operand == null) {
return;
}
operands.add(operand);
}
ConstantValue? result = constantFolding.stringInterpolation(operands);
if (result != null) {
_setResult(instr, result);
} else {
_setNonConstant(instr);
}
}
@override
void visitComparison(Comparison instr) {
switch (instr.op) {
@@ -464,6 +529,27 @@ final class ConstantPropagation extends Pass
}
}
@override
void visitUnaryBoolOp(UnaryBoolOp instr) {
if (_isNonConstant(instr.operand)) {
_setNonConstant(instr);
return;
}
ConstantValue? operand = _getConstantValue(instr.operand);
if (operand != null) {
ConstantValue? result = constantFolding.unaryBoolOp(instr.op, operand);
_setResult(instr, result);
}
}
@override
void visitAllocateList(AllocateList instr) {
_setNonConstant(instr);
}
@override
void visitSetListElement(SetListElement instr) {}
@override
void visitParallelMove(ParallelMove instr) {}
+146
View File
@@ -4,6 +4,7 @@
import 'package:cfg/ir/constant_value.dart';
import 'package:cfg/ir/instructions.dart';
import 'package:cfg/ir/types.dart';
import 'package:cfg/ir/visitor.dart';
import 'package:cfg/passes/pass.dart';
import 'package:cfg/utils/misc.dart';
@@ -147,6 +148,25 @@ final class Simplification extends Pass
@override
Instruction visitInterfaceCall(InterfaceCall instr) => instr;
@override
Instruction visitClosureCall(ClosureCall instr) {
final closure = instr.closure;
if (closure is AllocateClosure) {
final replacement = DirectCall(
graph,
instr.sourcePosition,
closure.function,
instr.type,
inputCount: instr.inputCount,
);
for (int i = 0, n = instr.inputCount; i < n; ++i) {
replacement.setInputAt(i, instr.inputDefAt(i));
}
return replacement;
}
return instr;
}
@override
Instruction visitDynamicCall(DynamicCall instr) => instr;
@@ -174,6 +194,15 @@ final class Simplification extends Pass
@override
Instruction visitThrow(Throw instr) => instr;
@override
Instruction visitNullCheck(NullCheck instr) {
final operand = instr.operand;
if (!operand.type.isNullable) {
return operand;
}
return instr;
}
@override
Instruction visitTypeParameters(TypeParameters instr) => instr;
@@ -198,9 +227,57 @@ final class Simplification extends Pass
@override
Instruction visitTypeArguments(TypeArguments instr) => instr;
@override
Instruction visitTypeLiteral(TypeLiteral instr) => instr;
@override
Instruction visitAllocateObject(AllocateObject instr) => instr;
@override
Instruction visitAllocateClosure(AllocateClosure instr) => instr;
@override
Instruction visitAllocateListLiteral(AllocateListLiteral instr) => instr;
@override
Instruction visitAllocateMapLiteral(AllocateMapLiteral instr) => instr;
@override
Instruction visitStringInterpolation(StringInterpolation instr) {
final buf = _StringInterpolationBuffer(constantFolding);
buf.addStringInterpolation(instr);
if (buf.inputs.length == 1) {
final input = buf.inputs.single;
if (input is String) {
return graph.getConstant(ConstantValue.fromString(input));
} else if ((input as Definition).type is StringType) {
return input;
}
}
if (!buf.optimized) {
return instr;
}
final replacement = StringInterpolation(
graph,
instr.sourcePosition,
inputCount: buf.inputs.length,
);
for (int i = 0, n = buf.inputs.length; i < n; ++i) {
final input = buf.inputs[i];
final inputDef = input is String
? graph.getConstant(ConstantValue.fromString(input))
: input as Definition;
replacement.setInputAt(i, inputDef);
}
return replacement;
}
@override
Instruction visitAllocateList(AllocateList instr) => instr;
@override
Instruction visitSetListElement(SetListElement instr) => instr;
@override
Instruction visitParallelMove(ParallelMove instr) => instr;
@@ -499,4 +576,73 @@ final class Simplification extends Pass
}
return instr;
}
@override
Instruction visitUnaryBoolOp(UnaryBoolOp instr) {
final operand = instr.operand;
// Constant folding.
if (operand is Constant) {
ConstantValue? result = constantFolding.unaryBoolOp(
instr.op,
operand.value,
);
if (result != null) {
return graph.getConstant(result);
}
}
return instr;
}
}
/// Collects strings participating in the string interpolation.
class _StringInterpolationBuffer {
final ConstantFolding constantFolding;
// Contains either String or Definition.
final List<Object> inputs = [];
bool optimized = false;
_StringInterpolationBuffer(this.constantFolding);
void addString(String str) {
// Skip empty strings.
if (str.isEmpty) {
optimized = true;
return;
}
// Append string to the last string, if any.
if (inputs.isNotEmpty) {
final last = inputs.last;
if (last is String) {
inputs.last = last + str;
optimized = true;
return;
}
}
inputs.add(str);
}
void addStringInterpolation(StringInterpolation instr) {
for (int i = 0, n = instr.inputCount; i < n; ++i) {
final input = instr.inputDefAt(i);
switch (input) {
case Constant():
final str = constantFolding.computeToString(input.value);
if (str != null) {
addString(str);
} else {
inputs.add(input);
}
break;
case StringInterpolation() when input.singleUser == instr:
addStringInterpolation(input);
input.removeFromGraph();
optimized = true;
break;
default:
inputs.add(input);
}
}
}
}
+65
View File
@@ -508,5 +508,70 @@ void main() {
(double v) => v.truncateToDouble(),
);
});
test('unary bool op', () {
final values = <bool>[true, false];
void testOp(
UnaryBoolOpcode opcode,
bool Function(bool) expectedBehavior,
) {
for (final v in values) {
final expected = expectedBehavior(v);
expect(
constantFolding.unaryBoolOp(opcode, ConstantValue.fromBool(v)),
equals(ConstantValue.fromBool(expected)),
);
}
}
testOp(UnaryBoolOpcode.not, (bool v) => !v);
});
test('computeToString', () {
final testCases = <ConstantValue, String?>{
ConstantValue.fromString('abc'): 'abc',
ConstantValue.fromInt(-10): '-10',
ConstantValue.fromBool(false): 'false',
ConstantValue.fromDouble(3.14): '3.14',
ConstantValue.fromNull(): 'null',
ConstantValue(ast.ListConstant(const ast.DynamicType(), [])): null,
};
for (final e in testCases.entries) {
expect(constantFolding.computeToString(e.key), equals(e.value));
}
});
test('stringInterpolation', () {
expect(
constantFolding.stringInterpolation([
ConstantValue.fromString('x = '),
ConstantValue.fromInt(42),
ConstantValue.fromString(', y = '),
ConstantValue.fromDouble(double.nan),
ConstantValue.fromString(', z = '),
ConstantValue.fromBool(true),
]),
equals(ConstantValue.fromString('x = 42, y = NaN, z = true')),
);
expect(
constantFolding.stringInterpolation([
ConstantValue.fromString('x = '),
ConstantValue.fromInt(42),
ConstantValue.fromString(', y = '),
ConstantValue(
ast.MapConstant(
const ast.DynamicType(),
const ast.DynamicType(),
[],
),
),
ConstantValue.fromString(', z = '),
ConstantValue.fromBool(true),
]),
isNull,
);
});
});
}
+31
View File
@@ -33,6 +33,8 @@ void main() {
expect(IntType().kind, equals(TypeKind.intType));
expect(IntType().dartType, equals(intDartType));
expect(IntType().isNullable, isFalse);
expect(IntType().toNonNullableType, equals(IntType()));
expect(IntType().hashCode, equals(IntType(intDartType).hashCode));
expect(IntType(intDartType).kind, equals(TypeKind.intType));
@@ -70,6 +72,8 @@ void main() {
expect(DoubleType().kind, equals(TypeKind.doubleType));
expect(DoubleType().dartType, equals(doubleDartType));
expect(DoubleType().hashCode, equals(DoubleType(doubleDartType).hashCode));
expect(DoubleType().isNullable, isFalse);
expect(DoubleType().toNonNullableType, equals(DoubleType()));
expect(DoubleType(doubleDartType).kind, equals(TypeKind.doubleType));
expect(DoubleType(doubleDartType).dartType, equals(doubleDartType));
@@ -96,6 +100,8 @@ void main() {
expect(BoolType().kind, equals(TypeKind.boolType));
expect(BoolType().dartType, equals(boolDartType));
expect(BoolType().hashCode, equals(BoolType(boolDartType).hashCode));
expect(BoolType().isNullable, isFalse);
expect(BoolType().toNonNullableType, equals(BoolType()));
expect(BoolType(boolDartType).kind, equals(TypeKind.boolType));
expect(BoolType(boolDartType).dartType, equals(boolDartType));
@@ -122,6 +128,8 @@ void main() {
expect(StringType().kind, equals(TypeKind.stringType));
expect(StringType().dartType, equals(stringDartType));
expect(StringType().hashCode, equals(StringType(stringDartType).hashCode));
expect(StringType().isNullable, isFalse);
expect(StringType().toNonNullableType, equals(StringType()));
expect(StringType(stringDartType).kind, equals(TypeKind.stringType));
expect(StringType(stringDartType).dartType, equals(stringDartType));
@@ -155,6 +163,8 @@ void main() {
expect(ObjectType().kind, equals(TypeKind.objectType));
expect(ObjectType().dartType, equals(objectDartType));
expect(ObjectType().hashCode, equals(ObjectType(objectDartType).hashCode));
expect(ObjectType().isNullable, isFalse);
expect(ObjectType().toNonNullableType, equals(ObjectType()));
expect(ObjectType(objectDartType).kind, equals(TypeKind.objectType));
expect(ObjectType(objectDartType).dartType, equals(objectDartType));
@@ -180,6 +190,8 @@ void main() {
expect(NullType().kind, equals(TypeKind.nullType));
expect(NullType().dartType, equals(nullDartType));
expect(NullType().isNullable, isTrue);
expect(NullType().toNonNullableType, equals(NeverType()));
expect(NullType().isSubtypeOf(IntType()), isFalse);
expect(NullType().isSubtypeOf(DoubleType()), isFalse);
@@ -205,6 +217,8 @@ void main() {
expect(NeverType().kind, equals(TypeKind.neverType));
expect(NeverType().dartType, equals(neverDartType));
expect(NeverType().isNullable, isFalse);
expect(NeverType().toNonNullableType, equals(NeverType()));
expect(NeverType().isSubtypeOf(IntType()), isTrue);
expect(NeverType().isSubtypeOf(DoubleType()), isTrue);
@@ -230,12 +244,21 @@ void main() {
expect(TopType().kind, equals(TypeKind.top));
expect(TopType().dartType, equals(dynamicDartType));
expect(TopType().hashCode, equals(TopType(dynamicDartType).hashCode));
expect(TopType().isNullable, isTrue);
expect(TopType().toNonNullableType, equals(ObjectType()));
expect(TopType(voidDartType).kind, equals(TypeKind.top));
expect(TopType(voidDartType).dartType, equals(voidDartType));
expect(TopType(voidDartType).isNullable, isTrue);
expect(TopType(voidDartType).toNonNullableType, equals(ObjectType()));
expect(TopType(nullableObjDartType).kind, equals(TypeKind.top));
expect(TopType(nullableObjDartType).dartType, equals(nullableObjDartType));
expect(TopType(nullableObjDartType).isNullable, isTrue);
expect(
TopType(nullableObjDartType).toNonNullableType,
equals(ObjectType()),
);
expect(TopType().isSubtypeOf(IntType()), isFalse);
expect(TopType().isSubtypeOf(DoubleType()), isFalse);
@@ -258,6 +281,8 @@ void main() {
expect(listType.kind, equals(TypeKind.otherDartType));
expect(listType.dartType, equals(listDartType));
expect(listType.isNullable, isFalse);
expect(listType.toNonNullableType, equals(listType));
expect(listType.isSubtypeOf(IntType()), isFalse);
expect(listType.isSubtypeOf(DoubleType()), isFalse);
@@ -279,6 +304,12 @@ void main() {
listType.isSubtypeOf(StaticType(coreTypes.iterableNonNullableRawType)),
isTrue,
);
expect(StaticType(coreTypes.listNullableRawType).isNullable, isTrue);
expect(
StaticType(coreTypes.listNullableRawType).toNonNullableType,
equals(listType),
);
});
test('nothing', () {
+2 -2
View File
@@ -116,12 +116,12 @@ class CompileAndDumpIr extends RecursiveVisitor {
if (node.isAbstract) {
return;
}
if (node.hasGetter) {
if (node.hasGetter && !node.isStatic) {
compileAndDumpFunction(
functionRegistry.getFunction(node, isGetter: true),
);
}
if (node.hasSetter) {
if (node.hasSetter && !node.isStatic) {
compileAndDumpFunction(
functionRegistry.getFunction(node, isSetter: true),
);
+13
View File
@@ -52,6 +52,7 @@ void instanceCalls(A obj, A? obj2, int a, String b, double c) {
obj.foo3 = c + v;
}
}
print(obj.foo1);
}
int sField = 42;
@@ -69,6 +70,18 @@ void dynamicCalls(dynamic x, dynamic y, dynamic z) {
z.baz = v + 1;
}
void closureCalls(Function func1, int Function<T>(T, String) func2) {
func1(1, 'a');
func2<int>(2, 'b');
void func3(int x) => print(x);
func3(42);
() {
print('hey');
}();
}
void objectAllocation(int a) {
final obj = B(a);
obj.y += obj.x;
+25 -14
View File
@@ -145,6 +145,8 @@ B29 = TargetBlock() idom:B20
B31 = JoinBlock(B29, B28) idom:B20
Goto(B22)
B22 = JoinBlock(B31, B19) idom:B0
v42 = InterfaceCall tear-off A.foo1(v1)
DirectCall print(v42)
Return(v17)
--- staticCalls
@@ -164,9 +166,9 @@ B6 = TargetBlock() idom:B0
B7 = TargetBlock() idom:B0
Goto(B9)
B9 = JoinBlock(B7, B6) idom:B0
v17 = DirectCall getter sField()
v17 = LoadStaticField(sField)
v18 = BinaryIntOp +(v17, v12)
DirectCall setter sField(v18)
StoreStaticField(sField, v18)
Return(v20)
--- dynamicCalls
@@ -182,6 +184,27 @@ B0 = EntryBlock()
DynamicCall set baz(v3, v14)
Return(v16)
--- closureCalls
B0 = EntryBlock()
v4 = Constant(1)
v5 = Constant("a")
v9 = Constant(2)
v10 = Constant("b")
v15 = Constant(42)
v19 = Constant(null)
v1 = Parameter(func1)
v2 = Parameter(func2)
DynamicCall call(v1, v4, v5)
v7 = TypeArguments(<int>)
ClosureCall(v7, v2, v9, v10)
v12 = AllocateClosure()
DirectCall closure FunctionDeclarationImpl(void func3(int x) => print(x);) at closureCalls(v12, v15)
v17 = AllocateClosure()
DirectCall closure FunctionExpression(Null () {
print("hey");
}) at closureCalls(v17)
Return(v19)
--- objectAllocation
B0 = EntryBlock()
v13 = Constant(null)
@@ -199,18 +222,6 @@ B0 = EntryBlock()
v1 = Constant(null)
Return(v1)
--- getter sField
B0 = EntryBlock()
v1 = LoadStaticField(sField)
Return(v1)
--- setter sField
B0 = EntryBlock()
v4 = Constant(null)
v1 = Parameter(#value)
StoreStaticField(sField, v1)
Return(v4)
--- field-init sField
B0 = EntryBlock()
v1 = Constant(42)
@@ -60,4 +60,27 @@ void test4(int x, int z) {
print(y);
}
void nullCheck(int? x) {
if (1 != 2) {
x = 10;
}
print(x!);
}
void stringInterpolation(String s, int x) {
if (true) {
s = 'abc';
x = 10;
}
final str1 = 's = $s, x = $x';
print('result: $str1');
}
void boolNot(bool x) {
if (true) {
x = true;
}
print(!x);
}
void main() {}
@@ -47,6 +47,42 @@ B0 = EntryBlock()
DirectCall print(v2)
Return(v49)
--- nullCheck
B0 = EntryBlock()
Constant(1)
Constant(2)
v10 = Constant(10)
v16 = Constant(null)
Constant(false)
Parameter(x)
DirectCall print(v10)
Return(v16)
--- stringInterpolation
B0 = EntryBlock()
Constant(true)
Constant("abc")
Constant(10)
Constant("s = ")
Constant(", x = ")
Constant("result: ")
v24 = Constant(null)
Constant("result: s = ")
v30 = Constant("result: s = abc, x = 10")
Parameter(s)
Parameter(x)
DirectCall print(v30)
Return(v24)
--- boolNot
B0 = EntryBlock()
Constant(true)
v13 = Constant(null)
v16 = Constant(false)
Parameter(x)
DirectCall print(v16)
Return(v13)
--- main
B0 = EntryBlock()
v1 = Constant(null)
+36
View File
@@ -0,0 +1,36 @@
// Copyright (c) 2026, 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.
void listLiterals<T>(T x) {
print([]);
print(<T>[]);
print([1, 2, 3]);
print([1, 2, 3, 4, 5, 6, 7, 8, 9]);
print(<T>[x, x, x, x, x, x, x, x, x]);
}
void mapLiterals<S, T>(S key, T Function() value, S key2, T value2) {
print({});
print(<S, T>{});
print({'a': 'aa', 'b': 'bb'});
print({key: value(), key2: value2});
}
void nullChecks(Object? x) {
print(x!);
Object? y;
if (1 != 2) {
y = 42;
}
print(y!);
}
void logical(bool x, bool Function() y, bool z) {
print(!x);
print(x || y());
print(y() && x);
print(!(x && (y() || z)));
}
void main() {}
+118
View File
@@ -0,0 +1,118 @@
--- listLiterals
B0 = EntryBlock()
v4 = Constant(0)
v11 = Constant(1)
v12 = Constant(2)
v13 = Constant(3)
v17 = Constant(4)
v18 = Constant(5)
v19 = Constant(6)
v20 = Constant(7)
v21 = Constant(8)
v22 = Constant(9)
v37 = Constant(null)
v1 = Parameter(x)
v2 = TypeParameters()
v3 = TypeArguments(<dynamic>)
v5 = DirectCall _GrowableList.(v3, v4)
DirectCall print(v5)
v7 = TypeArguments(v2, <listLiterals.T%>)
v8 = DirectCall _GrowableList.(v7, v4)
DirectCall print(v8)
v10 = TypeArguments(<int>)
v14 = DirectCall _GrowableList._literal3(v10, v11, v12, v13)
DirectCall print(v14)
v23 = AllocateListLiteral(v10, v11, v12, v13, v17, v18, v19, v20, v21, v22)
DirectCall print(v23)
v35 = AllocateListLiteral(v7, v1, v1, v1, v1, v1, v1, v1, v1, v1)
DirectCall print(v35)
Return(v37)
--- mapLiterals
B0 = EntryBlock()
v13 = Constant("a")
v14 = Constant("aa")
v15 = Constant("b")
v16 = Constant("bb")
v27 = Constant(null)
v1 = Parameter(key)
v2 = Parameter(value)
v3 = Parameter(key2)
v4 = Parameter(value2)
v5 = TypeParameters()
v6 = TypeArguments(<dynamic, dynamic>)
v7 = AllocateMapLiteral(v6)
DirectCall print(v7)
v9 = TypeArguments(v5, <mapLiterals.S%, mapLiterals.T%>)
v10 = AllocateMapLiteral(v9)
DirectCall print(v10)
v12 = TypeArguments(<String, String>)
v17 = AllocateMapLiteral(v12, v13, v14, v15, v16)
DirectCall print(v17)
v22 = ClosureCall(v2)
v25 = AllocateMapLiteral(v9, v1, v22, v3, v4)
DirectCall print(v25)
Return(v27)
--- nullChecks
B0 = EntryBlock()
v5 = Constant(null)
Constant(1)
Constant(2)
v15 = Constant(42)
Constant(false)
v1 = Parameter(x)
v3 = NullCheck(v1)
DirectCall print(v3)
DirectCall print(v15)
Return(v5)
--- logical
B0 = EntryBlock() dominates:(B9, B13, B8)
v11 = Constant(true)
v26 = Constant(false)
v55 = Constant(null)
v1 = Parameter(x)
v2 = Parameter(y)
v3 = Parameter(z)
v5 = UnaryBoolOp !(v1)
DirectCall print(v5)
Branch(v1, true: B8, false: B9)
B8 = TargetBlock() idom:B0
Goto(B13)
B9 = TargetBlock() idom:B0
v16 = ClosureCall(v2)
Goto(B13)
B13 = JoinBlock(B9, B8) idom:B0 dominates:(B24, B28, B23)
v57 = Phi(v16, v11)
DirectCall print(v57)
v22 = ClosureCall(v2)
Branch(v22, true: B23, false: B24)
B23 = TargetBlock() idom:B13
Goto(B28)
B24 = TargetBlock() idom:B13
Goto(B28)
B28 = JoinBlock(B24, B23) idom:B13 dominates:(B37, B40, B36)
v58 = Phi(v26, v1)
DirectCall print(v58)
Branch(v1, true: B36, false: B37)
B36 = TargetBlock() idom:B28 dominates:(B45, B44)
v43 = ClosureCall(v2)
Branch(v43, true: B44, false: B45)
B44 = TargetBlock() idom:B36
Goto(B40)
B45 = TargetBlock() idom:B36
v50 = UnaryBoolOp !(v3)
Goto(B40)
B37 = TargetBlock() idom:B28
Goto(B40)
B40 = JoinBlock(B37, B45, B44) idom:B28
v59 = Phi(v11, v50, v26)
DirectCall print(v59)
Return(v55)
--- main
B0 = EntryBlock()
v1 = Constant(null)
Return(v1)
+19
View File
@@ -107,4 +107,23 @@ void redundantPhi(int x) {
print(x);
}
void stringInterpolation(int x) {
final empty = '';
final s = 'string';
final i = 42;
final d = 3.14;
final b = true;
final n = null;
print('$empty');
print('Hey, $s! i=$i, d=$d, b=$b, n=$n');
print('enclosing start... ${'some nested $s, x=$x'} ...end');
}
void closureCall() {
final x = (int arg) {
print(arg);
};
x(42);
}
void main() {}
@@ -192,6 +192,43 @@ B0 = EntryBlock()
DirectCall print(v1)
Return(v16)
--- stringInterpolation
B0 = EntryBlock()
v2 = Constant("")
Constant("string")
Constant(42)
Constant(3.14)
Constant(true)
v12 = Constant(null)
Constant("Hey, ")
Constant("! i=")
Constant(", d=")
Constant(", b=")
Constant(", n=")
Constant("enclosing start... ")
Constant("some nested ")
Constant(", x=")
v35 = Constant(" ...end")
v40 = Constant("Hey, string! i=42, d=3.14, b=true, n=null")
Constant("some nested string, x=")
v44 = Constant("enclosing start... some nested string, x=")
v1 = Parameter(x)
DirectCall print(v2)
DirectCall print(v40)
v43 = StringInterpolation(v44, v1, v35)
DirectCall print(v43)
Return(v12)
--- closureCall
B0 = EntryBlock()
v4 = Constant(42)
v6 = Constant(null)
v1 = AllocateClosure()
DirectCall closure FunctionExpression(Null (int arg) {
print(arg);
}) at closureCall(v1, v4)
Return(v6)
--- main
B0 = EntryBlock()
v1 = Constant(null)
+6
View File
@@ -14,6 +14,12 @@ class A<T> {
}
}
void baz<S>() {
print(List);
print(T);
print(Map<S, T>);
}
factory A() => A<T>._();
A._();
}
+13
View File
@@ -36,6 +36,19 @@ B8 = TargetBlock() idom:B0
B10 = JoinBlock(B8, B7) idom:B0
Return(v16)
--- A.baz
B0 = EntryBlock()
v4 = Constant(TypeLiteralConstant(List<dynamic>))
v10 = Constant(null)
v1 = Parameter(this)
v3 = TypeParameters(v1)
DirectCall print(v4)
v6 = TypeLiteral(A.T%, v3)
DirectCall print(v6)
v8 = TypeLiteral(Map<A.baz.S%, A.T%>, v3)
DirectCall print(v8)
Return(v10)
--- A.
B0 = EntryBlock()
v1 = TypeParameters()
+28
View File
@@ -91,4 +91,32 @@ void unreachableBothTryEndAndCatchEnd() {
List<num> unreachableFieldInitializer = [10, 1 + (throw 'Bye') + 2, 20];
String unreachableStringInterpolation(int x, int y) =>
'x = $x, boom = ${throw 'Bye'}, y = $y';
List<int> unreachableListLiteral() => [
1,
2,
3,
4,
5,
6,
7,
8,
9,
(throw 'Bye'),
10,
11,
];
Map<int, String> unreachableMapLiteral() => {
10: 'aa',
20: 'bb',
30: (throw 'Bye'),
40: 'dd',
};
bool unreachableLogicExpr(bool c1, bool c2, bool c3) =>
!(c1 && (c2 || (throw 'Bye') || c3));
void main() {}
+60 -12
View File
@@ -146,23 +146,71 @@ B2 = CatchBlock() idom:B0
DirectCall print(v13)
Throw(v15)
--- unreachableStringInterpolation
B0 = EntryBlock()
Constant("x = ")
Constant(", boom = ")
v6 = Constant("Bye")
Constant(null)
Parameter(x)
Parameter(y)
Throw(v6)
--- unreachableListLiteral
B0 = EntryBlock()
Constant(1)
Constant(2)
Constant(3)
Constant(4)
Constant(5)
Constant(6)
Constant(7)
Constant(8)
Constant(9)
v11 = Constant("Bye")
Constant(null)
TypeArguments(<int>)
Throw(v11)
--- unreachableMapLiteral
B0 = EntryBlock()
Constant(10)
Constant("aa")
Constant(20)
Constant("bb")
Constant(30)
v7 = Constant("Bye")
Constant(null)
TypeArguments(<int, String>)
Throw(v7)
--- unreachableLogicExpr
B0 = EntryBlock() dominates:(B6, B10, B5)
v8 = Constant(true)
v16 = Constant("Bye")
Constant(null)
v19 = Constant(false)
v1 = Parameter(c1)
v2 = Parameter(c2)
Parameter(c3)
Branch(v1, true: B5, false: B6)
B5 = TargetBlock() idom:B0 dominates:(B14, B13)
Branch(v2, true: B13, false: B14)
B13 = TargetBlock() idom:B5
Goto(B10)
B14 = TargetBlock() idom:B5
Throw(v16)
B6 = TargetBlock() idom:B0
Goto(B10)
B10 = JoinBlock(B6, B13) idom:B0
v24 = Phi(v8, v19)
Return(v24)
--- main
B0 = EntryBlock()
v1 = Constant(null)
Return(v1)
--- getter unreachableFieldInitializer
B0 = EntryBlock()
v1 = LoadStaticField(unreachableFieldInitializer)
Return(v1)
--- setter unreachableFieldInitializer
B0 = EntryBlock()
v4 = Constant(null)
v1 = Parameter(#value)
StoreStaticField(unreachableFieldInitializer, v1)
Return(v4)
--- field-init unreachableFieldInitializer
B0 = EntryBlock()
Constant(10)