| // 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_ |