blob: 74b6e446e985bc2b169e6d2a8b3b1d54a969fd04 [file] [log] [blame]
/*
* Copyright 2024 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
*
* 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.
*/
#ifndef MPACT_CHERIOT__RISCV_CHERIOT_INSTRUCTION_HELPERS_H_
#define MPACT_CHERIOT__RISCV_CHERIOT_INSTRUCTION_HELPERS_H_
#include <any>
#include <cstdint>
#include <functional>
#include <limits>
#include <tuple>
#include <type_traits>
#include "absl/log/log.h"
#include "cheriot/cheriot_register.h"
#include "cheriot/cheriot_state.h"
#include "cheriot/riscv_cheriot_fp_state.h"
#include "mpact/sim/generic/arch_state.h"
#include "mpact/sim/generic/data_buffer.h"
#include "mpact/sim/generic/instruction.h"
#include "mpact/sim/generic/operand_interface.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_state.h"
namespace mpact {
namespace sim {
namespace cheriot {
using ::mpact::sim::generic::Instruction;
using ::mpact::sim::generic::operator*;
using ::mpact::sim::generic::FPTypeInfo;
using ::mpact::sim::generic::RegisterBase;
using ::mpact::sim::riscv::FPExceptions;
using ::mpact::sim::riscv::FPRoundingMode;
using ::mpact::sim::riscv::ScopedFPRoundingMode;
using ::mpact::sim::riscv::ScopedFPStatus;
using CapReg = CheriotRegister;
using PB = ::mpact::sim::cheriot::CheriotRegister::PermissionBits;
// Get the destination capability register.
static inline CapReg *GetCapDest(const Instruction *instruction, int i) {
return static_cast<CapReg *>(
std::any_cast<RegisterBase *>(instruction->Destination(i)->GetObject()));
}
// Writing an integer result requires invalidating the capability and setting
// it to null.
template <typename Result>
static inline void WriteCapIntResult(const Instruction *instruction, int i,
Result value) {
auto *cap_reg = GetCapDest(instruction, i);
cap_reg->data_buffer()->Set<Result>(0, value);
cap_reg->Invalidate();
cap_reg->set_is_null();
}
// Templated helper function for convert instruction semantic functions.
template <typename From, typename To>
inline std::tuple<To, uint32_t> CvtHelper(From value) {
constexpr From kMax = static_cast<From>(std::numeric_limits<To>::max());
constexpr From kMin = static_cast<From>(std::numeric_limits<To>::min());
if (FPTypeInfo<From>::IsNaN(value)) {
return std::make_tuple(std::numeric_limits<To>::max(),
*FPExceptions::kInvalidOp);
}
if (value > kMax) {
return std::make_tuple(std::numeric_limits<To>::max(),
*FPExceptions::kInvalidOp);
}
if (value < kMin) {
if (std::is_unsigned<To>::value && (value > -1.0)) {
using SignedTo = typename std::make_signed<To>::type;
SignedTo signed_val = static_cast<SignedTo>(value);
if (signed_val == 0) {
return std::make_tuple(0, *FPExceptions::kInexact);
}
}
return std::make_tuple(std::numeric_limits<To>::min(),
*FPExceptions::kInvalidOp);
}
auto output_value = static_cast<To>(value);
return std::make_tuple(output_value, 0);
}
// TODO(torerik): Modify as needed if used to produce values in capability
// registers.
// Generic helper function for floating op instructions that do not
// require NaN boxing since they produce non fp-values, but set fflags.
template <typename CapRegType, typename Result, typename From, typename To>
inline void RiscVConvertFloatWithFflagsOp(const Instruction *instruction) {
constexpr From kMax = static_cast<From>(std::numeric_limits<To>::max());
constexpr From kMin = static_cast<From>(std::numeric_limits<To>::min());
From lhs = generic::GetInstructionSource<From>(instruction, 0);
uint32_t flags = 0;
uint32_t rm = generic::GetInstructionSource<uint32_t>(instruction, 1);
To value = 0;
if (FPTypeInfo<From>::IsNaN(lhs)) {
value = std::numeric_limits<To>::max();
flags = *FPExceptions::kInvalidOp;
} else if (lhs >= kMax) {
value = std::numeric_limits<To>::max();
flags = *FPExceptions::kInvalidOp;
} else if (lhs < kMin) {
bool is_set = false;
if (std::is_unsigned<To>::value && (lhs > -1.0)) {
using SignedTo = typename std::make_signed<To>::type;
SignedTo signed_val = static_cast<SignedTo>(lhs);
if (signed_val == 0) {
value = 0;
flags = *FPExceptions::kInexact;
is_set = true;
}
}
if (!is_set) {
value = std::numeric_limits<To>::min();
flags = *FPExceptions::kInvalidOp;
is_set = true;
}
} else if (lhs == 0.0) {
value = 0;
} else {
// static_cast<>() doesn't necessarily round, so will have to force
// rounding before converting to the integer type if necessary.
using FromUint = typename FPTypeInfo<From>::UIntType;
auto constexpr kBias = FPTypeInfo<From>::kExpBias;
auto constexpr kExpMask = FPTypeInfo<From>::kExpMask;
auto constexpr kSigSize = FPTypeInfo<From>::kSigSize;
auto constexpr kSigMask = FPTypeInfo<From>::kSigMask;
FromUint lhs_u = *reinterpret_cast<FromUint *>(&lhs);
FromUint exp = kExpMask & lhs_u;
int exp_value = exp >> kSigSize;
int unbiased_exp = exp_value - kBias;
FromUint sig = kSigMask & lhs_u;
// If the number of bits in the significand is greater or equal to
// unbiased exponent, and there is a 1 among the extra bits, we need to
// perform rounding.
if (unbiased_exp < 0) {
flags = *FPExceptions::kInexact;
} else if (unbiased_exp <= kSigSize) {
FromUint mask = (1ULL << (kSigSize - unbiased_exp)) - 1;
if ((sig & mask) != 0) {
flags = *FPExceptions::kInexact;
FromUint sign = lhs_u & (1ULL << (FPTypeInfo<From>::kBitSize - 1));
// Turn the value into a denormal.
constexpr FromUint hidden_bit = 1ULL << (kSigSize - 1);
FromUint tmp_u = sign | hidden_bit | (sig >> 1);
From tmp = *reinterpret_cast<From *>(&tmp_u);
// Divide so that only the bits we care about are left in the
// significand.
int shift = kBias + kSigSize - exp_value - 1;
FromUint div_exp = shift + kBias;
FromUint div_u = div_exp << kSigSize;
From div = *reinterpret_cast<From *>(&div_u);
auto *rv_fp =
static_cast<CheriotState *>(instruction->state())->rv_fp();
auto *host_fp_interface = rv_fp->host_fp_interface();
{
// The rounding happens during this division.
ScopedFPRoundingMode set_fp_rm(host_fp_interface, rm);
tmp /= div;
}
// Convert back to normalized number, by using the original sign
// and exponent, and the normalized and significand from the division.
tmp_u = *reinterpret_cast<FromUint *>(&tmp);
lhs_u = sign | exp | ((tmp_u << (shift + 1)) & kSigMask);
lhs = *reinterpret_cast<From *>(&lhs_u);
}
}
value = static_cast<To>(lhs);
}
using SignedTo = typename std::make_signed<To>::type;
// The final value is sign-extended to the register width, even if it's
// conversion to an unsigned value.
SignedTo signed_value = static_cast<SignedTo>(value);
Result dest_value = static_cast<Result>(signed_value);
WriteCapIntResult(instruction, 0, dest_value);
if (flags) {
auto *flag_db = instruction->Destination(1)->AllocateDataBuffer();
flag_db->Set<uint32_t>(0, flags);
flag_db->Submit();
}
}
// Helper function to read a NaN boxed source value, converting it to NaN if
// it isn't formatted properly.
template <typename RegValue, typename Argument>
inline Argument GetNaNBoxedSource(const Instruction *instruction, int arg) {
if (sizeof(RegValue) <= sizeof(Argument)) {
return generic::GetInstructionSource<Argument>(instruction, arg);
} else {
using SInt = typename std::make_signed<RegValue>::type;
using UInt = typename std::make_unsigned<RegValue>::type;
SInt val = generic::GetInstructionSource<SInt>(instruction, arg);
UInt uval = static_cast<UInt>(val);
UInt mask = std::numeric_limits<UInt>::max() << (sizeof(Argument) * 8);
if (((mask & uval) != mask)) {
return *reinterpret_cast<const Argument *>(
&FPTypeInfo<Argument>::kCanonicalNaN);
}
return generic::GetInstructionSource<Argument>(instruction, arg);
}
}
template <typename Register, typename Result, typename Argument>
inline void RiscVBinaryOp(const Instruction *instruction,
std::function<Result(Argument, Argument)> operation) {
using RegValue = typename Register::ValueType;
Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
Argument rhs = generic::GetInstructionSource<Argument>(instruction, 1);
Result dest_value = operation(lhs, rhs);
auto *reg = static_cast<generic::RegisterDestinationOperand<RegValue> *>(
instruction->Destination(0))
->GetRegister();
reg->data_buffer()->template Set<Result>(0, dest_value);
}
// Generic helper functions for binary instructions. Clears the tag bit for
// the capability register and sets it to null.
template <typename Register, typename Result, typename Argument1,
typename Argument2>
inline void RVCheriotBinaryOp(
const Instruction *instruction,
std::function<Result(Argument1, Argument2)> operation) {
Argument1 lhs = generic::GetInstructionSource<Argument1>(instruction, 0);
Argument2 rhs = generic::GetInstructionSource<Argument2>(instruction, 1);
Result dest_value = operation(lhs, rhs);
WriteCapIntResult(instruction, 0, dest_value);
}
template <typename Register, typename Result, typename Argument>
inline void RVCheriotBinaryOp(
const Instruction *instruction,
std::function<Result(Argument, Argument)> operation) {
Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
Argument rhs = generic::GetInstructionSource<Argument>(instruction, 1);
Result dest_value = operation(lhs, rhs);
WriteCapIntResult(instruction, 0, dest_value);
}
template <typename Register, typename Result>
inline void RVCheriotBinaryOp(const Instruction *instruction,
std::function<Result(Result, Result)> operation) {
Result lhs = generic::GetInstructionSource<Result>(instruction, 0);
Result rhs = generic::GetInstructionSource<Result>(instruction, 1);
Result dest_value = operation(lhs, rhs);
WriteCapIntResult(instruction, 0, dest_value);
}
// Generic helper function for unary instructions.
template <typename Register, typename Result, typename Argument>
inline void RVCheriotUnaryOp(const Instruction *instruction,
std::function<Result(Argument)> operation) {
auto lhs = generic::GetInstructionSource<Argument>(instruction, 0);
Result dest_value = operation(lhs);
WriteCapIntResult(instruction, 0, dest_value);
}
// Helper function for conditional branches.
template <typename RegisterType, typename ValueType>
static inline void RVCheriotBranchConditional(
const Instruction *instruction,
std::function<bool(ValueType, ValueType)> cond) {
using UIntType =
typename std::make_unsigned<typename RegisterType::ValueType>::type;
ValueType a = generic::GetInstructionSource<ValueType>(instruction, 0);
ValueType b = generic::GetInstructionSource<ValueType>(instruction, 1);
if (cond(a, b)) {
UIntType offset = generic::GetInstructionSource<UIntType>(instruction, 2);
UIntType target = offset + instruction->address();
auto *state = static_cast<CheriotState *>(instruction->state());
auto *pcc = state->pcc();
if (!pcc->HasPermission(PB::kPermitExecute)) {
state->HandleCheriRegException(
instruction, pcc->address(),
ExceptionCode::kCapExPermitExecuteViolation, pcc);
return;
}
pcc->set_address(target);
state->set_branch(true);
}
}
// Generic helper function for load instructions.
template <typename Register, typename ValueType>
inline void RVCheriotLoad(const Instruction *instruction) {
using RegVal = typename Register::ValueType;
using URegVal = typename std::make_unsigned<RegVal>::type;
// Bet the capability.
auto *cap_reg = static_cast<CheriotRegister *>(
static_cast<generic::RegisterSourceOperand<RegVal> *>(
instruction->Source(0))
->GetRegister());
URegVal base = cap_reg->address();
RegVal offset = generic::GetInstructionSource<RegVal>(instruction, 1);
URegVal address = base + offset;
auto *state = static_cast<CheriotState *>(instruction->state());
// Check for tag unset.
if (!cap_reg->tag()) {
state->HandleCheriRegException(instruction, instruction->address(),
ExceptionCode::kCapExTagViolation, cap_reg);
return;
}
// Check for sealed.
if (cap_reg->IsSealed()) {
state->HandleCheriRegException(instruction, instruction->address(),
ExceptionCode::kCapExSealViolation, cap_reg);
return;
}
// Check for permissions.
if (!cap_reg->HasPermission(CheriotRegister::kPermitLoad)) {
state->HandleCheriRegException(instruction, instruction->address(),
ExceptionCode::kCapExPermitLoadViolation,
cap_reg);
return;
}
// Check for bounds.
if (!cap_reg->IsInBounds(address, sizeof(ValueType))) {
state->HandleCheriRegException(instruction, instruction->address(),
ExceptionCode::kCapExBoundsViolation,
cap_reg);
return;
}
auto *value_db =
instruction->state()->db_factory()->Allocate(sizeof(ValueType));
value_db->set_latency(0);
auto *context = new riscv::LoadContext(value_db);
state->LoadMemory(instruction, address, value_db, instruction->child(),
context);
context->DecRef();
}
// Generic helper function for load instructions' "child instruction".
template <typename Register, typename ValueType>
inline void RVCheriotLoadChild(const Instruction *instruction) {
using RegVal = typename Register::ValueType;
using URegVal = typename std::make_unsigned<RegVal>::type;
using SRegVal = typename std::make_signed<URegVal>::type;
riscv::LoadContext *context =
static_cast<riscv::LoadContext *>(instruction->context());
if (std::is_signed<ValueType>::value) {
SRegVal value = static_cast<SRegVal>(context->value_db->Get<ValueType>(0));
WriteCapIntResult(instruction, 0, value);
} else {
URegVal value = static_cast<URegVal>(context->value_db->Get<ValueType>(0));
WriteCapIntResult(instruction, 0, value);
}
}
// Generic helper function for store instructions.
template <typename RegisterType, typename ValueType>
inline void RVCheriotStore(const Instruction *instruction) {
using URegVal =
typename std::make_unsigned<typename RegisterType::ValueType>::type;
using SRegVal = typename std::make_signed<URegVal>::type;
ValueType value = generic::GetInstructionSource<ValueType>(instruction, 2);
auto *cap_reg = static_cast<CheriotRegister *>(
static_cast<
generic::RegisterSourceOperand<typename RegisterType::ValueType> *>(
instruction->Source(0))
->GetRegister());
URegVal base = cap_reg->address();
SRegVal offset = generic::GetInstructionSource<SRegVal>(instruction, 1);
URegVal address = base + offset;
auto *state = static_cast<CheriotState *>(instruction->state());
// Check for tag unset.
if (!cap_reg->tag()) {
state->HandleCheriRegException(instruction, instruction->address(),
ExceptionCode::kCapExTagViolation, cap_reg);
return;
}
// Check for sealed.
if (cap_reg->IsSealed()) {
state->HandleCheriRegException(instruction, instruction->address(),
ExceptionCode::kCapExSealViolation, cap_reg);
return;
}
// Check for permissions.
if (!cap_reg->HasPermission(CheriotRegister::kPermitStore)) {
state->HandleCheriRegException(instruction, instruction->address(),
ExceptionCode::kCapExPermitStoreViolation,
cap_reg);
return;
}
// Check for bounds.
if (!cap_reg->IsInBounds(address, sizeof(ValueType))) {
state->HandleCheriRegException(instruction, instruction->address(),
ExceptionCode::kCapExBoundsViolation,
cap_reg);
return;
}
auto *db = state->db_factory()->Allocate(sizeof(ValueType));
db->Set<ValueType>(0, value);
state->StoreMemory(instruction, address, db);
db->DecRef();
}
// Generic helper function for binary instructions that take NaN boxed sources
// but produce the result in a capability register.
template <typename RegValue, typename Result, typename Argument>
inline void RVCheriotBinaryNaNBoxOp(
const Instruction *instruction,
std::function<Result(Argument, Argument)> operation) {
Argument lhs = GetNaNBoxedSource<RegValue, Argument>(instruction, 0);
Argument rhs = GetNaNBoxedSource<RegValue, Argument>(instruction, 1);
Result dest_value = operation(lhs, rhs);
// Check to see if we need to NaN box the result.
if (sizeof(RegValue) > sizeof(Result)) {
// If the floating point value is narrower than the register, the upper
// bits have to be set to all ones.
using UReg = typename std::make_unsigned<RegValue>::type;
using UInt = typename FPTypeInfo<Result>::UIntType;
auto dest_u_value = *reinterpret_cast<UInt *>(&dest_value);
UReg reg_value = std::numeric_limits<UReg>::max();
int shift = 8 * (sizeof(RegValue) - sizeof(Result));
reg_value = (reg_value << shift) | dest_u_value;
WriteCapIntResult(instruction, 0, reg_value);
return;
}
WriteCapIntResult(instruction, 0, dest_value);
}
// Generic helper function for binary instructions with NaN boxing. This is
// used for those instructions that produce results in fp registers, but are
// not really executing an fp operation that requires rounding.
template <typename RegValue, typename Result, typename Argument>
inline void RiscVBinaryNaNBoxOp(
const Instruction *instruction,
std::function<Result(Argument, Argument)> operation) {
Argument lhs = GetNaNBoxedSource<RegValue, Argument>(instruction, 0);
Argument rhs = GetNaNBoxedSource<RegValue, Argument>(instruction, 1);
Result dest_value = operation(lhs, rhs);
auto *reg = static_cast<generic::RegisterDestinationOperand<RegValue> *>(
instruction->Destination(0))
->GetRegister();
// Check to see if we need to NaN box the result.
if (sizeof(RegValue) > sizeof(Result)) {
// If the floating point value is narrower than the register, the upper
// bits have to be set to all ones.
using UReg = typename std::make_unsigned<RegValue>::type;
using UInt = typename FPTypeInfo<Result>::UIntType;
auto dest_u_value = *reinterpret_cast<UInt *>(&dest_value);
UReg reg_value = std::numeric_limits<UReg>::max();
int shift = 8 * (sizeof(RegValue) - sizeof(Result));
reg_value = (reg_value << shift) | dest_u_value;
reg->data_buffer()->template Set<RegValue>(0, reg_value);
return;
}
reg->data_buffer()->template Set<Result>(0, dest_value);
}
// Generic helper function for unary instructions with NaN boxing.
template <typename DstRegValue, typename SrcRegValue, typename Result,
typename Argument>
inline void RiscVUnaryNaNBoxOp(const Instruction *instruction,
std::function<Result(Argument)> operation) {
Argument lhs = GetNaNBoxedSource<SrcRegValue, Argument>(instruction, 0);
Result dest_value = operation(lhs);
// Check to see if we need to NaN box the result.
if (sizeof(DstRegValue) > sizeof(Result)) {
// If the floating point value is narrower than the register, the upper
// bits have to be set to all ones.
using UReg = typename std::make_unsigned<DstRegValue>::type;
using UInt = typename FPTypeInfo<Result>::UIntType;
auto dest_u_value = *reinterpret_cast<UInt *>(&dest_value);
UReg reg_value = std::numeric_limits<UReg>::max();
int shift = 8 * (sizeof(DstRegValue) - sizeof(Result));
reg_value = (reg_value << shift) | dest_u_value;
WriteCapIntResult(instruction, 0, reg_value);
return;
}
WriteCapIntResult(instruction, 0, dest_value);
}
// TODO(torerik): Modify as needed if used to produce values in capability
// registers.
// Generic helper function for unary floating point instructions. The main
// difference is that it handles rounding mode and performs NaN boxing.
template <typename DstRegValue, typename SrcRegValue, typename Result,
typename Argument>
inline void RiscVUnaryFloatNaNBoxOp(const Instruction *instruction,
std::function<Result(Argument)> operation) {
using ResUint = typename FPTypeInfo<Result>::UIntType;
Argument lhs = GetNaNBoxedSource<SrcRegValue, Argument>(instruction, 0);
// Get the rounding mode.
int rm_value = generic::GetInstructionSource<int>(instruction, 1);
// If the rounding mode is dynamic, read it from the current state.
auto *rv_fp = static_cast<CheriotState *>(instruction->state())->rv_fp();
if (rm_value == *FPRoundingMode::kDynamic) {
if (!rv_fp->rounding_mode_valid()) {
LOG(ERROR) << "Invalid rounding mode";
return;
}
rm_value = *(rv_fp->GetRoundingMode());
}
Result dest_value;
{
ScopedFPStatus set_fp_status(rv_fp->host_fp_interface(), rm_value);
dest_value = operation(lhs);
}
if (std::isnan(dest_value) && std::signbit(dest_value)) {
ResUint res_value = *reinterpret_cast<ResUint *>(&dest_value);
res_value &= FPTypeInfo<Result>::kInfMask;
dest_value = *reinterpret_cast<Result *>(&res_value);
}
auto *dest = instruction->Destination(0);
auto *reg_dest =
static_cast<generic::RegisterDestinationOperand<DstRegValue> *>(dest);
auto *reg = reg_dest->GetRegister();
// Check to see if we need to NaN box the result.
if (sizeof(DstRegValue) > sizeof(Result)) {
// If the floating point Value is narrower than the register, the upper
// bits have to be set to all ones.
using UReg = typename std::make_unsigned<DstRegValue>::type;
using UInt = typename FPTypeInfo<Result>::UIntType;
auto dest_u_value = *reinterpret_cast<UInt *>(&dest_value);
UReg reg_value = std::numeric_limits<UReg>::max();
int shift = 8 * (sizeof(DstRegValue) - sizeof(Result));
reg_value = (reg_value << shift) | dest_u_value;
reg->data_buffer()->template Set<DstRegValue>(0, reg_value);
return;
}
reg->data_buffer()->template Set<Result>(0, dest_value);
}
// TODO(torerik): Modify as needed if used to produce values in capability
// registers.
// Generic helper function for floating op instructions that do not require
// NaN boxing since they produce non fp-values.
template <typename Result, typename Argument>
inline void RiscVUnaryFloatOp(const Instruction *instruction,
std::function<Result(Argument)> operation) {
Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
// Get the rounding mode.
int rm_value = generic::GetInstructionSource<int>(instruction, 1);
auto *rv_fp = static_cast<CheriotState *>(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;
}
rm_value = *rv_fp->GetRoundingMode();
}
Result dest_value;
{
ScopedFPStatus set_fp_status(rv_fp->host_fp_interface(), rm_value);
dest_value = operation(lhs);
}
auto *dest = instruction->Destination(0);
using UInt = typename FPTypeInfo<Result>::UIntType;
auto *reg_dest =
static_cast<generic::RegisterDestinationOperand<UInt> *>(dest);
auto *reg = reg_dest->GetRegister();
reg->data_buffer()->template Set<Result>(0, dest_value);
}
// TODO(torerik): Modify as needed if used to produce values in capability
// registers.
// Generic helper function for floating op instructions that do not require
// NaN boxing since they produce non fp-values, but set fflags.
template <typename Result, typename Argument>
inline void RiscVUnaryFloatWithFflagsOp(
const Instruction *instruction,
std::function<Result(Argument, uint32_t &)> operation) {
Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
// Get the rounding mode.
int rm_value = generic::GetInstructionSource<int>(instruction, 1);
auto *rv_fp = static_cast<CheriotState *>(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;
}
rm_value = *rv_fp->GetRoundingMode();
}
uint32_t flag = 0;
Result dest_value;
{
ScopedFPStatus set_fp_status(rv_fp->host_fp_interface(), rm_value);
dest_value = operation(lhs, flag);
}
auto *dest = instruction->Destination(0);
using UInt = typename FPTypeInfo<Result>::UIntType;
auto *reg_dest =
static_cast<generic::RegisterDestinationOperand<UInt> *>(dest);
auto *reg = reg_dest->GetRegister();
reg->data_buffer()->template Set<Result>(0, dest_value);
auto *flag_db = instruction->Destination(1)->AllocateDataBuffer();
flag_db->Set<uint32_t>(0, flag);
flag_db->Submit();
}
// TODO(torerik): Modify as needed if used to produce values in capability
// registers.
// Generic helper function for binary floating point instructions. The main
// difference is that it handles rounding mode.
template <typename Register, typename Result, typename Argument>
inline void RiscVBinaryFloatNaNBoxOp(
const Instruction *instruction,
std::function<Result(Argument, Argument)> operation) {
Argument lhs = GetNaNBoxedSource<Register, Argument>(instruction, 0);
Argument rhs = GetNaNBoxedSource<Register, Argument>(instruction, 1);
// Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
// Argument rhs = generic::GetInstructionSource<Argument>(instruction, 1);
// Get the rounding mode.
int rm_value = generic::GetInstructionSource<int>(instruction, 2);
auto *rv_fp = static_cast<CheriotState *>(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;
}
rm_value = *rv_fp->GetRoundingMode();
}
Result dest_value;
{
ScopedFPStatus fp_status(rv_fp->host_fp_interface(), rm_value);
dest_value = operation(lhs, rhs);
}
if (std::isnan(dest_value)) {
*reinterpret_cast<typename FPTypeInfo<Result>::UIntType *>(&dest_value) =
FPTypeInfo<Result>::kCanonicalNaN;
}
auto *reg = static_cast<generic::RegisterDestinationOperand<Register> *>(
instruction->Destination(0))
->GetRegister();
// Check to see if we need to NaN box the result.
if (sizeof(Register) > sizeof(Result)) {
// If the floating point value is narrower than the register, the upper
// bits have to be set to all ones.
using UReg = typename std::make_unsigned<Register>::type;
using UInt = typename FPTypeInfo<Result>::UIntType;
auto dest_u_value = *reinterpret_cast<UInt *>(&dest_value);
UReg reg_value = std::numeric_limits<UReg>::max();
int shift = 8 * (sizeof(Register) - sizeof(Result));
reg_value = (reg_value << shift) | dest_u_value;
reg->data_buffer()->template Set<Register>(0, reg_value);
return;
}
reg->data_buffer()->template Set<Result>(0, dest_value);
}
// TODO(torerik): Modify as needed if used to produce values in capability
// registers.
// Generic helper function for ternary floating point instructions.
template <typename Register, typename Result, typename Argument>
inline void RiscVTernaryFloatNaNBoxOp(
const Instruction *instruction,
std::function<Result(Argument, Argument, Argument)> operation) {
Argument rs1 = generic::GetInstructionSource<Argument>(instruction, 0);
Argument rs2 = generic::GetInstructionSource<Argument>(instruction, 1);
Argument rs3 = generic::GetInstructionSource<Argument>(instruction, 2);
// Get the rounding mode.
int rm_value = generic::GetInstructionSource<int>(instruction, 3);
auto *rv_fp = static_cast<CheriotState *>(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;
}
rm_value = *rv_fp->GetRoundingMode();
}
Result dest_value;
{
ScopedFPStatus fp_status(rv_fp->host_fp_interface(), rm_value);
dest_value = operation(rs1, rs2, rs3);
}
auto *reg = static_cast<generic::RegisterDestinationOperand<Register> *>(
instruction->Destination(0))
->GetRegister();
// Check to see if we need to NaN box the result.
if (sizeof(Register) > sizeof(Result)) {
// If the floating point value is narrower than the register, the upper
// bits have to be set to all ones.
using UReg = typename std::make_unsigned<Register>::type;
using UInt = typename FPTypeInfo<Result>::UIntType;
auto dest_u_value = *reinterpret_cast<UInt *>(&dest_value);
UReg reg_value = std::numeric_limits<UReg>::max();
int shift = 8 * (sizeof(Register) - sizeof(Result));
reg_value = (reg_value << shift) | dest_u_value;
reg->data_buffer()->template Set<Register>(0, reg_value);
return;
}
reg->data_buffer()->template Set<Result>(0, dest_value);
}
// Helper function to classify floating point values.
template <typename T>
typename FPTypeInfo<T>::UIntType ClassifyFP(T val) {
using UIntType = typename FPTypeInfo<T>::UIntType;
auto int_value = *reinterpret_cast<UIntType *>(&val);
UIntType sign = int_value >> (FPTypeInfo<T>::kBitSize - 1);
UIntType exp_mask = (1 << FPTypeInfo<T>::kExpSize) - 1;
UIntType exp = (int_value >> FPTypeInfo<T>::kSigSize) & exp_mask;
UIntType sig =
int_value & ((static_cast<UIntType>(1) << FPTypeInfo<T>::kSigSize) - 1);
if (exp == 0) { // The number is denormal or zero.
if (sig == 0) { // The number is zero.
return sign ? 1 << 3 : 1 << 4;
} else { // subnormal.
return sign ? 1 << 2 : 1 << 5;
}
} else if (exp == exp_mask) { // The number is infinity or NaN.
if (sig == 0) { // infinity
return sign ? 1 : 1 << 7;
} else {
if ((sig >> (FPTypeInfo<T>::kSigSize - 1)) != 0) { // Quiet NaN.
return 1 << 9;
} else { // signaling NaN.
return 1 << 8;
}
}
}
return sign ? 1 << 1 : 1 << 6;
}
} // namespace cheriot
} // namespace sim
} // namespace mpact
#endif // MPACT_CHERIOT__RISCV_CHERIOT_INSTRUCTION_HELPERS_H_