-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
dual: arm: Add DynarecCPU early IR framework
- Loading branch information
Showing
8 changed files
with
360 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |