|
| 1 | +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file |
| 2 | +// for details. All rights reserved. Use of this source code is governed by a |
| 3 | +// BSD-style license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +import 'package:cfg/ir/instructions.dart'; |
| 6 | +import 'package:cfg/ir/global_context.dart'; |
| 7 | +import 'package:cfg/ir/types.dart'; |
| 8 | +import 'package:cfg/utils/misc.dart'; |
| 9 | +import 'package:kernel/ast.dart' as ast; |
| 10 | +import 'package:kernel/src/printer.dart' as ast_printer show AstPrinter; |
| 11 | +import 'package:kernel/type_environment.dart' show StaticTypeContext; |
| 12 | + |
| 13 | +/// Represents an arbitrary constant value. |
| 14 | +/// |
| 15 | +/// [ConstantValue] is a thin wrapper around [ast.Constant]. |
| 16 | +/// Constants which do not have corresponding representation in AST |
| 17 | +/// (e.g. constant type arguments) have dedicated subclasses of |
| 18 | +/// [ast.AuxiliaryConstant]. |
| 19 | +extension type ConstantValue(ast.Constant constant) { |
| 20 | + factory ConstantValue.fromInt(int value) => |
| 21 | + ConstantValue(ast.IntConstant(value)); |
| 22 | + factory ConstantValue.fromDouble(double value) => |
| 23 | + ConstantValue(ast.DoubleConstant(value)); |
| 24 | + factory ConstantValue.fromBool(bool value) => |
| 25 | + ConstantValue(ast.BoolConstant(value)); |
| 26 | + factory ConstantValue.fromNull() => ConstantValue(ast.NullConstant()); |
| 27 | + factory ConstantValue.fromString(String value) => |
| 28 | + ConstantValue(ast.StringConstant(value)); |
| 29 | + |
| 30 | + int get intValue => (constant as ast.IntConstant).value; |
| 31 | + double get doubleValue => (constant as ast.DoubleConstant).value; |
| 32 | + bool get boolValue => (constant as ast.BoolConstant).value; |
| 33 | + String get stringValue => (constant as ast.StringConstant).value; |
| 34 | + |
| 35 | + bool get isInt => constant is ast.IntConstant; |
| 36 | + bool get isDouble => constant is ast.DoubleConstant; |
| 37 | + bool get isBool => constant is ast.BoolConstant; |
| 38 | + bool get isNull => constant is ast.NullConstant; |
| 39 | + bool get isString => constant is ast.StringConstant; |
| 40 | + |
| 41 | + CType get type => switch (constant) { |
| 42 | + ast.IntConstant() => const IntType(), |
| 43 | + ast.DoubleConstant() => const DoubleType(), |
| 44 | + ast.BoolConstant() => const BoolType(), |
| 45 | + ast.NullConstant() => const NullType(), |
| 46 | + ast.StringConstant() => const StringType(), |
| 47 | + TypeArgumentsConstant() => const TypeArgumentsType(), |
| 48 | + _ => StaticType( |
| 49 | + constant.getType(GlobalContext.instance.staticTypeContextForConstants), |
| 50 | + ), |
| 51 | + }; |
| 52 | + |
| 53 | + bool get isZero => switch (constant) { |
| 54 | + ast.IntConstant(:var value) => value == 0, |
| 55 | + ast.DoubleConstant(:var value) => value == 0.0, |
| 56 | + _ => false, |
| 57 | + }; |
| 58 | + |
| 59 | + bool get isNegative => switch (constant) { |
| 60 | + ast.IntConstant(:var value) => value < 0, |
| 61 | + ast.DoubleConstant(:var value) => value.isNegative, |
| 62 | + _ => false, |
| 63 | + }; |
| 64 | + |
| 65 | + String valueToString() => switch (constant) { |
| 66 | + ast.StringConstant(:var value) => '"${value}"', |
| 67 | + ast.PrimitiveConstant(:var value) => value.toString(), |
| 68 | + _ => constant.toString(), |
| 69 | + }; |
| 70 | +} |
| 71 | + |
| 72 | +/// Utility class to perform operations on constant values. |
| 73 | +/// |
| 74 | +/// Methods of this class return `null` when constant folding |
| 75 | +/// cannot be performed (e.g. corresponding operation would |
| 76 | +/// throw an exception at runtime). |
| 77 | +class ConstantFolding { |
| 78 | + const ConstantFolding(); |
| 79 | + |
| 80 | + ConstantValue comparison( |
| 81 | + ComparisonOpcode op, |
| 82 | + ConstantValue left, |
| 83 | + ConstantValue right, |
| 84 | + ) { |
| 85 | + final result = switch (op) { |
| 86 | + ComparisonOpcode.equal => left.constant == right.constant, |
| 87 | + ComparisonOpcode.notEqual => left.constant != right.constant, |
| 88 | + ComparisonOpcode.identical => left.constant == right.constant, |
| 89 | + ComparisonOpcode.notIdentical => left.constant != right.constant, |
| 90 | + ComparisonOpcode.intEqual => left.intValue == right.intValue, |
| 91 | + ComparisonOpcode.intNotEqual => left.intValue != right.intValue, |
| 92 | + ComparisonOpcode.intLess => left.intValue < right.intValue, |
| 93 | + ComparisonOpcode.intLessOrEqual => left.intValue <= right.intValue, |
| 94 | + ComparisonOpcode.intGreater => left.intValue > right.intValue, |
| 95 | + ComparisonOpcode.intGreaterOrEqual => left.intValue >= right.intValue, |
| 96 | + ComparisonOpcode.intTestIsZero => (left.intValue & right.intValue) == 0, |
| 97 | + ComparisonOpcode.intTestIsNotZero => |
| 98 | + (left.intValue & right.intValue) != 0, |
| 99 | + ComparisonOpcode.doubleEqual => left.doubleValue == right.doubleValue, |
| 100 | + ComparisonOpcode.doubleNotEqual => left.doubleValue != right.doubleValue, |
| 101 | + ComparisonOpcode.doubleLess => left.doubleValue < right.doubleValue, |
| 102 | + ComparisonOpcode.doubleLessOrEqual => |
| 103 | + left.doubleValue <= right.doubleValue, |
| 104 | + ComparisonOpcode.doubleGreater => left.doubleValue > right.doubleValue, |
| 105 | + ComparisonOpcode.doubleGreaterOrEqual => |
| 106 | + left.doubleValue >= right.doubleValue, |
| 107 | + }; |
| 108 | + return ConstantValue.fromBool(result); |
| 109 | + } |
| 110 | + |
| 111 | + ConstantValue? binaryIntOp( |
| 112 | + BinaryIntOpcode op, |
| 113 | + ConstantValue left, |
| 114 | + ConstantValue right, |
| 115 | + ) { |
| 116 | + final a = left.intValue; |
| 117 | + final b = right.intValue; |
| 118 | + switch (op) { |
| 119 | + case BinaryIntOpcode.add: |
| 120 | + return ConstantValue.fromInt(a + b); |
| 121 | + case BinaryIntOpcode.sub: |
| 122 | + return ConstantValue.fromInt(a - b); |
| 123 | + case BinaryIntOpcode.mul: |
| 124 | + return ConstantValue.fromInt(a * b); |
| 125 | + case BinaryIntOpcode.truncatingDiv: |
| 126 | + return (b != 0) ? ConstantValue.fromInt(a ~/ b) : null; |
| 127 | + case BinaryIntOpcode.mod: |
| 128 | + return (b != 0) ? ConstantValue.fromInt(a % b) : null; |
| 129 | + case BinaryIntOpcode.rem: |
| 130 | + return (b != 0) ? ConstantValue.fromInt(a.remainder(b)) : null; |
| 131 | + case BinaryIntOpcode.bitOr: |
| 132 | + return ConstantValue.fromInt(a | b); |
| 133 | + case BinaryIntOpcode.bitAnd: |
| 134 | + return ConstantValue.fromInt(a & b); |
| 135 | + case BinaryIntOpcode.bitXor: |
| 136 | + return ConstantValue.fromInt(a ^ b); |
| 137 | + case BinaryIntOpcode.shiftLeft: |
| 138 | + return (b >= 0) ? ConstantValue.fromInt(a << b) : null; |
| 139 | + case BinaryIntOpcode.shiftRight: |
| 140 | + return (b >= 0) ? ConstantValue.fromInt(a >> b) : null; |
| 141 | + case BinaryIntOpcode.unsignedShiftRight: |
| 142 | + return (b >= 0) ? ConstantValue.fromInt(a >>> b) : null; |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + ConstantValue? unaryIntOp(UnaryIntOpcode op, ConstantValue operand) { |
| 147 | + final x = operand.intValue; |
| 148 | + switch (op) { |
| 149 | + case UnaryIntOpcode.neg: |
| 150 | + return ConstantValue.fromInt(-x); |
| 151 | + case UnaryIntOpcode.bitNot: |
| 152 | + return ConstantValue.fromInt(~x); |
| 153 | + case UnaryIntOpcode.toDouble: |
| 154 | + return ConstantValue.fromDouble(x.toDouble()); |
| 155 | + case UnaryIntOpcode.abs: |
| 156 | + return ConstantValue.fromInt(x.abs()); |
| 157 | + case UnaryIntOpcode.sign: |
| 158 | + return ConstantValue.fromInt(x.sign); |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + ConstantValue? binaryDoubleOp( |
| 163 | + BinaryDoubleOpcode op, |
| 164 | + ConstantValue left, |
| 165 | + ConstantValue right, |
| 166 | + ) { |
| 167 | + final a = left.doubleValue; |
| 168 | + final b = right.doubleValue; |
| 169 | + switch (op) { |
| 170 | + case BinaryDoubleOpcode.add: |
| 171 | + return ConstantValue.fromDouble(a + b); |
| 172 | + case BinaryDoubleOpcode.sub: |
| 173 | + return ConstantValue.fromDouble(a - b); |
| 174 | + case BinaryDoubleOpcode.mul: |
| 175 | + return ConstantValue.fromDouble(a * b); |
| 176 | + case BinaryDoubleOpcode.mod: |
| 177 | + return ConstantValue.fromDouble(a % b); |
| 178 | + case BinaryDoubleOpcode.rem: |
| 179 | + return ConstantValue.fromDouble(a.remainder(b)); |
| 180 | + case BinaryDoubleOpcode.div: |
| 181 | + return ConstantValue.fromDouble(a / b); |
| 182 | + case BinaryDoubleOpcode.truncatingDiv: |
| 183 | + final doubleResult = a / b; |
| 184 | + return doubleResult.isFinite |
| 185 | + ? ConstantValue.fromInt(doubleResult.truncate()) |
| 186 | + : null; |
| 187 | + } |
| 188 | + } |
| 189 | + |
| 190 | + ConstantValue? unaryDoubleOp(UnaryDoubleOpcode op, ConstantValue operand) { |
| 191 | + final x = operand.doubleValue; |
| 192 | + switch (op) { |
| 193 | + case UnaryDoubleOpcode.neg: |
| 194 | + return ConstantValue.fromDouble(-x); |
| 195 | + case UnaryDoubleOpcode.abs: |
| 196 | + return ConstantValue.fromDouble(x.abs()); |
| 197 | + case UnaryDoubleOpcode.sign: |
| 198 | + return ConstantValue.fromDouble(x.sign); |
| 199 | + case UnaryDoubleOpcode.square: |
| 200 | + return ConstantValue.fromDouble(x * x); |
| 201 | + case UnaryDoubleOpcode.round: |
| 202 | + return x.isFinite ? ConstantValue.fromInt(x.round()) : null; |
| 203 | + case UnaryDoubleOpcode.floor: |
| 204 | + return x.isFinite ? ConstantValue.fromInt(x.floor()) : null; |
| 205 | + case UnaryDoubleOpcode.ceil: |
| 206 | + return x.isFinite ? ConstantValue.fromInt(x.ceil()) : null; |
| 207 | + case UnaryDoubleOpcode.truncate: |
| 208 | + return x.isFinite ? ConstantValue.fromInt(x.truncate()) : null; |
| 209 | + case UnaryDoubleOpcode.roundToDouble: |
| 210 | + return ConstantValue.fromDouble(x.roundToDouble()); |
| 211 | + case UnaryDoubleOpcode.floorToDouble: |
| 212 | + return ConstantValue.fromDouble(x.floorToDouble()); |
| 213 | + case UnaryDoubleOpcode.ceilToDouble: |
| 214 | + return ConstantValue.fromDouble(x.ceilToDouble()); |
| 215 | + case UnaryDoubleOpcode.truncateToDouble: |
| 216 | + return ConstantValue.fromDouble(x.truncateToDouble()); |
| 217 | + } |
| 218 | + } |
| 219 | +} |
| 220 | + |
| 221 | +/// Constant type arguments. |
| 222 | +class TypeArgumentsConstant extends ast.AuxiliaryConstant { |
| 223 | + final List<ast.DartType> types; |
| 224 | + |
| 225 | + TypeArgumentsConstant(this.types); |
| 226 | + |
| 227 | + @override |
| 228 | + void visitChildren(ast.Visitor v) { |
| 229 | + ast.visitList(types, v); |
| 230 | + } |
| 231 | + |
| 232 | + @override |
| 233 | + void toTextInternal(ast_printer.AstPrinter printer) { |
| 234 | + printer.writeTypeArguments(types); |
| 235 | + } |
| 236 | + |
| 237 | + @override |
| 238 | + String toString() => toStringInternal(); |
| 239 | + |
| 240 | + @override |
| 241 | + int get hashCode => listHashCode(types); |
| 242 | + |
| 243 | + @override |
| 244 | + bool operator ==(Object other) { |
| 245 | + return other is TypeArgumentsConstant && |
| 246 | + listEquals(this.types, other.types); |
| 247 | + } |
| 248 | + |
| 249 | + @override |
| 250 | + ast.DartType getType(StaticTypeContext context) => const ast.DynamicType(); |
| 251 | +} |
0 commit comments