No public description PiperOrigin-RevId: 641011171 Change-Id: If0c4721ea3c770021f1d7e76d849b084cd501351
diff --git a/cheriot/BUILD b/cheriot/BUILD index 7d7e169..45446af 100644 --- a/cheriot/BUILD +++ b/cheriot/BUILD
@@ -54,8 +54,6 @@ "cheriot_register.cc", "cheriot_state.cc", "riscv_cheriot_a_instructions.cc", - "riscv_cheriot_f_instructions.cc", - "riscv_cheriot_fp_state.cc", "riscv_cheriot_i_instructions.cc", "riscv_cheriot_instructions.cc", "riscv_cheriot_m_instructions.cc", @@ -68,8 +66,6 @@ "cheriot_state.h", "riscv_cheriot_a_instructions.h", "riscv_cheriot_csr_enum.h", - "riscv_cheriot_f_instructions.h", - "riscv_cheriot_fp_state.h", "riscv_cheriot_i_instructions.h", "riscv_cheriot_instruction_helpers.h", "riscv_cheriot_instructions.h", @@ -78,10 +74,6 @@ "riscv_cheriot_priv_instructions.h", "riscv_cheriot_zicsr_instructions.h", ], - copts = [ - "-ffp-model=strict", - "-fprotect-parens", - ], tags = ["not_run:arm"], deps = [ "@com_google_absl//absl/container:flat_hash_map", @@ -317,20 +309,20 @@ ], ) -cc_library( - name = "cheriot_renode", +cc_binary( + name = "renode_mpact_cheriot", srcs = [ "cheriot_cli_forwarder.cc", - "cheriot_renode.cc", - "cheriot_renode_cli_top.cc", - "cheriot_renode_register_info.cc", - ], - hdrs = [ "cheriot_cli_forwarder.h", + "cheriot_renode.cc", "cheriot_renode.h", + "cheriot_renode_cli_top.cc", "cheriot_renode_cli_top.h", + "cheriot_renode_register_info.cc", "cheriot_renode_register_info.h", ], + linkshared = True, + linkstatic = True, deps = [ ":cheriot_debug_info", ":cheriot_debug_interface", @@ -362,16 +354,6 @@ ], ) -cc_binary( - name = "renode_mpact_cheriot", - linkshared = True, - linkstatic = True, - deps = [ - ":cheriot_renode", - "@com_google_mpact-sim//mpact/sim/util/renode", - ], -) - cc_library( name = "cheriot_test_rig_lib", srcs = [
diff --git a/cheriot/cheriot_state.h b/cheriot/cheriot_state.h index 42d22c5..7df9bba 100644 --- a/cheriot/cheriot_state.h +++ b/cheriot/cheriot_state.h
@@ -69,7 +69,6 @@ // Forward declare the CHERIoT register type. class CheriotRegister; -class RiscVCheriotFPState; // CHERIoT exception codes. These are used in addition to the ones defined for // vanilla RiscV. @@ -308,9 +307,6 @@ const CheriotRegister *executable_root() const { return executable_root_; } const CheriotRegister *sealing_root() const { return sealing_root_; } const CheriotRegister *memory_root() const { return memory_root_; } - // Floating point state. - RiscVCheriotFPState *rv_fp() const { return rv_fp_; } - void set_rv_fp(RiscVCheriotFPState *value) { rv_fp_ = value; } // Special capability registers. Pcc replaces the pc. Cgp is a global pointer // capability that is aliased with c3. CheriotRegister *pcc() const { return pcc_; } @@ -390,7 +386,6 @@ bool branch_ = false; uint64_t max_physical_address_; uint64_t min_physical_address_ = 0; - RiscVCheriotFPState *rv_fp_ = nullptr; int num_tags_per_load_; util::TaggedMemoryInterface *owned_tagged_memory_ = nullptr; util::TaggedMemoryInterface *tagged_memory_;
diff --git a/cheriot/cheriot_test_rig.cc b/cheriot/cheriot_test_rig.cc index 0daca50..ae9d3a7 100644 --- a/cheriot/cheriot_test_rig.cc +++ b/cheriot/cheriot_test_rig.cc
@@ -60,8 +60,6 @@ absl::bind_front(&CheriotTestRig::OnStore, this))); // Set up sim state. state_ = new CheriotState(kCheriotTestRigName, tagged_memory_watcher_); - fp_state_ = new RiscVCheriotFPState(state_); - state_->set_rv_fp(fp_state_); // Initialize pcc to 0x8000'0000. pcc_ = static_cast<CheriotRegister *>( state_->registers()->at(CheriotState::kPcName)); @@ -123,7 +121,6 @@ CheriotTestRig::~CheriotTestRig() { delete cheriot_decoder_; delete state_; - delete fp_state_; delete tagged_memory_; delete tagged_memory_watcher_; // Deallocate data buffers.
diff --git a/cheriot/cheriot_test_rig.h b/cheriot/cheriot_test_rig.h index 48f04d3..f8f714c 100644 --- a/cheriot/cheriot_test_rig.h +++ b/cheriot/cheriot_test_rig.h
@@ -23,7 +23,6 @@ #include "cheriot/cheriot_register.h" #include "cheriot/cheriot_state.h" #include "cheriot/cheriot_test_rig_decoder.h" -#include "cheriot/riscv_cheriot_fp_state.h" #include "cheriot/test_rig_packets.h" #include "mpact/sim/generic/component.h" #include "mpact/sim/generic/counters.h" @@ -73,7 +72,6 @@ int trace_version_ = 1; CheriotState *state_; - RiscVCheriotFPState *fp_state_; CheriotRegister *pcc_; CheriotTestRigDecoder *cheriot_decoder_ = nullptr; util::TaggedMemoryInterface *tagged_memory_ = nullptr;
diff --git a/cheriot/cheriot_top.cc b/cheriot/cheriot_top.cc index ad8574d..a8b7221 100644 --- a/cheriot/cheriot_top.cc +++ b/cheriot/cheriot_top.cc
@@ -35,7 +35,6 @@ #include "cheriot/cheriot_register.h" #include "cheriot/cheriot_state.h" #include "cheriot/riscv_cheriot_enums.h" -#include "cheriot/riscv_cheriot_fp_state.h" #include "cheriot/riscv_cheriot_register_aliases.h" #include "mpact/sim/generic/component.h" #include "mpact/sim/generic/core_debug_interface.h" @@ -151,7 +150,6 @@ delete cheriot_decode_cache_; delete cheriot_decoder_; delete state_; - delete fp_state_; delete tagged_watcher_; delete atomic_watcher_; delete atomic_memory_; @@ -164,8 +162,6 @@ atomic_watcher_ = new util::MemoryWatcher(atomic_memory_if_); atomic_memory_ = new util::AtomicMemory(atomic_watcher_); state_ = new CheriotState(kCheriotName, tagged_watcher_, atomic_memory_); - fp_state_ = new RiscVCheriotFPState(state_); - state_->set_rv_fp(fp_state_); pcc_ = static_cast<CheriotRegister *>( state_->registers()->at(CheriotState::kPcName)); // Set up the decoder and decode cache.
diff --git a/cheriot/cheriot_top.h b/cheriot/cheriot_top.h index e07838f..98d0421 100644 --- a/cheriot/cheriot_top.h +++ b/cheriot/cheriot_top.h
@@ -30,7 +30,6 @@ #include "cheriot/cheriot_register.h" #include "cheriot/cheriot_state.h" #include "cheriot/riscv_cheriot_enums.h" -#include "cheriot/riscv_cheriot_fp_state.h" #include "mpact/sim/generic/component.h" #include "mpact/sim/generic/core_debug_interface.h" #include "mpact/sim/generic/counters.h" @@ -179,7 +178,6 @@ absl::Notification *run_halted_ = nullptr; // The local CherIoT state. CheriotState *state_; - RiscVCheriotFPState *fp_state_; // Flag that indicates an instruction needs to be stepped over. bool need_to_step_over_ = false; // Action point manager.
diff --git a/cheriot/riscv_cheriot_fp_state.cc b/cheriot/riscv_cheriot_fp_state.cc deleted file mode 100644 index 1dcbf52..0000000 --- a/cheriot/riscv_cheriot_fp_state.cc +++ /dev/null
@@ -1,182 +0,0 @@ -// 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. - -#include "cheriot/riscv_cheriot_fp_state.h" - -#include <cstdint> - -#include "absl/log/log.h" -#include "cheriot/cheriot_state.h" -#include "mpact/sim/generic/type_helpers.h" -#include "riscv//riscv_csr.h" -#include "riscv//riscv_fp_host.h" -#include "riscv//riscv_fp_info.h" - -namespace mpact { -namespace sim { -namespace cheriot { - -using ::mpact::sim::riscv::FPRoundingMode; -using ::mpact::sim::generic::operator*; // NOLINT: is used below (clang error). -using ::mpact::sim::riscv::GetHostFloatingPointInterface; -using ::mpact::sim::riscv::RiscVCsrEnum; - -constexpr char kFcsrName[] = "fcsr"; -constexpr uint32_t kFcsrInitial = 0b000'0'0'0'0'0; -constexpr uint32_t kFcsrReadMask = 0b111'1'1111; -constexpr uint32_t kFcsrWriteMask = 0b111'1'1111; - -constexpr char kFflagsName[] = "fflags"; -constexpr uint32_t kFflagsInitial = 0b0'0000; -constexpr uint32_t kFflagsReadMask = 0b1'1111; -constexpr uint32_t kFflagsWriteMask = 0b1'1111; - -constexpr char kFrmName[] = "frm"; -constexpr uint32_t kFrmInitial = 0b000; -constexpr uint32_t kFrmReadMask = 0b111; -constexpr uint32_t kFrmWriteMask = 0b111; - -// Helper function to avoid some extra code below. -static inline void LogIfError(absl::Status status) { - if (status.ok()) return; - LOG(ERROR) << status.message(); -} - -RiscVCheriotFPState::RiscVCheriotFPState(CheriotState *rv_state) - : rv_state_(rv_state) { - fcsr_ = new RiscVFcsr(this); - frm_ = new RiscVFrm(this); - fflags_ = new RiscVFflags(this); - host_fp_interface_ = GetHostFloatingPointInterface(); - - LogIfError(rv_state_->csr_set()->AddCsr(fcsr_)); - LogIfError(rv_state_->csr_set()->AddCsr(frm_)); - LogIfError(rv_state_->csr_set()->AddCsr(fflags_)); -} - -RiscVCheriotFPState::~RiscVCheriotFPState() { - delete fcsr_; - delete frm_; - delete fflags_; - delete host_fp_interface_; -} - -FPRoundingMode RiscVCheriotFPState::GetRoundingMode() const { - return rounding_mode_; -} - -void RiscVCheriotFPState::SetRoundingMode(FPRoundingMode mode) { - if (rounding_mode_ != mode) { - switch (mode) { - case FPRoundingMode::kRoundToNearestTiesToMax: - case FPRoundingMode::kRoundToNearest: - break; - case FPRoundingMode::kRoundTowardsZero: - break; - case FPRoundingMode::kRoundDown: - break; - case FPRoundingMode::kRoundUp: - break; - default: - rounding_mode_valid_ = false; - LOG(ERROR) << "Illegal rounding mode: " << static_cast<int>(mode); - return; - } - rounding_mode_ = mode; - frm_->Write(*mode); - rounding_mode_valid_ = true; - } -} - -// The RiscV fp csr. -RiscVFcsr::RiscVFcsr(RiscVCheriotFPState *fp_state) - : RiscVSimpleCsr<uint32_t>(kFcsrName, RiscVCsrEnum::kFCsr, kFcsrInitial, - kFcsrReadMask, kFcsrWriteMask, - fp_state->rv_state()), - fp_state_(fp_state) {} - -// The status value is computed from the most recent x86 status value. -uint32_t RiscVFcsr::AsUint32() { - uint32_t status_value = fp_state_->host_fp_interface()->GetRiscVFcsr(); - auto csr_value = GetUint32(); - auto value = ((csr_value & ~0x1f) | (status_value & 0x1f)) & read_mask(); - return value; -} - -uint64_t RiscVFcsr::AsUint64() { return AsUint32(); } - -// The status value is converted to x86 and stored for use in next fp -// instruction. -void RiscVFcsr::Write(uint32_t value) { - auto wr_value = value & write_mask(); - Set(wr_value); - fp_state_->host_fp_interface()->SetRiscVFcsr(wr_value); - fp_state_->SetRoundingMode( - static_cast<FPRoundingMode>((wr_value >> 5) & 0b111)); -} - -void RiscVFcsr::Write(uint64_t value) { Write(static_cast<uint32_t>(value)); } - -// RiscVFflags translates reads and writes into reads and writes of fcsr. -RiscVFflags::RiscVFflags(RiscVCheriotFPState *fp_state) - : RiscVSimpleCsr<uint32_t>(kFflagsName, RiscVCsrEnum::kFFlags, - kFflagsInitial, kFflagsReadMask, - kFflagsWriteMask, fp_state->rv_state()), - fp_state_(fp_state) {} - -uint32_t RiscVFflags::AsUint32() { - uint32_t value = fp_state_->fcsr()->AsUint32(); - value &= 0b1'1111; - return value; -} - -void RiscVFflags::Write(uint32_t value) { - uint32_t current = fp_state_->fcsr()->AsUint32(); - uint32_t new_value = (current & ~write_mask()) | (value & write_mask()); - if (new_value == current) return; - fp_state_->fcsr()->Write(new_value); -} - -uint32_t RiscVFflags::GetUint32() { return AsUint32(); } - -void RiscVFflags::Set(uint32_t value) { Write(value); } - -// RiscV rm (rounding mode) csr translates reads and writes into reads -// and writes of fcsr. -RiscVFrm::RiscVFrm(RiscVCheriotFPState *fp_state) - : RiscVSimpleCsr<uint32_t>(kFrmName, RiscVCsrEnum::kFrm, kFrmInitial, - kFrmReadMask, kFrmWriteMask, - fp_state->rv_state()), - fp_state_(fp_state) {} - -uint32_t RiscVFrm::AsUint32() { - uint32_t value = fp_state_->fcsr()->AsUint32(); - uint32_t rm = (value >> 5) & read_mask(); - return rm; -} - -void RiscVFrm::Write(uint32_t value) { - uint32_t wr_value = value & write_mask(); - uint32_t fcsr = fp_state_->fcsr()->AsUint32(); - uint32_t new_fcsr = ((fcsr & 0b1'1111) | (wr_value << 5)); - fp_state_->fcsr()->Write(new_fcsr); -} - -uint32_t RiscVFrm::GetUint32() { return AsUint32(); } - -void RiscVFrm::Set(uint32_t value) { Write(value); } - -} // namespace cheriot -} // namespace sim -} // namespace mpact
diff --git a/cheriot/riscv_cheriot_instruction_helpers.h b/cheriot/riscv_cheriot_instruction_helpers.h index 74b6e44..ee5af8d 100644 --- a/cheriot/riscv_cheriot_instruction_helpers.h +++ b/cheriot/riscv_cheriot_instruction_helpers.h
@@ -27,15 +27,12 @@ #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 { @@ -44,12 +41,7 @@ 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; @@ -70,157 +62,6 @@ 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) { @@ -414,335 +255,6 @@ 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
diff --git a/cheriot/test/BUILD b/cheriot/test/BUILD index a65c979..08d716c 100644 --- a/cheriot/test/BUILD +++ b/cheriot/test/BUILD
@@ -121,26 +121,6 @@ ) cc_test( - name = "riscv_cheriot_f_instructions_test", - size = "small", - srcs = [ - "riscv_cheriot_f_instructions_test.cc", - ], - copts = [ - "-ffp-model=strict", - "-fprotect-parens", - ], - tags = ["not_run:arm"], - deps = [ - ":riscv_cheriot_fp_test_base", - "//cheriot:riscv_cheriot", - "@com_google_googletest//:gtest_main", - "@com_google_mpact-riscv//riscv:riscv_state", - "@com_google_mpact-sim//mpact/sim/generic:instruction", - ], -) - -cc_test( name = "riscv_cheriot_zicsr_instructions_test", size = "small", srcs = [
diff --git a/cheriot/test/riscv_cheriot_f_instructions_test.cc b/cheriot/test/riscv_cheriot_f_instructions_test.cc deleted file mode 100644 index 4fb1e27..0000000 --- a/cheriot/test/riscv_cheriot_f_instructions_test.cc +++ /dev/null
@@ -1,347 +0,0 @@ -// 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. - -#include "cheriot/riscv_cheriot_f_instructions.h" - -#include <cmath> -#include <cstdint> -#include <tuple> - -#include "cheriot/test/riscv_cheriot_fp_test_base.h" -#include "googlemock/include/gmock/gmock.h" -#include "mpact/sim/generic/instruction.h" -#include "riscv//riscv_register.h" - -namespace { - -using ::mpact::sim::cheriot::test::FPTypeInfo; -using ::mpact::sim::cheriot::test::OptimizationBarrier; -using ::mpact::sim::cheriot::test::RiscVFPInstructionTestBase; -using ::mpact::sim::riscv::FPExceptions; -using ::mpact::sim::riscv::RVFpRegister; - -using ::mpact::sim::cheriot::RiscVFAdd; -using ::mpact::sim::cheriot::RiscVFClass; -using ::mpact::sim::cheriot::RiscVFCmpeq; -using ::mpact::sim::cheriot::RiscVFCmple; -using ::mpact::sim::cheriot::RiscVFCmplt; -using ::mpact::sim::cheriot::RiscVFCvtSw; -using ::mpact::sim::cheriot::RiscVFCvtSwu; -using ::mpact::sim::cheriot::RiscVFCvtWs; -using ::mpact::sim::cheriot::RiscVFCvtWus; -using ::mpact::sim::cheriot::RiscVFDiv; -using ::mpact::sim::cheriot::RiscVFMadd; -using ::mpact::sim::cheriot::RiscVFMax; -using ::mpact::sim::cheriot::RiscVFMin; -using ::mpact::sim::cheriot::RiscVFMsub; -using ::mpact::sim::cheriot::RiscVFMul; -using ::mpact::sim::cheriot::RiscVFNmadd; -using ::mpact::sim::cheriot::RiscVFNmsub; -using ::mpact::sim::cheriot::RiscVFSgnj; -using ::mpact::sim::cheriot::RiscVFSgnjn; -using ::mpact::sim::cheriot::RiscVFSgnjx; -using ::mpact::sim::cheriot::RiscVFSqrt; -using ::mpact::sim::cheriot::RiscVFSub; - -class RVCheriot32FInstructionTest : public RiscVFPInstructionTestBase {}; - -static bool is_snan(float a) { - if (!std::isnan(a)) return false; - uint32_t ua = *reinterpret_cast<uint32_t *>(&a); - if ((ua & (1 << (FPTypeInfo<float>::kSigSize - 1))) == 0) return true; - return false; -} - -// Test basic arithmetic instructions. -TEST_F(RVCheriot32FInstructionTest, RiscVFadd) { - SetSemanticFunction(&RiscVFAdd); - BinaryOpFPTestHelper<float, float, float>( - "fadd", instruction_, {"f", "f", "f"}, 32, - [](float lhs, float rhs) -> float { return lhs + rhs; }); -} - -TEST_F(RVCheriot32FInstructionTest, RiscVFsub) { - SetSemanticFunction(&RiscVFSub); - BinaryOpFPTestHelper<float, float, float>( - "fsub", instruction_, {"f", "f", "f"}, 32, - [](float lhs, float rhs) -> float { return lhs - rhs; }); -} - -TEST_F(RVCheriot32FInstructionTest, RiscVFmul) { - SetSemanticFunction(&RiscVFMul); - BinaryOpFPTestHelper<float, float, float>( - "fmul", instruction_, {"f", "f", "f"}, 32, - [](float lhs, float rhs) -> float { return lhs * rhs; }); -} - -TEST_F(RVCheriot32FInstructionTest, RiscVFdiv) { - SetSemanticFunction(&RiscVFDiv); - BinaryOpFPTestHelper<float, float, float>( - "fdiv", instruction_, {"f", "f", "f"}, 32, - [](float lhs, float rhs) -> float { return lhs / rhs; }); -} - -// Test square root. -TEST_F(RVCheriot32FInstructionTest, RiscVFsqrt) { - SetSemanticFunction(&RiscVFSqrt); -} - -// Test Min/Max. -TEST_F(RVCheriot32FInstructionTest, RiscVFmin) { - SetSemanticFunction(&RiscVFMin); - BinaryOpWithFflagsFPTestHelper<float, float, float>( - "fmin", instruction_, {"f", "f", "f"}, 32, - [](float lhs, float rhs) -> std::tuple<float, uint32_t> { - uint32_t flag = 0; - if (is_snan(lhs) || is_snan(rhs)) { - flag = static_cast<uint32_t>(FPExceptions::kInvalidOp); - } - if (std::isnan(lhs) && std::isnan(rhs)) { - uint32_t val = FPTypeInfo<float>::kCanonicalNaN; - return std::tie(*reinterpret_cast<const float *>(&val), flag); - } - if (std::isnan(lhs)) return std::tie(rhs, flag); - if (std::isnan(rhs)) return std::tie(lhs, flag); - if ((lhs == 0.0) && (rhs == 0.0)) { - return std::tie(std::signbit(lhs) ? lhs : rhs, flag); - } - return std::tie(lhs > rhs ? rhs : lhs, flag); - }); -} - -TEST_F(RVCheriot32FInstructionTest, RiscVFmax) { - SetSemanticFunction(&RiscVFMax); - BinaryOpWithFflagsFPTestHelper<float, float, float>( - "fmax", instruction_, {"f", "f", "f"}, 32, - [](float lhs, float rhs) -> std::tuple<float, uint32_t> { - uint32_t flag = 0; - if (is_snan(lhs) || is_snan(rhs)) { - flag = static_cast<uint32_t>(FPExceptions::kInvalidOp); - } - if (std::isnan(lhs) && std::isnan(rhs)) { - uint32_t val = FPTypeInfo<float>::kCanonicalNaN; - return std::tie(*reinterpret_cast<const float *>(&val), flag); - } - if (std::isnan(lhs)) return std::tie(rhs, flag); - if (std::isnan(rhs)) return std::tie(lhs, flag); - if ((lhs == 0.0) && (rhs == 0.0)) { - return std::tie(std::signbit(lhs) ? rhs : lhs, flag); - } - return std::tie(lhs < rhs ? rhs : lhs, flag); - }); -} - -// Test MAC versions. -TEST_F(RVCheriot32FInstructionTest, RiscVFMadd) { - SetSemanticFunction(&RiscVFMadd); - TernaryOpFPTestHelper<float, float, float, float>( - "fmadd", instruction_, {"f", "f", "f", "f"}, 32, - [](float lhs, float mhs, float rhs) -> float { - return OptimizationBarrier(lhs * mhs) + rhs; - }); -} -TEST_F(RVCheriot32FInstructionTest, RiscVFMsub) { - SetSemanticFunction(&RiscVFMsub); - TernaryOpFPTestHelper<float, float, float, float>( - "fmsub", instruction_, {"f", "f", "f", "f"}, 32, - [](float lhs, float mhs, float rhs) -> float { - return OptimizationBarrier(lhs * mhs) - rhs; - }); -} -TEST_F(RVCheriot32FInstructionTest, RiscVFNmadd) { - SetSemanticFunction(&RiscVFNmadd); - TernaryOpFPTestHelper<float, float, float, float>( - "fnmadd", instruction_, {"f", "f", "f", "f"}, 32, - [](float lhs, float mhs, float rhs) -> float { - return -OptimizationBarrier(lhs * mhs) - rhs; - }); -} -TEST_F(RVCheriot32FInstructionTest, RiscVFNmsub) { - SetSemanticFunction(&RiscVFNmsub); - TernaryOpFPTestHelper<float, float, float, float>( - "fnmsub", instruction_, {"f", "f", "f", "f"}, 32, - [](float lhs, float mhs, float rhs) -> float { - return -OptimizationBarrier(lhs * mhs) + rhs; - }); -} - -// Test conversion instructions. -// Double to signed 32 bit integer. -TEST_F(RVCheriot32FInstructionTest, RiscVFCvtWs) { - SetSemanticFunction(&RiscVFCvtWs); - UnaryOpWithFflagsFPTestHelper<int32_t, float>( - "fcvt.w.s", instruction_, {"f", "x"}, 32, - [&](float lhs) -> std::tuple<int32_t, uint32_t> { - if (std::isnan(lhs)) - return std::make_tuple( - 0x7fff'ffff, static_cast<uint32_t>(FPExceptions::kInvalidOp)); - if (std::isinf(lhs)) - return std::make_tuple( - lhs < 0 ? 0x8000'0000 : 0x7fff'ffff, - static_cast<uint32_t>(FPExceptions::kInvalidOp)); - if (abs(lhs) > static_cast<float>(0x7fff'ffff)) - return std::make_tuple( - lhs < 0 ? 0x8000'0000 : 0x7fff'ffff, - static_cast<uint32_t>(FPExceptions::kInvalidOp)); - uint32_t flag = 0; - if (ceil(lhs) != lhs) { - flag |= static_cast<uint32_t>(FPExceptions::kInexact); - lhs = RoundToInteger(lhs); - } - return std::make_tuple(static_cast<int32_t>(lhs), flag); - }); -} - -// Signed 32 bit integer to float. -TEST_F(RVCheriot32FInstructionTest, RiscVFCvtSw) { - SetSemanticFunction(&RiscVFCvtSw); - UnaryOpFPTestHelper<float, int32_t>( - "fcvt.s.w", instruction_, {"x", "f"}, 32, - [](int32_t lhs) -> float { return static_cast<float>(lhs); }); -} - -// Double to unsigned 32 bit integer. -TEST_F(RVCheriot32FInstructionTest, RiscVFCvtWus) { - SetSemanticFunction(&RiscVFCvtWus); - UnaryOpWithFflagsFPTestHelper<uint32_t, float>( - "fcvt.wu.s", instruction_, {"f", "x"}, 32, - [&](float lhs) -> std::tuple<uint32_t, uint32_t> { - if (std::isnan(lhs)) - return std::make_tuple( - 0xffff'ffff, static_cast<uint32_t>(FPExceptions::kInvalidOp)); - if (lhs < 0) { - if ((lhs > -1.0) && (static_cast<int32_t>(lhs) == 0.0)) { - return std::make_tuple( - 0, static_cast<uint32_t>(FPExceptions::kInexact)); - } - return std::make_tuple( - 0, static_cast<uint32_t>(FPExceptions::kInvalidOp)); - } - if (std::isinf(lhs) || lhs > static_cast<float>(0xffff'ffffUL)) { - return std::make_tuple( - 0xffff'ffff, static_cast<uint32_t>(FPExceptions::kInvalidOp)); - } - uint32_t flag = 0; - if (ceil(lhs) != lhs) { - flag |= static_cast<uint32_t>(FPExceptions::kInexact); - lhs = RoundToInteger(lhs); - } - return std::make_tuple(static_cast<uint32_t>(lhs), flag); - }); -} - -// Unsigned 32 bit integer to float. -TEST_F(RVCheriot32FInstructionTest, RiscVFCvtSwu) { - SetSemanticFunction(&RiscVFCvtSwu); - UnaryOpFPTestHelper<float, uint32_t>( - "fcvt.s.w", instruction_, {"x", "f"}, 32, - [](uint32_t lhs) -> float { return static_cast<float>(lhs); }); -} - -// Test sign manipulation instructions. -TEST_F(RVCheriot32FInstructionTest, RiscVFSgnj) { - SetSemanticFunction(&RiscVFSgnj); - BinaryOpFPTestHelper<float, float, float>( - "fsgnj", instruction_, {"f", "f", "f"}, 32, - [](float lhs, float rhs) -> float { return copysign(abs(lhs), rhs); }); -} - -TEST_F(RVCheriot32FInstructionTest, RiscVFSgnjn) { - SetSemanticFunction(&RiscVFSgnjn); - BinaryOpFPTestHelper<float, float, float>( - "fsgnjn", instruction_, {"f", "f", "f"}, 32, - [](float lhs, float rhs) -> float { return copysign(abs(lhs), -rhs); }); -} - -TEST_F(RVCheriot32FInstructionTest, RiscVFSgnjx) { - SetSemanticFunction(&RiscVFSgnjx); - BinaryOpFPTestHelper<float, float, float>( - "fsgnjn", instruction_, {"f", "f", "f"}, 32, - [](float lhs, float rhs) -> float { - auto lhs_u = *reinterpret_cast<uint32_t *>(&lhs); - auto rhs_u = *reinterpret_cast<uint32_t *>(&rhs); - auto res_u = (lhs_u ^ rhs_u) & 0x8000'0000; - auto res = *reinterpret_cast<float *>(&res_u); - return copysign(abs(lhs), res); - }); -} - -// Test compare instructions. -TEST_F(RVCheriot32FInstructionTest, RiscVFCmpeq) { - SetSemanticFunction(&RiscVFCmpeq); - BinaryOpWithFflagsFPTestHelper<uint32_t, float, float>( - "fcmpeq", instruction_, {"f", "f", "x"}, 32, - [](float lhs, float rhs) -> std::tuple<uint32_t, uint32_t> { - uint32_t flag = 0; - if (is_snan(lhs) || is_snan(rhs)) { - flag = static_cast<uint32_t>(FPExceptions::kInvalidOp); - } - return std::make_tuple(lhs == rhs, flag); - }); -} - -TEST_F(RVCheriot32FInstructionTest, RiscVFCmplt) { - SetSemanticFunction(&RiscVFCmplt); - BinaryOpWithFflagsFPTestHelper<uint32_t, float, float>( - "fcmplt", instruction_, {"f", "f", "x"}, 32, - [](float lhs, float rhs) -> std::tuple<uint32_t, uint32_t> { - uint32_t flag = 0; - if (std::isnan(lhs) || std::isnan(rhs)) { - flag = static_cast<uint32_t>(FPExceptions::kInvalidOp); - } - return std::make_tuple(lhs < rhs, flag); - }); -} - -TEST_F(RVCheriot32FInstructionTest, RiscVFCmple) { - SetSemanticFunction(&RiscVFCmple); - BinaryOpWithFflagsFPTestHelper<uint32_t, float, float>( - "fcmple", instruction_, {"f", "f", "x"}, 32, - [](float lhs, float rhs) -> std::tuple<uint32_t, uint32_t> { - uint32_t flag = 0; - if (std::isnan(lhs) || std::isnan(rhs)) { - flag = static_cast<uint32_t>(FPExceptions::kInvalidOp); - } - return std::make_tuple(lhs <= rhs, flag); - }); -} - -// Test class instruction. -TEST_F(RVCheriot32FInstructionTest, RiscVFClass) { - SetSemanticFunction(&RiscVFClass); - UnaryOpFPTestHelper<uint32_t, float>( - "fclass.d", instruction_, {"f", "x"}, 32, [](float lhs) -> uint32_t { - auto fp_class = std::fpclassify(lhs); - switch (fp_class) { - case FP_INFINITE: - return std::signbit(lhs) ? 1 : 1 << 7; - case FP_NAN: { - auto uint_val = - *reinterpret_cast<typename FPTypeInfo<float>::IntType *>(&lhs); - bool quiet_nan = - (uint_val >> (FPTypeInfo<float>::kSigSize - 1)) & 1; - return quiet_nan ? 1 << 9 : 1 << 8; - } - case FP_ZERO: - return std::signbit(lhs) ? 1 << 3 : 1 << 4; - case FP_SUBNORMAL: - return std::signbit(lhs) ? 1 << 2 : 1 << 5; - case FP_NORMAL: - return std::signbit(lhs) ? 1 << 1 : 1 << 6; - } - return 0; - }); -} - -} // namespace