| // 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_m_instructions.h" |
| |
| #include <cstdint> |
| #include <limits> |
| #include <string> |
| #include <string_view> |
| #include <tuple> |
| #include <vector> |
| |
| #include "absl/random/random.h" |
| #include "cheriot/cheriot_register.h" |
| #include "cheriot/cheriot_state.h" |
| #include "googlemock/include/gmock/gmock.h" |
| #include "mpact/sim/generic/immediate_operand.h" |
| #include "mpact/sim/generic/instruction.h" |
| #include "mpact/sim/generic/type_helpers.h" |
| #include "mpact/sim/util/memory/tagged_flat_demand_memory.h" |
| |
| // This file contains tests for individual CHERIoT RiscV32M instructions. |
| |
| namespace { |
| |
| using ::mpact::sim::generic::operator*; // NOLINT: used below (clang error). |
| using ::mpact::sim::cheriot::CheriotRegister; |
| using ::mpact::sim::cheriot::CheriotState; |
| using ::mpact::sim::generic::ImmediateOperand; |
| using ::mpact::sim::generic::Instruction; |
| using ::mpact::sim::util::TaggedFlatDemandMemory; |
| using CH_EC = ::mpact::sim::cheriot::ExceptionCode; |
| using PB = CheriotRegister::PermissionBits; |
| |
| using ::mpact::sim::cheriot::MDiv; |
| using ::mpact::sim::cheriot::MDivu; |
| using ::mpact::sim::cheriot::MMul; |
| using ::mpact::sim::cheriot::MMulh; |
| using ::mpact::sim::cheriot::MMulhsu; |
| using ::mpact::sim::cheriot::MMulhu; |
| using ::mpact::sim::cheriot::MRem; |
| using ::mpact::sim::cheriot::MRemu; |
| |
| constexpr char kC1[] = "c1"; |
| constexpr char kC2[] = "c2"; |
| constexpr char kC3[] = "c3"; |
| |
| constexpr int kNumTests = 100; |
| |
| constexpr uint32_t kInstAddress = 0x2468; |
| |
| // The test fixture allocates a machine state object and an instruction object. |
| // It also contains convenience methods for interacting with the instruction |
| // object in a more short hand form. |
| class RVCheriotMInstructionTest : public testing::Test { |
| public: |
| RVCheriotMInstructionTest() { |
| mem_ = new TaggedFlatDemandMemory(8); |
| state_ = new CheriotState("test", mem_, nullptr); |
| instruction_ = new Instruction(kInstAddress, state_); |
| instruction_->set_size(4); |
| creg_1_ = state_->GetRegister<CheriotRegister>(kC1).first; |
| creg_2_ = state_->GetRegister<CheriotRegister>(kC2).first; |
| creg_3_ = state_->GetRegister<CheriotRegister>(kC3).first; |
| } |
| |
| ~RVCheriotMInstructionTest() override { |
| delete mem_; |
| delete state_; |
| delete instruction_; |
| } |
| |
| // Appends the source and destination operands for the register names |
| // given in the two vectors. |
| void AppendRegisterOperands(Instruction *inst, |
| const std::vector<std::string> &sources, |
| const std::vector<std::string> &destinations) { |
| for (auto ®_name : sources) { |
| auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first; |
| inst->AppendSource(reg->CreateSourceOperand()); |
| } |
| for (auto ®_name : destinations) { |
| auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first; |
| inst->AppendDestination(reg->CreateDestinationOperand(0)); |
| } |
| } |
| |
| void AppendRegisterOperands(const std::vector<std::string> &sources, |
| const std::vector<std::string> &destinations) { |
| AppendRegisterOperands(instruction_, sources, destinations); |
| } |
| |
| // Appends immediate source operands with the given values. |
| template <typename T> |
| void AppendImmediateOperands(const std::vector<T> &values) { |
| for (auto value : values) { |
| auto *src = new ImmediateOperand<T>(value); |
| instruction_->AppendSource(src); |
| } |
| } |
| |
| // Takes a vector of tuples of register names and values. Fetches each |
| // named register and sets it to the corresponding value. |
| template <typename T> |
| void SetRegisterValues(const std::vector<std::tuple<std::string, T>> values) { |
| for (auto &[reg_name, value] : values) { |
| auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first; |
| reg->set_address(value); |
| } |
| } |
| |
| // Initializes the semantic function of the instruction object. |
| void SetSemanticFunction(Instruction::SemanticFunction fcn) { |
| instruction_->set_semantic_function(fcn); |
| } |
| |
| // Returns the value of the named register. |
| template <typename T> |
| T GetRegisterValue(std::string_view reg_name) { |
| auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first; |
| return reg->address(); |
| } |
| |
| TaggedFlatDemandMemory *mem_; |
| CheriotState *state_; |
| Instruction *instruction_; |
| CheriotRegister *creg_1_; |
| CheriotRegister *creg_2_; |
| CheriotRegister *creg_3_; |
| absl::BitGen bitgen_; |
| }; |
| |
| TEST_F(RVCheriotMInstructionTest, MMul) { |
| instruction_->set_semantic_function(&MMul); |
| AppendRegisterOperands({kC1, kC2}, {kC3}); |
| for (int i = 0; i < kNumTests; i++) { |
| int32_t a = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<int32_t>::min(), |
| std::numeric_limits<int32_t>::max()); |
| int32_t b = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<int32_t>::min(), |
| std::numeric_limits<int32_t>::max()); |
| SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}}); |
| creg_3_->ResetMemoryRoot(); |
| instruction_->Execute(nullptr); |
| int32_t result = |
| static_cast<int32_t>(static_cast<int64_t>(a) * static_cast<int64_t>(b)); |
| EXPECT_EQ(creg_3_->address(), result); |
| EXPECT_FALSE(creg_3_->tag()); |
| EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->permissions(), 0); |
| EXPECT_EQ(creg_3_->object_type(), 0); |
| } |
| } |
| |
| TEST_F(RVCheriotMInstructionTest, MMulh) { |
| instruction_->set_semantic_function(&MMulh); |
| AppendRegisterOperands({kC1, kC2}, {kC3}); |
| for (int i = 0; i < kNumTests; i++) { |
| int32_t a = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<int32_t>::min(), |
| std::numeric_limits<int32_t>::max()); |
| int32_t b = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<int32_t>::min(), |
| std::numeric_limits<int32_t>::max()); |
| SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}}); |
| creg_3_->ResetMemoryRoot(); |
| instruction_->Execute(nullptr); |
| int32_t result = static_cast<int32_t>( |
| (static_cast<int64_t>(a) * static_cast<int64_t>(b)) >> 32); |
| EXPECT_EQ(creg_3_->address(), result); |
| EXPECT_FALSE(creg_3_->tag()); |
| EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->permissions(), 0); |
| EXPECT_EQ(creg_3_->object_type(), 0); |
| } |
| } |
| |
| TEST_F(RVCheriotMInstructionTest, MMulhu) { |
| instruction_->set_semantic_function(&MMulhu); |
| AppendRegisterOperands({kC1, kC2}, {kC3}); |
| for (int i = 0; i < kNumTests; i++) { |
| uint32_t a = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<uint32_t>::min(), |
| std::numeric_limits<uint32_t>::max()); |
| uint32_t b = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<uint32_t>::min(), |
| std::numeric_limits<uint32_t>::max()); |
| SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}}); |
| creg_3_->ResetMemoryRoot(); |
| instruction_->Execute(nullptr); |
| uint32_t result = static_cast<uint32_t>( |
| (static_cast<uint64_t>(a) * static_cast<uint64_t>(b)) >> 32); |
| EXPECT_EQ(creg_3_->address(), result); |
| EXPECT_FALSE(creg_3_->tag()); |
| EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->permissions(), 0); |
| EXPECT_EQ(creg_3_->object_type(), 0); |
| } |
| } |
| |
| TEST_F(RVCheriotMInstructionTest, MMulhsu) { |
| instruction_->set_semantic_function(&MMulhsu); |
| AppendRegisterOperands({kC1, kC2}, {kC3}); |
| for (int i = 0; i < kNumTests; i++) { |
| int32_t a = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<int32_t>::min(), |
| std::numeric_limits<int32_t>::max()); |
| uint32_t b = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<uint32_t>::min(), |
| std::numeric_limits<uint32_t>::max()); |
| SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}}); |
| creg_3_->ResetMemoryRoot(); |
| instruction_->Execute(nullptr); |
| int32_t result = static_cast<int32_t>( |
| (static_cast<int64_t>(a) * static_cast<uint64_t>(b)) >> 32); |
| EXPECT_EQ(creg_3_->address(), result); |
| EXPECT_FALSE(creg_3_->tag()); |
| EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->permissions(), 0); |
| EXPECT_EQ(creg_3_->object_type(), 0); |
| } |
| } |
| |
| TEST_F(RVCheriotMInstructionTest, MDiv) { |
| instruction_->set_semantic_function(&MDiv); |
| AppendRegisterOperands({kC1, kC2}, {kC3}); |
| for (int i = 0; i < kNumTests; i++) { |
| int32_t a = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<int32_t>::min(), |
| std::numeric_limits<int32_t>::max()); |
| int32_t b = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<int32_t>::min(), |
| std::numeric_limits<int32_t>::max()); |
| SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}}); |
| creg_3_->ResetMemoryRoot(); |
| instruction_->Execute(nullptr); |
| |
| int32_t result; |
| if (b == 0) { |
| result = -1; |
| } else if ((b == -1) && (a == std::numeric_limits<int32_t>::min())) { |
| result = std::numeric_limits<int32_t>::min(); |
| } else { |
| result = a / b; |
| } |
| EXPECT_EQ(creg_3_->address(), result); |
| EXPECT_FALSE(creg_3_->tag()); |
| EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->permissions(), 0); |
| EXPECT_EQ(creg_3_->object_type(), 0); |
| } |
| } |
| |
| TEST_F(RVCheriotMInstructionTest, MDivu) { |
| instruction_->set_semantic_function(&MDivu); |
| AppendRegisterOperands({kC1, kC2}, {kC3}); |
| for (int i = 0; i < kNumTests; i++) { |
| uint32_t a = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<uint32_t>::min(), |
| std::numeric_limits<uint32_t>::max()); |
| uint32_t b = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<uint32_t>::min(), |
| std::numeric_limits<uint32_t>::max()); |
| SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}}); |
| creg_3_->ResetMemoryRoot(); |
| instruction_->Execute(nullptr); |
| |
| uint32_t result; |
| if (b == 0) { |
| result = std::numeric_limits<uint32_t>::max(); |
| } else { |
| result = a / b; |
| } |
| EXPECT_EQ(creg_3_->address(), result); |
| EXPECT_FALSE(creg_3_->tag()); |
| EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->permissions(), 0); |
| EXPECT_EQ(creg_3_->object_type(), 0); |
| } |
| } |
| |
| TEST_F(RVCheriotMInstructionTest, MRem) { |
| instruction_->set_semantic_function(&MRem); |
| AppendRegisterOperands({kC1, kC2}, {kC3}); |
| for (int i = 0; i < kNumTests; i++) { |
| int32_t a = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<int32_t>::min(), |
| std::numeric_limits<int32_t>::max()); |
| int32_t b = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<int32_t>::min(), |
| std::numeric_limits<int32_t>::max()); |
| SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}}); |
| creg_3_->ResetMemoryRoot(); |
| instruction_->Execute(nullptr); |
| |
| int32_t result; |
| if (b == 0) { |
| result = a; |
| } else if ((b == -1) && (a == std::numeric_limits<int32_t>::min())) { |
| result = 0; |
| } else { |
| result = a % b; |
| } |
| EXPECT_EQ(creg_3_->address(), result); |
| EXPECT_FALSE(creg_3_->tag()); |
| EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->permissions(), 0); |
| EXPECT_EQ(creg_3_->object_type(), 0); |
| } |
| } |
| |
| TEST_F(RVCheriotMInstructionTest, MRemu) { |
| instruction_->set_semantic_function(&MRemu); |
| AppendRegisterOperands({kC1, kC2}, {kC3}); |
| for (int i = 0; i < kNumTests; i++) { |
| uint32_t a = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<uint32_t>::min(), |
| std::numeric_limits<uint32_t>::max()); |
| uint32_t b = absl::Uniform(absl::IntervalClosed, bitgen_, |
| std::numeric_limits<uint32_t>::min(), |
| std::numeric_limits<uint32_t>::max()); |
| SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}}); |
| creg_3_->ResetMemoryRoot(); |
| instruction_->Execute(nullptr); |
| |
| uint32_t result; |
| if (b == 0) { |
| result = a; |
| } else { |
| result = a % b; |
| } |
| EXPECT_EQ(creg_3_->address(), result); |
| EXPECT_FALSE(creg_3_->tag()); |
| EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff); |
| EXPECT_EQ(creg_3_->permissions(), 0); |
| EXPECT_EQ(creg_3_->object_type(), 0); |
| } |
| } |
| |
| } // namespace |