Skip to content

Commit

Permalink
dual: arm: Add DynarecCPU early IR framework
Browse files Browse the repository at this point in the history
  • Loading branch information
fleroviux committed Jan 20, 2025
1 parent 408ef29 commit b106ac6
Show file tree
Hide file tree
Showing 8 changed files with 360 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/dual/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ set(SOURCES
)

set(HEADERS
src/arm/dynarec/ir/emitter.hpp
src/arm/dynarec/ir/input.hpp
src/arm/dynarec/ir/instruction.hpp
src/arm/dynarec/ir/value.hpp
src/arm/dynarec/dynarec_cpu.hpp
src/arm/dynarec/state.hpp
src/arm/interpreter/handlers/arithmetic.inl
Expand Down
7 changes: 7 additions & 0 deletions src/dual/src/arm/dynarec/docs/ir/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

# Design Goals

- Easy to query if an instruction reads or creates a value
- Easy to update all uses of a value to another value (or constant)
- Easy to remove, add or reorder instructions
- Easy to query instructions that create and read a value
1 change: 1 addition & 0 deletions src/dual/src/arm/dynarec/dynarec_cpu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <span>

#include "arm/interpreter/interpreter_cpu.hpp"
#include "ir/emitter.hpp"
#include "state.hpp"

namespace dual::arm {
Expand Down
17 changes: 17 additions & 0 deletions src/dual/src/arm/dynarec/ir/basic_block.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

#pragma once

#include <vector>

#include "instruction.hpp"
#include "value.hpp"

namespace dual::arm::jit::ir {

struct BasicBlock {
Instruction* head{};
Instruction* tail{};
std::vector<Value*> values{};
};

} // namespace dual::arm::jit::ir
168 changes: 168 additions & 0 deletions src/dual/src/arm/dynarec/ir/emitter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@

#pragma once

#include <atom/arena.hpp>
#include <atom/integer.hpp>
#include <atom/panic.hpp>
#include <tuple>
#include <type_traits>
#include <utility>

#include "basic_block.hpp"
#include "value.hpp"
#include "input.hpp"
#include "instruction.hpp"

namespace dual::arm::jit::ir {

class Emitter {
public:
Emitter(BasicBlock& basic_block, atom::Arena& arena)
: m_basic_block{basic_block}
, m_arena{arena} {
}

const U32Value& Const(u32 imm_u32) {
auto& value = CreateValue<U32Value>();
value.create_ref.imm_u64 = imm_u32;
return value;
}

const I32Value& Const(i32 imm_i32) {
auto& value = CreateValue<I32Value>();
value.create_ref.imm_i64 = imm_i32;
return value;
}

const U32Value& LDGPR(GPR gpr) {
return std::get<0>(Emit<U32Value>(Instruction::Type::LDGPR, 0u, gpr));
}

void STGPR(GPR gpr, const U32Value& value) {
Emit(Instruction::Type::STGPR, 0u, gpr, value);
}

const U32Value& LDCPSR() {
return std::get<0>(Emit<U32Value>(Instruction::Type::LDCPSR, 0u));
}

void STCPSR(const U32Value& value) {
Emit(Instruction::Type::STCPSR, 0u, value);
}

const U32Value& CVT_HFLAG2NZCV(const HostFlagsValue& hflags) {
return std::get<0>(Emit<U32Value>(Instruction::Type::CVT_HFLAG2NZCV, 0u, hflags));
}

const U32Value& CVT_HFLAG2Q(const HostFlagsValue& hflags) {
return std::get<0>(Emit<U32Value>(Instruction::Type::CVT_HFLAG2Q, 0u, hflags));
}

const HostFlagsValue& CVT_NZCV2HFLAG(const U32Value& nzcv) {
return std::get<0>(Emit<HostFlagsValue>(Instruction::Type::CVT_NZCV2HFLAG, 0u, nzcv));
}

const U32Value& ADD(const U32Value& lhs, const U32Value& rhs) {
return std::get<0>(Emit<U32Value>(Instruction::Type::ADD, 0u, lhs, rhs));
}

private:
template<typename T>
using ConstRef = std::add_lvalue_reference_t<std::add_const_t<T>>;

template<typename... ResultTypes, typename... ArgumentTypes>
std::tuple<ConstRef<ResultTypes>...> Emit(Instruction::Type type, u16 flags, ArgumentTypes&&... args) {
static_assert(sizeof...(ResultTypes) <= Instruction::max_ret_slots);
static_assert(sizeof...(ArgumentTypes) <= Instruction::max_arg_slots);

Instruction& instruction = AppendInstruction(type, flags, sizeof...(ArgumentTypes), sizeof...(ResultTypes));

if constexpr(sizeof...(ArgumentTypes) != 0u) {
SetArguments(instruction, 0, std::forward<ArgumentTypes>(args)...);
}

if constexpr(sizeof...(ResultTypes) != 0u) {
return CreateResultValues<ResultTypes...>(instruction, 0);
} else {
return {};
}
}

Instruction& AppendInstruction(Instruction::Type type, u16 flags, size_t arg_slot_count, size_t ret_slot_count) {
const auto instruction = (Instruction*)m_arena.Allocate(sizeof(Instruction));
if(instruction == nullptr) [[unlikely]] {
ATOM_PANIC("ran out of memory arena space");
}
new(instruction) Instruction{type, flags, (u8)arg_slot_count, (u8)ret_slot_count};

if(m_basic_block.head == nullptr) [[unlikely]] {
m_basic_block.head = instruction;
m_basic_block.tail = instruction;
} else {
m_basic_block.tail->next = instruction;
instruction->prev = m_basic_block.tail;
m_basic_block.tail = instruction;
}

return *instruction;
}

template<typename ArgumentType, typename... RemainingArgumentTypes>
static void SetArguments(Instruction& instruction, int first_slot, ArgumentType&& arg, RemainingArgumentTypes&&... args) {
SetArgument(instruction, first_slot, std::forward<ArgumentType>(arg));
if constexpr(sizeof...(RemainingArgumentTypes) != 0u) {
SetArguments(instruction, first_slot + 1, std::forward<RemainingArgumentTypes>(args)...);
}
}

static void SetArgument(Instruction& instruction, int slot, const Value& value) {
instruction.arg_slots[slot] = Input{value};
value.use_refs.push_back({.instruction = &instruction, .slot = slot});
}

static void SetArgument(Instruction& instruction, int slot, GPR gpr) {
instruction.arg_slots[slot] = Input{gpr};
}

template<typename ResultType, typename... RemainingResultTypes>
std::tuple<ConstRef<ResultType>, ConstRef<RemainingResultTypes>...> CreateResultValues(Instruction& instruction, int first_slot) {
auto& value = CreateResultValue<ResultType>(instruction, first_slot);
if constexpr(sizeof...(RemainingResultTypes) == 0u) {
return std::tuple<const ResultType&>{value};
} else {
return std::tuple_cat(std::tuple<const ResultType&>{value}, CreateResultValues<RemainingResultTypes...>(instruction, first_slot + 1));
}
}

template<typename ResultType>
requires std::is_base_of_v<Value, ResultType>
const ResultType& CreateResultValue(Instruction& instruction, int slot) {
auto& value = CreateValue<ResultType>();
value.create_ref.instruction = &instruction;
value.create_ref.slot = slot;
instruction.ret_slots[slot] = value.id;
return value;
}

// @todo: move this to basic block?
template<typename ValueType>
requires std::is_base_of_v<Value, ValueType>
ValueType& CreateValue() {
const auto value = (ValueType*)m_arena.Allocate(sizeof(ValueType));
if(value == nullptr) {
ATOM_PANIC("ran out of memory arena space");
}
const Value::ID id = m_basic_block.values.size();
if(id == Value::invalid_id) {
ATOM_PANIC("exceeded maximum number of values per basic block limit");
}
new(value) ValueType{id};
m_basic_block.values.push_back(value);
return *value;
}

BasicBlock& m_basic_block;
atom::Arena& m_arena;
};

} // namespace dual::arm::jit::ir
54 changes: 54 additions & 0 deletions src/dual/src/arm/dynarec/ir/input.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

#pragma once

#include <atom/panic.hpp>
#include <dual/arm/cpu.hpp>

#include "value.hpp"

namespace dual::arm::jit::ir {

using GPR = dual::arm::CPU::GPR;

class Input {
public:
enum class Type {
Null,
Value,
GPR
};

Input() : m_type{Type::Null} {}
explicit Input(const Value& value) : m_type{Type::Value}, m_value{value.id} {}
explicit Input(GPR gpr) : m_type{Type::GPR}, m_gpr{gpr} {}

[[nodiscard]] Type GetType() const { return m_type; }
[[nodiscard]] bool Is(Type type) const { return m_type == type; }

[[nodiscard]] Value::ID AsValue() const {
DebugCheckType(Type::Value);
return m_value;
}

[[nodiscard]] GPR AsGPR() const {
DebugCheckType(Type::GPR);
return m_gpr;
}

private:
void DebugCheckType(Type type) const {
#ifndef NDEBUG
if(m_type != type) {
ATOM_PANIC("bad input type: expected {} but got {}", (int)type, (int)m_type);
}
#endif
}

Type m_type;
union {
Value::ID m_value;
GPR m_gpr;
};
};

} // namespace dual::arm::jit::ir
59 changes: 59 additions & 0 deletions src/dual/src/arm/dynarec/ir/instruction.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

#pragma once

#include <atom/integer.hpp>
#include <array>
#include <limits>

#include "input.hpp"
#include "value.hpp"

namespace dual::arm::jit::ir {

struct Instruction {
enum class Type : u16 {
// Guest Context Read/Write
LDGPR,
STGPR,
LDCPSR,
STCPSR,

// Flag Management
// @todo: CVT_NZCV2HFLAG operation might be wasteful, we will probably only need the carry usually.
CVT_HFLAG2NZCV,
CVT_HFLAG2Q,
CVT_NZCV2HFLAG,

// Binary Ops
ADD,
};

enum class Flags : u16 {
};

static constexpr size_t max_arg_slots = 2u;
static constexpr size_t max_ret_slots = 2u; // @todo: make naming consistent

Instruction(Type type, u16 flags, u8 arg_slot_count, u8 ret_slot_count)
: type{type}
, flags{flags}
, arg_slot_count{arg_slot_count}
, ret_slot_count{ret_slot_count} {
arg_slots.fill({});
ret_slots.fill(Value::invalid_id);
}

Type type;
u16 flags;
u8 arg_slot_count;
u8 ret_slot_count;
std::array<Input, max_arg_slots> arg_slots{};
std::array<Value::ID, max_ret_slots> ret_slots{};
Instruction* prev{};
Instruction* next{};

static_assert(max_arg_slots <= std::numeric_limits<decltype(arg_slot_count)>::max());
static_assert(max_ret_slots <= std::numeric_limits<decltype(ret_slot_count)>::max());
};

} // namespace dual::arm::jit::ir
50 changes: 50 additions & 0 deletions src/dual/src/arm/dynarec/ir/value.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

#pragma once

#include <atom/integer.hpp>
#include <atom/non_copyable.hpp>
#include <limits>
#include <vector>

namespace dual::arm::jit::ir {

struct Instruction;

struct Ref {
const Instruction* instruction{};
union {
int slot; //< instruction != null case
u64 imm_u64; //< instruction == null case
i64 imm_i64;
};
};

struct Value : atom::NonCopyable {
using ID = u16;

static constexpr ID invalid_id = std::numeric_limits<ID>::max();

enum class DataType : u8 {
U32,
I32,
HostFlags
};

Value(ID id, DataType data_type) : id{id}, data_type{data_type} {}

ID id;
DataType data_type;
Ref create_ref;
mutable std::vector<Ref> use_refs{};
};

template<Value::DataType data_type_>
struct TypedValue : Value {
explicit TypedValue(ID id) : Value{id, data_type_} {}
};

using U32Value = TypedValue<Value::DataType::U32>;
using I32Value = TypedValue<Value::DataType::I32>;
using HostFlagsValue = TypedValue<Value::DataType::HostFlags>;

} // namespace dual::arm::jit::ir

0 comments on commit b106ac6

Please sign in to comment.