| // Copyright (c) 2019, 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. | |
| #include "vm/compiler/backend/evaluator.h" | |
| namespace dart { | |
| static IntegerPtr BinaryIntegerEvaluateRaw(const Integer& left, | |
| const Integer& right, | |
| Token::Kind token_kind) { | |
| switch (token_kind) { | |
| case Token::kTRUNCDIV: | |
| FALL_THROUGH; | |
| case Token::kMOD: | |
| // Check right value for zero. | |
| if (right.Value() == 0) { | |
| break; // Will throw. | |
| } | |
| FALL_THROUGH; | |
| case Token::kADD: | |
| FALL_THROUGH; | |
| case Token::kSUB: | |
| FALL_THROUGH; | |
| case Token::kMUL: | |
| return left.ArithmeticOp(token_kind, right, Heap::kOld); | |
| case Token::kSHL: | |
| FALL_THROUGH; | |
| case Token::kSHR: | |
| FALL_THROUGH; | |
| case Token::kUSHR: | |
| if (right.Value() >= 0) { | |
| return left.ShiftOp(token_kind, right, Heap::kOld); | |
| } | |
| break; | |
| case Token::kBIT_AND: | |
| FALL_THROUGH; | |
| case Token::kBIT_OR: | |
| FALL_THROUGH; | |
| case Token::kBIT_XOR: | |
| return left.BitOp(token_kind, right, Heap::kOld); | |
| case Token::kDIV: | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| return Integer::null(); | |
| } | |
| static IntegerPtr UnaryIntegerEvaluateRaw(const Integer& value, | |
| Token::Kind token_kind, | |
| Zone* zone) { | |
| switch (token_kind) { | |
| case Token::kNEGATE: | |
| return value.ArithmeticOp(Token::kMUL, Smi::Handle(zone, Smi::New(-1)), | |
| Heap::kOld); | |
| case Token::kBIT_NOT: | |
| if (value.IsInteger()) { | |
| return Integer::New(~value.Value(), Heap::kOld); | |
| } | |
| break; | |
| case Token::kPOPCNT: | |
| if (value.IsInteger()) { | |
| return Integer::New(Utils::CountOneBits64(value.Value()), Heap::kOld); | |
| } | |
| break; | |
| case Token::kCTZ: | |
| if (value.IsInteger()) { | |
| const int64_t v = value.Value(); | |
| return Integer::New(v == 0 ? 64 : Utils::CountTrailingZeros64(v), | |
| Heap::kOld); | |
| } | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| return Integer::null(); | |
| } | |
| static IntegerPtr BitLengthEvaluateRaw(const Integer& value, Zone* zone) { | |
| if (value.IsInteger()) { | |
| return Integer::New(Utils::BitLength(value.Value()), Heap::kOld); | |
| } | |
| return Integer::null(); | |
| } | |
| int64_t Evaluator::TruncateTo(int64_t v, Representation r) { | |
| switch (r) { | |
| case kTagged: { | |
| const intptr_t kTruncateBits = | |
| kBitsPerInt64 - (compiler::target::kSmiBits + 1 /*sign bit*/); | |
| return Utils::ShiftLeftWithTruncation(v, kTruncateBits) >> kTruncateBits; | |
| } | |
| case kUnboxedInt32: | |
| return Utils::ShiftLeftWithTruncation(v, kBitsPerInt32) >> kBitsPerInt32; | |
| case kUnboxedUint32: | |
| return v & kMaxUint32; | |
| case kUnboxedInt64: | |
| return v; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| IntegerPtr Evaluator::BinaryIntegerEvaluate(const Object& left, | |
| const Object& right, | |
| Token::Kind token_kind, | |
| bool is_truncating, | |
| Representation representation, | |
| Thread* thread) { | |
| if (!left.IsInteger() || !right.IsInteger()) { | |
| return Integer::null(); | |
| } | |
| Zone* zone = thread->zone(); | |
| const Integer& left_int = Integer::Cast(left); | |
| const Integer& right_int = Integer::Cast(right); | |
| Integer& result = Integer::Handle( | |
| zone, BinaryIntegerEvaluateRaw(left_int, right_int, token_kind)); | |
| if (!result.IsNull()) { | |
| if (is_truncating) { | |
| const int64_t truncated = TruncateTo(result.Value(), representation); | |
| result = Integer::New(truncated, Heap::kOld); | |
| ASSERT(FlowGraph::IsConstantRepresentable( | |
| result, representation, /*tagged_value_must_be_smi=*/true)); | |
| } else if (!FlowGraph::IsConstantRepresentable( | |
| result, representation, /*tagged_value_must_be_smi=*/true)) { | |
| // If this operation is not truncating it would deoptimize on overflow. | |
| // Check that we match this behavior and don't produce a value that is | |
| // larger than something this operation can produce. We could have | |
| // specialized instructions that use this value under this assumption. | |
| return Integer::null(); | |
| } | |
| result ^= result.Canonicalize(thread); | |
| } | |
| return result.ptr(); | |
| } | |
| IntegerPtr Evaluator::UnaryIntegerEvaluate(const Object& value, | |
| Token::Kind token_kind, | |
| Representation representation, | |
| Thread* thread) { | |
| if (!value.IsInteger()) { | |
| return Integer::null(); | |
| } | |
| Zone* zone = thread->zone(); | |
| const Integer& value_int = Integer::Cast(value); | |
| Integer& result = Integer::Handle( | |
| zone, UnaryIntegerEvaluateRaw(value_int, token_kind, zone)); | |
| if (!result.IsNull()) { | |
| if (!FlowGraph::IsConstantRepresentable( | |
| result, representation, | |
| /*tagged_value_must_be_smi=*/true)) { | |
| // If this operation is not truncating it would deoptimize on overflow. | |
| // Check that we match this behavior and don't produce a value that is | |
| // larger than something this operation can produce. We could have | |
| // specialized instructions that use this value under this assumption. | |
| return Integer::null(); | |
| } | |
| result ^= result.Canonicalize(thread); | |
| } | |
| return result.ptr(); | |
| } | |
| IntegerPtr Evaluator::BitLengthEvaluate(const Object& value, | |
| Representation representation, | |
| Thread* thread) { | |
| if (!value.IsInteger()) { | |
| return Integer::null(); | |
| } | |
| Zone* zone = thread->zone(); | |
| const Integer& value_int = Integer::Cast(value); | |
| Integer& result = | |
| Integer::Handle(zone, BitLengthEvaluateRaw(value_int, zone)); | |
| if (!result.IsNull()) { | |
| if (!FlowGraph::IsConstantRepresentable( | |
| result, representation, | |
| /*tagged_value_must_be_smi=*/true)) { | |
| // If this operation is not truncating it would deoptimize on overflow. | |
| // Check that we match this behavior and don't produce a value that is | |
| // larger than something this operation can produce. We could have | |
| // specialized instructions that use this value under this assumption. | |
| return Integer::null(); | |
| } | |
| result ^= result.Canonicalize(thread); | |
| } | |
| return result.ptr(); | |
| } | |
| double Evaluator::EvaluateUnaryDoubleOp(const double value, | |
| Token::Kind token_kind, | |
| Representation representation) { | |
| // The different set of operations for float32 and float64 is due to the | |
| // different set of operations made available by dart:core.double and | |
| // dart:typed_data.Float64x2 versus dart:typed_data.Float32x4. | |
| if (representation == kUnboxedDouble) { | |
| switch (token_kind) { | |
| case Token::kABS: | |
| return fabs(value); | |
| case Token::kNEGATE: | |
| return -value; | |
| case Token::kSQRT: | |
| return sqrt(value); | |
| case Token::kSQUARE: | |
| return value * value; | |
| case Token::kTRUNCATE: | |
| return trunc(value); | |
| case Token::kFLOOR: | |
| return floor(value); | |
| case Token::kCEILING: | |
| return ceil(value); | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } else { | |
| ASSERT(representation == kUnboxedFloat); | |
| switch (token_kind) { | |
| case Token::kABS: | |
| return fabsf(static_cast<float>(value)); | |
| case Token::kNEGATE: | |
| return -static_cast<float>(value); | |
| case Token::kRECIPROCAL: | |
| return 1.0f / static_cast<float>(value); | |
| case Token::kRECIPROCAL_SQRT: | |
| return sqrtf(1.0f / static_cast<float>(value)); | |
| case Token::kSQRT: | |
| return sqrtf(static_cast<float>(value)); | |
| case Token::kSQUARE: | |
| return static_cast<float>(value) * static_cast<float>(value); | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| } | |
| template <typename T> | |
| static T MinMax(T left, T right, bool is_min) { | |
| if (isnan(left)) return left; | |
| if (isnan(right)) return right; | |
| if (left == right) { | |
| return (signbit(left) != 0) == is_min ? left : right; | |
| } | |
| return (left < right) == is_min ? left : right; | |
| } | |
| double Evaluator::EvaluateBinaryDoubleOp(const double left, | |
| const double right, | |
| Token::Kind token_kind, | |
| Representation representation) { | |
| if (representation == kUnboxedDouble) { | |
| switch (token_kind) { | |
| case Token::kADD: | |
| return left + right; | |
| case Token::kSUB: | |
| return left - right; | |
| case Token::kMUL: | |
| return left * right; | |
| case Token::kDIV: | |
| return Utils::DivideAllowZero(left, right); | |
| case Token::kMIN: | |
| return MinMax(left, right, /*is_min=*/true); | |
| case Token::kMAX: | |
| return MinMax(left, right, /*is_min=*/false); | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } else { | |
| ASSERT(representation == kUnboxedFloat); | |
| switch (token_kind) { | |
| case Token::kADD: | |
| return static_cast<float>(left) + static_cast<float>(right); | |
| case Token::kSUB: | |
| return static_cast<float>(left) - static_cast<float>(right); | |
| case Token::kMUL: | |
| return static_cast<float>(left) * static_cast<float>(right); | |
| case Token::kDIV: | |
| return Utils::DivideAllowZero(static_cast<float>(left), | |
| static_cast<float>(right)); | |
| case Token::kMIN: | |
| return MinMax(static_cast<float>(left), static_cast<float>(right), | |
| /*is_min=*/true); | |
| case Token::kMAX: | |
| return MinMax(static_cast<float>(left), static_cast<float>(right), | |
| /*is_min=*/false); | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| } | |
| bool Evaluator::ToIntegerConstant(Value* value, int64_t* result) { | |
| if (!value->BindsToConstant()) { | |
| UnboxInstr* unbox = value->definition()->AsUnbox(); | |
| if (unbox != nullptr) { | |
| switch (unbox->representation()) { | |
| case kUnboxedDouble: | |
| case kUnboxedInt64: | |
| return ToIntegerConstant(unbox->value(), result); | |
| case kUnboxedUint32: | |
| if (ToIntegerConstant(unbox->value(), result)) { | |
| *result = Evaluator::TruncateTo(*result, kUnboxedUint32); | |
| return true; | |
| } | |
| break; | |
| // No need to handle Unbox<Int32>(Constant(C)) because it gets | |
| // canonicalized to UnboxedConstant<Int32>(C). | |
| case kUnboxedInt32: | |
| default: | |
| break; | |
| } | |
| } | |
| return false; | |
| } | |
| const Object& constant = value->BoundConstant(); | |
| if (constant.IsDouble()) { | |
| const Double& double_constant = Double::Cast(constant); | |
| *result = Utils::SafeDoubleToInt<int64_t>(double_constant.value()); | |
| return (static_cast<double>(*result) == double_constant.value()); | |
| } else if (constant.IsInteger()) { | |
| *result = Integer::Cast(constant).Value(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| } // namespace dart |