blob: 410cd5e45598ed22f0e148316c21b59e777dddce [file]
// 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
//
// 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_zc_instructions.h"
#include <cstdint>
#include <type_traits>
#include "mpact/sim/generic/instruction.h"
#include "riscv/riscv_instruction_helpers.h"
#include "riscv/riscv_register.h"
#include "riscv/riscv_state.h"
// This file implements the semantic functions for the Zcm* extensions.
namespace mpact::sim::riscv {
using ::mpact::sim::generic::Instruction;
namespace RV32 {
namespace {
constexpr int kStackAdjBase[] = {
0, 0, 0, 0, 16, 16, 16, 16, 32, 32, 32, 32, 48, 48, 48, 64,
};
} // namespace
using RegType = ::mpact::sim::riscv::RV32Register;
using UIntReg = typename std::make_unsigned<typename RegType::ValueType>::type;
using IntReg = typename std::make_signed<UIntReg>::type;
// Zcmp instructions.
void RiscVZCmpPush(const Instruction* inst) {
RiscVState* state = static_cast<RiscVState*>(inst->state());
int num_regs = inst->SourcesSize() - 3;
auto* db = state->db_factory()->Allocate<UIntReg>(num_regs);
auto db_span = db->Get<UIntReg>();
// Get the register values and put them in the data buffer.
for (int i = 0; i < num_regs; ++i) {
auto value = generic::GetInstructionSource<UIntReg>(inst, i + 3);
db_span[i] = value;
}
// Store the data buffer to memory.
auto sp = generic::GetInstructionSource<UIntReg>(inst, 0);
state->StoreMemory(inst, sp - sizeof(UIntReg) * num_regs, db);
db->DecRef();
// Compute the stack adjustment.
auto spimm6 = generic::GetInstructionSource<UIntReg>(inst, 1);
auto rlist = generic::GetInstructionSource<UIntReg>(inst, 2);
auto sp_adjustment = spimm6 + kStackAdjBase[rlist];
// Compute the new stack pointer.
sp -= sp_adjustment;
RiscVWriteReg<RegType, UIntReg>(inst, 0, sp);
}
namespace {
// This helper pops the number of registers specified into the appropriate
// destination operands, and then adjusts the stack pointer.
void RiscVZCmpPopHelper(const Instruction* inst, int size) {
RiscVState* state = static_cast<RiscVState*>(inst->state());
// Compute the stack adjustment.
auto spimm6 = generic::GetInstructionSource<UIntReg>(inst, 1);
auto rlist = generic::GetInstructionSource<UIntReg>(inst, 2);
auto sp_adjustment = spimm6 + kStackAdjBase[rlist];
auto* db = state->db_factory()->Allocate<UIntReg>(size);
// Load registers from the stack.
auto sp = generic::GetInstructionSource<UIntReg>(inst, 0);
// Start address = sp + sp_adjustment - sizeof(UIntReg) * size;
uint64_t start_address = sp + sp_adjustment - sizeof(UIntReg) * size;
state->LoadMemory(inst, start_address, db, nullptr, nullptr);
auto db_span = db->Get<UIntReg>();
for (int i = 0; i < size; ++i) {
RiscVWriteReg<RegType, UIntReg>(inst, i, db_span[i]);
}
// Write to the stack pointer register.
db->DecRef();
RiscVWriteReg<RegType, UIntReg>(inst, size, sp + sp_adjustment);
}
} // namespace
void RiscVZCmpPop(const Instruction* inst) {
// Size is the number of registers to pop.
int size = inst->DestinationsSize() - 1;
RiscVZCmpPopHelper(inst, size);
}
void RiscVZCmpPopRet(const Instruction* inst) {
// Size is the number of registers to pop.
int size = inst->DestinationsSize() - 2; // x2 and next_pc.
RiscVZCmpPopHelper(inst, size);
// Now perform the return.
UIntReg target = generic::GetInstructionSource<UIntReg>(inst, 3);
auto* db = inst->Destination(size + 1)->AllocateDataBuffer();
db->SetSubmit<UIntReg>(0, target);
auto* state = static_cast<RiscVState*>(inst->state());
state->set_branch(true);
}
void RiscVZCmpPopRetz(const Instruction* inst) {
// Size is the number of registers to pop.
int size = inst->DestinationsSize() - 3; // x2, x10, and next_pc.
RiscVZCmpPopHelper(inst, size);
// Now clear a0.
RiscVWriteReg<RegType, UIntReg>(inst, size + 1, 0);
// Now perform the return.
UIntReg target = generic::GetInstructionSource<UIntReg>(inst, 3);
auto* db = inst->Destination(size + 2)->AllocateDataBuffer();
db->SetSubmit<UIntReg>(0, target);
auto* state = static_cast<RiscVState*>(inst->state());
state->set_branch(true);
}
void RiscVZCmpMvTwoRegs(const Instruction* inst) {
RiscVWriteReg<RegType, UIntReg>(
inst, 0, generic::GetInstructionSource<UIntReg>(inst, 0));
RiscVWriteReg<RegType, UIntReg>(
inst, 1, generic::GetInstructionSource<UIntReg>(inst, 1));
}
// Zcmt instructions.
namespace {
void RiscVZCmtJtHelper(const Instruction* inst, int dest_index) {
int index = generic::GetInstructionSource<UIntReg>(inst, 0);
auto* state = static_cast<RiscVState*>(inst->state());
auto jvt_value = state->jvt()->AsUint64();
auto mode = jvt_value & 0x3f;
if (mode != 0) {
state->Trap(/*is_interrupt=*/false, 0, *ExceptionCode::kIllegalInstruction,
inst->address(), inst);
return;
}
// Load target address from the jump table.
UIntReg entry_address = (jvt_value & ~0x3f) + (index * sizeof(UIntReg));
auto* db = state->db_factory()->Allocate<UIntReg>(1);
state->LoadMemory(inst, entry_address, db, nullptr, nullptr);
UIntReg target_address = db->Get<UIntReg>(0);
db->DecRef();
// Write the target address to the next pc operand.
auto* target_db = inst->Destination(0)->AllocateDataBuffer();
target_db->SetSubmit<UIntReg>(0, target_address);
state->set_branch(true);
}
} // namespace
void RiscVZCmtJt(const Instruction* inst) { RiscVZCmtJtHelper(inst, 0); }
void RiscVZCmtJalt(const Instruction* inst) {
RiscVZCmtJtHelper(inst, 1);
// Write the return address to the x1 (ra) operand.
RiscVWriteReg<RegType, UIntReg>(inst, 1, inst->address() + inst->size());
}
} // namespace RV32
namespace RV64 {
namespace {
constexpr int kStackAdjBase[] = {
0, 0, 0, 0, 16, 16, 32, 32, 48, 48, 64, 64, 80, 80, 96, 112,
};
} // namespace
using RegType = ::mpact::sim::riscv::RV64Register;
using UIntReg = typename std::make_unsigned<typename RegType::ValueType>::type;
using IntReg = typename std::make_signed<UIntReg>::type;
// Zcmp instructions.
void RiscVZCmpPush(const Instruction* inst) {
RiscVState* state = static_cast<RiscVState*>(inst->state());
int num_regs = inst->SourcesSize() - 3;
auto* db = state->db_factory()->Allocate<UIntReg>(num_regs);
auto db_span = db->Get<UIntReg>();
// Get the register values and put them in the data buffer.
for (int i = 0; i < num_regs; ++i) {
auto value = generic::GetInstructionSource<UIntReg>(inst, i + 3);
db_span[i] = value;
}
// Store the data buffer to memory.
auto sp = generic::GetInstructionSource<UIntReg>(inst, 0);
state->StoreMemory(inst, sp - sizeof(UIntReg) * num_regs, db);
db->DecRef();
// Compute the stack adjustment.
auto spimm6 = generic::GetInstructionSource<UIntReg>(inst, 1);
auto rlist = generic::GetInstructionSource<UIntReg>(inst, 2);
auto sp_adjustment = spimm6 + kStackAdjBase[rlist];
// Compute the new stack pointer.
sp -= sp_adjustment;
RiscVWriteReg<RegType, UIntReg>(inst, 0, sp);
}
namespace {
// This helper pops the number of registers specified into the appropriate
// destination operands, and then adjusts the stack pointer.
void RiscVZCmpPopHelper(const Instruction* inst, int size) {
RiscVState* state = static_cast<RiscVState*>(inst->state());
// Compute the stack adjustment.
auto spimm6 = generic::GetInstructionSource<UIntReg>(inst, 1);
auto rlist = generic::GetInstructionSource<UIntReg>(inst, 2);
auto sp_adjustment = spimm6 + kStackAdjBase[rlist];
auto* db = state->db_factory()->Allocate<UIntReg>(size);
// Load registers from the stack.
auto sp = generic::GetInstructionSource<UIntReg>(inst, 0);
// Start address = sp + sp_adjustment - sizeof(UIntReg) * size;
uint64_t start_address = sp + sp_adjustment - sizeof(UIntReg) * size;
state->LoadMemory(inst, start_address, db, nullptr, nullptr);
auto db_span = db->Get<UIntReg>();
for (int i = 0; i < size; ++i) {
RiscVWriteReg<RegType, UIntReg>(inst, i, db_span[i]);
}
// Write to the stack pointer register.
db->DecRef();
RiscVWriteReg<RegType, UIntReg>(inst, size, sp + sp_adjustment);
}
} // namespace
void RiscVZCmpPop(const Instruction* inst) {
// Size is the number of registers to pop.
int size = inst->DestinationsSize() - 1;
RiscVZCmpPopHelper(inst, size);
}
void RiscVZCmpPopRet(const Instruction* inst) {
// Size is the number of registers to pop.
int size = inst->DestinationsSize() - 2; // x2 and next_pc.
RiscVZCmpPopHelper(inst, size);
// Now perform the return.
UIntReg target = generic::GetInstructionSource<UIntReg>(inst, 3);
auto* db = inst->Destination(size + 1)->AllocateDataBuffer();
db->SetSubmit<UIntReg>(0, target);
auto* state = static_cast<RiscVState*>(inst->state());
state->set_branch(true);
}
void RiscVZCmpPopRetz(const Instruction* inst) {
// Size is the number of registers to pop.
int size = inst->DestinationsSize() - 3; // x2, x10, and next_pc.
RiscVZCmpPopHelper(inst, size);
// Now clear a0.
RiscVWriteReg<RegType, UIntReg>(inst, size + 1, 0);
// Now perform the return.
UIntReg target = generic::GetInstructionSource<UIntReg>(inst, 3);
auto* db = inst->Destination(size + 2)->AllocateDataBuffer();
db->SetSubmit<UIntReg>(0, target);
auto* state = static_cast<RiscVState*>(inst->state());
state->set_branch(true);
}
void RiscVZCmpMvTwoRegs(const Instruction* inst) {
RiscVWriteReg<RegType, UIntReg>(
inst, 0, generic::GetInstructionSource<UIntReg>(inst, 0));
RiscVWriteReg<RegType, UIntReg>(
inst, 1, generic::GetInstructionSource<UIntReg>(inst, 1));
}
// Zcmt instructions.
namespace {
void RiscVZCmtJtHelper(const Instruction* inst, int dest_index) {
int index = generic::GetInstructionSource<UIntReg>(inst, 0);
auto* state = static_cast<RiscVState*>(inst->state());
auto jvt_value = state->jvt()->AsUint64();
auto mode = jvt_value & 0x3f;
if (mode != 0) {
state->Trap(/*is_interrupt=*/false, 0, *ExceptionCode::kIllegalInstruction,
inst->address(), inst);
return;
}
// Load target address from the jump table.
UIntReg entry_address = (jvt_value & ~0x3f) + (index * sizeof(UIntReg));
auto* db = state->db_factory()->Allocate<UIntReg>(1);
state->LoadMemory(inst, entry_address, db, nullptr, nullptr);
UIntReg target_address = db->Get<UIntReg>(0);
db->DecRef();
// Write the target address to the next pc operand.
auto* target_db = inst->Destination(0)->AllocateDataBuffer();
target_db->SetSubmit<UIntReg>(0, target_address);
state->set_branch(true);
}
} // namespace
void RiscVZCmtJt(const Instruction* inst) { RiscVZCmtJtHelper(inst, 0); }
void RiscVZCmtJalt(const Instruction* inst) {
RiscVZCmtJtHelper(inst, 1);
// Write the return address to the x1 (ra) operand.
RiscVWriteReg<RegType, UIntReg>(inst, 1, inst->address() + inst->size());
}
} // namespace RV64
} // namespace mpact::sim::riscv