blob: a240e5e5955a5926d2be6978295392b828020770 [file]
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:cfg/ir/constant_value.dart';
import 'package:cfg/ir/flow_graph_builder.dart';
import 'package:cfg/ir/functions.dart';
import 'package:cfg/ir/global_context.dart';
import 'package:cfg/ir/instructions.dart';
import 'package:cfg/ir/ir_to_text.dart';
import 'package:cfg/ir/types.dart';
import 'package:kernel/ast.dart' as ast;
import 'package:kernel/library_index.dart' show LibraryIndex;
/// Build a fragment of IR corresponding to the recognized call or a function body.
/// Parameters are already pushed onto the [builder]'s stack.
typedef BuildIR = void Function(FlowGraphBuilder builder);
/// Base class for recognizing calls depending
/// on their argument types.
abstract class RecognizedCallMatcher {
/// Returns non-null [BuildIR] function if call is recognized.
BuildIR? match(List<CType> args);
}
/// Recognizes calls with arbitrary argument types.
class const AnyArgsMatcher(final BuildIR builder)
implements RecognizedCallMatcher {
@override
BuildIR? match(List<CType> args) => builder;
}
/// Recognizes calls to binary [num] operations (except [num./]).
class const BinaryNumOp(
final BinaryIntOpcode intOp,
final BinaryDoubleOpcode doubleOp,
) implements RecognizedCallMatcher {
/// Recognizes the following combinations of argument types:
///
/// int op int -> int
/// int op double -> double
/// double op int -> double
/// double op double -> double
@override
BuildIR? match(List<CType> args) {
switch (args) {
case [IntType(), IntType()]:
return (FlowGraphBuilder builder) {
builder.addBinaryIntOp(intOp);
};
case [IntType(), DoubleType()]:
return (FlowGraphBuilder builder) {
final right = builder.pop();
builder.addUnaryIntOp(UnaryIntOpcode.toDouble);
builder.push(right);
builder.addBinaryDoubleOp(doubleOp);
};
case [DoubleType(), DoubleType()]:
return (FlowGraphBuilder builder) {
builder.addBinaryDoubleOp(doubleOp);
};
case [DoubleType(), IntType()]:
return (FlowGraphBuilder builder) {
builder.addUnaryIntOp(UnaryIntOpcode.toDouble);
builder.addBinaryDoubleOp(doubleOp);
};
}
return null;
}
}
/// Recognizes calls to [num./].
class const NumDiv() implements RecognizedCallMatcher {
@override
BuildIR? match(List<CType> args) {
switch (args) {
case [IntType(), IntType()]:
return (FlowGraphBuilder builder) {
final right = builder.pop();
builder.addUnaryIntOp(UnaryIntOpcode.toDouble);
builder.push(right);
builder.addUnaryIntOp(UnaryIntOpcode.toDouble);
builder.addBinaryDoubleOp(BinaryDoubleOpcode.div);
};
case [IntType(), DoubleType()]:
return (FlowGraphBuilder builder) {
final right = builder.pop();
builder.addUnaryIntOp(UnaryIntOpcode.toDouble);
builder.push(right);
builder.addBinaryDoubleOp(BinaryDoubleOpcode.div);
};
case [DoubleType(), DoubleType()]:
return (FlowGraphBuilder builder) {
builder.addBinaryDoubleOp(BinaryDoubleOpcode.div);
};
case [DoubleType(), IntType()]:
return (FlowGraphBuilder builder) {
builder.addUnaryIntOp(UnaryIntOpcode.toDouble);
builder.addBinaryDoubleOp(BinaryDoubleOpcode.div);
};
}
return null;
}
}
/// Recognizes calls to [num.isNaN].
class const NumIsNaN() implements RecognizedCallMatcher {
@override
BuildIR? match(List<CType> args) {
switch (args) {
case [IntType()]:
return (FlowGraphBuilder builder) {
builder.pop();
builder.addBoolConstant(false);
};
case [DoubleType()]:
return (FlowGraphBuilder builder) {
final x = builder.pop();
builder.push(x);
builder.push(x);
builder.addComparison(.doubleNotEqual);
};
}
return null;
}
}
/// Recognizes calls to [num.toDouble].
class const NumToDouble() implements RecognizedCallMatcher {
@override
BuildIR? match(List<CType> args) {
switch (args) {
case [IntType()]:
return (FlowGraphBuilder builder) {
builder.addUnaryIntOp(UnaryIntOpcode.toDouble);
};
case [DoubleType()]:
return (FlowGraphBuilder builder) {
// no-op
};
}
return null;
}
}
/// Recognizes calls to [num.toInt].
class const NumToInt() implements RecognizedCallMatcher {
@override
BuildIR? match(List<CType> args) {
switch (args) {
case [IntType()]:
return (FlowGraphBuilder builder) {
// no-op
};
case [DoubleType()]:
return (FlowGraphBuilder builder) {
builder.addUnaryDoubleOp(UnaryDoubleOpcode.truncate);
};
}
return null;
}
}
/// Recognizes calls to [num] comparisons.
class const NumComparison(
final ComparisonOpcode intOp,
final ComparisonOpcode doubleOp,
) implements RecognizedCallMatcher {
@override
BuildIR? match(List<CType> args) {
switch (args) {
case [IntType(), IntType()]:
return (FlowGraphBuilder builder) {
builder.addComparison(intOp);
};
case [DoubleType(), DoubleType()]:
return (FlowGraphBuilder builder) {
builder.addComparison(doubleOp);
};
}
// TODO(alexmarkov): support other combinations.
return null;
}
}
/// Recognizes calls to binary [int] operations.
class const BinaryIntOp(final BinaryIntOpcode op)
implements RecognizedCallMatcher {
@override
BuildIR? match(List<CType> args) {
assert(args[0] is IntType && args[1] is IntType);
return (FlowGraphBuilder builder) {
builder.addBinaryIntOp(op);
};
}
}
/// Recognizes calls to unary [int] operations.
class const UnaryIntOp(final UnaryIntOpcode op)
implements RecognizedCallMatcher {
@override
BuildIR? match(List<CType> args) {
assert(args[0] is IntType);
return (FlowGraphBuilder builder) {
builder.addUnaryIntOp(op);
};
}
}
/// Recognizes calls to binary [double] operations.
class const BinaryDoubleOp(final BinaryDoubleOpcode op)
implements RecognizedCallMatcher {
@override
BuildIR? match(List<CType> args) {
assert(args[0] is DoubleType);
switch (args[1]) {
case IntType():
return (FlowGraphBuilder builder) {
builder.addUnaryIntOp(UnaryIntOpcode.toDouble);
builder.addBinaryDoubleOp(op);
};
case DoubleType():
return (FlowGraphBuilder builder) {
builder.addBinaryDoubleOp(op);
};
default:
return null;
}
}
}
/// Recognizes calls to unary [double] operations.
class const UnaryDoubleOp(final UnaryDoubleOpcode op)
implements RecognizedCallMatcher {
@override
BuildIR? match(List<CType> args) {
assert(args[0] is DoubleType);
return (FlowGraphBuilder builder) {
builder.addUnaryDoubleOp(op);
};
}
}
class const UnsafeCast() implements RecognizedCallMatcher {
@override
BuildIR? match(List<CType> args) {
return (FlowGraphBuilder builder) {
final value = builder.pop();
final typeArgs = builder.pop();
final testedType = CType.fromStaticType(switch (typeArgs) {
TypeArguments() => typeArgs.types.single,
Constant(
value: ConstantValue(constant: TypeArgumentsConstant(:var types)),
) =>
types.single,
_ =>
throw 'Unexpected unsafeCast type arguments ${IrToText.instruction(typeArgs)}',
});
final typeParameters = switch (typeArgs) {
TypeArguments() => [
for (var i = 0, n = typeArgs.inputCount; i < n; ++i)
typeArgs.inputDefAt(i),
],
Constant() => <Definition>[],
_ =>
throw 'Unexpected unsafeCast type arguments ${IrToText.instruction(typeArgs)}',
};
builder.push(value);
builder.addTypeCast(
testedType,
typeParameters: typeParameters,
isChecked: false,
);
};
}
}
/// Recognize certain Dart methods and calls based on the
/// target and static types and build IR for them.
abstract class RecognizedMethods {
/// Recognized instance method calls.
Map<ast.Member, RecognizedCallMatcher> get instanceInvocations;
/// Recognized instance getter calls.
Map<ast.Member, RecognizedCallMatcher> get instanceGetters;
/// Recognized static method calls.
Map<ast.Member, RecognizedCallMatcher> get staticInvocations;
/// Function body of the recognized functions.
BuildIR? getRecognizedFunctionBody(CFunction function);
}
/// Recognized methods shared by all back-ends.
class CommonRecognizedMethods implements RecognizedMethods {
final LibraryIndex index;
final bool requireMethods;
CommonRecognizedMethods({this.requireMethods = true})
: index = GlobalContext.instance.coreLibraries;
@override
late final instanceInvocations = <ast.Member, RecognizedCallMatcher>{
// Note: `const` is omitted on the values of null-aware map entries to work
// around a prebuilt SDK CFE bug in `_translateNullAwareMapEntry`.
?getProcedure('dart:core', 'num', '+'): const BinaryNumOp(
BinaryIntOpcode.add,
BinaryDoubleOpcode.add,
),
?getProcedure('dart:core', 'num', '-'): const BinaryNumOp(
BinaryIntOpcode.sub,
BinaryDoubleOpcode.sub,
),
?getProcedure('dart:core', 'num', '*'): const BinaryNumOp(
BinaryIntOpcode.mul,
BinaryDoubleOpcode.mul,
),
?getProcedure('dart:core', 'num', '%'): const BinaryNumOp(
BinaryIntOpcode.mod,
BinaryDoubleOpcode.mod,
),
?getProcedure('dart:core', 'num', '~/'): const BinaryNumOp(
BinaryIntOpcode.truncatingDiv,
BinaryDoubleOpcode.truncatingDiv,
),
?getProcedure('dart:core', 'num', 'remainder'): const BinaryNumOp(
BinaryIntOpcode.rem,
BinaryDoubleOpcode.rem,
),
?getProcedure('dart:core', 'num', '/'): const NumDiv(),
?getProcedure('dart:core', 'num', 'toDouble'): const NumToDouble(),
?getProcedure('dart:core', 'num', 'toInt'): const NumToInt(),
?getProcedure('dart:core', 'num', '=='): const NumComparison(
ComparisonOpcode.intEqual,
ComparisonOpcode.doubleEqual,
),
?getProcedure('dart:core', 'num', '<'): const NumComparison(
ComparisonOpcode.intLess,
ComparisonOpcode.doubleLess,
),
?getProcedure('dart:core', 'num', '<='): const NumComparison(
ComparisonOpcode.intLessOrEqual,
ComparisonOpcode.doubleLessOrEqual,
),
?getProcedure('dart:core', 'num', '>'): const NumComparison(
ComparisonOpcode.intGreater,
ComparisonOpcode.doubleGreater,
),
?getProcedure('dart:core', 'num', '>='): const NumComparison(
ComparisonOpcode.intGreaterOrEqual,
ComparisonOpcode.doubleGreaterOrEqual,
),
?getProcedure('dart:core', 'int', '|'): const BinaryIntOp(
BinaryIntOpcode.bitOr,
),
?getProcedure('dart:core', 'int', '&'): const BinaryIntOp(
BinaryIntOpcode.bitAnd,
),
?getProcedure('dart:core', 'int', '^'): const BinaryIntOp(
BinaryIntOpcode.bitXor,
),
?getProcedure('dart:core', 'int', '<<'): const BinaryIntOp(
BinaryIntOpcode.shiftLeft,
),
?getProcedure('dart:core', 'int', '>>'): const BinaryIntOp(
BinaryIntOpcode.shiftRight,
),
?getProcedure('dart:core', 'int', '>>>'): const BinaryIntOp(
BinaryIntOpcode.unsignedShiftRight,
),
?getProcedure('dart:core', 'int', 'unary-'): const UnaryIntOp(
UnaryIntOpcode.neg,
),
?getProcedure('dart:core', 'int', '~'): const UnaryIntOp(
UnaryIntOpcode.bitNot,
),
?getProcedure('dart:core', 'int', 'abs'): const UnaryIntOp(
UnaryIntOpcode.abs,
),
?getProcedure('dart:core', 'double', '+'): const BinaryDoubleOp(
BinaryDoubleOpcode.add,
),
?getProcedure('dart:core', 'double', '-'): const BinaryDoubleOp(
BinaryDoubleOpcode.sub,
),
?getProcedure('dart:core', 'double', '*'): const BinaryDoubleOp(
BinaryDoubleOpcode.mul,
),
?getProcedure('dart:core', 'double', '%'): const BinaryDoubleOp(
BinaryDoubleOpcode.mod,
),
?getProcedure('dart:core', 'double', '/'): const BinaryDoubleOp(
BinaryDoubleOpcode.div,
),
?getProcedure('dart:core', 'double', '~/'): const BinaryDoubleOp(
BinaryDoubleOpcode.truncatingDiv,
),
?getProcedure('dart:core', 'double', 'remainder'): const BinaryDoubleOp(
BinaryDoubleOpcode.rem,
),
?getProcedure('dart:core', 'double', 'unary-'): const UnaryDoubleOp(
UnaryDoubleOpcode.neg,
),
?getProcedure('dart:core', 'double', 'abs'): const UnaryDoubleOp(
UnaryDoubleOpcode.abs,
),
?getProcedure('dart:core', 'double', 'round'): const UnaryDoubleOp(
UnaryDoubleOpcode.round,
),
?getProcedure('dart:core', 'double', 'ceil'): const UnaryDoubleOp(
UnaryDoubleOpcode.ceil,
),
?getProcedure('dart:core', 'double', 'floor'): const UnaryDoubleOp(
UnaryDoubleOpcode.floor,
),
?getProcedure('dart:core', 'double', 'truncate'): const UnaryDoubleOp(
UnaryDoubleOpcode.truncate,
),
?getProcedure('dart:core', 'double', 'roundToDouble'): const UnaryDoubleOp(
UnaryDoubleOpcode.roundToDouble,
),
?getProcedure('dart:core', 'double', 'ceilToDouble'): const UnaryDoubleOp(
UnaryDoubleOpcode.ceilToDouble,
),
?getProcedure('dart:core', 'double', 'floorToDouble'): const UnaryDoubleOp(
UnaryDoubleOpcode.floorToDouble,
),
?getProcedure('dart:core', 'double', 'truncateToDouble'):
const UnaryDoubleOp(UnaryDoubleOpcode.truncateToDouble),
};
@override
late final instanceGetters = <ast.Member, RecognizedCallMatcher>{
?getProcedure('dart:core', 'num', 'get:isNaN'): const NumIsNaN(),
?getProcedure('dart:core', 'int', 'get:sign'): const UnaryIntOp(
UnaryIntOpcode.sign,
),
?getProcedure('dart:core', 'int', 'get:bitLength'): const UnaryIntOp(
UnaryIntOpcode.bitLength,
),
};
late final _recognizedMembers = <ast.Member, BuildIR>{
// dart:core
?getTopLevelProcedure(
'dart:core',
'identical',
): (FlowGraphBuilder builder) {
builder.addComparison(.identical);
},
};
@override
late final staticInvocations = <ast.Member, RecognizedCallMatcher>{
for (final MapEntry(key: member, value: builder)
in _recognizedMembers.entries)
member: AnyArgsMatcher(builder),
// dart:_internal
?getTopLevelProcedure('dart:_internal', 'unsafeCast'): const UnsafeCast(),
};
@override
BuildIR? getRecognizedFunctionBody(CFunction function) =>
_recognizedMembers[function.member];
ast.Procedure? getProcedure(
String libraryUri,
String className,
String memberName,
) => requireMethods
? index.getProcedure(libraryUri, className, memberName)
: index.tryGetProcedure(libraryUri, className, memberName);
ast.Procedure? getTopLevelProcedure(String libraryUri, String name) =>
requireMethods
? index.getTopLevelProcedure(libraryUri, name)
: index.tryGetProcedure(libraryUri, LibraryIndex.topLevel, name);
}