[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:
committed by
Commit Queue
parent
909786e1b9
commit
e314cccd3b
@@ -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].
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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', () {
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -14,6 +14,12 @@ class A<T> {
|
||||
}
|
||||
}
|
||||
|
||||
void baz<S>() {
|
||||
print(List);
|
||||
print(T);
|
||||
print(Map<S, T>);
|
||||
}
|
||||
|
||||
factory A() => A<T>._();
|
||||
A._();
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user