28d7984e31
Issue: https://github.com/dart-lang/sdk/issues/61635 Change-Id: I7476ec7e66edd958c66e31dabcd6ab8d818594e5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509640 Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
406 lines
14 KiB
Dart
406 lines
14 KiB
Dart
// 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/instructions.dart';
|
|
import 'package:cfg/ir/global_context.dart';
|
|
import 'package:cfg/ir/types.dart';
|
|
import 'package:cfg/utils/misc.dart';
|
|
import 'package:kernel/ast.dart' as ast;
|
|
import 'package:kernel/src/printer.dart' as ast_printer show AstPrinter;
|
|
import 'package:kernel/type_environment.dart' show StaticTypeContext;
|
|
|
|
/// Represents an arbitrary constant value.
|
|
///
|
|
/// [ConstantValue] is a thin wrapper around [ast.Constant].
|
|
/// Constants which do not have corresponding representation in AST
|
|
/// (e.g. constant type arguments) have dedicated subclasses of
|
|
/// [ast.AuxiliaryConstant].
|
|
extension type ConstantValue(ast.Constant constant) {
|
|
factory fromInt(int value) => ConstantValue(ast.IntConstant(value));
|
|
factory fromDouble(double value) => ConstantValue(ast.DoubleConstant(value));
|
|
factory fromBool(bool value) => ConstantValue(ast.BoolConstant(value));
|
|
factory fromNull() => ConstantValue(ast.NullConstant());
|
|
factory fromString(String value) => ConstantValue(ast.StringConstant(value));
|
|
|
|
int get intValue => switch (constant) {
|
|
ast.IntConstant(:var value) => value,
|
|
UnboxedIntConstant(:var value) => value,
|
|
_ => throw 'Unexpected int constant ${constant.runtimeType}',
|
|
};
|
|
double get doubleValue => switch (constant) {
|
|
ast.DoubleConstant(:var value) => value,
|
|
UnboxedDoubleConstant(:var value) => value,
|
|
_ => throw 'Unexpected double constant ${constant.runtimeType}',
|
|
};
|
|
bool get boolValue => (constant as ast.BoolConstant).value;
|
|
String get stringValue => (constant as ast.StringConstant).value;
|
|
|
|
bool get isInt =>
|
|
constant is ast.IntConstant || constant is UnboxedIntConstant;
|
|
bool get isDouble =>
|
|
constant is ast.DoubleConstant || constant is UnboxedDoubleConstant;
|
|
bool get isBool => constant is ast.BoolConstant;
|
|
bool get isNull => constant is ast.NullConstant;
|
|
bool get isString => constant is ast.StringConstant;
|
|
bool get isUnboxed => constant is UnboxedConstant;
|
|
bool get isTypeArgumentsConstant => constant is TypeArgumentsConstant;
|
|
|
|
CType get type => switch (constant) {
|
|
ast.IntConstant() || UnboxedIntConstant() => const IntType(),
|
|
ast.DoubleConstant() || UnboxedDoubleConstant() => const DoubleType(),
|
|
ast.BoolConstant() => const BoolType(),
|
|
ast.NullConstant() => const NullType(),
|
|
ast.StringConstant() => const StringType(),
|
|
TypeArgumentsConstant() => const TypeArgumentsType(),
|
|
SentinelConstant() => const LateValueType(),
|
|
_ => StaticType(
|
|
constant.getType(GlobalContext.instance.staticTypeContextForConstants),
|
|
),
|
|
};
|
|
|
|
bool get isZero => switch (constant) {
|
|
ast.IntConstant(:var value) => value == 0,
|
|
UnboxedIntConstant(:var value) => value == 0,
|
|
ast.DoubleConstant(:var value) => value == 0.0,
|
|
UnboxedDoubleConstant(:var value) => value == 0.0,
|
|
_ => false,
|
|
};
|
|
|
|
bool get isNegative => switch (constant) {
|
|
ast.IntConstant(:var value) => value < 0,
|
|
UnboxedIntConstant(:var value) => value < 0,
|
|
ast.DoubleConstant(:var value) => value.isNegative,
|
|
UnboxedDoubleConstant(:var value) => value.isNegative,
|
|
_ => false,
|
|
};
|
|
|
|
String valueToString() => switch (constant) {
|
|
ast.StringConstant(:var value) => '"${value}"',
|
|
ast.PrimitiveConstant(:var value) => value.toString(),
|
|
_ => constant.toString(),
|
|
};
|
|
}
|
|
|
|
/// Utility class to perform operations on constant values.
|
|
///
|
|
/// Methods of this class return `null` when constant folding
|
|
/// cannot be performed (e.g. corresponding operation would
|
|
/// throw an exception at runtime).
|
|
class const ConstantFolding() {
|
|
ConstantValue comparison(
|
|
ComparisonOpcode op,
|
|
ConstantValue left,
|
|
ConstantValue right,
|
|
) {
|
|
final result = switch (op) {
|
|
ComparisonOpcode.equal => left.constant == right.constant,
|
|
ComparisonOpcode.notEqual => left.constant != right.constant,
|
|
ComparisonOpcode.identical => left.constant == right.constant,
|
|
ComparisonOpcode.notIdentical => left.constant != right.constant,
|
|
ComparisonOpcode.intEqual => left.intValue == right.intValue,
|
|
ComparisonOpcode.intNotEqual => left.intValue != right.intValue,
|
|
ComparisonOpcode.intLess => left.intValue < right.intValue,
|
|
ComparisonOpcode.intLessOrEqual => left.intValue <= right.intValue,
|
|
ComparisonOpcode.intGreater => left.intValue > right.intValue,
|
|
ComparisonOpcode.intGreaterOrEqual => left.intValue >= right.intValue,
|
|
ComparisonOpcode.intTestIsZero => (left.intValue & right.intValue) == 0,
|
|
ComparisonOpcode.intTestIsNotZero =>
|
|
(left.intValue & right.intValue) != 0,
|
|
ComparisonOpcode.doubleEqual => left.doubleValue == right.doubleValue,
|
|
ComparisonOpcode.doubleNotEqual => left.doubleValue != right.doubleValue,
|
|
ComparisonOpcode.doubleLess => left.doubleValue < right.doubleValue,
|
|
ComparisonOpcode.doubleLessOrEqual =>
|
|
left.doubleValue <= right.doubleValue,
|
|
ComparisonOpcode.doubleGreater => left.doubleValue > right.doubleValue,
|
|
ComparisonOpcode.doubleGreaterOrEqual =>
|
|
left.doubleValue >= right.doubleValue,
|
|
};
|
|
return ConstantValue.fromBool(result);
|
|
}
|
|
|
|
ConstantValue? binaryIntOp(
|
|
BinaryIntOpcode op,
|
|
ConstantValue left,
|
|
ConstantValue right,
|
|
) {
|
|
final a = left.intValue;
|
|
final b = right.intValue;
|
|
switch (op) {
|
|
case BinaryIntOpcode.add:
|
|
return ConstantValue.fromInt(a + b);
|
|
case BinaryIntOpcode.sub:
|
|
return ConstantValue.fromInt(a - b);
|
|
case BinaryIntOpcode.mul:
|
|
return ConstantValue.fromInt(a * b);
|
|
case BinaryIntOpcode.truncatingDiv:
|
|
return (b != 0) ? ConstantValue.fromInt(a ~/ b) : null;
|
|
case BinaryIntOpcode.mod:
|
|
return (b != 0) ? ConstantValue.fromInt(a % b) : null;
|
|
case BinaryIntOpcode.rem:
|
|
return (b != 0) ? ConstantValue.fromInt(a.remainder(b)) : null;
|
|
case BinaryIntOpcode.bitOr:
|
|
return ConstantValue.fromInt(a | b);
|
|
case BinaryIntOpcode.bitAnd:
|
|
return ConstantValue.fromInt(a & b);
|
|
case BinaryIntOpcode.bitXor:
|
|
return ConstantValue.fromInt(a ^ b);
|
|
case BinaryIntOpcode.shiftLeft:
|
|
return (b >= 0) ? ConstantValue.fromInt(a << b) : null;
|
|
case BinaryIntOpcode.shiftRight:
|
|
return (b >= 0) ? ConstantValue.fromInt(a >> b) : null;
|
|
case BinaryIntOpcode.unsignedShiftRight:
|
|
return (b >= 0) ? ConstantValue.fromInt(a >>> b) : null;
|
|
}
|
|
}
|
|
|
|
ConstantValue? unaryIntOp(UnaryIntOpcode op, ConstantValue operand) {
|
|
final x = operand.intValue;
|
|
switch (op) {
|
|
case UnaryIntOpcode.neg:
|
|
return ConstantValue.fromInt(-x);
|
|
case UnaryIntOpcode.bitNot:
|
|
return ConstantValue.fromInt(~x);
|
|
case UnaryIntOpcode.toDouble:
|
|
return ConstantValue.fromDouble(x.toDouble());
|
|
case UnaryIntOpcode.abs:
|
|
return ConstantValue.fromInt(x.abs());
|
|
case UnaryIntOpcode.sign:
|
|
return ConstantValue.fromInt(x.sign);
|
|
}
|
|
}
|
|
|
|
ConstantValue? binaryDoubleOp(
|
|
BinaryDoubleOpcode op,
|
|
ConstantValue left,
|
|
ConstantValue right,
|
|
) {
|
|
final a = left.doubleValue;
|
|
final b = right.doubleValue;
|
|
switch (op) {
|
|
case BinaryDoubleOpcode.add:
|
|
return ConstantValue.fromDouble(a + b);
|
|
case BinaryDoubleOpcode.sub:
|
|
return ConstantValue.fromDouble(a - b);
|
|
case BinaryDoubleOpcode.mul:
|
|
return ConstantValue.fromDouble(a * b);
|
|
case BinaryDoubleOpcode.mod:
|
|
return ConstantValue.fromDouble(a % b);
|
|
case BinaryDoubleOpcode.rem:
|
|
return ConstantValue.fromDouble(a.remainder(b));
|
|
case BinaryDoubleOpcode.div:
|
|
return ConstantValue.fromDouble(a / b);
|
|
case BinaryDoubleOpcode.truncatingDiv:
|
|
final doubleResult = a / b;
|
|
return doubleResult.isFinite
|
|
? ConstantValue.fromInt(doubleResult.truncate())
|
|
: null;
|
|
}
|
|
}
|
|
|
|
ConstantValue? unaryDoubleOp(UnaryDoubleOpcode op, ConstantValue operand) {
|
|
final x = operand.doubleValue;
|
|
switch (op) {
|
|
case UnaryDoubleOpcode.neg:
|
|
return ConstantValue.fromDouble(-x);
|
|
case UnaryDoubleOpcode.abs:
|
|
return ConstantValue.fromDouble(x.abs());
|
|
case UnaryDoubleOpcode.sign:
|
|
return ConstantValue.fromDouble(x.sign);
|
|
case UnaryDoubleOpcode.square:
|
|
return ConstantValue.fromDouble(x * x);
|
|
case UnaryDoubleOpcode.round:
|
|
return x.isFinite ? ConstantValue.fromInt(x.round()) : null;
|
|
case UnaryDoubleOpcode.floor:
|
|
return x.isFinite ? ConstantValue.fromInt(x.floor()) : null;
|
|
case UnaryDoubleOpcode.ceil:
|
|
return x.isFinite ? ConstantValue.fromInt(x.ceil()) : null;
|
|
case UnaryDoubleOpcode.truncate:
|
|
return x.isFinite ? ConstantValue.fromInt(x.truncate()) : null;
|
|
case UnaryDoubleOpcode.roundToDouble:
|
|
return ConstantValue.fromDouble(x.roundToDouble());
|
|
case UnaryDoubleOpcode.floorToDouble:
|
|
return ConstantValue.fromDouble(x.floorToDouble());
|
|
case UnaryDoubleOpcode.ceilToDouble:
|
|
return ConstantValue.fromDouble(x.ceilToDouble());
|
|
case UnaryDoubleOpcode.truncateToDouble:
|
|
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.
|
|
class TypeArgumentsConstant(final List<ast.DartType> types)
|
|
extends ast.AuxiliaryConstant {
|
|
@override
|
|
void visitChildren(ast.Visitor v) {
|
|
ast.visitList(types, v);
|
|
}
|
|
|
|
@override
|
|
void toTextInternal(ast_printer.AstPrinter printer) {
|
|
printer.writeTypeArguments(types);
|
|
}
|
|
|
|
@override
|
|
String toString() => toStringInternal();
|
|
|
|
@override
|
|
int get hashCode => listHashCode(types);
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
return other is TypeArgumentsConstant &&
|
|
listEquals(this.types, other.types);
|
|
}
|
|
|
|
@override
|
|
ast.DartType getType(StaticTypeContext context) => const ast.DynamicType();
|
|
}
|
|
|
|
/// Synthetic sentinel value which is used represent the uninitialized
|
|
/// value of a late local variable, late or static field or
|
|
/// a value of an optional parameter which was not passed.
|
|
class SentinelConstant extends ast.AuxiliaryConstant {
|
|
@override
|
|
void visitChildren(ast.Visitor v) {}
|
|
|
|
@override
|
|
void toTextInternal(ast_printer.AstPrinter printer) {
|
|
printer.write('#sentinel');
|
|
}
|
|
|
|
@override
|
|
String toString() => toStringInternal();
|
|
|
|
@override
|
|
int get hashCode => 2031;
|
|
|
|
@override
|
|
bool operator ==(Object other) => other is SentinelConstant;
|
|
|
|
@override
|
|
ast.DartType getType(StaticTypeContext context) => const ast.DynamicType();
|
|
}
|
|
|
|
/// Base class for unboxed constant values.
|
|
///
|
|
/// Used by certain back-ends to distinguish raw unboxed values
|
|
/// (incompatible with Dart objects) from regular constants.
|
|
abstract base class UnboxedConstant extends ast.AuxiliaryConstant;
|
|
|
|
/// Unboxed int constant.
|
|
///
|
|
/// Used by certain back-ends to distinguish raw unboxed values
|
|
/// (incompatible with Dart objects) from regular constants.
|
|
final class UnboxedIntConstant(final int value) extends UnboxedConstant {
|
|
@override
|
|
void visitChildren(ast.Visitor v) {}
|
|
|
|
@override
|
|
void toTextInternal(ast_printer.AstPrinter printer) {
|
|
printer.write('#unboxed $value');
|
|
}
|
|
|
|
@override
|
|
String toString() => 'UnboxedIntConstant($value)';
|
|
|
|
@override
|
|
int get hashCode => value.hashCode;
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
other is UnboxedIntConstant && other.value == value;
|
|
|
|
@override
|
|
ast.DartType getType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.intNonNullableRawType;
|
|
}
|
|
|
|
/// Unboxed double constant.
|
|
///
|
|
/// Used by certain back-ends to distinguish raw unboxed values
|
|
/// (incompatible with Dart objects) from regular constants.
|
|
final class UnboxedDoubleConstant(final double value) extends UnboxedConstant {
|
|
@override
|
|
void visitChildren(ast.Visitor v) {}
|
|
|
|
@override
|
|
void toTextInternal(ast_printer.AstPrinter printer) {
|
|
printer.write('#unboxed $value');
|
|
}
|
|
|
|
@override
|
|
String toString() => 'UnboxedDoubleConstant($value)';
|
|
|
|
@override
|
|
int get hashCode => value.hashCode;
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
other is UnboxedDoubleConstant && identical(value, other.value);
|
|
|
|
@override
|
|
ast.DartType getType(StaticTypeContext context) =>
|
|
context.typeEnvironment.coreTypes.doubleNonNullableRawType;
|
|
}
|
|
|
|
/// Synthetic constant representing undefined value of a local variable.
|
|
class UndefinedConstant extends ast.AuxiliaryConstant {
|
|
@override
|
|
void visitChildren(ast.Visitor v) {}
|
|
|
|
@override
|
|
void toTextInternal(ast_printer.AstPrinter printer) {
|
|
printer.write('#undefined');
|
|
}
|
|
|
|
@override
|
|
String toString() => toStringInternal();
|
|
|
|
@override
|
|
int get hashCode => 2053;
|
|
|
|
@override
|
|
bool operator ==(Object other) => other is UndefinedConstant;
|
|
|
|
@override
|
|
ast.DartType getType(StaticTypeContext context) => const ast.DynamicType();
|
|
}
|