diff --git a/pkg/cfg/lib/front_end/ast_to_ir.dart b/pkg/cfg/lib/front_end/ast_to_ir.dart index eb132635106..838381a7682 100644 --- a/pkg/cfg/lib/front_end/ast_to_ir.dart +++ b/pkg/cfg/lib/front_end/ast_to_ir.dart @@ -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 = [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 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]. diff --git a/pkg/cfg/lib/ir/constant_value.dart b/pkg/cfg/lib/ir/constant_value.dart index 2825223de44..d09ff888acd 100644 --- a/pkg/cfg/lib/ir/constant_value.dart +++ b/pkg/cfg/lib/ir/constant_value.dart @@ -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 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. diff --git a/pkg/cfg/lib/ir/flow_graph_builder.dart b/pkg/cfg/lib/ir/flow_graph_builder.dart index 51f15690158..371f27b8f4b 100644 --- a/pkg/cfg/lib/ir/flow_graph_builder.dart +++ b/pkg/cfg/lib/ir/flow_graph_builder.dart @@ -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; + } } diff --git a/pkg/cfg/lib/ir/flow_graph_checker.dart b/pkg/cfg/lib/ir/flow_graph_checker.dart index 505fe971b83..001bd526b1b 100644 --- a/pkg/cfg/lib/ir/flow_graph_checker.dart +++ b/pkg/cfg/lib/ir/flow_graph_checker.dart @@ -240,6 +240,9 @@ final class FlowGraphChecker extends Pass implements InstructionVisitor { @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 { 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 { 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 { 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 { 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) {} } diff --git a/pkg/cfg/lib/ir/instructions.dart b/pkg/cfg/lib/ir/instructions.dart index c3cdd22e0c9..874b7f74230 100644 --- a/pkg/cfg/lib/ir/instructions.dart +++ b/pkg/cfg/lib/ir/instructions.dart @@ -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(InstructionVisitor 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(InstructionVisitor 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(InstructionVisitor 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(InstructionVisitor 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 types; TypeArguments( @@ -1165,6 +1210,32 @@ final class TypeArguments extends Definition with NoThrow, Pure, Idempotent { R accept(InstructionVisitor 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(InstructionVisitor 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(InstructionVisitor 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(InstructionVisitor 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(InstructionVisitor 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(InstructionVisitor 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(InstructionVisitor v) => v.visitStringInterpolation(this); +} + enum BinaryIntOpcode { add('+'), sub('-'), @@ -1387,6 +1546,34 @@ final class UnaryDoubleOp extends Definition with NoThrow, Pure, Idempotent { R accept(InstructionVisitor 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(InstructionVisitor 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(InstructionVisitor 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(InstructionVisitor 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(InstructionVisitor v) => v.visitSetListElement(this); +} + /// Base class for move operations, part of [ParallelMove]. abstract base class MoveOp {} diff --git a/pkg/cfg/lib/ir/ir_to_text.dart b/pkg/cfg/lib/ir/ir_to_text.dart index af3e00ef5ac..761ed812591 100644 --- a/pkg/cfg/lib/ir/ir_to_text.dart +++ b/pkg/cfg/lib/ir/ir_to_text.dart @@ -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(), }; } diff --git a/pkg/cfg/lib/ir/types.dart b/pkg/cfg/lib/ir/types.dart index c4c9e881973..44c33ee6c88 100644 --- a/pkg/cfg/lib/ir/types.dart +++ b/pkg/cfg/lib/ir/types.dart @@ -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() => ''; } @@ -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; diff --git a/pkg/cfg/lib/ir/visitor.dart b/pkg/cfg/lib/ir/visitor.dart index 7798ec60547..c61284d340e 100644 --- a/pkg/cfg/lib/ir/visitor.dart +++ b/pkg/cfg/lib/ir/visitor.dart @@ -21,6 +21,7 @@ abstract interface class InstructionVisitor { 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 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 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 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); } diff --git a/pkg/cfg/lib/passes/constant_propagation.dart b/pkg/cfg/lib/passes/constant_propagation.dart index d6a15df73fb..47dccfdbfa5 100644 --- a/pkg/cfg/lib/passes/constant_propagation.dart +++ b/pkg/cfg/lib/passes/constant_propagation.dart @@ -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 = []; + 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) {} diff --git a/pkg/cfg/lib/passes/simplification.dart b/pkg/cfg/lib/passes/simplification.dart index 42e2266dff7..3541d033f52 100644 --- a/pkg/cfg/lib/passes/simplification.dart +++ b/pkg/cfg/lib/passes/simplification.dart @@ -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 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); + } + } + } } diff --git a/pkg/cfg/test/ir/constant_value_test.dart b/pkg/cfg/test/ir/constant_value_test.dart index 41f238e5a01..59cd64dc73f 100644 --- a/pkg/cfg/test/ir/constant_value_test.dart +++ b/pkg/cfg/test/ir/constant_value_test.dart @@ -508,5 +508,70 @@ void main() { (double v) => v.truncateToDouble(), ); }); + + test('unary bool op', () { + final values = [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.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, + ); + }); }); } diff --git a/pkg/cfg/test/ir/types_test.dart b/pkg/cfg/test/ir/types_test.dart index 729e2056a8d..6201da2d3dc 100644 --- a/pkg/cfg/test/ir/types_test.dart +++ b/pkg/cfg/test/ir/types_test.dart @@ -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', () { diff --git a/pkg/cfg/test/ir_test.dart b/pkg/cfg/test/ir_test.dart index 2ceff8982ef..998935c34b3 100644 --- a/pkg/cfg/test/ir_test.dart +++ b/pkg/cfg/test/ir_test.dart @@ -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), ); diff --git a/pkg/cfg/testcases/calls.dart b/pkg/cfg/testcases/calls.dart index 6e09f6c504f..d2e8e665e05 100644 --- a/pkg/cfg/testcases/calls.dart +++ b/pkg/cfg/testcases/calls.dart @@ -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, String) func2) { + func1(1, 'a'); + func2(2, 'b'); + + void func3(int x) => print(x); + func3(42); + + () { + print('hey'); + }(); +} + void objectAllocation(int a) { final obj = B(a); obj.y += obj.x; diff --git a/pkg/cfg/testcases/calls.dart.expect b/pkg/cfg/testcases/calls.dart.expect index 705bb5ada29..553e431e855 100644 --- a/pkg/cfg/testcases/calls.dart.expect +++ b/pkg/cfg/testcases/calls.dart.expect @@ -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() + 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) diff --git a/pkg/cfg/testcases/constant_propagation.dart b/pkg/cfg/testcases/constant_propagation.dart index 9cafe88e3e1..72f1ad002a5 100644 --- a/pkg/cfg/testcases/constant_propagation.dart +++ b/pkg/cfg/testcases/constant_propagation.dart @@ -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() {} diff --git a/pkg/cfg/testcases/constant_propagation.dart.expect b/pkg/cfg/testcases/constant_propagation.dart.expect index 037a251ac3b..6474bdd3071 100644 --- a/pkg/cfg/testcases/constant_propagation.dart.expect +++ b/pkg/cfg/testcases/constant_propagation.dart.expect @@ -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) diff --git a/pkg/cfg/testcases/expressions.dart b/pkg/cfg/testcases/expressions.dart new file mode 100644 index 00000000000..9b5ca7deb83 --- /dev/null +++ b/pkg/cfg/testcases/expressions.dart @@ -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 x) { + print([]); + print([]); + print([1, 2, 3]); + print([1, 2, 3, 4, 5, 6, 7, 8, 9]); + print([x, x, x, x, x, x, x, x, x]); +} + +void mapLiterals(S key, T Function() value, S key2, T value2) { + print({}); + print({}); + 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() {} diff --git a/pkg/cfg/testcases/expressions.dart.expect b/pkg/cfg/testcases/expressions.dart.expect new file mode 100644 index 00000000000..9b7cb48a46b --- /dev/null +++ b/pkg/cfg/testcases/expressions.dart.expect @@ -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() + v5 = DirectCall _GrowableList.(v3, v4) + DirectCall print(v5) + v7 = TypeArguments(v2, ) + v8 = DirectCall _GrowableList.(v7, v4) + DirectCall print(v8) + v10 = TypeArguments() + 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() + v7 = AllocateMapLiteral(v6) + DirectCall print(v7) + v9 = TypeArguments(v5, ) + v10 = AllocateMapLiteral(v9) + DirectCall print(v10) + v12 = TypeArguments() + 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) + diff --git a/pkg/cfg/testcases/simplification.dart b/pkg/cfg/testcases/simplification.dart index cab90d8fc04..d793d4f91c2 100644 --- a/pkg/cfg/testcases/simplification.dart +++ b/pkg/cfg/testcases/simplification.dart @@ -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() {} diff --git a/pkg/cfg/testcases/simplification.dart.expect b/pkg/cfg/testcases/simplification.dart.expect index 064bd0a4b90..d4a988f0f09 100644 --- a/pkg/cfg/testcases/simplification.dart.expect +++ b/pkg/cfg/testcases/simplification.dart.expect @@ -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) diff --git a/pkg/cfg/testcases/types.dart b/pkg/cfg/testcases/types.dart index 747987236e8..0c7401f92ee 100644 --- a/pkg/cfg/testcases/types.dart +++ b/pkg/cfg/testcases/types.dart @@ -14,6 +14,12 @@ class A { } } + void baz() { + print(List); + print(T); + print(Map); + } + factory A() => A._(); A._(); } diff --git a/pkg/cfg/testcases/types.dart.expect b/pkg/cfg/testcases/types.dart.expect index 19433021be1..e9fb74022be 100644 --- a/pkg/cfg/testcases/types.dart.expect +++ b/pkg/cfg/testcases/types.dart.expect @@ -36,6 +36,19 @@ B8 = TargetBlock() idom:B0 B10 = JoinBlock(B8, B7) idom:B0 Return(v16) +--- A.baz +B0 = EntryBlock() + v4 = Constant(TypeLiteralConstant(List)) + v10 = Constant(null) + v1 = Parameter(this) + v3 = TypeParameters(v1) + DirectCall print(v4) + v6 = TypeLiteral(A.T%, v3) + DirectCall print(v6) + v8 = TypeLiteral(Map, v3) + DirectCall print(v8) + Return(v10) + --- A. B0 = EntryBlock() v1 = TypeParameters() diff --git a/pkg/cfg/testcases/unreachable_ast.dart b/pkg/cfg/testcases/unreachable_ast.dart index 8e62a30b3c8..ad33ddea2b1 100644 --- a/pkg/cfg/testcases/unreachable_ast.dart +++ b/pkg/cfg/testcases/unreachable_ast.dart @@ -91,4 +91,32 @@ void unreachableBothTryEndAndCatchEnd() { List unreachableFieldInitializer = [10, 1 + (throw 'Bye') + 2, 20]; +String unreachableStringInterpolation(int x, int y) => + 'x = $x, boom = ${throw 'Bye'}, y = $y'; + +List unreachableListLiteral() => [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + (throw 'Bye'), + 10, + 11, +]; + +Map 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() {} diff --git a/pkg/cfg/testcases/unreachable_ast.dart.expect b/pkg/cfg/testcases/unreachable_ast.dart.expect index c5c094e0fb9..728da3c3a73 100644 --- a/pkg/cfg/testcases/unreachable_ast.dart.expect +++ b/pkg/cfg/testcases/unreachable_ast.dart.expect @@ -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() + Throw(v11) + +--- unreachableMapLiteral +B0 = EntryBlock() + Constant(10) + Constant("aa") + Constant(20) + Constant("bb") + Constant(30) + v7 = Constant("Bye") + Constant(null) + TypeArguments() + 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)