blob: 8f4257cd43cbc0d8c10ab52c96c26f74d14664a3 [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_f_instructions.h"
#include <cmath>
#include <cstdint>
#include <functional>
#include <limits>
#include <type_traits>
#include "absl/log/log.h"
#include "mpact/sim/generic/instruction.h"
#include "mpact/sim/generic/register.h"
#include "mpact/sim/generic/type_helpers.h"
#include "riscv/riscv_fp_host.h"
#include "riscv/riscv_fp_info.h"
#include "riscv/riscv_instruction_helpers.h"
#include "riscv/riscv_register.h"
#include "riscv/riscv_state.h"
namespace mpact {
namespace sim {
namespace riscv {
// The following instruction semantic functions implement the single 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.
using FPRegister = RVFpRegister;
using RegUInt = typename std::make_unsigned<RVFpRegister::ValueType>::type;
// These types are used instead of uint32_t and int32_t to represent the
// integer type of equal value to float when values of these types are
// really reinterpreted float values.
using FPUInt = FPTypeInfo<float>::UIntType;
using FPSInt = FPTypeInfo<float>::IntType;
// Note, for any SP operation on values in 64-bit DP registers, the input
// values have to be properly NaN-boxed. If not, the value is treated as
// a canonical NaN.
// Templated helper functions.
namespace internal {
// Convert float to signed 32 bit integer.
template <typename XInt>
static inline void RVFCvtWs(const Instruction* instruction) {
RiscVConvertFloatWithFflagsOp<XInt, float, int32_t>(instruction);
}
// Convert float to unsigned 32 bit integer.
template <typename XUint>
static inline void RVFCvtWus(const Instruction* instruction) {
RiscVConvertFloatWithFflagsOp<XUint, float, uint32_t>(instruction);
}
// Convert float to signed 64 bit integer.
template <typename XInt>
static inline void RVFCvtLs(const Instruction* instruction) {
RiscVConvertFloatWithFflagsOp<XInt, float, int64_t>(instruction);
}
// Convert float to unsigned 64 bit integer.
template <typename XUint>
static inline void RVFCvtLus(const Instruction* instruction) {
RiscVConvertFloatWithFflagsOp<XUint, float, uint64_t>(instruction);
}
// Single precision compare equal.
template <typename XRegister>
static inline void RVFCmpeq(const Instruction* instruction) {
RiscVBinaryNaNBoxOp<typename XRegister::ValueType,
typename XRegister::ValueType, float>(
instruction,
[instruction](float a, float b) -> typename XRegister::ValueType {
if (FPTypeInfo<float>::IsSNaN(a) || FPTypeInfo<float>::IsSNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
return a == b;
});
}
// Single precicion compare less than.
template <typename XRegister>
static inline void RVFCmplt(const Instruction* instruction) {
RiscVBinaryNaNBoxOp<typename XRegister::ValueType,
typename XRegister::ValueType, float>(
instruction,
[instruction](float a, float b) -> typename XRegister::ValueType {
if (FPTypeInfo<float>::IsNaN(a) || FPTypeInfo<float>::IsNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
return a < b;
});
}
// Single precision compare less than or equal.
template <typename XRegister>
static inline void RVFCmple(const Instruction* instruction) {
RiscVBinaryNaNBoxOp<typename XRegister::ValueType,
typename XRegister::ValueType, float>(
instruction,
[instruction](float a, float b) -> typename XRegister::ValueType {
if (FPTypeInfo<float>::IsNaN(a) || FPTypeInfo<float>::IsNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
return a <= b;
});
}
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
// Load child instruction.
void RiscVIFlwChild(const Instruction* instruction) {
LoadContext* context = static_cast<LoadContext*>(instruction->context());
auto value = context->value_db->Get<FPUInt>(0);
auto* reg =
static_cast<generic::RegisterDestinationOperand<FPRegister::ValueType>*>(
instruction->Destination(0))
->GetRegister();
if (sizeof(FPRegister::ValueType) > sizeof(FPUInt)) {
// NaN box the loaded value.
auto reg_value = std::numeric_limits<FPRegister::ValueType>::max();
reg_value <<= sizeof(FPUInt) * 8;
reg_value |= value;
reg->data_buffer()->Set<FPRegister::ValueType>(0, reg_value);
return;
}
reg->data_buffer()->Set<FPRegister::ValueType>(0, value);
}
// Basic arithmetic instructions.
void RiscVFAdd(const Instruction* instruction) {
RiscVBinaryFloatNaNBoxOp<FPRegister::ValueType, float, float>(
instruction, [](float a, float b) { return a + b; });
}
void RiscVFSub(const Instruction* instruction) {
RiscVBinaryFloatNaNBoxOp<FPRegister::ValueType, float, float>(
instruction, [](float a, float b) { return a - b; });
}
void RiscVFMul(const Instruction* instruction) {
RiscVBinaryFloatNaNBoxOp<FPRegister::ValueType, float, float>(
instruction, [](float a, float b) { return a * b; });
}
void RiscVFDiv(const Instruction* instruction) {
RiscVBinaryFloatNaNBoxOp<FPRegister::ValueType, float, float>(
instruction, [](float a, float b) { return a / b; });
}
// Square root uses the library square root, but check for special conditions
// to set flags that may not be set correctly with the library version.
void RiscVFSqrt(const Instruction* instruction) {
RiscVUnaryNaNBoxOp<FPRegister::ValueType, FPRegister::ValueType, float,
float>(instruction, [instruction](float a) -> float {
// If the input value is NaN or less than zero, set the invalid op flag.
if (FPTypeInfo<float>::IsNaN(a) || (a < 0.0)) {
if (!FPTypeInfo<float>::IsQNaN(a)) {
auto* flag_db = instruction->Destination(1)->AllocateDataBuffer();
flag_db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
flag_db->Submit();
}
return *reinterpret_cast<const float*>(&FPTypeInfo<float>::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 float*>(
&FPTypeInfo<float>::kCanonicalNaN);
}
rm_value = *rv_fp->GetRoundingMode();
}
float 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 RiscVFMin(const Instruction* instruction) {
RiscVBinaryNaNBoxOp<FPRegister::ValueType, float, float>(
instruction, [instruction](float a, float b) -> float {
if (FPTypeInfo<float>::IsSNaN(a) || FPTypeInfo<float>::IsSNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
if (FPTypeInfo<float>::IsNaN(a)) {
if (FPTypeInfo<float>::IsNaN(b)) {
FPTypeInfo<float>::UIntType not_a_number =
FPTypeInfo<float>::kCanonicalNaN;
return *reinterpret_cast<float*>(&not_a_number);
}
return b;
}
if (FPTypeInfo<float>::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 RiscVFMax(const Instruction* instruction) {
RiscVBinaryNaNBoxOp<FPRegister::ValueType, float, float>(
instruction, [instruction](float a, float b) -> float {
if (FPTypeInfo<float>::IsSNaN(a) || FPTypeInfo<float>::IsSNaN(b)) {
auto* db = instruction->Destination(1)->AllocateDataBuffer();
db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
db->Submit();
}
if (FPTypeInfo<float>::IsNaN(a)) {
if (FPTypeInfo<float>::IsNaN(b)) {
FPTypeInfo<float>::UIntType not_a_number =
FPTypeInfo<float>::kCanonicalNaN;
return *reinterpret_cast<float*>(&not_a_number);
}
return b;
}
if (FPTypeInfo<float>::IsNaN(b)) return a;
// If both are zero, return the positive zero if there is one.
if ((a == 0.0) && (b == 0.0)) return (std::signbit(b)) ? a : b;
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 RiscVFMadd(const Instruction* instruction) {
using T = float;
RiscVTernaryFloatNaNBoxOp<FPRegister::ValueType, T, T>(
instruction, [instruction](T a, T b, T c) -> T {
// Propagate any NaNs.
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();
}
return internal::CanonicalizeNaN(fma(a, b, c));
});
}
void RiscVFMsub(const Instruction* instruction) {
using T = float;
RiscVTernaryFloatNaNBoxOp<FPRegister::ValueType, T, T>(
instruction, [instruction](T a, T b, T c) -> T {
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();
}
return internal::CanonicalizeNaN(fma(a, b, -c));
});
}
void RiscVFNmadd(const Instruction* instruction) {
using T = float;
RiscVTernaryFloatNaNBoxOp<FPRegister::ValueType, T, T>(
instruction, [instruction](T a, T b, T c) -> T {
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();
}
return internal::CanonicalizeNaN(fma(-a, b, -c));
});
}
void RiscVFNmsub(const Instruction* instruction) {
using T = float;
RiscVTernaryFloatNaNBoxOp<FPRegister::ValueType, T, T>(
instruction, [instruction](T a, T b, T c) -> T {
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();
}
return internal::CanonicalizeNaN(fma(-a, b, c));
});
}
// Set sign of the first operand to that of the second.
void RiscVFSgnj(const Instruction* instruction) {
RiscVBinaryNaNBoxOp<FPRegister::ValueType, FPUInt, FPUInt>(
instruction,
[](FPUInt a, FPUInt b) { return (a & 0x7fff'ffff) | (b & 0x8000'0000); });
}
// Set the sign of the first operand to the opposite of the second.
void RiscVFSgnjn(const Instruction* instruction) {
RiscVBinaryNaNBoxOp<FPRegister::ValueType, FPUInt, FPUInt>(
instruction, [](FPUInt a, FPUInt b) {
return (a & 0x7fff'ffff) | (~b & 0x8000'0000);
});
}
// Set the sign of the first operand to the xor of the signs of the two
// operands.
void RiscVFSgnjx(const Instruction* instruction) {
RiscVBinaryNaNBoxOp<FPRegister::ValueType, FPUInt, FPUInt>(
instruction, [](FPUInt a, FPUInt b) {
return (a & 0x7fff'ffff) | ((a ^ b) & 0x8000'0000);
});
}
// Convert signed 32 bit integer to float.
void RiscVFCvtSw(const Instruction* instruction) {
RiscVUnaryFloatNaNBoxOp<FPRegister::ValueType, uint32_t, float, int32_t>(
instruction, [](int32_t a) -> float { return static_cast<float>(a); });
}
// Convert unsigned 32 bit integer to float.
void RiscVFCvtSwu(const Instruction* instruction) {
RiscVUnaryFloatNaNBoxOp<FPRegister::ValueType, uint32_t, float, uint32_t>(
instruction, [](uint32_t a) -> float { return static_cast<float>(a); });
}
// Convert signed 64 bit integer to float.
void RiscVFCvtSl(const Instruction* instruction) {
RiscVUnaryFloatNaNBoxOp<FPRegister::ValueType, uint64_t, float, int64_t>(
instruction, [](int64_t a) -> float { return static_cast<float>(a); });
}
// Convert unsigned 64 bit integer to float.
void RiscVFCvtSlu(const Instruction* instruction) {
RiscVUnaryFloatNaNBoxOp<FPRegister::ValueType, uint64_t, float, uint64_t>(
instruction, [](uint64_t a) -> float { return static_cast<float>(a); });
}
// Single precision move instruction from integer to fp register file.
void RiscVFMvwx(const Instruction* instruction) {
RiscVUnaryNaNBoxOp<FPRegister::ValueType, uint32_t, uint32_t, uint32_t>(
instruction, [](uint32_t a) -> uint32_t { return a; });
}
namespace RV32 {
using XRegister = RV32Register;
using XUint = typename std::make_unsigned<XRegister::ValueType>::type;
using XInt = typename std::make_signed<XRegister::ValueType>::type;
void RiscVFSw(const Instruction* instruction) {
using T = uint32_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();
}
// Single precision conversion instructions.
// Convert float to signed 32 bit integer.
void RiscVFCvtWs(const Instruction* instruction) {
internal::RVFCvtWs<XInt>(instruction);
}
// Convert float to unsigned 32 bit integer.
void RiscVFCvtWus(const Instruction* instruction) {
internal::RVFCvtWus<XUint>(instruction);
}
// Single precision move instruction to integer register file, with
// sign-extension.
void RiscVFMvxw(const Instruction* instruction) {
RiscVUnaryOp<XRegister, int32_t, int32_t>(instruction,
[](int32_t a) { return a; });
}
// Single precision compare equal.
void RiscVFCmpeq(const Instruction* instruction) {
internal::RVFCmpeq<XRegister>(instruction);
}
// Single precicion compare less than.
void RiscVFCmplt(const Instruction* instruction) {
internal::RVFCmplt<XRegister>(instruction);
}
// Single precision compare less than or equal.
void RiscVFCmple(const Instruction* instruction) {
internal::RVFCmple<XRegister>(instruction);
}
// Single precision fp class instruction.
void RiscVFClass(const Instruction* instruction) {
RiscVUnaryOp<XRegister, uint32_t, float>(
instruction,
[](float a) -> uint32_t { return static_cast<uint32_t>(ClassifyFP(a)); });
}
} // namespace RV32
namespace RV64 {
using XRegister = RV64Register;
using XUint = typename std::make_unsigned<XRegister::ValueType>::type;
using XInt = typename std::make_signed<XRegister::ValueType>::type;
void RiscVFSw(const Instruction* instruction) {
using T = uint32_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 float to signed 32 bit integer in a 64 bit register.
void RiscVFCvtWs(const Instruction* instruction) {
internal::RVFCvtWs<XInt>(instruction);
}
// Convert float to unsigned 32 bit integer in a 64 bit register.
void RiscVFCvtWus(const Instruction* instruction) {
internal::RVFCvtWus<XUint>(instruction);
}
// Convert float to signed 64 bit integer.
void RiscVFCvtLs(const Instruction* instruction) {
internal::RVFCvtLs<XInt>(instruction);
}
// Convert float to unsigned 64 bit integer.
void RiscVFCvtLus(const Instruction* instruction) {
internal::RVFCvtLus<XUint>(instruction);
}
// Single precision move instruction to integer register file, with
// sign-extension.
void RiscVFMvxw(const Instruction* instruction) {
RiscVUnaryOp<XRegister, XInt, int32_t>(
instruction, [](int32_t a) { return static_cast<XInt>(a); });
}
// Single precision move instruction from integer to fp register file.
void RiscVFMvwx(const Instruction* instruction) {
RiscVUnaryNaNBoxOp<FPRegister::ValueType, uint32_t, uint32_t, uint32_t>(
instruction, [](uint32_t a) -> uint32_t { return a; });
}
// Single precision compare equal.
void RiscVFCmpeq(const Instruction* instruction) {
internal::RVFCmpeq<XRegister>(instruction);
}
// Single precicion compare less than.
void RiscVFCmplt(const Instruction* instruction) {
internal::RVFCmplt<XRegister>(instruction);
}
// Single precision compare less than or equal.
void RiscVFCmple(const Instruction* instruction) {
internal::RVFCmple<XRegister>(instruction);
}
// Single precision fp class instruction.
void RiscVFClass(const Instruction* instruction) {
RiscVUnaryOp<XRegister, uint32_t, float>(
instruction,
[](float a) -> uint32_t { return static_cast<uint32_t>(ClassifyFP(a)); });
}
} // namespace RV64
} // namespace riscv
} // namespace sim
} // namespace mpact