diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index daf5b7d8..32a2095d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,8 @@ add_library(dynarmic common/fp/fpsr.h common/fp/info.h common/fp/mantissa_util.h + common/fp/op.cpp + common/fp/op.h common/fp/process_exception.cpp common/fp/process_exception.h common/fp/rounding_mode.h diff --git a/src/common/fp/op.cpp b/src/common/fp/op.cpp new file mode 100644 index 00000000..38d9ec91 --- /dev/null +++ b/src/common/fp/op.cpp @@ -0,0 +1,101 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2018 MerryMage + * This software may be used and distributed according to the terms of the GNU + * General Public License version 2 or any later version. + */ + +#include "common/assert.h" +#include "common/bit_util.h" +#include "common/common_types.h" +#include "common/safe_ops.h" +#include "common/fp/fpsr.h" +#include "common/fp/mantissa_util.h" +#include "common/fp/op.h" +#include "common/fp/process_exception.h" +#include "common/fp/rounding_mode.h" +#include "common/fp/unpacked.h" +#include "frontend/A64/FPCR.h" + +namespace Dynarmic::FP { + +template +u64 FPToFixed(size_t ibits, FPT op, size_t fbits, bool unsigned_, FPCR fpcr, RoundingMode rounding, FPSR& fpsr) { + ASSERT(rounding != RoundingMode::ToOdd); + ASSERT(ibits <= 64); + ASSERT(fbits <= ibits); + + auto [type, sign, value] = FPUnpack(op, fpcr, fpsr); + + if (type == FPType::SNaN || type == FPType::QNaN) { + FPProcessException(FPExc::InvalidOp, fpcr, fpsr); + } + + // Handle zero + if (value.mantissa == 0) { + return 0; + } + + if (sign && unsigned_) { + FPProcessException(FPExc::InvalidOp, fpcr, fpsr); + return 0; + } + + // value *= 2.0^fbits + value.exponent += static_cast(fbits); + + u64 int_result = sign ? Safe::Negate(value.mantissa) : static_cast(value.mantissa); + const ResidualError error = ResidualErrorOnRightShift(int_result, -value.exponent); + int_result = Safe::ArithmeticShiftLeft(int_result, value.exponent); + + bool round_up = false; + switch (rounding) { + case RoundingMode::ToNearest_TieEven: + round_up = error > ResidualError::Half || (error == ResidualError::Half && Common::Bit<0>(int_result)); + break; + case RoundingMode::TowardsPlusInfinity: + round_up = error != ResidualError::Zero; + break; + case RoundingMode::TowardsMinusInfinity: + round_up = false; + break; + case RoundingMode::TowardsZero: + round_up = error != ResidualError::Zero && Common::MostSignificantBit(int_result); + break; + case RoundingMode::ToNearest_TieAwayFromZero: + round_up = error > ResidualError::Half || (error == ResidualError::Half && !Common::MostSignificantBit(int_result)); + break; + case RoundingMode::ToOdd: + UNREACHABLE(); + } + + if (round_up) { + int_result++; + } + + // Detect Overflow + const int min_exponent_for_overflow = static_cast(ibits) - static_cast(Common::HighestSetBit(value.mantissa + (round_up ? 1 : 0))) - (unsigned_ ? 0 : 1); + if (value.exponent >= min_exponent_for_overflow) { + // Positive overflow + if (unsigned_ || !sign) { + FPProcessException(FPExc::InvalidOp, fpcr, fpsr); + return Common::Ones(ibits - (unsigned_ ? 0 : 1)); + } + + // Negative overflow + const u64 min_value = Safe::Negate(static_cast(1) << (ibits - 1)); + if (!(value.exponent == min_exponent_for_overflow && int_result == min_value)) { + FPProcessException(FPExc::InvalidOp, fpcr, fpsr); + return static_cast(1) << (ibits - 1); + } + } + + if (error != ResidualError::Zero) { + FPProcessException(FPExc::Inexact, fpcr, fpsr); + } + return int_result & Common::Ones(ibits); +} + +template u64 FPToFixed(size_t ibits, u32 op, size_t fbits, bool unsigned_, FPCR fpcr, RoundingMode rounding, FPSR& fpsr); +template u64 FPToFixed(size_t ibits, u64 op, size_t fbits, bool unsigned_, FPCR fpcr, RoundingMode rounding, FPSR& fpsr); + +} // namespace Dynarmic::FP diff --git a/src/common/fp/op.h b/src/common/fp/op.h new file mode 100644 index 00000000..435070b0 --- /dev/null +++ b/src/common/fp/op.h @@ -0,0 +1,21 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2018 MerryMage + * This software may be used and distributed according to the terms of the GNU + * General Public License version 2 or any later version. + */ + +#pragma once + +#include "common/common_types.h" +#include "common/fp/fpsr.h" +#include "common/fp/rounding_mode.h" +#include "frontend/A64/FPCR.h" + +namespace Dynarmic::FP { + +using FPCR = A64::FPCR; + +template +u64 FPToFixed(size_t ibits, FPT op, size_t fbits, bool unsigned_, FPCR fpcr, RoundingMode rounding, FPSR& fpsr); + +} // namespace Dynarmic::FP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e48e18c1..7680dc4a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -29,6 +29,7 @@ add_executable(dynarmic_tests A64/inst_gen.cpp A64/inst_gen.h A64/testenv.h + fp/FPToFixed.cpp fp/mantissa_util_tests.cpp fp/unpacked_tests.cpp main.cpp diff --git a/tests/fp/FPToFixed.cpp b/tests/fp/FPToFixed.cpp new file mode 100644 index 00000000..1a507dfb --- /dev/null +++ b/tests/fp/FPToFixed.cpp @@ -0,0 +1,38 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2018 MerryMage + * This software may be used and distributed according to the terms of the GNU + * General Public License version 2 or any later version. + */ + +#include +#include + +#include + +#include "common/fp/fpsr.h" +#include "common/fp/op.h" +#include "rand_int.h" + +using namespace Dynarmic; +using namespace Dynarmic::FP; + +TEST_CASE("FPToFixed", "[fp]") { + const std::vector> test_cases { + {0x447A0000, 64, 0x000003E8, 0x00}, + {0xC47A0000, 32, 0xFFFFFC18, 0x00}, + {0x4479E000, 64, 0x000003E8, 0x10}, + {0x50800000, 32, 0x7FFFFFFF, 0x01}, + {0xD0800000, 32, 0x80000000, 0x01}, + {0xCF000000, 32, 0x80000000, 0x00}, + {0x80002B94, 64, 0x00000000, 0x10}, + {0x80636D24, 64, 0x00000000, 0x10}, + }; + + const FPCR fpcr; + for (auto [input, ibits, expected_output, expected_fpsr] : test_cases) { + FPSR fpsr; + const u64 output = FPToFixed(ibits, input, 0, false, fpcr, RoundingMode::ToNearest_TieEven, fpsr); + REQUIRE(output == expected_output); + REQUIRE(fpsr.Value() == expected_fpsr); + } +}