Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 106 additions & 73 deletions src/hotspot/share/opto/mulnode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,44 +281,115 @@ Node *MulINode::Ideal(PhaseGVN *phase, bool can_reshape) {
return res; // Return final result
}

//------------------------------mul_ring---------------------------------------
// Compute the product type of two integer ranges into this node.
const Type *MulINode::mul_ring(const Type *t0, const Type *t1) const {
const TypeInt *r0 = t0->is_int(); // Handy access
const TypeInt *r1 = t1->is_int();

// Fetch endpoints of all ranges
jint lo0 = r0->_lo;
double a = (double)lo0;
jint hi0 = r0->_hi;
double b = (double)hi0;
jint lo1 = r1->_lo;
double c = (double)lo1;
jint hi1 = r1->_hi;
double d = (double)hi1;

// Compute all endpoints & check for overflow
int32_t A = java_multiply(lo0, lo1);
if( (double)A != a*c ) return TypeInt::INT; // Overflow?
int32_t B = java_multiply(lo0, hi1);
if( (double)B != a*d ) return TypeInt::INT; // Overflow?
int32_t C = java_multiply(hi0, lo1);
if( (double)C != b*c ) return TypeInt::INT; // Overflow?
int32_t D = java_multiply(hi0, hi1);
if( (double)D != b*d ) return TypeInt::INT; // Overflow?

if( A < B ) { lo0 = A; hi0 = B; } // Sort range endpoints
else { lo0 = B; hi0 = A; }
if( C < D ) {
if( C < lo0 ) lo0 = C;
if( D > hi0 ) hi0 = D;
} else {
if( D < lo0 ) lo0 = D;
if( C > hi0 ) hi0 = C;
// Classes to perform mul_ring() for MulI/MulLNode.
//
// This class checks if all cross products of the left and right input of a multiplication have the same "overflow value".
// Without overflow/underflow:
// Product is positive? High signed multiplication result: 0
// Product is negative? High signed multiplication result: -1
//
// We normalize these values (see normalize_overflow_value()) such that we get the same "overflow value" by adding 1 if
// the product is negative. This allows us to compare all the cross product "overflow values". If one is different,
// compared to the others, then we know that this multiplication has a different number of over- or underflows compared
// to the others. In this case, we need to use bottom type and cannot guarantee a better type. Otherwise, we can take
// the min und max of all computed cross products as type of this Mul node.
template<typename IntegerType>
class IntegerMulRing {
using NativeType = std::conditional_t<std::is_same<TypeInt, IntegerType>::value, jint, jlong>;

NativeType _lo_left;
NativeType _lo_right;
NativeType _hi_left;
NativeType _hi_right;
NativeType _lo_lo_product;
NativeType _lo_hi_product;
NativeType _hi_lo_product;
NativeType _hi_hi_product;
short _widen_left;
short _widen_right;

static const Type* overflow_type();
static NativeType multiply_high_signed_overflow_value(NativeType x, NativeType y);

// Pre-compute cross products which are used at several places
void compute_cross_products() {
_lo_lo_product = java_multiply(_lo_left, _lo_right);
_lo_hi_product = java_multiply(_lo_left, _hi_right);
_hi_lo_product = java_multiply(_hi_left, _lo_right);
_hi_hi_product = java_multiply(_hi_left, _hi_right);
}

bool cross_products_not_same_overflow() const {
const NativeType lo_lo_high_product = multiply_high_signed_overflow_value(_lo_left, _lo_right);
const NativeType lo_hi_high_product = multiply_high_signed_overflow_value(_lo_left, _hi_right);
const NativeType hi_lo_high_product = multiply_high_signed_overflow_value(_hi_left, _lo_right);
const NativeType hi_hi_high_product = multiply_high_signed_overflow_value(_hi_left, _hi_right);
return lo_lo_high_product != lo_hi_high_product ||
lo_hi_high_product != hi_lo_high_product ||
hi_lo_high_product != hi_hi_high_product;
}

static NativeType normalize_overflow_value(const NativeType x, const NativeType y, NativeType result) {
return java_multiply(x, y) < 0 ? result + 1 : result;
}

public:
IntegerMulRing(const IntegerType* left, const IntegerType* right) : _lo_left(left->_lo), _lo_right(right->_lo),
_hi_left(left->_hi), _hi_right(right->_hi), _widen_left(left->_widen), _widen_right(right->_widen) {
compute_cross_products();
}

// Compute the product type by multiplying the two input type ranges. We take the minimum and maximum of all possible
// values (requires 4 multiplications of all possible combinations of the two range boundary values). If any of these
// multiplications overflows/underflows, we need to make sure that they all have the same number of overflows/underflows
// If that is not the case, we return the bottom type to cover all values due to the inconsistent overflows/underflows).
const Type* compute() const {
if (cross_products_not_same_overflow()) {
return overflow_type();
}
const NativeType min = MIN4(_lo_lo_product, _lo_hi_product, _hi_lo_product, _hi_hi_product);
const NativeType max = MAX4(_lo_lo_product, _lo_hi_product, _hi_lo_product, _hi_hi_product);
return IntegerType::make(min, max, MAX2(_widen_left, _widen_right));
}
return TypeInt::make(lo0, hi0, MAX2(r0->_widen,r1->_widen));
};


template <>
const Type* IntegerMulRing<TypeInt>::overflow_type() {
return TypeInt::INT;
}

template <>
jint IntegerMulRing<TypeInt>::multiply_high_signed_overflow_value(const jint x, const jint y) {
const jlong x_64 = x;
const jlong y_64 = y;
const jlong product = x_64 * y_64;
const jint result = (jint)((uint64_t)product >> 32u);
return normalize_overflow_value(x, y, result);
}

template <>
const Type* IntegerMulRing<TypeLong>::overflow_type() {
return TypeLong::LONG;
}

template <>
jlong IntegerMulRing<TypeLong>::multiply_high_signed_overflow_value(const jlong x, const jlong y) {
const jlong result = multiply_high_signed(x, y);
return normalize_overflow_value(x, y, result);
}

// Compute the product type of two integer ranges into this node.
const Type* MulINode::mul_ring(const Type* type_left, const Type* type_right) const {
const IntegerMulRing<TypeInt> integer_mul_ring(type_left->is_int(), type_right->is_int());
return integer_mul_ring.compute();
}

// Compute the product type of two long ranges into this node.
const Type* MulLNode::mul_ring(const Type* type_left, const Type* type_right) const {
const IntegerMulRing<TypeLong> integer_mul_ring(type_left->is_long(), type_right->is_long());
return integer_mul_ring.compute();
}

//=============================================================================
//------------------------------Ideal------------------------------------------
Expand Down Expand Up @@ -377,44 +448,6 @@ Node *MulLNode::Ideal(PhaseGVN *phase, bool can_reshape) {
return res; // Return final result
}

//------------------------------mul_ring---------------------------------------
// Compute the product type of two integer ranges into this node.
const Type *MulLNode::mul_ring(const Type *t0, const Type *t1) const {
const TypeLong *r0 = t0->is_long(); // Handy access
const TypeLong *r1 = t1->is_long();

// Fetch endpoints of all ranges
jlong lo0 = r0->_lo;
double a = (double)lo0;
jlong hi0 = r0->_hi;
double b = (double)hi0;
jlong lo1 = r1->_lo;
double c = (double)lo1;
jlong hi1 = r1->_hi;
double d = (double)hi1;

// Compute all endpoints & check for overflow
jlong A = java_multiply(lo0, lo1);
if( (double)A != a*c ) return TypeLong::LONG; // Overflow?
jlong B = java_multiply(lo0, hi1);
if( (double)B != a*d ) return TypeLong::LONG; // Overflow?
jlong C = java_multiply(hi0, lo1);
if( (double)C != b*c ) return TypeLong::LONG; // Overflow?
jlong D = java_multiply(hi0, hi1);
if( (double)D != b*d ) return TypeLong::LONG; // Overflow?

if( A < B ) { lo0 = A; hi0 = B; } // Sort range endpoints
else { lo0 = B; hi0 = A; }
if( C < D ) {
if( C < lo0 ) lo0 = C;
if( D > hi0 ) hi0 = D;
} else {
if( D < lo0 ) lo0 = D;
if( C > hi0 ) hi0 = C;
}
return TypeLong::make(lo0, hi0, MAX2(r0->_widen,r1->_widen));
}

//=============================================================================
//------------------------------mul_ring---------------------------------------
// Compute the product type of two double ranges into this node.
Expand Down
47 changes: 47 additions & 0 deletions src/hotspot/share/utilities/globalDefinitions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,22 @@ inline TYPE NAME (TYPE lhs, jint rhs) { \

JAVA_INTEGER_SHIFT_OP(<<, java_shift_left, jint, juint)
JAVA_INTEGER_SHIFT_OP(<<, java_shift_left, jlong, julong)

// For signed shift right, assume C++ implementation >> sign extends.
//
// C++14 5.8/3: In the description of "E1 >> E2" it says "If E1 has a signed type
// and a negative value, the resulting value is implementation-defined."
//
// However, C++20 7.6.7/3 further defines integral arithmetic, as part of
// requiring two's-complement behavior.
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r3.html
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1236r1.html
// The corresponding C++20 text is "Right-shift on signed integral types is an
// arithmetic right shift, which performs sign-extension."
//
// As discussed in the two's complement proposal, all known modern C++ compilers
// already behave that way. And it is unlikely any would go off and do something
// different now, with C++20 tightening things up.
JAVA_INTEGER_SHIFT_OP(>>, java_shift_right, jint, jint)
JAVA_INTEGER_SHIFT_OP(>>, java_shift_right, jlong, jlong)
// For >>> use C++ unsigned >>.
Expand Down Expand Up @@ -1266,6 +1281,38 @@ SATURATED_INTEGER_OP(+, saturated_add, uint, uint)

#undef SATURATED_INTEGER_OP

// Taken from rom section 8-2 of Henry S. Warren, Jr., Hacker's Delight (2nd ed.) (Addison Wesley, 2013), 173-174.
inline uint64_t multiply_high_unsigned(const uint64_t x, const uint64_t y) {
const uint64_t x1 = x >> 32u;
const uint64_t x2 = x & 0xFFFFFFFF;
const uint64_t y1 = y >> 32u;
const uint64_t y2 = y & 0xFFFFFFFF;
const uint64_t z2 = x2 * y2;
const uint64_t t = x1 * y2 + (z2 >> 32u);
uint64_t z1 = t & 0xFFFFFFFF;
const uint64_t z0 = t >> 32u;
z1 += x2 * y1;

return x1 * y1 + z0 + (z1 >> 32u);
}

// Taken from java.lang.Math::multiplyHigh which uses the technique from section 8-2 of Henry S. Warren, Jr.,
// Hacker's Delight (2nd ed.) (Addison Wesley, 2013), 173-174 but adapted for signed longs.
inline int64_t multiply_high_signed(const int64_t x, const int64_t y) {
const jlong x1 = java_shift_right((jlong)x, 32);
const jlong x2 = x & 0xFFFFFFFF;
const jlong y1 = java_shift_right((jlong)y, 32);
const jlong y2 = y & 0xFFFFFFFF;

const uint64_t z2 = x2 * y2;
const int64_t t = x1 * y2 + (z2 >> 32u); // Unsigned shift
int64_t z1 = t & 0xFFFFFFFF;
const int64_t z0 = java_shift_right((jlong)t, 32);
z1 += x2 * y1;

return x1 * y1 + z0 + java_shift_right((jlong)z1, 32);
}

// Dereference vptr
// All C++ compilers that we know of have the vtbl pointer in the first
// word. If there are exceptions, this function needs to be made compiler
Expand Down
Loading