blob: 60ab5d8e5a52068d977a2b13f6fed11f2e229edb [file]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MPACT_SIM_GENERIC_INSTRUCTION_H_
#define MPACT_SIM_GENERIC_INSTRUCTION_H_
#include <cstdint>
#include <functional>
#include <string>
#include <utility>
#include <vector>
#include "absl/numeric/int128.h"
#include "absl/types/span.h"
#include "mpact/sim/generic/arch_state.h"
#include "mpact/sim/generic/operand_interface.h"
#include "mpact/sim/generic/ref_count.h"
namespace mpact {
namespace sim {
namespace generic {
class ResourceOperandInterface;
// This is the class used to as the internal simulator representation of a
// target architecture instruction or a component operation of such an
// instruction. An example would be the individual operations of a VLIW
// instruction, or those instructions with semantics that may need to be modeled
// over multiple distinct architectural cycles (some memory loads for instance,
// where the returned data has to be transformed before being written back to
// the register). It is also used to represent the function responsible for
// issuing instructions, manages updates of simulated state due to instruction
// side-effects, and advances the program counter. Modeling the instruction
// issue as an instruction (with the instruction issue performed as its semantic
// function) makes it easier for the simulator core to remain
// architecture-agnostic. In this case, the semantic function responsible for
// instruction issue would call Execute on the child instruction, allocating and
// passing in any context structure as may be necessary for that architecture.
// Since the context may contain values that need to be accessed through a
// handle to the Instruction instance during the execution of the semantic
// function, it is copied to the instruction instance before the semantic
// function is called.
//
// The Instruction instance has pointers to Next, Child and Parent Instruction
// instances to manage the decomposition of complex instruction and instruction
// issue relationships. This is used to enable modeling of instruction
// hierarchies such as instructions in a VLIW ISA. In this case, a top level
// instruction (or instruction bundle) can use the Child pointer to point to the
// list of the individual instructions (or operations) that are to be issued as
// one "instruction bundle". The semantic function of the top level instruction
// is then responsible for "issuing" (call Execute()) for each of those
// instructions and handle any state updates/interactions required.
//
// The following diagram illustrates a possible VLIW instruction structure.
// Down arrows are the "next" pointers, whereas the right arrows are the Child
// pointers. The master_instruction implements instruction issue and
// architectural state maintenance. There is one master_instruction instance
// allocated for each PC that is decoded/executed.
//
// master_instruction ---> vliw_bundle 0 ---> inst 0
// |
// V
// inst 1
// |
// V
// inst 2
//
//
// master_instruction ---> vliw_bundle 1 ---> inst 0
// |
// V
// inst 1
// ...
// inst N
class Instruction : public ReferenceCount {
public:
// Type Alias for the semantic function.
using SemanticFunction = std::function<void(Instruction *)>;
// Constructors and Destructors.
explicit Instruction(ArchState *state);
Instruction(uint64_t address, ArchState *state);
~Instruction() override;
// Appends the instruction to the "next" list of instructions.
void Append(Instruction *inst);
// Appends the instruction the "child" list of instructions.
void AppendChild(Instruction *inst);
// Methods used for navigating instruction hierarchy.
Instruction *child() const { return child_; }
Instruction *parent() const { return parent_; }
Instruction *next() const { return next_; }
// Execute the instruction with the given context
// Note: The context is stored into the instruction instance instead of
// being passed as a parameter to the semantic function. This is intentional
// to facilitate accessing the data in the context from the Instruction
// instance itself. For instance, some values used as source operands for
// an instruction may be stored in the context. The Operand instance has
// only a handle to the Instruction instance as that is available during
// instruction decode, and accessing the context otherwise would require
// modifying the interface for all operands.
void Execute(ReferenceCount *context) {
context_ = context;
semantic_fcn_(this);
context_ = nullptr;
}
// Execute the instruction without context (context_ remains nullptr).
void Execute() { semantic_fcn_(this); }
// Accessors (getters/setters).
ReferenceCount *context() const { return context_; }
ArchState *state() const { return state_; }
// Returns the pc value for the instruction.
uint64_t address() const { return address_; }
// The address should seldom be set outside the constructor.
void set_address(uint64_t address) { address_ = address; }
// The opcode as set by the decoder.
int opcode() const { return opcode_; }
void set_opcode(int opcode) { opcode_ = opcode; }
// Returns the size in terms of pc increment value.
int size() const { return size_; }
// Sets the instruction size (pc increment).
void set_size(int sz) { size_ = sz; }
// Sets the semantic function callable - typically only used by the decoder.
// The callable must be convertible to std::function<void(Instruction *)>.
template <typename F>
void set_semantic_function(F callable) {
semantic_fcn_ = SemanticFunction(callable);
}
// PredicateOperand interface used for those ISAs that implement
// instruction predicates.
PredicateOperandInterface *Predicate() const { return predicate_; }
void SetPredicate(PredicateOperandInterface *predicate);
// SourceOperand interfaces for the instruction.
SourceOperandInterface *Source(int i) const { return sources_[i]; }
void AppendSource(SourceOperandInterface *op);
int SourcesSize() const;
// DestinationOperand interfaces for the instruction.
DestinationOperandInterface *Destination(int i) const { return dests_[i]; }
void AppendDestination(DestinationOperandInterface *op);
int DestinationsSize() const;
// Hold ResourceOperand interfaces for the instruction.
inline std::vector<ResourceOperandInterface *> &ResourceHold() {
return resource_hold_;
}
inline void AppendResourceHold(ResourceOperandInterface *op) {
resource_hold_.push_back(op);
}
// Acquire ResourceOperand interfaces for the instruction.
inline std::vector<ResourceOperandInterface *> &ResourceAcquire() {
return resource_acquire_;
}
inline void AppendResourceAcquire(ResourceOperandInterface *op) {
resource_acquire_.push_back(op);
}
void SetDisassemblyString(std::string disasm) {
disasm_string_ = std::move(disasm);
}
std::string AsString() const;
// Setter and getter for the integer attributes.
absl::Span<const int> Attributes() const { return attributes_; }
void SetAttributes(absl::Span<const int> attributes);
private:
// Instruction operands.
PredicateOperandInterface *predicate_ = nullptr;
std::vector<SourceOperandInterface *> sources_;
std::vector<DestinationOperandInterface *> dests_;
// The resources that must be available in order to issue the instruction.
// This includes any registers that are read.
std::vector<ResourceOperandInterface *> resource_hold_;
// The resources that must be reserved/acquired by the instruction. Each
// vector element is a set of resources that are acquired when the instruction
// issues. The method Acquire() should be called on each element of the
// vector. The operands should contain all registers and other resources that
// that need to be reserved for writing.
std::vector<ResourceOperandInterface *> resource_acquire_;
// Simulated instruction size.
int size_;
// Simulated instruction address.
uint64_t address_;
// Integer value of the opcode enum.
int opcode_;
// Text string of disassembly of the instruction.
std::string disasm_string_;
// Optional integer attribute array. This allows the decoder to create and
// store a set of different attributes in the instruction. This is implemented
// as absl::Span<int> so it can be used as an array of integer attributes
// with length determined by the decoder. An example attribute is a
// privilege level, or whether the instruction is a branch or not.
// The attribute array is owned by the Instruction object. The attributes are
// accessed as a const span, so the attributes are read only.
int *attribute_array_ = nullptr;
absl::Span<const int> attributes_ =
absl::MakeConstSpan(static_cast<int *>(nullptr), 0);
// Architecture state object.
ArchState *state_;
// Instruction execution context (this is usuall nullptr).
ReferenceCount *context_;
// Semantic function that implements the instruction semantics.
SemanticFunction semantic_fcn_;
// Pointer to the child (or sub) instruction. Used to break an instruction
// up into multiple semantic actions, such as a VLIW instruction.
Instruction *child_;
// Parent instruction pointer from child instruction.
Instruction *parent_;
// Pointer to the "next" instruction (instructions can be linked into a list
// of instructions), such as those instances that make up the instructions in
// a VLIW instruction word.
Instruction *next_;
};
// Templated inline helper functions for operand access. These are intended to
// provide access to instruction operands from instruction semantic functions
// that are themselves templated on the operand types.
// The base case shouldn't be matched. No return statement is provided.
template <typename T>
inline T GetInstructionSource(const Instruction *inst, int index) { /*empty */ }
// The following provide specializations for each of the integral types of
// operand values, both signed and unsigned.
template <>
inline bool GetInstructionSource<bool>(const Instruction *inst, int index) {
return inst->Source(index)->AsBool(0);
}
template <>
inline uint8_t GetInstructionSource<uint8_t>(const Instruction *inst,
int index) {
return inst->Source(index)->AsUint8(0);
}
template <>
inline int8_t GetInstructionSource<int8_t>(const Instruction *inst, int index) {
return inst->Source(index)->AsInt8(0);
}
template <>
inline uint16_t GetInstructionSource<uint16_t>(const Instruction *inst,
int index) {
return inst->Source(index)->AsUint16(0);
}
template <>
inline int16_t GetInstructionSource<int16_t>(const Instruction *inst,
int index) {
return inst->Source(index)->AsInt16(0);
}
template <>
inline uint32_t GetInstructionSource<uint32_t>(const Instruction *inst,
int index) {
return inst->Source(index)->AsUint32(0);
}
template <>
inline int32_t GetInstructionSource<int32_t>(const Instruction *inst,
int index) {
return inst->Source(index)->AsInt32(0);
}
template <>
inline float GetInstructionSource<float>(const Instruction *inst, int index) {
auto value = inst->Source(index)->AsUint32(0);
return *reinterpret_cast<float *>(&value);
}
template <>
inline uint64_t GetInstructionSource<uint64_t>(const Instruction *inst,
int index) {
return inst->Source(index)->AsUint64(0);
}
template <>
inline int64_t GetInstructionSource<int64_t>(const Instruction *inst,
int index) {
return inst->Source(index)->AsInt64(0);
}
template <>
inline double GetInstructionSource<double>(const Instruction *inst, int index) {
auto value = inst->Source(index)->AsUint64(0);
return *reinterpret_cast<double *>(&value);
}
template <>
inline absl::uint128 GetInstructionSource<absl::uint128>(
const Instruction *inst, int index) {
return static_cast<absl::uint128>(inst->Source(index)->AsUint64(0));
}
template <>
inline absl::int128 GetInstructionSource<absl::int128>(const Instruction *inst,
int index) {
return static_cast<absl::int128>(inst->Source(index)->AsInt64(0));
}
// The base case shouldn't be matched. No return statement is provided, so it
// will generate a compile time error if no other case is matched.
template <typename T>
inline T GetInstructionSource(const Instruction *inst, int index,
int element) { /*empty */ }
// The following provide specializations for each of the integral types of
// operand values, both signed and unsigned.
template <>
inline bool GetInstructionSource<bool>(const Instruction *inst, int index,
int element) {
return inst->Source(index)->AsBool(element);
}
template <>
inline uint8_t GetInstructionSource<uint8_t>(const Instruction *inst, int index,
int element) {
return inst->Source(index)->AsUint8(element);
}
template <>
inline int8_t GetInstructionSource<int8_t>(const Instruction *inst, int index,
int element) {
return inst->Source(index)->AsInt8(element);
}
template <>
inline uint16_t GetInstructionSource<uint16_t>(const Instruction *inst,
int index, int element) {
return inst->Source(index)->AsUint16(element);
}
template <>
inline int16_t GetInstructionSource<int16_t>(const Instruction *inst, int index,
int element) {
return inst->Source(index)->AsInt16(element);
}
template <>
inline uint32_t GetInstructionSource<uint32_t>(const Instruction *inst,
int index, int element) {
return inst->Source(index)->AsUint32(element);
}
template <>
inline int32_t GetInstructionSource<int32_t>(const Instruction *inst, int index,
int element) {
return inst->Source(index)->AsInt32(element);
}
template <>
inline float GetInstructionSource<float>(const Instruction *inst, int index,
int element) {
auto value = inst->Source(index)->AsUint32(element);
return *reinterpret_cast<float *>(&value);
}
template <>
inline uint64_t GetInstructionSource<uint64_t>(const Instruction *inst,
int index, int element) {
return inst->Source(index)->AsUint64(element);
}
template <>
inline int64_t GetInstructionSource<int64_t>(const Instruction *inst, int index,
int element) {
return inst->Source(index)->AsInt64(element);
}
template <>
inline double GetInstructionSource<double>(const Instruction *inst, int index,
int element) {
auto value = inst->Source(index)->AsUint64(element);
return *reinterpret_cast<double *>(&value);
}
} // namespace generic
} // namespace sim
} // namespace mpact
#endif // MPACT_SIM_GENERIC_INSTRUCTION_H_