forked from OSchip/llvm-project
[flang] Testing framework, debugging of fixed-point code.
Original-commit: flang-compiler/f18@a8fb2d75a5 Reviewed-on: https://github.com/flang-compiler/f18/pull/101 Tree-same-pre-rewrite: false
This commit is contained in:
parent
5bc907602c
commit
66107803fd
|
@ -106,3 +106,4 @@ configure_file(
|
|||
|
||||
enable_testing()
|
||||
add_test(NAME LZBC COMMAND leading-zero-bit-count-test)
|
||||
add_test(NAME FixedPoint COMMAND fixed-point-test)
|
||||
|
|
|
@ -15,80 +15,104 @@
|
|||
#ifndef FORTRAN_EVALUATE_FIXED_POINT_H_
|
||||
#define FORTRAN_EVALUATE_FIXED_POINT_H_
|
||||
|
||||
// Emulates integers of a nearly arbitrary fixed size for use when the C++
|
||||
// environment does not support it. The size must be some multiple of
|
||||
// 32 bits. Signed and unsigned operations are distinct.
|
||||
// Emulates integers of a arbitrary static size for use when the C++
|
||||
// environment does not support it. Signed and unsigned operations are
|
||||
// distinguished by member function interface; the data are typeless.
|
||||
|
||||
#include "leading-zero-bit-count.h"
|
||||
#include <cinttypes>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
|
||||
namespace Fortran::evaluate {
|
||||
|
||||
enum class Ordering { Less, Equal, Greater };
|
||||
static constexpr Ordering Reverse Ordering ordering) {
|
||||
static constexpr Ordering Reverse(Ordering ordering) {
|
||||
if (ordering == Ordering::Less) {
|
||||
return Ordering::Greater;
|
||||
}
|
||||
if (ordering == Ordering::Greater) {
|
||||
} else if (ordering == Ordering::Greater) {
|
||||
return Ordering::Less;
|
||||
} else {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
return Ordering::Equal;
|
||||
}
|
||||
|
||||
typedef <int BITS>
|
||||
// Implement an integer as an assembly of smaller (i.e., 32-bit) integers.
|
||||
// These are stored in little-endian order. To facilitate exhaustive
|
||||
// testing of what would otherwise be more rare edge cases, this template class
|
||||
// may be configured to use other part types &/or partial fields in the
|
||||
// parts.
|
||||
template <int BITS, int PARTBITS=32,
|
||||
typename PART = std::uint32_t, typename BIGPART = std::uint64_t>
|
||||
class FixedPoint {
|
||||
private:
|
||||
using Part = std::uint32_t;
|
||||
using BigPart = std::uint64_t;
|
||||
public:
|
||||
static constexpr int bits{BITS};
|
||||
static constexpr int partBits{CHAR_BIT * sizeof(Part)};
|
||||
static_assert(bits >= partBits);
|
||||
static_assert(sizeof(BigPart) == 2 * partBits);
|
||||
static constexpr int parts{bits / partBits};
|
||||
static_assert(bits * partBits == parts); // no partial part
|
||||
static constexpr int partBits{PARTBITS};
|
||||
using Part = PART;
|
||||
using BigPart = BIGPART;
|
||||
static_assert(sizeof(BigPart) >= 2 * sizeof(Part));
|
||||
|
||||
private:
|
||||
static constexpr int maxPartBits{CHAR_BIT * sizeof(Part)};
|
||||
static_assert(partBits > 0 && partBits <= maxPartBits);
|
||||
static constexpr int extraPartBits{maxPartBits - partBits};
|
||||
static constexpr int parts{(bits + partBits - 1) / partBits};
|
||||
static_assert(parts >= 1);
|
||||
static constexpr int extraTopPartBits{extraPartBits + (parts * partBits) - bits};
|
||||
static constexpr int topPartBits{maxPartBits - extraTopPartBits};
|
||||
static_assert(topPartBits > 0 && topPartBits <= partBits);
|
||||
static_assert((parts - 1) * partBits + topPartBits == bits);
|
||||
static constexpr Part partMask{static_cast<Part>(~0) >> extraPartBits};
|
||||
static constexpr Part topPartMask{static_cast<Part>(~0) >> extraTopPartBits};
|
||||
|
||||
public:
|
||||
FixedPoint() = delete;
|
||||
constexpr FixedPoint() {} // zero
|
||||
constexpr FixedPoint(const FixedPoint &) = default;
|
||||
constexpr FixedPoint(std::uint64_t n) {
|
||||
for (int j{0}; j < parts; ++j) {
|
||||
part_[j] = n;
|
||||
for (int j{0}; j < parts - 1; ++j) {
|
||||
part_[j] = n & partMask;
|
||||
if constexpr (partBits < 64) {
|
||||
n >>= partBits;
|
||||
} else {
|
||||
n = 0;
|
||||
}
|
||||
}
|
||||
part_[parts - 1] = n & topPartMask;
|
||||
}
|
||||
constexpr FixedPoint(std::int64_t n) {
|
||||
std::int64_t signExtension{-(n < 0) << partBits};
|
||||
for (int j{0}; j < parts; ++j) {
|
||||
part_[j] = n;
|
||||
std::int64_t signExtension{-(n < 0)};
|
||||
signExtension <<= partBits;
|
||||
for (int j{0}; j < parts - 1; ++j) {
|
||||
part_[j] = n & partMask;
|
||||
if constexpr (partBits < 64) {
|
||||
n = (n >> partBits) | signExtension;
|
||||
} else {
|
||||
n = signExtension;
|
||||
}
|
||||
}
|
||||
part_[parts - 1] = n & topPartMask;
|
||||
}
|
||||
|
||||
constexpr FixedPoint &operator=(const FixedPoint &) = default;
|
||||
|
||||
constexpr Ordering CompareToZeroUnsigned() const {
|
||||
constexpr bool IsZero() const {
|
||||
for (int j{0}; j < parts; ++j) {
|
||||
if (part_[j] != 0) {
|
||||
return Ordering::Greater;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return Ordering::Equal;
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool IsNegative() const {
|
||||
return (part_[parts-1] >> (topPartBits - 1)) & 1;
|
||||
}
|
||||
|
||||
constexpr Ordering CompareToZeroSigned() const {
|
||||
if (IsNegative()) {
|
||||
return Ordering::Less;
|
||||
}
|
||||
return CompareToZeroUnsigned();
|
||||
return IsZero() ? Ordering::Equal : Ordering::Greater;
|
||||
}
|
||||
|
||||
constexpr Ordering CompareUnsigned(const FixedPoint &y) const {
|
||||
|
@ -104,56 +128,64 @@ public:
|
|||
}
|
||||
|
||||
constexpr Ordering CompareSigned(const FixedPoint &y) const {
|
||||
if (IsNegative()) {
|
||||
if (!y.IsNegative()) {
|
||||
return Ordering::Less;
|
||||
}
|
||||
return Reverse(CompareUnsigned(y));
|
||||
} else if (y.IsNegative()) {
|
||||
return Ordering::Greater;
|
||||
} else {
|
||||
return CompareUnsigned(y);
|
||||
bool isNegative{IsNegative()};
|
||||
if (isNegative != y.IsNegative()) {
|
||||
return isNegative ? Ordering::Less : Ordering::Greater;
|
||||
}
|
||||
return CompareUnsigned(y);
|
||||
}
|
||||
|
||||
constexpr int LeadingZeroBitCount() const {
|
||||
for (int j{0}; j < parts; ++j) {
|
||||
if (part_[j] != 0) {
|
||||
return (j * partBits) + evaluate::LeadingZeroBitCount(part_[j]);
|
||||
if (part_[parts-1] != 0) {
|
||||
int lzbc{evaluate::LeadingZeroBitCount(part_[parts-1])};
|
||||
return lzbc - extraTopPartBits;
|
||||
}
|
||||
int upperZeroes{topPartBits};
|
||||
for (int j{1}; j < parts; ++j) {
|
||||
if (Part p{part_[parts - 1 - j]}) {
|
||||
int lzbc{evaluate::LeadingZeroBitCount(p)};
|
||||
return upperZeroes + lzbc - extraPartBits;
|
||||
}
|
||||
upperZeroes += partBits;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
constexpr std::uint64_t ToUInt64() const {
|
||||
std::uint64_t n{0};
|
||||
int filled{0};
|
||||
static constexpr int toFill{bits < 64 ? bits : 64};
|
||||
for (int j{0}; filled < 64; ++j, filled += partBits) {
|
||||
std::uint64_t n{part_[0]};
|
||||
int filled{partBits};
|
||||
for (int j{1}; filled < 64 && j < parts; ++j, filled += partBits) {
|
||||
n |= part_[j] << filled;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
constexpr std::int64_t ToInt64() const {
|
||||
return static_cast<std::int64_t>(ToUInt64());
|
||||
std::int64_t signExtended = ToUInt64();
|
||||
if (bits < 64) {
|
||||
signExtended |= -(signExtended >> (bits - 1)) << bits;
|
||||
}
|
||||
return signExtended;
|
||||
}
|
||||
|
||||
constexpr void OnesComplement() {
|
||||
for (int j{0}; j < parts; ++j) {
|
||||
part_[j] = ~part_[j];
|
||||
for (int j{0}; j + 1 < parts; ++j) {
|
||||
part_[j] = ~part_[j] & partMask;
|
||||
}
|
||||
part_[parts-1] = ~part_[parts-1] & topPartMask;
|
||||
}
|
||||
|
||||
// Returns true on overflow (i.e., negating the most negative number)
|
||||
// Returns true on overflow (i.e., negating the most negative signed number)
|
||||
constexpr bool TwosComplement() {
|
||||
Part carry{1};
|
||||
for (int j{0}; j < parts; ++j) {
|
||||
for (int j{0}; j + 1 < parts; ++j) {
|
||||
Part newCarry{part_[j] == 0 && carry};
|
||||
part_[j] = ~part_[j] + carry;
|
||||
part_[j] = (~part_[j] + carry) & partMask;
|
||||
carry = newCarry;
|
||||
}
|
||||
return carry != IsNegative();
|
||||
Part before{part_[parts-1]};
|
||||
part_[parts-1] = (~before + carry) & topPartMask;
|
||||
return before != 0 && part_[parts-1] == before;
|
||||
}
|
||||
|
||||
constexpr void And(const FixedPoint &y) {
|
||||
|
@ -176,25 +208,27 @@ public:
|
|||
|
||||
constexpr void ShiftLeft(int count) {
|
||||
if (count < 0) {
|
||||
ShiftRight(-count);
|
||||
} else {
|
||||
ShiftRightLogical(-count);
|
||||
} else if (count > 0) {
|
||||
int shiftParts{count / partBits};
|
||||
int bitShift{count - partBits * shiftParts};
|
||||
int j{parts-1};
|
||||
if (bitShift == 0) {
|
||||
for (; j >= shiftParts; --j) {
|
||||
part_[j] = part_[j - shiftParts];
|
||||
part_[j] = part_[j - shiftParts] & PartMask(j);
|
||||
}
|
||||
for (; j >= 0; --j) {
|
||||
part_[j] = 0;
|
||||
}
|
||||
} else {
|
||||
for (; j > shiftParts; --j) {
|
||||
part_[j] = (part_[j - shiftParts] << bitShift) |
|
||||
(part_[j - shiftParts - 1] >> (partBits - bitShift);
|
||||
part_[j] = ((part_[j - shiftParts] << bitShift) |
|
||||
(part_[j - shiftParts - 1] >> (partBits - bitShift))) &
|
||||
PartMask(j);
|
||||
}
|
||||
if (j == shiftParts) {
|
||||
part_[j--] = part_[0] << bitShift;
|
||||
part_[j] = (part_[0] << bitShift) & PartMask(j);
|
||||
--j;
|
||||
}
|
||||
for (; j >= 0; --j) {
|
||||
part_[j] = 0;
|
||||
|
@ -206,7 +240,7 @@ public:
|
|||
constexpr void ShiftRightLogical(int count) { // i.e., unsigned
|
||||
if (count < 0) {
|
||||
ShiftLeft(-count);
|
||||
} else {
|
||||
} else if (count > 0) {
|
||||
int shiftParts{count / partBits};
|
||||
int bitShift{count - partBits * shiftParts};
|
||||
int j{0};
|
||||
|
@ -219,8 +253,9 @@ public:
|
|||
}
|
||||
} else {
|
||||
for (; j + shiftParts + 1 < parts; ++j) {
|
||||
part_[j] = (part_[j + shiftParts] >> bitShift) |
|
||||
(part_[j + shiftParts + 1] << (partBits - bitShift);
|
||||
part_[j] = ((part_[j + shiftParts] >> bitShift) |
|
||||
(part_[j + shiftParts + 1] << (partBits - bitShift))) &
|
||||
partMask;
|
||||
}
|
||||
if (j + shiftParts + 1 == parts) {
|
||||
part_[j++] = part_[parts - 1] >> bitShift;
|
||||
|
@ -233,27 +268,36 @@ public:
|
|||
}
|
||||
|
||||
// Returns carry out.
|
||||
constexpr bool AddUnsigned(const FixedPoint &y, bool carryIn{false}) {
|
||||
constexpr bool AddUnsigned(const FixedPoint &y, bool carryIn = false) {
|
||||
BigPart carry{carryIn};
|
||||
for (int j{0}; j < parts; ++j) {
|
||||
for (int j{0}; j + 1 < parts; ++j) {
|
||||
carry += part_[j];
|
||||
part_[j] = carry += y.part_[j];
|
||||
carry >>= 32;
|
||||
carry += y.part_[j];
|
||||
part_[j] = carry & partMask;
|
||||
carry >>= partBits;
|
||||
}
|
||||
return carry != 0;
|
||||
carry += part_[parts-1];
|
||||
carry += y.part_[parts-1];
|
||||
part_[parts-1] = carry & topPartMask;
|
||||
return carry > topPartMask;
|
||||
}
|
||||
|
||||
// Returns true on overflow.
|
||||
constexpr bool AddSigned(const FixedPoint &y) {
|
||||
bool carry{AddUnsigned(y)};
|
||||
return carry != IsNegative();
|
||||
bool isNegative{IsNegative()};
|
||||
bool sameSign{isNegative == y.IsNegative()};
|
||||
AddUnsigned(y);
|
||||
return sameSign && IsNegative() != isNegative;
|
||||
}
|
||||
|
||||
// Returns true on overflow.
|
||||
constexpr bool SubtractSigned(const FixedPoint &y) {
|
||||
bool isNegative{IsNegative()};
|
||||
bool sameSign{isNegative == y.IsNegative()};
|
||||
FixedPoint minusy{y};
|
||||
minusy.TwosComplement();
|
||||
return AddSigned(minusy);
|
||||
AddUnsigned(minusy);
|
||||
return !sameSign && IsNegative() != isNegative;
|
||||
}
|
||||
|
||||
// Overwrites *this with lower half of full product.
|
||||
|
@ -263,10 +307,11 @@ public:
|
|||
if (part_[j] != 0) {
|
||||
for (int k{0}; k < parts; ++k) {
|
||||
if (y.part_[k] != 0) {
|
||||
BigPart x{part_[j]};
|
||||
x *= y.part_[k];
|
||||
BigPart xy{part_[j]};
|
||||
xy *= y.part_[k];
|
||||
for (int to{j+k}; xy != 0; ++to) {
|
||||
product[to] = xy += product[to];
|
||||
xy += product[to];
|
||||
product[to] = xy & partMask;
|
||||
xy >>= partBits;
|
||||
}
|
||||
}
|
||||
|
@ -277,6 +322,11 @@ public:
|
|||
part_[j] = product[j];
|
||||
upper.part_[j] = product[j + parts];
|
||||
}
|
||||
if (topPartBits < partBits) {
|
||||
upper.ShiftLeft(partBits - topPartBits);
|
||||
upper.part_[0] |= part_[parts-1] >> topPartBits;
|
||||
part_[parts-1] &= topPartMask;
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrites *this with lower half of full product.
|
||||
|
@ -301,53 +351,130 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
// Overwrites *this with quotient.
|
||||
constexpr void DivideUnsigned(const FixedPoint &divisor, FixedPoint &remainder) {
|
||||
// Overwrites *this with quotient. Returns true on division by zero.
|
||||
constexpr bool DivideUnsigned(const FixedPoint &divisor, FixedPoint &remainder) {
|
||||
remainder.Clear();
|
||||
if (divisor.IsZero()) {
|
||||
RightMask(bits);
|
||||
return true;
|
||||
}
|
||||
FixedPoint top{*this};
|
||||
*this = remainder = FixedPoint{0};
|
||||
Clear();
|
||||
int bitsDone{top.LeadingZeroBitCount()};
|
||||
top.ShiftLeft(bitsDone);
|
||||
for (; bitsDone < bits; ++bitsDone) {
|
||||
remainder.AddUnsigned(remainder, top.AddUnsigned(top));
|
||||
bool nextBit{remainder.CompareUnsigned(divisor) != Ordering::Less};
|
||||
quotient.AddUnsigned(quotient, nextBit);
|
||||
AddUnsigned(*this, nextBit);
|
||||
if (nextBit) {
|
||||
remainder.SubtractSigned(divisor);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Overwrites *this with quotient. Returns true on overflow (viz.,
|
||||
// the most negative value divided by -1) or division by zero.
|
||||
// A nonzero remainder has the sign of the dividend, i.e., it is
|
||||
// the MOD intrinsic (X-INT(X/Y)*Y), not MODULO.
|
||||
constexpr bool DivideSigned(FixedPoint divisor, FixedPoint &remainder) {
|
||||
bool negateQuotient{false}, negateRemainder{false};
|
||||
if (IsNegative()) {
|
||||
negateQuotient = negateRemainder = true;
|
||||
TwosComplement();
|
||||
}
|
||||
bool dividendIsNegative{IsNegative()};
|
||||
bool negateQuotient{dividendIsNegative};
|
||||
Ordering divisorOrdering{divisor.CompareToZeroSigned()};
|
||||
bool overflow{divisorOrdering == Ordering::Equal};
|
||||
if (divisorOrdering == Ordering::Less) {
|
||||
negateQuotient = !negateQuotient;
|
||||
divisor.TwosComplement();
|
||||
if (divisor.TwosComplement()) {
|
||||
// divisor was (and is) the most negative number
|
||||
if (CompareUnsigned(divisor) == Ordering::Equal) {
|
||||
RightMask(1);
|
||||
remainder.Clear();
|
||||
return bits <= 1; // edge case: 1-bit signed numbers overflow on 1!
|
||||
} else {
|
||||
remainder = *this;
|
||||
Clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (divisorOrdering == Ordering::Equal) {
|
||||
// division by zero
|
||||
remainder.Clear();
|
||||
if (dividendIsNegative) {
|
||||
LeftMask(1); // most negative signed number
|
||||
} else {
|
||||
RightMask(bits - 1); // most positive signed number
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (dividendIsNegative) {
|
||||
if (TwosComplement()) {
|
||||
// Dividend was (and remains) the most negative number.
|
||||
// See whether the original divisor was -1 (if so, it's 1 now).
|
||||
if (divisorOrdering == Ordering::Less &&
|
||||
divisor.CompareUnsigned(FixedPoint{std::uint64_t{1}}) == Ordering::Equal) {
|
||||
// most negative number / -1 is the sole overflow case
|
||||
remainder.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Overflow is not possible, and both the dividend (*this) and divisor
|
||||
// are now positive.
|
||||
DivideUnsigned(divisor, remainder);
|
||||
overflow |= IsNegative();
|
||||
if (negateQuotient) {
|
||||
TwosComplement();
|
||||
}
|
||||
if (negateRemainder) {
|
||||
if (dividendIsNegative) {
|
||||
remainder.TwosComplement();
|
||||
}
|
||||
return overflow;
|
||||
return false;
|
||||
}
|
||||
|
||||
// MASKR intrinsic
|
||||
constexpr void RightMask(int places) {
|
||||
int j{0};
|
||||
for (; j + 1 < parts && places >= partBits; ++j, places -= partBits) {
|
||||
part_[j] = partMask;
|
||||
}
|
||||
if (places > 0) {
|
||||
if (j + 1 < parts) {
|
||||
part_[j++] = partMask >> (partBits - places);
|
||||
} else if (j + 1 == parts) {
|
||||
if (places >= topPartBits) {
|
||||
part_[j++] = topPartMask;
|
||||
} else {
|
||||
part_[j++] = topPartMask >> (topPartBits - places);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; j < parts; ++j) {
|
||||
part_[j] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// MASKL intrinsic
|
||||
constexpr void LeftMask(int places) {
|
||||
if (places < 0) {
|
||||
Clear();
|
||||
} else if (places >= bits) {
|
||||
RightMask(bits);
|
||||
} else {
|
||||
RightMask(bits - places);
|
||||
OnesComplement();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr bool IsNegative() const {
|
||||
return (part_[parts-1] >> (partBits - 1)) & 1;
|
||||
static constexpr Part PartMask(int part) {
|
||||
return part == parts-1 ? topPartMask : partMask;
|
||||
}
|
||||
|
||||
Part part_[parts]; // little-endian order: [parts-1] is most significant
|
||||
constexpr void Clear() {
|
||||
for (int j{0}; j < parts; ++j) {
|
||||
part_[j] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Part part_[parts]{}; // little-endian order: [parts-1] is most significant
|
||||
};
|
||||
} // namespace Fortran::evaluate
|
||||
#endif // FORTRAN_EVALUATE_FIXED_POINT_H_
|
||||
|
|
|
@ -12,10 +12,24 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
add_library(FortranEvaluateTesting
|
||||
testing.cc
|
||||
)
|
||||
|
||||
add_executable(leading-zero-bit-count-test
|
||||
leading-zero-bit-count.cc
|
||||
leading-zero-bit-count-test.cc
|
||||
)
|
||||
|
||||
target_link_libraries(leading-zero-bit-count-test
|
||||
FortranEvaluate
|
||||
FortranEvaluateTesting
|
||||
)
|
||||
|
||||
add_executable(fixed-point-test
|
||||
fixed-point-test.cc
|
||||
)
|
||||
|
||||
target_link_libraries(fixed-point-test
|
||||
FortranEvaluate
|
||||
FortranEvaluateTesting
|
||||
)
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
// Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "../../lib/evaluate/fixed-point.h"
|
||||
#include "testing.h"
|
||||
#include <cstdio>
|
||||
|
||||
using Fortran::evaluate::Ordering;
|
||||
using Fortran::evaluate::FixedPoint;
|
||||
|
||||
template<int BITS, typename FP = FixedPoint<BITS>>
|
||||
void exhaustiveTesting() {
|
||||
COMPARE(BITS, ==, FP::bits);
|
||||
std::uint64_t maxUnsignedValue{(std::uint64_t{1} << BITS) - 1};
|
||||
std::int64_t maxPositiveSignedValue{(std::int64_t{1} << (BITS - 1)) - 1};
|
||||
std::int64_t mostNegativeSignedValue{-(std::int64_t{1} << (BITS-1))};
|
||||
char desc[64];
|
||||
std::snprintf(desc, sizeof desc, "BITS=%d, PARTBITS=%d, sizeof(Part)=%d", BITS, FP::partBits, static_cast<int>(sizeof(typename FP::Part)));
|
||||
FP zero;
|
||||
TEST(zero.IsZero())(desc);
|
||||
for (std::uint64_t x{0}; x <= maxUnsignedValue; ++x) {
|
||||
FP a{x};
|
||||
COMPARE(x, ==, a.ToUInt64())(desc);
|
||||
FP copy{a};
|
||||
COMPARE(x, ==, copy.ToUInt64())(desc);
|
||||
copy = a;
|
||||
COMPARE(x, ==, copy.ToUInt64())(desc);
|
||||
COMPARE(x==0, ==, a.IsZero())("%s, x=0x%llx", desc, x);
|
||||
copy.OnesComplement();
|
||||
COMPARE(x ^ maxUnsignedValue, ==, copy.ToUInt64())("%s, x=0x%llx", desc, x);
|
||||
copy = a;
|
||||
bool over{copy.TwosComplement()};
|
||||
COMPARE(over, ==, x == std::uint64_t{1} << (BITS-1))("%s, x=0x%llx", desc, x);
|
||||
COMPARE(-x & maxUnsignedValue, ==, copy.ToUInt64())("%s, x=0x%llx", desc, x);
|
||||
int lzbc{a.LeadingZeroBitCount()};
|
||||
COMPARE(lzbc, >=, 0)("%s, x=0x%llx", desc, x);
|
||||
COMPARE(lzbc, <=, BITS)("%s, x=0x%llx", desc, x);
|
||||
COMPARE(x==0, ==, lzbc == BITS)("%s, x=0x%llx, lzbc=%d", desc, x, lzbc);
|
||||
std::uint64_t lzcheck{std::uint64_t{1} << (BITS - lzbc)};
|
||||
COMPARE(x, <, lzcheck)("%s, x=0x%llx, lzbc=%d", desc, x, lzbc);
|
||||
COMPARE(x + x + !x, >=, lzcheck)("%s, x=0x%llx, lzbc=%d", desc, x, lzbc);
|
||||
Ordering ord{Ordering::Equal};
|
||||
std::int64_t sx = x;
|
||||
if (x + x > maxUnsignedValue) {
|
||||
TEST(a.IsNegative())("%s, x=0x%llx", desc, x);
|
||||
sx = x | (~std::uint64_t{0} << BITS);
|
||||
TEST(sx < 0)("%s, x=0x%llx %lld", desc, x, sx);
|
||||
ord = Ordering::Less;
|
||||
} else {
|
||||
TEST(!a.IsNegative())("%s, x=0x%llx", desc, x);
|
||||
TEST(sx >= 0)("%s, x=0x%llx %lld", desc, x, sx);
|
||||
if (sx > 0) {
|
||||
ord = Ordering::Greater;
|
||||
} else {
|
||||
ord = Ordering::Equal;
|
||||
}
|
||||
}
|
||||
TEST(sx == a.ToInt64())("%s, x=0x%llx %lld", desc, x, sx);
|
||||
TEST(a.CompareToZeroSigned() == ord)("%s, x=0x%llx %lld", desc, x, sx);
|
||||
for (int count{0}; count <= BITS + 1; ++count) {
|
||||
copy = a;
|
||||
copy.ShiftLeft(count);
|
||||
COMPARE((x << count) & maxUnsignedValue, ==, copy.ToUInt64())("%s, x=0x%llx, count=%d", desc, x, count);
|
||||
copy = a;
|
||||
copy.ShiftRightLogical(count);
|
||||
COMPARE(x >> count, ==, copy.ToUInt64())("%s, x=0x%llx, count=%d", desc, x, count);
|
||||
copy = a;
|
||||
copy.ShiftLeft(-count);
|
||||
COMPARE(x >> count, ==, copy.ToUInt64())("%s, x=0x%llx, count=%d", desc, x, count);
|
||||
copy = a;
|
||||
copy.ShiftRightLogical(-count);
|
||||
COMPARE((x << count) & maxUnsignedValue, ==, copy.ToUInt64())("%s, x=0x%llx, count=%d", desc, x, count);
|
||||
}
|
||||
for (std::uint64_t y{0}; y <= maxUnsignedValue; ++y) {
|
||||
std::int64_t sy = y;
|
||||
if (y + y > maxUnsignedValue) {
|
||||
sy = y | (~std::uint64_t{0} << BITS);
|
||||
}
|
||||
FP b{y};
|
||||
if (x < y) {
|
||||
ord = Ordering::Less;
|
||||
} else if (x > y) {
|
||||
ord = Ordering::Greater;
|
||||
} else {
|
||||
ord = Ordering::Equal;
|
||||
}
|
||||
TEST(a.CompareUnsigned(b) == ord)("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
if (sx < sy) {
|
||||
ord = Ordering::Less;
|
||||
} else if (sx > sy) {
|
||||
ord = Ordering::Greater;
|
||||
} else {
|
||||
ord = Ordering::Equal;
|
||||
}
|
||||
TEST(a.CompareSigned(b) == ord)("%s, x=0x%llx %lld %d, y=0x%llx %lld %d", desc, x, sx, a.IsNegative(), y, sy, b.IsNegative());
|
||||
copy = a;
|
||||
copy.And(b);
|
||||
COMPARE(x & y, ==, copy.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
copy = a;
|
||||
copy.Or(b);
|
||||
COMPARE(x | y, ==, copy.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
copy = a;
|
||||
copy.Xor(b);
|
||||
COMPARE(x ^ y, ==, copy.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
copy = a;
|
||||
bool carry{copy.AddUnsigned(b)};
|
||||
COMPARE(x + y, ==, copy.ToUInt64() + (std::uint64_t{carry} << BITS))("%s, x=0x%llx, y=0x%llx, carry=%d", desc, x, y, carry);
|
||||
copy = a;
|
||||
over = copy.AddSigned(b);
|
||||
COMPARE((sx + sy) & maxUnsignedValue, ==, copy.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
COMPARE(over, ==, sx+sy < mostNegativeSignedValue || sx+sy > maxPositiveSignedValue)("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
copy = a;
|
||||
over = copy.SubtractSigned(b);
|
||||
COMPARE((sx - sy) & maxUnsignedValue, ==, copy.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
COMPARE(over, ==, sx-sy < mostNegativeSignedValue || sx-sy > maxPositiveSignedValue)("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
copy = a;
|
||||
FP upper;
|
||||
copy.MultiplyUnsigned(b, upper);
|
||||
COMPARE(x * y, ==, (upper.ToUInt64() << BITS) ^ copy.ToUInt64())("%s, x=0x%llx, y=0x%llx, lower=0x%llx, upper=0x%llx", desc, x, y, copy.ToUInt64(), upper.ToUInt64());
|
||||
copy = a;
|
||||
copy.MultiplySigned(b, upper);
|
||||
COMPARE((sx * sy) & maxUnsignedValue, ==, copy.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
COMPARE(((sx * sy) >> BITS) & maxUnsignedValue, ==, upper.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
copy = a;
|
||||
FP rem;
|
||||
COMPARE(y == 0, ==, copy.DivideUnsigned(b, rem))("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
if (y == 0) {
|
||||
COMPARE(maxUnsignedValue, ==, copy.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
COMPARE(0, ==, rem.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
} else {
|
||||
COMPARE(x / y, ==, copy.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
COMPARE(x % y, ==, rem.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
}
|
||||
copy = a;
|
||||
bool badCase{sx == mostNegativeSignedValue &&
|
||||
((sy == -1 && sx != sy) || (BITS==1 && sx==sy))};
|
||||
COMPARE(y == 0 || badCase, ==, copy.DivideSigned(b, rem))("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
if (y == 0) {
|
||||
if (sx >= 0) {
|
||||
COMPARE(maxPositiveSignedValue, ==, copy.ToInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
} else {
|
||||
COMPARE(mostNegativeSignedValue, ==, copy.ToInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
}
|
||||
COMPARE(0, ==, rem.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
} else if (badCase) {
|
||||
COMPARE(x, ==, copy.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
COMPARE(0, ==, rem.ToUInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
} else {
|
||||
COMPARE(sx/sy, ==, copy.ToInt64())("%s, x=0x%llx %lld, y=0x%llx %lld; unsigned 0x%llx", desc, x, sx, y, sy, copy.ToUInt64());
|
||||
COMPARE(sx-sy*(sx/sy), ==, rem.ToInt64())("%s, x=0x%llx, y=0x%llx", desc, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
TEST(Reverse(Ordering::Less) == Ordering::Greater);
|
||||
TEST(Reverse(Ordering::Greater) == Ordering::Less);
|
||||
TEST(Reverse(Ordering::Equal) == Ordering::Equal);
|
||||
exhaustiveTesting<1>();
|
||||
exhaustiveTesting<2>();
|
||||
exhaustiveTesting<7>();
|
||||
exhaustiveTesting<8>();
|
||||
exhaustiveTesting<9>();
|
||||
exhaustiveTesting<9, FixedPoint<9, 1>>();
|
||||
exhaustiveTesting<9, FixedPoint<9, 1, std::uint8_t, std::uint16_t>>();
|
||||
exhaustiveTesting<9, FixedPoint<9, 2>>();
|
||||
exhaustiveTesting<9, FixedPoint<9, 2, std::uint8_t, std::uint16_t>>();
|
||||
exhaustiveTesting<9, FixedPoint<9, 8, std::uint8_t, std::uint16_t>>();
|
||||
// exhaustiveTesting<15>();
|
||||
// exhaustiveTesting<16>();
|
||||
return testing::Complete();
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "../../lib/evaluate/leading-zero-bit-count.h"
|
||||
#include "testing.h"
|
||||
|
||||
using Fortran::evaluate::LeadingZeroBitCount;
|
||||
|
||||
int main() {
|
||||
COMPARE(64, ==, LeadingZeroBitCount(std::uint64_t{0}));
|
||||
for (int j{0}; j < 64; ++j) {
|
||||
for (int k{0}; k < j; ++k) {
|
||||
std::uint64_t x = (std::uint64_t{1} << j) | (std::uint64_t{1} << k);
|
||||
COMPARE(63 - j, ==, LeadingZeroBitCount(x))("j=%d, k=%d", j, k);
|
||||
}
|
||||
}
|
||||
COMPARE(32, ==, LeadingZeroBitCount(std::uint32_t{0}));
|
||||
for (int j{0}; j < 32; ++j) {
|
||||
for (int k{0}; k < j; ++k) {
|
||||
std::uint32_t x = (std::uint32_t{1} << j) | (std::uint32_t{1} << k);
|
||||
COMPARE(31 - j, ==, LeadingZeroBitCount(x))("j=%d, k=%d", j, k);
|
||||
}
|
||||
}
|
||||
COMPARE(16, ==, LeadingZeroBitCount(std::uint16_t{0}));
|
||||
for (int j{0}; j < 16; ++j) {
|
||||
for (int k{0}; k < j; ++k) {
|
||||
std::uint16_t x = (std::uint16_t{1} << j) | (std::uint16_t{1} << k);
|
||||
COMPARE(15 - j, ==, LeadingZeroBitCount(x))("j=%d, k=%d", j, k);
|
||||
}
|
||||
}
|
||||
COMPARE(8, ==, LeadingZeroBitCount(std::uint8_t{0}));
|
||||
for (int j{0}; j < 8; ++j) {
|
||||
for (int k{0}; k < j; ++k) {
|
||||
std::uint8_t x = (std::uint8_t{1} << j) | (std::uint8_t{1} << k);
|
||||
COMPARE(7 - j, ==, LeadingZeroBitCount(x))("j=%d, k=%d", j, k);
|
||||
}
|
||||
}
|
||||
return testing::Complete();
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
#include "../../lib/evaluate/leading-zero-bit-count.h"
|
||||
#include "testing.h"
|
||||
#include <cinttypes>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
using Fortran::evaluate::LeadingZeroBitCount;
|
||||
|
||||
int main() {
|
||||
CHECK(64, LeadingZeroBitCount(std::uint64_t{0}));
|
||||
for (int j{0}; j < 64; ++j) {
|
||||
for (int k{0}; k < j; ++k) {
|
||||
std::uint64_t x = (std::uint64_t{1} << j) | (std::uint64_t{1} << k);
|
||||
CHECK_CASE(x, 63 - j, LeadingZeroBitCount(x));
|
||||
}
|
||||
}
|
||||
CHECK(32, LeadingZeroBitCount(std::uint32_t{0}));
|
||||
for (int j{0}; j < 32; ++j) {
|
||||
for (int k{0}; k < j; ++k) {
|
||||
std::uint32_t x = (std::uint32_t{1} << j) | (std::uint32_t{1} << k);
|
||||
CHECK_CASE(x, 31 - j, LeadingZeroBitCount(x));
|
||||
}
|
||||
}
|
||||
CHECK(16, LeadingZeroBitCount(std::uint16_t{0}));
|
||||
for (int j{0}; j < 16; ++j) {
|
||||
for (int k{0}; k < j; ++k) {
|
||||
std::uint16_t x = (std::uint16_t{1} << j) | (std::uint16_t{1} << k);
|
||||
CHECK_CASE(x, 15 - j, LeadingZeroBitCount(x));
|
||||
}
|
||||
}
|
||||
CHECK(8, LeadingZeroBitCount(std::uint8_t{0}));
|
||||
for (int j{0}; j < 8; ++j) {
|
||||
for (int k{0}; k < j; ++k) {
|
||||
std::uint8_t x = (std::uint8_t{1} << j) | (std::uint8_t{1} << k);
|
||||
CHECK_CASE(x, 7 - j, LeadingZeroBitCount(x));
|
||||
}
|
||||
}
|
||||
return testing::Complete();
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "testing.h"
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
int passes{0};
|
||||
int failures{0};
|
||||
}
|
||||
|
||||
static void BitBucket(const char *, ...) {}
|
||||
|
||||
static void PrintFailureDetails(const char *format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
fputs("\t", stderr);
|
||||
vfprintf(stderr, format, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
|
||||
FailureDetailPrinter Test(const char *file, int line, const char *predicate,
|
||||
bool pass) {
|
||||
if (pass) {
|
||||
++passes;
|
||||
return BitBucket;
|
||||
} else {
|
||||
++failures;
|
||||
fprintf(stderr, "%s:%d: FAIL %s\n", file, line, predicate);
|
||||
return PrintFailureDetails;
|
||||
}
|
||||
}
|
||||
|
||||
FailureDetailPrinter Compare(const char *file, int line, const char *xs, const char *rel, const char *ys, unsigned long long x, unsigned long long y) {
|
||||
while (*rel == ' ') {
|
||||
++rel;
|
||||
}
|
||||
bool pass{false};
|
||||
if (*rel == '<') {
|
||||
if (rel[1] == '=') {
|
||||
pass = x <= y;
|
||||
} else {
|
||||
pass = x < y;
|
||||
}
|
||||
} else if (*rel == '>') {
|
||||
if (rel[1] == '=') {
|
||||
pass = x >= y;
|
||||
} else {
|
||||
pass = x > y;
|
||||
}
|
||||
} else if (*rel == '=') {
|
||||
pass = x == y;
|
||||
} else if (*rel == '!') {
|
||||
pass = x != y;
|
||||
}
|
||||
if (pass) {
|
||||
++passes;
|
||||
return BitBucket;
|
||||
} else {
|
||||
++failures;
|
||||
fprintf(stderr, "%s:%d: FAIL %s[0x%llx] %s %s[0x%llx]:\n", file, line,
|
||||
xs, x, rel, ys, y);
|
||||
return PrintFailureDetails;
|
||||
}
|
||||
}
|
||||
|
||||
int Complete() {
|
||||
if (failures == 0) {
|
||||
if (passes == 1) {
|
||||
std::cout << "single test PASSES\n";
|
||||
} else {
|
||||
std::cout << "all " << std::dec << passes << " tests PASS\n";
|
||||
}
|
||||
passes = 0;
|
||||
return EXIT_SUCCESS;
|
||||
} else {
|
||||
if (passes == 1) {
|
||||
std::cerr << "1 test passes, ";
|
||||
} else {
|
||||
std::cerr << std::dec << passes << " tests pass, ";
|
||||
}
|
||||
if (failures == 1) {
|
||||
std::cerr << "1 test FAILS\n";
|
||||
} else {
|
||||
std::cerr << std::dec << failures << " tests FAIL\n";
|
||||
}
|
||||
passes = failures = 0;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
} // namespace testing
|
|
@ -15,68 +15,22 @@
|
|||
#ifndef FORTRAN_EVALUATE_TESTING_H_
|
||||
#define FORTRAN_EVALUATE_TESTING_H_
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
namespace testing {
|
||||
|
||||
int passes{0};
|
||||
int failures{0};
|
||||
// Returns EXIT_SUCCESS or EXIT_FAILURE, so a test's main() should end
|
||||
// with "return testing::Complete()".
|
||||
int Complete();
|
||||
|
||||
void Check(const char *file, int line, std::uint64_t want, std::uint64_t got) {
|
||||
if (want != got) {
|
||||
++failures;
|
||||
std::cerr << file << ':' << std::dec << line << '(' << (passes + failures)
|
||||
<< "): want 0x" << std::hex << want
|
||||
<< ", got 0x" << got << '\n' << std::dec;
|
||||
} else {
|
||||
++passes;
|
||||
}
|
||||
}
|
||||
|
||||
void Check(const char *file, int line, std::uint64_t x, std::uint64_t want,
|
||||
std::uint64_t got) {
|
||||
if (want != got) {
|
||||
++failures;
|
||||
std::cerr << file << ':' << std::dec << line << '(' << (passes + failures)
|
||||
<< ")[0x" << std::hex << x << "]: want 0x" << want
|
||||
<< ", got 0x" << got << '\n' << std::hex;
|
||||
} else {
|
||||
++passes;
|
||||
}
|
||||
}
|
||||
|
||||
int Complete() {
|
||||
if (failures == 0) {
|
||||
if (passes == 1) {
|
||||
std::cout << "test PASSES\n";
|
||||
} else {
|
||||
std::cout << "all " << std::dec << passes << " tests PASS\n";
|
||||
}
|
||||
passes = 0;
|
||||
return EXIT_SUCCESS;
|
||||
} else {
|
||||
if (passes == 1) {
|
||||
std::cerr << std::dec << "1 test passes, ";
|
||||
} else {
|
||||
std::cerr << std::dec << passes << " tests pass, ";
|
||||
}
|
||||
if (failures == 1) {
|
||||
std::cerr << "1 test FAILS\n";
|
||||
} else {
|
||||
std::cerr << std::dec << failures << " tests FAIL\n";
|
||||
}
|
||||
passes = failures = 0;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
// Pass/fail testing. These macros return a pointer to a printf-like
|
||||
// function that can be optionally called to print more detail, e.g.
|
||||
// COMPARE(x, ==, y)("z is 0x%llx", z);
|
||||
// will also print z after the usual failure message if x != y.
|
||||
#define TEST(predicate) testing::Test(__FILE__, __LINE__, #predicate, (predicate))
|
||||
#define COMPARE(x, rel, y) testing::Compare(__FILE__, __LINE__, #x, #rel, #y, (x), (y))
|
||||
|
||||
// Functions called by thesemacros; do not call directly.
|
||||
using FailureDetailPrinter = void (*)(const char *, ...);
|
||||
FailureDetailPrinter Test(const char *file, int line, const char *predicate, bool pass);
|
||||
FailureDetailPrinter Compare(const char *file, int line, const char *xs, const char *rel, const char *ys, unsigned long long x, unsigned long long y);
|
||||
} // namespace testing
|
||||
|
||||
#define CHECK(want, got) \
|
||||
testing::Check(__FILE__, __LINE__, (want), (got))
|
||||
#define CHECK_CASE(n, want, got) \
|
||||
testing::Check(__FILE__, __LINE__, (n), (want), (got))
|
||||
|
||||
#endif // FORTRAN_EVALUATE_TESTING_H_
|
||||
|
|
Loading…
Reference in New Issue