blob: 51f4c0311300fb21d799035c10c80abccaccad7a [file]
// Copyright 2023 Google LLC
//
// 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
//
// https://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 "riscv/riscv_d_instructions.h"
#include <cmath>
#include <cstdint>
#include <type_traits>
#include "absl/log/log.h"
#include "mpact/sim/generic/instruction.h"
#include "mpact/sim/generic/type_helpers.h"
#include "riscv/riscv_fp_host.h"
#include "riscv/riscv_fp_info.h"
#include "riscv/riscv_fp_state.h"
#include "riscv/riscv_instruction_helpers.h"
#include "riscv/riscv_register.h"
#include "riscv/riscv_state.h"
namespace mpact {
namespace sim {
namespace riscv {
using ::mpact::sim::generic::Instruction;
using ::mpact::sim::generic::operator*; // NOLINT: is used below.
// The following instruction semantic functions implement the double precision
// floating point instructions in the RiscV architecture. They all utilize the
// templated helper functions in riscv_instruction_helpers.h to implement
// the boiler plate code.
// These types are used instead of uint64_t and int64_t to represent the
// integer type of equal width to double when values of these types are
// really reinterpreted double values.
using UInt = FPTypeInfo<double>::UIntType;
using SInt = FPTypeInfo<double>::IntType;
using FPRegister = RVFpRegister;
namespace internal {
template <typename T>
static inline T CanonicalizeNaN(T value) {
if (!std::isnan(value)) return value;
auto nan_value = FPTypeInfo<T>::kCanonicalNaN;
return *reinterpret_cast<T*>(&nan_value);
}
} // namespace internal
// Basic arithmetic operations.
void RiscVDAdd(const Instruction* instruction) {
RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, double, double>(
instruction, [](double a, double b) { return a + b; });
}
void RiscVDSub(const Instruction* instruction) {
RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, double, double>(
instruction, [](double a, double b) { return a - b; });
}
void RiscVDMul(const Instruction* instruction) {
RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, double, double>(
instruction, [](double a, double b) { return a * b; });
}
void RiscVDDiv(const Instruction* instruction) {
RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, double, double>(
instruction, [](double a, double b) { return a / b; });
}
// Square root uses the library square root.
void RiscVDSqrt(const Instruction* instruction) {
RiscVUnaryNaNBoxOp<FPRegister::ValueType, FPRegister::ValueType, double,
double>(instruction, [instruction](double a) -> double {
// If the input value is NaN or less than zero, set the invalid op flag.
if (FPTypeInfo<double>::IsNaN(a) || (a < 0.0)) {
if (!FPTypeInfo<double>::IsQNaN(a)) {
auto* flag_db = instruction->Destination(1)->AllocateDataBuffer();
flag_db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
flag_db->Submit();
}
return *reinterpret_cast<const double*>(
&FPTypeInfo<double>::kCanonicalNaN);
}
// Square root of 0 returns 0, and of -0.0 returns -0.0.
if (a == 0.0) return a;
// For all other cases use the library sqrt.
// Get the rounding mode.
int rm_value = generic::GetInstructionSource<int>(instruction, 1);
auto* rv_fp = static_cast<RiscVState*>(instruction->state())->rv_fp();
// If the rounding mode is dynamic, read it from the current state.
if (rm_value == *FPRoundingMode::kDynamic) {
if (!rv_fp->rounding_mode_valid()) {
LOG(ERROR) << "Invalid rounding mode";
return *reinterpret_cast<const double*>(
&FPTypeInfo<double>::kCanonicalNaN);
}
rm_value = *rv_fp->GetRoundingMode();
}
double res;
{
ScopedFPStatus set_fp_status(rv_fp->host_fp_interface(), rm_value);
res = sqrt(a);
}
return res;
});
}
// If either operand is NaN return the other.
void RiscVDMin(const Instruction* instruction) {
RiscVBinaryOp<FPRegister, double, double>(
instruction, [instruction](double a, double b) -> double {
if (FPTypeInfo<double>::IsSNaN(a) || FPTypeInfo<double>::IsSNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
if (FPTypeInfo<double>::IsNaN(a)) {
if (FPTypeInfo<double>::IsNaN(b)) {
UInt not_a_number = FPTypeInfo<double>::kCanonicalNaN;
return *reinterpret_cast<double*>(&not_a_number);
}
return b;
}
if (FPTypeInfo<double>::IsNaN(b)) return a;
// If both are zero, return the negative zero if there is one.
if ((a == 0.0) && (b == 0.0)) return (std::signbit(a)) ? a : b;
return (a > b) ? b : a;
});
}
// If either operand is NaN return the other.
void RiscVDMax(const Instruction* instruction) {
RiscVBinaryOp<FPRegister, double, double>(
instruction, [instruction](double a, double b) {
if (FPTypeInfo<double>::IsSNaN(a) || FPTypeInfo<double>::IsSNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
if (FPTypeInfo<double>::IsNaN(a)) {
if (FPTypeInfo<double>::IsNaN(b)) {
UInt not_a_number = FPTypeInfo<double>::kCanonicalNaN;
return *reinterpret_cast<double*>(&not_a_number);
}
return b;
}
if (FPTypeInfo<double>::IsNaN(b)) return a;
// If both are zero, return the negative zero if there is one.
if ((a == 0.0) && (b == 0.0)) return (std::signbit(a)) ? b : a;
return (a < b) ? b : a;
});
}
// Four flavors of multiply accumulate.
// Multiply-add (a * b) + c
// Multiply-subtract (a * b) - c
// Negated multiply-add -((a * b) + c)
// Negated multiply-subtract -((a * b) - c)
void RiscVDMadd(const Instruction* instruction) {
using T = double;
RiscVTernaryFloatNaNBoxOp<FPRegister::ValueType, T, T>(
instruction, [instruction](T a, T b, T c) -> T {
if (FPTypeInfo<T>::IsNaN(a)) return internal::CanonicalizeNaN(a);
if (FPTypeInfo<T>::IsNaN(b)) return internal::CanonicalizeNaN(b);
if ((std::isinf(a) && (b == 0.0)) || ((std::isinf(b) && (a == 0.0)))) {
auto* flag_db = instruction->Destination(1)->AllocateDataBuffer();
flag_db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
flag_db->Submit();
}
if (FPTypeInfo<T>::IsNaN(c)) return internal::CanonicalizeNaN(c);
if (std::isinf(c) && !std::isinf(a) && !std::isinf(b)) return c;
if (c == 0.0) {
if ((a == 0.0 && !std::isinf(b)) || (b == 0.0 && !std::isinf(a))) {
UInt c_sign =
*reinterpret_cast<UInt*>(&c) >> (FPTypeInfo<T>::kBitSize - 1);
UInt ua = *reinterpret_cast<UInt*>(&a);
UInt ub = *reinterpret_cast<UInt*>(&b);
UInt prod_sign = (ua ^ ub) >> (FPTypeInfo<T>::kBitSize - 1);
if (prod_sign != c_sign) return 0.0;
return c;
}
return internal::CanonicalizeNaN(a * b);
}
return internal::CanonicalizeNaN(fma(a, b, c));
});
}
void RiscVDMsub(const Instruction* instruction) {
using T = double;
RiscVTernaryFloatNaNBoxOp<FPRegister::ValueType, T, T>(
instruction, [instruction](T a, T b, T c) -> T {
if (FPTypeInfo<T>::IsNaN(a)) return internal::CanonicalizeNaN(a);
if (FPTypeInfo<T>::IsNaN(b)) return internal::CanonicalizeNaN(b);
if ((std::isinf(a) && (b == 0.0)) || ((std::isinf(b) && (a == 0.0)))) {
auto* flag_db = instruction->Destination(1)->AllocateDataBuffer();
flag_db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
flag_db->Submit();
}
if (FPTypeInfo<T>::IsNaN(c)) return internal::CanonicalizeNaN(c);
if (std::isinf(c) && !std::isinf(a) && !std::isinf(b)) return -c;
if (c == 0.0) {
if ((a == 0.0 && !std::isinf(b)) || (b == 0.0 && !std::isinf(a))) {
UInt c_sign =
-*reinterpret_cast<UInt*>(&c) >> (FPTypeInfo<T>::kBitSize - 1);
UInt ua = *reinterpret_cast<UInt*>(&a);
UInt ub = *reinterpret_cast<UInt*>(&b);
UInt prod_sign = (ua ^ ub) >> (FPTypeInfo<T>::kBitSize - 1);
if (prod_sign == c_sign) return 0.0;
return -c;
}
return internal::CanonicalizeNaN(a * b);
}
return internal::CanonicalizeNaN(fma(a, b, -c));
});
}
void RiscVDNmadd(const Instruction* instruction) {
using T = double;
RiscVTernaryFloatNaNBoxOp<FPRegister::ValueType, T, T>(
instruction, [instruction](T a, T b, T c) -> T {
if (FPTypeInfo<T>::IsNaN(a)) return internal::CanonicalizeNaN(a);
if (FPTypeInfo<T>::IsNaN(b)) return internal::CanonicalizeNaN(b);
if ((std::isinf(a) && (b == 0.0)) || ((std::isinf(b) && (a == 0.0)))) {
auto* flag_db = instruction->Destination(1)->AllocateDataBuffer();
flag_db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
flag_db->Submit();
}
if (FPTypeInfo<T>::IsNaN(c)) return internal::CanonicalizeNaN(c);
if (std::isinf(c) && !std::isinf(a) && !std::isinf(b)) return -c;
if (c == 0.0) {
if ((a == 0.0 && !std::isinf(b)) || (b == 0.0 && !std::isinf(a))) {
UInt c_sign =
*reinterpret_cast<UInt*>(&c) >> (FPTypeInfo<T>::kBitSize - 1);
UInt ua = *reinterpret_cast<UInt*>(&a);
UInt ub = *reinterpret_cast<UInt*>(&b);
UInt prod_sign = (ua ^ ub) >> (FPTypeInfo<T>::kBitSize - 1);
if (prod_sign != c_sign) return 0.0;
return -c;
}
return internal::CanonicalizeNaN(-a * b);
}
return internal::CanonicalizeNaN(-fma(a, b, c));
});
}
void RiscVDNmsub(const Instruction* instruction) {
using T = double;
RiscVTernaryFloatNaNBoxOp<FPRegister::ValueType, T, T>(
instruction, [instruction](T a, T b, T c) -> T {
if (FPTypeInfo<T>::IsNaN(a)) return internal::CanonicalizeNaN(a);
if (FPTypeInfo<T>::IsNaN(b)) return internal::CanonicalizeNaN(b);
// Illegal operation flag set if either a or b are infinite, and
// the other is zero.
if ((std::isinf(a) && (b == 0.0)) || ((std::isinf(b) && (a == 0.0)))) {
auto* flag_db = instruction->Destination(1)->AllocateDataBuffer();
flag_db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
flag_db->Submit();
}
if (FPTypeInfo<T>::IsNaN(c)) return internal::CanonicalizeNaN(c);
if (std::isinf(c) && !std::isinf(a) && !std::isinf(b)) return c;
if (c == 0.0) {
if ((a == 0.0 && !std::isinf(b)) || (b == 0.0 && !std::isinf(a))) {
UInt c_sign =
-*reinterpret_cast<UInt*>(&c) >> (FPTypeInfo<T>::kBitSize - 1);
UInt ua = *reinterpret_cast<UInt*>(&a);
UInt ub = *reinterpret_cast<UInt*>(&b);
UInt prod_sign = (ua ^ ub) >> (FPTypeInfo<T>::kBitSize - 1);
if (prod_sign != c_sign) return 0.0;
return c;
}
return internal::CanonicalizeNaN(-a * b);
}
return internal::CanonicalizeNaN(-fma(a, b, -c));
});
}
// Conversion instructions.
// Convert int to double.
void RiscVDCvtDw(const Instruction* instruction) {
RiscVUnaryFloatOp<double, int32_t>(
instruction, [](int32_t a) -> double { return static_cast<double>(a); });
}
// Convert unsigned word to double.
void RiscVDCvtDwu(const Instruction* instruction) {
RiscVUnaryFloatOp<double, uint32_t>(
instruction, [](uint32_t a) -> double { return static_cast<double>(a); });
}
// Convert double to float.
void RiscVDCvtSd(const Instruction* instruction) {
RiscVUnaryFloatNaNBoxOp<RVFpRegister::ValueType, RVFpRegister::ValueType,
float, double>(instruction, [](double a) -> float {
if (FPTypeInfo<double>::IsNaN(a)) {
typename FPTypeInfo<float>::UIntType uint_value;
uint_value = FPTypeInfo<float>::kCanonicalNaN;
return *reinterpret_cast<float*>(&uint_value);
}
return static_cast<float>(a);
});
}
// Convert float to double.
void RiscVDCvtDs(const Instruction* instruction) {
RiscVUnaryFloatOp<double, float>(instruction, [](float a) -> double {
if (FPTypeInfo<float>::IsNaN(a)) {
typename FPTypeInfo<double>::UIntType uint_value;
uint_value = FPTypeInfo<double>::kCanonicalNaN;
return *reinterpret_cast<double*>(&uint_value);
}
return static_cast<double>(a);
});
}
// Use sign of the second operand as the sign in the first.
void RiscVDSgnj(const Instruction* instruction) {
RiscVBinaryOp<FPRegister, UInt, UInt>(
instruction, [](UInt a, UInt b) -> UInt {
return (a & 0x7fff'ffff'ffff'ffff) | (b & 0x8000'0000'0000'0000);
});
}
// Use negation of the sign of the second operand as the sign in the first.
void RiscVDSgnjn(const Instruction* instruction) {
RiscVBinaryOp<FPRegister, UInt, UInt>(
instruction, [](UInt a, UInt b) -> UInt {
return (a & 0x7fff'ffff'ffff'ffff) | (~b & 0x8000'0000'0000'0000);
});
}
// Use the xor of the signs of the two operands as the sign in the first.
void RiscVDSgnjx(const Instruction* instruction) {
RiscVBinaryOp<FPRegister, UInt, UInt>(
instruction, [](UInt a, UInt b) -> UInt {
return (a & 0x7fff'ffff'ffff'ffff) | ((a ^ b) & 0x8000'0000'0000'0000);
});
}
namespace RV32 {
using XRegister = RV32Register;
using XInt = std::make_signed<RV32Register::ValueType>::type;
using XUInt = std::make_unsigned<RV32Register::ValueType>::type;
void RiscVDSd(const Instruction* instruction) {
using T = uint64_t;
auto* state = static_cast<RiscVState*>(instruction->state());
if (state->mstatus()->fs() == 0) return;
XUInt base = generic::GetInstructionSource<XUInt>(instruction, 0);
XInt offset = generic::GetInstructionSource<XInt>(instruction, 1);
XUInt address = base + offset;
T value = generic::GetInstructionSource<T>(instruction, 2);
auto* db = state->db_factory()->Allocate(sizeof(T));
db->Set<T>(0, value);
state->StoreMemory(instruction, address, db);
db->DecRef();
}
// Convert double to int.
void RiscVDCvtWd(const Instruction* instruction) {
RiscVConvertFloatWithFflagsOp<XInt, double, int32_t>(instruction);
}
// Convert double to unsigned word.
void RiscVDCvtWud(const Instruction* instruction) {
RiscVConvertFloatWithFflagsOp<XUInt, double, uint32_t>(instruction);
}
// Double precision floating point compare equal.
void RiscVDCmpeq(const Instruction* instruction) {
RiscVBinaryOp<XRegister, uint32_t, double>(
instruction, [instruction](double a, double b) -> uint32_t {
if (FPTypeInfo<double>::IsSNaN(a) || FPTypeInfo<double>::IsSNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
return a == b;
});
}
// Double precision floating point compare less.
void RiscVDCmplt(const Instruction* instruction) {
RiscVBinaryOp<XRegister, uint32_t, double>(
instruction, [instruction](double a, double b) -> uint32_t {
if (FPTypeInfo<double>::IsNaN(a) || FPTypeInfo<double>::IsNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
return a < b;
});
}
// Double precision floating point compare less than or equal.
void RiscVDCmple(const Instruction* instruction) {
RiscVBinaryOp<XRegister, uint32_t, double>(
instruction, [instruction](double a, double b) -> uint32_t {
if (FPTypeInfo<double>::IsNaN(a) || FPTypeInfo<double>::IsNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
return a <= b;
});
}
// Return the class attribute of the source operand.
void RiscVDClass(const Instruction* instruction) {
RiscVUnaryOp<XRegister, XUInt, double>(instruction, [](double a) -> XUInt {
auto res = static_cast<XUInt>(ClassifyFP(a));
return res;
});
}
} // namespace RV32
namespace RV64 {
using XRegister = RV64Register;
using XInt = std::make_signed<RV64Register::ValueType>::type;
using XUInt = std::make_unsigned<RV64Register::ValueType>::type;
void RiscVDSd(const Instruction* instruction) {
using T = uint64_t;
auto* state = static_cast<RiscVState*>(instruction->state());
if (state->mstatus()->fs() == 0) return;
XUInt base = generic::GetInstructionSource<XUInt>(instruction, 0);
XInt offset = generic::GetInstructionSource<XInt>(instruction, 1);
XUInt address = base + offset;
T value = generic::GetInstructionSource<T>(instruction, 2);
auto* db = state->db_factory()->Allocate(sizeof(T));
db->Set<T>(0, value);
state->StoreMemory(instruction, address, db);
db->DecRef();
}
// Convert double to int.
void RiscVDCvtWd(const Instruction* instruction) {
RiscVConvertFloatWithFflagsOp<XInt, double, int32_t>(instruction);
}
// Convert double to unsigned word.
void RiscVDCvtWud(const Instruction* instruction) {
RiscVConvertFloatWithFflagsOp<XUInt, double, uint32_t>(instruction);
}
// Double precision floating point compare equal.
void RiscVDCmpeq(const Instruction* instruction) {
RiscVBinaryOp<XRegister, XUInt, double>(
instruction, [instruction](double a, double b) -> XUInt {
if (FPTypeInfo<double>::IsSNaN(a) || FPTypeInfo<double>::IsSNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
return a == b;
});
}
// Double precision floating point compare less.
void RiscVDCmplt(const Instruction* instruction) {
RiscVBinaryOp<XRegister, XUInt, double>(
instruction, [instruction](double a, double b) -> XUInt {
if (FPTypeInfo<double>::IsNaN(a) || FPTypeInfo<double>::IsNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
return a < b;
});
}
// Double precision floating point compare less than or equal.
void RiscVDCmple(const Instruction* instruction) {
RiscVBinaryOp<XRegister, XUInt, double>(
instruction, [instruction](double a, double b) -> XUInt {
if (FPTypeInfo<double>::IsNaN(a) || FPTypeInfo<double>::IsNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
return a <= b;
});
}
// Return the class attribute of the source operand.
void RiscVDClass(const Instruction* instruction) {
RiscVUnaryOp<XRegister, UInt, double>(instruction, [](double a) -> UInt {
auto res = static_cast<UInt>(ClassifyFP(a));
return res;
});
}
// Convert double to 64 bit signed integer.
void RiscVDCvtLd(const Instruction* instruction) {
RiscVConvertFloatWithFflagsOp<XInt, double, int64_t>(instruction);
}
// Convert double to 64 bit unsigned integer.
void RiscVDCvtLud(const Instruction* instruction) {
RiscVConvertFloatWithFflagsOp<XInt, double, uint64_t>(instruction);
}
// Convert signed 64 bit integer to double.
void RiscVDCvtDl(const Instruction* instruction) {
RiscVUnaryFloatOp<double, int64_t>(
instruction, [](int64_t a) -> double { return static_cast<double>(a); });
}
// Convert unsigned 64 bit integer to double.
void RiscVDCvtDlu(const Instruction* instruction) {
RiscVUnaryFloatOp<double, uint64_t>(
instruction, [](uint64_t a) -> double { return static_cast<double>(a); });
}
void RiscVDMvxd(const Instruction* instruction) {
RiscVUnaryOp<XRegister, uint64_t, uint64_t>(
instruction, [](uint64_t a) -> uint64_t { return a; });
}
void RiscVDMvdx(const Instruction* instruction) {
RiscVUnaryOp<FPRegister, uint64_t, uint64_t>(
instruction, [](uint64_t a) -> uint64_t { return a; });
}
} // namespace RV64
} // namespace riscv
} // namespace sim
} // namespace mpact