base: Create wide multiply functions in intmath.hh.

These implementations are from the x86 multiply microops. When
multipying two integers of a certain width together, these functions
will produce two values of the same size which hold the upper and lower
part of the multiplication result.

The version which works for 32 bit values and smaller just takes
advantage of 64 bit multiplication using standard types. The 64 bit
version needs to do more work since there isn't a built in standard
facility for doing those sorts of multiplications.

Change-Id: If7b3d3aa174dd13aae6f383772cbc5291181de5d
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/42358
Reviewed-by: Gabe Black <gabe.black@gmail.com>
Maintainer: Gabe Black <gabe.black@gmail.com>
Tested-by: kokoro <noreply+kokoro@google.com>
This commit is contained in:
Gabe Black
2021-03-05 22:48:56 -08:00
parent d7d549408f
commit 6f5668be86
2 changed files with 211 additions and 0 deletions

View File

@@ -108,6 +108,91 @@ divCeil(const T& a, const U& b)
return (a + b - 1) / b;
}
/**
* @ingroup api_base_utils
*/
template <typename T>
static constexpr std::enable_if_t<sizeof(T) <= sizeof(uint32_t)>
mulUnsigned(std::make_unsigned_t<T> &high, std::make_unsigned_t<T> &low,
std::make_unsigned_t<T> val_a, std::make_unsigned_t<T> val_b)
{
uint64_t product = (uint64_t)val_a * (uint64_t)val_b;
low = product;
high = (product >> (sizeof(low) * 8));
};
/**
* @ingroup api_base_utils
*/
template <typename T>
static constexpr std::enable_if_t<sizeof(T) <= sizeof(uint32_t)>
mulSigned(std::make_signed_t<T> &high, std::make_signed_t<T> &low,
std::make_signed_t<T> val_a, std::make_signed_t<T> val_b)
{
uint64_t product = (int64_t)val_a * (int64_t)val_b;
low = product;
high = (product >> (sizeof(low) * 8));
};
/**
* @ingroup api_base_utils
* Multiply two values with place value p.
*
* (A * p + a) * (B * p + b) =
* (A * B) * p^2 + (a * B + A * b) * p + (a * b)
*
* low result = (a * B + A * b) * p + (a * b)
* high result = (A * B) + carry out from low result.
*
* As long as p is at most half the capacity of the underlying type, no
* individual multiplication will overflow. We just have to carefully manage
* carries to avoid losing any during the addition steps.
*/
template <typename T>
static constexpr std::enable_if_t<sizeof(T) == sizeof(uint64_t)>
mulUnsigned(std::make_unsigned_t<T> &high, std::make_unsigned_t<T> &low,
std::make_unsigned_t<T> val_a, std::make_unsigned_t<T> val_b)
{
low = val_a * val_b;
uint64_t A = (uint32_t)(val_a >> 32);
uint64_t a = (uint32_t)val_a;
uint64_t B = (uint32_t)(val_b >> 32);
uint64_t b = (uint32_t)val_b;
uint64_t c1 = 0, c2 = 0; // Carry between place values.
uint64_t ab = a * b, Ab = A * b, aB = a * B, AB = A * B;
c1 = (uint32_t)(ab >> 32);
// Be careful to avoid overflow.
c2 = (c1 >> 1) + (Ab >> 1) + (aB >> 1);
c2 += ((c1 & 0x1) + (Ab & 0x1) + (aB & 0x1)) >> 1;
c2 >>= 31;
high = AB + c2;
}
/**
* @ingroup api_base_utils
*/
template <typename T>
static constexpr std::enable_if_t<sizeof(T) == sizeof(uint64_t)>
mulSigned(std::make_signed_t<T> &high, std::make_signed_t<T> &low,
std::make_signed_t<T> val_a, std::make_signed_t<T> val_b)
{
uint64_t u_high = 0, u_low = 0;
mulUnsigned<T>(u_high, u_low, val_a, val_b);
if (val_a < 0)
u_high -= val_b;
if (val_b < 0)
u_high -= val_a;
high = u_high;
low = u_low;
}
/**
* This function is used to align addresses in memory.
*

View File

@@ -112,6 +112,132 @@ TEST(IntmathTest, divCeil)
EXPECT_EQ(46, divCeil(451, 10));
}
TEST(IntmathTest, mulUnsignedNarrow)
{
uint8_t a = 0xff;
uint8_t b = 0x02;
uint8_t hi;
uint8_t low;
mulUnsigned<uint8_t>(hi, low, a, b);
EXPECT_EQ(hi, 0x1);
EXPECT_EQ(low, 0xfe);
a = 14;
b = 9;
mulUnsigned<uint8_t>(hi, low, a, b);
EXPECT_EQ(hi, 0);
EXPECT_EQ(low, 0x7e);
a = 0;
b = 0x55;
mulUnsigned<uint8_t>(hi, low, a, b);
EXPECT_EQ(hi, 0);
EXPECT_EQ(low, 0);
}
TEST(IntmathTest, mulSignedNarrow)
{
int8_t a = -0x80;
int8_t b = -0x7f;
int8_t hi;
int8_t low;
mulSigned<int8_t>(hi, low, a, b);
EXPECT_EQ(hi, 0x3f);
EXPECT_EQ(low, -0x80);
a = 14;
b = -9;
mulSigned<int8_t>(hi, low, a, b);
EXPECT_EQ(hi, -0x01);
EXPECT_EQ(low, -0x7e);
a = 0;
b = -0x55;
mulSigned<int8_t>(hi, low, a, b);
EXPECT_EQ(hi, 0);
EXPECT_EQ(low, 0);
}
TEST(IntmathTest, mulUnsignedMid)
{
uint32_t a = 0xffffffffULL;
uint32_t b = 0x00000002ULL;
uint32_t hi;
uint32_t low;
mulUnsigned<uint32_t>(hi, low, a, b);
EXPECT_EQ(hi, 0x1);
EXPECT_EQ(low, 0xfffffffe);
a = 68026386;
b = 5152;
mulUnsigned<uint32_t>(hi, low, a, b);
EXPECT_EQ(hi, 0x51);
EXPECT_EQ(low, 0x99c16a40);
a = 0;
b = 0x55555555;
mulUnsigned<uint32_t>(hi, low, a, b);
EXPECT_EQ(hi, 0);
EXPECT_EQ(low, 0);
}
TEST(IntmathTest, mulSignedMid)
{
int32_t a = -0x80000000;
int32_t b = -0x7fffffff;
int32_t hi;
int32_t low;
mulSigned<int32_t>(hi, low, a, b);
EXPECT_EQ(hi, 0x3fffffff);
EXPECT_EQ(low, -0x80000000);
a = -68026386;
b = 5152;
mulSigned<int32_t>(hi, low, a, b);
EXPECT_EQ(hi, -0x52);
EXPECT_EQ(low, -0x99c16a40);
a = 0;
b = -0x55555555;
mulSigned<int32_t>(hi, low, a, b);
EXPECT_EQ(hi, 0);
EXPECT_EQ(low, 0);
}
TEST(IntmathTest, mulUnsignedWide)
{
uint64_t a = 0xffffffffffffffffULL;
uint64_t b = 0x0000000000000002ULL;
uint64_t hi;
uint64_t low;
mulUnsigned<uint64_t>(hi, low, a, b);
EXPECT_EQ(hi, 0x1);
EXPECT_EQ(low, 0xfffffffffffffffe);
a = 0;
b = 0x5555555555555555;
mulUnsigned<uint64_t>(hi, low, a, b);
EXPECT_EQ(hi, 0);
EXPECT_EQ(low, 0);
}
TEST(IntmathTest, mulSignedWide)
{
int64_t a = -0x8000000000000000;
int64_t b = -0x7fffffffffffffff;
int64_t hi;
int64_t low;
mulSigned<int64_t>(hi, low, a, b);
EXPECT_EQ(hi, 0x3fffffffffffffff);
EXPECT_EQ(low, -0x8000000000000000);
a = 0;
b = -0x5555555555555555;
mulSigned<int64_t>(hi, low, a, b);
EXPECT_EQ(hi, 0);
EXPECT_EQ(low, 0);
}
TEST(IntmathTest, roundUp)
{
EXPECT_EQ(4104, roundUp(4101, 4));